162 lines
6.0 KiB
JavaScript
162 lines
6.0 KiB
JavaScript
/**
|
|
* --------------------------------------------------------------------
|
|
* docmd : the zero-config documentation engine.
|
|
*
|
|
* Phase 3 PR 3.C — Validate rewrite + workspace errors + init example
|
|
*
|
|
* Tests:
|
|
* - M-1: `docmd validate` trailing-slash false-positives
|
|
* - F8: workspace validation throws raw stack traces
|
|
* - F9: default init `index.md` has a broken `::: button` example
|
|
*
|
|
* Run: `node tests/runner.js`
|
|
* --------------------------------------------------------------------
|
|
*/
|
|
|
|
import {
|
|
DOCMD,
|
|
setup,
|
|
writeFile,
|
|
exitCodeOf,
|
|
runTestFile
|
|
} from '../shared.js';
|
|
import { execSync } from 'node:child_process';
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
|
|
let passed = 0;
|
|
let failed = 0;
|
|
const failures = [];
|
|
|
|
function assert(condition, message) {
|
|
if (!condition) {
|
|
failed++;
|
|
failures.push(message);
|
|
console.log(` ❌ ${message}`);
|
|
} else {
|
|
passed++;
|
|
console.log(` ✅ ${message}`);
|
|
}
|
|
}
|
|
|
|
export const test = runTestFile({
|
|
name: 'Validate rewrite + workspace errors + init example (Phase 3 PR 3.C — F8, F9, M-1)',
|
|
emoji: '📐',
|
|
run: () => {
|
|
|
|
// M-1 — `validate` must not false-positive on a valid link with a
|
|
// trailing slash. Pre-fix: `[page 2](/page-2/)` was reported as
|
|
// broken because the code did `fs.existsSync('docs/page-2/.md')`
|
|
// (always false). Post-fix: the trailing slash is stripped before
|
|
// the existence checks, and the link resolves to `docs/page-2.md`.
|
|
{
|
|
const dir = setup('validate-workspace-28-m1-trailing-slash-ok');
|
|
writeFile(dir, 'docs/index.md', '# P1\n\n[page 2](/page-2/).\n');
|
|
writeFile(dir, 'docs/page-2.md', '# P2\n');
|
|
const code = exitCodeOf(`node ${DOCMD} validate`, dir);
|
|
assert(code === 0, 'M-1: trailing-slash link to existing .md is valid (exit 0)');
|
|
}
|
|
|
|
// M-1 — genuine broken link is still caught.
|
|
{
|
|
const dir = setup('validate-workspace-28-m1-broken-still-caught');
|
|
writeFile(dir, 'docs/index.md', '# P1\n\n[bad](/nope/)\n');
|
|
const code = exitCodeOf(`node ${DOCMD} validate`, dir);
|
|
assert(code === 1, 'M-1: genuinely broken link still exits 1');
|
|
}
|
|
|
|
// F8 — workspace validation errors exit 1 with a clean TUI message,
|
|
// not a raw stack trace.
|
|
{
|
|
const dir = setup('validate-workspace-28-f8-workspace-error');
|
|
writeFile(dir, 'docmd.config.json', JSON.stringify({
|
|
title: 'F8',
|
|
src: './docs',
|
|
out: './site',
|
|
workspace: {
|
|
projects: [{ src: './totally-missing-f8', prefix: '/' }]
|
|
}
|
|
}));
|
|
const code = exitCodeOf(`node ${DOCMD} build`, dir);
|
|
assert(code === 1, 'F8: workspace with missing source dir exits 1');
|
|
}
|
|
|
|
// F8 — duplicate-prefix error also exits 1.
|
|
{
|
|
const dir = setup('validate-workspace-28-f8-workspace-duplicate');
|
|
writeFile(dir, 'docmd.config.json', JSON.stringify({
|
|
title: 'F8 dup',
|
|
src: './docs',
|
|
out: './site',
|
|
workspace: {
|
|
projects: [
|
|
{ src: './docs', prefix: '/api' },
|
|
{ src: './docs', prefix: '/api' }
|
|
]
|
|
}
|
|
}));
|
|
const code = exitCodeOf(`node ${DOCMD} build`, dir);
|
|
assert(code === 1, 'F8: duplicate workspace prefix exits 1');
|
|
}
|
|
|
|
// F9 — default init `index.md` must NOT contain the F2 trap pattern
|
|
// (`::: card ... ::: button ... :::` with the orphan `:::`).
|
|
// We check the bundled `initProject` source rather than running
|
|
// `init` (which would write to disk) — we just confirm the
|
|
// template string no longer teaches the F2 pattern.
|
|
{
|
|
const initSrc = fs.readFileSync(
|
|
path.resolve(import.meta.dirname, '..', '..', 'packages/core/dist/commands/init.js'),
|
|
'utf8'
|
|
);
|
|
// The broken pattern was: `::: button "View Documentation" https://...`
|
|
// followed by a `:::` close. The replacement uses
|
|
// `[View the docs →](https://docmd.io){.docmd-button}` instead.
|
|
assert(!/button "View Documentation"/.test(initSrc), 'F9: bundled init no longer contains broken `::: button ... :::` trap');
|
|
assert(/\{\.docmd-button\}/.test(initSrc), 'F9: bundled init uses `{.docmd-button}` styled link');
|
|
}
|
|
|
|
// M-2 — workspace config without a root project must show an
|
|
// actionable error message: explains the requirement, shows a
|
|
// valid example, and points at the docs. The old message was
|
|
// a single bare sentence with no example.
|
|
{
|
|
const dir = setup('validate-workspace-28-m2-workspace-no-root-message');
|
|
// The source dirs must exist so the per-project existence check
|
|
// does not fire first and mask the "no root project" failure.
|
|
writeFile(dir, 'docs/index.md', '# Root\n');
|
|
writeFile(dir, 'docmd.config.json', JSON.stringify({
|
|
title: 'M2',
|
|
src: './docs',
|
|
out: './site',
|
|
workspace: {
|
|
projects: [
|
|
{ src: './docs', prefix: '/api/' },
|
|
{ src: './docs', prefix: '/blog/' }
|
|
]
|
|
}
|
|
}));
|
|
let captured = '';
|
|
let code = -1;
|
|
try {
|
|
execSync(`node ${DOCMD} build`, { cwd: dir, stdio: 'pipe', encoding: 'utf8' });
|
|
} catch (e) {
|
|
code = e.status == null ? -1 : e.status;
|
|
const stderr = typeof e.stderr === 'string' ? e.stderr : (e.stderr ? e.stderr.toString() : '');
|
|
const stdout = typeof e.stdout === 'string' ? e.stdout : (e.stdout ? e.stdout.toString() : '');
|
|
captured = stderr + stdout;
|
|
}
|
|
assert(code === 1, 'M-2: workspace with no root project exits 1');
|
|
assert(/root project with prefix "\/"/.test(captured), 'M-2: error message states the root prefix requirement');
|
|
assert(/Example:/.test(captured), 'M-2: error message includes an Example block');
|
|
assert(/"prefix": "\/"/.test(captured), 'M-2: error message example shows prefix "/"');
|
|
assert(/docs\.docmd\.io/.test(captured), 'M-2: error message links to the workspace guide');
|
|
}
|
|
}
|
|
});
|
|
|
|
export const results = {
|
|
get passed() { return passed; },
|
|
get failed() { return failed; },
|
|
get failures() { return [...failures]; }
|
|
}; |