Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
126 lines
5.2 KiB
TypeScript
126 lines
5.2 KiB
TypeScript
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);
|
||
}
|
||
});
|