test: remove 70 low-value cases and four suites (#34400)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
OrcaWin
2026-10-27 04:58:01 -07:00
committed by GitHub
co-authored by OrcaWin
parent 70d81598b0
commit 0f76063ce6
20 changed files with 7 additions and 1793 deletions
-4
View File
@@ -8772,12 +8772,10 @@
"src/renderer/src/components/settings/agent-permission-conversion-persistence.test.tsx": 8787,
"src/renderer/src/components/settings/bitbucket-integration-card.test.tsx": 421,
"src/renderer/src/components/settings/browser-session-host-selection.test.ts": 43,
"src/renderer/src/components/settings/bundled-skill-setup-labels.test.tsx": 3388,
"src/renderer/src/components/settings/bundled-skill-setup-result-copy.test.ts": 304,
"src/renderer/src/components/settings/chat-naming-persistence.test.tsx": 3654,
"src/renderer/src/components/settings/chat-settings-unsaved-guard.test.tsx": 1560,
"src/renderer/src/components/settings/cli-install-failure.test.ts": 34,
"src/renderer/src/components/settings/cli-source-control-integration-cards.test.tsx": 695,
"src/renderer/src/components/settings/codex-config-sync-warning.test.ts": 34,
"src/renderer/src/components/settings/codex-session-source-home-control.test.tsx": 154,
"src/renderer/src/components/settings/developer-permissions-search.test.ts": 178,
@@ -9241,8 +9239,6 @@
"src/renderer/src/components/skills/skill-install-progress-state.test.tsx": 258,
"src/renderer/src/components/skills/skill-install-provider-groups.test.ts": 32,
"src/renderer/src/components/skills/skill-install-workspace-choices.test.ts": 55,
"src/renderer/src/components/skills/skill-package-checklist-items.test.ts": 123,
"src/renderer/src/components/skills/skill-package-install-risk.test.ts": 73,
"src/renderer/src/components/skills/skill-share-link.test.ts": 38,
"src/renderer/src/components/skills/skill-share-package-selection.test.ts": 48,
"src/renderer/src/components/skills/skill-share-preview-summary.test.ts": 149,
@@ -1,219 +0,0 @@
// @vitest-environment happy-dom
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { BundledSkillOp } from '../../../../shared/bundled-skill-plan'
import {
doneResult,
planEntry,
planOf,
planOp
} from '../skills/bundled-skill-plan-fixtures.test-support'
import { AgentSkillSetupPanel } from './AgentSkillSetupPanel'
import { LinearAgentSkillInstallCta } from './linear-agent-skill-install-cta'
const mocks = vi.hoisted(() => ({ plan: vi.fn(), execute: vi.fn(), refresh: vi.fn() }))
const TARGET = { kind: 'environment', environmentId: 'agents-host' } as const
vi.mock('@/hooks/useActiveProjectSkillRuntime', () => ({
useActiveProjectSkillRuntime: () => ({ bundledSkillTarget: TARGET })
}))
vi.mock('@/hooks/installed-agent-skill-discovery', async (importOriginal) => ({
...(await importOriginal()),
notifyInstalledAgentSkillsChanged: vi.fn()
}))
vi.mock('@/hooks/useInstalledAgentSkills', async (importOriginal) => ({
...(await importOriginal()),
GLOBAL_AGENT_SKILL_SOURCE_KINDS: ['home'],
notifyInstalledAgentSkillsRefreshed: vi.fn(),
useInstalledAgentSkillNames: () => ({
installed: true,
loading: false,
error: null,
skills: [],
refresh: mocks.refresh
})
}))
vi.mock('@/hooks/useSkillFreshness', () => ({ refreshSkillFreshness: vi.fn() }))
vi.mock('@/runtime/runtime-bundled-skills-client', () => ({
planBundledSkillsOnTarget: mocks.plan,
executeBundledSkillsOnTarget: mocks.execute
}))
function operations(
name: string,
update: boolean,
agents: string[],
edited = false
): BundledSkillOp[] {
return [
planOp(update ? 'update' : 'keep', `/home/u/.agents/skills/${name}`),
planOp('keep', `/home/u/.claude/skills/${name}`, {
observed: { kind: 'link', target: `/home/u/.agents/skills/${name}` }
}),
...agents.map((agent) =>
planOp('link', `/home/u/.${agent}/skills/${name}`, {
agents: agent === 'cursor' ? ['cursor'] : ['gemini']
})
),
...(edited
? [
planOp('leave', `/home/u/.factory/skills/${name}`, {
reason: 'edited',
agents: ['droid'],
replaceable: true
})
]
: [])
]
}
beforeEach(() => {
mocks.plan.mockReset()
mocks.execute.mockReset()
mocks.refresh.mockReset()
})
afterEach(cleanup)
for (const surface of ['panel', 'linear'] as const) {
describe(`${surface} setup plan labels`, () => {
it.each([
{
update: false,
agents: ['cursor'],
button: 'Install for Cursor',
result: 'Installed for Cursor'
},
{
update: false,
agents: ['cursor', 'gemini'],
button: 'Install for 2 more agents',
result: 'Installed for 2 more agents'
},
{ update: true, agents: [], button: 'Update', result: 'Updated' },
{
update: true,
agents: ['cursor'],
button: 'Update',
result: 'Updated · Installed for Cursor'
},
{
update: true,
agents: ['cursor', 'gemini'],
button: 'Update',
result: 'Updated · Installed for 2 more agents'
}
])('labels $button and reports $result', async ({ update, agents, button, result }) => {
const name = surface === 'panel' ? 'orca-cli' : 'orca-linear'
const plan = planOf([planEntry(name, operations(name, update, agents))])
mocks.plan.mockResolvedValue({ kind: 'plan', plan })
mocks.execute.mockResolvedValue(doneResult(plan))
render(
surface === 'panel' ? (
<AgentSkillSetupPanel
title="CLI skill"
description={null}
skillNames={[name]}
installed
loading={false}
error={null}
onRecheck={mocks.refresh}
/>
) : (
<LinearAgentSkillInstallCta settings={null} />
)
)
fireEvent.click(await screen.findByRole('button', { name: button }))
expect(await screen.findByText(result, { exact: true })).toBeDefined()
expect(mocks.plan).toHaveBeenCalledWith(TARGET, { names: [name] })
expect(mocks.execute).toHaveBeenCalledWith(TARGET, plan.planDigest, { names: [name] })
})
it('shows the current verdict without an install button when every copy is current', async () => {
const name = surface === 'panel' ? 'orca-cli' : 'orca-linear'
mocks.plan.mockResolvedValue({
kind: 'plan',
plan: planOf([planEntry(name, operations(name, false, []))])
})
render(
surface === 'panel' ? (
<AgentSkillSetupPanel
title="CLI skill"
description={null}
skillNames={[name]}
installed
loading={false}
error={null}
onRecheck={mocks.refresh}
/>
) : (
<LinearAgentSkillInstallCta settings={null} />
)
)
await waitFor(() =>
expect(screen.queryByRole('button', { name: /^(Install|Update)/ })).toBeNull()
)
expect(
await screen.findByText(surface === 'panel' ? 'Up to date' : 'Installed', { exact: true })
).toBeDefined()
expect(mocks.execute).not.toHaveBeenCalled()
})
})
}
it('keeps the edited-copy suffix after the mixed result', async () => {
const plan = planOf([planEntry('orca-cli', operations('orca-cli', true, ['cursor'], true))])
mocks.plan.mockResolvedValue({ kind: 'plan', plan })
mocks.execute.mockResolvedValue(doneResult(plan))
render(
<AgentSkillSetupPanel
title="CLI skill"
description={null}
skillNames={['orca-cli']}
installed
loading={false}
error={null}
onRecheck={mocks.refresh}
/>
)
fireEvent.click(await screen.findByRole('button', { name: 'Update' }))
expect(
await screen.findByText(
'Updated · Installed for Cursor · Update skipped in Droid: Skill edited outside Orca'
)
).toBeDefined()
})
it.each([
{ agents: ['cursor'], result: 'Installed for Cursor' },
{ agents: ['cursor', 'gemini'], result: 'Installed for 2 agents' }
])('uses first-install wording for $result', async ({ agents, result }) => {
const plan = planOf([
planEntry('orca-cli', [
planOp('write', '/home/u/.agents/skills/orca-cli'),
...agents.map((agent) =>
planOp('link', `/home/u/.${agent}/skills/orca-cli`, {
agents: agent === 'cursor' ? ['cursor'] : ['gemini']
})
)
])
])
mocks.plan.mockResolvedValue({ kind: 'plan', plan })
mocks.execute.mockResolvedValue(doneResult(plan))
render(
<AgentSkillSetupPanel
title="CLI skill"
description={null}
skillNames={['orca-cli']}
installed={false}
loading={false}
error={null}
onRecheck={mocks.refresh}
/>
)
await waitFor(() =>
expect(screen.getByRole('button', { name: 'Install' }).hasAttribute('disabled')).toBe(false)
)
fireEvent.click(screen.getByRole('button', { name: 'Install' }))
expect(await screen.findByText(result, { exact: true })).toBeDefined()
})
@@ -1,129 +0,0 @@
// @vitest-environment happy-dom
import { act } from 'react'
import { createRoot, type Root } from 'react-dom/client'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { getExecutionHostLabel } from '../../../../shared/execution-host'
import {
GitHubIntegrationCard,
GitLabIntegrationCard
} from './cli-source-control-integration-cards'
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: null as StoreState | null },
preflight: {
statuses: {
ghStatus: 'connected',
glabStatus: 'connected',
bitbucketStatus: 'not-configured',
azureDevOpsStatus: 'not-configured',
giteaStatus: 'not-configured',
bitbucketAccount: null,
azureDevOpsAccount: null,
giteaAccount: null
},
unavailable: false,
refresh: vi.fn()
}
}))
vi.mock('@/store', () => ({
useAppStore: (selector: (state: StoreState) => unknown) => {
if (!mocks.store.current) {
throw new Error('Store state was not installed')
}
return selector(mocks.store.current)
}
}))
vi.mock('./source-control-preflight-card-status', () => ({
usePreflightCardStatuses: () => mocks.preflight
}))
let root: Root | null = null
let container: HTMLDivElement | null = null
async function renderCard(card: React.ReactNode): Promise<HTMLDivElement> {
container = document.createElement('div')
document.body.appendChild(container)
root = createRoot(container)
await act(async () => {
root?.render(card)
})
return container
}
describe('CLI source-control integration card account scope', () => {
afterEach(async () => {
if (root) {
await act(async () => {
root?.unmount()
})
}
root = null
container?.remove()
container = null
mocks.store.current = null
mocks.preflight.statuses.ghStatus = 'connected'
mocks.preflight.statuses.glabStatus = 'connected'
mocks.preflight.unavailable = false
mocks.preflight.refresh.mockClear()
})
it('shows local-client ownership for connected GitHub CLI credentials', async () => {
const openSettingsPage = vi.fn()
const openSettingsTarget = vi.fn()
mocks.store.current = {
settings: { activeRuntimeEnvironmentId: null },
openSettingsPage,
openSettingsTarget
}
const rendered = await renderCard(<GitHubIntegrationCard />)
expect(rendered.textContent).toContain('GitHub')
expect(rendered.textContent).toContain('Connected')
expect(rendered.textContent).toContain(`Account scope: ${LOCAL_HOST_LABEL}`)
expect(rendered.textContent).toContain(
'Credentials and account checks for this provider are owned by this desktop client. To use a server, change Settings > Remote Orca Servers > New projects open on.'
)
await act(async () => {
Array.from(rendered.querySelectorAll('button'))
.find((button) => button.textContent === 'Open Remote Servers')
?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
})
expect(openSettingsPage).toHaveBeenCalledTimes(1)
expect(openSettingsTarget).toHaveBeenCalledWith({
pane: 'servers',
repoId: null,
sectionId: 'default-runtime'
})
})
it('shows remote-server ownership for GitLab CLI credential checks', async () => {
mocks.store.current = {
settings: { activeRuntimeEnvironmentId: 'runtime-1' },
openSettingsPage: vi.fn(),
openSettingsTarget: vi.fn()
}
mocks.preflight.statuses.glabStatus = 'not-authenticated'
const rendered = await renderCard(<GitLabIntegrationCard />)
expect(rendered.textContent).toContain('GitLab')
expect(rendered.textContent).toContain('Account scope: Remote server: runtime-1')
expect(rendered.textContent).toContain(
'Credentials and account checks for this provider are owned by this remote server. To use another host, change Settings > Remote Orca Servers > New projects open on.'
)
expect(rendered.textContent).toContain('glab auth login')
})
})
@@ -108,43 +108,6 @@ describe('LinearAgentSkillInstallCta', () => {
container = null
})
it('offers Install and the explanation when the skill is missing', async () => {
const rendered = await renderCta()
expect(rendered.textContent).toContain('Agent skill:')
expect(rendered.textContent).toContain('orca-linear')
expect(rendered.textContent).toContain('Not installed')
expect(rendered.textContent).toContain(
'Full guided setup (connect + skill + visibility) is under Settings → Task Sources.'
)
expect(rendered.textContent).toContain('Install skill')
expect(rendered.textContent).not.toContain('Copy command')
})
it("installs the skill directly on the agents' host", async () => {
const rendered = await renderCta()
await act(async () => {
Array.from(rendered.querySelectorAll('button'))
.find((button) => button.textContent === 'Install skill')
?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
})
expect(mocks.plan).toHaveBeenCalledWith({ kind: 'local' }, { names: ['orca-linear'] })
expect(rendered.textContent).toContain('Update Orca on the server to install its skills.')
})
it('shows a subtle confirmation and Update when installed', async () => {
mocks.skillState.installed = true
mocks.skillState.skills = [discoveredSkill({ name: 'orca-linear' })]
const rendered = await renderCta()
expect(rendered.textContent).toContain('Installed')
expect(rendered.textContent).toContain('Update')
expect(rendered.textContent).not.toContain('Not installed')
})
it('updates through the legacy skill name when only linear-tickets is installed', async () => {
mocks.skillState.installed = true
mocks.skillState.skills = [
@@ -166,16 +129,6 @@ describe('LinearAgentSkillInstallCta', () => {
expect(mocks.plan).toHaveBeenCalledWith({ kind: 'local' }, { names: ['linear-tickets'] })
})
it('notes that remote agent environments need their own setup', async () => {
const rendered = await renderCta({
settings: { activeRuntimeEnvironmentId: 'runtime-1', terminalWindowsShell: '' }
})
expect(rendered.textContent).toContain(
'This installs host setup; remote agent environments may need separate setup.'
)
})
it('keeps the resolved WSL project on the old command flow', async () => {
mocks.agentRuntime = { runtime: 'wsl', wslDistro: 'Debian', label: 'WSL Debian' }
const rendered = await renderCta()
@@ -189,31 +142,4 @@ describe('LinearAgentSkillInstallCta', () => {
expect.objectContaining({ discoveryTarget: { runtime: 'wsl', wslDistro: 'Debian' } })
)
})
it('uses project host discovery even when legacy global settings still prefer WSL', async () => {
mocks.agentRuntime = { runtime: 'host', label: 'Windows' }
await renderCta({
settings: {
localAgentRuntime: 'wsl',
localAgentWslDistro: 'Ubuntu',
terminalWindowsShell: 'wsl.exe'
}
})
expect(mocks.useInstalledAgentSkillNames).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({ discoveryTarget: undefined })
)
})
it('re-checks the skill scan on demand', async () => {
const rendered = await renderCta()
await act(async () => {
Array.from(rendered.querySelectorAll('button'))
.find((button) => button.textContent?.includes('Re-check'))
?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
})
expect(mocks.skillState.refresh).toHaveBeenCalledTimes(1)
})
})
@@ -4,17 +4,9 @@ import { act, render, cleanup, fireEvent } from '@testing-library/react'
import '../../__fixtures__/settings-persistence-test-fixture'
import React from 'react'
import type { TuiAgent } from '../../../../../../shared/tui-agent'
import { renderToStaticMarkup } from 'react-dom/server'
import { normalizeRepoSourceControlAiOverrides } from '../../../../../../shared/source-control-ai-repo-settings'
import type {
RepoSourceControlAiOverrides,
SourceControlAiSettings
} from '../../../../../../shared/source-control-ai-types'
import {
SOURCE_CONTROL_ACTION_IDS,
setSourceControlActionAgentDefault,
type SourceControlActionId
} from '../../../../../../shared/source-control-ai-actions'
import type { RepoSourceControlAiOverrides } from '../../../../../../shared/source-control-ai-types'
import { setSourceControlActionAgentDefault } from '../../../../../../shared/source-control-ai-actions'
import {
computeActionDirtyById,
dropRepoLegacyInstructionForAction,
@@ -29,7 +21,6 @@ import {
import { sourceControlActionRecipeMatchesTarget } from '../../../right-sidebar/source-control-action-recipe-match'
import { SourceControlActionAgentPicker } from '../../SourceControlActionAgentPicker'
import { RepositorySourceControlAiActionRows } from './RepositorySourceControlAiActionRows'
import { RepositorySourceControlAiEnablement } from './RepositorySourceControlAiEnablement'
import { SourceControlAiActionRecipeDefaults } from '../../SourceControlAiActionRecipeDefaults'
import { useAppStore } from '../../../../store/index'
import { TooltipProvider } from '../../../ui/tooltip'
@@ -103,24 +94,6 @@ describe('saved chat-or-CLI pick', () => {
})
})
describe('RepositorySourceControlAiEnablement', () => {
it('describes repo enablement as Source Control AI action visibility', () => {
const markup = renderToStaticMarkup(
React.createElement(RepositorySourceControlAiEnablement, {
value: false,
source: { enabled: true } as SourceControlAiSettings,
onChange: () => {}
})
)
expect(markup).toContain('Show Source Control AI actions')
expect(markup).toContain('Controls whether Source Control AI buttons are shown')
expect(markup).toContain('separate features follows those features&#x27; settings')
expect(markup).toContain('Show')
expect(markup).not.toContain('Source Control AI enabled')
})
})
describe('RepositorySourceControlAiActionRows', () => {
it.each([
{ agent: 'codex', detected: ['codex'], surface: undefined },
@@ -270,67 +243,6 @@ describe('RepositorySourceControlAiActionRows', () => {
expect(onEdit).not.toHaveBeenCalled()
}
)
it.each([undefined, null, '', '--repo'])(
'renders save controls and argument ownership for %s',
(repoAgentArgs) => {
const actionDirtyById = Object.fromEntries(
SOURCE_CONTROL_ACTION_IDS.map((actionId) => [actionId, actionId === 'fixCommitFailure'])
) as Record<SourceControlActionId, boolean>
const source: SourceControlAiSettings = {
enabled: true,
agentId: null,
selectedModelByAgent: {},
selectedThinkingByModel: {},
customAgentCommand: '',
instructionsByOperation: {},
actions: { fixCommitFailure: { agentId: null, agentArgs: '--global' } }
}
const rows = React.createElement(RepositorySourceControlAiActionRows, {
repoId: 'repo-1',
repoAi: {
actionOverrides: {
fixCommitFailure: {
agentId: null,
commandInputTemplate: '{basePrompt}\n\nrepo',
...(repoAgentArgs !== undefined ? { agentArgs: repoAgentArgs } : {})
}
}
},
source,
defaultTuiAgent: 'codex',
savingActionIds: {},
actionDirtyById,
onActionModeChange: () => {},
onActionAgentChange: () => {},
onActionLaunchPreferencesChange: () => {},
onActionTemplateChange: () => {},
onActionAgentArgsChange: () => {},
onAppendVariable: () => {},
onActionDiscard: () => {},
onActionSave: () => {}
})
const markup = renderToStaticMarkup(React.createElement(TooltipProvider, null, rows))
expect(markup).toContain('Commit failure fixes')
expect(markup).toContain('Push failure fixes')
expect(markup).toContain('Unsaved changes')
expect(markup).toContain('Discard')
expect(markup).toContain('Save')
expect(markup).toContain('repo-repo-1-source-control-ai-fixCommitFailure')
const card = new DOMParser()
.parseFromString(markup, 'text/html')
.getElementById('repo-repo-1-source-control-ai-fixCommitFailure')
expect(card?.textContent).toContain(
repoAgentArgs === undefined
? 'Global custom args'
: repoAgentArgs
? 'Repository custom args'
: 'No args'
)
}
)
})
describe('dropRepoLegacyInstructionForAction', () => {
@@ -353,17 +265,6 @@ describe('dropRepoLegacyInstructionForAction', () => {
commandInputTemplate: '{basePrompt}\n\nUse PR style.'
})
})
it('leaves launch action recipes alone because they have no legacy instruction key', () => {
const value = {
instructionsByOperation: { commitMessage: 'Use repo style.' },
actionOverrides: {
fixChecks: { commandInputTemplate: '{basePrompt}' }
}
}
expect(dropRepoLegacyInstructionForAction(value, 'fixChecks')).toBe(value)
})
})
it.each(['', '--model raw'])(
@@ -443,19 +443,6 @@ describe('editing one repository action', () => {
expect(set().launchPreferences).toBeUndefined()
})
it('makes the copied model owned by the action, so it outranks the raw model it yielded to', () => {
const legacy = { actionOverrides: { fixCommitFailure: { agentArgs: '--model gpt-5.6-sol' } } }
const stored = {
settings,
repo: { sourceControlAi: legacy },
actionId: 'fixCommitFailure'
} as const
expect(resolveSourceControlActionRecipe(stored).launchPreferencesYieldToAgentArgs).toBe(true)
const edited = reload(editPrompt(legacy, 'fixCommitFailure'), 'fixCommitFailure')
expect(edited).toMatchObject({ commandInputTemplate: 'mine', launchPreferences: terra })
expect(edited.launchPreferencesYieldToAgentArgs).toBeUndefined()
})
it('saves a prompt edit without authoring args the user never typed or touching a sibling', async () => {
const sibling = { agentArgs: '--repo' }
const cleared = {
@@ -38,14 +38,6 @@ describe('createRepoAiPersistQueue', () => {
h = makeQueue()
})
it('persists a changed value and stores the normalized saved result', async () => {
h.updateRepo.mockResolvedValue(true)
const ok = await h.queue.persistTransform((base) => ({ ...base, enabled: true }))
expect(ok).toBe(true)
expect(h.updateRepo).toHaveBeenCalledTimes(1)
expect(h.getPersisted()).toMatchObject({ enabled: true })
})
it('skips the backend write when the transform is a no-op', async () => {
h.updateRepo.mockResolvedValue(true)
const ok = await h.queue.persistTransform((base) => base)
@@ -128,20 +120,4 @@ describe('createRepoAiPersistQueue', () => {
// Completing a stale write must not seed the new repo's local persisted base.
expect(h.setPersisted).not.toHaveBeenCalled()
})
it('does not apply setPersisted when the repo switches during an in-flight updateRepo', async () => {
let release: (ok: boolean) => void = () => {}
h.updateRepo.mockImplementation(
() =>
new Promise<boolean>((resolve) => {
release = resolve
})
)
const pending = h.queue.persistTransform((base) => ({ ...base, enabled: true }))
h.setRepoId('repo-2')
release(true)
await expect(pending).resolves.toBe(true)
expect(h.setPersisted).not.toHaveBeenCalled()
expect(h.onError).not.toHaveBeenCalled()
})
})
@@ -4,36 +4,6 @@ import { resolveSettingsHostScope } from './settings-host-scope'
const saved = ['env-a', 'env-b']
describe('resolveSettingsHostScope', () => {
it('starts on the default host setting until the user picks one', () => {
expect(
resolveSettingsHostScope({
choice: null,
defaultEnvironmentId: ' env-a ',
savedEnvironmentIds: saved,
catalogHydrated: true
})
).toEqual({ target: { kind: 'environment', environmentId: 'env-a' }, available: true })
})
it('keeps the picked host when the default host setting changes', () => {
expect(
resolveSettingsHostScope({
choice: { kind: 'local' },
defaultEnvironmentId: 'env-a',
savedEnvironmentIds: saved,
catalogHydrated: true
})
).toEqual({ target: { kind: 'local' }, available: true })
expect(
resolveSettingsHostScope({
choice: { kind: 'environment', environmentId: 'env-b' },
defaultEnvironmentId: null,
savedEnvironmentIds: saved,
catalogHydrated: true
})
).toEqual({ target: { kind: 'environment', environmentId: 'env-b' }, available: true })
})
it('marks a removed server unavailable instead of falling back to this computer', () => {
expect(
resolveSettingsHostScope({
@@ -1,44 +1,8 @@
import { describe, expect, it } from 'vitest'
import type { KeybindingDefinition } from '../../../../shared/keybindings'
import type { ActivePluginCommand } from '@/store/plugin-panels'
import { buildShortcutDefinitionCatalog } from './shortcut-definition-catalog'
import { groupDefinitions } from './shortcut-groups'
const pluginDefinition: KeybindingDefinition = {
id: 'plugin:orca-samples.tasks/open',
title: 'Open Tasks — Tasks',
group: 'Plugins',
scope: 'global',
searchKeywords: ['plugin', 'tasks'],
defaultBindings: {
darwin: ['Mod+Alt+T'],
linux: ['Mod+Alt+T'],
win32: ['Mod+Alt+T']
}
}
describe('shortcut groups', () => {
it('includes dynamic plugin command definitions in Settings', () => {
expect(groupDefinitions([], [pluginDefinition])).toEqual(
expect.arrayContaining([
expect.objectContaining({
title: 'Plugins',
items: [pluginDefinition]
})
])
)
})
it('exposes the agent dashboard toggle as a customizable Global row', () => {
const global = groupDefinitions([]).find((group) => group.title === 'Global')
expect(global?.items).toEqual(
expect.arrayContaining([
expect.objectContaining({ id: 'dashboard.toggle', title: 'Toggle Agent Dashboard' })
])
)
})
it('reports a plugin default that shadows a built-in shortcut', () => {
const command: ActivePluginCommand = {
pluginKey: 'orca-samples.tasks',
@@ -115,36 +115,6 @@ function renderSurface(
)
}
for (const surface of ['panel', 'guided-panel', 'linear', 'browser', 'capabilities'] as const) {
describe(`${surface} blocked setup`, () => {
describe.each([
[
'update-server',
'Update Orca on the server to install its skills.',
'Update Orca on the server'
],
['connect-host', 'Connect to this SSH host to install its skills.', 'Connect the SSH host']
] as const)('%s', (reason, message, pill) => {
it.each(surface === 'browser' || surface === 'capabilities' ? [false] : [false, true])(
'explains the prerequisite before any click (installed: %s)',
async (installed) => {
mocks.installed = installed
mocks.plan.mockResolvedValue({ kind: 'unsupported', reason })
renderSurface(surface)
expect(await screen.findByText(message)).toBeDefined()
expect(screen.queryByRole('button', { name: /^(Install|Update|Replace)/ })).toBeNull()
expect(screen.queryByText('Check failed')).toBeNull()
if (surface === 'panel' || surface === 'guided-panel' || surface === 'linear') {
expect(screen.getByText(pill, { exact: true })).toBeDefined()
}
expect(mocks.execute).not.toHaveBeenCalled()
}
)
})
})
}
for (const surface of ['panel', 'linear', 'browser', 'capabilities'] as const) {
describe(`${surface} blocked setup`, () => {
it.each([false, true])(
@@ -17,7 +17,6 @@ import {
} from '@/runtime/host-structured-agents'
import { TooltipProvider } from '../ui/tooltip'
import { SourceControlActionRecipeRow } from './SourceControlActionRecipeRow'
import { shouldShowSourceControlActionArguments } from './source-control-action-arguments-visibility'
vi.mock('@/runtime/runtime-rpc-client', () => ({
callRuntimeRpc: vi.fn(async () => ({ agents: [{ agent: 'grok', capabilities: {} }] }))
@@ -67,27 +66,6 @@ beforeEach(() => {
})
afterEach(resetHostStructuredAgentsForTests)
it.each([
['chat on', 'claude', true, undefined, false],
['chat off', 'claude', false, undefined, true],
['provider off', 'claude', true, { claude: false }, true],
['no chat support', 'goose', true, undefined, true]
] as const)('%s: shows Arguments only for the CLI', (_name, agent, chat, providers, visible) => {
configure({ experimentalNativeChat: chat, nativeChatProviders: providers })
render(<Row agent={agent} />)
expect(Boolean(screen.queryByRole('textbox', { name: 'Arguments' }))).toBe(visible)
expect(screen.getByText('Command template')).toBeDefined()
})
it('hides an unresolved launch even with chat off, but always shows text action Arguments', () => {
configure({ experimentalNativeChat: false })
useAppStore.setState({ localDetectedAgentIdsByContext: {} })
const view = render(<Row />)
expect(screen.queryByRole('textbox', { name: 'Arguments' })).toBeNull()
view.rerender(<Row actionId="commitMessage" />)
expect(screen.getByDisplayValue('--saved')).toBeDefined()
})
it('keeps the draft and chip while Model/Effort controls stay hidden', () => {
configure({ experimentalNativeChat: false })
render(<Row />)
@@ -124,22 +102,3 @@ it('reacts to the real host registry when another agent gains chat support', asy
)
expect(screen.queryByRole('textbox', { name: 'Arguments' })).toBeNull()
})
it('honors an explicit future surface only after an agent resolves', () => {
const input = {
agent: 'claude',
settings: { experimentalNativeChat: true },
chatAgents: ['claude']
} as const
expect(shouldShowSourceControlActionArguments({ ...input, launchKind: 'terminal' })).toBe(true)
expect(
shouldShowSourceControlActionArguments({
...input,
settings: { experimentalNativeChat: false },
launchKind: 'chat'
})
).toBe(false)
expect(
shouldShowSourceControlActionArguments({ ...input, agent: null, launchKind: 'terminal' })
).toBe(false)
})
@@ -1,7 +1,6 @@
import { afterEach, describe, expect, it } from 'vitest'
import {
MAX_LINEAR_AGENT_SKILL_SETUP_REMINDER_RUNTIME_KEYS,
createLinearAgentSkillSetupActivationId,
getExistingLinearAgentSkillSetupReminderState,
getLinearAgentSkillSetupReminderState,
getLinearAgentSkillSetupReminderStateCountForTests,
@@ -77,13 +76,4 @@ describe('linear agent skill setup reminders', () => {
expect(getExistingLinearAgentSkillSetupReminderState('missing')).toBeUndefined()
expect(getLinearAgentSkillSetupReminderStateCountForTests()).toBe(0)
})
it('resets activation ids with reminder state for tests', () => {
expect(createLinearAgentSkillSetupActivationId()).toBe('linear-agent-skill-setup-0')
expect(createLinearAgentSkillSetupActivationId()).toBe('linear-agent-skill-setup-1')
resetLinearAgentSkillSetupReminderState()
expect(createLinearAgentSkillSetupActivationId()).toBe('linear-agent-skill-setup-0')
})
})
@@ -1,148 +1,11 @@
import { describe, expect, it, vi } from 'vitest'
import { expect, it, vi } from 'vitest'
import type { SkillDeletePlan } from '../../../../shared/skill-delete-contract'
import {
skillDeleteBlockedLines,
skillDeletePlacementSummary,
skillDeleteResultLines,
skillDeleteRetainedSourceLines
} from './skill-delete-copy'
import { skillDeletePlacementSummary } from './skill-delete-copy'
function plan(skills: SkillDeletePlan['skills']): SkillDeletePlan {
return { operationId: 'op', skills }
}
describe('skillDeletePlacementSummary', () => {
it('summarizes folders, links, and the roots they sit in', () => {
const summary = skillDeletePlacementSummary(
plan([
{
id: 'a',
name: 'demo',
canonicalPath: '/root/demo/SKILL.md',
placements: [
{
path: '/home/.agents/skills/demo',
kind: 'canonical',
rootLabel: 'Agent skills home'
},
{ path: '/home/.claude/skills/demo', kind: 'alias-dir', rootLabel: 'Claude Code home' }
]
}
])
)
expect(summary).toContain('1')
expect(summary).toContain('Agent skills home')
expect(summary).toContain('Claude Code home')
})
it('drops the zero half so a link-only delete never reads "0 folders"', () => {
const summary = skillDeletePlacementSummary(
plan([
{
id: 'a',
name: 'demo',
canonicalPath: '/home/.local/share/devex/skills/demo/SKILL.md',
placements: [
{ path: '/repo/.agents/skills/demo', kind: 'alias-dir', rootLabel: 'Repo app .agents' }
]
}
])
)
expect(summary).not.toContain('0')
expect(summary).toContain('link')
expect(summary).not.toContain('folder')
})
it('is null when the plan removes nothing', () => {
expect(
skillDeletePlacementSummary(
plan([
{
id: 'a',
name: 'demo',
canonicalPath: '/elsewhere/demo/SKILL.md',
placements: [],
blocked: 'unowned'
}
])
)
).toBeNull()
})
})
describe('skillDeleteRetainedSourceLines', () => {
it('says where the content stays when only links are removed', () => {
const lines = skillDeleteRetainedSourceLines(
plan([
{
id: 'a',
name: 'demo',
canonicalPath: '/home/.local/share/devex/skills/demo/SKILL.md',
placements: [
{ path: '/repo/.agents/skills/demo', kind: 'alias-dir', rootLabel: 'Repo app .agents' }
]
}
])
)
expect(lines).toHaveLength(1)
expect(lines[0]).toContain('/home/.local/share/devex/skills/demo')
})
it('stays silent when a real folder is being removed', () => {
expect(
skillDeleteRetainedSourceLines(
plan([
{
id: 'a',
name: 'demo',
canonicalPath: '/home/.agents/skills/demo/SKILL.md',
placements: [
{
path: '/home/.agents/skills/demo',
kind: 'canonical',
rootLabel: 'Agent skills home'
}
]
}
])
)
).toEqual([])
})
})
describe('skillDeleteBlockedLines', () => {
it('groups by typed reason rather than one merged skipped line', () => {
const lines = skillDeleteBlockedLines(
plan([
{ id: 'a', name: 'a', canonicalPath: '/p', placements: [], blocked: 'bundled' },
{ id: 'b', name: 'b', canonicalPath: '/p', placements: [], blocked: 'bundled' },
{ id: 'c', name: 'c', canonicalPath: '/p', placements: [], blocked: 'plugin' }
])
)
expect(lines).toHaveLength(2)
expect(lines.some((line) => line.includes('2') && line.includes('Bundled'))).toBe(true)
})
})
describe('skillDeleteResultLines', () => {
it('omits deleted rows and keeps one line per distinct outcome', () => {
const lines = skillDeleteResultLines([
{ id: 'a', name: 'a', status: 'deleted', removedPaths: ['/x'] },
{ id: 'b', name: 'b', status: 'skipped', blocked: 'unowned', removedPaths: [] },
{ id: 'c', name: 'c', status: 'skipped', blocked: 'unowned', removedPaths: [] },
{ id: 'd', name: 'd', status: 'busy', removedPaths: [] }
])
expect(lines.map((line) => line.key)).toEqual(['skipped:unowned', 'busy'])
expect(lines[0].label).toContain('2')
})
it('renders nothing when every skill was deleted', () => {
expect(
skillDeleteResultLines([{ id: 'a', name: 'a', status: 'deleted', removedPaths: ['/x'] }])
).toEqual([])
})
})
it('sorts deletion roots once with unchanged case, accent, and number ordering', () => {
const labels = ['éclair', 'Eclair', 'item2', 'item10', 'Ångström', 'zebra', 'İstanbul']
const expected = [...labels].sort((a, b) =>
@@ -1,114 +0,0 @@
import { describe, expect, it } from 'vitest'
import type { SkillCloudVersion } from '../../../../shared/skill-cloud-contract'
import { checklistItemSummary, checklistItemsFromVersion } from './skill-package-checklist-items'
type ManifestFile = {
path: string
size: number
executable: boolean
classification: 'binary' | 'text'
sha256: string
identitySha256: string
}
function file(path: string, executable = false): ManifestFile {
return {
path,
size: 1024,
executable,
classification: 'text' as const,
sha256: 'a'.repeat(64),
identitySha256: 'a'.repeat(64)
}
}
function singleVersion(): SkillCloudVersion {
return {
packageId: 'pkg_1',
versionId: 'ver_1',
name: 'published-name',
description: 'Published description',
packageDigest: 'a'.repeat(64),
archiveSha256: 'b'.repeat(64),
compressedBytes: 128,
createdAt: '2026-08-11T00:00:00.000Z',
releaseNotes: '',
manifest: {
schemaVersion: 1,
packageId: 'pkg_1',
versionId: 'ver_1',
name: 'manifest-name',
description: 'Manifest description',
createdAt: '2026-08-11T00:00:00.000Z',
files: [file('SKILL.md')],
packageDigest: 'a'.repeat(64)
}
}
}
describe('checklistItemsFromVersion', () => {
it('turns a single-skill package into a one-row checklist', () => {
expect(checklistItemsFromVersion(singleVersion())).toEqual([
{
id: 'manifest-name',
name: 'published-name',
description: 'Published description',
files: [file('SKILL.md')]
}
])
})
it('keeps each bundled skill as its own row', () => {
const bundled = {
...singleVersion(),
manifest: {
schemaVersion: 1 as const,
packageId: 'pkg_1',
versionId: 'ver_1',
bundleName: 'shared-skills',
description: 'Synthetic bundle description',
createdAt: '2026-08-11T00:00:00.000Z',
bundleDigest: 'c'.repeat(64),
skills: [
{
id: 'a',
name: 'alpha',
description: 'Alpha',
digest: 'd'.repeat(64),
files: [file('SKILL.md')]
},
{
id: 'b',
name: 'beta',
description: 'Beta',
digest: 'e'.repeat(64),
files: [file('SKILL.md')]
}
]
}
} as unknown as SkillCloudVersion
expect(checklistItemsFromVersion(bundled).map((item) => item.name)).toEqual(['alpha', 'beta'])
})
})
describe('checklistItemSummary', () => {
it('keeps instruction-only and supporting-file summaries simple', () => {
expect(checklistItemSummary([file('SKILL.md')])).toEqual({
label: '1 file'
})
expect(checklistItemSummary([file('SKILL.md'), file('references/guide.md')])).toEqual({
label: '2 files'
})
})
it('keeps runnable and binary classifications out of the row summary', () => {
expect(checklistItemSummary([file('SKILL.md'), file('scripts/setup.sh')])).toEqual({
label: '2 files'
})
expect(checklistItemSummary([file('bin/tool', true)])).toEqual({ label: '1 file' })
const binary = { ...file('assets/logo.png'), classification: 'binary' as const }
expect(checklistItemSummary([file('SKILL.md'), binary])).toEqual({
label: '2 files'
})
})
})
@@ -1,74 +0,0 @@
import { describe, expect, it } from 'vitest'
import {
isSkillRunnableFile,
summarizeSkillInstallRisk,
type SkillInstallRiskFile
} from './skill-package-install-risk'
function file(
path: string,
options: { classification?: 'text' | 'binary'; executable?: boolean } = {}
): SkillInstallRiskFile {
return {
path,
classification: options.classification ?? 'text',
executable: options.executable ?? false
}
}
describe('isSkillRunnableFile', () => {
it('recognizes scripts outside the scripts folder and executable files without extensions', () => {
expect(isSkillRunnableFile(file('release.py'))).toBe(true)
expect(isSkillRunnableFile(file('tools/setup.PS1'))).toBe(true)
expect(isSkillRunnableFile(file('bin/team-tool'))).toBe(true)
expect(isSkillRunnableFile(file('references/guide.md', { executable: true }))).toBe(true)
})
it('does not label ordinary supporting files as runnable', () => {
expect(isSkillRunnableFile(file('references/guide.md'))).toBe(false)
expect(isSkillRunnableFile(file('assets/logo.png', { classification: 'binary' }))).toBe(false)
})
})
describe('summarizeSkillInstallRisk', () => {
const items = [
{ id: 'plain', name: 'plain', files: [file('SKILL.md')] },
{
id: 'docs',
name: 'docs',
files: [file('SKILL.md'), file('references/guide.md')]
},
{
id: 'tools',
name: 'tools',
files: [
file('SKILL.md'),
file('release.py'),
file('bin/tool', { classification: 'binary', executable: true })
]
}
]
it('separates supporting files from content that needs acknowledgement', () => {
expect(summarizeSkillInstallRisk(items, new Set(['docs']))).toMatchObject({
selectedSkillCount: 1,
additionalFileCount: 1,
runnableFileCount: 0,
binaryFileCount: 0,
cautionFileCount: 0,
requiresAcknowledgement: false
})
})
it('counts a runnable binary once for the acknowledgement gate', () => {
expect(summarizeSkillInstallRisk(items, new Set(['tools']))).toEqual({
selectedSkillCount: 1,
additionalFileCount: 2,
runnableFileCount: 2,
binaryFileCount: 1,
cautionFileCount: 2,
cautionSkillNames: ['tools'],
requiresAcknowledgement: true
})
})
})
@@ -15,8 +15,7 @@ import {
isWorkspaceSpaceRowReadyToDelete,
pruneWorkspaceSpaceSelectedIds,
resolveWorkspaceSpaceInspectedWorktreeId,
resolveWorkspaceSpaceTreemapZoomWorktreeId,
sortWorkspaceSpaceRows
resolveWorkspaceSpaceTreemapZoomWorktreeId
} from './workspace-space-presentation'
import { getWorkspaceSpaceGitStatusRefreshCandidates } from './workspace-space-git-status-order'
import { getWorkspaceDecisionDetails } from './workspace-space-decision-details'
@@ -228,37 +227,6 @@ describe('workspace space presentation helpers', () => {
expect(isWorkspaceSpaceRowReadyToDelete(sshRow, ssh)).toBe(false)
})
it('sorts rows by the selected key and direction', () => {
const rows = [
row({ worktreeId: 'small', displayName: 'Small', sizeBytes: 10 }),
row({ worktreeId: 'large', displayName: 'Large', sizeBytes: 100 }),
row({ worktreeId: 'mid', displayName: 'Mid', sizeBytes: 50 })
]
expect(sortWorkspaceSpaceRows(rows, 'size', 'desc').map((item) => item.worktreeId)).toEqual([
'large',
'mid',
'small'
])
expect(sortWorkspaceSpaceRows(rows, 'name', 'asc').map((item) => item.worktreeId)).toEqual([
'large',
'mid',
'small'
])
})
it('filters by search text and deletable status', () => {
const rows = [
row({ worktreeId: 'a', displayName: 'Frontend Cache', repoDisplayName: 'app' }),
row({ worktreeId: 'b', displayName: 'Main', repoDisplayName: 'api', canDelete: false })
]
expect(filterWorkspaceSpaceRows(rows, 'cache', false).map((item) => item.worktreeId)).toEqual([
'a'
])
expect(filterWorkspaceSpaceRows(rows, '', true).map((item) => item.worktreeId)).toEqual(['a'])
})
it('rejects oversized pasted filters before reading workspace rows', () => {
const oversizedQuery = 'secret-workspace-space'.repeat(WORKSPACE_SPACE_FILTER_QUERY_MAX_BYTES)
const rows = [
@@ -456,117 +424,6 @@ describe('workspace space presentation helpers', () => {
expect(details.reviewLabel).toBeNull()
})
it('hides only a matching suppressed GitHub review from workspace decisions', () => {
const matching = getWorkspaceDecisionDetails(
row({ branch: 'refs/heads/feature/local' }),
decisionInputs({
hostedReviewCache: {
'local::repo::feature/local': {
data: {
provider: 'github',
number: 12,
state: 'open',
status: 'success',
title: 'Suppressed PR'
}
}
},
worktreeMap: new Map([
[
'wt',
worktreeRecord({
branch: 'refs/heads/feature/local',
linkedPR: null,
suppressedGitHubPR: 12
})
]
])
})
)
const different = getWorkspaceDecisionDetails(
row({ branch: 'refs/heads/feature/local' }),
decisionInputs({
hostedReviewCache: {
'local::repo::feature/local': {
data: {
provider: 'github',
number: 13,
state: 'open',
status: 'success',
title: 'Different PR'
}
}
},
worktreeMap: new Map([
[
'wt',
worktreeRecord({
branch: 'refs/heads/feature/local',
linkedPR: null,
suppressedGitHubPR: 12
})
]
])
})
)
expect(matching.reviewLabel).toBeNull()
expect(different.reviewLabel).toBe('PR #13 Open, success')
})
it('preserves explicit links and non-GitHub reviews in workspace decisions', () => {
const cachedReview = {
number: 12,
state: 'open',
status: 'success',
title: 'Review'
}
const explicit = getWorkspaceDecisionDetails(
row({ branch: 'refs/heads/feature/local' }),
decisionInputs({
hostedReviewCache: {
'local::repo::feature/local': {
data: { ...cachedReview, provider: 'github' }
}
},
worktreeMap: new Map([
[
'wt',
worktreeRecord({
branch: 'refs/heads/feature/local',
linkedPR: 12,
suppressedGitHubPR: 12
})
]
])
})
)
const gitLab = getWorkspaceDecisionDetails(
row({ branch: 'refs/heads/feature/local' }),
decisionInputs({
hostedReviewCache: {
'local::repo::feature/local': {
data: { ...cachedReview, provider: 'gitlab' }
}
},
worktreeMap: new Map([
[
'wt',
worktreeRecord({
branch: 'refs/heads/feature/local',
linkedPR: null,
suppressedGitHubPR: 12,
linkedGitLabMR: 12
})
]
])
})
)
expect(explicit.reviewLabel).toBe('PR #12 Open, success')
expect(gitLab.reviewLabel).toBe('MR !12 Open, success')
})
it('counts migration-unsupported agent entries by worktree id', () => {
const count = countWorkspaceSpaceActiveAgents({
worktreeId: 'wt',
@@ -1,61 +1,8 @@
import { describe, expect, it } from 'vitest'
import {
getCandidateFactStatuses,
getDirtyGitLabel,
shouldShowGitMetadataChip
} from './workspace-cleanup-candidate-row-data'
import { getCandidateFactStatuses } from './workspace-cleanup-candidate-row-data'
import { makeCandidate } from './workspace-cleanup-presentation-fixtures'
describe('workspace cleanup candidate row data', () => {
it('shows facts instead of cleanup policy tiers', () => {
expect(getCandidateFactStatuses(makeCandidate({ tier: 'ready' }))).toEqual([])
expect(getCandidateFactStatuses(makeCandidate({ tier: 'review' }))).toEqual([])
expect(getCandidateFactStatuses(makeCandidate({ reasons: ['archived'] }))).toContainEqual(
expect.objectContaining({ label: 'Archived' })
)
})
it('does not duplicate git status blockers as a separate git icon label', () => {
const gitStatusError = makeCandidate({
blockers: ['git-status-error'],
git: {
clean: null,
upstreamAhead: null,
upstreamBehind: null,
checkedAt: null
}
})
const unknownBase = makeCandidate({
blockers: ['unknown-base'],
git: {
clean: true,
upstreamAhead: null,
upstreamBehind: null,
checkedAt: 1
}
})
expect(getDirtyGitLabel(gitStatusError)).toBe('Git status check failed')
expect(shouldShowGitMetadataChip(gitStatusError)).toBe(false)
expect(getDirtyGitLabel(unknownBase)).toBe('Git status could not be verified')
expect(shouldShowGitMetadataChip(unknownBase)).toBe(false)
})
it('keeps the git metadata chip for ordinary clean rows', () => {
expect(
shouldShowGitMetadataChip(
makeCandidate({
git: {
clean: true,
upstreamAhead: 0,
upstreamBehind: 0,
checkedAt: 1
}
})
)
).toBe(true)
})
it('keeps unpushed risk visible for archived rows', () => {
expect(
getCandidateFactStatuses(
@@ -72,59 +19,4 @@ describe('workspace cleanup candidate row data', () => {
)
).toContainEqual(expect.objectContaining({ label: 'Unpushed commits' }))
})
it('renders every blocker fact instead of only the first', () => {
expect(
getCandidateFactStatuses(makeCandidate({ blockers: ['pinned', 'git-status-error'] }))
).toEqual([
expect.objectContaining({ label: 'Pinned' }),
expect.objectContaining({
label: 'Git status unavailable',
tone: 'destructive'
})
])
})
it('suppresses the git metadata chip when the status pill already names git risk', () => {
expect(
shouldShowGitMetadataChip(
makeCandidate({
blockers: ['unpushed-commits'],
git: {
clean: true,
upstreamAhead: 2,
upstreamBehind: 0,
checkedAt: 1
}
})
)
).toBe(false)
expect(
shouldShowGitMetadataChip(
makeCandidate({
tier: 'review',
blockers: [],
git: {
clean: true,
upstreamAhead: 2,
upstreamBehind: 0,
checkedAt: 1
}
})
)
).toBe(false)
expect(
shouldShowGitMetadataChip(
makeCandidate({
blockers: ['dirty-files'],
git: {
clean: false,
upstreamAhead: 0,
upstreamBehind: 0,
checkedAt: 1
}
})
)
).toBe(false)
})
})
@@ -33,108 +33,6 @@ describe('CandidateRow', () => {
container = null
})
it('does not present cleanup policy tiers as workspace facts', () => {
const candidate = makeCandidate({ tier: 'ready' })
act(() => {
root?.render(
<CandidateRow
identity={getWorkspaceCleanupCandidateIdentity(candidate)}
candidate={candidate}
expanded={false}
last
lastActivityLabel="1d ago"
reviewInfo={{
hasReview: false,
label: null,
provider: null,
state: null,
title: null
}}
selected
onIgnore={vi.fn()}
onRemove={vi.fn()}
onToggleExpanded={vi.fn()}
onToggleSelected={vi.fn()}
onView={vi.fn()}
/>
)
})
expect(container?.textContent).not.toContain('Ready')
expect(container?.textContent).not.toContain('Status unavailable')
})
it('shows factual status, disk size, activity, and git state before expansion', () => {
const candidate = makeCandidate({ tier: 'ready' })
act(() => {
root?.render(
<CandidateRow
identity={getWorkspaceCleanupCandidateIdentity(candidate)}
candidate={candidate}
expanded={false}
last
lastActivityLabel="1d ago"
sizeLabel="4.00 MB"
workspaceStatusLabel="In progress"
reviewInfo={{
hasReview: false,
label: null,
provider: null,
state: null,
title: null
}}
selected
onIgnore={vi.fn()}
onRemove={vi.fn()}
onToggleExpanded={vi.fn()}
onToggleSelected={vi.fn()}
onView={vi.fn()}
/>
)
})
expect(container?.textContent).toContain('In progress')
expect(container?.textContent).toContain('4.00 MB')
expect(container?.textContent).toContain('1d')
expect(container?.textContent).toContain('Clean')
})
it('uses an external-link action to open the workspace', () => {
const candidate = makeCandidate()
act(() => {
root?.render(
<CandidateRow
identity={getWorkspaceCleanupCandidateIdentity(candidate)}
candidate={candidate}
expanded={false}
last
lastActivityLabel="1d ago"
reviewInfo={{
hasReview: false,
label: null,
provider: null,
state: null,
title: null
}}
selected
onIgnore={vi.fn()}
onRemove={vi.fn()}
onToggleExpanded={vi.fn()}
onToggleSelected={vi.fn()}
onView={vi.fn()}
/>
)
})
const openButton = container?.querySelector(
`[aria-label="Open ${getWorkspaceCleanupCandidateAccessibleName(candidate)}"]`
)
expect(openButton?.querySelector('.lucide-external-link')).not.toBeNull()
})
it('hides selection and remove controls while the workspace is already deleting', () => {
const candidate = makeCandidate()
@@ -169,43 +67,6 @@ describe('CandidateRow', () => {
expect(container?.querySelector(`[aria-label="Remove ${accessibleName}"]`)).toBeNull()
})
it.each([
['deleting' as const, 'Deleting…'],
['queued' as const, 'Queued for deletion']
])('replaces the status pill with the %s state', (deletionPhase, label) => {
const candidate = makeCandidate()
act(() => {
root?.render(
<CandidateRow
identity={getWorkspaceCleanupCandidateIdentity(candidate)}
candidate={candidate}
deletionPhase={deletionPhase}
expanded={false}
last
lastActivityLabel="1d ago"
removing
reviewInfo={{
hasReview: false,
label: null,
provider: null,
state: null,
title: null
}}
selected
onIgnore={vi.fn()}
onRemove={vi.fn()}
onToggleExpanded={vi.fn()}
onToggleSelected={vi.fn()}
onView={vi.fn()}
/>
)
})
expect(container?.textContent).toContain(label)
expect(container?.textContent).not.toContain('Ready')
})
it('visibly and accessibly distinguishes colliding local and SSH rows', () => {
const local = makeCandidate({ executionHostId: 'local' })
const remote = makeCandidate({ connectionId: 'builder', executionHostId: 'ssh:builder' })
@@ -247,78 +108,4 @@ describe('CandidateRow', () => {
expect(container?.querySelector(`[aria-label="Select ${remoteName}"]`)).not.toBeNull()
expect(container?.querySelector(`[aria-label="Host: builder"]`)).not.toBeNull()
})
it('colors the review pill by state the way the PR page and item dialog do', () => {
const states = [
{ state: 'merged' as const, label: 'PR #1', hue: 'purple' },
{ state: 'closed' as const, label: 'PR #2', hue: 'rose' },
{ state: 'open' as const, label: 'PR #3', hue: 'emerald' },
{ state: 'draft' as const, label: 'PR #4', hue: 'slate' }
]
for (const { state, label, hue } of states) {
const candidate = makeCandidate()
act(() => {
root?.render(
<CandidateRow
identity={getWorkspaceCleanupCandidateIdentity(candidate)}
candidate={candidate}
expanded={false}
last
lastActivityLabel="1d ago"
reviewInfo={{
hasReview: true,
label,
provider: 'github',
state,
title: 'Some change'
}}
selected={false}
onIgnore={vi.fn()}
onRemove={vi.fn()}
onToggleExpanded={vi.fn()}
onToggleSelected={vi.fn()}
onView={vi.fn()}
/>
)
})
const pill = container?.querySelector(`[aria-label*="${label}"]`)
expect(pill?.className).toContain(`text-${hue}-`)
expect(pill?.className).not.toContain('text-muted-foreground')
}
})
it('leaves a review with an unfetched state neutral', () => {
const candidate = makeCandidate()
act(() => {
root?.render(
<CandidateRow
identity={getWorkspaceCleanupCandidateIdentity(candidate)}
candidate={candidate}
expanded={false}
last
lastActivityLabel="1d ago"
reviewInfo={{
hasReview: true,
label: 'PR #9',
provider: 'github',
state: 'unknown',
title: null
}}
selected={false}
onIgnore={vi.fn()}
onRemove={vi.fn()}
onToggleExpanded={vi.fn()}
onToggleSelected={vi.fn()}
onView={vi.fn()}
/>
)
})
const pill = container?.querySelector('[aria-label*="PR #9"]')
expect(pill?.className).toContain('text-muted-foreground')
})
})
@@ -3,11 +3,7 @@ import { getHostedReviewCacheKey } from '@/store/slices/hosted-review-cache-iden
import {
filterWorkspaceCleanupCandidates,
getWorkspaceCleanupGitLabel,
getWorkspaceCleanupReviewInfo,
getWorkspaceCleanupSearchText,
hasWorkspaceCleanupLocalContext,
sortWorkspaceCleanupCandidates,
type WorkspaceCleanupReviewInfo
getWorkspaceCleanupReviewInfo
} from './workspace-cleanup-presentation'
import {
DEFAULT_FILTERS,
@@ -19,22 +15,6 @@ import {
import type { Repo } from '../../../../shared/repo-types'
describe('workspace cleanup presentation', () => {
it('finds hosted review details from renderer state', () => {
const cacheKey = getHostedReviewCacheKey('/repo', 'alpha', {}, 'repo-1', undefined)
const state = makeState({
hostedReviewCache: {
[cacheKey]: { data: makeReview(), fetchedAt: NOW }
}
})
expect(getWorkspaceCleanupReviewInfo(makeCandidate(), state)).toMatchObject({
hasReview: true,
label: 'PR #42',
state: 'open',
title: 'Review alpha cleanup'
})
})
it('hides only the matching suppressed GitHub review', () => {
const cacheKey = getHostedReviewCacheKey('/repo', 'alpha', {}, 'repo-1', undefined)
const matching = makeState({
@@ -65,48 +45,6 @@ describe('workspace cleanup presentation', () => {
})
})
it('preserves explicit GitHub links and non-GitHub reviews despite stale suppression', () => {
const cacheKey = getHostedReviewCacheKey('/repo', 'alpha', {}, 'repo-1', undefined)
const explicit = makeState({
worktreesByRepo: {
'repo-1': [
{
...makeState().worktreesByRepo['repo-1']![0]!,
linkedPR: 42,
suppressedGitHubPR: 42
}
]
},
hostedReviewCache: {
[cacheKey]: { data: makeReview({ number: 42 }), fetchedAt: NOW }
}
})
const gitLab = makeState({
worktreesByRepo: {
'repo-1': [
{
...makeState().worktreesByRepo['repo-1']![0]!,
linkedPR: null,
suppressedGitHubPR: 42,
linkedGitLabMR: 42
}
]
},
hostedReviewCache: {
[cacheKey]: {
data: makeReview({ provider: 'gitlab', number: 42 }),
fetchedAt: NOW
}
}
})
expect(getWorkspaceCleanupReviewInfo(makeCandidate(), explicit).hasReview).toBe(true)
expect(getWorkspaceCleanupReviewInfo(makeCandidate(), gitLab)).toMatchObject({
hasReview: true,
provider: 'gitlab'
})
})
it('host-qualifies same-id repo, worktree, and review cache joins', () => {
const base = makeState()
const localRepo = { ...base.repos[0]!, executionHostId: 'local' as const }
@@ -177,66 +115,6 @@ describe('workspace cleanup presentation', () => {
expect(getWorkspaceCleanupReviewInfo(makeCandidate(), state).hasReview).toBe(false)
})
it('filters by time, review, git, and context', () => {
const openReview: WorkspaceCleanupReviewInfo = {
hasReview: true,
label: 'PR #7',
state: 'open',
provider: 'github',
title: 'Review row'
}
const rows = [
makeCandidate({ worktreeId: 'repo-1::/repo/clean', displayName: 'clean' }),
makeCandidate({
worktreeId: 'repo-1::/repo/dirty-review',
displayName: 'dirty-review',
git: { clean: false, upstreamAhead: 0, upstreamBehind: 0, checkedAt: NOW },
lastActivityAt: NOW - 100 * 24 * 60 * 60 * 1000,
localContext: {
terminalTabCount: 1,
cleanEditorTabCount: 0,
browserTabCount: 0,
diffCommentCount: 0,
newestDiffCommentAt: null,
retainedDoneAgentCount: 0
}
}),
makeCandidate({
worktreeId: 'repo-1::/repo/unknown',
displayName: 'unknown',
git: { clean: null, upstreamAhead: null, upstreamBehind: null, checkedAt: null },
blockers: ['git-status-error']
})
]
const reviewInfo = new Map<string, WorkspaceCleanupReviewInfo>([
['repo-1::/repo/dirty-review', openReview]
])
expect(
filterWorkspaceCleanupCandidates(
rows,
{
...DEFAULT_FILTERS,
time: '90d',
review: 'open-review',
git: 'dirty',
context: 'has-context'
},
reviewInfo,
NOW
).map((row) => row.displayName)
).toEqual(['dirty-review'])
expect(
filterWorkspaceCleanupCandidates(
rows,
{ ...DEFAULT_FILTERS, git: 'unknown' },
reviewInfo,
NOW
).map((row) => row.displayName)
).toEqual(['unknown'])
})
it('treats unknown-base clean rows as unknown git risk', () => {
const candidate = makeCandidate({
git: { clean: true, upstreamAhead: null, upstreamBehind: null, checkedAt: NOW },
@@ -261,111 +139,4 @@ describe('workspace cleanup presentation', () => {
)
).toEqual([candidate])
})
it('search includes repo, branch, path, review, git, and context labels', () => {
const candidate = makeCandidate({
repoName: 'Search Repo',
branch: 'feature/search',
path: '/repo/search-target',
git: { clean: null, upstreamAhead: null, upstreamBehind: null, checkedAt: null },
localContext: {
terminalTabCount: 0,
cleanEditorTabCount: 0,
browserTabCount: 1,
diffCommentCount: 0,
newestDiffCommentAt: null,
retainedDoneAgentCount: 0
}
})
const reviewInfo: WorkspaceCleanupReviewInfo = {
hasReview: true,
label: 'MR #9',
state: 'closed',
provider: 'gitlab',
title: 'Searchable review'
}
const text = getWorkspaceCleanupSearchText(candidate, reviewInfo)
expect(text).toContain('search repo')
expect(text).toContain('feature/search')
expect(text).toContain('/repo/search-target')
expect(text).toContain('mr #9')
expect(text).toContain('unknown')
expect(text).toContain('has context')
})
it('sorts by activity, name, repo, review, and git', () => {
const rows = [
makeCandidate({
worktreeId: 'repo-1::/repo/bravo',
displayName: 'bravo',
repoName: 'Repo B',
lastActivityAt: NOW - 5,
git: { clean: false, upstreamAhead: 0, upstreamBehind: 0, checkedAt: NOW }
}),
makeCandidate({
worktreeId: 'repo-1::/repo/alpha',
displayName: 'alpha',
repoName: 'Repo A',
lastActivityAt: NOW - 10,
git: { clean: true, upstreamAhead: 0, upstreamBehind: 0, checkedAt: NOW }
}),
makeCandidate({
worktreeId: 'repo-1::/repo/charlie',
displayName: 'charlie',
repoName: 'Repo C',
lastActivityAt: NOW - 1,
git: { clean: true, upstreamAhead: 2, upstreamBehind: 0, checkedAt: NOW },
blockers: ['unpushed-commits']
})
]
const reviewInfo = new Map<string, WorkspaceCleanupReviewInfo>([
[
'repo-1::/repo/charlie',
{
hasReview: true,
label: 'PR #3',
state: 'open',
provider: 'github',
title: null
}
]
])
expect(
sortWorkspaceCleanupCandidates(rows, 'activity', 'asc').map((row) => row.displayName)
).toEqual(['alpha', 'bravo', 'charlie'])
expect(
sortWorkspaceCleanupCandidates(rows, 'name', 'asc').map((row) => row.displayName)
).toEqual(['alpha', 'bravo', 'charlie'])
expect(
sortWorkspaceCleanupCandidates(rows, 'repo', 'asc').map((row) => row.displayName)
).toEqual(['alpha', 'bravo', 'charlie'])
expect(
sortWorkspaceCleanupCandidates(rows, 'review', 'desc', reviewInfo).map(
(row) => row.displayName
)
).toEqual(['charlie', 'alpha', 'bravo'])
expect(
sortWorkspaceCleanupCandidates(rows, 'git', 'desc').map((row) => row.displayName)
).toEqual(['charlie', 'bravo', 'alpha'])
})
it('detects local context', () => {
expect(hasWorkspaceCleanupLocalContext(makeCandidate())).toBe(false)
expect(
hasWorkspaceCleanupLocalContext(
makeCandidate({
localContext: {
terminalTabCount: 0,
cleanEditorTabCount: 1,
browserTabCount: 0,
diffCommentCount: 0,
newestDiffCommentAt: null,
retainedDoneAgentCount: 0
}
})
)
).toBe(true)
})
})
@@ -35,12 +35,6 @@ function render(sort: WorkspaceCleanupSortState, handlers: Record<string, unknow
)
}
function sortOption(label: string): HTMLButtonElement | undefined {
return [...(container?.querySelectorAll<HTMLButtonElement>('[data-sort-option]') ?? [])].find(
(button) => button.textContent?.trim() === label
)
}
describe('WorkspaceCleanupSortHeader', () => {
beforeEach(() => {
container = document.createElement('div')
@@ -57,34 +51,6 @@ describe('WorkspaceCleanupSortHeader', () => {
container = null
})
it('shows the active sort in one menu trigger', () => {
render({ field: 'size', direction: 'desc' })
expect(container?.querySelector('[aria-label="Sort by Size"]')?.textContent).toContain(
'Sort by Size'
)
})
it('routes a menu choice through the sort toggle', () => {
const onToggleSortField = vi.fn()
render({ field: 'last-activity', direction: 'asc' }, { onToggleSortField })
act(() => sortOption('Repository')?.click())
expect(onToggleSortField).toHaveBeenCalledWith('repo')
})
it('select-all reads the query result rather than the rendered page', () => {
const onToggleSelectAll = vi.fn()
render({ field: 'name', direction: 'asc' }, { onToggleSelectAll })
const checkbox = container?.querySelector<HTMLButtonElement>('[role="checkbox"]')
expect(checkbox?.getAttribute('aria-checked')).toBe('false')
act(() => checkbox?.click())
expect(onToggleSelectAll).toHaveBeenCalledWith(true)
})
it('exposes a partial selection as mixed', () => {
render({ field: 'name', direction: 'asc' }, { selectableCount: 3, selectedCount: 1 })
@@ -92,29 +58,4 @@ describe('WorkspaceCleanupSortHeader', () => {
container?.querySelector<HTMLButtonElement>('[role="checkbox"]')?.getAttribute('aria-checked')
).toBe('mixed')
})
it('states how many workspaces select-all would take', () => {
render({ field: 'name', direction: 'asc' }, { selectableCount: 12 })
expect(container?.textContent).toContain('Select all 12 safety-checked workspaces')
})
it('uses singular copy for one safety-checked workspace', () => {
render({ field: 'name', direction: 'asc' }, { selectableCount: 1 })
expect(container?.textContent).toContain('Select 1 safety-checked workspace')
})
it('does not report all-selected when the selected rows are not the selectable ones', () => {
const onToggleSelectAll = vi.fn()
render(
{ field: 'name', direction: 'asc' },
{ selectableCount: 3, selectedCount: 0, onToggleSelectAll }
)
const checkbox = container?.querySelector('[role="checkbox"]')
expect(checkbox?.getAttribute('aria-checked')).toBe('false')
act(() => (checkbox as HTMLElement | null)?.click())
expect(onToggleSelectAll).toHaveBeenCalledWith(true)
})
})