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.
124 lines
5.1 KiB
TypeScript
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/);
|
|
});
|