mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 05:16:23 -06:00
fix(ui): preserve panel actions when session storage is unavailable
This commit is contained in:
@@ -14,6 +14,16 @@ import { ShellChromeOwner, type ShellChromeHost } from "./app-shell-chrome.ts";
|
||||
import type { ApplicationContext } from "./context.ts";
|
||||
import type { LazyCustomElementRequestController } from "./lazy-custom-element.ts";
|
||||
import { persistLazyShellAction, readLazyShellAction } from "./lazy-shell-action.ts";
|
||||
import { retryStaleChunkReloadWhenReachable } from "./stale-chunk-reload.ts";
|
||||
|
||||
vi.mock("./stale-chunk-reload.ts", async () => {
|
||||
const actual =
|
||||
await vi.importActual<typeof import("./stale-chunk-reload.ts")>("./stale-chunk-reload.ts");
|
||||
return {
|
||||
...actual,
|
||||
retryStaleChunkReloadWhenReachable: vi.fn(async () => true),
|
||||
};
|
||||
});
|
||||
|
||||
type ShellPanelToggleState = {
|
||||
lazyCustomElements: LazyCustomElementRequestController;
|
||||
@@ -56,6 +66,7 @@ function configureTerminalShell(terminalElement: TestOptionalCustomElement): She
|
||||
afterEach(() => {
|
||||
window.history.replaceState(null, "", "/");
|
||||
resetAppHostTestGlobals();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("OpenClaw shell panel toggles", () => {
|
||||
@@ -102,6 +113,35 @@ describe("OpenClaw shell panel toggles", () => {
|
||||
expect(delivered.detail).toEqual(event.detail);
|
||||
});
|
||||
|
||||
it("retries a stale panel in place when its action cannot survive a document reload", async () => {
|
||||
vi.stubGlobal("sessionStorage", undefined);
|
||||
const error = new Error("Failed to fetch dynamically imported module: terminal.js");
|
||||
const terminalElement = createLazyElementSpec("storage-disabled terminal panel", {
|
||||
firstError: error,
|
||||
});
|
||||
const terminalToggle = vi.fn();
|
||||
const shell = configureTerminalShell(terminalElement);
|
||||
const owner = chromeOwner(shell);
|
||||
const event = new CustomEvent(TERMINAL_PANEL_TOGGLE_EVENT, {
|
||||
detail: { dock: "right", open: true },
|
||||
});
|
||||
window.addEventListener(TERMINAL_PANEL_TOGGLE_EVENT, terminalToggle);
|
||||
|
||||
try {
|
||||
owner.handleDeferredTerminalToggle(event);
|
||||
await vi.waitFor(() => expect(shell.lazyCustomElements.visibleState?.status).toBe("error"));
|
||||
|
||||
shell.lazyCustomElements.retry();
|
||||
|
||||
expect(retryStaleChunkReloadWhenReachable).not.toHaveBeenCalled();
|
||||
await vi.waitFor(() => expect(terminalToggle).toHaveBeenCalledOnce());
|
||||
const delivered = terminalToggle.mock.calls[0]?.[0] as CustomEvent;
|
||||
expect(delivered.detail).toEqual(event.detail);
|
||||
} finally {
|
||||
window.removeEventListener(TERMINAL_PANEL_TOGGLE_EVENT, terminalToggle);
|
||||
}
|
||||
});
|
||||
|
||||
it("restores a structured panel event once in a replacement shell", async () => {
|
||||
vi.stubGlobal("sessionStorage", createStorageMock());
|
||||
const terminalElement = createLazyElementSpec("restored terminal");
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
} from "./app-host.test-support.ts";
|
||||
import "./app-host.ts";
|
||||
import { DEBUG_OVERLAY_ELEMENT } from "./lazy-custom-element.ts";
|
||||
import { readLazyShellAction } from "./lazy-shell-action.ts";
|
||||
import { hasStoredLazyShellAction, readLazyShellAction } from "./lazy-shell-action.ts";
|
||||
|
||||
const storageKey = "openclaw:lazy-event";
|
||||
|
||||
@@ -33,6 +33,58 @@ async function withConnectedShell(shell: ShellLifecycle, run: () => void | Promi
|
||||
afterEach(resetAppHostTestGlobals);
|
||||
|
||||
describe("lazy shell action storage", () => {
|
||||
it("does not report a stored action when session storage is unavailable", () => {
|
||||
vi.stubGlobal("sessionStorage", undefined);
|
||||
|
||||
expect(hasStoredLazyShellAction()).toBe(false);
|
||||
});
|
||||
|
||||
it("does not report a stored action when browser storage access is denied", () => {
|
||||
const originalVitest = process.env.VITEST;
|
||||
const originalStorage = Object.getOwnPropertyDescriptor(globalThis, "sessionStorage");
|
||||
process.env.VITEST = "";
|
||||
Object.defineProperty(globalThis, "sessionStorage", {
|
||||
configurable: true,
|
||||
get() {
|
||||
throw new DOMException("Access is denied", "SecurityError");
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
expect(hasStoredLazyShellAction()).toBe(false);
|
||||
} finally {
|
||||
if (originalVitest === undefined) {
|
||||
Reflect.deleteProperty(process.env, "VITEST");
|
||||
} else {
|
||||
process.env.VITEST = originalVitest;
|
||||
}
|
||||
if (originalStorage) {
|
||||
Object.defineProperty(globalThis, "sessionStorage", originalStorage);
|
||||
} else {
|
||||
Reflect.deleteProperty(globalThis, "sessionStorage");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("does not report a stored action when session storage rejects reads", () => {
|
||||
const storage = createStorageMock();
|
||||
storage.getItem = () => {
|
||||
throw new DOMException("Access is denied", "SecurityError");
|
||||
};
|
||||
vi.stubGlobal("sessionStorage", storage);
|
||||
|
||||
expect(hasStoredLazyShellAction()).toBe(false);
|
||||
});
|
||||
|
||||
it("reports only actions actually present in available session storage", () => {
|
||||
const storage = createStorageMock();
|
||||
vi.stubGlobal("sessionStorage", storage);
|
||||
|
||||
expect(hasStoredLazyShellAction()).toBe(false);
|
||||
storage.setItem(storageKey, JSON.stringify({ eventType: COMMAND_PALETTE_OPEN_EVENT }));
|
||||
expect(hasStoredLazyShellAction()).toBe(true);
|
||||
});
|
||||
|
||||
it.each([
|
||||
"{",
|
||||
JSON.stringify({ eventType: COMMAND_PALETTE_OPEN_EVENT, extra: true }),
|
||||
|
||||
@@ -44,7 +44,7 @@ export function lazyShellEvent(
|
||||
|
||||
export function hasStoredLazyShellAction(): boolean {
|
||||
try {
|
||||
return getSafeSessionStorage()?.getItem(STORAGE_KEY) !== null;
|
||||
return getSafeSessionStorage()?.getItem(STORAGE_KEY) != null;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user