Skip to content

Commit 03e9495

Browse files
committed
test: Enhance WorkplaceProfilePage tests and add useReviewHelpful hook tests
1 parent b3fa0c9 commit 03e9495

File tree

2 files changed

+305
-0
lines changed

2 files changed

+305
-0
lines changed

apps/jobboard-frontend/src/__tests__/workplace/integration/WorkplaceProfilePage.test.tsx

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { render, screen, waitFor } from '@testing-library/react';
2+
import userEvent from '@testing-library/user-event';
23
import WorkplaceProfilePage from '@/pages/WorkplaceProfilePage';
34
import { describe, it, expect, vi } from 'vitest';
45
import { MemoryRouter, Route, Routes } from 'react-router-dom';
@@ -91,4 +92,178 @@ describe('WorkplaceProfilePage Integration', () => {
9192
expect(screen.getByText('employer1')).toBeInTheDocument();
9293
});
9394
});
95+
96+
it('renders helpful initial state from API', async () => {
97+
const pagedReviews = {
98+
content: [
99+
{
100+
id: 201,
101+
workplaceId: 1,
102+
userId: 5,
103+
username: 'user5',
104+
title: 'Helpful toggle test',
105+
content: 'Toggle me',
106+
anonymous: false,
107+
helpfulCount: 1,
108+
helpfulByUser: false,
109+
overallRating: 4,
110+
ethicalPolicyRatings: {},
111+
createdAt: '2024-01-10',
112+
updatedAt: '2024-01-10',
113+
},
114+
{
115+
id: 202,
116+
workplaceId: 1,
117+
userId: 6,
118+
username: 'user6',
119+
title: 'Already voted',
120+
content: 'I voted before',
121+
anonymous: false,
122+
helpfulCount: 5,
123+
helpfulByUser: true,
124+
overallRating: 5,
125+
ethicalPolicyRatings: {},
126+
createdAt: '2024-01-09',
127+
updatedAt: '2024-01-09',
128+
},
129+
],
130+
page: 0,
131+
size: 10,
132+
totalElements: 2,
133+
totalPages: 1,
134+
hasNext: false,
135+
hasPrevious: false,
136+
};
137+
138+
server.use(
139+
http.get(`${API_BASE_URL}/workplace/:id/review`, async () => HttpResponse.json(pagedReviews)),
140+
http.post(`${API_BASE_URL}/workplace/:workplaceId/review/:reviewId/helpful`, async () =>
141+
HttpResponse.json({
142+
...pagedReviews.content[0],
143+
helpfulCount: 2,
144+
helpfulByUser: true,
145+
}),
146+
),
147+
);
148+
149+
renderPage();
150+
151+
await waitFor(() => {
152+
expect(screen.getByText('Helpful toggle test')).toBeInTheDocument();
153+
expect(screen.getByText('Already voted')).toBeInTheDocument();
154+
});
155+
156+
// Initial counts reflect server data
157+
expect(
158+
screen.getByRole('button', { name: /reviews.helpful \(1\)/i }),
159+
).toBeInTheDocument();
160+
expect(
161+
screen.getByRole('button', { name: /reviews.helpful \(5\)/i }),
162+
).toBeInTheDocument();
163+
});
164+
165+
it('toggles helpful on and off in the page', async () => {
166+
const review = {
167+
id: 301,
168+
workplaceId: 1,
169+
userId: 7,
170+
username: 'user7',
171+
title: 'Toggle roundtrip',
172+
content: 'Click twice',
173+
anonymous: false,
174+
helpfulCount: 1,
175+
helpfulByUser: false,
176+
overallRating: 4,
177+
ethicalPolicyRatings: {},
178+
createdAt: '2024-02-01',
179+
updatedAt: '2024-02-01',
180+
};
181+
182+
server.use(
183+
http.get(`${API_BASE_URL}/workplace/:id/review`, async () =>
184+
HttpResponse.json({
185+
content: [review],
186+
page: 0,
187+
size: 10,
188+
totalElements: 1,
189+
totalPages: 1,
190+
hasNext: false,
191+
hasPrevious: false,
192+
}),
193+
),
194+
);
195+
196+
let toggled = false;
197+
server.use(
198+
http.post(`${API_BASE_URL}/workplace/:workplaceId/review/:reviewId/helpful`, async () => {
199+
toggled = !toggled;
200+
return HttpResponse.json({
201+
...review,
202+
helpfulCount: toggled ? 2 : 1,
203+
helpfulByUser: toggled,
204+
});
205+
}),
206+
);
207+
208+
renderPage();
209+
const user = userEvent.setup();
210+
211+
const button = await screen.findByRole('button', { name: /reviews.helpful \(1\)/i });
212+
213+
await user.click(button);
214+
await waitFor(() =>
215+
expect(screen.getByRole('button', { name: /reviews.helpful \(2\)/i })).toBeInTheDocument(),
216+
);
217+
218+
await user.click(screen.getByRole('button', { name: /reviews.helpful \(2\)/i }));
219+
await waitFor(() =>
220+
expect(screen.getByRole('button', { name: /reviews.helpful \(1\)/i })).toBeInTheDocument(),
221+
);
222+
});
223+
224+
it('rolls back helpful count on error', async () => {
225+
const review = {
226+
id: 401,
227+
workplaceId: 1,
228+
userId: 8,
229+
username: 'user8',
230+
title: 'Toggle error',
231+
content: 'Should rollback',
232+
anonymous: false,
233+
helpfulCount: 3,
234+
helpfulByUser: false,
235+
overallRating: 4,
236+
ethicalPolicyRatings: {},
237+
createdAt: '2024-03-01',
238+
updatedAt: '2024-03-01',
239+
};
240+
241+
server.use(
242+
http.get(`${API_BASE_URL}/workplace/:id/review`, async () =>
243+
HttpResponse.json({
244+
content: [review],
245+
page: 0,
246+
size: 10,
247+
totalElements: 1,
248+
totalPages: 1,
249+
hasNext: false,
250+
hasPrevious: false,
251+
}),
252+
),
253+
http.post(`${API_BASE_URL}/workplace/:workplaceId/review/:reviewId/helpful`, async () =>
254+
new HttpResponse(null, { status: 500 }),
255+
),
256+
);
257+
258+
renderPage();
259+
const user = userEvent.setup();
260+
261+
const button = await screen.findByRole('button', { name: /reviews.helpful \(3\)/i });
262+
await user.click(button);
263+
264+
// Optimistic update will change text briefly; final state should roll back to original count
265+
await waitFor(() =>
266+
expect(screen.getByRole('button', { name: /reviews.helpful \(3\)/i })).toBeInTheDocument(),
267+
);
268+
});
94269
});
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import { renderHook, act } from '@testing-library/react';
2+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
3+
import { useReviewHelpful } from '../useReviewHelpful';
4+
import type { ReviewResponse } from '@/types/workplace.types';
5+
6+
const mockMarkReviewHelpful = vi.fn();
7+
8+
vi.mock('@/services/reviews.service', () => ({
9+
markReviewHelpful: (...args: unknown[]) => mockMarkReviewHelpful(...args),
10+
}));
11+
12+
vi.mock('react-toastify', () => ({
13+
toast: {
14+
success: vi.fn(),
15+
error: vi.fn(),
16+
},
17+
}));
18+
19+
vi.mock('react-i18next', () => ({
20+
useTranslation: () => ({
21+
t: (key: string) => key,
22+
}),
23+
}));
24+
25+
const baseReview: ReviewResponse = {
26+
id: 1,
27+
workplaceId: 10,
28+
userId: 20,
29+
anonymous: false,
30+
helpfulCount: 0,
31+
overallRating: 4,
32+
ethicalPolicyRatings: {},
33+
createdAt: '2025-12-05T17:52:06.771Z',
34+
updatedAt: '2025-12-05T17:52:06.771Z',
35+
};
36+
37+
describe('useReviewHelpful', () => {
38+
beforeEach(() => {
39+
mockMarkReviewHelpful.mockReset();
40+
});
41+
42+
afterEach(() => {
43+
vi.clearAllMocks();
44+
});
45+
46+
it('initializes from provided count and helpfulByUser', () => {
47+
const { result } = renderHook(() =>
48+
useReviewHelpful({
49+
workplaceId: 10,
50+
reviewId: 1,
51+
initialHelpfulCount: 5,
52+
initialUserVoted: true,
53+
}),
54+
);
55+
56+
expect(result.current.helpfulCount).toBe(5);
57+
expect(result.current.userVoted).toBe(true);
58+
});
59+
60+
it('marks helpful and syncs response state', async () => {
61+
mockMarkReviewHelpful.mockResolvedValue({
62+
...baseReview,
63+
helpfulCount: 6,
64+
helpfulByUser: true,
65+
});
66+
67+
const { result } = renderHook(() =>
68+
useReviewHelpful({
69+
workplaceId: 10,
70+
reviewId: 1,
71+
initialHelpfulCount: 5,
72+
initialUserVoted: false,
73+
}),
74+
);
75+
76+
await act(async () => {
77+
await result.current.toggleHelpful();
78+
});
79+
80+
expect(mockMarkReviewHelpful).toHaveBeenCalledWith(10, 1);
81+
expect(result.current.helpfulCount).toBe(6);
82+
expect(result.current.userVoted).toBe(true);
83+
});
84+
85+
it('removes helpful when user clicks again', async () => {
86+
mockMarkReviewHelpful.mockResolvedValue({
87+
...baseReview,
88+
helpfulCount: 4,
89+
helpfulByUser: false,
90+
});
91+
92+
const { result } = renderHook(() =>
93+
useReviewHelpful({
94+
workplaceId: 10,
95+
reviewId: 1,
96+
initialHelpfulCount: 5,
97+
initialUserVoted: true,
98+
}),
99+
);
100+
101+
await act(async () => {
102+
await result.current.toggleHelpful();
103+
});
104+
105+
expect(mockMarkReviewHelpful).toHaveBeenCalledWith(10, 1);
106+
expect(result.current.helpfulCount).toBe(4);
107+
expect(result.current.userVoted).toBe(false);
108+
});
109+
110+
it('reverts optimistic update on error', async () => {
111+
mockMarkReviewHelpful.mockRejectedValue(new Error('network'));
112+
113+
const { result } = renderHook(() =>
114+
useReviewHelpful({
115+
workplaceId: 10,
116+
reviewId: 1,
117+
initialHelpfulCount: 2,
118+
initialUserVoted: false,
119+
}),
120+
);
121+
122+
await act(async () => {
123+
await result.current.toggleHelpful();
124+
});
125+
126+
expect(result.current.helpfulCount).toBe(2);
127+
expect(result.current.userVoted).toBe(false);
128+
});
129+
});
130+

0 commit comments

Comments
 (0)