测试文档只在需要的测试里安装。提示和圆点按项分开。清单「去做」走客户端跳转。输入框高亮在离开页面时清掉。六个没有入口的旧校正组件删掉。 Co-Authored-By: Grok (agent loop)
116 lines
6.2 KiB
TypeScript
116 lines
6.2 KiB
TypeScript
import "./install-test-document";
|
|
import assert from "node:assert/strict";
|
|
import { readFileSync } from "node:fs";
|
|
import test from "node:test";
|
|
import React from "react";
|
|
|
|
import { PersonalReportPage } from "../src/components/personal-report/personal-report-page";
|
|
import { SidebarProvider } from "../src/components/ui/sidebar";
|
|
import { assembleReportFactTables } from "../src/lib/report-fact-tables";
|
|
import { containsHan } from "../src/lib/report-language";
|
|
import { createClientLifecycleHarness } from "./react-client-lifecycle-test-support";
|
|
|
|
// BUG-1126: the reader and the fact tables were sibling elements keyed by the
|
|
// same language, so switching editions left the first edition's reader on
|
|
// screen and appended the new one below it. Real reports always carry fact
|
|
// tables, so the whole page is rendered with them. Fixtures are real engine
|
|
// output for a fictional birth (AGENTS §7-4).
|
|
Object.assign(globalThis, { React });
|
|
|
|
const engine = JSON.parse(readFileSync(new URL("./fixtures/report-density-fictional-engine.json", import.meta.url), "utf8"));
|
|
const en = JSON.parse(readFileSync(new URL("./fixtures/report-reader-main-fictional-en.json", import.meta.url), "utf8"));
|
|
const zh = JSON.parse(readFileSync(new URL("./fixtures/report-reader-main-fictional-zh-pair.json", import.meta.url), "utf8"));
|
|
|
|
const REPORT_ID = "00000000-0000-4000-8000-000000001126";
|
|
const BODY = {
|
|
report: { id: REPORT_ID, status: "ready", createdAt: "2026-09-30T00:00:00.000Z" },
|
|
englishEdition: "ready",
|
|
longformMarkdown: zh.markdown,
|
|
longformMarkdownEn: en.markdown,
|
|
factTables: assembleReportFactTables(engine),
|
|
factTablesEn: assembleReportFactTables(engine, "en"),
|
|
};
|
|
|
|
type Node = ReturnType<ReturnType<typeof createClientLifecycleHarness>["elements"]>[number];
|
|
const hasClass = (node: Node, name: string) => (node.getAttribute("class") ?? "").split(/\s+/).includes(name);
|
|
|
|
async function openReader(initialLanguage: "zh" | "en") {
|
|
const h = createClientLifecycleHarness();
|
|
const location = { href: `https://staging.example/reports/${REPORT_ID}${initialLanguage === "en" ? "?lang=en" : ""}` };
|
|
const history = { state: null, replaceState: (_state: unknown, _title: string, url: URL) => { location.href = String(url); } };
|
|
Object.assign(globalThis.window as object, { location, history });
|
|
Object.assign(globalThis.document as object, { hidden: false, querySelector: () => null, querySelectorAll: () => [] });
|
|
const originalFetch = globalThis.fetch;
|
|
// Lazy sections and the chapter jump schedule frames on the global; the harness only has them on window.
|
|
const frameWindow = globalThis.window as unknown as { requestAnimationFrame: typeof requestAnimationFrame; cancelAnimationFrame: typeof cancelAnimationFrame };
|
|
const originalFrames = { request: globalThis.requestAnimationFrame, cancel: globalThis.cancelAnimationFrame };
|
|
Object.assign(globalThis, { requestAnimationFrame: frameWindow.requestAnimationFrame, cancelAnimationFrame: frameWindow.cancelAnimationFrame });
|
|
globalThis.fetch = (async () => new Response(JSON.stringify(BODY), { status: 200, headers: { "content-type": "application/json" } })) as typeof fetch;
|
|
await h.render(<SidebarProvider><PersonalReportPage reportId={REPORT_ID} initialLanguage={initialLanguage} /></SidebarProvider>);
|
|
for (let i = 0; i < 5 && !h.elements().some(node => hasClass(node, "personal-report-md-layout")); i += 1) await h.idle();
|
|
const read = () => {
|
|
const layouts = h.elements().filter(node => hasClass(node, "personal-report-md-layout"));
|
|
const toc = h.elements().find(node => hasClass(node, "personal-report-toc-desktop"));
|
|
// 原值:选中的语言是 BUTTON 且 aria-pressed="true"
|
|
// 新值:分段页签 role=tab 且 aria-selected="true"
|
|
// 原因:K5 报告语言改用和星盘页盘型条一样的 Tabs。正文仍只能有一份
|
|
const pressed = h.elements().find(node => node.getAttribute("role") === "tab" && node.getAttribute("aria-selected") === "true");
|
|
// The article only: the 「到底部」 jump beside it is app chrome and stays Chinese (English edition decision 6).
|
|
const articles = h.elements().filter(node => hasClass(node, "personal-report-md-article"));
|
|
return { layouts: layouts.length, reader: articles.map(node => node.text).join(""), toc: toc?.childNodes[0]?.text, pressed: pressed?.text, url: location.href };
|
|
};
|
|
const pick = async (label: string) => {
|
|
const button = h.elements().find(node => node.getAttribute("role") === "tab" && node.text === label);
|
|
assert.ok(button, `${label} switch button`);
|
|
await h.event(button);
|
|
await h.idle();
|
|
};
|
|
const close = async () => {
|
|
globalThis.fetch = originalFetch;
|
|
Object.assign(globalThis, { requestAnimationFrame: originalFrames.request, cancelAnimationFrame: originalFrames.cancel });
|
|
await h.close();
|
|
};
|
|
return { h, read, pick, close };
|
|
}
|
|
|
|
test("switching editions replaces the reader, fact tables and all (BUG-1126)", async () => {
|
|
const page = await openReader("zh");
|
|
try {
|
|
let state = page.read();
|
|
assert.equal(state.layouts, 1);
|
|
assert.equal(state.toc, "目录");
|
|
assert.equal(state.pressed, "中文");
|
|
|
|
await page.pick("English");
|
|
state = page.read();
|
|
assert.equal(state.pressed, "English");
|
|
assert.match(state.url, /\?lang=en$/);
|
|
assert.equal(state.layouts, 1, "the Chinese reader must not stay on screen");
|
|
assert.equal(state.toc, "Contents");
|
|
assert.equal(containsHan(state.reader), false, "the English reader carries no Chinese");
|
|
|
|
await page.pick("中文");
|
|
state = page.read();
|
|
assert.equal(state.pressed, "中文");
|
|
assert.equal(state.layouts, 1, "the English reader must not stay on screen");
|
|
assert.equal(state.toc, "目录");
|
|
assert.equal(page.h.errors.length, 0);
|
|
} finally { await page.close(); }
|
|
});
|
|
|
|
test("a report opened in English switches back to Chinese (BUG-1126)", async () => {
|
|
const page = await openReader("en");
|
|
try {
|
|
let state = page.read();
|
|
assert.equal(state.layouts, 1);
|
|
assert.equal(state.toc, "Contents");
|
|
|
|
await page.pick("中文");
|
|
state = page.read();
|
|
assert.equal(state.layouts, 1);
|
|
assert.equal(state.toc, "目录");
|
|
assert.ok(containsHan(state.reader));
|
|
assert.doesNotMatch(state.url, /lang=en/);
|
|
} finally { await page.close(); }
|
|
});
|