mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
test: remove low-value renderer suites and redundant shared test cases (#33819)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
@@ -9,25 +9,6 @@ function stepAt(clientY: number, frameDeltaMs = ONE_FRAME_MS): number {
|
||||
}
|
||||
|
||||
describe('getDragAutoScrollStepPx', () => {
|
||||
it('stays still while the pointer is away from both edges', () => {
|
||||
expect(stepAt(300)).toBe(0)
|
||||
expect(stepAt(130)).toBe(0)
|
||||
expect(stepAt(470)).toBe(0)
|
||||
})
|
||||
|
||||
it('pulls down past the bottom edge and up past the top edge', () => {
|
||||
expect(stepAt(490)).toBeGreaterThan(0)
|
||||
expect(stepAt(110)).toBeLessThan(0)
|
||||
})
|
||||
|
||||
it('pulls harder the further past the edge the pointer is', () => {
|
||||
expect(stepAt(600)).toBeGreaterThan(stepAt(490))
|
||||
})
|
||||
|
||||
it('caps the pull however far past the edge the pointer goes', () => {
|
||||
expect(stepAt(5000)).toBe(stepAt(100_000))
|
||||
})
|
||||
|
||||
it('covers the same distance per millisecond at any refresh rate', () => {
|
||||
expect(stepAt(600, ONE_FRAME_MS / 2) * 2).toBeCloseTo(stepAt(600, ONE_FRAME_MS), 8)
|
||||
})
|
||||
|
||||
@@ -15,13 +15,6 @@ function tab(id: string): OpenFile {
|
||||
}
|
||||
|
||||
describe('editorSavedEventView', () => {
|
||||
it('is the saving view while it is open', () => {
|
||||
const files = [tab('other'), tab('saver')]
|
||||
expect(
|
||||
editorSavedEventView(files, { fileId: 'saver', recordViewIds: ['other', 'saver'] })?.id
|
||||
).toBe('saver')
|
||||
})
|
||||
|
||||
it("falls back to the record's other views when the saving view closed mid-write", () => {
|
||||
const files = [tab('unrelated'), tab('other')]
|
||||
expect(
|
||||
@@ -29,9 +22,4 @@ describe('editorSavedEventView', () => {
|
||||
).toBe('other')
|
||||
expect(editorSavedEventView(files, { fileId: 'closed' })).toBeUndefined()
|
||||
})
|
||||
|
||||
it('ignores malformed details', () => {
|
||||
expect(editorSavedEventView([tab('a')], null)).toBeUndefined()
|
||||
expect(editorSavedEventView([tab('a')], { content: 'x' })).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { computeMonacoRevealRange } from './monaco-reveal-range'
|
||||
|
||||
describe('computeMonacoRevealRange', () => {
|
||||
it('returns the requested single-line match range when it is already valid', () => {
|
||||
expect(
|
||||
computeMonacoRevealRange({
|
||||
line: 4,
|
||||
column: 7,
|
||||
matchLength: 5,
|
||||
maxLine: 20,
|
||||
lineMaxColumn: 80
|
||||
})
|
||||
).toEqual({
|
||||
startLineNumber: 4,
|
||||
startColumn: 7,
|
||||
endLineNumber: 4,
|
||||
endColumn: 12
|
||||
})
|
||||
})
|
||||
|
||||
it('clamps the line and columns to the model bounds', () => {
|
||||
expect(
|
||||
computeMonacoRevealRange({
|
||||
line: 999,
|
||||
column: 200,
|
||||
matchLength: 10,
|
||||
maxLine: 6,
|
||||
lineMaxColumn: 14
|
||||
})
|
||||
).toEqual({
|
||||
startLineNumber: 6,
|
||||
startColumn: 14,
|
||||
endLineNumber: 6,
|
||||
endColumn: 15
|
||||
})
|
||||
})
|
||||
|
||||
it('falls back to a one-column range when match length is zero', () => {
|
||||
expect(
|
||||
computeMonacoRevealRange({
|
||||
line: 2,
|
||||
column: 3,
|
||||
matchLength: 0,
|
||||
maxLine: 10,
|
||||
lineMaxColumn: 9
|
||||
})
|
||||
).toEqual({
|
||||
startLineNumber: 2,
|
||||
startColumn: 3,
|
||||
endLineNumber: 2,
|
||||
endColumn: 4
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -23,15 +23,4 @@ describe('getLinearIssueTextSavePlan', () => {
|
||||
|
||||
expect(plan).toEqual({ kind: 'changed', patch: { description: 'Updated details' } })
|
||||
})
|
||||
|
||||
it('preserves the required-title guard', () => {
|
||||
const plan = getLinearIssueTextSavePlan({
|
||||
descriptionDraft: 'Details',
|
||||
field: 'title',
|
||||
issue: { title: 'Issue title', description: 'Details' },
|
||||
titleDraft: ' '
|
||||
})
|
||||
|
||||
expect(plan).toEqual({ kind: 'empty-title' })
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { getAgentImageHandling, isNativeChatPastedImagePath } from './native-chat-image-paste'
|
||||
|
||||
describe('image paste agent map', () => {
|
||||
it('vision-capable TUIs take image attachments', () => {
|
||||
expect(getAgentImageHandling('claude')).toBe('attachment')
|
||||
expect(getAgentImageHandling('codex')).toBe('attachment')
|
||||
expect(getAgentImageHandling('grok')).toBe('attachment')
|
||||
})
|
||||
|
||||
it('unknown/custom agent is unsupported', () => {
|
||||
expect(getAgentImageHandling('some-custom-agent')).toBe('unsupported')
|
||||
})
|
||||
})
|
||||
|
||||
describe('isNativeChatPastedImagePath', () => {
|
||||
it('detects clipboard-paste temp files (so the chip shows a friendly label)', () => {
|
||||
expect(
|
||||
isNativeChatPastedImagePath(
|
||||
'/var/folders/x/orca-paste-1782775228480-c9a3c86b-1234-5678-9abc-def012345678.png'
|
||||
)
|
||||
).toBe(true)
|
||||
// Windows-style separators resolve to the same basename.
|
||||
expect(isNativeChatPastedImagePath('C:\\Temp\\orca-paste-1-2.png')).toBe(true)
|
||||
})
|
||||
|
||||
it('leaves picked/dropped files showing their real name', () => {
|
||||
expect(isNativeChatPastedImagePath('/Users/me/Pictures/hero-image-2.png')).toBe(false)
|
||||
expect(isNativeChatPastedImagePath('/tmp/screenshot.png')).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -1,34 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
deriveNativeChatCanSend,
|
||||
shouldChatTakeOverMobileSurface
|
||||
} from './native-chat-send-eligibility'
|
||||
|
||||
describe('deriveNativeChatCanSend', () => {
|
||||
it('blocks sends when a mobile client holds the pty (presence-lock active)', () => {
|
||||
expect(deriveNativeChatCanSend({ kind: 'mobile', clientId: 'phone-1' })).toBe(false)
|
||||
})
|
||||
|
||||
it('allows sends when the desktop drives the pty', () => {
|
||||
expect(deriveNativeChatCanSend({ kind: 'desktop' })).toBe(true)
|
||||
})
|
||||
|
||||
it('allows sends when the pty is idle', () => {
|
||||
expect(deriveNativeChatCanSend({ kind: 'idle' })).toBe(true)
|
||||
})
|
||||
|
||||
it('treats an unresolved driver (null/undefined) as unlocked', () => {
|
||||
expect(deriveNativeChatCanSend(null)).toBe(true)
|
||||
expect(deriveNativeChatCanSend(undefined)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('shouldChatTakeOverMobileSurface', () => {
|
||||
it('takes over the mobile surface when the tab is in chat view', () => {
|
||||
expect(shouldChatTakeOverMobileSurface('chat')).toBe(true)
|
||||
})
|
||||
|
||||
it('leaves the terminal mobile overlay in place in terminal view', () => {
|
||||
expect(shouldChatTakeOverMobileSurface('terminal')).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -1,42 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { normalizeAiVaultScopeForContext } from './ai-vault-scope-state'
|
||||
|
||||
describe('normalizeAiVaultScopeForContext', () => {
|
||||
it('falls back from project to all when no active project is available', () => {
|
||||
expect(
|
||||
normalizeAiVaultScopeForContext({
|
||||
scope: 'project',
|
||||
activeProjectKey: null,
|
||||
activeWorktreePath: '/repo'
|
||||
})
|
||||
).toBe('all')
|
||||
})
|
||||
|
||||
it('falls back from workspace to all when no active workspace path is available', () => {
|
||||
expect(
|
||||
normalizeAiVaultScopeForContext({
|
||||
scope: 'workspace',
|
||||
activeProjectKey: 'project:orca',
|
||||
activeWorktreePath: null
|
||||
})
|
||||
).toBe('all')
|
||||
})
|
||||
|
||||
it('keeps available project and workspace scopes selected', () => {
|
||||
expect(
|
||||
normalizeAiVaultScopeForContext({
|
||||
scope: 'project',
|
||||
activeProjectKey: 'project:orca',
|
||||
activeWorktreePath: '/repo'
|
||||
})
|
||||
).toBe('project')
|
||||
|
||||
expect(
|
||||
normalizeAiVaultScopeForContext({
|
||||
scope: 'workspace',
|
||||
activeProjectKey: null,
|
||||
activeWorktreePath: '/repo'
|
||||
})
|
||||
).toBe('workspace')
|
||||
})
|
||||
})
|
||||
@@ -1,31 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
shouldShowCommitFailureDialog,
|
||||
syncCommitFailureDialogState,
|
||||
type CommitFailureDialogState
|
||||
} from './commit-failure-dialog-state'
|
||||
|
||||
describe('commit failure dialog state', () => {
|
||||
it('keeps an open dialog visible when a new detailed error arrives for the same worktree', () => {
|
||||
const state: CommitFailureDialogState = { worktreeKey: 'wt-1', open: true }
|
||||
|
||||
expect(syncCommitFailureDialogState(state, 'wt-1', true)).toBe(state)
|
||||
expect(shouldShowCommitFailureDialog(state, 'wt-1', true)).toBe(true)
|
||||
})
|
||||
|
||||
it('closes the dialog when the failure moves to another worktree', () => {
|
||||
expect(syncCommitFailureDialogState({ worktreeKey: 'wt-1', open: true }, 'wt-2', true)).toEqual(
|
||||
{
|
||||
worktreeKey: 'wt-2',
|
||||
open: false
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('closes the dialog when the latest failure no longer has expanded details', () => {
|
||||
const next = syncCommitFailureDialogState({ worktreeKey: 'wt-1', open: true }, 'wt-1', false)
|
||||
|
||||
expect(next).toEqual({ worktreeKey: 'wt-1', open: false })
|
||||
expect(shouldShowCommitFailureDialog(next, 'wt-1', false)).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -1,28 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { shouldShowLocalWorkspacePortSections } from './local-workspace-port-sections'
|
||||
|
||||
const empty = { activePorts: [], otherWorkspacePorts: [], externalPorts: [] }
|
||||
|
||||
describe('shouldShowLocalWorkspacePortSections', () => {
|
||||
it('shows the sections whenever the scan succeeded', () => {
|
||||
expect(shouldShowLocalWorkspacePortSections(null, empty)).toBe(true)
|
||||
expect(shouldShowLocalWorkspacePortSections({}, empty)).toBe(true)
|
||||
})
|
||||
|
||||
// Failed scans retain last-good ports; preserve their actions alongside the status bar's count/list.
|
||||
it.each([
|
||||
['activePorts', { ...empty, activePorts: [{}] }],
|
||||
['otherWorkspacePorts', { ...empty, otherWorkspacePorts: [{}] }],
|
||||
['externalPorts', { ...empty, externalPorts: [{}] }]
|
||||
])('keeps the sections when a failed scan retained %s', (_section, sections) => {
|
||||
expect(shouldShowLocalWorkspacePortSections({ unavailableReason: 'dropped' }, sections)).toBe(
|
||||
true
|
||||
)
|
||||
})
|
||||
|
||||
it('lets the notice stand alone when a failed scan has nothing left to list', () => {
|
||||
expect(shouldShowLocalWorkspacePortSections({ unavailableReason: 'dropped' }, empty)).toBe(
|
||||
false
|
||||
)
|
||||
})
|
||||
})
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { shouldShowPrimaryTooltip } from './source-control-primary-action-tooltip'
|
||||
import type { PrimaryActionKind } from './source-control-primary-action'
|
||||
|
||||
function action(kind: PrimaryActionKind, disabled: boolean) {
|
||||
return { kind, disabled, label: kind, title: kind }
|
||||
}
|
||||
|
||||
describe('shouldShowPrimaryTooltip', () => {
|
||||
it('shows the tooltip when disabled — the title carries the blocking reason', () => {
|
||||
for (const kind of ['stage', 'create_pr', 'create_pr_intent', 'commit'] as const) {
|
||||
expect(shouldShowPrimaryTooltip(action(kind, true))).toBe(true)
|
||||
}
|
||||
})
|
||||
|
||||
it('hides pure repeats on enabled Stage All and Create PR', () => {
|
||||
expect(shouldShowPrimaryTooltip(action('stage', false))).toBe(false)
|
||||
expect(shouldShowPrimaryTooltip(action('create_pr', false))).toBe(false)
|
||||
})
|
||||
|
||||
it('keeps the Create PR intent tooltip — it explains the prepare step the label omits', () => {
|
||||
expect(shouldShowPrimaryTooltip(action('create_pr_intent', false))).toBe(true)
|
||||
})
|
||||
|
||||
it('shows informative tooltips for commit and remote counts', () => {
|
||||
for (const kind of ['commit', 'push', 'pull', 'sync', 'publish'] as const) {
|
||||
expect(shouldShowPrimaryTooltip(action(kind, false))).toBe(true)
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -1,45 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { DirEntry } from '../../../../shared/filesystem-entry-types'
|
||||
import {
|
||||
getSparseBrowseTrail,
|
||||
joinSparseBrowsePath,
|
||||
listBrowsableDirectories
|
||||
} from './sparse-directory-browse'
|
||||
|
||||
const entry = (name: string, isDirectory: boolean): DirEntry => ({
|
||||
name,
|
||||
isDirectory,
|
||||
isSymlink: false
|
||||
})
|
||||
|
||||
describe('listBrowsableDirectories', () => {
|
||||
it('keeps only sorted, non-hidden directories', () => {
|
||||
expect(
|
||||
listBrowsableDirectories([
|
||||
entry('packages', true),
|
||||
entry('README.md', false),
|
||||
entry('.git', true),
|
||||
entry('.vscode', true),
|
||||
entry('node_modules', true),
|
||||
entry('apps', true)
|
||||
])
|
||||
).toEqual(['apps', 'packages'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('joinSparseBrowsePath', () => {
|
||||
it('omits the separator at the repository root', () => {
|
||||
expect(joinSparseBrowsePath('', 'apps')).toBe('apps')
|
||||
expect(joinSparseBrowsePath('apps', 'web')).toBe('apps/web')
|
||||
})
|
||||
})
|
||||
|
||||
describe('getSparseBrowseTrail', () => {
|
||||
it('builds cumulative segments', () => {
|
||||
expect(getSparseBrowseTrail('')).toEqual([])
|
||||
expect(getSparseBrowseTrail('apps/web')).toEqual([
|
||||
{ name: 'apps', path: 'apps' },
|
||||
{ name: 'web', path: 'apps/web' }
|
||||
])
|
||||
})
|
||||
})
|
||||
@@ -1,8 +1,5 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
addSparseDirectoryEntries,
|
||||
parseSparseDirectoryEntryInput
|
||||
} from './sparse-directory-entry-input'
|
||||
import { parseSparseDirectoryEntryInput } from './sparse-directory-entry-input'
|
||||
|
||||
describe('parseSparseDirectoryEntryInput', () => {
|
||||
it('normalizes a single typed path', () => {
|
||||
@@ -26,12 +23,3 @@ describe('parseSparseDirectoryEntryInput', () => {
|
||||
expect(parseSparseDirectoryEntryInput('.').error).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
describe('addSparseDirectoryEntries', () => {
|
||||
it('appends only new entries and preserves order', () => {
|
||||
expect(addSparseDirectoryEntries(['apps/web'], ['apps/web', 'packages/ui'])).toEqual([
|
||||
'apps/web',
|
||||
'packages/ui'
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
formatBytes,
|
||||
formatCompactCount,
|
||||
getWorkspaceSpaceScanTimeLabel,
|
||||
getWorkspaceSpaceStatusLabel
|
||||
} from './workspace-space-format'
|
||||
|
||||
describe('workspace space format helpers', () => {
|
||||
it('formats byte values with stable units', () => {
|
||||
expect(formatBytes(0)).toBe('0 B')
|
||||
expect(formatBytes(512)).toBe('512 B')
|
||||
expect(formatBytes(1536)).toBe('1.50 KB')
|
||||
expect(formatBytes(5 * 1024 * 1024)).toBe('5.00 MB')
|
||||
})
|
||||
|
||||
it('formats counts and statuses for dense table UI', () => {
|
||||
expect(formatCompactCount(1530)).toBe('1.5k')
|
||||
expect(formatCompactCount(25_000)).toBe('25k')
|
||||
expect(getWorkspaceSpaceStatusLabel('permission-denied')).toBe('No access')
|
||||
})
|
||||
|
||||
it('formats scan times as relative age labels', () => {
|
||||
const now = new Date('2026-05-14T22:15:00Z').getTime()
|
||||
// Pinned to 'en' so the expectation matches the UI language, not the machine's OS locale.
|
||||
const formatter = new Intl.RelativeTimeFormat('en', { numeric: 'auto' })
|
||||
|
||||
expect(getWorkspaceSpaceScanTimeLabel(now - 2 * 60_000, now)).toBe(
|
||||
formatter.format(-2, 'minute')
|
||||
)
|
||||
expect(getWorkspaceSpaceScanTimeLabel(now - 3 * 60 * 60_000, now)).toBe(
|
||||
formatter.format(-3, 'hour')
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -1,52 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { getChecksLabel, getChecksPillTone } from './task-page-checks-pill'
|
||||
|
||||
describe('task page checks pill', () => {
|
||||
it('labels a green summary carrying one neutral check as passing', () => {
|
||||
const item = {
|
||||
checksSummary: {
|
||||
state: 'success' as const,
|
||||
total: 2,
|
||||
passed: 1,
|
||||
failed: 0,
|
||||
pending: 0,
|
||||
neutral: 1
|
||||
}
|
||||
}
|
||||
|
||||
expect(getChecksLabel(item)).toBe('1/2 passed')
|
||||
expect(getChecksPillTone(item)).toContain('emerald')
|
||||
})
|
||||
|
||||
it('only says unresolved when nothing passed, failed or is running', () => {
|
||||
const item = {
|
||||
checksSummary: {
|
||||
state: 'neutral' as const,
|
||||
total: 1,
|
||||
passed: 0,
|
||||
failed: 0,
|
||||
pending: 0,
|
||||
neutral: 1
|
||||
}
|
||||
}
|
||||
|
||||
expect(getChecksLabel(item)).toBe('Unresolved checks')
|
||||
expect(getChecksPillTone(item)).toContain('text-muted-foreground')
|
||||
})
|
||||
|
||||
it('falls back while the summary is unknown or empty', () => {
|
||||
expect(getChecksLabel({})).toBe('Checks')
|
||||
expect(
|
||||
getChecksLabel({
|
||||
checksSummary: {
|
||||
state: 'none',
|
||||
total: 0,
|
||||
passed: 0,
|
||||
failed: 0,
|
||||
pending: 0,
|
||||
neutral: 0
|
||||
}
|
||||
})
|
||||
).toBe('No checks')
|
||||
})
|
||||
})
|
||||
@@ -1,42 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { getRepoBackedTaskEmptyState } from './task-page-empty-state'
|
||||
|
||||
describe('getRepoBackedTaskEmptyState', () => {
|
||||
it('explains when no repo-backed task source is selected', () => {
|
||||
expect(
|
||||
getRepoBackedTaskEmptyState({
|
||||
provider: 'github',
|
||||
selectedRepoCount: 0
|
||||
})
|
||||
).toEqual({
|
||||
title: 'No project sources selected',
|
||||
description:
|
||||
'Select at least one project source so Orca knows which host/account to fetch tasks from.'
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps GitHub no-match copy when sources are selected', () => {
|
||||
expect(
|
||||
getRepoBackedTaskEmptyState({
|
||||
provider: 'github',
|
||||
selectedRepoCount: 2
|
||||
})
|
||||
).toEqual({
|
||||
title: 'No matching GitHub work',
|
||||
description: 'Change the query or clear it.'
|
||||
})
|
||||
})
|
||||
|
||||
it('uses GitLab view-specific no-match copy when sources are selected', () => {
|
||||
expect(
|
||||
getRepoBackedTaskEmptyState({
|
||||
provider: 'gitlab',
|
||||
selectedRepoCount: 1,
|
||||
gitlabView: 'mrs'
|
||||
})
|
||||
).toEqual({
|
||||
title: 'No GitLab merge requests',
|
||||
description: 'No GitLab MRs match this filter.'
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,35 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { getPageNumbers } from './task-page-pagination-page-numbers'
|
||||
|
||||
describe('getPageNumbers', () => {
|
||||
it('lists every zero-based page when the total fits the no-ellipsis threshold', () => {
|
||||
expect(getPageNumbers(0, 1)).toEqual([0])
|
||||
expect(getPageNumbers(4, 9)).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8])
|
||||
})
|
||||
|
||||
it('returns an empty list when there are no pages', () => {
|
||||
expect(getPageNumbers(0, 0)).toEqual([])
|
||||
})
|
||||
|
||||
it('collapses the tail when the current page is near the start', () => {
|
||||
expect(getPageNumbers(0, 20)).toEqual([0, 1, 2, 'ellipsis', 19])
|
||||
})
|
||||
|
||||
it('collapses the head when the current page is near the end', () => {
|
||||
expect(getPageNumbers(19, 20)).toEqual([0, 'ellipsis', 17, 18, 19])
|
||||
})
|
||||
|
||||
it('collapses both sides around a middle page', () => {
|
||||
expect(getPageNumbers(10, 20)).toEqual([0, 'ellipsis', 8, 9, 10, 11, 12, 'ellipsis', 19])
|
||||
})
|
||||
|
||||
it('omits the ellipsis when the window is exactly adjacent to an edge', () => {
|
||||
expect(getPageNumbers(3, 10)).toEqual([0, 1, 2, 3, 4, 5, 'ellipsis', 9])
|
||||
})
|
||||
|
||||
it('drops the window entirely when the current page is out of range', () => {
|
||||
expect(getPageNumbers(-5, 12)).toEqual([0, 'ellipsis', 11])
|
||||
expect(getPageNumbers(100, 12)).toEqual([0, 'ellipsis', 11])
|
||||
})
|
||||
})
|
||||
@@ -1,44 +0,0 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const toastError = vi.hoisted(() => vi.fn())
|
||||
|
||||
vi.mock('sonner', () => ({
|
||||
toast: {
|
||||
error: toastError
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('@/i18n/i18n', () => ({
|
||||
translate: (_key: string, fallback: string) => fallback
|
||||
}))
|
||||
|
||||
import { showTerminalDropWriteFailure } from './terminal-drop-write-failure'
|
||||
|
||||
afterEach(() => {
|
||||
toastError.mockClear()
|
||||
})
|
||||
|
||||
describe('terminal drop write failure notification', () => {
|
||||
it('does not show an error for stale targets', () => {
|
||||
showTerminalDropWriteFailure('target-stale')
|
||||
showTerminalDropWriteFailure(undefined)
|
||||
|
||||
expect(toastError).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('shows a timeout error without dropped path content', () => {
|
||||
showTerminalDropWriteFailure('operation-timeout')
|
||||
|
||||
expect(toastError).toHaveBeenCalledWith(
|
||||
'File drop cancelled: terminal did not accept the path before the safety timeout.'
|
||||
)
|
||||
})
|
||||
|
||||
it('shows a rejected-write error without dropped path content', () => {
|
||||
showTerminalDropWriteFailure('write-rejected')
|
||||
|
||||
expect(toastError).toHaveBeenCalledWith(
|
||||
'File drop cancelled: terminal could not accept the path.'
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -1,32 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
resolveTabTitleAfterPaneClose,
|
||||
shouldClearLaunchAgentForClosedPane
|
||||
} from './terminal-pane-close-identity'
|
||||
|
||||
describe('shouldClearLaunchAgentForClosedPane', () => {
|
||||
it('clears launch identity only when the launch-owning PTY closes', () => {
|
||||
const tab = { launchAgent: 'codex' as const, ptyId: 'pty-agent' }
|
||||
|
||||
expect(shouldClearLaunchAgentForClosedPane(tab, 'pty-agent')).toBe(true)
|
||||
expect(shouldClearLaunchAgentForClosedPane(tab, 'pty-shell')).toBe(false)
|
||||
})
|
||||
|
||||
it('does not mutate identity-free or not-yet-bound tabs', () => {
|
||||
expect(shouldClearLaunchAgentForClosedPane({ ptyId: 'pty-1' }, 'pty-1')).toBe(false)
|
||||
expect(
|
||||
shouldClearLaunchAgentForClosedPane({ launchAgent: 'claude', ptyId: null }, 'pty-1')
|
||||
).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('resolveTabTitleAfterPaneClose', () => {
|
||||
it('uses the promoted sibling title when one is known', () => {
|
||||
expect(resolveTabTitleAfterPaneClose({ 2: 'codex' }, 2)).toBe('codex')
|
||||
})
|
||||
|
||||
it('resets to the tab fallback when the promoted shell has no title', () => {
|
||||
expect(resolveTabTitleAfterPaneClose({ 1: 'closed agent' }, 2)).toBe('')
|
||||
expect(resolveTabTitleAfterPaneClose({}, null)).toBe('')
|
||||
})
|
||||
})
|
||||
-12
@@ -11,16 +11,4 @@ describe('createCaptureId', () => {
|
||||
|
||||
expect(createCaptureId(paneKey)).toMatch(TERMINAL_RENDER_DESYNC_CAPTURE_ID_PATTERN)
|
||||
})
|
||||
|
||||
it('keeps the leaf id so a capture is still traceable to its pane', () => {
|
||||
const leafId = createBrowserUuid()
|
||||
|
||||
expect(createCaptureId(makePaneKey(createBrowserUuid(), leafId))).toContain(leafId)
|
||||
})
|
||||
|
||||
it('does not collide for repeated captures of the same pane', () => {
|
||||
const paneKey = makePaneKey(createBrowserUuid(), createBrowserUuid())
|
||||
|
||||
expect(createCaptureId(paneKey)).not.toBe(createCaptureId(paneKey))
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { FORK_PUSH_NO_MAINTAINER_EDIT_WARNING, getForkPushWarning } from './fork-push-warning'
|
||||
|
||||
const forkTarget = { remoteName: 'pr-contributor-orca', branchName: 'contributor/fix' }
|
||||
const originTarget = { remoteName: 'origin', branchName: 'feature/fix' }
|
||||
|
||||
describe('getForkPushWarning', () => {
|
||||
it('warns for a fork PR whose author disabled maintainer edits', () => {
|
||||
expect(getForkPushWarning({ pushTarget: forkTarget, maintainerCanModify: false })).toBe(
|
||||
FORK_PUSH_NO_MAINTAINER_EDIT_WARNING
|
||||
)
|
||||
})
|
||||
|
||||
it('does not warn when maintainer edits are allowed', () => {
|
||||
expect(getForkPushWarning({ pushTarget: forkTarget, maintainerCanModify: true })).toBeNull()
|
||||
})
|
||||
|
||||
it('does not warn when the maintainer flag is unknown', () => {
|
||||
expect(getForkPushWarning({ pushTarget: forkTarget })).toBeNull()
|
||||
})
|
||||
|
||||
it('does not warn for a same-repo PR even if the flag is false (we own origin)', () => {
|
||||
expect(getForkPushWarning({ pushTarget: originTarget, maintainerCanModify: false })).toBeNull()
|
||||
})
|
||||
|
||||
it('does not warn when there is no resolved push target', () => {
|
||||
expect(getForkPushWarning({ maintainerCanModify: false })).toBeNull()
|
||||
})
|
||||
})
|
||||
@@ -1,34 +0,0 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { sshConnectingLabel, sshConnectVerb } from './ssh-connect-verb'
|
||||
|
||||
vi.mock('@/i18n/i18n', () => ({
|
||||
translate: (_key: string, fallback: string) => fallback
|
||||
}))
|
||||
|
||||
// One shared vocabulary keeps simultaneous sidebar/pane/header/status controls consistent.
|
||||
describe('sshConnectVerb', () => {
|
||||
it('names an authentication failure a reconnect', () => {
|
||||
expect(sshConnectVerb('auth-failed')).toBe('Reconnect')
|
||||
})
|
||||
|
||||
it.each(['error', 'reconnection-failed'] as const)('names %s a retry', (status) => {
|
||||
expect(sshConnectVerb(status)).toBe('Retry')
|
||||
})
|
||||
|
||||
it.each(['disconnected', 'connecting', 'deploying-relay', 'reconnecting', 'connected'] as const)(
|
||||
'falls back to Connect for %s',
|
||||
(status) => {
|
||||
expect(sshConnectVerb(status)).toBe('Connect')
|
||||
}
|
||||
)
|
||||
|
||||
it.each([null, undefined])('falls back to Connect for %s', (status) => {
|
||||
expect(sshConnectVerb(status)).toBe('Connect')
|
||||
})
|
||||
})
|
||||
|
||||
describe('sshConnectingLabel', () => {
|
||||
it('uses the single-character ellipsis, matching the rest of the catalog', () => {
|
||||
expect(sshConnectingLabel()).toBe('Connecting…')
|
||||
})
|
||||
})
|
||||
@@ -51,13 +51,6 @@ describe('compareSidebarText', () => {
|
||||
expect(compareSidebarText(`CAFE${COMBINING_ACUTE}`, nfc)).toBeLessThan(0)
|
||||
})
|
||||
|
||||
it('returns 0 only for canonically equal text, leaving the tie to the caller', () => {
|
||||
expect(compareSidebarText('same', 'same')).toBe(0)
|
||||
expect(compareSidebarText('', '')).toBe(0)
|
||||
expect(compareSidebarText('', 'a')).toBeLessThan(0)
|
||||
expect(compareSidebarText('a', 'a ')).toBeLessThan(0)
|
||||
})
|
||||
|
||||
it('is antisymmetric across a mixed corpus', () => {
|
||||
const corpus = [
|
||||
'w2',
|
||||
@@ -85,11 +78,6 @@ describe('compareCodePoints', () => {
|
||||
expect('🎉'.charCodeAt(0)).toBeLessThan(REPLACEMENT_CHAR.charCodeAt(0))
|
||||
expect(compareCodePoints('🎉', REPLACEMENT_CHAR)).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it('orders a prefix first', () => {
|
||||
expect(compareCodePoints('local|a', 'local|ab')).toBeLessThan(0)
|
||||
expect(compareCodePoints('x', 'x')).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('createSidebarTextComparator', () => {
|
||||
|
||||
@@ -10,17 +10,6 @@ const comparators = [
|
||||
] as const
|
||||
|
||||
describe.each(comparators)('%s', (_name, valuesEqual) => {
|
||||
it('compares primitives and identical references', () => {
|
||||
expect(valuesEqual('a', 'a')).toBe(true)
|
||||
expect(valuesEqual('a', 'b')).toBe(false)
|
||||
expect(valuesEqual(null, null)).toBe(true)
|
||||
expect(valuesEqual(null, {})).toBe(false)
|
||||
expect(valuesEqual(undefined, null)).toBe(false)
|
||||
const fn = (): void => {}
|
||||
expect(valuesEqual(fn, fn)).toBe(true)
|
||||
expect(valuesEqual(fn, (): void => {})).toBe(false)
|
||||
})
|
||||
|
||||
it('walks arrays element-wise and never mixes them with records', () => {
|
||||
expect(valuesEqual([1, [2, { a: 'b' }]], [1, [2, { a: 'b' }]])).toBe(true)
|
||||
expect(valuesEqual([1, 2], [1, 2, 3])).toBe(false)
|
||||
@@ -56,11 +45,6 @@ describe.each(comparators)('%s', (_name, valuesEqual) => {
|
||||
expect(valuesEqual(new Row(), new Row())).toBe(false)
|
||||
expect(valuesEqual(new Row(), { x: 1 })).toBe(false)
|
||||
})
|
||||
|
||||
it('ignores symbol keys', () => {
|
||||
const key = Symbol('marker')
|
||||
expect(valuesEqual({ [key]: 1, a: 1 }, { [key]: 2, a: 1 })).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('structuralValuesEqual', () => {
|
||||
|
||||
@@ -122,9 +122,4 @@ describe('first structured chat prompt', () => {
|
||||
const url = 'https://example.test/issues/123'
|
||||
expect(firstStructuredAgentSessionPrompt([message(url), message('Later')])).toBe(url)
|
||||
})
|
||||
|
||||
it('returns empty when no user prose exists', () => {
|
||||
expect(firstStructuredAgentSessionPrompt([])).toBe('')
|
||||
expect(firstStructuredAgentSessionPrompt([message(' ')])).toBe('')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -21,12 +21,6 @@ describe('stripTerminalSelectionGutter', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('drops the gutter from a single wrapped line', () => {
|
||||
expect(stripTerminalSelectionGutter(' one logical line, joined by xterm')).toBe(
|
||||
'one logical line, joined by xterm'
|
||||
)
|
||||
})
|
||||
|
||||
it('keeps relative indentation inside the gutter', () => {
|
||||
const nested = [' def run():', ' return 1', '', ' run()'].join('\n')
|
||||
expect(stripTerminalSelectionGutter(nested)).toBe(
|
||||
@@ -39,17 +33,6 @@ describe('stripTerminalSelectionGutter', () => {
|
||||
expect(stripTerminalSelectionGutter(midLine)).toBe(midLine)
|
||||
})
|
||||
|
||||
it('leaves unindented output untouched', () => {
|
||||
const shellOutput = ['$ git status', 'On branch main', 'nothing to commit'].join('\n')
|
||||
expect(stripTerminalSelectionGutter(shellOutput)).toBe(shellOutput)
|
||||
})
|
||||
|
||||
it('ignores blank lines when measuring the gutter', () => {
|
||||
expect(stripTerminalSelectionGutter([' a', '', ' b'].join('\n'))).toBe(
|
||||
['a', '', 'b'].join('\n')
|
||||
)
|
||||
})
|
||||
|
||||
it('ignores whitespace-only lines when measuring the gutter', () => {
|
||||
expect(stripTerminalSelectionGutter([' a', ' ', ' b'].join('\n'))).toBe(
|
||||
['a', '', 'b'].join('\n')
|
||||
@@ -71,21 +54,11 @@ describe('stripTerminalSelectionGutter', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('leaves wide characters and emoji in the content alone', () => {
|
||||
expect(
|
||||
stripTerminalSelectionGutter([' 変更を適用しました 🎉', ' お疲れさま'].join('\n'))
|
||||
).toBe(['変更を適用しました 🎉', 'お疲れさま'].join('\n'))
|
||||
})
|
||||
|
||||
it('does not strip past a shorter line', () => {
|
||||
const uneven = [' deep', ' shallow'].join('\n')
|
||||
expect(stripTerminalSelectionGutter(uneven)).toBe([' deep', 'shallow'].join('\n'))
|
||||
})
|
||||
|
||||
it('is a no-op on an empty selection', () => {
|
||||
expect(stripTerminalSelectionGutter('')).toBe('')
|
||||
})
|
||||
|
||||
it('leaves tab-indented text alone (terminal cells never hold tabs)', () => {
|
||||
const tabbed = ['\tone', '\ttwo'].join('\n')
|
||||
expect(stripTerminalSelectionGutter(tabbed)).toBe(tabbed)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { FLOATING_TERMINAL_WORKTREE_ID } from '../constants'
|
||||
import { ownedEditorFileId } from '../workspace-session-pane-ownership'
|
||||
import { build, emptyModel, testContext, WS } from './workspace-layout-command.test-fixture'
|
||||
import { applyLayoutCommand } from './workspace-layout-commands'
|
||||
@@ -40,12 +39,6 @@ function workspace(
|
||||
}
|
||||
|
||||
describe('the file id main gives a file it opens', () => {
|
||||
it('is the owned id of a file no tab shows, never the bare path another host may hold', () => {
|
||||
expect(editorOpenFileId(workspace(WT, []), file('/repo/a.ts'))).toBe(
|
||||
ownedEditorFileId('/repo/a.ts', WT, null)
|
||||
)
|
||||
})
|
||||
|
||||
it("is the id a tab already shows the same owner's file under, the bare path included", () => {
|
||||
expect(
|
||||
editorOpenFileId(workspace(WT, [file('/repo/a.ts')], ['/repo/a.ts']), file('/repo/a.ts'))
|
||||
@@ -58,12 +51,6 @@ describe('the file id main gives a file it opens', () => {
|
||||
ownedEditorFileId('/repo/a.ts', WT, null)
|
||||
)
|
||||
})
|
||||
|
||||
it('is an owned id in the floating workspace', () => {
|
||||
expect(
|
||||
editorOpenFileId(workspace(FLOATING_TERMINAL_WORKTREE_ID, []), file('/tmp/notes.md'))
|
||||
).toBe(ownedEditorFileId('/tmp/notes.md', FLOATING_TERMINAL_WORKTREE_ID, null))
|
||||
})
|
||||
})
|
||||
|
||||
describe('an open main makes itself, naming the file but not its id', () => {
|
||||
|
||||
@@ -1,48 +1,6 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { z } from 'zod'
|
||||
|
||||
import { parseWorkspaceSession, workspaceSessionStateSchema } from './workspace-session-schema'
|
||||
import type { WorkspaceSessionState } from './workspace-session-state-types'
|
||||
|
||||
/** Zod strips unlisted fields at local and SSH load boundaries although writes accept them. Pin schema coverage to WorkspaceSessionState so fields survive quit/relaunch, including close tombstones that otherwise resurrect tabs. */
|
||||
const PERSISTED_WORKSPACE_SESSION_FIELDS = {
|
||||
activeRepoId: true,
|
||||
activeWorkspaceKey: true,
|
||||
activeWorkspaceExecutionHostId: true,
|
||||
activeWorktreeId: true,
|
||||
activeTabId: true,
|
||||
tabsByWorktree: true,
|
||||
terminalLayoutsByTabId: true,
|
||||
localOnlyScrollbackByTabId: true,
|
||||
activeWorktreeIdsOnShutdown: true,
|
||||
openFilesByWorktree: true,
|
||||
activeFileIdByWorktree: true,
|
||||
markdownFrontmatterVisible: true,
|
||||
browserTabsByWorktree: true,
|
||||
browserPagesByWorkspace: true,
|
||||
activeBrowserTabIdByWorktree: true,
|
||||
clientHostedBrowserPagesByWorktree: true,
|
||||
clientHostedBrowserCloseIntentsByEnvironment: true,
|
||||
pairedTerminalTabCloseIntentsByEnvironment: true,
|
||||
activeTabTypeByWorktree: true,
|
||||
browserUrlHistory: true,
|
||||
workspaceDocHistory: true,
|
||||
activeTabIdByWorktree: true,
|
||||
unifiedTabs: true,
|
||||
tabGroups: true,
|
||||
tabGroupLayouts: true,
|
||||
activeGroupIdByWorktree: true,
|
||||
activeConnectionIdsAtShutdown: true,
|
||||
remoteSessionIdsByTabId: true,
|
||||
lastVisitedAtByWorktreeId: true,
|
||||
defaultTerminalTabsAppliedByWorktreeId: true,
|
||||
sleepingAgentSessionsByPaneKey: true,
|
||||
terminalPtyIncarnationsByPaneKey: true,
|
||||
terminalTopologyRevisionByRepoId: true,
|
||||
terminalSurfaceTombstonesByPaneKey: true,
|
||||
closedTerminalTabTombstonesByTabId: true,
|
||||
closedTerminalPanesByPaneKey: true
|
||||
} satisfies Record<keyof WorkspaceSessionState, true>
|
||||
import { parseWorkspaceSession } from './workspace-session-schema'
|
||||
|
||||
const MINIMAL_SESSION = {
|
||||
activeRepoId: null,
|
||||
@@ -53,21 +11,6 @@ const MINIMAL_SESSION = {
|
||||
}
|
||||
|
||||
describe('workspaceSessionStateSchema field coverage', () => {
|
||||
it('parses every field declared on WorkspaceSessionState', () => {
|
||||
// The satisfies guard checks the field list; this runtime check catches schema omissions by name.
|
||||
// The public ZodType annotation hides shape; inspect the underlying object without widening that API.
|
||||
const schemaKeys = new Set(
|
||||
Object.keys(
|
||||
(workspaceSessionStateSchema as unknown as z.ZodObject<Record<string, z.ZodTypeAny>>).shape
|
||||
)
|
||||
)
|
||||
const missing = Object.keys(PERSISTED_WORKSPACE_SESSION_FIELDS).filter(
|
||||
(field) => !schemaKeys.has(field)
|
||||
)
|
||||
|
||||
expect(missing, `declared on WorkspaceSessionState but absent from the load schema`).toEqual([])
|
||||
})
|
||||
|
||||
it('round-trips the closed-tab tombstone map through parseWorkspaceSession', () => {
|
||||
// A persisted close must survive relaunch or disconnected tabs reappear despite every single-process test passing.
|
||||
const parsed = parseWorkspaceSession({
|
||||
|
||||
@@ -99,16 +99,4 @@ describe('workspace space scan budget', () => {
|
||||
).rejects.toBeInstanceOf(WorkspaceSpaceScanCapacityError)
|
||||
expect(closed).toBe(true)
|
||||
})
|
||||
|
||||
it('reports the configured cap rather than the default limits', async () => {
|
||||
await expect(
|
||||
collectWorkspaceSpaceDirectoryEntries(
|
||||
[{ name: 'first' }, { name: 'second' }],
|
||||
'/workspace',
|
||||
(entry) => entry.name,
|
||||
createWorkspaceSpaceScanBudget({ maxEntries: 1, maxRetainedBytes: 4 * 1024 * 1024 }),
|
||||
() => undefined
|
||||
)
|
||||
).rejects.toThrow('limit: 1 entries or 4 MiB of live scan state')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -61,15 +61,6 @@ describe('readRetiredNameRegistryForRepo', () => {
|
||||
})
|
||||
|
||||
describe('retiredNamesAfterRefresh', () => {
|
||||
it('takes the new registry on success', () => {
|
||||
expect(
|
||||
retiredNamesAfterRefresh(loadOf('repo-1', ['nautilus']), 'repo-1', {
|
||||
exhaustedTiers: 1,
|
||||
names: ['nautilus-2']
|
||||
})
|
||||
).toEqual(loadOf('repo-1', ['nautilus-2'], 1))
|
||||
})
|
||||
|
||||
// A failed refresh must not un-retire names while the create form requests suggestions.
|
||||
it('holds the previous registry when the refresh fails', () => {
|
||||
expect(retiredNamesAfterRefresh(loadOf('repo-1', ['nautilus'], 2), 'repo-1', null)).toEqual(
|
||||
@@ -83,12 +74,6 @@ describe('retiredNamesAfterRefresh', () => {
|
||||
registry: EMPTY_RETIRED_NAME_REGISTRY
|
||||
})
|
||||
})
|
||||
|
||||
it('answers empty when the first fetch for a repo fails', () => {
|
||||
expect(retiredNamesAfterRefresh(null, 'repo-1', null).registry).toEqual(
|
||||
EMPTY_RETIRED_NAME_REGISTRY
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('selectRetiredNameRegistry', () => {
|
||||
@@ -99,9 +84,4 @@ describe('selectRetiredNameRegistry', () => {
|
||||
expect(selectRetiredNameRegistry(loaded, null)).toEqual(EMPTY_RETIRED_NAME_REGISTRY)
|
||||
expect(selectRetiredNameRegistry(null, 'repo-1')).toEqual(EMPTY_RETIRED_NAME_REGISTRY)
|
||||
})
|
||||
|
||||
it('returns the stored registry itself so downstream memos do not rerun', () => {
|
||||
const loaded = loadOf('repo-1', ['nautilus'])
|
||||
expect(selectRetiredNameRegistry(loaded, 'repo-1')).toBe(loaded.registry)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user