verify-subsystem-pages.spec.ts 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. /** Regression coverage for package-group subsystem-page ownership. */
  2. import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
  3. import { tmpdir } from 'node:os'
  4. import { dirname, join } from 'node:path'
  5. import { describe, expect, it, onTestFinished } from 'vitest'
  6. import { auditSubsystemPages } from './verify-subsystem-pages.ts'
  7. function fixture(): string {
  8. const root = mkdtempSync(join(tmpdir(), 'dsh-subsystem-pages-'))
  9. onTestFinished(() => {
  10. rmSync(root, { recursive: true, force: true })
  11. })
  12. return root
  13. }
  14. function write(root: string, path: string, source: string): void {
  15. const absolute = join(root, path)
  16. mkdirSync(dirname(absolute), { recursive: true })
  17. writeFileSync(absolute, source)
  18. }
  19. describe('package-group subsystem pages', () => {
  20. it('accepts a direct page link and a justified no-page group', () => {
  21. const root = fixture()
  22. write(root, 'packages/alpha/README.md', '[types](../../docs/subsystems/alpha.md#contract)\n')
  23. write(root, 'packages/alpha/alpha/package.json', '{}\n')
  24. write(root, 'docs/subsystems/alpha.md', '# Alpha\n')
  25. write(root, 'packages/adapter/README.md', '# Adapter\n')
  26. expect(auditSubsystemPages(root, { adapter: 'Adapter over an existing subsystem.' })).toEqual({
  27. groups: 2,
  28. linked: 1,
  29. exempt: 1,
  30. violations: [],
  31. })
  32. })
  33. it('rejects a new group whose README never declares subsystem ownership', () => {
  34. const root = fixture()
  35. write(root, 'packages/schedule/README.md', '# Schedule\n')
  36. write(root, 'packages/schedule/tool-schedule/package.json', '{}\n')
  37. expect(auditSubsystemPages(root, {}).violations).toEqual([
  38. 'packages/schedule/README.md: no reader-visible direct docs/subsystems/*.md link; add the owning page and link, or add a justified GROUPS_WITHOUT_SUBSYSTEM_PAGE entry',
  39. ])
  40. })
  41. it('does not treat the subsystem index or a Chinese counterpart as an owning page', () => {
  42. const root = fixture()
  43. write(
  44. root,
  45. 'packages/wrong/README.md',
  46. '[index](../../docs/subsystems/README.md) [Chinese](../../docs/subsystems/wrong.zh.md)\n',
  47. )
  48. write(root, 'docs/subsystems/README.md', '# Subsystems\n')
  49. write(root, 'docs/subsystems/wrong.zh.md', '# Wrong\n')
  50. expect(auditSubsystemPages(root, {}).violations).toEqual([
  51. 'packages/wrong/README.md: no reader-visible direct docs/subsystems/*.md link; add the owning page and link, or add a justified GROUPS_WITHOUT_SUBSYSTEM_PAGE entry',
  52. ])
  53. })
  54. it('does not count links hidden in code, comments, or image syntax', () => {
  55. const root = fixture()
  56. write(
  57. root,
  58. 'packages/hidden/README.md',
  59. [
  60. '`[inline](../../docs/subsystems/hidden.md)`',
  61. '```md',
  62. '[fenced](../../docs/subsystems/hidden.md)',
  63. '```',
  64. '<!-- [comment](../../docs/subsystems/hidden.md) -->',
  65. '![image](../../docs/subsystems/hidden.md)',
  66. '',
  67. ].join('\n'),
  68. )
  69. write(root, 'docs/subsystems/hidden.md', '# Hidden\n')
  70. expect(auditSubsystemPages(root, {}).violations).toEqual([
  71. 'packages/hidden/README.md: no reader-visible direct docs/subsystems/*.md link; add the owning page and link, or add a justified GROUPS_WITHOUT_SUBSYSTEM_PAGE entry',
  72. ])
  73. })
  74. it('rejects a link that escapes the subsystem directory', () => {
  75. const root = fixture()
  76. write(root, 'packages/escape/README.md', '[escape](../../docs/subsystems/../architecture.md)\n')
  77. write(root, 'docs/architecture.md', '# Architecture\n')
  78. expect(auditSubsystemPages(root, {}).violations).toEqual([
  79. 'packages/escape/README.md: no reader-visible direct docs/subsystems/*.md link; add the owning page and link, or add a justified GROUPS_WITHOUT_SUBSYSTEM_PAGE entry',
  80. ])
  81. })
  82. it('rejects missing group READMEs and missing linked pages', () => {
  83. const root = fixture()
  84. write(root, 'packages/no-readme/pkg/package.json', '{}\n')
  85. write(root, 'packages/broken/README.md', '[missing](../../docs/subsystems/missing.md)\n')
  86. expect(auditSubsystemPages(root, {}).violations).toEqual([
  87. 'packages/broken/README.md: linked subsystem page does not exist: docs/subsystems/missing.md',
  88. 'packages/no-readme/README.md: package group has no group README declaring subsystem ownership',
  89. ])
  90. })
  91. it('rejects blank, orphaned, and stale exemptions', () => {
  92. const root = fixture()
  93. write(root, 'packages/linked/README.md', '[types](../../docs/subsystems/linked.md)\n')
  94. write(root, 'docs/subsystems/linked.md', '# Linked\n')
  95. write(root, 'packages/blank/README.md', '# Blank\n')
  96. expect(auditSubsystemPages(root, {
  97. blank: ' ',
  98. linked: 'No page.',
  99. orphan: 'Removed group.',
  100. }).violations).toEqual([
  101. 'exemption blank: missing justification for omitting a subsystem page',
  102. 'exemption orphan: no matching package group; remove the stale entry',
  103. 'packages/linked/README.md: links a subsystem page but remains exempt; remove the stale exemption',
  104. ])
  105. })
  106. })