Skip to content

Commit 75a5c35

Browse files
committed
Merge branch 'frontend-unit-test' into n11-unit-tests
2 parents 221d737 + 97d6b6c commit 75a5c35

File tree

7 files changed

+3593
-269
lines changed

7 files changed

+3593
-269
lines changed

.github/workflows/test.yml

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ on:
1010
branches:
1111
- main
1212
- staging
13-
13+
workflow_dispatch:
1414

1515
jobs:
1616
question-service-tests:
@@ -53,6 +53,30 @@ jobs:
5353
cd ./apps/question-service
5454
go test ./...
5555
56+
frontend-unit-tests:
57+
- name: Set up Node.js
58+
uses: actions/setup-node@v2
59+
with:
60+
node-version: '22'
61+
62+
- name: Setup .env
63+
run: |
64+
cd ./apps/frontend
65+
cp .env.example .env
66+
67+
- name: Install pnpm
68+
run: npm i -g pnpm
69+
70+
- name: Install dependencies
71+
run: |
72+
cd ./apps/frontend
73+
pnpm i
74+
75+
- name: Run tests
76+
run: |
77+
cd ./apps/frontend
78+
pnpm test
79+
5680
test:
5781
runs-on: ubuntu-latest
5882

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { formatTime } from "@/utils/DateTime"
2+
3+
describe("datetime module", () => {
4+
it("formats a time correctly", () => {
5+
expect(formatTime(10)).toBe("00:10")
6+
});
7+
})
8+
9+
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { GetQuestions } from "@/app/services/question"
2+
import { getToken } from "@/app/services/login-store";
3+
4+
const TOKEN = 'mocktoken';
5+
6+
jest.mock("@/app/services/login-store", () => {
7+
return {
8+
__esModule: true,
9+
getToken: jest.fn(() => TOKEN)
10+
};
11+
})
12+
13+
beforeEach(() => {
14+
global.fetch = jest.fn().mockResolvedValue({
15+
async json() {
16+
return {}
17+
}
18+
});
19+
})
20+
21+
describe("mock", () => {
22+
23+
it("mocks correctly", async () => {
24+
await GetQuestions()
25+
expect(jest.mocked(getToken).mock.calls).toEqual([[]])
26+
expect(jest.mocked(fetch).mock.calls[0][1]).toEqual({
27+
"headers": {
28+
"Authorization": `Bearer ${TOKEN}`,
29+
},
30+
"method": "GET",
31+
})
32+
});
33+
34+
})
Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
import { CreateQuestion, DeleteQuestion, GetQuestions, GetSingleQuestion, Question } from "@/app/services/question"
2+
3+
const NEXT_PUBLIC_QUESTION_SERVICE_URL = process.env.NEXT_PUBLIC_QUESTION_SERVICE_URL
4+
5+
const QUESTIONS = [
6+
{
7+
"id": 12345,
8+
"docRefId": "asdfDocRef",
9+
"title": "Asdf",
10+
"description": "Asdf description",
11+
"categories": ["Arrays", "Algorithms"],
12+
"complexity": "hard"
13+
},
14+
{
15+
"id": 12346,
16+
"docRefId": "qwerDocRef",
17+
"title": "Qwer",
18+
"description": "Qwer description",
19+
"categories": ["Strings", "Data Structures"],
20+
"complexity": "medium"
21+
},
22+
{
23+
"id": 12347,
24+
"docRefId": "zxcvDocRef",
25+
"title": "Zxcv",
26+
"description": "Zxcv description",
27+
"categories": ["Graphs", "Algorithms"],
28+
"complexity": "easy"
29+
},
30+
{
31+
"id": 12348,
32+
"docRefId": "tyuiDocRef",
33+
"title": "Tyui",
34+
"description": "Tyui description",
35+
"categories": ["Trees", "Recursion"],
36+
"complexity": "hard"
37+
},
38+
{
39+
"id": 12349,
40+
"docRefId": "ghjkDocRef",
41+
"title": "Ghjk",
42+
"description": "Ghjk description",
43+
"categories": ["Dynamic Programming", "Math"],
44+
"complexity": "medium"
45+
},
46+
{
47+
"id": 12350,
48+
"docRefId": "bnmlDocRef",
49+
"title": "Bnml",
50+
"description": "Bnml description",
51+
"categories": ["Sorting", "Searching"],
52+
"complexity": "easy"
53+
},
54+
{
55+
"id": 12351,
56+
"docRefId": "poiuDocRef",
57+
"title": "Poiu",
58+
"description": "Poiu description",
59+
"categories": ["Bit Manipulation", "Algorithms"],
60+
"complexity": "hard"
61+
},
62+
{
63+
"id": 12352,
64+
"docRefId": "lkjhDocRef",
65+
"title": "Lkjh",
66+
"description": "Lkjh description",
67+
"categories": ["Greedy", "Data Structures"],
68+
"complexity": "medium"
69+
},
70+
{
71+
"id": 12353,
72+
"docRefId": "mnbvDocRef",
73+
"title": "Mnbv",
74+
"description": "Mnbv description",
75+
"categories": ["Backtracking", "Recursion"],
76+
"complexity": "easy"
77+
},
78+
{
79+
"id": 12354,
80+
"docRefId": "vcxzDocRef",
81+
"title": "Vcxz",
82+
"description": "Vcxz description",
83+
"categories": ["Graphs", "Dynamic Programming"],
84+
"complexity": "hard"
85+
}
86+
]
87+
88+
jest.mock("@/app/services/login-store", () => {
89+
return {
90+
__esModule: true,
91+
getToken: jest.fn(() => TOKEN)
92+
};
93+
})
94+
95+
function createMockResponse(obj: any): Response {
96+
// @ts-ignore don't need the whole response
97+
return {
98+
json: () => Promise.resolve(obj),
99+
ok: true,
100+
status: 200
101+
}
102+
}
103+
104+
const TOKEN = "mockjwttoken"
105+
106+
describe("GetQuestions", () => {
107+
beforeEach(() => {
108+
global.fetch = jest.fn().mockResolvedValue({
109+
async json() {
110+
return QUESTIONS
111+
}
112+
});
113+
})
114+
115+
it("gets all questions on the first page with () call", async () => {
116+
117+
const res = await GetQuestions()
118+
119+
expect(jest.mocked(fetch).mock.calls).toStrictEqual([[`${NEXT_PUBLIC_QUESTION_SERVICE_URL}questions`, {
120+
headers: {
121+
"Authorization": `Bearer ${TOKEN}`,
122+
},
123+
method: "GET",
124+
}]])
125+
expect(res).toStrictEqual(QUESTIONS)
126+
127+
});
128+
129+
it("gets all questions on the 2nd page with (2) call", async () => {
130+
131+
const res = await GetQuestions(2)
132+
133+
expect(jest.mocked(fetch).mock.calls).toStrictEqual([[`${NEXT_PUBLIC_QUESTION_SERVICE_URL}questions?offset=10`, {
134+
headers: {
135+
"Authorization": `Bearer ${TOKEN}`,
136+
},
137+
method: "GET",
138+
}]])
139+
});
140+
141+
it("gets all questions on the 2nd page with (limit=3) call", async () => {
142+
143+
await GetQuestions(undefined, 3)
144+
145+
expect(jest.mocked(fetch).mock.calls).toStrictEqual([[`${NEXT_PUBLIC_QUESTION_SERVICE_URL}questions?limit=3`, {
146+
headers: {
147+
"Authorization": `Bearer ${TOKEN}`,
148+
},
149+
method: "GET",
150+
}]])
151+
});
152+
153+
it("gets all questions on the 2nd page with (limit=3) call", async () => {
154+
155+
await GetQuestions(undefined, undefined, "difficulty asc")
156+
157+
expect(jest.mocked(fetch).mock.calls).toStrictEqual([[`${NEXT_PUBLIC_QUESTION_SERVICE_URL}questions?sortField=difficulty&sortValue=asc`, {
158+
headers: {
159+
"Authorization": `Bearer ${TOKEN}`,
160+
},
161+
method: "GET",
162+
}]])
163+
});
164+
165+
it("gets all questions on the 2nd page with (limit=3) call", async () => {
166+
167+
await GetQuestions(undefined, undefined, undefined, ["easy", "hard"])
168+
169+
expect(jest.mocked(fetch).mock.calls).toStrictEqual([[`${NEXT_PUBLIC_QUESTION_SERVICE_URL}questions?complexity=easy,hard`, {
170+
headers: {
171+
"Authorization": `Bearer ${TOKEN}`,
172+
},
173+
method: "GET",
174+
}]])
175+
});
176+
177+
it("formats urls for categories", async () => {
178+
179+
await GetQuestions(undefined, undefined, undefined, undefined, ["CatA", "CatB"])
180+
181+
expect(jest.mocked(fetch).mock.calls).toStrictEqual([[
182+
`${NEXT_PUBLIC_QUESTION_SERVICE_URL}questions?categories=CatA,CatB`,
183+
{
184+
headers: {
185+
"Authorization": `Bearer ${TOKEN}`,
186+
},
187+
method: "GET",
188+
}
189+
]])
190+
});
191+
192+
it("formats url for title", async () => {
193+
194+
await GetQuestions(undefined, undefined, undefined, undefined, undefined, "The Title Name")
195+
196+
expect(jest.mocked(fetch).mock.calls).toStrictEqual([[
197+
`${NEXT_PUBLIC_QUESTION_SERVICE_URL}questions?title=The%20Title%20Name`,
198+
{
199+
headers: {
200+
"Authorization": `Bearer ${TOKEN}`,
201+
},
202+
method: "GET",
203+
}
204+
]])
205+
});
206+
})
207+
208+
209+
describe("GetSingleQuestion", () => {
210+
const DOCREF = "mockdocref";
211+
beforeEach(() => {
212+
global.fetch = jest.fn().mockResolvedValue({
213+
async json() {
214+
return QUESTIONS[0]
215+
}
216+
});
217+
});
218+
219+
it("gets a question by docref", async () => {
220+
const res = await GetSingleQuestion(DOCREF);
221+
222+
expect(jest.mocked(fetch).mock.calls).toStrictEqual([[`${NEXT_PUBLIC_QUESTION_SERVICE_URL}questions/${DOCREF}`, {
223+
headers: {
224+
"Authorization": `Bearer ${TOKEN}`,
225+
},
226+
method: "GET",
227+
}]])
228+
expect(res).toStrictEqual(QUESTIONS[0]);
229+
})
230+
})
231+
232+
describe("CreateQuestion", () => {
233+
it("uploads a question", async () => {
234+
// grabs a subset of QUESTIONS[0]
235+
const newQuestion = (({title, description, categories, complexity}) => ({title, description, categories, complexity}))(QUESTIONS[0])
236+
const createdQuestion = QUESTIONS[0];
237+
238+
global.fetch = jest.fn().mockResolvedValue({
239+
status: 200,
240+
statusText: "OK",
241+
async json() {
242+
return createdQuestion
243+
}
244+
});
245+
246+
const res = await CreateQuestion(newQuestion);
247+
248+
expect(jest.mocked(fetch).mock.calls).toStrictEqual([[`${NEXT_PUBLIC_QUESTION_SERVICE_URL}questions`, {
249+
headers: {
250+
"Content-Type": "application/json",
251+
"Authorization": `Bearer ${TOKEN}`,
252+
},
253+
method: "POST",
254+
body: JSON.stringify(newQuestion)
255+
}]])
256+
expect(res).toStrictEqual(createdQuestion);
257+
})
258+
259+
it("fails uploading question", async () => {
260+
// grabs a subset of QUESTIONS[0]
261+
const newQuestion = (({title, description, categories, complexity}) => ({title, description, categories, complexity}))(QUESTIONS[0])
262+
263+
global.fetch = jest.fn().mockResolvedValue({
264+
status: 400,
265+
statusText: "Not Found",
266+
data: "Question title already exists"
267+
})
268+
269+
const res = CreateQuestion(newQuestion);
270+
271+
expect(jest.mocked(fetch).mock.calls).toStrictEqual([[`${NEXT_PUBLIC_QUESTION_SERVICE_URL}questions`, {
272+
headers: {
273+
"Content-Type": "application/json",
274+
"Authorization": `Bearer ${TOKEN}`,
275+
},
276+
method: "POST",
277+
body: JSON.stringify(newQuestion)
278+
}]])
279+
await expect(res).rejects.toThrow("Error creating question: 400 Not Found")
280+
})
281+
282+
283+
})
284+
285+
286+
describe("DeleteQuestion", () => {
287+
const DOCREF = "mockdocref";
288+
beforeEach(() => {
289+
global.fetch = jest.fn().mockResolvedValue({
290+
status: 200,
291+
statusText: "OK",
292+
data: `Question with ID ${DOCREF} deleted successfully`
293+
});
294+
});
295+
296+
it("deletes successfully", async () => {
297+
const shouldbeNothing = await DeleteQuestion(DOCREF);
298+
299+
expect(jest.mocked(fetch).mock.calls).toStrictEqual([[
300+
`${NEXT_PUBLIC_QUESTION_SERVICE_URL}questions/${DOCREF}`,
301+
{
302+
headers: {
303+
"Authorization": `Bearer ${TOKEN}`,
304+
},
305+
method: "DELETE",
306+
}
307+
]])
308+
expect(shouldbeNothing).toBeUndefined();
309+
})
310+
})

0 commit comments

Comments
 (0)