mirror of
https://github.com/stablyai/orca.git
synced 2026-09-26 00:02:34 +00:00
266 lines
9.4 KiB
TypeScript
266 lines
9.4 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest'
|
|
import { fingerprintPluginConsent } from '../../shared/plugins/plugin-consent-fingerprint'
|
|
import { pluginManifestSchema } from '../../shared/plugins/plugin-manifest'
|
|
import type { ValidDiscoveredPlugin } from './plugin-discovery'
|
|
import { PluginCommandRegistry } from './plugin-command-registry'
|
|
|
|
function commandPlugin(
|
|
id: string,
|
|
contributes: {
|
|
commands: Record<string, unknown>[]
|
|
keybindings?: Record<string, unknown>[]
|
|
}
|
|
): ValidDiscoveredPlugin {
|
|
const manifest = pluginManifestSchema.parse({
|
|
manifestVersion: 1,
|
|
id,
|
|
publisher: 'orca-samples',
|
|
name: id,
|
|
version: '1.0.0',
|
|
engines: { orca: '>=1.0.0' },
|
|
pluginApi: 1,
|
|
...(contributes.commands.some((command) => command.action === undefined)
|
|
? { main: 'worker.js' }
|
|
: {}),
|
|
contributes,
|
|
capabilities: []
|
|
})
|
|
return {
|
|
pluginKey: `orca-samples.${id}`,
|
|
rootDir: `/plugins/${id}`,
|
|
manifest,
|
|
consentFingerprint: fingerprintPluginConsent(manifest, `content-${id}`),
|
|
consentContentHash: `content-${id}`,
|
|
contentHash: `content-${id}`,
|
|
isDev: false
|
|
}
|
|
}
|
|
|
|
describe('PluginCommandRegistry', () => {
|
|
it('reads binding command IDs once while preserving declaration and binding order', () => {
|
|
const commands = Array.from({ length: 256 }, (_, index) => ({
|
|
id: `command-${index}`,
|
|
title: `Command ${index}`,
|
|
action: 'view.tasks'
|
|
}))
|
|
const keys = Array.from(
|
|
{ length: 104 },
|
|
(_, index) =>
|
|
`Mod+${Math.floor(index / 26) & 1 ? 'Alt+' : ''}${Math.floor(index / 26) & 2 ? 'Shift+' : ''}${String.fromCharCode(65 + (index % 26))}`
|
|
)
|
|
// Distinct physical chords, with two bindings belonging to the same command.
|
|
const uniqueKeys = [...new Set(keys)]
|
|
const plugin = commandPlugin('many-commands', {
|
|
commands,
|
|
keybindings: uniqueKeys.map((key, index) => ({ command: `command-${index % 32}`, key }))
|
|
})
|
|
let reads = 0
|
|
for (const binding of plugin.manifest.contributes.keybindings) {
|
|
const command = binding.command
|
|
Object.defineProperty(binding, 'command', {
|
|
get: () => {
|
|
reads++
|
|
return command
|
|
}
|
|
})
|
|
}
|
|
const registry = new PluginCommandRegistry()
|
|
registry.reconcile([plugin], () => false)
|
|
const preview = registry.preview(plugin.pluginKey)
|
|
expect(preview.map((command) => command.id)).toEqual(commands.map((command) => command.id))
|
|
expect(preview[0].keybindings.map((binding) => binding.key)).toEqual(
|
|
plugin.manifest.contributes.keybindings
|
|
.filter((_, index) => index % 32 === 0)
|
|
.map((binding) => binding.key)
|
|
)
|
|
expect(preview[255].keybindings).toEqual([])
|
|
expect(registry.list()).toEqual([])
|
|
expect(reads).toBe(uniqueKeys.length)
|
|
})
|
|
|
|
it('records each conflicting owner once instead of every pair', () => {
|
|
const plugins = Array.from({ length: 128 }, (_, index) =>
|
|
commandPlugin(`plugin-${index}`, {
|
|
commands: [{ id: 'tasks', title: 'Tasks', action: 'view.tasks' }],
|
|
keybindings: [{ command: 'tasks', key: 'Mod+Alt+T' }]
|
|
})
|
|
)
|
|
const registry = new PluginCommandRegistry()
|
|
const errors = (registry as unknown as { errors: Map<string, string> }).errors
|
|
const writes = vi.spyOn(errors, 'set')
|
|
registry.reconcile(plugins, () => true)
|
|
expect(registry.list()).toEqual([])
|
|
for (const plugin of plugins) {
|
|
expect(registry.preview(plugin.pluginKey)).toHaveLength(1)
|
|
expect(registry.error(plugin.pluginKey)).toBe(
|
|
'plugin keybinding Mod+Alt+T conflicts with another plugin'
|
|
)
|
|
}
|
|
expect(writes).toHaveBeenCalledTimes(plugins.length)
|
|
})
|
|
|
|
it('preserves the last conflicting spelling for repeated owners and chord groups', () => {
|
|
const plugin = commandPlugin('repeat', {
|
|
commands: [
|
|
{ id: 'one', title: 'One', action: 'view.tasks' },
|
|
{ id: 'two', title: 'Two', action: 'view.tasks', context: 'worktree' }
|
|
]
|
|
})
|
|
const registry = new PluginCommandRegistry()
|
|
registry.reconcile(
|
|
[plugin],
|
|
() => true,
|
|
{
|
|
'plugin:orca-samples.repeat/one': ['Mod+Alt+T', 'Mod+Alt+Y'],
|
|
'plugin:orca-samples.repeat/two': ['Ctrl+Alt+T', 'Ctrl+Alt+Y']
|
|
},
|
|
'linux'
|
|
)
|
|
expect(registry.list()).toEqual([])
|
|
expect(registry.error(plugin.pluginKey)).toBe(
|
|
'plugin keybinding Ctrl+Alt+Y conflicts with another plugin'
|
|
)
|
|
})
|
|
|
|
it('retains pending previews and exposes only approved commands', () => {
|
|
const plugin = commandPlugin('aliases', {
|
|
commands: [{ id: 'tasks', title: 'Open Tasks', action: 'view.tasks' }],
|
|
keybindings: [{ command: 'tasks', key: 'mod+alt+t' }]
|
|
})
|
|
const registry = new PluginCommandRegistry()
|
|
|
|
registry.reconcile([plugin], () => false)
|
|
expect(registry.list()).toEqual([])
|
|
expect(registry.preview(plugin.pluginKey)).toEqual([
|
|
{
|
|
pluginKey: plugin.pluginKey,
|
|
id: 'tasks',
|
|
title: 'Open Tasks',
|
|
context: 'global',
|
|
handler: { type: 'built-in', action: 'view.tasks' },
|
|
keybindings: [{ key: 'Mod+Alt+T', when: 'global' }]
|
|
}
|
|
])
|
|
|
|
registry.reconcile([plugin], () => true)
|
|
expect(registry.list()).toHaveLength(1)
|
|
})
|
|
|
|
it('projects worker commands and inherited worktree keybinding context', () => {
|
|
const plugin = commandPlugin('worker', {
|
|
commands: [{ id: 'create', title: 'Create Task', context: 'worktree' }],
|
|
keybindings: [{ command: 'create', key: 'Mod+Shift+A' }]
|
|
})
|
|
const registry = new PluginCommandRegistry()
|
|
|
|
registry.reconcile([plugin], () => true)
|
|
|
|
expect(registry.list()).toMatchObject([
|
|
{
|
|
context: 'worktree',
|
|
handler: { type: 'worker' },
|
|
keybindings: [{ key: 'Mod+Shift+A', when: 'worktree' }]
|
|
}
|
|
])
|
|
})
|
|
|
|
it('errors approved plugins whose keybindings overlap', () => {
|
|
const global = commandPlugin('global', {
|
|
commands: [{ id: 'tasks', title: 'Tasks', action: 'view.tasks' }],
|
|
keybindings: [{ command: 'tasks', key: 'Mod+Alt+T', when: 'global' }]
|
|
})
|
|
const worktree = commandPlugin('worktree', {
|
|
commands: [{ id: 'tasks', title: 'Tasks', context: 'worktree', action: 'view.tasks' }],
|
|
keybindings: [{ command: 'tasks', key: 'Mod+Alt+T', when: 'worktree' }]
|
|
})
|
|
const registry = new PluginCommandRegistry()
|
|
|
|
registry.reconcile([global, worktree], () => true)
|
|
|
|
expect(registry.list()).toEqual([])
|
|
expect(registry.error(global.pluginKey)).toContain('conflicts')
|
|
expect(registry.error(worktree.pluginKey)).toContain('conflicts')
|
|
})
|
|
|
|
it('uses saved effective bindings to recover conflicting plugins', () => {
|
|
const first = commandPlugin('first', {
|
|
commands: [{ id: 'tasks', title: 'Tasks', action: 'view.tasks' }],
|
|
keybindings: [{ command: 'tasks', key: 'Mod+Alt+T' }]
|
|
})
|
|
const second = commandPlugin('second', {
|
|
commands: [{ id: 'tasks', title: 'Tasks', action: 'view.tasks' }],
|
|
keybindings: [{ command: 'tasks', key: 'Mod+Alt+T' }]
|
|
})
|
|
const registry = new PluginCommandRegistry()
|
|
|
|
registry.reconcile(
|
|
[first, second],
|
|
() => true,
|
|
{ 'plugin:orca-samples.first/tasks': ['Mod+Shift+T'] },
|
|
'linux'
|
|
)
|
|
|
|
expect(registry.list()).toHaveLength(2)
|
|
expect(registry.error(first.pluginKey)).toBeNull()
|
|
expect(registry.error(second.pluginKey)).toBeNull()
|
|
})
|
|
|
|
it('rejects conflicting saved bindings within one plugin', () => {
|
|
const plugin = commandPlugin('aliases', {
|
|
commands: [
|
|
{ id: 'tasks', title: 'Tasks', action: 'view.tasks' },
|
|
{ id: 'sidebar', title: 'Sidebar', action: 'sidebar.left.toggle' }
|
|
]
|
|
})
|
|
const registry = new PluginCommandRegistry()
|
|
|
|
registry.reconcile(
|
|
[plugin],
|
|
() => true,
|
|
{
|
|
'plugin:orca-samples.aliases/tasks': ['Mod+Alt+T'],
|
|
'plugin:orca-samples.aliases/sidebar': ['Mod+Alt+T']
|
|
},
|
|
'linux'
|
|
)
|
|
|
|
expect(registry.list()).toEqual([])
|
|
expect(registry.error(plugin.pluginKey)).toContain('conflicts')
|
|
})
|
|
|
|
it('detects cross-platform Mod and physical Ctrl conflicts', () => {
|
|
const portable = commandPlugin('portable', {
|
|
commands: [{ id: 'tasks', title: 'Tasks', action: 'view.tasks' }],
|
|
keybindings: [{ command: 'tasks', key: 'Mod+Alt+T' }]
|
|
})
|
|
const physical = commandPlugin('physical', {
|
|
commands: [{ id: 'tasks', title: 'Tasks', action: 'view.tasks' }],
|
|
keybindings: [{ command: 'tasks', key: 'Ctrl+Alt+T' }]
|
|
})
|
|
const registry = new PluginCommandRegistry()
|
|
|
|
registry.reconcile([portable, physical], () => true, {}, 'linux')
|
|
|
|
expect(registry.list()).toEqual([])
|
|
expect(registry.error(portable.pluginKey)).toContain('conflicts')
|
|
expect(registry.error(physical.pluginKey)).toContain('conflicts')
|
|
})
|
|
|
|
it('allows the same worktree-only chord after one plugin is disabled', () => {
|
|
const first = commandPlugin('first', {
|
|
commands: [{ id: 'tasks', title: 'Tasks', context: 'worktree', action: 'view.tasks' }],
|
|
keybindings: [{ command: 'tasks', key: 'Mod+Alt+T' }]
|
|
})
|
|
const second = commandPlugin('second', {
|
|
commands: [{ id: 'tasks', title: 'Tasks', context: 'worktree', action: 'view.tasks' }],
|
|
keybindings: [{ command: 'tasks', key: 'Mod+Alt+T' }]
|
|
})
|
|
const registry = new PluginCommandRegistry()
|
|
|
|
registry.reconcile([first, second], (plugin) => plugin === first)
|
|
|
|
expect(registry.list()).toHaveLength(1)
|
|
expect(registry.error(first.pluginKey)).toBeNull()
|
|
})
|
|
})
|