Files
gerrit/polygerrit-ui/app/mixins/keyboard-shortcut-mixin/keyboard-shortcut-mixin_test.js
Ben Rohlfs 4add6f7ffd Prevent mark-reviewed checkbox from stealing focus on click
We want to allow users to click the checkbox and then follow-up with
keyboard shortcuts, e.g. for going to the next file.

Change-Id: I5722f518150fca27bd66d085d79b0b73dec468b3
2020-09-24 09:31:11 +02:00

453 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @license
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import '../../test/common-test-setup-karma.js';
import {
KeyboardShortcutMixin, Shortcut,
ShortcutManager, ShortcutSection, SPECIAL_SHORTCUT,
} from './keyboard-shortcut-mixin.js';
import {html} from '@polymer/polymer/lib/utils/html-tag.js';
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
const basicFixture =
fixtureFromElement('keyboard-shortcut-mixin-test-element');
const withinOverlayFixture = fixtureFromTemplate(html`
<gr-overlay>
<keyboard-shortcut-mixin-test-element>
</keyboard-shortcut-mixin-test-element>
</gr-overlay>
`);
class GrKeyboardShortcutMixinTestElement extends
KeyboardShortcutMixin(PolymerElement) {
static get is() {
return 'keyboard-shortcut-mixin-test-element';
}
get keyBindings() {
return {
k: '_handleKey',
enter: '_handleKey',
};
}
_handleKey() {}
}
customElements.define(GrKeyboardShortcutMixinTestElement.is,
GrKeyboardShortcutMixinTestElement);
suite('keyboard-shortcut-mixin tests', () => {
let element;
let overlay;
setup(() => {
element = basicFixture.instantiate();
overlay = withinOverlayFixture.instantiate();
});
suite('ShortcutManager', () => {
test('bindings management', () => {
const mgr = new ShortcutManager();
const NEXT_FILE = Shortcut.NEXT_FILE;
assert.isUndefined(mgr.getBindingsForShortcut(NEXT_FILE));
mgr.bindShortcut(NEXT_FILE, ']', '}', 'right');
assert.deepEqual(
mgr.getBindingsForShortcut(NEXT_FILE),
[']', '}', 'right']);
});
test('getShortcut', () => {
const mgr = new ShortcutManager();
const NEXT_FILE = Shortcut.NEXT_FILE;
assert.isUndefined(mgr.getBindingsForShortcut(NEXT_FILE));
mgr.bindShortcut(NEXT_FILE, ']', '}', 'right');
assert.equal(mgr.getShortcut(NEXT_FILE), '],},→');
});
test('getShortcut with modifiers', () => {
const mgr = new ShortcutManager();
const NEXT_FILE = Shortcut.NEXT_FILE;
assert.isUndefined(mgr.getBindingsForShortcut(NEXT_FILE));
mgr.bindShortcut(NEXT_FILE, 'Shift+a:key');
assert.equal(mgr.getShortcut(NEXT_FILE), 'Shift+a');
});
suite('binding descriptions', () => {
function mapToObject(m) {
const o = {};
m.forEach((v, k) => o[k] = v);
return o;
}
test('single combo description', () => {
const mgr = new ShortcutManager();
assert.deepEqual(mgr.describeBinding('a'), ['a']);
assert.deepEqual(mgr.describeBinding('a:keyup'), ['a']);
assert.deepEqual(mgr.describeBinding('ctrl+a'), ['Ctrl', 'a']);
assert.deepEqual(
mgr.describeBinding('ctrl+shift+up:keyup'),
['Ctrl', 'Shift', '↑']);
});
test('combo set description', () => {
const mgr = new ShortcutManager();
assert.isNull(mgr.describeBindings(Shortcut.NEXT_FILE));
mgr.bindShortcut(Shortcut.GO_TO_OPENED_CHANGES,
SPECIAL_SHORTCUT.GO_KEY, 'o');
assert.deepEqual(
mgr.describeBindings(Shortcut.GO_TO_OPENED_CHANGES),
[['g', 'o']]);
mgr.bindShortcut(Shortcut.NEXT_FILE, SPECIAL_SHORTCUT.DOC_ONLY,
']', 'ctrl+shift+right:keyup');
assert.deepEqual(
mgr.describeBindings(Shortcut.NEXT_FILE),
[[']'], ['Ctrl', 'Shift', '→']]);
mgr.bindShortcut(Shortcut.PREV_FILE, '[');
assert.deepEqual(mgr.describeBindings(Shortcut.PREV_FILE), [['[']]);
});
test('combo set description width', () => {
const mgr = new ShortcutManager();
assert.strictEqual(mgr.comboSetDisplayWidth([['u']]), 1);
assert.strictEqual(mgr.comboSetDisplayWidth([['g', 'o']]), 2);
assert.strictEqual(mgr.comboSetDisplayWidth([['Shift', 'r']]), 6);
assert.strictEqual(mgr.comboSetDisplayWidth([['x'], ['y']]), 4);
assert.strictEqual(
mgr.comboSetDisplayWidth([['x'], ['y'], ['Shift', 'z']]),
12);
});
test('distribute shortcut help', () => {
const mgr = new ShortcutManager();
assert.deepEqual(mgr.distributeBindingDesc([['o']]), [[['o']]]);
assert.deepEqual(
mgr.distributeBindingDesc([['g', 'o']]),
[[['g', 'o']]]);
assert.deepEqual(
mgr.distributeBindingDesc([['ctrl', 'shift', 'meta', 'enter']]),
[[['ctrl', 'shift', 'meta', 'enter']]]);
assert.deepEqual(
mgr.distributeBindingDesc([
['ctrl', 'shift', 'meta', 'enter'],
['o'],
]),
[
[['ctrl', 'shift', 'meta', 'enter']],
[['o']],
]);
assert.deepEqual(
mgr.distributeBindingDesc([
['ctrl', 'enter'],
['meta', 'enter'],
['ctrl', 's'],
['meta', 's'],
]),
[
[['ctrl', 'enter'], ['meta', 'enter']],
[['ctrl', 's'], ['meta', 's']],
]);
});
test('active shortcuts by section', () => {
const mgr = new ShortcutManager();
mgr.bindShortcut(Shortcut.NEXT_FILE, ']');
mgr.bindShortcut(Shortcut.NEXT_LINE, 'j');
mgr.bindShortcut(Shortcut.GO_TO_OPENED_CHANGES, 'g+o');
mgr.bindShortcut(Shortcut.SEARCH, '/');
assert.deepEqual(
mapToObject(mgr.activeShortcutsBySection()),
{});
mgr.attachHost({
keyboardShortcuts() {
return {
[Shortcut.NEXT_FILE]: null,
};
},
});
assert.deepEqual(
mapToObject(mgr.activeShortcutsBySection()),
{
[ShortcutSection.NAVIGATION]: [
{shortcut: Shortcut.NEXT_FILE, text: 'Go to next file'},
],
});
mgr.attachHost({
keyboardShortcuts() {
return {
[Shortcut.NEXT_LINE]: null,
};
},
});
assert.deepEqual(
mapToObject(mgr.activeShortcutsBySection()),
{
[ShortcutSection.DIFFS]: [
{shortcut: Shortcut.NEXT_LINE, text: 'Go to next line'},
],
[ShortcutSection.NAVIGATION]: [
{shortcut: Shortcut.NEXT_FILE, text: 'Go to next file'},
],
});
mgr.attachHost({
keyboardShortcuts() {
return {
[Shortcut.SEARCH]: null,
[Shortcut.GO_TO_OPENED_CHANGES]: null,
};
},
});
assert.deepEqual(
mapToObject(mgr.activeShortcutsBySection()),
{
[ShortcutSection.DIFFS]: [
{shortcut: Shortcut.NEXT_LINE, text: 'Go to next line'},
],
[ShortcutSection.EVERYWHERE]: [
{shortcut: Shortcut.SEARCH, text: 'Search'},
{
shortcut: Shortcut.GO_TO_OPENED_CHANGES,
text: 'Go to Opened Changes',
},
],
[ShortcutSection.NAVIGATION]: [
{shortcut: Shortcut.NEXT_FILE, text: 'Go to next file'},
],
});
});
test('directory view', () => {
const mgr = new ShortcutManager();
mgr.bindShortcut(Shortcut.NEXT_FILE, ']');
mgr.bindShortcut(Shortcut.NEXT_LINE, 'j');
mgr.bindShortcut(Shortcut.GO_TO_OPENED_CHANGES,
SPECIAL_SHORTCUT.GO_KEY, 'o');
mgr.bindShortcut(Shortcut.SEARCH, '/');
mgr.bindShortcut(
Shortcut.SAVE_COMMENT, 'ctrl+enter', 'meta+enter',
'ctrl+s', 'meta+s');
assert.deepEqual(mapToObject(mgr.directoryView()), {});
mgr.attachHost({
keyboardShortcuts() {
return {
[Shortcut.GO_TO_OPENED_CHANGES]: null,
[Shortcut.NEXT_FILE]: null,
[Shortcut.NEXT_LINE]: null,
[Shortcut.SAVE_COMMENT]: null,
[Shortcut.SEARCH]: null,
};
},
});
assert.deepEqual(
mapToObject(mgr.directoryView()),
{
[ShortcutSection.DIFFS]: [
{binding: [['j']], text: 'Go to next line'},
{
binding: [['Ctrl', 'Enter'], ['Meta', 'Enter']],
text: 'Save comment',
},
{
binding: [['Ctrl', 's'], ['Meta', 's']],
text: 'Save comment',
},
],
[ShortcutSection.EVERYWHERE]: [
{binding: [['/']], text: 'Search'},
{binding: [['g', 'o']], text: 'Go to Opened Changes'},
],
[ShortcutSection.NAVIGATION]: [
{binding: [[']']], text: 'Go to next file'},
],
});
});
});
});
test('doesnt block kb shortcuts for non-allowed els', done => {
const divEl = document.createElement('div');
element.appendChild(divEl);
element._handleKey = e => {
assert.isFalse(element.shouldSuppressKeyboardShortcut(e));
done();
};
MockInteractions.keyDownOn(divEl, 75, null, 'k');
});
test('blocks kb shortcuts for input els', done => {
const inputEl = document.createElement('input');
element.appendChild(inputEl);
element._handleKey = e => {
assert.isTrue(element.shouldSuppressKeyboardShortcut(e));
done();
};
MockInteractions.keyDownOn(inputEl, 75, null, 'k');
});
test('doesnt block kb shortcuts for checkboxes', done => {
const inputEl = document.createElement('input');
inputEl.setAttribute('type', 'checkbox');
element.appendChild(inputEl);
element._handleKey = e => {
assert.isFalse(element.shouldSuppressKeyboardShortcut(e));
done();
};
MockInteractions.keyDownOn(inputEl, 75, null, 'k');
});
test('blocks kb shortcuts for textarea els', done => {
const textareaEl = document.createElement('textarea');
element.appendChild(textareaEl);
element._handleKey = e => {
assert.isTrue(element.shouldSuppressKeyboardShortcut(e));
done();
};
MockInteractions.keyDownOn(textareaEl, 75, null, 'k');
});
test('blocks kb shortcuts for anything in a gr-overlay', done => {
const divEl = document.createElement('div');
const element =
overlay.querySelector('keyboard-shortcut-mixin-test-element');
element.appendChild(divEl);
element._handleKey = e => {
assert.isTrue(element.shouldSuppressKeyboardShortcut(e));
done();
};
MockInteractions.keyDownOn(divEl, 75, null, 'k');
});
test('blocks enter shortcut on an anchor', done => {
const anchorEl = document.createElement('a');
const element =
overlay.querySelector('keyboard-shortcut-mixin-test-element');
element.appendChild(anchorEl);
element._handleKey = e => {
assert.isTrue(element.shouldSuppressKeyboardShortcut(e));
done();
};
MockInteractions.keyDownOn(anchorEl, 13, null, 'enter');
});
test('modifierPressed returns accurate values', () => {
const spy = sinon.spy(element, 'modifierPressed');
element._handleKey = e => {
element.modifierPressed(e);
};
MockInteractions.keyDownOn(element, 75, 'shift', 'k');
assert.isTrue(spy.lastCall.returnValue);
MockInteractions.keyDownOn(element, 75, null, 'k');
assert.isFalse(spy.lastCall.returnValue);
MockInteractions.keyDownOn(element, 75, 'ctrl', 'k');
assert.isTrue(spy.lastCall.returnValue);
MockInteractions.keyDownOn(element, 75, null, 'k');
assert.isFalse(spy.lastCall.returnValue);
MockInteractions.keyDownOn(element, 75, 'meta', 'k');
assert.isTrue(spy.lastCall.returnValue);
MockInteractions.keyDownOn(element, 75, null, 'k');
assert.isFalse(spy.lastCall.returnValue);
MockInteractions.keyDownOn(element, 75, 'alt', 'k');
assert.isTrue(spy.lastCall.returnValue);
});
test('isModifierPressed returns accurate value', () => {
const spy = sinon.spy(element, 'isModifierPressed');
element._handleKey = e => {
element.isModifierPressed(e, 'shiftKey');
};
MockInteractions.keyDownOn(element, 75, 'shift', 'k');
assert.isTrue(spy.lastCall.returnValue);
MockInteractions.keyDownOn(element, 75, null, 'k');
assert.isFalse(spy.lastCall.returnValue);
MockInteractions.keyDownOn(element, 75, 'ctrl', 'k');
assert.isFalse(spy.lastCall.returnValue);
MockInteractions.keyDownOn(element, 75, null, 'k');
assert.isFalse(spy.lastCall.returnValue);
MockInteractions.keyDownOn(element, 75, 'meta', 'k');
assert.isFalse(spy.lastCall.returnValue);
MockInteractions.keyDownOn(element, 75, null, 'k');
assert.isFalse(spy.lastCall.returnValue);
MockInteractions.keyDownOn(element, 75, 'alt', 'k');
assert.isFalse(spy.lastCall.returnValue);
});
suite('GO_KEY timing', () => {
let handlerStub;
setup(() => {
element._shortcut_go_table.set('a', '_handleA');
handlerStub = element._handleA = sinon.stub();
sinon.stub(Date, 'now').returns(10000);
});
test('success', () => {
const e = {detail: {key: 'a'}, preventDefault: () => {}};
sinon.stub(element, 'shouldSuppressKeyboardShortcut').returns(false);
element._shortcut_go_key_last_pressed = 9000;
element._handleGoAction(e);
assert.isTrue(handlerStub.calledOnce);
assert.strictEqual(handlerStub.lastCall.args[0], e);
});
test('go key not pressed', () => {
const e = {detail: {key: 'a'}, preventDefault: () => {}};
sinon.stub(element, 'shouldSuppressKeyboardShortcut').returns(false);
element._shortcut_go_key_last_pressed = null;
element._handleGoAction(e);
assert.isFalse(handlerStub.called);
});
test('go key pressed too long ago', () => {
const e = {detail: {key: 'a'}, preventDefault: () => {}};
sinon.stub(element, 'shouldSuppressKeyboardShortcut').returns(false);
element._shortcut_go_key_last_pressed = 3000;
element._handleGoAction(e);
assert.isFalse(handlerStub.called);
});
test('should suppress', () => {
const e = {detail: {key: 'a'}, preventDefault: () => {}};
sinon.stub(element, 'shouldSuppressKeyboardShortcut').returns(true);
element._shortcut_go_key_last_pressed = 9000;
element._handleGoAction(e);
assert.isFalse(handlerStub.called);
});
test('unrecognized key', () => {
const e = {detail: {key: 'f'}, preventDefault: () => {}};
sinon.stub(element, 'shouldSuppressKeyboardShortcut').returns(false);
element._shortcut_go_key_last_pressed = 9000;
element._handleGoAction(e);
assert.isFalse(handlerStub.called);
});
});
});