Skip to content

Commit 35919a0

Browse files
committed
test: add tests for package version page
1 parent 9b7412c commit 35919a0

1 file changed

Lines changed: 186 additions & 0 deletions

File tree

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
import { beforeEach, describe, expect, it, vi } from 'vitest'
2+
import { mountSuspended } from '@nuxt/test-utils/runtime'
3+
import type * as FastNpmMeta from 'fast-npm-meta'
4+
import type * as NpmApi from '~/utils/npm/api'
5+
import VersionsPage from '~/pages/package/[[org]]/[name]/versions.vue'
6+
7+
// ── Mocks ─────────────────────────────────────────────────────────────────────
8+
9+
// Phase 1: lightweight version summary (page load)
10+
const mockGetVersions = vi.fn()
11+
vi.mock('fast-npm-meta', async importOriginal => {
12+
const actual = await importOriginal<typeof FastNpmMeta>()
13+
return {
14+
...actual,
15+
getVersions: (...args: unknown[]) => mockGetVersions(...args),
16+
}
17+
})
18+
19+
// Phase 2: full metadata (loaded on first group expand)
20+
const mockFetchAllPackageVersions = vi.fn()
21+
vi.mock('~/utils/npm/api', async importOriginal => {
22+
const actual = await importOriginal<typeof NpmApi>()
23+
return {
24+
...actual,
25+
fetchAllPackageVersions: (...args: unknown[]) => mockFetchAllPackageVersions(...args),
26+
}
27+
})
28+
29+
// ── Helpers ───────────────────────────────────────────────────────────────────
30+
31+
function makeVersionData(
32+
versions: string[],
33+
distTags: Record<string, string>,
34+
time?: Record<string, string>,
35+
) {
36+
return {
37+
distTags,
38+
versions,
39+
time:
40+
time ??
41+
Object.fromEntries(versions.map((v, i) => [v, new Date(2024, 0, 15 - i).toISOString()])),
42+
}
43+
}
44+
45+
async function mountPage(route = '/package/test-package/versions') {
46+
return mountSuspended(VersionsPage, { route })
47+
}
48+
49+
// ── Tests ─────────────────────────────────────────────────────────────────────
50+
51+
describe('package versions page', () => {
52+
beforeEach(() => {
53+
mockGetVersions.mockReset()
54+
mockFetchAllPackageVersions.mockReset()
55+
clearNuxtData()
56+
})
57+
58+
describe('basic rendering', () => {
59+
it('renders the package name in the header', async () => {
60+
mockGetVersions.mockResolvedValue(makeVersionData(['1.0.0'], { latest: '1.0.0' }))
61+
const component = await mountPage()
62+
await vi.waitFor(() => expect(component.text()).toContain('test-package'))
63+
})
64+
65+
it('renders "Version History" section with total count', async () => {
66+
mockGetVersions.mockResolvedValue(makeVersionData(['2.0.0', '1.0.0'], { latest: '2.0.0' }))
67+
const component = await mountPage()
68+
await vi.waitFor(() => {
69+
expect(component.text()).toContain('Version History')
70+
expect(component.text()).toContain('(2)')
71+
})
72+
})
73+
})
74+
75+
describe('current tags section', () => {
76+
it('renders latest version in the featured card', async () => {
77+
mockGetVersions.mockResolvedValue(makeVersionData(['2.0.0', '1.0.0'], { latest: '2.0.0' }))
78+
const component = await mountPage()
79+
await vi.waitFor(() => {
80+
expect(component.text()).toContain('latest')
81+
expect(component.text()).toContain('2.0.0')
82+
})
83+
})
84+
85+
it('renders non-latest dist-tags in compact list', async () => {
86+
mockGetVersions.mockResolvedValue(
87+
makeVersionData(['2.0.0', '1.0.0', '1.0.0-beta.1'], {
88+
latest: '2.0.0',
89+
stable: '1.0.0',
90+
beta: '1.0.0-beta.1',
91+
}),
92+
)
93+
const component = await mountPage()
94+
await vi.waitFor(() => {
95+
expect(component.text()).toContain('stable')
96+
expect(component.text()).toContain('beta')
97+
})
98+
})
99+
})
100+
101+
describe('version history groups', () => {
102+
it('renders group headers for each major version', async () => {
103+
mockGetVersions.mockResolvedValue(
104+
makeVersionData(['2.1.0', '2.0.0', '1.0.0'], { latest: '2.1.0' }),
105+
)
106+
const component = await mountPage()
107+
await vi.waitFor(() => {
108+
expect(component.text()).toContain('2.x')
109+
expect(component.text()).toContain('1.x')
110+
})
111+
})
112+
113+
it('groups 0.x versions by major.minor (not just major)', async () => {
114+
mockGetVersions.mockResolvedValue(
115+
makeVersionData(['0.10.1', '0.10.0', '0.9.0'], { latest: '0.10.1' }),
116+
)
117+
const component = await mountPage()
118+
await vi.waitFor(() => {
119+
expect(component.text()).toContain('0.10.x')
120+
expect(component.text()).toContain('0.9.x')
121+
})
122+
})
123+
})
124+
125+
describe('group expand / collapse', () => {
126+
it('expands a group and shows version rows on click', async () => {
127+
mockGetVersions.mockResolvedValue(makeVersionData(['1.1.0', '1.0.0'], { latest: '1.1.0' }))
128+
mockFetchAllPackageVersions.mockResolvedValue([
129+
{ version: '1.1.0', time: '2024-01-15T00:00:00.000Z', hasProvenance: false },
130+
{ version: '1.0.0', time: '2024-01-10T00:00:00.000Z', hasProvenance: false },
131+
])
132+
const component = await mountPage()
133+
await vi.waitFor(() => expect(component.text()).toContain('1.x'))
134+
135+
const header = component.find('button[aria-expanded="false"]')
136+
await header.trigger('click')
137+
138+
await vi.waitFor(() => {
139+
expect(header.attributes('aria-expanded')).toBe('true')
140+
})
141+
})
142+
143+
it('only fetches full metadata once across multiple group expansions', async () => {
144+
mockGetVersions.mockResolvedValue(makeVersionData(['2.0.0', '1.0.0'], { latest: '2.0.0' }))
145+
mockFetchAllPackageVersions.mockResolvedValue([
146+
{ version: '2.0.0', time: '2024-01-15T00:00:00.000Z', hasProvenance: false },
147+
{ version: '1.0.0', time: '2024-01-10T00:00:00.000Z', hasProvenance: false },
148+
])
149+
const component = await mountPage()
150+
await vi.waitFor(() => {
151+
expect(component.findAll('button[aria-expanded="false"]').length).toBeGreaterThanOrEqual(2)
152+
})
153+
154+
const [first, second] = component.findAll('button[aria-expanded="false"]')
155+
await first!.trigger('click')
156+
await vi.waitFor(() => expect(mockFetchAllPackageVersions).toHaveBeenCalledTimes(1))
157+
158+
await second!.trigger('click')
159+
expect(mockFetchAllPackageVersions).toHaveBeenCalledTimes(1)
160+
})
161+
})
162+
163+
describe('version filter', () => {
164+
it('filters groups by substring match', async () => {
165+
// Use versions where the filter string "1.0" is unique to the 1.x group
166+
mockGetVersions.mockResolvedValue(
167+
makeVersionData(['3.0.0', '2.0.0', '1.0.0'], { latest: '3.0.0' }),
168+
)
169+
const component = await mountPage()
170+
await vi.waitFor(() => {
171+
expect(component.text()).toContain('1.x')
172+
expect(component.text()).toContain('2.x')
173+
expect(component.text()).toContain('3.x')
174+
})
175+
176+
const input = component.find('input[placeholder="Filter versions\u2026"]')
177+
await input.setValue('1.0')
178+
179+
await vi.waitFor(() => {
180+
expect(component.text()).toContain('1.x')
181+
expect(component.text()).not.toContain('2.x')
182+
expect(component.text()).not.toContain('3.x')
183+
})
184+
})
185+
})
186+
})

0 commit comments

Comments
 (0)