project-doc-site.spec.ts 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. /** Tests for the documentation website projection adapter. */
  2. import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
  3. import { tmpdir } from 'node:os'
  4. import { join } from 'node:path'
  5. import { afterEach, describe, expect, it } from 'vitest'
  6. import { docsPages, type DocsPage } from '../website/docs.ts'
  7. import { addProjectionFrontmatter, rewriteMarkdown } from './project-doc-site.ts'
  8. const roots: string[] = []
  9. afterEach(() => {
  10. for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true })
  11. })
  12. function fixture(): { root: string; pages: DocsPage[] } {
  13. const root = mkdtempSync(join(tmpdir(), 'dsh-doc-site-'))
  14. roots.push(root)
  15. mkdirSync(join(root, 'docs'), { recursive: true })
  16. mkdirSync(join(root, 'packages'), { recursive: true })
  17. writeFileSync(join(root, 'docs/a.md'), '# A\n')
  18. writeFileSync(join(root, 'docs/b.md'), '# B\n')
  19. writeFileSync(join(root, 'packages/tool.ts'), 'one\ntwo\n')
  20. writeFileSync(join(root, 'packages/logo.svg'), '<svg/>\n')
  21. return {
  22. root,
  23. pages: [
  24. { locale: 'root', contentLocale: 'en-US', source: 'docs/a.md', route: 'a.md', label: 'A', sidebar: 'zh-reference', section: 'Test', order: 1 },
  25. { locale: 'root', contentLocale: 'en-US', source: 'docs/b.md', route: 'reference-root/b.md', label: 'B', sidebar: 'zh-reference', section: 'Test', order: 2 },
  26. { locale: 'en', contentLocale: 'en-US', source: 'docs/a.md', route: 'en/a.md', label: 'A', sidebar: 'en-reference', section: 'Test', order: 1 },
  27. { locale: 'en', contentLocale: 'en-US', source: 'docs/b.md', route: 'en/reference/b.md', label: 'B', sidebar: 'en-reference', section: 'Test', order: 2 },
  28. ],
  29. }
  30. }
  31. describe('rewriteMarkdown', () => {
  32. it('maps published pages and pins unpublished source links', () => {
  33. const { root, pages } = fixture()
  34. const source = '[B](b.md#part) [source](../packages/tool.ts:2) [web](https://example.com)\n'
  35. expect(rewriteMarkdown(source, {
  36. locale: 'en',
  37. sourcePath: 'docs/a.md',
  38. route: 'en/a.md',
  39. pages,
  40. repoRoot: root,
  41. repositoryRef: 'abc123',
  42. })).toBe(
  43. '[B](./reference/b.md#part) '
  44. + '[source](https://github.com/deepseek-harness/deepseek-harness/blob/abc123/packages/tool.ts#L2) '
  45. + '[web](https://example.com)\n',
  46. )
  47. })
  48. it('selects the published target in the current site locale', () => {
  49. const { root, pages } = fixture()
  50. expect(rewriteMarkdown('[B](b.md)\n', {
  51. locale: 'root',
  52. sourcePath: 'docs/a.md',
  53. route: 'a.md',
  54. pages,
  55. repoRoot: root,
  56. repositoryRef: 'abc123',
  57. })).toBe('[B](./reference-root/b.md)\n')
  58. })
  59. it('uses raw GitHub content for unpublished images', () => {
  60. const { root, pages } = fixture()
  61. expect(rewriteMarkdown('![logo](../packages/logo.svg)\n', {
  62. locale: 'en',
  63. sourcePath: 'docs/a.md',
  64. route: 'en/a.md',
  65. pages,
  66. repoRoot: root,
  67. repositoryRef: 'abc123',
  68. })).toBe('![logo](https://raw.githubusercontent.com/deepseek-harness/deepseek-harness/abc123/packages/logo.svg)\n')
  69. })
  70. it('does not rewrite Markdown-looking text inside code fences', () => {
  71. const { root, pages } = fixture()
  72. const source = '```md\n[B](b.md)\n```\n'
  73. expect(rewriteMarkdown(source, {
  74. locale: 'en',
  75. sourcePath: 'docs/a.md',
  76. route: 'en/a.md',
  77. pages,
  78. repoRoot: root,
  79. repositoryRef: 'abc123',
  80. })).toBe(source)
  81. })
  82. it('fails loud when a relative target is missing', () => {
  83. const { root, pages } = fixture()
  84. expect(() => rewriteMarkdown('[missing](missing.md)\n', {
  85. locale: 'en',
  86. sourcePath: 'docs/a.md',
  87. route: 'en/a.md',
  88. pages,
  89. repoRoot: root,
  90. repositoryRef: 'abc123',
  91. })).toThrow('links to missing path "missing.md"')
  92. })
  93. })
  94. describe('docsPages locale routes', () => {
  95. it('publishes the same canonical source at every corresponding locale route', () => {
  96. const byRoute = new Map(docsPages.map(page => [page.route, page]))
  97. for (const page of docsPages.filter(page => page.locale === 'root')) {
  98. const counterpart = byRoute.get(`en/${page.route}`)
  99. expect(counterpart, page.route).toBeDefined()
  100. expect(counterpart?.locale).toBe('en')
  101. expect(counterpart?.source).toBe(page.source)
  102. expect(counterpart?.contentLocale).toBe(page.contentLocale)
  103. }
  104. })
  105. })
  106. describe('addProjectionFrontmatter', () => {
  107. it('adds frontmatter to an ordinary Markdown page', () => {
  108. expect(addProjectionFrontmatter('# Guide\n', 'docs/guide.md')).toBe(
  109. '---\neditSource: "docs/guide.md"\n---\n\n# Guide\n',
  110. )
  111. })
  112. it('extends existing VitePress frontmatter', () => {
  113. expect(addProjectionFrontmatter('---\nlayout: home\n---\n', 'docs/index.md')).toBe(
  114. '---\neditSource: "docs/index.md"\nlayout: home\n---\n',
  115. )
  116. })
  117. })