Files
orca/tests/e2e/worktree-lifecycle.spec.ts

160 lines
5.2 KiB
TypeScript

// #532: removal must drop owned terminal/editor/browser state.
import { test, expect } from './helpers/orca-app'
import {
waitForSessionReady,
waitForActiveWorktree,
getActiveWorktreeId,
getAllWorktreeIds,
getWorktreeTabs,
getOpenFiles,
getBrowserTabs,
switchToWorktree,
ensureTerminalVisible
} from './helpers/store'
import { clickFileInExplorer, openFileExplorer } from './helpers/file-explorer'
async function createIsolatedWorktree(
page: Parameters<typeof getActiveWorktreeId>[0]
): Promise<string> {
const name = `e2e-lifecycle-${Date.now()}`
return page.evaluate(async (worktreeName) => {
const store = window.__store
if (!store) {
throw new Error('window.__store is not available')
}
const state = store.getState()
const activeWorktreeId = state.activeWorktreeId
if (!activeWorktreeId) {
throw new Error('No active worktree to derive repo from')
}
const activeWorktree = Object.values(state.worktreesByRepo)
.flat()
.find((worktree) => worktree.id === activeWorktreeId)
if (!activeWorktree) {
throw new Error(`Active worktree ${activeWorktreeId} not found`)
}
const result = await state.createWorktree(activeWorktree.repoId, worktreeName)
await state.fetchWorktrees(activeWorktree.repoId)
return result.worktree.id
}, name)
}
async function removeWorktreeViaStore(
page: Parameters<typeof getActiveWorktreeId>[0],
worktreeId: string
): Promise<{ ok: boolean; error?: string }> {
return page.evaluate(async (id) => {
const store = window.__store
if (!store) {
return { ok: false as const, error: 'store unavailable' }
}
const state = store.getState()
const result = await state.removeWorktree(
{ id, executionHostId: state.getKnownWorktreeById(id)?.hostId ?? null },
true
)
return result
}, worktreeId)
}
test.describe('Worktree Lifecycle', () => {
// Track isolated worktrees for afterEach cleanup so aborted tests cannot leak branches/directories into the shared worker.
let createdWorktreeId: string | null = null
test.beforeEach(async ({ orcaPage }) => {
await waitForSessionReady(orcaPage)
await waitForActiveWorktree(orcaPage)
await ensureTerminalVisible(orcaPage)
})
test.afterEach(async ({ orcaPage }) => {
if (!createdWorktreeId) {
return
}
const idToClean = createdWorktreeId
createdWorktreeId = null
await orcaPage
.evaluate(async (id) => {
try {
const state = window.__store?.getState()
await state?.removeWorktree(
{ id, executionHostId: state.getKnownWorktreeById(id)?.hostId ?? null },
true
)
} catch {
/* best-effort cleanup */
}
}, idToClean)
.catch(() => undefined)
})
// #532: removing a worktree must drop all owned surface state.
test('removing a worktree clears its tabs, open files, and browser tabs', async ({
orcaPage
}) => {
const originalWorktreeId = await waitForActiveWorktree(orcaPage)
createdWorktreeId = await createIsolatedWorktree(orcaPage)
const newWorktreeId = createdWorktreeId
await switchToWorktree(orcaPage, newWorktreeId)
await expect
.poll(async () => getActiveWorktreeId(orcaPage), { timeout: 10_000 })
.toBe(newWorktreeId)
await ensureTerminalVisible(orcaPage)
// Seed all three surface types to require atomic cleanup.
await orcaPage.evaluate((worktreeId) => {
const store = window.__store
if (!store) {
return
}
const state = store.getState()
state.createTab(worktreeId)
state.createBrowserTab(worktreeId, 'about:blank', {
title: 'lifecycle-test',
activate: false
})
}, newWorktreeId)
await openFileExplorer(orcaPage)
await clickFileInExplorer(orcaPage, ['README.md', 'package.json'])
expect((await getWorktreeTabs(orcaPage, newWorktreeId)).length).toBeGreaterThan(0)
expect((await getBrowserTabs(orcaPage, newWorktreeId)).length).toBeGreaterThan(0)
expect((await getOpenFiles(orcaPage, newWorktreeId)).length).toBeGreaterThan(0)
// Remove an inactive worktree; deleting the active one follows an easier path that misses this cleanup regression.
await switchToWorktree(orcaPage, originalWorktreeId)
await expect
.poll(async () => getActiveWorktreeId(orcaPage), { timeout: 10_000 })
.toBe(originalWorktreeId)
const result = await removeWorktreeViaStore(orcaPage, newWorktreeId)
expect(result.ok).toBe(true)
createdWorktreeId = null
// Require all removed-worktree keyed surfaces gone to prevent a worktree-less tab strip.
await expect
.poll(async () => (await getWorktreeTabs(orcaPage, newWorktreeId)).length, {
timeout: 10_000,
message: 'tabsByWorktree still holds entries for the removed worktree'
})
.toBe(0)
await expect
.poll(async () => (await getBrowserTabs(orcaPage, newWorktreeId)).length, { timeout: 5_000 })
.toBe(0)
await expect
.poll(async () => (await getOpenFiles(orcaPage, newWorktreeId)).length, { timeout: 5_000 })
.toBe(0)
const allIds = await getAllWorktreeIds(orcaPage)
expect(allIds).not.toContain(newWorktreeId)
})
})