mirror of
https://github.com/stablyai/orca.git
synced 2026-10-23 08:03:39 +00:00
test(sidebar): prove paired folder host grouping
This commit is contained in:
@@ -1,4 +1,7 @@
|
||||
import type { HostSectionRow } from '../../host-section-rows'
|
||||
import { getFolderWorkspaceHostId } from '../../folder-workspace-host-id'
|
||||
import { composeWorktreeHostIdentity } from '../../../../../../shared/worktree/host-qualified-identity'
|
||||
import { folderWorkspaceKey } from '../../../../../../shared/workspace-scope'
|
||||
|
||||
type WorktreeItemRow = Extract<HostSectionRow, { type: 'item' }>
|
||||
export type RenderRow =
|
||||
@@ -25,7 +28,11 @@ export function getRenderRowKey(row: RenderRow): string {
|
||||
return `pending:${row.creationId}`
|
||||
}
|
||||
if (row.type === 'folder-workspace') {
|
||||
return `folder-workspace:${row.folderWorkspace.id}`
|
||||
const hostId = getFolderWorkspaceHostId(row.folderWorkspace, row.projectGroup, 'local')
|
||||
return `folder-workspace:${composeWorktreeHostIdentity(
|
||||
hostId,
|
||||
folderWorkspaceKey(row.folderWorkspace.id)
|
||||
)}`
|
||||
}
|
||||
return `wt:${row.rowKey}`
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { VirtualItem } from '@tanstack/react-virtual'
|
||||
import type { ExecutionHostId } from '../../../../../../shared/execution-host'
|
||||
import type { FolderWorkspace } from '../../../../../../shared/folder-workspace-types'
|
||||
import type { ProjectGroup } from '../../../../../../shared/project-group-types'
|
||||
import {
|
||||
HOST_STICKY_PINNED_HEIGHT,
|
||||
buildLineageRowRekeyMap,
|
||||
@@ -41,6 +43,21 @@ function itemStub(id: string): RenderRow {
|
||||
return { type: 'item', key: id } as unknown as RenderRow
|
||||
}
|
||||
|
||||
function folderRow(executionHostId: ExecutionHostId): RenderRow {
|
||||
return {
|
||||
type: 'folder-workspace',
|
||||
key: 'folder-workspace:same-id',
|
||||
folderWorkspace: {
|
||||
id: 'same-id',
|
||||
executionHostId,
|
||||
projectGroupId: 'group-id'
|
||||
} as FolderWorkspace,
|
||||
projectGroup: { id: 'group-id', executionHostId } as ProjectGroup,
|
||||
depth: 0,
|
||||
groupDepth: 0
|
||||
}
|
||||
}
|
||||
|
||||
function virtualItem(index: number, start: number): VirtualItem {
|
||||
return { index, start } as VirtualItem
|
||||
}
|
||||
@@ -74,6 +91,13 @@ describe('getRenderRowKey', () => {
|
||||
'hdr:workspace-status:in-progress'
|
||||
)
|
||||
})
|
||||
|
||||
it('keeps identical folder workspace ids distinct across hosts', () => {
|
||||
expect(getRenderRowKey(folderRow('local'))).toBe('folder-workspace:local|folder:same-id')
|
||||
expect(getRenderRowKey(folderRow('runtime:owner'))).toBe(
|
||||
'folder-workspace:runtime:owner|folder:same-id'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getActiveStickyIndexesForScroll', () => {
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
import type { Page, TestInfo } from '@stablyai/playwright-test'
|
||||
import { expect } from './helpers/orca-app'
|
||||
|
||||
export async function expectFolderWorkspaceSidebarGrouping(
|
||||
page: Page,
|
||||
testInfo: TestInfo,
|
||||
args: { folderPath: string; hostId: `runtime:${string}` }
|
||||
): Promise<void> {
|
||||
const target = await page.evaluate(({ folderPath, hostId }) => {
|
||||
const store = window.__store
|
||||
if (!store) {
|
||||
throw new Error('Renderer store unavailable')
|
||||
}
|
||||
const workspace = store
|
||||
.getState()
|
||||
.folderWorkspaces.find(
|
||||
(candidate) => candidate.folderPath === folderPath && candidate.executionHostId === hostId
|
||||
)
|
||||
if (!workspace) {
|
||||
throw new Error('Runtime folder unavailable for sidebar grouping')
|
||||
}
|
||||
store.getState().setVisibleWorkspaceHostIds(['local', hostId])
|
||||
return { id: workspace.id, name: workspace.name, executionHostId: workspace.executionHostId }
|
||||
}, args)
|
||||
expect(target.executionHostId).toBe(args.hostId)
|
||||
|
||||
const cases = [
|
||||
{ id: 'repo', label: 'Project' },
|
||||
{ id: 'workspace-status', label: 'Status' },
|
||||
{ id: 'pr-status', label: 'PR' },
|
||||
{ id: 'none', label: 'None' }
|
||||
] as const
|
||||
const identity = `${args.hostId}|folder:${target.id}`
|
||||
const placements: { groupBy: string; hostId: string; identity: string }[] = []
|
||||
|
||||
for (const groupBy of cases) {
|
||||
await page.evaluate((groupById) => window.__store?.getState().setGroupBy(groupById), groupBy.id)
|
||||
await expect
|
||||
.poll(() => page.evaluate(() => window.__store?.getState().groupBy ?? null))
|
||||
.toBe(groupBy.id)
|
||||
await expect
|
||||
.poll(() =>
|
||||
page.evaluate(
|
||||
({ folderPath, hostId }) =>
|
||||
window.__store
|
||||
?.getState()
|
||||
.folderWorkspaces.some(
|
||||
(workspace) =>
|
||||
workspace.folderPath === folderPath && workspace.executionHostId === hostId
|
||||
) ?? false,
|
||||
args
|
||||
)
|
||||
)
|
||||
.toBe(true)
|
||||
await page.evaluate(
|
||||
({ folderWorkspaceId, hostId }) => {
|
||||
window.__store?.getState().revealWorktreeInSidebar(`folder:${folderWorkspaceId}`, {
|
||||
behavior: 'auto',
|
||||
executionHostId: hostId
|
||||
})
|
||||
},
|
||||
{ folderWorkspaceId: target.id, hostId: args.hostId }
|
||||
)
|
||||
|
||||
const folderRow = page.locator(`[data-worktree-host-identity="${identity}"]`)
|
||||
await expect(folderRow).toBeVisible()
|
||||
await expect(folderRow).toContainText(target.name)
|
||||
const placement = await page.evaluate((targetIdentity) => {
|
||||
const rows = [
|
||||
...document.querySelectorAll<HTMLElement>('[data-worktree-sidebar] [data-index]')
|
||||
].sort(
|
||||
(left, right) =>
|
||||
Number(left.dataset.index ?? Number.MAX_SAFE_INTEGER) -
|
||||
Number(right.dataset.index ?? Number.MAX_SAFE_INTEGER)
|
||||
)
|
||||
let hostId: string | null = null
|
||||
for (const row of rows) {
|
||||
const hostHeader = row.querySelector<HTMLElement>('[data-host-header-drag-id]')
|
||||
if (hostHeader) {
|
||||
hostId = hostHeader.dataset.hostHeaderDragId ?? null
|
||||
}
|
||||
if (row.dataset.worktreeHostIdentity === targetIdentity) {
|
||||
return { hostId, identity: row.dataset.worktreeHostIdentity ?? '' }
|
||||
}
|
||||
}
|
||||
return null
|
||||
}, identity)
|
||||
expect(placement).toEqual({ hostId: args.hostId, identity })
|
||||
placements.push({ groupBy: groupBy.id, ...placement! })
|
||||
await page.screenshot({
|
||||
path: testInfo.outputPath(`sta-4964-${groupBy.id}.png`),
|
||||
fullPage: true
|
||||
})
|
||||
}
|
||||
|
||||
console.info(`[sta-4964-sidebar] ${JSON.stringify(placements)}`)
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
import { mkdirSync } from 'node:fs'
|
||||
import type { FolderWorkspace } from '../../src/shared/folder-workspace-types'
|
||||
import type { ProjectGroup } from '../../src/shared/project-group-types'
|
||||
import { expect, test } from './helpers/orca-app'
|
||||
import {
|
||||
createRuntimeDesktopPairingOffer,
|
||||
launchPairedElectronClient
|
||||
} from './helpers/paired-electron-client'
|
||||
import { waitForSessionReady } from './helpers/store'
|
||||
import { expectFolderWorkspaceSidebarGrouping } from './sta-4964-folder-sidebar-oracle'
|
||||
|
||||
test('keeps a runtime-owned folder under its paired host in every grouping mode @headful', async ({
|
||||
orcaPage
|
||||
}, testInfo) => {
|
||||
test.setTimeout(120_000)
|
||||
await waitForSessionReady(orcaPage)
|
||||
const offer = await createRuntimeDesktopPairingOffer(orcaPage)
|
||||
const client = await launchPairedElectronClient(offer, testInfo, 'STA-4964 runtime')
|
||||
const runtimeHostId = `runtime:${client.environmentId}` as const
|
||||
const runtimeFolderPath = testInfo.outputPath('runtime-folder')
|
||||
const localFolderPath = testInfo.outputPath('local-collision-folder')
|
||||
mkdirSync(runtimeFolderPath, { recursive: true })
|
||||
mkdirSync(localFolderPath, { recursive: true })
|
||||
|
||||
try {
|
||||
await client.app.evaluate(({ BrowserWindow }) => BrowserWindow.getAllWindows()[0]?.show())
|
||||
await expect.poll(() => client.page.evaluate(() => window.__store != null)).toBe(true)
|
||||
const seeded = await client.page.evaluate(
|
||||
async (args) => {
|
||||
const store = window.__store
|
||||
if (!store) {
|
||||
throw new Error('Renderer store unavailable')
|
||||
}
|
||||
await store.getState().setActiveRuntimeEnvironmentPreference(null)
|
||||
const groupResponse = await window.api.runtimeEnvironments.call({
|
||||
selector: args.environmentId,
|
||||
method: 'projectGroup.create',
|
||||
params: {
|
||||
name: 'Runtime-owned group',
|
||||
parentPath: args.runtimeFolderPath,
|
||||
createdFrom: 'manual'
|
||||
},
|
||||
timeoutMs: 15_000
|
||||
})
|
||||
if (!groupResponse.ok) {
|
||||
throw new Error(groupResponse.error.message)
|
||||
}
|
||||
const createdGroup = (groupResponse.result as { group: ProjectGroup }).group
|
||||
const folderResponse = await window.api.runtimeEnvironments.call({
|
||||
selector: args.environmentId,
|
||||
method: 'folderWorkspace.create',
|
||||
params: {
|
||||
folderPath: args.runtimeFolderPath,
|
||||
name: 'Runtime-owned folder',
|
||||
projectGroupId: createdGroup.id
|
||||
},
|
||||
timeoutMs: 15_000
|
||||
})
|
||||
if (!folderResponse.ok) {
|
||||
throw new Error(folderResponse.error.message)
|
||||
}
|
||||
const createdFolder = (folderResponse.result as { folderWorkspace: FolderWorkspace })
|
||||
.folderWorkspace
|
||||
await Promise.all([
|
||||
store.getState().fetchProjectGroups({ runtimeEnvironmentId: args.environmentId }),
|
||||
store.getState().fetchFolderWorkspaces({ runtimeEnvironmentId: args.environmentId })
|
||||
])
|
||||
const state = store.getState()
|
||||
const runtimeFolder = state.folderWorkspaces.find(
|
||||
(workspace) =>
|
||||
workspace.id === createdFolder.id && workspace.executionHostId === args.runtimeHostId
|
||||
)
|
||||
const runtimeGroup = state.projectGroups.find(
|
||||
(group) => group.id === createdGroup.id && group.executionHostId === args.runtimeHostId
|
||||
)
|
||||
if (!runtimeFolder || !runtimeGroup) {
|
||||
throw new Error('Runtime-owned folder catalog unavailable')
|
||||
}
|
||||
const localGroupId = `${runtimeGroup.id}-local-collision`
|
||||
store.setState({
|
||||
folderWorkspaces: [
|
||||
{
|
||||
...runtimeFolder,
|
||||
name: 'Local same-ID collision',
|
||||
folderPath: args.localFolderPath,
|
||||
projectGroupId: localGroupId,
|
||||
executionHostId: 'local'
|
||||
},
|
||||
...state.folderWorkspaces
|
||||
],
|
||||
projectGroups: [
|
||||
{
|
||||
...runtimeGroup,
|
||||
id: localGroupId,
|
||||
name: 'Local same-ID collision',
|
||||
parentPath: args.localFolderPath,
|
||||
executionHostId: 'local'
|
||||
},
|
||||
...state.projectGroups
|
||||
],
|
||||
workspaceHostScope: 'all',
|
||||
activeWorkspaceExecutionHostId: 'local'
|
||||
})
|
||||
await Promise.all([
|
||||
store.getState().fetchProjectGroups({ runtimeEnvironmentId: args.environmentId }),
|
||||
store.getState().fetchFolderWorkspaces({ runtimeEnvironmentId: args.environmentId })
|
||||
])
|
||||
return store
|
||||
.getState()
|
||||
.folderWorkspaces.filter((workspace) => workspace.id === createdFolder.id)
|
||||
.map((workspace) => ({
|
||||
id: workspace.id,
|
||||
executionHostId: workspace.executionHostId
|
||||
}))
|
||||
},
|
||||
{
|
||||
environmentId: client.environmentId,
|
||||
localFolderPath,
|
||||
runtimeFolderPath,
|
||||
runtimeHostId
|
||||
}
|
||||
)
|
||||
expect(new Set(seeded.map((workspace) => workspace.id)).size).toBe(1)
|
||||
expect(seeded.map((workspace) => workspace.executionHostId)).toEqual(['local', runtimeHostId])
|
||||
|
||||
await expectFolderWorkspaceSidebarGrouping(client.page, testInfo, {
|
||||
folderPath: runtimeFolderPath,
|
||||
hostId: runtimeHostId
|
||||
})
|
||||
} finally {
|
||||
await client.dispose()
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user