mirror of
https://github.com/stablyai/orca.git
synced 2026-11-13 17:37:40 +00:00
test: remove 44 low-value test declarations (#37969)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
@@ -14646,7 +14646,7 @@
|
||||
"oracle": "A quarter-circle Claude task title authorizes without a provider lookup only on the exact PTY incarnation carrying verified managed-Claude launch identity; the same title in a bare pane, with an unverified launch hint, or after PTY incarnation replacement is refused while current Claude activity remains a working signal. Fresh explicit state plus ordinary PowerShell plus confirmed recognized agent is sendable on the same PTY. Confirmed shell/non-agent, unavailable confirmation, PTY exit, handle rebind, or a callback PTY mismatch returns a refusal or not-writable result and writes zero bytes.",
|
||||
"commands": [
|
||||
"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/quarter-circle-title-send-authorization.test.ts",
|
||||
"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/terminal-send.test.ts src/main/ipc/pty/pty-runtime-kill-and-exit.test.ts src/main/providers/agent-foreground-process.test.ts src/main/providers/local-pty/local-pty-provider-foreground-process.test.ts src/main/daemon/pty-subprocess-foreground-degraded-scan.test.ts src/main/providers/windows-console-attached-processes.test.ts src/main/daemon/pty-subprocess.test.ts src/renderer/src/lib/active-agent-note-send-explicit-target.test.ts src/renderer/src/components/browser-pane/annotate/BrowserAnnotationSendMenuContent.test.tsx"
|
||||
"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/terminal-send.test.ts src/main/ipc/pty/pty-runtime-kill-and-exit.test.ts src/main/providers/agent-foreground-process.test.ts src/main/providers/local-pty/local-pty-provider-foreground-process.test.ts src/main/daemon/pty-subprocess-foreground-degraded-scan.test.ts src/main/providers/windows-console-attached-processes.test.ts src/main/daemon/pty-subprocess.test.ts src/renderer/src/lib/active-agent-note-send-explicit-target.test.ts"
|
||||
],
|
||||
"testFiles": [
|
||||
"src/main/runtime/quarter-circle-title-send-authorization.test.ts",
|
||||
@@ -14658,8 +14658,7 @@
|
||||
"src/main/daemon/pty-subprocess-foreground-degraded-scan.test.ts",
|
||||
"src/main/providers/windows-console-attached-processes.test.ts",
|
||||
"src/main/daemon/pty-subprocess.test.ts",
|
||||
"src/renderer/src/lib/active-agent-note-send-explicit-target.test.ts",
|
||||
"src/renderer/src/components/browser-pane/annotate/BrowserAnnotationSendMenuContent.test.tsx"
|
||||
"src/renderer/src/lib/active-agent-note-send-explicit-target.test.ts"
|
||||
],
|
||||
"assertionRefs": [
|
||||
{
|
||||
@@ -14721,13 +14720,6 @@
|
||||
{
|
||||
"file": "src/renderer/src/lib/active-agent-note-send-explicit-target.test.ts",
|
||||
"assertions": ["selected active-agent note sends retain guarded paste and submit routing"]
|
||||
},
|
||||
{
|
||||
"file": "src/renderer/src/components/browser-pane/annotate/BrowserAnnotationSendMenuContent.test.tsx",
|
||||
"assertions": [
|
||||
"browser annotation send content routes through review-notes send so existing agent sessions remain selectable",
|
||||
"both browser annotation send surfaces wire the shared menu content"
|
||||
]
|
||||
}
|
||||
],
|
||||
"evidenceRuns": [
|
||||
@@ -14745,7 +14737,7 @@
|
||||
"runner": "local",
|
||||
"platform": "macos",
|
||||
"command": "pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/terminal-send.test.ts src/main/ipc/pty-runtime-kill-and-exit.test.ts src/main/providers/agent-foreground-process.test.ts src/main/providers/local-pty-provider-foreground-process.test.ts src/main/daemon/pty-subprocess-foreground-degraded-scan.test.ts src/main/providers/windows-console-attached-processes.test.ts src/main/providers/windows-pty-job-membership.test.ts src/main/daemon/daemon-foreground-confirmation-protocol.test.ts src/main/daemon/pty-subprocess.test.ts src/renderer/src/lib/active-agent-note-send-explicit-target.test.ts src/renderer/src/components/browser-pane/annotate/BrowserAnnotationSendMenuContent.test.tsx",
|
||||
"currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/terminal-send.test.ts src/main/ipc/pty/pty-runtime-kill-and-exit.test.ts src/main/providers/agent-foreground-process.test.ts src/main/providers/local-pty/local-pty-provider-foreground-process.test.ts src/main/daemon/pty-subprocess-foreground-degraded-scan.test.ts src/main/providers/windows-console-attached-processes.test.ts src/main/daemon/pty-subprocess.test.ts src/renderer/src/lib/active-agent-note-send-explicit-target.test.ts src/renderer/src/components/browser-pane/annotate/BrowserAnnotationSendMenuContent.test.tsx",
|
||||
"currentCommand": "pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/rpc/terminal-send.test.ts src/main/ipc/pty/pty-runtime-kill-and-exit.test.ts src/main/providers/agent-foreground-process.test.ts src/main/providers/local-pty/local-pty-provider-foreground-process.test.ts src/main/daemon/pty-subprocess-foreground-degraded-scan.test.ts src/main/providers/windows-console-attached-processes.test.ts src/main/daemon/pty-subprocess.test.ts src/renderer/src/lib/active-agent-note-send-explicit-target.test.ts",
|
||||
"result": "passed",
|
||||
"durationSeconds": 13.74,
|
||||
"summary": "Twelve focused test files passed (1351 tests, 1 skipped), covering runtime confirmation and PTY revalidation, guarded RPC zero-write behavior, PTY controller routing, local/daemon fresh scans, exact ConPTY membership, and unchanged renderer note routing."
|
||||
|
||||
@@ -950,7 +950,6 @@
|
||||
"src/main/agent-hooks/wsl-opencode-settings-lifetime.test.ts": 1951,
|
||||
"src/main/agent-hooks/wsl-opencode-settings.test.ts": 1364,
|
||||
"src/main/agent-launch/agent-launch-account-env.test.ts": 958,
|
||||
"src/main/agent-launch/agent-launch-decided-mode-contract.test.ts": 38,
|
||||
"src/main/agent-launch/agent-launch-executor-cancel.test.ts": 1455,
|
||||
"src/main/agent-launch/agent-launch-executor.test.ts": 1098,
|
||||
"src/main/agent-launch/agent-launch-legacy-host-policy.test.ts": 1028,
|
||||
@@ -5660,7 +5659,6 @@
|
||||
"src/main/runtime/rpc/methods/orchestration-worker-mode-opacity.test.ts": 5585,
|
||||
"src/main/runtime/rpc/methods/orchestration-worker-release-incarnation-fallback.test.ts": 5246,
|
||||
"src/main/runtime/rpc/methods/orchestration-worker-start-mode-selection.test.ts": 5749,
|
||||
"src/main/runtime/rpc/methods/orchestration-worker-start-mode.test.ts": 851,
|
||||
"src/main/runtime/rpc/methods/orchestration-worker-start-registered-agent.test.ts": 1426,
|
||||
"src/main/runtime/rpc/methods/orchestration/agent-message-command-contract.test.ts": 6968,
|
||||
"src/main/runtime/rpc/methods/orchestration/cli-runtime-boundary.test.ts": 5671,
|
||||
@@ -7376,7 +7374,6 @@
|
||||
"src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.popup-notices.test.tsx": 6196,
|
||||
"src/renderer/src/components/browser-pane/ClientHostedBrowserPagePane.test.tsx": 6541,
|
||||
"src/renderer/src/components/browser-pane/ReopenBrowserPageOnServerButton.test.tsx": 497,
|
||||
"src/renderer/src/components/browser-pane/annotate/BrowserAnnotationSendMenuContent.test.tsx": 275,
|
||||
"src/renderer/src/components/browser-pane/annotate/GrabConfirmationSheet.test.ts": 261,
|
||||
"src/renderer/src/components/browser-pane/annotate/MarkupOverlay.test.tsx": 1124,
|
||||
"src/renderer/src/components/browser-pane/annotate/browser-annotation-output.test.ts": 74,
|
||||
@@ -8865,7 +8862,6 @@
|
||||
"src/renderer/src/components/settings/voice/VoicePane.test.tsx": 1042,
|
||||
"src/renderer/src/components/settings/voice/VoiceSpeechModelSection.test.tsx": 503,
|
||||
"src/renderer/src/components/settings/general/WorkspaceDirectorySetting.test.tsx": 1982,
|
||||
"src/renderer/src/components/settings/agents/launch/ZcodeCliSetupSection.test.tsx": 818,
|
||||
"src/renderer/src/components/settings/accounts/usage/ZcodePlanAccountsSection.test.tsx": 1002,
|
||||
"src/renderer/src/components/settings/accounts/sign-in/account-add-cancel-target.test.ts": 217,
|
||||
"src/renderer/src/components/settings/accounts/usage/accounts-pane-opencode-credentials.test.ts": 519,
|
||||
@@ -10352,7 +10348,6 @@
|
||||
"src/renderer/src/lib/agent-paste-draft-submit-retry.test.ts": 315,
|
||||
"src/renderer/src/lib/agent-paste-draft.test.ts": 445,
|
||||
"src/renderer/src/lib/agent-picker-search.test.ts": 265,
|
||||
"src/renderer/src/lib/agent-ready-wait.test.ts": 119,
|
||||
"src/renderer/src/lib/agent-resume-launch-target.test.ts": 3911,
|
||||
"src/renderer/src/lib/agent-session/launch/structured-agent-session-launch-after-failure.test.ts": 870,
|
||||
"src/renderer/src/lib/agent-session/launch/structured-agent-session-launch-close-race.test.ts": 983,
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
import { createElement } from 'react'
|
||||
import { act, create, type ReactTestRenderer } from 'react-test-renderer'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { CodexResetCreditAction } from './CodexResetCreditAction'
|
||||
import { feedback } from '../theme/mobile-theme'
|
||||
import { findAllByHostType, findByHostType } from '../test-support/react-test-renderer-host-nodes'
|
||||
|
||||
vi.mock('react-native', () => ({
|
||||
ActivityIndicator: 'ActivityIndicator',
|
||||
Pressable: 'Pressable',
|
||||
StyleSheet: { create: (styles: unknown) => styles, hairlineWidth: 1 },
|
||||
Text: 'Text',
|
||||
View: 'View'
|
||||
}))
|
||||
|
||||
vi.mock('lucide-react-native', () => ({ RotateCcw: 'RotateCcw' }))
|
||||
|
||||
const summary = {
|
||||
availableCount: 1,
|
||||
availabilityLabel: '1 reset available',
|
||||
expiryLabel: 'Expires in 5d'
|
||||
}
|
||||
|
||||
function renderAction(busy: boolean, disabled: boolean): ReactTestRenderer {
|
||||
let renderer: ReactTestRenderer | null = null
|
||||
act(() => {
|
||||
renderer = create(
|
||||
createElement(CodexResetCreditAction, {
|
||||
summary,
|
||||
scopeLabel: 'dev@example.com on the host',
|
||||
busy,
|
||||
disabled,
|
||||
onPress: vi.fn()
|
||||
})
|
||||
)
|
||||
})
|
||||
if (!renderer) {
|
||||
throw new Error('Reset action did not render')
|
||||
}
|
||||
return renderer
|
||||
}
|
||||
|
||||
describe('CodexResetCreditAction', () => {
|
||||
it('exposes a 44pt touch target and enabled accessibility state', () => {
|
||||
const renderer = renderAction(false, false)
|
||||
const button = findByHostType(renderer.root, 'Pressable')
|
||||
|
||||
expect(button.props.accessibilityLabel).toBe('Use Codex rate-limit reset')
|
||||
expect(button.props.accessibilityState).toEqual({ busy: false, disabled: false })
|
||||
expect(button.props.accessibilityHint).toContain('dev@example.com on the host')
|
||||
expect(button.props.hitSlop).toBe(8)
|
||||
expect(button.props.style({ pressed: false })[0]).toMatchObject({ minHeight: 44 })
|
||||
act(() => renderer.unmount())
|
||||
})
|
||||
|
||||
it('announces progress and visually dims a busy disabled action', () => {
|
||||
const renderer = renderAction(true, true)
|
||||
const button = findByHostType(renderer.root, 'Pressable')
|
||||
const text = findAllByHostType(renderer.root, 'Text').map((node) =>
|
||||
node.children.filter((child) => typeof child === 'string').join('')
|
||||
)
|
||||
|
||||
expect(button.props.accessibilityLabel).toBe('Resetting Codex rate limits')
|
||||
expect(button.props.accessibilityState).toEqual({ busy: true, disabled: true })
|
||||
expect(button.props.style({ pressed: false })[1]).toMatchObject({
|
||||
opacity: feedback.disabledOpacity
|
||||
})
|
||||
expect(text).toContain('Resetting…')
|
||||
act(() => renderer.unmount())
|
||||
})
|
||||
})
|
||||
@@ -1,7 +1,6 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import {
|
||||
clampDragReorderIndex,
|
||||
dragReorderLayout,
|
||||
dragReorderTarget,
|
||||
dragReorderPositionsFromKeys,
|
||||
@@ -10,18 +9,6 @@ import {
|
||||
} from './drag-reorder-positions'
|
||||
|
||||
describe('drag reorder positions', () => {
|
||||
it('round-trips keys through positions', () => {
|
||||
const keys = ['escape', 'tab', 'enter']
|
||||
expect(orderedKeysFromDragReorderPositions(dragReorderPositionsFromKeys(keys))).toEqual(keys)
|
||||
})
|
||||
|
||||
it('clamps drag indexes to the list bounds', () => {
|
||||
expect(clampDragReorderIndex(-2, 3)).toBe(0)
|
||||
expect(clampDragReorderIndex(1, 3)).toBe(1)
|
||||
expect(clampDragReorderIndex(7, 3)).toBe(2)
|
||||
expect(clampDragReorderIndex(0, 0)).toBe(0)
|
||||
})
|
||||
|
||||
it('shifts intermediate rows down when dragging a row later', () => {
|
||||
const positions = dragReorderPositionsFromKeys(['a', 'b', 'c', 'd'])
|
||||
expect(orderedKeysFromDragReorderPositions(moveDragReorderKey(positions, 'a', 2))).toEqual([
|
||||
@@ -41,12 +28,6 @@ describe('drag reorder positions', () => {
|
||||
'c'
|
||||
])
|
||||
})
|
||||
|
||||
it('returns the same positions for no-op or unknown moves', () => {
|
||||
const positions = dragReorderPositionsFromKeys(['a', 'b'])
|
||||
expect(moveDragReorderKey(positions, 'a', 0)).toBe(positions)
|
||||
expect(moveDragReorderKey(positions, 'missing', 1)).toBe(positions)
|
||||
})
|
||||
})
|
||||
|
||||
it('projects a thousand variable-height rows and all row lookups with linear reads', () => {
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
import { createElement } from 'react'
|
||||
import { act, create, type ReactTestRenderer } from 'react-test-renderer'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import {
|
||||
useNewWorktreeDrawerNavigation,
|
||||
type NewWorktreeDrawerView
|
||||
} from './use-new-worktree-drawer-navigation'
|
||||
|
||||
type Nav = ReturnType<typeof useNewWorktreeDrawerNavigation>
|
||||
const mounted: ReactTestRenderer[] = []
|
||||
|
||||
function renderNavigation(modalVisible: boolean): { current: Nav } {
|
||||
const handle: { current: Nav | null } = { current: null }
|
||||
function Probe(props: { modalVisible: boolean }) {
|
||||
handle.current = useNewWorktreeDrawerNavigation(props.modalVisible)
|
||||
return null
|
||||
}
|
||||
act(() => {
|
||||
mounted.push(create(createElement(Probe, { modalVisible })))
|
||||
})
|
||||
return {
|
||||
get current() {
|
||||
if (!handle.current) {
|
||||
throw new Error('Navigation did not mount')
|
||||
}
|
||||
return handle.current
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe('useNewWorktreeDrawerNavigation', () => {
|
||||
beforeEach(() => vi.useFakeTimers())
|
||||
afterEach(() => {
|
||||
for (const renderer of mounted.splice(0)) {
|
||||
act(() => renderer.unmount())
|
||||
}
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
// The shared native Modal stays mounted; a transition rendering no sheet leaves a transparent window that swallows taps until the timer fires.
|
||||
it('shows the form sheet for the whole transition, even if the queued timer never lands', () => {
|
||||
const nav = renderNavigation(true)
|
||||
act(() => nav.current.openSourceDrawer())
|
||||
expect(nav.current.drawerView).toBe<NewWorktreeDrawerView>('source')
|
||||
|
||||
act(() => nav.current.transitionDrawer('form'))
|
||||
expect(nav.current.drawerView).toBe<NewWorktreeDrawerView>('transition')
|
||||
expect(nav.current.formSheetVisible).toBe(true)
|
||||
expect(nav.current.formSheetInteractive).toBe(false)
|
||||
})
|
||||
|
||||
it('keeps the form pinned under a picker instead of swapping it out', () => {
|
||||
const nav = renderNavigation(true)
|
||||
act(() => nav.current.transitionDrawer('agent'))
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(500)
|
||||
})
|
||||
|
||||
expect(nav.current.drawerView).toBe<NewWorktreeDrawerView>('agent')
|
||||
expect(nav.current.formSheetVisible).toBe(true)
|
||||
expect(nav.current.formSheetInteractive).toBe(false)
|
||||
})
|
||||
|
||||
it('hands the form back interactive once the transition lands', () => {
|
||||
const nav = renderNavigation(true)
|
||||
act(() => nav.current.openSourceDrawer())
|
||||
act(() => nav.current.transitionDrawer('form'))
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(500)
|
||||
})
|
||||
|
||||
expect(nav.current.drawerView).toBe<NewWorktreeDrawerView>('form')
|
||||
expect(nav.current.formSheetVisible).toBe(true)
|
||||
expect(nav.current.formSheetInteractive).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -34,24 +34,6 @@ const props = {
|
||||
alt: 'Architecture'
|
||||
}
|
||||
|
||||
it('shows the loaded image and expands it in the existing image viewer', async () => {
|
||||
loader.mockResolvedValue('data:image/png;base64,cG5n')
|
||||
await act(async () => {
|
||||
tree = create(createElement(MobileMarkdownPreviewImage, props))
|
||||
})
|
||||
const image = tree!.root.find((node) => String(node.type) === 'Image')
|
||||
expect(image.props.source.uri).toBe('data:image/png;base64,cG5n')
|
||||
expect(tree!.root.find((node) => String(node.type) === 'Image').props.style.aspectRatio).toBe(
|
||||
600 / 220
|
||||
)
|
||||
await act(async () =>
|
||||
tree!.root.find((node) => String(node.type) === 'Pressable').props.onPress()
|
||||
)
|
||||
expect(tree!.root.find((node) => String(node.type) === 'ImageViewer').props.uri).toBe(
|
||||
'data:image/png;base64,cG5n'
|
||||
)
|
||||
})
|
||||
|
||||
it('retries a refused image without hiding the surrounding document', async () => {
|
||||
loader
|
||||
.mockRejectedValueOnce(new Error('Unable to reach the desktop filesystem'))
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
import { useRef } from 'react'
|
||||
import { act, create, type ReactTestRenderer } from 'react-test-renderer'
|
||||
import { afterEach, expect, it, vi } from 'vitest'
|
||||
import { findAllByHostType, findByHostType } from '../test-support/react-test-renderer-host-nodes'
|
||||
import { MobileEdgeFade } from '../components/MobileEdgeFade'
|
||||
import { colors, spacing } from '../theme/mobile-theme'
|
||||
import { MobileQueuedMessageScrollList } from './MobileQueuedMessageScrollList'
|
||||
|
||||
vi.mock('react-native', () => ({
|
||||
Platform: { OS: 'ios' },
|
||||
StyleSheet: { create: (styles: unknown) => styles },
|
||||
View: 'View'
|
||||
}))
|
||||
vi.mock('react-native-reanimated', () => ({
|
||||
default: { ScrollView: 'ScrollView' },
|
||||
useAnimatedRef: () => useRef(null),
|
||||
useSharedValue: (value: unknown) => useRef({ value }).current
|
||||
}))
|
||||
|
||||
let renderer: ReactTestRenderer | null = null
|
||||
afterEach(() => act(() => renderer?.unmount()))
|
||||
|
||||
it('keeps the queue edge cues, colors and dimensions with independent gradient references', () => {
|
||||
act(() => {
|
||||
renderer = create(
|
||||
<MobileQueuedMessageScrollList messageIds={['a']} newestIsPersons={false}>
|
||||
{() => 'queued body'}
|
||||
</MobileQueuedMessageScrollList>
|
||||
)
|
||||
})
|
||||
const tree = renderer!
|
||||
const scroll = findByHostType(tree.root, 'ScrollView')
|
||||
const fades = () => tree.root.findAllByType(MobileEdgeFade)
|
||||
expect(fades()).toHaveLength(0)
|
||||
act(() => scroll.props.onLayout({ nativeEvent: { layout: { height: 160 } } }))
|
||||
act(() => scroll.props.onContentSizeChange(0, 220))
|
||||
expect(fades().map((node) => node.props.edge)).toEqual(['bottom'])
|
||||
act(() => scroll.props.onScroll({ nativeEvent: { contentOffset: { y: 24 } } }))
|
||||
expect(fades().map((node) => node.props.edge)).toEqual(['top', 'bottom'])
|
||||
const ids = fades().map((fade) => {
|
||||
const overlay = findByHostType(fade, 'View')
|
||||
expect(overlay.props.pointerEvents).toBe('none')
|
||||
expect(Object.assign({}, ...overlay.props.style)).toMatchObject({
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: spacing.lg
|
||||
})
|
||||
const gradient = findByHostType(fade, 'LinearGradient')
|
||||
expect(findByHostType(fade, 'Rect').props.fill).toBe(`url(#${gradient.props.id})`)
|
||||
return gradient.props.id
|
||||
})
|
||||
expect(new Set(ids).size).toBe(2)
|
||||
expect(
|
||||
fades().map((fade) =>
|
||||
findAllByHostType(fade, 'Stop').map((node) => [node.props.stopColor, node.props.stopOpacity])
|
||||
)
|
||||
).toEqual([
|
||||
[
|
||||
[colors.bgPanel, 1],
|
||||
[colors.bgPanel, 0]
|
||||
],
|
||||
[
|
||||
[colors.bgPanel, 0],
|
||||
[colors.bgPanel, 1]
|
||||
]
|
||||
])
|
||||
act(() => scroll.props.onScroll({ nativeEvent: { contentOffset: { y: 60 } } }))
|
||||
expect(fades().map((node) => node.props.edge)).toEqual(['top'])
|
||||
act(() => scroll.props.onContentSizeChange(0, 100))
|
||||
act(() => scroll.props.onScroll({ nativeEvent: { contentOffset: { y: 0 } } }))
|
||||
expect(fades()).toHaveLength(0)
|
||||
})
|
||||
@@ -9,42 +9,6 @@ vi.mock('react-native', () => ({
|
||||
vi.mock('lucide-react-native', () => ({ ImagePlus: 'ImagePlus', Mic: 'Mic' }))
|
||||
import { MobileTerminalInputActions } from './MobileTerminalInputActions'
|
||||
|
||||
it('removes the disabled microphone while keeping image attachment available', async () => {
|
||||
const attach = vi.fn()
|
||||
const props = {
|
||||
canSend: true,
|
||||
isAttaching: false,
|
||||
dictation: { isStarting: false, isRecording: false, isProcessing: false },
|
||||
dictationMode: 'toggle',
|
||||
dictationEnabled: false,
|
||||
buttonStyle: {},
|
||||
activeButtonStyle: {},
|
||||
disabledButtonStyle: {},
|
||||
onAttachImage: attach,
|
||||
onAttachFile: vi.fn(),
|
||||
onDictationToggle: vi.fn(),
|
||||
onDictationPressIn: vi.fn(),
|
||||
onDictationPressOut: vi.fn(),
|
||||
onDictationCancel: vi.fn()
|
||||
}
|
||||
let renderer!: ReturnType<typeof create>
|
||||
await act(async () => {
|
||||
renderer = create(createElement(MobileTerminalInputActions, props))
|
||||
})
|
||||
const buttons = () => renderer.root.findAll((node) => String(node.type) === 'Pressable')
|
||||
expect(buttons().map((b) => b.props.accessibilityLabel)).toEqual(['Attach a photo'])
|
||||
await act(async () => buttons()[0]!.props.onPress())
|
||||
expect(attach).toHaveBeenCalledOnce()
|
||||
await act(async () =>
|
||||
renderer.update(createElement(MobileTerminalInputActions, { ...props, dictationEnabled: true }))
|
||||
)
|
||||
expect(buttons().map((b) => b.props.accessibilityLabel)).toEqual([
|
||||
'Attach a photo',
|
||||
'Start voice dictation'
|
||||
])
|
||||
await act(async () => renderer.unmount())
|
||||
})
|
||||
|
||||
it('keeps the microphone able to stop a recording while the connection is down', async () => {
|
||||
const idle = { isStarting: false, isRecording: false, isProcessing: false }
|
||||
const props = {
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
import { createElement } from 'react'
|
||||
import { act, create } from 'react-test-renderer'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import type { ForceReconnect } from '../transport/rpc-client-context-contract'
|
||||
|
||||
vi.mock('react-native', () => ({ Platform: { OS: 'web' } }))
|
||||
vi.mock('../platform/use-reduced-motion', () => ({ useReducedMotionEnabled: () => null }))
|
||||
vi.mock('../layout/use-shell-side-rail', () => ({
|
||||
useShellSideRail: () => false,
|
||||
useDirectionPadAvailable: () => false
|
||||
}))
|
||||
|
||||
import { useMobileSessionPresentation } from './use-mobile-session-presentation'
|
||||
|
||||
type Presentation = ReturnType<typeof useMobileSessionPresentation>
|
||||
|
||||
function present(forceReconnectHost: ForceReconnect): Presentation {
|
||||
const scope = {
|
||||
created: undefined,
|
||||
worktreeId: 'wt-1',
|
||||
router: { setParams: () => {} },
|
||||
insets: { top: 0, bottom: 0, left: 0, right: 0 },
|
||||
connState: 'reconnecting',
|
||||
client: null,
|
||||
reconnectAttempts: 20,
|
||||
lastConnectedAt: null,
|
||||
terminalsLoaded: false,
|
||||
activeHandle: null,
|
||||
creating: false,
|
||||
creatingBrowser: false,
|
||||
creatingMarkdown: false,
|
||||
keyboardHeight: 0,
|
||||
terminalKeyboardMetrics: new Map(),
|
||||
toastOpacityRef: { current: 1 },
|
||||
hostEndpoint: null,
|
||||
initialSessionAutoCreateRef: { current: null },
|
||||
terminalFrameRef: { current: null },
|
||||
handleCreateTerminal: () => Promise.resolve(),
|
||||
visibleTabs: [],
|
||||
forceReconnectHost
|
||||
}
|
||||
type Scope = Parameters<typeof useMobileSessionPresentation>[0]
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the presentation hook reads only these members of the session scope; the rest of the controller is unreachable from it.
|
||||
const sessionScope = scope as unknown as Scope
|
||||
const read: { value: Presentation | null } = { value: null }
|
||||
function Probe(): null {
|
||||
read.value = useMobileSessionPresentation(sessionScope)
|
||||
return null
|
||||
}
|
||||
act(() => {
|
||||
create(createElement(Probe))
|
||||
})
|
||||
if (read.value === null) {
|
||||
throw new Error('the hook did not run')
|
||||
}
|
||||
return read.value
|
||||
}
|
||||
|
||||
describe("the session header's tap to retry", () => {
|
||||
it('is not offered on the page, and the status says what is wrong without promising a tap', () => {
|
||||
const { showConnectionRetry, connectionEscalated, terminalSummary } = present(null)
|
||||
expect({ showConnectionRetry, connectionEscalated, terminalSummary }).toEqual({
|
||||
showConnectionRetry: false,
|
||||
connectionEscalated: true,
|
||||
terminalSummary: "Can't reach desktop"
|
||||
})
|
||||
})
|
||||
|
||||
it('is still offered natively', () => {
|
||||
const { showConnectionRetry, connectionEscalated, terminalSummary } = present(() =>
|
||||
Promise.resolve()
|
||||
)
|
||||
expect({ showConnectionRetry, connectionEscalated, terminalSummary }).toEqual({
|
||||
showConnectionRetry: true,
|
||||
connectionEscalated: true,
|
||||
terminalSummary: "Can't reach desktop — tap to retry"
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -66,12 +66,6 @@ describe('mobile session route', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('omits an absent workspace name instead of sending an empty param', () => {
|
||||
expect(
|
||||
mobileSessionRouteTarget({ hostId: 'host-1', worktreeId: 'repo::/tmp/wt' }).params
|
||||
).toEqual({ hostId: 'host-1', worktreeId: 'repo::/tmp/wt' })
|
||||
})
|
||||
|
||||
it('mounts the host, then opens the session over its list', () => {
|
||||
const harness = navigationHarness({ index: 0, routes: [{ name: 'index' }] })
|
||||
const push = vi.fn()
|
||||
|
||||
@@ -25,17 +25,6 @@ function prompt(freeTextInput?: AgentJournalFreeTextInput): StructuredQuestionIt
|
||||
}
|
||||
|
||||
describe('mobile structured free-text questions', () => {
|
||||
it('projects editor input metadata onto the existing question card', () => {
|
||||
const freeTextInput = {
|
||||
allowEmpty: true,
|
||||
multiline: true,
|
||||
initialValue: 'draft',
|
||||
placeholder: 'Edit here'
|
||||
}
|
||||
expect(projectStructuredQuestion(prompt(freeTextInput))?.freeTextInput).toEqual(freeTextInput)
|
||||
expect(projectStructuredQuestion(prompt())?.freeTextInput).toBeUndefined()
|
||||
})
|
||||
|
||||
it('accepts empty and whitespace text only for the matching allowed prompt', () => {
|
||||
const current = prompt({ allowEmpty: true })
|
||||
const projected = projectStructuredQuestion(current)!
|
||||
|
||||
@@ -64,11 +64,4 @@ describe('useMobileNativeChatQueuedEditFocus', () => {
|
||||
})
|
||||
expect(focus).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('passes no Edit through when the cards offer none', async () => {
|
||||
await act(async () => {
|
||||
renderer = create(createElement(Harness, {}))
|
||||
})
|
||||
expect(latest!.editQueuedMessage).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,7 +2,6 @@ import { describe, expect, it } from 'vitest'
|
||||
import type { NativeChatMessage } from '../../../../src/shared/native-chat/native-chat-types'
|
||||
import {
|
||||
applyAppend,
|
||||
boundNativeChatWindow,
|
||||
createNativeChatMerger,
|
||||
mergeNativeChatMessages,
|
||||
replaceList
|
||||
@@ -54,13 +53,6 @@ describe('mobile stateful merger — oracle vs pure merge', () => {
|
||||
expect(after[after.length - 1]).toBe(tail)
|
||||
})
|
||||
|
||||
it('bounds the window to the most-recent tail', () => {
|
||||
const long = Array.from({ length: 10 }, (_, i) => msg(`m${i}`))
|
||||
expect(boundNativeChatWindow(long, 3).map((m) => m.id)).toEqual(['m7', 'm8', 'm9'])
|
||||
expect(boundNativeChatWindow(long, 0)).toBe(long)
|
||||
expect(boundNativeChatWindow(long, 20)).toBe(long)
|
||||
})
|
||||
|
||||
it('bounds live appends and rebuilds the cached indexes for the retained tail', () => {
|
||||
const merger = createNativeChatMerger()
|
||||
replaceList(merger, [msg('a'), msg('b'), msg('c')])
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
isNoiseMessage,
|
||||
stripNoiseMessages
|
||||
} from '../../../../src/shared/native-chat/native-chat-noise'
|
||||
import { isNoiseMessage } from '../../../../src/shared/native-chat/native-chat-noise'
|
||||
import type { NativeChatMessage } from '../../../../src/shared/native-chat/native-chat-types'
|
||||
|
||||
function msg(
|
||||
@@ -20,25 +17,6 @@ function msg(
|
||||
}
|
||||
|
||||
describe('isNoiseMessage', () => {
|
||||
it('flags task-notification and system-reminder user turns', () => {
|
||||
expect(isNoiseMessage(msg('user', '<task-notification>\n<task-id>x</task-id>'))).toBe(true)
|
||||
expect(isNoiseMessage(msg('user', '<system-reminder>\nbe careful'))).toBe(true)
|
||||
expect(
|
||||
isNoiseMessage(
|
||||
msg(
|
||||
'user',
|
||||
'Caveat: The messages below were generated by the user while running local commands'
|
||||
)
|
||||
)
|
||||
).toBe(true)
|
||||
expect(isNoiseMessage(msg('user', '[Request interrupted by user]'))).toBe(true)
|
||||
})
|
||||
|
||||
it('flags inter-agent harness turns (teammate/agent messages)', () => {
|
||||
expect(isNoiseMessage(msg('user', '<teammate-message>ping</teammate-message>'))).toBe(true)
|
||||
expect(isNoiseMessage(msg('user', '<agent-message>status update'))).toBe(true)
|
||||
})
|
||||
|
||||
it('keeps real user messages', () => {
|
||||
expect(isNoiseMessage(msg('user', 'make it work for codex'))).toBe(false)
|
||||
expect(isNoiseMessage(msg('user', '<bash-brothers> is my band'))).toBe(false)
|
||||
@@ -60,13 +38,4 @@ describe('isNoiseMessage', () => {
|
||||
).toBe(false)
|
||||
}
|
||||
})
|
||||
|
||||
it('stripNoiseMessages removes only the noise', () => {
|
||||
const out = stripNoiseMessages([
|
||||
msg('user', 'hello'),
|
||||
msg('user', '<task-notification>done'),
|
||||
msg('assistant', 'hi')
|
||||
])
|
||||
expect(out.map((m) => m.role)).toEqual(['user', 'assistant'])
|
||||
})
|
||||
})
|
||||
|
||||
+1
-9
@@ -1,7 +1,7 @@
|
||||
import { createElement } from 'react'
|
||||
import { act, create } from 'react-test-renderer'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { FONT_SCALE_STEP, quantizeFontScale } from './mobile-native-chat-message-text'
|
||||
import { quantizeFontScale } from './mobile-native-chat-message-text'
|
||||
|
||||
const handlers: { start?: () => void; update?: (e: { scale: number }) => void } = {}
|
||||
|
||||
@@ -34,14 +34,6 @@ describe('quantizeFontScale', () => {
|
||||
expect(quantizeFontScale(0.1)).toBe(0.8)
|
||||
expect(quantizeFontScale(Number.NaN)).toBe(1)
|
||||
})
|
||||
|
||||
it('collapses a full-range pinch to at most one commit per step', () => {
|
||||
const committed = new Set<number>()
|
||||
for (let i = 0; i <= 600; i++) {
|
||||
committed.add(quantizeFontScale(0.7 + i * 0.002))
|
||||
}
|
||||
expect(committed.size).toBeLessThanOrEqual(Math.round(1 / FONT_SCALE_STEP) + 1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('useMobileNativeChatPinchGesture', () => {
|
||||
|
||||
@@ -40,19 +40,7 @@ async function run(
|
||||
return shown
|
||||
}
|
||||
|
||||
const goneRow = { path: 'gone.txt', resolveActionId: 'resolve:gone.txt' }
|
||||
|
||||
describe('useMobileConflictRunners', () => {
|
||||
it('sends the chosen resolution for the row to the host', async () => {
|
||||
const sendGitRequest = vi.fn(async () => ({ ok: true }))
|
||||
const shown = await run(sendGitRequest, (r) => r.resolveConflict(goneRow, 'keep_deletion'))
|
||||
expect(shown).toBeNull()
|
||||
expect(sendGitRequest).toHaveBeenCalledWith('git.resolveConflict', {
|
||||
filePath: 'gone.txt',
|
||||
resolution: 'keep_deletion'
|
||||
})
|
||||
})
|
||||
|
||||
it('shows Git’s refusal when the host reports Complete merge failed in its reply', async () => {
|
||||
const sendGitRequest = vi.fn(async () => ({
|
||||
success: false,
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { TUI_AGENT_CONFIG } from '../../../src/shared/agent-launch/catalog/tui-agent-config'
|
||||
import { TUI_AGENT_DISPLAY_NAMES } from '../../../src/shared/agent-launch/catalog/tui-agent-display-names'
|
||||
import { TUI_AGENT_AUTO_PICK_ORDER } from '../../../src/shared/agent-launch/catalog/tui-agent-selection'
|
||||
import { MOBILE_AGENT_CATALOG } from './mobile-agent-catalog'
|
||||
|
||||
describe('mobile agent catalog', () => {
|
||||
it('follows desktop auto-pick order and covers every configured TUI agent', () => {
|
||||
expect(MOBILE_AGENT_CATALOG.map((agent) => agent.id)).toEqual([...TUI_AGENT_AUTO_PICK_ORDER])
|
||||
expect(new Set(MOBILE_AGENT_CATALOG.map((agent) => agent.id))).toEqual(
|
||||
new Set(Object.keys(TUI_AGENT_CONFIG))
|
||||
)
|
||||
})
|
||||
|
||||
it('labels every agent with the desktop display name', () => {
|
||||
for (const entry of MOBILE_AGENT_CATALOG) {
|
||||
expect(entry.label).toBe(TUI_AGENT_DISPLAY_NAMES[entry.id])
|
||||
}
|
||||
})
|
||||
|
||||
it('keeps Claude Agent Teams in the catalog without a favicon domain', () => {
|
||||
const teams = MOBILE_AGENT_CATALOG.find((agent) => agent.id === 'claude-agent-teams')
|
||||
expect(teams).toBeDefined()
|
||||
expect(teams).not.toHaveProperty('faviconDomain')
|
||||
})
|
||||
})
|
||||
@@ -1,33 +0,0 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import type { AgentLaunchExecution } from './agent-launch-executor'
|
||||
import type { AgentLaunchModeReceipt } from '../../shared/agent-launch/contracts/agent-launch-intent'
|
||||
import type { AgentLaunchSurfaceFactory } from './agent-launch-surface-factories'
|
||||
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: only the execution's type is checked; nothing runs it.
|
||||
const runtime = {} as AgentLaunchExecution['runtime']
|
||||
const decidedMode: AgentLaunchModeReceipt = {
|
||||
mode: 'terminal',
|
||||
preferred: 'terminal',
|
||||
reason: 'user_default',
|
||||
detail: 'Started a terminal agent, the default for new agent tabs in your settings.'
|
||||
}
|
||||
|
||||
describe('a caller-decided launch mode', () => {
|
||||
it('cannot be combined with the pre-flight inputs it replaces', () => {
|
||||
const intent = {
|
||||
agent: 'claude' as const,
|
||||
target: { kind: 'existing' as const, worktree: 'wt' }
|
||||
}
|
||||
const surfaces: AgentLaunchSurfaceFactory = {
|
||||
createStructuredSession: vi.fn(),
|
||||
createTerminalAgent: vi.fn()
|
||||
}
|
||||
const accepts = (execution: AgentLaunchExecution) => execution
|
||||
// @ts-expect-error a decided mode already settled what terminalOnly would force
|
||||
accepts({ runtime, intent, surfaces, decidedMode, terminalOnly: true })
|
||||
// @ts-expect-error a decided mode already settled what the caller's chat support would downgrade
|
||||
accepts({ runtime, intent, surfaces, decidedMode, callerRendersStructured: false })
|
||||
expect(accepts({ runtime, intent, surfaces, decidedMode }).decidedMode).toBe(decidedMode)
|
||||
expect(accepts({ runtime, intent, surfaces, terminalOnly: true }).terminalOnly).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -1,20 +1,7 @@
|
||||
import { describe, it, expect, vi, afterEach } from 'vitest'
|
||||
import { buildPrintToPdfOptions, CdpPdfStreamStore } from './cdp-print-to-pdf'
|
||||
|
||||
const DEFAULT_MARGIN_INCHES = 1 / 2.54
|
||||
import { CdpPdfStreamStore } from './cdp-print-to-pdf'
|
||||
const TTL_MS = 5 * 60 * 1000
|
||||
|
||||
describe('buildPrintToPdfOptions', () => {
|
||||
it('defaults omitted margin sides to 1cm when any side is set', () => {
|
||||
expect(buildPrintToPdfOptions({ marginTop: 0.25 }).margins).toEqual({
|
||||
top: 0.25,
|
||||
bottom: DEFAULT_MARGIN_INCHES,
|
||||
left: DEFAULT_MARGIN_INCHES,
|
||||
right: DEFAULT_MARGIN_INCHES
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('CdpPdfStreamStore', () => {
|
||||
afterEach(() => {
|
||||
vi.useRealTimers()
|
||||
|
||||
@@ -52,22 +52,6 @@ describe('clientRouteCookieImportSources', () => {
|
||||
expect(clientRouteCookieImportSources('env-a')).toEqual({ default: chromeSource })
|
||||
})
|
||||
|
||||
it('replaces the source on re-import of the same jar', () => {
|
||||
recordClientRouteCookieImportSource({
|
||||
environmentId: 'env-a',
|
||||
profileId: 'default',
|
||||
source: chromeSource
|
||||
})
|
||||
const edge = { browserFamily: 'edge' as const, importedAt: 2_000 }
|
||||
recordClientRouteCookieImportSource({
|
||||
environmentId: 'env-a',
|
||||
profileId: 'default',
|
||||
source: edge
|
||||
})
|
||||
|
||||
expect(clientRouteCookieImportSources('env-a')).toEqual({ default: edge })
|
||||
})
|
||||
|
||||
it('tolerates a corrupt store file by starting empty', () => {
|
||||
recordClientRouteCookieImportSource({
|
||||
environmentId: 'env-a',
|
||||
|
||||
@@ -67,11 +67,5 @@ describe('browserManager grab operations', () => {
|
||||
expect(selection.kind).toBe('cancelled')
|
||||
expect(selection.opId).toBe('op-1')
|
||||
})
|
||||
|
||||
it('returns false if injection fails', async () => {
|
||||
guestExecuteJavaScriptMock.mockRejectedValue(new Error('Injection failed'))
|
||||
const result = await browserManager.setGrabMode('tab-1', true, guest)
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
/** Launch mode follows settings, with explicit fallback reasons; unsupported preference must not refuse ordinary worker-start. */
|
||||
|
||||
import { EVERY_AGENT_CHAT } from '../../../../shared/native-chat/every-agent-chat.test-fixture'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
decideWorkerStartMode,
|
||||
downgradeWorkerStartModeForHost
|
||||
} from './orchestration-worker-start-mode'
|
||||
import type { AgentLaunchModeReceipt as WorkerStartModeReceipt } from '../../../../shared/agent-launch/contracts/agent-launch-intent'
|
||||
|
||||
const STRUCTURED_DEFAULT = {
|
||||
nativeChatProviders: EVERY_AGENT_CHAT
|
||||
}
|
||||
|
||||
function decide(
|
||||
overrides: {
|
||||
params?: Parameters<typeof decideWorkerStartMode>[0]['params']
|
||||
settings?: Parameters<typeof decideWorkerStartMode>[0]['settings']
|
||||
} = {}
|
||||
): WorkerStartModeReceipt {
|
||||
return decideWorkerStartMode({
|
||||
params: { agent: 'claude', ...overrides.params },
|
||||
settings: overrides.settings === undefined ? STRUCTURED_DEFAULT : overrides.settings
|
||||
})
|
||||
}
|
||||
|
||||
describe('worker start mode from the user default', () => {
|
||||
it.each(['claude', 'codex'] as const)('starts a local %s worker structured', (agent) => {
|
||||
expect(decide({ params: { agent } })).toMatchObject({
|
||||
mode: 'structured',
|
||||
preferred: 'structured',
|
||||
reason: 'user_default'
|
||||
})
|
||||
})
|
||||
|
||||
it.each([
|
||||
['the agent is set to CLI', { ...STRUCTURED_DEFAULT, nativeChatProviders: { claude: false } }],
|
||||
['the agent has no saved choice', {}],
|
||||
['no settings at all', null]
|
||||
])('starts a terminal worker when %s', (_name, settings) => {
|
||||
expect(decide({ settings })).toMatchObject({
|
||||
mode: 'terminal',
|
||||
preferred: 'terminal',
|
||||
reason: 'user_default'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('a structured default this dispatch cannot honour', () => {
|
||||
it.each([
|
||||
['a remote --on', { on: 'server-1' }, 'remote_execution_host'],
|
||||
['an existing --terminal', { terminal: 'term_1' }, 'reused_terminal'],
|
||||
['a non-structured agent', { agent: 'cursor' }, 'agent_without_structured_session']
|
||||
])('falls back to a terminal worker for %s', (_name, params, reason) => {
|
||||
const receipt = decide({ params: { agent: 'claude', ...params } })
|
||||
expect(receipt).toMatchObject({ mode: 'terminal', preferred: 'structured', reason })
|
||||
// Never a silent fallback: the receipt states the default AND why it did not apply.
|
||||
expect(receipt.detail).toContain('Your default is a structured chat session')
|
||||
})
|
||||
})
|
||||
|
||||
describe('the executing host settles what the client cannot', () => {
|
||||
it.each([
|
||||
['wsl', 'wsl_execution_runtime'],
|
||||
['remote', 'remote_execution_host'],
|
||||
['agent', 'structured_unsupported_on_host']
|
||||
] as const)('downgrades on a %s refusal', (reason, expected) => {
|
||||
expect(downgradeWorkerStartModeForHost(decide(), { supported: false, reason })).toMatchObject({
|
||||
mode: 'terminal',
|
||||
preferred: 'structured',
|
||||
reason: expected
|
||||
})
|
||||
})
|
||||
|
||||
it('downgrades on a refusal that names no reason', () => {
|
||||
expect(downgradeWorkerStartModeForHost(decide(), { supported: false }).reason).toBe(
|
||||
'structured_unsupported_on_host'
|
||||
)
|
||||
})
|
||||
})
|
||||
-98
@@ -1,98 +0,0 @@
|
||||
import React from 'react'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { BrowserAnnotationSendMenuContent } from './BrowserAnnotationSendMenuContent'
|
||||
|
||||
type ReactElementLike = {
|
||||
type: unknown
|
||||
props: Record<string, unknown>
|
||||
}
|
||||
|
||||
vi.mock('@/components/editor/ReviewNotesSendMenuContent', () => ({
|
||||
ReviewNotesSendMenuContent: function ReviewNotesSendMenuContent(props: Record<string, unknown>) {
|
||||
return { type: 'ReviewNotesSendMenuContent', props }
|
||||
}
|
||||
}))
|
||||
|
||||
function expand(node: unknown): unknown {
|
||||
if (node == null || typeof node === 'string' || typeof node === 'number') {
|
||||
return node
|
||||
}
|
||||
if (Array.isArray(node)) {
|
||||
return node.map((entry) => expand(entry))
|
||||
}
|
||||
if (!React.isValidElement(node)) {
|
||||
if (typeof node === 'object' && 'props' in node) {
|
||||
const element = node as ReactElementLike
|
||||
return {
|
||||
...element,
|
||||
props: {
|
||||
...element.props,
|
||||
children: expand(element.props.children)
|
||||
}
|
||||
}
|
||||
}
|
||||
return node
|
||||
}
|
||||
const element = node as React.ReactElement<Record<string, unknown>>
|
||||
if (typeof element.type === 'function') {
|
||||
const Component = element.type as (props: Record<string, unknown>) => unknown
|
||||
return expand(Component(element.props))
|
||||
}
|
||||
return {
|
||||
type: element.type,
|
||||
props: {
|
||||
...element.props,
|
||||
children: expand(element.props.children)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function findByType(node: unknown, type: string): ReactElementLike {
|
||||
if (node && typeof node === 'object' && 'type' in node) {
|
||||
const element = node as ReactElementLike
|
||||
if (element.type === type) {
|
||||
return element
|
||||
}
|
||||
const children = element.props.children
|
||||
if (Array.isArray(children)) {
|
||||
for (const child of children) {
|
||||
try {
|
||||
return findByType(child, type)
|
||||
} catch {
|
||||
// Continue searching siblings.
|
||||
}
|
||||
}
|
||||
} else if (children) {
|
||||
return findByType(children, type)
|
||||
}
|
||||
}
|
||||
throw new Error(`Unable to find ${type}`)
|
||||
}
|
||||
|
||||
describe('BrowserAnnotationSendMenuContent', () => {
|
||||
it('forwards annotation content and delivery callbacks to the review notes menu', () => {
|
||||
const onPromptDelivered = vi.fn()
|
||||
const onPromptHandedOff = vi.fn()
|
||||
const tree = expand(
|
||||
<BrowserAnnotationSendMenuContent
|
||||
worktreeId="wt-1"
|
||||
groupId="group-1"
|
||||
prompt="browser annotation prompt"
|
||||
includePageDetails={false}
|
||||
onIncludePageDetailsChange={vi.fn()}
|
||||
onPromptDelivered={onPromptDelivered}
|
||||
onPromptHandedOff={onPromptHandedOff}
|
||||
/>
|
||||
)
|
||||
|
||||
expect(findByType(tree, 'ReviewNotesSendMenuContent').props).toMatchObject({
|
||||
worktreeId: 'wt-1',
|
||||
groupId: 'group-1',
|
||||
prompt: 'browser annotation prompt',
|
||||
promptDelivery: 'submit-after-ready',
|
||||
launchSource: 'notes_send',
|
||||
onPromptDelivered,
|
||||
onPromptHandedOff
|
||||
})
|
||||
})
|
||||
})
|
||||
-18
@@ -49,24 +49,6 @@ describe('issue detail cache repository identity', () => {
|
||||
}
|
||||
)
|
||||
|
||||
it('keeps RPC detail caches bound to the addressed repository', () => {
|
||||
const sourceContext: TaskSourceContext = {
|
||||
kind: 'task-source',
|
||||
provider: 'github',
|
||||
projectId: 'project-1',
|
||||
hostId: 'runtime:env-1',
|
||||
repoId: 'runtime-repo'
|
||||
}
|
||||
const args = { ...keyArgs, ownerRepo: ORIGIN, sourceContext }
|
||||
|
||||
expect(getWorkItemDetailsCacheKey(args)).toBe(
|
||||
getWorkItemDetailsCacheKey({ ...args, issueSourcePreference: 'upstream' })
|
||||
)
|
||||
expect(getWorkItemDetailsCacheKey(args)).not.toBe(
|
||||
getWorkItemDetailsCacheKey({ ...args, ownerRepo: null })
|
||||
)
|
||||
})
|
||||
|
||||
it('invalidates both repository variants after a mutation', () => {
|
||||
const originKey = getWorkItemDetailsCacheKey({ ...keyArgs, ownerRepo: ORIGIN })
|
||||
const upstreamKey = getWorkItemDetailsCacheKey({ ...keyArgs, ownerRepo: UPSTREAM })
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import '@testing-library/jest-dom/vitest'
|
||||
|
||||
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { ZcodeCliSetupSection } from './ZcodeCliSetupSection'
|
||||
|
||||
const mocks = vi.hoisted(() => ({ settings: { agentCmdOverrides: { zcode: '' } } }))
|
||||
vi.mock('@/store/index', () => ({
|
||||
useAppStore: (selector: (state: unknown) => unknown) => selector(mocks)
|
||||
}))
|
||||
vi.mock('../../../onboarding/OnboardingInlineCommandTerminal', () => ({
|
||||
OnboardingInlineCommandTerminal: ({
|
||||
command,
|
||||
description
|
||||
}: {
|
||||
command: string
|
||||
description: string
|
||||
}) => (
|
||||
<div data-testid="setup-terminal">
|
||||
<output>{command}</output>
|
||||
<p>{description}</p>
|
||||
</div>
|
||||
)
|
||||
}))
|
||||
vi.mock('../../section-layout/SearchableSetting', () => ({
|
||||
SearchableSetting: ({ children }: { children: React.ReactNode }) => <div>{children}</div>
|
||||
}))
|
||||
|
||||
beforeEach(() => {
|
||||
mocks.settings.agentCmdOverrides.zcode = ''
|
||||
})
|
||||
afterEach(() => cleanup())
|
||||
|
||||
describe('ZCode CLI-owned sign-in setup', () => {
|
||||
it.each([
|
||||
['Z.AI browser sign-in', 'zcode login zai --no-browser'],
|
||||
['BigModel browser sign-in', 'zcode login bigmodel --no-browser'],
|
||||
['ZCode API-key setup', 'zcode']
|
||||
])('delegates %s to the supported upstream CLI command', async (label, command) => {
|
||||
render(<ZcodeCliSetupSection />)
|
||||
fireEvent.click(screen.getByRole('button', { name: label }))
|
||||
expect(await screen.findByTestId('setup-terminal')).toHaveTextContent(command)
|
||||
expect(screen.getByRole('button', { name: 'Z.AI browser sign-in' })).toBeDisabled()
|
||||
expect(screen.getByRole('button', { name: 'BigModel browser sign-in' })).toBeDisabled()
|
||||
expect(screen.getByRole('button', { name: 'ZCode API-key setup' })).toBeDisabled()
|
||||
if (label === 'ZCode API-key setup') {
|
||||
expect(screen.getByText(/enter \/login.*masked prompt/)).toBeInTheDocument()
|
||||
} else {
|
||||
expect(screen.getByText(/authorization URL printed by ZCode/)).toBeInTheDocument()
|
||||
}
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Close setup terminal' }))
|
||||
expect(screen.queryByTestId('setup-terminal')).not.toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: label })).toBeEnabled()
|
||||
})
|
||||
|
||||
it('preserves the configured CLI executable instead of inventing model flags', async () => {
|
||||
mocks.settings.agentCmdOverrides.zcode = 'node "C:\\ZCode CLI\\zcode.cjs"'
|
||||
render(<ZcodeCliSetupSection />)
|
||||
fireEvent.click(screen.getByRole('button', { name: 'BigModel browser sign-in' }))
|
||||
expect(await screen.findByTestId('setup-terminal')).toHaveTextContent(
|
||||
'node "C:\\ZCode CLI\\zcode.cjs" login bigmodel --no-browser'
|
||||
)
|
||||
expect(screen.getByTestId('setup-terminal')).not.toHaveTextContent('--model')
|
||||
})
|
||||
})
|
||||
@@ -2,11 +2,7 @@ import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { i18n } from '@/i18n/i18n'
|
||||
import es from '@/i18n/locales/es.json'
|
||||
import { keybindingFileDiagnostic } from '../../../../../shared/keybindings/keybinding-diagnostic-copy'
|
||||
import { normalizeKeybinding } from '../../../../../shared/keybindings/normalization'
|
||||
import {
|
||||
keybindingDiagnosticText,
|
||||
keybindingValidationErrorText
|
||||
} from './keybinding-diagnostic-text'
|
||||
import { keybindingDiagnosticText } from './keybinding-diagnostic-text'
|
||||
|
||||
const originalLanguage = i18n.language
|
||||
|
||||
@@ -15,31 +11,6 @@ afterEach(async () => {
|
||||
})
|
||||
|
||||
describe('keybinding diagnostic text', () => {
|
||||
it('translates a diagnostic and its nested Orca reason as whole sentences', async () => {
|
||||
i18n.addResourceBundle('es', 'translation', es)
|
||||
await i18n.changeLanguage('es')
|
||||
const invalid = normalizeKeybinding('Shift+P')
|
||||
if (invalid.ok) {
|
||||
throw new Error('expected a validation failure')
|
||||
}
|
||||
const reason = es.settings.shortcuts.validation.modifierRequired
|
||||
expect(keybindingValidationErrorText(invalid)).toBe(reason)
|
||||
const diagnostic = keybindingFileDiagnostic(
|
||||
{ severity: 'error', actionId: 'terminal.search' },
|
||||
'shortcutIgnored',
|
||||
{ actionId: 'terminal.search', reason: invalid.error },
|
||||
invalid.errorCode
|
||||
)
|
||||
expect(diagnostic.message).toBe(
|
||||
'Shortcut for "terminal.search" was ignored: Include at least one modifier key.'
|
||||
)
|
||||
expect(keybindingDiagnosticText(diagnostic)).toBe(
|
||||
es.settings.shortcuts.diagnostics.shortcutIgnored
|
||||
.replace('{{actionId}}', 'terminal.search')
|
||||
.replace('{{reason}}', reason)
|
||||
)
|
||||
})
|
||||
|
||||
it('keeps verbatim reasons and falls back to the English message', async () => {
|
||||
i18n.addResourceBundle('es', 'translation', es)
|
||||
await i18n.changeLanguage('es')
|
||||
|
||||
-13
@@ -32,19 +32,6 @@ function mount(initial: number | undefined = undefined): ReturnType<typeof vi.fn
|
||||
}
|
||||
|
||||
describe('terminal contrast modes', () => {
|
||||
it('lets users turn correction off and restore automatic without editing a number', () => {
|
||||
const persist = mount()
|
||||
expect(screen.getByRole('radio', { name: 'Automatic' }).getAttribute('aria-checked')).toBe(
|
||||
'true'
|
||||
)
|
||||
expect(screen.queryByRole('spinbutton')).toBeNull()
|
||||
fireEvent.click(screen.getByRole('radio', { name: 'Off' }))
|
||||
expect(persist).toHaveBeenLastCalledWith({ terminalMinimumContrastRatio: 1 })
|
||||
expect(screen.queryByRole('spinbutton')).toBeNull()
|
||||
fireEvent.click(screen.getByRole('radio', { name: 'Automatic' }))
|
||||
expect(persist).toHaveBeenLastCalledWith({ terminalMinimumContrastRatio: undefined })
|
||||
})
|
||||
|
||||
it('restores the custom target when toggling through off and automatic', () => {
|
||||
const persist = mount(7)
|
||||
fireEvent.click(screen.getByRole('radio', { name: 'Off' }))
|
||||
|
||||
@@ -45,21 +45,6 @@ describe('task page initial repo selection', () => {
|
||||
expect(reads).toBeLessThan(40_000)
|
||||
})
|
||||
|
||||
it('retains preferred selection, missing-ID fallback, and explicit empty defaults', () => {
|
||||
const repos: Repo[] = ['a', 'b'].map((id) => ({
|
||||
id,
|
||||
path: `/repos/${id}`,
|
||||
displayName: id,
|
||||
badgeColor: '',
|
||||
addedAt: 0,
|
||||
kind: 'git'
|
||||
}))
|
||||
expect(resolveSelection(repos, ['b'], 'a')).toEqual(['a'])
|
||||
expect(resolveSelection(repos, ['missing'])).toEqual(['a', 'b'])
|
||||
expect(resolveSelection(repos, [])).toEqual(['a', 'b'])
|
||||
expect(resolveSelection(repos, ['missing', 'b', 'b'])).toEqual(['b'])
|
||||
})
|
||||
|
||||
it('ignores persisted IDs for ineligible repos, including folder workspaces', () => {
|
||||
const repos: Repo[] = [
|
||||
{
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { waitForAgentReady } from './agent-ready-wait'
|
||||
import { useAppStore } from '@/store'
|
||||
import { inspectRuntimeTerminalProcess } from '@/runtime/runtime-terminal-inspection'
|
||||
|
||||
vi.mock('@/store', () => ({
|
||||
useAppStore: {
|
||||
getState: vi.fn()
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('@/runtime/runtime-terminal-inspection', () => ({
|
||||
inspectRuntimeTerminalProcess: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('../../../shared/shell-process-detection', () => ({
|
||||
isShellProcess: vi.fn(() => false)
|
||||
}))
|
||||
|
||||
describe('waitForAgentReady', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
vi.stubGlobal('window', { setTimeout })
|
||||
vi.mocked(useAppStore.getState).mockReturnValue({
|
||||
ptyIdsByTabId: { 'tab-1': ['pty-1'] },
|
||||
runtimePaneTitlesByTabId: {},
|
||||
tabsByWorktree: {},
|
||||
settings: {}
|
||||
} as never)
|
||||
})
|
||||
|
||||
it('recognizes a Windows foreground process reported as a full executable path', async () => {
|
||||
vi.mocked(inspectRuntimeTerminalProcess).mockResolvedValue({
|
||||
foregroundProcess: String.raw`C:\Users\dev\AppData\Roaming\npm\claude.exe`,
|
||||
hasChildProcesses: false
|
||||
})
|
||||
|
||||
await expect(waitForAgentReady('tab-1', 'claude', { timeoutMs: 100 })).resolves.toEqual({
|
||||
ready: true,
|
||||
reason: 'foreground-match'
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,8 +1,6 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { resetReactCommitCascadeTelemetryForTests } from './react-commit-cascade-telemetry'
|
||||
import {
|
||||
REACT_COMMIT_CASCADE_INSTALL_CHECK_MS,
|
||||
REACT_COMMIT_CASCADE_UNINSTALLED_BREADCRUMB,
|
||||
installReactCommitCascadeObserver,
|
||||
resetReactCommitCascadeObserverForTests
|
||||
} from './react-commit-cascade-observer'
|
||||
@@ -50,24 +48,3 @@ describe('installReactCommitCascadeObserver', () => {
|
||||
expect(previous).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
// Import/hoist changes can silently disable diagnostics; self-check exposes that in field reports.
|
||||
describe('install self-check', () => {
|
||||
// Failed hook assignments must arm self-check too.
|
||||
it('breadcrumbs when the hook refuses the callback assignment', () => {
|
||||
vi.useFakeTimers()
|
||||
resetReactCommitCascadeObserverForTests()
|
||||
const frozenHook = Object.freeze({ isDisabled: false, supportsFiber: true })
|
||||
;(globalThis as { __REACT_DEVTOOLS_GLOBAL_HOOK__?: unknown }).__REACT_DEVTOOLS_GLOBAL_HOOK__ =
|
||||
frozenHook
|
||||
|
||||
expect(() => installReactCommitCascadeObserver()).not.toThrow()
|
||||
vi.advanceTimersByTime(REACT_COMMIT_CASCADE_INSTALL_CHECK_MS)
|
||||
|
||||
expect((frozenHook as { onCommitFiberRoot?: unknown }).onCommitFiberRoot).toBeUndefined()
|
||||
expect(recordBreadcrumb).toHaveBeenCalledWith(
|
||||
REACT_COMMIT_CASCADE_UNINSTALLED_BREADCRUMB,
|
||||
undefined
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { create, type StoreApi, type UseBoundStore } from 'zustand'
|
||||
import { withReactCommitCascadeWriteProbe } from './react-commit-cascade-write-probe'
|
||||
import { withEditorBufferRecords } from './editor-buffer-record-lifecycle'
|
||||
import type { EditorBufferRecords } from './slices/editor/buffers/editor-buffer-record'
|
||||
import type { OpenFile } from './slices/editor/types/open-file'
|
||||
import type { ReactCommitCascadeWriteBoundary } from '@/lib/react-commit-cascade-store-write-samples'
|
||||
import {
|
||||
EMPTY_EDITOR_BUFFER_VIEW_INDEX,
|
||||
type EditorBufferViewIndex
|
||||
} from './slices/editor/buffers/editor-buffer-views'
|
||||
|
||||
const { probe, noteWrite } = vi.hoisted(() => ({
|
||||
probe: { armed: false },
|
||||
@@ -45,33 +38,4 @@ describe('withReactCommitCascadeWriteProbe', () => {
|
||||
expect(() => useProbedStore.setState({ ticks: 3 })).not.toThrow()
|
||||
expect(useProbedStore.getState().ticks).toBe(3)
|
||||
})
|
||||
|
||||
// Buffer middleware must preserve caller partials/write frames or cascade reports lose distinct write sites/keys.
|
||||
it('sees each original partial and caller under the editor buffer middleware', () => {
|
||||
type BufferedState = EditorBufferViewIndex & {
|
||||
ticks: number
|
||||
openFiles: OpenFile[]
|
||||
editorBuffers: EditorBufferRecords
|
||||
tick: () => void
|
||||
}
|
||||
const useBufferedStore = create<BufferedState>()(
|
||||
withEditorBufferRecords(
|
||||
withReactCommitCascadeWriteProbe<BufferedState>((set) => ({
|
||||
ticks: 0,
|
||||
openFiles: [],
|
||||
editorBuffers: {},
|
||||
...EMPTY_EDITOR_BUFFER_VIEW_INDEX,
|
||||
tick: () => set({ ticks: 1 })
|
||||
}))
|
||||
)
|
||||
)
|
||||
probe.armed = true
|
||||
|
||||
useBufferedStore.getState().tick()
|
||||
useBufferedStore.setState({ ticks: 2 })
|
||||
|
||||
expect(noteWrite.mock.calls.map(([, partial]) => partial)).toEqual([{ ticks: 1 }, { ticks: 2 }])
|
||||
expect(noteWrite.mock.calls[1]?.[0]).toBe(useBufferedStore.setState)
|
||||
expect(useBufferedStore.getState().ticks).toBe(2)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user