Files
Jyotisha/frontend/tests/rectification-board-model.test.ts
T
Jesse_ChenandCursor 5c476ba784
Independent Staging Quality Gate / validate (push) Failing after 9m31s
Independent Staging Quality Gate / publish (push) Has been skipped
fix(rectification): cover all candidate minutes on the compare card and keep delivery narration (BUG-614/615)
Engine by_time ledgers now include the full candidate set so posterior columns can look up fit and windows. Delivery turns keep up to three sentences after idle persist; the card subtracts repeated traits and folds the board chart.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-09 18:32:03 +08:00

168 lines
6.1 KiB
TypeScript

import assert from "node:assert/strict";
import test from "node:test";
import { createElement } from "react";
import { renderToStaticMarkup } from "react-dom/server";
import {
changedHouseNumbers,
diffRectificationBoard,
groupWindowTransitions,
houseTableToNorthIndianChart,
rectificationBoardPeekCopy,
} from "../src/lib/rectification-board-model.ts";
import { parseRectificationCandidateResult } from "../src/lib/rectification-candidate-result.ts";
import type { WindowScanTransition } from "../src/lib/rectification-agentic/v9/refinement-packet.ts";
import { RectificationBoard } from "../src/components/rectification-board.tsx";
const CANDIDATE_ID = "88888888-8888-4888-8888-888888888881";
const SECOND_CANDIDATE_ID = "88888888-8888-4888-8888-888888888882";
const THIRD_CANDIDATE_ID = "88888888-8888-4888-8888-888888888883";
function transition(layer: WindowScanTransition["layer"], at: string): WindowScanTransition {
return { layer, at, user_meaning: `${layer}${at} 发生变化` };
}
function houses(lagna: string, seventh = lagna) {
return Array.from({ length: 12 }, (_, index) => ({
house: index + 1,
sign: index === 6 ? seventh : lagna,
occupants: index === 0 ? ["太阳"] : [],
}));
}
function snapshot(input: Readonly<{
time: string;
lagna: string;
seventh?: string;
transitions?: readonly WindowScanTransition[];
}>) {
return parseRectificationCandidateResult({
resultId: "11111111-1111-4111-8111-111111111111",
candidates: [
{ candidateId: CANDIDATE_ID, rank: 1, time: input.time, relativeSupport: 34, tiedMinuteCount: 1 },
{ candidateId: SECOND_CANDIDATE_ID, rank: 2, time: "05:08", relativeSupport: 33, tiedMinuteCount: 1 },
{ candidateId: THIRD_CANDIDATE_ID, rank: 3, time: "05:09", relativeSupport: 33, tiedMinuteCount: 1 },
],
overallConfidence: "low",
selectionAllowed: true,
confirmationAllowed: false,
representativeTime: input.time,
selectedTime: null,
selectionKind: null,
decisionReceipt: {
house_table: {
time: input.time,
lagna: input.lagna,
houses: houses(input.lagna, input.seventh ?? input.lagna),
},
window_scan: {
scanned: true,
d9_lagna_count: 2,
d10_lagna_count: 1,
transitions: input.transitions ?? [],
},
},
});
}
test("groups 换升时刻 by clock minute and splits scoring layers from display layers", () => {
const grouped = groupWindowTransitions([
transition("d11", "04:47"),
transition("hora", "04:47"),
transition("d24", "04:47"),
transition("d10", "04:48"),
transition("pranapada", "05:14"),
transition("d9", "04:57"),
]);
assert.deepEqual(grouped.map((row) => row.at), ["04:47", "04:48", "04:57", "05:14"]);
assert.deepEqual(grouped[0], {
at: "04:47",
scoringLayers: ["d24", "d11"],
displayLayers: ["hora"],
});
assert.deepEqual(grouped[1]?.scoringLayers, ["d10"]);
assert.deepEqual(grouped[3], {
at: "05:14",
scoringLayers: [],
displayLayers: ["pranapada"],
});
});
test("marks only houses whose sign or occupants actually changed", () => {
const before = snapshot({ time: "04:45", lagna: "金牛座" })?.houseTable ?? null;
const after = snapshot({ time: "04:48", lagna: "金牛座", seventh: "天秤座" })?.houseTable ?? null;
assert.deepEqual(changedHouseNumbers(before, after), [7]);
assert.deepEqual(changedHouseNumbers(null, after), []);
});
test("announces the first board fill and later house or 换升 changes without dumping the table", () => {
const first = snapshot({
time: "04:45",
lagna: "金牛座",
transitions: [transition("d9", "04:57")],
});
const second = snapshot({
time: "04:48",
lagna: "金牛座",
seventh: "天秤座",
transitions: [transition("d9", "04:57"), transition("d10", "04:48")],
});
assert.ok(first && second);
const opened = diffRectificationBoard(null, first);
assert.equal(opened.liveMessage, "当前本命宫位已按 04:45 排出,上升金牛座。");
assert.deepEqual(opened.houseNumbers, []);
assert.equal(opened.timeChanged, false);
const updated = diffRectificationBoard(first, second);
assert.equal(updated.timeChanged, true);
assert.deepEqual(updated.houseNumbers, [7]);
assert.deepEqual(updated.transitionMinutes, ["04:48"]);
assert.match(updated.liveMessage, /本命宫位已按 04:48 重算(上升 金牛座)。/);
assert.match(updated.liveMessage, /第7宫已更新/);
assert.match(updated.liveMessage, /换升时刻已更新/);
assert.doesNotMatch(updated.liveMessage, /太阳|第1宫|第2宫|下面是本轮/);
});
test("peek copy stays a control label, not a second dump of the house table", () => {
const empty = rectificationBoardPeekCopy(null);
assert.equal(empty.title, "当前盘面");
assert.equal(empty.detail, "补充经历后会在这里更新");
const filled = rectificationBoardPeekCopy(snapshot({ time: "04:48", lagna: "金牛座" }));
assert.equal(filled.title, "当前盘面");
assert.equal(filled.detail, "04:48 · 上升金牛座");
});
test("board shows a north-indian chart and folds 换升时刻 by default", () => {
const result = snapshot({
time: "04:45",
lagna: "金牛座",
transitions: [transition("d9", "04:57"), transition("hora", "04:47")],
});
assert.ok(result);
const chart = houseTableToNorthIndianChart(result.houseTable!);
assert.equal(chart.houses.length, 12);
assert.equal(chart.houses[0]?.houseNumber, 1);
assert.equal(chart.houses[0]?.sign, "金牛座");
const html = renderToStaticMarkup(createElement(RectificationBoard, {
result,
declaredTime: "04:45",
savedStatus: null,
diff: diffRectificationBoard(null, result),
compact: false,
open: true,
boardId: "board",
titleId: "board-title",
onClose: () => undefined,
}));
assert.match(html, /role="img"/);
assert.match(html, /当前本命盘,上升金牛座/);
assert.match(html, /换升时刻 · 2 个/);
assert.match(html, /<details/);
assert.doesNotMatch(html, /<details[^>]*\sopen/);
assert.match(html, /本命宫位表/);
assert.doesNotMatch(html, /<section[^>]*aria-label="换升时刻"/);
});