mirror of
https://github.com/stablyai/orca.git
synced 2026-11-03 18:47:40 +00:00
test: remove 68 low-value cases and 18 suites (#34112)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
@@ -7379,7 +7379,6 @@
|
||||
"src/renderer/src/components/dashboard/AgentDashboardSettingsMenu.test.tsx": 479,
|
||||
"src/renderer/src/components/dashboard/DashboardAgentChildDisclosure.test.tsx": 1607,
|
||||
"src/renderer/src/components/dashboard/DashboardAgentRow.test.tsx": 1601,
|
||||
"src/renderer/src/components/dashboard/DashboardAgentRowToolStep.test.tsx": 60,
|
||||
"src/renderer/src/components/dashboard/agent-dashboard-open-mode.test.ts": 43,
|
||||
"src/renderer/src/components/dashboard/agent-finished-timestamp.test.ts": 115,
|
||||
"src/renderer/src/components/dashboard/agent-row-lineage-model.test.ts": 99,
|
||||
@@ -7909,7 +7908,6 @@
|
||||
"src/renderer/src/components/landing-preflight-runtime-boundary.test.ts": 1576,
|
||||
"src/renderer/src/components/landing-preflight-unverifiable-host.test.tsx": 1436,
|
||||
"src/renderer/src/components/linear-api-key-dialog-state.test.ts": 53,
|
||||
"src/renderer/src/components/linear-issue-attribute-filter-coverage-agreement.test.ts": 590,
|
||||
"src/renderer/src/components/linear-issue-attribute-filter-dropdowns.test.tsx": 1226,
|
||||
"src/renderer/src/components/linear-issue-attribute-filter-primary-team.test.ts": 53,
|
||||
"src/renderer/src/components/linear-issue-attribute-filter-team-ids.test.ts": 48,
|
||||
@@ -7919,7 +7917,6 @@
|
||||
"src/renderer/src/components/linear-issue-view-storage.test.ts": 168,
|
||||
"src/renderer/src/components/linear-issue-workspace-detail-state.test.tsx": 205,
|
||||
"src/renderer/src/components/linear-issue-workspace-text.test.ts": 128,
|
||||
"src/renderer/src/components/linear-project-presentation.test.ts": 32,
|
||||
"src/renderer/src/components/linear-project-search-query.test.ts": 66,
|
||||
"src/renderer/src/components/linear-project-view-surfaces.test.tsx": 440,
|
||||
"src/renderer/src/components/linear-scope-selector.test.ts": 155,
|
||||
@@ -8443,7 +8440,6 @@
|
||||
"src/renderer/src/components/right-sidebar/PortsPanel.test.tsx": 1370,
|
||||
"src/renderer/src/components/right-sidebar/PullRequestComposer.generate-tooltip.test.tsx": 804,
|
||||
"src/renderer/src/components/right-sidebar/SearchResultItems.test.tsx": 158,
|
||||
"src/renderer/src/components/right-sidebar/SessionRowTrailingActions.test.tsx": 212,
|
||||
"src/renderer/src/components/right-sidebar/SourceControl.branch-line-total.test.tsx": 1173,
|
||||
"src/renderer/src/components/right-sidebar/SourceControl.commit-drafts.test.ts": 146,
|
||||
"src/renderer/src/components/right-sidebar/SourceControl.commit-failure-recovery.test.ts": 68,
|
||||
@@ -8573,7 +8569,6 @@
|
||||
"src/renderer/src/components/right-sidebar/file-explorer-watch-selected-host.test.ts": 1446,
|
||||
"src/renderer/src/components/right-sidebar/file-search-include-pattern.test.ts": 36,
|
||||
"src/renderer/src/components/right-sidebar/folder-workspace-attached-worktrees.test.ts": 57,
|
||||
"src/renderer/src/components/right-sidebar/fork-push-target-label.test.ts": 35,
|
||||
"src/renderer/src/components/right-sidebar/git-status-push-signal-refresh.test.ts": 161,
|
||||
"src/renderer/src/components/right-sidebar/git-status-refresh-branch-line-total.test.ts": 442,
|
||||
"src/renderer/src/components/right-sidebar/git-status-refresh-scheduler.test.ts": 64,
|
||||
@@ -8611,7 +8606,6 @@
|
||||
"src/renderer/src/components/right-sidebar/source-control-active-open-file-keys.test.ts": 28,
|
||||
"src/renderer/src/components/right-sidebar/source-control-branch-context-row.test.tsx": 248,
|
||||
"src/renderer/src/components/right-sidebar/source-control-branch-context-stats.test.ts": 102,
|
||||
"src/renderer/src/components/right-sidebar/source-control-branch-section-heading.test.tsx": 1344,
|
||||
"src/renderer/src/components/right-sidebar/source-control-commit-eligibility.test.ts": 57,
|
||||
"src/renderer/src/components/right-sidebar/source-control-commit-message-rows.test.ts": 70,
|
||||
"src/renderer/src/components/right-sidebar/source-control-create-pr-intent-flow.test.ts": 57,
|
||||
@@ -8860,7 +8854,6 @@
|
||||
"src/renderer/src/components/settings/TerminalSettingsPreview.lifecycle.test.tsx": 318,
|
||||
"src/renderer/src/components/settings/TerminalTccAttributionNotice.test.tsx": 282,
|
||||
"src/renderer/src/components/settings/TerminalThemeSections.lifecycle.test.ts": 1337,
|
||||
"src/renderer/src/components/settings/UnsealedCredentialNotice.test.tsx": 304,
|
||||
"src/renderer/src/components/settings/VoiceDictationSettingsSection.test.tsx": 491,
|
||||
"src/renderer/src/components/settings/VoiceMicrophoneSetting.test.tsx": 577,
|
||||
"src/renderer/src/components/settings/VoicePane.test.tsx": 770,
|
||||
@@ -8908,7 +8901,6 @@
|
||||
"src/renderer/src/components/settings/plugin-install-source.test.ts": 110,
|
||||
"src/renderer/src/components/settings/provider-account-scope.test.ts": 109,
|
||||
"src/renderer/src/components/settings/provider-account-visibility.test.ts": 49,
|
||||
"src/renderer/src/components/settings/provider-rate-limit-scope-panels.test.tsx": 475,
|
||||
"src/renderer/src/components/settings/repository-source-control-ai-global-ux.test.ts": 585,
|
||||
"src/renderer/src/components/settings/repository/RepositoryJiraProjectSection.test.tsx": 292,
|
||||
"src/renderer/src/components/settings/repository/RepositoryPaneDraftInput.test.tsx": 428,
|
||||
@@ -8931,7 +8923,6 @@
|
||||
"src/renderer/src/components/settings/runtime-server-row-presentation.test.ts": 357,
|
||||
"src/renderer/src/components/settings/runtime-server-row-unverifiable-host.test.tsx": 2162,
|
||||
"src/renderer/src/components/settings/runtime-share-computer-button.test.tsx": 1304,
|
||||
"src/renderer/src/components/settings/session-history-status-copy.test.ts": 225,
|
||||
"src/renderer/src/components/settings/session-search-computer-rollup.test.ts": 42,
|
||||
"src/renderer/src/components/settings/settings-deep-link-target-watcher.test.ts": 149,
|
||||
"src/renderer/src/components/settings/settings-form-option-filter.test.ts": 41,
|
||||
@@ -8941,7 +8932,6 @@
|
||||
"src/renderer/src/components/settings/settings-project-regrouping.test.ts": 467,
|
||||
"src/renderer/src/components/settings/settings-search-keywords.test.ts": 123,
|
||||
"src/renderer/src/components/settings/settings-search.test.ts": 49,
|
||||
"src/renderer/src/components/settings/settings-setup-guide-progress-hook.test.tsx": 85,
|
||||
"src/renderer/src/components/settings/settings-setup-guide-progress.test.ts": 408,
|
||||
"src/renderer/src/components/settings/shortcut-binding-list-mutations.test.ts": 46,
|
||||
"src/renderer/src/components/settings/shortcut-definition-catalog.test.ts": 222,
|
||||
@@ -8990,7 +8980,6 @@
|
||||
"src/renderer/src/components/sidebar/AddWslHostFormPanel.test.tsx": 220,
|
||||
"src/renderer/src/components/sidebar/AgentDashboardSidebarHost.test.tsx": 1548,
|
||||
"src/renderer/src/components/sidebar/AutoRenameFailedDialog.test.tsx": 1246,
|
||||
"src/renderer/src/components/sidebar/CacheTimer.test.tsx": 79,
|
||||
"src/renderer/src/components/sidebar/CommentMarkdown.code-block-renderer.test.tsx": 2013,
|
||||
"src/renderer/src/components/sidebar/CommentMarkdown.file-link-cases.test.tsx": 1688,
|
||||
"src/renderer/src/components/sidebar/CommentMarkdown.github-attachment-image.test.tsx": 1597,
|
||||
@@ -9025,7 +9014,6 @@
|
||||
"src/renderer/src/components/sidebar/RemoveFolderDialog.test.tsx": 754,
|
||||
"src/renderer/src/components/sidebar/SetupGuideSidebarEntry.test.tsx": 433,
|
||||
"src/renderer/src/components/sidebar/SetupScriptPromptCard.test.ts": 73,
|
||||
"src/renderer/src/components/sidebar/SetupScriptPromptCardShell.test.tsx": 375,
|
||||
"src/renderer/src/components/sidebar/SeveredTerminalDaemonBanner.test.tsx": 393,
|
||||
"src/renderer/src/components/sidebar/Sidebar.test.tsx": 597,
|
||||
"src/renderer/src/components/sidebar/SidebarAgentsList.test.tsx": 2110,
|
||||
@@ -9072,14 +9060,12 @@
|
||||
"src/renderer/src/components/sidebar/WorktreeCardLinkedItemsSection.test.tsx": 3296,
|
||||
"src/renderer/src/components/sidebar/WorktreeCardMeta.interaction.test.tsx": 1658,
|
||||
"src/renderer/src/components/sidebar/WorktreeCardMeta.test.tsx": 2091,
|
||||
"src/renderer/src/components/sidebar/WorktreeCardPorts.test.tsx": 970,
|
||||
"src/renderer/src/components/sidebar/WorktreeCardSshHostControl.test.tsx": 3049,
|
||||
"src/renderer/src/components/sidebar/WorktreeCardStatusSlot.test.tsx": 312,
|
||||
"src/renderer/src/components/sidebar/WorktreeContextMenu.copy-worktree-name.test.tsx": 2107,
|
||||
"src/renderer/src/components/sidebar/WorktreeContextMenu.delete-shortcut.test.tsx": 1570,
|
||||
"src/renderer/src/components/sidebar/WorktreeContextMenu.react185-bystander.test.tsx": 1475,
|
||||
"src/renderer/src/components/sidebar/WorktreeContextMenu.test.ts": 164,
|
||||
"src/renderer/src/components/sidebar/WorktreeDeveloperMenu.test.tsx": 63,
|
||||
"src/renderer/src/components/sidebar/WorktreeDeveloperMenuReveal.test.tsx": 702,
|
||||
"src/renderer/src/components/sidebar/WorktreeList.card-memo-stability.test.tsx": 819,
|
||||
"src/renderer/src/components/sidebar/WorktreeList.empty-project-rows.test.ts": 3395,
|
||||
@@ -9096,7 +9082,6 @@
|
||||
"src/renderer/src/components/sidebar/WorktreeReferenceStack.test.tsx": 354,
|
||||
"src/renderer/src/components/sidebar/WorktreeTitleInlineRename.begin-editing.test.tsx": 121,
|
||||
"src/renderer/src/components/sidebar/WorktreeTitleInlineRename.editor-lifecycle.test.tsx": 475,
|
||||
"src/renderer/src/components/sidebar/WorktreeTitleInlineRename.test.tsx": 184,
|
||||
"src/renderer/src/components/sidebar/WorktreeVisibilityDialog.test.tsx": 2043,
|
||||
"src/renderer/src/components/sidebar/WorktreeVisibilityHelpPopover.test.tsx": 431,
|
||||
"src/renderer/src/components/sidebar/WorktreeVisibilitySourceList.test.tsx": 408,
|
||||
@@ -9138,7 +9123,6 @@
|
||||
"src/renderer/src/components/sidebar/host-section-collapse.test.ts": 230,
|
||||
"src/renderer/src/components/sidebar/host-section-context-labels.test.ts": 75,
|
||||
"src/renderer/src/components/sidebar/host-section-folder-workspace-counts.test.ts": 37,
|
||||
"src/renderer/src/components/sidebar/host-section-local-label.test.ts": 58,
|
||||
"src/renderer/src/components/sidebar/host-section-order.test.ts": 57,
|
||||
"src/renderer/src/components/sidebar/host-section-reveal.test.tsx": 1750,
|
||||
"src/renderer/src/components/sidebar/host-section-rows.test.ts": 66,
|
||||
@@ -9395,7 +9379,6 @@
|
||||
"src/renderer/src/components/skills/skill-source-inventory.test.ts": 73,
|
||||
"src/renderer/src/components/skills/skill-update-run-store.test.ts": 137,
|
||||
"src/renderer/src/components/skills/skills-filter.test.ts": 130,
|
||||
"src/renderer/src/components/source-control/SourceControlActionVariableChips.test.tsx": 191,
|
||||
"src/renderer/src/components/sparse/SparseCheckoutPresetDraftForm.test.tsx": 466,
|
||||
"src/renderer/src/components/sparse/SparsePresetChooser.test.tsx": 510,
|
||||
"src/renderer/src/components/sparse/sparse-directory-browse.test.ts": 45,
|
||||
@@ -10289,7 +10272,6 @@
|
||||
"src/renderer/src/components/ui/popover-wheel-scroll.test.tsx": 409,
|
||||
"src/renderer/src/components/ui/repo-multi-combobox.test.ts": 187,
|
||||
"src/renderer/src/components/ui/scroll-area.test.tsx": 219,
|
||||
"src/renderer/src/components/ui/security-surface.test.tsx": 357,
|
||||
"src/renderer/src/components/ui/slider.test.tsx": 282,
|
||||
"src/renderer/src/components/ui/switch.test.tsx": 68,
|
||||
"src/renderer/src/components/unexpected-signout/unexpected-signout-card.test.tsx": 1890,
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
import { renderToStaticMarkup } from 'react-dom/server'
|
||||
import type { ComponentProps } from 'react'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { DashboardAgentRowToolStep } from './DashboardAgentRowToolStep'
|
||||
|
||||
function renderStep(props: Partial<ComponentProps<typeof DashboardAgentRowToolStep>>): string {
|
||||
return renderToStaticMarkup(
|
||||
<DashboardAgentRowToolStep
|
||||
expanded={false}
|
||||
showsTool
|
||||
reservesHeight={false}
|
||||
toolName=""
|
||||
toolInput=""
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
describe('DashboardAgentRowToolStep', () => {
|
||||
it('shows a program message alone when no tool is named', () => {
|
||||
const markup = renderStep({ toolInput: 'Pouring wget' })
|
||||
|
||||
expect(markup).toContain('data-agent-row-tool-input="true"')
|
||||
expect(markup).toContain('Pouring wget')
|
||||
expect(markup).not.toContain('lucide-wrench')
|
||||
expect(markup).not.toContain('data-agent-row-tool-header')
|
||||
expect(markup).not.toContain('data-agent-row-tool-placeholder')
|
||||
})
|
||||
|
||||
it('keeps the placeholder when neither a tool nor input is known yet', () => {
|
||||
const markup = renderStep({ reservesHeight: true })
|
||||
|
||||
expect(markup).toContain('data-agent-row-tool-placeholder="true"')
|
||||
expect(markup).not.toContain('data-agent-row-tool-input')
|
||||
})
|
||||
|
||||
it('renders nothing with no tool, no input and no reserved height', () => {
|
||||
expect(renderStep({})).toBe('')
|
||||
})
|
||||
|
||||
it('keeps the wrench header when a tool is named', () => {
|
||||
const markup = renderStep({ toolName: 'bash', toolInput: 'ls' })
|
||||
|
||||
expect(markup).toContain('lucide-wrench')
|
||||
expect(markup).not.toContain('data-agent-row-tool-input')
|
||||
})
|
||||
})
|
||||
@@ -1,39 +0,0 @@
|
||||
// The pill and inline coverage notice must use one predicate so their claims cannot diverge.
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { LinearFacetCoverageNotice } from './linear-issue-attribute-filter-coverage-notice'
|
||||
import { isLinearMetadataGroupSelectionPartial } from './linear-issue-attribute-filter-team-ids'
|
||||
|
||||
function randomGroups(rng: () => number): { key: string; ids: string[] }[] {
|
||||
const count = 1 + Math.floor(rng() * 8)
|
||||
let next = 0
|
||||
return Array.from({ length: count }, (_unused, group) => ({
|
||||
key: `g${group}`,
|
||||
ids: Array.from({ length: 1 + Math.floor(rng() * 4) }, () => `id-${next++}`)
|
||||
}))
|
||||
}
|
||||
|
||||
describe('coverage pill and notice agree', () => {
|
||||
it('shows the notice exactly when the pill reads partial', () => {
|
||||
let seed = 1337
|
||||
const rng = (): number => {
|
||||
seed = (seed * 1103515245 + 12345) % 2147483648
|
||||
return seed / 2147483648
|
||||
}
|
||||
|
||||
for (let run = 0; run < 20000; run += 1) {
|
||||
const groups = randomGroups(rng)
|
||||
const every = groups.flatMap((group) => group.ids)
|
||||
const selected = every.filter(() => rng() < 0.5)
|
||||
const truncated = rng() < 0.3
|
||||
const partial = isLinearMetadataGroupSelectionPartial(groups, selected, truncated)
|
||||
const notice = LinearFacetCoverageNotice({
|
||||
facet: 'status',
|
||||
options: groups,
|
||||
selectedIds: selected,
|
||||
max: 100,
|
||||
truncated
|
||||
})
|
||||
expect(notice === null).toBe(!partial)
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -1,38 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import type { LinearProjectSummary } from '../../../shared/linear/project-types'
|
||||
import {
|
||||
linearProjectDateLabel,
|
||||
linearProjectMetadataLabels,
|
||||
linearProjectPriorityLabel,
|
||||
linearProjectProgressPercent,
|
||||
linearProjectUnknownText,
|
||||
linearProjectWorkspaceLabel
|
||||
} from './linear-project-presentation'
|
||||
|
||||
function project(progress?: number | null): LinearProjectSummary {
|
||||
return { id: 'project-1', name: 'Compiler', progress }
|
||||
}
|
||||
|
||||
describe('Linear project presentation', () => {
|
||||
it('keeps optional mixed-version metadata permissive', () => {
|
||||
expect(linearProjectUnknownText({ displayName: 'Ada' })).toBe('Ada')
|
||||
expect(linearProjectUnknownText({ body: ' Shipped ' })).toBe('Shipped')
|
||||
expect(linearProjectUnknownText({ unknown: 'value' })).toBeNull()
|
||||
expect(linearProjectUnknownText(undefined)).toBeNull()
|
||||
expect(linearProjectMetadataLabels([{ name: 'A' }, null, { title: 'B' }], 1)).toEqual(['A'])
|
||||
})
|
||||
|
||||
it('preserves date, priority, progress, and workspace fallbacks', () => {
|
||||
expect(linearProjectDateLabel(null)).toBe('None')
|
||||
expect(linearProjectDateLabel('not-a-date')).toBe('not-a-date')
|
||||
expect(linearProjectPriorityLabel(2, 'Urgent')).toBe('Urgent')
|
||||
expect(linearProjectPriorityLabel(0, null)).toBe('None')
|
||||
expect(linearProjectPriorityLabel(3, null)).toBe('P3')
|
||||
expect(linearProjectProgressPercent(project(0.42))).toBe(42)
|
||||
expect(linearProjectProgressPercent(project(42.4))).toBe(42)
|
||||
expect(linearProjectProgressPercent(project(Number.NaN))).toBeNull()
|
||||
expect(linearProjectWorkspaceLabel('all', 'Acme')).toBe('Acme')
|
||||
expect(linearProjectWorkspaceLabel('workspace-1', 'Acme')).toBeNull()
|
||||
})
|
||||
})
|
||||
@@ -1,47 +0,0 @@
|
||||
import { renderToStaticMarkup } from 'react-dom/server'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { TooltipProvider } from '@/components/ui/tooltip'
|
||||
import type { AiVaultSession } from '../../../../shared/ai-vault-types'
|
||||
import { SessionRowTrailingActions } from './SessionRowTrailingActions'
|
||||
|
||||
const session = { agent: 'claude' } as AiVaultSession
|
||||
|
||||
function renderActions(onContinueInNewSession?: () => void): string {
|
||||
return renderToStaticMarkup(
|
||||
<TooltipProvider>
|
||||
<SessionRowTrailingActions
|
||||
session={session}
|
||||
detailsExpanded={false}
|
||||
detailsId="session-details"
|
||||
detailsTooltip="Show Details"
|
||||
resumeDisabled={false}
|
||||
resumeLabel="Resume in New Tab"
|
||||
worktreeInfo={null}
|
||||
onToggleDetails={vi.fn()}
|
||||
showJumpToWorktree={false}
|
||||
onResume={vi.fn()}
|
||||
onContinueInNewSession={onContinueInNewSession}
|
||||
onCopyId={vi.fn()}
|
||||
onCopyPath={vi.fn()}
|
||||
deleteBlockedReason={null}
|
||||
onRequestDelete={vi.fn()}
|
||||
/>
|
||||
</TooltipProvider>
|
||||
)
|
||||
}
|
||||
|
||||
describe('SessionRowTrailingActions', () => {
|
||||
it('renders the new-session action as a hover-revealed control when available', () => {
|
||||
const markup = renderActions(vi.fn())
|
||||
|
||||
expect(markup).toContain('data-testid="ai-vault-session-continue-in-new-session"')
|
||||
expect(markup).toContain('aria-label="Hand Off to Another Agent"')
|
||||
// Hidden hover actions must reject clicks as well as disappear.
|
||||
const buttonMarkup = markup.split('data-testid="ai-vault-session-continue-in-new-session"')[0]
|
||||
expect(buttonMarkup.slice(-600)).toContain('can-hover:pointer-events-none')
|
||||
})
|
||||
|
||||
it('omits the new-session action when the session has no valid target', () => {
|
||||
expect(renderActions()).not.toContain('data-testid="ai-vault-session-continue-in-new-session"')
|
||||
})
|
||||
})
|
||||
@@ -1,43 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { GitPushTarget } from '../../../../shared/worktree/types'
|
||||
import { describeForkPushTarget } from './source-control/panel/fork-push-target-label'
|
||||
|
||||
function target(overrides: Partial<GitPushTarget>): GitPushTarget {
|
||||
return {
|
||||
remoteName: 'pr-contributor-orca',
|
||||
branchName: 'contributor/fix',
|
||||
...overrides
|
||||
}
|
||||
}
|
||||
|
||||
describe('describeForkPushTarget', () => {
|
||||
it('derives owner:branch from an SSH fork URL', () => {
|
||||
expect(
|
||||
describeForkPushTarget(target({ remoteUrl: 'git@github.com:contributor/orca.git' }))
|
||||
).toBe('contributor:contributor/fix')
|
||||
})
|
||||
|
||||
it('derives owner:branch from an HTTPS fork URL', () => {
|
||||
expect(
|
||||
describeForkPushTarget(target({ remoteUrl: 'https://github.com/contributor/orca.git' }))
|
||||
).toBe('contributor:contributor/fix')
|
||||
})
|
||||
|
||||
it('handles a URL without a .git suffix', () => {
|
||||
expect(
|
||||
describeForkPushTarget(target({ remoteUrl: 'https://github.com/contributor/orca' }))
|
||||
).toBe('contributor:contributor/fix')
|
||||
})
|
||||
|
||||
it('falls back to remoteName/branch when there is no remote URL', () => {
|
||||
expect(describeForkPushTarget(target({ remoteUrl: undefined }))).toBe(
|
||||
'pr-contributor-orca/contributor/fix'
|
||||
)
|
||||
})
|
||||
|
||||
it('works for non-GitHub hosts via the generic owner segment', () => {
|
||||
expect(
|
||||
describeForkPushTarget(target({ remoteUrl: 'git@gitlab.com:contributor/orca.git' }))
|
||||
).toBe('contributor:contributor/fix')
|
||||
})
|
||||
})
|
||||
-77
@@ -1,77 +0,0 @@
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { renderToStaticMarkup } from 'react-dom/server'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { SourceControlBranchSection } from './source-control/listing/branch-section'
|
||||
import type { GitBranchCompareSummary } from '../../../../shared/git-diff-compare-types'
|
||||
|
||||
const readySummary: GitBranchCompareSummary = {
|
||||
baseRef: 'refs/remotes/origin/main',
|
||||
baseOid: 'base',
|
||||
compareRef: 'feature',
|
||||
headOid: 'head',
|
||||
mergeBase: 'base',
|
||||
changedFiles: 4,
|
||||
commitsAhead: 5,
|
||||
status: 'ready'
|
||||
}
|
||||
|
||||
function render(
|
||||
summary: GitBranchCompareSummary,
|
||||
fileCount = 4,
|
||||
totalBranchEntryCount = fileCount
|
||||
): string {
|
||||
return renderToStaticMarkup(
|
||||
<SourceControlBranchSection
|
||||
branchSummary={summary}
|
||||
filteredBranchEntries={Array.from({ length: fileCount }, (_, index) => ({
|
||||
path: `src/file-${index}.ts`,
|
||||
status: 'modified'
|
||||
}))}
|
||||
totalBranchEntryCount={totalBranchEntryCount}
|
||||
// Collapsed: the heading is the subject here, not the virtualized list.
|
||||
collapsedSections={new Set(['branch'])}
|
||||
toggleSection={vi.fn()}
|
||||
sourceControlViewMode="list"
|
||||
visibleBranchTreeRows={[]}
|
||||
fileListScrollElement={null}
|
||||
collapsedTreeDirs={new Set()}
|
||||
toggleTreeDir={vi.fn()}
|
||||
currentWorktreeId="wt-1"
|
||||
worktreePath={join(tmpdir(), 'wt-1')}
|
||||
revealInExplorer={vi.fn()}
|
||||
activeConnectionId={null}
|
||||
openCommittedDiff={vi.fn()}
|
||||
openBranchAllDiffs={vi.fn()}
|
||||
diffCommentCountByPath={new Map()}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
describe('SourceControlBranchSection heading', () => {
|
||||
// Count current differences from the base, not every historically touched file on a rebased branch.
|
||||
it('names the compare base on the count, leaving the heading text alone', () => {
|
||||
const markup = render(readySummary)
|
||||
|
||||
expect(markup).toContain('Committed on Branch')
|
||||
expect(markup).toContain('title="4 files changed vs origin/main"')
|
||||
// An aria-label inside the toggle would replace its accessible name; use the count title only.
|
||||
expect(markup).not.toContain('aria-label="4 files changed vs origin/main"')
|
||||
})
|
||||
|
||||
it('uses the singular count label for one file', () => {
|
||||
expect(render(readySummary, 1)).toContain('title="1 file changed vs origin/main"')
|
||||
})
|
||||
|
||||
// Filtered counts describe matches, not all files changed against the base.
|
||||
it('leaves the count unlabelled while a filter narrows the list', () => {
|
||||
expect(render(readySummary, 2, 4)).not.toContain('files changed vs')
|
||||
})
|
||||
|
||||
it('leaves the count unlabelled when the summary has no base ref', () => {
|
||||
const markup = render({ ...readySummary, baseRef: ' ' })
|
||||
|
||||
expect(markup).toContain('Committed on Branch')
|
||||
expect(markup).not.toContain('files changed vs')
|
||||
})
|
||||
})
|
||||
@@ -9,13 +9,6 @@ afterEach(() => {
|
||||
})
|
||||
|
||||
describe('source-control commit message row sizing', () => {
|
||||
it('preserves the existing row clamp for ordinary commit messages', () => {
|
||||
expect(getCommitMessageTextareaRows('')).toBe(2)
|
||||
expect(getCommitMessageTextareaRows('feat: keep rows stable')).toBe(2)
|
||||
expect(getCommitMessageTextareaRows('subject\n\nbody')).toBe(3)
|
||||
expect(getCommitMessageTextareaRows('\n'.repeat(20))).toBe(12)
|
||||
})
|
||||
|
||||
it('clamps newline-heavy pasted commit messages without splitting the payload', () => {
|
||||
const split = vi.spyOn(String.prototype, 'split')
|
||||
const charCodeAt = vi.spyOn(String.prototype, 'charCodeAt')
|
||||
|
||||
@@ -60,25 +60,4 @@ describe('RuntimeHostAccessForm', () => {
|
||||
expect(markup).toContain('Enter an Orca access link or bare pairing code.')
|
||||
expect(markup).toContain('aria-invalid="true"')
|
||||
})
|
||||
|
||||
it('shows the persistence error after host verification succeeds', () => {
|
||||
const markup = renderToStaticMarkup(
|
||||
<RuntimeHostAccessForm
|
||||
name="Linux workstation"
|
||||
accessLink={accessLink('ws://100.76.32.125:6768')}
|
||||
busy={false}
|
||||
failure={{
|
||||
kind: 'environment-save-failed',
|
||||
message: 'A server named "Linux workstation" already exists.'
|
||||
}}
|
||||
onNameChange={vi.fn()}
|
||||
onAccessLinkChange={vi.fn()}
|
||||
onCancel={vi.fn()}
|
||||
onSubmit={vi.fn()}
|
||||
/>
|
||||
)
|
||||
|
||||
expect(markup).toContain('Could not save the host')
|
||||
expect(markup).toContain('A server named "Linux workstation" already exists.')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -39,12 +39,6 @@ function renderForm(
|
||||
}
|
||||
|
||||
describe('RuntimePairingGeneratorForm', () => {
|
||||
it('uses detected interfaces for another-device intent', () => {
|
||||
const markup = renderForm('another', '100.76.32.125')
|
||||
expect(markup).toContain('role="combobox"')
|
||||
expect(markup).not.toContain('id="runtime-pairing-custom-address"')
|
||||
})
|
||||
|
||||
it('requires a dedicated value for custom-address intent', () => {
|
||||
const emptyMarkup = renderForm('custom', '')
|
||||
expect(emptyMarkup).toContain('id="runtime-pairing-custom-address"')
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// @vitest-environment happy-dom
|
||||
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
|
||||
import { renderToStaticMarkup } from 'react-dom/server'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { Repo } from '../../../../shared/repo-types'
|
||||
import type { SparsePreset } from '../../../../shared/worktree/create-types'
|
||||
@@ -35,17 +34,6 @@ describe('SparsePresetSettingsSection', () => {
|
||||
storeMock.state.removeSparsePreset.mockReset()
|
||||
})
|
||||
|
||||
it('surfaces sparse preset load failures inline instead of showing an endless loader', () => {
|
||||
storeMock.state.sparsePresetsLoadStatusByRepo = { 'repo-1': 'error' }
|
||||
storeMock.state.sparsePresetsErrorByRepo = { 'repo-1': 'disk failed' }
|
||||
|
||||
const markup = renderToStaticMarkup(<SparsePresetSettingsSection repoId="repo-1" />)
|
||||
|
||||
expect(markup).toContain('role="alert"')
|
||||
expect(markup).toContain('disk failed')
|
||||
expect(markup).toContain('Sparse presets could not be loaded.')
|
||||
})
|
||||
|
||||
it('offers a load retry and prevents creating against an unknown preset list', () => {
|
||||
storeMock.state.sparsePresetsLoadStatusByRepo = { 'repo-1': 'error' }
|
||||
storeMock.state.sparsePresetsErrorByRepo = { 'repo-1': 'disk failed' }
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import { cleanup, render, screen } from '@testing-library/react'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
vi.mock('@/i18n/i18n', () => ({
|
||||
translate: (_key: string, fallback: string, params?: Record<string, string>) =>
|
||||
fallback.replace(/\{\{(\w+)\}\}/g, (_match, name: string) => params?.[name] ?? '')
|
||||
}))
|
||||
|
||||
const { UnsealedCredentialNotice } = await import('./UnsealedCredentialNotice')
|
||||
|
||||
describe('UnsealedCredentialNotice', () => {
|
||||
afterEach(cleanup)
|
||||
|
||||
it('warns when the stored credential is plaintext', () => {
|
||||
render(<UnsealedCredentialNotice protection="plaintext" credentialName="MiniMax API key" />)
|
||||
const text = screen.getByRole('alert').textContent ?? ''
|
||||
expect(text).toContain('MiniMax API key is stored unencrypted')
|
||||
// The remedy has to be in the message; the console warning it replaces had none.
|
||||
expect(text).toMatch(/gnome-keyring|kwallet/)
|
||||
})
|
||||
|
||||
it('renders nothing when the credential is sealed', () => {
|
||||
render(<UnsealedCredentialNotice protection="sealed" credentialName="MiniMax API key" />)
|
||||
expect(screen.queryByRole('alert')).toBeNull()
|
||||
})
|
||||
|
||||
// Null means absent/unreadable storage, not evidence of plaintext exposure.
|
||||
it('renders nothing when protection is unknown', () => {
|
||||
render(<UnsealedCredentialNotice protection={null} credentialName="MiniMax API key" />)
|
||||
expect(screen.queryByRole('alert')).toBeNull()
|
||||
})
|
||||
|
||||
it('names the credential so a pane with several is unambiguous', () => {
|
||||
render(
|
||||
<UnsealedCredentialNotice protection="plaintext" credentialName="MiniMax Session Cookie" />
|
||||
)
|
||||
expect(screen.getByRole('alert').textContent).toContain('MiniMax Session Cookie')
|
||||
})
|
||||
})
|
||||
@@ -1,62 +0,0 @@
|
||||
import { renderToStaticMarkup } from 'react-dom/server'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { getExecutionHostLabel } from '../../../../shared/execution-host'
|
||||
import { GitHubRateLimitPanel } from '@/components/github/github-rate-limit-display'
|
||||
import { GitLabRateLimitPanel } from '@/components/gitlab/gitlab-rate-limit-display'
|
||||
|
||||
const LOCAL_HOST_LABEL = getExecutionHostLabel('local')
|
||||
|
||||
type StoreState = {
|
||||
settings: { activeRuntimeEnvironmentId: string | null }
|
||||
openSettingsPage: () => void
|
||||
openSettingsTarget: (target: { pane: string; repoId: string | null }) => void
|
||||
}
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
store: {
|
||||
current: {
|
||||
settings: { activeRuntimeEnvironmentId: null },
|
||||
openSettingsPage: vi.fn(),
|
||||
openSettingsTarget: vi.fn()
|
||||
} as StoreState
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('@/store', () => ({
|
||||
useAppStore: (selector: (state: StoreState) => unknown) => selector(mocks.store.current)
|
||||
}))
|
||||
|
||||
describe('provider rate-limit panels account scope', () => {
|
||||
it('shows the Settings host, not the focused server, for GitHub API budget', () => {
|
||||
mocks.store.current = {
|
||||
settings: { activeRuntimeEnvironmentId: 'runtime-focused' },
|
||||
openSettingsPage: vi.fn(),
|
||||
openSettingsTarget: vi.fn()
|
||||
}
|
||||
|
||||
const markup = renderToStaticMarkup(<GitHubRateLimitPanel target={{ kind: 'local' }} />)
|
||||
|
||||
expect(markup).toContain(`Budget scope: ${LOCAL_HOST_LABEL}`)
|
||||
expect(markup).toContain(
|
||||
'GitHub API budget is fetched from the CLI on this desktop client. Choose a server in the Host menu at the top of Settings to see its budget.'
|
||||
)
|
||||
expect(markup).toContain('Open Remote Servers')
|
||||
})
|
||||
|
||||
it('shows the remote server scope for GitLab API budget', () => {
|
||||
mocks.store.current = {
|
||||
settings: { activeRuntimeEnvironmentId: null },
|
||||
openSettingsPage: vi.fn(),
|
||||
openSettingsTarget: vi.fn()
|
||||
}
|
||||
|
||||
const markup = renderToStaticMarkup(
|
||||
<GitLabRateLimitPanel target={{ kind: 'environment', environmentId: 'runtime-1' }} />
|
||||
)
|
||||
|
||||
expect(markup).toContain('Budget scope: Remote server: runtime-1')
|
||||
expect(markup).toContain(
|
||||
'GitLab API budget is fetched from the CLI on this remote server. Choose another host in the Host menu at the top of Settings to see its budget.'
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -1,43 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { AiVaultSearchStatus } from '../../../../shared/ai-vault-search-types'
|
||||
import { unavailableSessionSearchStatus } from '../../../../shared/ai-vault-search-client'
|
||||
import { isSessionSearchIndexReady } from './session-history-status-copy'
|
||||
|
||||
function status(overrides: Partial<AiVaultSearchStatus>): AiVaultSearchStatus {
|
||||
return { ...unavailableSessionSearchStatus(), enabled: true, ...overrides }
|
||||
}
|
||||
|
||||
describe('isSessionSearchIndexReady', () => {
|
||||
it('is ready once a whole sweep finished with nothing due', () => {
|
||||
expect(isSessionSearchIndexReady(status({ phase: 'current', lastSweepCompletedAt: 1 }))).toBe(
|
||||
true
|
||||
)
|
||||
})
|
||||
|
||||
it('counts a finished sweep with unreadable files as ready', () => {
|
||||
expect(
|
||||
isSessionSearchIndexReady(
|
||||
status({ phase: 'degraded', filesFailed: 2, filesDue: 0, lastSweepCompletedAt: 1 })
|
||||
)
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it('is not ready before the first sweep finishes', () => {
|
||||
expect(isSessionSearchIndexReady(null)).toBe(false)
|
||||
expect(isSessionSearchIndexReady(status({ phase: 'idle' }))).toBe(false)
|
||||
expect(isSessionSearchIndexReady(status({ phase: 'indexing', filesDue: 40 }))).toBe(false)
|
||||
// A degraded pass that has not completed a sweep yet is still building.
|
||||
expect(isSessionSearchIndexReady(status({ phase: 'degraded', filesFailed: 1 }))).toBe(false)
|
||||
expect(
|
||||
isSessionSearchIndexReady(status({ phase: 'degraded', filesDue: 3, lastSweepCompletedAt: 1 }))
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
it('is not ready while search is off', () => {
|
||||
expect(
|
||||
isSessionSearchIndexReady(
|
||||
status({ enabled: false, phase: 'current', lastSweepCompletedAt: 1 })
|
||||
)
|
||||
).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -1,86 +0,0 @@
|
||||
import { renderToStaticMarkup } from 'react-dom/server'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { FeatureWallSetupProgress } from '../feature-wall/feature-wall-setup-progress'
|
||||
import { useSettingsSetupGuideProgress } from './settings-setup-guide-progress'
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
useSetupGuideProgress: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('../setup-guide/use-setup-guide-progress', () => ({
|
||||
useSetupGuideProgress: mocks.useSetupGuideProgress
|
||||
}))
|
||||
|
||||
function makeProgress(): FeatureWallSetupProgress {
|
||||
return {
|
||||
ready: true,
|
||||
stepDone: {
|
||||
'default-agent': true,
|
||||
'add-two-repos': false,
|
||||
notifications: true,
|
||||
'two-worktrees': true,
|
||||
browser: false,
|
||||
'task-sources': true,
|
||||
'agent-capabilities': false,
|
||||
'setup-script': false
|
||||
},
|
||||
coreDoneCount: 4,
|
||||
coreTotal: 8
|
||||
}
|
||||
}
|
||||
|
||||
function SettingsProgressProbe(): React.JSX.Element {
|
||||
const progress = useSettingsSetupGuideProgress(true)
|
||||
return <span>{`${progress.doneCount}/${progress.total}`}</span>
|
||||
}
|
||||
|
||||
describe('useSettingsSetupGuideProgress', () => {
|
||||
beforeEach(() => {
|
||||
mocks.useSetupGuideProgress.mockReset()
|
||||
})
|
||||
|
||||
it('uses the same setup progress path as the main sidebar', () => {
|
||||
mocks.useSetupGuideProgress.mockReturnValue(makeProgress())
|
||||
|
||||
expect(renderToStaticMarkup(<SettingsProgressProbe />)).toContain('4/8')
|
||||
expect(mocks.useSetupGuideProgress).toHaveBeenCalledWith(true, false, false)
|
||||
})
|
||||
|
||||
it('uses legacy-aware completion returned by the shared setup progress path', () => {
|
||||
mocks.useSetupGuideProgress.mockReturnValue({
|
||||
...makeProgress(),
|
||||
stepDone: {
|
||||
'default-agent': true,
|
||||
'add-two-repos': true,
|
||||
notifications: true,
|
||||
'two-worktrees': true,
|
||||
browser: true,
|
||||
'task-sources': true,
|
||||
'agent-capabilities': true,
|
||||
'setup-script': true
|
||||
},
|
||||
coreDoneCount: 8
|
||||
})
|
||||
|
||||
expect(renderToStaticMarkup(<SettingsProgressProbe />)).toContain('8/8')
|
||||
})
|
||||
|
||||
it('shows browser incomplete after the browser migration has already run for fresh users', () => {
|
||||
mocks.useSetupGuideProgress.mockReturnValue({
|
||||
...makeProgress(),
|
||||
stepDone: {
|
||||
'default-agent': true,
|
||||
'add-two-repos': true,
|
||||
notifications: true,
|
||||
'two-worktrees': true,
|
||||
browser: false,
|
||||
'task-sources': true,
|
||||
'agent-capabilities': true,
|
||||
'setup-script': true
|
||||
},
|
||||
coreDoneCount: 7
|
||||
})
|
||||
|
||||
expect(renderToStaticMarkup(<SettingsProgressProbe />)).toContain('7/8')
|
||||
})
|
||||
})
|
||||
@@ -39,18 +39,6 @@ function renderCreateStep({
|
||||
}
|
||||
|
||||
describe('CreateStep', () => {
|
||||
it('renders the name-first create UI with advanced controls collapsed', () => {
|
||||
const html = renderCreateStep()
|
||||
|
||||
expect(html).toContain('Create a new project')
|
||||
expect(html).toContain('Name')
|
||||
expect(html).toContain('Git repository in ~/orca/projects')
|
||||
expect(html).toContain('aria-expanded="false"')
|
||||
expect(html).not.toContain('Project kind')
|
||||
expect(html).not.toContain('Location</span>')
|
||||
expect(html).not.toContain('aria-label="Browse host filesystem"')
|
||||
})
|
||||
|
||||
it('shows the Git-required explanation in the collapsed summary', () => {
|
||||
const html = renderCreateStep({
|
||||
createName: 'demo-project',
|
||||
|
||||
@@ -43,24 +43,6 @@ describe('AddWslHostFormPanel', () => {
|
||||
expect([ubuntu, debian, legacy, added].map(canAddWslDistro)).toEqual([true, true, false, false])
|
||||
})
|
||||
|
||||
it('makes starting a stopped distro an explicit action', () => {
|
||||
const html = render({ selectedDistro: 'Debian' })
|
||||
expect(html).toContain('Debian is stopped. Adding it starts it.')
|
||||
expect(html).toContain('Start and add')
|
||||
expect(render({ selectedDistro: 'Ubuntu' })).toContain('Add host')
|
||||
})
|
||||
|
||||
it('shows a refusal inline and the setup progress while adding', () => {
|
||||
expect(render({ error: 'Already runs the Orca server for “dev”.' })).toContain('role="alert"')
|
||||
const adding = render({ selectedDistro: 'Ubuntu', adding: true })
|
||||
expect(adding).toContain('Setting up…')
|
||||
expect(adding).toContain('The first time can take a minute.')
|
||||
})
|
||||
|
||||
it('explains an empty list', () => {
|
||||
expect(render({ distros: [] })).toContain('wsl --install')
|
||||
})
|
||||
|
||||
it('says this build cannot run WSL hosts and offers nothing to add', () => {
|
||||
const html = render({ selectedDistro: 'Ubuntu', serverFilesMissing: true })
|
||||
expect(html).toContain('This build of Orca can’t run WSL hosts (server files missing).')
|
||||
@@ -68,9 +50,4 @@ describe('AddWslHostFormPanel', () => {
|
||||
expect(html).not.toContain('wsl --install')
|
||||
expect(html).toMatch(/<button[^>]*disabled=""[^>]*>Add host<\/button>/)
|
||||
})
|
||||
|
||||
it('offers an explicit refresh, and none when nothing could be added', () => {
|
||||
expect(render()).toContain('Refresh')
|
||||
expect(render({ serverFilesMissing: true })).not.toContain('Refresh')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
import { renderToStaticMarkup } from 'react-dom/server'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { usePromptCacheCountdownStartedAt } from './CacheTimer'
|
||||
|
||||
const scanMocks = vi.hoisted(() => ({
|
||||
getMostUrgentPromptCacheStartedAt: vi.fn(() => 1_000)
|
||||
}))
|
||||
|
||||
type MockState = {
|
||||
cacheTimerByKey: Record<string, number | null>
|
||||
settings?: {
|
||||
promptCacheTimerEnabled?: boolean
|
||||
promptCacheTtlMs?: number
|
||||
}
|
||||
tabsByWorktree: Record<string, { id: string }[]>
|
||||
}
|
||||
|
||||
let mockState: MockState
|
||||
|
||||
vi.mock('@/store', () => ({
|
||||
useAppStore: (selector: (state: MockState) => unknown) => selector(mockState)
|
||||
}))
|
||||
|
||||
vi.mock('./prompt-cache-timer-selection', () => ({
|
||||
getMostUrgentPromptCacheStartedAt: scanMocks.getMostUrgentPromptCacheStartedAt,
|
||||
getPromptCacheCountdownForPane: vi.fn(() => null)
|
||||
}))
|
||||
|
||||
function AggregateTimerProbe({ active = true }: { active?: boolean }): React.JSX.Element {
|
||||
const startedAt = usePromptCacheCountdownStartedAt('wt-1', active)
|
||||
return <span>{startedAt ?? 'none'}</span>
|
||||
}
|
||||
|
||||
describe('usePromptCacheCountdownStartedAt', () => {
|
||||
beforeEach(() => {
|
||||
scanMocks.getMostUrgentPromptCacheStartedAt.mockClear()
|
||||
mockState = {
|
||||
cacheTimerByKey: { 'tab-1:11111111-1111-4111-8111-111111111111': 1_000 },
|
||||
settings: {
|
||||
promptCacheTimerEnabled: true,
|
||||
promptCacheTtlMs: 60_000
|
||||
},
|
||||
tabsByWorktree: {
|
||||
'wt-1': [{ id: 'tab-1' }]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('does not scan aggregate cache timers while inactive', () => {
|
||||
const markup = renderToStaticMarkup(<AggregateTimerProbe active={false} />)
|
||||
|
||||
expect(markup).toContain('none')
|
||||
expect(scanMocks.getMostUrgentPromptCacheStartedAt).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('does not scan aggregate cache timers while disabled', () => {
|
||||
mockState.settings = {
|
||||
promptCacheTimerEnabled: false,
|
||||
promptCacheTtlMs: 60_000
|
||||
}
|
||||
|
||||
const markup = renderToStaticMarkup(<AggregateTimerProbe />)
|
||||
|
||||
expect(markup).toContain('none')
|
||||
expect(scanMocks.getMostUrgentPromptCacheStartedAt).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('does not scan aggregate cache timers when ttl is zero', () => {
|
||||
mockState.settings = {
|
||||
promptCacheTimerEnabled: true,
|
||||
promptCacheTtlMs: 0
|
||||
}
|
||||
|
||||
const markup = renderToStaticMarkup(<AggregateTimerProbe />)
|
||||
|
||||
expect(markup).toContain('none')
|
||||
expect(scanMocks.getMostUrgentPromptCacheStartedAt).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('scans aggregate cache timers only when the timer can render', () => {
|
||||
const markup = renderToStaticMarkup(<AggregateTimerProbe />)
|
||||
|
||||
expect(markup).toContain('1000')
|
||||
expect(scanMocks.getMostUrgentPromptCacheStartedAt).toHaveBeenCalledTimes(1)
|
||||
expect(scanMocks.getMostUrgentPromptCacheStartedAt).toHaveBeenCalledWith(
|
||||
mockState.tabsByWorktree['wt-1'],
|
||||
mockState.cacheTimerByKey
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -1,43 +0,0 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import { render } from '@testing-library/react'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { TooltipProvider } from '@/components/ui/tooltip'
|
||||
import { SetupScriptPromptCardShell } from './SetupScriptPromptCardShell'
|
||||
|
||||
describe('SetupScriptPromptCardShell', () => {
|
||||
it('floats above its anchor without reserving a sidebar background panel', () => {
|
||||
const view = render(
|
||||
<TooltipProvider>
|
||||
<SetupScriptPromptCardShell
|
||||
repoBadgeColor="blue"
|
||||
repoDisplayName="orca"
|
||||
isInspectionError={false}
|
||||
sharedSetupIgnored={false}
|
||||
isPackageManagerSuggestion={false}
|
||||
hasCandidate={false}
|
||||
candidateSource={null}
|
||||
candidateProvenance={null}
|
||||
detectedSetupDraft=""
|
||||
isImporting={false}
|
||||
renderedStateOk
|
||||
onDismiss={vi.fn()}
|
||||
onRetryInspection={vi.fn()}
|
||||
onConfigure={vi.fn()}
|
||||
onImport={vi.fn()}
|
||||
onSetupDraftChange={vi.fn()}
|
||||
/>
|
||||
</TooltipProvider>
|
||||
)
|
||||
const layer = view.container.querySelector('[data-setup-script-prompt-layer]')
|
||||
const surface = layer?.firstElementChild
|
||||
|
||||
expect(layer?.classList.contains('absolute')).toBe(true)
|
||||
expect(layer?.classList.contains('inset-x-0')).toBe(true)
|
||||
expect(layer?.classList.contains('bottom-full')).toBe(true)
|
||||
expect(layer?.classList.contains('pointer-events-none')).toBe(true)
|
||||
expect(surface?.classList.contains('pointer-events-auto')).toBe(true)
|
||||
expect(surface?.classList.contains('bg-popover')).toBe(true)
|
||||
expect(surface?.classList.contains('text-popover-foreground')).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -1,56 +0,0 @@
|
||||
import { renderToStaticMarkup } from 'react-dom/server'
|
||||
import type { ReactNode } from 'react'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import type { WorkspacePort } from '../../../../shared/workspace-ports'
|
||||
|
||||
vi.mock('@/store', () => ({
|
||||
useAppStore: (selector: (state: unknown) => unknown) =>
|
||||
selector({
|
||||
createBrowserTab: vi.fn(),
|
||||
setRemoteBrowserPageHandle: vi.fn(),
|
||||
replaceWorkspacePortScans: vi.fn(),
|
||||
setWorkspacePortScanRefreshing: vi.fn(),
|
||||
settings: null
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock('@/components/ui/tooltip', () => ({
|
||||
Tooltip: ({ children }: { children: ReactNode }) => <>{children}</>,
|
||||
TooltipContent: ({ children }: { children: ReactNode }) => <>{children}</>,
|
||||
TooltipTrigger: ({ children }: { children: ReactNode }) => <>{children}</>
|
||||
}))
|
||||
|
||||
const port: WorkspacePort = {
|
||||
id: '127.0.0.1:58941:1234',
|
||||
bindHost: '127.0.0.1',
|
||||
connectHost: '127.0.0.1',
|
||||
port: 58941,
|
||||
pid: 1234,
|
||||
processName: 'node',
|
||||
protocol: 'http',
|
||||
kind: 'workspace',
|
||||
owner: {
|
||||
worktreeId: 'repo::/workspace/app',
|
||||
repoId: 'repo',
|
||||
displayName: 'app',
|
||||
path: '/workspace/app',
|
||||
confidence: 'cwd'
|
||||
},
|
||||
advertisedUrl: 'http://dev.preview.localhost:58941'
|
||||
}
|
||||
|
||||
describe('WorktreeCardPortsDetails', () => {
|
||||
it('shows advertised port addresses in workspace hover details', async () => {
|
||||
const { WorktreeCardPortsDetails } = await import('./WorktreeCardPorts')
|
||||
|
||||
const markup = renderToStaticMarkup(<WorktreeCardPortsDetails ports={[port]} />)
|
||||
|
||||
expect(markup).toContain('dev.preview.localhost:58941')
|
||||
expect(markup).toContain('aria-label="Copy dev.preview.localhost:58941"')
|
||||
expect(markup).toContain('Open in Browser. Shift+Ctrl+click for system browser')
|
||||
expect(markup).toContain(
|
||||
'<section class="space-y-1.5"><div class="flex items-center gap-1.5 px-1'
|
||||
)
|
||||
expect(markup).toContain('class="border-l border-border/70 pl-3 space-y-0.5"')
|
||||
})
|
||||
})
|
||||
@@ -1,58 +0,0 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import { renderToStaticMarkup } from 'react-dom/server'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { WorktreeDeveloperMenu } from './WorktreeDeveloperMenu'
|
||||
|
||||
type MenuItemProps = {
|
||||
children?: ReactNode
|
||||
onSelect?: () => void
|
||||
}
|
||||
|
||||
const menuItems = vi.hoisted(() => ({ values: [] as MenuItemProps[] }))
|
||||
const requestManualPark = vi.hoisted(() => vi.fn())
|
||||
|
||||
vi.mock('@/components/ui/dropdown-menu', async () => {
|
||||
const React_ = await import('react')
|
||||
const passthrough = ({ children }: { children?: ReactNode }) =>
|
||||
React_.createElement(React_.Fragment, null, children)
|
||||
return {
|
||||
DropdownMenuSub: passthrough,
|
||||
DropdownMenuSubContent: passthrough,
|
||||
DropdownMenuSubTrigger: passthrough,
|
||||
DropdownMenuItem: (props: MenuItemProps) => {
|
||||
menuItems.values.push(props)
|
||||
return React_.createElement(React_.Fragment, null, props.children)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('@/i18n/i18n', () => ({ translate: (_key: string, fallback: string) => fallback }))
|
||||
vi.mock('@/lib/manual-terminal-worktree-parking', () => ({
|
||||
requestManualTerminalWorktreePark: requestManualPark
|
||||
}))
|
||||
|
||||
describe('WorktreeDeveloperMenu', () => {
|
||||
beforeEach(() => {
|
||||
menuItems.values = []
|
||||
requestManualPark.mockReset()
|
||||
})
|
||||
|
||||
it('shows the developer submenu with the parking action', () => {
|
||||
const markup = renderToStaticMarkup(
|
||||
<WorktreeDeveloperMenu worktreeId="worktree-1" disabled={false} />
|
||||
)
|
||||
|
||||
expect(markup).toContain('Developer')
|
||||
expect(markup).toContain('Park terminal')
|
||||
expect(markup).toContain('lucide-code-xml')
|
||||
expect(markup).toContain('lucide-square-parking')
|
||||
})
|
||||
|
||||
it('requests parking for the context worktree', () => {
|
||||
renderToStaticMarkup(<WorktreeDeveloperMenu worktreeId="worktree-1" disabled={false} />)
|
||||
|
||||
menuItems.values[0]?.onSelect?.()
|
||||
|
||||
expect(requestManualPark).toHaveBeenCalledWith('worktree-1')
|
||||
})
|
||||
})
|
||||
@@ -2,34 +2,11 @@
|
||||
import { cleanup, render } from '@testing-library/react'
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { WorktreeReferenceStack } from './WorktreeReferenceStack'
|
||||
import { WorktreeCardMetaBadges } from './WorktreeCardMetaBadges'
|
||||
import { getWorkspaceAttachmentKey } from '../../../../shared/workspace-attachment-normalization'
|
||||
import { referenceAttachment, referenceReview } from './workspace-reference-fixtures.test-support'
|
||||
import type { WorkspaceAttachment } from '../../../../shared/worktree/types'
|
||||
|
||||
afterEach(cleanup)
|
||||
describe('fixed workspace reference stacks', () => {
|
||||
it('preserves singleton brand/status icons without ghosts or a count', () => {
|
||||
const linear: WorkspaceAttachment = {
|
||||
provider: 'linear',
|
||||
type: 'issue',
|
||||
number: 0,
|
||||
identifier: 'ENG-1'
|
||||
}
|
||||
const { container } = render(
|
||||
<>
|
||||
<WorktreeReferenceStack items={[referenceAttachment()]} />
|
||||
<WorktreeReferenceStack items={[linear]} />
|
||||
</>
|
||||
)
|
||||
expect(container.querySelectorAll('[data-reference-front]')).toHaveLength(2)
|
||||
expect(container.querySelectorAll('svg')).toHaveLength(2)
|
||||
expect(container.querySelector('[data-reference-count]')).toBeNull()
|
||||
expect(container.querySelector('.workspace-reference-stack-ghost')).toBeNull()
|
||||
expect(container.textContent).toContain('Linked PR #7')
|
||||
expect(container.textContent).toContain('Linked Linear ENG-1')
|
||||
expect(container.textContent).not.toContain('1 linked')
|
||||
})
|
||||
it('prioritizes the review needing attention over a legacy primary and preserves sibling states', () => {
|
||||
const items = [referenceAttachment(1), referenceAttachment(2), referenceAttachment(3)]
|
||||
const details = Object.fromEntries(
|
||||
@@ -53,34 +30,4 @@ describe('fixed workspace reference stacks', () => {
|
||||
expect(container.querySelector('[data-reference-count]')?.textContent).toBe('3')
|
||||
expect(container.textContent).toContain('PR #2 · draft')
|
||||
})
|
||||
it('keeps two provider cells with the same six-pixel gap and no new metadata row at twelve references', () => {
|
||||
const refs: WorkspaceAttachment[] = [
|
||||
...Array.from({ length: 12 }, (_, index) => referenceAttachment(index + 1)),
|
||||
...Array.from({ length: 12 }, (_, index) => ({
|
||||
provider: 'linear' as const,
|
||||
type: 'issue' as const,
|
||||
number: 0,
|
||||
identifier: `ENG-${index + 1}`
|
||||
}))
|
||||
]
|
||||
const { container } = render(
|
||||
<WorktreeCardMetaBadges
|
||||
linkedItemCount={refs.length}
|
||||
referenceItems={refs}
|
||||
issue={null}
|
||||
linearIssue={null}
|
||||
review={null}
|
||||
comment={null}
|
||||
/>
|
||||
)
|
||||
const group = container.querySelector('[data-workspace-reference-groups]')
|
||||
expect(group?.classList.contains('gap-1.5')).toBe(true)
|
||||
expect(group?.querySelectorAll('[data-workspace-reference-stack]')).toHaveLength(2)
|
||||
expect(
|
||||
Array.from(group?.querySelectorAll('[data-reference-count]') ?? []).map(
|
||||
(node) => node.textContent
|
||||
)
|
||||
).toEqual(['12', '12'])
|
||||
expect(container.textContent).not.toContain('24 linked')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
import { renderToStaticMarkup } from 'react-dom/server'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { TooltipProvider } from '@/components/ui/tooltip'
|
||||
import {
|
||||
WorktreeTitleInlineRename,
|
||||
getWorktreeTitleRenameCommit,
|
||||
isWorktreeTitleTruncated
|
||||
} from './WorktreeTitleInlineRename'
|
||||
|
||||
describe('getWorktreeTitleRenameCommit', () => {
|
||||
it('cancels blank or unchanged inline titles', () => {
|
||||
expect(getWorktreeTitleRenameCommit('feature/login', '')).toEqual({ kind: 'cancel' })
|
||||
expect(getWorktreeTitleRenameCommit('feature/login', ' ')).toEqual({ kind: 'cancel' })
|
||||
expect(getWorktreeTitleRenameCommit('feature/login', ' feature/login ')).toEqual({
|
||||
kind: 'cancel'
|
||||
})
|
||||
})
|
||||
|
||||
it('trims and saves changed inline titles', () => {
|
||||
expect(getWorktreeTitleRenameCommit('feature/login', ' Login polish ')).toEqual({
|
||||
kind: 'save',
|
||||
displayName: 'Login polish'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('WorktreeTitleInlineRename', () => {
|
||||
it('treats only actual text overflow as truncation', () => {
|
||||
expect(isWorktreeTitleTruncated({ clientWidth: 120, scrollWidth: 120 })).toBe(false)
|
||||
expect(isWorktreeTitleTruncated({ clientWidth: 120, scrollWidth: 119 })).toBe(false)
|
||||
expect(isWorktreeTitleTruncated({ clientWidth: 120, scrollWidth: 121 })).toBe(true)
|
||||
})
|
||||
|
||||
it('renders the title as the double-click inline rename target', () => {
|
||||
const markup = renderToStaticMarkup(
|
||||
<TooltipProvider>
|
||||
<WorktreeTitleInlineRename
|
||||
displayName="Feature workspace"
|
||||
showUnreadEmphasis
|
||||
onRename={vi.fn()}
|
||||
/>
|
||||
</TooltipProvider>
|
||||
)
|
||||
|
||||
expect(markup).toContain('data-worktree-title-inline-rename=""')
|
||||
expect(markup).not.toContain('cursor-text')
|
||||
expect(markup).not.toContain('title="Feature workspace"')
|
||||
expect(markup).toContain('tabindex="0"')
|
||||
expect(markup).toContain('font-semibold text-foreground')
|
||||
expect(markup).toContain('Unread:')
|
||||
expect(markup).toContain('Feature workspace')
|
||||
})
|
||||
|
||||
it('keeps read titles at the default foreground color unless requested', () => {
|
||||
const markup = renderToStaticMarkup(
|
||||
<TooltipProvider>
|
||||
<WorktreeTitleInlineRename displayName="Feature workspace" onRename={vi.fn()} />
|
||||
</TooltipProvider>
|
||||
)
|
||||
|
||||
expect(markup).toContain('font-normal text-foreground')
|
||||
expect(markup).not.toContain('text-foreground/80')
|
||||
expect(markup).not.toContain('Unread:')
|
||||
})
|
||||
|
||||
it('dims read titles when requested by the experimental card style', () => {
|
||||
const markup = renderToStaticMarkup(
|
||||
<TooltipProvider>
|
||||
<WorktreeTitleInlineRename
|
||||
displayName="Feature workspace"
|
||||
dimReadTitle
|
||||
onRename={vi.fn()}
|
||||
/>
|
||||
</TooltipProvider>
|
||||
)
|
||||
|
||||
expect(markup).toContain('font-normal text-foreground/80')
|
||||
expect(markup).not.toContain('Unread:')
|
||||
})
|
||||
})
|
||||
@@ -1,43 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { Repo } from '../../../../shared/repo-types'
|
||||
import type { Row } from '../../../../shared/sidebar-list/row-types'
|
||||
import { addHostSectionRows } from '../../../../shared/sidebar-list/host-section-rows'
|
||||
|
||||
function repoHeader(id: string, connectionId?: string): Row {
|
||||
const repo: Repo = {
|
||||
id,
|
||||
path: `/${id}`,
|
||||
displayName: id,
|
||||
badgeColor: '',
|
||||
addedAt: 0,
|
||||
connectionId
|
||||
}
|
||||
return {
|
||||
type: 'header',
|
||||
key: `repo:${id}`,
|
||||
label: id,
|
||||
count: 1,
|
||||
icon: { kind: 'project' },
|
||||
repo
|
||||
}
|
||||
}
|
||||
|
||||
describe('addHostSectionRows local host label', () => {
|
||||
// The client’s platform must not determine the listed host’s label.
|
||||
it('names an unlisted local host with the label it is given', () => {
|
||||
const sectioned = addHostSectionRows({
|
||||
rows: [repoHeader('local'), repoHeader('ssh', 'ssh-1')],
|
||||
hostOptions: [
|
||||
{ id: 'ssh:ssh-1', kind: 'ssh', label: 'Builder', detail: 'SSH', health: 'available' },
|
||||
{ id: 'ssh:ssh-2', kind: 'ssh', label: 'Other', detail: 'SSH', health: 'available' }
|
||||
],
|
||||
workspaceHostScope: 'all',
|
||||
visibleWorkspaceHostIds: ['local', 'ssh:ssh-1'],
|
||||
defaultHostId: 'local',
|
||||
localHostLabel: 'Local Windows'
|
||||
})
|
||||
expect(
|
||||
sectioned.find((row) => row.type === 'host-header' && row.hostId === 'local')
|
||||
).toMatchObject({ label: 'Local Windows', detail: 'This computer' })
|
||||
})
|
||||
})
|
||||
@@ -8,10 +8,6 @@ const folder = (name: string) => ({ name, isDirectory: true })
|
||||
const file = (name: string) => ({ name, isDirectory: false })
|
||||
|
||||
describe('initialRemoteFileBrowserHighlight', () => {
|
||||
it('starts on the first row', () => {
|
||||
expect(initialRemoteFileBrowserHighlight([folder('a'), folder('b')], false)).toBe(0)
|
||||
})
|
||||
|
||||
// Why: Enter on a filter that leaves one folder has always opened it, even past files.
|
||||
it('starts on the only folder a filter leaves', () => {
|
||||
expect(initialRemoteFileBrowserHighlight([file('src.md'), folder('src')], false)).toBe(1)
|
||||
@@ -24,12 +20,6 @@ describe('initialRemoteFileBrowserHighlight', () => {
|
||||
})
|
||||
|
||||
describe('stepRemoteFileBrowserHighlight', () => {
|
||||
it('moves within the list and stops at both ends', () => {
|
||||
expect(stepRemoteFileBrowserHighlight(0, 1, 3)).toBe(1)
|
||||
expect(stepRemoteFileBrowserHighlight(2, 1, 3)).toBe(2)
|
||||
expect(stepRemoteFileBrowserHighlight(0, -1, 3)).toBe(0)
|
||||
})
|
||||
|
||||
it('enters the list from no row at the end the key points to', () => {
|
||||
expect(stepRemoteFileBrowserHighlight(-1, 1, 3)).toBe(0)
|
||||
expect(stepRemoteFileBrowserHighlight(-1, -1, 3)).toBe(2)
|
||||
|
||||
-17
@@ -22,13 +22,6 @@ function renderIndicator(reason: unknown): string {
|
||||
}
|
||||
|
||||
describe('FolderPathStatusIndicator', () => {
|
||||
it('marks a folder missing for a declared reason', () => {
|
||||
const markup = renderIndicator('missing')
|
||||
|
||||
expect(markup).toContain('aria-label="Folder not found"')
|
||||
expect(markup).toContain('text-destructive')
|
||||
})
|
||||
|
||||
// Keep an imprecise marker for unknown host reasons so broken folders cannot appear healthy.
|
||||
it('still renders the marker when a newer host sends an undeclared reason', () => {
|
||||
const markup = renderIndicator('permission-denied')
|
||||
@@ -51,14 +44,4 @@ describe('FolderPathStatusIndicator', () => {
|
||||
// Why: hasOwn coerces its key, so ['missing'] must not be read as the confirmed-stale 'missing'.
|
||||
expect(markup).not.toContain('aria-label="Folder not found"')
|
||||
})
|
||||
|
||||
it('renders nothing for a healthy folder', () => {
|
||||
const markup = renderToStaticMarkup(
|
||||
<TooltipProvider>
|
||||
<FolderPathStatusIndicator status={{ path: '/srv/scans', exists: true }} />
|
||||
</TooltipProvider>
|
||||
)
|
||||
|
||||
expect(markup).toBe('')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import { renderToStaticMarkup } from 'react-dom/server'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { SourceControlActionVariableChips } from './SourceControlActionVariableChips'
|
||||
|
||||
vi.mock('../ui/hover-card', () => ({
|
||||
HoverCard: ({ children }: { children: ReactNode }) => (
|
||||
<div data-slot="hover-card">{children}</div>
|
||||
),
|
||||
HoverCardContent: ({ children, className }: { children: ReactNode; className?: string }) => (
|
||||
<div data-slot="hover-card-content" className={className}>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
HoverCardTrigger: ({ children }: { children: ReactNode }) => (
|
||||
<div data-slot="hover-card-trigger">{children}</div>
|
||||
)
|
||||
}))
|
||||
|
||||
describe('SourceControlActionVariableChips', () => {
|
||||
it('renders variable details in a scrollable hover card', () => {
|
||||
const markup = renderToStaticMarkup(
|
||||
<SourceControlActionVariableChips
|
||||
actionId="commitMessage"
|
||||
variablePreviews={{ basePrompt: 'Generate a commit message.\n\nInclude staged changes.' }}
|
||||
onInsert={() => {}}
|
||||
/>
|
||||
)
|
||||
|
||||
expect(markup).toContain('data-slot="hover-card-content"')
|
||||
expect(markup).toContain('scrollbar-sleek')
|
||||
expect(markup).toContain('overflow-y-auto')
|
||||
expect(markup).toContain('Generate a commit message.')
|
||||
})
|
||||
|
||||
// Keep the linkedIssue warning beside previews: unlinked workspaces produce Fixes #, and GitLab has no value.
|
||||
it.each([
|
||||
{ label: 'a linked workspace', preview: '4242', expected: '4242' },
|
||||
{ label: 'an unlinked workspace', preview: '', expected: '(empty)' }
|
||||
])(
|
||||
'keeps the linkedIssue description and example alongside $label preview',
|
||||
({ preview, expected }) => {
|
||||
const markup = renderToStaticMarkup(
|
||||
<SourceControlActionVariableChips
|
||||
actionId="commitMessage"
|
||||
variablePreviews={{ linkedIssue: preview }}
|
||||
onInsert={() => {}}
|
||||
/>
|
||||
)
|
||||
|
||||
expect(markup).toContain('Empty when no GitHub issue is linked')
|
||||
expect(markup).toContain('GitLab-linked')
|
||||
expect(markup).toContain('Example')
|
||||
expect(markup).toContain('This workspace')
|
||||
expect(markup).toContain(expected)
|
||||
}
|
||||
)
|
||||
|
||||
it('shows only the rendered prompt for a basePrompt preview', () => {
|
||||
const markup = renderToStaticMarkup(
|
||||
<SourceControlActionVariableChips
|
||||
actionId="commitMessage"
|
||||
variablePreviews={{ basePrompt: 'Generate a commit message.' }}
|
||||
onInsert={() => {}}
|
||||
/>
|
||||
)
|
||||
|
||||
expect(markup).not.toContain('built-in prompt for this action')
|
||||
})
|
||||
})
|
||||
@@ -1,61 +0,0 @@
|
||||
// @vitest-environment happy-dom
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
import { cleanup, render } from '@testing-library/react'
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { Dialog, DialogContent, DialogDescription, DialogTitle } from './dialog'
|
||||
import { SecuritySurface } from './security-surface'
|
||||
|
||||
// Every dialog that asks the user to trust, approve, run or remove code. A new one belongs here.
|
||||
const TRUST_DIALOGS = [
|
||||
'sidebar/OrcaYamlTrustDialog.tsx',
|
||||
'editor/IpynbViewer.tsx',
|
||||
'settings/PluginConsentDialog.tsx',
|
||||
'settings/PluginInstallDialog.tsx',
|
||||
'settings/PluginMarketplacePreviewDialog.tsx',
|
||||
'settings/PluginMarketplaceSourceDialog.tsx',
|
||||
'settings/PluginRemoveDialog.tsx',
|
||||
'settings/PluginRollbackDialog.tsx'
|
||||
]
|
||||
|
||||
// Trust decisions made outside a dialog: agent command approvals and certificate overrides.
|
||||
const INLINE_TRUST_SURFACES = [
|
||||
'native-chat/NativeChatApprovalCard.tsx',
|
||||
'browser-pane/navigate/browser-load-failure-overlay.tsx'
|
||||
]
|
||||
|
||||
afterEach(cleanup)
|
||||
|
||||
describe('security surfaces', () => {
|
||||
it('applies the host-owned token set', () => {
|
||||
render(
|
||||
<Dialog open>
|
||||
<DialogContent surface="security">
|
||||
<DialogTitle>Trust</DialogTitle>
|
||||
<DialogDescription>Review</DialogDescription>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
expect(document.querySelector('[role="dialog"]')?.classList).toContain('plugin-security-chrome')
|
||||
})
|
||||
|
||||
it.each(TRUST_DIALOGS)('%s renders on the security surface', (file) => {
|
||||
const source = readFileSync(join(import.meta.dirname, '..', file), 'utf8')
|
||||
expect(source).toMatch(/<DialogContent\s[^>]*surface="security"/)
|
||||
expect(source).not.toContain('plugin-security-chrome')
|
||||
})
|
||||
|
||||
it('applies the host-owned token set outside dialogs', () => {
|
||||
const { container } = render(
|
||||
<SecuritySurface>
|
||||
<div>Allow</div>
|
||||
</SecuritySurface>
|
||||
)
|
||||
expect(container.firstElementChild?.classList).toContain('plugin-security-chrome')
|
||||
})
|
||||
|
||||
it.each(INLINE_TRUST_SURFACES)('%s renders inside a security surface', (file) => {
|
||||
const source = readFileSync(join(import.meta.dirname, '..', file), 'utf8')
|
||||
expect(source).toMatch(/<SecuritySurface[\s>]/)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user