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 { assembleChartView } from "../src/lib/chart-view-load.ts"; import type { ChartViewOk } from "../src/lib/chart-view-contract.ts"; Object.assign(globalThis, { React }); // Real engine output (see the fixture's sourceCommand); nothing here is hand-shaped. const golden = JSON.parse( readFileSync(new URL("./fixtures/chart-view-golden.json", import.meta.url), "utf8"), ) as { chart: Record; varga_full: { result: Record; planets: Record> }> }; chara: Record; }; 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", }; async function vargaView(): Promise { 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 as unknown as Record }; return { status: "http_error", path, elapsedMs: 1, httpStatus: 500 }; }, }); assert.equal(result.body.status, "ok"); return result.body as ChartViewOk; } function render(view: ChartViewOk, vargaId: string): string { return renderToStaticMarkup(React.createElement(ChartVedicTab, { view, vargaId, onSelectVarga: () => {} })); } /** The table row whose header cell names this body. */ function row(markup: string, label: string): string { const tbody = markup.match(/([\s\S]*?)<\/tbody>/)?.[1] ?? ""; const hit = [...tbody.matchAll(/([\s\S]*?)<\/tr>/g)].map((match) => match[1]!).find((cells) => { const header = cells.match(/([\s\S]*?)<\/th>/)?.[1] ?? ""; return header.includes(label); }); assert.ok(hit, `no row for ${label}`); return hit.replace(//g, ""); } function degreeMinutes(degree: number): string { const whole = Math.floor(degree); const minutes = Math.floor((degree - whole) * 60); return `${whole}°${String(minutes).padStart(2, "0")}′`; } // BUG-1076: the D9 caption changed but every row stayed D1. test("a divisional chart's table carries that chart's signs, degrees and houses", async () => { const view = await vargaView(); const d9 = golden.varga_full.result.D9_Navamsa!; const d1Markup = render(view, "D1"); const d9Markup = render(view, "D9"); const sunD9 = row(d9Markup, "太阳"); const moonD9 = row(d9Markup, "月亮"); assert.match(sunD9, /天秤/, "D9 Sun is in Libra"); assert.ok(sunD9.includes(degreeMinutes(Number(d9.planets.Sun!.degree))), "D9 Sun degree is the in-varga degree"); assert.match(moonD9, /双鱼/, "D9 Moon is in Pisces"); assert.match(moonD9, />4宫 { const view = await vargaView(); const markup = render(view, "D1"); const table = markup.match(/
[\s\S]*?<\/table><\/div>/)?.[0]; // Rendered from the baseline commit 5dc977ef with the same golden. const baseline = readFileSync(new URL("./fixtures/chart-d1-table-baseline.html", import.meta.url), "utf8"); assert.equal(table, baseline); }); test("divisional rows carry engine dignity and vargottama, and drop nakshatra columns", async () => { const view = await vargaView(); const d9 = view.vedic.vargas.find((item) => item.id === "D9"); assert.ok(d9?.rows); const saturn = d9.rows.find((item) => item.name === "Saturn"); // D1 Saturn is in Capricorn and D9 Saturn is in Capricorn: own sign, vargottama. assert.equal(saturn?.sign, "Capricorn"); assert.equal(saturn?.dignity, "own"); assert.equal(saturn?.vargottama, true); const sun = d9.rows.find((item) => item.name === "Sun"); assert.equal(sun?.sign, "Libra"); assert.equal(sun?.dignity, "debilitated"); assert.equal(sun?.vargottama, false); const markup = render(view, "D9"); const header = markup.match(/([\s\S]*?)<\/thead>/)?.[1] ?? ""; for (const column of ["行星", "分盘星座", "宫位", "本命星座", "庙旺", "同位"]) assert.match(header, new RegExp(`>${column}<`)); assert.doesNotMatch(header, /星宿|宿主|pada/); assert.match(row(markup, "土星"), />自宫<[\s\S]*>同位落陷