Skip to content

Commit ec3d3dc

Browse files
committed
Test - vitest setup for unit testing
1 parent 456e760 commit ec3d3dc

File tree

12 files changed

+830
-378
lines changed

12 files changed

+830
-378
lines changed

client/package-lock.json

+655-338
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/package.json

+7-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
"build": "tsc && vite build",
99
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
1010
"format": "npx prettier . --write",
11-
"preview": "vite preview"
11+
"preview": "vite preview",
12+
"test": "vitest",
13+
"coverage": "vitest run --coverage"
1214
},
1315
"dependencies": {
1416
"@hookform/resolvers": "^3.3.4",
@@ -29,6 +31,8 @@
2931
"devDependencies": {
3032
"@tanstack/eslint-plugin-query": "^5.18.1",
3133
"@tanstack/react-query-devtools": "^5.18.1",
34+
"@testing-library/jest-dom": "^6.4.1",
35+
"@testing-library/react": "^14.2.1",
3236
"@types/react": "^18.2.52",
3337
"@types/react-dom": "^18.2.17",
3438
"@typescript-eslint/eslint-plugin": "^6.20.0",
@@ -40,6 +44,8 @@
4044
"eslint-plugin-react-hooks": "^4.6.0",
4145
"eslint-plugin-react-refresh": "^0.4.5",
4246
"eslint-plugin-simple-import-sort": "^10.0.0",
47+
"history": "^5.3.0",
48+
"jsdom": "^24.0.0",
4349
"postcss": "^8.4.33",
4450
"prettier": "^3.2.4",
4551
"prettier-plugin-tailwindcss": "^0.5.11",

client/src/components/navbar/Authenticated.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ export const Authenticated = () => {
3131
<span className="block truncate text-sm font-medium">{email}</span>
3232
</Dropdown.Header>
3333
{/* <Dropdown.Item>Movies</Dropdown.Item> */}
34-
<Dropdown.Item>Modify Movies</Dropdown.Item>
35-
<Dropdown.Item>Delete Account</Dropdown.Item>
34+
{/* <Dropdown.Item>Modify Movies</Dropdown.Item>
35+
<Dropdown.Item>Delete Account</Dropdown.Item> */}
3636
<Dropdown.Divider />
3737
<Dropdown.Item onClick={HandleLogout}>Logout</Dropdown.Item>
3838
</Dropdown>

client/src/components/navbar/Navbar.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export const Navbar = () => {
1111
const { mode } = useThemeMode();
1212
const isAuthenticated = checkUserAuth();
1313
return (
14-
<Component fluid className="shadow-xl">
14+
<Component role="navbar" fluid className="shadow-xl">
1515
<Component.Brand href="/">
1616
<div className="flex gap-x-2">
1717
<div>

client/src/test/setup.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import "@testing-library/jest-dom"

client/src/test/unit/Button.test.tsx

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { render, fireEvent, screen } from "@testing-library/react";
2+
import { Button } from "../../components";
3+
import { describe, it, expect } from "vitest"
4+
5+
describe("Button component", () => {
6+
7+
it("renders the button", () => {
8+
const title = "Button"
9+
render(<Button title={title} />);
10+
const buttonElement = screen.queryByText(/Button/i)
11+
expect(buttonElement).toBeVisible()
12+
13+
});
14+
15+
it("renders the button with the correct title", () => {
16+
const title = "Click me";
17+
const { getByText } = render(<Button title={title} />);
18+
const buttonElement = getByText(title);
19+
expect(buttonElement).toBeVisible();
20+
});
21+
22+
it("calls the onClick function when clicked", () => {
23+
const onClickMock = vitest.fn();
24+
const { getByText } = render(<Button title="Click me" onClick={onClickMock} />);
25+
const buttonElement = getByText("Click me");
26+
fireEvent.click(buttonElement);
27+
expect(onClickMock).toHaveBeenCalled();
28+
});
29+
it('renders children', () => {
30+
render(<Button title="Test Button"><span>Child</span></Button>);
31+
const childElement = screen.getByText('Child');
32+
expect(childElement).toBeInTheDocument();
33+
});
34+
35+
it('renders with custom class', () => {
36+
render(<Button title="Test Button" className="custom-class" />);
37+
const buttonElement = screen.getByRole('button');
38+
expect(buttonElement).toHaveClass('custom-class');
39+
});
40+
41+
});

client/src/test/unit/Footer.test.tsx

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { render, fireEvent, screen } from "@testing-library/react";
2+
import { Footer } from "../../components/footer/Footer";
3+
import { createMemoryHistory } from 'history'
4+
import { BrowserRouter } from 'react-router-dom'
5+
6+
describe('Footer component', () => {
7+
it('renders the footer', () => {
8+
render(
9+
<BrowserRouter >
10+
<Footer />
11+
</BrowserRouter>
12+
);
13+
const footerElement = screen.getByRole('contentinfo');
14+
expect(footerElement).toBeVisible();
15+
});
16+
17+
it('navigates to home page when Movies link is clicked', () => {
18+
const history = createMemoryHistory({ initialEntries: ['/'] })
19+
render(
20+
<BrowserRouter >
21+
<Footer />
22+
</BrowserRouter>
23+
);
24+
const moviesLink = screen.getByText('Movies');
25+
fireEvent.click(moviesLink);
26+
expect(history.location.pathname).toBe('/');
27+
});
28+
29+
it('navigates to sign up page when Sign up link is clicked', () => {
30+
const history = createMemoryHistory({ initialEntries: ['/sign-up']})
31+
render(
32+
<BrowserRouter >
33+
<Footer />
34+
</BrowserRouter>
35+
);
36+
const signUpLink = screen.getByText('Sign up');
37+
fireEvent.click(signUpLink);
38+
expect(history.location.pathname).toBe('/sign-up');
39+
});
40+
41+
it('navigates to login page when Login link is clicked', () => {
42+
const history = createMemoryHistory({ initialEntries: ['/login']})
43+
render(
44+
<BrowserRouter >
45+
<Footer />
46+
</BrowserRouter>
47+
);
48+
const loginLink = screen.getByText('Login');
49+
fireEvent.click(loginLink);
50+
expect(history.location.pathname).toBe('/login');
51+
});
52+
});

client/src/test/unit/Navbar.test.tsx

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { render, screen } from "@testing-library/react"
2+
import { Navbar } from "../../components/navbar/Navbar";
3+
import { checkUserAuth } from '../../utils/checkAuthentication';
4+
import { Mock } from "vitest";
5+
6+
vitest.mock('../../utils/checkAuthentication');
7+
8+
describe('Navbar component', () => {
9+
it('renders the navbar', () => {
10+
render(<Navbar />);
11+
const navbarElement = screen.getByRole('navbar');
12+
expect(navbarElement).toBeVisible();
13+
});
14+
15+
it('renders the brand', () => {
16+
render(<Navbar />);
17+
const brandElement = screen.getByRole('link', { name: /movie night/i });
18+
expect(brandElement).toBeVisible();
19+
});
20+
21+
it('renders the authenticated component when user is authenticated', () => {
22+
(checkUserAuth as Mock).mockReturnValue(true);
23+
render(<Navbar />);
24+
const authenticatedElement = screen.getByTestId('authenticated');
25+
expect(authenticatedElement).toBeVisible();
26+
});
27+
28+
it('renders the unauthenticated component when user is not authenticated', () => {
29+
(checkUserAuth as Mock).mockReturnValue(false);
30+
render(<Navbar />);
31+
const unauthenticatedElement = screen.getByTestId('unauthenticated');
32+
expect(unauthenticatedElement).toBeVisible();
33+
});
34+
});

client/tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"isolatedModules": true,
1313
"noEmit": true,
1414
"jsx": "react-jsx",
15+
"types": ["vitest/globals"],
1516

1617
/* Linting */
1718
"strict": true,

client/vite.config.ts

+9-9
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1+
/// <reference types="vitest" />
2+
/// <reference types="vite/client" />
3+
14
import { defineConfig } from "vite";
25
import react from "@vitejs/plugin-react-swc";
36
import tsconfigPaths from "vite-tsconfig-paths";
4-
import { fileURLToPath } from "node:url";
57
import { compression } from "vite-plugin-compression2";
68

79
// https://vitejs.dev/config/
810
export default defineConfig({
911
plugins: [react(), tsconfigPaths(), compression()],
10-
resolve: {
11-
alias: [
12-
{
13-
find: "@",
14-
replacement: fileURLToPath(new URL("./src", import.meta.url)),
15-
},
16-
],
17-
},
12+
test: {
13+
globals: true,
14+
environment: "jsdom",
15+
css: true,
16+
setupFiles: "./src/test/setup.ts",
17+
}
1818
});

server/src/__tests__/integration/movie.test.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -243,13 +243,13 @@ describe("Movie - Controller", () => {
243243
await request(app).delete(`/api/user/${testUser.email}`);
244244
});
245245
describe("Update Movie", () => {
246-
it("POST api/movies/movieId should return 403 No Token with missing auth header", async () => {
246+
it("POST api/movie/movieId should return 403 No Token with missing auth header", async () => {
247247
const response = await request(app).get(`/api/movie/${movieId}`);
248248
expect(response.status).toBe(403);
249249
expect(response.body.status).toEqual(false);
250250
expect(response.body.message).toEqual("No token provided!");
251251
});
252-
it("POST api/movies/movieId should return 401 Unauthorized with invalid token", async () => {
252+
it("POST api/movie/movieId should return 401 Unauthorized with invalid token", async () => {
253253
const response = await request(app)
254254
.get(`/api/movie/${movieId}`)
255255
.set("authorization", `Bearer ${token}1`)
@@ -258,7 +258,7 @@ describe("Movie - Controller", () => {
258258
expect(response.body.status).toEqual("error");
259259
expect(response.body.message).toEqual("Invalid Token");
260260
});
261-
it("POST api/movies/movieId should return 200 Movie Updated with valid data", async () => {
261+
it("POST api/movie/movieId should return 200 Movie Updated with valid data", async () => {
262262
const payload = {
263263
title: "test-movie-updated",
264264
description: "test-movie-description-updated",
@@ -278,7 +278,7 @@ describe("Movie - Controller", () => {
278278
expect(response.body.userId).toEqual(userId);
279279
expect(response.body.id).toEqual(movieId);
280280
});
281-
it("POST api/movies/movieId should return 404 Movie Not Found with invalid MovieId", async () => {
281+
it("POST api/movie/movieId should return 404 Movie Not Found with invalid MovieId", async () => {
282282
const modifiedMovieId = "8fcc29dd-4961-4be1-8cf0-ce179634a4ce";
283283
const response = await request(app)
284284
.post(`/api/movie/${modifiedMovieId}`)
@@ -287,7 +287,7 @@ describe("Movie - Controller", () => {
287287
expect(response.status).toBe(404);
288288
expect(response.body.message).toEqual("Movie Not Found");
289289
});
290-
it("POST api/movies/movieId should return 422 Movie Not Updated with missing UserId OR MovieId undefined", async () => {
290+
it("POST api/movie/movieId should return 422 Movie Not Updated with missing UserId OR MovieId undefined", async () => {
291291
const modifiedMovieId = undefined;
292292
const modifiedPayload = JSON.parse(JSON.stringify(payload));
293293
delete modifiedPayload.userId;

server/src/__tests__/integration/review.test.ts

+22-22
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,18 @@ import request from "supertest";
33
import app from "../../app";
44

55
type ReviewPayload = {
6-
title: string;
7-
description: string;
8-
rating: number;
9-
userId?: string;
6+
title: string;
7+
description: string;
8+
rating: number;
9+
userId?: string;
1010
};
1111

1212
type MoviewPayload = {
13-
title: string;
14-
description: string;
15-
poster: string;
16-
trailer: string;
17-
userId?: string;
13+
title: string;
14+
description: string;
15+
poster: string;
16+
trailer: string;
17+
userId?: string;
1818
};
1919

2020
describe("Review - Controller", () => {
@@ -189,22 +189,22 @@ describe("Review - Controller", () => {
189189
.set("authorization", `Bearer ${token}`);
190190
await request(app).delete(`/api/user/${testUser.email}`);
191191
});
192-
describe("GET Reviews", () => {
193-
it("GET api/reviews/reviewId should return 403 No Token with missing auth header", async () => {
192+
describe.only("GET Reviews", () => {
193+
it("GET api/review/reviewId should return 403 No Token with missing auth header", async () => {
194194
const response = await request(app).get(`/api/review/${reviewId}`);
195195
expect(response.status).toBe(403);
196196
expect(response.body.status).toEqual(false);
197197
expect(response.body.message).toEqual("No token provided!");
198198
});
199-
it("GET api/reviews/reviewId should return 401 Unauthorized with invalid token", async () => {
199+
it("GET api/review/reviewId should return 401 Unauthorized with invalid token", async () => {
200200
const response = await request(app)
201201
.get(`/api/review/${reviewId}`)
202202
.set("authorization", `Bearer ${token}1`);
203203
expect(response.status).toBe(401);
204204
expect(response.body.status).toEqual("error");
205205
expect(response.body.message).toEqual("Invalid Token");
206206
});
207-
it("GET api/reviews/reviewId should return 200 OK with valid data", async () => {
207+
it("GET api/review/reviewId should return 200 OK with valid data", async () => {
208208
const response = await request(app)
209209
.get(`/api/review/${reviewId}`)
210210
.set("authorization", `Bearer ${token}`);
@@ -214,7 +214,7 @@ describe("Review - Controller", () => {
214214
expect(response.body.rating).toBe(reviewPayload.rating);
215215
expect(response.body.userId).toBe(reviewPayload.userId);
216216
});
217-
it("GET api/reviews/reviewId should return 422 Unprocessable Content with missing reviewId", async () => {
217+
it("GET api/review/reviewId should return 422 Unprocessable Content with missing reviewId", async () => {
218218
const modifiedReviewId = undefined;
219219
const response = await request(app)
220220
.get(`/api/review/${modifiedReviewId}`)
@@ -305,15 +305,15 @@ describe("Review - Controller", () => {
305305
await request(app).delete(`/api/user/${testUser.email}`);
306306
});
307307
describe("UPDATE Reviews", () => {
308-
it("POST api/reviews/reviewId should return 403 No Token with missing auth header", async () => {
308+
it("POST api/review/reviewId should return 403 No Token with missing auth header", async () => {
309309
const response = await request(app)
310310
.post(`/api/review/${reviewId}`)
311311
.send(reviewPayload);
312312
expect(response.status).toBe(403);
313313
expect(response.body.status).toEqual(false);
314314
expect(response.body.message).toEqual("No token provided!");
315315
});
316-
it("POST api/reviews/reviewId should return 401 Unauthorized with invalid token", async () => {
316+
it("POST api/review/reviewId should return 401 Unauthorized with invalid token", async () => {
317317
const response = await request(app)
318318
.post(`/api/review/${reviewId}`)
319319
.set("authorization", `Bearer ${token}1`)
@@ -322,7 +322,7 @@ describe("Review - Controller", () => {
322322
expect(response.body.status).toEqual("error");
323323
expect(response.body.message).toEqual("Invalid Token");
324324
});
325-
it("POST api/reviews/reviewId should return 200 OK with valid data", async () => {
325+
it("POST api/review/reviewId should return 200 OK with valid data", async () => {
326326
const response = await request(app)
327327
.get(`/api/review/${reviewId}`)
328328
.set("authorization", `Bearer ${token}`)
@@ -333,7 +333,7 @@ describe("Review - Controller", () => {
333333
expect(response.body.rating).toBe(reviewPayload.rating);
334334
expect(response.body.userId).toBe(reviewPayload.userId);
335335
});
336-
it("POST api/reviews/reviewId should return 422 Unprocessable Content with missing reviewId", async () => {
336+
it("POST api/review/reviewId should return 422 Unprocessable Content with missing reviewId", async () => {
337337
const modifiedReviewId = undefined;
338338
const response = await request(app)
339339
.get(`/api/review/${modifiedReviewId}`)
@@ -419,15 +419,15 @@ describe("Review - Controller", () => {
419419
await request(app).delete(`/api/user/${testUser.email}`);
420420
});
421421
describe("DELETE Reviews", () => {
422-
it("DELETE api/reviews/reviewId should return 403 No Token with missing auth header", async () => {
422+
it("DELETE api/review/reviewId should return 403 No Token with missing auth header", async () => {
423423
const response = await request(app)
424424
.delete(`/api/review/${reviewId}`)
425425
.send(reviewPayload);
426426
expect(response.status).toBe(403);
427427
expect(response.body.status).toEqual(false);
428428
expect(response.body.message).toEqual("No token provided!");
429429
});
430-
it("DELETE api/reviews/reviewId should return 401 Unauthorized with invalid token", async () => {
430+
it("DELETE api/review/reviewId should return 401 Unauthorized with invalid token", async () => {
431431
const response = await request(app)
432432
.delete(`/api/review/${reviewId}`)
433433
.set("authorization", `Bearer ${token}1`)
@@ -436,14 +436,14 @@ describe("Review - Controller", () => {
436436
expect(response.body.status).toEqual("error");
437437
expect(response.body.message).toEqual("Invalid Token");
438438
});
439-
it("DELETE api/reviews/reviewId should return 200 OK with valid data", async () => {
439+
it("DELETE api/review/reviewId should return 200 OK with valid data", async () => {
440440
const response = await request(app)
441441
.delete(`/api/review/${reviewId}`)
442442
.set("authorization", `Bearer ${token}`)
443443
.send(reviewPayload);
444444
expect(response.status).toBe(200);
445445
});
446-
it("DELETE api/reviews/reviewId should return 422 Unprocessable Content with missing reviewId", async () => {
446+
it("DELETE api/review/reviewId should return 422 Unprocessable Content with missing reviewId", async () => {
447447
const modifiedReviewId = undefined;
448448
const response = await request(app)
449449
.delete(`/api/review/${modifiedReviewId}`)

0 commit comments

Comments
 (0)