diff --git a/addons/web_editor/static/src/js/editor/odoo-editor/test/editor-test.js b/addons/web_editor/static/src/js/editor/odoo-editor/test/editor-test.js
index b081311b8e2..a708c96a551 100644
--- a/addons/web_editor/static/src/js/editor/odoo-editor/test/editor-test.js
+++ b/addons/web_editor/static/src/js/editor/odoo-editor/test/editor-test.js
@@ -12,6 +12,7 @@ import './spec/fontAwesome.test.js';
import './spec/autostep.test.js';
import './spec/urlRegex.test.js';
import './spec/collab.test.js';
+import './spec/powerbox.test.js';
/* global mocha */
mocha.run(failures => {
diff --git a/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/powerbox.test.js b/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/powerbox.test.js
new file mode 100644
index 00000000000..a0d79ae9b81
--- /dev/null
+++ b/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/powerbox.test.js
@@ -0,0 +1,284 @@
+import { setSelection } from '../../src/OdooEditor.js';
+import { Powerbox } from '../../src/powerbox/Powerbox.js';
+import { BasicEditor, insertText, testEditor, triggerEvent } from '../utils.js';
+
+const getCurrentCommandNames = powerbox => {
+ return [...powerbox.el.querySelectorAll('.oe-powerbox-commandName')].map(c => c.innerText);
+}
+
+describe('Powerbox', () => {
+ describe('integration', () => {
+ it('should open the Powerbox on type `/`', async () => {
+ await testEditor(BasicEditor, {
+ contentBefore: '
ab[]
',
+ stepFunction: async editor => {
+ window.chai.expect(editor.powerbox.isOpen).to.eql(false);
+ window.chai.expect(editor.powerbox.el.style.display).to.eql('none');
+ await insertText(editor, '/');
+ window.chai.expect(editor.powerbox.isOpen).to.eql(true);
+ window.chai.expect(editor.powerbox.el.style.display).not.to.eql('none');
+ },
+ });
+ });
+ it('should filter the Powerbox contents with term', async () => {
+ await testEditor(BasicEditor, {
+ contentBefore: 'ab[]
',
+ stepFunction: async editor => {
+ await insertText(editor, '/');
+ await insertText(editor, 'head');
+ triggerEvent(editor.editable, 'keyup');
+ window.chai.expect(getCurrentCommandNames(editor.powerbox)).to.eql(['Heading 1', 'Heading 2', 'Heading 3']);
+ },
+ });
+ });
+ it('should execute command and remove term and hot character on Enter', async () => {
+ await testEditor(BasicEditor, {
+ contentBefore: 'ab[]
',
+ stepFunction: async editor => {
+ editor.powerbox.el.classList.add('yo');
+ await insertText(editor, '/');
+ await insertText(editor, 'head');
+ triggerEvent(editor.editable, 'keyup');
+ triggerEvent(editor.editable, 'keydown', { key: 'Enter' });
+ },
+ contentAfter: 'ab[]
',
+ });
+ });
+ });
+ describe('class', () => {
+ it('should properly order default commands and categories', async () => {
+ const editable = document.createElement('div');
+ document.body.append(editable);
+ const powerbox = new Powerbox({
+ categories: [
+ {name: 'a', priority: 2},
+ {name: 'b', priority: 4},
+ {name: 'c', priority: 1}, // redefined lower -> ignore
+ {name: 'd', priority: 4}, // same as b -> alphabetical
+ {name: 'c', priority: 3},
+ ],
+ commands: [
+ {category: 'f', name: 'f1'}, // category doesn't exist -> end
+ {category: 'e', name: 'e1'}, // category doesn't exist -> end
+ {category: 'a', name: 'a1', priority: 1},
+ {category: 'a', name: 'a4', priority: 3},
+ {category: 'a', name: 'a2', priority: 3},
+ {category: 'a', name: 'a3', priority: 2},
+ {category: 'b', name: 'b3'}, // no priority -> priority 1
+ {category: 'b', name: 'b1'},
+ {category: 'b', name: 'b2'},
+ {category: 'c', name: 'c1'},
+ {category: 'd', name: 'd1'},
+ ],
+ editable,
+ });
+ powerbox.open();
+ window.chai.expect(getCurrentCommandNames(powerbox)).to.eql(
+ ['b1', 'b2', 'b3', 'd1', 'c1', 'a2', 'a4', 'a3', 'a1', 'e1', 'f1']
+ );
+ powerbox.destroy();
+ editable.remove();
+ });
+ it('should navigate through commands with arrow keys', async () => {
+ const editable = document.createElement('div');
+ document.body.append(editable);
+ const powerbox = new Powerbox({
+ categories: [],
+ commands: [
+ {category: 'a', name: '2'},
+ {category: 'a', name: '3'},
+ {category: 'a', name: '1'},
+ ],
+ editable,
+ });
+ powerbox.open();
+ window.chai.expect(powerbox._context.selectedCommand.name).to.eql('1');
+ triggerEvent(editable, 'keydown', { key: 'ArrowDown'});
+ window.chai.expect(powerbox._context.selectedCommand.name).to.eql('2');
+ triggerEvent(editable, 'keydown', { key: 'ArrowDown'});
+ window.chai.expect(powerbox._context.selectedCommand.name).to.eql('3');
+ triggerEvent(editable, 'keydown', { key: 'ArrowUp'});
+ window.chai.expect(powerbox._context.selectedCommand.name).to.eql('2');
+ triggerEvent(editable, 'keydown', { key: 'ArrowUp'});
+ window.chai.expect(powerbox._context.selectedCommand.name).to.eql('1');
+ powerbox.destroy();
+ editable.remove();
+ });
+ it('should execute command on press Enter', async () => {
+ const editable = document.createElement('div');
+ document.body.append(editable);
+ const powerbox = new Powerbox({
+ categories: [],
+ commands: [
+ {category: 'a', name: '2', callback: () => editable.innerText = '2'},
+ {category: 'a', name: '3', callback: () => editable.innerText = '3'},
+ {category: 'a', name: '1', callback: () => editable.innerText = '1'},
+ ],
+ editable,
+ });
+ powerbox.open();
+ window.chai.expect(editable.innerText).to.eql('');
+ triggerEvent(editable, 'keydown', { key: 'Enter'});
+ window.chai.expect(editable.innerText).to.eql('1');
+ triggerEvent(editable, 'keydown', { key: 'ArrowDown'});
+ triggerEvent(editable, 'keydown', { key: 'Enter'});
+ window.chai.expect(editable.innerText).to.eql('2');
+ powerbox.destroy();
+ editable.remove();
+ });
+ it('should filter commands with `commandFilters`', async () => {
+ const editable = document.createElement('div');
+ document.body.append(editable);
+ const powerbox = new Powerbox({
+ categories: [],
+ commands: [
+ {category: 'a', name: 'a2'},
+ {category: 'a', name: 'a3'},
+ {category: 'a', name: 'a1'},
+ {category: 'b', name: 'b2x'},
+ {category: 'b', name: 'b3x'},
+ {category: 'b', name: 'b1y'},
+ ],
+ editable,
+ commandFilters: [commands => commands.filter(command => command.category === 'b')],
+ });
+ powerbox.open();
+ window.chai.expect(getCurrentCommandNames(powerbox)).to.eql(['b1y', 'b2x', 'b3x']);
+ powerbox.close();
+ powerbox.commandFilters.push(commands => commands.filter(command => command.name.includes('x')))
+ powerbox.open();
+ window.chai.expect(getCurrentCommandNames(powerbox)).to.eql(['b2x', 'b3x']);
+ powerbox.destroy();
+ editable.remove();
+ });
+ it('should filter commands with `isDisabled`', async () => {
+ const editable = document.createElement('div');
+ document.body.append(editable);
+ let disableCommands = false;
+ const powerbox = new Powerbox({
+ categories: [],
+ commands: [
+ {category: 'a', name: 'a2', isDisabled: () => disableCommands},
+ {category: 'a', name: 'a3', isDisabled: () => disableCommands},
+ {category: 'a', name: 'a1', isDisabled: () => disableCommands},
+ {category: 'b', name: 'b2x'},
+ {category: 'b', name: 'b3x'},
+ {category: 'b', name: 'b1y'},
+ ],
+ editable,
+ });
+ powerbox.open();
+ window.chai.expect(getCurrentCommandNames(powerbox)).to.eql(['a1', 'a2', 'a3', 'b1y', 'b2x', 'b3x']);
+ powerbox.close();
+ disableCommands = true;
+ powerbox.open();
+ window.chai.expect(getCurrentCommandNames(powerbox)).to.eql(['b1y', 'b2x', 'b3x']);
+ powerbox.close();
+ disableCommands = false;
+ powerbox.open();
+ window.chai.expect(getCurrentCommandNames(powerbox)).to.eql(['a1', 'a2', 'a3', 'b1y', 'b2x', 'b3x']);
+ powerbox.destroy();
+ editable.remove();
+ });
+ it('should filter commands with filter text', async () => {
+ const editable = document.createElement('div');
+ document.body.append(editable);
+ editable.append(document.createTextNode('original text'));
+ setSelection(editable.firstChild, 13);
+ const powerbox = new Powerbox({
+ categories: [],
+ commands: [
+ {category: 'a', name: 'a2'},
+ {category: 'a', name: 'a3'},
+ {category: 'a', name: 'a1'},
+ {category: 'b', name: 'b2x'},
+ {category: 'b', name: 'b3x'},
+ {category: 'b', name: 'b1y'},
+ ],
+ editable,
+ });
+ powerbox.open();
+ window.chai.expect(powerbox._context.initialValue).to.eql('original text');
+ window.chai.expect(getCurrentCommandNames(powerbox)).to.eql(['a1', 'a2', 'a3', 'b1y', 'b2x', 'b3x']);
+ // filter: '1'
+ editable.append(document.createTextNode('1'));
+ triggerEvent(editable, 'keyup');
+ window.chai.expect(getCurrentCommandNames(powerbox)).to.eql(['a1', 'b1y']);
+ // filter: ''
+ editable.lastChild.remove();
+ triggerEvent(editable, 'keyup');
+ window.chai.expect(getCurrentCommandNames(powerbox)).to.eql(['a1', 'a2', 'a3', 'b1y', 'b2x', 'b3x']);
+ // filter: 'a'
+ editable.append(document.createTextNode('a'));
+ triggerEvent(editable, 'keyup'); // filter: 'a'.
+ window.chai.expect(getCurrentCommandNames(powerbox)).to.eql(['a1', 'a2', 'a3']);
+ editable.append(document.createTextNode('1'));
+ triggerEvent(editable, 'keyup'); // filter: 'a1'.
+ window.chai.expect(getCurrentCommandNames(powerbox)).to.eql(['a1']);
+ powerbox.destroy();
+ editable.remove();
+ });
+ it('should close the Powerbox on remove last filter text with Backspace', async () => {
+ const editable = document.createElement('div');
+ document.body.append(editable);
+ editable.append(document.createTextNode('1'));
+ setSelection(editable.firstChild, 13);
+ const powerbox = new Powerbox({
+ categories: [],
+ commands: [
+ {category: 'a', name: 'a2'},
+ {category: 'a', name: 'a3'},
+ {category: 'a', name: 'a1'},
+ {category: 'b', name: 'b2x'},
+ {category: 'b', name: 'b3x'},
+ {category: 'b', name: 'b1y'},
+ ],
+ editable,
+ });
+ powerbox.open();
+ // Text: '1'
+ window.chai.expect(powerbox._context.initialValue).to.eql('1');
+ window.chai.expect(getCurrentCommandNames(powerbox)).to.eql(['a1', 'a2', 'a3', 'b1y', 'b2x', 'b3x']);
+ // Text: '1y' -> filter: 'y'
+ editable.append(document.createTextNode('x'));
+ triggerEvent(editable, 'keyup');
+ window.chai.expect(getCurrentCommandNames(powerbox)).to.eql(['b2x', 'b3x']);
+ // Text: '1'
+ editable.lastChild.remove();
+ triggerEvent(editable, 'keydown', { key: 'Backspace' });
+ triggerEvent(editable, 'keyup');
+ window.chai.expect(getCurrentCommandNames(powerbox)).to.eql(['a1', 'a2', 'a3', 'b1y', 'b2x', 'b3x']);
+ window.chai.expect(powerbox.isOpen).to.eql(true);
+ window.chai.expect(powerbox.el.style.display).not.to.eql('none');
+ // Text: ''
+ editable.lastChild.remove();
+ triggerEvent(editable, 'keydown', { key: 'Backspace' });
+ triggerEvent(editable, 'keyup');
+ window.chai.expect(powerbox.isOpen).to.eql(false);
+ window.chai.expect(powerbox.el.style.display).to.eql('none');
+ powerbox.destroy();
+ editable.remove();
+ });
+ it('should close the Powerbox on press Escape', async () => {
+ const editable = document.createElement('div');
+ document.body.append(editable);
+ const powerbox = new Powerbox({
+ categories: [],
+ commands: [
+ {category: 'a', name: '2'},
+ {category: 'a', name: '3'},
+ {category: 'a', name: '1'},
+ ],
+ editable,
+ });
+ powerbox.open();
+ window.chai.expect(powerbox.isOpen).to.eql(true);
+ window.chai.expect(powerbox.el.style.display).not.to.eql('none');
+ triggerEvent(editable, 'keydown', { key: 'Escape' });
+ window.chai.expect(powerbox.isOpen).to.eql(false);
+ window.chai.expect(powerbox.el.style.display).to.eql('none');
+ powerbox.destroy();
+ editable.remove();
+ });
+ });
+});