fix(reports): strip chart fences across newline styles

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
jesse-ux
2026-09-24 18:27:14 +08:00
co-authored by Claude Code
parent 5ec29eb736
commit fa8fef559b
7 changed files with 70 additions and 8 deletions
+1 -1
View File
@@ -80,7 +80,7 @@ const SIGN_NUMBER: Record<string, number> = {
双鱼座: 12,
};
const FENCE_RE = /(?:\n[ \t]*)?```jyotish-chart\n[\s\S]*?```(?:\n[ \t]*)?/g;
const FENCE_RE = /(?:\r?\n[ \t]*)?```jyotish-chart\r?\n[\s\S]*?```(?:\r?\n[ \t]*)?/g;
export const reportChartBlockSchema = z.strictObject({
version: z.literal(1),
+12
View File
@@ -100,6 +100,18 @@ test("stripReportChartBlocks removes fences and keeps engine SVGs", () => {
assert.equal((stripped.match(/<svg/g) ?? []).length, (markdown.match(/<svg/g) ?? []).length);
});
test("stripReportChartBlocks removes chart fences from CRLF and mixed-newline input", () => {
const lf = wrapMarkdown(fixture.blocks);
for (const markdown of [
lf.replace(/\n/g, "\r\n"),
lf.split("\n").map((line, index) => index % 2 ? `${line}\r` : line).join("\n"),
]) {
const stripped = stripReportChartBlocks(markdown);
assert.doesNotMatch(stripped, /```jyotish-chart/);
assert.equal((stripped.match(/<svg/g) ?? []).length, (markdown.match(/<svg/g) ?? []).length);
}
});
test("house polygons fill the square without overlapping interiors", () => {
const total = HOUSE_NUMBERS.reduce((sum, houseNumber) => sum + polygonArea(HOUSE_POLYGONS[houseNumber]), 0);
assert.ok(Math.abs(total - CHART_SIZE * CHART_SIZE) < 1e-6, `area ${total}`);
@@ -1,4 +1,5 @@
import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { readFileSync } from "node:fs";
import test from "node:test";
import { buildReportExportBlocks, chartBlockText, exportFilename, factTableToMarkdown, joinExportBlocks, presetSelection } from "../src/lib/report-export-blocks";
@@ -54,6 +55,24 @@ test("export counts actual Unicode characters, not bytes or estimates", () => {
test("full golden body is byte identical to the retired ordinary export", () => {
assert.equal(joinExportBlocks(body), ordinaryReportDownloadMarkdown(fixture.markdown));
});
test("LF golden ordinary export keeps its frozen output hash", () => {
const output = ordinaryReportDownloadMarkdown(fixture.markdown);
assert.equal(createHash("sha256").update(output).digest("hex"), "3c52e8bfcfcb3ce3852bd5323867a69bc47d0f9aa821a34692172e7d61c6bcb8");
});
test("CRLF and mixed-newline golden exports remove every JSON chart fence", () => {
const variants = [
fixture.markdown.replace(/\n/g, "\r\n"),
fixture.markdown.split("\n").map((line: string, index: number) => index % 2 ? `${line}\r` : line).join("\n"),
];
for (const markdown of variants) {
const output = ordinaryReportDownloadMarkdown(markdown);
assert.equal((output.match(/```jyotish-chart/g) ?? []).length, 0);
assert.equal((output.match(/<svg\b/gi) ?? []).length, 22);
assert.deepEqual(ordinaryOutputLeaks(output), []);
}
});
test("all-group export adds charts and tables after unchanged ordinary body", () => {
assert.ok(joinExportBlocks(blocks).startsWith(ordinaryReportDownloadMarkdown(fixture.markdown) + "\n\n####"));
assert.notEqual(joinExportBlocks(blocks), joinExportBlocks(body));