mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 00:57:45 +00:00
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:
@@ -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,
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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'
|
||||
)
|
||||
})
|
||||
})
|
||||
-44
@@ -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)
|
||||
})
|
||||
})
|
||||
-9
@@ -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()
|
||||
|
||||
-87
@@ -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')
|
||||
|
||||
-82
@@ -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)
|
||||
|
||||
-58
@@ -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()
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user