Files
Jyotisha/frontend/tests/chart-view-zero-year.test.ts
T
jesse-ux e0c6070bef feat: fold dasha periods and redraw the western wheel
The dasha tab leads with a now card and a duration bar, and keeps sub-periods inside the current details element. A zero-year Chara row stays grey, shows 0 年, and cannot expand. The tropical wheel uses a 520 view box, element bands, separated planet labels, and aspect lines that stay visible while a selected body is highlighted.
2026-09-25 02:59:18 +08:00

124 lines
5.1 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:
* C:\Users\74082\anaconda3\python.exe
* 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,
qizheng: 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/);
assert.doesNotMatch(zero, /<details/);
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);
assert.doesNotMatch(tracks[1] ?? "", /<details[^>]*open[^>]*>[\s\S]*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/);
});