Files
Jyotisha/frontend/tests/chart-varga-table.test.tsx
T

126 lines
5.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<string, unknown>;
varga_full: { result: Record<string, { ascendant: Record<string, unknown>; planets: Record<string, Record<string, unknown>> }> };
chara: Record<string, unknown>;
};
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<ChartViewOk> {
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<string, unknown> };
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(/<tbody>([\s\S]*?)<\/tbody>/)?.[1] ?? "";
const hit = [...tbody.matchAll(/<tr>([\s\S]*?)<\/tr>/g)].map((match) => match[1]!).find((cells) => {
const header = cells.match(/<th scope="row">([\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宫</, "D9 Moon house counts from the D9 ascendant");
assert.notEqual(sunD9, row(d1Markup, "太阳"));
assert.notEqual(moonD9, row(d1Markup, "月亮"));
const ascD9 = row(d9Markup, "上升");
assert.match(ascD9, /射手/, "D9 ascendant is Sagittarius");
});
test("the D1 table is byte-for-byte the table the page drew before BUG-1076", async () => {
const view = await vargaView();
const markup = render(view, "D1");
const table = markup.match(/<div class="chart-page-planet-table-wrap">[\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(/<thead>([\s\S]*?)<\/thead>/)?.[1] ?? "";
for (const column of ["行星", "分盘星座", "宫位", "本命星座", "庙旺", "同位"]) assert.match(header, new RegExp(`>${column}<`));
assert.doesNotMatch(header, /星宿|宿主|pada/);
assert.match(row(markup, "土星"), />自宫<[\s\S]*>同位</);
assert.match(row(markup, "太阳"), />落陷</);
// Every divisional chart the engine returned has its own rows, D1 has none.
for (const varga of view.vedic.vargas) {
if (varga.id === "D1") assert.equal(varga.rows, undefined);
else assert.equal(varga.rows?.length, 9, varga.id);
}
});