220 lines
8.2 KiB
TypeScript
220 lines
8.2 KiB
TypeScript
/** Tests for the documentation website projection adapter. */
|
|
|
|
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
|
import { tmpdir } from 'node:os'
|
|
import { join } from 'node:path'
|
|
import { afterEach, describe, expect, it } from 'vitest'
|
|
import { docsPages, type DocsPage } from '../website/docs.ts'
|
|
import { addProjectionFrontmatter, projectedPageContent, rewriteMarkdown } from './project-doc-site.ts'
|
|
|
|
const roots: string[] = []
|
|
|
|
afterEach(() => {
|
|
for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true })
|
|
})
|
|
|
|
function fixture(): { root: string; pages: DocsPage[] } {
|
|
const root = mkdtempSync(join(tmpdir(), 'dsh-doc-site-'))
|
|
roots.push(root)
|
|
mkdirSync(join(root, 'docs'), { recursive: true })
|
|
mkdirSync(join(root, 'packages'), { recursive: true })
|
|
writeFileSync(join(root, 'docs/a.md'), '# A\n')
|
|
writeFileSync(join(root, 'docs/b.md'), '# B\n')
|
|
writeFileSync(join(root, 'docs/x(y).md'), '# Parentheses\n')
|
|
writeFileSync(join(root, 'packages/tool.ts'), 'one\ntwo\n')
|
|
writeFileSync(join(root, 'packages/logo.svg'), '<svg/>\n')
|
|
return {
|
|
root,
|
|
pages: [
|
|
{ locale: 'root', contentLocale: 'en-US', source: 'docs/a.md', route: 'a.md', label: 'A', sidebar: 'zh-reference', section: 'Test', order: 1 },
|
|
{ locale: 'root', contentLocale: 'en-US', source: 'docs/b.md', route: 'reference-root/b.md', label: 'B', sidebar: 'zh-reference', section: 'Test', order: 2 },
|
|
{ locale: 'en', contentLocale: 'en-US', source: 'docs/a.md', route: 'en/a.md', label: 'A', sidebar: 'en-reference', section: 'Test', order: 1 },
|
|
{ locale: 'en', contentLocale: 'en-US', source: 'docs/b.md', route: 'en/reference/b.md', label: 'B', sidebar: 'en-reference', section: 'Test', order: 2 },
|
|
],
|
|
}
|
|
}
|
|
|
|
describe('rewriteMarkdown', () => {
|
|
it('maps published pages and pins unpublished source links', () => {
|
|
const { root, pages } = fixture()
|
|
const source = '[B](b.md#part) [source](../packages/tool.ts:2) [web](https://example.com)\n'
|
|
expect(rewriteMarkdown(source, {
|
|
locale: 'en',
|
|
sourcePath: 'docs/a.md',
|
|
route: 'en/a.md',
|
|
pages,
|
|
repoRoot: root,
|
|
repositoryRef: 'abc123',
|
|
})).toBe(
|
|
'[B](./reference/b.md#part) '
|
|
+ '[source](https://github.com/deepseek-harness/deepseek-harness/blob/abc123/packages/tool.ts#L2) '
|
|
+ '[web](https://example.com)\n',
|
|
)
|
|
})
|
|
|
|
it('selects the published target in the current site locale', () => {
|
|
const { root, pages } = fixture()
|
|
expect(rewriteMarkdown('[B](b.md)\n', {
|
|
locale: 'root',
|
|
sourcePath: 'docs/a.md',
|
|
route: 'a.md',
|
|
pages,
|
|
repoRoot: root,
|
|
repositoryRef: 'abc123',
|
|
})).toBe('[B](./reference-root/b.md)\n')
|
|
})
|
|
|
|
it('uses raw GitHub content for unpublished images', () => {
|
|
const { root, pages } = fixture()
|
|
expect(rewriteMarkdown('\n', {
|
|
locale: 'en',
|
|
sourcePath: 'docs/a.md',
|
|
route: 'en/a.md',
|
|
pages,
|
|
repoRoot: root,
|
|
repositoryRef: 'abc123',
|
|
})).toBe('\n')
|
|
})
|
|
|
|
it('does not rewrite Markdown-looking text inside code fences', () => {
|
|
const { root, pages } = fixture()
|
|
const source = '```md\n[B](b.md)\n```\n'
|
|
expect(rewriteMarkdown(source, {
|
|
locale: 'en',
|
|
sourcePath: 'docs/a.md',
|
|
route: 'en/a.md',
|
|
pages,
|
|
repoRoot: root,
|
|
repositoryRef: 'abc123',
|
|
})).toBe(source)
|
|
})
|
|
|
|
it('replaces the destination token without changing repeated titles or escapes', () => {
|
|
const { root, pages } = fixture()
|
|
const source = '[title](b.md "b.md") [escaped](x\\(y\\).md)\n'
|
|
expect(rewriteMarkdown(source, {
|
|
locale: 'en',
|
|
sourcePath: 'docs/a.md',
|
|
route: 'en/a.md',
|
|
pages,
|
|
repoRoot: root,
|
|
repositoryRef: 'abc123',
|
|
})).toBe(
|
|
'[title](./reference/b.md "b.md") '
|
|
+ '[escaped](https://github.com/deepseek-harness/deepseek-harness/blob/abc123/docs/x(y).md)\n',
|
|
)
|
|
})
|
|
|
|
it('routes a pair switcher across locales while ordinary links stay in locale', () => {
|
|
const { root, pages } = fixture()
|
|
writeFileSync(join(root, 'docs/a.zh.md'), '# A\n')
|
|
const paired = pages.filter(page => page.source !== 'docs/a.md')
|
|
paired.push(
|
|
{
|
|
locale: 'root', contentLocale: 'zh-CN', source: 'docs/a.zh.md', sourceAliases: ['docs/a.md'],
|
|
route: 'guide/a.md', label: 'A', sidebar: 'zh-guide', section: 'Test', order: 1,
|
|
},
|
|
{
|
|
locale: 'en', contentLocale: 'en-US', source: 'docs/a.md', sourceAliases: ['docs/a.zh.md'],
|
|
route: 'en/guide/a.md', label: 'A', sidebar: 'en-guide', section: 'Test', order: 1,
|
|
},
|
|
)
|
|
expect(rewriteMarkdown('[English](a.md) [B](b.md)\n', {
|
|
locale: 'root',
|
|
sourcePath: 'docs/a.zh.md',
|
|
route: 'guide/a.md',
|
|
pages: paired,
|
|
repoRoot: root,
|
|
repositoryRef: 'abc123',
|
|
})).toBe('[English](../en/guide/a.md) [B](../reference-root/b.md)\n')
|
|
})
|
|
|
|
it('fails loud when a relative target is missing', () => {
|
|
const { root, pages } = fixture()
|
|
expect(() => rewriteMarkdown('[missing](missing.md)\n', {
|
|
locale: 'en',
|
|
sourcePath: 'docs/a.md',
|
|
route: 'en/a.md',
|
|
pages,
|
|
repoRoot: root,
|
|
repositoryRef: 'abc123',
|
|
})).toThrow('links to missing path "missing.md"')
|
|
})
|
|
})
|
|
|
|
describe('docsPages locale routes', () => {
|
|
it('publishes every route in both locales and selects paired user sources', () => {
|
|
const byRoute = new Map(docsPages.map(page => [page.route, page]))
|
|
for (const page of docsPages.filter(page => page.locale === 'root')) {
|
|
const counterpart = byRoute.get(`en/${page.route}`)
|
|
expect(counterpart, page.route).toBeDefined()
|
|
expect(counterpart?.locale).toBe('en')
|
|
if (page.source.startsWith('docs/user/')) {
|
|
expect(page.source).toMatch(/\.zh\.md$/)
|
|
expect(page.contentLocale).toBe('zh-CN')
|
|
expect(counterpart?.source).toBe(page.source.replace(/\.zh\.md$/, '.md'))
|
|
expect(counterpart?.contentLocale).toBe('en-US')
|
|
} else {
|
|
expect(counterpart?.source).toBe(page.source)
|
|
expect(counterpart?.contentLocale).toBe(page.contentLocale)
|
|
}
|
|
}
|
|
})
|
|
|
|
it('publishes the Cordis core API under matching locale structures', () => {
|
|
const files = ['context.md', 'events.md', 'fiber.md', 'registry.md', 'service.md']
|
|
for (const file of files) {
|
|
const root = docsPages.find(page => page.route === `reference/cordis-api/${file}`)
|
|
const english = docsPages.find(page => page.route === `en/reference/cordis-api/${file}`)
|
|
expect(root?.source).toBe(`docs/cordis-catalog/core/${file}`)
|
|
expect(root?.section).toBe('Cordis API')
|
|
expect(english?.source).toBe(root?.source)
|
|
expect(english?.section).toBe('Cordis Core API')
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('addProjectionFrontmatter', () => {
|
|
it('adds frontmatter to an ordinary Markdown page', () => {
|
|
expect(addProjectionFrontmatter('# Guide\n', 'docs/guide.md')).toBe(
|
|
'---\neditSource: "docs/guide.md"\n---\n\n# Guide\n',
|
|
)
|
|
})
|
|
|
|
it('extends existing VitePress frontmatter', () => {
|
|
expect(addProjectionFrontmatter('---\nlayout: home\n---\n', 'docs/index.md')).toBe(
|
|
'---\neditSource: "docs/index.md"\nlayout: home\n---\n',
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('projectedPageContent', () => {
|
|
const page = (sidebar: DocsPage['sidebar']): DocsPage => ({
|
|
locale: 'root',
|
|
contentLocale: 'zh-CN',
|
|
source: 'docs/index.zh.md',
|
|
route: 'index.md',
|
|
label: 'Home',
|
|
sidebar,
|
|
section: 'Home',
|
|
order: 0,
|
|
})
|
|
|
|
it('omits the source-only body from locale home pages', () => {
|
|
expect(projectedPageContent(
|
|
'---\nlayout: home\nhero:\n name: Harness\n---\n\n# Harness\n\n[English](index.md) | 中文\n',
|
|
page(null),
|
|
)).toBe('---\nlayout: home\nhero:\n name: Harness\n---\n')
|
|
})
|
|
|
|
it('keeps the full body for ordinary pages', () => {
|
|
const markdown = '---\ntitle: Guide\n---\n\n# Guide\n'
|
|
expect(projectedPageContent(markdown, page('zh-guide'))).toBe(markdown)
|
|
})
|
|
|
|
it('rejects a locale home source without frontmatter', () => {
|
|
expect(() => projectedPageContent('# Harness\n', page(null)))
|
|
.toThrow('locale home source "docs/index.zh.md" must start with YAML frontmatter')
|
|
})
|
|
})
|