diff --git a/server/api/registry/analysis/[...pkg].get.ts b/server/api/registry/analysis/[...pkg].get.ts index e9a2e906a0..940bc25925 100644 --- a/server/api/registry/analysis/[...pkg].get.ts +++ b/server/api/registry/analysis/[...pkg].get.ts @@ -41,7 +41,7 @@ export default defineCachedEventHandler( try { const { packageName, version } = v.parse(PackageRouteParamsSchema, { - packageName: rawPackageName, + packageName: decodeURIComponent(rawPackageName), version: rawVersion, }) diff --git a/server/api/registry/install-size/[...pkg].get.ts b/server/api/registry/install-size/[...pkg].get.ts index 007ae4d680..1035e4c664 100644 --- a/server/api/registry/install-size/[...pkg].get.ts +++ b/server/api/registry/install-size/[...pkg].get.ts @@ -18,7 +18,7 @@ export default defineCachedEventHandler( try { const { packageName, version: requestedVersion } = v.parse(PackageRouteParamsSchema, { - packageName: rawPackageName, + packageName: decodeURIComponent(rawPackageName), version: rawVersion, }) diff --git a/test/e2e/interactions.spec.ts b/test/e2e/interactions.spec.ts index adcefba804..4a1a582fb9 100644 --- a/test/e2e/interactions.spec.ts +++ b/test/e2e/interactions.spec.ts @@ -47,6 +47,48 @@ test.describe('Compare Page', () => { const noDepColumn = grid.locator('.comparison-cell-nodep') await expect(noDepColumn).toBeVisible() }) + + test('loads install-size data for a scoped package', async ({ page, goto }) => { + // Intercept the internal API call the browser makes for install-size. + // The browser will request /api/registry/install-size/@nuxt%2Fkit (encoded slash). + // Before the fix this would fail to parse and return an error; after it returns 200. + const installSizeResponse = page.waitForResponse( + res => + res.url().includes('/api/registry/install-size/') && + res.url().includes('nuxt') && + res.request().method() === 'GET', + { timeout: 20_000 }, + ) + + await goto('/compare?packages=@nuxt/kit,vue', { waitUntil: 'hydration' }) + + const response = await installSizeResponse + expect(response.status()).toBe(200) + + const body = await response.json() + // The API should return a valid install size object, not an error + expect(body).toHaveProperty('selfSize') + expect(body).toHaveProperty('totalSize') + }) + + test('loads analysis data for a scoped package', async ({ page, goto }) => { + const analysisResponse = page.waitForResponse( + res => + res.url().includes('/api/registry/analysis/') && + res.url().includes('nuxt') && + res.request().method() === 'GET', + { timeout: 20_000 }, + ) + + await goto('/compare?packages=@nuxt/kit,vue', { waitUntil: 'hydration' }) + + const response = await analysisResponse + expect(response.status()).toBe(200) + + const body = await response.json() + expect(body).toHaveProperty('package', '@nuxt/kit') + expect(body).toHaveProperty('moduleFormat', 'esm') + }) }) test.describe('Search Pages', () => { diff --git a/test/unit/server/utils/parse-package-params.spec.ts b/test/unit/server/utils/parse-package-params.spec.ts new file mode 100644 index 0000000000..f020f70680 --- /dev/null +++ b/test/unit/server/utils/parse-package-params.spec.ts @@ -0,0 +1,53 @@ +import { describe, expect, it } from 'vitest' +import { parsePackageParams } from '#server/utils/parse-package-params' + +describe('parsePackageParams', () => { + describe('unscoped packages', () => { + it('parses package name without version', () => { + const segments = ['vue'] + const result = parsePackageParams(segments) + expect(result).toEqual({ + rawPackageName: 'vue', + rawVersion: undefined, + }) + }) + + it('parses package name with version', () => { + const segments = ['vue', 'v', '3.4.0'] + const result = parsePackageParams(segments) + expect(result).toEqual({ + rawPackageName: 'vue', + rawVersion: '3.4.0', + }) + }) + + it('parses package name with prerelease version', () => { + const segments = ['nuxt', 'v', '4.0.0-rc.1'] + const result = parsePackageParams(segments) + expect(result).toEqual({ + rawPackageName: 'nuxt', + rawVersion: '4.0.0-rc.1', + }) + }) + }) + + describe('scoped packages', () => { + it('parses scoped package name without version', () => { + const segments = ['@nuxt', 'kit'] + const result = parsePackageParams(segments) + expect(result).toEqual({ + rawPackageName: '@nuxt/kit', + rawVersion: undefined, + }) + }) + + it('parses scoped package name with version', () => { + const segments = ['@nuxt', 'kit', 'v', '1.0.0'] + const result = parsePackageParams(segments) + expect(result).toEqual({ + rawPackageName: '@nuxt/kit', + rawVersion: '1.0.0', + }) + }) + }) +})