diff --git a/packages/app/e2e/settings-host-page.spec.ts b/packages/app/e2e/settings-host-page.spec.ts index cb1a1df19..d95dc9503 100644 --- a/packages/app/e2e/settings-host-page.spec.ts +++ b/packages/app/e2e/settings-host-page.spec.ts @@ -23,6 +23,12 @@ async function openHostPage(page: Page, serverId: string) { await expect(page.getByTestId(`settings-host-page-${serverId}`)).toBeVisible(); } +async function expectHostLabelHeader(page: Page) { + await expect(page.getByTestId("settings-detail-header-title")).toHaveText(TEST_HOST_LABEL); + await expect(page.getByTestId("host-page-label-edit-button")).toBeVisible(); + await expect(page.getByTestId("host-page-label-input")).toHaveCount(0); +} + test.describe("Settings host page", () => { test("host page shows seeded label, connection endpoint, inject MCP toggle, and all action rows", async ({ page, @@ -34,16 +40,8 @@ test.describe("Settings host page", () => { await openSettings(page); await openHostPage(page, serverId); - // Label renders as a title with a pencil edit affordance; the input is hidden until edit. - await expect(page.getByTestId("host-page-label-card")).toBeVisible(); - await expect(page.getByTestId("host-page-label-edit-button")).toBeVisible(); - await expect(page.getByTestId("host-page-label-input")).toHaveCount(0); - await expect( - page.getByTestId("host-page-label-card").getByText(TEST_HOST_LABEL, { exact: true }), - ).toBeVisible(); - - // Desktop detail pane shows a ScreenHeader with the host label. - await expect(page.getByTestId("settings-detail-header-title")).toHaveText(TEST_HOST_LABEL); + // Label renders in the detail header with a pencil edit affordance; the input is hidden until edit. + await expectHostLabelHeader(page); // Connections is its own section with a "Connections" heading and the seeded endpoint row. const connectionsCard = page.getByTestId("host-page-connections-card"); @@ -124,7 +122,7 @@ test.describe("Settings host page", () => { await page.goto(`/settings/hosts/${encodeURIComponent(serverId)}`); await expect(page.getByTestId(`settings-host-page-${serverId}`)).toBeVisible(); - await expect(page.getByTestId("host-page-label-card")).toBeVisible(); + await expectHostLabelHeader(page); await expect(page.getByTestId("host-page-remove-host-card")).toBeVisible(); }); diff --git a/packages/app/e2e/settings-navigation.spec.ts b/packages/app/e2e/settings-navigation.spec.ts index 301d40e75..f5425c4cc 100644 --- a/packages/app/e2e/settings-navigation.spec.ts +++ b/packages/app/e2e/settings-navigation.spec.ts @@ -93,6 +93,15 @@ test.describe("Settings — compact master-detail", () => { await expect(page.getByTestId("settings-sidebar")).toBeVisible(); } + async function expectCompactSettingsRootList(page: Page) { + await expect(page).toHaveURL(/\/settings$/); + await expect(page.getByTestId("settings-sidebar")).toBeVisible(); + + await expect(page.getByText("Theme", { exact: true })).toHaveCount(0); + await expect(page.getByRole("button", { name: "Play test" })).toHaveCount(0); + await expect(page.locator('[data-testid^="settings-host-page-"]')).toHaveCount(0); + } + test("/settings renders only the sidebar list (no section content)", async ({ page }) => { await openCompactSettingsRoot(page); @@ -110,12 +119,12 @@ test.describe("Settings — compact master-detail", () => { ).toBeVisible(); // Section detail content is NOT rendered at the root. - await expect(page.getByText("Theme", { exact: true })).toHaveCount(0); - await expect(page.getByRole("button", { name: "Play test" })).toHaveCount(0); - await expect(page.getByTestId("host-page-label-card")).toHaveCount(0); + await expectCompactSettingsRootList(page); - // Root shows the menu header, not a back button. - await expect(page.getByRole("button", { name: "Back", exact: true })).toHaveCount(0); + const rootBackButton = page.getByRole("button", { name: "Back", exact: true }); + await expect(rootBackButton).toBeVisible(); + await rootBackButton.click(); + await expect(page).not.toHaveURL(/\/settings(\/|$)/); }); test("tapping a section pushes /settings/[section] and shows a back button", async ({ page }) => { @@ -144,9 +153,8 @@ test.describe("Settings — compact master-detail", () => { await expect(page).toHaveURL(/\/settings\/about$/); await page.getByRole("button", { name: "Back", exact: true }).click(); - await expect(page).toHaveURL(/\/settings$/); - await expect(page.getByTestId("settings-sidebar")).toBeVisible(); - await expect(page.getByRole("button", { name: "Back", exact: true })).toHaveCount(0); + await expectCompactSettingsRootList(page); + await expect(page.getByRole("button", { name: "Back", exact: true })).toBeVisible(); }); test("tapping a host entry pushes /settings/hosts/[serverId]", async ({ page }) => { diff --git a/packages/server/src/server/worktree-core.test.ts b/packages/server/src/server/worktree-core.test.ts index 28828354e..a519801c8 100644 --- a/packages/server/src/server/worktree-core.test.ts +++ b/packages/server/src/server/worktree-core.test.ts @@ -1,5 +1,13 @@ import { execSync } from "node:child_process"; -import { existsSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs"; +import { + existsSync, + mkdtempSync, + readdirSync, + readFileSync, + realpathSync, + rmSync, + writeFileSync, +} from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; import { fileURLToPath } from "node:url"; @@ -45,6 +53,42 @@ function createCoreDeps(options?: { }; } +function findDirectCreateWorktreeCallSites(serverSrc: string): string[] { + const matches: string[] = []; + + function walk(directory: string) { + for (const entry of readdirSync(directory, { withFileTypes: true })) { + const absolutePath = path.join(directory, entry.name); + + if (entry.isDirectory()) { + walk(absolutePath); + continue; + } + + if (!entry.isFile()) { + continue; + } + + const relativePath = path + .relative(serverSrc, absolutePath) + .split(path.sep) + .join(path.posix.sep); + + if (relativePath === "utils/worktree.ts" || relativePath.endsWith(".test.ts")) { + continue; + } + + // Keep this literal in the test file so the invariant proves tests are allowed to inspect createWorktree(. + if (/createWorktree\(/.test(readFileSync(absolutePath, "utf8"))) { + matches.push(relativePath); + } + } + } + + walk(serverSrc); + return matches.sort(); +} + function createGitRepo(): { tempDir: string; repoDir: string; paseoHome: string } { const tempDir = realpathSync(mkdtempSync(path.join(tmpdir(), "worktree-core-test-"))); const repoDir = path.join(tempDir, "repo"); @@ -435,21 +479,6 @@ describe.skipIf(process.platform === "win32")("createWorktreeCore", () => { test("keeps direct createWorktree calls isolated to the core layer", () => { const serverSrc = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); - // Keep this literal in the test file so the invariant proves tests are allowed to inspect createWorktree(. - const output = execSync( - "rg --files-with-matches 'createWorktree\\(' . --glob '!**/utils/worktree.ts' --glob '!**/*.test.ts'", - { - cwd: serverSrc, - stdio: "pipe", - }, - ) - .toString() - .trim() - .split("\n") - .map((line) => line.replace(/^\.\//, "")) - .filter(Boolean) - .sort(); - - expect(output).toEqual(["server/worktree-core.ts"]); + expect(findDirectCreateWorktreeCallSites(serverSrc)).toEqual(["server/worktree-core.ts"]); }); });