implemented jest tests

This commit is contained in:
cscough 2024-05-25 01:03:21 -04:00
parent acca5c387f
commit f1b3d9e5be
14 changed files with 7389 additions and 185 deletions

View File

@ -5,11 +5,18 @@ It contains:
- Navbar - Navbar
- A page to search for movies by title: - A page to search for movies by title:
- searchParams to string
- Object.entries(searchParams)
.map(
([key, value], i, arr) =>
`${key}=${value}${i < arr.length - 1 ? "&" : ""}`
)
.toString();
- SearchBar component: - SearchBar component:
- Input for Title (Year & Plot too?) - Input for Title
- Submit button - Submit button
- Cear button - Clear button
- List of results - paginated so we might need a pagination component: - List of results - paginated so we might need a pagination component:
- Title - Title
- Year - Year
@ -50,7 +57,7 @@ External API Docs: https://www.omdbapi.com/
Built on: Built on:
Next.js Next.js
Server side rendering for list & movie details to hide api token - Could possibly use Route Handler Server side rendering for list & movie details to hide api token/network requests and eliminate the need for state-management on data returned from the external API.
Switched to Tailwindcss for styling. Was using chakra ui components but it isn't server-side render friendly Switched to Tailwindcss for styling. Was using chakra ui components but it isn't server-side render friendly
@ -60,18 +67,13 @@ Since we're using server side rendering for pages that fetch data, we don't need
We can implement Context in a client component for managing "Favorites" for a user if we'd like We can implement Context in a client component for managing "Favorites" for a user if we'd like
this might look like: this might look like:
favorites/page.tsx - FavoriteContext - favorites, setFavorites
cosnt FavoriteContext = createContext({ - FavoriteProvider - A wrapper for FavoriteContext.Provider with local state that we can render client-side and pass children through to make use of server-side rendering for child components (interleaving)
favorites,
addFavorite
})
const FavoriteProvider = ()=>{ - FavoriteBtn - a client-side component rendering a button and using useContext(FormContext) to consume favorites and setFavorites - filter favorites by id to prevent duplicats/allow removing from list.
// local state management
return (
) Known issues:
} * https://github.com/vercel/next.js/issues/65161
* https://github.com/vercel/next.js/issues/54757
const { searchResults } = useContext(FavoriteContext);

View File

@ -0,0 +1,2 @@
process.env.API_KEY='6200aa0'
process.env.OMDB_URL='http://www.omdbapi.com/'

View File

@ -0,0 +1,16 @@
import "@testing-library/jest-dom";
import {render, screen} from '@testing-library/react'
import MovieCard from '../src/app/Search/MovieCard.tsx'
const movie = {
Title: "Dune",
Year: "2000",
imdbID: "tt0142032",
Type: "series",
Poster: "https://m.media-amazon.com/images/M/MV5BMTU4MjMyMTkxN15BMl5BanBnXkFtZTYwODA5OTU5._V1_SX300.jpg"
}
test('MovieCard Renders',()=>{
render(<MovieCard movie={movie} />)
const movieTitle = screen.getByRole('heading')
expect(movieTitle).toHaveTextContent('Dune')
})

View File

@ -0,0 +1,39 @@
import "@testing-library/jest-dom";
import { fireEvent, render, screen } from "@testing-library/react";
import SearchBar from "@/app/Search/SearchBar.tsx";
import { queryOMDb } from "@/api/omdb";
test("SearchBar Allows user input", () => {
render(<SearchBar />);
const input = screen.getByPlaceholderText("Search by Title");
fireEvent.change(input, { target: { value: "some value" } });
expect(input.value).toBe("some value");
});
test("SearchBar Allows user input and Clear button clears", () => {
render(<SearchBar />);
const input = screen.getByPlaceholderText("Search by Title");
const clear = screen.getByText("Clear");
fireEvent.change(input, { target: { value: "some value" } });
expect(input.value).toBe("some value");
fireEvent.click(clear);
expect(input.value).toBe('');
});
// test("SearchBar Allows user submit", () => {
// render(<SearchBar />);
// const input = screen.getByPlaceholderText("Search by Title");
// const submit = screen.getByText("Search");
// const spy = jest.spyOn({ queryOMDb }, "queryOMDb");
// fireEvent.change(input, { target: { value: "back to the" } });
// fireEvent.click(submit);
// // Somehow test for form submission - currently facing this issue
// // https://github.com/vercel/next.js/issues/54757
// // expect(spy).toHaveBeenCalled();
// });

View File

@ -0,0 +1,8 @@
import "@testing-library/jest-dom";
import { queryOMDb } from "../src/api/omdb.ts";
test("queryOMDb returns movie data", async () => {
const data = await queryOMDb("s=back to");
expect(data).toHaveProperty("Response");
});

View File

