Skip to content

Commit a45a8a3

Browse files
committed
test: add package-name tests
Adds a few missing tests while i was getting my head around the codebase.
1 parent b607f69 commit a45a8a3

2 files changed

Lines changed: 232 additions & 0 deletions

File tree

test/unit/install-command.spec.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import {
33
getInstallCommand,
44
getInstallCommandParts,
55
getPackageSpecifier,
6+
getExecuteCommand,
7+
getExecuteCommandParts,
68
} from '../../app/utils/install-command'
79
import type { JsrPackageInfo } from '../../shared/types/jsr'
810

@@ -284,4 +286,44 @@ describe('install command generation', () => {
284286
expect(parts).toEqual([])
285287
})
286288
})
289+
290+
describe('getExecuteCommand', () => {
291+
it('returns correct execute command for npm', () => {
292+
const command = getExecuteCommand({
293+
packageName: 'esbuild',
294+
packageManager: 'npm',
295+
jsrInfo: jsrNotAvailable,
296+
})
297+
expect(command).toBe('npx esbuild')
298+
})
299+
300+
it('returns package manager specific execute command', () => {
301+
const command = getExecuteCommand({
302+
packageName: 'esbuild',
303+
packageManager: 'pnpm',
304+
jsrInfo: jsrNotAvailable,
305+
})
306+
expect(command).toBe('pnpm dlx esbuild')
307+
})
308+
})
309+
310+
describe('getExecuteCommandParts', () => {
311+
it('returns correct parts for npm', () => {
312+
const parts = getExecuteCommandParts({
313+
packageName: 'esbuild',
314+
packageManager: 'npm',
315+
jsrInfo: jsrNotAvailable,
316+
})
317+
expect(parts).toEqual(['npx', 'esbuild'])
318+
})
319+
320+
it('returns empty for unknown package manager', () => {
321+
const parts = getExecuteCommandParts({
322+
packageName: 'esbuild',
323+
packageManager: 'unknown' as never,
324+
jsrInfo: jsrNotAvailable,
325+
})
326+
expect(parts).toEqual([])
327+
})
328+
})
287329
})

