Files
OpenHands/vitest.setup.ts
T
Graham Neubigandopenhands 6a628ede6e Gate browser tool requests on server support (#64)
* Gate browser tool requests on server support

Co-authored-by: openhands <openhands@all-hands.dev>

* Support usable_tools browser gating

Co-authored-by: openhands <openhands@all-hands.dev>

* Remove available_tools fallback

Co-authored-by: openhands <openhands@all-hands.dev>

* QA artifacts: live verification GIFs against agent-server PR #3028

Captured against two live agent-server containers (one built from
ghcr.io/openhands/agent-server:a3c0247-python with chromium, one with
chromium binaries removed):

- browser-tool-not-advertised: server's /server_info.usable_tools omits
  browser_tool_set; frontend POST /api/conversations sends
  agent.tools = ["terminal", "file_editor", "task_tracker"]
- browser-tool-advertised: server advertises browser_tool_set; frontend
  POST /api/conversations sends
  agent.tools = ["terminal", "file_editor", "task_tracker", "browser_tool_set"]

Co-authored-by: openhands <openhands@all-hands.dev>

* test: drain microtasks in vitest afterEach to avoid late-rejection flake

After main reverted HeroUI v3 to v2 (#124) and PR #64 added new tests
that change the file ordering, CI started flaking with unhandled
rejections like 'ReferenceError: window is not defined' originating
inside react-dom's resolveUpdatePriority. The root cause is that HeroUI
v2 components (e.g. Tooltip used by ConversationStatusDot) wrap content
in framer-motion's LazyMotion, which queues a setState in a microtask
that can resolve after the test file's jsdom environment is torn down.

Awaiting two microtask ticks at the end of every afterEach lets those
queued updates settle while window is still defined, eliminating the
spurious failures without affecting tests that use fake timers (we use
Promise.resolve() rather than setTimeout(0)).

Co-authored-by: openhands <openhands@all-hands.dev>

---------

Co-authored-by: openhands <openhands@all-hands.dev>
2026-05-08 16:39:14 -04:00

95 lines
2.7 KiB
TypeScript

import { afterAll, afterEach, beforeAll, vi } from "vitest";
import { cleanup } from "@testing-library/react";
import { server } from "#/mocks/node";
import "@testing-library/jest-dom/vitest";
if (typeof HTMLCanvasElement !== "undefined") {
HTMLCanvasElement.prototype.getContext = vi.fn();
}
if (typeof HTMLElement !== "undefined") {
HTMLElement.prototype.scrollTo = vi.fn();
}
const windowStub =
typeof window === "undefined"
? ({ event: undefined } as unknown as Window & typeof globalThis)
: window;
vi.stubGlobal("window", windowStub);
windowStub.scrollTo = vi.fn();
if (typeof requestAnimationFrame === "undefined") {
vi.stubGlobal("requestAnimationFrame", (callback: FrameRequestCallback) =>
setTimeout(() => callback(0), 0),
);
vi.stubGlobal(
"cancelAnimationFrame",
(timeoutId: ReturnType<typeof setTimeout>) => clearTimeout(timeoutId),
);
}
// Mock ResizeObserver for test environment
class MockResizeObserver {
observe = vi.fn();
unobserve = vi.fn();
disconnect = vi.fn();
}
// Mock the i18n provider
vi.mock("react-i18next", async (importOriginal) => ({
...(await importOriginal<typeof import("react-i18next")>()),
useTranslation: () => ({
t: (key: string) => key,
i18n: {
language: "en",
exists: () => false,
},
}),
}));
vi.mock("#/hooks/use-is-on-tos-page", () => ({
useIsOnTosPage: () => false,
}));
vi.mock("#/hooks/use-is-on-intermediate-page", () => ({
useIsOnIntermediatePage: () => false,
}));
// Mock useRevalidator from react-router to allow direct store manipulation in tests
vi.mock("react-router", async (importOriginal) => ({
...(await importOriginal<typeof import("react-router")>()),
useRevalidator: () => ({
revalidate: vi.fn(),
}),
}));
// Import the Zustand mock to enable automatic store resets
vi.mock("zustand");
// Mock requests during tests
beforeAll(() => {
server.listen({ onUnhandledRequest: "bypass" });
vi.stubGlobal("ResizeObserver", MockResizeObserver);
});
afterEach(async () => {
server.resetHandlers();
// Cleanup the document body after each test
cleanup();
// Drain any queued microtasks before jsdom is torn down between test files.
// Without this, async state updates queued during render (for example by
// HeroUI v2 components wrapped in framer-motion's LazyMotion) can resolve
// after `window` is gone and trigger spurious unhandled rejections in
// react-dom's `resolveUpdatePriority`. We use `Promise.resolve()` (a
// microtask) rather than `setTimeout(0)` so this stays compatible with
// tests that install fake timers.
await Promise.resolve();
await Promise.resolve();
});
afterAll(() => {
server.close();
vi.unstubAllGlobals();
});