1+ import type { RepositoryInfo } from '#shared/utils/git-providers'
12import { describe , expect , it , vi , beforeAll } from 'vitest'
23
34// Mock the global Nuxt auto-import before importing the module
@@ -14,6 +15,18 @@ beforeAll(() => {
1415// Import after mock is set up
1516const { renderReadmeHtml } = await import ( '../../../../server/utils/readme' )
1617
18+ // Helper to create mock repository info
19+ function createRepoInfo ( overrides ?: Partial < RepositoryInfo > ) : RepositoryInfo {
20+ return {
21+ provider : 'github' ,
22+ owner : 'test-owner' ,
23+ repo : 'test-repo' ,
24+ rawBaseUrl : 'https://raw.githubusercontent.com/test-owner/test-repo/HEAD' ,
25+ blobBaseUrl : 'https://github.com/test-owner/test-repo/blob/HEAD' ,
26+ ...overrides ,
27+ }
28+ }
29+
1730describe ( 'Playground Link Extraction' , ( ) => {
1831 describe ( 'StackBlitz' , ( ) => {
1932 it ( 'extracts stackblitz.com links' , async ( ) => {
@@ -131,3 +144,166 @@ describe('Playground Link Extraction', () => {
131144 } )
132145 } )
133146} )
147+
148+ describe ( 'Markdown File URL Resolution' , ( ) => {
149+ describe ( 'with repository info' , ( ) => {
150+ it ( 'resolves relative .md links to blob URL for rendered viewing' , async ( ) => {
151+ const repoInfo = createRepoInfo ( )
152+ const markdown = `[Contributing](./CONTRIBUTING.md)`
153+ const result = await renderReadmeHtml ( markdown , 'test-pkg' , repoInfo )
154+
155+ expect ( result . html ) . toContain (
156+ 'href="https://github.com/test-owner/test-repo/blob/HEAD/CONTRIBUTING.md"' ,
157+ )
158+ } )
159+
160+ it ( 'resolves relative .MD links (uppercase) to blob URL' , async ( ) => {
161+ const repoInfo = createRepoInfo ( )
162+ const markdown = `[Guide](./GUIDE.MD)`
163+ const result = await renderReadmeHtml ( markdown , 'test-pkg' , repoInfo )
164+
165+ expect ( result . html ) . toContain (
166+ 'href="https://github.com/test-owner/test-repo/blob/HEAD/GUIDE.MD"' ,
167+ )
168+ } )
169+
170+ it ( 'resolves nested relative .md links to blob URL' , async ( ) => {
171+ const repoInfo = createRepoInfo ( )
172+ const markdown = `[API Docs](./docs/api/reference.md)`
173+ const result = await renderReadmeHtml ( markdown , 'test-pkg' , repoInfo )
174+
175+ expect ( result . html ) . toContain (
176+ 'href="https://github.com/test-owner/test-repo/blob/HEAD/docs/api/reference.md"' ,
177+ )
178+ } )
179+
180+ it ( 'resolves relative .md links with query strings to blob URL' , async ( ) => {
181+ const repoInfo = createRepoInfo ( )
182+ const markdown = `[FAQ](./FAQ.md?ref=main)`
183+ const result = await renderReadmeHtml ( markdown , 'test-pkg' , repoInfo )
184+
185+ expect ( result . html ) . toContain (
186+ 'href="https://github.com/test-owner/test-repo/blob/HEAD/FAQ.md?ref=main"' ,
187+ )
188+ } )
189+
190+ it ( 'resolves relative .md links with anchors to blob URL' , async ( ) => {
191+ const repoInfo = createRepoInfo ( )
192+ const markdown = `[Install Section](./CONTRIBUTING.md#installation)`
193+ const result = await renderReadmeHtml ( markdown , 'test-pkg' , repoInfo )
194+
195+ expect ( result . html ) . toContain (
196+ 'href="https://github.com/test-owner/test-repo/blob/HEAD/CONTRIBUTING.md#installation"' ,
197+ )
198+ } )
199+
200+ it ( 'resolves non-.md files to raw URL (not blob)' , async ( ) => {
201+ const repoInfo = createRepoInfo ( )
202+ const markdown = `[Image](./assets/logo.png)`
203+ const result = await renderReadmeHtml ( markdown , 'test-pkg' , repoInfo )
204+
205+ expect ( result . html ) . toContain (
206+ 'href="https://raw.githubusercontent.com/test-owner/test-repo/HEAD/assets/logo.png"' ,
207+ )
208+ } )
209+
210+ it ( 'handles monorepo directory for .md links' , async ( ) => {
211+ const repoInfo = createRepoInfo ( {
212+ directory : 'packages/core' ,
213+ } )
214+ const markdown = `[Changelog](./CHANGELOG.md)`
215+ const result = await renderReadmeHtml ( markdown , 'test-pkg' , repoInfo )
216+
217+ expect ( result . html ) . toContain (
218+ 'href="https://github.com/test-owner/test-repo/blob/HEAD/packages/core/CHANGELOG.md"' ,
219+ )
220+ } )
221+
222+ it ( 'handles parent directory navigation for .md links' , async ( ) => {
223+ const repoInfo = createRepoInfo ( {
224+ directory : 'packages/core' ,
225+ } )
226+ const markdown = `[Root Contributing](../../CONTRIBUTING.md)`
227+ const result = await renderReadmeHtml ( markdown , 'test-pkg' , repoInfo )
228+
229+ expect ( result . html ) . toContain (
230+ 'href="https://github.com/test-owner/test-repo/blob/HEAD/CONTRIBUTING.md"' ,
231+ )
232+ } )
233+ } )
234+
235+ describe ( 'without repository info' , ( ) => {
236+ it ( 'leaves relative .md links unchanged (no jsdelivr fallback)' , async ( ) => {
237+ const markdown = `[Contributing](./CONTRIBUTING.md)`
238+ const result = await renderReadmeHtml ( markdown , 'test-pkg' )
239+
240+ // Should remain unchanged, not converted to jsdelivr
241+ expect ( result . html ) . toContain ( 'href="./CONTRIBUTING.md"' )
242+ } )
243+
244+ it ( 'resolves non-.md files to jsdelivr CDN' , async ( ) => {
245+ const markdown = `[Schema](./schema.json)`
246+ const result = await renderReadmeHtml ( markdown , 'test-pkg' )
247+
248+ expect ( result . html ) . toContain ( 'href="https://cdn.jsdelivr.net/npm/test-pkg/schema.json"' )
249+ } )
250+ } )
251+
252+ describe ( 'absolute URLs' , ( ) => {
253+ it ( 'leaves absolute .md URLs unchanged' , async ( ) => {
254+ const repoInfo = createRepoInfo ( )
255+ const markdown = `[External Guide](https://example.com/guide.md)`
256+ const result = await renderReadmeHtml ( markdown , 'test-pkg' , repoInfo )
257+
258+ expect ( result . html ) . toContain ( 'href="https://example.com/guide.md"' )
259+ } )
260+
261+ it ( 'leaves absolute non-.md URLs unchanged' , async ( ) => {
262+ const repoInfo = createRepoInfo ( )
263+ const markdown = `[Docs](https://docs.example.com/)`
264+ const result = await renderReadmeHtml ( markdown , 'test-pkg' , repoInfo )
265+
266+ expect ( result . html ) . toContain ( 'href="https://docs.example.com/"' )
267+ } )
268+ } )
269+
270+ describe ( 'anchor links' , ( ) => {
271+ it ( 'prefixes anchor links with user-content-' , async ( ) => {
272+ const markdown = `[Jump to section](#installation)`
273+ const result = await renderReadmeHtml ( markdown , 'test-pkg' )
274+
275+ expect ( result . html ) . toContain ( 'href="#user-content-installation"' )
276+ } )
277+ } )
278+
279+ describe ( 'different git providers' , ( ) => {
280+ it ( 'uses correct blob URL format for GitLab' , async ( ) => {
281+ const repoInfo = createRepoInfo ( {
282+ provider : 'gitlab' ,
283+ host : 'gitlab.com' ,
284+ rawBaseUrl : 'https://gitlab.com/owner/repo/-/raw/HEAD' ,
285+ blobBaseUrl : 'https://gitlab.com/owner/repo/-/blob/HEAD' ,
286+ } )
287+ const markdown = `[Docs](./docs/guide.md)`
288+ const result = await renderReadmeHtml ( markdown , 'test-pkg' , repoInfo )
289+
290+ expect ( result . html ) . toContain (
291+ 'href="https://gitlab.com/owner/repo/-/blob/HEAD/docs/guide.md"' ,
292+ )
293+ } )
294+
295+ it ( 'uses correct blob URL format for Bitbucket' , async ( ) => {
296+ const repoInfo = createRepoInfo ( {
297+ provider : 'bitbucket' ,
298+ rawBaseUrl : 'https://bitbucket.org/owner/repo/raw/HEAD' ,
299+ blobBaseUrl : 'https://bitbucket.org/owner/repo/src/HEAD' ,
300+ } )
301+ const markdown = `[Readme](./other/README.md)`
302+ const result = await renderReadmeHtml ( markdown , 'test-pkg' , repoInfo )
303+
304+ expect ( result . html ) . toContain (
305+ 'href="https://bitbucket.org/owner/repo/src/HEAD/other/README.md"' ,
306+ )
307+ } )
308+ } )
309+ } )
0 commit comments