Skip to content

Commit 95cf86d

Browse files
committed
🤖 refactor: organize browser UI integration harness
1 parent 38bb752 commit 95cf86d

File tree

9 files changed

+68
-16
lines changed

9 files changed

+68
-16
lines changed

jest.config.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,10 @@ module.exports = {
5454
displayName: "browser-ui",
5555
testEnvironment: "jsdom",
5656
testMatch: ["<rootDir>/tests/browser/**/*.test.tsx"],
57-
setupFiles: ["<rootDir>/tests/browser/global-setup.js"],
57+
setupFiles: ["<rootDir>/tests/browser/harness/global-setup.js"],
5858
setupFilesAfterEnv: [
5959
"<rootDir>/tests/setup.ts",
60-
"<rootDir>/tests/browser/jestSetup.ts",
60+
"<rootDir>/tests/browser/harness/jestSetup.ts",
6161
],
6262
},
6363
],

tests/browser/appLoader.test.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@
88
import userEvent from "@testing-library/user-event";
99
import "@testing-library/jest-dom";
1010
import { shouldRunIntegrationTests } from "../testUtils";
11-
import { renderWithBackend } from "./renderWithBackend";
12-
import { createTempGitRepo, cleanupTempGitRepo } from "./setup";
1311
import {
12+
renderWithBackend,
13+
createTempGitRepo,
14+
cleanupTempGitRepo,
1415
waitForAppLoad,
1516
addProjectViaUI,
1617
expandProject,
1718
getProjectName,
18-
} from "./uiHelpers";
19+
} from "./harness";
1920

2021
const describeIntegration = shouldRunIntegrationTests() ? describe : describe.skip;
2122

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import type { BrowserWindow, WebContents } from "electron";
1414
import { Config } from "@/node/config";
1515
import { ServiceContainer } from "@/node/services/serviceContainer";
1616
import type { ORPCContext } from "@/node/orpc/context";
17-
import { createOrpcTestClient, type OrpcTestClient } from "../ipc/orpcTestClient";
17+
import { createOrpcTestClient, type OrpcTestClient } from "../../ipc/orpcTestClient";
1818
import type { APIClient } from "@/browser/contexts/API";
1919

2020
const execAsync = promisify(exec);

tests/browser/harness/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/**
2+
* Browser UI integration test harness.
3+
*
4+
* Keep all non-test helpers in this folder so the top-level `tests/browser/`
5+
* contains only test entrypoints.
6+
*/
7+
8+
export * from "./env";
9+
export * from "./render";
10+
export * from "./ui";
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import React from "react";
1010
import { act, render, type RenderOptions, type RenderResult } from "@testing-library/react";
1111
import { AppLoader } from "@/browser/components/AppLoader";
1212
import type { APIClient } from "@/browser/contexts/API";
13-
import { createBrowserTestEnv, type BrowserTestEnv } from "./setup";
13+
import { createBrowserTestEnv, type BrowserTestEnv } from "./env";
1414

