mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
test: remove 69 low-value syntax, script, policy and chat tests (#34497)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
@@ -1,25 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
removeDevRolldownRuntimeEnv,
|
||||
withDevRolldownRuntimeEnv
|
||||
} from './dev-rolldown-runtime-env.mjs'
|
||||
|
||||
describe('dev Rolldown runtime env', () => {
|
||||
it('removes only the keys it added, keeping caller choices', () => {
|
||||
const env = withDevRolldownRuntimeEnv({ RAYON_NUM_THREADS: '2', PATH: '/bin' }, 24)
|
||||
expect(env).toMatchObject({
|
||||
MIMALLOC_PURGE_DELAY: '0',
|
||||
ROLLDOWN_WORKER_THREADS: '4',
|
||||
RAYON_NUM_THREADS: '2'
|
||||
})
|
||||
removeDevRolldownRuntimeEnv(env)
|
||||
expect(env).toEqual({ RAYON_NUM_THREADS: '2', PATH: '/bin' })
|
||||
})
|
||||
|
||||
it('keeps Rolldown thread defaults on small hosts', () => {
|
||||
expect(withDevRolldownRuntimeEnv({}, 4)).toEqual({
|
||||
MIMALLOC_PURGE_DELAY: '0',
|
||||
ORCA_DEV_RUNTIME_ENV_KEYS: 'MIMALLOC_PURGE_DELAY'
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,17 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { withRolldownThreadLimit } from './rolldown-thread-limit.mjs'
|
||||
|
||||
describe('withRolldownThreadLimit', () => {
|
||||
it('caps both pools on large hosts', () => {
|
||||
expect(withRolldownThreadLimit({ A: '1' }, 24)).toEqual({
|
||||
A: '1',
|
||||
ROLLDOWN_WORKER_THREADS: '8',
|
||||
RAYON_NUM_THREADS: '8'
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps Rolldown defaults on small hosts and explicit sizes everywhere', () => {
|
||||
expect(withRolldownThreadLimit({}, 8)).toEqual({})
|
||||
expect(withRolldownThreadLimit({ RAYON_NUM_THREADS: '2' }, 24).RAYON_NUM_THREADS).toBe('2')
|
||||
})
|
||||
})
|
||||
@@ -2,11 +2,7 @@ import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { afterEach, expect, it } from 'vitest'
|
||||
import {
|
||||
createRunTempDirectory,
|
||||
resolveRunTempBase,
|
||||
sweepStaleRunDirectories
|
||||
} from './vitest-run-temp-directory.mjs'
|
||||
import { createRunTempDirectory, sweepStaleRunDirectories } from './vitest-run-temp-directory.mjs'
|
||||
|
||||
const bases = []
|
||||
|
||||
@@ -51,12 +47,3 @@ it('removes the run directory and leaves an unmarked one with the same name alon
|
||||
runDirectory(base, 105, false)
|
||||
expect(createRunTempDirectory({ base, pid: 105 })).toBeNull()
|
||||
})
|
||||
|
||||
it('keeps POSIX run roots no longer than the default macOS temp root', () => {
|
||||
const macTemp = '/var/folders/zz/zyxvpxvq6csfxvn_n0000000000000gn/T'
|
||||
expect(resolveRunTempBase(macTemp, 'ov255r', 'darwin')).toBe('/tmp')
|
||||
expect(resolveRunTempBase('/tmp', 'ov255r', 'linux')).toBe('/tmp')
|
||||
expect(resolveRunTempBase('C:\\Users\\a\\AppData\\Local\\Temp', 'ov255r', 'win32')).toBe(
|
||||
'C:\\Users\\a\\AppData\\Local\\Temp'
|
||||
)
|
||||
})
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { parse } from 'yaml'
|
||||
|
||||
// The rehearsal must sign and reseal the same template payload release-cut ships, or Windows reseal regressions only surface in a real release.
|
||||
function readWorkflow(name) {
|
||||
return parse(readFileSync(new URL(`../../.github/workflows/${name}`, import.meta.url), 'utf8'))
|
||||
}
|
||||
|
||||
const RESEAL_STEP = 'Reseal the orcad template over its signed binaries'
|
||||
const release = readWorkflow('release-cut.yml').jobs.build
|
||||
const { jobs } = readWorkflow('windows-signing-rehearsal.yml')
|
||||
const rehearse = jobs.rehearse
|
||||
const stepIndex = (name) => rehearse.steps.findIndex((step) => step.name === name)
|
||||
|
||||
describe('Windows signing rehearsal ships the orcad template', () => {
|
||||
it('packages the qualified template built from the dispatched commit', () => {
|
||||
expect(jobs['orcad-template'].uses).toBe('./.github/workflows/qualified-orcad-template.yml')
|
||||
expect(jobs['orcad-template'].with).toEqual({ ref: '${{ github.sha }}', fast_runners: false })
|
||||
expect(rehearse.needs).toBe('orcad-template')
|
||||
expect(rehearse.env.ORCA_REQUIRE_ORCAD_TEMPLATE).toBe('1')
|
||||
const download = stepIndex('Download the orcad deployment template')
|
||||
expect(download).toBeGreaterThan(stepIndex('Build app'))
|
||||
expect(download).toBeLessThan(stepIndex('Package Windows app and export the NSIS uninstaller'))
|
||||
expect(rehearse.steps[download].with).toEqual({
|
||||
name: 'orcad-template',
|
||||
path: 'out/orcad-template'
|
||||
})
|
||||
})
|
||||
|
||||
it('reseals the signed template exactly like release-cut, before the installer rebuild', () => {
|
||||
const releaseReseal = release.steps.find((step) => step.name === RESEAL_STEP)
|
||||
const reseal = stepIndex(RESEAL_STEP)
|
||||
expect(rehearse.steps[reseal].run).toBe(releaseReseal.run)
|
||||
expect(reseal).toBeGreaterThan(stepIndex('Restore signed inner binaries into unpacked app'))
|
||||
expect(reseal).toBeLessThan(stepIndex('Build NSIS installer from signed unpacked app'))
|
||||
})
|
||||
|
||||
it('keeps non-Windows template addons out of SignPath staging', () => {
|
||||
const filter = "'^resources[\\\\/]orcad-template[\\\\/]targets[\\\\/](?!win32-)'"
|
||||
const staging = (job) => job.steps.find((step) => step.id === 'stage-inner').run
|
||||
expect(staging(release)).toContain(filter)
|
||||
expect(staging(rehearse)).toContain(filter)
|
||||
})
|
||||
})
|
||||
@@ -35,36 +35,6 @@ describe('createTextMateTokensProvider', () => {
|
||||
expect(after.tokens.length).toBeGreaterThan(1)
|
||||
})
|
||||
|
||||
it('tokenizes Nim with the vendored TextMate grammar', async () => {
|
||||
const provider = await createTextMateTokensProvider({ loadGrammar: loadNimTextMateGrammar })
|
||||
|
||||
const procLine = provider.tokenize('proc greet(name: string) =', provider.getInitialState())
|
||||
const procScopes = procLine.tokens.map((token) => token.scopes)
|
||||
expect(procScopes).toContain('keyword.other')
|
||||
expect(procScopes).toContain('entity.name.function.nim')
|
||||
expect(procScopes).toContain('storage.type.concrete.nim')
|
||||
|
||||
const commentLine = provider.tokenize('# hello', provider.getInitialState())
|
||||
expect(commentLine.tokens.map((token) => token.scopes)).toContain(
|
||||
'comment.line.number-sign.nim'
|
||||
)
|
||||
})
|
||||
|
||||
it('tokenizes Typst markup, code, and math through the lazy grammar loader', async () => {
|
||||
const provider = await createTextMateTokensProvider({ loadGrammar: loadTypstTextMateGrammar })
|
||||
const scopesOf = (line: string) =>
|
||||
provider.tokenize(line, provider.getInitialState()).tokens.map((token) => token.scopes)
|
||||
|
||||
expect(scopesOf('#let width = 12pt')).toEqual(
|
||||
expect.arrayContaining(['keyword.other.typst', 'constant.numeric.length.typst'])
|
||||
)
|
||||
expect(scopesOf('#set text(font: "Inter")')).toEqual(
|
||||
expect.arrayContaining(['entity.name.function.typst', 'string.quoted.double.typst'])
|
||||
)
|
||||
expect(scopesOf('// note')).toContain('comment.line.double-slash.typst')
|
||||
expect(scopesOf('$ sum_(k=0)^n k $')).toContain('string.other.math.typst')
|
||||
})
|
||||
|
||||
it.each([
|
||||
['```rust', 'let x = 1', '```', '#let y = 2'],
|
||||
['````', '```', '````', '#let y = 2']
|
||||
|
||||
@@ -95,31 +95,6 @@ describe('highlightSyntax', () => {
|
||||
expect(find(result, 'total')).toMatchObject({ dark: '#9CDCFE' })
|
||||
})
|
||||
|
||||
it('leaves a line too long to color plain, along with every line after it', async () => {
|
||||
const typescript = await highlighterFor('typescript')
|
||||
const code = ['const a = 1', `const long = "${'x'.repeat(2000)}"`, 'const b = 2'].join('\n')
|
||||
|
||||
const result = typescript(code)
|
||||
|
||||
expect(result.outcome).toBe('degraded')
|
||||
expect(find(result, 'const')).toMatchObject({ dark: '#569CD6' })
|
||||
expect(result.lines[1]).toEqual([expect.objectContaining({ dark: undefined })])
|
||||
expect(result.lines[2]).toEqual([
|
||||
{
|
||||
content: 'const b = 2',
|
||||
offset: code.lastIndexOf('const b'),
|
||||
light: undefined,
|
||||
dark: undefined,
|
||||
lightFontStyle: 0,
|
||||
darkFontStyle: 0
|
||||
}
|
||||
])
|
||||
expect(typescript('const c = 3', result.state)).toMatchObject({
|
||||
outcome: 'degraded',
|
||||
lines: [[expect.objectContaining({ content: 'const c = 3', dark: undefined })]]
|
||||
})
|
||||
})
|
||||
|
||||
it('leaves a line that runs out of time plain instead of guessing the rest', async () => {
|
||||
const typescript = await highlighterFor('typescript')
|
||||
let now = 0
|
||||
@@ -143,16 +118,6 @@ describe('highlightSyntax', () => {
|
||||
})
|
||||
|
||||
describe('loadSyntaxLanguage', () => {
|
||||
it('reports a language without a grammar as unsupported', async () => {
|
||||
await expect(loadSyntaxLanguage('not-a-real-language')).resolves.toBe('unsupported')
|
||||
expect(loadedSyntaxHighlighter('not-a-real-language')).toBeNull()
|
||||
})
|
||||
|
||||
it('accepts aliases and any letter case', async () => {
|
||||
await expect(loadSyntaxLanguage('PS1')).resolves.toBe('ready')
|
||||
expect(loadedSyntaxHighlighter('ps1')).not.toBeNull()
|
||||
})
|
||||
|
||||
it('retries a failed load after a pause', async () => {
|
||||
const { loadSyntaxLanguage, loadedSyntaxHighlighter } = await freshHighlighterModule()
|
||||
let now = 0
|
||||
|
||||
@@ -31,9 +31,4 @@ describe('readableMermaidFlowchartSvg', () => {
|
||||
)
|
||||
expect(labelFill(readableMermaidFlowchartSvg(stadium, '#e0e0e0', '#111111'))).toBe('#e0e0e0')
|
||||
})
|
||||
|
||||
it('leaves a label that already reads on its fill', () => {
|
||||
const svg = readableMermaidFlowchartSvg(flowchart('#1e3a8a', '#e0e0e0'), '#e0e0e0', '#111111')
|
||||
expect(labelFill(svg)).toBe('#e0e0e0')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { canToggleNativeChat } from './native-chat-availability'
|
||||
import { isNativeChatTranscriptLocalReadable } from './native-chat-transcript-readability'
|
||||
|
||||
describe('canToggleNativeChat', () => {
|
||||
// omp exposes no hook transcript path; its session file requires readable agent storage.
|
||||
it('rejects Model-A SSH omp but accepts it local and runtime-owned', () => {
|
||||
const forConnection = (connectionId: string | null): boolean =>
|
||||
canToggleNativeChat({
|
||||
contentType: 'terminal',
|
||||
launchAgent: 'omp',
|
||||
nativeChatTranscriptIsLocalReadable: isNativeChatTranscriptLocalReadable(connectionId)
|
||||
})
|
||||
expect(forConnection('ssh-target-1')).toBe(false)
|
||||
expect(forConnection(null)).toBe(true)
|
||||
expect(forConnection('runtime-ssh-env-1')).toBe(true)
|
||||
})
|
||||
|
||||
it('lets an existing Model-A SSH Grok chat toggle back to terminal', () => {
|
||||
expect(
|
||||
canToggleNativeChat({
|
||||
contentType: 'terminal',
|
||||
launchAgent: 'grok',
|
||||
nativeChatTranscriptIsLocalReadable: false,
|
||||
isChatViewMode: true
|
||||
})
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it('rejects a stale supported title when live detection found an unsupported agent', () => {
|
||||
expect(
|
||||
canToggleNativeChat({
|
||||
contentType: 'terminal',
|
||||
launchAgent: null,
|
||||
detectedAgent: 'gemini',
|
||||
resolvedAgent: 'codex'
|
||||
})
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
it('rejects stale launch metadata when live detection found an unsupported agent', () => {
|
||||
expect(
|
||||
canToggleNativeChat({
|
||||
contentType: 'terminal',
|
||||
launchAgent: 'codex',
|
||||
detectedAgent: 'gemini'
|
||||
})
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
it('rejects a stale supported title when launch metadata names an unsupported agent', () => {
|
||||
expect(
|
||||
canToggleNativeChat({
|
||||
contentType: 'terminal',
|
||||
launchAgent: 'gemini',
|
||||
resolvedAgent: 'claude'
|
||||
})
|
||||
).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -1,9 +1,6 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { nativeChatMessageCopyText } from './native-chat-message-copy-text'
|
||||
import {
|
||||
NATIVE_CHAT_STOPPED_BEFORE_START_PRESENTATION,
|
||||
NATIVE_CHAT_STOPPED_BEFORE_START_TEXT
|
||||
} from './native-chat-stopped-before-start'
|
||||
|
||||
import { subagentGroupFallbackText } from './native-chat-subagent-summary'
|
||||
import type { NativeChatSubagentEntry } from './native-chat-types'
|
||||
|
||||
@@ -29,15 +26,4 @@ describe('nativeChatMessageCopyText', () => {
|
||||
)
|
||||
expect(nativeChatMessageCopyText({ role: 'user', blocks: [{ type: 'text', text }] })).toBe(text)
|
||||
})
|
||||
|
||||
it('copies the stopped-before-start notice in the caller’s words', () => {
|
||||
const blocks = [
|
||||
{
|
||||
type: 'text' as const,
|
||||
text: NATIVE_CHAT_STOPPED_BEFORE_START_TEXT,
|
||||
presentation: NATIVE_CHAT_STOPPED_BEFORE_START_PRESENTATION
|
||||
}
|
||||
]
|
||||
expect(nativeChatMessageCopyText({ role: 'assistant', blocks }, 'Arrêté')).toBe('Arrêté')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
nativeChatModelPillLabel,
|
||||
sayNativeChatSessionOptionEnglish as say
|
||||
} from './native-chat-session-option-labels'
|
||||
import type { SessionOptionDescriptor } from './native-chat/input/native-chat-session-options'
|
||||
|
||||
function modelDescriptor(
|
||||
valueSource: SessionOptionDescriptor['valueSource'],
|
||||
currentValue?: string
|
||||
): SessionOptionDescriptor {
|
||||
return {
|
||||
id: 'model',
|
||||
label: 'Model',
|
||||
valueSource,
|
||||
transport: 'catalog',
|
||||
settable: true,
|
||||
kind: {
|
||||
type: 'select',
|
||||
...(currentValue ? { currentValue } : {}),
|
||||
choices: [{ value: 'grok-4.5', label: 'Grok 4.5' }]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe('nativeChatModelPillLabel', () => {
|
||||
it('withholds a value it has no evidence for', () => {
|
||||
expect(nativeChatModelPillLabel(modelDescriptor('unknown', 'grok-4.5'), say)).toBe('Model')
|
||||
expect(nativeChatModelPillLabel(modelDescriptor('default'), say)).toBe('Model')
|
||||
})
|
||||
|
||||
it('falls back to the raw id when the list no longer offers it', () => {
|
||||
// Keep naming a tracked model after discovery drops it; the running selection is still known.
|
||||
expect(nativeChatModelPillLabel(modelDescriptor('reported', 'grok-build'), say)).toBe(
|
||||
'grok-build'
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -36,34 +36,6 @@ function discovery(owner: string | null, rootPath = '/Users/test/.agents/skills'
|
||||
}
|
||||
|
||||
describe('isNativeChatSkillForAgent', () => {
|
||||
it('shows Codex-native and generic agent skills for Codex chat', () => {
|
||||
expect(isNativeChatSkillForAgent('codex', skill({ providers: ['codex'] }))).toBe(true)
|
||||
expect(isNativeChatSkillForAgent('codex', skill({ providers: ['agent-skills'] }))).toBe(true)
|
||||
})
|
||||
|
||||
it('keeps Claude skills out of the Codex skill picker', () => {
|
||||
expect(isNativeChatSkillForAgent('codex', skill({ providers: ['claude'] }))).toBe(false)
|
||||
})
|
||||
|
||||
it('does not enable skill autocomplete for other agents yet', () => {
|
||||
expect(isNativeChatSkillForAgent('claude', skill({ providers: ['agent-skills'] }))).toBe(false)
|
||||
})
|
||||
|
||||
it('uses explicit source ownership and keeps shared roots visible', () => {
|
||||
const shared = discovery(null)
|
||||
expect(isNativeChatSkillForAgent('codex', skill({}), shared)).toBe(true)
|
||||
expect(isNativeChatSkillForAgent('claude', skill({}), shared)).toBe(true)
|
||||
expect(isNativeChatSkillForAgent('grok', skill({}), shared)).toBe(true)
|
||||
})
|
||||
|
||||
it('aliases OpenClaude to Claude roots without exposing them to other agents', () => {
|
||||
const claude = discovery('claude')
|
||||
expect(isNativeChatSkillForAgent('claude', skill({}), claude)).toBe(true)
|
||||
expect(isNativeChatSkillForAgent('openclaude', skill({}), claude)).toBe(true)
|
||||
expect(isNativeChatSkillForAgent('codex', skill({}), claude)).toBe(false)
|
||||
expect(isNativeChatSkillForAgent('grok', skill({}), claude)).toBe(false)
|
||||
})
|
||||
|
||||
it('shows OpenCode the Claude skill folders it reads, and not Claude plugins', () => {
|
||||
const claude = discovery('claude')
|
||||
expect(isNativeChatSkillForAgent('opencode', skill({}), claude)).toBe(true)
|
||||
|
||||
@@ -7,15 +7,6 @@ import {
|
||||
} from './structured-agent-session-option-pick-book'
|
||||
|
||||
describe('structured option pick commits', () => {
|
||||
it('commits all returned values for the latest write', () => {
|
||||
const book = createStructuredOptionPickBook()
|
||||
const pick = beginStructuredOptionPick(book, 'model', 'opus', false)
|
||||
expect(structuredOptionPickCommit(book, pick, { model: 'opus', effort: 'high' })).toEqual({
|
||||
model: 'opus',
|
||||
effort: 'high'
|
||||
})
|
||||
})
|
||||
|
||||
it('commits only its own option when another option was picked since', () => {
|
||||
const book = createStructuredOptionPickBook()
|
||||
const model = beginStructuredOptionPick(book, 'model', 'opus', false)
|
||||
|
||||
@@ -125,33 +125,6 @@ describe('structured agent session options', () => {
|
||||
expect(model.kind.type === 'select' ? model.kind.currentValue : null).toBe('persisted-unknown')
|
||||
})
|
||||
|
||||
it('projects live options as directly settable descriptors', () => {
|
||||
const state = applyStructuredAgentSessionOptions(
|
||||
createStructuredAgentSessionOptionState('codex'),
|
||||
CODEX_SESSION_OPTION_CATALOG,
|
||||
{
|
||||
models: [
|
||||
{
|
||||
id: 'account-model',
|
||||
label: 'Account Model',
|
||||
isDefault: true,
|
||||
defaultEffort: 'medium',
|
||||
efforts: [
|
||||
{ value: 'medium', label: 'Medium' },
|
||||
{ value: 'high', label: 'High' }
|
||||
]
|
||||
}
|
||||
],
|
||||
current: { model: 'account-model', effort: 'medium' }
|
||||
}
|
||||
)
|
||||
|
||||
const snapshot = structuredAgentSessionOptionSnapshot(state)
|
||||
expect(snapshot.map((descriptor) => descriptor.id)).toEqual(['model', 'effort'])
|
||||
expect(snapshot.every((descriptor) => descriptor.settable)).toBe(true)
|
||||
expect(snapshot.every((descriptor) => descriptor.action === undefined)).toBe(true)
|
||||
})
|
||||
|
||||
it('projects Fast mode only from positive session and model capability', () => {
|
||||
const supported = applyStructuredAgentSessionOptions(
|
||||
createStructuredAgentSessionOptionState('codex'),
|
||||
|
||||
@@ -3,8 +3,6 @@ import type { AgentSessionOptionsResult } from './agent-session-wire'
|
||||
import { sortNativeChatSessionOptions } from './native-chat/input/native-chat-session-option-snapshot'
|
||||
import {
|
||||
applyStructuredAgentSessionOptions,
|
||||
commitStructuredAgentSessionOption,
|
||||
commitStructuredAgentSessionOptionValues,
|
||||
createStructuredAgentSessionOptionState,
|
||||
structuredAgentSessionOptionPicks,
|
||||
structuredAgentSessionOptionSnapshot
|
||||
@@ -75,45 +73,6 @@ describe('the permission mode picker', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('shows nothing for a host that states no mode, or an agent with none', () => {
|
||||
expect(permission(live('codex', {}))).toBeUndefined()
|
||||
expect(permission(live('pi', { permissionMode: 'ask' }))).toBeUndefined()
|
||||
})
|
||||
|
||||
it("offers Codex's three modes with its own descriptions", () => {
|
||||
const descriptor = permission(live('codex', { permissionMode: 'ask' }))
|
||||
expect(descriptor?.kind.type === 'select' && descriptor.kind.choices).toEqual([
|
||||
{
|
||||
value: 'ask',
|
||||
label: 'Ask for approval',
|
||||
description: "Asks before edits or commands it isn't already allowed to make"
|
||||
},
|
||||
{ value: 'auto', label: 'Approve for me', description: 'Reviews approval requests for you' },
|
||||
{
|
||||
value: 'full-access',
|
||||
label: 'Full access',
|
||||
description: 'Never asks; can edit any file and use the network'
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('offers all Claude modes regardless of catalog support, and shows the host mode as stated', () => {
|
||||
const unsupported = permission(live('claude', { permissionMode: 'ask' }, false))
|
||||
expect(
|
||||
unsupported?.kind.type === 'select' && unsupported.kind.choices.map((c) => c.value)
|
||||
).toEqual(['ask', 'accept-edits', 'auto', 'full-access'])
|
||||
expect(permission(live('claude', { permissionMode: 'auto' }, false))).toMatchObject({
|
||||
kind: { currentValue: 'auto' }
|
||||
})
|
||||
const state = createStructuredAgentSessionOptionState('claude')
|
||||
const descriptor = permission(
|
||||
structuredAgentSessionOptionView(state, { permissionMode: 'ask' }, {})
|
||||
)
|
||||
expect(
|
||||
descriptor?.kind.type === 'select' && descriptor.kind.choices.map((c) => c.value)
|
||||
).toContain('auto')
|
||||
})
|
||||
|
||||
it("shows a held pick over the host's value, and the host's over the seed", () => {
|
||||
const state = live('codex', { permissionMode: 'auto' })
|
||||
const seed = { permissionMode: 'ask' }
|
||||
@@ -125,19 +84,6 @@ describe('the permission mode picker', () => {
|
||||
).toMatchObject({ kind: { currentValue: 'full-access' } })
|
||||
})
|
||||
|
||||
it('takes a settled pick, and the record a write answers with', () => {
|
||||
const state = live('codex', { permissionMode: 'ask' })
|
||||
expect(commitStructuredAgentSessionOption(state, 'permissionMode', 'auto').permissionMode).toBe(
|
||||
'auto'
|
||||
)
|
||||
expect(
|
||||
commitStructuredAgentSessionOptionValues(state, {
|
||||
model: 'model-a',
|
||||
permissionMode: 'full-access'
|
||||
}).permissionMode
|
||||
).toBe('full-access')
|
||||
})
|
||||
|
||||
it('stays out of the per-model option pill and out of the saved selection', () => {
|
||||
const state = live('codex', { permissionMode: 'ask' })
|
||||
expect(
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { structuredConversationCommandHold } from './structured-conversation-command-hold'
|
||||
|
||||
describe('a conversation command held on this side', () => {
|
||||
const idle = {
|
||||
waitsInLine: false,
|
||||
agentWorking: false,
|
||||
promptPending: false,
|
||||
backgroundTasksRunning: false,
|
||||
sendPending: false
|
||||
}
|
||||
|
||||
it('is held here only by a message the host does not have yet, or background work', () => {
|
||||
const inLine = { ...idle, waitsInLine: true }
|
||||
expect(
|
||||
structuredConversationCommandHold({
|
||||
...inLine,
|
||||
agentWorking: true,
|
||||
promptPending: true
|
||||
})
|
||||
).toBeNull()
|
||||
expect(structuredConversationCommandHold({ ...inLine, sendPending: true })).toBe('ahead')
|
||||
expect(structuredConversationCommandHold({ ...inLine, backgroundTasksRunning: true })).toBe(
|
||||
'background'
|
||||
)
|
||||
})
|
||||
|
||||
it('against a host that cannot hold it, and for /clear, keeps every check in true words', () => {
|
||||
expect(structuredConversationCommandHold(idle)).toBeNull()
|
||||
expect(structuredConversationCommandHold({ ...idle, agentWorking: true })).toBe('working')
|
||||
expect(structuredConversationCommandHold({ ...idle, sendPending: true })).toBe('sending')
|
||||
expect(
|
||||
structuredConversationCommandHold({ ...idle, agentWorking: true, sendPending: true })
|
||||
).toBe('working')
|
||||
expect(structuredConversationCommandHold({ ...idle, promptPending: true })).toBe('prompt')
|
||||
})
|
||||
})
|
||||
@@ -1,196 +0,0 @@
|
||||
import { writeFileSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
resolveCanonicalPaneAgentIdentity,
|
||||
type CanonicalPaneAgentIdentity
|
||||
} from './pane-agent-identity-adapter'
|
||||
import { resolveTabAgentFromSignals } from './tab-agent-from-signals'
|
||||
import type { TuiAgent } from './tui-agent'
|
||||
|
||||
const AGENTS: readonly TuiAgent[] = ['claude', 'codex']
|
||||
const SLOT_COUNT = 7
|
||||
const COMBINATION_COUNT = 3 ** SLOT_COUNT * 4 * 2
|
||||
const TITLES: readonly string[] = ['', 'zsh', 'Task - claude', 'Task - codex']
|
||||
|
||||
type Breakdown = Record<
|
||||
'launch' | 'completed-hook' | 'sleeping-session' | 'process' | 'sibling' | 'title',
|
||||
number
|
||||
>
|
||||
|
||||
function slotValues(mask: number): (TuiAgent | null)[] {
|
||||
let remaining = mask
|
||||
return Array.from({ length: SLOT_COUNT }, () => {
|
||||
const value = remaining % 3
|
||||
remaining = Math.floor(remaining / 3)
|
||||
return value === 0 ? null : AGENTS[value - 1]
|
||||
})
|
||||
}
|
||||
|
||||
function canonicalResult(
|
||||
values: readonly (TuiAgent | null)[],
|
||||
title: string,
|
||||
withProof: boolean
|
||||
): CanonicalPaneAgentIdentity {
|
||||
const [hook, siblingHook, completed, siblingCompleted, process, sleeping, launch] = values
|
||||
return resolveCanonicalPaneAgentIdentity({
|
||||
hookAgent: hook,
|
||||
hookIsLive: hook !== null,
|
||||
completedHookAgent: completed,
|
||||
launchAgent: launch,
|
||||
foregroundAgent: process,
|
||||
processProof:
|
||||
withProof && process
|
||||
? {
|
||||
agent: process,
|
||||
processIncarnation: 'fixture-process',
|
||||
authorityId: 'fixture-authority',
|
||||
capturedAgeMs: 10,
|
||||
validForMs: 1_000
|
||||
}
|
||||
: undefined,
|
||||
sleepingSessionAgent: sleeping,
|
||||
siblingAgents: [siblingHook, siblingCompleted].filter(
|
||||
(agent): agent is TuiAgent => agent !== null
|
||||
),
|
||||
allowSibling: true,
|
||||
title
|
||||
})
|
||||
}
|
||||
|
||||
function realResult(values: readonly (TuiAgent | null)[], title: string, remote: boolean) {
|
||||
const [hook, siblingHook, completed, siblingCompleted, process, sleeping, launch] = values
|
||||
// Hold the runtime memory bit true; the approved 17,496 steady-state shapes cover seven other slots.
|
||||
return resolveTabAgentFromSignals({
|
||||
hasObservedAgentSignal: true,
|
||||
isRemote: remote,
|
||||
title,
|
||||
hookAgent: hook,
|
||||
siblingHookAgent: siblingHook,
|
||||
focusedCompletedHookAgent: completed,
|
||||
siblingCompletedHookAgent: siblingCompleted,
|
||||
processAgent: process,
|
||||
processShellForeground: false,
|
||||
sleepingSessionAgent: sleeping,
|
||||
launchAgent: launch ?? undefined
|
||||
})
|
||||
}
|
||||
|
||||
function runDecisionTable(withProof: boolean) {
|
||||
let disagreements = 0
|
||||
let flipped = 0
|
||||
const breakdown: Breakdown = {
|
||||
launch: 0,
|
||||
'completed-hook': 0,
|
||||
'sleeping-session': 0,
|
||||
process: 0,
|
||||
sibling: 0,
|
||||
title: 0
|
||||
}
|
||||
for (let mask = 0; mask < 3 ** SLOT_COUNT; mask += 1) {
|
||||
const values = slotValues(mask)
|
||||
for (const title of TITLES) {
|
||||
for (const remote of [false, true]) {
|
||||
const real = realResult(values, title, remote)
|
||||
const canonical = canonicalResult(values, title, withProof)
|
||||
if (real !== canonical.agent) {
|
||||
disagreements += 1
|
||||
if (canonical.source !== null) {
|
||||
breakdown[canonical.source] += 1
|
||||
}
|
||||
}
|
||||
if (!withProof) {
|
||||
const proven = canonicalResult(values, title, true)
|
||||
if (
|
||||
canonical.agent !== proven.agent &&
|
||||
proven.source === 'process' &&
|
||||
(canonical.source === 'launch' ||
|
||||
canonical.source === 'completed-hook' ||
|
||||
canonical.source === 'sleeping-session')
|
||||
) {
|
||||
flipped += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return { disagreements, flipped, breakdown }
|
||||
}
|
||||
|
||||
describe('renderer ladder decision table', () => {
|
||||
it('replays the real shipping ladder and records all rung disagreements', () => {
|
||||
const proofFree = runDecisionTable(false)
|
||||
const freshProof = runDecisionTable(true)
|
||||
const result = {
|
||||
combinations: COMBINATION_COUNT,
|
||||
proofOmitted: proofFree,
|
||||
freshProof,
|
||||
flippedByAddingProof: proofFree.flipped
|
||||
}
|
||||
writeFileSync(
|
||||
join(tmpdir(), 'orca-pane-agent-identity-decision-table-real.json'),
|
||||
`${JSON.stringify(result, null, 2)}\n`
|
||||
)
|
||||
// Re-derived against resolveTabAgentFromSignals (not a hand-written model).
|
||||
expect(proofFree).toEqual({
|
||||
disagreements: 2_622,
|
||||
flipped: 1_872,
|
||||
breakdown: {
|
||||
launch: 1_884,
|
||||
'completed-hook': 478,
|
||||
'sleeping-session': 144,
|
||||
process: 0,
|
||||
sibling: 54,
|
||||
title: 6
|
||||
}
|
||||
})
|
||||
expect(freshProof).toEqual({
|
||||
disagreements: 658,
|
||||
flipped: 0,
|
||||
breakdown: {
|
||||
launch: 588,
|
||||
'completed-hook': 46,
|
||||
'sleeping-session': 0,
|
||||
process: 0,
|
||||
sibling: 6,
|
||||
title: 2
|
||||
}
|
||||
})
|
||||
expect(proofFree.flipped).toBe(1_872)
|
||||
})
|
||||
|
||||
it('requires both freshness fields before process evidence can change the no-proof result', () => {
|
||||
const values = [null, null, null, null, 'codex', null, 'claude'] as const
|
||||
expect(canonicalResult(values, '', false)).toMatchObject({
|
||||
agent: 'claude',
|
||||
source: 'launch'
|
||||
})
|
||||
expect(
|
||||
resolveCanonicalPaneAgentIdentity({
|
||||
foregroundAgent: 'codex',
|
||||
processProof: {
|
||||
agent: 'codex',
|
||||
processIncarnation: 'fixture-process',
|
||||
authorityId: 'fixture-authority',
|
||||
capturedAgeMs: undefined as unknown as number,
|
||||
validForMs: 1_000
|
||||
},
|
||||
launchAgent: 'claude'
|
||||
})
|
||||
).toMatchObject({ agent: 'claude', source: 'launch' })
|
||||
expect(
|
||||
resolveCanonicalPaneAgentIdentity({
|
||||
foregroundAgent: 'codex',
|
||||
processProof: {
|
||||
agent: 'codex',
|
||||
processIncarnation: 'fixture-process',
|
||||
authorityId: 'fixture-authority',
|
||||
capturedAgeMs: 10,
|
||||
validForMs: undefined as unknown as number
|
||||
},
|
||||
launchAgent: 'claude'
|
||||
})
|
||||
).toMatchObject({ agent: 'claude', source: 'launch' })
|
||||
})
|
||||
})
|
||||
@@ -2,7 +2,6 @@ import { describe, expect, it } from 'vitest'
|
||||
import { getAgentLabel as getSharedAgentLabel } from './agent-title-identity'
|
||||
import { isOpenCodeNativeTitle } from './opencode-terminal-title'
|
||||
import {
|
||||
isClaudeAgent,
|
||||
isClaudeIdentityFrameTitle,
|
||||
isGrokRotatingWorkingTitle,
|
||||
resolveExplicitTerminalTitleAgentType,
|
||||
@@ -37,20 +36,6 @@ describe('isGrokRotatingWorkingTitle', () => {
|
||||
})
|
||||
|
||||
describe('resolveExplicitTerminalTitleAgentType', () => {
|
||||
it('maps explicit product-name titles to their TuiAgent id', () => {
|
||||
expect(resolveExplicitTerminalTitleAgentType('✳ Claude Code')).toBe('claude')
|
||||
expect(resolveExplicitTerminalTitleAgentType('⠋ Codex')).toBe('codex')
|
||||
expect(resolveExplicitTerminalTitleAgentType('✦ Gemini CLI')).toBe('gemini')
|
||||
expect(resolveExplicitTerminalTitleAgentType('MiMo Code')).toBe('mimo-code')
|
||||
expect(resolveExplicitTerminalTitleAgentType('⠋ OpenClaude')).toBe('openclaude')
|
||||
expect(resolveExplicitTerminalTitleAgentType('OMP')).toBe('omp')
|
||||
})
|
||||
|
||||
it('reads Pi/OMP identity only from the leading brand', () => {
|
||||
expect(resolveExplicitTerminalTitleAgentType('⠂ OMP: fix title detection')).toBeNull()
|
||||
expect(resolveExplicitTerminalTitleAgentType('Fix Pi > OMP routing - grok')).toBe('grok')
|
||||
})
|
||||
|
||||
it('treats Claude generic status prefixes as activity-only, not identity', () => {
|
||||
expect(resolveExplicitTerminalTitleAgentType('✳ investigating startup')).toBeNull()
|
||||
expect(resolveExplicitTerminalTitleAgentType('⠸ investigating startup')).toBeNull()
|
||||
@@ -77,22 +62,6 @@ describe('resolveExplicitTerminalTitleAgentType', () => {
|
||||
expect(resolveExplicitTerminalTitleAgentType('⠋ Fix foo | OC | bar')).toBeNull()
|
||||
})
|
||||
|
||||
it('keeps other agents classified correctly alongside OpenCode native titles', () => {
|
||||
expect(resolveExplicitTerminalTitleAgentType('✳ Claude Code')).toBe('claude')
|
||||
expect(resolveExplicitTerminalTitleAgentType('⠋ Codex')).toBe('codex')
|
||||
expect(resolveExplicitTerminalTitleAgentType('✦ Gemini CLI')).toBe('gemini')
|
||||
expect(resolveExplicitTerminalTitleAgentType('Cursor Agent')).toBe('cursor')
|
||||
expect(resolveExplicitTerminalTitleAgentType('Pi ready')).toBe('pi')
|
||||
expect(resolveExplicitTerminalTitleAgentType('OpenCode ready')).toBe('opencode')
|
||||
expect(resolveTerminalTitleAgentType('OC | ⠋ implementing the feature')).toBe('opencode')
|
||||
expect(isClaudeAgent('OC | ⠋ implementing the feature')).toBe(false)
|
||||
expect(isClaudeAgent('OC | Understand about the plugin')).toBe(false)
|
||||
})
|
||||
|
||||
it('still resolves Claude when the title explicitly names Claude', () => {
|
||||
expect(resolveExplicitTerminalTitleAgentType('. Claude Code compare Opencode')).toBe('claude')
|
||||
})
|
||||
|
||||
// Only explicit Claude identity may replace the owner; task-text mentions do not prove identity (#8940).
|
||||
it('separates Claude identity frames from an incidental claude token', () => {
|
||||
for (const title of [
|
||||
@@ -123,11 +92,6 @@ describe('resolveExplicitTerminalTitleAgentType', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('returns null for plain shell and unknown titles', () => {
|
||||
expect(resolveExplicitTerminalTitleAgentType('Terminal 1')).toBeNull()
|
||||
expect(resolveExplicitTerminalTitleAgentType('zsh')).toBeNull()
|
||||
})
|
||||
|
||||
// cursor is ordinary task vocabulary; only Cursor’s identity frames can claim the pane.
|
||||
it('resolves Cursor by its identity titles, never a bare cursor token', () => {
|
||||
expect(resolveExplicitTerminalTitleAgentType('Cursor Agent')).toBe('cursor')
|
||||
@@ -155,13 +119,3 @@ describe('resolveTerminalTitleAgentType', () => {
|
||||
expect(resolveTerminalTitleAgentType('⠋ Codex: fix cursor offsets')).toBe('codex')
|
||||
})
|
||||
})
|
||||
|
||||
// Pin this module’s parallel isClaudeAgent guard directly so it cannot drift from agent-title-identity.ts.
|
||||
describe('isClaudeAgent', () => {
|
||||
it('excludes real Cursor identity titles, keeps cursor-mentioning Claude braille titles', () => {
|
||||
expect(isClaudeAgent('⠋ Cursor Agent')).toBe(false)
|
||||
expect(isClaudeAgent('Cursor ready')).toBe(false)
|
||||
expect(isClaudeAgent('⠋ preserve cursor visibility across replays')).toBe(true)
|
||||
expect(isClaudeAgent('⠋ OpenClaude')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
isRecentTabSwitcherCommitRelease,
|
||||
isWindowShortcutModifierChord,
|
||||
matchesRecentTabSwitcherChord,
|
||||
resolveWindowShortcutAction,
|
||||
type WindowShortcutAction,
|
||||
@@ -59,59 +58,6 @@ describe('resolveWindowShortcutAction', () => {
|
||||
}
|
||||
)
|
||||
|
||||
it('resolves the explicit window shortcut allowlist on macOS', () => {
|
||||
expect(
|
||||
resolveWindowShortcutAction(
|
||||
{ code: 'KeyJ', key: 'j', meta: true, control: false, alt: false, shift: false },
|
||||
'darwin'
|
||||
)
|
||||
).toEqual({ type: 'toggleWorktreePalette' })
|
||||
|
||||
expect(
|
||||
resolveWindowShortcutAction(
|
||||
{ code: 'KeyP', key: 'p', meta: true, control: false, alt: false, shift: false },
|
||||
'darwin'
|
||||
)
|
||||
).toEqual({ type: 'openQuickOpen' })
|
||||
|
||||
expect(
|
||||
resolveWindowShortcutAction(
|
||||
{ code: 'Digit3', key: '3', meta: true, control: false, alt: false, shift: false },
|
||||
'darwin'
|
||||
)
|
||||
).toEqual({ type: 'jumpToWorktreeIndex', index: 2 })
|
||||
|
||||
expect(
|
||||
resolveWindowShortcutAction(
|
||||
{ code: 'Digit3', key: '3', meta: false, control: true, alt: false, shift: false },
|
||||
'darwin'
|
||||
)
|
||||
).toEqual({ type: 'jumpToTabIndex', index: 2 })
|
||||
})
|
||||
|
||||
it('uses Alt+number for tab jumps on Windows/Linux without stealing workspace jumps', () => {
|
||||
expect(
|
||||
resolveWindowShortcutAction(
|
||||
{ code: 'Digit4', key: '4', meta: false, control: true, alt: false, shift: false },
|
||||
'linux'
|
||||
)
|
||||
).toEqual({ type: 'jumpToWorktreeIndex', index: 3 })
|
||||
|
||||
expect(
|
||||
resolveWindowShortcutAction(
|
||||
{ code: 'Digit4', key: '4', meta: false, control: false, alt: true, shift: false },
|
||||
'linux'
|
||||
)
|
||||
).toEqual({ type: 'jumpToTabIndex', index: 3 })
|
||||
|
||||
expect(
|
||||
resolveWindowShortcutAction(
|
||||
{ code: 'Digit4', key: '4', meta: false, control: false, alt: true, shift: true },
|
||||
'win32'
|
||||
)
|
||||
).toBeNull()
|
||||
})
|
||||
|
||||
it('resolves customized quick-command menu shortcuts with terminal policy gating', () => {
|
||||
const input: WindowShortcutInput = {
|
||||
code: 'KeyQ',
|
||||
@@ -273,82 +219,6 @@ describe('resolveWindowShortcutAction', () => {
|
||||
).toEqual({ type: 'jumpToTabIndex', index: 2 })
|
||||
})
|
||||
|
||||
it('does not resolve the removed PDF export shortcut globally', () => {
|
||||
expect(
|
||||
resolveWindowShortcutAction(
|
||||
{ code: 'KeyE', key: 'e', meta: true, control: false, alt: false, shift: true },
|
||||
'darwin'
|
||||
)
|
||||
).toBeNull()
|
||||
})
|
||||
|
||||
it('routes menu-backed actions through the same window shortcut policy', () => {
|
||||
expect(
|
||||
resolveWindowShortcutAction(
|
||||
{ code: 'KeyR', key: 'r', meta: false, control: true, alt: false, shift: true },
|
||||
'linux'
|
||||
)
|
||||
).toEqual({ type: 'forceReload' })
|
||||
expect(
|
||||
resolveWindowShortcutAction(
|
||||
{ code: 'KeyR', key: 'r', meta: false, control: true, alt: false, shift: true },
|
||||
'linux',
|
||||
undefined,
|
||||
{ context: 'terminal', terminalShortcutPolicy: 'terminal-first' }
|
||||
)
|
||||
).toBeNull()
|
||||
})
|
||||
|
||||
it('requires shift for the non-mac worktree palette shortcut', () => {
|
||||
expect(
|
||||
resolveWindowShortcutAction(
|
||||
{ code: 'KeyJ', key: 'j', meta: false, control: true, alt: false, shift: false },
|
||||
'win32'
|
||||
)
|
||||
).toBeNull()
|
||||
|
||||
expect(
|
||||
resolveWindowShortcutAction(
|
||||
{ code: 'KeyJ', key: 'j', meta: false, control: true, alt: false, shift: true },
|
||||
'win32'
|
||||
)
|
||||
).toEqual({ type: 'toggleWorktreePalette' })
|
||||
})
|
||||
|
||||
it('resolves dictation using the layout-aware key value', () => {
|
||||
expect(
|
||||
resolveWindowShortcutAction(
|
||||
{ code: 'KeyD', key: 'e', meta: true, control: false, alt: false, shift: false },
|
||||
'darwin'
|
||||
)
|
||||
).toEqual({ type: 'dictationKeyDown' })
|
||||
|
||||
expect(
|
||||
resolveWindowShortcutAction(
|
||||
{ code: 'KeyE', key: 'd', meta: true, control: false, alt: false, shift: false },
|
||||
'darwin'
|
||||
)
|
||||
).toBeNull()
|
||||
})
|
||||
|
||||
it('applies custom keybinding overrides to dictation and main-process shortcuts', () => {
|
||||
expect(
|
||||
resolveWindowShortcutAction(
|
||||
{ code: 'KeyE', key: 'e', meta: false, control: true, alt: false, shift: false },
|
||||
'linux',
|
||||
{ 'voice.dictation': [] }
|
||||
)
|
||||
).toBeNull()
|
||||
|
||||
expect(
|
||||
resolveWindowShortcutAction(
|
||||
{ code: 'KeyY', key: 'y', meta: false, control: true, alt: false, shift: true },
|
||||
'linux',
|
||||
{ 'voice.dictation': ['Mod+Shift+Y'] }
|
||||
)
|
||||
).toEqual({ type: 'dictationKeyDown' })
|
||||
})
|
||||
|
||||
it('applies custom keybinding overrides to main-process shortcuts', () => {
|
||||
const overrides: KeybindingOverrides = {
|
||||
'worktree.quickOpen': ['Mod+Shift+O'],
|
||||
@@ -413,15 +283,6 @@ describe('resolveWindowShortcutAction', () => {
|
||||
).toEqual({ type: 'deleteCurrentWorkspace' })
|
||||
})
|
||||
|
||||
it('resolves the MRU tab quick-toggle chord', () => {
|
||||
expect(
|
||||
resolveWindowShortcutAction(
|
||||
{ code: 'Tab', key: 'Tab', meta: false, control: true, alt: false, shift: false },
|
||||
'linux'
|
||||
)
|
||||
).toEqual({ type: 'switchRecentTab' })
|
||||
})
|
||||
|
||||
it('gates the held Ctrl+Tab switcher on the configurable binding', () => {
|
||||
const input = { code: 'Tab', key: 'Tab', meta: false, control: true, alt: false, shift: true }
|
||||
const domInput = {
|
||||
@@ -540,80 +401,6 @@ describe('resolveWindowShortcutAction', () => {
|
||||
).toBeNull()
|
||||
})
|
||||
|
||||
it('resolves the worktree-history chord despite carrying Alt', () => {
|
||||
expect(
|
||||
resolveWindowShortcutAction(
|
||||
{
|
||||
code: 'ArrowLeft',
|
||||
key: 'ArrowLeft',
|
||||
meta: true,
|
||||
control: false,
|
||||
alt: true,
|
||||
shift: false
|
||||
},
|
||||
'darwin'
|
||||
)
|
||||
).toEqual({ type: 'worktreeHistoryNavigate', direction: 'back' })
|
||||
|
||||
expect(
|
||||
resolveWindowShortcutAction(
|
||||
{
|
||||
code: 'ArrowRight',
|
||||
key: 'ArrowRight',
|
||||
meta: true,
|
||||
control: false,
|
||||
alt: true,
|
||||
shift: false
|
||||
},
|
||||
'darwin'
|
||||
)
|
||||
).toEqual({ type: 'worktreeHistoryNavigate', direction: 'forward' })
|
||||
|
||||
expect(
|
||||
resolveWindowShortcutAction(
|
||||
{
|
||||
code: 'ArrowLeft',
|
||||
key: 'ArrowLeft',
|
||||
meta: false,
|
||||
control: true,
|
||||
alt: true,
|
||||
shift: false
|
||||
},
|
||||
'linux'
|
||||
)
|
||||
).toEqual({ type: 'worktreeHistoryNavigate', direction: 'back' })
|
||||
})
|
||||
|
||||
it('resolves the floating terminal chord despite carrying Alt', () => {
|
||||
expect(
|
||||
resolveWindowShortcutAction(
|
||||
{
|
||||
code: 'KeyA',
|
||||
key: 'a',
|
||||
meta: true,
|
||||
control: false,
|
||||
alt: true,
|
||||
shift: false
|
||||
},
|
||||
'darwin'
|
||||
)
|
||||
).toEqual({ type: 'toggleFloatingTerminal' })
|
||||
|
||||
expect(
|
||||
resolveWindowShortcutAction(
|
||||
{
|
||||
code: 'KeyA',
|
||||
key: 'a',
|
||||
meta: false,
|
||||
control: true,
|
||||
alt: true,
|
||||
shift: false
|
||||
},
|
||||
'linux'
|
||||
)
|
||||
).toEqual({ type: 'toggleFloatingTerminal' })
|
||||
})
|
||||
|
||||
it('resolves the floating terminal chord when macOS Option composes the letter', () => {
|
||||
expect(
|
||||
resolveWindowShortcutAction(
|
||||
@@ -630,172 +417,6 @@ describe('resolveWindowShortcutAction', () => {
|
||||
).toEqual({ type: 'toggleFloatingTerminal' })
|
||||
})
|
||||
|
||||
it('rejects floating terminal chord variants with Shift or opposite primary modifier', () => {
|
||||
expect(
|
||||
resolveWindowShortcutAction(
|
||||
{
|
||||
code: 'KeyA',
|
||||
key: 'a',
|
||||
meta: true,
|
||||
control: false,
|
||||
alt: true,
|
||||
shift: true
|
||||
},
|
||||
'darwin'
|
||||
)
|
||||
).toBeNull()
|
||||
|
||||
expect(
|
||||
resolveWindowShortcutAction(
|
||||
{
|
||||
code: 'KeyA',
|
||||
key: 'a',
|
||||
meta: true,
|
||||
control: true,
|
||||
alt: true,
|
||||
shift: false
|
||||
},
|
||||
'linux'
|
||||
)
|
||||
).toBeNull()
|
||||
})
|
||||
|
||||
it('rejects the history chord when Shift is also held', () => {
|
||||
expect(
|
||||
resolveWindowShortcutAction(
|
||||
{
|
||||
code: 'ArrowLeft',
|
||||
key: 'ArrowLeft',
|
||||
meta: true,
|
||||
control: false,
|
||||
alt: true,
|
||||
shift: true
|
||||
},
|
||||
'darwin'
|
||||
)
|
||||
).toBeNull()
|
||||
})
|
||||
|
||||
it('leaves Alt+Arrow without a primary modifier untouched (word-nav territory)', () => {
|
||||
expect(
|
||||
resolveWindowShortcutAction(
|
||||
{
|
||||
code: 'ArrowLeft',
|
||||
key: 'ArrowLeft',
|
||||
meta: false,
|
||||
control: false,
|
||||
alt: true,
|
||||
shift: false
|
||||
},
|
||||
'darwin'
|
||||
)
|
||||
).toBeNull()
|
||||
})
|
||||
|
||||
it('ignores Cmd/Ctrl+Alt combined with ArrowUp or ArrowDown', () => {
|
||||
// Leave primary+Alt+Up/Down to renderer/PTTY shell handling; history claims only Left/Right.
|
||||
expect(
|
||||
resolveWindowShortcutAction(
|
||||
{
|
||||
code: 'ArrowUp',
|
||||
key: 'ArrowUp',
|
||||
meta: true,
|
||||
control: false,
|
||||
alt: true,
|
||||
shift: false
|
||||
},
|
||||
'darwin'
|
||||
)
|
||||
).toBeNull()
|
||||
|
||||
expect(
|
||||
resolveWindowShortcutAction(
|
||||
{
|
||||
code: 'ArrowDown',
|
||||
key: 'ArrowDown',
|
||||
meta: false,
|
||||
control: true,
|
||||
alt: true,
|
||||
shift: false
|
||||
},
|
||||
'linux'
|
||||
)
|
||||
).toBeNull()
|
||||
})
|
||||
|
||||
it('rejects the history chord when the opposite primary modifier is also held', () => {
|
||||
// Do not intercept Cmd+Ctrl+Alt on macOS or Ctrl+Meta+Alt on Linux: they collide with OS workspace switching.
|
||||
expect(
|
||||
resolveWindowShortcutAction(
|
||||
{
|
||||
code: 'ArrowLeft',
|
||||
key: 'ArrowLeft',
|
||||
meta: true,
|
||||
control: true,
|
||||
alt: true,
|
||||
shift: false
|
||||
},
|
||||
'darwin'
|
||||
)
|
||||
).toBeNull()
|
||||
|
||||
expect(
|
||||
resolveWindowShortcutAction(
|
||||
{
|
||||
code: 'ArrowRight',
|
||||
key: 'ArrowRight',
|
||||
meta: true,
|
||||
control: true,
|
||||
alt: true,
|
||||
shift: false
|
||||
},
|
||||
'linux'
|
||||
)
|
||||
).toBeNull()
|
||||
})
|
||||
|
||||
it('still returns null for other Cmd/Ctrl+Alt combos (not an allowlist escape)', () => {
|
||||
// History’s special branch must not claim unrelated primary+Alt chords without an explicit new action.
|
||||
expect(
|
||||
resolveWindowShortcutAction(
|
||||
{
|
||||
code: 'KeyY',
|
||||
key: 'y',
|
||||
meta: true,
|
||||
control: false,
|
||||
alt: true,
|
||||
shift: false
|
||||
},
|
||||
'darwin'
|
||||
)
|
||||
).toBeNull()
|
||||
})
|
||||
|
||||
it('routes Cmd/Ctrl+Shift+N to the unified new-workspace composer', () => {
|
||||
// Preserve the old Create-from chord; the unified composer now switches sources in its name field.
|
||||
expect(
|
||||
resolveWindowShortcutAction(
|
||||
{ code: 'KeyN', key: 'n', meta: true, control: false, alt: false, shift: true },
|
||||
'darwin'
|
||||
)
|
||||
).toEqual({ type: 'openNewWorkspace' })
|
||||
|
||||
expect(
|
||||
resolveWindowShortcutAction(
|
||||
{ code: 'KeyN', key: 'n', meta: false, control: true, alt: false, shift: true },
|
||||
'linux'
|
||||
)
|
||||
).toEqual({ type: 'openNewWorkspace' })
|
||||
|
||||
// Keep Alt chords unclaimed for future actions.
|
||||
expect(
|
||||
resolveWindowShortcutAction(
|
||||
{ code: 'KeyN', key: 'n', meta: true, control: false, alt: true, shift: true },
|
||||
'darwin'
|
||||
)
|
||||
).toBeNull()
|
||||
})
|
||||
|
||||
it('resolves letter shortcuts by layout-aware key, with code as fallback', () => {
|
||||
// Prefer layout-aware letter keys over physical QWERTY codes; only empty/nonletter IME/dead-key values need code fallback.
|
||||
|
||||
@@ -848,18 +469,6 @@ describe('resolveWindowShortcutAction', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('exposes the shared platform modifier gate used by browser guests', () => {
|
||||
expect(
|
||||
isWindowShortcutModifierChord({ meta: true, control: false, alt: false }, 'darwin')
|
||||
).toBe(true)
|
||||
expect(isWindowShortcutModifierChord({ meta: false, control: true, alt: false }, 'linux')).toBe(
|
||||
true
|
||||
)
|
||||
expect(isWindowShortcutModifierChord({ meta: false, control: true, alt: true }, 'linux')).toBe(
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
it('resolves an allowlisted action from a synthetic double-tap input', () => {
|
||||
const overrides: KeybindingOverrides = { 'worktree.quickOpen': ['DoubleTap+Shift'] }
|
||||
expect(
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
WORKSPACE_BOARD_COLUMN_WIDTH_DEFAULT,
|
||||
WORKSPACE_BOARD_COLUMN_WIDTH_MAX,
|
||||
WORKSPACE_BOARD_COLUMN_WIDTH_MIN,
|
||||
clampWorkspaceBoardColumnWidth,
|
||||
cloneDefaultWorkspaceStatuses,
|
||||
normalizePersistedWorkspaceStatuses,
|
||||
normalizeWorkspaceStatuses
|
||||
@@ -21,17 +17,6 @@ describe('workspace status visuals', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('keeps the default workflow order', () => {
|
||||
expect(cloneDefaultWorkspaceStatuses().map((status) => status.id)).toEqual([
|
||||
'todo',
|
||||
'in-progress',
|
||||
'in-review',
|
||||
'completed'
|
||||
])
|
||||
expect(cloneDefaultWorkspaceStatuses()[0]).toMatchObject({ id: 'todo', label: 'Todo' })
|
||||
expect(cloneDefaultWorkspaceStatuses().at(-1)).toMatchObject({ id: 'completed', label: 'Done' })
|
||||
})
|
||||
|
||||
it('migrates legacy default statuses to the default workflow order', () => {
|
||||
const statuses = normalizePersistedWorkspaceStatuses(
|
||||
[
|
||||
@@ -56,31 +41,6 @@ describe('workspace status visuals', () => {
|
||||
expect(statuses).toEqual(cloneDefaultWorkspaceStatuses())
|
||||
})
|
||||
|
||||
it('migrates the old default status visuals without reordering the board', () => {
|
||||
const statuses = normalizePersistedWorkspaceStatuses(
|
||||
[
|
||||
{ id: 'todo', label: 'Todo', color: 'neutral', icon: 'circle' },
|
||||
{ id: 'in-progress', label: 'In progress', color: 'blue', icon: 'circle-dot' },
|
||||
{ id: 'in-review', label: 'In review', color: 'violet', icon: 'git-pull-request' },
|
||||
{ id: 'completed', label: 'Completed', color: 'emerald', icon: 'circle-check' }
|
||||
],
|
||||
{ migrateLegacyDefaultStatusVisuals: true }
|
||||
)
|
||||
|
||||
expect(statuses.map((status) => status.id)).toEqual([
|
||||
'todo',
|
||||
'in-progress',
|
||||
'in-review',
|
||||
'completed'
|
||||
])
|
||||
expect(statuses.map((status) => status.color)).toEqual([
|
||||
'neutral',
|
||||
'conductor-progress',
|
||||
'conductor-review',
|
||||
'conductor-done'
|
||||
])
|
||||
})
|
||||
|
||||
it('preserves explicit status order while migrating default visuals', () => {
|
||||
const statuses = normalizePersistedWorkspaceStatuses(
|
||||
[
|
||||
@@ -104,27 +64,6 @@ describe('workspace status visuals', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('preserves default-label reordered statuses unless a default migration is requested', () => {
|
||||
const statuses = normalizePersistedWorkspaceStatuses([
|
||||
{ id: 'completed', label: 'Completed', color: 'conductor-done', icon: 'conductor-done' },
|
||||
{ id: 'in-review', label: 'In review', color: 'conductor-review', icon: 'conductor-review' },
|
||||
{
|
||||
id: 'in-progress',
|
||||
label: 'In progress',
|
||||
color: 'conductor-progress',
|
||||
icon: 'conductor-progress'
|
||||
},
|
||||
{ id: 'todo', label: 'Todo', color: 'neutral', icon: 'circle' }
|
||||
])
|
||||
|
||||
expect(statuses.map((status) => status.id)).toEqual([
|
||||
'completed',
|
||||
'in-review',
|
||||
'in-progress',
|
||||
'todo'
|
||||
])
|
||||
})
|
||||
|
||||
it('migrates exact reordered default statuses to the new Done label when requested', () => {
|
||||
const statuses = normalizePersistedWorkspaceStatuses(
|
||||
[
|
||||
@@ -173,30 +112,6 @@ describe('workspace status visuals', () => {
|
||||
expect(statuses).toEqual(cloneDefaultWorkspaceStatuses())
|
||||
})
|
||||
|
||||
it('repairs the exact reordered default status payload with the Done label', () => {
|
||||
const statuses = normalizePersistedWorkspaceStatuses(
|
||||
[
|
||||
{ id: 'completed', label: 'Done', color: 'conductor-done', icon: 'conductor-done' },
|
||||
{
|
||||
id: 'in-review',
|
||||
label: 'In review',
|
||||
color: 'conductor-review',
|
||||
icon: 'conductor-review'
|
||||
},
|
||||
{
|
||||
id: 'in-progress',
|
||||
label: 'In progress',
|
||||
color: 'conductor-progress',
|
||||
icon: 'conductor-progress'
|
||||
},
|
||||
{ id: 'todo', label: 'Todo', color: 'neutral', icon: 'circle' }
|
||||
],
|
||||
{ repairReorderedDefaultStatuses: true }
|
||||
)
|
||||
|
||||
expect(statuses).toEqual(cloneDefaultWorkspaceStatuses())
|
||||
})
|
||||
|
||||
it('does not repair reordered default-label statuses with a different raw shape', () => {
|
||||
const statuses = normalizePersistedWorkspaceStatuses(
|
||||
[
|
||||
@@ -216,47 +131,6 @@ describe('workspace status visuals', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('leaves custom persisted status layouts in their saved order', () => {
|
||||
const statuses = normalizePersistedWorkspaceStatuses([
|
||||
{ id: 'completed', label: 'Shipped', color: 'conductor-done', icon: 'conductor-done' },
|
||||
{ id: 'todo', label: 'Todo', color: 'neutral', icon: 'circle' }
|
||||
])
|
||||
|
||||
expect(statuses.map((status) => status.id)).toEqual(['completed', 'todo'])
|
||||
})
|
||||
|
||||
it('uses Conductor-style visuals for the default status icons', () => {
|
||||
const statuses = cloneDefaultWorkspaceStatuses()
|
||||
const inProgress = statuses.find((status) => status.id === 'in-progress')
|
||||
const inReview = statuses.find((status) => status.id === 'in-review')
|
||||
const completed = statuses.find((status) => status.id === 'completed')
|
||||
|
||||
expect(inProgress).toMatchObject({
|
||||
color: 'conductor-progress',
|
||||
icon: 'conductor-progress'
|
||||
})
|
||||
expect(inReview).toMatchObject({
|
||||
color: 'conductor-review',
|
||||
icon: 'conductor-review'
|
||||
})
|
||||
expect(completed).toMatchObject({
|
||||
color: 'conductor-done',
|
||||
icon: 'conductor-done'
|
||||
})
|
||||
})
|
||||
|
||||
it('migrates the old in-progress blue dot default only when requested', () => {
|
||||
const statuses = normalizePersistedWorkspaceStatuses(
|
||||
[{ id: 'in-progress', label: 'In progress', color: 'blue', icon: 'circle-dot' }],
|
||||
{ migrateLegacyDefaultStatusVisuals: true }
|
||||
)
|
||||
|
||||
expect(statuses[0]).toMatchObject({
|
||||
color: 'conductor-progress',
|
||||
icon: 'conductor-progress'
|
||||
})
|
||||
})
|
||||
|
||||
it('preserves valid legacy visuals for default-label statuses at runtime', () => {
|
||||
const statuses = normalizeWorkspaceStatuses([
|
||||
{ id: 'in-progress', label: 'In progress', color: 'blue', icon: 'circle-dot' }
|
||||
@@ -275,11 +149,4 @@ describe('workspace status visuals', () => {
|
||||
|
||||
expect(status.label).toBe('a'.repeat(31))
|
||||
})
|
||||
|
||||
it('clamps workspace board column widths to resizable bounds', () => {
|
||||
expect(clampWorkspaceBoardColumnWidth(undefined)).toBe(WORKSPACE_BOARD_COLUMN_WIDTH_DEFAULT)
|
||||
expect(clampWorkspaceBoardColumnWidth(100)).toBe(WORKSPACE_BOARD_COLUMN_WIDTH_MIN)
|
||||
expect(clampWorkspaceBoardColumnWidth(321.6)).toBe(322)
|
||||
expect(clampWorkspaceBoardColumnWidth(900)).toBe(WORKSPACE_BOARD_COLUMN_WIDTH_MAX)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user