blob: 5b38a8ab94caac445cd4c8d55d08597b8baee4a6 [file] [log] [blame]
Ben Rohlfs966586e2021-10-06 16:15:24 +02001/**
2 * @license
Ben Rohlfs94fcbbc2022-05-27 10:45:03 +02003 * Copyright 2021 Google LLC
4 * SPDX-License-Identifier: Apache-2.0
Ben Rohlfs966586e2021-10-06 16:15:24 +02005 */
Frank Bordenbe9451a2022-09-12 15:44:29 +02006import '../../test/common-test-setup';
Ben Rohlfsbe0c0ba2021-10-17 19:36:43 +02007import {
Ben Rohlfsbe0c0ba2021-10-17 19:36:43 +02008 COMBO_TIMEOUT_MS,
Ben Rohlfs410f5222021-10-19 23:44:02 +02009 describeBinding,
10 ShortcutsService,
Ben Rohlfsbe0c0ba2021-10-17 19:36:43 +020011} from '../../services/shortcuts/shortcuts-service';
Ben Rohlfs966586e2021-10-06 16:15:24 +020012import {Shortcut, ShortcutSection} from './shortcuts-config';
Ben Rohlfs1aeb1d32022-06-30 23:02:59 +020013import {SinonFakeTimers, SinonSpy} from 'sinon';
14import {Binding, Key, Modifier} from '../../utils/dom-util';
Chris Poucetefcdb202021-11-22 23:54:26 +010015import {getAppContext} from '../app-context';
Ben Rohlfs1aeb1d32022-06-30 23:02:59 +020016import {pressKey} from '../../test/test-utils';
Frank Bordene1ba8212022-08-29 15:20:01 +020017import {assert} from '@open-wc/testing';
Ben Rohlfs1aeb1d32022-06-30 23:02:59 +020018
19const KEY_A: Binding = {key: 'a'};
Ben Rohlfs42333cb2021-10-07 07:54:10 +020020
Ben Rohlfs966586e2021-10-06 16:15:24 +020021suite('shortcuts-service tests', () => {
Ben Rohlfs42333cb2021-10-07 07:54:10 +020022 let service: ShortcutsService;
23
24 setup(() => {
Chris Poucetefcdb202021-11-22 23:54:26 +010025 service = new ShortcutsService(
26 getAppContext().userModel,
27 getAppContext().reportingService
28 );
Ben Rohlfs42333cb2021-10-07 07:54:10 +020029 });
30
Ben Rohlfs966586e2021-10-06 16:15:24 +020031 test('getShortcut', () => {
Ben Rohlfs410f5222021-10-19 23:44:02 +020032 assert.equal(service.getShortcut(Shortcut.NEXT_FILE), ']');
33 assert.equal(service.getShortcut(Shortcut.TOGGLE_LEFT_PANE), 'A');
Ben Rohlfs966586e2021-10-06 16:15:24 +020034 });
35
Ben Rohlfs1aeb1d32022-06-30 23:02:59 +020036 suite('addShortcut()', () => {
37 let el: HTMLElement;
38 let listener: SinonSpy<[KeyboardEvent], void>;
39
40 setup(() => {
41 el = document.createElement('div');
42 listener = sinon.spy() as SinonSpy<[KeyboardEvent], void>;
43 });
44
45 test('standard call', () => {
46 service.addShortcut(el, KEY_A, listener);
Ben Rohlfse1f975e2022-07-04 12:03:06 +020047 assert.isTrue(listener.notCalled);
Ben Rohlfs1aeb1d32022-06-30 23:02:59 +020048 pressKey(el, KEY_A.key);
49 assert.isTrue(listener.calledOnce);
50 });
51
Ben Rohlfse1f975e2022-07-04 12:03:06 +020052 test('preventDefault option default false', () => {
Ben Rohlfs1aeb1d32022-06-30 23:02:59 +020053 service.addShortcut(el, KEY_A, listener);
54 pressKey(el, KEY_A.key);
55 assert.isTrue(listener.calledOnce);
56 assert.isTrue(listener.lastCall.firstArg?.defaultPrevented);
57 });
58
Ben Rohlfse1f975e2022-07-04 12:03:06 +020059 test('preventDefault option force false', () => {
60 service.addShortcut(el, KEY_A, listener, {preventDefault: false});
Ben Rohlfs1aeb1d32022-06-30 23:02:59 +020061 pressKey(el, KEY_A.key);
62 assert.isTrue(listener.calledOnce);
63 assert.isFalse(listener.lastCall.firstArg?.defaultPrevented);
64 });
65
Ben Rohlfse1f975e2022-07-04 12:03:06 +020066 test('preventDefault option force true', () => {
67 service.addShortcut(el, KEY_A, listener, {preventDefault: true});
68 pressKey(el, KEY_A.key);
69 assert.isTrue(listener.calledOnce);
70 assert.isTrue(listener.lastCall.firstArg?.defaultPrevented);
71 });
72
Ben Rohlfs1aeb1d32022-06-30 23:02:59 +020073 test('shouldSuppress option default true', () => {
74 service.shortcutsDisabled = true;
75 service.addShortcut(el, KEY_A, listener);
76 pressKey(el, KEY_A.key);
77 assert.isTrue(listener.notCalled);
78 });
79
80 test('shouldSuppress option force true', () => {
81 service.shortcutsDisabled = true;
82 service.addShortcut(el, KEY_A, listener, {shouldSuppress: true});
83 pressKey(el, KEY_A.key);
84 assert.isTrue(listener.notCalled);
85 });
86
87 test('shouldSuppress option force false', () => {
88 service.shortcutsDisabled = true;
89 service.addShortcut(el, KEY_A, listener, {shouldSuppress: false});
90 pressKey(el, KEY_A.key);
91 assert.isTrue(listener.calledOnce);
92 });
93 });
94
Ben Rohlfs966586e2021-10-06 16:15:24 +020095 suite('binding descriptions', () => {
96 function mapToObject<K, V>(m: Map<K, V>) {
97 const o: any = {};
98 m.forEach((v: V, k: K) => (o[k] = v));
99 return o;
100 }
101
102 test('single combo description', () => {
Ben Rohlfs410f5222021-10-19 23:44:02 +0200103 assert.deepEqual(describeBinding({key: 'a'}), ['a']);
104 assert.deepEqual(
105 describeBinding({key: 'a', modifiers: [Modifier.CTRL_KEY]}),
106 ['Ctrl', 'a']
107 );
108 assert.deepEqual(
109 describeBinding({
110 key: Key.UP,
111 modifiers: [Modifier.CTRL_KEY, Modifier.SHIFT_KEY],
112 }),
113 ['Shift', 'Ctrl', '↑']
114 );
Ben Rohlfs966586e2021-10-06 16:15:24 +0200115 });
116
117 test('combo set description', () => {
Ben Rohlfs42333cb2021-10-07 07:54:10 +0200118 assert.deepEqual(
119 service.describeBindings(Shortcut.GO_TO_OPENED_CHANGES),
120 [['g', 'o']]
121 );
122 assert.deepEqual(service.describeBindings(Shortcut.SAVE_COMMENT), [
Ben Rohlfsfea82402021-10-06 17:50:47 +0200123 ['Ctrl', 'Enter'],
Ben Rohlfs410f5222021-10-19 23:44:02 +0200124 ['Meta/Cmd', 'Enter'],
Ben Rohlfsfea82402021-10-06 17:50:47 +0200125 ['Ctrl', 's'],
Ben Rohlfs410f5222021-10-19 23:44:02 +0200126 ['Meta/Cmd', 's'],
Ben Rohlfs966586e2021-10-06 16:15:24 +0200127 ]);
Ben Rohlfs42333cb2021-10-07 07:54:10 +0200128 assert.deepEqual(service.describeBindings(Shortcut.PREV_FILE), [['[']]);
Ben Rohlfs966586e2021-10-06 16:15:24 +0200129 });
130
131 test('combo set description width', () => {
Ben Rohlfs42333cb2021-10-07 07:54:10 +0200132 assert.strictEqual(service.comboSetDisplayWidth([['u']]), 1);
133 assert.strictEqual(service.comboSetDisplayWidth([['g', 'o']]), 2);
134 assert.strictEqual(service.comboSetDisplayWidth([['Shift', 'r']]), 6);
135 assert.strictEqual(service.comboSetDisplayWidth([['x'], ['y']]), 4);
Ben Rohlfs966586e2021-10-06 16:15:24 +0200136 assert.strictEqual(
Ben Rohlfs42333cb2021-10-07 07:54:10 +0200137 service.comboSetDisplayWidth([['x'], ['y'], ['Shift', 'z']]),
Ben Rohlfs966586e2021-10-06 16:15:24 +0200138 12
139 );
140 });
141
142 test('distribute shortcut help', () => {
Ben Rohlfs42333cb2021-10-07 07:54:10 +0200143 assert.deepEqual(service.distributeBindingDesc([['o']]), [[['o']]]);
144 assert.deepEqual(service.distributeBindingDesc([['g', 'o']]), [
145 [['g', 'o']],
146 ]);
Ben Rohlfs966586e2021-10-06 16:15:24 +0200147 assert.deepEqual(
Ben Rohlfs42333cb2021-10-07 07:54:10 +0200148 service.distributeBindingDesc([['ctrl', 'shift', 'meta', 'enter']]),
Ben Rohlfs966586e2021-10-06 16:15:24 +0200149 [[['ctrl', 'shift', 'meta', 'enter']]]
150 );
151 assert.deepEqual(
Ben Rohlfs42333cb2021-10-07 07:54:10 +0200152 service.distributeBindingDesc([
153 ['ctrl', 'shift', 'meta', 'enter'],
154 ['o'],
155 ]),
Ben Rohlfs966586e2021-10-06 16:15:24 +0200156 [[['ctrl', 'shift', 'meta', 'enter']], [['o']]]
157 );
158 assert.deepEqual(
Ben Rohlfs42333cb2021-10-07 07:54:10 +0200159 service.distributeBindingDesc([
Ben Rohlfs966586e2021-10-06 16:15:24 +0200160 ['ctrl', 'enter'],
161 ['meta', 'enter'],
162 ['ctrl', 's'],
163 ['meta', 's'],
164 ]),
165 [
166 [
167 ['ctrl', 'enter'],
168 ['meta', 'enter'],
169 ],
170 [
171 ['ctrl', 's'],
172 ['meta', 's'],
173 ],
174 ]
175 );
176 });
177
178 test('active shortcuts by section', () => {
Ben Rohlfs42333cb2021-10-07 07:54:10 +0200179 assert.deepEqual(mapToObject(service.activeShortcutsBySection()), {});
Ben Rohlfs966586e2021-10-06 16:15:24 +0200180
Chris Poucet35764c32022-06-21 09:48:10 +0200181 service.addShortcutListener(Shortcut.NEXT_FILE, _ => {});
Ben Rohlfs42333cb2021-10-07 07:54:10 +0200182 assert.deepEqual(mapToObject(service.activeShortcutsBySection()), {
Ben Rohlfs966586e2021-10-06 16:15:24 +0200183 [ShortcutSection.NAVIGATION]: [
Ben Rohlfsfea82402021-10-06 17:50:47 +0200184 {
185 shortcut: Shortcut.NEXT_FILE,
186 text: 'Go to next file',
Ben Rohlfs410f5222021-10-19 23:44:02 +0200187 bindings: [{key: ']'}],
Ben Rohlfsfea82402021-10-06 17:50:47 +0200188 },
Ben Rohlfs966586e2021-10-06 16:15:24 +0200189 ],
190 });
Chris Poucet35764c32022-06-21 09:48:10 +0200191 service.addShortcutListener(Shortcut.NEXT_LINE, _ => {});
Ben Rohlfs42333cb2021-10-07 07:54:10 +0200192 assert.deepEqual(mapToObject(service.activeShortcutsBySection()), {
Ben Rohlfs966586e2021-10-06 16:15:24 +0200193 [ShortcutSection.DIFFS]: [
Ben Rohlfsfea82402021-10-06 17:50:47 +0200194 {
195 shortcut: Shortcut.NEXT_LINE,
196 text: 'Go to next line',
Ben Rohlfs5f5cb5e2021-11-08 12:10:25 +0100197 bindings: [
198 {allowRepeat: true, key: 'j'},
199 {allowRepeat: true, key: 'ArrowDown'},
200 ],
Ben Rohlfsfea82402021-10-06 17:50:47 +0200201 },
Ben Rohlfs966586e2021-10-06 16:15:24 +0200202 ],
203 [ShortcutSection.NAVIGATION]: [
Ben Rohlfsfea82402021-10-06 17:50:47 +0200204 {
205 shortcut: Shortcut.NEXT_FILE,
206 text: 'Go to next file',
Ben Rohlfs410f5222021-10-19 23:44:02 +0200207 bindings: [{key: ']'}],
Ben Rohlfsfea82402021-10-06 17:50:47 +0200208 },
Ben Rohlfs966586e2021-10-06 16:15:24 +0200209 ],
210 });
211
Chris Poucet35764c32022-06-21 09:48:10 +0200212 service.addShortcutListener(Shortcut.SEARCH, _ => {});
213 service.addShortcutListener(Shortcut.GO_TO_OPENED_CHANGES, _ => {});
Ben Rohlfs42333cb2021-10-07 07:54:10 +0200214 assert.deepEqual(mapToObject(service.activeShortcutsBySection()), {
Ben Rohlfs966586e2021-10-06 16:15:24 +0200215 [ShortcutSection.DIFFS]: [
Ben Rohlfsfea82402021-10-06 17:50:47 +0200216 {
217 shortcut: Shortcut.NEXT_LINE,
218 text: 'Go to next line',
Ben Rohlfs5f5cb5e2021-11-08 12:10:25 +0100219 bindings: [
220 {allowRepeat: true, key: 'j'},
221 {allowRepeat: true, key: 'ArrowDown'},
222 ],
Ben Rohlfsfea82402021-10-06 17:50:47 +0200223 },
Ben Rohlfs966586e2021-10-06 16:15:24 +0200224 ],
225 [ShortcutSection.EVERYWHERE]: [
Ben Rohlfsfea82402021-10-06 17:50:47 +0200226 {
227 shortcut: Shortcut.SEARCH,
228 text: 'Search',
Ben Rohlfs410f5222021-10-19 23:44:02 +0200229 bindings: [{key: '/'}],
Ben Rohlfsfea82402021-10-06 17:50:47 +0200230 },
Ben Rohlfs966586e2021-10-06 16:15:24 +0200231 {
232 shortcut: Shortcut.GO_TO_OPENED_CHANGES,
233 text: 'Go to Opened Changes',
Ben Rohlfs410f5222021-10-19 23:44:02 +0200234 bindings: [{key: 'o', combo: 'g'}],
Ben Rohlfs966586e2021-10-06 16:15:24 +0200235 },
236 ],
237 [ShortcutSection.NAVIGATION]: [
Ben Rohlfsfea82402021-10-06 17:50:47 +0200238 {
239 shortcut: Shortcut.NEXT_FILE,
240 text: 'Go to next file',
Ben Rohlfs410f5222021-10-19 23:44:02 +0200241 bindings: [{key: ']'}],
Ben Rohlfsfea82402021-10-06 17:50:47 +0200242 },
Ben Rohlfs966586e2021-10-06 16:15:24 +0200243 ],
244 });
245 });
246
247 test('directory view', () => {
Ben Rohlfs42333cb2021-10-07 07:54:10 +0200248 assert.deepEqual(mapToObject(service.directoryView()), {});
Ben Rohlfs966586e2021-10-06 16:15:24 +0200249
Chris Poucet35764c32022-06-21 09:48:10 +0200250 service.addShortcutListener(Shortcut.GO_TO_OPENED_CHANGES, _ => {});
251 service.addShortcutListener(Shortcut.NEXT_FILE, _ => {});
252 service.addShortcutListener(Shortcut.NEXT_LINE, _ => {});
253 service.addShortcutListener(Shortcut.SAVE_COMMENT, _ => {});
254 service.addShortcutListener(Shortcut.SEARCH, _ => {});
Ben Rohlfs42333cb2021-10-07 07:54:10 +0200255 assert.deepEqual(mapToObject(service.directoryView()), {
Ben Rohlfs966586e2021-10-06 16:15:24 +0200256 [ShortcutSection.DIFFS]: [
Ben Rohlfsfea82402021-10-06 17:50:47 +0200257 {binding: [['j'], ['↓']], text: 'Go to next line'},
Ben Rohlfs966586e2021-10-06 16:15:24 +0200258 {
Ben Rohlfs410f5222021-10-19 23:44:02 +0200259 binding: [['Ctrl', 'Enter']],
Ben Rohlfs966586e2021-10-06 16:15:24 +0200260 text: 'Save comment',
261 },
262 {
263 binding: [
Ben Rohlfs410f5222021-10-19 23:44:02 +0200264 ['Meta/Cmd', 'Enter'],
Ben Rohlfs966586e2021-10-06 16:15:24 +0200265 ['Ctrl', 's'],
Ben Rohlfs966586e2021-10-06 16:15:24 +0200266 ],
267 text: 'Save comment',
268 },
Ben Rohlfs410f5222021-10-19 23:44:02 +0200269 {
270 binding: [['Meta/Cmd', 's']],
271 text: 'Save comment',
272 },
Ben Rohlfs966586e2021-10-06 16:15:24 +0200273 ],
274 [ShortcutSection.EVERYWHERE]: [
275 {binding: [['/']], text: 'Search'},
276 {binding: [['g', 'o']], text: 'Go to Opened Changes'},
277 ],
278 [ShortcutSection.NAVIGATION]: [
279 {binding: [[']']], text: 'Go to next file'},
280 ],
281 });
282 });
283 });
Ben Rohlfsbe0c0ba2021-10-17 19:36:43 +0200284
285 suite('combo keys', () => {
286 let clock: SinonFakeTimers;
287 setup(() => {
288 clock = sinon.useFakeTimers();
289 clock.tick(1000);
290 });
291
292 teardown(() => {
293 clock.restore();
294 });
295
296 test('not in combo key mode initially', () => {
297 assert.isFalse(service.isInComboKeyMode());
298 });
299
300 test('pressing f does not switch into combo key mode', () => {
301 const event = new KeyboardEvent('keydown', {key: 'f'});
302 document.dispatchEvent(event);
303 assert.isFalse(service.isInComboKeyMode());
304 });
305
306 test('pressing g switches into combo key mode', () => {
307 const event = new KeyboardEvent('keydown', {key: 'g'});
308 document.dispatchEvent(event);
309 assert.isTrue(service.isInComboKeyMode());
310 });
311
312 test('pressing v switches into combo key mode', () => {
313 const event = new KeyboardEvent('keydown', {key: 'v'});
314 document.dispatchEvent(event);
315 assert.isTrue(service.isInComboKeyMode());
316 });
317
318 test('combo key mode timeout', () => {
319 const event = new KeyboardEvent('keydown', {key: 'g'});
320 document.dispatchEvent(event);
321 assert.isTrue(service.isInComboKeyMode());
322 clock.tick(COMBO_TIMEOUT_MS / 2);
323 assert.isTrue(service.isInComboKeyMode());
324 clock.tick(COMBO_TIMEOUT_MS);
325 assert.isFalse(service.isInComboKeyMode());
326 });
327 });
Ben Rohlfs966586e2021-10-06 16:15:24 +0200328});