113 lines
4.2 KiB
TypeScript
113 lines
4.2 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("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");
|
|
});
|