diff --git a/config/scripts/ci/sharding/ci-shard-timings.json b/config/scripts/ci/sharding/ci-shard-timings.json index 1287d2a7ca9..aacbb8121b5 100644 --- a/config/scripts/ci/sharding/ci-shard-timings.json +++ b/config/scripts/ci/sharding/ci-shard-timings.json @@ -4699,7 +4699,6 @@ "src/renderer/src/components/github/code-host-rate-limit-stopped-wsl.test.tsx": 2457, "src/renderer/src/components/github/github-mention-option-filter.test.ts": 52, "src/renderer/src/components/github/github-pr-reviewer-candidate-filter.test.ts": 47, - "src/renderer/src/components/github/github-work-item-issue-target.test.ts": 324, "src/renderer/src/components/github/github-work-item-mutation-target.test.ts": 348, "src/renderer/src/components/github/use-image-input.test.ts": 2619, "src/renderer/src/components/gitlab-item-dialog/use-gitlab-primary-actions.test.ts": 2141, diff --git a/src/main/droid/hook-service.test.ts b/src/main/droid/hook-service.test.ts index 16e5b07c7d5..1372f2b5160 100644 --- a/src/main/droid/hook-service.test.ts +++ b/src/main/droid/hook-service.test.ts @@ -42,15 +42,6 @@ describe('DroidHookService', () => { removeTreeSync(hoisted.home) }) - it('does not register PermissionRequest', () => { - const status = new DroidHookService().install() - - expect(status.state).toBe('installed') - const hooks = readSettings().hooks - expect(hooks?.PermissionRequest).toBeUndefined() - expect(JSON.stringify(hooks?.Notification)).toContain('droid-hook') - }) - it('removes a managed PermissionRequest command and keeps a user command', () => { mkdirSync(dirname(settingsPath()), { recursive: true }) writeFileSync( @@ -90,30 +81,4 @@ describe('DroidHookService', () => { } ]) }) - - it('drops a previously managed PermissionRequest hook but keeps the user hook on it', () => { - const configPath = join(hoisted.home, '.factory', 'settings.json') - const userDefinition = { - matcher: 'Edit', - hooks: [{ type: 'command', command: '/usr/local/bin/my-approval.sh' }] - } - const managedCommand = join(hoisted.home, '.orca', 'agent-hooks', 'droid-hook.sh') - mkdirSync(dirname(configPath), { recursive: true }) - writeFileSync( - configPath, - JSON.stringify({ - hooks: { - PermissionRequest: [ - { matcher: '*', hooks: [{ type: 'command', command: managedCommand }] }, - userDefinition - ] - } - }) - ) - - expect(new DroidHookService().install().state).toBe('installed') - - const installed = readSettings() - expect(installed.hooks?.PermissionRequest).toEqual([userDefinition]) - }) }) diff --git a/src/main/runtime/session-tabs/session-tabs-phone-layout-moves.test.ts b/src/main/runtime/session-tabs/session-tabs-phone-layout-moves.test.ts index caacf138943..5f37f25c88f 100644 --- a/src/main/runtime/session-tabs/session-tabs-phone-layout-moves.test.ts +++ b/src/main/runtime/session-tabs/session-tabs-phone-layout-moves.test.ts @@ -2,7 +2,6 @@ import { describe, expect, it, vi } from 'vitest' import { HEADLESS_RUNTIME_WINDOW_ID } from '../../../shared/runtime-session-contracts' import type { RuntimeMobileSessionTabsResult } from '../../../shared/runtime-session-contracts' -import * as selection from './mobile-session-tab-selection' vi.mock('../../../shared/layout-owner-switch', () => ({ ORCA_LAYOUT_OWNER_IS_MAIN: true })) @@ -19,43 +18,9 @@ const WORKTREE = `id:${TEST_WORKTREE_ID}` const CHAT = 'agent-session:s1' /** The three saved tabs on a server with no window, whose spawns bind their pane as the host's do. */ -async function headlessRuntime(count = 3) { +async function headlessRuntime() { const harness = threeTabSurfaceRuntime() const { runtime, bindPane } = harness - if (count > 3) { - const session = harness.store.getSession() - const tabs = session.tabsByWorktree[TEST_WORKTREE_ID]! - const extra = Array.from({ length: count - 3 }, (_, index) => ({ - ...tabs[0]!, - id: `00000000-0000-4000-8000-${index.toString().padStart(12, '0')}`, - ptyId: null, - sortOrder: index + 3, - createdAt: index + 4 - })) - harness.setSession({ - ...session, - tabsByWorktree: { ...session.tabsByWorktree, [TEST_WORKTREE_ID]: [...tabs, ...extra] }, - terminalLayoutsByTabId: { - ...session.terminalLayoutsByTabId, - ...Object.fromEntries( - extra.map((tab, index) => { - const leafId = `10000000-0000-4000-8000-${index.toString().padStart(12, '0')}` - return [ - tab.id, - { root: { type: 'leaf', leafId }, activeLeafId: leafId, expandedLeafId: null } - ] - }) - ) - }, - tabGroups: { - ...session.tabGroups, - [TEST_WORKTREE_ID]: session.tabGroups![TEST_WORKTREE_ID]!.map((group) => ({ - ...group, - tabOrder: [...group.tabOrder, ...extra.map((tab) => tab.id)] - })) - } - }) - } runtime.syncWindowGraph(HEADLESS_RUNTIME_WINDOW_ID, { tabs: [], leaves: [] }) runtime.setPtyController({ spawn: vi.fn(async (args: { worktreeId?: string; tabId?: string; leafId?: string }) => { @@ -83,60 +48,6 @@ const orderOf = (rows: readonly { type: string; id: string; parentTabId?: string ] describe("a phone's layout change on a windowless server (switch on)", () => { - it('bounds explicit split selection traversal at N and 10N tabs', async () => { - const measurements: number[] = [] - const select = selection.selectMobileSessionTab - for (const count of [50, 500]) { - const { runtime } = await headlessRuntime(count) - let visits = 0 - function observed(items: T[]): T[] { - const rows = [...items] - items.forEach((item, index) => { - Object.defineProperty(rows, index, { - get() { - visits++ - return item - } - }) - }) - return rows - } - let selectionVisits = 0 - const selected = vi - .spyOn(selection, 'selectMobileSessionTab') - .mockImplementation((snapshot, active) => { - const tabs = observed(snapshot.tabs) - const tabGroups = snapshot.tabGroups?.map((group) => ({ - ...group, - tabOrder: observed(group.tabOrder) - })) - visits = 0 - const result = select({ ...snapshot, tabs, tabGroups }, active) - selectionVisits += visits - return result - }) - await runtime.moveMobileSessionTab(WORKTREE, { - kind: 'split', - tabId: SECOND_TAB_ID, - targetGroupId: SAVED_GROUP_ID, - splitDirection: 'right' - }) - expect(selected).toHaveBeenCalledTimes(1) - // The original tab has two panes: resolve + flags + one row copy, plus one tab-order scan. - expect(selectionVisits).toBeGreaterThan(0) - expect(selectionVisits).toBeLessThanOrEqual(3 * (count + 1) + count) - measurements.push(selectionVisits) - const stored = runtime.mobileSessionTabsByWorktree.get(TEST_WORKTREE_ID)! - expect(stored.tabs).toHaveLength(count + 1) - expect(stored.activeTabId).toBe(`${SECOND_TAB_ID}::77777777-7777-4777-8777-777777777777`) - expect(stored.tabGroups?.[1]?.tabOrder).toEqual([SECOND_TAB_ID]) - expect(stored.tabs.filter((row) => row.isActive).map((row) => row.id)).toEqual([ - `${SECOND_TAB_ID}::77777777-7777-4777-8777-777777777777` - ]) - selected.mockRestore() - } - expect(measurements[1]).toBeLessThanOrEqual(10 * measurements[0]!) - }) it("splits a tab into a group of its own in main's layout, and selects it there", async () => { const { runtime, store } = await headlessRuntime() const selected = `${SECOND_TAB_ID}::77777777-7777-4777-8777-777777777777` @@ -217,23 +128,6 @@ describe("a phone's layout change on a windowless server (switch on)", () => { ) }) - it("answers a split main refuses with main's refusal code", async () => { - const { runtime } = await headlessRuntime() - vi.spyOn(runtime, 'applyLayoutCommand').mockResolvedValue({ ok: false, code: 'invalid_params' }) - - await expect( - runtime.moveMobileSessionTab(WORKTREE, { - kind: 'split', - tabId: SECOND_TAB_ID, - targetGroupId: SAVED_GROUP_ID, - splitDirection: 'right' - }) - ).rejects.toThrow('invalid_params') - expect(tabStrip(await runtime.listMobileSessionTabs(WORKTREE))).toEqual([ - [SURFACE_TAB_ID, SECOND_TAB_ID, THIRD_TAB_ID] - ]) - }) - it.each([ { client: 'a phone with split-group placement', diff --git a/src/main/window/desktop-integration/app-icon.test.ts b/src/main/window/desktop-integration/app-icon.test.ts index 72e218c12d0..9ca3a30eb73 100644 --- a/src/main/window/desktop-integration/app-icon.test.ts +++ b/src/main/window/desktop-integration/app-icon.test.ts @@ -76,27 +76,6 @@ describe('app icon selection', () => { expect(dockSetIconMock.mock.lastCall?.[0].isEmpty()).toBe(true) }) - it('keeps an imported icon out of the app bundle and leaves the live Dock tile alone', async () => { - const commands: string[][] = [] - dockSetIconMock.mockClear() - persistMacDockIcon('custom', { - appBundlePath: '/Applications/Orca.app', - execFile: (file, args, _options, callback) => { - commands.push([file, ...args]) - callback?.(null) - }, - isDevApp: false, - platform: 'darwin' - }) - await waitForQueuedPersistence() - - expect(commands.some((command) => command.join(' ').includes('setIcon:image'))).toBe(false) - expect(commands.some((command) => command.join(' ').includes('setIcon:(missing value)'))).toBe( - true - ) - expect(dockSetIconMock).not.toHaveBeenCalled() - }) - it('serializes rapid macOS dock icon persistence so the last icon request wins', async () => { const pendingCallbacks: (() => void)[] = [] const execFile = vi.fn( diff --git a/src/renderer/src/components/github/GitHubMarkdownComposer.test.tsx b/src/renderer/src/components/github/GitHubMarkdownComposer.test.tsx index c71dff7bde7..317f12b3967 100644 --- a/src/renderer/src/components/github/GitHubMarkdownComposer.test.tsx +++ b/src/renderer/src/components/github/GitHubMarkdownComposer.test.tsx @@ -373,25 +373,6 @@ it('refuses normalized data-image HTML without dropping surrounding text', async expect(imageUploads.upload).not.toHaveBeenCalled() }) -it('leaves ordinary HTML paste on the editor parser path', async () => { - const view = render( {}} placeholder="HTML" />) - await waitFor(() => expect(view.container.querySelector('.tiptap')).not.toBeNull()) - const parse = vi.spyOn(DOMParser.prototype, 'parseFromString') - try { - fireEvent.paste(getComposerEditor(view.container), { - clipboardData: { - files: [], - getData: (type: string) => - type === 'text/html' ? '

ordinary formatted text

' : '' - } - }) - expect(getComposerEditor(view.container).textContent).toBe('ordinary formatted text') - expect(parse).not.toHaveBeenCalled() - } finally { - parse.mockRestore() - } -}) - it('shows a failed hosted image as its filename link without changing the uploaded Markdown', async () => { imageUploads.upload .mockReset() diff --git a/src/renderer/src/components/github/github-work-item-issue-target.test.ts b/src/renderer/src/components/github/github-work-item-issue-target.test.ts deleted file mode 100644 index 29cce06d54e..00000000000 --- a/src/renderer/src/components/github/github-work-item-issue-target.test.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest' -import { runIssueUpdate, runWorkItemBodyUpdate } from './github-work-item-edit-mutations' -import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' -import type { TaskSourceContext } from '../../../../shared/work-items/task-source-context' -import { callRuntimeRpc } from '@/runtime/rpc/calls/runtime-rpc-call' - -vi.mock('@/runtime/rpc/calls/runtime-rpc-call', () => ({ - callRuntimeRpc: vi.fn() -})) -vi.mock('@/store', () => ({ useAppStore: { getState: vi.fn(() => ({ repos: [] })) } })) -vi.mock('@/components/github/github-work-item-comment-mutations', () => ({ - notifyWorkItemDetailsMutation: vi.fn() -})) - -const ORIGIN = { owner: 'fork-owner', repo: 'widgets', host: 'github.com' } -const UPSTREAM = { owner: 'upstream-owner', repo: 'widgets', host: 'github.com' } -const projectOrigin = { - ...UPSTREAM, - number: 12, - type: 'issue' as const, - projectId: 'project-1', - projectItemId: 'project-item-12', - cacheKey: 'project-cache' -} -const localSource: TaskSourceContext = { - kind: 'task-source', - provider: 'github', - projectId: 'project-1', - hostId: 'local', - repoId: 'repo-1' -} -const item: GitHubWorkItem = { - id: 'issue:12', - type: 'issue', - number: 12, - title: 'Origin issue', - state: 'open', - url: 'https://github.com/fork-owner/widgets/issues/12', - labels: [], - updatedAt: '', - author: null, - repoId: 'repo-1' -} - -describe('issue edits retain the displayed repository', () => { - beforeEach(() => { - vi.clearAllMocks() - vi.mocked(callRuntimeRpc).mockResolvedValue({ ok: true }) - vi.stubGlobal('window', { - api: { - gh: { - updateIssue: vi.fn().mockResolvedValue({ ok: true }), - updateIssueBySlug: vi.fn(), - updatePullRequestBySlug: vi.fn() - } - } - }) - }) - - describe.each(['local', 'runtime:project-source'] as const)('Project source %s', (hostId) => { - const sourceContext = { ...localSource, hostId } - it('saves the Project row body through the original exact slug actor', async () => { - const bodyImageUrls = { - 'https://github.com/user-attachments/assets/ce11040a-fb66-4289-927f-547b16dfc488': - 'https://private-user-images.githubusercontent.com/1/image.png?jwt=TEST' - } - vi.mocked(window.api.gh.updateIssueBySlug).mockResolvedValue({ ok: true, bodyImageUrls }) - vi.mocked(callRuntimeRpc).mockResolvedValue({ ok: true, bodyImageUrls }) - const result = await runWorkItemBodyUpdate({ - item: { ...item, url: 'https://github.com/upstream-owner/widgets/issues/12' }, - repoPath: '/home/fixture/fork-widgets', - sourceContext, - projectOrigin, - body: 'Project edit', - parsedSlug: UPSTREAM - }) - expect(result).toEqual({ bodyImageUrls }) - }) - }) - - it.each([ - { state: 'closed' as const }, - { addLabels: ['bug'] }, - { addAssignees: ['fork-assignee'] } - ])('pins field edits to the opened issue repository: %j', async (updates) => { - await runIssueUpdate({ - repoPath: 'C:\\workspace\\widgets', - repoId: 'repo-1', - projectOrigin: undefined, - number: 12, - issueRepo: ORIGIN, - updates - }) - - expect(window.api.gh.updateIssue).toHaveBeenCalledWith( - expect.objectContaining({ - repoPath: 'C:\\workspace\\widgets', - ownerRepo: ORIGIN, - updates - }) - ) - expect(window.api.gh.updateIssueBySlug).not.toHaveBeenCalled() - }) - it.each([true, false])( - 'forwards local PR body PATCH maps without adding a reread (%s)', - async (hasMap) => { - const body = - '![shot](https://github.com/user-attachments/assets/ce11040a-fb66-4289-927f-547b16dfc488)' - vi.mocked(window.api.gh.updatePullRequestBySlug).mockResolvedValue( - hasMap - ? { - ok: true, - bodyImageUrls: { - 'https://github.com/user-attachments/assets/ce11040a-fb66-4289-927f-547b16dfc488': - 'https://private-user-images.githubusercontent.com/1/image.png?jwt=TEST' - } - } - : { ok: true } - ) - const result = await runWorkItemBodyUpdate({ - item: { ...item, type: 'pr' }, - repoPath: '/repo', - sourceContext: localSource, - projectOrigin: undefined, - body, - parsedSlug: ORIGIN - }) - expect(result).toEqual({ - bodyImageUrls: hasMap - ? { - 'https://github.com/user-attachments/assets/ce11040a-fb66-4289-927f-547b16dfc488': - 'https://private-user-images.githubusercontent.com/1/image.png?jwt=TEST' - } - : undefined - }) - expect(window.api.gh.updatePullRequestBySlug).toHaveBeenCalledWith( - expect.objectContaining({ - updates: { - body: '![shot](https://github.com/user-attachments/assets/ce11040a-fb66-4289-927f-547b16dfc488)' - } - }) - ) - } - ) -}) diff --git a/src/renderer/src/components/settings/virtual-machines/recipes/EphemeralVmsPane.test.tsx b/src/renderer/src/components/settings/virtual-machines/recipes/EphemeralVmsPane.test.tsx index 76723a6b2f3..035d2fee5aa 100644 --- a/src/renderer/src/components/settings/virtual-machines/recipes/EphemeralVmsPane.test.tsx +++ b/src/renderer/src/components/settings/virtual-machines/recipes/EphemeralVmsPane.test.tsx @@ -166,41 +166,4 @@ describe('EphemeralVmsPane', () => { expect(container.textContent).toContain('Newest Recipe') expect(container.textContent).not.toContain('Stale Recipe') }) - - it('names the credential-like variables a recipe inherits, without values', async () => { - const container = await renderPane() - await vi.waitFor(() => - expect(container.querySelector('[data-testid="recipe-env-exposure"]')?.textContent).toContain( - 'ANTHROPIC_API_KEY, GITHUB_TOKEN' - ) - ) - expect(container.textContent).toContain('Set passEnv on the recipe') - }) - - it('lists only allowlisted credentials when the recipe sets passEnv', async () => { - const listRecipeCatalog = window.api.ephemeralVm.listRecipeCatalog as ReturnType - listRecipeCatalog.mockReset().mockResolvedValue([ - { - repoId: 'repo-1', - repoName: 'Repo', - repoPath: '/repo', - diagnostics: [], - recipes: [ - { - id: 'limited', - name: 'Limited', - create: 'create', - passEnv: ['GITHUB_TOKEN'] - } - ] - } - ]) - const container = await renderPane() - await vi.waitFor(() => - expect(container.querySelector('[data-testid="recipe-env-exposure"]')?.textContent).toContain( - 'from passEnv: GITHUB_TOKEN.' - ) - ) - expect(container.textContent).not.toContain('ANTHROPIC_API_KEY') - }) }) diff --git a/src/renderer/src/components/terminal/window-close-running-work.test.ts b/src/renderer/src/components/terminal/window-close-running-work.test.ts index bda5bf66542..8ce38a8201b 100644 --- a/src/renderer/src/components/terminal/window-close-running-work.test.ts +++ b/src/renderer/src/components/terminal/window-close-running-work.test.ts @@ -1,4 +1,4 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { beforeEach, describe, expect, it, vi } from 'vitest' const { getStateMock, inspectRuntimeTerminalProcessMock, getStatusFeedMock } = vi.hoisted(() => ({ getStateMock: vi.fn(), @@ -22,14 +22,6 @@ import { assessWindowCloseRunningWork } from './window-close-running-work' const LOCAL_PTY = 'pty-local' const SSH_PTY = 'ssh:openclaw@@pty-7' -const RUNTIME_PTY = 'remote:env-1@@handle-1' -const OWNERLESS_RUNTIME_PTY = 'remote:handle-2' - -const BUSY = { - foregroundProcess: 'pnpm build', - hasChildProcesses: true, - foregroundProcessEvidence: {} -} const UNVERIFIABLE = { foregroundProcess: null, @@ -81,41 +73,7 @@ beforeEach(() => { getStatusFeedMock.mockReturnValue(feed(null, 'unverifiable')) }) -afterEach(() => { - vi.useRealTimers() -}) - describe('assessWindowCloseRunningWork', () => { - it.each(['working', 'attention'])( - 'warns for a live %s chat without a PTY or ownership metadata', - async (status) => { - setState([]) - representChat() - getStatusFeedMock.mockReturnValue(feed(status)) - - await expect(assessWindowCloseRunningWork({ isQuitting: false })).resolves.toEqual({ - kind: 'running' - }) - expect(inspectRuntimeTerminalProcessMock).not.toHaveBeenCalled() - } - ) - - it.each([ - ['working', 'unverifiable', {}], - ['idle', 'live', { hostExecutionOwned: true, children: [{ kind: 'agent', state: 'working' }] }], - ['idle', 'live', { children: [{ kind: 'agent', state: 'unverifiable' }] }], - [null, 'live', {}], - [undefined, 'live', {}] - ])('does not warn for status %s observed as %s', async (status, observation, extra) => { - setState([]) - representChat() - getStatusFeedMock.mockReturnValue(feed(status, observation, extra)) - - await expect(assessWindowCloseRunningWork({ isQuitting: false })).resolves.toEqual({ - kind: 'none' - }) - }) - it('keeps equal chat IDs separate across hosts and resolves legacy tabs through their workspace owner', async () => { setState([]) const state = getStateMock() @@ -135,17 +93,6 @@ describe('assessWindowCloseRunningWork', () => { }) }) - it('does not use a local observation for an unserved SSH chat', async () => { - setState([]) - representChat('ssh:openclaw') - getStatusFeedMock.mockReturnValue(feed('working')) - - await expect(assessWindowCloseRunningWork({ isQuitting: false })).resolves.toEqual({ - kind: 'none' - }) - expect(getStatusFeedMock).not.toHaveBeenCalled() - }) - it('uses current chat membership, owner and status after terminal probes settle', async () => { setState([LOCAL_PTY]) representChat() @@ -183,72 +130,4 @@ describe('assessWindowCloseRunningWork', () => { }) expect(getStatusFeedMock).not.toHaveBeenCalled() }) - // #29916: a paired runtime owns its terminals and keeps them after this client quits. - it('does not warn on quit, or probe, for a live process on a paired runtime host', async () => { - setState([RUNTIME_PTY, OWNERLESS_RUNTIME_PTY]) - answerWith({ [RUNTIME_PTY]: BUSY, [OWNERLESS_RUNTIME_PTY]: BUSY }) - - await expect(assessWindowCloseRunningWork({ isQuitting: true })).resolves.toEqual({ - kind: 'none' - }) - expect(inspectRuntimeTerminalProcessMock).not.toHaveBeenCalled() - }) - - it('still warns when closing the window over a live process on a paired runtime host', async () => { - setState([RUNTIME_PTY]) - answerWith({ [RUNTIME_PTY]: BUSY }) - - await expect(assessWindowCloseRunningWork({ isQuitting: false })).resolves.toEqual({ - kind: 'running' - }) - }) - - // Unreachable remote work is unverifiable, never proof of exit; quitting silently could orphan it. - it('warns rather than quitting silently when a remote host answers unverifiable', async () => { - setState([SSH_PTY]) - answerWith({ [SSH_PTY]: UNVERIFIABLE }) - - await expect(assessWindowCloseRunningWork({ isQuitting: true })).resolves.toEqual({ - kind: 'unverifiable' - }) - }) - - it('reports a live process even when a sibling remote pane is only unverifiable', async () => { - setState([SSH_PTY, RUNTIME_PTY]) - answerWith({ [SSH_PTY]: UNVERIFIABLE, [RUNTIME_PTY]: BUSY }) - - await expect(assessWindowCloseRunningWork({ isQuitting: false })).resolves.toEqual({ - kind: 'running' - }) - }) - - it('still warns about a live local process when closing the window', async () => { - setState([LOCAL_PTY]) - answerWith({ [LOCAL_PTY]: BUSY }) - - await expect(assessWindowCloseRunningWork({ isQuitting: false })).resolves.toEqual({ - kind: 'running' - }) - }) - - // Local probe failure has no lost-transport ambiguity and must not block closing. - it('does not warn when only a local probe is unverifiable', async () => { - setState([LOCAL_PTY]) - answerWith({ [LOCAL_PTY]: UNVERIFIABLE }) - - await expect(assessWindowCloseRunningWork({ isQuitting: false })).resolves.toEqual({ - kind: 'none' - }) - }) - - // Quit consents to end this machine’s processes (#524); it does not consent to end remote work. - it('leaves local-only quit unprompted, and never probes for it', async () => { - setState([LOCAL_PTY]) - answerWith({ [LOCAL_PTY]: BUSY }) - - await expect(assessWindowCloseRunningWork({ isQuitting: true })).resolves.toEqual({ - kind: 'none' - }) - expect(inspectRuntimeTerminalProcessMock).not.toHaveBeenCalled() - }) }) diff --git a/tests/component-fixtures/orchestration-crew-card.spec.ts b/tests/component-fixtures/orchestration-crew-card.spec.ts deleted file mode 100644 index a5956498ee8..00000000000 --- a/tests/component-fixtures/orchestration-crew-card.spec.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { expect, test, type Page } from '@playwright/test' - -async function openFixture(page: Page, name: string) { - await page.goto(`/fixtures.html?id=orchestration-crew/OrchestrationCrewPanel:${name}`) - await expect(page.locator('html')).toHaveAttribute('data-fixture-state', 'ready') - const card = page.getByRole('complementary', { name: 'Orchestration' }) - await expect(card).toBeVisible() - const rows = card.locator('.overflow-y-auto') - const bounds = await card.boundingBox() - if (!bounds) { - throw new Error('Card has no bounds') - } - // Ten workers never fit a short pane, so the rows must scroll inside the card. - expect(await rows.evaluate((element) => element.scrollHeight > element.clientHeight)).toBe(true) - return bounds -} - -test('a saved orchestration opt-in keeps the native panel hidden and composer available', async ({ - page -}) => { - await page.goto( - '/fixtures.html?id=orchestration-crew/OrchestrationCrewPanel:tenWorkersShortChatExpanded' - ) - await expect(page.locator('html')).toHaveAttribute('data-fixture-state', 'ready') - await expect(page.getByRole('complementary', { name: 'Orchestration' })).toHaveCount(0) - await expect(page.getByTestId('composer')).toBeVisible() -}) - -test('in a short terminal the card keeps the bottom quarter clear and scrolls', async ({ - page -}) => { - const card = await openFixture(page, 'tenWorkersShortPaneExpanded') - const pane = await page.locator('.pane').boundingBox() - const limit = pane!.y + pane!.height * 0.75 - // It reaches the limit, since the rows are taller than the room, but never passes it. - expect(card.y + card.height).toBeLessThanOrEqual(limit + 0.5) - expect(card.y + card.height).toBeGreaterThan(limit - 2) -}) - -test('in a terminal at its minimum split height the collapse button stays clickable', async ({ - page -}) => { - await page.goto( - '/fixtures.html?id=orchestration-crew/OrchestrationCrewPanel:twoWorkersMinimumPaneExpanded' - ) - await expect(page.locator('html')).toHaveAttribute('data-fixture-state', 'ready') - const collapse = page.getByRole('button', { name: 'Collapse orchestration panel' }) - const button = await collapse.boundingBox() - const pane = await page.locator('.pane').boundingBox() - // Aim at the part of the button the pane doesn't clip. - const x = button!.x + button!.width / 2 - const y = (button!.y + Math.min(button!.y + button!.height, pane!.y + pane!.height)) / 2 - expect(y).toBeLessThan(pane!.y + pane!.height) - await page.mouse.click(x, y) - await expect(page.getByRole('complementary', { name: 'Orchestration' })).toHaveCount(0) -}) - -for (const [name, width] of [ - ['twoWorkersNarrowPaneExpanded', 100], - ['twoWorkersMinimumWidthPaneExpanded', 50] -] as const) { - test(`in a ${width}px-wide pane the whole collapse button fits inside the card`, async ({ - page - }) => { - await page.goto(`/fixtures.html?id=orchestration-crew/OrchestrationCrewPanel:${name}`) - await expect(page.locator('html')).toHaveAttribute('data-fixture-state', 'ready') - const card = await page.getByRole('complementary', { name: 'Orchestration' }).boundingBox() - const collapse = page.getByRole('button', { name: 'Collapse orchestration panel' }) - const button = await collapse.boundingBox() - expect(button!.x).toBeGreaterThanOrEqual(card!.x) - expect(button!.x + button!.width).toBeLessThanOrEqual(card!.x + card!.width + 0.5) - await collapse.click() - await expect(page.getByRole('complementary', { name: 'Orchestration' })).toHaveCount(0) - }) -} - -for (const name of ['terminalQuestionDetails']) { - for (const width of [280, 360]) { - test(`${name} keeps long expanded details inside a ${width}px frame`, async ({ page }) => { - await page.goto(`/fixtures.html?id=orchestration-crew/OrchestrationCrewPanel:${name}`) - await expect(page.locator('html')).toHaveAttribute('data-fixture-state', 'ready') - const frame = page.locator('#component-fixture-frame') - await frame.evaluate((element, frameWidth) => { - element.style.width = `${frameWidth}px` - }, width) - const card = page.getByRole('complementary', { name: 'Orchestration' }) - const details = card.locator('dl') - await expect(details).toBeVisible() - await expect(details).toContainText('dispatched') - expect(await details.innerText()).toContain('unbroken'.repeat(128)) - const overflow = await details.evaluate((element) => { - const card = element.closest('aside')! - const bounds = card.getBoundingClientRect() - const violations: string[] = [] - for (const value of element.querySelectorAll('dd')) { - const rect = value.getBoundingClientRect() - if (rect.left < bounds.left || rect.right > bounds.right + 1) { - violations.push('detail value outside card') - } - if (value.scrollWidth > value.clientWidth + 1) { - violations.push('detail value scrolls horizontally') - } - const walker = document.createTreeWalker(value, NodeFilter.SHOW_TEXT) - while (walker.nextNode()) { - const range = document.createRange() - range.selectNodeContents(walker.currentNode) - for (const line of range.getClientRects()) { - if (line.left < bounds.left || line.right > bounds.right + 1) { - violations.push('text outside card') - } - } - } - } - return violations - }) - await test.info().attach('expanded-details', { - body: await frame.screenshot(), - contentType: 'image/png' - }) - expect(overflow).toEqual([]) - await card.getByRole('button', { name: /^Show .*task and latest message$/ }).click() - await expect(details).toHaveCount(0) - }) - } -}