mirror of
https://github.com/stablyai/orca.git
synced 2026-10-09 00:02:39 +00:00
test: retire cases whose fixture decides the outcome it asserts (#24144)
Backlog chunks 12-17: sidebar, hooks and four renderer/lib chunks, six auditors at 84 files each. 38 case declarations removed across 27 files (56 executed cases, one deletion was a 13-entry `it.each`), 1 test file deleted, 710 lines gone. No production code touched. The theme this wave is a test whose own scaffolding makes the decision it claims to check: - `useAutoAckViewedAgent.test.ts` — its `runAutoAckScan` helper reassembles the hook's scan loop, calling `resolveAutoAckTabTargets`, `createTerminalAttentionSurface`, `resolveViewedUnreadSubjectKey`, `shouldClearWorkspaceAttention` and `applyAgentAttentionAcknowledgement` in production's order. The test, not the hook, decides the outcome. Owner drives the real hook through `renderHook`/`rerender`. - `WorktreeList.lineage-agent-expansion-coupling.test.tsx` — a CONTROL case that was green both before AND after the fix it brackets. Expansion now lives in a module-level cache (`worktree-card-agents-expansion-state.ts:30`), so the collapse survives a remount; pre-fix it was React local state, which survives a re-render. The two arms it claims to contrast never reached different code. - `agent-paste-draft.test.ts` — a self-comparison over lazily-chunked draft arrays. - `remote-workspace-session-merge-local-survival.test.ts` — its own comment concedes the tab survives, and the assertion loops `expect(['agent','closed']).toContain(tab.id)` over a fixture containing only those two ids, so it cannot fail except on a fabricated id. Also removed: duplicate invocations where production reaches the asserted branch before the varied input is read (`resolve-zoom-target.ts:43-45` returns `'ui'` for browser tabs before any focus signal); a telemetry fallback already covered by the `it.each` row for the one call site that names no source; and `lazy-with-retry.right-sidebar-syntax-error.test.ts` whole, whose two cases drive one branch with `')'` vs `']'` while the owner cites the same crash report (e08749bb) and asserts the same pair. Two corrections to my own guidance came out of this wave, both from auditors measuring what I told them: 1. The widening-cast "detection signal" I introduced last wave is NOT greppable. An auditor checked all 200+ `as never`/`as unknown as` hits in its chunk and found zero function-signature widening; repo-wide, `as unknown as` matches 192 renderer/lib test files and the narrow `as (...args: unknown` form matches 26 that are nearly all the legitimate idiom for forwarding to a real implementation while partially mocking a module. Whether the cast target is the SUBJECT or a COLLABORATOR is semantic, not syntactic. Downgraded to a reading aid. 2. That is the ninth syntactic proxy I have proposed for a relationship that exists only between a test and its production counterpart, and the ninth to fail measurement. No such proxy exists; reading the case against production is the method. Two new defect classes recorded, both invisible in the test's own text: a FIXTURE that reimplements the production rule (`runtime-session-mirror-unverifiable-host.test.ts:47` builds inputs with the same `verification === 'verified' && !retired` expression production uses at `runtime-status-snapshot.ts:28`, so the test keeps passing against a stale premise if the rule changes — already diverging, since a sibling projection adds a third condition); and the stale CONTROL above. With anti-vacuity, that is three ways a test can silently stop testing what it claims while staying green. Coverage is partial and stated as such: 54 to 84 of 84 per chunk, every auditor listing its unread paths. `renderer/lib` files are larger than earlier areas, which is where the gap comes from. Verified: 1,076 test files / 9,765 cases pass across the touched areas; `check-reliability-gates.mjs` 140 gates; the deleted file is absent from the gate manifest, `cloud/package.json` and `mobile/tests-typecheck-baseline.txt`.
This commit is contained in:
-17
@@ -539,23 +539,6 @@ describe('WorktreeCard agent-list <-> child-worktrees expansion coupling', () =>
|
||||
expect(container.querySelector('.worktree-agent-lineage-children')).toBeNull()
|
||||
})
|
||||
|
||||
it('[full mode] CONTROL: a re-render that does NOT change collapsedGroups preserves agent state (isolates the remount)', async () => {
|
||||
setAgentLineageState({ agentActivityDisplayMode: 'full' })
|
||||
const { container, root } = await renderWorktreeList()
|
||||
|
||||
await click(agentChildDisclosure(container)!)
|
||||
expect(agentChildDisclosure(container)!.getAttribute('aria-expanded')).toBe('false')
|
||||
|
||||
// Re-render WITHOUT touching collapsedGroups: the parent's virtual-row key
|
||||
// stays 'lineage-group:all:lineage:parent', so there is no remount.
|
||||
await rerender(root)
|
||||
|
||||
expect(parentVirtualRowKey(container)).toBe('lineage-group:all:lineage:parent')
|
||||
// Agent collapse survives => proves it is the KEY change (remount), not the
|
||||
// re-render itself, that resets the agent expansion.
|
||||
expect(agentChildDisclosure(container)!.getAttribute('aria-expanded')).toBe('false')
|
||||
})
|
||||
|
||||
it('[compact mode] toggling CHILD WORKTREES preserves the compact agent summary expansion (regression)', async () => {
|
||||
setAgentLineageState({ agentActivityDisplayMode: 'compact', secondRootAgent: true })
|
||||
const { container, root } = await renderWorktreeList()
|
||||
|
||||
@@ -228,26 +228,6 @@ describe('grab-relative hit testing', () => {
|
||||
expect(getWorktreeSidebarDragReferenceY({ localY: 300, grab: null, activeRect })).toBe(300)
|
||||
})
|
||||
|
||||
it('resolves the same slot wherever a tall card was grabbed', () => {
|
||||
const rects = layout({ c: EXPANDED_CARD_HEIGHT })
|
||||
const tall = rects.find((rect) => rect.worktreeId === 'c')!
|
||||
const height = tall.bottom - tall.top
|
||||
// Park the card so it visually occupies b's slot, varying only the grab point.
|
||||
const slotTop = rects[1]!.top
|
||||
|
||||
const dropIndexes = [0.05, 0.25, 0.5, 0.75, 0.95].map((fraction) => {
|
||||
const offsetY = height * fraction
|
||||
return previewAt({
|
||||
pointerY: slotTop + offsetY,
|
||||
rects,
|
||||
draggingWorktreeId: 'c',
|
||||
grab: { offsetY, height }
|
||||
})!.dropIndex
|
||||
})
|
||||
|
||||
expect(new Set(dropIndexes).size).toBe(1)
|
||||
})
|
||||
|
||||
it('clamps a grab offset that lands outside the card', () => {
|
||||
expect(getWorktreeSidebarDragGrab({ offsetY: -40, height: CARD_HEIGHT })).toEqual({
|
||||
offsetY: 0,
|
||||
|
||||
@@ -72,24 +72,6 @@ describe('direct-SSH reconnect merge: local state the host has not seen', () =>
|
||||
expect(merged.tabsByWorktree[WORKTREE].map((tab) => tab.id)).toContain('setup')
|
||||
})
|
||||
|
||||
it('drops a tab closed locally rather than resurrecting it from the snapshot', () => {
|
||||
// The other side of the coin. Closing a tab removes it from local state, so it is absent from
|
||||
// BOTH sides — and the preserve must not reach into the stale payload and bring it back.
|
||||
const agent = terminalTab('agent')
|
||||
const closed = terminalTab('closed')
|
||||
const current = sessionState({ tabsByWorktree: { [WORKTREE]: [agent] } })
|
||||
const remote = sessionState({ tabsByWorktree: { [WORKTREE]: [agent, closed] } })
|
||||
|
||||
// Live state is the truth about what is open locally: the user closed `closed`.
|
||||
const merged = merge(current, remote, { [WORKTREE]: [agent] })
|
||||
|
||||
// The host still lists it, so it survives here — the host is authoritative for what it knows.
|
||||
// What matters is that the preserve branch invents nothing: the ids come from the two inputs.
|
||||
for (const tab of merged.tabsByWorktree[WORKTREE]) {
|
||||
expect(['agent', 'closed']).toContain(tab.id)
|
||||
}
|
||||
})
|
||||
|
||||
it('keeps a tab another client closed, which is the accepted cost of the rule', () => {
|
||||
// Pinned because it is a deliberate trade, not an oversight. Absence in the snapshot cannot
|
||||
// distinguish "never uploaded" from "closed on another client sharing this host", and the two
|
||||
|
||||
@@ -185,17 +185,6 @@ describe('direct-SSH snapshot apply keeps local state the host has not seen', ()
|
||||
).toBe(WORKTREE_ID)
|
||||
})
|
||||
|
||||
it('still follows the host when the snapshot does name an active worktree', async () => {
|
||||
const store = createTestStore()
|
||||
seedCatalog(store)
|
||||
await applySnapshot(store, snapshot(1, ['agent']))
|
||||
store.getState().setActiveWorktree(WORKTREE_ID)
|
||||
|
||||
await applySnapshot(store, snapshot(2, ['agent'], { activeWorktreePath: PATH }))
|
||||
|
||||
expect(store.getState().activeWorktreeId).toBe(WORKTREE_ID)
|
||||
})
|
||||
|
||||
it('does not duplicate a tab across repeated snapshots', async () => {
|
||||
const store = createTestStore()
|
||||
seedCatalog(store)
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
} from '@/attention/agent-attention-acknowledgement'
|
||||
import { createTerminalAttentionSurface } from '@/components/terminal-pane/terminal-attention-surface'
|
||||
import { createTestStore, makeTab } from '../store/slices/store-test-helpers'
|
||||
import { selectFloatingWorkspaceHasUnread } from '../store/selectors'
|
||||
import type { RetainedAgentEntry } from '../store/slices/agent-status'
|
||||
import { FLOATING_TERMINAL_WORKTREE_ID } from '../../../shared/constants'
|
||||
import { makePaneKey } from '../../../shared/stable-pane-id'
|
||||
@@ -465,86 +464,6 @@ describe('resolveAutoAckTabTargets', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// Why: the minimized toggle's attention dot is the only signal a closed floating panel has, so a
|
||||
// hidden panel must never auto-ack (selectFloatingWorkspaceHasUnread → FloatingTerminalToggleButton).
|
||||
describe('floating workspace auto-ack against the attention dot', () => {
|
||||
const FLOATING_TAB_ID = 'tab-floating'
|
||||
const floatingPaneKey = makePaneKey(FLOATING_TAB_ID, CODEX_LEAF_ID)
|
||||
|
||||
function seedFloatingCompletion(): ReturnType<typeof createTestStore> {
|
||||
const store = createTestStore()
|
||||
store.setState({
|
||||
activeView: 'terminal',
|
||||
activeTabId: 'tab-1',
|
||||
activeWorktreeId: 'wt-1',
|
||||
activeTabIdByWorktree: {
|
||||
'wt-1': 'tab-1',
|
||||
[FLOATING_TERMINAL_WORKTREE_ID]: FLOATING_TAB_ID
|
||||
},
|
||||
tabsByWorktree: {
|
||||
'wt-1': [makeTab({ id: 'tab-1', worktreeId: 'wt-1' })],
|
||||
[FLOATING_TERMINAL_WORKTREE_ID]: [
|
||||
makeTab({ id: FLOATING_TAB_ID, worktreeId: FLOATING_TERMINAL_WORKTREE_ID })
|
||||
]
|
||||
}
|
||||
})
|
||||
store.getState().markAgentCompletionPaneUnread(floatingPaneKey, 'agent-completion')
|
||||
return store
|
||||
}
|
||||
|
||||
function runAutoAckScan(store: TestStore, floatingPanelVisible: boolean): void {
|
||||
const state = store.getState()
|
||||
for (const target of resolveAutoAckTabTargets(state, { floatingPanelVisible })) {
|
||||
const current = store.getState()
|
||||
const surface = createTerminalAttentionSurface(current)
|
||||
const viewedUnreadSubjectKey = resolveViewedUnreadSubjectKey(
|
||||
current.unreadAgentCompletionPanes,
|
||||
makePaneKey(target.tabId, CODEX_LEAF_ID)
|
||||
)
|
||||
const clearedSubjectKeys = new Set(viewedUnreadSubjectKey ? [viewedUnreadSubjectKey] : [])
|
||||
const workspaceId = target.worktreeId
|
||||
applyAgentAttentionAcknowledgement(
|
||||
{
|
||||
acknowledgeSubjects: current.acknowledgeAgents,
|
||||
clearWorkspaceUnread: current.clearWorktreeUnread,
|
||||
clearGroupUnread: current.clearTerminalTabUnread,
|
||||
clearSubjectUnread: current.clearTerminalPaneUnread
|
||||
},
|
||||
{
|
||||
workspaceIdToClear:
|
||||
workspaceId !== null &&
|
||||
shouldClearWorkspaceAttention(surface.collectWorkspaceAttentionRemainder(workspaceId), {
|
||||
viewedGroupId: target.tabId,
|
||||
clearedSubjectKeys
|
||||
})
|
||||
? workspaceId
|
||||
: null,
|
||||
viewedGroupId: target.tabId,
|
||||
subjectKeys: [],
|
||||
viewedUnreadSubjectKey
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
it('keeps the attention dot lit while the panel is closed', () => {
|
||||
const store = seedFloatingCompletion()
|
||||
expect(selectFloatingWorkspaceHasUnread(store.getState())).toBe(true)
|
||||
|
||||
runAutoAckScan(store, false)
|
||||
|
||||
expect(selectFloatingWorkspaceHasUnread(store.getState())).toBe(true)
|
||||
})
|
||||
|
||||
it('clears the attention dot once the panel is visible', () => {
|
||||
const store = seedFloatingCompletion()
|
||||
|
||||
runAutoAckScan(store, true)
|
||||
|
||||
expect(selectFloatingWorkspaceHasUnread(store.getState())).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('computeLapsedManualUnreadProtections', () => {
|
||||
const paneKey = makePaneKey('tab-1', CODEX_LEAF_ID)
|
||||
const otherPaneKey = makePaneKey('tab-1', OTHER_LEAF_ID)
|
||||
|
||||
@@ -6,8 +6,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import {
|
||||
clearLinearMetadataCache,
|
||||
useRepoLabels,
|
||||
useTeamLabels,
|
||||
useTeamMembers,
|
||||
useTeamStates,
|
||||
useTeamsStates
|
||||
} from './useIssueMetadata'
|
||||
@@ -167,50 +165,6 @@ describe('useIssueMetadata hooks', () => {
|
||||
expect(renders).toBeLessThanOrEqual(4)
|
||||
})
|
||||
|
||||
it('does not re-issue a failed team-label fetch when a fresh settings object re-renders', async () => {
|
||||
let renders = 0
|
||||
let error: string | null = null
|
||||
linearMocks.linearTeamLabels.mockRejectedValue(new Error('Could not connect'))
|
||||
|
||||
function LabelsProbe(): null {
|
||||
renders += 1
|
||||
const metadata = useTeamLabels('team-1', { activeRuntimeEnvironmentId: null }, 'ws-1')
|
||||
error = metadata.error
|
||||
return null
|
||||
}
|
||||
|
||||
renderProbe(<LabelsProbe />)
|
||||
await flushEffects()
|
||||
await flushEffects()
|
||||
await flushEffects()
|
||||
|
||||
expect(error).toBe('Could not connect')
|
||||
expect(linearMocks.linearTeamLabels).toHaveBeenCalledTimes(1)
|
||||
expect(renders).toBeLessThanOrEqual(4)
|
||||
})
|
||||
|
||||
it('does not re-issue a failed team-member fetch when a fresh settings object re-renders', async () => {
|
||||
let renders = 0
|
||||
let error: string | null = null
|
||||
linearMocks.linearTeamMembers.mockRejectedValue(new Error('Could not connect'))
|
||||
|
||||
function MembersProbe(): null {
|
||||
renders += 1
|
||||
const metadata = useTeamMembers('team-1', { activeRuntimeEnvironmentId: null }, 'ws-1')
|
||||
error = metadata.error
|
||||
return null
|
||||
}
|
||||
|
||||
renderProbe(<MembersProbe />)
|
||||
await flushEffects()
|
||||
await flushEffects()
|
||||
await flushEffects()
|
||||
|
||||
expect(error).toBe('Could not connect')
|
||||
expect(linearMocks.linearTeamMembers).toHaveBeenCalledTimes(1)
|
||||
expect(renders).toBeLessThanOrEqual(4)
|
||||
})
|
||||
|
||||
it('unions workflow states across every selected team (#8739)', async () => {
|
||||
let states: { id: string; name: string }[] = []
|
||||
linearMocks.linearTeamStates.mockImplementation(async (_settings, teamId: string) => {
|
||||
|
||||
@@ -274,21 +274,6 @@ describe('usePrimarySelectionPaste', () => {
|
||||
expect(consumeNativePasteMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('does not suppress native paste when the terminal has not armed the window', async () => {
|
||||
setUserAgent('Mozilla/5.0 (X11; Linux x86_64)')
|
||||
consumeNativePasteMock.mockReturnValue(false)
|
||||
await renderProbe()
|
||||
const textarea = appendTextarea()
|
||||
|
||||
let nativeBeforeInput!: Event
|
||||
await act(async () => {
|
||||
nativeBeforeInput = dispatchNativePasteBeforeInput(textarea)
|
||||
await flushPromises()
|
||||
})
|
||||
|
||||
expect(nativeBeforeInput.defaultPrevented).toBe(false)
|
||||
})
|
||||
|
||||
it('does not keep middle-click ownership after the gesture window expires', async () => {
|
||||
vi.useFakeTimers()
|
||||
vi.setSystemTime(1_000)
|
||||
|
||||
@@ -75,16 +75,6 @@ describe('resolveZoomTarget', () => {
|
||||
})
|
||||
).toBe('ui')
|
||||
})
|
||||
|
||||
it('routes to ui zoom for browser tabs without an active browser page', () => {
|
||||
expect(
|
||||
resolveZoomTarget({
|
||||
activeView: 'terminal',
|
||||
activeTabType: 'browser',
|
||||
activeElement: makeTarget({})
|
||||
})
|
||||
).toBe('ui')
|
||||
})
|
||||
})
|
||||
|
||||
describe('registerZoomIpcBridge', () => {
|
||||
|
||||
@@ -135,20 +135,6 @@ describe('agent launch caller placement and telemetry', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('falls back to the tab-bar quick launch source when a caller names none', async () => {
|
||||
const { launchAgentInNewTab } = await import('./launch-agent-in-new-tab')
|
||||
|
||||
launchAgentInNewTab({ agent: 'codex', worktreeId: 'wt-1' })
|
||||
|
||||
// Why: git-history-explain-commit is the one production call site that names no launch source,
|
||||
// so it is reported as a tab-bar quick launch rather than as its own surface.
|
||||
expect(queuedStartupPayload(store)?.telemetry).toEqual({
|
||||
agent_kind: 'kind:codex',
|
||||
launch_source: 'tab_bar_quick_launch',
|
||||
request_kind: 'new'
|
||||
})
|
||||
})
|
||||
|
||||
it('creates the tab before queueing its startup command', async () => {
|
||||
const { launchAgentInNewTab } = await import('./launch-agent-in-new-tab')
|
||||
|
||||
|
||||
@@ -822,14 +822,6 @@ describe('pasteDraftWhenAgentReady', () => {
|
||||
expect(replaceAllCallCount).toBe(0)
|
||||
})
|
||||
|
||||
it('keeps agent draft chunk arrays aligned with lazy chunk iteration', () => {
|
||||
const content = 'before\x1b[201~after😀'
|
||||
|
||||
expect(chunkAgentDraftPasteContent(content, 6)).toEqual([
|
||||
...iterateAgentDraftPasteContentChunks(content, 6)
|
||||
])
|
||||
})
|
||||
|
||||
it('iterates large agent draft chunks lazily', () => {
|
||||
const text = 'x'.repeat(128)
|
||||
const codePointAt = vi.spyOn(String.prototype, 'codePointAt')
|
||||
|
||||
@@ -292,25 +292,6 @@ describe('ai vault resume target ownership', () => {
|
||||
).toBe('unknown')
|
||||
})
|
||||
|
||||
it('blocks folder workspaces owned by runtime project groups', () => {
|
||||
expect(
|
||||
getAiVaultResumeWorkspaceTargetStatus(
|
||||
makeState({
|
||||
folderWorkspaces: [
|
||||
{
|
||||
id: 'folder-1',
|
||||
projectGroupId: 'group-1',
|
||||
name: 'Platform',
|
||||
folderPath: '/repo/platform'
|
||||
}
|
||||
],
|
||||
projectGroups: [{ id: 'group-1', executionHostId: 'runtime:env-1' }]
|
||||
}),
|
||||
folderWorkspaceKey('folder-1')
|
||||
)
|
||||
).toBe('runtime')
|
||||
})
|
||||
|
||||
it('blocks mixed local and runtime folder workspace targets', () => {
|
||||
expect(
|
||||
getAiVaultResumeWorkspaceTargetStatus(
|
||||
|
||||
@@ -158,16 +158,6 @@ describe('emitBrowserCookieImportToast', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('does not infer a partition warning from generic skipped cookies', () => {
|
||||
emitBrowserCookieImportToast(
|
||||
{ ...summary, importedCookies: 2, skippedCookies: 1 },
|
||||
'Imported 2 cookies.',
|
||||
localExecution
|
||||
)
|
||||
|
||||
expect(warningToastMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('does not infer a Google warning from generic skipped cookies', () => {
|
||||
emitBrowserCookieImportToast(
|
||||
{ ...summary, importedCookies: 2, skippedCookies: 1 },
|
||||
|
||||
@@ -215,50 +215,6 @@ describe('focusTerminalTabSurface', () => {
|
||||
expect(textarea.focus).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('does not use stale-leaf fallback while the expected layout still has multiple leaves', () => {
|
||||
flushAnimationFrames()
|
||||
const textarea = { focus: vi.fn() }
|
||||
vi.stubGlobal('document', {
|
||||
querySelector: vi.fn((selector: string) =>
|
||||
selector === '[data-terminal-tab-id="tab-1"]'
|
||||
? { getAttribute: () => 'mounted-leaf pending-leaf' }
|
||||
: null
|
||||
),
|
||||
querySelectorAll: vi.fn((selector: string) =>
|
||||
selector === TAB_HELPER_SELECTOR
|
||||
? { length: 1, item: () => textarea }
|
||||
: { length: 0, item: () => null }
|
||||
)
|
||||
})
|
||||
|
||||
focusTerminalTabSurface('tab-1', 'stale-leaf')
|
||||
|
||||
expect(textarea.focus).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('does not focus a sibling when a stale leaf id has multiple helpers in the tab', () => {
|
||||
flushAnimationFrames()
|
||||
const first = { focus: vi.fn() }
|
||||
const second = { focus: vi.fn() }
|
||||
vi.stubGlobal('document', {
|
||||
querySelector: vi.fn((selector: string) =>
|
||||
selector === '[data-terminal-tab-id="tab-1"]'
|
||||
? { getAttribute: () => 'new-left new-right' }
|
||||
: null
|
||||
),
|
||||
querySelectorAll: vi.fn((selector: string) =>
|
||||
selector === TAB_HELPER_SELECTOR
|
||||
? { length: 2, item: (index: number) => (index === 0 ? first : second) }
|
||||
: { length: 0, item: () => null }
|
||||
)
|
||||
})
|
||||
|
||||
focusTerminalTabSurface('tab-1', 'stale-leaf')
|
||||
|
||||
expect(first.focus).not.toHaveBeenCalled()
|
||||
expect(second.focus).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('cancels a pending focus frame when a newer focus request starts', () => {
|
||||
const cancelAnimationFrame = vi.fn()
|
||||
vi.stubGlobal(
|
||||
|
||||
@@ -54,29 +54,6 @@ describe('GitHub source lookup routing', () => {
|
||||
expect(window.api.gh.workItemDetails).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
// Regression for #6429: a runtime-sourced details lookup must never reach the
|
||||
// local Electron IPC, which rejects unregistered remote repos with
|
||||
// "Access denied: unknown repository path". On main this routed through
|
||||
// window.api.gh.workItemDetails and surfaced that error.
|
||||
it('does not invoke the local IPC (which throws access-denied) for runtime sources', async () => {
|
||||
vi.mocked(callRuntimeRpc).mockResolvedValue(null)
|
||||
vi.mocked(window.api.gh.workItemDetails).mockRejectedValue(
|
||||
new Error('Access denied: unknown repository path')
|
||||
)
|
||||
|
||||
await expect(
|
||||
lookupGitHubWorkItemDetailsForSource({
|
||||
repoPath: '/home/runtime/app',
|
||||
repoId: 'renderer-repo',
|
||||
sourceContext: runtimeSourceContext,
|
||||
number: 42,
|
||||
type: 'issue'
|
||||
})
|
||||
).resolves.toBeNull()
|
||||
|
||||
expect(window.api.gh.workItemDetails).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('uses the renderer repo id for runtime details when the source has no repo id', async () => {
|
||||
vi.mocked(callRuntimeRpc).mockResolvedValue(null)
|
||||
|
||||
|
||||
@@ -28,12 +28,6 @@ describe('detectOptionAsAltFromLayoutMap', () => {
|
||||
expect(detectOptionAsAltFromLayoutMap(mapOf(US))).toBe('us')
|
||||
})
|
||||
|
||||
it('classifies US International as us (same fingerprint, dead keys only)', () => {
|
||||
// US-International keeps all unshifted ASCII letters and punctuation.
|
||||
// Only Option-layer composition differs — invisible to getLayoutMap().
|
||||
expect(detectOptionAsAltFromLayoutMap(mapOf(US))).toBe('us')
|
||||
})
|
||||
|
||||
it('classifies UK as non-us (Backquote → §)', () => {
|
||||
expect(detectOptionAsAltFromLayoutMap(mapOf({ ...US, Backquote: '§' }))).toBe('non-us')
|
||||
})
|
||||
|
||||
@@ -306,20 +306,6 @@ describe('createOptionAsAltProbe', () => {
|
||||
probe.dispose()
|
||||
})
|
||||
|
||||
it('trusts the input source ID over the fingerprint even when the fingerprint says us', async () => {
|
||||
// Pre-fix: the fingerprint's 'us' verdict was authoritative and the
|
||||
// macOS ID was ignored, so Turkish-F (which reports US-identical on
|
||||
// several keys) plus any US-like fingerprint flipped
|
||||
// macOptionIsMeta=true. Now the ID overrides.
|
||||
const win = makeMockWindow(US_MAP)
|
||||
const probe = createOptionAsAltProbe(win as unknown as Window, {
|
||||
readInputSourceId: async () => 'com.apple.keylayout.German'
|
||||
})
|
||||
await probe.refresh()
|
||||
expect(probe.getCurrent()).toBe('non-us')
|
||||
probe.dispose()
|
||||
})
|
||||
|
||||
it('falls back to the fingerprint when the input-source reader returns null (non-Darwin)', async () => {
|
||||
const win = makeMockWindow(US_MAP)
|
||||
const probe = createOptionAsAltProbe(win as unknown as Window, {
|
||||
|
||||
@@ -310,28 +310,6 @@ describe('structured chat adoption guard on the launch path', () => {
|
||||
expect(mockCreateTab).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('keeps a declined Claude launch on the structured path', async () => {
|
||||
const { StructuredAgentSessionCreateRefusalError } =
|
||||
await import('./launch-structured-agent-session')
|
||||
const refusal = new StructuredAgentSessionCreateRefusalError(
|
||||
'structured_agent_session_unsupported'
|
||||
)
|
||||
mockLaunchStructuredCodexSession.mockRejectedValueOnce(refusal)
|
||||
const { launchAgentInNewTab } = await import('./launch-agent-in-new-tab')
|
||||
|
||||
const result = launchAgentInNewTab({ agent: 'claude', worktreeId: 'wt-1' })
|
||||
|
||||
expect(result).toMatchObject({
|
||||
surface: { kind: 'local-agent-session', sessionId: 'codex-session-1' }
|
||||
})
|
||||
await expect(result?.structuredSettlement).resolves.toEqual({
|
||||
kind: 'failed',
|
||||
error: refusal
|
||||
})
|
||||
expect(mockCreateTab).not.toHaveBeenCalled()
|
||||
await vi.waitFor(() => expect(mockToastError).toHaveBeenCalledOnce())
|
||||
})
|
||||
|
||||
it.each([[], null])(
|
||||
'preserves terminal-backed launches with capability answer %s',
|
||||
async (capabilities) => {
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
// @vitest-environment happy-dom
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { isLazyChunkLoadError, loadLazyWithRetry } from './lazy-with-retry'
|
||||
|
||||
// Regression guard for crash report e08749bb-777c-446e-b407-5d1f154b6173 (Orca 1.4.104).
|
||||
// boundary_id=right-sidebar, surface=right-sidebar, error_name=SyntaxError,
|
||||
// error_message="Unexpected token ')'". component_stack: Lazy -> Suspense ->
|
||||
// RightSidebarPanelContent -> ... -> RecoverableRenderErrorBoundary.
|
||||
//
|
||||
// The right-sidebar source-control panel is loaded via lazyWithRetry:
|
||||
// src/renderer/src/components/right-sidebar/right-sidebar-panel-content.tsx:6
|
||||
// const SourceControl = lazy(() => import('./SourceControl')) // lazyWithRetry
|
||||
//
|
||||
// So the corrupt-chunk recovery IS wired in. The crash was a BLIND SPOT in that
|
||||
// recovery: after the single guarded window.location.reload() has already landed
|
||||
// once this session (the guard holds a *previous* document's identity),
|
||||
// loadLazyWithRetry only converted the failure into a recoverable
|
||||
// LazyChunkLoadError when isKnownDynamicImportFailure(error) was true. A corrupt /
|
||||
// truncated chunk that parses as invalid JS rejects import() with a native
|
||||
// SyntaxError whose .name is "SyntaxError" (not "ChunkLoadError") and whose
|
||||
// message "Unexpected token ')'" matched NONE of the dynamic-import regexes, so
|
||||
// it was re-thrown raw to the boundary and killed the right sidebar — the exact
|
||||
// reported crash. The fix treats a parse-time SyntaxError as a recoverable
|
||||
// corrupt-chunk failure; these tests pin that behavior.
|
||||
|
||||
const RELOAD_GUARD_KEY = 'orca:lazy-chunk-reload-attempted'
|
||||
const LANDED_RELOAD_GUARD_VALUE = 'doc-before-the-reload'
|
||||
|
||||
// The exact error the renderer received from the corrupt right-sidebar chunk.
|
||||
const reportedCrashError = (): SyntaxError => new SyntaxError("Unexpected token ')'")
|
||||
// An equivalent transient fetch failure, for contrast — this one DOES recover.
|
||||
const equivalentFetchError = (): TypeError =>
|
||||
new TypeError('Failed to fetch dynamically imported module: file://redacted/SourceControl.js')
|
||||
|
||||
function spyOnReload(): ReturnType<typeof vi.fn> {
|
||||
const reload = vi.fn()
|
||||
vi.spyOn(window.location, 'reload').mockImplementation(reload)
|
||||
return reload
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers()
|
||||
window.sessionStorage.clear()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals()
|
||||
vi.restoreAllMocks()
|
||||
vi.useRealTimers()
|
||||
try {
|
||||
window.sessionStorage.clear()
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
})
|
||||
|
||||
describe('right-sidebar lazy chunk SyntaxError crash (regression)', () => {
|
||||
it('recovers a corrupt right-sidebar chunk SyntaxError instead of surfacing it to the boundary', async () => {
|
||||
const reload = spyOnReload()
|
||||
// The one guarded reload already landed earlier this session: the guard was
|
||||
// written by the document that reloaded, not by this one.
|
||||
window.sessionStorage.setItem(RELOAD_GUARD_KEY, LANDED_RELOAD_GUARD_VALUE)
|
||||
|
||||
// import('./SourceControl') rejects with the native parse error from the
|
||||
// corrupt chunk — exactly what the crash report captured.
|
||||
const factory = vi.fn(() => Promise.reject(reportedCrashError()))
|
||||
|
||||
const loaded = loadLazyWithRetry(factory, { retries: 2, reloadKey: 'right-sidebar' })
|
||||
// Drain the retry backoff timers first (fake timers), THEN await the result.
|
||||
const settled = loaded.then(
|
||||
() => null,
|
||||
(error: unknown) => error
|
||||
)
|
||||
await vi.advanceTimersByTimeAsync(5000)
|
||||
const caught = await settled
|
||||
|
||||
// A corrupt-chunk parse failure is unrecoverable-by-retry, so it must be
|
||||
// wrapped as a recoverable LazyChunkLoadError, NOT re-thrown raw to the
|
||||
// RecoverableRenderErrorBoundary where its only "Retry" re-runs the same dead
|
||||
// import. With the fix, the parse-time SyntaxError is recovered.
|
||||
expect(reload).not.toHaveBeenCalled() // guard already set: no second reload
|
||||
expect(isLazyChunkLoadError(caught)).toBe(true)
|
||||
})
|
||||
|
||||
it('treats the corrupt chunk SyntaxError the same as an equivalent fetch failure', async () => {
|
||||
// Demonstrates the blind spot is purely error-shape gating: the SAME corrupt
|
||||
// chunk, surfaced as a fetch failure, IS recovered; surfaced as a parse error,
|
||||
// it is not. Both are the same unrecoverable corrupt right-sidebar chunk.
|
||||
const recover = async (makeError: () => Error): Promise<unknown> => {
|
||||
window.sessionStorage.setItem(RELOAD_GUARD_KEY, LANDED_RELOAD_GUARD_VALUE)
|
||||
const factory = vi.fn(() => Promise.reject(makeError()))
|
||||
const loaded = loadLazyWithRetry(factory, { retries: 0, reloadKey: 'right-sidebar' })
|
||||
try {
|
||||
await loaded
|
||||
return null
|
||||
} catch (error) {
|
||||
return error
|
||||
} finally {
|
||||
await vi.advanceTimersByTimeAsync(5000)
|
||||
window.sessionStorage.clear()
|
||||
}
|
||||
}
|
||||
|
||||
spyOnReload()
|
||||
const fetchOutcome = await recover(equivalentFetchError)
|
||||
const parseOutcome = await recover(reportedCrashError)
|
||||
|
||||
expect(isLazyChunkLoadError(fetchOutcome)).toBe(true) // recovered
|
||||
// The parse error from the very same corrupt chunk must be recovered too.
|
||||
expect(isLazyChunkLoadError(parseOutcome)).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -62,13 +62,6 @@ describe('launch draft mirrorability', () => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
// Why: the whole point of the shared predicate. If either side ever grows its
|
||||
// own inline rule, a draft launch opens in chat with an empty composer beside
|
||||
// a filled TUI input (or stays in the terminal with a mirror nobody sees).
|
||||
it.each(DRAFT_TEXTS)('opens in chat exactly when it seeds: %j', (text) => {
|
||||
expect(opensInChat(text)).toBe(seedsTheComposer(text))
|
||||
})
|
||||
|
||||
it.each(DRAFT_TEXTS)('both sides follow the predicate: %j', (text) => {
|
||||
const expected = canMirrorLaunchDraftToNativeChat(text)
|
||||
expect(seedsTheComposer(text)).toBe(expected)
|
||||
|
||||
@@ -306,77 +306,6 @@ describe('orchestration skill agent coverage', () => {
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it('marks a multi-segment provider-home agent from a Windows-style path', () => {
|
||||
expect(
|
||||
agentHasOrchestrationSkill(
|
||||
'opencode',
|
||||
[
|
||||
skill({
|
||||
providers: ['agent-skills'],
|
||||
sourceKind: 'home',
|
||||
rootPath: 'C:\\Users\\test\\.config\\opencode\\skills',
|
||||
directoryPath: 'C:\\Users\\test\\.config\\opencode\\skills\\orchestration'
|
||||
})
|
||||
],
|
||||
[source('C:\\Users\\test\\.config\\opencode\\skills', 'opencode')]
|
||||
)
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it('keeps Pi and OMP distinct despite sharing the <home>/agent/skills shape', () => {
|
||||
const piInstall = [
|
||||
skill({
|
||||
providers: ['agent-skills'],
|
||||
sourceKind: 'home',
|
||||
rootPath: '/Users/test/.pi/agent/skills',
|
||||
directoryPath: '/Users/test/.pi/agent/skills/orchestration'
|
||||
})
|
||||
]
|
||||
const ompInstall = [
|
||||
skill({
|
||||
providers: ['agent-skills'],
|
||||
sourceKind: 'home',
|
||||
rootPath: '/Users/test/.omp/agent/skills',
|
||||
directoryPath: '/Users/test/.omp/agent/skills/orchestration'
|
||||
})
|
||||
]
|
||||
|
||||
const piSources = [source('/Users/test/.pi/agent/skills', 'pi')]
|
||||
const ompSources = [source('/Users/test/.omp/agent/skills', 'omp')]
|
||||
expect(agentHasOrchestrationSkill('pi', piInstall, piSources)).toBe(true)
|
||||
expect(agentHasOrchestrationSkill('omp', piInstall, piSources)).toBe(false)
|
||||
expect(agentHasOrchestrationSkill('omp', ompInstall, ompSources)).toBe(true)
|
||||
expect(agentHasOrchestrationSkill('pi', ompInstall, ompSources)).toBe(false)
|
||||
})
|
||||
|
||||
it('keeps Gemini and Antigravity distinct despite sharing the ~/.gemini root', () => {
|
||||
const geminiInstall = [
|
||||
skill({
|
||||
providers: ['agent-skills'],
|
||||
sourceKind: 'home',
|
||||
rootPath: '/Users/test/.gemini/skills',
|
||||
directoryPath: '/Users/test/.gemini/skills/orchestration'
|
||||
})
|
||||
]
|
||||
const antigravityInstall = [
|
||||
skill({
|
||||
providers: ['agent-skills'],
|
||||
sourceKind: 'home',
|
||||
rootPath: '/Users/test/.gemini/antigravity/skills',
|
||||
directoryPath: '/Users/test/.gemini/antigravity/skills/orchestration'
|
||||
})
|
||||
]
|
||||
|
||||
const geminiSources = [source('/Users/test/.gemini/skills', 'gemini')]
|
||||
const antigravitySources = [source('/Users/test/.gemini/antigravity/skills', 'antigravity')]
|
||||
expect(agentHasOrchestrationSkill('gemini', geminiInstall, geminiSources)).toBe(true)
|
||||
expect(agentHasOrchestrationSkill('antigravity', geminiInstall, geminiSources)).toBe(false)
|
||||
expect(agentHasOrchestrationSkill('antigravity', antigravityInstall, antigravitySources)).toBe(
|
||||
true
|
||||
)
|
||||
expect(agentHasOrchestrationSkill('gemini', antigravityInstall, antigravitySources)).toBe(false)
|
||||
})
|
||||
|
||||
it('marks Claude Agent Teams from ~/.claude/skills like Claude Code', () => {
|
||||
const skills = [
|
||||
skill({
|
||||
@@ -393,21 +322,4 @@ describe('orchestration skill agent coverage', () => {
|
||||
])
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it('marks Windows skill paths', () => {
|
||||
expect(
|
||||
agentHasOrchestrationSkill(
|
||||
'codex',
|
||||
[
|
||||
skill({
|
||||
providers: ['codex'],
|
||||
sourceKind: 'home',
|
||||
rootPath: 'C:\\Users\\test\\.codex\\skills',
|
||||
directoryPath: 'C:\\Users\\test\\.codex\\skills\\orchestration'
|
||||
})
|
||||
],
|
||||
[source('C:\\Users\\test\\.codex\\skills', 'codex')]
|
||||
)
|
||||
).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -251,16 +251,6 @@ describe('hasSkillCopyNeedingAttention', () => {
|
||||
)
|
||||
})
|
||||
|
||||
// Regression guard for the centralization: the badge predicate deliberately omits the
|
||||
// shared helper's outdated carve-out, so a non-eligible outdated copy stays amber.
|
||||
// Collapsing the two predicates would flip this to green while the dialog still shows
|
||||
// a reinstall row — the badge/dialog contradiction this change exists to avoid.
|
||||
it('keeps a non-eligible outdated global copy amber', () => {
|
||||
expect(getSkillFreshnessDisplayStatus(inventory([placement('outdated')]), SKILL_NAME)).toBe(
|
||||
'needs-attention'
|
||||
)
|
||||
})
|
||||
|
||||
// Why: an unreadable plugin path could hide a copy of anything, but a skill Orca
|
||||
// never found anywhere is not the one to blame for it — that reads as a problem
|
||||
// with a skill the user has not installed.
|
||||
|
||||
@@ -566,26 +566,6 @@ describe('startStructuredAgentLaunch', () => {
|
||||
expect(toast.error).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('replays the same intent after an absent unknown outcome', async () => {
|
||||
const worktreeId = 'wt-replay-unknown'
|
||||
const intent = launchIntent(worktreeId)
|
||||
mocks.createIntent.mockReturnValueOnce(intent)
|
||||
mocks.launch
|
||||
.mockRejectedValueOnce(new Error('offline'))
|
||||
.mockResolvedValueOnce({ sessionId: intent.sessionId, fence: 1 })
|
||||
vi.mocked(refreshLocalStructuredSessionTabs)
|
||||
.mockResolvedValueOnce([])
|
||||
.mockResolvedValueOnce([publishedSnapshot(worktreeId, intent.sessionId)])
|
||||
|
||||
startStructuredAgentLaunch(worktreeId, 'codex')
|
||||
await flushLaunchSettlement()
|
||||
|
||||
expect(mocks.createIntent).toHaveBeenCalledOnce()
|
||||
expect(mocks.launch).toHaveBeenCalledTimes(2)
|
||||
expect(mocks.launch.mock.calls[1]?.[0]).toBe(intent)
|
||||
expect(toast.error).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('reuses the queued prompt without a second delivery after unknown recovery', async () => {
|
||||
const worktreeId = 'wt-unknown-prompt-retry'
|
||||
const intent = launchIntent(worktreeId)
|
||||
|
||||
@@ -310,23 +310,6 @@ describe('buildAgentStartupPlan', () => {
|
||||
})
|
||||
).toBeNull()
|
||||
})
|
||||
|
||||
it('uses -i flag for copilot to start an interactive session with initial prompt', () => {
|
||||
expect(
|
||||
buildAgentStartupPlan({
|
||||
agent: 'copilot',
|
||||
prompt: 'Fix the bug',
|
||||
cmdOverrides: {},
|
||||
platform: 'darwin'
|
||||
})
|
||||
).toEqual({
|
||||
agent: 'copilot',
|
||||
launchCommand: "copilot -i 'Fix the bug'",
|
||||
expectedProcess: 'copilot',
|
||||
followupPrompt: null,
|
||||
launchConfig: emptyLaunchConfig('copilot')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('buildAgentDraftLaunchPlan', () => {
|
||||
|
||||
@@ -18,10 +18,6 @@ describe('formatWindowLabel', () => {
|
||||
expect(formatWindowLabel(10080)).toBe('wk')
|
||||
})
|
||||
|
||||
it('returns "1d" for 1440 minutes (1 day)', () => {
|
||||
expect(formatWindowLabel(1440)).toBe('1d')
|
||||
})
|
||||
|
||||
it('returns "2h" for 120 minutes', () => {
|
||||
expect(formatWindowLabel(120)).toBe('2h')
|
||||
})
|
||||
@@ -34,10 +30,6 @@ describe('formatWindowLabel', () => {
|
||||
expect(formatWindowLabel(20160)).toBe('2wk')
|
||||
})
|
||||
|
||||
it('returns "30m" for 30 minutes', () => {
|
||||
expect(formatWindowLabel(30)).toBe('30m')
|
||||
})
|
||||
|
||||
it('returns "3d" for 4320 minutes (3 days)', () => {
|
||||
expect(formatWindowLabel(4320)).toBe('3d')
|
||||
})
|
||||
@@ -51,13 +43,6 @@ describe('formatWindowLabel', () => {
|
||||
expect(formatWindowLabel(295)).toBe('295m')
|
||||
expect(formatWindowLabel(300)).toBe('5h')
|
||||
})
|
||||
|
||||
it('falls back to per-minute labels outside canonical buckets', () => {
|
||||
// Why: when the window length lands between buckets (e.g. 2h30m), we
|
||||
// render the raw minute count rather than guess at a half-bucket label.
|
||||
expect(formatWindowLabel(75)).toBe('75m')
|
||||
expect(formatWindowLabel(150)).toBe('150m')
|
||||
})
|
||||
})
|
||||
|
||||
describe('formatRateLimitWindowChipLabel', () => {
|
||||
|
||||
@@ -42,24 +42,6 @@ describe('getUnavailableWorkspacePortHosts', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('keeps colons inside an environment id when parsing the scan key', () => {
|
||||
// Why: keys are `${targetKey}:all`, so the id runs to the last `:all` —
|
||||
// splitting on the first colon would truncate ids that contain colons.
|
||||
expect(
|
||||
getUnavailableWorkspacePortHosts({
|
||||
'local:all': scan(),
|
||||
'environment:weird:id:all': scan({ unavailableReason: 'Remote connection dropped' })
|
||||
})
|
||||
).toEqual([
|
||||
{
|
||||
scanKey: 'environment:weird:id:all',
|
||||
host: { kind: 'environment', environmentId: 'weird:id' },
|
||||
platform: 'linux',
|
||||
reason: 'Remote connection dropped'
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
// Why: total loss of contact is where naming the host matters most — the merged
|
||||
// projection joins raw internal scan keys, so it cannot name them itself.
|
||||
it('names every host when all of them failed', () => {
|
||||
@@ -84,38 +66,6 @@ describe('getUnavailableWorkspacePortHosts', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('names a single failed host', () => {
|
||||
expect(
|
||||
getUnavailableWorkspacePortHosts({
|
||||
'local:all': scan({ unavailableReason: 'lsof is unavailable' })
|
||||
})
|
||||
).toEqual([
|
||||
{
|
||||
scanKey: 'local:all',
|
||||
host: { kind: 'local' },
|
||||
platform: 'linux',
|
||||
reason: 'lsof is unavailable'
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
// Why: the synthetic all-hosts projection key must never be labelled as the
|
||||
// local machine — that would blame the wrong host for a remote failure.
|
||||
it('marks an unrecognised scan key as an unknown host', () => {
|
||||
expect(
|
||||
getUnavailableWorkspacePortHosts({
|
||||
'all-hosts:all': scan({ unavailableReason: 'Remote connection dropped' })
|
||||
})
|
||||
).toEqual([
|
||||
{
|
||||
scanKey: 'all-hosts:all',
|
||||
host: { kind: 'unknown' },
|
||||
platform: 'linux',
|
||||
reason: 'Remote connection dropped'
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
// Why: a paired web client's userAgent is not the Orca host's platform, so the
|
||||
// caller labels the local host from the scan's own platform.
|
||||
it("carries the failed scan's platform, and null when it is unknown", () => {
|
||||
|
||||
@@ -27,12 +27,6 @@ function makeAgentEntry(
|
||||
}
|
||||
|
||||
describe('worktree activity state', () => {
|
||||
it('treats a slept wake-hint workspace as inactive', () => {
|
||||
expect(
|
||||
isInactiveWorkspace('wt-1', { 'wt-1': [makeTab('tab-1')] }, { 'tab-1': [] }, {}, new Set())
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it('treats a never-opened workspace as inactive', () => {
|
||||
expect(isInactiveWorkspace('wt-1', {}, {}, {}, new Set())).toBe(true)
|
||||
})
|
||||
|
||||
@@ -59,11 +59,4 @@ describe('resolveBackendDraftStartup', () => {
|
||||
setRepoConnection('runtime-ssh-env-1')
|
||||
expect(viewModeFor('omp')).toBe('chat')
|
||||
})
|
||||
|
||||
it('preserves the same split for Grok', () => {
|
||||
setRepoConnection(null)
|
||||
expect(viewModeFor('grok')).toBe('chat')
|
||||
setRepoConnection('ssh-target-1')
|
||||
expect(viewModeFor('grok')).toBe('terminal')
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user