From dcdbbd8b3b5fa72ea75a81ebc1619bfd0110ece4 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 17 Feb 2026 01:57:42 +0100 Subject: [PATCH] test: replace ui prototype method patches with instance stubs --- AGENTS.md | 1 + ui/src/ui/navigation.browser.test.ts | 28 ++++------------------------ ui/src/ui/test-helpers/app-mount.ts | 10 +++------- 3 files changed, 8 insertions(+), 31 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 9c825792b0..e7c4bc9f31 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -73,6 +73,7 @@ - Never add `@ts-nocheck` and do not disable `no-explicit-any`; fix root causes and update Oxlint/Oxfmt config only when required. - Never share class behavior via prototype mutation (`applyPrototypeMixins`, `Object.defineProperty` on `.prototype`, or exporting `Class.prototype` for merges). Use explicit inheritance/composition (`A extends B extends C`) or helper composition so TypeScript can typecheck. - If this pattern is needed, stop and get explicit approval before shipping; default behavior is to split/refactor into an explicit class hierarchy and keep members strongly typed. +- In tests, prefer per-instance stubs over prototype mutation (`SomeClass.prototype.method = ...`) unless a test explicitly documents why prototype-level patching is required. - Add brief code comments for tricky or non-obvious logic. - Keep files concise; extract helpers instead of “V2” copies. Use existing patterns for CLI options and dependency injection via `createDefaultDeps`. - Aim to keep files under ~700 LOC; guideline only (not a hard guardrail). Split/refactor when it improves clarity or testability. diff --git a/ui/src/ui/navigation.browser.test.ts b/ui/src/ui/navigation.browser.test.ts index c2ef3865d4..853bc58b6e 100644 --- a/ui/src/ui/navigation.browser.test.ts +++ b/ui/src/ui/navigation.browser.test.ts @@ -1,15 +1,11 @@ -import { afterEach, beforeEach, describe, expect, it } from "vitest"; -import { OpenClawApp } from "./app.ts"; +import { describe, expect, it } from "vitest"; import "../styles.css"; +import { mountApp as mountTestApp, registerAppMountHooks } from "./test-helpers/app-mount.ts"; -// oxlint-disable-next-line typescript/unbound-method -const originalConnect = OpenClawApp.prototype.connect; +registerAppMountHooks(); function mountApp(pathname: string) { - window.history.replaceState({}, "", pathname); - const app = document.createElement("openclaw-app") as OpenClawApp; - document.body.append(app); - return app; + return mountTestApp(pathname); } function nextFrame() { @@ -18,22 +14,6 @@ function nextFrame() { }); } -beforeEach(() => { - OpenClawApp.prototype.connect = () => { - // no-op: avoid real gateway WS connections in browser tests - }; - window.__OPENCLAW_CONTROL_UI_BASE_PATH__ = undefined; - localStorage.clear(); - document.body.innerHTML = ""; -}); - -afterEach(() => { - OpenClawApp.prototype.connect = originalConnect; - window.__OPENCLAW_CONTROL_UI_BASE_PATH__ = undefined; - localStorage.clear(); - document.body.innerHTML = ""; -}); - describe("control UI routing", () => { it("hydrates the tab from the location", async () => { const app = mountApp("/sessions"); diff --git a/ui/src/ui/test-helpers/app-mount.ts b/ui/src/ui/test-helpers/app-mount.ts index d21e453c44..f64c9da6dd 100644 --- a/ui/src/ui/test-helpers/app-mount.ts +++ b/ui/src/ui/test-helpers/app-mount.ts @@ -1,28 +1,24 @@ import { afterEach, beforeEach } from "vitest"; import { OpenClawApp } from "../app.ts"; -// oxlint-disable-next-line typescript/unbound-method -const originalConnect = OpenClawApp.prototype.connect; - export function mountApp(pathname: string) { window.history.replaceState({}, "", pathname); const app = document.createElement("openclaw-app") as OpenClawApp; + app.connect = () => { + // no-op: avoid real gateway WS connections in browser tests + }; document.body.append(app); return app; } export function registerAppMountHooks() { beforeEach(() => { - OpenClawApp.prototype.connect = () => { - // no-op: avoid real gateway WS connections in browser tests - }; window.__OPENCLAW_CONTROL_UI_BASE_PATH__ = undefined; localStorage.clear(); document.body.innerHTML = ""; }); afterEach(() => { - OpenClawApp.prototype.connect = originalConnect; window.__OPENCLAW_CONTROL_UI_BASE_PATH__ = undefined; localStorage.clear(); document.body.innerHTML = "";