mirror of
https://github.com/OpenHands/OpenHands.git
synced 2026-10-07 16:19:05 +08:00
* 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>
95 lines
2.7 KiB
TypeScript
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();
|
|
});
|