import type { RepositoryInfo } from '#shared/utils/git-providers' import { describe, expect, it, vi, beforeAll } from 'vitest' // Mock the global Nuxt auto-import before importing the module beforeAll(() => { vi.stubGlobal( 'getShikiHighlighter', vi.fn().mockResolvedValue({ getLoadedLanguages: () => [], codeToHtml: (code: string) => `
${code}
`, }), ) }) // Import after mock is set up const { renderReadmeHtml } = await import('../../../../server/utils/readme') // Helper to create mock repository info function createRepoInfo(overrides?: Partial): RepositoryInfo { return { provider: 'github', owner: 'test-owner', repo: 'test-repo', rawBaseUrl: 'https://raw.githubusercontent.com/test-owner/test-repo/HEAD', blobBaseUrl: 'https://github.com/test-owner/test-repo/blob/HEAD', ...overrides, } } describe('Playground Link Extraction', () => { describe('StackBlitz', () => { it('extracts stackblitz.com links', async () => { const markdown = `Check out [Demo on StackBlitz](https://stackblitz.com/github/user/repo)` const result = await renderReadmeHtml(markdown, 'test-pkg') expect(result.playgroundLinks).toHaveLength(1) expect(result.playgroundLinks[0]).toMatchObject({ provider: 'stackblitz', providerName: 'StackBlitz', label: 'Demo on StackBlitz', url: 'https://stackblitz.com/github/user/repo', }) }) }) describe('CodeSandbox', () => { it('extracts codesandbox.io links', async () => { const markdown = `[Try it](https://codesandbox.io/s/example-abc123)` const result = await renderReadmeHtml(markdown, 'test-pkg') expect(result.playgroundLinks).toHaveLength(1) expect(result.playgroundLinks[0]).toMatchObject({ provider: 'codesandbox', providerName: 'CodeSandbox', }) }) it('extracts githubbox.com links as CodeSandbox', async () => { const markdown = `[Demo](https://githubbox.com/user/repo/tree/main/examples)` const result = await renderReadmeHtml(markdown, 'test-pkg') expect(result.playgroundLinks).toHaveLength(1) expect(result.playgroundLinks[0]!.provider).toBe('codesandbox') }) it('extracts label from image link', async () => { const markdown = `[![Edit CodeSandbox](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/example-abc123)` const result = await renderReadmeHtml(markdown, 'test-pkg') expect(result.playgroundLinks).toHaveLength(1) expect(result.playgroundLinks[0]).toMatchObject({ provider: 'codesandbox', providerName: 'CodeSandbox', label: 'Edit CodeSandbox', url: 'https://codesandbox.io/s/example-abc123', }) }) }) describe('Other Providers', () => { it('extracts CodePen links', async () => { const markdown = `[Pen](https://codepen.io/user/pen/abc123)` const result = await renderReadmeHtml(markdown, 'test-pkg') expect(result.playgroundLinks[0]!.provider).toBe('codepen') }) it('extracts Replit links', async () => { const markdown = `[Repl](https://replit.com/@user/project)` const result = await renderReadmeHtml(markdown, 'test-pkg') expect(result.playgroundLinks[0]!.provider).toBe('replit') }) it('extracts Gitpod links', async () => { const markdown = `[Open in Gitpod](https://gitpod.io/#https://github.com/user/repo)` const result = await renderReadmeHtml(markdown, 'test-pkg') expect(result.playgroundLinks[0]!.provider).toBe('gitpod') }) }) describe('Multiple Links', () => { it('extracts multiple playground links', async () => { const markdown = ` - [StackBlitz](https://stackblitz.com/example1) - [CodeSandbox](https://codesandbox.io/s/example2) ` const result = await renderReadmeHtml(markdown, 'test-pkg') expect(result.playgroundLinks).toHaveLength(2) expect(result.playgroundLinks[0]!.provider).toBe('stackblitz') expect(result.playgroundLinks[1]!.provider).toBe('codesandbox') }) it('deduplicates same URL', async () => { const markdown = ` [Demo 1](https://stackblitz.com/example) [Demo 2](https://stackblitz.com/example) ` const result = await renderReadmeHtml(markdown, 'test-pkg') expect(result.playgroundLinks).toHaveLength(1) }) }) describe('Non-Playground Links', () => { it('ignores regular GitHub links', async () => { const markdown = `[Repo](https://github.com/user/repo)` const result = await renderReadmeHtml(markdown, 'test-pkg') expect(result.playgroundLinks).toHaveLength(0) }) it('ignores npm links', async () => { const markdown = `[Package](https://npmjs.com/package/test)` const result = await renderReadmeHtml(markdown, 'test-pkg') expect(result.playgroundLinks).toHaveLength(0) }) }) describe('Edge Cases', () => { it('returns empty array for empty content', async () => { const result = await renderReadmeHtml('', 'test-pkg') expect(result.playgroundLinks).toEqual([]) expect(result.html).toBe('') }) it('handles badge images wrapped in links', async () => { const markdown = `[![Open in StackBlitz](https://img.shields.io/badge/Open-StackBlitz-blue)](https://stackblitz.com/example)` const result = await renderReadmeHtml(markdown, 'test-pkg') expect(result.playgroundLinks).toHaveLength(1) expect(result.playgroundLinks[0]!.provider).toBe('stackblitz') }) }) }) describe('Markdown File URL Resolution', () => { describe('with repository info', () => { it('resolves relative .md links to blob URL for rendered viewing', async () => { const repoInfo = createRepoInfo() const markdown = `[Contributing](./CONTRIBUTING.md)` const result = await renderReadmeHtml(markdown, 'test-pkg', repoInfo) expect(result.html).toContain( 'href="https://github.com/test-owner/test-repo/blob/HEAD/CONTRIBUTING.md"', ) }) it('resolves relative .MD links (uppercase) to blob URL', async () => { const repoInfo = createRepoInfo() const markdown = `[Guide](./GUIDE.MD)` const result = await renderReadmeHtml(markdown, 'test-pkg', repoInfo) expect(result.html).toContain( 'href="https://github.com/test-owner/test-repo/blob/HEAD/GUIDE.MD"', ) }) it('resolves nested relative .md links to blob URL', async () => { const repoInfo = createRepoInfo() const markdown = `[API Docs](./docs/api/reference.md)` const result = await renderReadmeHtml(markdown, 'test-pkg', repoInfo) expect(result.html).toContain( 'href="https://github.com/test-owner/test-repo/blob/HEAD/docs/api/reference.md"', ) }) it('resolves relative .md links with query strings to blob URL', async () => { const repoInfo = createRepoInfo() const markdown = `[FAQ](./FAQ.md?ref=main)` const result = await renderReadmeHtml(markdown, 'test-pkg', repoInfo) expect(result.html).toContain( 'href="https://github.com/test-owner/test-repo/blob/HEAD/FAQ.md?ref=main"', ) }) it('resolves relative .md links with anchors to blob URL', async () => { const repoInfo = createRepoInfo() const markdown = `[Install Section](./CONTRIBUTING.md#installation)` const result = await renderReadmeHtml(markdown, 'test-pkg', repoInfo) expect(result.html).toContain( 'href="https://github.com/test-owner/test-repo/blob/HEAD/CONTRIBUTING.md#installation"', ) }) it('resolves non-.md files to raw URL (not blob)', async () => { const repoInfo = createRepoInfo() const markdown = `[Image](./assets/logo.png)` const result = await renderReadmeHtml(markdown, 'test-pkg', repoInfo) expect(result.html).toContain( 'href="https://raw.githubusercontent.com/test-owner/test-repo/HEAD/assets/logo.png"', ) }) it('handles monorepo directory for .md links', async () => { const repoInfo = createRepoInfo({ directory: 'packages/core', }) const markdown = `[Changelog](./CHANGELOG.md)` const result = await renderReadmeHtml(markdown, 'test-pkg', repoInfo) expect(result.html).toContain( 'href="https://github.com/test-owner/test-repo/blob/HEAD/packages/core/CHANGELOG.md"', ) }) it('handles parent directory navigation for .md links', async () => { const repoInfo = createRepoInfo({ directory: 'packages/core', }) const markdown = `[Root Contributing](../../CONTRIBUTING.md)` const result = await renderReadmeHtml(markdown, 'test-pkg', repoInfo) expect(result.html).toContain( 'href="https://github.com/test-owner/test-repo/blob/HEAD/CONTRIBUTING.md"', ) }) }) describe('without repository info', () => { it('leaves relative .md links unchanged (no jsdelivr fallback)', async () => { const markdown = `[Contributing](./CONTRIBUTING.md)` const result = await renderReadmeHtml(markdown, 'test-pkg') // Should remain unchanged, not converted to jsdelivr expect(result.html).toContain('href="./CONTRIBUTING.md"') }) it('resolves non-.md files to jsdelivr CDN', async () => { const markdown = `[Schema](./schema.json)` const result = await renderReadmeHtml(markdown, 'test-pkg') expect(result.html).toContain('href="https://cdn.jsdelivr.net/npm/test-pkg/schema.json"') }) }) describe('absolute URLs', () => { it('leaves absolute .md URLs unchanged', async () => { const repoInfo = createRepoInfo() const markdown = `[External Guide](https://example.com/guide.md)` const result = await renderReadmeHtml(markdown, 'test-pkg', repoInfo) expect(result.html).toContain('href="https://example.com/guide.md"') }) it('leaves absolute non-.md URLs unchanged', async () => { const repoInfo = createRepoInfo() const markdown = `[Docs](https://docs.example.com/)` const result = await renderReadmeHtml(markdown, 'test-pkg', repoInfo) expect(result.html).toContain('href="https://docs.example.com/"') }) }) describe('anchor links', () => { it('prefixes anchor links with user-content-', async () => { const markdown = `[Jump to section](#installation)` const result = await renderReadmeHtml(markdown, 'test-pkg') expect(result.html).toContain('href="#user-content-installation"') }) }) describe('different git providers', () => { it('uses correct blob URL format for GitLab', async () => { const repoInfo = createRepoInfo({ provider: 'gitlab', host: 'gitlab.com', rawBaseUrl: 'https://gitlab.com/owner/repo/-/raw/HEAD', blobBaseUrl: 'https://gitlab.com/owner/repo/-/blob/HEAD', }) const markdown = `[Docs](./docs/guide.md)` const result = await renderReadmeHtml(markdown, 'test-pkg', repoInfo) expect(result.html).toContain( 'href="https://gitlab.com/owner/repo/-/blob/HEAD/docs/guide.md"', ) }) it('uses correct blob URL format for Bitbucket', async () => { const repoInfo = createRepoInfo({ provider: 'bitbucket', rawBaseUrl: 'https://bitbucket.org/owner/repo/raw/HEAD', blobBaseUrl: 'https://bitbucket.org/owner/repo/src/HEAD', }) const markdown = `[Readme](./other/README.md)` const result = await renderReadmeHtml(markdown, 'test-pkg', repoInfo) expect(result.html).toContain( 'href="https://bitbucket.org/owner/repo/src/HEAD/other/README.md"', ) }) }) describe('npm.js urls', () => { it('redirects npmjs.com urls to local', async () => { const markdown = `[Some npmjs.com link](https://www.npmjs.com/package/test-pkg)` const result = await renderReadmeHtml(markdown, 'test-pkg') expect(result.html).toContain('href="/package/test-pkg"') }) it('redirects npmjs.com urls to local (no www and http)', async () => { const markdown = `[Some npmjs.com link](http://npmjs.com/package/test-pkg)` const result = await renderReadmeHtml(markdown, 'test-pkg') expect(result.html).toContain('href="/package/test-pkg"') }) it('does not redirect npmjs.com to local if they are in the list of exceptions', async () => { const markdown = `[Root Contributing](https://www.npmjs.com/products)` const result = await renderReadmeHtml(markdown, 'test-pkg') expect(result.html).toContain('href="https://www.npmjs.com/products"') }) }) }) describe('ReadmeResponse shape (HTML route contract)', () => { it('returns ReadmeResponse with html, mdExists, playgroundLinks, toc', async () => { const markdown = `# Title\n\nSome **bold** text.` const result = await renderReadmeHtml(markdown, 'test-pkg') expect(result).toMatchObject({ html: expect.any(String), mdExists: true, playgroundLinks: [], toc: expect.any(Array), }) expect(result.html).toContain('Title') expect(result.html).toContain('bold') }) it('returns empty-state shape when content is empty', async () => { const result = await renderReadmeHtml('', 'test-pkg') expect(result).toMatchObject({ html: '', playgroundLinks: [], toc: [], }) expect(result.playgroundLinks).toHaveLength(0) expect(result.toc).toHaveLength(0) }) it('extracts toc from headings', async () => { const markdown = `# Install\n\n## CLI\n\n## API` const result = await renderReadmeHtml(markdown, 'test-pkg') expect(result.toc).toHaveLength(3) expect(result.toc[0]).toMatchObject({ text: 'Install', depth: 1 }) expect(result.toc[1]).toMatchObject({ text: 'CLI', depth: 2 }) expect(result.toc[2]).toMatchObject({ text: 'API', depth: 2 }) expect(result.toc.every(t => t.id.startsWith('user-content-'))).toBe(true) }) }) describe('HTML output', () => { it('returns sanitized html', async () => { const markdown = `# Title\n\nSome **bold** text and a [link](https://example.com).` const result = await renderReadmeHtml(markdown, 'test-pkg') expect(result.html) .toBe(`

Title

Some bold text and a link.

`) }) })