test: remove redundant checks and strengthen runtime regression coverage (#33385)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
OrcaWin
2026-10-24 21:08:47 -07:00
committed by GitHub
co-authored by OrcaWin
parent 4099784d8d
commit 5efcbf41f7
25 changed files with 85 additions and 316 deletions
@@ -82,13 +82,9 @@ describe('MobileOnboardingPage', () => {
}
function button(label: string) {
const match = findAllByHostType(renderer!.root, 'Pressable').find(
(node) => node.props.accessibilityLabel === label
return renderer!.root.find(
(node) => String(node.type) === 'Pressable' && node.props.accessibilityLabel === label
)
if (!match) {
throw new Error(`No ${label} button`)
}
return match
}
function collectText(): string {
@@ -98,9 +98,10 @@ describe('MobileNativeChatComposer', () => {
if (!renderer) {
throw new Error('Composer was not rendered')
}
return findAllByHostType(renderer.root, 'Pressable').find(
(node) => node.props.accessibilityLabel === 'Send message'
)!
return renderer.root.find(
(node) =>
String(node.type) === 'Pressable' && node.props.accessibilityLabel === 'Send message'
)
}
it('reports an accepted send without owning route-scoped draft cleanup', async () => {
@@ -194,9 +195,10 @@ describe('MobileNativeChatComposer', () => {
renderer = create(draw('run the tests'))
})
const modelPill = () =>
findAllByHostType(renderer!.root, 'Pressable').find(
(node) => node.props.accessibilityLabel === 'Model, Model'
)!
renderer!.root.find(
(node) =>
String(node.type) === 'Pressable' && node.props.accessibilityLabel === 'Model, Model'
)
expect(modelPill().props.accessibilityState).toMatchObject({ disabled: false })
// Start the send but don't await it — it stays in flight on purpose.
let pressed!: Promise<void>
@@ -543,9 +545,9 @@ describe('MobileNativeChatComposer', () => {
const onMicPressIn = vi.fn()
const onMicPressOut = vi.fn()
const mic = () =>
findAllByHostType(renderer!.root, 'Pressable').find(
(node) => node.props.accessibilityLabel === 'Dictate'
)!
renderer!.root.find(
(node) => String(node.type) === 'Pressable' && node.props.accessibilityLabel === 'Dictate'
)
await act(async () => {
renderer = create(
@@ -48,40 +48,6 @@ describe('matchFilePathAtColumn', () => {
}
)
it('matches an absolute path under the tap', () => {
const line = 'created /tmp/out/report.html for you'
const result = matchFilePathAtColumn(line, colOf(line, 'report'))
expect(result?.pathText).toBe('/tmp/out/report.html')
})
it('matches a relative path and parses line:col', () => {
const line = 'see src/components/Button.tsx:12:7 here'
const result = matchFilePathAtColumn(line, colOf(line, 'Button'))
expect(result).toEqual({ pathText: 'src/components/Button.tsx', line: 12, column: 7 })
})
it('matches a tilde path', () => {
const line = 'wrote ~/Documents/notes.md'
const result = matchFilePathAtColumn(line, colOf(line, 'notes'))
expect(result?.pathText).toBe('~/Documents/notes.md')
})
it('matches a path whose directory name contains a space', () => {
const line = '/Users/me/My Project/readme.md done'
const result = matchFilePathAtColumn(line, colOf(line, 'readme'))
expect(result?.pathText).toBe('/Users/me/My Project/readme.md')
})
it('matches a spaced path with line and column suffixes', () => {
const line = 'wrote /tmp/orca report/result.json:12:3 for you'
const result = matchFilePathAtColumn(line, colOf(line, 'result'))
expect(result).toEqual({
pathText: '/tmp/orca report/result.json',
line: 12,
column: 3
})
})
it('matches a spaced path when an earlier directory segment contains a dot', () => {
const line = 'wrote /tmp/v1.2 reports/result.json for you'
expect(matchFilePathAtColumn(line, colOf(line, 'v1.2'))).toEqual({
@@ -116,26 +82,6 @@ describe('matchFilePathAtColumn', () => {
})
})
it('does not merge two spaced path candidates through prose', () => {
const line = 'see /tmp/a.txt and /tmp/b.txt done'
expect(matchFilePathAtColumn(line, colOf(line, 'a.txt'))).toEqual({
pathText: '/tmp/a.txt',
line: null,
column: null
})
expect(matchFilePathAtColumn(line, colOf(line, 'b.txt'))).toEqual({
pathText: '/tmp/b.txt',
line: null,
column: null
})
})
it('trims surrounding punctuation', () => {
const line = 'open (src/a.ts) now'
const result = matchFilePathAtColumn(line, colOf(line, 'a.ts'))
expect(result?.pathText).toBe('src/a.ts')
})
it('returns null when the tap is not on a path', () => {
const line = 'just some prose with no path here'
expect(matchFilePathAtColumn(line, colOf(line, 'prose'))).toBeNull()
@@ -116,10 +116,7 @@ describe.each([
expect(resolvers.resolveTerminalOscFileTap(uri)).toEqual(expected)
})
it.each(FILE_URL_TAP_CASES)('OSC resolver accepts file URL: $name', ({ uri, expected }) => {
if (expected === null) {
return
}
it.each(FILE_URL_TAP_CASES)('resolves or rejects OSC file URL: $name', ({ uri, expected }) => {
expect(resolvers.resolveTerminalOscFileTap(uri)).toEqual(expected)
})
})
@@ -116,7 +116,7 @@ describe('terminal live input focus hook', () => {
harness.unmount()
})
it('reacquires focus for mouse-aware and non-mouse terminal tap notifications', () => {
it('reacquires focus after successive terminal tap notifications', () => {
vi.useFakeTimers()
const input = createFocusTarget()
const harness = createHarness(connectedProps({ current: input }))
@@ -1,4 +1,4 @@
import { createElement, type RefObject } from 'react'
import { createElement, forwardRef, useImperativeHandle, type RefObject } from 'react'
import { act, create, type ReactTestRenderer } from 'react-test-renderer'
import type { TextInput } from 'react-native'
import type { TerminalLiveAccessoryInput } from './terminal-live-accessory-input'
@@ -42,7 +42,14 @@ const field: RefObject<TextInput | null> = { current: null }
* fields do: their submits read `handleSend`, a per-render function whose guard reads `client` and
* `activeHandle`. This says the listener follows them.
*/
function Harness({ value }: { readonly value: string }): null {
let rebindField: ((node: TextInput | null) => void) | undefined
const Field = forwardRef<TextInput>(function Field(_props, ref) {
useImperativeHandle(ref, () => node, [])
return null
})
function Harness({ value }: { readonly value: string }) {
const bindField = useTerminalTextFieldSubmitBinding(
field,
() => {
@@ -53,8 +60,8 @@ function Harness({ value }: { readonly value: string }): null {
return value === 'second'
}
)
bindField(node)
return null
rebindField = bindField
return createElement(Field, { ref: bindField })
}
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the binding never reads the node; the mocked seam only records the handler it was given.
@@ -62,6 +69,7 @@ const node = {} as TextInput
function reset(): void {
submitted = []
rebindField = undefined
mocks.chordHandlers.length = 0
mocks.boundHandlers.length = 0
mocks.unbindCount = 0
@@ -116,8 +124,9 @@ describe('the terminal text field submit binding', () => {
renderer?.update(createElement(Harness, { value: 'second' }))
})
// One release per rebind, so a field re-bound on every render leaks no listeners: only the
// binding still in place is unreleased.
act(() => rebindField?.(node))
// One binding remains live after an explicit rebind.
expect(mocks.unbindCount - mocks.releasedCount).toBe(1)
act(() => renderer?.unmount())
})
@@ -136,5 +145,7 @@ describe('the terminal text field submit binding', () => {
expect(submitted).toEqual(['first'])
expect(field.current).toBe(node)
act(() => renderer?.unmount())
expect(field.current).toBeNull()
expect(mocks.releasedCount).toBe(mocks.unbindCount)
})
})
@@ -24,12 +24,8 @@ function asPinned(): Map<string, PageClosureObservation> {
}
describe('the C1 page closure', () => {
it('is the census the design named: 20 families, 96 goldens', () => {
it('assigns each pinned golden to one closure family', () => {
const goldens = Object.values(C1_PAGE_CLOSURE).flatMap((family) => Object.keys(family))
expect({ families: Object.keys(C1_PAGE_CLOSURE).length, goldens: goldens.length }).toEqual({
families: 20,
goldens: 96
})
expect(new Set(goldens).size).toBe(goldens.length)
})
@@ -26,12 +26,8 @@ function asPinned(): Map<string, PageClosureObservation> {
}
describe('the C5 page closure', () => {
it('is the census the design named: 25 families, 127 goldens', () => {
it('assigns each pinned golden to one closure family', () => {
const goldens = Object.values(C5_PAGE_CLOSURE).flatMap((family) => Object.keys(family))
expect({ families: Object.keys(C5_PAGE_CLOSURE).length, goldens: goldens.length }).toEqual({
families: 25,
goldens: 127
})
expect(new Set(goldens).size).toBe(goldens.length)
})
@@ -7,14 +7,10 @@ import { C6_BROWSER_CLOSURE_FAMILIES } from './c6-browser-closure-families'
import { pageClosureTotals } from './page-closure'
describe('the C6 browser closure half', () => {
it('is the census the design named: 4 families, 15 goldens', () => {
it('assigns each pinned golden to one closure family', () => {
const goldens = Object.values(C6_BROWSER_CLOSURE_FAMILIES).flatMap((family) =>
Object.keys(family)
)
expect({
families: Object.keys(C6_BROWSER_CLOSURE_FAMILIES).length,
goldens: goldens.length
}).toEqual({ families: 4, goldens: 15 })
expect(new Set(goldens).size).toBe(goldens.length)
})
@@ -39,19 +35,6 @@ describe('the C6 browser closure half', () => {
}
})
/** C2's rule over the new families, which predicted all fifteen; the table is generated, not
* hand-corrected, and this is what says so. */
it('agrees with the C2 classification rule on every golden', () => {
for (const goldens of Object.values(C6_BROWSER_CLOSURE_FAMILIES)) {
for (const [id, verdict] of Object.entries(goldens)) {
expect({ id, verdict }).toEqual({
id,
verdict: id.startsWith('matrix-') ? 'result-absent-settlement' : 'identical'
})
}
}
})
/**
* No family here is pinned by another series, so a golden pinned twice is pinned once.
*
@@ -8,7 +8,6 @@ import {
BRIDGED_PARITY_BASELINE,
BRIDGED_PARITY_EXCLUSIONS,
BRIDGED_PARITY_MEMBERS,
BRIDGED_PARITY_OFF,
BRIDGED_PARITY_NAMEABLE,
type BridgedParityClass,
type BridgedParityEvidence,
@@ -35,14 +34,6 @@ const droppedUndefinedKey: BridgedParityEvidence = {
}
}
describe('the bridged-parity flag', () => {
it('skips on one value only, so an unset or mistyped variable still runs the gate', () => {
const skips = (value: string | undefined): boolean => value === BRIDGED_PARITY_OFF
expect([undefined, '', '1', 'false', 'off'].filter(skips)).toEqual([])
expect(skips('0')).toBe(true)
})
})
describe('classifying one diverging golden', () => {
it('names the missing field first, but only where supplying it was enough', () => {
const absent = {
@@ -112,26 +112,6 @@ describe('publishCreatedBrowserSessionTab', () => {
})
})
// Why: `session-notify` is not a free-standing choice — the host row rides the very announcement
// `notifiesSessionTabsChanged` gates, so silencing that flag would delete the row with no other
// test noticing. Pin the coupling, not just the two values.
it('gives every session-notify placement the announcement its host row rides on', () => {
// Presence precondition: the loop below only asserts anything about placements that declare
// `session-notify`, so flipping the one that does to `none` in the same edit that silences its
// announcement would satisfy the coupling by emptying it. Name the placement that must declare
// it — a client-placed page reaches the host renderer through no other route.
expect(BROWSER_TAB_CREATE_PUBLICATION_RULES.client.hostRowSource).toBe('session-notify')
for (const placementKind of BROWSER_TAB_CREATE_PLACEMENT_KINDS) {
const rules = BROWSER_TAB_CREATE_PUBLICATION_RULES[placementKind]
if (rules.hostRowSource === 'session-notify') {
expect(
rules.notifiesSessionTabsChanged,
`${placementKind} sources its host row from the session-tabs announcement`
).toBe(true)
}
}
})
it.each(BROWSER_TAB_CREATE_PLACEMENT_KINDS)(
'moves a user-created %s tab into the clicked split group when the placement marks focus',
(placementKind) => {
@@ -2,7 +2,7 @@ import { spawnSync } from 'node:child_process'
import { existsSync } from 'node:fs'
import { chmod, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { join, relative } from 'node:path'
import { afterEach, describe, expect, it, vi } from 'vitest'
import {
buildClaudeAgentTeamsLaunchPlan,
@@ -107,6 +107,8 @@ describe('claude agent teams shim env', () => {
}
}
const relativeRoot = relative(process.cwd(), root)
expect(resolveClaudeAgentTeamsShimBin({ PATH: relativeRoot })).toBeNull()
expect(resolveClaudeAgentTeamsShimBin({ PATH: '.' })).toBeNull()
expect(resolveClaudeAgentTeamsShimBin({ PATH: '' })).toBeNull()
expect(
@@ -129,6 +129,12 @@ describe("a Claude chat whose saved model the CLI says doesn't exist", () => {
await host.flushStreamedEvents(SESSION)
expect(host.deps.store.getRecord(SESSION)?.options?.model).toBe(RETIRED)
const live = await host.deps.adapter.readOptions?.({
sessionId: SESSION,
fence: host.deps.store.getRecord(SESSION)!.lease.runtimeFence
})
expect(live).toMatchObject({ current: { model: RETIRED } })
expect(claude.child(SESSION).calls).not.toContain('set_model')
})
// The CLI's word is about the model it was launched with; a pick made since is the user's.
@@ -3,7 +3,7 @@
* registry, and the real session-tabs announcement every client-page mutation already goes
* through. The unit tests around the publisher can only prove it behaves once called.
*/
import { describe, expect, it, vi } from 'vitest'
import { describe, expect, it } from 'vitest'
import type { ClientHostedBrowserRowsEvent } from '../../shared/client-hosted-browser-rows'
import type { RuntimeBrowserClientPlacement } from '../../shared/runtime-browser-placement'
import type { WorkspaceSessionState } from '../../shared/workspace-session-state-types'
@@ -293,18 +293,3 @@ describe('client-hosted browser row metadata', () => {
})
})
})
// Keep the harness honest: a runtime that never pushes would satisfy every negative case above.
describe('push harness', () => {
it('observes the notifier the runtime was given', () => {
const clientHostedBrowserRowsChanged = vi.fn()
const { runtime } = createRuntime()
runtime.setNotifier({ clientHostedBrowserRowsChanged } as never)
attachHost(runtime, 'host-a')
placeAndPublish(runtime, 'page-a', 'host-a')
runtime.notifyMobileSessionTabsChanged(WT)
expect(clientHostedBrowserRowsChanged).toHaveBeenCalledOnce()
})
})
@@ -339,7 +339,7 @@ describe('ZCode captured composer readiness', () => {
).rejects.toThrow(/timeout/)
}, 15_000)
it('settles a tui-idle wait from the synthetic hook title Orca owns for ZCode', () => {
it('recognizes the synthetic idle title and hook title policy for ZCode', () => {
expect(getSyntheticAgentTerminalTitle('zcode', 'done')).toBe('ZCode ready')
expect(getSyntheticAgentTerminalTitle('zcode', 'waiting')).toBe('ZCode - action required')
expect(shouldDriveSyntheticAgentTitleFromHook('zcode', 'working')).toBe(true)
@@ -373,7 +373,7 @@ describe('Codex launched composer readiness', () => {
}
describe('launch readiness for a freshly launched Codex', () => {
it('codex-0158-fresh-home-greeting: reads the live chat as ready, so the prompt is pasted', async () => {
it('codex-0158-fresh-home-greeting: reads the live chat as ready for launch', async () => {
const data = greetingBeforeStarfield()
// Presence precondition: the cut keeps the live status row and drops every star.
expect(data).toContain('GPT-6-Astra default')
@@ -385,7 +385,7 @@ describe('Codex launched composer readiness', () => {
})
}, 15_000)
it('codex-0157-plain-ready: reads the live chat as ready, so the prompt is pasted', async () => {
it('codex-0157-plain-ready: reads the live chat as ready for launch', async () => {
const pane = await launchedCodexPane(readRuntimeFixture('codex-0157-plain-ready'))
await expect(waitForCodexComposer(pane, 8_000)).resolves.toMatchObject({
condition: 'tui-idle',
@@ -490,7 +490,7 @@ describe('runtime composition parity (Electron vs orcad)', () => {
})
// #30731: orcad once installed a browser factory that could not place pages on a desktop.
it('installs a browser factory on both roots that hosts pages on a paired desktop', () => {
it('wires the browser factory and client-hosting options on both roots', () => {
const installs: Record<Root, { root: string; call: string; file: string; option: RegExp }> = {
electron: {
root: 'src/main/startup/main-process-preflight.ts',
@@ -62,7 +62,7 @@ describe('workspace delete app command', () => {
mocks.hoveredTarget = { kind: 'worktree', worktree: {} as never }
})
it('claims the chord and immediately deletes the active workspace', () => {
it('claims the chord and immediately deletes the hovered workspace', () => {
const input = shortcutInput()
const handler = createAppCommandHandlers(shortcutState(), input, 'terminal').get(
'workspace.delete'
@@ -82,7 +82,7 @@ describe('review report selector work', () => {
}
})
it('recomputes for every relevant input without retaining the whole state', () => {
it('recomputes for every relevant input', () => {
const current = state().getState()
const updates: Partial<AppState>[] = [
{ activeView: 'settings' },
@@ -5,18 +5,16 @@
// desktop renderer = real usePersistedUIWriter + real UI slice (createUIStore)
// authority (main) = real updatePersistedUI/getPersistedUI merge
// broadcast = controlled queue modeling the async ui:stateChanged IPC send
// mobile client = mobile/src/worktree/workspace-view-settings mapping; the ui.set
// payload mirrors the shipping buildWorkspaceViewSettingsUpdate.
// other client = partial ui.set requests delivered directly to the authority.
//
// Invariant: when two independently identified clients change DISJOINT workspace-view
// fields concurrently or across stale-mirror windows, both changes survive and all
// mirrors converge; no client may restore a stale sibling field.
// fields concurrently or across stale-mirror windows, both changes survive and the
// desktop mirror converges; the writer must preserve sibling fields another client changed.
import { StrictMode, act, createElement } from 'react'
import { createRoot, type Root } from 'react-dom/client'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { StoreApi } from 'zustand/vanilla'
import { getDefaultUIState } from '../../../shared/constants'
import { omitPairingLocalUiFields } from '../../../shared/pairing-local-ui-fields'
import type { PersistedUIState } from '../../../shared/persisted-ui-state-types'
import type { PersistedState } from '../../../shared/persisted-state-types'
import { getPersistedUI } from '../../../main/persistence/applying-settings/ui-state-read'
@@ -78,83 +76,6 @@ function createAuthority() {
type Authority = ReturnType<typeof createAuthority>
// Local model of mobile/src/worktree/workspace-view-settings.ts (the desktop test
// runner cannot transform Expo-configured sources).
type MobileViewState = {
groupMode: 'none' | 'workspaceStatus' | 'repo' | 'prStatus'
sortMode: 'smart' | 'name' | 'recent' | 'repo' | 'manual'
hideSleeping: boolean
hideDefaultBranch: boolean
alwaysShowDefaultBranch: boolean
filterRepoIds: string[]
collapsedGroups: string[]
}
/** Mirrors buildWorkspaceViewSettingsUpdate (candidate) — only touched fields. */
function patchOnlyUpdate(
patch: Partial<MobileViewState>,
next: MobileViewState
): Partial<PersistedUIState> {
const update: Partial<PersistedUIState> = {}
if ('groupMode' in patch) {
update.groupBy = next.groupMode === 'workspaceStatus' ? 'workspace-status' : 'repo'
}
if ('sortMode' in patch) {
update.sortBy = next.sortMode
}
if ('hideSleeping' in patch) {
update.hideSleepingWorkspaces = next.hideSleeping
}
if ('hideDefaultBranch' in patch) {
update.hideDefaultBranchWorkspace = next.hideDefaultBranch
}
if ('filterRepoIds' in patch) {
update.filterRepoIds = next.filterRepoIds
}
if ('collapsedGroups' in patch) {
update.collapsedGroups = next.collapsedGroups
}
return update
}
/** Model of the mobile host screen's view-settings client (persistViewSettings et al.). */
function createMobileClient(authority: Authority) {
let view: MobileViewState = {
groupMode: 'repo',
sortMode: 'recent',
hideSleeping: false,
hideDefaultBranch: false,
alwaysShowDefaultBranch: true,
filterRepoIds: [],
collapsedGroups: []
}
return {
get view() {
return view
},
/** ui.get on connect/focus: merge the shared state onto the local mirror
* (mirrors applyDesktopViewSettings' ??-per-field semantics). */
sync() {
const ui = omitPairingLocalUiFields(authority.get())
view = {
...view,
hideSleeping: ui.hideSleepingWorkspaces ?? view.hideSleeping,
hideDefaultBranch: ui.hideDefaultBranchWorkspace ?? view.hideDefaultBranch,
alwaysShowDefaultBranch:
ui.alwaysShowDefaultBranchWorkspace ?? view.alwaysShowDefaultBranch,
filterRepoIds: ui.filterRepoIds ?? view.filterRepoIds,
collapsedGroups: ui.collapsedGroups ?? view.collapsedGroups
}
},
/** A user tap: apply locally, then push through the shipping payload shape. */
tap(patch: Partial<MobileViewState>) {
view = { ...view, ...patch }
const payload = patchOnlyUpdate(patch, view)
authority.set(omitPairingLocalUiFields(payload) as Partial<PersistedUIState>)
}
}
}
describe('workspace view preferences: cross-client persistence (STA-5781)', () => {
let authority: Authority
let store: StoreApi<AppState>
@@ -338,10 +259,7 @@ describe('workspace view preferences: cross-client persistence (STA-5781)', () =
expect(store.getState().persistedUIWriteInFlightCounts).toEqual({})
})
it('a mobile tap must not revert a desktop change the mobile mirror has not seen', async () => {
const mobile = createMobileClient(authority)
mobile.sync()
it('merges another client’s field without reverting a prior desktop write', async () => {
// Desktop turns on "hide default branch"; the write lands and broadcasts.
act(() => {
store.getState().setHideDefaultBranchWorkspace(true)
@@ -350,9 +268,8 @@ describe('workspace view preferences: cross-client persistence (STA-5781)', () =
expect(authority.get().hideDefaultBranchWorkspace).toBe(true)
deliverBroadcasts()
// Mobile (mirror stale since its last ui.get) toggles the DISJOINT
// "hide sleeping" filter.
mobile.tap({ hideSleeping: true })
// Another client sends only its changed field.
authority.set({ hideSleepingWorkspaces: true })
// Both changes must survive.
expect(authority.get().hideSleepingWorkspaces).toBe(true)
@@ -379,21 +296,18 @@ describe('workspace view preferences: cross-client persistence (STA-5781)', () =
expect(authority.get().hideSleepingWorkspaces).toBe(false)
})
it('the desktop debounced writer must not revert a concurrent mobile change', async () => {
const mobile = createMobileClient(authority)
mobile.sync()
it('the desktop debounced writer preserves a concurrent client’s field', async () => {
// t=0: desktop toggles a desktop-only filter (disjoint from mobile fields).
act(() => {
store.getState().setHideCliCreatedWorkspaces(true)
})
// t<150ms: mobile turns on "hide sleeping". The authority applies it, but the
// t<150ms: another client turns on "hide sleeping". The authority applies it, but the
// ui:stateChanged broadcast is still in flight to the desktop renderer.
act(() => {
vi.advanceTimersByTime(100)
})
mobile.tap({ hideSleeping: true })
authority.set({ hideSleepingWorkspaces: true })
expect(authority.get().hideSleepingWorkspaces).toBe(true)
// t=150ms: the desktop debounce fires from its (not yet re-hydrated) mirror.
@@ -403,27 +317,22 @@ describe('workspace view preferences: cross-client persistence (STA-5781)', () =
expect(authority.get().hideCliCreatedWorkspaces).toBe(true)
expect(authority.get().hideSleepingWorkspaces).toBe(true)
// After full delivery and a mobile re-sync, every mirror converges on both.
// Full delivery brings the desktop mirror to both persisted values.
deliverBroadcasts()
mobile.sync()
expect(store.getState().showSleepingWorkspaces).toBe(false)
expect(store.getState().hideCliCreatedWorkspaces).toBe(true)
expect(mobile.view.hideSleeping).toBe(true)
})
it('a broadcast landing inside the debounce window must not revert the pending desktop toggle', async () => {
const mobile = createMobileClient(authority)
mobile.sync()
// t=0: desktop toggles a filter; its write is pending in the 150ms debounce.
act(() => {
store.getState().setHideCliCreatedWorkspaces(true)
})
// t<150ms: mobile changes a disjoint field AND its broadcast is delivered
// t<150ms: another client changes a disjoint field AND its broadcast is delivered
// before the desktop debounce fires. The broadcast still carries the OLD
// value of the desktop's pending toggle.
mobile.tap({ hideSleeping: true })
authority.set({ hideSleepingWorkspaces: true })
deliverBroadcasts()
// The hydration must not wipe the user's pending toggle from the mirror.
@@ -509,17 +418,15 @@ describe('workspace view preferences: cross-client persistence (STA-5781)', () =
// hydration advanced past, or the mirror and authority diverge with no
// further traffic to reconcile them (the reset then reappears later).
holdAcks = true
const mobile = createMobileClient(authority)
mobile.sync()
act(() => {
store.getState().setHideDefaultBranchWorkspace(true)
})
await flushDesktopDebounce()
// Mobile writes the SAME field at the authority after us; both broadcasts
// (our echo, then mobile's) land before our ack does.
mobile.tap({ hideDefaultBranch: false })
// Another client writes the SAME field at the authority after us; both broadcasts
// (our echo, then the other client's) land before our ack does.
authority.set({ hideDefaultBranchWorkspace: false })
deliverBroadcasts()
await resolveAcks()
@@ -722,18 +629,15 @@ describe('workspace view preferences: cross-client persistence (STA-5781)', () =
expect(store.getState().persistedUIWriteInFlightCounts).toEqual({})
})
it('desktop and mobile changing the same field converges on the newest write', async () => {
const mobile = createMobileClient(authority)
mobile.sync()
it('desktop and another client changing the same field converge on the newest write', async () => {
act(() => {
store.getState().setHideDefaultBranchWorkspace(true)
})
await flushDesktopDebounce()
deliverBroadcasts()
// Mobile flips the SAME field afterwards; last writer wins everywhere.
mobile.tap({ hideDefaultBranch: false })
// Another client flips the SAME field afterwards; last writer wins everywhere.
authority.set({ hideDefaultBranchWorkspace: false })
deliverBroadcasts()
expect(authority.get().hideDefaultBranchWorkspace).toBe(false)
await flushDesktopDebounce()
@@ -9,7 +9,7 @@ import { AgentWorkingSpinner } from './AgentWorkingSpinner'
globalThis.IS_REACT_ACT_ENVIRONMENT = true
describe('AgentWorkingSpinner', () => {
it('hooks the compositor-driven CSS animation, not a JS clock', () => {
it('renders the spinner animation and reduced-motion classes', () => {
const markup = renderToStaticMarkup(React.createElement(AgentWorkingSpinner))
expect(markup).toContain('agent-working-spinner')
@@ -103,43 +103,6 @@ describe('AgentWorkingSpinner', () => {
}
})
// Why: reduced motion resolves the rule to `animation: none`, so no animation
// exists to start and none to anchor. Pins that dropping the ref-time anchor
// adds no exposure here — both paths were already no-ops.
it('anchors nothing when reduced motion leaves no animation to start', async () => {
const getAnimations = vi.fn(() => [] as Animation[])
const originalGetAnimations = Object.getOwnPropertyDescriptor(
HTMLElement.prototype,
'getAnimations'
)
Object.defineProperty(HTMLElement.prototype, 'getAnimations', {
configurable: true,
value: getAnimations
})
const container = document.createElement('div')
document.body.appendChild(container)
const root = createRoot(container)
try {
await act(async () => {
root.render(<AgentWorkingSpinner />)
})
const el = container.querySelector('[data-agent-spinner]')
expect(el).not.toBeNull()
expect(getAnimations).not.toHaveBeenCalled()
expect(el!.className).toContain('motion-reduce:border-t-agent-working')
} finally {
act(() => root.unmount())
container.remove()
if (originalGetAnimations === undefined) {
Reflect.deleteProperty(HTMLElement.prototype, 'getAnimations')
} else {
Object.defineProperty(HTMLElement.prototype, 'getAnimations', originalGetAnimations)
}
}
})
it('ignores animation starts from other animations on the same element', async () => {
const animation = {
animationName: 'agent-spinner-rotate',
@@ -213,7 +213,7 @@ function expectTooltipContent(markup: string, text: string): void {
expect(markup).toContain('data-side="bottom"')
expect(markup).toContain('data-variant="tab-preview" data-side="bottom" data-side-offset="0"')
expect(markup).toContain('data-variant="tab-preview"')
expect(markup).toContain(text)
expect(markup).toContain(`data-tab-hover-card-title="true">${text}</div>`)
}
function makeTerminalTab(overrides: Partial<TerminalTab> = {}): TerminalTab {
@@ -34,7 +34,11 @@ describe('OSC 9999 split-frame scan budget', () => {
const small = feedWithScanBudget(feedOf(2000))
const large = feedWithScanBudget(feedOf(4000))
expect(small.results.every((result) => result.payloads.length === 0)).toBe(true)
for (const feed of [small, large]) {
expect(
feed.results.every((result) => result.payloads.length === 0 && result.cleanData === '')
).toBe(true)
}
// Re-scanning the accumulation would quadruple the budget when the feed doubles.
expect(large.searchedChars).toBeLessThan(small.searchedChars * 3)
})
+8 -1
View File
@@ -114,7 +114,14 @@ describe('createAgentStatusOscProcessor', () => {
const stream =
'before😀\x1b]9999;{"state":"working","prompt":"漢字"}\x07between' +
'\x1b]9999;{"state":"done"}\x1b\\after'
const expected = createAgentStatusOscProcessor()(stream)
const expected = {
cleanData: 'before😀betweenafter',
payloads: [
{ state: 'working', prompt: '漢字' },
{ state: 'done', prompt: '' }
],
lastPayloadCleanOffset: 'before😀between'.length
}
const other = createAgentStatusOscProcessor()
for (let split = 0; split <= stream.length; split += 1) {
@@ -22,7 +22,7 @@ describe('agent status transport envelope', () => {
const first = authority.applyMutation({ parent: { subject: parent, firstObservedAt: 10 } })
expect(first).not.toBeNull()
const replica = createAgentStatusStore({ epoch: 'replica-placeholder', mode: 'replica' })
const replica = createAgentStatusStore({ epoch: 'epoch-a', mode: 'replica' })
expect(replica.applyTransportEnvelope(first)).toBe(false)
expect(
replica.applyTransportEnvelope({ type: 'snapshot', snapshot: authority.getSnapshot() })