Skip to content

Commit c14743f

Browse files
NodeDiverclaude
andcommitted
Week 1 Task 3: Add 5 comprehensive tests for 70%+ coverage
- Add E2E tests for Load More functionality - Add E2E tests for language switching (EN/ES) - Add API tests for communities pagination (skip/take, page/limit, search) - Add API tests for password reset flow (forgot-password, reset-password) - Add unit tests for CommunitiesGrid component (rendering, load more, search) - Add test selectors (data-testid) to components for E2E stability - Install @vitest/coverage-v8 for test coverage reporting 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 9a62c33 commit c14743f

File tree

9 files changed

+1849
-1
lines changed

9 files changed

+1849
-1
lines changed

package-lock.json

Lines changed: 488 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
"@types/react": "^19",
8080
"@types/react-dom": "^19",
8181
"@vitejs/plugin-react": "^5.0.4",
82+
"@vitest/coverage-v8": "^3.2.4",
8283
"@vitest/ui": "^3.2.4",
8384
"eslint": "^9",
8485
"eslint-config-next": "15.5.4",

src/components/communities-grid.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ export function CommunitiesGrid({
158158
{communities.map((community) => (
159159
<Card
160160
key={community.id}
161+
data-testid="community-card"
161162
className="hover:shadow-lg transition-shadow cursor-pointer"
162163
onClick={() => handleViewCommunity(community.slug)}
163164
>

src/components/language-toggle.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export function LanguageToggle() {
4343
<Button
4444
variant="outline"
4545
size="icon"
46+
data-testid="language-toggle"
4647
className="hover-lift hover-scale animate-bounce"
4748
>
4849
<Languages className="h-[1.2rem] w-[1.2rem]" />
Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
import { describe, it, expect, beforeEach } from "vitest";
2+
import {
3+
createTestUser,
4+
createTestCommunity,
5+
cleanDatabase,
6+
} from "../helpers/test-db";
7+
8+
describe("Communities API Pagination", () => {
9+
beforeEach(async () => {
10+
await cleanDatabase();
11+
});
12+
13+
describe("GET /api/communities with skip/take", () => {
14+
it("should return array when using skip/take", async () => {
15+
const user = await createTestUser();
16+
17+
// Create 15 communities
18+
for (let i = 0; i < 15; i++) {
19+
await createTestCommunity(user.id, {
20+
name: `Community ${i}`,
21+
slug: `community-${i}`,
22+
isPublic: true,
23+
});
24+
}
25+
26+
const response = await fetch(
27+
"http://localhost:3000/api/communities?skip=0&take=10",
28+
);
29+
30+
expect(response.status).toBe(200);
31+
32+
const data = await response.json();
33+
34+
// Should return array directly (not object)
35+
expect(Array.isArray(data)).toBe(true);
36+
expect(data.length).toBe(10);
37+
expect(data[0]).toHaveProperty("name");
38+
expect(data[0]).toHaveProperty("slug");
39+
});
40+
41+
it("should skip correctly with skip parameter", async () => {
42+
const user = await createTestUser();
43+
44+
// Create 20 communities
45+
for (let i = 0; i < 20; i++) {
46+
await createTestCommunity(user.id, {
47+
name: `Community ${i}`,
48+
slug: `community-${i}`,
49+
isPublic: true,
50+
});
51+
}
52+
53+
// Get first 10
54+
const response1 = await fetch(
55+
"http://localhost:3000/api/communities?skip=0&take=10",
56+
);
57+
const page1 = await response1.json();
58+
59+
// Get next 10
60+
const response2 = await fetch(
61+
"http://localhost:3000/api/communities?skip=10&take=10",
62+
);
63+
const page2 = await response2.json();
64+
65+
expect(Array.isArray(page1)).toBe(true);
66+
expect(Array.isArray(page2)).toBe(true);
67+
expect(page1.length).toBe(10);
68+
expect(page2.length).toBe(10);
69+
70+
// Results should be different (ordered by createdAt DESC)
71+
expect(page1[0].id).not.toBe(page2[0].id);
72+
});
73+
});
74+
75+
describe("GET /api/communities with page/limit", () => {
76+
it("should return object with pagination metadata", async () => {
77+
const user = await createTestUser();
78+
79+
// Create 25 communities
80+
for (let i = 0; i < 25; i++) {
81+
await createTestCommunity(user.id, {
82+
name: `Community ${i}`,
83+
slug: `community-${i}`,
84+
isPublic: true,
85+
});
86+
}
87+
88+
const response = await fetch(
89+
"http://localhost:3000/api/communities?page=1&limit=10",
90+
);
91+
92+
expect(response.status).toBe(200);
93+
94+
const data = await response.json();
95+
96+
// Should return object with communities and pagination
97+
expect(data).toHaveProperty("communities");
98+
expect(data).toHaveProperty("pagination");
99+
100+
expect(Array.isArray(data.communities)).toBe(true);
101+
expect(data.communities.length).toBe(10);
102+
103+
expect(data.pagination).toHaveProperty("page");
104+
expect(data.pagination).toHaveProperty("limit");
105+
expect(data.pagination).toHaveProperty("total");
106+
expect(data.pagination).toHaveProperty("pages");
107+
108+
expect(data.pagination.page).toBe(1);
109+
expect(data.pagination.limit).toBe(10);
110+
expect(data.pagination.total).toBe(25);
111+
expect(data.pagination.pages).toBe(3);
112+
});
113+
114+
it("should return correct page with page parameter", async () => {
115+
const user = await createTestUser();
116+
117+
// Create 30 communities
118+
for (let i = 0; i < 30; i++) {
119+
await createTestCommunity(user.id, {
120+
name: `Community ${i}`,
121+
slug: `community-${i}`,
122+
isPublic: true,
123+
});
124+
}
125+
126+
// Get page 2
127+
const response = await fetch(
128+
"http://localhost:3000/api/communities?page=2&limit=10",
129+
);
130+
131+
const data = await response.json();
132+
133+
expect(data.communities.length).toBe(10);
134+
expect(data.pagination.page).toBe(2);
135+
expect(data.pagination.total).toBe(30);
136+
expect(data.pagination.pages).toBe(3);
137+
});
138+
139+
it("should return empty array for page beyond total", async () => {
140+
const user = await createTestUser();
141+
142+
// Create 5 communities
143+
for (let i = 0; i < 5; i++) {
144+
await createTestCommunity(user.id, {
145+
name: `Community ${i}`,
146+
slug: `community-${i}`,
147+
isPublic: true,
148+
});
149+
}
150+
151+
// Request page 5 (beyond available)
152+
const response = await fetch(
153+
"http://localhost:3000/api/communities?page=5&limit=10",
154+
);
155+
156+
const data = await response.json();
157+
158+
expect(data.communities.length).toBe(0);
159+
expect(data.pagination.total).toBe(5);
160+
expect(data.pagination.pages).toBe(1);
161+
});
162+
});
163+
164+
describe("GET /api/communities with search", () => {
165+
it("should filter communities by name", async () => {
166+
const user = await createTestUser();
167+
168+
await createTestCommunity(user.id, {
169+
name: "Bitcoin Developers",
170+
slug: "bitcoin-devs",
171+
isPublic: true,
172+
});
173+
174+
await createTestCommunity(user.id, {
175+
name: "Lightning Network",
176+
slug: "lightning",
177+
isPublic: true,
178+
});
179+
180+
await createTestCommunity(user.id, {
181+
name: "Ethereum Devs",
182+
slug: "ethereum",
183+
isPublic: true,
184+
});
185+
186+
const response = await fetch(
187+
"http://localhost:3000/api/communities?search=bitcoin&skip=0&take=20",
188+
);
189+
190+
const data = await response.json();
191+
192+
expect(Array.isArray(data)).toBe(true);
193+
expect(data.length).toBe(1);
194+
expect(data[0].name).toBe("Bitcoin Developers");
195+
});
196+
197+
it("should filter communities by slug", async () => {
198+
const user = await createTestUser();
199+
200+
await createTestCommunity(user.id, {
201+
name: "Test Community",
202+
slug: "lightning-network",
203+
isPublic: true,
204+
});
205+
206+
await createTestCommunity(user.id, {
207+
name: "Another Community",
208+
slug: "bitcoin-devs",
209+
isPublic: true,
210+
});
211+
212+
const response = await fetch(
213+
"http://localhost:3000/api/communities?search=lightning&skip=0&take=20",
214+
);
215+
216+
const data = await response.json();
217+
218+
expect(Array.isArray(data)).toBe(true);
219+
expect(data.length).toBe(1);
220+
expect(data[0].slug).toBe("lightning-network");
221+
});
222+
223+
it("should be case insensitive", async () => {
224+
const user = await createTestUser();
225+
226+
await createTestCommunity(user.id, {
227+
name: "Bitcoin Developers",
228+
slug: "bitcoin-devs",
229+
isPublic: true,
230+
});
231+
232+
const response = await fetch(
233+
"http://localhost:3000/api/communities?search=BITCOIN&skip=0&take=20",
234+
);
235+
236+
const data = await response.json();
237+
238+
expect(Array.isArray(data)).toBe(true);
239+
expect(data.length).toBe(1);
240+
expect(data[0].name).toBe("Bitcoin Developers");
241+
});
242+
});
243+
244+
describe("GET /api/communities ordering", () => {
245+
it("should order communities by createdAt DESC", async () => {
246+
const user = await createTestUser();
247+
248+
// Create communities with slight delay
249+
const community1 = await createTestCommunity(user.id, {
250+
name: "First Community",
251+
slug: "first",
252+
isPublic: true,
253+
});
254+
255+
await new Promise((resolve) => setTimeout(resolve, 100));
256+
257+
const community2 = await createTestCommunity(user.id, {
258+
name: "Second Community",
259+
slug: "second",
260+
isPublic: true,
261+
});
262+
263+
await new Promise((resolve) => setTimeout(resolve, 100));
264+
265+
const community3 = await createTestCommunity(user.id, {
266+
name: "Third Community",
267+
slug: "third",
268+
isPublic: true,
269+
});
270+
271+
const response = await fetch(
272+
"http://localhost:3000/api/communities?skip=0&take=10",
273+
);
274+
275+
const data = await response.json();
276+
277+
// Newest first (DESC order)
278+
expect(data[0].id).toBe(community3.id);
279+
expect(data[1].id).toBe(community2.id);
280+
expect(data[2].id).toBe(community1.id);
281+
});
282+
});
283+
});

0 commit comments

Comments
 (0)