189 lines
6.1 KiB
TypeScript
189 lines
6.1 KiB
TypeScript
import { type Page } from "@playwright/test";
|
|
import fs from "node:fs";
|
|
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 creation lives in file-browser-toolbar.tsx ("New file" button) +
|
|
// inline-file-input.tsx (InlineFileInput) + file-browser.tsx
|
|
// (handleStartCreate / handleCreateFileSubmit).
|
|
// Behaviour to lock in:
|
|
// - "New file" at the tree root creates a file at root
|
|
// - After expanding a folder, "New file" creates inside that folder
|
|
// - Typing a path-like name (subdir/file.ts) creates implicit parent folders
|
|
// - Escape cancels the input
|
|
// There is no separate "create folder" affordance today; users create folders
|
|
// implicitly by including a "/" in the new-file name.
|
|
|
|
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;
|
|
}
|
|
|
|
async function startCreateAtRoot(testPage: Page) {
|
|
const btn = testPage.getByRole("button", { name: "New file" });
|
|
await expect(btn).toBeVisible({ timeout: 15_000 });
|
|
await btn.click();
|
|
const input = testPage.getByPlaceholder("filename...");
|
|
await expect(input).toBeVisible({ timeout: 5_000 });
|
|
await expect(input).toBeFocused({ timeout: 2_000 });
|
|
return input;
|
|
}
|
|
|
|
test.describe("File tree create file", () => {
|
|
test("New file at root creates a file on disk and in the tree", async ({
|
|
testPage,
|
|
apiClient,
|
|
seedData,
|
|
backend,
|
|
}) => {
|
|
const repoDir = path.join(backend.tmpDir, "repos", "e2e-repo");
|
|
const git = new GitHelper(repoDir, makeGitEnv(backend.tmpDir));
|
|
// Seed at least one file so the tree loads. Without any files the tree
|
|
// shows "No files found" instead of the toolbar.
|
|
git.createFile("seed.ts", "seed");
|
|
git.stageAll();
|
|
git.commit("seed");
|
|
|
|
const session = await setupTask(
|
|
testPage,
|
|
apiClient,
|
|
seedData,
|
|
"ft-create-root",
|
|
"FT Create Root",
|
|
);
|
|
|
|
await expect(session.fileTreeNode("seed.ts")).toBeVisible({ timeout: 15_000 });
|
|
|
|
const input = await startCreateAtRoot(testPage);
|
|
await input.fill("brand-new.ts");
|
|
await input.press("Enter");
|
|
|
|
await expect(session.fileTreeNode("brand-new.ts")).toBeVisible({ timeout: 10_000 });
|
|
await expect
|
|
.poll(() => fs.existsSync(path.join(repoDir, "brand-new.ts")), { timeout: 10_000 })
|
|
.toBe(true);
|
|
});
|
|
|
|
test("New file inside expanded folder creates the file in that folder", async ({
|
|
testPage,
|
|
apiClient,
|
|
seedData,
|
|
backend,
|
|
}) => {
|
|
const repoDir = path.join(backend.tmpDir, "repos", "e2e-repo");
|
|
const git = new GitHelper(repoDir, makeGitEnv(backend.tmpDir));
|
|
git.createFile("scope/existing.ts", "x");
|
|
git.stageAll();
|
|
git.commit("seed scope");
|
|
|
|
const session = await setupTask(
|
|
testPage,
|
|
apiClient,
|
|
seedData,
|
|
"ft-create-folder",
|
|
"FT Create In Folder",
|
|
);
|
|
|
|
// Expand the folder so it becomes the "active folder" for handleStartCreate.
|
|
const folder = session.fileTreeNode("scope");
|
|
await expect(folder).toBeVisible({ timeout: 15_000 });
|
|
await folder.click();
|
|
await expect(session.fileTreeNode("scope/existing.ts")).toBeVisible({ timeout: 10_000 });
|
|
|
|
await testPage.getByRole("button", { name: "New file" }).click();
|
|
const input = testPage.getByPlaceholder("filename...");
|
|
await expect(input).toBeVisible({ timeout: 5_000 });
|
|
await input.fill("inside.ts");
|
|
await input.press("Enter");
|
|
|
|
await expect(session.fileTreeNode("scope/inside.ts")).toBeVisible({ timeout: 10_000 });
|
|
await expect
|
|
.poll(() => fs.existsSync(path.join(repoDir, "scope", "inside.ts")), { timeout: 10_000 })
|
|
.toBe(true);
|
|
});
|
|
|
|
test("typing path-like name creates implicit parent folder", async ({
|
|
testPage,
|
|
apiClient,
|
|
seedData,
|
|
backend,
|
|
}) => {
|
|
const repoDir = path.join(backend.tmpDir, "repos", "e2e-repo");
|
|
const git = new GitHelper(repoDir, makeGitEnv(backend.tmpDir));
|
|
git.createFile("seed.ts", "seed");
|
|
git.stageAll();
|
|
git.commit("seed");
|
|
|
|
const session = await setupTask(
|
|
testPage,
|
|
apiClient,
|
|
seedData,
|
|
"ft-create-implicit",
|
|
"FT Create Implicit Folder",
|
|
);
|
|
|
|
await expect(session.fileTreeNode("seed.ts")).toBeVisible({ timeout: 15_000 });
|
|
|
|
const input = await startCreateAtRoot(testPage);
|
|
await input.fill("newdir/leaf.ts");
|
|
await input.press("Enter");
|
|
|
|
// The file appears under the new folder on disk.
|
|
await expect
|
|
.poll(() => fs.existsSync(path.join(repoDir, "newdir", "leaf.ts")), { timeout: 10_000 })
|
|
.toBe(true);
|
|
});
|
|
|
|
test("Escape cancels the inline input without creating a file", async ({
|
|
testPage,
|
|
apiClient,
|
|
seedData,
|
|
backend,
|
|
}) => {
|
|
const repoDir = path.join(backend.tmpDir, "repos", "e2e-repo");
|
|
const git = new GitHelper(repoDir, makeGitEnv(backend.tmpDir));
|
|
git.createFile("seed.ts", "seed");
|
|
git.stageAll();
|
|
git.commit("seed");
|
|
|
|
const session = await setupTask(
|
|
testPage,
|
|
apiClient,
|
|
seedData,
|
|
"ft-create-cancel",
|
|
"FT Create Cancel",
|
|
);
|
|
|
|
await expect(session.fileTreeNode("seed.ts")).toBeVisible({ timeout: 15_000 });
|
|
|
|
const input = await startCreateAtRoot(testPage);
|
|
await input.fill("ghost.ts");
|
|
await input.press("Escape");
|
|
|
|
await expect(testPage.getByPlaceholder("filename...")).toHaveCount(0, { timeout: 5_000 });
|
|
await expect(session.fileTreeNode("ghost.ts")).toHaveCount(0);
|
|
expect(fs.existsSync(path.join(repoDir, "ghost.ts"))).toBe(false);
|
|
});
|
|
});
|