修复前端测试偶发超时与异步清理
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 5m15s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 5m36s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 6m6s
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 6m16s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m45s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 2m31s
Project CI / Native shell tests (pull_request) Failing after 2m14s
Project CI / AI game creator shell web tests (pull_request) Failing after 44s
Project CI / Frontend tests (pull_request) Successful in 4m6s
Project CI / Repository checks (pull_request) Successful in 3m0s
Project CI / Backend tests (pull_request) Successful in 6m49s

Direct 活动回合读取失败后的重试定时器随组件卸载清理,避免测试环境销毁后访问 window。

后台素材查询大量缩略图测试合并可见性触发,并补充独立超时预算。

创作首页活动图与资源卡预览调度重载用例分别补充独立超时预算。

清单快照测试补齐 Direct 活动回合 IPC mock,避免未预期命令进入重试。
This commit is contained in:
2026-09-16 12:53:24 +00:00
parent cec971c438
commit 0382cf7394
5 changed files with 39 additions and 12 deletions
@@ -38,6 +38,7 @@ vi.mock('../api/adminApiClient', () => ({
interface MockIntersectionObserverController {
enter: (target: Element) => void;
enterAll: (targets: Element[]) => void;
isObserved: (target: Element) => boolean;
}
@@ -106,6 +107,25 @@ function installIntersectionObserverMock(): MockIntersectionObserverController {
);
});
},
enterAll(targets) {
act(() => {
for (const target of targets) {
const record = observed.get(target);
if (!record) {
throw new Error('目标缩略图尚未进入 IntersectionObserver');
}
record.callback(
[
{
isIntersecting: true,
target,
} as IntersectionObserverEntry,
],
record.observer,
);
}
});
},
isObserved(target) {
return observed.has(target);
},
@@ -753,10 +773,10 @@ test('后台素材查询为大量同时可见的缩略图持续错峰换签', as
const thumbnails = entries.map((entry) =>
thumbnailElementForLabel(entry.label),
);
thumbnails.forEach((thumbnail) => {
for (const thumbnail of thumbnails) {
expect(observer.isObserved(thumbnail)).toBe(true);
observer.enter(thumbnail);
});
}
observer.enterAll(thumbnails);
await act(async () => {
await Promise.resolve();
});
@@ -776,7 +796,7 @@ test('后台素材查询为大量同时可见的缩略图持续错峰换签', as
await vi.advanceTimersByTimeAsync(200);
});
expect(getAdminAssetReadUrl).toHaveBeenCalledTimes(105);
});
}, 10_000);
test('后台素材查询读取更多后为新进入可视区域的素材换签', async () => {
const observer = installIntersectionObserverMock();
@@ -38,11 +38,16 @@ export function useDirectActiveTurns({
const [snapshotReadFailed, setSnapshotReadFailed] = useState(false);
const mountedRef = useRef(true);
const inFlightRef = useRef<Promise<void> | null>(null);
const retryTimerRef = useRef<number | null>(null);
useEffect(() => {
mountedRef.current = true;
return () => {
mountedRef.current = false;
if (retryTimerRef.current !== null) {
window.clearTimeout(retryTimerRef.current);
retryTimerRef.current = null;
}
};
}, []);
@@ -73,12 +78,12 @@ export function useDirectActiveTurns({
return;
} catch {
if (attempt < DIRECT_ACTIVE_TURNS_READ_ATTEMPTS) {
await new Promise((resolve) =>
window.setTimeout(
resolve,
DIRECT_ACTIVE_TURNS_READ_RETRY_DELAY_MS * attempt,
),
);
await new Promise((resolve) => {
retryTimerRef.current = window.setTimeout(() => {
retryTimerRef.current = null;
resolve();
}, DIRECT_ACTIVE_TURNS_READ_RETRY_DELAY_MS * attempt);
});
}
}
}
@@ -3350,7 +3350,7 @@ export function registerProjectWorkbenchFoundationTests() {
expect(URL.createObjectURL).toHaveBeenCalledTimes(
objectUrlCountBeforeLateResult,
);
});
}, 20_000);
it('renders immutable manifest versions, their parent graph, and bound asset highlights', async () => {
const manifest = createGameCreationAppManifest(
@@ -106,6 +106,8 @@ function installInvokeMock() {
return { revision: HELD_REVISION };
case 'get_design_agent_runtime_mode':
return null;
case 'list_game_creator_direct_active_turns':
return [];
case 'read_project_permission_policy':
return {
projectPath: PROJECT_PATH,
@@ -1351,7 +1351,7 @@ describe('CreationLandingView', () => {
`/api/assets/read-url?objectKey=${encodeURIComponent(campaignObjectKey)}`,
expect.objectContaining({ method: 'GET' }),
);
});
}, 10_000);
it('loads more featured resources when the list reaches the end', async () => {
const observers = installIntersectionObserverMock();