import { existsSync, readdirSync, readFileSync } from 'node:fs' import * as path from 'node:path' import { basename, dirname, join, relative, resolve } from 'node:path' import { fileURLToPath } from 'node:url' import { describe, expect, it } from 'vitest' const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..') const NUMBERED = /^(\d{3})-[\p{L}0-9-]+\.md$/u const numbered = readdirSync(join(ROOT, 'docs')).filter(name => NUMBERED.test(name)).sort() const entries = ['README.md', '设置.md'] const documents = [...entries, ...readdirSync(join(ROOT, 'docs')).filter(name => name.endsWith('.md')).map(name => `docs/${name}`)] const read = (path: string): string => readFileSync(join(ROOT, path), 'utf8') function links(file: string): string[] { return [...readFileSync(file, 'utf8').matchAll(/\]\(([^)\s]+)\)/g)].map(match => match[1]!.split('#')[0]!).filter(target => target && !/^[a-z]+:/.test(target)) } const files = (directory: string): string[] => readdirSync(join(ROOT, directory), { recursive: true, withFileTypes: true }) .filter(entry => entry.isFile()).map(entry => relative(ROOT, join(entry.parentPath, entry.name)) .split(path.sep) .join('/')) /** * Extract repo-relative file paths from the first column of a table under `## ` in `docs/007-架构.md`. * Bare filenames inherit the directory of the preceding path in the same cell. */ function listed(heading: string): string[] { const lines = read('docs/007-架构.md').split('\n') const start = lines.indexOf(`## ${heading}`) const end = lines.findIndex((line, index) => index > start && line.startsWith('## ')) // The first two rows are the header and its separator. const rows = lines.slice(start, end < 0 ? undefined : end).filter(line => line.startsWith('| ')).slice(2) return rows.flatMap(row => { let directory = '' return [...row.split('|')[1]!.matchAll(/`([^`]+)`/g)].map(([, name]) => { if (!name!.includes('/')) return join(directory, name!).split(path.sep).join('/') directory = dirname(name!) return name! }) }) } describe('documentation', () => { it('numbers documents consecutively from 000', () => { expect(numbered.map(name => name.slice(0, 3))).toEqual(numbered.map((_, index) => String(index).padStart(3, '0'))) }) it('links numbered documents only to earlier numbered documents', () => { const violations = numbered.flatMap(name => links(join(ROOT, 'docs', name)).flatMap(target => { const linked = resolve(ROOT, 'docs', target) if (!linked.endsWith('.md')) return [] const number = NUMBERED.exec(basename(linked))?.[1] return dirname(linked) === join(ROOT, 'docs') && number !== undefined && number < name.slice(0, 3) ? [] : [`${name} → ${target}`] })) expect(violations).toEqual([]) }) it('resolves every local link', () => { const files = [...entries.map(name => join(ROOT, name)), ...numbered.map(name => join(ROOT, 'docs', name))] const missing = files.flatMap(file => links(file).filter(target => !existsSync(resolve(dirname(file), target))).map(target => `${file.slice(ROOT.length + 1)} → ${target}`)) expect(missing).toEqual([]) }) it('leaves no merge conflict markers in any document', () => { expect(documents.filter(path => /^(<{7}|>{7}) /m.test(read(path)))).toEqual([]) }) it('lists every module of src in the module table of 007 Architecture, and only files that exist', () => { const modules = listed('Modules') expect(files('src').filter(path => !modules.includes(path))).toEqual([]) expect(modules.filter(path => !existsSync(join(ROOT, path)))).toEqual([]) }) it('lists every test in the test table of 007 Architecture, and only files that exist', () => { const tests = listed('Tests') expect(files('tests').filter(path => /\.spec\.tsx?$/.test(path) && !tests.includes(path))).toEqual([]) expect(tests.filter(path => !existsSync(join(ROOT, path)))).toEqual([]) }) })