From a25967b434802d74672b3a03b206370a878a9a61 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Tue, 8 Sep 2026 07:02:54 -0700 Subject: [PATCH] test: keep Desktop pane-focus setup outside cold imports --- .../src/store/session-pane-focus.test.ts | 56 +++++++++++++------ evals/desktop-pane-fixture/README.md | 52 +++++++++++++++++ evals/desktop-pane-fixture/vitest.config.mts | 28 ++++++++++ 3 files changed, 119 insertions(+), 17 deletions(-) create mode 100644 evals/desktop-pane-fixture/README.md create mode 100644 evals/desktop-pane-fixture/vitest.config.mts diff --git a/apps/desktop/src/store/session-pane-focus.test.ts b/apps/desktop/src/store/session-pane-focus.test.ts index 0c0b2688b1..58a954cc21 100644 --- a/apps/desktop/src/store/session-pane-focus.test.ts +++ b/apps/desktop/src/store/session-pane-focus.test.ts @@ -1,21 +1,24 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest' +import { afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest' -async function setup() { - const tree = await import('@/components/pane-shell/tree/store') - const model = await import('@/components/pane-shell/tree/model') - const { registry } = await import('@/contrib/registry') - const session = await import('@/store/session') - const states = await import('@/store/session-states') - const { paneMirror } = await import('@/app/chat/pane-mirror') +import { paneMirror } from '@/app/chat/pane-mirror' +import * as model from '@/components/pane-shell/tree/model' +import * as tree from '@/components/pane-shell/tree/store' +import { registry } from '@/contrib/registry' +import { applyDesktopOverlay } from '@/store/profile-share' +import * as session from '@/store/session' +import * as states from '@/store/session-states' - registry.register({ +// These app-lifetime watchers have no unsubscribe API. Install them once in +// Vitest's isolated file graph, not once per case (which accumulates listeners). +beforeAll(() => { + const disposeWorkspace = registry.register({ area: 'panes', data: { placement: 'main', uncloseable: true }, id: 'workspace', render: () => null, title: 'Chat' }) - tree.declareDefaultTree(model.group(['workspace'], { active: 'workspace', id: 'main' })) + tree.watchContributedPanes() paneMirror({ source: states.$sessionTiles, @@ -27,6 +30,23 @@ async function setup() { render: () => null, close: states.closeSessionTile })() + + return disposeWorkspace +}) + +function resetState() { + // Empty the source while the mirror is live so it also disposes its pane. + states.discardSessionTile('canonical-chat') + tree.$layoutTree.set(null) + tree.$activeTreeGroup.set(null) + tree.$activePresetId.set('default') + session.$selectedStoredSessionId.set(null) + session.$lastReadAtBySessionId.set({}) + window.localStorage.clear() +} + +function setup() { + tree.declareDefaultTree(model.group(['workspace'], { active: 'workspace', id: 'main' })) session.$selectedStoredSessionId.set('previous-chat') const scope = { @@ -42,17 +62,19 @@ async function setup() { } describe('focusing a saved Bot Chat requires a visible pane', () => { - let ctx: Awaited> + let ctx: ReturnType const paneId = 'session-tile:canonical-chat' - beforeEach(async () => { - window.localStorage.clear() - vi.resetModules() - ctx = await setup() + beforeEach(() => { + resetState() + ctx = setup() + expect(model.findGroupOfPane(tree.$layoutTree.get()!, 'workspace')?.id).toBe('main') + expect(states.$sessionTiles.get().map(tile => tile.storedSessionId)).toEqual(['canonical-chat']) }) - it('re-adopts a saved tab after a profile overlay replaces the layout', async () => { - const { applyDesktopOverlay } = await import('@/store/profile-share') + afterEach(resetState) + + it('re-adopts a saved tab after a profile overlay replaces the layout', () => { const { model, scope, states, tree } = ctx const saved = states.$sessionTiles.get() applyDesktopOverlay('imported-profile', { diff --git a/evals/desktop-pane-fixture/README.md b/evals/desktop-pane-fixture/README.md new file mode 100644 index 0000000000..7423b4a53a --- /dev/null +++ b/evals/desktop-pane-fixture/README.md @@ -0,0 +1,52 @@ +# Saved-pane fixture import-delay control + +From a root `npm ci` installation, run in `apps/desktop`: + +```sh +npx vitest run --config ../../evals/desktop-pane-fixture/vitest.config.mts --reporter verbose +npx vitest run --project ui src/store/session-pane-focus.test.ts --sequence.shuffle.tests --sequence.seed=12345 --reporter verbose +``` + +The Vite plugin appends an 11-second asynchronous wait to the real pane-tree +store's evaluation. It does not mock pane functions, inspect source text in an +assertion, lower a deadline, or alter the two test bodies. The UI test deadline +remains 15 seconds and the default hook deadline remains 10 seconds. This is a +scheduling-controlled reproduction of import latency, not a claim that a local +machine reproduced the original CI load unassisted. It is opt-in, not a slow CI +test. + +For A/B, apply this eval directory to the parent revision and run the same +command, then run on the fixture fix. Base evaluates the graph inside each +`beforeEach`; fixed evaluates it once during test collection. The delay marker +appears twice on base and once on fixed. Expected: base fails with `Hook timed +out in 10000ms`; fixed runs both behavioral assertions successfully. + +Measured on the initial base `fb3446a281e`: + +| Control | Result | +| --- | --- | +| Base + evaluation delay | 2 hook timeouts, 20.04s test phase | +| Fixed + identical delay | 2 passed, 5ms test phase; 11.84s import phase | +| Fixed normal and reversed test order (seed 12345) | 2 passed in each order | +| Store directory, 8 workers | 123 files / 1491 tests passed | +| Complete UI project, 8 workers | 753 files / 7355 assertions passed, but exit 1 from an unrelated Radix focus-scope timer's wrong-realm Event in `statusbar-visibility.test.tsx` | + +Mutation controls, applied separately to `session-states.ts` and then restored: + +- Remove `revealTreePane(paneId)` in `focusOpenSession`: the overlay re-adoption + case fails (`null` instead of `canonical-chat`); the miss case passes. +- Make `focusWorkspaceOwnerSessionTile` return the stored id regardless of + `focusOpenSession`'s result: the miss case fails (`canonical-chat` instead of + `null`); the overlay case passes. + +The fixture uses the real registry, tree watcher, pane mirror, overlay, tile +store, and focus helpers. Install app-lifetime watchers once per isolated Vitest +file graph. Discard the fixture tile through the real action to clear both the +reactive source and its persisted in-memory bucket; reset layout, focus, preset, +selection, read baseline, and localStorage between cases. The mirror observes the +discard and unregisters its contribution. No production API needs a test-only +reset or disposer. + +If the host has exhausted inotify watches, prefix commands with +`CHOKIDAR_USEPOLLING=true`; this changes file watching, not test deadlines. The +recorded store/full-suite and final focused controls used that workaround. diff --git a/evals/desktop-pane-fixture/vitest.config.mts b/evals/desktop-pane-fixture/vitest.config.mts new file mode 100644 index 0000000000..784fe7535a --- /dev/null +++ b/evals/desktop-pane-fixture/vitest.config.mts @@ -0,0 +1,28 @@ +// From apps/desktop: npx vitest run --config ../../evals/desktop-pane-fixture/vitest.config.mts +// Replay a slow module evaluation without changing any pane behavior or deadlines. +import { defineConfig, mergeConfig } from 'vitest/config' + +import desktopConfig from '../../apps/desktop/vite.config.ts' + +export default defineConfig(env => + mergeConfig(desktopConfig(env), { + plugins: [ + { + name: 'slow-pane-store-evaluation', + enforce: 'post', + transform(code: string, id: string) { + if (id.endsWith('/components/pane-shell/tree/store.ts')) { + return `${code}\nconsole.info('[pane-fixture] delaying real tree store evaluation by 11000ms');\nawait new Promise(resolve => setTimeout(resolve, 11000));\n` + } + } + } + ], + test: { + environment: 'jsdom', + setupFiles: ['./vitest.setup.ts'], + include: ['src/store/session-pane-focus.test.ts'], + globals: true, + testTimeout: 15_000 + } + }) +)