test/unit/package-name.spec.ts

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
2+
import {
3+
checkPackageExists,
4+
findSimilarPackages,
5+
normalizePackageName,
6+
} from '../../app/utils/package-name'
7+
8+
describe('normalizePackageName', () => {
9+
it.each([
10+
['esbuild', 'esbuild'],
11+
['@scope/package', 'package'],
12+
['ESBuild', 'esbuild'],
13+
['my.package', 'mypackage'],
14+
['my-package', 'mypackage'],
15+
['my_package', 'mypackage'],
16+
['jslint', 'lint'],
17+
['nodefoo', 'foo'],
18+
['foojs', 'foo'],
19+
['foonode', 'foo'],
20+
['foo-js', 'foo'],
21+
['foo-node', 'foo'],
22+
['@foo/bar', 'bar'],
23+
] as const)('"%s" -> "%s"', (input, expected) => {
24+
expect(normalizePackageName(input)).toBe(expected)
25+
})
26+
})
27+
28+
describe('checkPackageExists', () => {
29+
let fetchMock: ReturnType<typeof vi.fn>
30+
31+
beforeEach(() => {
32+
fetchMock = vi.fn()
33+
vi.stubGlobal('$fetch', fetchMock)
34+
})
35+
36+
afterEach(() => {
37+
vi.unstubAllGlobals()
38+
})
39+
40+
it('returns true when package exists', async () => {
41+
fetchMock.mockResolvedValue(undefined)
42+
43+
const result = await checkPackageExists('vue')
44+
45+
expect(result).toBe(true)
46+
expect(fetchMock).toHaveBeenCalledWith('https://registry.npmjs.org/vue', { method: 'HEAD' })
47+
})
48+
49+
it('returns false when package does not exist', async () => {
50+
fetchMock.mockRejectedValue(new Error('404'))
51+
52+
const result = await checkPackageExists('nonexistent-package')
53+
54+
expect(result).toBe(false)
55+
})
56+
57+
it('encodes regular package names', async () => {
58+
fetchMock.mockResolvedValue(undefined)
59+
60+
await checkPackageExists('some-package')
61+
62+
expect(fetchMock).toHaveBeenCalledWith('https://registry.npmjs.org/some-package', {
63+
method: 'HEAD',
64+
})
65+
})
66+
67+
it('encodes scoped package names correctly', async () => {
68+
fetchMock.mockResolvedValue(undefined)
69+
70+
await checkPackageExists('@vue/core')
71+
72+
expect(fetchMock).toHaveBeenCalledWith('https://registry.npmjs.org/@vue%2Fcore', {
73+
method: 'HEAD',
74+
})
75+
})
76+
})
77+
78+
describe('findSimilarPackages', () => {
79+
let fetchMock: ReturnType<typeof vi.fn>
80+
81+
beforeEach(() => {
82+
fetchMock = vi.fn()
83+
vi.stubGlobal('$fetch', fetchMock)
84+
})
85+
86+
afterEach(() => {
87+
vi.unstubAllGlobals()
88+
})
89+
90+
it('returns empty array on error', async () => {
91+
fetchMock.mockRejectedValue(new Error('Network error'))
92+
93+
const result = await findSimilarPackages('test-package')
94+
95+
expect(result).toEqual([])
96+
})
97+
98+
it('marks exact name matches as exact-match', async () => {
99+
fetchMock.mockResolvedValue({
100+
objects: [{ package: { name: 'svelte', description: 'speed.' } }],
101+
})
102+
103+
const result = await findSimilarPackages('svelte')
104+
105+
expect(result).toHaveLength(1)
106+
expect(result[0]).toMatchObject({
107+
name: 'svelte',
108+
similarity: 'exact-match',
109+
})
110+
})
111+
112+
it('returns partial matches up to the exact match', async () => {
113+
fetchMock.mockResolvedValue({
114+
objects: [
115+
{ package: { name: 'svel-te', description: 'spe-ed' } },
116+
{ package: { name: 'svelte', description: 'speed.' } },
117+
],
118+
})
119+
120+
const result = await findSimilarPackages('svelte')
121+
122+
expect(result).toHaveLength(2)
123+
expect(result[0]).toEqual({
124+
name: 'svelte',
125+
description: 'speed.',
126+
similarity: 'exact-match',
127+
})
128+
expect(result[1]).toEqual({
129+
name: 'svel-te',
130+
description: 'spe-ed',
131+
similarity: 'very-similar',
132+
})
133+
})
134+
135+
it('marks normalized matches as very-similar', async () => {
136+
fetchMock.mockResolvedValue({
137+
objects: [{ package: { name: 'my-pkg', description: 'A package' } }],
138+
})
139+
140+
const result = await findSimilarPackages('my_pkg')
141+
142+
expect(result).toHaveLength(1)
143+
expect(result[0]).toMatchObject({
144+
name: 'my-pkg',
145+
similarity: 'very-similar',
146+
})
147+
})
148+
149+
it('excludes non-matching packages', async () => {
150+
fetchMock.mockResolvedValue({
151+
objects: [{ package: { name: 'absolute-nonsense' } }],
152+
})
153+
154+
const result = await findSimilarPackages('esbuild')
155+
156+
expect(result).toEqual([])
157+
})
158+
159+
it('includes packages within levenshtein distance threshold', async () => {
160+
fetchMock.mockResolvedValue({
161+
objects: [{ package: { name: 'sebuild', description: 'a confused esbuild' } }],
162+
})
163+
164+
const result = await findSimilarPackages('esbuild')
165+
166+
expect(result).toHaveLength(1)
167+
expect(result[0]).toMatchObject({
168+
name: 'sebuild',
169+
similarity: 'similar',
170+
})
171+
})
172+
173+
it('sorts results by similarity: exact > very-similar > similar', async () => {
174+
fetchMock.mockResolvedValue({
175+
objects: [
176+
{ package: { name: 'sebuild' } }, // similar
177+
{ package: { name: 'esbuild' } }, // exact-match
178+
{ package: { name: 'es-build' } }, // very-similar
179+
],
180+
})
181+
182+
const result = await findSimilarPackages('esbuild')
183+
184+
expect(result).toEqual([
185+
{ name: 'esbuild', similarity: 'exact-match' },
186+
{ name: 'es-build', similarity: 'very-similar' },
187+
{ name: 'sebuild', similarity: 'similar' },
188+
])
189+
})
190+
})

0 commit comments

Comments
 (0)