- 建档重读人物名单时保留旧名单,失败也不卡在加载画面(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
150 lines
5.1 KiB
TypeScript
150 lines
5.1 KiB
TypeScript
import "./install-test-document";
|
|
import assert from "node:assert/strict";
|
|
import { readFileSync } from "node:fs";
|
|
import test from "node:test";
|
|
|
|
import { OnboardingHintLayer } from "../src/components/onboarding-hint-layer.tsx";
|
|
import { shouldAttachHintObserver, type HintFacts } from "../src/lib/onboarding-hints.ts";
|
|
import { OnboardingJourneyContext, type OnboardingJourney } from "../src/lib/onboarding-journey.tsx";
|
|
import { ONBOARDING_HINT_IDS } from "../src/lib/onboarding-state.ts";
|
|
import { createClientLifecycleHarness } from "./react-client-lifecycle-test-support.ts";
|
|
|
|
const layerSource = readFileSync(new URL("../src/components/onboarding-hint-layer.tsx", import.meta.url), "utf8");
|
|
|
|
function facts(patch: Partial<HintFacts> = {}): HintFacts {
|
|
return {
|
|
hintsSeen: [],
|
|
followUpArmed: false,
|
|
chartVisited: false,
|
|
sidebarOpen: false,
|
|
activeChartTab: null,
|
|
synReady: false,
|
|
ephemerisArmed: false,
|
|
blocked: false,
|
|
anchors: {},
|
|
...patch,
|
|
};
|
|
}
|
|
|
|
function journey(hintWatch: boolean): OnboardingJourney {
|
|
return {
|
|
showChecklist: false,
|
|
checklistPending: false,
|
|
userId: "",
|
|
items: [],
|
|
label: "",
|
|
celebration: false,
|
|
activeHint: null,
|
|
hintText: "",
|
|
showChartDot: false,
|
|
tabHasDot: () => false,
|
|
dismissChecklist: () => {},
|
|
dismissHint: async () => true,
|
|
go: () => {},
|
|
setChartTab: () => {},
|
|
markChartVisited: () => {},
|
|
setChecklistOpen: () => {},
|
|
setLive: () => {},
|
|
registerHome: () => {},
|
|
hintWatch,
|
|
motionEnabled: false,
|
|
};
|
|
}
|
|
|
|
function installObserverCount() {
|
|
let live = 0;
|
|
class CountingObserver {
|
|
constructor(_callback: MutationCallback) { live += 1; }
|
|
observe() {}
|
|
disconnect() { live -= 1; }
|
|
takeRecords() { return []; }
|
|
}
|
|
globalThis.MutationObserver = CountingObserver as unknown as typeof MutationObserver;
|
|
return () => live;
|
|
}
|
|
|
|
test("a full hint list or a closed checklist attaches no observer", async () => {
|
|
assert.doesNotMatch(layerSource, /observe\(\s*document\.body,\s*\{[^}]*subtree:\s*true/);
|
|
const armed = facts({ followUpArmed: true, sidebarOpen: true });
|
|
assert.equal(shouldAttachHintObserver({
|
|
checklistClosed: false,
|
|
streaming: false,
|
|
facts: facts({ hintsSeen: [...ONBOARDING_HINT_IDS], followUpArmed: true }),
|
|
}), false);
|
|
assert.equal(shouldAttachHintObserver({ checklistClosed: true, streaming: false, facts: armed }), false);
|
|
assert.equal(shouldAttachHintObserver({ checklistClosed: false, streaming: false, facts: armed }), true);
|
|
|
|
const live = installObserverCount();
|
|
const harness = createClientLifecycleHarness();
|
|
try {
|
|
await harness.render(
|
|
<OnboardingJourneyContext.Provider value={journey(shouldAttachHintObserver({
|
|
checklistClosed: false,
|
|
streaming: false,
|
|
facts: facts({ hintsSeen: [...ONBOARDING_HINT_IDS], followUpArmed: true }),
|
|
}))}>
|
|
<OnboardingHintLayer />
|
|
</OnboardingJourneyContext.Provider>,
|
|
);
|
|
await harness.idle();
|
|
await harness.render(
|
|
<OnboardingJourneyContext.Provider value={journey(shouldAttachHintObserver({
|
|
checklistClosed: true,
|
|
streaming: false,
|
|
facts: armed,
|
|
}))}>
|
|
<OnboardingHintLayer />
|
|
</OnboardingJourneyContext.Provider>,
|
|
);
|
|
await harness.idle();
|
|
assert.equal(live(), 0);
|
|
assert.equal(harness.errors.length, 0, harness.errors.map(String).join("\n"));
|
|
} finally {
|
|
await harness.close();
|
|
}
|
|
});
|
|
|
|
function StreamingPage({ count, hintWatch }: { count: number; hintWatch: boolean }) {
|
|
return (
|
|
<OnboardingJourneyContext.Provider value={journey(hintWatch)}>
|
|
<OnboardingHintLayer />
|
|
<div>{Array.from({ length: count }, (_, index) => <span key={index}>字</span>)}</div>
|
|
</OnboardingJourneyContext.Provider>
|
|
);
|
|
}
|
|
|
|
test("two hundred streaming fragments do not scan for hints", async () => {
|
|
const armed = facts({ followUpArmed: true, sidebarOpen: true });
|
|
assert.equal(shouldAttachHintObserver({ checklistClosed: false, streaming: true, facts: armed }), false);
|
|
const harness = createClientLifecycleHarness();
|
|
let streaming = false;
|
|
let scans = 0;
|
|
const countScan = (selector: string) => {
|
|
if (streaming && selector.includes("data-onboarding")) scans += 1;
|
|
return selector.includes("All") ? [] : null;
|
|
};
|
|
harness.document.querySelector = ((selector: string) => countScan(selector)) as typeof harness.document.querySelector;
|
|
harness.document.querySelectorAll = ((selector: string) => countScan(selector) ?? []) as typeof harness.document.querySelectorAll;
|
|
try {
|
|
await harness.render(<StreamingPage count={0} hintWatch={shouldAttachHintObserver({
|
|
checklistClosed: false,
|
|
streaming: false,
|
|
facts: armed,
|
|
})} />);
|
|
await harness.idle();
|
|
streaming = true;
|
|
for (let count = 1; count <= 200; count += 1) {
|
|
await harness.render(<StreamingPage count={count} hintWatch={shouldAttachHintObserver({
|
|
checklistClosed: false,
|
|
streaming: true,
|
|
facts: armed,
|
|
})} />);
|
|
}
|
|
await harness.idle();
|
|
assert.equal(scans, 0);
|
|
assert.equal(harness.errors.length, 0, harness.errors.map(String).join("\n"));
|
|
} finally {
|
|
await harness.close();
|
|
}
|
|
});
|