Files
Jyotisha/frontend/tests/subject-catalog-refresh-20261027.test.ts
T
Jesse_ChenandClaude Opus 5.5 de9d4826c1 #111 第 3 轮:建档不退回加载、右栏能拖宽、清单不迟到、月亮问句与别人的上升句(BUG-1455~1459)
- 建档重读人物名单时保留旧名单,失败也不卡在加载画面(BUG-1455)
- 对话右栏改用自己的拖宽把手,侧栏与右栏都能用方向键(BUG-1456/1457)
- 启程清单胶囊和首页一起出来,读状态时用上次的样子占位(BUG-1458)
- 起步第一问与别人的天空过场读一次已算好的星盘取星座,上升只在不会换座时用(BUG-1459,产品 10-27 同意)
- 补回删减的断言与三栏说明;登录代码改回原样,替身放进测试;删旧校正画面的无用样式
- 首屏 +7.54% 产品已接受,写进 DESIGN

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LutgzyJqNhPpZy1nFsJtN3
2026-10-27 10:24:57 +08:00

93 lines
3.3 KiB
TypeScript

/**
* BUG-1455: after the page has revealed with a people list, a reload must not
* drop the list (that put the whole chat back on 「正在载入账户」), and a failed
* reload keeps the old list. Synthetic account and person only.
*/
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
import {
currentSubjectCatalogReady,
loadSubjectCatalog,
peekSubjectCatalog,
refreshSubjectCatalog,
resetSubjectCatalogForTests,
subscribeSubjectCatalog,
} from "../src/lib/current-subject.ts";
import { emptyProfile, type Account } from "../src/lib/home-types.ts";
const account: Account = {
user: { id: "11111111-1111-4111-8111-111111111111", email: null },
profile: { ...emptyProfile, name: "Synthetic" },
avatar: null, credits: 10, isAdmin: false, adminUrl: null,
rectificationPriceCredits: 0, activeSubscription: null,
hasConfirmedBirthTime: false, hasUsableBirthTime: false,
};
const person = {
id: "person-1",
role: "other",
profile: { ...emptyProfile, name: "Synthetic Friend" },
updated_at: "2026-10-27T00:00:00.000Z",
};
function stubFetch(libraryResponses: Array<() => Response>) {
const original = globalThis.fetch;
let libraryCalls = 0;
globalThis.fetch = (async (input: unknown) => {
const url = String(input);
if (url === "/api/account") return new Response(JSON.stringify(account), { status: 200 });
if (url === "/api/chart-profiles") {
const next = libraryResponses[Math.min(libraryCalls, libraryResponses.length - 1)];
libraryCalls += 1;
return next();
}
return new Response("{}", { status: 404 });
}) as typeof fetch;
return () => { globalThis.fetch = original; };
}
const ok = () => new Response(JSON.stringify({ profiles: [person] }), { status: 200 });
const failed = () => new Response("{}", { status: 500 });
test("a refresh after reveal keeps the list ready while it reloads", async () => {
resetSubjectCatalogForTests();
const restore = stubFetch([ok]);
try {
await loadSubjectCatalog();
assert.equal(currentSubjectCatalogReady(), true);
const readiness: boolean[] = [];
const off = subscribeSubjectCatalog(() => readiness.push(currentSubjectCatalogReady()));
const pending = refreshSubjectCatalog();
assert.equal(currentSubjectCatalogReady(), true, "the page must not fall back to the loading screen");
assert.ok(peekSubjectCatalog()?.some((row) => row.id === "person-1"));
await pending;
off();
assert.ok(readiness.every(Boolean), `ready never drops: ${readiness.join(",")}`);
} finally {
restore();
resetSubjectCatalogForTests();
}
});
test("a failed refresh keeps the full list instead of a degraded one", async () => {
resetSubjectCatalogForTests();
const restore = stubFetch([ok, failed]);
try {
await loadSubjectCatalog();
await assert.rejects(refreshSubjectCatalog());
assert.equal(currentSubjectCatalogReady(), true);
assert.ok(peekSubjectCatalog()?.some((row) => row.id === "person-1"));
} finally {
restore();
resetSubjectCatalogForTests();
}
});
test("onboarding reloads the list without invalidating it", () => {
const source = readFileSync(new URL("../src/hooks/use-profile-onboarding.ts", import.meta.url), "utf8");
assert.doesNotMatch(source, /invalidateSubjectCatalog/);
assert.match(source, /refreshSubjectCatalog\(\)/);
});