Files
Jyotisha/frontend/tests/birth-sky-polish-20260928.test.ts
T

71 lines
4.7 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 { tryOpenRedeemPaywall } from "../src/lib/redeem-paywall-once.ts";
// TASK-birth-sky-polish-20260928 P1 (poster-only dialog) and P4 (paywall timing, BUG-1080).
const read = (path: string) => readFileSync(new URL(`../${path}`, import.meta.url), "utf8");
const converge = read("src/components/birth-sky/birth-sky-converge.tsx");
const hook = read("src/hooks/use-consultation-run.ts");
const page = read("src/app/(app)/page.tsx");
// 原值(测试名与断言):chart page dialog shows only the picture…——静态海报弹层:`<img>` + 图下「保存图片」+ 右上 ×,
// backdrop 点击关闭,`birth-sky.css` 的舞台与按钮尺寸
// 新值:chart page replay stays on the poster…——弹层删除,改为重放汇聚后定格(定格帧即海报);左上 ×、右上「分享」,
// 同样 44px、夜色底;断言移到 `birth-sky-replay.tsx` / `birth-sky-converge.css`
// 原因:TASK-mobile-chart-and-confirmed-edit-20260929 M4,产品 2026-09-29「点击那一刻的天空重新跑星星动画,定格到最后一帧,右上角分享」
test("chart page replay stays on the poster: × top left, 「分享」 top right, no title bar or panel", () => {
const replay = read("src/components/birth-sky/birth-sky-replay.tsx");
const convergeCss = read("src/components/birth-sky/birth-sky-converge.css");
assert.doesNotMatch(replay, /<h2|<header|birth-sky-panel|<img|保存图片/);
assert.match(replay, /mode="hold"/);
assert.match(replay, /<button className="birth-sky-control" type="button" onClick=\{onClose\} aria-label="关闭">\s*<X aria-hidden="true" \/>/);
assert.match(replay, /<button className="birth-sky-control birth-sky-share" type="button" onClick=\{\(\) => void share\(\)\}>/);
assert.match(replay, /await saveBirthSkyImage\(cover\.blob\)/);
assert.match(convergeCss, /\.birth-sky-controls \{[^}]*justify-content: space-between;/);
assert.match(convergeCss, /\.birth-sky-control \{[^}]*min-width: 44px;[^}]*height: 44px;/);
});
test("paywall gate: the first refusal opens the paywall, every later one falls through to billing", () => {
const shown = { current: false };
const opened: boolean[] = [];
const open = (value: boolean) => { opened.push(value); };
assert.equal(tryOpenRedeemPaywall({ uiPreview: false, shown, open }), true);
assert.equal(shown.current, true);
assert.deepEqual(opened, [true]);
assert.equal(tryOpenRedeemPaywall({ uiPreview: false, shown, open }), false, "second refusal → billing");
assert.deepEqual(opened, [true]);
});
test("paywall gate: UI preview or a caller without the paywall never opens it", () => {
const shown = { current: false };
const opened: boolean[] = [];
assert.equal(tryOpenRedeemPaywall({ uiPreview: true, shown, open: (value) => opened.push(value) }), false);
assert.equal(tryOpenRedeemPaywall({ uiPreview: false }), false);
assert.equal(tryOpenRedeemPaywall({ uiPreview: false, shown }), false);
assert.equal(shown.current, false);
assert.deepEqual(opened, []);
});
test("the paywall only opens from a refused send; the typed question stays in the composer", () => {
// No page effect opens it when the starter home appears.
assert.doesNotMatch(page, /setOnboardingPaywallOpen\(true\)/);
assert.doesNotMatch(page, /!starterHomeVisible\s*\n\s*\|\| !account/);
// Pre-flight balance check: before send() clears the draft, paywall first, else billing.
const guard = hook.slice(hook.indexOf("if (account.credits <= 0 && !account.activeSubscription) {"));
assert.match(guard, /^if \(account\.credits <= 0 && !account\.activeSubscription\) \{\s*if \(!openRedeemPaywallOnce\(\)\) openAccountDialog\("billing", \{ source: "insufficient-credits" \}\);\s*return false;/);
assert.ok(hook.indexOf("if (!openRedeemPaywallOnce())") < hook.indexOf("conversationAnchor.pinLatestTurn();\n setDraft(\"\");"));
// 402: same order; the existing rollback restores the draft (reserve did not commit).
assert.match(hook, /if \(status === 402 && !openRedeemPaywallOnce\(\)\) openAccountDialog\("billing", \{ source: "insufficient-credits" \}\);/);
assert.match(hook, /\|\| caught\.status === 402[\s\S]{0,200}if \(reserveDidNotCommit\) \{[\s\S]{0,300}setDraft\(originalQuestion\);/);
// Rectification's own 402 path is out of scope and unchanged.
assert.match(read("src/lib/rectification-chat-turn-run.ts"), /response\.status === 402/);
});
test("the convergence overlay is portalled to <body>, outside the inert SidebarInset (BUG-968, BUG-1080)", () => {
assert.match(converge, /import \{ createPortal \} from "react-dom";/);
assert.match(converge, /return createPortal\(\s*<dialog[\s\S]*<\/dialog>,\s*document\.body,\s*\);/);
});