四个页签用下划线,盘型条用分段并可以横滑,全部盘型从底部打开。桌面上盘和参数可以拖宽,窄屏上下排。行星表仍跨满整行。换盘型时星体移到新位置;减少动态效果只淡入。第一次进场不重播。
自测: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)
127 lines
5.3 KiB
TypeScript
127 lines
5.3 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { readFileSync } from "node:fs";
|
|
import React from "react";
|
|
import test from "node:test";
|
|
import { renderToStaticMarkup } from "react-dom/server";
|
|
|
|
import { ChartDashaTab } from "../src/components/chart-page/chart-dasha-tab.tsx";
|
|
import type { ChartViewOk } from "../src/lib/chart-view-contract.ts";
|
|
import { assembleChartView } from "../src/lib/chart-view-load.ts";
|
|
import { CHARA_ZERO_YEAR_LABEL, CHARA_ZERO_YEAR_NOTE } from "../src/lib/chart-view-labels.ts";
|
|
import { buildChartView, type ChartViewProfileInput } from "../src/lib/chart-view-mapper.ts";
|
|
|
|
Object.assign(globalThis, { React });
|
|
|
|
/**
|
|
* Generated from the real engine, not a hand-built shape, via the configured
|
|
* Python and JyotishAPIHandler.__new__()._compute_chart + _compute_chara_dasha.
|
|
* 1985-08-03 03:59, lat 39.9042, lon 116.4074, tz 8, ayanamsa raman, node_mode mean,
|
|
* skip_vedastro_main_entry_overview, antardasha true.
|
|
* Sagittarius duration_years is 0 (debilitated). scripts/jaimini.py was not modified.
|
|
*/
|
|
const golden = JSON.parse(readFileSync(new URL("./fixtures/chara-zero-year-1985-08-03-beijing.golden.json", import.meta.url), "utf8")) as {
|
|
chart: Record<string, unknown>;
|
|
chara: Record<string, unknown>;
|
|
};
|
|
|
|
const profile: ChartViewProfileInput = {
|
|
name: "虚构",
|
|
date: "1985-08-03",
|
|
time: "03:59",
|
|
placeLabel: "北京",
|
|
latitude: 39.9042,
|
|
longitude: 116.4074,
|
|
timezoneOffset: 8,
|
|
timezoneId: "Asia/Shanghai",
|
|
ayanamsa: "raman",
|
|
birthTimeStatus: "reported",
|
|
};
|
|
|
|
function viewFrom(chara: Record<string, unknown>): ChartViewOk {
|
|
return buildChartView({
|
|
chart: golden.chart,
|
|
varga: null,
|
|
chara,
|
|
western: null,
|
|
profile,
|
|
asOf: "2026-09-25",
|
|
});
|
|
}
|
|
|
|
test("a real zero-year Chara mahadasha keeps twelve continuous periods and does not throw", async () => {
|
|
const view = viewFrom(golden.chara);
|
|
const periods = view.dasha.chara.periods;
|
|
assert.equal(periods.length, 12);
|
|
const sagittarius = periods.find((period) => period.sign === "Sagittarius");
|
|
assert.ok(sagittarius);
|
|
assert.equal(sagittarius.zeroYear, true);
|
|
assert.equal(sagittarius.antardashas.length, 0);
|
|
assert.equal(sagittarius.start, sagittarius.end);
|
|
for (let index = 0; index < periods.length - 1; index += 1) {
|
|
assert.equal(periods[index]?.end, periods[index + 1]?.start);
|
|
}
|
|
const assembled = await assembleChartView({
|
|
userId: "synthetic",
|
|
accountId: "synthetic",
|
|
profileFingerprint: "fixture-zero-year",
|
|
profile,
|
|
asOf: "2026-09-25",
|
|
layers: ["chara"],
|
|
postEngine: async (path) => {
|
|
if (path === "/api/chart") return { status: "ok", payload: golden.chart };
|
|
if (path === "/api/dasha/chara") return { status: "ok", payload: golden.chara };
|
|
return { status: "http_error", path, elapsedMs: 0, httpStatus: 404 };
|
|
},
|
|
});
|
|
assert.equal(assembled.httpStatus, 200);
|
|
assert.equal(assembled.body.status, "ok");
|
|
});
|
|
|
|
test("zero antardasha weights on a positive mahadasha do not produce an invalid date", () => {
|
|
const chara = structuredClone(golden.chara) as {
|
|
result: { chara_dasha: { dasha_sequence: Array<{ duration_years: number; antardashas?: Array<{ duration_years: number }> }> } };
|
|
};
|
|
const positive = chara.result.chara_dasha.dasha_sequence.find((row) => row.duration_years > 0);
|
|
assert.ok(positive?.antardashas?.length);
|
|
for (const antar of positive.antardashas ?? []) antar.duration_years = 0;
|
|
const view = viewFrom(chara);
|
|
const mapped = view.dasha.chara.periods.find((period) => period.sign !== "Sagittarius" && period.antardashas.length > 0);
|
|
assert.ok(mapped);
|
|
for (const antar of mapped.antardashas) {
|
|
assert.match(antar.start, /^\d{4}-\d{2}-\d{2}$/);
|
|
assert.match(antar.end, /^\d{4}-\d{2}-\d{2}$/);
|
|
}
|
|
});
|
|
|
|
test("a zero-year period is grey, closed, and absent from the timeline", () => {
|
|
const view = viewFrom(golden.chara);
|
|
const markup = renderToStaticMarkup(React.createElement(ChartDashaTab, { view }));
|
|
const zero = markup.split("<li").find((item) => item.includes(CHARA_ZERO_YEAR_LABEL) && item.includes("射手"));
|
|
assert.ok(zero);
|
|
assert.match(zero, /is-zero/);
|
|
// 原值:零年行没有 `<details`
|
|
// 新值:零年行没有 Collapsible,展开中的行也不包含「0 年」
|
|
// 原因:K3 大运改用 Collapsible。零年仍然不能展开。
|
|
assert.doesNotMatch(zero, /<details|data-slot="collapsible"/);
|
|
assert.match(zero, new RegExp(CHARA_ZERO_YEAR_NOTE));
|
|
const tracks = markup.split("chart-page-dasha-track").slice(1);
|
|
assert.equal(tracks.length, 2);
|
|
const charaSegments = tracks[1]?.match(/data-dasha-segment=/g) ?? [];
|
|
assert.equal(charaSegments.length, view.dasha.chara.periods.filter((period) => !period.zeroYear).length);
|
|
assert.equal(charaSegments.length, 11);
|
|
for (const piece of (tracks[1] ?? "").split("<li")) {
|
|
if (piece.includes("data-open")) assert.doesNotMatch(piece, /0 年/);
|
|
}
|
|
});
|
|
|
|
test("the zero-year Chara row shows 0 年 and does not render sub-periods", () => {
|
|
const view = viewFrom(golden.chara);
|
|
const markup = renderToStaticMarkup(React.createElement(ChartDashaTab, { view }));
|
|
const marker = markup.indexOf(CHARA_ZERO_YEAR_LABEL);
|
|
const note = markup.indexOf(CHARA_ZERO_YEAR_NOTE, marker);
|
|
assert.ok(marker >= 0 && note > marker);
|
|
const slice = markup.slice(Math.max(0, marker - 40), note);
|
|
assert.match(slice, /射手/);
|
|
assert.doesNotMatch(markup.slice(marker, note), /<ul/);
|
|
});
|