Files
wehub-resource-sync 6db8fca185
docmd CI verification / verify (push) Failing after 0s
chore: import upstream snapshot with attribution
2026-07-13 12:31:55 +08:00

152 lines
6.3 KiB
JavaScript

/**
* --------------------------------------------------------------------
* docmd : the zero-config documentation engine.
*
* #167 — Offline-mode internal links must work in every hosting shape.
*
* The bug: in `--offline` builds, the markdown link processor emitted
* absolute paths (`<a href="/destination/">`) that only resolve on an
* HTTP server, breaking `file://` access. The button container was
* offline-aware but markdown links were not.
*
* The fix: the markdown processor now post-processes the rendered HTML
* with the same `fixHtmlLinks` logic the button uses, so internal hrefs
* are rewritten to relative `.html` paths in offline mode. Normal builds
* are unchanged (clean URLs preserved).
*
* Run: `node tests/runner.js --only=offline-links`
* --------------------------------------------------------------------
*/
import {
DOCMD,
setup,
writeFile,
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}`);
}
}
function extractHrefs(html) {
const out = [];
// Match `href=` on `<a>` and `src=` on `<img>`. Both can target internal
// routes and both need to be checked for the offline-mode rewrite.
const re = /<a\s+[^>]*?\bhref\s*=\s*("([^"]*)"|'([^']*)')|<img\s+[^>]*?\bsrc\s*=\s*("([^"]*)"|'([^']*)')/gi;
let m;
while ((m = re.exec(html)) !== null) {
out.push(m[2] !== undefined ? m[2] : (m[3] !== undefined ? m[3] : (m[4] !== undefined ? m[4] : m[5])));
}
return out;
}
export const test = runTestFile({
name: 'Offline-mode internal links work in every hosting shape (#167)',
emoji: '🔗',
run: () => {
// Root page (index.html at the build root) — the original bug repro.
// Before the fix: offline build emitted `<a href="/destination/">` for
// the markdown link. After: `./destination/index.html`.
{
const dir = setup('offline-links-31-167-root-page');
writeFile(dir, 'docs/index.md', '# Home\n\n[link](/destination.md)\n');
writeFile(dir, 'docs/destination.md', '# Destination\n');
execSync(`node ${DOCMD} build --offline`, { cwd: dir, stdio: 'pipe' });
const html = fs.readFileSync(path.join(dir, 'site/index.html'), 'utf8');
const hrefs = extractHrefs(html);
assert(hrefs.includes('./destination/index.html'), 'M-2: root-page markdown link rewritten to relative .html (offline mode)');
assert(!hrefs.some((h) => h === '/destination/'), 'M-2: no absolute "/destination/" path leaked into offline HTML');
// File existence check — the file:// test.
const target = path.resolve(dir, 'site/destination/index.html');
assert(fs.existsSync(target), 'M-2: rewritten target file actually exists on disk');
}
// Nested page (site/api/index.html) — depth-adjusted relative paths.
// The fix must add `../` so the path navigates up correctly from the
// subfolder in offline mode.
{
const dir = setup('offline-links-31-167-nested-page');
writeFile(dir, 'docs/api/index.md', '# API\n\n[link](/destination.md)\n');
writeFile(dir, 'docs/destination.md', '# Destination\n');
execSync(`node ${DOCMD} build --offline`, { cwd: dir, stdio: 'pipe' });
const html = fs.readFileSync(path.join(dir, 'site/api/index.html'), 'utf8');
const hrefs = extractHrefs(html);
assert(hrefs.includes('../destination/index.html'), 'M-2: nested-page markdown link gets "../" prefix (offline mode)');
assert(!hrefs.some((h) => h === '/destination/'), 'M-2: no absolute path on the nested page either');
const target = path.resolve(dir, 'site/destination/index.html');
assert(fs.existsSync(target), 'M-2: nested-page target resolves correctly on disk');
}
// Normal (non-offline) build must be UNCHANGED — clean URLs preserved.
// The fix must not regress HTTP-server deployments.
{
const dir = setup('offline-links-31-167-non-offline-unchanged');
writeFile(dir, 'docs/index.md', '# Home\n\n[link](/destination.md)\n');
writeFile(dir, 'docs/destination.md', '# Destination\n');
execSync(`node ${DOCMD} build`, { cwd: dir, stdio: 'pipe' });
const html = fs.readFileSync(path.join(dir, 'site/index.html'), 'utf8');
const hrefs = extractHrefs(html);
// In non-offline mode, the markdown link stays as the canonical
// clean URL (no .html suffix). The button container may rewrite
// its own href to a relative clean URL — both are fine.
assert(!hrefs.some((h) => h.endsWith('.html') && !h.includes('#')), 'M-2: non-offline build still emits clean URLs (no .html suffix on internal hrefs)');
}
// External, hash-only, and asset hrefs must NEVER be rewritten.
{
const dir = setup('offline-links-31-167-passthrough');
writeFile(dir, 'docs/index.md', [
'# Home',
'',
'[external](https://example.com/page)',
'[anchor](#section)',
'![img](/assets/img.png)',
'[internal](/destination.md)',
''
].join('\n'));
writeFile(dir, 'docs/destination.md', '# Destination\n');
execSync(`node ${DOCMD} build --offline`, { cwd: dir, stdio: 'pipe' });
const html = fs.readFileSync(path.join(dir, 'site/index.html'), 'utf8');
const hrefs = extractHrefs(html);
assert(hrefs.includes('https://example.com/page'), 'M-2: external https URL is unchanged');
assert(hrefs.some((h) => h === '#section' || h.startsWith('#section')), 'M-2: hash-only anchor is unchanged');
// Asset paths ARE rewritten to relative for offline (file:// needs
// relative paths even for images), but the `/` prefix is dropped so
// they resolve to a real file on disk.
assert(hrefs.some((h) => h.includes('assets/img.png') && !h.startsWith('/assets/')), 'M-2: asset path is rewritten to relative (no leading /) for file://');
assert(hrefs.includes('./destination/index.html'), 'M-2: internal href rewritten to relative .html');
}
}
});
export const results = {
get passed() { return passed; },
get failed() { return failed; },
get failures() { return [...failures]; }
};