mirror of
https://github.com/stablyai/orca.git
synced 2026-10-20 16:03:20 +00:00
Add E2E test for worktree jump-palette host filtering
Tests filter interaction via keyboard, filter/project intersection, empty state when filters exclude all results, and ephemeral filter reset on modal close.
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
import type { Page } from '@stablyai/playwright-test'
|
||||
import { expect, test } from './helpers/orca-app'
|
||||
import { waitForActiveWorktree, waitForSessionReady } from './helpers/store'
|
||||
|
||||
const LOCAL_PROJECT = 'E2E Palette Local Project'
|
||||
const REMOTE_PROJECT = 'E2E Palette Remote Project'
|
||||
const REMOTE_WORKSPACE = 'E2E Palette Remote Workspace'
|
||||
const REMOTE_HOST = 'E2E Palette Builder'
|
||||
|
||||
type PaletteFilterFixture = { localWorktreeId: string; remoteWorktreeId: string }
|
||||
|
||||
async function seedPaletteFilterFixture(page: Page): Promise<PaletteFilterFixture> {
|
||||
return page.evaluate(
|
||||
({ localProject, remoteHost, remoteProject, remoteWorkspace }) => {
|
||||
const store = window.__store
|
||||
if (!store) {
|
||||
throw new Error('window.__store is unavailable')
|
||||
}
|
||||
|
||||
const state = store.getState()
|
||||
const sourceRepo = state.repos[0]
|
||||
const sourceWorktree = Object.values(state.worktreesByRepo)
|
||||
.flat()
|
||||
.find((worktree) => worktree.repoId === sourceRepo?.id && !worktree.isArchived)
|
||||
if (!sourceRepo || !sourceWorktree) {
|
||||
throw new Error('Palette filter E2E needs the seeded local repository')
|
||||
}
|
||||
|
||||
const token = crypto.randomUUID()
|
||||
const remoteConnectionId = `e2e-palette-host-${token}`
|
||||
const remoteRepoId = `e2e-palette-remote-repo-${token}`
|
||||
const remoteWorktreeId = `e2e-palette-remote-worktree-${token}`
|
||||
const remoteRepo = {
|
||||
...sourceRepo,
|
||||
id: remoteRepoId,
|
||||
path: `${sourceRepo.path}-e2e-palette-remote-${token}`,
|
||||
displayName: remoteProject,
|
||||
connectionId: remoteConnectionId,
|
||||
executionHostId: `ssh:${remoteConnectionId}`
|
||||
}
|
||||
const remoteWorktree = {
|
||||
...sourceWorktree,
|
||||
id: remoteWorktreeId,
|
||||
repoId: remoteRepoId,
|
||||
path: `${sourceWorktree.path}-e2e-palette-remote-${token}`,
|
||||
displayName: remoteWorkspace,
|
||||
title: remoteWorkspace,
|
||||
branch: 'refs/heads/e2e-palette-remote',
|
||||
isMainWorktree: false,
|
||||
isArchived: false,
|
||||
hostId: `ssh:${remoteConnectionId}`
|
||||
}
|
||||
|
||||
const sshTargetLabels = new Map(state.sshTargetLabels)
|
||||
sshTargetLabels.set(remoteConnectionId, remoteHost)
|
||||
store.setState({
|
||||
repos: [
|
||||
...state.repos.map((repo) =>
|
||||
repo.id === sourceRepo.id ? { ...repo, displayName: localProject } : repo
|
||||
),
|
||||
remoteRepo
|
||||
],
|
||||
sshTargetLabels,
|
||||
worktreesByRepo: {
|
||||
...state.worktreesByRepo,
|
||||
[sourceRepo.id]: (state.worktreesByRepo[sourceRepo.id] ?? []).map((worktree) =>
|
||||
worktree.id === sourceWorktree.id ? { ...worktree, hostId: 'local' } : worktree
|
||||
),
|
||||
[remoteRepoId]: [remoteWorktree]
|
||||
}
|
||||
})
|
||||
|
||||
return { localWorktreeId: sourceWorktree.id, remoteWorktreeId }
|
||||
},
|
||||
{
|
||||
localProject: LOCAL_PROJECT,
|
||||
remoteHost: REMOTE_HOST,
|
||||
remoteProject: REMOTE_PROJECT,
|
||||
remoteWorkspace: REMOTE_WORKSPACE
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function worktreeRow(page: Page, worktreeId: string) {
|
||||
return palette(page).locator(`[cmdk-item][data-value="worktree:${worktreeId}"]`)
|
||||
}
|
||||
|
||||
function palette(page: Page) {
|
||||
return page.getByRole('dialog', { name: 'Jump to...' })
|
||||
}
|
||||
|
||||
function filterTrigger(page: Page) {
|
||||
return page.getByRole('button', { name: 'Filter results' })
|
||||
}
|
||||
|
||||
async function openPalette(page: Page): Promise<void> {
|
||||
await page.evaluate(() => window.__store?.getState().openModal('worktree-palette'))
|
||||
await expect(palette(page)).toBeVisible()
|
||||
}
|
||||
|
||||
async function searchFixtureWorkspaces(page: Page, fixture: PaletteFilterFixture): Promise<void> {
|
||||
const input = palette(page).getByPlaceholder('Search worktrees, settings, tabs, and actions...')
|
||||
await input.fill('E2E Palette')
|
||||
await expect(worktreeRow(page, fixture.localWorktreeId)).toBeVisible()
|
||||
await expect(worktreeRow(page, fixture.remoteWorktreeId)).toBeVisible()
|
||||
}
|
||||
|
||||
async function selectRemoteHost(page: Page, useKeyboard = false): Promise<void> {
|
||||
if (useKeyboard) {
|
||||
const input = palette(page).getByPlaceholder('Search worktrees, settings, tabs, and actions...')
|
||||
await input.press('Tab')
|
||||
await expect(filterTrigger(page)).toBeFocused()
|
||||
await filterTrigger(page).click()
|
||||
} else {
|
||||
await filterTrigger(page).click()
|
||||
}
|
||||
|
||||
await expect(palette(page).getByText('Hosts', { exact: true })).toBeVisible()
|
||||
await palette(page).getByText('Hosts', { exact: true }).click()
|
||||
const hosts = palette(page).getByRole('listbox', { name: 'Hosts' })
|
||||
await expect(hosts.getByRole('option', { name: REMOTE_HOST })).toBeVisible()
|
||||
await hosts.getByRole('option', { name: REMOTE_HOST }).click()
|
||||
await filterTrigger(page).click()
|
||||
}
|
||||
|
||||
test.describe('Worktree jump-palette filters', () => {
|
||||
test.beforeEach(async ({ orcaPage }) => {
|
||||
await waitForSessionReady(orcaPage)
|
||||
await waitForActiveWorktree(orcaPage)
|
||||
})
|
||||
|
||||
test('filters workspace results by host, intersects project selection, and resets on close', async ({
|
||||
orcaPage
|
||||
}) => {
|
||||
const fixture = await seedPaletteFilterFixture(orcaPage)
|
||||
await openPalette(orcaPage)
|
||||
await searchFixtureWorkspaces(orcaPage, fixture)
|
||||
|
||||
// P1: keyboard focus reaches the control; its rendered selection narrows rows.
|
||||
await selectRemoteHost(orcaPage, true)
|
||||
await expect(filterTrigger(orcaPage)).toContainText('1')
|
||||
await expect(palette(orcaPage).getByLabel(`Remove filter ${REMOTE_HOST}`)).toBeVisible()
|
||||
await expect(worktreeRow(orcaPage, fixture.remoteWorktreeId)).toBeVisible()
|
||||
await expect(worktreeRow(orcaPage, fixture.localWorktreeId)).toHaveCount(0)
|
||||
|
||||
// P2: host and project fields intersect, with the filter-specific empty state.
|
||||
await palette(orcaPage)
|
||||
.getByPlaceholder('Search worktrees, settings, tabs, and actions...')
|
||||
.fill('')
|
||||
await filterTrigger(orcaPage).click()
|
||||
await palette(orcaPage).getByText('Projects', { exact: true }).click()
|
||||
const projects = palette(orcaPage).getByRole('listbox', { name: 'Projects' })
|
||||
const localProject = projects.getByRole('option').filter({ hasNotText: REMOTE_PROJECT }).first()
|
||||
await expect(localProject).toBeVisible()
|
||||
await localProject.click()
|
||||
await filterTrigger(orcaPage).click()
|
||||
await expect(palette(orcaPage).getByText('No results match the active filter')).toBeVisible()
|
||||
await expect(
|
||||
palette(orcaPage).getByText('Clear the filter above, or widen it to more hosts and projects.')
|
||||
).toBeVisible()
|
||||
|
||||
// P3: clear restores both rows; closing drops the ephemeral filter.
|
||||
await filterTrigger(orcaPage).click()
|
||||
await palette(orcaPage).getByRole('button', { name: 'Clear all' }).last().click()
|
||||
await filterTrigger(orcaPage).click()
|
||||
await expect(filterTrigger(orcaPage)).not.toContainText('1')
|
||||
await searchFixtureWorkspaces(orcaPage, fixture)
|
||||
|
||||
await selectRemoteHost(orcaPage)
|
||||
await orcaPage.evaluate(() => window.__store?.getState().closeModal())
|
||||
await expect(palette(orcaPage)).toBeHidden()
|
||||
await openPalette(orcaPage)
|
||||
await searchFixtureWorkspaces(orcaPage, fixture)
|
||||
await expect(filterTrigger(orcaPage)).not.toContainText('1')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user