Skip to content

Commit db58690

Browse files
committed
test: fix expection & add unit tests for filter and toggle
1 parent 048f497 commit db58690

1 file changed

Lines changed: 293 additions & 5 deletions

File tree

test/nuxt/pages/PackageVersionsPage.spec.ts

Lines changed: 293 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,17 @@ describe('package versions page', () => {
104104
beta: '1.0.0-beta.1',
105105
})
106106
const component = await mountPage()
107-
await vi.waitFor(() => {
108-
expect(component.text()).toContain('stable')
109-
expect(component.text()).toContain('beta')
110-
})
107+
108+
// stable is a non-prerelease tag — visible by default
109+
await vi.waitFor(() => expect(component.text()).toContain('stable'))
110+
111+
// beta points to a prerelease version — hidden by default, revealed via "Show all"
112+
expect(component.text()).not.toContain('beta')
113+
const showAllButton = component.findAll('button').find(b => b.text().includes('Show all'))
114+
expect(showAllButton).toBeDefined()
115+
await showAllButton!.trigger('click')
116+
117+
await vi.waitFor(() => expect(component.text()).toContain('beta'))
111118
})
112119
})
113120

@@ -164,7 +171,6 @@ describe('package versions page', () => {
164171

165172
describe('version filter', () => {
166173
it('filters groups by substring match', async () => {
167-
// Use versions where the filter string "1.0" is unique to the 1.x group
168174
nextFetchResponse = makeVersionData(['3.0.0', '2.0.0', '1.0.0'], { latest: '3.0.0' })
169175
const component = await mountPage()
170176
await vi.waitFor(() => {
@@ -182,5 +188,287 @@ describe('package versions page', () => {
182188
expect(component.text()).not.toContain('3.x')
183189
})
184190
})
191+
192+
it('filters groups by semver range', async () => {
193+
nextFetchResponse = makeVersionData(['3.0.0', '2.1.0', '2.0.0', '1.0.0'], {
194+
latest: '3.0.0',
195+
})
196+
const component = await mountPage()
197+
await vi.waitFor(() => expect(component.text()).toContain('3.x'))
198+
199+
const input = component.find('input[autocomplete="off"]')
200+
await input.setValue('>=2.0.0 <3.0.0')
201+
202+
await vi.waitFor(() => {
203+
expect(component.text()).toContain('2.x')
204+
expect(component.text()).not.toContain('1.x')
205+
expect(component.text()).not.toContain('3.x')
206+
})
207+
})
208+
209+
it('shows no-match message when filter matches nothing', async () => {
210+
nextFetchResponse = makeVersionData(['2.0.0', '1.0.0'], { latest: '2.0.0' })
211+
const component = await mountPage()
212+
await vi.waitFor(() => expect(component.text()).toContain('2.x'))
213+
214+
const input = component.find('input[autocomplete="off"]')
215+
await input.setValue('9.9.9')
216+
217+
await vi.waitFor(() => {
218+
expect(component.text()).not.toContain('1.x')
219+
expect(component.text()).not.toContain('2.x')
220+
// no-match status message rendered
221+
expect(component.find('[role="status"]').exists()).toBe(true)
222+
})
223+
})
224+
225+
it('shows error indicator for an invalid semver range', async () => {
226+
nextFetchResponse = makeVersionData(['1.0.0'], { latest: '1.0.0' })
227+
const component = await mountPage()
228+
await vi.waitFor(() => expect(component.text()).toContain('1.x'))
229+
230+
const input = component.find('input[autocomplete="off"]')
231+
await input.setValue('not-a-range!!!')
232+
233+
await vi.waitFor(() => {
234+
expect(input.attributes('aria-invalid')).toBe('true')
235+
})
236+
})
237+
238+
it('clearing the filter restores all groups', async () => {
239+
nextFetchResponse = makeVersionData(['2.0.0', '1.0.0'], { latest: '2.0.0' })
240+
const component = await mountPage()
241+
await vi.waitFor(() => {
242+
expect(component.text()).toContain('1.x')
243+
expect(component.text()).toContain('2.x')
244+
})
245+
246+
const input = component.find('input[autocomplete="off"]')
247+
await input.setValue('1.0')
248+
await vi.waitFor(() => expect(component.text()).not.toContain('2.x'))
249+
250+
await input.setValue('')
251+
await vi.waitFor(() => {
252+
expect(component.text()).toContain('1.x')
253+
expect(component.text()).toContain('2.x')
254+
})
255+
})
256+
})
257+
258+
describe('filter popover', () => {
259+
it('opens and closes on toggle button click', async () => {
260+
nextFetchResponse = makeVersionData(['1.0.0'], { latest: '1.0.0' })
261+
const component = await mountPage()
262+
await vi.waitFor(() => expect(component.text()).toContain('test-package'))
263+
264+
const toggleBtn = component.find('button[aria-haspopup="dialog"]')
265+
expect(toggleBtn.attributes('aria-expanded')).toBe('false')
266+
267+
await toggleBtn.trigger('click')
268+
expect(toggleBtn.attributes('aria-expanded')).toBe('true')
269+
expect(component.find('[role="dialog"]').exists()).toBe(true)
270+
271+
await toggleBtn.trigger('click')
272+
expect(toggleBtn.attributes('aria-expanded')).toBe('false')
273+
expect(component.find('[role="dialog"]').exists()).toBe(false)
274+
})
275+
276+
it('closes when Escape is pressed', async () => {
277+
nextFetchResponse = makeVersionData(['1.0.0'], { latest: '1.0.0' })
278+
const component = await mountPage()
279+
await vi.waitFor(() => expect(component.text()).toContain('test-package'))
280+
281+
const toggleBtn = component.find('button[aria-haspopup="dialog"]')
282+
await toggleBtn.trigger('click')
283+
expect(toggleBtn.attributes('aria-expanded')).toBe('true')
284+
285+
window.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }))
286+
await vi.waitFor(() => expect(toggleBtn.attributes('aria-expanded')).toBe('false'))
287+
})
288+
289+
it('shows a badge counting active filters', async () => {
290+
nextFetchResponse = makeVersionData(['1.0.0'], { latest: '1.0.0' })
291+
const component = await mountPage()
292+
await vi.waitFor(() => expect(component.text()).toContain('test-package'))
293+
294+
const toggleBtn = component.find('button[aria-haspopup="dialog"]')
295+
expect(toggleBtn.text()).toBe('') // no badge when no filters active
296+
297+
await toggleBtn.trigger('click')
298+
const checkboxes = component.find('[role="dialog"]').findAll('input[type="checkbox"]')
299+
300+
await checkboxes[0].setValue(true) // enable show prereleases
301+
await vi.waitFor(() => expect(toggleBtn.text()).toBe('1'))
302+
303+
await checkboxes[1].setValue(true) // enable show deprecated
304+
await vi.waitFor(() => expect(toggleBtn.text()).toBe('2'))
305+
306+
await checkboxes[0].setValue(false) // disable show prereleases
307+
await vi.waitFor(() => expect(toggleBtn.text()).toBe('1'))
308+
})
309+
})
310+
311+
describe('show prereleases toggle', () => {
312+
it('hides prerelease-only version groups by default', async () => {
313+
// 1.0.0-alpha.1 is the only version in 1.x — group is invisible until toggled
314+
nextFetchResponse = makeVersionData(['2.0.0', '1.0.0-alpha.1'], { latest: '2.0.0' })
315+
const component = await mountPage()
316+
await vi.waitFor(() => {
317+
expect(component.text()).toContain('2.x')
318+
expect(component.text()).not.toContain('1.x')
319+
})
320+
})
321+
322+
it('reveals prerelease version groups when the toggle is enabled', async () => {
323+
nextFetchResponse = makeVersionData(['2.0.0', '1.0.0-alpha.1'], { latest: '2.0.0' })
324+
const component = await mountPage()
325+
await vi.waitFor(() => expect(component.text()).toContain('2.x'))
326+
327+
const toggleBtn = component.find('button[aria-haspopup="dialog"]')
328+
await toggleBtn.trigger('click')
329+
const checkboxes = component.find('[role="dialog"]').findAll('input[type="checkbox"]')
330+
await checkboxes[0].setValue(true) // showPrereleases
331+
332+
await vi.waitFor(() => {
333+
expect(component.text()).toContain('2.x')
334+
expect(component.text()).toContain('1.x')
335+
})
336+
})
337+
})
338+
339+
describe('show deprecated toggle', () => {
340+
it('hides deprecated-only version groups by default once metadata loads', async () => {
341+
nextFetchResponse = makeVersionData(['2.0.0', '1.0.0'], { latest: '2.0.0' })
342+
mockFetchAllPackageVersions.mockResolvedValue([
343+
{ version: '2.0.0', hasProvenance: false },
344+
{ version: '1.0.0', deprecated: 'Use 2.x instead', hasProvenance: false },
345+
])
346+
const component = await mountPage()
347+
// fullVersionMap populates async; wait for the 1.x group to disappear
348+
await vi.waitFor(() => {
349+
expect(component.text()).toContain('2.x')
350+
expect(component.text()).not.toContain('1.x')
351+
})
352+
})
353+
354+
it('reveals deprecated version groups when the toggle is enabled', async () => {
355+
nextFetchResponse = makeVersionData(['2.0.0', '1.0.0'], { latest: '2.0.0' })
356+
mockFetchAllPackageVersions.mockResolvedValue([
357+
{ version: '2.0.0', hasProvenance: false },
358+
{ version: '1.0.0', deprecated: 'Use 2.x instead', hasProvenance: false },
359+
])
360+
const component = await mountPage()
361+
await vi.waitFor(() => expect(component.text()).not.toContain('1.x'))
362+
363+
const toggleBtn = component.find('button[aria-haspopup="dialog"]')
364+
await toggleBtn.trigger('click')
365+
const checkboxes = component.find('[role="dialog"]').findAll('input[type="checkbox"]')
366+
await checkboxes[1].setValue(true) // showDeprecated
367+
368+
await vi.waitFor(() => expect(component.text()).toContain('1.x'))
369+
})
370+
371+
it('marks a group header with a deprecated badge when all its versions are deprecated', async () => {
372+
nextFetchResponse = makeVersionData(['2.0.0', '1.1.0', '1.0.0'], { latest: '2.0.0' })
373+
mockFetchAllPackageVersions.mockResolvedValue([
374+
{ version: '2.0.0', hasProvenance: false },
375+
{ version: '1.1.0', deprecated: 'Use 2.x instead', hasProvenance: false },
376+
{ version: '1.0.0', deprecated: 'Use 2.x instead', hasProvenance: false },
377+
])
378+
const component = await mountPage()
379+
await vi.waitFor(() => expect(component.text()).not.toContain('1.x'))
380+
381+
// Enable show deprecated to reveal the all-deprecated 1.x group
382+
const toggleBtn = component.find('button[aria-haspopup="dialog"]')
383+
await toggleBtn.trigger('click')
384+
const checkboxes = component.find('[role="dialog"]').findAll('input[type="checkbox"]')
385+
await checkboxes[1].setValue(true)
386+
387+
await vi.waitFor(() => {
388+
expect(component.text()).toContain('1.x')
389+
expect(component.text()).toContain('deprecated')
390+
})
391+
})
392+
})
393+
394+
describe('sort tags buttons', () => {
395+
it('does not render sort controls with only one non-latest tag', async () => {
396+
nextFetchResponse = makeVersionData(['2.0.0', '1.0.0'], {
397+
latest: '2.0.0',
398+
stable: '1.0.0',
399+
})
400+
const component = await mountPage()
401+
await vi.waitFor(() => expect(component.text()).toContain('stable'))
402+
403+
expect(component.findAll('button[aria-pressed]')).toHaveLength(0)
404+
})
405+
406+
it('renders sort controls when there are two or more non-latest tags', async () => {
407+
nextFetchResponse = makeVersionData(['3.0.0', '2.0.0', '1.0.0'], {
408+
latest: '3.0.0',
409+
next: '2.0.0',
410+
legacy: '1.0.0',
411+
})
412+
const component = await mountPage()
413+
await vi.waitFor(() => expect(component.text()).toContain('next'))
414+
415+
expect(component.findAll('button[aria-pressed]')).toHaveLength(2)
416+
})
417+
418+
it('"Sort by tag" is active (aria-pressed) by default', async () => {
419+
nextFetchResponse = makeVersionData(['3.0.0', '2.0.0', '1.0.0'], {
420+
latest: '3.0.0',
421+
next: '2.0.0',
422+
legacy: '1.0.0',
423+
})
424+
const component = await mountPage()
425+
await vi.waitFor(() => expect(component.text()).toContain('next'))
426+
427+
const [sortByTagBtn, sortByDateBtn] = component.findAll('button[aria-pressed]')
428+
expect(sortByTagBtn.attributes('aria-pressed')).toBe('true')
429+
expect(sortByDateBtn.attributes('aria-pressed')).toBe('false')
430+
})
431+
432+
it('clicking "Sort by date" activates date sort mode', async () => {
433+
nextFetchResponse = makeVersionData(['3.0.0', '2.0.0', '1.0.0'], {
434+
latest: '3.0.0',
435+
next: '2.0.0',
436+
legacy: '1.0.0',
437+
})
438+
const component = await mountPage()
439+
await vi.waitFor(() => expect(component.text()).toContain('next'))
440+
441+
const [sortByTagBtn, sortByDateBtn] = component.findAll('button[aria-pressed]')
442+
await sortByDateBtn.trigger('click')
443+
444+
await vi.waitFor(() => {
445+
expect(sortByDateBtn.attributes('aria-pressed')).toBe('true')
446+
expect(sortByTagBtn.attributes('aria-pressed')).toBe('false')
447+
})
448+
})
449+
450+
it('clicking "Sort by date" twice toggles the sort direction', async () => {
451+
nextFetchResponse = makeVersionData(['3.0.0', '2.0.0', '1.0.0'], {
452+
latest: '3.0.0',
453+
next: '2.0.0',
454+
legacy: '1.0.0',
455+
})
456+
const component = await mountPage()
457+
await vi.waitFor(() => expect(component.text()).toContain('next'))
458+
459+
const [, sortByDateBtn] = component.findAll('button[aria-pressed]')
460+
461+
// First click: date sort, defaults to newest-first
462+
await sortByDateBtn.trigger('click')
463+
await vi.waitFor(() =>
464+
expect(sortByDateBtn.attributes('aria-label')).toContain('newest first'),
465+
)
466+
467+
// Second click on the same active button: flips to oldest-first
468+
await sortByDateBtn.trigger('click')
469+
await vi.waitFor(() =>
470+
expect(sortByDateBtn.attributes('aria-label')).toContain('oldest first'),
471+
)
472+
})
185473
})
186474
})

0 commit comments

Comments
 (0)