修复项目切换清单竞态测试超时
等待事件监听注册后再触发 manifest 失效并以有界断言确认读取启动 将迟到响应收尾纳入异步 act 避免测试时序悬挂 补充 React 异步事件测试准则
This commit is contained in:
@@ -359,17 +359,11 @@ export function registerClientHomeTests() {
|
||||
source: { kind: 'generated' },
|
||||
},
|
||||
];
|
||||
let resolveStaleRefresh!: (
|
||||
manifest: typeof staleFirstManifest,
|
||||
) => void;
|
||||
let resolveStaleRefresh!: (manifest: typeof staleFirstManifest) => void;
|
||||
const staleRefresh = new Promise<typeof staleFirstManifest>((resolve) => {
|
||||
resolveStaleRefresh = resolve;
|
||||
});
|
||||
let holdFirstRefresh = false;
|
||||
let signalFirstRefreshStarted!: () => void;
|
||||
const firstRefreshStarted = new Promise<void>((resolve) => {
|
||||
signalFirstRefreshStarted = resolve;
|
||||
});
|
||||
const runtimeHarness = createProjectSupervisorRuntimeHarness({
|
||||
projectPath: firstProjectPath,
|
||||
});
|
||||
@@ -393,7 +387,6 @@ export function registerClientHomeTests() {
|
||||
return secondManifest;
|
||||
}
|
||||
if (holdFirstRefresh) {
|
||||
signalFirstRefreshStarted();
|
||||
return staleRefresh;
|
||||
}
|
||||
return firstManifest;
|
||||
@@ -461,11 +454,30 @@ export function registerClientHomeTests() {
|
||||
});
|
||||
fireEvent.click(screen.getByRole('button', { name: '打开' }));
|
||||
await screen.findByLabelText('项目开发工作台');
|
||||
await waitFor(() => {
|
||||
expect(runtimeHarness.listen).toHaveBeenCalledWith(
|
||||
'game-creator-manifest-invalidated',
|
||||
expect.any(Function),
|
||||
);
|
||||
});
|
||||
const firstProjectManifestReadsBeforeRefresh = invoke.mock.calls.filter(
|
||||
([command, args]) =>
|
||||
command === 'get_local_game_manifest' &&
|
||||
args?.projectPath === firstProjectPath,
|
||||
).length;
|
||||
holdFirstRefresh = true;
|
||||
act(() => {
|
||||
runtimeHarness.emitManifestInvalidated('code-prototype');
|
||||
});
|
||||
await firstRefreshStarted;
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
invoke.mock.calls.filter(
|
||||
([command, args]) =>
|
||||
command === 'get_local_game_manifest' &&
|
||||
args?.projectPath === firstProjectPath,
|
||||
).length,
|
||||
).toBeGreaterThan(firstProjectManifestReadsBeforeRefresh);
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '项目组' }));
|
||||
fireEvent.change(screen.getByLabelText('项目目录'), {
|
||||
@@ -481,8 +493,10 @@ export function registerClientHomeTests() {
|
||||
await screen.findByRole('button', { name: /second\.png/ }),
|
||||
).not.toBeNull();
|
||||
|
||||
act(() => resolveStaleRefresh(staleFirstManifest));
|
||||
await Promise.resolve();
|
||||
await act(async () => {
|
||||
resolveStaleRefresh(staleFirstManifest);
|
||||
await staleRefresh;
|
||||
});
|
||||
expect(
|
||||
screen.queryByRole('button', { name: /stale-first\.png/ }),
|
||||
).toBeNull();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# React 组件测试准则
|
||||
|
||||
更新时间:`2026-06-26`
|
||||
更新时间:`2026-08-07`
|
||||
|
||||
## 背景
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
- `data-testid` 名称必须描述用户或稳定渲染边界,例如 `image-canvas-editor-snap-guide-vertical`;不要描述 React 私有 state 名称。
|
||||
- 测试用 fixture 只包含本行为需要的字段。演化中的 payload 使用 `expect.objectContaining(...)` 或 helper 生成默认对象,避免一处契约加字段导致大量无关用例碎裂。
|
||||
- 当测试是为防止历史回归,应在测试名或邻近注释中说明防的是什么行为,而不是记录实现步骤。
|
||||
- 测试 React effect 中注册的事件监听时,触发事件前先用可观测的 listener 调用确认注册已完成;异步请求已开始应用有界 `waitFor` 断言确认,不要用无界手工 Promise 等待一次性信号。解除挂起请求时将 Promise 收尾纳入异步 `act`,确保后续 React 更新在断言前已冲刷。
|
||||
|
||||
## 试点调整
|
||||
|
||||
|
||||
Reference in New Issue
Block a user