Files
Jyotisha/frontend/tests/personal-report-markdown-view.test.ts
T
jesse-uxandJesse_Chen 98c98aa535 feat(report): replace the reader edition with the full-data report
Generate the full-data edition (pl9_personal_long_report.v4) and remove the reader edition. English still contains Han, so the API withholds it (BUG-1272, investigating). This commit is not pushed to staging.
2026-10-08 22:29:31 +08:00

136 lines
5.3 KiB
TypeScript

import assert from "node:assert/strict";
import test from "node:test";
import React from "react";
import { renderToStaticMarkup } from "react-dom/server";
import { PersonalReportMarkdownView } from "../src/components/personal-report/personal-report-markdown-view.tsx";
import {
buildLongformOutline,
extractLongformSummary,
personalReportMarkdownFilename,
} from "../src/lib/personal-report-longform-outline.ts";
import { buildLongformCoverDocument } from "../src/lib/personal-report-longform-cover.ts";
import { safeParseServerReportDocument } from "../src/lib/personal-report-contract.server-core.ts";
Object.assign(globalThis, { React });
const SAMPLE = `# 标题
<script>alert(1)</script>
<img src="https://evil.example/x.png">
[外链](https://evil.example)
## 成品阅读导航
导航应立即出现。
### 质量验收矩阵
| 段落 | 状态 |
| --- | --- |
| KP | blocked |
## 主题解读与时间展开
### 解读摘要
这是摘要正文,用于卡片截取。
### 本命主轴
懒加载段落不应出现在首屏。
## 后段
后段正文。
`;
test("outline keeps 导航 and 摘要 eager and extracts the card summary", () => {
const outline = buildLongformOutline(SAMPLE);
assert.ok(outline.headings.some((heading) => heading.title === "成品阅读导航"));
assert.ok(outline.headings.some((heading) => heading.title === "质量验收矩阵"));
assert.ok(outline.headings.some((heading) => heading.title === "解读摘要"));
assert.match(outline.leadMarkdown, /成品阅读导航/);
assert.match(outline.leadMarkdown, /质量验收矩阵/);
assert.match(outline.leadMarkdown, /解读摘要/);
assert.equal(extractLongformSummary(SAMPLE), "这是摘要正文,用于卡片截取。");
const lazy = outline.sections.filter((section) => !section.eager);
assert.ok(lazy.some((section) => section.title === "主题解读与时间展开"));
assert.doesNotMatch(lazy.find((section) => section.title === "主题解读与时间展开")?.markdown ?? "", /解读摘要/);
});
test("markdown view skips HTML, blocks remote resources, wraps tables, and lazy-renders later H2s", () => {
const started = Date.now();
const markup = renderToStaticMarkup(React.createElement(PersonalReportMarkdownView, { markdown: SAMPLE }));
const elapsed = Date.now() - started;
assert.ok(elapsed < 1500, `first paint ${elapsed}ms`);
assert.doesNotMatch(markup, /<script>/);
assert.doesNotMatch(markup, /src="https:\/\/evil\.example/);
assert.doesNotMatch(markup, /href="https:\/\/evil\.example"/);
assert.match(markup, /personal-report-table-wrap/);
assert.match(markup, /成品阅读导航/);
assert.match(markup, /质量验收矩阵/);
assert.match(markup, /解读摘要/);
assert.match(markup, /这是摘要正文/);
assert.doesNotMatch(markup, /懒加载段落不应出现在首屏/);
assert.doesNotMatch(markup, /后段正文/);
assert.match(markup, /aria-label="报告目录"/);
});
test("long markdown pages one chapter and keeps the first paint small", () => {
const chapters = Array.from({ length: 80 }, (_, index) => {
const body = index === 0 ? `第一章正文${"字".repeat(80)}` : `秘密正文-${index}`;
return `## 第${index}章\n\n${body}\n`;
});
for (const size of [1_000_000, 3_500_000]) {
const markdown = `# 标题\n\n${chapters.join("\n")}${"补".repeat(size)}`;
const before = process.memoryUsage().heapUsed;
const started = Date.now();
const markup = renderToStaticMarkup(React.createElement(PersonalReportMarkdownView, { markdown }));
const elapsed = Date.now() - started;
const delta = process.memoryUsage().heapUsed - before;
assert.ok(elapsed < 1500, `size ${size} first paint ${elapsed}ms heap ${delta}`);
assert.match(markup, /第一章正文/);
assert.match(markup, /上一章/);
assert.match(markup, /下一章/);
assert.match(markup, /1 \/ 80/);
assert.doesNotMatch(markup, /秘密正文-79/);
assert.doesNotMatch(markup, /type="search"|placeholder="搜索"/);
assert.ok(delta < 120 * 1024 * 1024, `size ${size} heap delta ${delta}`);
}
});
test("export filename includes the report date", () => {
assert.equal(personalReportMarkdownFilename("2026-09-06T12:00:00.000Z"), "个人报告-2026-09-06");
});
test("cover stub is a legal v2 document and is not the reading body", () => {
const document = buildLongformCoverDocument({
report: {
id: "123e4567-e89b-12d3-a456-426614174000",
reportType: "personal_full",
presentationMode: "default",
depth: "standard",
requestedThemes: ["career", "wealth"],
skillName: "jyotish-personal-report",
skillVersion: "1.0.0",
skillSourceCommit: null,
skillSnapshotSha256: "a".repeat(64),
},
subject: {
displayName: "测试对象",
birthTimeStatus: "confirmed",
birthPlaceLabel: "测试地点",
},
markdown: SAMPLE,
generatedAt: "2026-09-06T12:00:00.000Z",
});
const parsed = safeParseServerReportDocument(document);
assert.equal(parsed.ok, true);
assert.equal(document.executiveSummary.summary, "这是摘要正文,用于卡片截取。");
assert.equal(document.charts.filter((chart) => chart.id === "D1").length, 1);
assert.equal(document.blockedConflictDisclosure.length, 2);
assert.equal(document.thematicNarrative.length, 0);
assert.equal(document.executiveSummary.overallClaimStatus, "parameter_sensitive");
});