[READ-ONLY] a fast, modern browser for the npm registry
at main 209 lines 5.4 kB view raw
1import { describe, expect, it, vi } from 'vitest' 2import type { JsDelivrFileNode, PackageFileTree } from '../../../../shared/types' 3import { 4 convertToFileTree, 5 fetchFileTree, 6 getPackageFileTree, 7} from '../../../../server/utils/file-tree' 8 9const getChildren = (node?: PackageFileTree): PackageFileTree[] => node?.children ?? [] 10 11const mockFetchOk = <T>(body: T) => { 12 const fetchMock = vi.fn().mockResolvedValue({ 13 ok: true, 14 json: async () => body, 15 }) 16 vi.stubGlobal('fetch', fetchMock) 17 return fetchMock 18} 19 20const mockFetchError = (status: number) => { 21 const fetchMock = vi.fn().mockResolvedValue({ 22 ok: false, 23 status, 24 }) 25 vi.stubGlobal('fetch', fetchMock) 26 return fetchMock 27} 28 29const mockCreateError = () => { 30 const createErrorMock = vi.fn((opts: { statusCode: number; message: string }) => opts) 31 vi.stubGlobal('createError', createErrorMock) 32 return createErrorMock 33} 34 35describe('convertToFileTree', () => { 36 it('converts jsDelivr nodes to a sorted tree with directories first', () => { 37 const input: JsDelivrFileNode[] = [ 38 { type: 'file', name: 'zeta.txt', size: 120 }, 39 { 40 type: 'directory', 41 name: 'src', 42 files: [ 43 { type: 'file', name: 'b.ts', size: 5 }, 44 { type: 'file', name: 'a.ts', size: 3 }, 45 ], 46 }, 47 { type: 'file', name: 'alpha.txt', size: 10 }, 48 { 49 type: 'directory', 50 name: 'assets', 51 files: [{ type: 'file', name: 'logo.svg', size: 42 }], 52 }, 53 ] 54 55 const tree = convertToFileTree(input) 56 57 const names = tree.map(node => node.name) 58 expect(names).toEqual(['assets', 'src', 'alpha.txt', 'zeta.txt']) 59 60 const srcNode = tree.find(node => node.name === 'src') 61 expect(srcNode?.type).toBe('directory') 62 expect(getChildren(srcNode).map(child => child.name)).toEqual(['a.ts', 'b.ts']) 63 }) 64 65 it('builds correct paths and preserves file sizes', () => { 66 const input: JsDelivrFileNode[] = [ 67 { 68 type: 'directory', 69 name: 'src', 70 files: [ 71 { type: 'file', name: 'index.ts', size: 100 }, 72 { 73 type: 'directory', 74 name: 'utils', 75 files: [{ type: 'file', name: 'format.ts', size: 22 }], 76 }, 77 ], 78 }, 79 ] 80 81 const tree = convertToFileTree(input) 82 83 const src = tree[0] 84 expect(src?.path).toBe('src') 85 86 const indexFile = getChildren(src).find(child => child.name === 'index.ts') 87 expect(indexFile?.path).toBe('src/index.ts') 88 expect(indexFile?.size).toBe(100) 89 90 const utilsDir = getChildren(src).find(child => child.name === 'utils') 91 expect(utilsDir?.type).toBe('directory') 92 93 const formatFile = getChildren(utilsDir).find(child => child.name === 'format.ts') 94 expect(formatFile?.path).toBe('src/utils/format.ts') 95 expect(formatFile?.size).toBe(22) 96 }) 97 98 it('returns an empty tree for empty input', () => { 99 const tree = convertToFileTree([]) 100 const empty: PackageFileTree[] = [] 101 expect(tree).toEqual(empty) 102 }) 103 104 it('handles directories without a files property', () => { 105 const input: JsDelivrFileNode[] = [ 106 { 107 type: 'directory', 108 name: 'src', 109 }, 110 ] 111 112 const tree = convertToFileTree(input) 113 114 expect(tree[0]?.type).toBe('directory') 115 expect(tree[0]?.children).toEqual([]) 116 }) 117}) 118 119describe('fetchFileTree', () => { 120 it('returns parsed json when response is ok', async () => { 121 const body = { 122 type: 'npm', 123 name: 'pkg', 124 version: '1.0.0', 125 default: 'index.js', 126 files: [], 127 } 128 129 mockFetchOk(body) 130 131 try { 132 const result = await fetchFileTree('pkg', '1.0.0') 133 expect(result).toEqual(body) 134 } finally { 135 vi.unstubAllGlobals() 136 } 137 }) 138 139 it('throws a 404 error when package is not found', async () => { 140 mockFetchError(404) 141 mockCreateError() 142 143 try { 144 await expect(fetchFileTree('pkg', '1.0.0')).rejects.toMatchObject({ statusCode: 404 }) 145 } finally { 146 vi.unstubAllGlobals() 147 } 148 }) 149 150 it('throws a 502 error for non-404 failures', async () => { 151 mockFetchError(500) 152 mockCreateError() 153 154 try { 155 await expect(fetchFileTree('pkg', '1.0.0')).rejects.toMatchObject({ statusCode: 502 }) 156 } finally { 157 vi.unstubAllGlobals() 158 } 159 }) 160}) 161 162describe('getPackageFileTree', () => { 163 it('returns metadata and a converted tree', async () => { 164 const body = { 165 type: 'npm', 166 name: 'pkg', 167 version: '1.0.0', 168 default: 'index.js', 169 files: [ 170 { 171 type: 'directory', 172 name: 'src', 173 files: [{ type: 'file', name: 'index.js', size: 5 }], 174 }, 175 ], 176 } 177 178 mockFetchOk(body) 179 180 try { 181 const result = await getPackageFileTree('pkg', '1.0.0') 182 expect(result.package).toBe('pkg') 183 expect(result.version).toBe('1.0.0') 184 expect(result.default).toBe('index.js') 185 expect(result.tree[0]?.path).toBe('src') 186 expect(result.tree[0]?.children?.[0]?.path).toBe('src/index.js') 187 } finally { 188 vi.unstubAllGlobals() 189 } 190 }) 191 192 it('returns undefined when default is missing', async () => { 193 const body = { 194 type: 'npm', 195 name: 'pkg', 196 version: '1.0.0', 197 files: [], 198 } 199 200 mockFetchOk(body) 201 202 try { 203 const result = await getPackageFileTree('pkg', '1.0.0') 204 expect(result.default).toBeUndefined() 205 } finally { 206 vi.unstubAllGlobals() 207 } 208 }) 209})