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
- 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:
- Input for Title (Year & Plot too?)
- Input for Title
- Submit button
- Cear button
- Clear button
- List of results - paginated so we might need a pagination component:
- Title
- Year
@ -50,7 +57,7 @@ External API Docs: https://www.omdbapi.com/
Built on:
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
@ -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
this might look like:
favorites/page.tsx
- FavoriteContext - favorites, setFavorites
cosnt FavoriteContext = createContext({
favorites,
addFavorite
})
- 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)
const FavoriteProvider = ()=>{
// local state management
return (
- 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.
)
}
const { searchResults } = useContext(FavoriteContext);
Known issues:
* https://github.com/vercel/next.js/issues/65161
* https://github.com/vercel/next.js/issues/54757

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",
"build": "next build",
"start": "next start",
"lint": "next lint"
"lint": "next lint",
"test": "jest",
"test:watch": "jest --watch"
},
"dependencies": {
"@emotion/react": "^11.11.4",
@ -14,15 +16,21 @@
"framer-motion": "^11.2.6",
"next": "14.2.3",
"react": "^18",
"react-dom": "^18"
"react-dom": "^18",
"ts-node-dev": "^2.0.0"
},
"devDependencies": {
"@testing-library/jest-dom": "^6.4.5",
"@testing-library/react": "^15.0.7",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"autoprefixer": "^10.4.19",
"eslint": "^8",
"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",
"tailwindcss": "^3.4.3",
"typescript": "^5"

View File

@ -1,13 +1,17 @@
const url = `${process.env.OMDB_URL}?apikey=${process.env.API_KEY}`;
export const queryOMDb = async (queryParamString: string) => {
"use server";
"use server";
try {
const queryUrl = `${url}&${queryParamString}`;
const response = await fetch(queryUrl);
if (!response.ok) {
throw new Error("Failed to fetch movie data");
}
const result = await response.json();
console.log(queryUrl, result);
// console.log(queryUrl, 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 ImgCard from "@/components/ImgCard";
import Image from "next/image";
import Link from "next/link";
export interface IMovieCardItem {
@ -14,20 +13,6 @@ interface IMovieCardProps {
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) => {
return (
<ImgCard

View File

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

View File

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

View File

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

View File

@ -21,6 +21,6 @@
"@/*": ["./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"]
}