fix(web): stale-closure + focus trap + 去边界强转 + a11y/性能打磨 + 类型化客户端
P1-6 useStyleLearn/useKimiOauth 修 stale-closure 依赖。 P1-7 CommandPalette/Drawer 加 focus trap(新 lib/a11y/focusTrap)。 P1-8 SSE 解析与 server.ts 去 as 强转改类型守卫/运行时校验。 P2 删冗余强转、开 noUncheckedIndexedAccess、Toast 清理+稳定 key、列表 key 稳定化、 rel=noopener、useMemo 大文本、ConflictCard role=group、useInjection 加锁、 client.test 真断言。 codegen 重生成 schema.d.ts + 消费 DimensionEntry/ReviewConflictView 强类型。
This commit is contained in:
41
apps/web/lib/style/useStyleLearn.test.ts
Normal file
41
apps/web/lib/style/useStyleLearn.test.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
// P1-6 回归守卫:学文风轮询完成(done 边沿)时,effect 必须用「最近一次 learn 传入的
|
||||
// projectId」去拉指纹,而非闭包捕获的旧值。useStyleLearn 用 ref 追踪 projectId 来保证这点。
|
||||
//
|
||||
// 该 hook 依赖浏览器/React 渲染,node 环境无法整体跑;这里以最小模型固化 ref 追踪契约:
|
||||
// 切项目(连续两次 learn)后,done 时读到的应是后者,不会是首次的陈旧值。
|
||||
describe("useStyleLearn projectId 追踪(P1-6 stale-closure 守卫)", () => {
|
||||
// 复刻 hook 内部:learn 同步写 ref,done 时同步读 ref。
|
||||
function makeTracker() {
|
||||
const projectIdRef: { current: string | null } = { current: null };
|
||||
return {
|
||||
// learn(pid):受理时记录最近项目。
|
||||
learn(pid: string): void {
|
||||
projectIdRef.current = pid;
|
||||
},
|
||||
// onDone():轮询完成时取用于拉指纹的项目(应为最新)。
|
||||
onDone(): string | null {
|
||||
return projectIdRef.current;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
it("done 时用最近一次 learn 的 projectId(切项目后不复用旧值)", () => {
|
||||
// Arrange
|
||||
const t = makeTracker();
|
||||
|
||||
// Act:先 learn 项目 A,再切到项目 B(模拟用户换项目重学),此时 A 的轮询尚未完成。
|
||||
t.learn("project-A");
|
||||
t.learn("project-B");
|
||||
const used = t.onDone();
|
||||
|
||||
// Assert:done 边沿读到的是 B,而非闭包里陈旧的 A。
|
||||
expect(used).toBe("project-B");
|
||||
});
|
||||
|
||||
it("未 learn 时为 null(不误拉指纹)", () => {
|
||||
const t = makeTracker();
|
||||
expect(t.onDone()).toBeNull();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user