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; chara: Record; }; 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): 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(" item.includes(CHARA_ZERO_YEAR_LABEL) && item.includes("射手")); assert.ok(zero); assert.match(zero, /is-zero/); assert.doesNotMatch(zero, /
!period.zeroYear).length); assert.equal(charaSegments.length, 11); assert.doesNotMatch(tracks[1] ?? "", /]*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), /