Fix the default options for keyboard shortcuts
Change 339776 has introduced a new option `do not prevent default` for
keyboard shortcuts. The default options object was set to
`{shouldSuppress: true, doNotPrevent: false}`, and the author thought
that passing `{doNotPrevent: false}` would retain the default of
`shouldSuppress`, but of course it does not.
So keyboard shortcuts `j` and `k` were not suppressed properly anymore
when the user types into input elements.
The fix is to avoid a default options object, but instead calculate
defaults individually.
Release-Notes: skip
Change-Id: I7397cbd85f215c3197754b4aea3ef963e1834f5c
diff --git a/polygerrit-ui/app/services/shortcuts/shortcuts-service.ts b/polygerrit-ui/app/services/shortcuts/shortcuts-service.ts
index 09cebd0..8c17578 100644
--- a/polygerrit-ui/app/services/shortcuts/shortcuts-service.ts
+++ b/polygerrit-ui/app/services/shortcuts/shortcuts-service.ts
@@ -84,7 +84,8 @@
private comboKeyLastPressed: {key?: ComboKey; timestampMs?: number} = {};
/** Keeps track of the corresponding user preference. */
- private shortcutsDisabled = false;
+ // visible for testing
+ shortcutsDisabled = false;
private readonly keydownListener: (e: KeyboardEvent) => void;
@@ -155,11 +156,10 @@
element: HTMLElement,
shortcut: Binding,
listener: (e: KeyboardEvent) => void,
- options: ShortcutOptions = {
- shouldSuppress: true,
- doNotPrevent: false,
- }
+ options?: ShortcutOptions
) {
+ const optShouldSuppress = options?.shouldSuppress ?? true;
+ const optDoNotPrevent = options?.doNotPrevent ?? false;
const wrappedListener = (e: KeyboardEvent) => {
if (e.repeat && !shortcut.allowRepeat) return;
if (!eventMatchesShortcut(e, shortcut)) return;
@@ -168,13 +168,13 @@
} else {
if (this.isInComboKeyMode()) return;
}
- if (options.shouldSuppress && shouldSuppress(e)) return;
+ if (optShouldSuppress && shouldSuppress(e)) return;
// `shortcutsDisabled` refers to disabling global shortcuts like 'n'. If
// `shouldSuppress` is false (e.g.for Ctrl - ENTER), then don't disable
// the shortcut.
- if (options.shouldSuppress && this.shortcutsDisabled) return;
- if (!options.doNotPrevent) e.preventDefault();
- if (!options.doNotPrevent) e.stopPropagation();
+ if (optShouldSuppress && this.shortcutsDisabled) return;
+ if (!optDoNotPrevent) e.preventDefault();
+ if (!optDoNotPrevent) e.stopPropagation();
this.reportTriggered(e);
listener(e);
};
diff --git a/polygerrit-ui/app/services/shortcuts/shortcuts-service_test.ts b/polygerrit-ui/app/services/shortcuts/shortcuts-service_test.ts
index 69c371e..35da84e 100644
--- a/polygerrit-ui/app/services/shortcuts/shortcuts-service_test.ts
+++ b/polygerrit-ui/app/services/shortcuts/shortcuts-service_test.ts
@@ -10,9 +10,12 @@
ShortcutsService,
} from '../../services/shortcuts/shortcuts-service';
import {Shortcut, ShortcutSection} from './shortcuts-config';
-import {SinonFakeTimers} from 'sinon';
-import {Key, Modifier} from '../../utils/dom-util';
+import {SinonFakeTimers, SinonSpy} from 'sinon';
+import {Binding, Key, Modifier} from '../../utils/dom-util';
import {getAppContext} from '../app-context';
+import {pressKey} from '../../test/test-utils';
+
+const KEY_A: Binding = {key: 'a'};
suite('shortcuts-service tests', () => {
let service: ShortcutsService;
@@ -34,6 +37,64 @@
);
});
+ suite('addShortcut()', () => {
+ let el: HTMLElement;
+ let listener: SinonSpy<[KeyboardEvent], void>;
+
+ setup(() => {
+ el = document.createElement('div');
+ listener = sinon.spy() as SinonSpy<[KeyboardEvent], void>;
+ });
+
+ test('standard call', () => {
+ service.addShortcut(el, KEY_A, listener);
+ pressKey(el, KEY_A.key);
+ assert.isTrue(listener.calledOnce);
+ });
+
+ test('doNotPrevent option default false', () => {
+ service.addShortcut(el, KEY_A, listener);
+ pressKey(el, KEY_A.key);
+ assert.isTrue(listener.calledOnce);
+ assert.isTrue(listener.lastCall.firstArg?.defaultPrevented);
+ });
+
+ test('doNotPrevent option force false', () => {
+ service.addShortcut(el, KEY_A, listener, {doNotPrevent: false});
+ pressKey(el, KEY_A.key);
+ assert.isTrue(listener.calledOnce);
+ assert.isTrue(listener.lastCall.firstArg?.defaultPrevented);
+ });
+
+ test('doNotPrevent option force true', () => {
+ service.addShortcut(el, KEY_A, listener, {doNotPrevent: true});
+ pressKey(el, KEY_A.key);
+ assert.isTrue(listener.calledOnce);
+ assert.isFalse(listener.lastCall.firstArg?.defaultPrevented);
+ });
+
+ test('shouldSuppress option default true', () => {
+ service.shortcutsDisabled = true;
+ service.addShortcut(el, KEY_A, listener);
+ pressKey(el, KEY_A.key);
+ assert.isTrue(listener.notCalled);
+ });
+
+ test('shouldSuppress option force true', () => {
+ service.shortcutsDisabled = true;
+ service.addShortcut(el, KEY_A, listener, {shouldSuppress: true});
+ pressKey(el, KEY_A.key);
+ assert.isTrue(listener.notCalled);
+ });
+
+ test('shouldSuppress option force false', () => {
+ service.shortcutsDisabled = true;
+ service.addShortcut(el, KEY_A, listener, {shouldSuppress: false});
+ pressKey(el, KEY_A.key);
+ assert.isTrue(listener.calledOnce);
+ });
+ });
+
suite('binding descriptions', () => {
function mapToObject<K, V>(m: Map<K, V>) {
const o: any = {};
diff --git a/polygerrit-ui/app/test/test-utils.ts b/polygerrit-ui/app/test/test-utils.ts
index f7e6672..98c7c14 100644
--- a/polygerrit-ui/app/test/test-utils.ts
+++ b/polygerrit-ui/app/test/test-utils.ts
@@ -272,6 +272,7 @@
const eventOptions = {
key,
bubbles: true,
+ cancelable: true,
composed: true,
altKey: modifiers.includes(Modifier.ALT_KEY),
ctrlKey: modifiers.includes(Modifier.CTRL_KEY),