@ -0,0 +1,19 @@
import type { Config } from 'jest'
import nextJest from 'next/jest.js'
const createJestConfig = nextJest({
// Provide the path to your Next.js app to load next.config.js and .env files in your test environment
dir: './',
})
// Add any custom config to be passed to Jest
const config: Config = {
coverageProvider: 'v8',
testEnvironment: 'jsdom',
setupFilesAfterEnv: ["<rootDir>/.jest/setEnvVars.js", "jest-fetch-mock"]
// Add more setup options before each test is run
// setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
}
// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
export default createJestConfig(config)

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,9 @@
"dev": "next dev", "dev": "next dev",
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"lint": "next lint" "lint": "next lint",
"test": "jest",
"test:watch": "jest --watch"
}, },
"dependencies": { "dependencies": {
"@emotion/react": "^11.11.4", "@emotion/react": "^11.11.4",
@ -14,15 +16,21 @@
"framer-motion": "^11.2.6", "framer-motion": "^11.2.6",
"next": "14.2.3", "next": "14.2.3",
"react": "^18", "react": "^18",
"react-dom": "^18" "react-dom": "^18",
"ts-node-dev": "^2.0.0"
}, },
"devDependencies": { "devDependencies": {
"@testing-library/jest-dom": "^6.4.5",
"@testing-library/react": "^15.0.7",
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^18", "@types/react": "^18",
"@types/react-dom": "^18", "@types/react-dom": "^18",
"autoprefixer": "^10.4.19", "autoprefixer": "^10.4.19",
"eslint": "^8", "eslint": "^8",
"eslint-config-next": "14.2.3", "eslint-config-next": "14.2.3",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"jest-fetch-mock": "^3.0.3",
"postcss": "^8.4.38", "postcss": "^8.4.38",
"tailwindcss": "^3.4.3", "tailwindcss": "^3.4.3",
"typescript": "^5" "typescript": "^5"

View File

@ -1,13 +1,17 @@
const url = `${process.env.OMDB_URL}?apikey=${process.env.API_KEY}`; const url = `${process.env.OMDB_URL}?apikey=${process.env.API_KEY}`;
export const queryOMDb = async (queryParamString: string) => { export const queryOMDb = async (queryParamString: string) => {
"use server"; "use server";
try {
const queryUrl = `${url}&${queryParamString}`; const queryUrl = `${url}&${queryParamString}`;
const response = await fetch(queryUrl); const response = await fetch(queryUrl);
if (!response.ok) { if (!response.ok) {
throw new Error("Failed to fetch movie data"); throw new Error("Failed to fetch movie data");
} }
const result = await response.json(); const result = await response.json();
console.log(queryUrl, result); // console.log(queryUrl, result);
return result; return result;
}; } catch (e) {
throw new Error("Something went wrong searching OMDb");
}
};

View File

@ -1,6 +1,5 @@
import FavoriteBtn from "@/components/FavoriteBtn"; import FavoriteBtn from "@/components/FavoriteBtn";
import ImgCard from "@/components/ImgCard"; import ImgCard from "@/components/ImgCard";
import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
export interface IMovieCardItem { export interface IMovieCardItem {
@ -14,20 +13,6 @@ interface IMovieCardProps {
movie: IMovieCardItem; movie: IMovieCardItem;
} }
const testclass = {
boxShadow: "md",
_hover: {
cursor: "pointer",
border: "1px solid blue",
borderRadius: 4,
// boxShadow:'border'
},
maxW: "sm",
borderWidth: "1px",
borderRadius: "lg",
overflow: "hidden",
}.toString();
export default ({ movie }: IMovieCardProps) => { export default ({ movie }: IMovieCardProps) => {
return ( return (
<ImgCard <ImgCard

View File

@ -16,7 +16,7 @@ export const stringifyQueryParams = (searchParams: object) =>
export default async ({ searchParams }: INextJsProps) => { export default async ({ searchParams }: INextJsProps) => {
const queryParamString = stringifyQueryParams(searchParams!); const queryParamString = stringifyQueryParams(searchParams!);
console.log(queryParamString) // console.log(queryParamString)
const { Search } = await queryOMDb(queryParamString); const { Search } = await queryOMDb(queryParamString);
const movies = Search || []; const movies = Search || [];

View File

@ -14,7 +14,6 @@ export default ({ movie }: any) => {
if (isFavorite) { if (isFavorite) {
setFavorites([...filteredFavorites]) setFavorites([...filteredFavorites])
} else { } else {
setFavorites([...favorites, movie]); setFavorites([...favorites, movie]);
} }
}} }}

View File

@ -3,15 +3,11 @@ import Link from "next/link";
export default () => { export default () => {
return ( return (
<nav className="px-2 flex gap-4"> <nav className="px-2 flex gap-4">
<Link href="/"> <Link href="/">
<strong>Movie Search</strong> <strong>Home</strong>
</Link> </Link>
<Link href="/Search">Search</Link>
<div className="flex gap-4"> <Link href="/Favorites">Favorites</Link>
<Link href="/Search">Search</Link>
<Link href="/Favorites">Favorites</Link>
</div>
</nav> </nav>
); );
}; };

View File

@ -21,6 +21,6 @@
"@/*": ["./src/*"] "@/*": ["./src/*"]
} }
}, },
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "__tests__/*.jsx"],
"exclude": ["node_modules"] "exclude": ["node_modules"]
} }