mirror of
https://github.com/stablyai/orca.git
synced 2026-11-02 10:08:13 +00:00
Extract subprocess execution and process contracts into @orca/process-host with explicit public entry points, independent checks, and one Node 18 compatible CommonJS runtime. Migrate application consumers and integrate package builds, dependency selection, import enforcement, and runtime resource staging. Preserve daemon startup ownership and profile isolation, and qualify development rebuild recovery and concurrent output publication. Remove unnecessary aliases, test forwarding exports, and a duplicate output scan. Validation: full typechecks, changed-code quality, package and artifact checks, native Windows builds/types plus 306 runtime tests, and independent native reviews. Matched full E2E controls on original main and unchanged reviewed head both pass all 17 applicable tests (nine skips). Earlier intermittent E2E failures are documented in the PR; no assertions were weakened. The final combined-tree audit preserves both branches with no import-boundary violations.
394 lines
14 KiB
TypeScript
394 lines
14 KiB
TypeScript
import { mkdirSync } from 'node:fs'
|
|
import { runProcess } from '@orca/process-host'
|
|
import type { Page } from '@stablyai/playwright-test'
|
|
import { test, expect } from './helpers/orca-app'
|
|
import { waitForActiveWorktree, waitForSessionReady } from './helpers/store'
|
|
|
|
async function prepareSidebarForScrollTest(page: Page): Promise<void> {
|
|
await page.evaluate(() => {
|
|
const store = window.__store
|
|
if (!store) {
|
|
throw new Error('window.__store is not available')
|
|
}
|
|
|
|
const state = store.getState()
|
|
state.setActiveView('terminal')
|
|
state.setSidebarOpen(true)
|
|
state.setGroupBy('none')
|
|
state.setSortBy('recent')
|
|
state.setShowActiveOnly(false)
|
|
state.setShowSleepingWorkspaces(true)
|
|
state.setHideDefaultBranchWorkspace(false)
|
|
state.setFilterRepoIds([])
|
|
})
|
|
}
|
|
|
|
test.describe('Reveal active workspace button', () => {
|
|
test.beforeEach(async ({ orcaPage }) => {
|
|
// Why: headless Electron under xvfb never ticks a smooth-scroll animation,
|
|
// so the reveal's `scrollTo({ behavior: 'smooth' })` would never reach its
|
|
// target. Reduced-motion makes the reveal jump instantly (see
|
|
// worktree-sidebar-reveal.ts) so the geometry assertions are deterministic.
|
|
await orcaPage.emulateMedia({ reducedMotion: 'reduce' })
|
|
await waitForSessionReady(orcaPage)
|
|
await waitForActiveWorktree(orcaPage)
|
|
})
|
|
|
|
// Note: the "clipped in the production sidebar" pixel-containment test was
|
|
// removed — it forced a ~44px synthetic viewport and asserted ±1px scroll
|
|
// precision, which the row virtualizer can't guarantee under CI saturation
|
|
// (not a scenario real users hit). Reveal-into-view is covered robustly by
|
|
// the "outside the virtualized window" test below.
|
|
|
|
test('adjusts sidebar filters before revealing a hidden current workspace', async ({
|
|
orcaPage,
|
|
testRepoPath
|
|
}, testInfo) => {
|
|
const filterRepoPath = testInfo.outputPath('filter-repo')
|
|
mkdirSync(filterRepoPath, { recursive: true })
|
|
for (const args of [
|
|
['init', filterRepoPath],
|
|
[
|
|
'-C',
|
|
filterRepoPath,
|
|
'-c',
|
|
'user.name=E2E',
|
|
'-c',
|
|
'user.email=e2e@test.local',
|
|
'commit',
|
|
'--allow-empty',
|
|
'-m',
|
|
'Filter fixture'
|
|
]
|
|
]) {
|
|
const result = await runProcess({ program: 'git', args })
|
|
expect(result.code, result.stderr).toBe(0)
|
|
}
|
|
const filterRepoId = await orcaPage.evaluate(async (repoPath) => {
|
|
const result = await window.api.repos.add({ path: repoPath })
|
|
if ('error' in result) {
|
|
throw new Error(result.error)
|
|
}
|
|
return result.repo.id
|
|
}, filterRepoPath)
|
|
await expect
|
|
.poll(() =>
|
|
orcaPage.evaluate(async (id) => {
|
|
await window.__store!.getState().fetchRepos()
|
|
return window.__store!.getState().repos.some((repo) => repo.id === id)
|
|
}, filterRepoId)
|
|
)
|
|
.toBe(true)
|
|
await prepareSidebarForScrollTest(orcaPage)
|
|
|
|
// Other specs can add worktrees to the shared repository before this test runs.
|
|
const targetId = await orcaPage.evaluate((repoPath) => {
|
|
const state = window.__store!.getState()
|
|
const repo = state.repos.find((candidate) => candidate.path === repoPath)
|
|
return repo
|
|
? state.worktreesByRepo[repo.id]?.find(
|
|
(worktree) => worktree.branch === 'refs/heads/e2e-secondary'
|
|
)?.id
|
|
: undefined
|
|
}, testRepoPath)
|
|
if (!targetId) {
|
|
throw new Error('Seeded secondary worktree is missing')
|
|
}
|
|
const targetRepoId = await orcaPage.evaluate(
|
|
(repoPath) => window.__store!.getState().repos.find((repo) => repo.path === repoPath)?.id,
|
|
testRepoPath
|
|
)
|
|
if (!targetRepoId) {
|
|
throw new Error('Seeded repository is missing')
|
|
}
|
|
|
|
const targetRows = orcaPage.locator(
|
|
`[data-worktree-sidebar] [data-worktree-id=${JSON.stringify(targetId)}]`
|
|
)
|
|
const targetRow = targetRows.first()
|
|
await expect(targetRows.and(orcaPage.getByRole('option'))).toHaveCount(1)
|
|
const revealButton = orcaPage.getByRole('button', { name: 'Reveal active workspace' })
|
|
|
|
await orcaPage.evaluate((targetId) => {
|
|
const store = window.__store
|
|
if (!store) {
|
|
throw new Error('window.__store is not available')
|
|
}
|
|
const state = store.getState()
|
|
const target = Object.values(state.worktreesByRepo)
|
|
.flat()
|
|
.find((worktree) => worktree.id === targetId)
|
|
if (!target) {
|
|
throw new Error(`Target workspace ${targetId} not found`)
|
|
}
|
|
store.setState({
|
|
activeRepoId: target.repoId,
|
|
activeWorktreeId: target.id,
|
|
activeWorkspaceKey: `worktree:${target.id}`,
|
|
pendingRevealWorktree: null
|
|
})
|
|
}, targetId)
|
|
await expect(targetRow).toHaveAttribute('aria-current', 'page')
|
|
|
|
// Catalog refreshes prune nonexistent IDs, so use a real repo to keep the filter applied.
|
|
await orcaPage.evaluate((repoId) => {
|
|
window.__store!.getState().setFilterRepoIds([repoId])
|
|
}, filterRepoId)
|
|
await expect(targetRows).toHaveCount(0)
|
|
|
|
await revealButton.click()
|
|
await orcaPage
|
|
.getByRole('dialog', { name: 'Reveal hidden workspace?' })
|
|
.getByRole('button', { name: 'Adjust filters and reveal' })
|
|
.click()
|
|
|
|
await expect(targetRow).toBeVisible()
|
|
await expect(targetRow).toHaveAttribute('data-scroll-reveal-highlight', 'true')
|
|
await expect
|
|
.poll(
|
|
() =>
|
|
orcaPage.evaluate(() => {
|
|
const store = window.__store
|
|
if (!store) {
|
|
throw new Error('window.__store is not available')
|
|
}
|
|
return store.getState().filterRepoIds
|
|
}),
|
|
{
|
|
timeout: 10_000,
|
|
message:
|
|
'Reveal button should preserve selected repos while revealing the current workspace'
|
|
}
|
|
)
|
|
.toEqual([filterRepoId, targetRepoId])
|
|
})
|
|
|
|
test('reveals the current workspace when it starts outside the virtualized window', async ({
|
|
orcaPage
|
|
}) => {
|
|
await prepareSidebarForScrollTest(orcaPage)
|
|
|
|
const targetId = await orcaPage.evaluate(() => {
|
|
const store = window.__store
|
|
if (!store) {
|
|
throw new Error('window.__store is not available')
|
|
}
|
|
|
|
const state = store.getState()
|
|
const repo = state.repos[0]
|
|
if (!repo) {
|
|
throw new Error('Expected a seeded e2e repo')
|
|
}
|
|
|
|
const now = Date.now()
|
|
const seededWorktrees = state.worktreesByRepo[repo.id] ?? []
|
|
const syntheticWorktrees = Array.from({ length: 60 }, (_, index) => {
|
|
const suffix = String(index).padStart(2, '0')
|
|
return {
|
|
id: `${repo.id}::/virtual-reveal-${suffix}`,
|
|
instanceId: `virtual-reveal-${suffix}`,
|
|
repoId: repo.id,
|
|
path: `${repo.path}/../virtual-reveal-${suffix}`,
|
|
displayName: `Virtual reveal ${suffix}`,
|
|
comment: '',
|
|
linkedIssue: null,
|
|
linkedPR: null,
|
|
linkedLinearIssue: null,
|
|
isArchived: false,
|
|
isUnread: false,
|
|
isPinned: false,
|
|
sortOrder: 10_000 - index,
|
|
lastActivityAt: now - index - 100,
|
|
head: '0000000000000000000000000000000000000000',
|
|
branch: `virtual-reveal-${suffix}`,
|
|
isBare: false,
|
|
isMainWorktree: false
|
|
}
|
|
})
|
|
const target = syntheticWorktrees.at(-1)
|
|
if (!target) {
|
|
throw new Error('Expected a synthetic target worktree')
|
|
}
|
|
|
|
store.setState({
|
|
activeRepoId: repo.id,
|
|
activeWorktreeId: target.id,
|
|
activeWorkspaceKey: `worktree:${target.id}`,
|
|
pendingRevealWorktree: null,
|
|
sortBy: 'manual',
|
|
worktreesByRepo: {
|
|
...state.worktreesByRepo,
|
|
[repo.id]: [...seededWorktrees, ...syntheticWorktrees]
|
|
}
|
|
})
|
|
return target.id
|
|
})
|
|
|
|
const scroller = orcaPage.locator('[data-worktree-sidebar]')
|
|
await expect
|
|
.poll(
|
|
() =>
|
|
orcaPage.evaluate(() => {
|
|
const scroller = document.querySelector<HTMLElement>('[data-worktree-sidebar]')
|
|
return scroller?.scrollTop ?? null
|
|
}),
|
|
{ timeout: 10_000, message: 'sidebar scroller did not mount' }
|
|
)
|
|
.not.toBeNull()
|
|
|
|
await scroller.evaluate((element) => {
|
|
element.scrollTop = 0
|
|
element.dispatchEvent(new Event('scroll', { bubbles: true }))
|
|
})
|
|
await expect
|
|
.poll(
|
|
() =>
|
|
orcaPage.evaluate((targetId) => {
|
|
const scroller = document.querySelector<HTMLElement>('[data-worktree-sidebar]')
|
|
const target = [...document.querySelectorAll<HTMLElement>('[data-worktree-id]')].find(
|
|
(candidate) => candidate.dataset.worktreeId === targetId
|
|
)
|
|
if (!scroller || !target) {
|
|
return false
|
|
}
|
|
const scrollerBounds = scroller.getBoundingClientRect()
|
|
const targetBounds = target.getBoundingClientRect()
|
|
return (
|
|
targetBounds.top >= scrollerBounds.top - 1 &&
|
|
targetBounds.bottom <= scrollerBounds.bottom + 1
|
|
)
|
|
}, targetId),
|
|
{ timeout: 10_000, message: 'target workspace should start outside the sidebar viewport' }
|
|
)
|
|
.toBe(false)
|
|
|
|
const revealButton = orcaPage.getByRole('button', { name: 'Reveal active workspace' })
|
|
await expect(revealButton).toBeVisible()
|
|
await expect(revealButton).toBeEnabled()
|
|
|
|
await revealButton.click()
|
|
const targetRow = orcaPage
|
|
.locator(`[data-worktree-sidebar] [data-worktree-id=${JSON.stringify(targetId)}]`)
|
|
.first()
|
|
await expect(targetRow).toBeVisible()
|
|
await expect(targetRow).toHaveAttribute('data-scroll-reveal-highlight', 'true')
|
|
})
|
|
|
|
test('uses the active workspace key when the legacy active worktree id is not set', async ({
|
|
orcaPage
|
|
}) => {
|
|
await prepareSidebarForScrollTest(orcaPage)
|
|
|
|
const folderWorktreeId = await orcaPage.evaluate(() => {
|
|
const store = window.__store
|
|
if (!store) {
|
|
throw new Error('window.__store is not available')
|
|
}
|
|
|
|
const now = Date.now()
|
|
const folderWorkspaces = Array.from({ length: 36 }, (_, index) => {
|
|
const suffix = String(index).padStart(2, '0')
|
|
return {
|
|
id: `reveal-folder-workspace-${suffix}`,
|
|
projectGroupId: 'reveal-folder-group',
|
|
name: `Reveal folder workspace ${suffix}`,
|
|
folderPath: `/tmp/reveal-folder-workspace-${suffix}`,
|
|
linkedTask: null,
|
|
comment: '',
|
|
isArchived: false,
|
|
isUnread: false,
|
|
isPinned: false,
|
|
sortOrder: 1_000 - index,
|
|
lastActivityAt: now - index,
|
|
createdAt: now,
|
|
updatedAt: now
|
|
}
|
|
})
|
|
const targetFolder = folderWorkspaces.at(-1)
|
|
if (!targetFolder) {
|
|
throw new Error('Expected a target folder workspace')
|
|
}
|
|
const folderWorktreeId = `folder:${targetFolder.id}`
|
|
store.setState({
|
|
activeRepoId: null,
|
|
activeWorktreeId: null,
|
|
activeWorkspaceKey: folderWorktreeId,
|
|
projectGroups: [
|
|
{
|
|
id: 'reveal-folder-group',
|
|
name: 'Reveal folder group',
|
|
parentPath: '/tmp/reveal-folder-group',
|
|
parentGroupId: null,
|
|
createdFrom: 'manual',
|
|
tabOrder: 1,
|
|
isCollapsed: false,
|
|
color: null,
|
|
createdAt: now,
|
|
updatedAt: now
|
|
}
|
|
],
|
|
folderWorkspaces
|
|
})
|
|
store.getState().setGroupBy('repo')
|
|
return folderWorktreeId
|
|
})
|
|
|
|
const scroller = orcaPage.locator('[data-worktree-sidebar]')
|
|
await scroller.evaluate((element) => {
|
|
element.scrollTop = 0
|
|
element.dispatchEvent(new Event('scroll', { bubbles: true }))
|
|
})
|
|
await expect
|
|
.poll(
|
|
() =>
|
|
orcaPage.evaluate((targetId) => {
|
|
const scroller = document.querySelector<HTMLElement>('[data-worktree-sidebar]')
|
|
const target = [...document.querySelectorAll<HTMLElement>('[data-worktree-id]')].find(
|
|
(candidate) => candidate.dataset.worktreeId === targetId
|
|
)
|
|
if (!scroller || !target) {
|
|
return false
|
|
}
|
|
const scrollerBounds = scroller.getBoundingClientRect()
|
|
const targetBounds = target.getBoundingClientRect()
|
|
return (
|
|
targetBounds.top >= scrollerBounds.top - 1 &&
|
|
targetBounds.bottom <= scrollerBounds.bottom + 1
|
|
)
|
|
}, folderWorktreeId),
|
|
{ timeout: 10_000, message: 'target folder workspace should start outside the viewport' }
|
|
)
|
|
.toBe(false)
|
|
|
|
const revealButton = orcaPage.getByRole('button', { name: 'Reveal active workspace' })
|
|
await expect(revealButton).toBeVisible()
|
|
await expect(revealButton).toBeEnabled()
|
|
await revealButton.click()
|
|
|
|
await expect
|
|
.poll(
|
|
() =>
|
|
orcaPage.evaluate((targetId) => {
|
|
const scroller = document.querySelector<HTMLElement>('[data-worktree-sidebar]')
|
|
const target = [...document.querySelectorAll<HTMLElement>('[data-worktree-id]')].find(
|
|
(candidate) => candidate.dataset.worktreeId === targetId
|
|
)
|
|
if (!scroller || !target) {
|
|
return false
|
|
}
|
|
const scrollerBounds = scroller.getBoundingClientRect()
|
|
const targetBounds = target.getBoundingClientRect()
|
|
return (
|
|
targetBounds.top >= scrollerBounds.top - 1 &&
|
|
targetBounds.bottom <= scrollerBounds.bottom + 1
|
|
)
|
|
}, folderWorktreeId),
|
|
{
|
|
timeout: 10_000,
|
|
message: 'Reveal button should scroll to the active folder workspace'
|
|
}
|
|
)
|
|
.toBe(true)
|
|
})
|
|
})
|