Skip to content

Commit ba46b14

Browse files
committed
Add test cases for question list page
1 parent 38e964b commit ba46b14

File tree

3 files changed

+239
-40
lines changed

3 files changed

+239
-40
lines changed

backend/user-service/package-lock.json

Lines changed: 51 additions & 40 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
2+
import "@testing-library/jest-dom";
3+
import * as hooks from "../../contexts/AuthContext";
4+
import QuestionList from ".";
5+
import { questionClient } from "../../utils/api";
6+
7+
/* Mock useNavigate */
8+
const mockUseNavigate = jest.fn();
9+
jest.mock("react-router-dom", () => ({
10+
...jest.requireActual("react-router-dom"),
11+
useNavigate: () => mockUseNavigate,
12+
}));
13+
14+
/* Mock questionClient APIs */
15+
jest.mock("../../utils/api", () => ({
16+
questionClient: {
17+
get: jest.fn(),
18+
},
19+
}));
20+
const mockedGet = questionClient.get as jest.MockedFunction<typeof questionClient.get>;
21+
const questions = [
22+
{
23+
id: "1",
24+
title: "Question 1",
25+
description: "Description of Question 1",
26+
complexity: "Easy",
27+
categories: ["Trees"],
28+
},
29+
{
30+
id: "2",
31+
title: "Question 2",
32+
description: "Description of Question 2",
33+
complexity: "Medium",
34+
categories: ["Algorithms", "Data Structures"],
35+
}
36+
];
37+
mockedGet.mockImplementation((url: string) => {
38+
switch (url) {
39+
case "/categories":
40+
return Promise.resolve({
41+
data: {
42+
categories: ["Algorithms", "Data Structures", "Trees"],
43+
},
44+
});
45+
case "":
46+
return Promise.resolve({
47+
data: {
48+
questions: questions,
49+
questionCount: questions.length,
50+
},
51+
});
52+
default:
53+
return Promise.reject(new Error("Not found"));
54+
}
55+
});
56+
57+
describe("Question List Components For All Users", () => {
58+
/* Mock useAuth */
59+
beforeEach(() => {
60+
jest.spyOn(hooks, "useAuth").mockImplementation(() => ({
61+
signup: jest.fn(),
62+
login: jest.fn(),
63+
logout: jest.fn(),
64+
user: null,
65+
}));
66+
});
67+
68+
it("Page header is rendered", async () => {
69+
render(<QuestionList />);
70+
expect(await screen.findByText("Questions")).toBeInTheDocument();
71+
});
72+
73+
it("Title search is rendered", async () => {
74+
render(<QuestionList />);
75+
expect(await screen.findByLabelText("Title")).toBeInTheDocument();
76+
});
77+
78+
it("Complexity filter is rendered", async () => {
79+
render(<QuestionList />);
80+
expect(await screen.findByLabelText("Complexity")).toBeInTheDocument();
81+
});
82+
83+
it("Category filter is rendered", async () => {
84+
render(<QuestionList />);
85+
expect(await screen.findByLabelText("Category")).toBeInTheDocument();
86+
});
87+
88+
it("Question table headers are rendered", async () => {
89+
render(<QuestionList />);
90+
expect(await screen.findByRole("columnheader", { name: "Title" })).toBeInTheDocument();
91+
expect(await screen.findByRole("columnheader", { name: "Complexity" })).toBeInTheDocument();
92+
expect(await screen.findByRole("columnheader", { name: "Categories" })).toBeInTheDocument();
93+
});
94+
95+
it("Question table rows are rendered", async () => {
96+
render(<QuestionList />);
97+
for (const question of questions) {
98+
expect(await screen.findByText(question.title)).toBeInTheDocument();
99+
expect(await screen.findByText(question.complexity)).toBeInTheDocument();
100+
for (const category of question.categories) {
101+
expect(await screen.findByText(category)).toBeInTheDocument();
102+
}
103+
}
104+
});
105+
106+
it("Question table pagination is rendered", async () => {
107+
render(<QuestionList />);
108+
expect(await screen.findByRole("button", { name: /previous page/i })).toBeInTheDocument();
109+
expect(await screen.findByRole("button", { name: /next page/i })).toBeInTheDocument();
110+
});
111+
112+
it("Create button is not rendered", async () => {
113+
render(<QuestionList />);
114+
await waitFor(() => {
115+
expect(screen.queryByRole("button", { name: "Create" })).not.toBeInTheDocument();
116+
});
117+
});
118+
119+
it("Edit/Delete menu is not rendered", async () => {
120+
render(<QuestionList />);
121+
await waitFor(() => {
122+
expect(screen.queryByTestId("edit-delete-menu")).not.toBeInTheDocument();
123+
});
124+
});
125+
});
126+
127+
describe("Question List Components for Admin", () => {
128+
/* Mock useAuth */
129+
beforeEach(() => {
130+
jest.spyOn(hooks, "useAuth").mockImplementation(() => ({
131+
signup: jest.fn(),
132+
login: jest.fn(),
133+
logout: jest.fn(),
134+
user: {
135+
id: "1",
136+
firstName: "Admin",
137+
lastName: "User",
138+
username: "administrator",
139+
140+
profilePictureUrl: "",
141+
biography: "",
142+
createdAt: "",
143+
isAdmin: true,
144+
},
145+
}));
146+
});
147+
148+
it("Create button is rendered", async () => {
149+
render(<QuestionList />);
150+
const createButton = await screen.findByRole("button", { name: "Create" });
151+
expect(createButton).toBeInTheDocument();
152+
153+
fireEvent.click(createButton);
154+
expect(mockUseNavigate).toHaveBeenCalledWith("new");
155+
});
156+
157+
it("Edit/Delete menu is rendered", async () => {
158+
render(<QuestionList />);
159+
const editDeleteMenu = await screen.findAllByTestId("edit-delete-menu");
160+
expect(editDeleteMenu.length).toBe(questions.length);
161+
editDeleteMenu.forEach((button) => {
162+
expect(button).toBeInTheDocument();
163+
});
164+
165+
fireEvent.click(editDeleteMenu[0]);
166+
expect(screen.getByRole("menuitem", { name: "Edit" })).toBeInTheDocument();
167+
expect(screen.getByRole("menuitem", { name: "Delete" })).toBeInTheDocument();
168+
});
169+
170+
it("Edit button redirects to edit question page", async () => {
171+
render(<QuestionList />);
172+
const editDeleteMenu = await screen.findAllByTestId("edit-delete-menu");
173+
fireEvent.click(editDeleteMenu[0]);
174+
const editButton = screen.getByRole("menuitem", { name: "Edit" });
175+
fireEvent.click(editButton);
176+
expect(mockUseNavigate).toHaveBeenCalledWith(`${questions[0].id}/edit`);
177+
});
178+
179+
it("Delete button opens confirmation dialog", async () => {
180+
render(<QuestionList />);
181+
const editDeleteMenu = await screen.findAllByTestId("edit-delete-menu");
182+
fireEvent.click(editDeleteMenu[0]);
183+
const deleteButton = screen.getByRole("menuitem", { name: "Delete" });
184+
fireEvent.click(deleteButton);
185+
expect(screen.getByRole("dialog")).toBeInTheDocument();
186+
});
187+
});

frontend/src/pages/QuestionList/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,7 @@ const QuestionList: React.FC = () => {
347347
{isAdmin && (
348348
<TableCell sx={{ borderTop: "1px solid #E0E0E0" }}>
349349
<IconButton
350+
data-testid="edit-delete-menu"
350351
type="button"
351352
onClick={(event) => handleMenuOpen(event, question.id)}
352353
>

0 commit comments

Comments
 (0)