From aab207db6a66fd9306a401b438801455a7c772d4 Mon Sep 17 00:00:00 2001 From: Jinwoo-H Date: Thu, 20 Aug 2026 21:22:15 -0400 Subject: [PATCH] test(cmd-j): harden visible performance oracle --- .../components/WorktreeJumpPalette.test.tsx | 8 ++-- ...jump-palette-interleaved-sections.test.tsx | 25 +++++++---- .../lib/palette-match/palette-match-budget.ts | 1 + tests/e2e/cmd-j-cold-open-performance.spec.ts | 42 ++++++++++++++++--- 4 files changed, 58 insertions(+), 18 deletions(-) diff --git a/src/renderer/src/components/WorktreeJumpPalette.test.tsx b/src/renderer/src/components/WorktreeJumpPalette.test.tsx index e9c560dfc4b..cbbb9426809 100644 --- a/src/renderer/src/components/WorktreeJumpPalette.test.tsx +++ b/src/renderer/src/components/WorktreeJumpPalette.test.tsx @@ -2,7 +2,7 @@ import { act } from 'react' import { createRoot, type Root } from 'react-dom/client' -import { fireEvent } from '@testing-library/react' +import { fireEvent, waitFor } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import type * as ReactI18Next from 'react-i18next' import { useAppStore } from '@/store' @@ -356,9 +356,9 @@ describe('WorktreeJumpPalette', () => { await act(async () => { setCommandQuery?.('Feature') }) - await flushEffects() - - expect(testContainer.textContent).toContain('Feature workspace') + await waitFor(() => { + expect(testContainer.textContent).toContain('Feature workspace') + }) }) // STA-4343 closed: two workspaces sharing `repoId::path` across hosts are two distinct diff --git a/src/renderer/src/components/worktree-jump-palette-interleaved-sections.test.tsx b/src/renderer/src/components/worktree-jump-palette-interleaved-sections.test.tsx index 62055b5027b..9102ecfbe8f 100644 --- a/src/renderer/src/components/worktree-jump-palette-interleaved-sections.test.tsx +++ b/src/renderer/src/components/worktree-jump-palette-interleaved-sections.test.tsx @@ -2,6 +2,7 @@ import { act } from 'react' import { createRoot, type Root } from 'react-dom/client' +import { waitFor } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import type * as ReactI18Next from 'react-i18next' import type { Repo } from '../../../shared/repo-types' @@ -314,12 +315,13 @@ describe('WorktreeJumpPalette interleaved primary sections', () => { await act(async () => { setCommandQuery?.('perf') }) - await flushEffects() + await waitFor(() => { + const rows = getPrimaryRowsBySectionHeader() + expect(rows.filter((row) => row.rowId.startsWith('workspace-tab:'))).toHaveLength(8) + expect(rows.filter((row) => row.rowId.startsWith('worktree:'))).toHaveLength(5) + }) const rows = getPrimaryRowsBySectionHeader() - // Why the counts: both remainders must still render, just under a re-emitted header. - expect(rows.filter((row) => row.rowId.startsWith('workspace-tab:'))).toHaveLength(8) - expect(rows.filter((row) => row.rowId.startsWith('worktree:'))).toHaveLength(5) for (const { header, rowId } of rows) { expect(header).toBe(rowId.startsWith('workspace-tab:') ? 'Open Tabs' : 'Worktrees') } @@ -331,7 +333,11 @@ describe('WorktreeJumpPalette interleaved primary sections', () => { await act(async () => { setCommandQuery?.('perf') }) - await flushEffects() + await waitFor(() => { + const rows = getPrimaryRowsBySectionHeader() + expect(rows.filter((row) => row.rowId.startsWith('workspace-tab:'))).toHaveLength(8) + expect(rows.filter((row) => row.rowId.startsWith('worktree:'))).toHaveLength(5) + }) const renderedIds = Array.from( testContainer.querySelectorAll('[data-command-item]') @@ -370,10 +376,11 @@ describe('WorktreeJumpPalette interleaved primary sections', () => { await act(async () => { setCommandQuery?.('ubuntu') }) - await flushEffects() - - const rows = getPrimaryRowsBySectionHeader() - expect(rows).toEqual([{ header: 'Open Tabs', rowId: 'workspace-tab:tab-0' }]) + await waitFor(() => { + expect(getPrimaryRowsBySectionHeader()).toEqual([ + { header: 'Open Tabs', rowId: 'workspace-tab:tab-0' } + ]) + }) expect(testContainer.textContent).toContain('Open Tabs') expect(testContainer.textContent).not.toContain('Worktrees') }) diff --git a/src/renderer/src/lib/palette-match/palette-match-budget.ts b/src/renderer/src/lib/palette-match/palette-match-budget.ts index 742ae8f300b..e8eb24ddb8e 100644 --- a/src/renderer/src/lib/palette-match/palette-match-budget.ts +++ b/src/renderer/src/lib/palette-match/palette-match-budget.ts @@ -28,6 +28,7 @@ export const PALETTE_MATCH_BUDGET = { export const PALETTE_INTERACTION_BUDGET = { rendererStoreDispatchMs: 50, firstVisibleResultsMs: 100, + coldIndexReadyMs: 350, coldImmediateQueryResultsMs: 250, /** Five 60 Hz frames, including the palette's initial layout and focus commit. */ maxFrameGapMs: 84 diff --git a/tests/e2e/cmd-j-cold-open-performance.spec.ts b/tests/e2e/cmd-j-cold-open-performance.spec.ts index 9bf164eb195..f9a2c0cdf56 100644 --- a/tests/e2e/cmd-j-cold-open-performance.spec.ts +++ b/tests/e2e/cmd-j-cold-open-performance.spec.ts @@ -7,6 +7,7 @@ const WORKSPACE_COUNT = 800 const { rendererStoreDispatchMs: MAX_STORE_DISPATCH_MS, firstVisibleResultsMs: MAX_FIRST_VISIBLE_MS, + coldIndexReadyMs: MAX_COLD_INDEX_READY_MS, coldImmediateQueryResultsMs: MAX_COLD_IMMEDIATE_QUERY_MS, maxFrameGapMs: MAX_FRAME_GAP_MS } = PALETTE_INTERACTION_BUDGET @@ -317,7 +318,7 @@ async function waitForStableFrameCadence(page: Page): Promise { const now = performance.now() stableFrames = now - previousFrameAt <= 25 ? stableFrames + 1 : 0 previousFrameAt = now - if (stableFrames >= 6) { + if (stableFrames >= 12) { resolve() return } @@ -342,7 +343,22 @@ async function expectHostQualifiedNeedleOrder(dialog: Locator): Promise { expect(matchingTexts[1]).toContain('acme/orca-local') } -test.describe('Cmd-J cold accumulated-workspace performance', () => { +async function expectHostSpecificNeedle( + dialog: Locator, + query: string, + expectedName: string, + expectedRepo: string +): Promise { + await dialog.getByPlaceholder(/Search chats, terminals, worktrees/).fill(query) + const matchingRows = dialog + .locator('[cmdk-item]:has([data-slot="palette-worktree-name"])') + .filter({ hasText: 'Needle' }) + await expect(matchingRows).toHaveCount(1) + await expect(matchingRows).toContainText(expectedName) + await expect(matchingRows).toContainText(expectedRepo) +} + +test.describe('Cmd-J cold accumulated-workspace performance @headful', () => { test.beforeEach(async ({ orcaPage }) => { await waitForSessionReady(orcaPage) await waitForActiveWorktree(orcaPage) @@ -381,14 +397,28 @@ test.describe('Cmd-J cold accumulated-workspace performance', () => { expect(indexedQuery).not.toBeNull() await expectHostQualifiedNeedleOrder(dialog) + await expectHostSpecificNeedle( + dialog, + 'accumulated-0399-remote', + TARGET_REMOTE_NAME, + 'acme/orca-remote' + ) + await expectHostSpecificNeedle( + dialog, + 'accumulated-0399-local', + TARGET_LOCAL_NAME, + 'acme/orca-local' + ) await togglePaletteFromMain(electronApp) - await expect(dialog).not.toBeVisible() - // Reopen inside the close linger so documents and matcher indexes remain mounted. - await orcaPage.waitForTimeout(100) + await expect(dialog).toHaveAttribute('data-state', 'closed') await beginProbe(orcaPage, [TARGET_REMOTE_NAME, TARGET_LOCAL_NAME]) await togglePaletteFromMain(electronApp) await expect(dialog).toBeVisible() + await expect(dialog.locator('[data-worktree-index-pending]')).toHaveAttribute( + 'data-worktree-index-pending', + 'false' + ) await expect.poll(() => readMetrics(orcaPage), { timeout: 10_000 }).not.toBeNull() const warmReopen = await readMetrics(orcaPage) expect(warmReopen).not.toBeNull() @@ -414,10 +444,12 @@ test.describe('Cmd-J cold accumulated-workspace performance', () => { expect(coldOpen!.storeDispatchMs).not.toBeNull() expect(coldOpen!.storeDispatchMs!).toBeLessThanOrEqual(MAX_STORE_DISPATCH_MS) expect(coldOpen!.firstVisibleMs).toBeLessThanOrEqual(MAX_FIRST_VISIBLE_MS) + expect(coldOpen!.indexReadyMs).toBeLessThanOrEqual(MAX_COLD_INDEX_READY_MS) expect(coldOpen!.maxFrameGapMs).toBeLessThanOrEqual(MAX_FRAME_GAP_MS) expect(indexedQuery!.firstVisibleMs).toBeLessThanOrEqual(MAX_FIRST_VISIBLE_MS) expect(indexedQuery!.maxFrameGapMs).toBeLessThanOrEqual(MAX_FRAME_GAP_MS) expect(warmReopen!.firstVisibleMs).toBeLessThanOrEqual(MAX_FIRST_VISIBLE_MS) + expect(warmReopen!.indexReadyMs).toBe(warmReopen!.firstVisibleMs) expect(warmReopen!.maxFrameGapMs).toBeLessThanOrEqual(MAX_FRAME_GAP_MS) await orcaPage.evaluate(() => (window as PerformanceProbeWindow).__cmdJPerformanceProbe?.stop())