/** * front matter 解析器回归测试:拿仓库里全部真实文章跑无损往返。 * * node test/frontmatter-roundtrip.mjs [contentDir] * * 断言三件事: * 1. split → join 能**逐字节**还原原文件(说明切分没吃掉任何字符) * 2. parse → stringify → parse 后对象与首次解析**深相等**(说明丢了不信息) * 3. 记录所有解析失败的样本,便于判断是否需要补语法 */ import fs from 'node:fs'; import path from 'node:path'; import { splitFrontMatter, joinFrontMatter, parse, stringify, deepEqual } from '../src/frontmatter.mjs'; const root = process.argv[2] || path.resolve(import.meta.dirname, '../../content/posts'); function walk(dir, out = []) { for (const name of fs.readdirSync(dir)) { const p = path.join(dir, name); const st = fs.statSync(p); if (st.isDirectory()) walk(p, out); else if (name.endsWith('.md')) out.push(p); } return out; } const files = walk(root); let ok = 0; const byteFail = []; const parseFail = []; const roundFail = []; for (const f of files) { const text = fs.readFileSync(f, 'utf8'); const rel = path.relative(root, f); const { raw, body, eol } = splitFrontMatter(text); if (raw == null) { parseFail.push([rel, '没有 front matter']); continue; } // 1. 逐字节还原(含原始换行风格) if (joinFrontMatter(raw, body, eol) !== text) byteFail.push(rel); // 2. 解析 + 往返 let first; try { first = parse(raw); } catch (e) { parseFail.push([rel, e.message]); continue; } let second; try { second = parse(stringify(first)); } catch (e) { roundFail.push([rel, 'stringify/parse 失败: ' + e.message]); continue; } if (!deepEqual(first, second)) { const diff = Object.keys(first).concat(Object.keys(second)).filter( (k, i, a) => a.indexOf(k) === i && !deepEqual(first[k], second[k]), ); roundFail.push([rel, '字段不一致: ' + diff.join(', ')]); continue; } ok++; } const total = files.length; console.log('样本总数 :', total); console.log('解析 + 往返通过 :', ok); console.log('切分不还原 :', byteFail.length); console.log('解析失败 :', parseFail.length); console.log('往返不一致 :', roundFail.length); const show = (title, arr) => { if (!arr.length) return; console.log('\n--- ' + title + ' ---'); for (const [f, why] of arr.slice(0, 12)) console.log(' ' + f + ' || ' + why); if (arr.length > 12) console.log(' … 另有 ' + (arr.length - 12) + ' 条'); }; show('切分不还原(必须为 0)', byteFail.map((f) => [f, 'join != 原文'])); show('解析失败', parseFail); show('往返不一致', roundFail); const bad = byteFail.length + parseFail.length + roundFail.length; process.exit(bad === 0 ? 0 : 1);