forked from
npmx.dev/npmx.dev
[READ-ONLY]
a fast, modern browser for the npm registry
1import { describe, expect, it, vi, beforeEach } from 'vitest'
2import { parsePackageParams } from '../../../../server/utils/parse-package-params'
3import { NPM_MISSING_README_SENTINEL } from '#shared/utils/constants'
4
5// Mock Nitro globals before importing the module
6vi.stubGlobal('defineCachedFunction', (fn: Function) => fn)
7const $fetchMock = vi.fn()
8vi.stubGlobal('$fetch', $fetchMock)
9vi.stubGlobal('parsePackageParams', parsePackageParams)
10
11const fetchNpmPackageMock = vi.fn()
12vi.stubGlobal('fetchNpmPackage', fetchNpmPackageMock)
13
14const parseRepositoryInfoMock = vi.fn()
15vi.stubGlobal('parseRepositoryInfo', parseRepositoryInfoMock)
16
17const { fetchReadmeFromJsdelivr, isStandardReadme, resolvePackageReadmeSource } =
18 await import('../../../../server/utils/readme-loaders')
19
20describe('isStandardReadme', () => {
21 it('returns true for standard README filenames', () => {
22 expect(isStandardReadme('README.md')).toBe(true)
23 expect(isStandardReadme('readme.md')).toBe(true)
24 expect(isStandardReadme('Readme.md')).toBe(true)
25 expect(isStandardReadme('README')).toBe(true)
26 expect(isStandardReadme('readme')).toBe(true)
27 expect(isStandardReadme('README.markdown')).toBe(true)
28 expect(isStandardReadme('readme.markdown')).toBe(true)
29 })
30
31 it('returns false for non-standard filenames', () => {
32 expect(isStandardReadme('CONTRIBUTING.md')).toBe(false)
33 expect(isStandardReadme('README.txt')).toBe(false)
34 expect(isStandardReadme('readme.rst')).toBe(false)
35 expect(isStandardReadme(undefined)).toBe(false)
36 expect(isStandardReadme('')).toBe(false)
37 })
38})
39
40describe('fetchReadmeFromJsdelivr', () => {
41 it('returns content when first filename succeeds', async () => {
42 const content = '# Package'
43 const fetchMock = vi.fn().mockResolvedValue({
44 ok: true,
45 text: async () => content,
46 })
47 vi.stubGlobal('fetch', fetchMock)
48
49 const result = await fetchReadmeFromJsdelivr('some-pkg', ['README.md'])
50
51 expect(result).toBe(content)
52 expect(fetchMock).toHaveBeenCalledWith('https://cdn.jsdelivr.net/npm/some-pkg/README.md')
53 })
54
55 it('includes version in URL when version is passed', async () => {
56 const fetchMock = vi.fn().mockResolvedValue({
57 ok: true,
58 text: async () => '',
59 })
60 vi.stubGlobal('fetch', fetchMock)
61
62 await fetchReadmeFromJsdelivr('pkg', ['README.md'], '1.2.3')
63
64 expect(fetchMock).toHaveBeenCalledWith('https://cdn.jsdelivr.net/npm/pkg@1.2.3/README.md')
65 })
66
67 it('returns null when all fetches fail', async () => {
68 const fetchMock = vi.fn().mockResolvedValue({ ok: false })
69 vi.stubGlobal('fetch', fetchMock)
70
71 const result = await fetchReadmeFromJsdelivr('pkg', ['README.md', 'readme.md'])
72
73 expect(result).toBeNull()
74 expect(fetchMock).toHaveBeenCalledTimes(2)
75 })
76})
77
78describe('resolvePackageReadmeSource', () => {
79 beforeEach(() => {
80 fetchNpmPackageMock.mockReset()
81 parseRepositoryInfoMock.mockReset()
82 })
83
84 it('returns markdown and repoInfo when package has valid npm readme (latest)', async () => {
85 const markdown = '# Hello'
86 fetchNpmPackageMock.mockResolvedValue({
87 readme: markdown,
88 readmeFilename: 'README.md',
89 repository: { url: 'https://github.com/u/r' },
90 versions: {},
91 })
92 parseRepositoryInfoMock.mockReturnValue({
93 provider: 'github',
94 owner: 'u',
95 repo: 'r',
96 rawBaseUrl: 'https://raw.githubusercontent.com/u/r/HEAD',
97 blobBaseUrl: 'https://github.com/u/r/blob/HEAD',
98 })
99
100 const result = await resolvePackageReadmeSource('some-pkg')
101
102 expect(result).toMatchObject({
103 packageName: 'some-pkg',
104 version: undefined,
105 markdown,
106 repoInfo: { provider: 'github', owner: 'u', repo: 'r' },
107 })
108 expect(fetchNpmPackageMock).toHaveBeenCalledWith('some-pkg')
109 })
110
111 it('returns markdown from version when packagePath includes version', async () => {
112 const markdown = '# Version readme'
113 fetchNpmPackageMock.mockResolvedValue({
114 readme: 'latest readme',
115 readmeFilename: 'README.md',
116 repository: undefined,
117 versions: {
118 '1.0.0': { readme: markdown, readmeFilename: 'README.md' },
119 },
120 })
121 parseRepositoryInfoMock.mockReturnValue(undefined)
122
123 const result = await resolvePackageReadmeSource('some-pkg/v/1.0.0')
124
125 expect(result).toMatchObject({
126 packageName: 'some-pkg',
127 version: '1.0.0',
128 markdown,
129 })
130 })
131
132 it('falls back to jsdelivr when npm readme is missing sentinel', async () => {
133 const jsdelivrContent = '# From CDN'
134 fetchNpmPackageMock.mockResolvedValue({
135 readme: NPM_MISSING_README_SENTINEL,
136 readmeFilename: 'README.md',
137 repository: undefined,
138 versions: {},
139 })
140 parseRepositoryInfoMock.mockReturnValue(undefined)
141 const fetchMock = vi.fn().mockResolvedValue({
142 ok: true,
143 text: async () => jsdelivrContent,
144 })
145 vi.stubGlobal('fetch', fetchMock)
146
147 const result = await resolvePackageReadmeSource('pkg')
148
149 expect(result).toMatchObject({
150 packageName: 'pkg',
151 markdown: jsdelivrContent,
152 repoInfo: undefined,
153 })
154 expect(fetchMock).toHaveBeenCalled()
155 })
156
157 it('falls back to jsdelivr when readmeFilename is not standard', async () => {
158 const jsdelivrContent = '# From CDN'
159 fetchNpmPackageMock.mockResolvedValue({
160 readme: 'content',
161 readmeFilename: 'DOCS.md',
162 repository: undefined,
163 versions: {},
164 })
165 parseRepositoryInfoMock.mockReturnValue(undefined)
166 const fetchMock = vi.fn().mockResolvedValue({
167 ok: true,
168 text: async () => jsdelivrContent,
169 })
170 vi.stubGlobal('fetch', fetchMock)
171
172 const result = await resolvePackageReadmeSource('pkg')
173
174 expect(result).toMatchObject({ markdown: jsdelivrContent })
175 })
176
177 it('returns undefined markdown when no content and jsdelivr fails', async () => {
178 fetchNpmPackageMock.mockResolvedValue({
179 readme: undefined,
180 readmeFilename: undefined,
181 repository: undefined,
182 versions: {},
183 })
184 parseRepositoryInfoMock.mockReturnValue(undefined)
185 const fetchMock = vi.fn().mockResolvedValue({ ok: false })
186 vi.stubGlobal('fetch', fetchMock)
187
188 const result = await resolvePackageReadmeSource('pkg')
189
190 expect(result).toMatchObject({
191 packageName: 'pkg',
192 version: undefined,
193 markdown: undefined,
194 repoInfo: undefined,
195 })
196 })
197
198 it('returns undefined markdown when content is NPM_MISSING_README_SENTINEL and jsdelivr fails', async () => {
199 fetchNpmPackageMock.mockResolvedValue({
200 readme: NPM_MISSING_README_SENTINEL,
201 readmeFilename: 'README.md',
202 repository: undefined,
203 versions: {},
204 })
205 const fetchMock = vi.fn().mockResolvedValue({ ok: false })
206 vi.stubGlobal('fetch', fetchMock)
207
208 const result = await resolvePackageReadmeSource('pkg')
209
210 expect(result).toMatchObject({
211 packageName: 'pkg',
212 markdown: undefined,
213 repoInfo: undefined,
214 })
215 })
216
217 it('fetches from jsdelivr when packument readme exceeds truncation threshold', async () => {
218 const truncatedReadme = 'x'.repeat(64_000)
219 const fullReadme = 'x'.repeat(80_000)
220 fetchNpmPackageMock.mockResolvedValue({
221 'readme': truncatedReadme,
222 'readmeFilename': 'README.md',
223 'repository': undefined,
224 'versions': {},
225 'dist-tags': { latest: '1.0.0' },
226 })
227 parseRepositoryInfoMock.mockReturnValue(undefined)
228 const fetchMock = vi.fn().mockResolvedValue({
229 ok: true,
230 text: async () => fullReadme,
231 })
232 vi.stubGlobal('fetch', fetchMock)
233
234 const result = await resolvePackageReadmeSource('pkg')
235
236 expect(result).toMatchObject({ markdown: fullReadme })
237 expect(fetchMock).toHaveBeenCalled()
238 })
239
240 it('uses package repository for repoInfo when markdown is present', async () => {
241 fetchNpmPackageMock.mockResolvedValue({
242 readme: '# Hi',
243 readmeFilename: 'README.md',
244 repository: { url: 'https://github.com/a/b' },
245 versions: {},
246 })
247 const repoInfo = {
248 provider: 'github' as const,
249 owner: 'a',
250 repo: 'b',
251 rawBaseUrl: 'https://raw.githubusercontent.com/a/b/HEAD',
252 blobBaseUrl: 'https://github.com/a/b/blob/HEAD',
253 }
254 parseRepositoryInfoMock.mockReturnValue(repoInfo)
255
256 const result = await resolvePackageReadmeSource('pkg')
257
258 expect(result?.repoInfo).toEqual(repoInfo)
259 expect(parseRepositoryInfoMock).toHaveBeenCalledWith({ url: 'https://github.com/a/b' })
260 })
261})