import { readFileSync } from "node:fs"; import { resolve } from "node:path"; import { expect, test } from "@playwright/test"; import { isClawdTransportInterruption, shouldRestoreClawdComposerAfterFailure, } from "@/lib/openclaw-chat/request-lifecycle"; function readSource(path: string) { return readFileSync(resolve(process.cwd(), path), "utf8"); } test.describe("Claw request lifecycle", () => { test("restores the composer only after an explicit user cancellation", () => { expect( shouldRestoreClawdComposerAfterFailure({ explicitlyCanceled: true }) ).toBe(true); expect( shouldRestoreClawdComposerAfterFailure({ explicitlyCanceled: false }) ).toBe(false); }); test("recognizes browser and network interruptions without treating them as edits", () => { expect( isClawdTransportInterruption( new DOMException("The operation was aborted", "AbortError") ) ).toBe(true); expect(isClawdTransportInterruption(new Error("fetch failed"))).toBe(true); expect(isClawdTransportInterruption(new Error("provider unavailable"))).toBe( false ); }); test("does not cancel the upstream request when the browser connection closes", () => { const source = readSource("app/api/canvas/clawd-chat/route.ts"); expect(source).not.toContain('request.signal.addEventListener("abort"'); expect(source).not.toContain("cancelClawdRequest(cancelKey)"); expect(source).toMatch( /registerActiveClawdRequest\(\s*(?:params\.)?cancelKey/ ); }); test("does not classify every AbortError as an explicit stop action", () => { const source = readSource("components/canvas/CanvasChatPanel.tsx"); expect(source).toContain("shouldRestoreClawdComposerAfterFailure"); expect(source).not.toContain( '(error instanceof Error && error.name === "AbortError")' ); }); });