rx-hotkeys 2.3.0 → 2.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/hotkeys.d.ts +39 -20
- package/dist/hotkeys.d.ts.map +1 -1
- package/dist/hotkeys.js +62 -51
- package/dist/hotkeys.test.js +59 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -165,8 +165,8 @@ Cleans up all subscriptions and resources. Essential to call to prevent memory l
|
|
|
165
165
|
`KeyCombinationConfig`
|
|
166
166
|
|
|
167
167
|
* `id: string` (required): Unique identifier for the shortcut.
|
|
168
|
-
* `keys: { key: StandardKey; ctrlKey?: boolean; altKey?: boolean; shiftKey?: boolean; metaKey?: boolean; } | StandardKey
|
|
169
|
-
* `callback: (event
|
|
168
|
+
* `keys: { key: StandardKey; ctrlKey?: boolean; altKey?: boolean; shiftKey?: boolean; metaKey?: boolean; } | StandardKey | Array<{ key: StandardKey; ctrlKey?: boolean; altKey?: boolean; shiftKey?: boolean; metaKey?: boolean; } | StandardKey>` (required): Defines the main key (from Keys) and optional modifier keys.
|
|
169
|
+
* `callback: (event: KeyboardEvent) => void` (required): Function to execute when the shortcut is triggered. The triggering `KeyboardEvent` is passed as an argument.
|
|
170
170
|
* `context?: string | null`: Specifies the context in which this shortcut is active. If `null` or `undefined`, it's a global shortcut.
|
|
171
171
|
* `preventDefault?: boolean`: If true, `event.preventDefault()` will be called when the shortcut triggers. Defaults to `false`.
|
|
172
172
|
* `description?: string`: An optional description for the shortcut (e.g., for help menus).
|
|
@@ -175,7 +175,7 @@ Cleans up all subscriptions and resources. Essential to call to prevent memory l
|
|
|
175
175
|
|
|
176
176
|
* `id: string` (required): Unique identifier.
|
|
177
177
|
* `sequence: StandardKey[]` (required): An array of `StandardKey` values (from `Keys`) representing the key sequence.
|
|
178
|
-
* `callback: (event
|
|
178
|
+
* `callback: (event: KeyboardEvent) => void` (required): Function to execute. The last `KeyboardEvent` of the sequence is passed.
|
|
179
179
|
* `context?: string | null`: Context for activation.
|
|
180
180
|
* `preventDefault?: boolean`: If true, `event.preventDefault()` is called for the last event in the sequence. Defaults to `false`.
|
|
181
181
|
* `description?: string`: Optional description.
|
package/dist/hotkeys.d.ts
CHANGED
|
@@ -1,16 +1,37 @@
|
|
|
1
|
-
import { Observable } from "rxjs";
|
|
2
|
-
import { StandardKey } from "./keys.js";
|
|
1
|
+
import { Subscription, Observable } from "rxjs";
|
|
2
|
+
import { type StandardKey } from "./keys.js";
|
|
3
3
|
export declare enum ShortcutTypes {
|
|
4
4
|
Combination = "combination",
|
|
5
5
|
Sequence = "sequence"
|
|
6
6
|
}
|
|
7
7
|
interface ShortcutConfigBase {
|
|
8
8
|
id: string;
|
|
9
|
-
callback: (event
|
|
9
|
+
callback: (event: KeyboardEvent) => void;
|
|
10
10
|
context?: string | null;
|
|
11
11
|
preventDefault?: boolean;
|
|
12
12
|
description?: string;
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* Defines a single key trigger, which can be a StandardKey (for simple presses like "Escape")
|
|
16
|
+
* or an object specifying the main key and its modifiers (e.g., { key: Keys.S, ctrlKey: true }).
|
|
17
|
+
*/
|
|
18
|
+
type KeyCombinationTrigger = {
|
|
19
|
+
/**
|
|
20
|
+
* The main key for the combination.
|
|
21
|
+
* This MUST be a value from the exported `Keys` object
|
|
22
|
+
* (e.g., `Keys.A`, `Keys.Enter`, `Keys.Escape`).
|
|
23
|
+
* The library handles case-insensitivity for single character keys (like A-Z, 0-9)
|
|
24
|
+
* automatically when comparing with the actual browser event's `event.key`.
|
|
25
|
+
* For special, multi-character keys (e.g. "ArrowUp", "Escape"), the value from
|
|
26
|
+
* `Keys` ensures the correct case-sensitive string is used.
|
|
27
|
+
* Refer to: https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values
|
|
28
|
+
*/
|
|
29
|
+
key: StandardKey;
|
|
30
|
+
ctrlKey?: boolean;
|
|
31
|
+
altKey?: boolean;
|
|
32
|
+
shiftKey?: boolean;
|
|
33
|
+
metaKey?: boolean;
|
|
34
|
+
} | StandardKey;
|
|
14
35
|
export interface KeyCombinationConfig extends ShortcutConfigBase {
|
|
15
36
|
/**
|
|
16
37
|
* Defines the key or key combination.
|
|
@@ -23,23 +44,7 @@ export interface KeyCombinationConfig extends ShortcutConfigBase {
|
|
|
23
44
|
* Example: `Keys.Escape` for the Escape key. When using this shorthand,
|
|
24
45
|
* it implies that no modifier keys (Ctrl, Alt, Shift, Meta) should be active.
|
|
25
46
|
*/
|
|
26
|
-
keys:
|
|
27
|
-
/**
|
|
28
|
-
* The main key for the combination.
|
|
29
|
-
* This MUST be a value from the exported `Keys` object
|
|
30
|
-
* (e.g., `Keys.A`, `Keys.Enter`, `Keys.Escape`).
|
|
31
|
-
* The library handles case-insensitivity for single character keys (like A-Z, 0-9)
|
|
32
|
-
* automatically when comparing with the actual browser event's `event.key`.
|
|
33
|
-
* For special, multi-character keys (e.g. "ArrowUp", "Escape"), the value from
|
|
34
|
-
* `Keys` ensures the correct case-sensitive string is used.
|
|
35
|
-
* Refer to: https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values
|
|
36
|
-
*/
|
|
37
|
-
key: StandardKey;
|
|
38
|
-
ctrlKey?: boolean;
|
|
39
|
-
altKey?: boolean;
|
|
40
|
-
shiftKey?: boolean;
|
|
41
|
-
metaKey?: boolean;
|
|
42
|
-
} | StandardKey;
|
|
47
|
+
keys: KeyCombinationTrigger | KeyCombinationTrigger[];
|
|
43
48
|
}
|
|
44
49
|
export interface KeySequenceConfig extends ShortcutConfigBase {
|
|
45
50
|
/**
|
|
@@ -59,6 +64,12 @@ export interface KeySequenceConfig extends ShortcutConfigBase {
|
|
|
59
64
|
*/
|
|
60
65
|
sequenceTimeoutMs?: number;
|
|
61
66
|
}
|
|
67
|
+
type ShortcutConfig = KeyCombinationConfig | KeySequenceConfig;
|
|
68
|
+
export interface ActiveShortcut {
|
|
69
|
+
id: string;
|
|
70
|
+
config: ShortcutConfig;
|
|
71
|
+
subscription: Subscription;
|
|
72
|
+
}
|
|
62
73
|
/**
|
|
63
74
|
* Manages keyboard shortcuts for web applications.
|
|
64
75
|
* Allows registration of single key combinations (e.g., Ctrl+S) and key sequences (e.g., g -> i).
|
|
@@ -125,6 +136,14 @@ export declare class Hotkeys {
|
|
|
125
136
|
hasShortcut(id: string): boolean;
|
|
126
137
|
private filterByContext;
|
|
127
138
|
private _registerShortcut;
|
|
139
|
+
/**
|
|
140
|
+
* Parses a single key trigger definition (either shorthand StandardKey or an object with modifiers)
|
|
141
|
+
* into its constituent parts: main key and modifier states.
|
|
142
|
+
* @param keyInput - The KeyCombinationTrigger to parse.
|
|
143
|
+
* @param shortcutId - The ID of the shortcut this key trigger belongs to (for logging).
|
|
144
|
+
* @returns An object containing configuredMainKey and modifier states, or null if parsing fails.
|
|
145
|
+
*/
|
|
146
|
+
private _parseKeyTrigger;
|
|
128
147
|
/**
|
|
129
148
|
* Registers a key combination shortcut (e.g., Ctrl+S, Shift+Enter, or a single key like Escape).
|
|
130
149
|
* The callback is triggered when the specified key and modifier keys (if any) are pressed.
|
package/dist/hotkeys.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hotkeys.d.ts","sourceRoot":"","sources":["../src/hotkeys.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"hotkeys.d.ts","sourceRoot":"","sources":["../src/hotkeys.ts"],"names":[],"mappings":"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"}
|
package/dist/hotkeys.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { fromEvent, BehaviorSubject, EMPTY, filter, map, bufferCount, withLatestFrom, tap, catchError, scan, } from "rxjs";
|
|
1
|
+
import { fromEvent, BehaviorSubject, EMPTY, filter, map, bufferCount, withLatestFrom, tap, catchError, scan, merge, } from "rxjs";
|
|
2
2
|
// --- Enums, Interfaces and Types ---
|
|
3
3
|
export var ShortcutTypes;
|
|
4
4
|
(function (ShortcutTypes) {
|
|
@@ -150,6 +150,34 @@ export class Hotkeys {
|
|
|
150
150
|
}
|
|
151
151
|
return config.id;
|
|
152
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* Parses a single key trigger definition (either shorthand StandardKey or an object with modifiers)
|
|
155
|
+
* into its constituent parts: main key and modifier states.
|
|
156
|
+
* @param keyInput - The KeyCombinationTrigger to parse.
|
|
157
|
+
* @param shortcutId - The ID of the shortcut this key trigger belongs to (for logging).
|
|
158
|
+
* @returns An object containing configuredMainKey and modifier states, or null if parsing fails.
|
|
159
|
+
*/
|
|
160
|
+
_parseKeyTrigger(keyInput, shortcutId) {
|
|
161
|
+
if (typeof keyInput === "string") {
|
|
162
|
+
if (keyInput === "") {
|
|
163
|
+
console.warn(`${Hotkeys.LOG_PREFIX} Invalid key (shorthand) in shortcut "${shortcutId}". Key string must not be empty.`);
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
return { configuredMainKey: keyInput, ctrlKeyConfig: false, altKeyConfig: false, shiftKeyConfig: false, metaKeyConfig: false, logDetails: `key: "${keyInput}" (no mods)` };
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
if (!keyInput.key || keyInput.key === "") {
|
|
170
|
+
console.warn(`${Hotkeys.LOG_PREFIX} Invalid "key" property in shortcut "${shortcutId}". Key must be a non-empty value from Keys.`);
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
const logDetails = `key: "${keyInput.key}"` +
|
|
174
|
+
(keyInput.ctrlKey !== undefined ? `, ctrl: ${keyInput.ctrlKey}` : "") +
|
|
175
|
+
(keyInput.altKey !== undefined ? `, alt: ${keyInput.altKey}` : "") +
|
|
176
|
+
(keyInput.shiftKey !== undefined ? `, shift: ${keyInput.shiftKey}` : "") +
|
|
177
|
+
(keyInput.metaKey !== undefined ? `, meta: ${keyInput.metaKey}` : "");
|
|
178
|
+
return { configuredMainKey: keyInput.key, ctrlKeyConfig: keyInput.ctrlKey, altKeyConfig: keyInput.altKey, shiftKeyConfig: keyInput.shiftKey, metaKeyConfig: keyInput.metaKey, logDetails };
|
|
179
|
+
}
|
|
180
|
+
}
|
|
153
181
|
/**
|
|
154
182
|
* Registers a key combination shortcut (e.g., Ctrl+S, Shift+Enter, or a single key like Escape).
|
|
155
183
|
* The callback is triggered when the specified key and modifier keys (if any) are pressed.
|
|
@@ -178,67 +206,50 @@ export class Hotkeys {
|
|
|
178
206
|
*/
|
|
179
207
|
addCombination(config) {
|
|
180
208
|
const { keys, callback, context, preventDefault = false, id } = config;
|
|
181
|
-
let
|
|
182
|
-
let
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
if (typeof keys === "string") {
|
|
188
|
-
// Shorthand: keys is StandardKey, implying no modifiers should be active
|
|
189
|
-
// Corrected validation: Check for actual empty string, not a string that trims to empty.
|
|
190
|
-
if (keys === "") { // StandardKey type should prevent this, but check for robustness.
|
|
191
|
-
console.warn(`${Hotkeys.LOG_PREFIX} Invalid "keys" (shorthand) for combination shortcut "${id}". Key must not be an empty string. Shortcut not added.`);
|
|
192
|
-
return undefined;
|
|
193
|
-
}
|
|
194
|
-
configuredMainKey = keys;
|
|
195
|
-
ctrlKeyConfig = false; // Shorthand implies no modifiers
|
|
196
|
-
altKeyConfig = false;
|
|
197
|
-
shiftKeyConfig = false;
|
|
198
|
-
metaKeyConfig = false;
|
|
199
|
-
keyDetailsForLog = `key: "${keys}" (no modifiers implied)`;
|
|
209
|
+
let finalShortcut$;
|
|
210
|
+
let overallLogDetails = "";
|
|
211
|
+
const keyTriggers = Array.isArray(keys) ? keys : [keys];
|
|
212
|
+
if (keyTriggers.length === 0) {
|
|
213
|
+
console.warn(`${Hotkeys.LOG_PREFIX} "keys" array for combination shortcut "${id}" is empty. Shortcut not added.`);
|
|
214
|
+
return undefined;
|
|
200
215
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
216
|
+
const observables = [];
|
|
217
|
+
const logParts = [];
|
|
218
|
+
for (const keyInput of keyTriggers) {
|
|
219
|
+
const parsedTrigger = this._parseKeyTrigger(keyInput, id);
|
|
220
|
+
if (!parsedTrigger) {
|
|
221
|
+
// Error already logged by _parseKeyTrigger
|
|
206
222
|
return undefined;
|
|
207
223
|
}
|
|
208
|
-
configuredMainKey =
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
(
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
224
|
+
const { configuredMainKey, ctrlKeyConfig, altKeyConfig, shiftKeyConfig, metaKeyConfig, logDetails } = parsedTrigger;
|
|
225
|
+
logParts.push(`{ ${logDetails} }`);
|
|
226
|
+
const stream = this.filterByContext(this.keydown$, context).pipe(filter(event => {
|
|
227
|
+
const ctrlMatch = (ctrlKeyConfig === undefined) ? true : (event.ctrlKey === ctrlKeyConfig);
|
|
228
|
+
const altMatch = (altKeyConfig === undefined) ? true : (event.altKey === altKeyConfig);
|
|
229
|
+
const shiftMatch = (shiftKeyConfig === undefined) ? true : (event.shiftKey === shiftKeyConfig);
|
|
230
|
+
const metaMatch = (metaKeyConfig === undefined) ? true : (event.metaKey === metaKeyConfig);
|
|
231
|
+
return ctrlMatch && altMatch && shiftMatch && metaMatch;
|
|
232
|
+
}), filter(event => compareKey(event.key, configuredMainKey)));
|
|
233
|
+
observables.push(stream);
|
|
218
234
|
}
|
|
219
|
-
|
|
220
|
-
//
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
const shiftMatch = (shiftKeyConfig === undefined) ? true : (event.shiftKey === shiftKeyConfig);
|
|
228
|
-
const metaMatch = (metaKeyConfig === undefined) ? true : (event.metaKey === metaKeyConfig);
|
|
229
|
-
return ctrlMatch && altMatch && shiftMatch && metaMatch;
|
|
230
|
-
}), filter(event => compareKey(event.key, configuredMainKey)), tap(event => {
|
|
235
|
+
if (observables.length === 0) {
|
|
236
|
+
// Should be caught by keyTriggers.length === 0, but as a safeguard.
|
|
237
|
+
console.warn(`${Hotkeys.LOG_PREFIX} No valid key triggers for combination shortcut "${id}". Shortcut not added.`);
|
|
238
|
+
return undefined;
|
|
239
|
+
}
|
|
240
|
+
finalShortcut$ = merge(...observables);
|
|
241
|
+
overallLogDetails = Array.isArray(keys) ? `Triggers: [ ${logParts.join(", ")} ]` : logParts[0];
|
|
242
|
+
const subscription = finalShortcut$.pipe(tap(event => {
|
|
231
243
|
if (this.debugMode) {
|
|
232
244
|
const preventAction = preventDefault ? ", preventing default" : "";
|
|
233
|
-
console.log(`${Hotkeys.LOG_PREFIX} Combination "${id}" triggered${preventAction}.`);
|
|
245
|
+
console.log(`${Hotkeys.LOG_PREFIX} Combination "${id}" triggered by key "${event.key}" ${preventAction}.`);
|
|
234
246
|
}
|
|
235
247
|
if (preventDefault)
|
|
236
248
|
event.preventDefault();
|
|
237
249
|
}), catchError(err => {
|
|
238
250
|
console.error(`${Hotkeys.LOG_PREFIX} Error in combination stream for shortcut "${id}":`, err);
|
|
239
251
|
return EMPTY;
|
|
240
|
-
}))
|
|
241
|
-
const subscription = shortcut$.subscribe(event => {
|
|
252
|
+
})).subscribe(event => {
|
|
242
253
|
try {
|
|
243
254
|
callback(event);
|
|
244
255
|
}
|
|
@@ -246,7 +257,7 @@ export class Hotkeys {
|
|
|
246
257
|
console.error(`${Hotkeys.LOG_PREFIX} Error in user callback for combination shortcut "${id}":`, e);
|
|
247
258
|
}
|
|
248
259
|
});
|
|
249
|
-
return this._registerShortcut(config, subscription, ShortcutTypes.Combination,
|
|
260
|
+
return this._registerShortcut(config, subscription, ShortcutTypes.Combination, overallLogDetails);
|
|
250
261
|
}
|
|
251
262
|
/**
|
|
252
263
|
* Registers a key sequence shortcut (e.g., g -> i, or ArrowUp -> ArrowUp -> ArrowDown).
|
package/dist/hotkeys.test.js
CHANGED
|
@@ -242,22 +242,22 @@ describe("Hotkeys Library (Node.js Test Runner)", () => {
|
|
|
242
242
|
});
|
|
243
243
|
});
|
|
244
244
|
describe("addCombination", () => {
|
|
245
|
-
it(
|
|
245
|
+
it(`should trigger callback for a simple key combination (e.g., "A")`, () => {
|
|
246
246
|
const config = { id: "simpleA", keys: { key: Keys.A }, callback: mockCallback };
|
|
247
247
|
const result = keyManager.addCombination(config);
|
|
248
248
|
assert.strictEqual(result, "simpleA");
|
|
249
249
|
dispatchKeyEvent("a");
|
|
250
|
-
assert.strictEqual(mockCallback.calledCount, 1,
|
|
250
|
+
assert.strictEqual(mockCallback.calledCount, 1, `Callback for "a" not called`);
|
|
251
251
|
mockCallback.mockClear();
|
|
252
252
|
dispatchKeyEvent("A");
|
|
253
|
-
assert.strictEqual(mockCallback.calledCount, 1,
|
|
253
|
+
assert.strictEqual(mockCallback.calledCount, 1, `Callback for "A" not called`);
|
|
254
254
|
});
|
|
255
255
|
it("should return undefined and warn if keys.key is null or undefined (runtime check in object form)", () => {
|
|
256
256
|
const config = { id: "nullKey", keys: { key: null }, callback: mockCallback };
|
|
257
257
|
const result = keyManager.addCombination(config);
|
|
258
258
|
assert.strictEqual(result, undefined, "Should return undefined for null key");
|
|
259
259
|
assert.strictEqual(consoleWarnMock.mock.calls.length, 1);
|
|
260
|
-
assert.ok(consoleWarnMock.mock.calls[0].arguments[0].includes(`Invalid "
|
|
260
|
+
assert.ok(consoleWarnMock.mock.calls[0].arguments[0].includes(`Invalid "key" property in shortcut "nullKey". Key must be a non-empty value from Keys.`));
|
|
261
261
|
});
|
|
262
262
|
it("should pass the KeyboardEvent to the callback", () => {
|
|
263
263
|
const config = { id: "eventPass", keys: { key: Keys.E }, callback: mockCallback };
|
|
@@ -317,30 +317,75 @@ describe("Hotkeys Library (Node.js Test Runner)", () => {
|
|
|
317
317
|
dispatchKeyEvent("w");
|
|
318
318
|
assert.strictEqual(workingCallback.calledCount, 1);
|
|
319
319
|
});
|
|
320
|
+
it("should trigger callback if any of multiple key combinations are pressed", () => {
|
|
321
|
+
const config = {
|
|
322
|
+
id: "multiCombo",
|
|
323
|
+
keys: [
|
|
324
|
+
{ key: Keys.A, ctrlKey: true }, // Ctrl+A
|
|
325
|
+
Keys.Escape, // Escape
|
|
326
|
+
{ key: Keys.B, shiftKey: true, altKey: true } // Shift+Alt+B
|
|
327
|
+
],
|
|
328
|
+
callback: mockCallback
|
|
329
|
+
};
|
|
330
|
+
keyManager.addCombination(config);
|
|
331
|
+
dispatchKeyEvent("A", { ctrlKey: true });
|
|
332
|
+
assert.strictEqual(mockCallback.calledCount, 1, "Ctrl+A did not trigger");
|
|
333
|
+
dispatchKeyEvent("Escape");
|
|
334
|
+
assert.strictEqual(mockCallback.calledCount, 2, "Escape did not trigger");
|
|
335
|
+
dispatchKeyEvent("B", { shiftKey: true, altKey: true });
|
|
336
|
+
assert.strictEqual(mockCallback.calledCount, 3, "Shift+Alt+B did not trigger");
|
|
337
|
+
dispatchKeyEvent("C"); // Should not trigger
|
|
338
|
+
assert.strictEqual(mockCallback.calledCount, 3, "Unrelated key C triggered");
|
|
339
|
+
});
|
|
340
|
+
it(`should return undefined and warn if "keys" array is empty`, () => {
|
|
341
|
+
const config = { id: "emptyKeysArray", keys: [], callback: mockCallback };
|
|
342
|
+
const result = keyManager.addCombination(config);
|
|
343
|
+
assert.strictEqual(result, undefined);
|
|
344
|
+
assert.ok(consoleWarnMock.mock.calls.some(call => call.arguments[0].includes(`"keys" array for combination shortcut "emptyKeysArray" is empty`)));
|
|
345
|
+
});
|
|
346
|
+
it(`should return undefined and warn if a key in "keys" array is invalid (shorthand)`, () => {
|
|
347
|
+
const config = { id: "invalidInArrayShorthand", keys: [Keys.A, ""], callback: mockCallback };
|
|
348
|
+
const result = keyManager.addCombination(config);
|
|
349
|
+
assert.strictEqual(result, undefined);
|
|
350
|
+
assert.ok(consoleWarnMock.mock.calls.some(call => call.arguments[0].includes(`Invalid key (shorthand) in shortcut "invalidInArrayShorthand"`)));
|
|
351
|
+
});
|
|
352
|
+
it(`should return undefined and warn if a key in "keys" array is invalid (object)`, () => {
|
|
353
|
+
const config = { id: "invalidInArrayObject", keys: [Keys.A, { key: "" }], callback: mockCallback };
|
|
354
|
+
const result = keyManager.addCombination(config);
|
|
355
|
+
assert.strictEqual(result, undefined);
|
|
356
|
+
assert.ok(consoleWarnMock.mock.calls.some(call => call.arguments[0].includes(`Invalid "key" property in shortcut "invalidInArrayObject"`)));
|
|
357
|
+
});
|
|
358
|
+
it("should correctly log multiple key triggers in debug mode", () => {
|
|
359
|
+
const consoleLogMock = mock.method(console, "log");
|
|
360
|
+
keyManager.setDebugMode(true);
|
|
361
|
+
keyManager.addCombination({ id: "multiLog", keys: [Keys.F1, { key: Keys.F2, ctrlKey: true }], callback: mockCallback });
|
|
362
|
+
assert.ok(consoleLogMock.mock.calls.some(call => call.arguments[0].includes(`Triggers: [ { key: "F1" (no mods) }, { key: "F2", ctrl: true } ]`)), "Multi-trigger log format incorrect");
|
|
363
|
+
consoleLogMock.mock.restore();
|
|
364
|
+
});
|
|
320
365
|
describe("addCombination - Shorthand Syntax", () => {
|
|
321
366
|
it("should trigger callback for a simple key using shorthand (e.g., Keys.X)", () => {
|
|
322
367
|
const config = { id: "shorthandX", keys: Keys.X, callback: mockCallback };
|
|
323
368
|
keyManager.addCombination(config);
|
|
324
369
|
dispatchKeyEvent(Keys.X.toLowerCase());
|
|
325
|
-
assert.strictEqual(mockCallback.calledCount, 1,
|
|
370
|
+
assert.strictEqual(mockCallback.calledCount, 1, `Callback for "x" (shorthand) not called`);
|
|
326
371
|
mockCallback.mockClear();
|
|
327
372
|
dispatchKeyEvent(Keys.X);
|
|
328
|
-
assert.strictEqual(mockCallback.calledCount, 1,
|
|
373
|
+
assert.strictEqual(mockCallback.calledCount, 1, `Callback for "X" (shorthand) not called`);
|
|
329
374
|
});
|
|
330
375
|
it("should NOT trigger callback for shorthand if modifier is pressed", () => {
|
|
331
376
|
const config = { id: "shorthandY", keys: Keys.Y, callback: mockCallback };
|
|
332
377
|
keyManager.addCombination(config);
|
|
333
378
|
dispatchKeyEvent(Keys.Y, { ctrlKey: true });
|
|
334
|
-
assert.strictEqual(mockCallback.calledCount, 0,
|
|
379
|
+
assert.strictEqual(mockCallback.calledCount, 0, `Callback for "y" (shorthand) should not be called with Ctrl`);
|
|
335
380
|
mockCallback.mockClear();
|
|
336
381
|
dispatchKeyEvent(Keys.Y, { altKey: true });
|
|
337
|
-
assert.strictEqual(mockCallback.calledCount, 0,
|
|
382
|
+
assert.strictEqual(mockCallback.calledCount, 0, `Callback for "y" (shorthand) should not be called with Alt`);
|
|
338
383
|
mockCallback.mockClear();
|
|
339
384
|
dispatchKeyEvent(Keys.Y, { shiftKey: true });
|
|
340
|
-
assert.strictEqual(mockCallback.calledCount, 0,
|
|
385
|
+
assert.strictEqual(mockCallback.calledCount, 0, `Callback for "y" (shorthand) should not be called with Shift`);
|
|
341
386
|
mockCallback.mockClear();
|
|
342
387
|
dispatchKeyEvent(Keys.Y, { metaKey: true });
|
|
343
|
-
assert.strictEqual(mockCallback.calledCount, 0,
|
|
388
|
+
assert.strictEqual(mockCallback.calledCount, 0, `Callback for "y" (shorthand) should not be called with Meta`);
|
|
344
389
|
});
|
|
345
390
|
it("should trigger callback for shorthand if ONLY the key is pressed (no modifiers)", () => {
|
|
346
391
|
const config = { id: "shorthandZ", keys: Keys.Z, callback: mockCallback };
|
|
@@ -370,16 +415,16 @@ describe("Hotkeys Library (Node.js Test Runner)", () => {
|
|
|
370
415
|
const result = keyManager.addCombination(config);
|
|
371
416
|
assert.strictEqual(result, undefined);
|
|
372
417
|
assert.strictEqual(consoleWarnMock.mock.calls.length, 1);
|
|
373
|
-
assert.ok(consoleWarnMock.mock.calls[0].arguments[0].includes(`Invalid
|
|
418
|
+
assert.ok(consoleWarnMock.mock.calls[0].arguments[0].includes(`Invalid key (shorthand) in shortcut "emptyShorthand". Key string must not be empty.`));
|
|
374
419
|
});
|
|
375
420
|
it("should correctly log shorthand key details in debug mode", () => {
|
|
376
421
|
const consoleLogMock = mock.method(console, "log");
|
|
377
422
|
keyManager.setDebugMode(true);
|
|
378
423
|
const config = { id: "debugShorthand", keys: Keys.D, callback: mockCallback };
|
|
379
424
|
keyManager.addCombination(config);
|
|
380
|
-
const logMessage = consoleLogMock.mock.calls.find(call => call.arguments[0].includes(`combination shortcut "debugShorthand" added
|
|
425
|
+
const logMessage = consoleLogMock.mock.calls.find(call => call.arguments[0].includes(`combination shortcut "debugShorthand" added.`));
|
|
381
426
|
assert.ok(logMessage, "Debug log for adding shortcut not found");
|
|
382
|
-
assert.ok(logMessage.arguments[0].includes(`
|
|
427
|
+
assert.ok(logMessage.arguments[0].includes(`{ key: "D" (no mods) }`), `Log message content mismatch: ${logMessage.arguments[0]}`);
|
|
383
428
|
consoleLogMock.mock.restore();
|
|
384
429
|
keyManager.setDebugMode(false);
|
|
385
430
|
});
|
|
@@ -425,7 +470,7 @@ describe("Hotkeys Library (Node.js Test Runner)", () => {
|
|
|
425
470
|
assert.strictEqual(consoleWarnMock.mock.calls.length, 1);
|
|
426
471
|
assert.ok(consoleWarnMock.mock.calls[0].arguments[0].includes(`Sequence for shortcut "emptySeq" is empty`));
|
|
427
472
|
});
|
|
428
|
-
it(
|
|
473
|
+
it(`should return undefined and warn if sequence contains an invalid key (runtime check with "as any")`, () => {
|
|
429
474
|
// This test checks runtime robustness if `any` is used to bypass StandardKey[]
|
|
430
475
|
const config = { id: "invalidKeyInSeq", sequence: [Keys.A, "", Keys.C], callback: mockCallback };
|
|
431
476
|
const result = keyManager.addSequence(config);
|