diff --git a/mobile/src/session/mobile-terminal-records.test.ts b/mobile/src/session/mobile-terminal-records.test.ts index f9d9357be4f..5265a1df4be 100644 --- a/mobile/src/session/mobile-terminal-records.test.ts +++ b/mobile/src/session/mobile-terminal-records.test.ts @@ -38,10 +38,8 @@ describe('mobile terminal records', () => { }) try { - for (let comparison = 0; comparison < 1_000; comparison += 1) { - expect(mobileTerminalThemesEqual(darkTheme, equivalentTheme)).toBe(true) - expect(mobileTerminalThemesEqual(darkTheme, lightTheme)).toBe(false) - } + expect(mobileTerminalThemesEqual(darkTheme, equivalentTheme)).toBe(true) + expect(mobileTerminalThemesEqual(darkTheme, lightTheme)).toBe(false) } finally { stringify.mockRestore() } diff --git a/mobile/src/session/mobile-terminal-tab-agent.test.ts b/mobile/src/session/mobile-terminal-tab-agent.test.ts index 22208fcb06c..1917180ab4f 100644 --- a/mobile/src/session/mobile-terminal-tab-agent.test.ts +++ b/mobile/src/session/mobile-terminal-tab-agent.test.ts @@ -166,10 +166,6 @@ describe('getMobileSessionTabTitle', () => { ) }) - it('keeps generic status titles unstripped when no agent identity is known', () => { - expect(getMobileSessionTabTitle(terminalTab('✳ investigating'))).toBe('✳ investigating') - }) - it('preserves browser title fallbacks after moving the helper out of the route', () => { const blankBrowserTab: Extract = { type: 'browser', diff --git a/mobile/src/session/mobile-terminal-viewport-resubscribe.test.ts b/mobile/src/session/mobile-terminal-viewport-resubscribe.test.ts index 442e1bda408..ba833803759 100644 --- a/mobile/src/session/mobile-terminal-viewport-resubscribe.test.ts +++ b/mobile/src/session/mobile-terminal-viewport-resubscribe.test.ts @@ -67,20 +67,6 @@ describe('resolveTerminalViewportResubscribe', () => { } }) - it('keeps holding on absent dims across repeated frames without spending budget', () => { - for (let frame = 0; frame < 50; frame += 1) { - expect( - resolveTerminalViewportResubscribe({ - hostCols: null, - hostRows: null, - viewportMeasured: true, - viewport: PHONE, - attempts: 0 - }).kind - ).toBe('hold') - } - }) - it('converges when host dims match the measured viewport', () => { expect( resolveTerminalViewportResubscribe({ @@ -307,7 +293,10 @@ describe('runTerminalViewportFitPass', () => { } } - const settle = () => new Promise((resolve) => setTimeout(resolve, 0)) + const settle = async () => { + await Promise.resolve() + await Promise.resolve() + } it('holds without any teardown when host dims are absent (STA-3337 regression)', async () => { const h = makeHarness({ viewportMeasured: true, viewport: PHONE }) diff --git a/mobile/src/session/pr-actions-engine.test.ts b/mobile/src/session/pr-actions-engine.test.ts index 3656b2732f6..b8f1f47763e 100644 --- a/mobile/src/session/pr-actions-engine.test.ts +++ b/mobile/src/session/pr-actions-engine.test.ts @@ -31,25 +31,26 @@ function makeEngine(mutations: Partial) { describe('PrActionsEngine — scoped busy clear (overlapping actions)', () => { it('a late-resolving action does not clear a newer action busy state', async () => { const slow = deferred() + const newer = deferred() const engine = makeEngine({ - // merge resolves slowly; state resolves immediately. mergePR: () => slow.promise, - updatePRState: async () => ({ ok: true }) + updatePRState: () => newer.promise }) // Start merge (sets busy = merge) but don't let it resolve yet. const mergePromise = engine.merge() expect(engine.isBusy({ kind: 'merge' })).toBe(true) - // While merge is in-flight, run state to completion (sets then clears busy=state). - await engine.updateState('closed') - // State began after merge, so it overwrote busy to 'state', then cleared its own. - expect(engine.isBusy({ kind: 'state' })).toBe(false) + const statePromise = engine.updateState('closed') + expect(engine.isBusy({ kind: 'state' })).toBe(true) // Now let the slow merge finish. Its finally must NOT clear a busy it no longer owns. slow.resolve({ ok: true }) await mergePromise - // busy stays null (state already cleared); the key point is merge didn't clobber. + expect(engine.isBusy({ kind: 'state' })).toBe(true) + + newer.resolve({ ok: true }) + await statePromise expect(engine.busy).toBeNull() }) diff --git a/mobile/src/session/pr-comment-actions.test.ts b/mobile/src/session/pr-comment-actions.test.ts index 6f5d5778084..e2c1cbb5c78 100644 --- a/mobile/src/session/pr-comment-actions.test.ts +++ b/mobile/src/session/pr-comment-actions.test.ts @@ -116,6 +116,8 @@ describe('isMutablePRConversationComment', () => { it('requires a positive integer id', () => { expect(isMutablePRConversationComment(comment({ id: 0 }))).toBe(false) expect(isMutablePRConversationComment(comment({ id: -1 }))).toBe(false) + expect(isMutablePRConversationComment(comment({ id: 1.5 }))).toBe(false) + expect(isMutablePRConversationComment(comment({ id: Number.MAX_SAFE_INTEGER + 1 }))).toBe(false) }) }) diff --git a/mobile/src/session/session-panel-host.test.ts b/mobile/src/session/session-panel-host.test.ts index 1b17fa4f1e1..7756e35e4ca 100644 --- a/mobile/src/session/session-panel-host.test.ts +++ b/mobile/src/session/session-panel-host.test.ts @@ -104,7 +104,7 @@ describe('shouldShowSessionHeaderChecksAction', () => { shouldShowSessionHeaderChecksAction({ isFolderWorkspaceRoute: false, repoContextLoaded: false, - hostedChecksSupported: false + hostedChecksSupported: true }) ).toBe(false) }) diff --git a/mobile/src/session/tab-strip-scroll.test.ts b/mobile/src/session/tab-strip-scroll.test.ts index e7afae05d47..397fe9248d2 100644 --- a/mobile/src/session/tab-strip-scroll.test.ts +++ b/mobile/src/session/tab-strip-scroll.test.ts @@ -64,9 +64,9 @@ describe('resolveTabStripScrollOffset', () => { tabWidth: 128, viewportWidth: 0, contentWidth: 0, - currentOffset: 0 + currentOffset: 150 }) - ).toBe(0) + ).toBe(150) }) }) diff --git a/src/main/persistence/profile-state/profile-state-store-factory.test.ts b/src/main/persistence/profile-state/profile-state-store-factory.test.ts index 234005c0002..90c84af7beb 100644 --- a/src/main/persistence/profile-state/profile-state-store-factory.test.ts +++ b/src/main/persistence/profile-state/profile-state-store-factory.test.ts @@ -2,11 +2,7 @@ import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync import { tmpdir } from 'node:os' import { dirname, join } from 'node:path' import { afterEach, describe, expect, it, vi } from 'vitest' -import { - isProfileStateSqliteAvailable, - openProfileStateDatabase, - profileStateDatabaseFile -} from './profile-state-database' +import { openProfileStateDatabase, profileStateDatabaseFile } from './profile-state-database' import { createProfileStateStore as createProfileStateStoreImpl, type ProfileStateStoreFactoryOptions @@ -138,18 +134,6 @@ describe('profile state Store authority factory', () => { expect(() => initial.value.takeParsedState?.()).toThrow('already been consumed') }) - it('uses a capability probe that remains false on a Node 18-style host', () => { - const original = process.getBuiltinModule - vi.spyOn(process, 'getBuiltinModule').mockImplementation((id) => { - if (id === 'node:sqlite') { - return undefined - } - return original(id) - }) - - expect(isProfileStateSqliteAvailable()).toBe(false) - }) - it('imports legacy state by default and preserves its original bytes', () => { const options = createOptions() const source = JSON.stringify({ settings: { theme: 'dark' }, unknownDomain: { keep: true } }) @@ -195,21 +179,6 @@ describe('profile state Store authority factory', () => { } ) - it('migrates JSON and constructs a SQLite Store', () => { - const options = createOptions() - const source = JSON.stringify({ settings: { theme: 'dark' }, unknownDomain: { keep: true } }) - writeFileSync(options.dataFile, source) - - const result = createProfileStateStore({ ...options }) - - expect(result.backend).toBe('sqlite') - expect(result.classification).toBe('json-only') - expect(result.migrated).toBe(true) - expect(result.store.getSettings().theme).toBe('dark') - expect(existsSync(options.databaseFile)).toBe(true) - expect(readFileSync(options.dataFile, 'utf8')).toBe(source) - }) - it('reuses the candidate authority after the legacy export is removed', () => { const options = createOptions() writeFileSync(options.dataFile, JSON.stringify({ settings: { theme: 'dark' } })) @@ -224,28 +193,6 @@ describe('profile state Store authority factory', () => { expect(result.store.getSettings().theme).toBe('dark') }) - it('fails closed instead of falling back to a stale JSON mirror when SQLite is missing', () => { - const options = createOptions() - writeFileSync(options.dataFile, JSON.stringify({ settings: { theme: 'dark' } })) - const migrated = createProfileStateStore({ ...options }) - const exportPath = profileStateJsonExportPath(options.dataFile, 1) - expect(existsSync(exportPath)).toBe(true) - migrated.store.freezeWrites() - rmSync(options.databaseFile) - - expect(() => createProfileStateStore({ ...options })).toThrow(ProfileStateRecoveryRequiredError) - }) - - it('does not let candidate mode re-import JSON after SQLite was established', () => { - const options = createOptions() - writeFileSync(options.dataFile, JSON.stringify({ settings: { theme: 'dark' } })) - const migrated = createProfileStateStore({ ...options }) - migrated.store.freezeWrites() - rmSync(options.databaseFile) - - expect(() => createProfileStateStore({ ...options })).toThrow(ProfileStateRecoveryRequiredError) - }) - it('reopens JSON after an explicit rollback removes the SQLite export marker', () => { const options = createOptions() writeFileSync(options.dataFile, JSON.stringify({ settings: { theme: 'dark' } })) diff --git a/src/main/persistence/profile-state/profile-state-writer-close.test.ts b/src/main/persistence/profile-state/profile-state-writer-close.test.ts index 45004627b1b..b50d039df0b 100644 --- a/src/main/persistence/profile-state/profile-state-writer-close.test.ts +++ b/src/main/persistence/profile-state/profile-state-writer-close.test.ts @@ -107,19 +107,6 @@ it('keeps an unacknowledged close pending, then holds abort until the terminated expect(vi.getTimerCount()).toBe(0) }) -it('completes a clean close only after the acknowledged worker exits', async () => { - const { client, worker, onFailure } = await createClient() - let settled = false - const closing = client.close().then(() => (settled = true)) - await waitForPoll() - expect(settled).toBe(false) - worker.emit('exit', 0) - await closing - expect(worker.terminate).not.toHaveBeenCalled() - expect(onFailure).not.toHaveBeenCalled() - expect(vi.getTimerCount()).toBe(0) -}) - it('joins an aborted writer without reporting a close request that was never sent', async () => { const { client, worker } = await createClient() const aborting = client.abort() diff --git a/src/main/persistence/profile-state/profile-state-writer-protocol-faults.test.ts b/src/main/persistence/profile-state/profile-state-writer-protocol-faults.test.ts index ceb31638d7f..25a717d0899 100644 --- a/src/main/persistence/profile-state/profile-state-writer-protocol-faults.test.ts +++ b/src/main/persistence/profile-state/profile-state-writer-protocol-faults.test.ts @@ -88,28 +88,6 @@ describe('writer protocol refuses uncertain acknowledgements', () => { }) }) - it('keeps an unanswered request pending past the slow warning without faulting', async () => { - const notify = vi.fn() - const client = clientFor('', undefined, notify) - await client.ready - let settled = false - const write = client - .writeSerializedDomains([{ domain: 'settings', payload: '{}' }]) - .finally(() => (settled = true)) - await new Promise((resolve) => setTimeout(resolve, 200)) - expect(settled).toBe(false) - expect(notify).not.toHaveBeenCalled() - expect(() => client.assertWritable()).toThrow( - expect.objectContaining({ code: 'profile-state-writer-busy' }) - ) - const rejected = expect(write).rejects.toMatchObject({ - code: 'profile-state-writer-aborted', - outcome: 'indeterminate' - }) - await client.abort() - await rejected - }) - it('reports an idle writer exit even without a subsequent save', async () => { const notify = vi.fn() const client = clientFor('', undefined, notify, 'setTimeout(() => process.exit(1), 50)') diff --git a/src/main/persistence/record-patch-versus-full-save.test.ts b/src/main/persistence/record-patch-versus-full-save.test.ts index 370bde55dfd..cdd3e022c09 100644 --- a/src/main/persistence/record-patch-versus-full-save.test.ts +++ b/src/main/persistence/record-patch-versus-full-save.test.ts @@ -515,7 +515,15 @@ describe.each(([APPLY, MOVE, SAVE] as const).map((name) => [name, WRITERS[name]] it.each(WORKSPACE_LAYOUT_PROFILES.map((profile) => [profile.name, profile] as const))( '%s: the same session, key order aside, as its load normalizes nothing', (_name, profile) => { - for (const call of write(profile).flat()) { + const calls = write(profile).flat() + const skipsPatch = + profile.name === 'empty profile' + ? writer !== SAVE + : writer === MOVE + ? profile.hostId !== LOCAL_EXECUTION_HOST_ID + : writer === SAVE && profile.hostId.startsWith('runtime:') + expect(calls.length === 0).toBe(skipsPatch) + for (const call of calls) { expect(recordsDiffering(call.stored, call.before)).toEqual([]) expect(stableJson(call.patched)).toBe(stableJson(call.full)) } diff --git a/src/main/persistence/restoring-sessions/folder-workspace-sort-stamps.test.ts b/src/main/persistence/restoring-sessions/folder-workspace-sort-stamps.test.ts index 4eac55ae39a..336bd47274a 100644 --- a/src/main/persistence/restoring-sessions/folder-workspace-sort-stamps.test.ts +++ b/src/main/persistence/restoring-sessions/folder-workspace-sort-stamps.test.ts @@ -48,6 +48,7 @@ describe('folder workspace sort stamps', () => { operations.updateFolderWorkspace(id, { smartTier: undefined, smartAt: undefined }) expect(operations.getFolderWorkspace(id)?.smartTier).toBeUndefined() + expect(operations.getFolderWorkspace(id)?.smartAt).toBeUndefined() // An update that does not name the tier leaves it alone. operations.updateFolderWorkspace(id, { smartTier: 'working', smartAt: 10 }) operations.updateFolderWorkspace(id, { comment: 'x' }) diff --git a/src/renderer/src/components/terminal-pane/mouse-hide-while-typing.test.ts b/src/renderer/src/components/terminal-pane/mouse-hide-while-typing.test.ts index ba20d948b1b..e41b10b9c43 100644 --- a/src/renderer/src/components/terminal-pane/mouse-hide-while-typing.test.ts +++ b/src/renderer/src/components/terminal-pane/mouse-hide-while-typing.test.ts @@ -71,6 +71,8 @@ describe('installMouseHideWhileTyping', () => { const disposable = installMouseHideWhileTyping(terminal, container as unknown as HTMLElement) disposable.dispose() + expect(terminal.onData.mock.results[0]?.value.dispose).toHaveBeenCalledTimes(1) + expect(container.style.cursor).toBe('') // After dispose, mousemove should not crash and cursor stays restored. container.dispatchEvent(new Event('mousemove')) diff --git a/src/renderer/src/components/terminal-pane/osc52-clipboard-default-on-notice.test.ts b/src/renderer/src/components/terminal-pane/osc52-clipboard-default-on-notice.test.ts index 67bdc0d0099..480a2771786 100644 --- a/src/renderer/src/components/terminal-pane/osc52-clipboard-default-on-notice.test.ts +++ b/src/renderer/src/components/terminal-pane/osc52-clipboard-default-on-notice.test.ts @@ -111,7 +111,7 @@ describe('useOsc52ClipboardDefaultOnNotice', () => { expect(storeState.clearOsc52ClipboardDefaultOnNotice).toHaveBeenCalledTimes(1) }) - it('dedupes the StrictMode double-invoke with a stable toast id', async () => { + it('assigns a stable toast id for notice deduplication', async () => { // Why pin the id: the effect re-runs against the same closure, so the early return // cannot catch the second pass — sonner collapses it only because the id matches. await mountProbe(true) diff --git a/src/renderer/src/components/terminal-pane/pane-foreground-agent-tracker.test.ts b/src/renderer/src/components/terminal-pane/pane-foreground-agent-tracker.test.ts index 3021132c315..4d898958434 100644 --- a/src/renderer/src/components/terminal-pane/pane-foreground-agent-tracker.test.ts +++ b/src/renderer/src/components/terminal-pane/pane-foreground-agent-tracker.test.ts @@ -760,9 +760,12 @@ describe('createPaneForegroundAgentTracker', () => { onCommandFinished: () => tracker.onCommandFinished() }) - lifecycle.handlePtyData("printf '\\033]133;A\\007\\033]777;agent=codex\\007\\033]133;D;0\\007'") + lifecycle.handlePtyData('\x1b]133;C\x07\x1b]777;agent=codex\x07') await flushSettleRead(COMMAND_SETTLE_MS) + expect(remoteRead).toHaveBeenCalledExactlyOnceWith('ssh:conn-1@@pty-9', { + expectedIncarnationId: 'inc-remote' + }) expect(publish).not.toHaveBeenCalledWith( expect.objectContaining({ agent: 'codex', shellForeground: false }) ) diff --git a/src/renderer/src/components/terminal-pane/parked-terminal-byte-watcher.test.ts b/src/renderer/src/components/terminal-pane/parked-terminal-byte-watcher.test.ts index 890d29ebf87..aae0257c98c 100644 --- a/src/renderer/src/components/terminal-pane/parked-terminal-byte-watcher.test.ts +++ b/src/renderer/src/components/terminal-pane/parked-terminal-byte-watcher.test.ts @@ -268,7 +268,7 @@ describe('startParkedTerminalByteWatcher', () => { dispose() }) - it('suppresses the completion OS notification when only terminal attention is on', async () => { + it('forwards completion to notification policy when only terminal attention is on', async () => { mockStoreState.settings = { ...mockStoreState.settings, experimentalTerminalAttention: true, diff --git a/src/renderer/src/components/terminal-pane/parse-osc7.test.ts b/src/renderer/src/components/terminal-pane/parse-osc7.test.ts index db6139dd220..c7c52ca201a 100644 --- a/src/renderer/src/components/terminal-pane/parse-osc7.test.ts +++ b/src/renderer/src/components/terminal-pane/parse-osc7.test.ts @@ -10,7 +10,7 @@ describe('parseOsc7', () => { expect(parseOsc7('file:///home/jin')).toBe('/home/jin') }) - it('percent-decodes spaces and unicode', () => { + it('percent-decodes spaces', () => { expect(parseOsc7('file:///home/jin/my%20code')).toBe('/home/jin/my code') }) diff --git a/src/shared/agent-startup-plan-inputs-extra-agent-args.spec.ts b/src/shared/agent-startup-plan-inputs-extra-agent-args.spec.ts index d42b608260a..05ee98669b5 100644 --- a/src/shared/agent-startup-plan-inputs-extra-agent-args.spec.ts +++ b/src/shared/agent-startup-plan-inputs-extra-agent-args.spec.ts @@ -24,12 +24,13 @@ describe('resolveAgentStartupPlanInputs with extra agent args', () => { extraAgentArgs: '--model opus --add-dir "my docs"' }) const plan = buildAgentStartupPlan({ ...inputs, prompt: 'go' }) + expect(plan?.launchCommand).toContain("'--add-dir' 'my docs'") expect(plan?.launchCommand).toContain('opus') expect(plan?.launchCommand).not.toContain('sonnet') expect(plan?.launchCommand).toContain('--dangerously-skip-permissions') }) - it('builds the same command the desktop path builds', () => { + it('matches explicitly composed launch defaults and extras', () => { const headless = resolveAgentStartupPlanInputs({ agent: 'claude', settings: SETTINGS,