import assert from "node:assert/strict"; import { spawnSync } from "node:child_process"; import { readFileSync } from "node:fs"; import test from "node:test"; import { fileURLToPath } from "node:url"; import { cleanReaderAppendixMarkdown } from "../src/lib/reader-appendix-language.ts"; const golden = JSON.parse(readFileSync(new URL("./fixtures/report-density-fictional-reader.json", import.meta.url), "utf8")); // Vocabulary-only cases, not invented calculation fixtures. const pageCases = [ "PL9 第 42 页", "PL9第42页", "PL9.pdf 第 42 页", "PL9.pdf第42页后文", "参见PL9 第 42 页。", "PL9 第 43–44 页", "PL9 第 40 / 48 页", "PL9 pages 99-100", "PL9 p-42", "PL9第42页中的原始字段", ]; const chartFence = ' ```jyotish-chart\r\n{"title":"PL9 第 42 页 parameter_sensitive","id":"D1"}\r\n ```\r\n'; const copyCases = [ ...pageCases, "中文parameter_sensitive中文 PyJHora中文 cmd_full_reading后文", "some_parameter_sensitive_field PL99 XPL9 PL9_suffix", "The blocked / executed / available / computed result is not a claim.", "| blocked | `executed` | available | computed | sign_cn | 12.50 |\n", `parameter_sensitive\r\n${chartFence}| parameter_sensitive |\r\n`, ]; function pythonClean(inputs: string[]): string[] { const script = [ "import json, sys", "from scripts.reader_appendix_language import clean_reader_appendix_markdown", "sys.stdout.write(json.dumps([clean_reader_appendix_markdown(value) for value in json.load(sys.stdin)], ensure_ascii=False))", ].join("\n"); const result = spawnSync(process.env.PYTHON ?? (process.platform === "win32" ? "python" : "python3"), ["-c", script], { cwd: fileURLToPath(new URL("../../", import.meta.url)), encoding: "utf8", input: JSON.stringify(inputs), maxBuffer: 16 * 1024 * 1024, env: { ...process.env, PYTHONIOENCODING: "utf-8", PYTHONDONTWRITEBYTECODE: "1" }, }); assert.equal(result.status, 0, `${result.error ?? ""}\n${result.stderr}`); return JSON.parse(result.stdout); } test("appendix Python and TS share byte-identical golden and Chinese-boundary output", () => { assert.equal(golden.fixtureProvenance.fictional, true); const inputs = [golden.markdown, golden.reader_dasha_applicability, ...copyCases] as string[]; const python = pythonClean(inputs); for (const [index, input] of inputs.entries()) { const ts = cleanReaderAppendixMarkdown(input); assert.deepEqual(Buffer.from(python[index], "utf8"), Buffer.from(ts, "utf8"), `case ${index}`); assert.equal(cleanReaderAppendixMarkdown(ts), ts, `idempotent case ${index}`); } assert.doesNotMatch(python[0], /第\s*\d+(?:\s*[–—/-]\s*\d+)*\s*页/); assert.doesNotMatch(cleanReaderAppendixMarkdown(golden.markdown), /第\s*\d+(?:\s*[–—/-]\s*\d+)*\s*页/); }); test("appendix page references disappear without swallowing adjacent Chinese prose", () => { for (const source of pageCases) { const output = cleanReaderAppendixMarkdown(source); assert.doesNotMatch(output, /PL9|第\s*\d+|pages?\s*\d+|p-42/); } assert.equal(cleanReaderAppendixMarkdown("参见PL9第42页中的原始字段"), "参见外部参照资料中的原始字段"); assert.equal(cleanReaderAppendixMarkdown("PL9.pdf第42页后文"), "外部参照资料后文"); assert.equal(cleanReaderAppendixMarkdown("some_parameter_sensitive_field PL99 XPL9"), "some_parameter_sensitive_field PL99 外部参照资料"); }); test("appendix golden preserves line and table shape, field names, numeric cells and chart bytes", () => { const raw = golden.markdown as string; const clean = cleanReaderAppendixMarkdown(raw); const sourceLines = raw.split("\n"); const cleanLines = clean.split("\n"); assert.equal(cleanLines.length, sourceLines.length); for (const [index, line] of sourceLines.entries()) { if (!line.startsWith("|")) continue; const cells = line.split("|"); const outputCells = cleanLines[index].split("|"); assert.equal(outputCells.length, cells.length, `table columns at line ${index}`); cells.forEach((cell, column) => { if (/^\s*-?\d+(?:\.\d+)?\s*$/.test(cell)) assert.equal(outputCells[column], cell); }); } for (const field of ["sign_cn", "degree_in_sign", "nakshatra_lord"]) { assert.equal(clean.split(field).length, raw.split(field).length); } const fences = (text: string) => [...text.matchAll(/^[ \t]*```jyotish-chart[^\n]*\n[\s\S]*?^[ \t]*```[^\n]*(?:\n|$)/gm)].map(match => Buffer.from(match[0])); assert.deepEqual(fences(clean), fences(raw)); assert.deepEqual(fences(cleanReaderAppendixMarkdown(copyCases.at(-1)!)), [Buffer.from(chartFence)]); });