Files
Jyotisha/frontend/tests/personal-report-markdown-view.test.ts
T
Jesse_Chen 809bdf13e9
Independent Staging Quality Gate / validate (push) Failing after 26m47s
Independent Staging Quality Gate / publish (push) Has been skipped
docs(report): record MD-page push SHA and keep markdown view locks
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-06 21:48:00 +08:00

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");
});