forked from
npmx.dev/npmx.dev
[READ-ONLY]
a fast, modern browser for the npm registry
1import { describe, expect, it } from 'vitest'
2import type { PackageFileTree } from '../../../../shared/types'
3import {
4 createImportResolver,
5 flattenFileTree,
6 resolveRelativeImport,
7} from '../../../../server/utils/import-resolver'
8
9describe('flattenFileTree', () => {
10 it('flattens nested trees into a file set', () => {
11 const tree: PackageFileTree[] = [
12 {
13 name: 'dist',
14 path: 'dist',
15 type: 'directory',
16 children: [
17 { name: 'index.js', path: 'dist/index.js', type: 'file', size: 10 },
18 {
19 name: 'utils',
20 path: 'dist/utils',
21 type: 'directory',
22 children: [{ name: 'format.js', path: 'dist/utils/format.js', type: 'file', size: 5 }],
23 },
24 ],
25 },
26 ]
27
28 const files = flattenFileTree(tree)
29
30 expect(files.has('dist/index.js')).toBe(true)
31 expect(files.has('dist/utils/format.js')).toBe(true)
32 expect(files.has('dist/utils')).toBe(false)
33 })
34
35 it('returns an empty set for an empty tree', () => {
36 const files = flattenFileTree([])
37 expect(files.size).toBe(0)
38 })
39
40 it('includes root-level files', () => {
41 const tree: PackageFileTree[] = [
42 { name: 'index.js', path: 'index.js', type: 'file', size: 5 },
43 { name: 'cli.js', path: 'cli.js', type: 'file', size: 3 },
44 ]
45
46 const files = flattenFileTree(tree)
47
48 expect(files.has('index.js')).toBe(true)
49 expect(files.has('cli.js')).toBe(true)
50 })
51})
52
53describe('resolveRelativeImport', () => {
54 it('resolves a relative import with extension priority for JS files', () => {
55 const files = new Set<string>(['dist/utils.js', 'dist/utils.ts'])
56 const resolved = resolveRelativeImport('./utils', 'dist/index.js', files)
57
58 expect(resolved?.path).toBe('dist/utils.js')
59 })
60
61 it('resolves a relative import with extension priority for TS files', () => {
62 const files = new Set<string>(['src/utils.ts', 'src/utils.js'])
63 const resolved = resolveRelativeImport('./utils', 'src/index.ts', files)
64
65 expect(resolved?.path).toBe('src/utils.ts')
66 })
67
68 it('resolves a relative import to .d.ts when source is a declaration file', () => {
69 const files = new Set<string>(['dist/types.d.ts', 'dist/types.ts'])
70 const resolved = resolveRelativeImport('./types', 'dist/index.d.ts', files)
71
72 expect(resolved?.path).toBe('dist/types.d.ts')
73 })
74
75 it('resolves an exact extension match', () => {
76 const files = new Set<string>(['src/utils.ts', 'src/utils.js'])
77 const resolved = resolveRelativeImport('./utils.ts', 'src/index.ts', files)
78
79 expect(resolved?.path).toBe('src/utils.ts')
80 })
81
82 it('resolves a quoted specifier', () => {
83 const files = new Set<string>(['dist/utils.js'])
84 const resolved = resolveRelativeImport("'./utils'", 'dist/index.js', files)
85
86 expect(resolved?.path).toBe('dist/utils.js')
87 })
88
89 it('resolves a relative import with extension priority for MTS files', () => {
90 const files = new Set<string>(['src/utils.mts', 'src/utils.mjs', 'src/utils.ts'])
91 const resolved = resolveRelativeImport('./utils', 'src/index.mts', files)
92
93 expect(resolved?.path).toBe('src/utils.mts')
94 })
95
96 it('resolves a relative import with extension priority for MJS files', () => {
97 const files = new Set<string>(['dist/utils.mjs', 'dist/utils.js'])
98 const resolved = resolveRelativeImport('./utils', 'dist/index.mjs', files)
99
100 expect(resolved?.path).toBe('dist/utils.mjs')
101 })
102
103 it('resolves a relative import with extension priority for CTS files', () => {
104 const files = new Set<string>(['src/utils.cts', 'src/utils.cjs', 'src/utils.ts'])
105 const resolved = resolveRelativeImport('./utils', 'src/index.cts', files)
106
107 expect(resolved?.path).toBe('src/utils.cts')
108 })
109
110 it('resolves a relative import with extension priority for CJS files', () => {
111 const files = new Set<string>(['dist/utils.cjs', 'dist/utils.js'])
112 const resolved = resolveRelativeImport('./utils', 'dist/index.cjs', files)
113
114 expect(resolved?.path).toBe('dist/utils.cjs')
115 })
116
117 it('resolves directory imports to index files', () => {
118 const files = new Set<string>(['dist/components/index.js'])
119 const resolved = resolveRelativeImport('./components', 'dist/index.js', files)
120
121 expect(resolved?.path).toBe('dist/components/index.js')
122 })
123
124 it('resolves parent directory paths', () => {
125 const files = new Set<string>(['dist/shared/helpers.js'])
126 const resolved = resolveRelativeImport('../shared/helpers', 'dist/pages/home.js', files)
127
128 expect(resolved?.path).toBe('dist/shared/helpers.js')
129 })
130
131 it('returns null when the path would go above the package root', () => {
132 const files = new Set<string>(['dist/index.js'])
133 const resolved = resolveRelativeImport('../../outside', 'dist/index.js', files)
134
135 expect(resolved).toBeNull()
136 })
137
138 it('returns null for non-relative imports', () => {
139 const files = new Set<string>(['dist/utils.js'])
140 const resolved = resolveRelativeImport('react', 'dist/index.js', files)
141
142 expect(resolved).toBeNull()
143 })
144
145 it('returns null when no matching file is found', () => {
146 const files = new Set<string>(['dist/utils.js'])
147 const resolved = resolveRelativeImport('./missing', 'dist/index.js', files)
148
149 expect(resolved).toBeNull()
150 })
151})
152
153describe('createImportResolver', () => {
154 it('creates a resolver that returns code browser URLs', () => {
155 const files = new Set<string>(['dist/utils.js'])
156 const resolver = createImportResolver(files, 'dist/index.js', 'pkg-name', '1.2.3')
157
158 const url = resolver('./utils')
159
160 expect(url).toBe('/package-code/pkg-name/v/1.2.3/dist/utils.js')
161 })
162
163 it('returns null when the import cannot be resolved', () => {
164 const files = new Set<string>(['dist/utils.js'])
165 const resolver = createImportResolver(files, 'dist/index.js', 'pkg-name', '1.2.3')
166
167 const url = resolver('./missing')
168
169 expect(url).toBeNull()
170 })
171
172 it('handles scoped package names in URLs', () => {
173 const files = new Set<string>(['dist/utils.js'])
174 const resolver = createImportResolver(files, 'dist/index.js', '@scope/pkg', '1.2.3')
175
176 const url = resolver('./utils')
177
178 expect(url).toBe('/package-code/@scope/pkg/v/1.2.3/dist/utils.js')
179 })
180})