Files
Jyotisha/frontend/tests/ephemeris-page.test.tsx
T
jesse-ux a38a941522
Independent Staging Quality Gate / validate (push) Failing after 8m1s
Independent Staging Quality Gate / publish (push) Skipped
fix(qizheng): pass school options into the vendored chart and stabilize gates
BUG-710: call getSevenGovernorsChart with ketuMode/siderealMode so
calculation.ketu_mode is the engine school, not the request echo.
BUG-711: stop forbidding /ephemeris in the sidebar contract.
BUG-712: compare ephemeris event longitudes at 6 decimals and fail
closed when the golden is missing.
2026-09-15 17:54:20 +08:00

138 lines
6.4 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 test from "node:test";
import React from "react";
import { renderToStaticMarkup } from "react-dom/server";
import { EphemerisView } from "../src/components/ephemeris/ephemeris-page.tsx";
import { parseEphemerisOkResponse, type EphemerisOkResponse } from "../src/lib/ephemeris-contract.ts";
import {
EPHEMERIS_COPY,
ephemerisAskDraft,
panchangaFactorsFromEngine,
shiftEphemerisDate,
transitsFromEngine,
} from "../src/lib/ephemeris-view.ts";
Object.assign(globalThis, { React });
const pageSource = readFileSync(new URL("../src/components/ephemeris/ephemeris-page.tsx", import.meta.url), "utf8");
const routePage = readFileSync(new URL("../src/app/ephemeris/page.tsx", import.meta.url), "utf8");
const css = readFileSync(new URL("../src/app/globals.css", import.meta.url), "utf8");
const panchangaGolden = JSON.parse(readFileSync(new URL("./fixtures/ephemeris-panchanga-range.golden.json", import.meta.url), "utf8"));
const natalGolden = JSON.parse(readFileSync(new URL("./fixtures/ephemeris-chart-natal.golden.json", import.meta.url), "utf8"));
const transit15 = JSON.parse(readFileSync(new URL("./fixtures/ephemeris-chart-transit-2026-09-15.golden.json", import.meta.url), "utf8"));
const transit14 = JSON.parse(readFileSync(new URL("./fixtures/ephemeris-chart-transit-2026-09-14.golden.json", import.meta.url), "utf8"));
function payloadFor(date: "2026-09-14" | "2026-09-15", events: EphemerisOkResponse["events"] = { status: "unavailable" }): EphemerisOkResponse {
const transits = transitsFromEngine({
transitChart: date === "2026-09-14" ? transit14 : transit15,
natalChart: natalGolden,
});
const parsed = parseEphemerisOkResponse({
status: "ok",
date,
today: "2026-09-15",
timezoneId: "Asia/Shanghai",
panchanga: {
available: true,
ayanamsaNote: "lahiri_not_account",
factors: panchangaFactorsFromEngine(panchangaGolden, date),
},
transits: {
available: true,
natalRelativeAvailable: true,
natalRelativeNote: "shown",
ayanamsa: "raman",
bodies: transits.bodies,
},
events,
});
assert.ok(parsed);
return parsed;
}
function render(payload: EphemerisOkResponse | null, date = payload?.date ?? "2026-09-15") {
return renderToStaticMarkup(React.createElement(EphemerisView, {
date,
today: "2026-09-15",
payload,
onPrevious() {},
onNext() {},
onToday() {},
onAsk() {},
}));
}
test("the ephemeris page is its own route and never grows page.tsx", () => {
assert.match(routePage, /from "@\/components\/ephemeris\/ephemeris-page"/);
assert.doesNotMatch(readFileSync(new URL("../src/app/page.tsx", import.meta.url), "utf8"), /ephemeris-page/);
// 原值: assert.doesNotMatch(app-sidebar.tsx, /ephemeris/)
// 新值: 本单文件归属只锁 page.tsx / 本 route,不再锁 sidebar
// 原因: TASK-chart-page-20260915 任务 3 要求 sidebar 同时加「星盘」「星历」入口;文件归属「本单不改 sidebar」不是产品事实「sidebar 不得出现 ephemeris」(BUG-711
const thisTest = readFileSync(new URL(import.meta.url), "utf8");
assert.doesNotMatch(thisTest, /from "@\/components\/app-sidebar"/);
});
test("date switching replaces panchanga and transits", () => {
const fourteenth = payloadFor("2026-09-14");
const fifteenth = payloadFor("2026-09-15");
assert.notEqual(
fourteenth.panchanga.factors.find((item) => item.key === "tithi")?.value,
fifteenth.panchanga.factors.find((item) => item.key === "tithi")?.value,
);
const moon14 = fourteenth.transits.bodies.find((item) => item.planet === "Moon");
const moon15 = fifteenth.transits.bodies.find((item) => item.planet === "Moon");
assert.notEqual(moon14?.degreeInSign, moon15?.degreeInSign);
const markup14 = render(fourteenth);
const markup15 = render(fifteenth);
assert.match(markup14, /Shukla Chaturthi/);
assert.match(markup15, /Shukla Panchami/);
assert.doesNotMatch(markup15, /Shukla Chaturthi/);
assert.match(markup14, /2026年9月14日/);
assert.match(markup15, /2026年9月15日/);
assert.equal(shiftEphemerisDate("2026-09-15", -1), "2026-09-14");
});
test("unavailable events render a static note, not a blank page", () => {
const markup = render(payloadFor("2026-09-15"));
assert.match(markup, new RegExp(EPHEMERIS_COPY.eventsUnavailable));
assert.match(markup, /Shukla Panchami/);
assert.match(markup, /狮子座/);
assert.doesNotMatch(markup, /spinner|正在加载|skeleton/i);
});
test("the Lahiri note sits under panchanga and not under transits", () => {
const markup = render(payloadFor("2026-09-15"));
const panchanga = markup.indexOf(EPHEMERIS_COPY.panchangaHeading);
const note = markup.indexOf(EPHEMERIS_COPY.panchangaLahiriNote);
const transits = markup.indexOf(EPHEMERIS_COPY.transitsHeading);
assert.ok(panchanga >= 0 && note >= 0 && transits >= 0);
assert.ok(panchanga < note && note < transits);
const transitsBlock = markup.slice(transits);
assert.doesNotMatch(transitsBlock, new RegExp(EPHEMERIS_COPY.panchangaLahiriNote));
});
test("mobile CSS turns five panchanga cards into one stacked card", () => {
const markup = render(payloadFor("2026-09-15"));
assert.match(markup, /class="ephemeris-panchanga"/);
assert.equal([...markup.matchAll(/ephemeris-panchanga-item/g)].length, 5);
assert.match(css, /@media \(max-width: 767px\) \{[\s\S]*\.ephemeris-panchanga \{[\s\S]*grid-template-columns:\s*1fr/);
assert.match(css, /@media \(max-width: 767px\) \{[\s\S]*\.ephemeris-panchanga-item \{[\s\S]*grid-template-columns:\s*5\.5rem minmax\(0, 1fr\)/);
assert.match(css, /\.ephemeris-date-button \{[\s\S]*min-height:\s*44px/);
});
test("copy stays factual: no fortune-telling, no spinner, ask prefills the date", () => {
const markup = render(payloadFor("2026-09-15"));
assert.match(markup, new RegExp(EPHEMERIS_COPY.footerNote));
assert.match(markup, new RegExp(EPHEMERIS_COPY.ask));
assert.match(markup, new RegExp(EPHEMERIS_COPY.previousDay));
assert.match(markup, new RegExp(EPHEMERIS_COPY.nextDay));
assert.match(markup, new RegExp(EPHEMERIS_COPY.today));
assert.doesNotMatch(markup, /运势|适合|不宜|建议|吉祥日/);
assert.doesNotMatch(pageSource, /InlineSpinner|正在加载|skeleton/i);
assert.equal(ephemerisAskDraft("2026-09-15"), "请按 2026年9月15日 这一天的天象来看。");
assert.match(pageSource, /setComposerDraft\(ephemerisAskDraft/);
});