1515
export interface RenderWithBackendResult extends RenderResult {
1616
/** Test environment with real backend */
Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@
88
* Use these instead of direct oRPC calls to keep tests UI-driven.
99
*/
1010

11-
import { waitFor, type RenderResult } from "@testing-library/react";
11+
import { waitFor, within, type RenderResult } from "@testing-library/react";
1212
import type { UserEvent } from "@testing-library/user-event";
1313

1414
type Queries = Pick<
1515
RenderResult,
1616
| "findByRole"
1717
| "findByText"
18+
| "findByTestId"
1819
| "findByPlaceholderText"
1920
| "queryByRole"
2021
| "queryByText"
@@ -27,12 +28,51 @@ type Queries = Pick<
2728
export async function waitForAppLoad(queries: Pick<Queries, "queryByText">) {
2829
await waitFor(
2930
() => {
30-
expect(queries.queryByText(/loading workspaces/i)).not.toBeInTheDocument();
31+
expect(queries.queryByText(/loading workspaces/i)).toBeNull();
3132
},
3233
{ timeout: 5000 }
3334
);
3435
}
3536

37+
/**
38+
* Open the Settings modal.
39+
*
40+
* Note: the Dialog is rendered in a portal; queries must search `document.body`.
41+
* RTL's render() uses `document.body` as baseElement by default, so RenderResult
42+
* queries work fine.
43+
*/
44+
export async function openSettingsModal(
45+
user: UserEvent,
46+
queries: Pick<Queries, "findByTestId" | "findByRole">
47+
): Promise<HTMLElement> {
48+
const settingsButton = await queries.findByTestId("settings-button");
49+
await user.click(settingsButton);
50+
51+
const modal = await queries.findByRole("dialog");
52+
expect(modal).toBeTruthy();
53+
return modal;
54+
}
55+
56+
/**
57+
* Open the Settings modal and navigate to a particular section.
58+
*/
59+
export async function openSettingsToSection(
60+
user: UserEvent,
61+
queries: Pick<Queries, "findByTestId" | "findByRole">,
62+
sectionLabel: "General" | "Providers" | "Projects" | "Models"
63+
): Promise<HTMLElement> {
64+
const modal = await openSettingsModal(user, queries);
65+
66+
if (sectionLabel !== "General") {
67+
const sectionButton = within(modal).getByRole("button", {
68+
name: new RegExp(`^${sectionLabel}$`, "i"),
69+
});
70+
await user.click(sectionButton);
71+
}
72+
73+
return modal;
74+
}
75+
3676
/**
3777
* Add a project via the UI.
3878
*
@@ -56,7 +96,7 @@ export async function addProjectViaUI(
5696

5797
// Wait for modal to open
5898
const modal = await queries.findByRole("dialog");
59-
expect(modal).toBeInTheDocument();
99+
expect(modal).toBeTruthy();
60100

61101
// Type the project path in the input
62102
const pathInput = await queries.findByPlaceholderText(/home.*project|path/i);
@@ -71,7 +111,7 @@ export async function addProjectViaUI(
71111
await waitFor(
72112
() => {
73113
// Modal should close on success
74-
expect(queries.queryByRole("dialog")).not.toBeInTheDocument();
114+
expect(queries.queryByRole("dialog")).toBeNull();
75115
},
76116
{ timeout: 5000 }
77117
);
@@ -142,7 +182,7 @@ export async function removeProjectViaUI(
142182
await waitFor(() => {
143183
expect(
144184
queries.queryByRole("button", { name: new RegExp(`project ${projectName}`, "i") })
145-
).not.toBeInTheDocument();
185+
).toBeNull();
146186
});
147187
}
148188

@@ -210,7 +250,7 @@ export async function removeWorkspaceViaUI(
210250
await waitFor(() => {
211251
expect(
212252
queries.queryByRole("button", { name: `Select workspace ${workspaceName}` })
213-
).not.toBeInTheDocument();
253+
).toBeNull();
214254
});
215255
}
216256

tests/browser/projectManagement.test.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ import { waitFor } from "@testing-library/react";
1111
import userEvent from "@testing-library/user-event";
1212
import "@testing-library/jest-dom";
1313
import { shouldRunIntegrationTests } from "../testUtils";
14-
import { renderWithBackend } from "./renderWithBackend";
15-
import { createTempGitRepo, cleanupTempGitRepo } from "./setup";
1614
import {
15+
renderWithBackend,
16+
createTempGitRepo,
17+
cleanupTempGitRepo,
1718
waitForAppLoad,
1819
addProjectViaUI,
1920
expandProject,
@@ -23,7 +24,7 @@ import {
2324
removeWorkspaceViaUI,
2425
clickNewChat,
2526
getProjectName,
26-
} from "./uiHelpers";
27+
} from "./harness";
2728

2829
const describeIntegration = shouldRunIntegrationTests() ? describe : describe.skip;
2930

0 commit comments

Comments
 (0)