Round 3 followed the competitor screenshots instead of prototype v2. Restored from the prototype source: page head (name, system switch, report and chat buttons), layer row left / style right, the caption strip with dasha chips, north board drawn from house centroids with sign glyph + name + house number, dignity colours and " R", twelve house theme chips with dots, wrapped antardasha chips, card hints, inspector empty state, deep-reading hints, chevron date steps and 此刻, year axis under the mahadasha scroll, three-column birth data. Point layers stay in the layer group; side triangles stack bodies so nothing leaves the frame (BUG-1465 test kept). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Ad9rqAB8pXXvyzh7f4X8v
370 lines
20 KiB
TypeScript
370 lines
20 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 { ChartBoardSvg } from "../src/components/chart-board/chart-board-svg.tsx";
|
||
import { ChartInspector, inspectorQuestion } from "../src/components/chart-board/chart-inspector.tsx";
|
||
import { ChartGrahaCard } from "../src/components/chart-board/chart-side-cards.tsx";
|
||
import {
|
||
aspectedHouses,
|
||
boardRelations,
|
||
bodyHouse,
|
||
dignityStep,
|
||
houseLord,
|
||
lordedHouses,
|
||
stepSelection,
|
||
} from "../src/lib/chart-board-model.ts";
|
||
import { boardSource, glanceOf, natalBoard, savBand, transitBySign } from "../src/lib/chart-board-view.ts";
|
||
import { readStoredChartBoardStyle, writeStoredChartBoardStyle } from "../src/lib/chart-board-style.ts";
|
||
import { assembleChartView } from "../src/lib/chart-view-load.ts";
|
||
import type { ChartViewOk } from "../src/lib/chart-view-contract.ts";
|
||
import { CHART_VIEW_LAYERS, parseTransitDate } from "../src/lib/chart-view-engine.ts";
|
||
import { mapAshtakavarga, mapBirthCalendar, mapPoints, mapShadbala, mapYogas, shadbalaLevel } from "../src/lib/chart-view-insight.ts";
|
||
import { pointSelection, pointsOf, stepPoint } from "../src/lib/chart-board-view.ts";
|
||
import { vimshottariSplit } from "../src/lib/dasha-span.ts";
|
||
import { HOUSE_POLYGONS, pointInPolygon } from "../src/lib/north-indian-chart-geometry.ts";
|
||
import { SIGN_ORDER, signZh } from "../src/lib/chart-view-labels.ts";
|
||
|
||
Object.assign(globalThis, { React });
|
||
|
||
const golden = JSON.parse(readFileSync(new URL("./fixtures/chart-view-golden.json", import.meta.url), "utf8")) as {
|
||
chart: Record<string, unknown>;
|
||
varga_full: Record<string, unknown>;
|
||
};
|
||
/** Real engine output (see `sourceCommand`), trimmed to the fields the page reads. */
|
||
const insight = JSON.parse(readFileSync(new URL("./fixtures/chart-insight-golden.json", import.meta.url), "utf8")) as {
|
||
chart: Record<string, unknown>;
|
||
chart_modules_shadbala: Record<string, unknown>;
|
||
ashtakavarga: Record<string, unknown>;
|
||
bhava_bala: Record<string, unknown>;
|
||
chart_points: Record<string, unknown>;
|
||
second_case: {
|
||
yogas: unknown[];
|
||
vimshottari_current: { antardasha: { lord: string; start: string; end: string } };
|
||
five_levels: { pratyantar: { lord: string; total_years: number } };
|
||
};
|
||
};
|
||
const insightChart = { ...insight.chart, modules: { shadbala: insight.chart_modules_shadbala } };
|
||
|
||
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 okView(): Promise<ChartViewOk> {
|
||
const result = await assembleChartView({
|
||
userId: "user-1",
|
||
profile,
|
||
asOf: "2026-10-27",
|
||
layers: CHART_VIEW_LAYERS,
|
||
postEngine: async (path) => {
|
||
if (path === "/api/chart") return { status: "ok", payload: { ...golden.chart, ...insightChart } };
|
||
if (path === "/api/varga_full") return { status: "ok", payload: golden.varga_full };
|
||
if (path === "/api/ashtakavarga") return { status: "ok", payload: insight.ashtakavarga };
|
||
if (path === "/api/bhava_bala") return { status: "ok", payload: insight.bhava_bala };
|
||
if (path === "/api/chart_points") return { status: "ok", payload: insight.chart_points };
|
||
return { status: "http_error", path, elapsedMs: 1, httpStatus: 500 };
|
||
},
|
||
});
|
||
assert.equal(result.body.status, "ok");
|
||
return result.body as ChartViewOk;
|
||
}
|
||
|
||
test("shadbala rows keep the engine totals and minimum, and print a strong/medium/weak band", () => {
|
||
const rows = mapShadbala(insightChart);
|
||
assert.equal(rows.length, 7);
|
||
const sun = rows.find((row) => row.name === "Sun")!;
|
||
assert.equal(sun.required, 6.5);
|
||
assert.equal(sun.percent, Math.round((sun.rupas / 6.5) * 1000) / 10);
|
||
assert.ok(sun.parts, "the six components come from modules.shadbala");
|
||
const mercury = rows.find((row) => row.name === "Mercury")!;
|
||
assert.equal(mercury.level, "medium", "6.94 of 7.0 rupas is 99 %");
|
||
assert.equal(shadbalaLevel(120), "strong");
|
||
assert.equal(shadbalaLevel(119.9), "medium");
|
||
assert.equal(shadbalaLevel(89.9), "weak");
|
||
assert.deepEqual(mapShadbala({}), [], "no packet, no rows; nothing is estimated");
|
||
});
|
||
|
||
test("yogas come from the consultation's closed list; reductions mark a partial yoga", () => {
|
||
const none = mapYogas(insightChart);
|
||
assert.deepEqual(none.formed, [], "Amala Yoga is outside the closed list");
|
||
assert.equal(none.notFormed.length, 8);
|
||
const second = mapYogas({ yogas: insight.second_case.yogas });
|
||
assert.deepEqual(second.formed.map((yoga) => yoga.id), ["ruchaka", "sasa"]);
|
||
assert.ok(second.formed.every((yoga) => yoga.reductions.length > 0));
|
||
assert.deepEqual(second.formed[0]!.planets, ["Mars"]);
|
||
assert.equal(second.notFormed.length, 6);
|
||
});
|
||
|
||
test("the ashtakavarga layer keeps SAV by sign, three BAV readings and house strength", () => {
|
||
const layer = mapAshtakavarga(insight.ashtakavarga, insight.bhava_bala);
|
||
assert.equal(layer.status, "ok");
|
||
if (layer.status !== "ok") return;
|
||
assert.equal(layer.sav.reduce((sum, value) => sum + value, 0), 337);
|
||
assert.deepEqual(layer.bav.map((row) => row.raw.reduce((a, b) => a + b, 0)), [48, 49, 39, 54, 56, 52, 39]);
|
||
for (const row of layer.bav) {
|
||
row.sodhita.forEach((value, index) => assert.ok(value <= row.trikona[index]! && row.trikona[index]! <= row.raw[index]!));
|
||
}
|
||
assert.equal(layer.bhavaBala?.length, 12);
|
||
assert.equal(mapAshtakavarga(null, null).status, "unavailable");
|
||
const withoutHouses = mapAshtakavarga(insight.ashtakavarga, null);
|
||
assert.equal(withoutHouses.status === "ok" && withoutHouses.bhavaBala, null, "house strength is optional");
|
||
});
|
||
|
||
test("the birth calendar prints the tithi in Chinese and the civil weekday", () => {
|
||
assert.deepEqual(mapBirthCalendar(insightChart, "1990-06-15"), { tithi: "黑分初七", weekday: "星期五" });
|
||
assert.deepEqual(mapBirthCalendar({}, "1990-06-15"), { tithi: null, weekday: "星期五" });
|
||
});
|
||
|
||
test("transitDate accepts any real calendar day in 1000–2999 and nothing else", () => {
|
||
assert.equal(parseTransitDate("2031-02-28"), "2031-02-28");
|
||
assert.equal(parseTransitDate("1850-07-01"), "1850-07-01");
|
||
assert.equal(parseTransitDate("2031-02-30"), undefined);
|
||
assert.equal(parseTransitDate("0999-01-01"), undefined);
|
||
assert.equal(parseTransitDate("2031-2-3"), undefined);
|
||
assert.equal(parseTransitDate(null), undefined);
|
||
});
|
||
|
||
test("a transit for another day posts that day under its own cache scope", async () => {
|
||
const calls: Array<{ path: string; body: Record<string, unknown>; scope?: string }> = [];
|
||
const result = await assembleChartView({
|
||
userId: "user-1",
|
||
profile,
|
||
asOf: "2026-10-27",
|
||
transitDate: "2031-02-28",
|
||
layers: ["transit"],
|
||
postEngine: async (path, body, scope) => {
|
||
calls.push({ path, body, scope });
|
||
return { status: "ok", payload: golden.chart };
|
||
},
|
||
});
|
||
const transitCall = calls.find((call) => call.scope?.startsWith("transit:"));
|
||
assert.equal(transitCall?.scope, "transit:2031-02-28");
|
||
assert.deepEqual([transitCall?.body.year, transitCall?.body.month, transitCall?.body.day], [2031, 2, 28]);
|
||
assert.equal(result.body.status === "ok" && result.body.transit.status === "ok" ? result.body.transit.date : null, "2031-02-28");
|
||
});
|
||
|
||
test("the third dasha level uses the same split as the engine's current pratyantar", () => {
|
||
const { antardasha } = insight.second_case.vimshottari_current;
|
||
const spans = vimshottariSplit(antardasha.lord, antardasha.start, antardasha.end);
|
||
assert.equal(spans.length, 9);
|
||
assert.equal(spans[0]!.lord, antardasha.lord);
|
||
assert.equal(spans[0]!.start, antardasha.start);
|
||
assert.equal(spans.at(-1)!.end, antardasha.end);
|
||
spans.slice(1).forEach((span, index) => assert.equal(span.start, spans[index]!.end));
|
||
const engine = insight.second_case.five_levels.pratyantar;
|
||
const now = spans.find((span) => span.start <= "2026-10-27" && "2026-10-27" < span.end)!;
|
||
assert.equal(now.lord, engine.lord, "the engine computed this run on 2026-10-27");
|
||
const days = (Date.parse(now.end) - Date.parse(now.start)) / 86_400_000;
|
||
assert.ok(Math.abs(days - engine.total_years * 365.25) <= 1.5);
|
||
});
|
||
|
||
test("rulership, graha drishti and relations follow the engine's rules", async () => {
|
||
const view = await okView();
|
||
const model = natalBoard(view);
|
||
assert.equal(model.houses[0]!.sign, 4, "Leo rising");
|
||
assert.equal(houseLord(model, 1), "sun");
|
||
assert.deepEqual(lordedHouses(model, "saturn"), [6, 7]);
|
||
assert.deepEqual(lordedHouses(model, "rahu"), []);
|
||
const mars = bodyHouse(model, "mars")!;
|
||
assert.deepEqual(aspectedHouses(model, "mars"), [4, 7, 8].map((n) => ((mars + n - 2) % 12) + 1));
|
||
const relations = boardRelations(model, { kind: "body", key: "jupiter" });
|
||
assert.equal(relations.focus, bodyHouse(model, "jupiter"));
|
||
assert.ok(relations.lit.has(relations.focus!));
|
||
relations.aspectLinks.forEach((house) => assert.ok(aspectedHouses(model, "jupiter").includes(house)));
|
||
const house = boardRelations(model, { kind: "house", house: 1 });
|
||
assert.deepEqual(house.lordLinks, bodyHouse(model, "sun") === 1 ? [] : [bodyHouse(model, "sun")]);
|
||
assert.deepEqual(stepSelection(model, { kind: "house", house: 12 }, 1), { kind: "house", house: 1 });
|
||
assert.deepEqual(stepSelection(model, { kind: "body", key: "sun" }, -1), { kind: "body", key: "ketu" });
|
||
});
|
||
|
||
test("dignity reads the engine's status label on a nine-step scale", () => {
|
||
assert.deepEqual(dignityStep("入旺(Exalted)"), { step: 8, label: "入旺" });
|
||
assert.deepEqual(dignityStep("落陷(Debilitated)"), { step: 0, label: "落陷" });
|
||
assert.deepEqual(dignityStep("落陷取消(Neecha Bhanga)"), { step: 1, label: "落陷取消" });
|
||
assert.deepEqual(dignityStep("中性(Neutral) · 逆行"), { step: 3, label: "中性" });
|
||
assert.equal(dignityStep("—"), null);
|
||
});
|
||
|
||
test("SAV bands use the traditional 28 line", () => {
|
||
assert.equal(savBand(28), "strong");
|
||
assert.equal(savBand(25), "medium");
|
||
assert.equal(savBand(24), "weak");
|
||
});
|
||
|
||
test("north and south boards draw twelve houses, every body, and lines for a selection", async () => {
|
||
const view = await okView();
|
||
const model = natalBoard(view);
|
||
for (const style of ["north", "south"] as const) {
|
||
const plain = renderToStaticMarkup(React.createElement(ChartBoardSvg, { model, style, selection: null, onSelect: () => {}, ariaLabel: "本命盘" }));
|
||
assert.equal((plain.match(/class="chart-board-cell/g) ?? []).length, 12);
|
||
// 原值:/class="chart-board-body /(类名后总跟一个星体颜色类)。新值:/class="chart-board-body[ "]/。
|
||
// 原因:按原型改为按庙旺/落陷着色,没有尊贵变化的星类名后面直接是引号;仍数九颗星。
|
||
assert.equal((plain.match(/class="chart-board-body[ "]/g) ?? []).length, 9);
|
||
assert.doesNotMatch(plain, /chart-board-link/);
|
||
const picked = renderToStaticMarkup(React.createElement(ChartBoardSvg, { model, style, selection: { kind: "body", key: "saturn" }, onSelect: () => {}, ariaLabel: "本命盘" }));
|
||
const relations = boardRelations(model, { kind: "body", key: "saturn" });
|
||
assert.equal((picked.match(/class="chart-board-link"/g) ?? []).length, relations.lordLinks.length);
|
||
assert.equal((picked.match(/class="chart-board-link is-aspect"/g) ?? []).length, relations.aspectLinks.length);
|
||
assert.match(picked, /chart-board-cell is-selected/);
|
||
}
|
||
});
|
||
|
||
// Issue #114 round 3: the North board wrote every sign name at its house's inner corner, so the
|
||
// four kendra names piled up in the centre and the triangle names met at the diagonals; bodies in
|
||
// the right-hand triangles were pushed past the frame. Each name and body must sit in its own house.
|
||
test("north board: every sign name and body sits inside its own house, and no two names meet", async () => {
|
||
const view = await okView();
|
||
const model = natalBoard(view);
|
||
const markup = renderToStaticMarkup(React.createElement(ChartBoardSvg, { model, style: "north", selection: null, onSelect: () => {}, ariaLabel: "本命盘" }));
|
||
const labels = [...markup.matchAll(/<text class="chart-board-sign[^"]*" x="([\d.]+)" y="([\d.]+)" text-anchor="(\w+)">\S+ ([^<]+)/g)]
|
||
.map((match) => ({ x: Number(match[1]), y: Number(match[2]), anchor: match[3]!, text: match[4]! }));
|
||
assert.equal(labels.length, 12);
|
||
for (const label of labels) {
|
||
const house = model.houses.find((item) => signZh(SIGN_ORDER[item.sign] ?? "") === label.text)!;
|
||
// Glyph, space and name at the 10-unit label font.
|
||
const width = 10 * (label.text.length + 2);
|
||
const left = label.anchor === "start" ? label.x : label.anchor === "end" ? label.x - width : label.x - width / 2;
|
||
for (const x of [left, left + width]) {
|
||
assert.ok(pointInPolygon({ x, y: label.y - 4 }, HOUSE_POLYGONS[house.house]!), `${label.text} stays in house ${house.house}`);
|
||
}
|
||
}
|
||
for (const [index, a] of labels.entries()) {
|
||
for (const b of labels.slice(index + 1)) {
|
||
assert.ok(Math.hypot(a.x - b.x, a.y - b.y) > 60, `${a.text} and ${b.text} are apart`);
|
||
}
|
||
}
|
||
const bodies = [...markup.matchAll(/aria-label="[^"]*第(\d+)宫"[^>]*><text class="chart-board-glyph" x="([\d.]+)" y="([\d.]+)" font-size="([\d.]+)"/g)];
|
||
assert.equal(bodies.length, 9);
|
||
for (const [, house, x, y, size] of bodies) {
|
||
const polygon = HOUSE_POLYGONS[Number(house)]!;
|
||
const s = Number(size);
|
||
// The glyph, and the degree written after it.
|
||
assert.ok(pointInPolygon({ x: Number(x) - s / 2, y: Number(y) - s / 2 }, polygon), `glyph in house ${house}`);
|
||
assert.ok(pointInPolygon({ x: Number(x) + s * 0.55 + 16, y: Number(y) - s / 2 }, polygon), `degree in house ${house}`);
|
||
}
|
||
});
|
||
|
||
test("the inspector shows the number with its band, and 问 AI only writes a question", async () => {
|
||
const view = await okView();
|
||
const source = boardSource(view, "D1")!;
|
||
const model = natalBoard(view);
|
||
const markup = renderToStaticMarkup(React.createElement(ChartInspector, {
|
||
view, transit: null, model, source,
|
||
selection: { kind: "body", key: "sun" },
|
||
onSelect: () => {}, onStep: () => {}, onAsk: () => {},
|
||
}));
|
||
const sun = view.shadbala!.find((row) => row.name === "Sun")!;
|
||
assert.ok(markup.includes(`达标 ${Math.round(sun.percent)}%`));
|
||
assert.match(markup, /chart-band is-strong/);
|
||
assert.match(markup, /问 AI:这颗星/);
|
||
assert.match(inspectorQuestion(view, model, { kind: "body", key: "sun" }), /^我的太阳在.+第\d+宫,对我意味着什么?$/);
|
||
const empty = renderToStaticMarkup(React.createElement(ChartInspector, {
|
||
view, transit: null, model, source, selection: null, onSelect: () => {}, onStep: () => {}, onAsk: () => {},
|
||
}));
|
||
assert.match(empty, /点盘上的星或宫看详情/);
|
||
assert.doesNotMatch(empty, /正在加载|spinner/i);
|
||
});
|
||
|
||
test("the 九曜 table marks the selected body and carries the six-strength column", async () => {
|
||
const view = await okView();
|
||
const markup = renderToStaticMarkup(React.createElement(ChartGrahaCard, { view, selection: { kind: "body", key: "moon" }, onSelect: () => {} }));
|
||
assert.equal((markup.match(/<tr class="is-selected"/g) ?? []).length, 1);
|
||
assert.match(markup, />六力</);
|
||
const compact = renderToStaticMarkup(React.createElement(ChartGrahaCard, { view, selection: null, onSelect: () => {}, compact: true }));
|
||
assert.doesNotMatch(compact, />六力</);
|
||
});
|
||
|
||
test("the summary names the strongest and weakest body from the engine's shadbala", async () => {
|
||
const view = await okView();
|
||
const glance = glanceOf(view, "2026-10-27");
|
||
const sorted = [...view.shadbala!].sort((a, b) => b.percent - a.percent);
|
||
assert.equal(glance.strongest?.name, sorted[0]!.name);
|
||
assert.equal(glance.weakest?.name, sorted.at(-1)!.name);
|
||
assert.equal(transitBySign(null).length, 0);
|
||
});
|
||
|
||
test("north or south is one site-wide choice, north by default", () => {
|
||
const values = new Map<string, string>();
|
||
const storage = { getItem: (key: string) => values.get(key) ?? null, setItem: (key: string, value: string) => { values.set(key, value); } };
|
||
assert.equal(readStoredChartBoardStyle(storage), "north");
|
||
writeStoredChartBoardStyle("south", storage);
|
||
assert.equal(readStoredChartBoardStyle(storage), "south");
|
||
assert.equal(readStoredChartBoardStyle(null), "north");
|
||
});
|
||
|
||
test("the board stylesheet stays off the first paint of the chat page", () => {
|
||
const frame = readFileSync(new URL("../src/components/chat-chart-frame.tsx", import.meta.url), "utf8");
|
||
const page = readFileSync(new URL("../src/app/(app)/page.tsx", import.meta.url), "utf8");
|
||
const route = readFileSync(new URL("../src/app/(app)/chart/page.tsx", import.meta.url), "utf8");
|
||
assert.match(frame, /import\("@\/components\/chart-board\/chart-board-styles"\)/);
|
||
assert.doesNotMatch(page, /chart-board/);
|
||
assert.match(route, /import "@\/components\/chart-board\/chart-board-styles";/);
|
||
});
|
||
|
||
test("the points layer keeps the engine's upagrahas, special lagnas, arudha padas and friendship", () => {
|
||
const layer = mapPoints(insight.chart_points);
|
||
assert.equal(layer.status, "ok");
|
||
if (layer.status !== "ok") return;
|
||
assert.equal(layer.upagrahas?.length, 11);
|
||
assert.deepEqual(layer.upagrahas!.slice(0, 5).map((point) => point.short), ["烟", "毁", "晕", "虹", "彗"]);
|
||
assert.equal(layer.special?.length, 10, "eight special lagnas plus Yogi and Avayogi");
|
||
assert.ok(layer.special!.every((point) => point.nakshatra && point.pada));
|
||
assert.deepEqual(layer.arudha!.map((point) => point.short), ["AL", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "A10", "A11", "UL"]);
|
||
assert.ok(layer.arudha!.every((point, index) => point.sourceHouse === index + 1));
|
||
const sun = layer.friendship!.rows.find((row) => row.name === "Sun")!;
|
||
for (const target of sun.targets) {
|
||
if (target.compound === 2) assert.ok(target.natural === 1 && target.temporary === 1);
|
||
if (target.compound === -2) assert.ok(target.natural === -1 && target.temporary === -1);
|
||
}
|
||
assert.equal(mapPoints(null).status, "unavailable");
|
||
});
|
||
|
||
test("the ascendant's own BAV row and the shadbala sub-components come through", () => {
|
||
const layer = mapAshtakavarga(insight.ashtakavarga, insight.bhava_bala);
|
||
assert.equal(layer.status === "ok" && layer.lagnaBav?.reduce((a, b) => a + b, 0), 49);
|
||
const sun = mapShadbala(insightChart).find((row) => row.name === "Sun")!;
|
||
assert.ok(sun.sthanaParts && sun.kalaParts);
|
||
assert.equal(sun.kalaParts!.nathonnata, 60);
|
||
});
|
||
|
||
test("a picked arudha pada sits in its sign and links back to its own house", async () => {
|
||
const view = await okView();
|
||
const model = natalBoard(view);
|
||
const ul = pointsOf(view, "arudha")!.find((point) => point.key === "UL")!;
|
||
const relations = boardRelations(model, pointSelection(ul));
|
||
assert.equal(relations.focus, ul.house);
|
||
assert.deepEqual(relations.aspectLinks, ul.house === 12 ? [] : [12]);
|
||
assert.equal(stepPoint(view, "arudha", "UL", 1)?.key, "A1");
|
||
const markup = renderToStaticMarkup(React.createElement(ChartBoardSvg, {
|
||
model, style: "south", selection: pointSelection(ul), onSelect: () => {}, ariaLabel: "本命盘",
|
||
pointMarks: pointsOf(view, "arudha")!.map((point) => ({ key: point.key, short: point.short, label: point.label, sign: pointSelection(point).sign })),
|
||
}));
|
||
assert.equal((markup.match(/class="chart-board-mark( is-selected)?"/g) ?? []).length, 12);
|
||
assert.equal((markup.match(/chart-board-mark is-selected/g) ?? []).length, 1);
|
||
});
|
||
|
||
test("the inspector explains a point and 问 AI writes a question about it", async () => {
|
||
const view = await okView();
|
||
const model = natalBoard(view);
|
||
const gulika = pointsOf(view, "upagraha")!.find((point) => point.key === "Gulika")!;
|
||
const markup = renderToStaticMarkup(React.createElement(ChartInspector, {
|
||
view, transit: null, model, source: boardSource(view, "D1")!,
|
||
selection: pointSelection(gulika), onSelect: () => {}, onStep: () => {}, onAsk: () => {},
|
||
}));
|
||
assert.match(markup, /古利伽/);
|
||
assert.match(markup, /传统含义/);
|
||
assert.match(markup, /问 AI:这个点/);
|
||
assert.match(inspectorQuestion(view, model, pointSelection(gulika)), /^我的古利伽(Gulika)在.+第\d+宫,说明什么?$/);
|
||
});
|