test: remove 128 low-value cases and 22 suites (#34206)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
OrcaWin
2026-10-26 22:21:44 -07:00
committed by GitHub
co-authored by OrcaWin
parent 577af08b12
commit a8f7afe84b
61 changed files with 10 additions and 2093 deletions
-22
View File
@@ -7326,7 +7326,6 @@
"src/renderer/src/components/confirmation-dialog.test.tsx": 979,
"src/renderer/src/components/confirmation-skip-preference.test.ts": 141,
"src/renderer/src/components/connect-dialog-outside-dismiss.test.tsx": 835,
"src/renderer/src/components/contextual-tours/ContextualTourControl.test.ts": 145,
"src/renderer/src/components/contextual-tours/ContextualTourOverlay.remeasure.test.tsx": 2292,
"src/renderer/src/components/contextual-tours/ContextualTourOverlay.test.tsx": 1598,
"src/renderer/src/components/contextual-tours/ContextualTourOverlay.visibility.test.tsx": 1302,
@@ -7349,7 +7348,6 @@
"src/renderer/src/components/dashboard-popout/AgentTerminalPreview.settings-geometry.test.tsx": 1712,
"src/renderer/src/components/dashboard-popout/AgentTerminalPreview.settings-lifetime.test.tsx": 1564,
"src/renderer/src/components/dashboard-popout/AgentTerminalPreview.test.tsx": 2249,
"src/renderer/src/components/dashboard-popout/DashboardHostBadge.test.tsx": 599,
"src/renderer/src/components/dashboard-popout/DashboardPopoutRoot.test.tsx": 271,
"src/renderer/src/components/dashboard-popout/agent-board-filtering.test.ts": 50,
"src/renderer/src/components/dashboard-popout/dashboard-agent-status-patch.test.ts": 59,
@@ -7464,7 +7462,6 @@
"src/renderer/src/components/editor/check-annotation-path.test.ts": 30,
"src/renderer/src/components/editor/check-details-reading-viewport.test.ts": 192,
"src/renderer/src/components/editor/check-job-step-status.test.ts": 31,
"src/renderer/src/components/editor/check-run-clipboard-text.test.ts": 56,
"src/renderer/src/components/editor/check-run-details-fix-with-ai.test.ts": 674,
"src/renderer/src/components/editor/check-run-details-tab.test.ts": 46,
"src/renderer/src/components/editor/closed-editor-model-lifetime.test.ts": 7598,
@@ -7492,7 +7489,6 @@
"src/renderer/src/components/editor/combined-diff/resolve-changes/combined-diff-section-cache-match.test.ts": 41,
"src/renderer/src/components/editor/combined-diff/resolve-changes/combined-diff-section-scaling.test.tsx": 377,
"src/renderer/src/components/editor/combined-diff/resolve-changes/use-combined-diff-section-index-map.test.tsx": 237,
"src/renderer/src/components/editor/combined-diff/review-controls/combined-diff-commit-message.test.ts": 55,
"src/renderer/src/components/editor/combined-diff/review-controls/use-combined-diff-section-actions.disk-precondition.test.tsx": 1206,
"src/renderer/src/components/editor/combined-diff/scroll-viewport/combined-diff-render-range.test.ts": 48,
"src/renderer/src/components/editor/combined-diff/scroll-viewport/combined-diff-restore-signal-equivalence.test.tsx": 350,
@@ -7639,7 +7635,6 @@
"src/renderer/src/components/editor/markdown-tokenizer-start.test.ts": 68,
"src/renderer/src/components/editor/mermaid-config.test.ts": 579,
"src/renderer/src/components/editor/monaco-auto-height.test.ts": 110,
"src/renderer/src/components/editor/monaco-codebase-search.test.ts": 60,
"src/renderer/src/components/editor/monaco-conflict-decorations.test.ts": 41,
"src/renderer/src/components/editor/monaco-content-sync.test.ts": 33,
"src/renderer/src/components/editor/monaco-content-sync.undo-history.test.ts": 996,
@@ -7873,7 +7868,6 @@
"src/renderer/src/components/host-picker/host-list-surfaces.large-config.test.tsx": 1713,
"src/renderer/src/components/jira-attachment-image.test.tsx": 1521,
"src/renderer/src/components/jira-connect-scoped-token.test.tsx": 492,
"src/renderer/src/components/jira-create-adf.test.ts": 27,
"src/renderer/src/components/jira-issue-assignee-control.test.tsx": 2380,
"src/renderer/src/components/jira-issue-transition-control.test.tsx": 651,
"src/renderer/src/components/jira-issue-workspace-actions.test.ts": 123,
@@ -8038,7 +8032,6 @@
"src/renderer/src/components/native-chat/StructuredAgentSessionStatusBridge.test.tsx": 1855,
"src/renderer/src/components/native-chat/agent-session-failure-words-text.test.ts": 875,
"src/renderer/src/components/native-chat/agent-session-write-notice-text.test.ts": 935,
"src/renderer/src/components/native-chat/background-task-words-text.test.ts": 151,
"src/renderer/src/components/native-chat/claude-model-switch-confirmation.test.ts": 1546,
"src/renderer/src/components/native-chat/claude-terminal-session-options.test.ts": 62,
"src/renderer/src/components/native-chat/native-chat-active-rail-item.test.ts": 31,
@@ -8069,7 +8062,6 @@
"src/renderer/src/components/native-chat/native-chat-composer-upload-completion.test.tsx": 2200,
"src/renderer/src/components/native-chat/native-chat-composer-workspace-file-drop.test.tsx": 662,
"src/renderer/src/components/native-chat/native-chat-context-command.test.tsx": 342,
"src/renderer/src/components/native-chat/native-chat-context-usage-summary.test.ts": 33,
"src/renderer/src/components/native-chat/native-chat-diff.test.ts": 68,
"src/renderer/src/components/native-chat/native-chat-dismiss-key.test.ts": 40,
"src/renderer/src/components/native-chat/native-chat-draft-cache.test.ts": 153,
@@ -8140,7 +8132,6 @@
"src/renderer/src/components/native-chat/native-chat-structured-session-notices.command-failure.test.tsx": 1670,
"src/renderer/src/components/native-chat/native-chat-structured-session-notices.create-message.test.tsx": 1828,
"src/renderer/src/components/native-chat/native-chat-subagent-delegation.test.ts": 45,
"src/renderer/src/components/native-chat/native-chat-subagent-group-words-text.test.ts": 189,
"src/renderer/src/components/native-chat/native-chat-subagent-handoff-copy-budget.test.ts": 521,
"src/renderer/src/components/native-chat/native-chat-subagent-section-work-runs.test.ts": 202,
"src/renderer/src/components/native-chat/native-chat-subagent-sections-retention.test.ts": 609,
@@ -8340,7 +8331,6 @@
"src/renderer/src/components/pet/PetOverlay.keyframes.test.tsx": 620,
"src/renderer/src/components/pet/PetOverlay.pet-switch.test.tsx": 185,
"src/renderer/src/components/pet/PetOverlay.pointer-interaction.test.tsx": 195,
"src/renderer/src/components/pet/PetOverlay.test.ts": 306,
"src/renderer/src/components/pet/pet-blob-cache.test.ts": 55,
"src/renderer/src/components/pet/pet-overlay-hit-area.test.tsx": 228,
"src/renderer/src/components/pet/pet-overlay-visibility.test.ts": 46,
@@ -8377,7 +8367,6 @@
"src/renderer/src/components/right-sidebar/AiVaultPanel.legacy-filter.test.tsx": 2390,
"src/renderer/src/components/right-sidebar/AiVaultPanelHeader.focus.test.tsx": 386,
"src/renderer/src/components/right-sidebar/AiVaultPanelSearch.test.tsx": 469,
"src/renderer/src/components/right-sidebar/AiVaultScanIssueBanners.test.tsx": 333,
"src/renderer/src/components/right-sidebar/AiVaultScanProgressStrip.test.tsx": 732,
"src/renderer/src/components/right-sidebar/AiVaultSearchEvidence.test.tsx": 2230,
"src/renderer/src/components/right-sidebar/AiVaultSessionActionMenuItems.test.tsx": 663,
@@ -8396,12 +8385,10 @@
"src/renderer/src/components/right-sidebar/CommitArea.generate.test.tsx": 1781,
"src/renderer/src/components/right-sidebar/CommitArea.test.tsx": 8618,
"src/renderer/src/components/right-sidebar/FileExplorerFilesTreePane.errors.test.tsx": 3001,
"src/renderer/src/components/right-sidebar/FileExplorerNameFilter.test.tsx": 197,
"src/renderer/src/components/right-sidebar/FileExplorerQueryStrip.focus.test.tsx": 530,
"src/renderer/src/components/right-sidebar/FileExplorerRow.actions.test.tsx": 287,
"src/renderer/src/components/right-sidebar/FileExplorerScopeNotice.test.tsx": 480,
"src/renderer/src/components/right-sidebar/FileExplorerToolbar.test.tsx": 1475,
"src/renderer/src/components/right-sidebar/FileExplorerViewSwitch.test.tsx": 133,
"src/renderer/src/components/right-sidebar/FileExplorerVirtualRows.row-handlers.test.tsx": 2029,
"src/renderer/src/components/right-sidebar/FileExplorerVirtualRowsAddProject.test.tsx": 1985,
"src/renderer/src/components/right-sidebar/FolderWorkspacePrChecksPanel.test.tsx": 809,
@@ -8511,7 +8498,6 @@
"src/renderer/src/components/right-sidebar/file-explorer-directory-listing.test.ts": 1367,
"src/renderer/src/components/right-sidebar/file-explorer-display-root.test.ts": 1535,
"src/renderer/src/components/right-sidebar/file-explorer-drag-scroll-marker.test.tsx": 2184,
"src/renderer/src/components/right-sidebar/file-explorer-entries.test.ts": 46,
"src/renderer/src/components/right-sidebar/file-explorer-expanded-dirs-refresh.test.ts": 1349,
"src/renderer/src/components/right-sidebar/file-explorer-inline-input-ime-enter.test.tsx": 465,
"src/renderer/src/components/right-sidebar/file-explorer-inline-input-outside-click.test.tsx": 197,
@@ -8557,7 +8543,6 @@
"src/renderer/src/components/right-sidebar/plugin-panel-watchdog-visibility.test.ts": 457,
"src/renderer/src/components/right-sidebar/plugin-panel-watchdog.test.ts": 131,
"src/renderer/src/components/right-sidebar/ports-panel-host.test.ts": 182,
"src/renderer/src/components/right-sidebar/pr-comment-ack-summary.test.ts": 36,
"src/renderer/src/components/right-sidebar/pr-comment-snapshotted-thread-resolver.test.ts": 63,
"src/renderer/src/components/right-sidebar/pr-comment-thread-resolution.test.ts": 29,
"src/renderer/src/components/right-sidebar/pr-comments-ai-launch-ack.test.ts": 75,
@@ -8566,7 +8551,6 @@
"src/renderer/src/components/right-sidebar/review-cache-entry-selection.test.ts": 24,
"src/renderer/src/components/right-sidebar/right-panel-comment-composer.ime-enter.test.tsx": 273,
"src/renderer/src/components/right-sidebar/right-panel-comment-focus-timers.test.ts": 82,
"src/renderer/src/components/right-sidebar/right-sidebar-activity-visibility.test.ts": 77,
"src/renderer/src/components/right-sidebar/right-sidebar-effective-tab.test.ts": 34,
"src/renderer/src/components/right-sidebar/right-sidebar-titlebar-drag-regions.render.test.tsx": 591,
"src/renderer/src/components/right-sidebar/right-sidebar-width.test.ts": 61,
@@ -8590,7 +8574,6 @@
"src/renderer/src/components/right-sidebar/source-control-entry-context-menu.test.tsx": 367,
"src/renderer/src/components/right-sidebar/source-control-file-filter.test.ts": 46,
"src/renderer/src/components/right-sidebar/source-control-header-toolbar-identity.test.tsx": 346,
"src/renderer/src/components/right-sidebar/source-control-header-toolbar.test.ts": 229,
"src/renderer/src/components/right-sidebar/source-control-hosted-review-creation-eligibility-snapshot.test.ts": 87,
"src/renderer/src/components/right-sidebar/source-control-hosted-review-push-target.test.ts": 52,
"src/renderer/src/components/right-sidebar/source-control-huge-repo-warning-dismissals.test.ts": 150,
@@ -8813,7 +8796,6 @@
"src/renderer/src/components/settings/TerminalAdvancedSection.test.tsx": 2069,
"src/renderer/src/components/settings/TerminalAppearanceSection.ghostty.test.ts": 490,
"src/renderer/src/components/settings/TerminalContrastSetting.test.tsx": 526,
"src/renderer/src/components/settings/TerminalFontSizeSetting.test.tsx": 1623,
"src/renderer/src/components/settings/TerminalPane.pwsh.test.ts": 530,
"src/renderer/src/components/settings/TerminalPane.section-lifetime.test.tsx": 757,
"src/renderer/src/components/settings/TerminalSettingsPreview.lifecycle.test.tsx": 318,
@@ -9105,7 +9087,6 @@
"src/renderer/src/components/sidebar/project-header-drag-start.test.ts": 147,
"src/renderer/src/components/sidebar/project-header-drop.test.ts": 212,
"src/renderer/src/components/sidebar/project-move-pause-follower.test.ts": 563,
"src/renderer/src/components/sidebar/project-order-manual-default-notice-visibility.test.ts": 34,
"src/renderer/src/components/sidebar/prompt-cache-countdown-clock.test.ts": 62,
"src/renderer/src/components/sidebar/remote-file-browser-drive-paths.test.ts": 47,
"src/renderer/src/components/sidebar/remote-file-browser-helpers.test.ts": 174,
@@ -9197,7 +9178,6 @@
"src/renderer/src/components/sidebar/worktree-card-compact-agent-row.stable-message.test.tsx": 2596,
"src/renderer/src/components/sidebar/worktree-card-details-hover-state.test.tsx": 166,
"src/renderer/src/components/sidebar/worktree-card-dom-events.test.ts": 115,
"src/renderer/src/components/sidebar/worktree-card-jira-issue-display.test.ts": 48,
"src/renderer/src/components/sidebar/worktree-card-option-items.test.tsx": 266,
"src/renderer/src/components/sidebar/worktree-card-pr-display.test.ts": 77,
"src/renderer/src/components/sidebar/worktree-card-send-target-inputs.test.ts": 26,
@@ -9583,7 +9563,6 @@
"src/renderer/src/components/task-page/jira/IssueAssigneeField.test.tsx": 1003,
"src/renderer/src/components/task-page/jira/IssueUserField.test.tsx": 2183,
"src/renderer/src/components/task-page/linear/task-page-linear-in-orca-issues.test.ts": 98,
"src/renderer/src/components/task-page/linear/task-page-linear-issue-empty-state.test.ts": 36,
"src/renderer/src/components/task-page/linear/task-page-linear-issue-grouping.test.ts": 1441,
"src/renderer/src/components/task-page/linear/task-page-linear-issue-request.test.ts": 57,
"src/renderer/src/components/task-page/linear/task-page-linear-team-selection.test.ts": 46,
@@ -10255,7 +10234,6 @@
"src/renderer/src/components/workspace-cleanup/workspace-cleanup-confirm-remove.test.tsx": 658,
"src/renderer/src/components/workspace-cleanup/workspace-cleanup-confirm-stop-agents.test.tsx": 343,
"src/renderer/src/components/workspace-cleanup/workspace-cleanup-deletion-phases.test.ts": 127,
"src/renderer/src/components/workspace-cleanup/workspace-cleanup-dialog-notices.test.tsx": 571,
"src/renderer/src/components/workspace-cleanup/workspace-cleanup-facet-controls.test.tsx": 601,
"src/renderer/src/components/workspace-cleanup/workspace-cleanup-facet-filter.test.ts": 107,
"src/renderer/src/components/workspace-cleanup/workspace-cleanup-facet-sort.test.ts": 127,
-24
View File
@@ -6,26 +6,6 @@ import {
} from './synthetic-title-spinner'
describe('advanceSyntheticTitleSpinnerEntries', () => {
it('advances live panes in one shared tick and wraps frames', () => {
const entries = new Map<string, SyntheticTitleSpinnerEntry<{ label: string }>>([
['pane-a', { frame: 1, profile: { label: 'A' } }],
['pane-b', { frame: 2, profile: { label: 'B' } }]
])
const ticks = advanceSyntheticTitleSpinnerEntries({
entries,
frameCount: 3,
getPtyIdForPaneKey: (paneKey) => `pty-${paneKey}`
})
expect(ticks).toEqual([
{ paneKey: 'pane-a', ptyId: 'pty-pane-a', frame: 2, profile: { label: 'A' } },
{ paneKey: 'pane-b', ptyId: 'pty-pane-b', frame: 0, profile: { label: 'B' } }
])
expect(entries.get('pane-a')?.frame).toBe(2)
expect(entries.get('pane-b')?.frame).toBe(0)
})
it('drops panes whose pty mapping disappeared', () => {
const entries = new Map<string, SyntheticTitleSpinnerEntry<{ label: string }>>([
['live-pane', { frame: 0, profile: { label: 'live' } }],
@@ -47,10 +27,6 @@ describe('advanceSyntheticTitleSpinnerEntries', () => {
})
describe('getSyntheticTitleSpinnerPaneKeyToStop', () => {
it('retires the spinner of a pane-scoped clear', () => {
expect(getSyntheticTitleSpinnerPaneKeyToStop({ paneKey: 'pane-a' })).toBe('pane-a')
})
it('keeps pane spinners running for a connection-scoped transient clear', () => {
expect(
getSyntheticTitleSpinnerPaneKeyToStop({
-8
View File
@@ -26,14 +26,6 @@ function fakeTemplate(width: number, height: number) {
}
describe('stampTrayDevBadge', () => {
it('returns the base unchanged when it has no pixels', () => {
createFromBitmapMock.mockClear()
const base = { getSize: () => ({ width: 0, height: 0 }), toBitmap: () => Buffer.alloc(0) }
expect(stampTrayDevBadge(base as never)).toBe(base)
expect(createFromBitmapMock).not.toHaveBeenCalled()
})
it('keeps the template canvas size and stamps opaque template-black pixels', () => {
createFromBitmapMock.mockClear()
const width = 22
@@ -31,17 +31,4 @@ describe('manual theme ordering', () => {
vi.restoreAllMocks()
}
})
it('returns a single dialog selection without collating', () => {
const construct = vi.spyOn(Intl, 'Collator')
try {
expect(createManualWarpThemeFileCandidates([]).map((file) => file.path)).toEqual([])
expect(createManualWarpThemeFileCandidates(['a/one.yaml']).map((file) => file.path)).toEqual([
'a/one.yaml'
])
expect(construct).not.toHaveBeenCalled()
} finally {
vi.restoreAllMocks()
}
})
})
@@ -6,23 +6,6 @@ import {
} from './comment-code-context-state'
describe('comment code context expansion state', () => {
it('keeps expanded context for the same comment', () => {
const expanded = updateCommentCodeContextExpansionState(
createCommentCodeContextExpansionState('comment-1'),
'comment-1',
{
contextBefore: 5,
contextAfter: 3
}
)
expect(resolveCommentCodeContextExpansionState(expanded, 'comment-1')).toEqual({
commentId: 'comment-1',
contextBefore: 5,
contextAfter: 3
})
})
it('resets expansion when a rendered row switches comments', () => {
const expanded = updateCommentCodeContextExpansionState(
createCommentCodeContextExpansionState('comment-1'),
@@ -1,55 +0,0 @@
import { describe, expect, it, vi, afterEach } from 'vitest'
import { toggleAutoRenameBranchFromWork } from './ContextualTourControl'
import { CONTEXTUAL_TOUR_ENABLE_AUTO_WORKSPACE_NAME_EVENT } from './contextual-tour-composer-events'
describe('toggleAutoRenameBranchFromWork', () => {
it('clears the generated workspace name when enabling first-message auto-name', () => {
const updateSettings = vi.fn()
const dispatchEvent = vi.fn()
toggleAutoRenameBranchFromWork({
enabled: false,
updateSettings,
dispatchEvent
})
expect(updateSettings).toHaveBeenCalledWith({ autoRenameBranchFromWork: true })
expect(dispatchEvent).toHaveBeenCalledTimes(1)
expect(dispatchEvent.mock.calls[0]?.[0]).toMatchObject({
type: CONTEXTUAL_TOUR_ENABLE_AUTO_WORKSPACE_NAME_EVENT
})
})
it('does not clear the workspace name when disabling first-message auto-name', () => {
const updateSettings = vi.fn()
const dispatchEvent = vi.fn()
toggleAutoRenameBranchFromWork({
enabled: true,
updateSettings,
dispatchEvent
})
expect(updateSettings).toHaveBeenCalledWith({ autoRenameBranchFromWork: false })
expect(dispatchEvent).not.toHaveBeenCalled()
})
})
const { rejectAppStoreAccess } = vi.hoisted(() => ({
rejectAppStoreAccess: vi.fn(() => {
throw new Error('Pure helper tests must not access the app store')
})
}))
vi.mock('@/store', () => ({
useAppStore: Object.assign(rejectAppStoreAccess, {
getState: rejectAppStoreAccess,
getInitialState: rejectAppStoreAccess,
setState: rejectAppStoreAccess,
subscribe: rejectAppStoreAccess
})
}))
afterEach(() => {
expect(rejectAppStoreAccess).not.toHaveBeenCalled()
})
@@ -1,54 +0,0 @@
// @vitest-environment happy-dom
import '@testing-library/jest-dom/vitest'
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
import { afterEach, describe, expect, it } from 'vitest'
import { TooltipProvider } from '@/components/ui/tooltip'
import { DashboardHostBadge } from './DashboardHostBadge'
afterEach(cleanup)
describe('DashboardHostBadge', () => {
it('names a saved SSH host in its focusable tooltip', async () => {
render(
<TooltipProvider>
<DashboardHostBadge
hostKind="ssh"
executionHostId="ssh:opaque-target"
hostLabel="openclaw"
keyboardFocusable
/>
</TooltipProvider>
)
const badge = screen.getByLabelText('SSH host · openclaw')
expect(badge).toHaveAttribute('data-dashboard-host-badge', 'ssh')
expect(badge.querySelector('.lucide-server')).toBeInTheDocument()
fireEvent.focus(badge)
expect(await screen.findByRole('tooltip')).toHaveTextContent('SSH host · openclaw')
})
it('distinguishes paired Orca hosts and omits local hosts', () => {
const { rerender } = render(
<TooltipProvider>
<DashboardHostBadge
hostKind="remote"
executionHostId="runtime:server-1"
hostLabel="Build Mac"
/>
</TooltipProvider>
)
const badge = screen.getByLabelText('Remote Orca host · Build Mac')
expect(badge).toHaveAttribute('data-dashboard-host-badge', 'remote')
expect(badge.querySelector('.lucide-server')).toBeInTheDocument()
rerender(
<TooltipProvider>
<DashboardHostBadge hostKind="local" executionHostId="local" />
</TooltipProvider>
)
expect(screen.queryByLabelText(/host/i)).not.toBeInTheDocument()
})
})
@@ -1,8 +1,7 @@
import { describe, expect, it } from 'vitest'
import {
analyzeDictationAudioChunk,
createDictationMeterAnalyzerState,
toPublicDictationMeterState
createDictationMeterAnalyzerState
} from './dictation-audio-meter'
describe('dictation audio meter', () => {
@@ -27,18 +26,4 @@ describe('dictation audio meter', () => {
expect(held.isClipping).toBe(true)
expect(released.isClipping).toBe(false)
})
it('publishes only presentation-safe meter fields', () => {
const analyzed = analyzeDictationAudioChunk(
new Float32Array(128).fill(0.25),
250,
createDictationMeterAnalyzerState()
)
expect(toPublicDictationMeterState(analyzed)).toEqual({
level: Math.round(analyzed.level * 100) / 100,
isSpeaking: analyzed.isSpeaking,
isClipping: analyzed.isClipping
})
})
})
@@ -38,13 +38,4 @@ describe('CheckRunCopyButton', () => {
)
expect(await screen.findByRole('button', { name: 'Copied' })).toBeDefined()
})
it('disables the action when there is no card text', () => {
renderCopyButton('')
const copyButton = screen.getByRole('button', { name: 'Nothing to copy' })
expect((copyButton as HTMLButtonElement).disabled).toBe(true)
fireEvent.click(copyButton)
expect(writeClipboardText).not.toHaveBeenCalled()
})
})
@@ -1,116 +0,0 @@
import { describe, expect, it, vi } from 'vitest'
import { normalizeCheckRunDetailsLogTails } from '@/lib/check-run-details-log-normalization'
import * as sanitation from '../../../../shared/check-job-log-excerpt'
import {
captured,
checkLogDetails,
checkLogJob
} from '@/lib/check-run-details-log-normalization-test-fixture'
import type { PRCheckAnnotation, PRCheckJob } from '../../../../shared/github/check-types'
import {
formatAnnotationsForClipboard,
formatCheckRunOutputForClipboard,
formatJobsForClipboard
} from './check-run-clipboard-text'
describe('check run clipboard text', () => {
it('formats the already normalized captured diagnostic and stack without sanitizing again', () => {
const sanitize = vi.spyOn(sanitation, 'sanitizeCheckJobLogExcerpt')
try {
const loaded = normalizeCheckRunDetailsLogTails(
checkLogDetails([checkLogJob(captured.rawLog)])
)
if (!loaded) {
throw new Error('Expected details')
}
const copied = formatJobsForClipboard(loaded.jobs, 'unknown')
expect(copied).toContain('job-1: failure\nTest shard: failure\n')
expect(copied).toContain(captured.expectedDiagnostic)
expect(copied).toContain(captured.expectedStackFrame)
expect(copied).not.toContain('\x1b')
expect(sanitize).toHaveBeenCalledOnce()
} finally {
sanitize.mockRestore()
}
})
it('formats all output sections in display order', () => {
expect(
formatCheckRunOutputForClipboard({
title: 'Build failed',
summary: 'One test failed.',
text: 'Run the suite locally.'
})
).toBe('Build failed\n\nOne test failed.\n\nRun the suite locally.')
expect(
formatCheckRunOutputForClipboard({ title: null, summary: 'Summary only', text: null })
).toBe('Summary only')
})
it('formats annotations with localized missing-path labels and separators', () => {
const annotations: PRCheckAnnotation[] = [
{
path: 'src/main.ts',
startLine: 10,
endLine: 10,
annotationLevel: 'failure',
title: 'Type error',
message: 'Expected string',
rawDetails: 'Actual: number'
},
{
path: null,
startLine: null,
endLine: null,
annotationLevel: null,
title: null,
message: 'Workflow failed',
rawDetails: null
}
]
expect(formatAnnotationsForClipboard(annotations, 'Anotacion')).toBe(
'src/main.ts:10\nfailure\nType error\nExpected string\nActual: number\n\nAnotacion\nWorkflow failed'
)
})
it('formats jobs with localized unknown states and log excerpts', () => {
const jobs: PRCheckJob[] = [
{
id: 1,
name: 'unit',
status: null,
conclusion: 'failure',
startedAt: null,
completedAt: null,
url: null,
logTail: 'AssertionError: expected true',
steps: [
{
name: 'test',
status: null,
conclusion: null,
startedAt: null,
completedAt: null
}
]
},
{
id: 2,
name: 'deploy',
status: null,
conclusion: null,
startedAt: null,
completedAt: null,
url: null,
logTail: null,
steps: []
}
]
expect(formatJobsForClipboard(jobs, 'desconocido')).toBe(
'unit: failure\ntest: desconocido\nAssertionError: expected true\n\ndeploy: desconocido'
)
})
})
@@ -1,44 +0,0 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import { getCombinedDiffCommitMessageBody } from './combined-diff-commit-message'
afterEach(() => {
vi.restoreAllMocks()
})
describe('getCombinedDiffCommitMessageBody', () => {
it('removes the commit subject from the returned body', () => {
expect(getCombinedDiffCommitMessageBody('Fix tests\n\nBody line', 'Fix tests')).toBe(
'Body line'
)
})
it('normalizes CRLF before matching the subject and body', () => {
expect(getCombinedDiffCommitMessageBody('Fix tests\r\n\r\nBody line', 'Fix tests')).toBe(
'Body line'
)
})
it('normalizes newline-heavy CRLF bodies without global replace or split', () => {
const replace = vi.spyOn(String.prototype, 'replace')
const split = vi.spyOn(String.prototype, 'split')
const message = `Fix tests\r\n\r\n${'Body line\r\n'.repeat(10_000)}`
const result = getCombinedDiffCommitMessageBody(message, 'Fix tests')
expect(result.startsWith('Body line\nBody line')).toBe(true)
expect(result).not.toContain('\r\n')
const usedCrlfReplace = replace.mock.calls.some(
([pattern]) => pattern instanceof RegExp && pattern.source === '\\r\\n'
)
expect(usedCrlfReplace).toBe(false)
expect(split).not.toHaveBeenCalled()
})
it('keeps messages without a matching subject and avoids splitting large bodies', () => {
const split = vi.spyOn(String.prototype, 'split')
const message = `Subject\n${'body\n'.repeat(10_000)}`
expect(getCombinedDiffCommitMessageBody(message, 'Other subject')).toBe(message.trim())
expect(split).not.toHaveBeenCalled()
})
})
@@ -1,26 +1,7 @@
import { describe, expect, it } from 'vitest'
import {
applyDiffEditorLineNumberOptions,
buildDiffEditorLineNumberOptions
} from './diff-editor-line-number-options'
import { applyDiffEditorLineNumberOptions } from './diff-editor-line-number-options'
import type { editor } from 'monaco-editor'
describe('buildDiffEditorLineNumberOptions', () => {
it('hides original line numbers in inline mode', () => {
expect(buildDiffEditorLineNumberOptions(false)).toEqual({
original: 'off',
modified: 'on'
})
})
it('shows both gutters in side-by-side mode', () => {
expect(buildDiffEditorLineNumberOptions(true)).toEqual({
original: 'on',
modified: 'on'
})
})
})
function createMockCodeEditor(initialLineNumbers: editor.LineNumbersType = 'on'): {
editor: editor.ICodeEditor
emitDidChangeConfiguration: () => void
@@ -6,36 +6,6 @@ afterEach(() => {
})
describe('computeLineStats', () => {
it('keeps existing added, deleted, and modified line count behavior', () => {
expect(computeLineStats('', 'a\nb', 'added')).toEqual({ added: 2, removed: 0 })
expect(computeLineStats('a\nb\n', '', 'deleted')).toEqual({ added: 0, removed: 3 })
expect(computeLineStats('a\nb\nc', 'a\nc\nd', 'modified')).toEqual({
added: 1,
removed: 1
})
})
it('counts newline-heavy added and deleted pasted content without splitting', () => {
const split = vi.spyOn(String.prototype, 'split')
const content = '\n'.repeat(100_000)
expect(computeLineStats('', content, 'added')).toEqual({ added: 100_001, removed: 0 })
expect(computeLineStats(content, '', 'deleted')).toEqual({ added: 0, removed: 100_001 })
expect(split).not.toHaveBeenCalled()
})
it('compares modified files without splitting either side into line arrays', () => {
const split = vi.spyOn(String.prototype, 'split')
expect(computeLineStats('same\nold\nkept', 'same\nnew\nkept', 'modified')).toEqual({
added: 1,
removed: 1
})
expect(split).not.toHaveBeenCalled()
})
it('keeps the existing large modified-file guard before building multisets', () => {
const split = vi.spyOn(String.prototype, 'split')
@@ -1,60 +0,0 @@
import type { IPosition, IRange } from 'monaco-editor'
import { describe, expect, it, vi } from 'vitest'
import { getMonacoCodebaseSearchQuery } from './monaco-codebase-search'
type FakeSelection = IRange & {
isEmpty: () => boolean
}
function selection(empty: boolean): FakeSelection {
return {
startLineNumber: 1,
startColumn: 1,
endLineNumber: 1,
endColumn: 1,
isEmpty: () => empty
}
}
function position(): IPosition {
return { lineNumber: 1, column: 5 }
}
function model(args: { selectedText?: string; word?: string }) {
return {
getValueInRange: vi.fn((_range: IRange) => args.selectedText ?? ''),
getWordAtPosition: vi.fn((_position: IPosition) =>
args.word === undefined ? null : { word: args.word }
)
}
}
describe('getMonacoCodebaseSearchQuery', () => {
it('prefers normalized selected text over the cursor word', () => {
const fakeModel = model({ selectedText: ' foo\r\n bar ', word: 'fallback' })
expect(getMonacoCodebaseSearchQuery(fakeModel, selection(false), position())).toBe('foo bar')
expect(fakeModel.getWordAtPosition).not.toHaveBeenCalled()
})
it('falls back to the cursor word when there is no selection', () => {
expect(
getMonacoCodebaseSearchQuery(model({ word: 'needle' }), selection(true), position())
).toBe('needle')
})
it('falls back to the cursor word when the selection normalizes to empty', () => {
expect(
getMonacoCodebaseSearchQuery(
model({ selectedText: ' \n\t ', word: 'cursorWord' }),
selection(false),
position()
)
).toBe('cursorWord')
})
it('returns null when neither selection nor cursor word yields a query', () => {
expect(getMonacoCodebaseSearchQuery(model({}), selection(true), position())).toBeNull()
expect(getMonacoCodebaseSearchQuery(null, selection(true), position())).toBeNull()
})
})
@@ -4,10 +4,7 @@ import { describe, expect, it, vi } from 'vitest'
import { Editor } from '@tiptap/core'
import { createRichMarkdownExtensions } from './rich-markdown-extensions'
import { createRichMarkdownEditorCodec } from './rich-markdown-source-transport'
import {
isRichMarkdownTableContextCommand,
runRichMarkdownContextCommand
} from './rich-markdown-context-command-routing'
import { runRichMarkdownContextCommand } from './rich-markdown-context-command-routing'
const TABLE = `| A | B |
| --- | --- |
@@ -31,11 +28,6 @@ function textPosition(editor: Editor, text: string): number {
}
describe('rich markdown context command routing', () => {
it('separates table commands for the shared rich-editor table owner', () => {
expect(isRichMarkdownTableContextCommand('delete-row')).toBe(true)
expect(isRichMarkdownTableContextCommand('bold')).toBe(false)
})
it('runs table actions against the clicked cell coordinates', () => {
const editor = new Editor({
element: document.createElement('div'),
@@ -25,15 +25,6 @@ describe('findRichMarkdownSearchMatches', () => {
])
})
it('forwards match-case and whole-word options to the range finder', () => {
expect(
findRichMarkdownSearchMatches(docFromText('Beta beta betas', 10), 'beta', {
matchCase: true,
wholeWord: true
})
).toEqual([{ from: 15, to: 19 }])
})
it('does not walk the document for oversized pasted search text', () => {
const doc = {
descendants() {
@@ -21,17 +21,6 @@ function command(overrides: Partial<SlashCommand>): SlashCommand {
}
describe('filterRichMarkdownSlashCommands', () => {
it('matches labels and aliases', () => {
const commands = [
command({ id: 'heading-1', label: 'Heading 1', aliases: ['h1', 'title'] }),
command({ id: 'table', label: 'Table', aliases: ['grid'] })
]
expect(filterRichMarkdownSlashCommands(commands, 'title').map((entry) => entry.id)).toEqual([
'heading-1'
])
})
it('returns no commands for oversized pasted queries before reading commands', () => {
const unreadableCommand = command({})
Object.defineProperty(unreadableCommand, 'label', {
@@ -4,33 +4,6 @@ import { RichMarkdownTable } from './rich-markdown-table'
afterEach(() => vi.restoreAllMocks())
it.each([
'',
'prose',
'| A | B |\n| --- | :---: |\n| a | b |',
'A | B\n--- | ---\na | b',
'| A |\nnot a separator\n',
'one\n\n| A |\n| --- |',
'| A |\r\n| --- |\r\n',
'| A |\n| - |',
'| A |\n| : |',
`| A |\n| - |\n${'prose\n'.repeat(10_000)}`
])('matches the upstream table detector: %j', (source) => {
const base = Table.config.markdownTokenizer
const guarded = RichMarkdownTable.config.markdownTokenizer
if (
!base ||
typeof base === 'function' ||
typeof base.start !== 'function' ||
!guarded ||
typeof guarded === 'function' ||
typeof guarded.start !== 'function'
) {
throw new Error('Table tokenizer contract changed')
}
expect(guarded.start(source)).toBe(base.start(source))
})
it('only passes the first two lines to the upstream detector', () => {
const base = Table.config.markdownTokenizer
const guarded = RichMarkdownTable.config.markdownTokenizer
@@ -10,22 +10,6 @@ function option(login: string, name: string | null = null): GitHubMentionOption
}
describe('github-mention-option-filter', () => {
it('filters mention options by login or display name', () => {
const options = [
option('octocat', 'Mona'),
option('release-manager', 'Octavia'),
option('build-bot')
]
expect(filterGitHubMentionOptions(options, 'oct')).toEqual([options[0], options[1]])
})
it('caps mention options at the provided limit', () => {
const options = [option('one'), option('two'), option('three')]
expect(filterGitHubMentionOptions(options, '', 2)).toEqual([options[0], options[1]])
})
it('enforces the query budget by UTF-8 byte length', () => {
const query = 'é'.repeat(GITHUB_MENTION_QUERY_MAX_BYTES)
@@ -11,28 +11,6 @@ function user(login: string, name: string | null = null): GitHubAssignableUser {
}
describe('github-pr-reviewer-candidate-filter', () => {
it('normalizes typed reviewer queries with optional at-prefixes', () => {
expect(getGitHubPRReviewerQueryState(' @OctoCat ')).toEqual({
query: 'octocat',
isTooLarge: false
})
})
it('filters reviewer candidates by login or display name and prioritizes login prefixes', () => {
const candidates = [
user('zebra-reviewer', 'Release Manager'),
user('alice', 'Feature Reviewer'),
user('review-bot', 'Automation')
]
expect(
filterGitHubPRReviewerCandidates({
candidates,
queryState: getGitHubPRReviewerQueryState('review')
}).map((candidate) => candidate.login)
).toEqual(['review-bot', 'alice', 'zebra-reviewer'])
})
it('enforces the query budget by UTF-8 byte length', () => {
const query = 'é'.repeat(GITHUB_PR_REVIEWER_QUERY_MAX_BYTES)
@@ -1,50 +0,0 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import { buildJiraCreateTextAdf } from './jira-create-adf'
afterEach(() => {
vi.restoreAllMocks()
})
describe('buildJiraCreateTextAdf', () => {
it('converts plain and empty lines to Jira ADF paragraphs', () => {
expect(buildJiraCreateTextAdf('one\n\ntwo')).toEqual({
type: 'doc',
version: 1,
content: [
{ type: 'paragraph', content: [{ type: 'text', text: 'one' }] },
{ type: 'paragraph', content: [] },
{ type: 'paragraph', content: [{ type: 'text', text: 'two' }] }
]
})
})
it('normalizes CRLF line boundaries and preserves trailing blank paragraphs', () => {
expect(buildJiraCreateTextAdf('one\r\ntwo\r\n')).toEqual({
type: 'doc',
version: 1,
content: [
{ type: 'paragraph', content: [{ type: 'text', text: 'one' }] },
{ type: 'paragraph', content: [{ type: 'text', text: 'two' }] },
{ type: 'paragraph', content: [] }
]
})
})
it('builds newline-heavy ADF without splitting the full textarea value', () => {
const split = vi.spyOn(String.prototype, 'split')
const text = Array.from({ length: 5_000 }, (_, index) => `line ${index + 1}`).join('\n')
const adf = buildJiraCreateTextAdf(text)
expect(adf.content).toHaveLength(5_000)
expect(adf.content[0]).toEqual({
type: 'paragraph',
content: [{ type: 'text', text: 'line 1' }]
})
expect(adf.content.at(-1)).toEqual({
type: 'paragraph',
content: [{ type: 'text', text: 'line 5000' }]
})
expect(split).not.toHaveBeenCalled()
})
})
@@ -2,8 +2,7 @@ import { describe, expect, it } from 'vitest'
import type { JiraProject } from '../../../shared/jira/jira-types'
import {
JIRA_PROJECT_PICKER_QUERY_MAX_BYTES,
filterJiraProjectPickerProjects,
getJiraProjectPickerDisplayLabel
filterJiraProjectPickerProjects
} from './jira-project-picker-filter'
function project(overrides: Partial<JiraProject> = {}): JiraProject {
@@ -18,37 +17,6 @@ function project(overrides: Partial<JiraProject> = {}): JiraProject {
}
describe('jira-project-picker-filter', () => {
it('formats project labels with optional site names', () => {
const candidate = project()
expect(getJiraProjectPickerDisplayLabel(candidate, true)).toBe(
'Engineering · Application (APP)'
)
expect(getJiraProjectPickerDisplayLabel(candidate, false)).toBe('Application (APP)')
})
it('matches project labels, keys, names, and site names', () => {
const projects = [
project({ id: '1', key: 'APP', name: 'Application', siteName: 'Engineering' }),
project({ id: '2', key: 'OPS', name: 'Operations', siteName: 'Support' })
]
expect(
filterJiraProjectPickerProjects({
projects,
query: 'ops',
includeSiteName: true
})
).toEqual([projects[1]])
expect(
filterJiraProjectPickerProjects({
projects,
query: 'engineering',
includeSiteName: true
})
).toEqual([projects[0]])
})
it('enforces the query budget by UTF-8 byte length', () => {
const query = 'é'.repeat(JIRA_PROJECT_PICKER_QUERY_MAX_BYTES)
@@ -1,10 +1,6 @@
import { describe, expect, it } from 'vitest'
import type { JiraIssue } from '../../../shared/jira/jira-types'
import {
renderJiraPromptTemplate,
getJiraPromptTemplates,
buildJiraIssuePrompt
} from './jira-prompt-templates'
import { renderJiraPromptTemplate, buildJiraIssuePrompt } from './jira-prompt-templates'
const issue: JiraIssue = {
id: '1',
key: 'APP-1',
@@ -33,14 +29,4 @@ describe('Jira prompt templates', () => {
renderJiraPromptTemplate('$other-prompt $other-prompt $unknown $jira-issues', issue, 'note')
).toBe('note note $unknown $jira-issues')
})
it('includes key, title, URL, description and the chosen goal in all built-in prompts', () => {
for (const template of getJiraPromptTemplates()) {
const prompt = renderJiraPromptTemplate(template.text, issue, 'Run integration checks.')
expect(prompt).toContain(issue.key)
expect(prompt).toContain(issue.title)
expect(prompt).toContain(issue.url)
expect(prompt).toContain(issue.description)
expect(prompt).toContain('Run integration checks.')
}
})
})
@@ -5,22 +5,6 @@ import {
} from './linear-issue-text-draft-state'
describe('linear issue text draft state', () => {
it('creates drafts from the issue source values', () => {
expect(
createLinearIssueTextDraftState({
description: undefined,
id: 'issue-1',
title: 'Initial title'
})
).toEqual({
description: '',
issueId: 'issue-1',
sourceDescription: '',
sourceTitle: 'Initial title',
title: 'Initial title'
})
})
it('resets both drafts when the issue changes', () => {
const state = createLinearIssueTextDraftState({
description: 'Old body',
@@ -1,49 +0,0 @@
import { describe, expect, it, vi } from 'vitest'
import { translate } from '@/i18n/i18n'
import type * as I18nModule from '@/i18n/i18n'
import en from '@/i18n/locales/en.json'
import {
BACKGROUND_TASK_COPY,
sayBackgroundTaskEnglish,
type BackgroundTaskCopyId
} from '../../../../shared/background-task-copy'
import { sayBackgroundTaskTranslated } from './background-task-words-text'
vi.mock('@/i18n/i18n', async (importOriginal) => {
const actual = await importOriginal<typeof I18nModule>()
return { ...actual, translate: vi.fn(actual.translate) }
})
const IDS = Object.keys(BACKGROUND_TASK_COPY).filter(
(id): id is BackgroundTaskCopyId => id in BACKGROUND_TASK_COPY
)
const VALUES = { value0: '2 agents' }
describe('desktop words for the background-tasks strip', () => {
it('says every piece as the phone does, in English', () => {
for (const id of IDS) {
expect([id, sayBackgroundTaskTranslated(id, VALUES)]).toEqual([
id,
sayBackgroundTaskEnglish(id, VALUES)
])
}
})
// The catalog answers first in English, so read the default each key is given directly.
it('keeps each existing key, with the shared piece as its English default', () => {
for (const id of IDS) {
vi.mocked(translate).mockClear()
sayBackgroundTaskTranslated(id, VALUES)
expect(vi.mocked(translate).mock.calls.map(([key, fallback]) => [key, fallback])).toEqual([
[`components.native-chat.backgroundTasks.${id}`, BACKGROUND_TASK_COPY[id]]
])
}
})
it('keeps the English catalog in step with the shared copy', () => {
const catalog: Record<string, string> = en.components['native-chat'].backgroundTasks
for (const id of IDS) {
expect([id, catalog[id]]).toEqual([id, BACKGROUND_TASK_COPY[id]])
}
})
})
@@ -1,40 +0,0 @@
import { describe, expect, it } from 'vitest'
import { formatContextTokenCount, summarizeContextUsage } from './native-chat-context-usage-summary'
describe('summarizeContextUsage', () => {
it('lists every non-empty CLI category, deferred ones included, largest first', () => {
const summary = summarizeContextUsage({
usedTokens: 29_400,
windowTokens: 200_000,
percentage: 15,
estimated: false,
categories: [
{ name: 'Messages', tokens: 10_200 },
{ name: 'System tools (deferred)', tokens: 17_600, deferred: true },
{ name: 'Skills', tokens: 0 },
{ name: 'Free space', tokens: 170_600 },
{ name: 'System prompt', tokens: 3_800 }
]
})
expect(summary.rows).toEqual([
{ name: 'Free space', tokens: 170_600, percentage: 85.3 },
{ name: 'System tools (deferred)', tokens: 17_600, percentage: 8.8 },
{ name: 'Messages', tokens: 10_200, percentage: 5.1 },
{ name: 'System prompt', tokens: 3_800, percentage: 1.9 }
])
expect(summary.estimated).toBe(false)
})
})
describe('formatContextTokenCount', () => {
it('keeps k for thousands and writes millions with a capital M', () => {
expect(formatContextTokenCount(10)).toBe('10')
expect(formatContextTokenCount(18_600)).toBe('18.6k')
expect(formatContextTokenCount(200_000)).toBe('200k')
expect(formatContextTokenCount(981_400)).toBe('981.4k')
expect(formatContextTokenCount(999_960)).toBe('1M')
expect(formatContextTokenCount(1_000_000)).toBe('1M')
expect(formatContextTokenCount(1_500_000)).toBe('1.5M')
expect(formatContextTokenCount(-5)).toBe('0')
})
})
@@ -16,23 +16,6 @@ function msg(
}
describe('orderNativeChatMessages', () => {
it('orders by ascending timestamp, null first', () => {
const ordered = orderNativeChatMessages([
msg({ id: 'b', timestamp: 20 }),
msg({ id: 'a', timestamp: 10 }),
msg({ id: 'n', timestamp: null })
])
expect(ordered.map((m) => m.id)).toEqual(['n', 'a', 'b'])
})
it('breaks timestamp ties by id deterministically', () => {
const ordered = orderNativeChatMessages([
msg({ id: 'z', timestamp: 5 }),
msg({ id: 'a', timestamp: 5 })
])
expect(ordered.map((m) => m.id)).toEqual(['a', 'z'])
})
it('sorts the streaming preview after real content but before optimistic echoes', () => {
const ordered = orderNativeChatMessages([
msg({ id: 'pending:abc', role: 'user', timestamp: 20, source: 'scrape' }),
@@ -1,54 +0,0 @@
import { describe, expect, it, vi } from 'vitest'
import { translate } from '@/i18n/i18n'
import type * as I18nModule from '@/i18n/i18n'
import en from '@/i18n/locales/en.json'
import {
NATIVE_CHAT_SUBAGENT_GROUP_COPY,
sayNativeChatSubagentGroupEnglish,
type NativeChatSubagentGroupCopyId
} from '../../../../shared/native-chat-subagent-group-header'
import { sayNativeChatSubagentGroupTranslated } from './native-chat-subagent-group-words-text'
vi.mock('@/i18n/i18n', async (importOriginal) => {
const actual = await importOriginal<typeof I18nModule>()
return { ...actual, translate: vi.fn(actual.translate) }
})
const IDS = Object.keys(NATIVE_CHAT_SUBAGENT_GROUP_COPY).filter(
(id): id is NativeChatSubagentGroupCopyId => id in NATIVE_CHAT_SUBAGENT_GROUP_COPY
)
const VALUES = { value0: 3 }
function isCatalogObject(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value)
}
function catalogEntry(key: string): unknown {
return key
.split('.')
.reduce<unknown>((node, part) => (isCatalogObject(node) ? node[part] : undefined), en)
}
describe('desktop words for a transcript roster row', () => {
it('says every piece as the phone does, in English', () => {
for (const id of IDS) {
expect([id, sayNativeChatSubagentGroupTranslated(id, VALUES)]).toEqual([
id,
sayNativeChatSubagentGroupEnglish(id, VALUES)
])
}
})
it('gives each key the shared piece as its English default, and the catalog agrees', () => {
for (const id of IDS) {
vi.mocked(translate).mockClear()
sayNativeChatSubagentGroupTranslated(id, VALUES)
const calls = vi.mocked(translate).mock.calls
expect(calls).toHaveLength(1)
const [key, fallback] = calls[0]!
expect([id, fallback]).toEqual([id, NATIVE_CHAT_SUBAGENT_GROUP_COPY[id]])
expect(key.startsWith('components.native-chat.subagents.')).toBe(true)
expect([key, catalogEntry(key)]).toEqual([key, NATIVE_CHAT_SUBAGENT_GROUP_COPY[id]])
}
})
})
@@ -1,60 +0,0 @@
import { describe, expect, it, afterEach, vi } from 'vitest'
import { clampPositionToViewport } from './PetOverlay'
describe('clampPositionToViewport', () => {
it('keeps positions inside the viewport minus pet size', () => {
expect(
clampPositionToViewport({ x: 480, y: 390 }, 120, {
width: 500,
height: 400
})
).toEqual({ x: 380, y: 280 })
})
it('clamps negative positions to the origin', () => {
expect(
clampPositionToViewport({ x: -25, y: -40 }, 120, {
width: 500,
height: 400
})
).toEqual({ x: 0, y: 0 })
})
it('keeps the overlay inside the viewport bounds', () => {
expect(
clampPositionToViewport({ x: 900, y: -20 }, 180, {
width: 800,
height: 600
})
).toEqual({ x: 620, y: 0 })
})
it('pins to the origin when the overlay is larger than the viewport', () => {
expect(
clampPositionToViewport({ x: 80, y: 40 }, 500, {
width: 320,
height: 240
})
).toEqual({ x: 0, y: 0 })
})
})
const { rejectAppStoreAccess } = vi.hoisted(() => ({
rejectAppStoreAccess: vi.fn(() => {
throw new Error('Pure helper tests must not access the app store')
})
}))
vi.mock('@/store', () => ({
useAppStore: Object.assign(rejectAppStoreAccess, {
getState: rejectAppStoreAccess,
getInitialState: rejectAppStoreAccess,
setState: rejectAppStoreAccess,
subscribe: rejectAppStoreAccess
})
}))
afterEach(() => {
expect(rejectAppStoreAccess).not.toHaveBeenCalled()
})
@@ -45,22 +45,6 @@ describe('getCheckCounts', () => {
expect(getChecksSummaryLabel([check('action_required')])).toBe('1 check needs action')
})
it('buckets neutral and still-running checks apart', () => {
const checks: PRCheckDetail[] = [
check('neutral'),
{ name: 'ci', status: 'in_progress', conclusion: null, url: null }
]
expect(getCheckCounts(checks)).toEqual({
passing: 0,
failing: 0,
needsAction: 0,
pending: 1,
neutral: 1
})
expect(getChecksSummaryLabel(checks)).toBe('1 check pending')
})
it('keeps a completed check without a conclusion unresolved', () => {
const checks: PRCheckDetail[] = [
{ name: 'external', status: 'completed', conclusion: null, url: null }
@@ -75,8 +59,4 @@ describe('getCheckCounts', () => {
})
expect(getChecksSummaryLabel(checks)).toBe('0 of 1 checks passing')
})
it('reports no checks for an empty list', () => {
expect(getChecksSummaryLabel([])).toBe('No checks found')
})
})
@@ -18,25 +18,6 @@ function user(login: string, extras: Partial<GitHubAssignableUser> = {}): GitHub
}
describe('buildMentionOptions', () => {
it('includes the item author, commenters, participants, and assignable users', () => {
const options = buildMentionOptions({
item,
comments: [comment('reviewer', 'https://avatars/reviewer')],
participants: [user('helper', { name: 'Helper' })],
assignableUsers: [user('teammate')]
})
expect(options.map((option) => option.login)).toEqual([
'octocat',
'reviewer',
'helper',
'teammate'
])
expect(options[0]?.source).toBe('PR author')
expect(options[1]?.avatarUrl).toBe('https://avatars/reviewer')
expect(options[2]?.name).toBe('Helper')
})
it('skips ghost and fills missing avatar/name on a later case-insensitive duplicate login', () => {
const options = buildMentionOptions({
item,
@@ -1,42 +0,0 @@
// @vitest-environment happy-dom
import { cleanup, render, screen } from '@testing-library/react'
import { afterEach, expect, it } from 'vitest'
import type { AiVaultListResult, AiVaultSession } from '../../../../shared/ai-vault-types'
import { AiVaultScanIssueBanners } from './AiVaultScanIssueBanners'
afterEach(() => {
cleanup()
})
it('keeps many partial-read notices in a bounded scroll area above the session list', () => {
const notices = Array.from({ length: 40 }, (_, index) => ({
executionHostId: 'ssh:box' as const,
agent: 'codex' as const,
kind: 'notice' as const,
path: `/home/u/.codex/sessions/rollout-${index}.jsonl`,
message: `Skipped 1 oversized transcript record over the 10.0 MiB limit (12.0 MiB at byte ${index}). The rest of the session was read.`
}))
const scanResult: AiVaultListResult = {
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: banners only read the session count.
sessions: [{} as AiVaultSession],
issues: [...notices, { agent: 'codex', path: '/broken.jsonl', message: 'Unreadable' }],
scannedAt: '2026-10-15T00:00:00.000Z'
}
render(<AiVaultScanIssueBanners scanResult={scanResult} />)
const region = screen.getByTestId('ai-vault-scan-notices')
expect(region.className).toContain('max-h-24')
expect(region.className).toContain('overflow-y-auto')
expect(region.children).toHaveLength(42)
expect(region.textContent).toContain('1 transcript skipped')
})
it('renders no notice area for a clean scan', () => {
render(
<AiVaultScanIssueBanners
scanResult={{ sessions: [], issues: [], scannedAt: '2026-10-15T00:00:00.000Z' }}
/>
)
expect(screen.queryByTestId('ai-vault-scan-notices')).toBeNull()
})
@@ -1,76 +0,0 @@
import { describe, expect, it, vi } from 'vitest'
import { Button } from '@/components/ui/button'
import { FileExplorerNameFilter } from './FileExplorerNameFilter'
import { visit, type ReactElementLike } from './file-explorer-element-tree-test-harness'
function findInputByAriaLabel(node: unknown, ariaLabel: string): ReactElementLike {
let found: ReactElementLike | null = null
visit(node, (entry) => {
if (entry.type === 'input' && entry.props['aria-label'] === ariaLabel) {
found = entry
}
})
if (!found) {
throw new Error(`${ariaLabel} input not found`)
}
return found
}
function findButtonByAriaLabel(node: unknown, ariaLabel: string): ReactElementLike {
let found: ReactElementLike | null = null
visit(node, (entry) => {
if (
entry.props['aria-label'] === ariaLabel &&
(entry.type === Button || entry.type === 'button')
) {
found = entry
}
})
if (!found) {
throw new Error(`${ariaLabel} button not found`)
}
return found
}
describe('FileExplorerNameFilter', () => {
it('reports text changes and shows the compact file filter input', () => {
const onQueryChange = vi.fn()
const element = FileExplorerNameFilter({
query: '',
onQueryChange,
onClear: vi.fn()
})
const input = findInputByAriaLabel(element, 'Find files')
;(input.props.onChange as (event: { currentTarget: { value: string } }) => void)({
currentTarget: { value: 'FileExplorer' }
})
expect(input.props.placeholder).toBe('Find files')
expect(onQueryChange).toHaveBeenCalledWith('FileExplorer')
})
it('clears the current file filter from the clear button', () => {
const onClear = vi.fn()
const element = FileExplorerNameFilter({
query: 'FileExplorer',
onQueryChange: vi.fn(),
onClear
})
const button = findButtonByAriaLabel(element, 'Clear file filter')
;(button.props.onClick as () => void)()
expect(onClear).toHaveBeenCalledTimes(1)
})
})
it('names the visible folder in the filter prompt', () => {
const element = FileExplorerNameFilter({
query: '',
scopeLabel: 'apps/web',
onQueryChange: vi.fn(),
onClear: vi.fn()
})
expect(findInputByAriaLabel(element, 'Find files').props.placeholder).toBe('Find in apps/web')
})
@@ -1,48 +0,0 @@
import { describe, expect, it, vi } from 'vitest'
import { FileExplorerViewSwitch } from './FileExplorerViewSwitch'
import { visit, type ReactElementLike } from './file-explorer-element-tree-test-harness'
function findElementByAriaLabel(node: unknown, ariaLabel: string): ReactElementLike {
let found: ReactElementLike | null = null
visit(node, (entry) => {
if (entry.props['aria-label'] === ariaLabel) {
found = entry
}
})
if (!found) {
throw new Error(`${ariaLabel} element not found`)
}
return found
}
describe('FileExplorerViewSwitch', () => {
it('switches between files and search views', () => {
const onSelectView = vi.fn()
const element = FileExplorerViewSwitch({
view: 'files',
onSelectView
})
const switchRoot = findElementByAriaLabel(element, 'Explorer search mode')
;(switchRoot.props.onValueChange as (value: string) => void)('search')
expect(onSelectView).toHaveBeenCalledWith('search')
})
it('renders names and contents labels', () => {
const element = FileExplorerViewSwitch({
view: 'search',
onSelectView: vi.fn()
})
const contentsTab = findElementByAriaLabel(element, 'Search file contents')
const namesTab = findElementByAriaLabel(element, 'Filter files by name')
const switchRoot = findElementByAriaLabel(element, 'Explorer search mode')
expect(switchRoot.props.value).toBe('search')
expect(contentsTab.props.value).toBe('search')
expect(namesTab.props.value).toBe('files')
expect(JSON.stringify(contentsTab.props.children)).toContain('Contents')
expect(JSON.stringify(namesTab.props.children)).toContain('Names')
})
})
@@ -1,54 +0,0 @@
import { describe, expect, it } from 'vitest'
import { isDotfileRelativePath, shouldIncludeFileExplorerEntry } from './file-explorer-entries'
describe('shouldIncludeFileExplorerEntry', () => {
it('keeps dotfiles loadable so visibility can be toggled client-side', () => {
expect(
shouldIncludeFileExplorerEntry({
name: '.env',
isDirectory: false,
isSymlink: false
})
).toBe(true)
expect(
shouldIncludeFileExplorerEntry({
name: '.config',
isDirectory: true,
isSymlink: false
})
).toBe(true)
})
it('still excludes internal and bulky directories', () => {
expect(
shouldIncludeFileExplorerEntry({
name: '.git',
isDirectory: true,
isSymlink: false
})
).toBe(false)
expect(
shouldIncludeFileExplorerEntry({
name: 'node_modules',
isDirectory: true,
isSymlink: false
})
).toBe(false)
})
})
describe('isDotfileRelativePath', () => {
it('matches dotfiles and descendants of dot folders across path separators', () => {
expect(isDotfileRelativePath('.env')).toBe(true)
expect(isDotfileRelativePath('.config/settings.json')).toBe(true)
expect(isDotfileRelativePath('src/.cache/result.json')).toBe(true)
expect(isDotfileRelativePath('src\\.cache\\result.json')).toBe(true)
})
it('does not match ordinary paths', () => {
expect(isDotfileRelativePath('src/index.ts')).toBe(false)
expect(isDotfileRelativePath('config/settings.json')).toBe(false)
})
})
@@ -1,4 +1,4 @@
import { FileText, Plug } from 'lucide-react'
import { Plug } from 'lucide-react'
import { describe, expect, it } from 'vitest'
import type { ActivePluginPanel } from '@/store/plugin-panels'
import { getPluginPanelActivityItems, resolvePluginPanelIcon } from './plugin-panel-activity-items'
@@ -12,13 +12,6 @@ const panel: ActivePluginPanel = {
}
describe('resolvePluginPanelIcon', () => {
it('resolves a curated icon name in both lucide naming styles', () => {
const dashed = resolvePluginPanelIcon('file-text')
// Without this, the equality below also passes when both sides fall back.
expect(dashed).toBe(FileText)
expect(resolvePluginPanelIcon('FileText')).toBe(dashed)
})
it.each(['constructor', '__proto__', 'toString', 'hasOwnProperty'])(
'falls back to Plug for the prototype member %s',
(iconName) => {
@@ -33,16 +26,3 @@ describe('resolvePluginPanelIcon', () => {
expect(item.icon).toBe(Plug)
})
})
describe('getPluginPanelActivityItems', () => {
it('projects watchdog failure into host-owned activity chrome', () => {
expect(
getPluginPanelActivityItems([panel], {
'plugin:orca-samples.demo/dashboard': true
})[0]
).toMatchObject({
id: panel.tabKey,
statusIndicator: 'failure'
})
})
})
@@ -1,64 +0,0 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import type { PRComment } from '../../../../shared/github/comment-types'
import { describePRCommentAckTarget } from './pr-comment-fixing-reply-body'
const comment = (body: string): PRComment => ({
id: 1,
author: 'alice',
authorAvatarUrl: '',
body,
createdAt: '',
url: ''
})
function originalSummary(body: string): string {
const line = body
.replace(/<!--[\s\S]*?-->/g, ' ')
.split('\n')
.map((line) =>
line
.replace(/^[\s>#*\-_`]+/, '')
.replace(/\s+/g, ' ')
.trim()
)
.find((line) => line.length > 0)
if (!line) {
return 'comment'
}
return `comment — ${line.length > 72 ? `${line.slice(0, 71).trimEnd()}…` : line}`
}
afterEach(() => vi.restoreAllMocks())
describe('review acknowledgement summary', () => {
it.each([
'',
'\n\r\n\t',
'# > ** _ - `\n\nReadable',
'<!-- first\nsecond -->\n## Hello\r\nignored',
'one<!-- hidden\nline -->two\nignored',
'<!-- unclosed\nreadable',
'<!-- outer <!-- inner -->visible -->',
'a\rb\nc',
'\u00a0##\u2028Hello\u2029world',
'a'.repeat(71),
'a'.repeat(72),
'a'.repeat(73),
`${'a'.repeat(70)} b`,
`${'a'.repeat(70)}😀tail`,
'\n<!-- only metadata -->\n',
'first\n<!-- tail\ncomment -->\nlast'
])('preserves the existing label for %j', (body) => {
expect(describePRCommentAckTarget(comment(body))).toBe(originalSummary(body))
})
it('skips tail normalization and full-document line splitting', () => {
const input = comment(`## Heading\n${'tail with whitespace\n'.repeat(10_000)}`)
const replace = vi.spyOn(String.prototype, 'replace')
const split = vi.spyOn(String.prototype, 'split')
const actual = describePRCommentAckTarget(input)
const replaces = replace.mock.calls.length
const splits = split.mock.calls.length
expect(actual).toBe('comment — Heading')
expect(replaces).toBe(3)
expect(splits).toBe(0)
})
})
@@ -1,81 +0,0 @@
import { describe, expect, it } from 'vitest'
import { Files } from 'lucide-react'
import type { ActivityBarItem } from './activity-bar-buttons'
import { getVisibleRightSidebarActivityItems } from './right-sidebar-activity-visibility'
const items: ActivityBarItem[] = [
{ id: 'explorer', icon: Files, title: 'Explorer', shortcut: '' },
{
id: 'workspaces',
icon: Files,
title: 'Workspaces',
shortcut: '',
folderOnly: true
},
{
id: 'pr-checks',
icon: Files,
title: 'PR Checks',
shortcut: '',
folderOnly: true
},
{
id: 'source-control',
icon: Files,
title: 'Source Control',
shortcut: '',
gitOnly: true
},
{ id: 'ports', icon: Files, title: 'Ports', shortcut: '', sshOnly: true },
// Plugin panels carry no visibility flags, so they show in every context.
{
id: 'plugin:orca-samples.my-plugin/dashboard',
icon: Files,
title: 'Dashboard',
shortcut: ''
}
]
describe('getVisibleRightSidebarActivityItems', () => {
it('shows ports only for workspaces on an SSH host', () => {
expect(
getVisibleRightSidebarActivityItems(items, {
isFolder: false,
isFolderWorkspace: false,
hasSshHost: false
}).map((item) => item.id)
).toEqual(['explorer', 'source-control', 'plugin:orca-samples.my-plugin/dashboard'])
expect(
getVisibleRightSidebarActivityItems(items, {
isFolder: false,
isFolderWorkspace: false,
hasSshHost: true
}).map((item) => item.id)
).toEqual(['explorer', 'source-control', 'ports', 'plugin:orca-samples.my-plugin/dashboard'])
})
it('shows Workspaces only for folder workspaces and hides git tabs for all folder scopes', () => {
expect(
getVisibleRightSidebarActivityItems(items, {
isFolder: true,
isFolderWorkspace: true,
hasSshHost: true
}).map((item) => item.id)
).toEqual([
'explorer',
'workspaces',
'pr-checks',
'ports',
'plugin:orca-samples.my-plugin/dashboard'
])
expect(
getVisibleRightSidebarActivityItems(items, {
isFolder: true,
isFolderWorkspace: false,
hasSshHost: true
}).map((item) => item.id)
).toEqual(['explorer', 'ports', 'plugin:orca-samples.my-plugin/dashboard'])
})
})
@@ -8,26 +8,6 @@ import {
} from './source-control/listing/file-filter'
describe('source-control-file-filter', () => {
it('normalizes bounded queries and filters entries by path', () => {
const filter = getSourceControlFileFilterState(' SRC/button ')
expect(filter).toEqual({ normalizedFilter: 'src/button', tooLarge: false })
expect(
filterSourceControlPathEntries(
[{ path: 'src/Button.tsx' }, { path: 'docs/Button.md' }],
filter
)
).toEqual([{ path: 'src/Button.tsx' }])
})
it('returns the original entries for empty filters', () => {
const entries = [{ path: 'src/a.ts' }]
expect(filterSourceControlPathEntries(entries, getSourceControlFileFilterState(' '))).toBe(
entries
)
})
it('rejects oversized filters before reading entry paths', () => {
const oversizedQuery = 'secret-source-control'.repeat(
SOURCE_CONTROL_FILE_FILTER_QUERY_MAX_BYTES
@@ -1,66 +0,0 @@
import { describe, expect, it } from 'vitest'
import {
getNextSourceControlViewMode,
shouldShowSourceControlCompareUnavailableCard
} from './source-control/panel/header-toolbar'
import type { GitBranchCompareSummary } from '../../../../shared/git-diff-compare-types'
const readySummary: GitBranchCompareSummary = {
baseRef: 'origin/main',
baseOid: 'base',
compareRef: 'feature',
headOid: 'head',
mergeBase: 'base',
changedFiles: 2,
commitsAhead: 1,
status: 'ready'
}
describe('source-control header toolbar helpers', () => {
it('toggles list and tree view modes', () => {
expect(getNextSourceControlViewMode('list')).toBe('tree')
expect(getNextSourceControlViewMode('tree')).toBe('list')
})
it('shows the compare-unavailable card only when compare failed and the body is empty', () => {
expect(
shouldShowSourceControlCompareUnavailableCard(
{ ...readySummary, status: 'error', errorMessage: 'nope' },
false,
false,
false
)
).toBe(true)
expect(
shouldShowSourceControlCompareUnavailableCard(
{ ...readySummary, status: 'error', errorMessage: 'nope' },
true,
false,
false
)
).toBe(false)
expect(
shouldShowSourceControlCompareUnavailableCard(
{ ...readySummary, status: 'error', errorMessage: 'nope' },
false,
true,
false
)
).toBe(false)
expect(shouldShowSourceControlCompareUnavailableCard(readySummary, false, false, false)).toBe(
false
)
expect(
shouldShowSourceControlCompareUnavailableCard(
{ ...readySummary, status: 'loading' },
false,
false,
false
)
).toBe(false)
})
})
@@ -76,15 +76,6 @@ function renderUncommittedSections({
}
describe('source control section header actions', () => {
it('routes the Changes header Stage all through the section handler', () => {
const handleStageSectionPaths = vi.fn(async () => {})
renderUncommittedSections({ handleStageSectionPaths })
fireEvent.click(screen.getByRole('button', { name: 'Stage all' }))
expect(handleStageSectionPaths).toHaveBeenCalledWith('unstaged', ['src/app.ts'])
})
it('includes untracked files in the Changes header Stage all', () => {
const handleStageSectionPaths = vi.fn(async () => {})
const changes: SourceControlDisplaySection = {
@@ -28,22 +28,6 @@ describe('useFileExplorerNameFilter', () => {
cleanup()
})
it('passes the active filename query to the runtime path search', () => {
const { result } = renderHook(() =>
useFileExplorerNameFilter({ isFilesViewActive: true, activeWorktreeId: 'worktree-1' })
)
act(() => result.current.setNameFilterQuery('AppDelegate.swift'))
expect(useRuntimeFileListForWorktreeMock).toHaveBeenLastCalledWith({
enabled: true,
worktreeId: 'worktree-1',
query: 'AppDelegate.swift',
hostFilterWhenCapped: true
})
expect(result.current.nameFilterSource?.query).toBe('AppDelegate.swift')
})
it('reports an unsettled source while the listing loads', () => {
useRuntimeFileListForWorktreeMock.mockReturnValue({
files: [],
@@ -1,89 +0,0 @@
// @vitest-environment happy-dom
import { act } from 'react'
import { createRoot, type Root } from 'react-dom/client'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { GlobalSettings } from '../../../../shared/global-settings-types'
import { TerminalFontSizeSetting } from './TerminalFontSizeSetting'
vi.mock('@/i18n/i18n', () => ({
translate: (_key: string, defaultValue: string) => defaultValue
}))
describe('TerminalFontSizeSetting', () => {
let container: HTMLDivElement
let root: Root
beforeEach(() => {
globalThis.IS_REACT_ACT_ENVIRONMENT = true
container = document.createElement('div')
document.body.appendChild(container)
root = createRoot(container)
})
afterEach(() => {
act(() => root.unmount())
document.body.replaceChildren()
})
function renderSetting(terminalFontSize: number, updateSettings = vi.fn()): void {
act(() => {
root.render(
<TerminalFontSizeSetting
settings={{ terminalFontSize } as GlobalSettings}
updateSettings={updateSettings}
forceVisible
/>
)
})
}
function getInput(): HTMLInputElement {
const input = container.querySelector<HTMLInputElement>('input[type="number"]')
if (!input) {
throw new Error('font size input not found')
}
return input
}
function getStepperButtons(): { decrement: HTMLButtonElement; increment: HTMLButtonElement } {
const [decrement, increment] = Array.from(
container.querySelectorAll<HTMLButtonElement>('button')
)
if (!decrement || !increment) {
throw new Error('stepper buttons not found')
}
return { decrement, increment }
}
it('keeps the two-digit input compact without native spin buttons', () => {
renderSetting(15)
expect(Array.from(getInput().classList)).toEqual(
expect.arrayContaining(['number-input-clean', 'w-14'])
)
})
it('steps the font size within the supported range', () => {
const updateSettings = vi.fn()
renderSetting(15, updateSettings)
const { decrement, increment } = getStepperButtons()
act(() => increment.click())
expect(updateSettings).toHaveBeenCalledWith({ terminalFontSize: 16 })
act(() => decrement.click())
expect(updateSettings).toHaveBeenCalledWith({ terminalFontSize: 14 })
})
it('disables the steppers at the range bounds', () => {
renderSetting(10)
expect(getStepperButtons().decrement.disabled).toBe(true)
expect(getStepperButtons().increment.disabled).toBe(false)
renderSetting(24)
expect(getStepperButtons().decrement.disabled).toBe(false)
expect(getStepperButtons().increment.disabled).toBe(true)
})
})
@@ -6,39 +6,6 @@ import {
} from './worktree-symlink-path-filter'
describe('worktree-symlink-path-filter', () => {
it('filters suggestions by normalized path text and caps visible results', () => {
const suggestions = [
{ name: 'node_modules', isDirectory: true },
{ name: 'packages/app', isDirectory: true },
{ name: 'packages/api', isDirectory: true },
{ name: '.env', isDirectory: false }
]
expect(
getWorktreeSymlinkPathFilterState({
query: '/packages',
suggestions,
existingPaths: [],
maxSuggestions: 1
})
).toEqual({
queryTrimmed: 'packages',
filtered: [{ name: 'packages/app', isDirectory: true }],
showLiteralItem: true,
isQueryTooLarge: false
})
})
it('hides the literal item when the query already exists', () => {
expect(
getWorktreeSymlinkPathFilterState({
query: 'node_modules',
suggestions: [{ name: 'node_modules', isDirectory: true }],
existingPaths: ['node_modules']
}).showLiteralItem
).toBe(false)
})
it('enforces the query budget by UTF-8 byte length', () => {
const query = 'é'.repeat(WORKTREE_SYMLINK_PATH_QUERY_MAX_BYTES)
@@ -55,17 +55,6 @@ describe('AddRepoDialogChrome dismissal', () => {
expect(screen.queryByRole('dialog', { name: 'Clone from URL' })).not.toBeNull()
})
it('allows backdrop dismissal when no clone is running', async () => {
const user = userEvent.setup()
const onOpenChange = vi.fn()
render(<CloneDialog isCloning={false} onOpenChange={onOpenChange} />)
await user.click(backdrop())
expect(onOpenChange).toHaveBeenCalledWith(false)
expect(screen.queryByRole('dialog', { name: 'Clone from URL' })).toBeNull()
})
it('allows backdrop dismissal after the clone settles', async () => {
const user = userEvent.setup()
const onOpenChange = vi.fn()
@@ -1,87 +0,0 @@
import { describe, expect, it } from 'vitest'
import { resolveProjectOrderManualDefaultNoticeDismissed } from '../../../../shared/project-order-manual-default-notice'
import { shouldShowProjectOrderManualDefaultNotice } from './project-order-manual-default-notice-visibility'
describe('resolveProjectOrderManualDefaultNoticeDismissed', () => {
it('keeps an explicit dismissal', () => {
expect(
resolveProjectOrderManualDefaultNoticeDismissed({
rawDismissed: true,
rawProjectOrderBy: undefined,
isExistingProfile: true
})
).toBe(true)
})
it('hides the notice for brand-new profiles', () => {
expect(
resolveProjectOrderManualDefaultNoticeDismissed({
rawDismissed: undefined,
rawProjectOrderBy: undefined,
isExistingProfile: false
})
).toBe(true)
})
it('hides the notice when recent ordering was already explicit', () => {
expect(
resolveProjectOrderManualDefaultNoticeDismissed({
rawDismissed: undefined,
rawProjectOrderBy: 'recent',
isExistingProfile: true
})
).toBe(true)
})
it('shows the notice for upgraded profiles without an explicit project order', () => {
expect(
resolveProjectOrderManualDefaultNoticeDismissed({
rawDismissed: undefined,
rawProjectOrderBy: undefined,
isExistingProfile: true
})
).toBe(false)
})
})
describe('shouldShowProjectOrderManualDefaultNotice', () => {
it('shows only when project grouping is active and repos exist', () => {
expect(
shouldShowProjectOrderManualDefaultNotice({
persistedUIReady: true,
projectOrderManualDefaultNoticeDismissed: false,
groupBy: 'repo',
projectOrderBy: 'manual',
repoCount: 2
})
).toBe(true)
expect(
shouldShowProjectOrderManualDefaultNotice({
persistedUIReady: true,
projectOrderManualDefaultNoticeDismissed: false,
groupBy: 'none',
projectOrderBy: 'manual',
repoCount: 2
})
).toBe(false)
expect(
shouldShowProjectOrderManualDefaultNotice({
persistedUIReady: true,
projectOrderManualDefaultNoticeDismissed: false,
groupBy: 'repo',
projectOrderBy: 'manual',
repoCount: 0
})
).toBe(false)
expect(
shouldShowProjectOrderManualDefaultNotice({
persistedUIReady: true,
projectOrderManualDefaultNoticeDismissed: false,
groupBy: 'repo',
projectOrderBy: 'recent',
repoCount: 2
})
).toBe(false)
})
})
@@ -1,60 +0,0 @@
import { describe, expect, it } from 'vitest'
import {
getConfiguredWorktreeCardJiraIssueDisplay,
getWorktreeCardJiraIssueDisplay
} from './worktree-card-jira-issue-display'
describe('getWorktreeCardJiraIssueDisplay', () => {
it('projects persisted Jira linked-item metadata for the workspace card', () => {
expect(
getWorktreeCardJiraIssueDisplay({
linkedWorkItem: {
provider: 'jira',
type: 'issue',
number: 1,
jiraIdentifier: 'KAN-1',
title: 'KAN-1 Test Jira card icon',
url: 'https://company.atlassian.net/browse/KAN-1'
}
})
).toEqual({
identifier: 'KAN-1',
title: 'Test Jira card icon',
url: 'https://company.atlassian.net/browse/KAN-1'
})
})
it('does not infer Jira from another provider', () => {
expect(
getWorktreeCardJiraIssueDisplay({
linkedWorkItem: {
provider: 'linear',
type: 'issue',
number: 1,
linearIdentifier: 'ENG-1',
title: 'Linear issue',
url: 'https://linear.app/acme/issue/ENG-1'
}
})
).toBeNull()
})
it('shows persisted Jira metadata only when the Jira display property is enabled', () => {
const worktree = {
linkedWorkItem: {
provider: 'jira' as const,
type: 'issue' as const,
number: 1,
jiraIdentifier: 'KAN-1',
title: 'Test Jira card preference',
url: 'https://company.atlassian.net/browse/KAN-1'
}
}
expect(getConfiguredWorktreeCardJiraIssueDisplay(worktree, [])).toBeNull()
expect(getConfiguredWorktreeCardJiraIssueDisplay(worktree, ['jira-issue'])).toMatchObject({
identifier: 'KAN-1',
title: 'Test Jira card preference'
})
})
})
@@ -39,19 +39,4 @@ describe('bundle retry selection', () => {
it('includes skills whose provider placement is incomplete', () => {
expect([...retryableSkillIds(result())]).toEqual(['partial-placement'])
})
it('includes a skill whose top-level result failed', () => {
const failedResult = result()
failedResult.skills = [
{
skillId: 'failed-skill',
name: 'failed-skill',
digest: 'd'.repeat(64),
status: 'failed',
placements: []
}
]
expect([...retryableSkillIds(failedResult)]).toEqual(['failed-skill'])
})
})
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest'
import type { SkillSharePreview } from '../../../../shared/skill-sharing-contract'
import { sensitiveShareFiles, summarizeShareRisk } from './skill-share-preview-summary'
import { sensitiveShareFiles } from './skill-share-preview-summary'
function preview(overrides: Partial<SkillSharePreview> = {}): SkillSharePreview {
return {
@@ -21,26 +21,6 @@ function preview(overrides: Partial<SkillSharePreview> = {}): SkillSharePreview
}
}
describe('summarizeShareRisk', () => {
it('reports the absence once instead of two zero counts', () => {
expect(summarizeShareRisk(preview())).toEqual({
risky: false,
label: 'No scripts or executables'
})
})
it('counts only the kinds that are present, with matching plurals', () => {
expect(summarizeShareRisk(preview({ scriptPaths: ['scripts/a.sh'] })).label).toBe('1 script')
expect(
summarizeShareRisk(preview({ scriptPaths: ['scripts/a.sh', 'scripts/b.sh'] })).label
).toBe('2 scripts')
expect(
summarizeShareRisk(preview({ scriptPaths: ['scripts/a.sh'], executablePaths: ['bin/x'] }))
.label
).toBe('1 script, 1 executable')
})
})
describe('sensitiveShareFiles', () => {
it('lists a file that is both script and executable once', () => {
const files = sensitiveShareFiles(
@@ -48,11 +28,4 @@ describe('sensitiveShareFiles', () => {
)
expect(files).toEqual([{ path: 'scripts/setup.sh', script: true, executable: true }])
})
it('sorts paths so the same package always reviews in the same order', () => {
const files = sensitiveShareFiles(
preview({ scriptPaths: ['scripts/b.sh', 'scripts/a.sh'], executablePaths: ['bin/tool'] })
)
expect(files.map((file) => file.path)).toEqual(['bin/tool', 'scripts/a.sh', 'scripts/b.sh'])
})
})
@@ -2,7 +2,6 @@ import { describe, expect, it } from 'vitest'
import type { DiscoveredSkill } from '../../../../shared/skills'
import {
SKILLS_FILTER_QUERY_MAX_BYTES,
countSkillsBySource,
filterSkills,
isSkillsFilterQueryTooLarge
} from './skills-filter'
@@ -79,14 +78,4 @@ describe('skills filtering', () => {
})
).toEqual([])
})
it('counts skills by source kind', () => {
expect(
countSkillsBySource([
skill({ sourceKind: 'home' }),
skill({ id: 'repo', sourceKind: 'repo' }),
skill({ id: 'plugin', sourceKind: 'plugin' })
])
).toEqual({ home: 1, repo: 1, bundled: 0, plugin: 1 })
})
})
@@ -19,16 +19,6 @@ describe.each([
['claude', getClaudeAccountSyncKey],
['codex', getCodexAccountSyncKey]
])('%s account sync key', (_provider, getSyncKey) => {
it('returns the same string for the same settings identity', () => {
const settings = makeSettings()
expect(getSyncKey(settings)).toBe(getSyncKey(settings))
})
it('answers no-settings when settings are absent', () => {
expect(getSyncKey(null)).toBe('no-settings')
expect(getSyncKey(undefined)).toBe('no-settings')
})
it('recomputes for a new settings identity', () => {
const first = getSyncKey(makeSettings())
const second = getSyncKey(
@@ -1,6 +1,6 @@
// @vitest-environment happy-dom
import { afterEach, describe, expect, it } from 'vitest'
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
import { cleanup, render, screen } from '@testing-library/react'
import { TaskPageJiraTextFallbackNotice } from './task-page-jira-text-fallback-notice'
afterEach(cleanup)
@@ -9,27 +9,6 @@ const REASON = "Field 'login' does not exist or you do not have permission to vi
const PROSE = { reason: REASON, likelyTypo: false }
describe('TaskPageJiraTextFallbackNotice', () => {
it("keeps Jira's reason behind Details", () => {
render(<TaskPageJiraTextFallbackNotice rejection={PROSE} />)
expect(screen.getByText(/Showing text matches/)).toBeTruthy()
expect(screen.getByRole('status').textContent).toContain('Showing text matches')
expect(screen.queryByText(REASON)).toBeNull()
fireEvent.click(screen.getByRole('button', { name: 'Details' }))
expect(screen.getByText(REASON)).toBeTruthy()
})
it("shows Jira's reason straight away for a likely JQL typo", () => {
render(<TaskPageJiraTextFallbackNotice rejection={{ reason: REASON, likelyTypo: true }} />)
expect(screen.getByText(REASON)).toBeTruthy()
})
it('omits Details when Jira gave no reason', () => {
render(<TaskPageJiraTextFallbackNotice rejection={{ reason: '', likelyTypo: true }} />)
expect(screen.getByText(/Showing text matches/)).toBeTruthy()
expect(screen.queryByRole('button', { name: 'Details' })).toBeNull()
})
it('updates one mounted live region instead of inserting a new one', () => {
const { rerender } = render(<TaskPageJiraTextFallbackNotice rejection={null} />)
const region = screen.getByRole('status')
@@ -1,82 +0,0 @@
import { describe, expect, it } from 'vitest'
import {
resolveLinearIssueEmptyKind,
shouldOfferLinearIssueFetchMore
} from './task-page-linear-issue-empty-state'
import { emptyLinearIssueAttributeFilter } from '../../../../../shared/linear/issue-attribute-filter'
describe('task-page-linear-issue-empty-state', () => {
it('prefers context, then search, then server filter, then client team, then unfiltered', () => {
expect(
resolveLinearIssueEmptyKind({
hasContextLabel: true,
searchActive: true,
attributeFilter: emptyLinearIssueAttributeFilter(),
serverIssueCount: 0,
filteredIssueCount: 0
})
).toBe('context')
expect(
resolveLinearIssueEmptyKind({
hasContextLabel: false,
searchActive: true,
attributeFilter: {
stateIds: ['s'],
priorities: [],
assignee: null,
labelIds: []
},
serverIssueCount: 0,
filteredIssueCount: 0
})
).toBe('search')
expect(
resolveLinearIssueEmptyKind({
hasContextLabel: false,
searchActive: false,
attributeFilter: {
stateIds: ['s'],
priorities: [],
assignee: null,
labelIds: []
},
serverIssueCount: 0,
filteredIssueCount: 0
})
).toBe('server-attribute-filter')
expect(
resolveLinearIssueEmptyKind({
hasContextLabel: false,
searchActive: false,
attributeFilter: emptyLinearIssueAttributeFilter(),
serverIssueCount: 3,
filteredIssueCount: 0
})
).toBe('client-team')
expect(
resolveLinearIssueEmptyKind({
hasContextLabel: false,
searchActive: false,
attributeFilter: emptyLinearIssueAttributeFilter(),
serverIssueCount: 0,
filteredIssueCount: 0
})
).toBe('unfiltered-scope')
})
it('offers fetch more only for client-team thinning with server hasMore', () => {
expect(shouldOfferLinearIssueFetchMore({ emptyKind: 'client-team', serverHasMore: true })).toBe(
true
)
expect(
shouldOfferLinearIssueFetchMore({
emptyKind: 'server-attribute-filter',
serverHasMore: true
})
).toBe(false)
})
})
@@ -16,37 +16,6 @@ const baseState: TaskPageListChromeVisibilityState = {
}
describe('shouldHideTaskPageListChrome', () => {
it('hides chrome for the active provider detail context', () => {
expect(
shouldHideTaskPageListChrome({
...baseState,
taskSource: 'github',
hasGitHubDetail: true
})
).toBe(true)
expect(
shouldHideTaskPageListChrome({
...baseState,
taskSource: 'gitlab',
hasGitLabDetail: true
})
).toBe(true)
expect(
shouldHideTaskPageListChrome({
...baseState,
taskSource: 'jira',
hasJiraDetail: true
})
).toBe(true)
expect(
shouldHideTaskPageListChrome({
...baseState,
taskSource: 'linear',
hasLinearProjectContext: true
})
).toBe(true)
})
it('keeps chrome visible when only another provider has stale detail state', () => {
expect(
shouldHideTaskPageListChrome({
@@ -2,9 +2,7 @@ import { describe, expect, it, vi } from 'vitest'
import {
LEGACY_PTY_ALLOCATION_HINT,
LEGACY_TERMINAL_PROCESS_LIMIT_HINT,
PTY_ALLOCATION_HINT,
TERMINAL_HOST_CAP_HINT,
TERMINAL_PROCESS_LIMIT_HINT
PTY_ALLOCATION_HINT
} from '../../../../shared/terminal-spawn-error-copy'
// Why a locale stand-in: English fallbacks equal the host copy, so only a translation shows the swap.
@@ -34,10 +32,6 @@ describe('localizeTerminalSpawnHints', () => {
)
})
it('translates the terminal process limit hint', () => {
expect(localizeTerminalSpawnHints(TERMINAL_PROCESS_LIMIT_HINT)).toBe('系统无法再启动终端进程。')
})
it('translates the multi-line hints an older host publishes', () => {
expect(localizeTerminalSpawnHints(`${LEGACY_PTY_ALLOCATION_HINT} ${RAW}`)).toBe(
`系统无法再分配 pty 设备。 ${RAW}`
@@ -46,29 +40,9 @@ describe('localizeTerminalSpawnHints', () => {
'系统无法再启动终端进程。'
)
})
it('translates the host terminal cap refusal', () => {
expect(localizeTerminalSpawnHints(TERMINAL_HOST_CAP_HINT)).toBe('主机终端已达上限。')
})
it('leaves unrelated errors untouched', () => {
expect(localizeTerminalSpawnHints('Paste failed.')).toBe('Paste failed.')
})
})
describe('withoutTerminalSpawnIssueRequest', () => {
it('drops the host issue request and keeps the diagnostic', () => {
expect(withoutTerminalSpawnIssueRequest(RAW)).toBe(
'Failed to spawn shell "/bin/zsh": node-pty: open_slave failed: EMFILE (errno 24, Too many open files) (shell: /bin/zsh).'
)
})
it('drops the request from an error before another pane error', () => {
expect(withoutTerminalSpawnIssueRequest(`${RAW}\nPaste failed.`)).toBe(
'Failed to spawn shell "/bin/zsh": node-pty: open_slave failed: EMFILE (errno 24, Too many open files) (shell: /bin/zsh).\nPaste failed.'
)
})
it('keeps the same words when they are part of the diagnostic', () => {
const error = `Command printed: If this persists, please file an issue.\nMore detail follows.`
expect(withoutTerminalSpawnIssueRequest(error)).toBe(error)
@@ -1,58 +0,0 @@
// @vitest-environment happy-dom
import { cleanup, render, screen } from '@testing-library/react'
import { afterEach, describe, expect, it, vi } from 'vitest'
import {
WorkspaceCleanupInitialScanBanner,
WorkspaceCleanupSizeScanBanner
} from './workspace-cleanup-dialog-notices'
afterEach(cleanup)
describe('WorkspaceCleanupInitialScanBanner', () => {
it('puts determinate progress in the title with matching typography', () => {
render(
<WorkspaceCleanupInitialScanBanner
progress={{
scanId: 'scan-1',
scannedAt: 1,
scannedWorktreeCount: 23,
totalWorktreeCount: 3048,
candidates: [],
errors: []
}}
/>
)
expect(screen.getByText('Scanning workspaces (23/3048)')).toBeTruthy()
expect(screen.queryByText('Checked workspaces so far: 23')).toBeNull()
})
})
describe('WorkspaceCleanupSizeScanBanner', () => {
it('explains why scanning is useful and exposes the action', () => {
render(
<WorkspaceCleanupSizeScanBanner
scanning={false}
scannedCount={0}
totalCount={0}
onRun={vi.fn()}
/>
)
expect(screen.getByText('Scan workspace sizes')).toBeTruthy()
expect(
screen.getByText('Scan disk usage to compare, sort, and filter workspaces by size.')
).toBeTruthy()
expect(screen.getByRole('button', { name: 'Scan' })).toBeTruthy()
})
it('shows determinate progress and disables duplicate runs', () => {
render(
<WorkspaceCleanupSizeScanBanner scanning scannedCount={23} totalCount={100} onRun={vi.fn()} />
)
expect(
(screen.getByRole('button', { name: 'Scanning 23/100' }) as HTMLButtonElement).disabled
).toBe(true)
})
})
@@ -15,28 +15,6 @@ const readyInput: ComposerCreateGateInput = {
}
describe('new workspace create gates', () => {
it('keeps quick create clickable when no repo is selected so submit can validate inline', () => {
expect(getQuickComposerCreateDisabled({ ...readyInput, repoId: '' })).toBe(false)
})
it('still blocks quick create for other missing form state and explicit setup choices', () => {
expect(getQuickComposerCreateDisabled({ ...readyInput, workspaceSeedName: '' })).toBe(true)
expect(getQuickComposerCreateDisabled({ ...readyInput, creating: true })).toBe(true)
expect(
getQuickComposerCreateDisabled({ ...readyInput, selectedRepoRequiresConnection: true })
).toBe(true)
expect(
getQuickComposerCreateDisabled({
...readyInput,
requiresExplicitSetupChoice: true,
hasSetupDecision: false
})
).toBe(true)
expect(getQuickComposerCreateDisabled({ ...readyInput, sparseError: 'Bad sparse path' })).toBe(
true
)
})
it('blocks quick create synchronously for unresolved source intent', () => {
const blocked = { ...readyInput, sourceIntentBlocksCreate: true }
@@ -30,15 +30,6 @@ describe('getRendererAppPlatform', () => {
expect(get).toHaveBeenCalledTimes(1)
})
it.each(['darwin', 'win32', 'linux'] as const)(
'reports the preload platform verbatim on %s',
(platform) => {
stubPlatformApi(() => ({ platform }))
expect(getRendererAppPlatform()).toBe(platform)
}
)
// The web platform bridge arrives after boot; early user-agent guesses must not become session-long cache entries.
it('does not cache the user-agent fallback', () => {
// 'freebsd' is never a user-agent fallback answer, so the swap is unambiguous.
@@ -7,8 +7,7 @@ import {
} from '../components/skills/bundled-skill-plan-fixtures.test-support'
import {
getSkillFreshnessDisplayStatus,
hasSkillCopyNeedingAttention,
skillPlanHasDetails
hasSkillCopyNeedingAttention
} from './skill-freshness-display-status'
const SKILL_NAME = 'orca-cli'
@@ -20,31 +19,6 @@ function planWith(...ops: BundledSkillOp[]) {
}
describe('getSkillFreshnessDisplayStatus', () => {
it('reports presence only without a plan or without a copy of Orca’s', () => {
expect(getSkillFreshnessDisplayStatus(null, SKILL_NAME)).toBe('installed')
expect(getSkillFreshnessDisplayStatus(planOf([]), SKILL_NAME)).toBe('installed')
expect(skillPlanHasDetails(null, SKILL_NAME)).toBe(false)
})
it('is up to date when every location is kept', () => {
const plan = planWith(planOp('keep', SHARED), planOp('keep', CLAUDE))
expect(getSkillFreshnessDisplayStatus(plan, SKILL_NAME)).toBe('up-to-date')
expect(skillPlanHasDetails(plan, SKILL_NAME)).toBe(false)
})
it('offers an update when a released copy would be rewritten', () => {
const plan = planWith(planOp('update', SHARED), planOp('keep', CLAUDE))
expect(getSkillFreshnessDisplayStatus(plan, SKILL_NAME)).toBe('update-available')
expect(hasSkillCopyNeedingAttention(plan, SKILL_NAME)).toBe(false)
expect(skillPlanHasDetails(plan, SKILL_NAME)).toBe(true)
})
it('treats link-only work as up to date but still offers details', () => {
const plan = planWith(planOp('keep', SHARED), planOp('link', CLAUDE))
expect(getSkillFreshnessDisplayStatus(plan, SKILL_NAME)).toBe('up-to-date')
expect(skillPlanHasDetails(plan, SKILL_NAME)).toBe(true)
})
it('flags an edited copy left alone, and an interrupted update to repair', () => {
const edited = planWith(
planOp('keep', SHARED),
@@ -34,14 +34,4 @@ describe('compareWorktreeDisplayName', () => {
expect(compareWorktreeDisplayName(a, b)).toBeLessThan(0)
expect(compareWorktreeDisplayName(b, a)).toBeGreaterThan(0)
})
it('treats two undefined names as equal without throwing', () => {
expect(compareWorktreeDisplayName(worktree('a', undefined), worktree('b', undefined))).toBe(0)
})
it('orders defined names lexicographically', () => {
expect(
compareWorktreeDisplayName(worktree('a', 'Apple'), worktree('b', 'Banana'))
).toBeLessThan(0)
})
})
@@ -15,11 +15,6 @@ const BASE = {
// Explicit contrast floor must travel with theme or mobile renders the same session differently (#10754).
describe('resolveMobileTerminalTheme contrast floor', () => {
it('omits the floor when the user has not set one', () => {
const theme = resolveMobileTerminalTheme(stateWith(BASE), true)
expect(theme?.minimumContrastRatio).toBeUndefined()
})
it('publishes the user floor so the phone stops lifting low-contrast output', () => {
const theme = resolveMobileTerminalTheme(
stateWith({ ...BASE, terminalMinimumContrastRatio: 1 }),
@@ -40,8 +35,4 @@ describe('resolveMobileTerminalTheme contrast floor', () => {
)?.minimumContrastRatio
).toBeUndefined()
})
it('returns nothing without settings', () => {
expect(resolveMobileTerminalTheme(stateWith(null), true)).toBeUndefined()
})
})