四个页签用下划线,盘型条用分段并可以横滑,全部盘型从底部打开。桌面上盘和参数可以拖宽,窄屏上下排。行星表仍跨满整行。换盘型时星体移到新位置;减少动态效果只淡入。第一次进场不重播。
自测:tsc 退出码 0;frontend 下 npm run lint 0 error(122 警告);定向测试 188 通过、0 失败、0 取消;npm run build 后 / 仍是 Static;首屏 JS gzip-9 为 640131,相对 d25dfdbe 记下的 639127 多 0.16%。没有跑全量 npm test、Python 快速门和 test:db。
Co-Authored-By: Grok (agent loop)
92 lines
3.7 KiB
TypeScript
92 lines
3.7 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { readFileSync } from "node:fs";
|
|
import React from "react";
|
|
import { renderToStaticMarkup } from "react-dom/server";
|
|
import test from "node:test";
|
|
|
|
import { ChartVedicTab } from "../src/components/chart-page/chart-vedic-tab.tsx";
|
|
import { glyphMotionKey, planGlyphMotion, type GlyphPoint } from "../src/components/chart-page/chart-glyph-motion.ts";
|
|
import { VedicChartSvg } from "../src/components/personal-report/vedic-chart-svg.tsx";
|
|
import { assembleChartView } from "../src/lib/chart-view-load.ts";
|
|
import type { ChartViewOk } from "../src/lib/chart-view-contract.ts";
|
|
|
|
Object.assign(globalThis, { React });
|
|
|
|
const golden = JSON.parse(readFileSync(new URL("./fixtures/chart-view-golden.json", import.meta.url), "utf8"));
|
|
|
|
const profile = {
|
|
name: "示例",
|
|
date: "1990-06-15",
|
|
time: "12:00",
|
|
placeLabel: "北京",
|
|
latitude: 39.9042,
|
|
longitude: 116.4074,
|
|
timezoneOffset: 8,
|
|
timezoneId: "Asia/Shanghai",
|
|
ayanamsa: "raman",
|
|
birthTimeStatus: "confirmed",
|
|
};
|
|
|
|
function glyphPoints(markup: string): Record<string, GlyphPoint> {
|
|
const points: Record<string, GlyphPoint> = {};
|
|
for (const match of markup.matchAll(/<text\b([^>]*)>([^<]*)</g)) {
|
|
const attrs = match[1] ?? "";
|
|
const className = attrs.match(/class="([^"]*)"/)?.[1] ?? "";
|
|
const tokens = className.split(/\s+/);
|
|
const kind = tokens.includes("personal-report-chart-asc")
|
|
? "asc"
|
|
: tokens.includes("personal-report-chart-glyph")
|
|
? "glyph"
|
|
: null;
|
|
if (!kind) continue;
|
|
points[glyphMotionKey(kind, match[2] ?? "")] = {
|
|
x: Number(attrs.match(/\bx="([^"]+)"/)?.[1]),
|
|
y: Number(attrs.match(/\by="([^"]+)"/)?.[1]),
|
|
};
|
|
}
|
|
return points;
|
|
}
|
|
|
|
async function view(): Promise<ChartViewOk> {
|
|
const result = await assembleChartView({
|
|
userId: "user-1",
|
|
profile,
|
|
asOf: "2026-09-15",
|
|
layers: ["varga"],
|
|
postEngine: async (path) => {
|
|
if (path === "/api/chart") return { status: "ok", payload: golden.chart };
|
|
if (path === "/api/varga_full") return { status: "ok", payload: golden.varga_full };
|
|
return { status: "http_error", path, elapsedMs: 1, httpStatus: 500 };
|
|
},
|
|
});
|
|
assert.equal(result.body.status, "ok");
|
|
return result.body as ChartViewOk;
|
|
}
|
|
|
|
test("switching chart type ends on the same glyph coordinates as a direct render", async () => {
|
|
const chart = await view();
|
|
const d1 = renderToStaticMarkup(React.createElement(ChartVedicTab, { view: chart, vargaId: "D1", onSelectVarga: () => {} }));
|
|
const d9 = renderToStaticMarkup(React.createElement(ChartVedicTab, { view: chart, vargaId: "D9", onSelectVarga: () => {} }));
|
|
const from = glyphPoints(d1);
|
|
const next = glyphPoints(d9);
|
|
const varga = chart.vedic.vargas.find((item) => item.id === "D9");
|
|
assert.ok(varga);
|
|
const direct = glyphPoints(renderToStaticMarkup(React.createElement(VedicChartSvg, {
|
|
chart: varga.chart,
|
|
ariaLabel: `${varga.id} 北印度盘 ${varga.meaning}`,
|
|
interactive: true,
|
|
})));
|
|
assert.deepEqual(next, direct);
|
|
const moved = planGlyphMotion(from, next, { firstPaint: false, reduced: false, chartTypeChanged: true });
|
|
assert.ok(moved.moves.length > 0);
|
|
for (const move of moved.moves) {
|
|
assert.deepEqual(move.to, next[move.key]);
|
|
assert.notDeepEqual(move.from, move.to);
|
|
}
|
|
assert.deepEqual(planGlyphMotion(from, next, { firstPaint: true, reduced: false, chartTypeChanged: true }).moves, []);
|
|
const reduced = planGlyphMotion(from, next, { firstPaint: false, reduced: true, chartTypeChanged: true });
|
|
assert.deepEqual(reduced.moves, []);
|
|
assert.equal(reduced.fadeOnly, true);
|
|
assert.equal(planGlyphMotion(null, next, { firstPaint: false, reduced: false, chartTypeChanged: false }).fadeOnly, false);
|
|
});
|