Files
kandev/apps/web/e2e/tests/task/file-tree-search.spec.ts
T

148 lines
5.3 KiB
TypeScript

import { type Page } from "@playwright/test";
import path from "node:path";
import { test, expect } from "../../fixtures/test-base";
import type { ApiClient } from "../../helpers/api-client";
import {
GitHelper,
makeGitEnv,
openTaskSession,
createStandardProfile,
} from "../../helpers/git-helper";
// File panel search lives in file-browser-search-header.tsx (input) +
// file-browser-hooks.ts (useFileBrowserSearch with 300ms debounce + WS call
// to searchWorkspaceFiles). Behaviour to lock in:
// - Clicking the Search files toolbar button reveals the input
// - Typing a query produces results that include matching files anywhere
// in the tree (including inside non-expanded folders)
// - Clearing the input goes back to the tree view
// - Escape closes search
async function setupTask(
testPage: Page,
apiClient: ApiClient,
seedData: { workspaceId: string; workflowId: string; startStepId: string; repositoryId: string },
profileName: string,
taskTitle: string,
) {
const profile = await createStandardProfile(apiClient, profileName);
await apiClient.createTaskWithAgent(seedData.workspaceId, taskTitle, profile.id, {
description: "/e2e:simple-message",
workflow_id: seedData.workflowId,
workflow_step_id: seedData.startStepId,
repository_ids: [seedData.repositoryId],
});
const session = await openTaskSession(testPage, taskTitle);
await session.clickTab("Files");
return session;
}
test.describe("File tree search", () => {
test("search reveals matches inside collapsed folders", async ({
testPage,
apiClient,
seedData,
backend,
}) => {
const repoDir = path.join(backend.tmpDir, "repos", "e2e-repo");
const git = new GitHelper(repoDir, makeGitEnv(backend.tmpDir));
git.createFile("search-alpha.ts", "a");
// Match lives inside a folder we never expand. Search must still find it.
git.createFile("deep/nested/needle-target.ts", "needle");
git.createFile("other-search/sibling.ts", "s");
git.stageAll();
git.commit("seed search");
const session = await setupTask(
testPage,
apiClient,
seedData,
"ft-search-collapsed",
"FT Search Collapsed",
);
await expect(session.fileTreeNode("search-alpha.ts")).toBeVisible({ timeout: 15_000 });
// Sanity: target file's tree row is not in the DOM (folder is collapsed).
await expect(session.fileTreeNode("deep/nested/needle-target.ts")).toHaveCount(0);
await testPage.getByRole("button", { name: "Search files" }).click();
const input = testPage.getByPlaceholder("Search files...");
await expect(input).toBeVisible({ timeout: 5_000 });
await input.fill("needle-target");
// Search panel shows the matching path even though the parent folders
// are not expanded in the tree.
const match = session.files.getByText("needle-target.ts", { exact: false });
await expect(match).toBeVisible({ timeout: 5_000 });
// The folder path should appear next to the name.
await expect(session.files.getByText("deep/nested/", { exact: false })).toBeVisible();
});
test("clearing the search input restores the tree view", async ({
testPage,
apiClient,
seedData,
backend,
}) => {
const repoDir = path.join(backend.tmpDir, "repos", "e2e-repo");
const git = new GitHelper(repoDir, makeGitEnv(backend.tmpDir));
git.createFile("clear-alpha.ts", "a");
git.createFile("clear-bravo.ts", "b");
git.stageAll();
git.commit("seed clear");
const session = await setupTask(
testPage,
apiClient,
seedData,
"ft-search-clear",
"FT Search Clear",
);
await expect(session.fileTreeNode("clear-alpha.ts")).toBeVisible({ timeout: 15_000 });
await testPage.getByRole("button", { name: "Search files" }).click();
const input = testPage.getByPlaceholder("Search files...");
await input.fill("clear-alpha");
// Wait for the debounced search and the filtered list to settle.
await expect(session.files.getByText("clear-alpha.ts", { exact: false })).toBeVisible({
timeout: 5_000,
});
// Clear input - tree comes back, both files visible again.
await input.fill("");
await expect(session.fileTreeNode("clear-alpha.ts")).toBeVisible({ timeout: 5_000 });
await expect(session.fileTreeNode("clear-bravo.ts")).toBeVisible({ timeout: 5_000 });
});
test("Escape closes the search header and returns to the tree", async ({
testPage,
apiClient,
seedData,
backend,
}) => {
const repoDir = path.join(backend.tmpDir, "repos", "e2e-repo");
const git = new GitHelper(repoDir, makeGitEnv(backend.tmpDir));
git.createFile("escape-target.ts", "e");
git.stageAll();
git.commit("seed escape");
const session = await setupTask(
testPage,
apiClient,
seedData,
"ft-search-escape",
"FT Search Escape",
);
await expect(session.fileTreeNode("escape-target.ts")).toBeVisible({ timeout: 15_000 });
await testPage.getByRole("button", { name: "Search files" }).click();
const input = testPage.getByPlaceholder("Search files...");
await expect(input).toBeVisible({ timeout: 5_000 });
await input.press("Escape");
await expect(input).toHaveCount(0, { timeout: 5_000 });
// Toolbar comes back - the search button is once again accessible.
await expect(testPage.getByRole("button", { name: "Search files" })).toBeVisible();
});
});