mirror of
https://github.com/stablyai/orca.git
synced 2026-11-14 01:47:24 +00:00
test: remove 88 low-value test declarations (#37280)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
@@ -138,10 +138,6 @@ describe.runIf(process.platform === 'darwin')(
|
||||
return failures
|
||||
}
|
||||
|
||||
it('finds the holder on its terminal', () => {
|
||||
expect(holderTty).toMatch(/^ttys\d+$/)
|
||||
})
|
||||
|
||||
it.each([
|
||||
['full snapshot', PS_ARGS, UTF8],
|
||||
['full snapshot (tdev)', ['-axo', PS_ARGS[1].replace('tty=', 'tdev=')], UTF8],
|
||||
|
||||
@@ -2,15 +2,11 @@ import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
applyMentionSuggestion,
|
||||
applyPickerSuggestion,
|
||||
applySlashSuggestion,
|
||||
buildNativeChatPickerItems,
|
||||
classifyNativeChatSend,
|
||||
deriveComposerAutocomplete,
|
||||
editReplacesTriggerToken,
|
||||
filterSlashCommands,
|
||||
isSkillPickerTriggered,
|
||||
isSlashCommandDraft,
|
||||
slashCommandDispatchText,
|
||||
type SlashCommandSuggestion
|
||||
} from './native-chat-composer-autocomplete'
|
||||
import { sessionSlashCommandSuggestions } from './native-chat/input/native-chat-slash-commands'
|
||||
@@ -252,29 +248,7 @@ describe('deriveComposerAutocomplete — one grammar for every agent', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('filterSlashCommands', () => {
|
||||
it('is case-insensitive prefix match', () => {
|
||||
expect(filterSlashCommands(COMMANDS, 'C').map((c) => c.name)).toEqual(['clear', 'compact'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('isSlashCommandDraft', () => {
|
||||
it('treats leading slash drafts as TUI commands, not chat prompts', () => {
|
||||
expect(isSlashCommandDraft('/clear')).toBe(true)
|
||||
expect(isSlashCommandDraft(' /compact')).toBe(true)
|
||||
expect(isSlashCommandDraft('please run /clear')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('apply suggestions', () => {
|
||||
it('applySlashSuggestion replaces the token with a trailing space', () => {
|
||||
expect(applySlashSuggestion({ name: 'clear' })).toBe('/clear ')
|
||||
})
|
||||
|
||||
it('slashCommandDispatchText returns the command without completion whitespace', () => {
|
||||
expect(slashCommandDispatchText({ name: 'clear' })).toBe('/clear')
|
||||
})
|
||||
|
||||
it('applyMentionSuggestion replaces the active @token at the caret', () => {
|
||||
const result = applyMentionSuggestion('open @sr more', 8, 'src/app.ts')
|
||||
expect(result.draft).toBe('open @src/app.ts more')
|
||||
@@ -433,22 +407,6 @@ describe('native skill and command picker', () => {
|
||||
expect(result.caret).toBe('validate it with /electron '.length)
|
||||
})
|
||||
|
||||
it('groups Claude commands and skills under slash', () => {
|
||||
const result = deriveComposerAutocomplete(
|
||||
'/',
|
||||
1,
|
||||
COMMANDS,
|
||||
[skill({ name: 'browser' })],
|
||||
getNativeChatAgentProfile('claude')
|
||||
)
|
||||
expect(result.mode).toBe('slash')
|
||||
if (result.mode === 'slash') {
|
||||
expect(result.grouped).toBe(true)
|
||||
expect(result.items.map((item) => item.kind)).toContain('command')
|
||||
expect(result.items.map((item) => item.kind)).toContain('skill')
|
||||
}
|
||||
})
|
||||
|
||||
it('lets a session report replace the disk scan and enrich the names it knows', () => {
|
||||
const items = buildNativeChatPickerItems(
|
||||
[],
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { NativeChatMessage } from './native-chat/native-chat-types'
|
||||
import {
|
||||
isPendingMessageId,
|
||||
pendingSendsAsMessages,
|
||||
prunePendingSends,
|
||||
type NativeChatPendingSend
|
||||
@@ -527,19 +526,6 @@ describe('pendingSendsAsMessages', () => {
|
||||
expect(pendingSendsAsMessages(pending, unreadableHistory)).toEqual([])
|
||||
})
|
||||
|
||||
it('maps pending sends to prefixed scrape-source user messages sorted by sentAt', () => {
|
||||
const messages = pendingSendsAsMessages([{ id: 'p1', text: 'queued text', sentAt: 42 }])
|
||||
expect(messages).toEqual([
|
||||
{
|
||||
id: 'pending:p1',
|
||||
role: 'user',
|
||||
blocks: [{ type: 'text', text: 'queued text' }],
|
||||
timestamp: 42,
|
||||
source: 'scrape'
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('includes image refs for pending attachment sends', () => {
|
||||
const messages = pendingSendsAsMessages([
|
||||
{ id: 'p1', text: 'what do you see?', imagePaths: ['/tmp/shot.png'], sentAt: 42 }
|
||||
@@ -638,10 +624,3 @@ describe('pendingSendsAsMessages', () => {
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('isPendingMessageId', () => {
|
||||
it('recognizes the pending id prefix', () => {
|
||||
expect(isPendingMessageId('pending:p1')).toBe(true)
|
||||
expect(isPendingMessageId('transcript-123')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -28,29 +28,6 @@ describe('native chat PTY session options', () => {
|
||||
expect(surface.getSnapshot()).toEqual([])
|
||||
})
|
||||
|
||||
it('renders the version-neutral seed picker before any host catalog arrives', () => {
|
||||
const surface = createNativeChatPtySessionOptions({
|
||||
agent: 'claude',
|
||||
scopeKey: 'pty-1',
|
||||
mode: 'live',
|
||||
modelLabel: 'Model',
|
||||
dispatchCommand: vi.fn()
|
||||
})!
|
||||
|
||||
expect(surface.getSnapshot()[0]).toMatchObject({
|
||||
id: 'model',
|
||||
valueSource: 'unknown',
|
||||
kind: {
|
||||
choices: [
|
||||
expect.objectContaining({ value: 'fable', label: 'Fable' }),
|
||||
expect.objectContaining({ value: 'opus', label: 'Opus' }),
|
||||
expect.objectContaining({ value: 'sonnet', label: 'Sonnet' }),
|
||||
expect.objectContaining({ value: 'haiku', label: 'Haiku' })
|
||||
]
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('uses model and effort reported by the live Claude terminal', () => {
|
||||
const surface = createNativeChatPtySessionOptions({
|
||||
agent: 'claude',
|
||||
|
||||
@@ -23,51 +23,6 @@ function claudeRecord(model?: string): NativeChatSessionOptionRecord {
|
||||
}
|
||||
|
||||
describe('buildNativeChatSessionOptionCommand', () => {
|
||||
it('builds the catalog midSession command for model and options', () => {
|
||||
expect(
|
||||
buildNativeChatSessionOptionCommand({
|
||||
optionId: 'model',
|
||||
value: 'opus',
|
||||
apply: CLAUDE_SESSION_OPTION_CATALOG.modelApply,
|
||||
modelId: null,
|
||||
catalog: CLAUDE_SESSION_OPTION_CATALOG,
|
||||
models: CLAUDE_SESSION_OPTION_CATALOG.models,
|
||||
record: claudeRecord()
|
||||
})
|
||||
).toBe('/model opus')
|
||||
const effortApply = CLAUDE_SESSION_OPTION_CATALOG.models
|
||||
.find((model) => model.id === 'sonnet')!
|
||||
.options.find((option) => option.id === 'effort')!.apply
|
||||
expect(
|
||||
buildNativeChatSessionOptionCommand({
|
||||
optionId: 'effort',
|
||||
value: 'high',
|
||||
apply: effortApply,
|
||||
modelId: 'sonnet',
|
||||
catalog: CLAUDE_SESSION_OPTION_CATALOG,
|
||||
models: CLAUDE_SESSION_OPTION_CATALOG.models,
|
||||
record: claudeRecord('sonnet')
|
||||
})
|
||||
).toBe('/effort high')
|
||||
})
|
||||
|
||||
it('returns the bare toggle command for flip-only options', () => {
|
||||
const fastModeApply = CLAUDE_SESSION_OPTION_CATALOG.models
|
||||
.find((model) => model.id === 'opus')!
|
||||
.options.find((option) => option.id === 'fastMode')!.apply
|
||||
expect(
|
||||
buildNativeChatSessionOptionCommand({
|
||||
optionId: 'fastMode',
|
||||
value: true,
|
||||
apply: fastModeApply,
|
||||
modelId: 'opus',
|
||||
catalog: CLAUDE_SESSION_OPTION_CATALOG,
|
||||
models: CLAUDE_SESSION_OPTION_CATALOG.models,
|
||||
record: claudeRecord('opus')
|
||||
})
|
||||
).toBe('/fast')
|
||||
})
|
||||
|
||||
it('does not turn a Codex model pick into pasted slash-command prose', () => {
|
||||
expect(
|
||||
buildNativeChatSessionOptionCommand({
|
||||
|
||||
@@ -105,19 +105,6 @@ describe('buildNativeChatSessionOptionSnapshot', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('is empty when the model list is empty', () => {
|
||||
expect(
|
||||
buildNativeChatSessionOptionSnapshot({
|
||||
catalog: CLAUDE_SESSION_OPTION_CATALOG,
|
||||
models: [],
|
||||
record: claudeRecord(),
|
||||
mode: 'live',
|
||||
modelLabel: 'Model',
|
||||
liveTransport: 'catalog'
|
||||
})
|
||||
).toEqual([])
|
||||
})
|
||||
|
||||
describe('sortNativeChatSessionOptions', () => {
|
||||
it('drops the model row and orders effort before model config before modes', () => {
|
||||
const descriptor = (id: string, category?: string): SessionOptionDescriptor =>
|
||||
@@ -177,34 +164,6 @@ describe('buildNativeChatSessionOptionSnapshot', () => {
|
||||
expect(snapshot.length).toBeGreaterThan(1)
|
||||
})
|
||||
|
||||
it('formats an unlisted model label while preserving its exact id in the details', () => {
|
||||
const record = claudeRecord()
|
||||
record.model = { value: 'experimental-model', source: 'reported' }
|
||||
const reconciled = withTrackedNativeChatModel(
|
||||
CLAUDE_SESSION_OPTION_CATALOG,
|
||||
CLAUDE_SESSION_OPTION_CATALOG.models,
|
||||
record
|
||||
)
|
||||
expect(reconciled.at(-1)).toEqual({
|
||||
id: 'experimental-model',
|
||||
label: 'Experimental Model',
|
||||
description: 'experimental-model',
|
||||
options: []
|
||||
})
|
||||
})
|
||||
|
||||
it('leaves the list alone when the tracked model is already listed', () => {
|
||||
const record = claudeRecord()
|
||||
record.model = { value: 'sonnet', source: 'reported' }
|
||||
expect(
|
||||
withTrackedNativeChatModel(
|
||||
CLAUDE_SESSION_OPTION_CATALOG,
|
||||
CLAUDE_SESSION_OPTION_CATALOG.models,
|
||||
record
|
||||
)
|
||||
).toEqual([...CLAUDE_SESSION_OPTION_CATALOG.models])
|
||||
})
|
||||
|
||||
it('re-injects a grok seed model an authoritative discovery dropped', () => {
|
||||
// PTY retires picks after authoritative discovery; shared reconciliation retains labeled pre-discovery selections.
|
||||
const record = createNativeChatSessionOptionRecord('grok')
|
||||
|
||||
@@ -1,57 +1,5 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
applySlashSuggestion,
|
||||
filterSlashCommands,
|
||||
getAgentSlashCommands,
|
||||
isSlashCommandDraft,
|
||||
sessionReportedSkills,
|
||||
sessionSlashCommandSuggestions,
|
||||
slashCommandDispatchText
|
||||
} from './native-chat-slash-commands'
|
||||
|
||||
describe('getAgentSlashCommands', () => {
|
||||
it('falls back to a small common set for an unknown agent (never empty)', () => {
|
||||
const names = getAgentSlashCommands('some-other-agent').map((c) => c.name)
|
||||
expect(names).toEqual(['clear', 'help'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('isSlashCommandDraft', () => {
|
||||
it('is true for a leading slash, even with leading whitespace', () => {
|
||||
expect(isSlashCommandDraft('/clear')).toBe(true)
|
||||
expect(isSlashCommandDraft(' /model')).toBe(true)
|
||||
})
|
||||
|
||||
it('is false for ordinary prose or a mid-line slash', () => {
|
||||
expect(isSlashCommandDraft('fix the bug')).toBe(false)
|
||||
expect(isSlashCommandDraft('run a/b test')).toBe(false)
|
||||
expect(isSlashCommandDraft('')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('filterSlashCommands', () => {
|
||||
const codex = getAgentSlashCommands('codex')
|
||||
|
||||
it('returns all commands for an empty query (bare /)', () => {
|
||||
expect(filterSlashCommands(codex, '')).toHaveLength(codex.length)
|
||||
})
|
||||
|
||||
it('prefix-matches case-insensitively', () => {
|
||||
const names = filterSlashCommands(codex, 'mod').map((c) => c.name)
|
||||
expect(names).toEqual(['model'])
|
||||
expect(filterSlashCommands(codex, 'MOD').map((c) => c.name)).toEqual(['model'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('dispatch vs completion text', () => {
|
||||
it('dispatch text has no trailing space (Enter dispatches the command)', () => {
|
||||
expect(slashCommandDispatchText({ name: 'clear' })).toBe('/clear')
|
||||
})
|
||||
|
||||
it('completion text has a trailing space (Tab completes for arguments)', () => {
|
||||
expect(applySlashSuggestion({ name: 'model' })).toBe('/model ')
|
||||
})
|
||||
})
|
||||
import { sessionReportedSkills, sessionSlashCommandSuggestions } from './native-chat-slash-commands'
|
||||
|
||||
describe('a session that reports its own command surface', () => {
|
||||
const reported = [
|
||||
@@ -67,11 +15,6 @@ describe('a session that reports its own command surface', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('does not resurrect a curated command the session never reported', () => {
|
||||
const names = sessionSlashCommandSuggestions('claude', reported).map((c) => c.name)
|
||||
expect(names).not.toContain('compact')
|
||||
})
|
||||
|
||||
it('splits skills out for the picker to group on its own', () => {
|
||||
expect(sessionReportedSkills(reported).map((skill) => skill.name)).toEqual(['ref-oss'])
|
||||
})
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
getAgentAnsweredNativeChatCommands,
|
||||
getHostClaimedNativeChatCommands,
|
||||
getNativeChatAgentProfile,
|
||||
getNativeChatCommandReply,
|
||||
getVerifiedNativeChatCommands
|
||||
} from './native-chat-agent-profiles'
|
||||
|
||||
describe('native chat agent picker profiles', () => {
|
||||
it('does not grant custom or unverified agents a skill grammar', () => {
|
||||
expect(getNativeChatAgentProfile('custom-agent')).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('host-claimed native chat commands', () => {
|
||||
function names(agent: string): string[] {
|
||||
return getHostClaimedNativeChatCommands(agent).map((command) => command.name)
|
||||
}
|
||||
|
||||
// Claude expands slash commands from message text; host claiming produced false unavailable replies.
|
||||
it('claims nothing from the Claude-family catalog', () => {
|
||||
expect(names('claude')).toEqual([])
|
||||
expect(names('openclaude')).toEqual([])
|
||||
})
|
||||
|
||||
it('keeps the Codex catalog claimed except the model-driven /goal', () => {
|
||||
expect(names('codex')).toContain('permissions')
|
||||
expect(names('codex')).toContain('vim')
|
||||
expect(names('codex')).not.toContain('goal')
|
||||
expect(getVerifiedNativeChatCommands('codex').map((command) => command.name)).toContain('goal')
|
||||
})
|
||||
|
||||
it('claims the whole catalog for agents with no pass-through policy', () => {
|
||||
expect(names('custom-agent')).toEqual(['clear', 'help'])
|
||||
expect(names('grok')).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('native chat command replies', () => {
|
||||
it('declares /context as answered by the composer for OMP and by a transcript row for OpenClaude', () => {
|
||||
expect(getNativeChatCommandReply('omp', 'context')).toBe('composer')
|
||||
expect(getNativeChatCommandReply('openclaude', 'context')).toBe('transcript')
|
||||
// Claude and Codex answer /context in their structured sessions.
|
||||
expect(getNativeChatCommandReply('claude', 'context')).toBeNull()
|
||||
expect(getNativeChatCommandReply('codex', 'context')).toBeNull()
|
||||
expect(getNativeChatCommandReply('omp', 'compact')).toBeNull()
|
||||
})
|
||||
|
||||
it('leaves out of the agent-answered catalog only the commands that declare a reply', () => {
|
||||
const names = (agent: string) =>
|
||||
getAgentAnsweredNativeChatCommands(agent).map(({ name }) => name)
|
||||
expect(names('openclaude')).toContain('compact')
|
||||
expect(names('openclaude')).not.toContain('context')
|
||||
expect(names('omp')).toContain('model')
|
||||
expect(names('omp')).toContain('usage')
|
||||
expect(names('omp')).not.toContain('context')
|
||||
expect(names('codex')).toContain('permissions')
|
||||
expect(names('grok')).toEqual([])
|
||||
})
|
||||
})
|
||||
@@ -1,78 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
isNativeChatSupportedAgent,
|
||||
nativeChatRequiresLocalTranscript,
|
||||
resolveNativeChatTranscriptAgent,
|
||||
shouldStepNativeChatAskAnswer
|
||||
} from './native-chat-agent-support'
|
||||
|
||||
describe('resolveNativeChatTranscriptAgent', () => {
|
||||
it('maps OpenClaude onto the Claude transcript format', () => {
|
||||
expect(resolveNativeChatTranscriptAgent('openclaude')).toBe('claude')
|
||||
expect(resolveNativeChatTranscriptAgent('claude')).toBe('claude')
|
||||
})
|
||||
|
||||
it('passes codex, grok and omp through and rejects everything else', () => {
|
||||
expect(resolveNativeChatTranscriptAgent('opencode')).toBe('opencode')
|
||||
expect(resolveNativeChatTranscriptAgent('opencode2')).toBe('opencode')
|
||||
// ZCode’s separate SQLite DB uses OpenCode-v1 history format.
|
||||
expect(resolveNativeChatTranscriptAgent('zcode')).toBe('opencode')
|
||||
expect(resolveNativeChatTranscriptAgent('codex')).toBe('codex')
|
||||
expect(resolveNativeChatTranscriptAgent('grok')).toBe('grok')
|
||||
expect(resolveNativeChatTranscriptAgent('omp')).toBe('omp')
|
||||
expect(resolveNativeChatTranscriptAgent('cursor')).toBeNull()
|
||||
expect(resolveNativeChatTranscriptAgent(null)).toBeNull()
|
||||
expect(resolveNativeChatTranscriptAgent(undefined)).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('isNativeChatSupportedAgent', () => {
|
||||
it('recognizes the parseable agents and rejects unknown / nullish input', () => {
|
||||
expect(isNativeChatSupportedAgent('opencode')).toBe(true)
|
||||
expect(isNativeChatSupportedAgent('opencode2')).toBe(true)
|
||||
expect(isNativeChatSupportedAgent('zcode')).toBe(true)
|
||||
expect(isNativeChatSupportedAgent('claude')).toBe(true)
|
||||
expect(isNativeChatSupportedAgent('openclaude')).toBe(true)
|
||||
expect(isNativeChatSupportedAgent('omp')).toBe(true)
|
||||
expect(isNativeChatSupportedAgent('cursor')).toBe(false)
|
||||
expect(isNativeChatSupportedAgent(null)).toBe(false)
|
||||
expect(isNativeChatSupportedAgent(undefined)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('nativeChatRequiresLocalTranscript', () => {
|
||||
it('covers the agents whose hook discloses no transcript path', () => {
|
||||
// Claude/Codex report `transcript_path`; Grok and omp report only an id, so
|
||||
// native chat has to find their file on a disk this process can read.
|
||||
expect(nativeChatRequiresLocalTranscript('opencode')).toBe(true)
|
||||
expect(nativeChatRequiresLocalTranscript('opencode2')).toBe(true)
|
||||
expect(nativeChatRequiresLocalTranscript('zcode')).toBe(true)
|
||||
expect(nativeChatRequiresLocalTranscript('grok')).toBe(true)
|
||||
expect(nativeChatRequiresLocalTranscript('omp')).toBe(true)
|
||||
expect(nativeChatRequiresLocalTranscript('claude')).toBe(false)
|
||||
expect(nativeChatRequiresLocalTranscript('openclaude')).toBe(false)
|
||||
expect(nativeChatRequiresLocalTranscript('codex')).toBe(false)
|
||||
expect(nativeChatRequiresLocalTranscript('cursor')).toBe(false)
|
||||
expect(nativeChatRequiresLocalTranscript(null)).toBe(false)
|
||||
expect(nativeChatRequiresLocalTranscript(undefined)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('shouldStepNativeChatAskAnswer', () => {
|
||||
it('steps the digit-commit selector agents (Claude, OpenClaude, Codex)', () => {
|
||||
expect(shouldStepNativeChatAskAnswer('claude')).toBe(true)
|
||||
expect(shouldStepNativeChatAskAnswer('openclaude')).toBe(true)
|
||||
// Codex 0.145 commits the highlighted row, ignoring pasted labels (STA-1860).
|
||||
expect(shouldStepNativeChatAskAnswer('codex')).toBe(true)
|
||||
expect(shouldStepNativeChatAskAnswer('opencode')).toBe(true)
|
||||
expect(shouldStepNativeChatAskAnswer('opencode2')).toBe(true)
|
||||
})
|
||||
|
||||
it('does not step other or unknown agents', () => {
|
||||
expect(shouldStepNativeChatAskAnswer('grok')).toBe(false)
|
||||
expect(shouldStepNativeChatAskAnswer('omp')).toBe(false)
|
||||
expect(shouldStepNativeChatAskAnswer('cursor')).toBe(false)
|
||||
expect(shouldStepNativeChatAskAnswer(null)).toBe(false)
|
||||
expect(shouldStepNativeChatAskAnswer(undefined)).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -37,24 +37,6 @@ describe('native chat appearance normalization', () => {
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('derives defaults without storing them', () => {
|
||||
expect(
|
||||
normalizeNativeChatAppearanceSettings({
|
||||
fontSize: 14,
|
||||
codeFontSize: 12,
|
||||
width: 'comfortable'
|
||||
})
|
||||
).toBeUndefined()
|
||||
expect(resolveNativeChatAppearanceSettings(undefined)).toEqual({
|
||||
fontFamily: '',
|
||||
fontSize: 14,
|
||||
codeFontSize: 12,
|
||||
width: 'comfortable',
|
||||
contrast: 100,
|
||||
matchTerminalInterface: false
|
||||
})
|
||||
})
|
||||
it('resets known controls while keeping settings from a newer version', () => {
|
||||
const fromNewerVersion = {
|
||||
fontFamily: 'Georgia',
|
||||
@@ -111,26 +93,6 @@ describe('native chat appearance normalization', () => {
|
||||
})
|
||||
|
||||
describe('chat contrast and terminal interface normalization', () => {
|
||||
it('removes default fields and the empty object', () => {
|
||||
expect(
|
||||
normalizeNativeChatAppearanceSettings({ contrast: 100, matchTerminalInterface: false })
|
||||
).toBeUndefined()
|
||||
expect(
|
||||
normalizeNativeChatAppearanceSettings({
|
||||
fontSize: 16,
|
||||
contrast: 100,
|
||||
matchTerminalInterface: false
|
||||
})
|
||||
).toEqual({ fontSize: 16 })
|
||||
expect(
|
||||
normalizeNativeChatAppearanceSettings({
|
||||
codeFontSize: 13,
|
||||
contrast: 120,
|
||||
matchTerminalInterface: true
|
||||
})
|
||||
).toEqual({ codeFontSize: 13, contrast: 120, matchTerminalInterface: true })
|
||||
})
|
||||
|
||||
it('clamps and rounds persisted contrast, accepting only an explicit true toggle', () => {
|
||||
expect(
|
||||
resolveNativeChatAppearanceSettings({ contrast: 999, matchTerminalInterface: 'true' })
|
||||
|
||||
@@ -11,14 +11,6 @@ import {
|
||||
} from './native-chat-visual-directive'
|
||||
|
||||
describe('parseNativeChatVisualDirectiveLine', () => {
|
||||
it('parses file and title', () => {
|
||||
expect(
|
||||
parseNativeChatVisualDirectiveLine(
|
||||
'::orca-visual{file="usage-3f2a.html" title="Usage by day"}'
|
||||
)
|
||||
).toEqual({ file: 'usage-3f2a.html', title: 'Usage by day' })
|
||||
})
|
||||
|
||||
it('treats title as optional and trims it', () => {
|
||||
expect(parseNativeChatVisualDirectiveLine('::orca-visual{file="a.html"}')).toEqual({
|
||||
file: 'a.html',
|
||||
@@ -101,10 +93,6 @@ describe('parseNativeChatVisualDirectiveLine', () => {
|
||||
})
|
||||
|
||||
describe('isNativeChatVisualFileName', () => {
|
||||
it.each(['a.html', 'usage-chart_3f2a.html', 'A1.b.html', '0.html'])('accepts %s', (name) => {
|
||||
expect(isNativeChatVisualFileName(name)).toBe(true)
|
||||
})
|
||||
|
||||
it.each([
|
||||
['empty', ''],
|
||||
['no extension', 'chart'],
|
||||
|
||||
@@ -43,18 +43,6 @@ describe('imageSourcePathFromText', () => {
|
||||
})
|
||||
|
||||
describe('normalizeImageTranscriptMessages', () => {
|
||||
it('merges the paired [Image: source]/[Image #1] turns into one image-ref turn', () => {
|
||||
const out = normalizeImageTranscriptMessages([
|
||||
userText('a', '[Image: source: /tmp/orca-paste-1-2.png]'),
|
||||
userText('b', '[Image #1] describe this')
|
||||
])
|
||||
expect(out).toHaveLength(1)
|
||||
expect(out[0]!.blocks).toEqual([
|
||||
{ type: 'image-ref', path: '/tmp/orca-paste-1-2.png' },
|
||||
{ type: 'text', text: 'describe this' }
|
||||
])
|
||||
})
|
||||
|
||||
it('merges a source turn into a prompt with a trailing image marker', () => {
|
||||
const out = normalizeImageTranscriptMessages([
|
||||
userText('a', '[Image: source: /tmp/orca-paste-1-2.png]'),
|
||||
@@ -109,11 +97,6 @@ describe('normalizeImageTranscriptMessages', () => {
|
||||
expect(stripImagePromptMarker(text)).toBe(expected)
|
||||
})
|
||||
|
||||
it('returns long marker-free whitespace without regex backtracking', () => {
|
||||
const text = ' '.repeat(50_000)
|
||||
expect(stripImagePromptMarker(text)).toBe(text)
|
||||
})
|
||||
|
||||
it('shares marker-aware text matching across multiple text blocks', () => {
|
||||
const message: NativeChatMessage = {
|
||||
...userText('prompt', 'unused'),
|
||||
@@ -140,16 +123,6 @@ describe('normalizeImageTranscriptMessages', () => {
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
it('converts a lone [Image: source] turn (no prompt) into an image-ref instead of raw text', () => {
|
||||
const out = normalizeImageTranscriptMessages([
|
||||
userText('a', '[Image: source: /Users/me/Pictures/hero-image-2.png]')
|
||||
])
|
||||
expect(out).toHaveLength(1)
|
||||
expect(out[0]!.blocks).toEqual([
|
||||
{ type: 'image-ref', path: '/Users/me/Pictures/hero-image-2.png' }
|
||||
])
|
||||
})
|
||||
|
||||
it('folds every source and strips every prompt marker for a multi-image send', () => {
|
||||
const out = normalizeImageTranscriptMessages([
|
||||
userText('a', '[Image: source: /tmp/a.png]'),
|
||||
@@ -381,12 +354,4 @@ describe('composer image chips at send and back', () => {
|
||||
expect(stripSentImagePromptMarkers(`[Image #1] in ${file} [Image #1]`, 1)).toBe(`in ${file}`)
|
||||
}
|
||||
})
|
||||
|
||||
it('sends a returned message with the numbers it had', () => {
|
||||
const sent = 'compare [Image #1] with [Image #2]'
|
||||
const ids = ['returned-m-1', 'returned-m-2']
|
||||
expect(imageReferencesToPromptMarkers(promptMarkersToImageReferences(sent, ids), ids)).toBe(
|
||||
sent
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -61,12 +61,4 @@ describe('the live line both clients draw', () => {
|
||||
reasoning: null
|
||||
})
|
||||
})
|
||||
|
||||
// The label may read the provider's own words; the block it discloses is still the open one.
|
||||
it('keeps the activity text it was handed', () => {
|
||||
expect(line({ activityText: 'Summarizing the plan' })).toMatchObject({
|
||||
activityText: 'Summarizing the plan',
|
||||
reasoning: { message: open }
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -199,13 +199,6 @@ describe('a send a Stop took back before its echo', () => {
|
||||
|
||||
// Old chats lack explicit turn attribution; newer hosts still expose sent sequence and move take-back rows, so preserve journal-order placement.
|
||||
describe('a send a Stop took back before its echo, in history written before the host named turns', () => {
|
||||
it('opens the turn recorded after it was sent and before it was taken back', () => {
|
||||
const items = [turn('t1', 2, 'codex:thread-1:t1:0'), stopNote('t1', 3), sent('opener', 4)]
|
||||
const submissions = [stopped('opener', { submittedSequence: 1 })]
|
||||
|
||||
expect(drawn(items, submissions).map(({ id }) => id)).toEqual([key('opener'), 'stop:t1'])
|
||||
})
|
||||
|
||||
it('opens it as the first sent, leaving a later send its own row after the turn', () => {
|
||||
const items = [
|
||||
turn('t1', 3, 'codex:thread-1:t1:0'),
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
formatSubagentTokens,
|
||||
nativeChatSubagentGroupHeader,
|
||||
sayNativeChatSubagentGroupEnglish as say
|
||||
} from './native-chat-subagent-group-header'
|
||||
import type { NativeChatSubagentEntry, NativeChatSubagentState } from '../native-chat-types'
|
||||
|
||||
function child(
|
||||
id: string,
|
||||
state: NativeChatSubagentState,
|
||||
extra: Partial<NativeChatSubagentEntry> = {}
|
||||
): NativeChatSubagentEntry {
|
||||
return { id, label: id, state, ...extra }
|
||||
}
|
||||
|
||||
describe('nativeChatSubagentGroupHeader', () => {
|
||||
it('counts working children while the group runs', () => {
|
||||
const header = nativeChatSubagentGroupHeader(
|
||||
[child('a', 'working', { startedAt: 1_000 }), child('b', 'working', { startedAt: 2_000 })],
|
||||
say
|
||||
)
|
||||
expect(header).toMatchObject({
|
||||
working: true,
|
||||
headline: 'Kicked off 2 subagents',
|
||||
verdictState: 'working',
|
||||
verdict: '2 working',
|
||||
alert: null,
|
||||
clockStartedAt: 1_000,
|
||||
tokens: null
|
||||
})
|
||||
})
|
||||
|
||||
it('reports a failed sibling before the rest settle', () => {
|
||||
const header = nativeChatSubagentGroupHeader(
|
||||
[child('a', 'working'), child('b', 'failed', { settledAt: 5 })],
|
||||
say
|
||||
)
|
||||
expect(header).toMatchObject({ alertState: 'failed', alert: '1 failed' })
|
||||
})
|
||||
|
||||
it('summarizes completed siblings with the run length and tokens', () => {
|
||||
const header = nativeChatSubagentGroupHeader(
|
||||
[
|
||||
child('a', 'completed', { startedAt: 0, settledAt: 60_000, tokens: 9_000 }),
|
||||
child('b', 'completed', { startedAt: 0, settledAt: 62_000, tokens: 3_000 })
|
||||
],
|
||||
say
|
||||
)
|
||||
expect(header).toMatchObject({
|
||||
working: false,
|
||||
headline: 'Ran 2 subagents',
|
||||
verdict: 'completed',
|
||||
clockStartedAt: 0,
|
||||
settledAt: 62_000,
|
||||
tokens: '12k tokens'
|
||||
})
|
||||
})
|
||||
|
||||
it('stops the clock when a settled child has no stamp', () => {
|
||||
const header = nativeChatSubagentGroupHeader(
|
||||
[child('a', 'unverifiable', { startedAt: 0 })],
|
||||
say
|
||||
)
|
||||
expect(header).toMatchObject({ verdict: 'status unavailable', clockStartedAt: null })
|
||||
})
|
||||
|
||||
it('reads a single child as a bare word', () => {
|
||||
expect(nativeChatSubagentGroupHeader([child('a', 'working')], say)).toMatchObject({
|
||||
headline: 'Kicked off 1 subagent',
|
||||
verdict: 'working'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('formatSubagentTokens', () => {
|
||||
it('shows scale', () => {
|
||||
expect(formatSubagentTokens(950)).toBe('950')
|
||||
expect(formatSubagentTokens(12_340)).toBe('12.3k')
|
||||
expect(formatSubagentTokens(2_000_000)).toBe('2M')
|
||||
})
|
||||
})
|
||||
@@ -14,16 +14,6 @@ function agent(entry: Partial<NativeChatSubagentEntry>): NativeChatSubagentEntry
|
||||
}
|
||||
|
||||
describe('summarizeSubagentGroup', () => {
|
||||
it('collapses in-flight children into one working count', () => {
|
||||
const summary = summarizeSubagentGroup([
|
||||
agent({ id: 'a', state: 'working' }),
|
||||
agent({ id: 'b', state: 'working' }),
|
||||
agent({ id: 'c', state: 'completed' })
|
||||
])
|
||||
|
||||
expect(summary).toMatchObject({ total: 3, working: 2, settledState: null, settledCount: 0 })
|
||||
})
|
||||
|
||||
it('ranks the settled verdict worst-first and reports ✓ completed last', () => {
|
||||
const cascade: [NativeChatSubagentEntry['state'][], string][] = [
|
||||
[['failed', 'stopped', 'idle', 'completed'], 'failed'],
|
||||
@@ -184,13 +174,6 @@ describe('summarizeSubagentGroup', () => {
|
||||
]).adverseState
|
||||
).toBeNull()
|
||||
})
|
||||
|
||||
it('keeps working the only non-terminal state', () => {
|
||||
expect(isTerminalSubagentState('working')).toBe(false)
|
||||
for (const state of ['idle', 'completed', 'failed', 'stopped', 'unverifiable']) {
|
||||
expect(isTerminalSubagentState(state)).toBe(true)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('subagentGroupFallbackText', () => {
|
||||
@@ -227,15 +210,6 @@ describe('subagentGroupFallbackText', () => {
|
||||
])
|
||||
).toBe('Kicked off 2 subagents (1 unverifiable)')
|
||||
})
|
||||
|
||||
it('stays quiet when nothing has gone wrong', () => {
|
||||
expect(subagentGroupFallbackText([agent({ id: 'a', state: 'working' })])).toBe(
|
||||
'Kicked off 1 subagent'
|
||||
)
|
||||
expect(subagentGroupFallbackText([agent({ id: 'a', state: 'completed' })])).toBe(
|
||||
'Ran 1 subagent'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
// Twin false positives hide real prose; false negatives duplicate rosters. Match shape because newer states cannot reproduce byte-for-byte here.
|
||||
|
||||
@@ -10,5 +10,4 @@ import './native-chat-live-line.spec'
|
||||
import '../native-chat-merge-pairs.spec'
|
||||
import '../native-chat-command-envelope.spec'
|
||||
import '../native-chat-streaming.spec'
|
||||
import './native-chat-subagent-group-header.spec'
|
||||
import '../tools/native-chat-tool-result-orphans.spec'
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { NativeChatMessage } from '../native-chat-types'
|
||||
import {
|
||||
createNativeChatTranscriptRetention,
|
||||
encodeNativeChatTranscriptIdentity
|
||||
} from './native-chat-transcript-retention'
|
||||
import { createNativeChatTranscriptRetention } from './native-chat-transcript-retention'
|
||||
|
||||
function message(id: string): NativeChatMessage {
|
||||
return { id, role: 'assistant', blocks: [], timestamp: 0, source: 'transcript' }
|
||||
@@ -32,10 +29,4 @@ describe('native chat transcript retention', () => {
|
||||
|
||||
expect(retention.visible({ identity: 'source', messages: fresh, settled: true })).toBe(fresh)
|
||||
})
|
||||
|
||||
it('encodes identity components without delimiter collisions', () => {
|
||||
expect(encodeNativeChatTranscriptIdentity(['host\0workspace', 'session'])).not.toBe(
|
||||
encodeNativeChatTranscriptIdentity(['host', 'workspace\0session'])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -52,16 +52,6 @@ describe('selectStructuredAgentTurnActivity', () => {
|
||||
).toEqual({ kind: 'description', text: 'Running a command' })
|
||||
})
|
||||
|
||||
it('shows matching ephemeral provider activity, never an older journal status', () => {
|
||||
const activity = selectStructuredAgentTurnActivity(
|
||||
[turnStart, item(2, { kind: 'status', text: 'Older journal status' })],
|
||||
'turn-1',
|
||||
{ turnId: 'turn-1', text: 'Inspecting the session wire' }
|
||||
)
|
||||
|
||||
expect(activity).toEqual({ kind: 'description', text: 'Inspecting the session wire' })
|
||||
})
|
||||
|
||||
it('ignores ephemeral activity from another or settled turn', () => {
|
||||
const providerActivity = { turnId: 'turn-1', text: 'Inspecting the session wire' }
|
||||
|
||||
|
||||
@@ -35,34 +35,10 @@ const SETTLED = new Set(['turn-1'])
|
||||
const NONE = new Set<string>()
|
||||
|
||||
describe('nativeChatTurnAnswerRows', () => {
|
||||
it('names the last assistant row that renders prose, not the first', () => {
|
||||
expect([...nativeChatTurnAnswerRows(TURN)]).toEqual([4])
|
||||
})
|
||||
|
||||
it('ignores rows that render no prose, so a trailing tool run is not the answer', () => {
|
||||
const rows = [row({ role: 'user' }), row(), row({ rendersProse: false })]
|
||||
expect([...nativeChatTurnAnswerRows(rows)]).toEqual([1])
|
||||
})
|
||||
|
||||
it('ignores reasoning and system rows, which are never the agent answering', () => {
|
||||
const rows = [row({ role: 'user' }), row(), row({ role: 'reasoning' }), row({ role: 'system' })]
|
||||
expect([...nativeChatTurnAnswerRows(rows)]).toEqual([1])
|
||||
})
|
||||
|
||||
it('names a failure report as the answer when nothing the agent said comes after it', () => {
|
||||
const rows = [row({ role: 'user' }), row(), FAILURE]
|
||||
expect([...nativeChatTurnAnswerRows(rows)]).toEqual([2])
|
||||
})
|
||||
|
||||
it('names the later prose as the answer when the agent recovered from a failure', () => {
|
||||
const rows = [row({ role: 'user' }), FAILURE, row()]
|
||||
expect([...nativeChatTurnAnswerRows(rows)]).toEqual([2])
|
||||
})
|
||||
|
||||
it('reports no answer for a turn that only ran tools', () => {
|
||||
const rows = [row({ role: 'user' }), row({ rendersProse: false })]
|
||||
expect(nativeChatTurnAnswerRows(rows).size).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('nativeChatTurnFold', () => {
|
||||
|
||||
@@ -2,9 +2,7 @@ import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
describeNativeChatActiveTurnLabel,
|
||||
describeNativeChatTurnStatus,
|
||||
formatNativeChatActiveTurnLabel,
|
||||
formatNativeChatDuration,
|
||||
formatNativeChatTurnStatusLabel,
|
||||
nativeChatElapsedSeconds,
|
||||
reduceNativeChatTurnTiming,
|
||||
selectNativeChatTurnStatuses,
|
||||
@@ -32,13 +30,6 @@ describe('formatNativeChatDuration', () => {
|
||||
})
|
||||
|
||||
describe('describeNativeChatTurnStatus', () => {
|
||||
it('prefers the settled duration over the running clock', () => {
|
||||
expect(describeNativeChatTurnStatus({ workedSeconds: 184, elapsedSeconds: 9 })).toEqual({
|
||||
key: 'workedFor',
|
||||
duration: '3m 4s'
|
||||
})
|
||||
})
|
||||
|
||||
it.each([
|
||||
['cancellation', 'interruptedAfter'],
|
||||
['superseded', 'interruptedAfter'],
|
||||
@@ -53,13 +44,6 @@ describe('describeNativeChatTurnStatus', () => {
|
||||
{ key, duration: '12s' }
|
||||
)
|
||||
})
|
||||
|
||||
it('counts from the first second of the turn', () => {
|
||||
expect(describeNativeChatTurnStatus({ workedSeconds: null, elapsedSeconds: 0 })).toEqual({
|
||||
key: 'workingFor',
|
||||
duration: '0s'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('describeNativeChatActiveTurnLabel', () => {
|
||||
@@ -99,57 +83,9 @@ describe('describeNativeChatActiveTurnLabel', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('formatNativeChatActiveTurnLabel', () => {
|
||||
it('renders the live tail line in English for platforms without i18n', () => {
|
||||
expect(
|
||||
formatNativeChatActiveTurnLabel({ activityText: 'Running pnpm test', thinking: false })
|
||||
).toBe('Running pnpm test')
|
||||
expect(formatNativeChatActiveTurnLabel({ thinking: true })).toBe('Thinking')
|
||||
expect(formatNativeChatActiveTurnLabel({ thinking: false })).toBe('Working…')
|
||||
expect(formatNativeChatActiveTurnLabel({ thinking: false, stopping: true })).toBe('Stopping…')
|
||||
})
|
||||
})
|
||||
|
||||
describe('formatNativeChatTurnStatusLabel', () => {
|
||||
it('renders the turn bar in English for platforms without i18n', () => {
|
||||
expect(formatNativeChatTurnStatusLabel({ workedSeconds: null, elapsedSeconds: 12 })).toBe(
|
||||
'Working for 12s'
|
||||
)
|
||||
expect(formatNativeChatTurnStatusLabel({ workedSeconds: 184, elapsedSeconds: 0 })).toBe(
|
||||
'Worked for 3m 4s'
|
||||
)
|
||||
})
|
||||
|
||||
// The phone's turn bar renders through this function, so it reads the same as desktop.
|
||||
it('reads a crash-cut turn as worked and a Stop as interrupted in English', () => {
|
||||
expect(
|
||||
formatNativeChatTurnStatusLabel({
|
||||
workedSeconds: 222,
|
||||
elapsedSeconds: 0,
|
||||
verdict: 'interruption'
|
||||
})
|
||||
).toBe('Worked for 3m 42s')
|
||||
expect(
|
||||
formatNativeChatTurnStatusLabel({
|
||||
workedSeconds: 222,
|
||||
elapsedSeconds: 0,
|
||||
verdict: 'cancellation'
|
||||
})
|
||||
).toBe('Interrupted after 3m 42s')
|
||||
})
|
||||
})
|
||||
|
||||
describe('reduceNativeChatTurnTiming', () => {
|
||||
const validTurnKeys = new Set(['u1'])
|
||||
|
||||
it('stamps a start when a turn begins working', () => {
|
||||
const next = reduceNativeChatTurnTiming(
|
||||
{},
|
||||
{ activeTurnKey: 'u1', validTurnKeys, isWorking: true, now: 1_000 }
|
||||
)
|
||||
expect(next).toEqual({ u1: { startedAt: 1_000, workedSeconds: null } })
|
||||
})
|
||||
|
||||
it('keeps the original start across later working ticks', () => {
|
||||
const first = reduceNativeChatTurnTiming(
|
||||
{},
|
||||
@@ -164,44 +100,6 @@ describe('reduceNativeChatTurnTiming', () => {
|
||||
expect(second).toBe(first)
|
||||
})
|
||||
|
||||
it('prefers an authoritative host start over the local stamp', () => {
|
||||
const next = reduceNativeChatTurnTiming(
|
||||
{},
|
||||
{
|
||||
activeTurnKey: 'u1',
|
||||
validTurnKeys,
|
||||
isWorking: true,
|
||||
workingStartedAt: 500,
|
||||
now: 1_000
|
||||
}
|
||||
)
|
||||
expect(next.u1?.startedAt).toBe(500)
|
||||
})
|
||||
|
||||
it('does not move a live turn later before its request origin arrives', () => {
|
||||
const optimistic = reduceNativeChatTurnTiming(
|
||||
{},
|
||||
{ activeTurnKey: 'u1', validTurnKeys, isWorking: true, now: 1_000 }
|
||||
)
|
||||
const turnStarted = reduceNativeChatTurnTiming(optimistic, {
|
||||
activeTurnKey: 'u1',
|
||||
validTurnKeys,
|
||||
isWorking: true,
|
||||
workingStartedAt: 8_000,
|
||||
now: 8_000
|
||||
})
|
||||
const exactOrigin = reduceNativeChatTurnTiming(turnStarted, {
|
||||
activeTurnKey: 'u1',
|
||||
validTurnKeys,
|
||||
isWorking: true,
|
||||
workingStartedAt: 900,
|
||||
now: 8_100
|
||||
})
|
||||
|
||||
expect(turnStarted).toBe(optimistic)
|
||||
expect(exactOrigin.u1).toEqual({ startedAt: 900, workedSeconds: null })
|
||||
})
|
||||
|
||||
it('settles the turn to whole elapsed seconds when work stops', () => {
|
||||
const working = reduceNativeChatTurnTiming(
|
||||
{},
|
||||
@@ -374,22 +272,6 @@ describe('selectNativeChatTurnStatuses', () => {
|
||||
expect(afterEcho.active?.startedAt).toBe(900)
|
||||
})
|
||||
|
||||
it('carries the reasoning verdict it is given onto the working turn', () => {
|
||||
const { active } = selectNativeChatTurnStatuses(
|
||||
{ u1: { startedAt: 1_000, workedSeconds: null } },
|
||||
{ activeTurnKey: 'u1', isWorking: true, thinking: true }
|
||||
)
|
||||
expect(active).toEqual({ startedAt: 1_000, thinking: true, workedSeconds: null })
|
||||
})
|
||||
|
||||
it('reports a working turn that is not reasoning as counting', () => {
|
||||
const { active } = selectNativeChatTurnStatuses(
|
||||
{ u1: { startedAt: 1_000, workedSeconds: null } },
|
||||
{ activeTurnKey: 'u1', isWorking: true, thinking: false }
|
||||
)
|
||||
expect(active?.thinking).toBe(false)
|
||||
})
|
||||
|
||||
it('exposes settled turns and resolves the active one from them when idle', () => {
|
||||
const { active, completedByTurn } = selectNativeChatTurnStatuses(
|
||||
{ u1: { startedAt: 1_000, workedSeconds: 12 } },
|
||||
|
||||
@@ -148,22 +148,6 @@ describe('fingerprintPluginConsent', () => {
|
||||
fingerprintPluginConsent(subject, 'b'.repeat(64))
|
||||
)
|
||||
})
|
||||
|
||||
it('does not invalidate inert content consent when only its content hash changes', () => {
|
||||
const subject = {
|
||||
main: undefined,
|
||||
capabilities: [],
|
||||
contributes: {
|
||||
keybindings: [],
|
||||
vmRecipes: [],
|
||||
agentHarnesses: []
|
||||
}
|
||||
}
|
||||
|
||||
expect(fingerprintPluginConsent(subject, 'a'.repeat(64))).toBe(
|
||||
fingerprintPluginConsent(subject, 'b'.repeat(64))
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('plugin install lockfile consent fingerprints', () => {
|
||||
|
||||
@@ -86,21 +86,6 @@ describe('plugin icon theme artifact prototype keys', () => {
|
||||
)
|
||||
).toThrow(/duplicate/)
|
||||
})
|
||||
|
||||
it('rejects case-folded duplicates of prototype names', () => {
|
||||
expect(() =>
|
||||
parse({ schemaVersion: 1, fileNames: { constructor: 'a.svg', CONSTRUCTOR: 'b.svg' } })
|
||||
).toThrow(/duplicate/)
|
||||
expect(() =>
|
||||
parse({ schemaVersion: 1, fileExtensions: { constructor: 'a.svg', CONSTRUCTOR: 'b.svg' } })
|
||||
).toThrow(/duplicate/)
|
||||
})
|
||||
|
||||
it('does not treat an inherited name as a duplicate of itself', () => {
|
||||
expect(() =>
|
||||
parse({ schemaVersion: 1, fileNames: { Constructor: 'a.svg', toString: 'b.svg' } })
|
||||
).not.toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
describe('isPluginIconThemeRegistration', () => {
|
||||
@@ -136,10 +121,6 @@ describe('isPluginIconThemeRegistration', () => {
|
||||
isPluginIconThemeRegistration({ ...base, fileNames: {}, folderNamesExpanded: { src: 1 } })
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
it('accepts in-range indexes', () => {
|
||||
expect(isPluginIconThemeRegistration({ ...base, fileNames: { constructor: 0 } })).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('isPlausiblePluginIconSvg', () => {
|
||||
|
||||
@@ -51,35 +51,6 @@ describe('plugin language-pack artifacts', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it.each([
|
||||
'PluginConsentDialog',
|
||||
// The provenance badge and install-error copy carry the trust decision.
|
||||
'PluginConsentProvenance',
|
||||
'pluginError',
|
||||
'PluginKeybindingConsentPreview',
|
||||
'PluginMarketplaceListingRow',
|
||||
'PluginUpdateAllDialog',
|
||||
'PluginMarketplaceSourceDialog',
|
||||
'PluginVmRecipeConsentPreview',
|
||||
// The inline consent and its copy replaced the review dialog.
|
||||
'PluginInstallConsent',
|
||||
'PluginPageAction',
|
||||
'pluginConsentCopy',
|
||||
'pluginInstallAction'
|
||||
])('prevents language packs from rewriting %s security copy', (component) => {
|
||||
expect(
|
||||
parsePluginLanguagePackArtifact(
|
||||
JSON.stringify({
|
||||
auto: {
|
||||
components: {
|
||||
settings: { [component]: { disclaimer: 'This plugin is perfectly safe.' } }
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
).toMatchObject({ ok: false, error: expect.stringContaining('protected security copy') })
|
||||
})
|
||||
|
||||
it.each([
|
||||
['PluginsSettingsSection', 'title', 'Плагины'],
|
||||
['PluginMarketplaceBrowser', 'refresh', 'Обновить'],
|
||||
@@ -230,16 +201,6 @@ describe('plugin language-pack artifacts', () => {
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it('still lets language packs translate non-plugin settings copy', () => {
|
||||
expect(
|
||||
parsePluginLanguagePackArtifact(
|
||||
JSON.stringify({
|
||||
auto: { components: { settings: { AppearanceSection: { title: 'Apariencia' } } } }
|
||||
})
|
||||
).ok
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it.each([
|
||||
['array leaf', { settings: { choices: ['one'] } }],
|
||||
['numeric leaf', { settings: { count: 1 } }],
|
||||
|
||||
@@ -9,17 +9,6 @@ const parseApp = (value: unknown) => () =>
|
||||
parsePluginAppThemeArtifact(JSON.stringify(value), 'strict')
|
||||
|
||||
describe('plugin app theme artifacts', () => {
|
||||
it('accepts public tokens with bounded color values', () => {
|
||||
expect(
|
||||
parsePluginAppThemeArtifact(
|
||||
JSON.stringify({
|
||||
base: 'light',
|
||||
tokens: { '--background': '#ffffff', '--foreground': 'oklch(0.2 0.02 250)' }
|
||||
})
|
||||
).tokens
|
||||
).toMatchObject({ '--background': '#ffffff', '--foreground': 'oklch(0.2 0.02 250)' })
|
||||
})
|
||||
|
||||
it.each([
|
||||
['a private token', { base: 'dark', tokens: { '--internal-gap': '#000' } }],
|
||||
['a URL', { base: 'dark', tokens: { '--background': 'url(x)' } }],
|
||||
@@ -114,12 +103,6 @@ describe('plugin terminal theme artifacts', () => {
|
||||
).toEqual({ mode: 'unknown', terminal })
|
||||
})
|
||||
|
||||
it('normalizes a usable palette', () => {
|
||||
expect(
|
||||
parsePluginTerminalThemeArtifact(JSON.stringify({ schemaVersion: 1, terminal }))
|
||||
).toEqual({ mode: 'unknown', terminal })
|
||||
})
|
||||
|
||||
it.each([
|
||||
['an unknown slot', { ...terminal, glow: '#000000' }],
|
||||
['an invalid color', { ...terminal, red: 'red; x' }],
|
||||
@@ -133,19 +116,6 @@ describe('plugin terminal theme artifacts', () => {
|
||||
).toThrow()
|
||||
})
|
||||
|
||||
it('skips unknown tokens and keys at runtime so newer themes still load', () => {
|
||||
const theme = parsePluginAppThemeArtifact(
|
||||
JSON.stringify({
|
||||
schemaVersion: 1,
|
||||
base: 'dark',
|
||||
tokens: { '--background': '#000', '--foreground': '#fff', '--future': '#123' },
|
||||
future: true
|
||||
})
|
||||
)
|
||||
expect(theme.tokens).not.toHaveProperty('--future')
|
||||
expect(parseApp({ schemaVersion: 2, base: 'dark', tokens: {} })).toThrow()
|
||||
})
|
||||
|
||||
it('validates registrations crossing IPC', () => {
|
||||
const registration = {
|
||||
id: 'plugin:samples.midnight/midnight',
|
||||
@@ -174,24 +144,6 @@ describe('plugin app theme completion', () => {
|
||||
}
|
||||
)
|
||||
|
||||
it('derives omitted surfaces and accents from the tokens a theme sets', () => {
|
||||
const { tokens } = parsePluginAppThemeArtifact(
|
||||
JSON.stringify({
|
||||
base: 'dark',
|
||||
tokens: { '--background': '#101018', '--foreground': '#f0f0ff', '--accent': '#2a2050' }
|
||||
})
|
||||
)
|
||||
expect(tokens).toMatchObject({
|
||||
'--card': '#101018',
|
||||
'--sidebar': 'color-mix(in srgb, #f0f0ff 8%, #101018)',
|
||||
'--worktree-sidebar': 'color-mix(in srgb, #f0f0ff 8%, #101018)',
|
||||
'--sidebar-accent': '#2a2050',
|
||||
'--worktree-sidebar-accent': '#2a2050',
|
||||
'--primary': '#f0f0ff',
|
||||
'--primary-foreground': '#101018'
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps a sidebar the theme sets', () => {
|
||||
const { tokens } = parsePluginAppThemeArtifact(
|
||||
JSON.stringify({
|
||||
|
||||
@@ -65,10 +65,6 @@ function manifestInput(): Record<string, unknown> {
|
||||
}
|
||||
|
||||
describe('catalog releases', () => {
|
||||
it('accepts a release that matches the exact bytes and manifest', () => {
|
||||
expect(check(release())).toBeNull()
|
||||
})
|
||||
|
||||
it.each([
|
||||
['a language pack', {}],
|
||||
[
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
isReservedPluginIdentity,
|
||||
gitRepositoryOwnerLabel,
|
||||
isSameGitRepository,
|
||||
parseGitRepositoryIdentity,
|
||||
pluginMarketplaceSchema,
|
||||
pluginMarketplaceTrustMetadataSchema
|
||||
} from './plugin-marketplace'
|
||||
@@ -25,20 +24,6 @@ function listing(id = 'community.nord'): Record<string, unknown> {
|
||||
}
|
||||
|
||||
describe('pluginMarketplaceSchema', () => {
|
||||
it('parses a bounded, pinned Git marketplace index', () => {
|
||||
expect(
|
||||
pluginMarketplaceSchema.parse({
|
||||
name: 'Community plugins',
|
||||
owner: 'community',
|
||||
plugins: [listing()]
|
||||
})
|
||||
).toEqual({
|
||||
name: 'Community plugins',
|
||||
owner: 'community',
|
||||
plugins: [listing()]
|
||||
})
|
||||
})
|
||||
|
||||
it.each([
|
||||
['root', { name: 'Plugins', owner: 'team', plugins: [], extra: true }],
|
||||
['entry', { name: 'Plugins', owner: 'team', plugins: [{ ...listing(), official: true }] }],
|
||||
@@ -194,15 +179,6 @@ describe('marketplace provenance contracts', () => {
|
||||
expect(gitRepositoryOwnerLabel('not a url')).toBeNull()
|
||||
})
|
||||
|
||||
it('parses nested repository paths without confusing the repository name', () => {
|
||||
expect(parseGitRepositoryIdentity('https://gitlab.com/team/subgroup/plugin.git')).toEqual({
|
||||
host: 'gitlab.com',
|
||||
owner: 'team',
|
||||
repository: 'plugin',
|
||||
path: 'team/subgroup/plugin'
|
||||
})
|
||||
})
|
||||
|
||||
it('prevents an untrusted listing from self-awarding trust metadata', () => {
|
||||
expect(pluginMarketplaceTrustMetadataSchema.parse({ official: true, bundled: true })).toEqual({
|
||||
official: true,
|
||||
|
||||
@@ -38,26 +38,6 @@ describe('content-pack manifest contributions', () => {
|
||||
expect(parsed.contributes.keybindings[0]?.key).toBe('Mod+Alt+T')
|
||||
})
|
||||
|
||||
it('accepts an optional native displayName on language packs', () => {
|
||||
const parsed = pluginManifestSchema.parse(
|
||||
manifest({
|
||||
languagePacks: [
|
||||
{
|
||||
locale: 'zh-TW',
|
||||
path: 'locales/zh-TW.json',
|
||||
displayName: '中文(繁體)'
|
||||
}
|
||||
]
|
||||
})
|
||||
)
|
||||
|
||||
expect(parsed.contributes.languagePacks[0]).toEqual({
|
||||
locale: 'zh-TW',
|
||||
path: 'locales/zh-TW.json',
|
||||
displayName: '中文(繁體)'
|
||||
})
|
||||
})
|
||||
|
||||
it('rejects blank language-pack displayName', () => {
|
||||
expect(
|
||||
parsePluginManifest(
|
||||
@@ -68,26 +48,6 @@ describe('content-pack manifest contributions', () => {
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
it('defaults every contribution registry to an empty array', () => {
|
||||
const parsed = pluginManifestSchema.parse(manifest({}))
|
||||
|
||||
expect(parsed.contributes).toEqual({
|
||||
panels: [],
|
||||
commands: [],
|
||||
events: [],
|
||||
languagePacks: [],
|
||||
keybindings: [],
|
||||
vmRecipes: [],
|
||||
settings: [],
|
||||
themes: [],
|
||||
terminalThemes: [],
|
||||
iconThemes: [],
|
||||
syntaxes: [],
|
||||
customEditors: [],
|
||||
newFiles: []
|
||||
})
|
||||
})
|
||||
|
||||
it('refuses items in the reserved skills contribution', () => {
|
||||
expect(parsePluginManifest(manifest({ skills: [{ path: 'skills' }] }))).toMatchObject({
|
||||
ok: false,
|
||||
|
||||
@@ -38,10 +38,6 @@ function parsed(overrides: Record<string, unknown> = {}) {
|
||||
}
|
||||
|
||||
describe('contributes.customEditors', () => {
|
||||
it('accepts a sandboxed-ui editor and defaults every other point', () => {
|
||||
expect(parsed().contributes.customEditors).toEqual([{ ...excalidraw, filenames: [] }])
|
||||
})
|
||||
|
||||
it('accepts a bytes editor, which edits like a text one', () => {
|
||||
const spreadsheet = {
|
||||
id: 'spreadsheet',
|
||||
@@ -67,14 +63,6 @@ describe('contributes.customEditors', () => {
|
||||
).toMatchObject({ ok: false })
|
||||
})
|
||||
|
||||
it('refuses the retired readonly field', () => {
|
||||
expect(
|
||||
parsePluginManifest(
|
||||
manifest({ contributes: { customEditors: [{ ...excalidraw, readonly: true }] } })
|
||||
)
|
||||
).toMatchObject({ ok: false })
|
||||
})
|
||||
|
||||
it('refuses an unknown content kind', () => {
|
||||
expect(
|
||||
parsePluginManifest(
|
||||
|
||||
@@ -20,11 +20,6 @@ function manifest(engine: string, panelFields: Record<string, unknown>) {
|
||||
}
|
||||
|
||||
describe('panel ui', () => {
|
||||
it('defaults to an html panel and leaves the field absent', () => {
|
||||
const parsed = pluginPanelContributionSchema.parse({ ...panel, entry: 'panel.html' })
|
||||
expect(parsed).toEqual({ ...panel, entry: 'panel.html' })
|
||||
})
|
||||
|
||||
it.each(['dist/panel.js', 'panel.mjs', 'Panel.JS', 'src/panel.tsx', 'panel.ts', 'panel.jsx'])(
|
||||
'accepts a components %s module',
|
||||
(entry) => {
|
||||
|
||||
Reference in New Issue
Block a user