@theia/core
Version:
Theia is a cloud & desktop IDE framework implemented in TypeScript.
268 lines (212 loc) • 9.87 kB
text/typescript
// *****************************************************************************
// Copyright (C) 2026 EclipseSource GmbH and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { expect } from 'chai';
import { TernarySearchTree, KeySequenceIterator } from './ternary-search-tree';
import { KeyCode, KeySequence, Key, KeyModifier } from './keys';
describe('KeySequenceIterator', () => {
let iterator: KeySequenceIterator;
beforeEach(() => {
iterator = new KeySequenceIterator();
});
it('should reset and iterate over a single key sequence', () => {
const keySequence: KeySequence = [
KeyCode.createKeyCode({ first: Key.KEY_A, modifiers: [KeyModifier.CtrlCmd] })
];
iterator.reset(keySequence);
expect(iterator.value()).to.not.be.empty;
expect(iterator.hasNext()).to.be.false;
});
it('should iterate over a multi-key sequence (chord)', () => {
const keySequence: KeySequence = [
KeyCode.createKeyCode({ first: Key.KEY_K, modifiers: [KeyModifier.CtrlCmd] }),
KeyCode.createKeyCode({ first: Key.KEY_C, modifiers: [KeyModifier.CtrlCmd] })
];
iterator.reset(keySequence);
expect(iterator.hasNext()).to.be.true;
const firstValue = iterator.value();
expect(firstValue).to.not.be.empty;
iterator.next();
expect(iterator.hasNext()).to.be.false;
const secondValue = iterator.value();
expect(secondValue).to.not.be.empty;
expect(secondValue).to.not.equal(firstValue);
});
it('should compare correctly with cmp()', () => {
const keySequence: KeySequence = [
KeyCode.createKeyCode({ first: Key.KEY_A, modifiers: [KeyModifier.CtrlCmd] })
];
iterator.reset(keySequence);
const currentValue = iterator.value();
// Same value should return 0
expect(iterator.cmp(currentValue)).to.equal(0);
// Different value should return non-zero
expect(iterator.cmp('different')).to.not.equal(0);
});
});
describe('TernarySearchTree for KeySequences', () => {
let tree: TernarySearchTree<KeySequence, string>;
beforeEach(() => {
tree = TernarySearchTree.forKeySequences<string>();
});
it('should store and retrieve a single keybinding', () => {
const keySequence: KeySequence = [
KeyCode.createKeyCode({ first: Key.KEY_A, modifiers: [KeyModifier.CtrlCmd] })
];
tree.set(keySequence, 'command.a');
expect(tree.get(keySequence)).to.equal('command.a');
});
it('should store and retrieve multiple keybindings', () => {
const keySequenceA: KeySequence = [
KeyCode.createKeyCode({ first: Key.KEY_A, modifiers: [KeyModifier.CtrlCmd] })
];
const keySequenceB: KeySequence = [
KeyCode.createKeyCode({ first: Key.KEY_B, modifiers: [KeyModifier.CtrlCmd] })
];
tree.set(keySequenceA, 'command.a');
tree.set(keySequenceB, 'command.b');
expect(tree.get(keySequenceA)).to.equal('command.a');
expect(tree.get(keySequenceB)).to.equal('command.b');
});
it('should store and retrieve chord keybindings', () => {
const chordSequence: KeySequence = [
KeyCode.createKeyCode({ first: Key.KEY_K, modifiers: [KeyModifier.CtrlCmd] }),
KeyCode.createKeyCode({ first: Key.KEY_C, modifiers: [KeyModifier.CtrlCmd] })
];
tree.set(chordSequence, 'command.chord');
expect(tree.get(chordSequence)).to.equal('command.chord');
});
it('should return undefined for non-existent keybindings', () => {
const keySequence: KeySequence = [
KeyCode.createKeyCode({ first: Key.KEY_X, modifiers: [KeyModifier.CtrlCmd] })
];
expect(tree.get(keySequence)).to.be.undefined;
});
it('should find superstrings (partial matches for chords)', () => {
const prefixSequence: KeySequence = [
KeyCode.createKeyCode({ first: Key.KEY_K, modifiers: [KeyModifier.CtrlCmd] })
];
const chordSequence1: KeySequence = [
KeyCode.createKeyCode({ first: Key.KEY_K, modifiers: [KeyModifier.CtrlCmd] }),
KeyCode.createKeyCode({ first: Key.KEY_C })
];
const chordSequence2: KeySequence = [
KeyCode.createKeyCode({ first: Key.KEY_K, modifiers: [KeyModifier.CtrlCmd] }),
KeyCode.createKeyCode({ first: Key.KEY_D })
];
tree.set(chordSequence1, 'command.kc');
tree.set(chordSequence2, 'command.kd');
// The prefix should find both chords as superstrings
const superstrIterator = tree.findSuperstr(prefixSequence);
expect(superstrIterator).to.not.be.undefined;
const results: string[] = [];
if (superstrIterator) {
let result = superstrIterator.next();
while (!result.done) {
results.push(result.value);
result = superstrIterator.next();
}
}
expect(results).to.include('command.kc');
expect(results).to.include('command.kd');
});
it('should not find superstrings when none exist', () => {
const keySequenceA: KeySequence = [
KeyCode.createKeyCode({ first: Key.KEY_A, modifiers: [KeyModifier.CtrlCmd] })
];
const searchSequence: KeySequence = [
KeyCode.createKeyCode({ first: Key.KEY_B, modifiers: [KeyModifier.CtrlCmd] })
];
tree.set(keySequenceA, 'command.a');
const superstrIterator = tree.findSuperstr(searchSequence);
expect(superstrIterator).to.be.undefined;
});
it('should delete keybindings', () => {
const keySequence: KeySequence = [
KeyCode.createKeyCode({ first: Key.KEY_A, modifiers: [KeyModifier.CtrlCmd] })
];
tree.set(keySequence, 'command.a');
expect(tree.get(keySequence)).to.equal('command.a');
tree.delete(keySequence);
expect(tree.get(keySequence)).to.be.undefined;
});
it('should clear all keybindings', () => {
const keySequenceA: KeySequence = [
KeyCode.createKeyCode({ first: Key.KEY_A, modifiers: [KeyModifier.CtrlCmd] })
];
const keySequenceB: KeySequence = [
KeyCode.createKeyCode({ first: Key.KEY_B, modifiers: [KeyModifier.CtrlCmd] })
];
tree.set(keySequenceA, 'command.a');
tree.set(keySequenceB, 'command.b');
tree.clear();
expect(tree.get(keySequenceA)).to.be.undefined;
expect(tree.get(keySequenceB)).to.be.undefined;
});
it('should update existing keybinding value', () => {
const keySequence: KeySequence = [
KeyCode.createKeyCode({ first: Key.KEY_A, modifiers: [KeyModifier.CtrlCmd] })
];
tree.set(keySequence, 'command.original');
expect(tree.get(keySequence)).to.equal('command.original');
tree.set(keySequence, 'command.updated');
expect(tree.get(keySequence)).to.equal('command.updated');
});
it('should iterate over all keybindings with forEach', () => {
const keySequenceA: KeySequence = [
KeyCode.createKeyCode({ first: Key.KEY_A, modifiers: [KeyModifier.CtrlCmd] })
];
const keySequenceB: KeySequence = [
KeyCode.createKeyCode({ first: Key.KEY_B, modifiers: [KeyModifier.CtrlCmd] })
];
tree.set(keySequenceA, 'command.a');
tree.set(keySequenceB, 'command.b');
const results: string[] = [];
tree.forEach(value => {
results.push(value);
});
expect(results).to.have.lengthOf(2);
expect(results).to.include('command.a');
expect(results).to.include('command.b');
});
it('should handle keybindings with different modifiers', () => {
const ctrlA: KeySequence = [
KeyCode.createKeyCode({ first: Key.KEY_A, modifiers: [KeyModifier.CtrlCmd] })
];
const shiftA: KeySequence = [
KeyCode.createKeyCode({ first: Key.KEY_A, modifiers: [KeyModifier.Shift] })
];
const altA: KeySequence = [
KeyCode.createKeyCode({ first: Key.KEY_A, modifiers: [KeyModifier.Alt] })
];
tree.set(ctrlA, 'command.ctrl.a');
tree.set(shiftA, 'command.shift.a');
tree.set(altA, 'command.alt.a');
expect(tree.get(ctrlA)).to.equal('command.ctrl.a');
expect(tree.get(shiftA)).to.equal('command.shift.a');
expect(tree.get(altA)).to.equal('command.alt.a');
});
it('should store arrays of bindings for the same key sequence', () => {
const keySequence: KeySequence = [
KeyCode.createKeyCode({ first: Key.KEY_A, modifiers: [KeyModifier.CtrlCmd] })
];
const bindings = ['command.a1', 'command.a2', 'command.a3'];
const arrayTree = TernarySearchTree.forKeySequences<string[]>();
arrayTree.set(keySequence, bindings);
const result = arrayTree.get(keySequence);
expect(result).to.deep.equal(bindings);
expect(result).to.have.lengthOf(3);
});
});