From c00e97c05880211db8b3582381f1ee45deaa2c00 Mon Sep 17 00:00:00 2001 From: Tin Date: Wed, 25 Mar 2026 20:35:18 +0100 Subject: [PATCH 1/7] fix(ui): View all x versions button redirect to Version History --- app/components/Package/Versions.vue | 7 +------ app/components/VersionSelector.vue | 6 +++--- app/utils/router.ts | 6 ++++++ 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/app/components/Package/Versions.vue b/app/components/Package/Versions.vue index fd5544db22..f915eb7594 100644 --- a/app/components/Package/Versions.vue +++ b/app/components/Package/Versions.vue @@ -96,12 +96,7 @@ function versionRoute(version: string): RouteLocationRaw { } // Route to the full versions history page -const versionsPageRoute = computed((): RouteLocationRaw => { - const [org, name = ''] = props.packageName.startsWith('@') - ? props.packageName.split('/') - : ['', props.packageName] - return { name: 'package-versions', params: { org, name } } -}) +const versionsPageRoute = computed(() => packageVersionsRoute(props.packageName)) // Version to tags lookup (supports multiple tags per version) const versionToTags = computed(() => buildVersionToTagsMap(props.distTags)) diff --git a/app/components/VersionSelector.vue b/app/components/VersionSelector.vue index eaac5f4fce..774aec5cb1 100644 --- a/app/components/VersionSelector.vue +++ b/app/components/VersionSelector.vue @@ -401,7 +401,7 @@ function handleListboxKeydown(event: KeyboardEvent) { const item = items[focusedIndex.value] if (item?.type === 'group') { const group = versionGroups.value.find(g => g.id === item.groupId) - if (group && !group.isExpanded && group.versions.length > 1) { + if (group && !group.isExpanded && (group.versions.length > 1 || !hasLoadedAll.value)) { toggleGroup(item.groupId) } } @@ -539,7 +539,7 @@ watch( > - + { { timeout: 2000 }, ) }) + + it('toggles older version groups for a single-version tagged release', async () => { + mockFetchAllPackageVersions.mockResolvedValue([ + { version: '1.0.0', time: '2024-01-15T12:00:00.000Z', hasProvenance: false }, + { version: '0.9.0', time: '2024-01-10T12:00:00.000Z', hasProvenance: false }, + ]) + + const component = await mountSuspended(VersionSelector, { + props: { + packageName: 'test-package', + currentVersion: '1.0.0', + versions: { '1.0.0': {}, '0.9.0': {} }, + distTags: { latest: '1.0.0' }, + urlPattern: '/package-docs/test-package/v/{version}', + }, + }) + + const button = component.find('button[aria-haspopup="listbox"]') + await button.trigger('click') + + const expandButton = component.find('[role="listbox"] button[aria-expanded="false"]') + await expandButton.trigger('click') + + await vi.waitFor(() => { + expect(mockFetchAllPackageVersions).toHaveBeenCalledWith('test-package') + }) + + await vi.waitFor(() => { + expect(component.find('[role="listbox"]').text()).toContain('0.9') + const expandedButton = component.find('[role="listbox"] button[aria-expanded="true"]') + expect(expandedButton.exists()).toBe(true) + }) + + const expandedButton = component.find('[role="listbox"] button[aria-expanded="true"]') + await expandedButton.trigger('click') + + await vi.waitFor(() => { + expect(component.find('[role="listbox"]').text()).not.toContain('0.9') + const collapsedButton = component.find('[role="listbox"] button[aria-expanded="false"]') + expect(collapsedButton.exists()).toBe(true) + }) + }) }) describe('0.x version grouping', () => { From cb828dbcca51768f95e8232c9521bd0cfcd809bd Mon Sep 17 00:00:00 2001 From: Tin Date: Wed, 25 Mar 2026 20:59:18 +0100 Subject: [PATCH 3/7] Fix version dropdown group expansion behavior - cover version page links for scoped and unscoped packages - add regression tests for collapsing groups, prop resets, and loading guards --- test/nuxt/components/Package/Versions.spec.ts | 38 ++++++++ test/nuxt/components/VersionSelector.spec.ts | 94 +++++++++++++++++++ 2 files changed, 132 insertions(+) diff --git a/test/nuxt/components/Package/Versions.spec.ts b/test/nuxt/components/Package/Versions.spec.ts index 93db7103ab..75b7779769 100644 --- a/test/nuxt/components/Package/Versions.spec.ts +++ b/test/nuxt/components/Package/Versions.spec.ts @@ -1,7 +1,9 @@ import { describe, expect, it, vi, beforeEach } from 'vitest' import { mountSuspended } from '@nuxt/test-utils/runtime' import type { DOMWrapper } from '@vue/test-utils' +import type { Router } from 'vue-router' import PackageVersions from '~/components/Package/Versions.vue' +import { packageVersionsRoute } from '~/utils/router' // Mock the fetchAllPackageVersions function const mockFetchAllPackageVersions = vi.fn() @@ -109,6 +111,42 @@ describe('PackageVersions', () => { expect(versionLinks[0]?.text()).toBe('1.0.0') }) + it('view-all-versions link uses packageVersionsRoute for unscoped packages', async () => { + const component = await mountSuspended(PackageVersions, { + props: { + packageName: 'test-package', + versions: { + '1.0.0': createVersion('1.0.0'), + }, + distTags: { latest: '1.0.0' }, + time: { '1.0.0': '2024-01-15T12:00:00.000Z' }, + }, + }) + + const router = component.vm.$router as Router + const expectedHref = router.resolve(packageVersionsRoute('test-package')).href + const viewAll = component.find('[data-testid="view-all-versions-link"]') + expect(viewAll.attributes('href')).toBe(expectedHref) + }) + + it('view-all-versions link uses packageVersionsRoute for scoped packages', async () => { + const component = await mountSuspended(PackageVersions, { + props: { + packageName: '@scope/test-package', + versions: { + '1.0.0': createVersion('1.0.0'), + }, + distTags: { latest: '1.0.0' }, + time: { '1.0.0': '2024-01-15T12:00:00.000Z' }, + }, + }) + + const router = component.vm.$router as Router + const expectedHref = router.resolve(packageVersionsRoute('@scope/test-package')).href + const viewAll = component.find('[data-testid="view-all-versions-link"]') + expect(viewAll.attributes('href')).toBe(expectedHref) + }) + it('highlights the current version row when selectedVersion prop matches', async () => { const component = await mountSuspended(PackageVersions, { props: { diff --git a/test/nuxt/components/VersionSelector.spec.ts b/test/nuxt/components/VersionSelector.spec.ts index e27d3c69ee..0d97c7e94e 100644 --- a/test/nuxt/components/VersionSelector.spec.ts +++ b/test/nuxt/components/VersionSelector.spec.ts @@ -1,5 +1,6 @@ import { describe, expect, it, vi, beforeEach } from 'vitest' import { mountSuspended } from '@nuxt/test-utils/runtime' +import type { PackageVersionInfo } from '#shared/types/npm-registry' import VersionSelector from '~/components/VersionSelector.vue' // Mock the fetchAllPackageVersions function @@ -466,6 +467,99 @@ describe('VersionSelector', () => { expect(collapsedButton.exists()).toBe(true) }) }) + + it('collapses additional version groups with ArrowLeft when showAllGroups is open', async () => { + mockFetchAllPackageVersions.mockResolvedValue([ + { version: '1.0.0', time: '2024-01-15T12:00:00.000Z', hasProvenance: false }, + { version: '0.9.0', time: '2024-01-10T12:00:00.000Z', hasProvenance: false }, + ]) + + const component = await mountSuspended(VersionSelector, { + props: { + packageName: 'test-package', + currentVersion: '1.0.0', + versions: { '1.0.0': {}, '0.9.0': {} }, + distTags: { latest: '1.0.0' }, + urlPattern: '/package-docs/test-package/v/{version}', + }, + }) + + const trigger = component.find('button[aria-haspopup="listbox"]') + await trigger.trigger('click') + + await component.find('[role="listbox"] button[aria-expanded="false"]').trigger('click') + + await vi.waitFor(() => { + expect(component.find('[role="listbox"]').text()).toContain('0.9') + }) + + const listbox = component.find('[role="listbox"]') + await listbox.trigger('keydown', { key: 'ArrowLeft' }) + + await vi.waitFor(() => { + expect(listbox.text()).not.toContain('0.9') + }) + }) + + it('resets showAllGroups when dist-tags props change after loading', async () => { + mockFetchAllPackageVersions.mockResolvedValue([ + { version: '1.0.0', time: '2024-01-15T12:00:00.000Z', hasProvenance: false }, + { version: '0.9.0', time: '2024-01-10T12:00:00.000Z', hasProvenance: false }, + ]) + + const component = await mountSuspended(VersionSelector, { + props: { + packageName: 'test-package', + currentVersion: '1.0.0', + versions: { '1.0.0': {}, '0.9.0': {} }, + distTags: { latest: '1.0.0' }, + urlPattern: '/package-docs/test-package/v/{version}', + }, + }) + + const trigger = component.find('button[aria-haspopup="listbox"]') + await trigger.trigger('click') + await component.find('[role="listbox"] button[aria-expanded="false"]').trigger('click') + + await vi.waitFor(() => { + expect(component.find('[role="listbox"]').text()).toContain('0.9') + }) + + await component.setProps({ distTags: { latest: '1.0.0' } }) + + await vi.waitFor(() => { + expect(component.find('[role="listbox"]').text()).not.toContain('0.9') + }) + }) + + it('ignores expand clicks while a group is already loading', async () => { + let finishLoad: (value: PackageVersionInfo[]) => void + const loadPromise = new Promise(resolve => { + finishLoad = resolve + }) + mockFetchAllPackageVersions.mockReturnValue(loadPromise) + + const component = await mountSuspended(VersionSelector, { + props: { + packageName: 'test-package', + currentVersion: '1.0.0', + versions: { '1.0.0': {} }, + distTags: { latest: '1.0.0' }, + urlPattern: '/package-docs/test-package/v/{version}', + }, + }) + + const trigger = component.find('button[aria-haspopup="listbox"]') + await trigger.trigger('click') + + const expandButton = component.find('[role="listbox"] button[aria-expanded]') + await expandButton.trigger('click') + await expandButton.trigger('click') + + expect(mockFetchAllPackageVersions).toHaveBeenCalledTimes(1) + + finishLoad!([{ version: '1.0.0', time: '2024-01-15T12:00:00.000Z', hasProvenance: false }]) + }) }) describe('0.x version grouping', () => { From 2f18d06e6e8346bb45e0fca9b9c401fe4e52932b Mon Sep 17 00:00:00 2001 From: Tin Date: Wed, 25 Mar 2026 21:07:03 +0100 Subject: [PATCH 4/7] fix(ui): update spec to stop casting Nuxt's typed to vue-router's generic Router. --- test/nuxt/components/Package/Versions.spec.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/test/nuxt/components/Package/Versions.spec.ts b/test/nuxt/components/Package/Versions.spec.ts index 75b7779769..bb5963bbf4 100644 --- a/test/nuxt/components/Package/Versions.spec.ts +++ b/test/nuxt/components/Package/Versions.spec.ts @@ -1,7 +1,6 @@ import { describe, expect, it, vi, beforeEach } from 'vitest' import { mountSuspended } from '@nuxt/test-utils/runtime' import type { DOMWrapper } from '@vue/test-utils' -import type { Router } from 'vue-router' import PackageVersions from '~/components/Package/Versions.vue' import { packageVersionsRoute } from '~/utils/router' @@ -41,6 +40,12 @@ function isVersionLink(a: DOMWrapper): boolean { ) } +function getRouter( + component: Awaited>, +): Pick { + return component.vm.$router +} + describe('PackageVersions', () => { beforeEach(() => { mockFetchAllPackageVersions.mockReset() @@ -123,7 +128,7 @@ describe('PackageVersions', () => { }, }) - const router = component.vm.$router as Router + const router = getRouter(component) const expectedHref = router.resolve(packageVersionsRoute('test-package')).href const viewAll = component.find('[data-testid="view-all-versions-link"]') expect(viewAll.attributes('href')).toBe(expectedHref) @@ -141,7 +146,7 @@ describe('PackageVersions', () => { }, }) - const router = component.vm.$router as Router + const router = getRouter(component) const expectedHref = router.resolve(packageVersionsRoute('@scope/test-package')).href const viewAll = component.find('[data-testid="view-all-versions-link"]') expect(viewAll.attributes('href')).toBe(expectedHref) From 9f51bed7f500df46ea517e9077da1bbb248e14b4 Mon Sep 17 00:00:00 2001 From: Tin Date: Wed, 25 Mar 2026 21:09:57 +0100 Subject: [PATCH 5/7] fix(ui): first-load expansion no longer sets showAllGroups unconditionally. --- app/components/VersionSelector.vue | 9 ++-- test/nuxt/components/Package/Versions.spec.ts | 4 +- test/nuxt/components/VersionSelector.spec.ts | 46 +++++++++++++++++++ 3 files changed, 53 insertions(+), 6 deletions(-) diff --git a/app/components/VersionSelector.vue b/app/components/VersionSelector.vue index dd55b76f23..6cf5c9c071 100644 --- a/app/components/VersionSelector.vue +++ b/app/components/VersionSelector.vue @@ -344,12 +344,15 @@ async function toggleGroup(groupId: string) { try { const allVersions = await loadAllVersions() processLoadedVersions(allVersions) - showAllGroups.value = hasAdditionalGroups.value // Find the group again after processing (it may have moved) const updatedGroup = versionGroups.value.find(g => g.id === groupId) - if (updatedGroup && hasNestedVersions(updatedGroup)) { - updatedGroup.isExpanded = true + if (updatedGroup) { + if (hasNestedVersions(updatedGroup)) { + updatedGroup.isExpanded = true + } else if (controlsAdditionalGroups(updatedGroup)) { + showAllGroups.value = true + } } } catch (error) { // eslint-disable-next-line no-console diff --git a/test/nuxt/components/Package/Versions.spec.ts b/test/nuxt/components/Package/Versions.spec.ts index bb5963bbf4..a6ff5af6ca 100644 --- a/test/nuxt/components/Package/Versions.spec.ts +++ b/test/nuxt/components/Package/Versions.spec.ts @@ -40,9 +40,7 @@ function isVersionLink(a: DOMWrapper): boolean { ) } -function getRouter( - component: Awaited>, -): Pick { +function getRouter(component: Awaited>): Pick { return component.vm.$router } diff --git a/test/nuxt/components/VersionSelector.spec.ts b/test/nuxt/components/VersionSelector.spec.ts index 0d97c7e94e..f9a8c75dd0 100644 --- a/test/nuxt/components/VersionSelector.spec.ts +++ b/test/nuxt/components/VersionSelector.spec.ts @@ -468,6 +468,52 @@ describe('VersionSelector', () => { }) }) + it('does not reveal unrelated older groups when expanding a tagged row with nested versions', async () => { + mockFetchAllPackageVersions.mockResolvedValue([ + { version: '1.2.0', time: '2024-01-15T12:00:00.000Z', hasProvenance: false }, + { version: '1.1.0', time: '2024-01-12T12:00:00.000Z', hasProvenance: false }, + { version: '1.0.0', time: '2024-01-10T12:00:00.000Z', hasProvenance: false }, + { version: '0.9.0', time: '2024-01-08T12:00:00.000Z', hasProvenance: false }, + ]) + + const component = await mountSuspended(VersionSelector, { + props: { + packageName: 'test-package', + currentVersion: '1.2.0', + versions: { '1.2.0': {}, '1.1.0': {}, '1.0.0': {}, '0.9.0': {} }, + distTags: { latest: '1.2.0' }, + urlPattern: '/package-docs/test-package/v/{version}', + }, + }) + + const trigger = component.find('button[aria-haspopup="listbox"]') + await trigger.trigger('click') + + const expandButton = component.find('[role="listbox"] button[aria-expanded="false"]') + await expandButton.trigger('click') + + await vi.waitFor(() => { + expect(mockFetchAllPackageVersions).toHaveBeenCalledWith('test-package') + }) + + await vi.waitFor(() => { + const listboxText = component.find('[role="listbox"]').text() + expect(listboxText).toContain('1.1.0') + expect(listboxText).toContain('1.0.0') + expect(listboxText).not.toContain('0.9') + }) + + const expandedButton = component.find('[role="listbox"] button[aria-expanded="true"]') + await expandedButton.trigger('click') + + await vi.waitFor(() => { + const listboxText = component.find('[role="listbox"]').text() + expect(listboxText).not.toContain('1.1.0') + expect(listboxText).not.toContain('1.0.0') + expect(listboxText).not.toContain('0.9') + }) + }) + it('collapses additional version groups with ArrowLeft when showAllGroups is open', async () => { mockFetchAllPackageVersions.mockResolvedValue([ { version: '1.0.0', time: '2024-01-15T12:00:00.000Z', hasProvenance: false }, From ce28d31d79d11a48077b512e235b3fdcb751b2a9 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Wed, 25 Mar 2026 20:11:16 +0000 Subject: [PATCH 6/7] [autofix.ci] apply automated fixes --- test/nuxt/components/Package/Versions.spec.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/nuxt/components/Package/Versions.spec.ts b/test/nuxt/components/Package/Versions.spec.ts index a6ff5af6ca..bb5963bbf4 100644 --- a/test/nuxt/components/Package/Versions.spec.ts +++ b/test/nuxt/components/Package/Versions.spec.ts @@ -40,7 +40,9 @@ function isVersionLink(a: DOMWrapper): boolean { ) } -function getRouter(component: Awaited>): Pick { +function getRouter( + component: Awaited>, +): Pick { return component.vm.$router } From c855fd8895f258ef3d1717522799427218c16b70 Mon Sep 17 00:00:00 2001 From: Tin Date: Wed, 25 Mar 2026 21:17:09 +0100 Subject: [PATCH 7/7] fix(ui): only reveal additional groups from controlling rows. --- test/nuxt/components/VersionSelector.spec.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/nuxt/components/VersionSelector.spec.ts b/test/nuxt/components/VersionSelector.spec.ts index f9a8c75dd0..cb1afeb5f8 100644 --- a/test/nuxt/components/VersionSelector.spec.ts +++ b/test/nuxt/components/VersionSelector.spec.ts @@ -641,12 +641,10 @@ describe('VersionSelector', () => { // Wait for versions to load await vi.waitFor( () => { - // 0.9.x versions should NOT be under the 0.10.x group - // They should be in a separate group const text = component.text() - // The component should have separate groups for 0.10 and 0.9 expect(text).toContain('0.10') - expect(text).toContain('0.9') + expect(text).toContain('0.10.0') + expect(text).not.toContain('0.9') }, { timeout: 2000 }, )