|
1 | 1 | import { describe, it, expect, afterEach } from "vitest"; |
2 | | -import { render, screen, cleanup } from "@testing-library/react"; |
| 2 | +import { render, screen, cleanup, fireEvent } from "@testing-library/react"; |
| 3 | +import { readFileSync } from "fs"; |
| 4 | +import { resolve } from "path"; |
3 | 5 | import { TopProjects } from "@/components/TopProjects"; |
4 | 6 | import type { RepoClassification, ContributionItem } from "@/lib/types"; |
5 | 7 |
|
6 | 8 | afterEach(cleanup); |
7 | 9 |
|
| 10 | +const dateRange = { from: new Date(), to: new Date() }; |
| 11 | + |
8 | 12 | const repos: RepoClassification[] = [ |
9 | 13 | { nameWithOwner: "bitcoin/bitcoin", url: "https://github.com/bitcoin/bitcoin", tier: "core", reason: "curated" }, |
10 | 14 | { nameWithOwner: "mempool/mempool", url: "https://github.com/mempool/mempool", tier: "ecosystem", reason: "curated" }, |
11 | 15 | { nameWithOwner: "user/nostr-tool", url: "https://github.com/user/nostr-tool", tier: "adjacent", reason: "keyword" }, |
12 | 16 | ]; |
13 | 17 |
|
14 | 18 | const contributions: ContributionItem[] = [ |
15 | | - { repoNameWithOwner: "bitcoin/bitcoin", type: "commit", count: 50, dateRange: { from: new Date(), to: new Date() } }, |
16 | | - { repoNameWithOwner: "mempool/mempool", type: "commit", count: 20, dateRange: { from: new Date(), to: new Date() } }, |
17 | | - { repoNameWithOwner: "user/nostr-tool", type: "commit", count: 5, dateRange: { from: new Date(), to: new Date() } }, |
| 19 | + { repoNameWithOwner: "bitcoin/bitcoin", type: "commit", count: 50, dateRange }, |
| 20 | + { repoNameWithOwner: "mempool/mempool", type: "commit", count: 20, dateRange }, |
| 21 | + { repoNameWithOwner: "user/nostr-tool", type: "commit", count: 5, dateRange }, |
| 22 | +]; |
| 23 | + |
| 24 | +// 7 repos to test pagination (PAGE_SIZE = 5) |
| 25 | +const manyRepos: RepoClassification[] = [ |
| 26 | + { nameWithOwner: "bitcoin/bitcoin", tier: "core", reason: "curated" }, |
| 27 | + { nameWithOwner: "lightning/lnd", tier: "core", reason: "curated" }, |
| 28 | + { nameWithOwner: "btcsuite/btcd", tier: "core", reason: "curated" }, |
| 29 | + { nameWithOwner: "mempool/mempool", tier: "ecosystem", reason: "curated" }, |
| 30 | + { nameWithOwner: "blockstream/esplora", tier: "ecosystem", reason: "curated" }, |
| 31 | + { nameWithOwner: "AcmeInc/bitcoin-lib", tier: "ecosystem", reason: "keyword" }, |
| 32 | + { nameWithOwner: "user/nostr-tool", tier: "adjacent", reason: "keyword" }, |
18 | 33 | ]; |
19 | 34 |
|
| 35 | +const manyContributions: ContributionItem[] = manyRepos.map((r, i) => ({ |
| 36 | + repoNameWithOwner: r.nameWithOwner, |
| 37 | + type: "commit", |
| 38 | + count: 70 - i * 10, |
| 39 | + dateRange, |
| 40 | +})); |
| 41 | + |
20 | 42 | describe("TopProjects", () => { |
| 43 | + it("has 'use client' directive", () => { |
| 44 | + const source = readFileSync( |
| 45 | + resolve(__dirname, "../TopProjects.tsx"), |
| 46 | + "utf-8" |
| 47 | + ); |
| 48 | + expect(source.trimStart().startsWith('"use client"')).toBe(true); |
| 49 | + }); |
| 50 | + |
21 | 51 | it("renders repos with tier badges", () => { |
22 | 52 | render( |
23 | 53 | <TopProjects bitcoinRepos={repos} contributions={contributions} showAdjacent /> |
@@ -93,4 +123,58 @@ describe("TopProjects", () => { |
93 | 123 | expect(screen.getByLabelText("Tier: ecosystem")).toBeInTheDocument(); |
94 | 124 | expect(screen.getByLabelText("Tier: adjacent")).toBeInTheDocument(); |
95 | 125 | }); |
| 126 | + |
| 127 | + it("shows only 5 projects initially when there are more", () => { |
| 128 | + render( |
| 129 | + <TopProjects bitcoinRepos={manyRepos} contributions={manyContributions} showAdjacent /> |
| 130 | + ); |
| 131 | + |
| 132 | + // First 5 by contribution count should be visible |
| 133 | + expect(screen.getByText("bitcoin/bitcoin")).toBeInTheDocument(); |
| 134 | + expect(screen.getByText("lightning/lnd")).toBeInTheDocument(); |
| 135 | + expect(screen.getByText("btcsuite/btcd")).toBeInTheDocument(); |
| 136 | + expect(screen.getByText("mempool/mempool")).toBeInTheDocument(); |
| 137 | + expect(screen.getByText("blockstream/esplora")).toBeInTheDocument(); |
| 138 | + |
| 139 | + // 6th and 7th should not be visible |
| 140 | + expect(screen.queryByText("AcmeInc/bitcoin-lib")).not.toBeInTheDocument(); |
| 141 | + expect(screen.queryByText("user/nostr-tool")).not.toBeInTheDocument(); |
| 142 | + }); |
| 143 | + |
| 144 | + it("shows Load more button when there are more than 5 projects", () => { |
| 145 | + render( |
| 146 | + <TopProjects bitcoinRepos={manyRepos} contributions={manyContributions} showAdjacent /> |
| 147 | + ); |
| 148 | + |
| 149 | + expect(screen.getByRole("button", { name: "Load more" })).toBeInTheDocument(); |
| 150 | + }); |
| 151 | + |
| 152 | + it("does not show Load more button when 5 or fewer projects", () => { |
| 153 | + render( |
| 154 | + <TopProjects bitcoinRepos={repos} contributions={contributions} showAdjacent /> |
| 155 | + ); |
| 156 | + |
| 157 | + expect(screen.queryByRole("button", { name: "Load more" })).not.toBeInTheDocument(); |
| 158 | + }); |
| 159 | + |
| 160 | + it("reveals remaining projects when Load more is clicked", () => { |
| 161 | + render( |
| 162 | + <TopProjects bitcoinRepos={manyRepos} contributions={manyContributions} showAdjacent /> |
| 163 | + ); |
| 164 | + |
| 165 | + fireEvent.click(screen.getByRole("button", { name: "Load more" })); |
| 166 | + |
| 167 | + expect(screen.getByText("AcmeInc/bitcoin-lib")).toBeInTheDocument(); |
| 168 | + expect(screen.getByText("user/nostr-tool")).toBeInTheDocument(); |
| 169 | + }); |
| 170 | + |
| 171 | + it("hides Load more button after all projects are shown", () => { |
| 172 | + render( |
| 173 | + <TopProjects bitcoinRepos={manyRepos} contributions={manyContributions} showAdjacent /> |
| 174 | + ); |
| 175 | + |
| 176 | + fireEvent.click(screen.getByRole("button", { name: "Load more" })); |
| 177 | + |
| 178 | + expect(screen.queryByRole("button", { name: "Load more" })).not.toBeInTheDocument(); |
| 179 | + }); |
96 | 180 | }); |
0 commit comments