mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:40:44 +00:00
test(ui): clean up navigation teardown (#76625)
This commit is contained in:
@@ -48,6 +48,10 @@ type LifecycleHost = {
|
||||
logsAutoFollow: boolean;
|
||||
logsAtBottom: boolean;
|
||||
logsEntries: unknown[];
|
||||
chatScrollFrame?: number | null;
|
||||
chatScrollTimeout?: number | null;
|
||||
logsScrollFrame?: number | null;
|
||||
controlUiTabPaintSeq?: number;
|
||||
popStateHandler: () => void;
|
||||
topbarObserver: ResizeObserver | null;
|
||||
};
|
||||
@@ -79,12 +83,31 @@ export function handleFirstUpdated(host: LifecycleHost) {
|
||||
observeTopbar(host as unknown as Parameters<typeof observeTopbar>[0]);
|
||||
}
|
||||
|
||||
function cancelHostAnimationFrame(frame: number | null | undefined) {
|
||||
if (frame != null && typeof window.cancelAnimationFrame === "function") {
|
||||
window.cancelAnimationFrame(frame);
|
||||
}
|
||||
}
|
||||
|
||||
function clearHostTimeout(timeout: number | null | undefined) {
|
||||
if (timeout != null && typeof window.clearTimeout === "function") {
|
||||
window.clearTimeout(timeout);
|
||||
}
|
||||
}
|
||||
|
||||
export function handleDisconnected(host: LifecycleHost) {
|
||||
host.connectGeneration += 1;
|
||||
host.controlUiTabPaintSeq = (host.controlUiTabPaintSeq ?? 0) + 1;
|
||||
window.removeEventListener("popstate", host.popStateHandler);
|
||||
stopNodesPolling(host as unknown as Parameters<typeof stopNodesPolling>[0]);
|
||||
stopLogsPolling(host as unknown as Parameters<typeof stopLogsPolling>[0]);
|
||||
stopDebugPolling(host as unknown as Parameters<typeof stopDebugPolling>[0]);
|
||||
cancelHostAnimationFrame(host.chatScrollFrame);
|
||||
host.chatScrollFrame = null;
|
||||
cancelHostAnimationFrame(host.logsScrollFrame);
|
||||
host.logsScrollFrame = null;
|
||||
clearHostTimeout(host.chatScrollTimeout);
|
||||
host.chatScrollTimeout = null;
|
||||
host.realtimeTalkSession?.stop();
|
||||
host.realtimeTalkSession = null;
|
||||
host.realtimeTalkActive = false;
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { Tab } from "./navigation.ts";
|
||||
|
||||
type ControlUiPerformanceHost = {
|
||||
tab: Tab;
|
||||
isConnected?: boolean;
|
||||
eventLog?: unknown[];
|
||||
eventLogBuffer?: unknown[];
|
||||
requestUpdate?: () => void;
|
||||
@@ -89,7 +90,7 @@ export function scheduleControlUiTabVisibleTiming(
|
||||
host.requestUpdate?.();
|
||||
|
||||
const record = () => {
|
||||
if (host.controlUiTabPaintSeq !== seq || host.tab !== tab) {
|
||||
if (host.isConnected === false || host.controlUiTabPaintSeq !== seq || host.tab !== tab) {
|
||||
return;
|
||||
}
|
||||
recordControlUiPerformanceEvent(host, "control-ui.tab.visible", {
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { mountApp as mountTestApp, registerAppMountHooks } from "./test-helpers/app-mount.ts";
|
||||
|
||||
registerAppMountHooks();
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
function mountApp(pathname: string) {
|
||||
return mountTestApp(pathname);
|
||||
}
|
||||
|
||||
@@ -41,9 +41,69 @@ function createMatchMediaMock(width: number) {
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const mountedApps = new Set<OpenClawApp>();
|
||||
|
||||
function collectMountedApps() {
|
||||
return new Set<OpenClawApp>([
|
||||
...mountedApps,
|
||||
...document.querySelectorAll<OpenClawApp>("openclaw-app"),
|
||||
]);
|
||||
}
|
||||
|
||||
function nextMicrotask() {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
function nextTimer() {
|
||||
return new Promise<void>((resolve) => window.setTimeout(resolve, 0));
|
||||
}
|
||||
|
||||
function nextFrame() {
|
||||
return new Promise<void>((resolve) => {
|
||||
if (typeof window.requestAnimationFrame !== "function") {
|
||||
window.setTimeout(resolve, 0);
|
||||
return;
|
||||
}
|
||||
window.requestAnimationFrame(() => resolve());
|
||||
});
|
||||
}
|
||||
|
||||
async function waitForAppUpdates(apps: Iterable<OpenClawApp>) {
|
||||
for (const app of apps) {
|
||||
await app.updateComplete;
|
||||
}
|
||||
}
|
||||
|
||||
async function drainAppWork(apps: Iterable<OpenClawApp>) {
|
||||
const snapshot = [...apps];
|
||||
await nextMicrotask();
|
||||
await waitForAppUpdates(snapshot);
|
||||
await nextFrame();
|
||||
await nextMicrotask();
|
||||
await nextFrame();
|
||||
await nextMicrotask();
|
||||
await waitForAppUpdates(snapshot);
|
||||
await nextTimer();
|
||||
await nextMicrotask();
|
||||
await waitForAppUpdates(snapshot);
|
||||
}
|
||||
|
||||
async function cleanupMountedApps() {
|
||||
const apps = collectMountedApps();
|
||||
await drainAppWork(apps);
|
||||
for (const app of apps) {
|
||||
app.remove();
|
||||
}
|
||||
document.body.replaceChildren();
|
||||
mountedApps.clear();
|
||||
await drainAppWork(apps);
|
||||
}
|
||||
|
||||
export function mountApp(pathname: string) {
|
||||
window.history.replaceState({}, "", pathname);
|
||||
const app = document.createElement("openclaw-app") as OpenClawApp;
|
||||
mountedApps.add(app);
|
||||
document.body.append(app);
|
||||
app.connected = true;
|
||||
app.requestUpdate();
|
||||
@@ -96,12 +156,14 @@ export function registerAppMountHooks() {
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await cleanupMountedApps();
|
||||
window.__OPENCLAW_CONTROL_UI_BASE_PATH__ = undefined;
|
||||
getSafeLocalStorage()?.clear();
|
||||
getSafeSessionStorage()?.clear();
|
||||
document.body.innerHTML = "";
|
||||
await i18n.setLocale("en");
|
||||
vi.restoreAllMocks();
|
||||
vi.unstubAllGlobals();
|
||||
await new Promise<void>((resolve) => setTimeout(resolve, 0));
|
||||
await nextTimer();
|
||||
await nextMicrotask();
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user