rx-hotkeys 3.1.1 → 4.0.0
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 +76 -43
- package/dist/core/hotkeys.d.ts +35 -14
- package/dist/core/hotkeys.d.ts.map +1 -1
- package/dist/core/hotkeys.js +106 -42
- package/dist/core/hotkeys.js.map +1 -1
- package/dist/core/hotkeys.test.js +200 -145
- package/dist/core/hotkeys.test.js.map +1 -1
- package/dist/integrations/react/index.d.ts +1 -1
- package/dist/integrations/react/index.d.ts.map +1 -1
- package/dist/integrations/react/index.js +1 -1
- package/dist/integrations/react/index.js.map +1 -1
- package/dist/integrations/react/provider.d.ts +11 -17
- package/dist/integrations/react/provider.d.ts.map +1 -1
- package/dist/integrations/react/provider.js +47 -96
- package/dist/integrations/react/provider.js.map +1 -1
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@ import { describe, it, before, beforeEach, afterEach, mock } from "node:test";
|
|
|
2
2
|
import assert from "node:assert";
|
|
3
3
|
import { Hotkeys, ShortcutTypes } from "./hotkeys.js";
|
|
4
4
|
import { Keys } from "./keys.js";
|
|
5
|
-
import { fromEvent, BehaviorSubject, Observable, EMPTY } from "rxjs";
|
|
5
|
+
import { fromEvent, BehaviorSubject, Observable, EMPTY, firstValueFrom } from "rxjs";
|
|
6
6
|
import { createMockFn, dispatchKeyEvent } from "./testutils.js";
|
|
7
7
|
import { JSDOM } from "jsdom";
|
|
8
8
|
// --- JSDOM and RxJS setup for Node.js tests ---
|
|
@@ -79,99 +79,54 @@ describe("Hotkeys Library (Node.js Test Runner)", () => {
|
|
|
79
79
|
}
|
|
80
80
|
});
|
|
81
81
|
// ... (Initialization and Basic Context tests remain the same) ...
|
|
82
|
-
describe("Initialization and Context Management", () => {
|
|
83
|
-
it("should initialize
|
|
82
|
+
describe("Initialization and Context Stack Management", () => {
|
|
83
|
+
it("should initialize with a base context (null by default)", () => {
|
|
84
84
|
assert(keyManager instanceof Hotkeys);
|
|
85
|
-
assert.strictEqual(keyManager.
|
|
85
|
+
assert.strictEqual(keyManager.getActiveContext(), null);
|
|
86
86
|
});
|
|
87
|
-
it("should initialize with a
|
|
88
|
-
assert.strictEqual(keyManager.getContext(), null);
|
|
89
|
-
});
|
|
90
|
-
it("should initialize with a given initial context (debug off)", () => {
|
|
87
|
+
it("should initialize with a given initial context", () => {
|
|
91
88
|
const manager = new Hotkeys("editor", false);
|
|
92
|
-
assert.strictEqual(manager.
|
|
93
|
-
manager.destroy();
|
|
94
|
-
});
|
|
95
|
-
it("should log library initialization with context in debug mode", () => {
|
|
96
|
-
const consoleLogMock = mock.method(console, "log");
|
|
97
|
-
const manager = new Hotkeys("debugInitCtx", true);
|
|
98
|
-
const initLog = consoleLogMock.mock.calls.find(call => call.arguments[0].includes(`Library initialized. Initial context: "debugInitCtx"`));
|
|
99
|
-
assert.ok(initLog, "Library initialization log not found or incorrect.");
|
|
89
|
+
assert.strictEqual(manager.getActiveContext(), "editor");
|
|
100
90
|
manager.destroy();
|
|
101
|
-
consoleLogMock.mock.restore();
|
|
102
|
-
});
|
|
103
|
-
it("should set and get context correctly", () => {
|
|
104
|
-
keyManager.setContext("modal");
|
|
105
|
-
assert.strictEqual(keyManager.getContext(), "modal");
|
|
106
|
-
keyManager.setContext(null);
|
|
107
|
-
assert.strictEqual(keyManager.getContext(), null);
|
|
108
|
-
});
|
|
109
|
-
it("should log context change correctly when context is different (debug mode on)", () => {
|
|
110
|
-
const consoleLogMock = mock.method(console, "log");
|
|
111
|
-
keyManager.setDebugMode(true);
|
|
112
|
-
// Initial context is null for keyManager
|
|
113
|
-
consoleLogMock.mock.resetCalls(); // Clear "Debug mode enabled" log
|
|
114
|
-
keyManager.setContext("debug_test");
|
|
115
|
-
assert.ok(consoleLogMock.mock.calls.some(call => call.arguments[0].includes(`Context changed from "null" to "debug_test"`)), "Log for context change from null incorrect.");
|
|
116
|
-
assert.strictEqual(keyManager.getContext(), "debug_test");
|
|
117
|
-
consoleLogMock.mock.resetCalls();
|
|
118
|
-
keyManager.setContext("another_test");
|
|
119
|
-
assert.ok(consoleLogMock.mock.calls.some(call => call.arguments[0].includes(`Context changed from "debug_test" to "another_test"`)), "Log for context change between non-null incorrect.");
|
|
120
|
-
assert.strictEqual(keyManager.getContext(), "another_test");
|
|
121
|
-
consoleLogMock.mock.restore();
|
|
122
91
|
});
|
|
123
|
-
it("should
|
|
92
|
+
it("should allow entering a new context, making it active", () => {
|
|
93
|
+
assert.strictEqual(keyManager.getActiveContext(), null);
|
|
94
|
+
keyManager.enterContext("modal");
|
|
95
|
+
assert.strictEqual(keyManager.getActiveContext(), "modal");
|
|
96
|
+
});
|
|
97
|
+
it("should allow leaving a context, restoring the previous one and returning the one that was left", () => {
|
|
98
|
+
keyManager.enterContext("modal");
|
|
99
|
+
keyManager.enterContext("dropdown");
|
|
100
|
+
assert.strictEqual(keyManager.getActiveContext(), "dropdown");
|
|
101
|
+
const leftDropdown = keyManager.leaveContext();
|
|
102
|
+
assert.strictEqual(leftDropdown, "dropdown", `Should return "dropdown"`);
|
|
103
|
+
assert.strictEqual(keyManager.getActiveContext(), "modal");
|
|
104
|
+
const leftModal = keyManager.leaveContext();
|
|
105
|
+
assert.strictEqual(leftModal, "modal", `Should return "modal"`);
|
|
106
|
+
assert.strictEqual(keyManager.getActiveContext(), null);
|
|
107
|
+
});
|
|
108
|
+
it("should not allow leaving the base context and should return undefined", () => {
|
|
109
|
+
assert.strictEqual(keyManager.getActiveContext(), null);
|
|
110
|
+
const leftResult1 = keyManager.leaveContext(); // Attempt to leave
|
|
111
|
+
assert.strictEqual(leftResult1, undefined, "Should return undefined when leaving the base");
|
|
112
|
+
assert.strictEqual(keyManager.getActiveContext(), null);
|
|
113
|
+
keyManager.enterContext("modal");
|
|
114
|
+
keyManager.leaveContext();
|
|
115
|
+
const leftResult2 = keyManager.leaveContext(); // Second attempt
|
|
116
|
+
assert.strictEqual(leftResult2, undefined, "Should return undefined on second attempt");
|
|
117
|
+
assert.strictEqual(keyManager.getActiveContext(), null);
|
|
118
|
+
});
|
|
119
|
+
it("should log context changes in debug mode", () => {
|
|
124
120
|
const consoleLogMock = mock.method(console, "log");
|
|
125
|
-
|
|
126
|
-
|
|
121
|
+
const manager = new Hotkeys("base", true); // Debug on
|
|
122
|
+
consoleLogMock.mock.resetCalls(); // Clear init logs
|
|
123
|
+
manager.enterContext("modal");
|
|
124
|
+
assert.ok(consoleLogMock.mock.calls.some(call => call.arguments[0].includes(`Entering context: "modal"`)));
|
|
125
|
+
assert.ok(consoleLogMock.mock.calls.some(call => call.arguments[0].includes(`Active context changed to: modal`)));
|
|
127
126
|
consoleLogMock.mock.resetCalls();
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
assert.ok(
|
|
131
|
-
assert.strictEqual(keyManager.getContext(), null);
|
|
132
|
-
consoleLogMock.mock.restore();
|
|
133
|
-
});
|
|
134
|
-
it(`should not call activeContext$.next and log "no change" if context is set to the same value (debug mode on)`, () => {
|
|
135
|
-
keyManager.setContext("sameCtx"); // Set initial context
|
|
136
|
-
const consoleLogMock = mock.method(console, "log");
|
|
137
|
-
keyManager.setDebugMode(true); // Enable debug for this test
|
|
138
|
-
consoleLogMock.mock.resetCalls(); // Clear "Debug mode enabled" log
|
|
139
|
-
// @ts-ignore: Accessing private member for test
|
|
140
|
-
const activeContextNextSpy = mock.method(keyManager["activeContext$"], "next");
|
|
141
|
-
keyManager.setContext("sameCtx"); // Attempt to set the same context
|
|
142
|
-
const noChangeLogCall = consoleLogMock.mock.calls.find(call => call.arguments[0].includes(`setContext called with the same context "sameCtx". No change made.`));
|
|
143
|
-
assert.ok(noChangeLogCall, "No-change log not found or incorrect for same context.");
|
|
144
|
-
const changedLogCall = consoleLogMock.mock.calls.find(call => call.arguments[0].includes(`Context changed from`));
|
|
145
|
-
assert.strictEqual(changedLogCall, undefined, "Context changed log should not appear for same context.");
|
|
146
|
-
assert.strictEqual(keyManager.getContext(), "sameCtx");
|
|
147
|
-
assert.strictEqual(activeContextNextSpy.mock.callCount(), 0, "activeContext$.next should not have been called.");
|
|
148
|
-
activeContextNextSpy.mock.restore();
|
|
149
|
-
consoleLogMock.mock.restore();
|
|
150
|
-
});
|
|
151
|
-
it("should not log or call next if context is set to the same value (debug mode off)", () => {
|
|
152
|
-
keyManager.setContext("sameCtxNoDebug"); // Set initial context
|
|
153
|
-
keyManager.setDebugMode(false);
|
|
154
|
-
// Ensure debug is off
|
|
155
|
-
const consoleLogMock = mock.method(console, "log");
|
|
156
|
-
// @ts-ignore: Accessing private member for test
|
|
157
|
-
const activeContextNextSpy = mock.method(keyManager["activeContext$"], "next");
|
|
158
|
-
keyManager.setContext("sameCtxNoDebug"); // Attempt to set the same context
|
|
159
|
-
assert.strictEqual(consoleLogMock.mock.callCount(), 0, "Console.log should not have been called with debug mode off.");
|
|
160
|
-
assert.strictEqual(activeContextNextSpy.mock.callCount(), 0, "activeContext$.next should not have been called.");
|
|
161
|
-
assert.strictEqual(keyManager.getContext(), "sameCtxNoDebug");
|
|
162
|
-
activeContextNextSpy.mock.restore();
|
|
163
|
-
consoleLogMock.mock.restore();
|
|
164
|
-
});
|
|
165
|
-
it(`should log "no change" if context is set to the same value (debug mode on)`, () => {
|
|
166
|
-
keyManager.setContext("sameCtx"); // Set initial context
|
|
167
|
-
const consoleLogMock = mock.method(console, "log");
|
|
168
|
-
keyManager.setDebugMode(true);
|
|
169
|
-
consoleLogMock.mock.resetCalls(); // Clear previous logs
|
|
170
|
-
keyManager.setContext("sameCtx"); // Attempt to set the same context
|
|
171
|
-
const noChangeLogCall = consoleLogMock.mock.calls.find(call => call.arguments[0].includes(`setContext called with the same context "sameCtx". No change made.`));
|
|
172
|
-
assert.ok(noChangeLogCall, "No-change log not found or incorrect for same context.");
|
|
173
|
-
const changedLogCall = consoleLogMock.mock.calls.find(call => call.arguments[0].includes("Context changed from"));
|
|
174
|
-
assert.strictEqual(changedLogCall, undefined, "Context changed log should not appear for same context.");
|
|
127
|
+
manager.leaveContext();
|
|
128
|
+
assert.ok(consoleLogMock.mock.calls.some(call => call.arguments[0].includes(`Leaving context: "modal"`)));
|
|
129
|
+
assert.ok(consoleLogMock.mock.calls.some(call => call.arguments[0].includes(`Active context changed to: base`)));
|
|
175
130
|
consoleLogMock.mock.restore();
|
|
176
131
|
});
|
|
177
132
|
it("should toggle debug mode and log its state", () => {
|
|
@@ -183,73 +138,171 @@ describe("Hotkeys Library (Node.js Test Runner)", () => {
|
|
|
183
138
|
assert.ok(consoleLogMock.mock.calls.some(call => call.arguments[0].includes("Debug mode disabled")));
|
|
184
139
|
consoleLogMock.mock.restore();
|
|
185
140
|
});
|
|
186
|
-
describe("
|
|
187
|
-
it("should
|
|
188
|
-
|
|
189
|
-
const
|
|
190
|
-
assert.strictEqual(
|
|
191
|
-
assert.strictEqual(keyManager.getContext(), "newContext");
|
|
192
|
-
});
|
|
193
|
-
it("should return true when context is changed from one value to another", () => {
|
|
194
|
-
keyManager.setContext("initialContext"); // Set a non-null initial context
|
|
195
|
-
const result = keyManager.setContext("changedContext");
|
|
196
|
-
assert.strictEqual(result, true, "setContext should return true for a value-to-value change");
|
|
197
|
-
assert.strictEqual(keyManager.getContext(), "changedContext");
|
|
198
|
-
});
|
|
199
|
-
it("should return false when context is set to the same value (non-null)", () => {
|
|
200
|
-
keyManager.setContext("sameContext");
|
|
201
|
-
const result = keyManager.setContext("sameContext");
|
|
202
|
-
assert.strictEqual(result, false, "setContext should return false for no change (non-null)");
|
|
203
|
-
assert.strictEqual(keyManager.getContext(), "sameContext");
|
|
204
|
-
});
|
|
205
|
-
it("should return true when context is changed from a value to null", () => {
|
|
206
|
-
keyManager.setContext("initialContext");
|
|
207
|
-
const result = keyManager.setContext(null);
|
|
208
|
-
assert.strictEqual(result, true, "setContext should return true for a change to null");
|
|
209
|
-
assert.strictEqual(keyManager.getContext(), null);
|
|
210
|
-
});
|
|
211
|
-
it("should return false when context is set to null and already null", () => {
|
|
212
|
-
keyManager.setContext(null); // Ensure context is null
|
|
213
|
-
const result = keyManager.setContext(null);
|
|
214
|
-
assert.strictEqual(result, false, "setContext should return false for no change (already null)");
|
|
215
|
-
assert.strictEqual(keyManager.getContext(), null);
|
|
216
|
-
});
|
|
217
|
-
});
|
|
218
|
-
describe("onContextChange$ observable", () => {
|
|
219
|
-
it("should emit initial context to new subscriber", () => {
|
|
220
|
-
const manager = new Hotkeys("initial", false);
|
|
221
|
-
const spy = createMockFn();
|
|
222
|
-
const sub = manager.onContextChange$.subscribe(spy);
|
|
223
|
-
assert.strictEqual(spy.calledCount, 1);
|
|
224
|
-
assert.strictEqual(spy.lastArgs[0], "initial");
|
|
225
|
-
sub.unsubscribe();
|
|
141
|
+
describe("activeContext$ observable", () => {
|
|
142
|
+
it("should emit the initial context to a new subscriber", async () => {
|
|
143
|
+
const manager = new Hotkeys("initial");
|
|
144
|
+
const activeContext = await firstValueFrom(manager.onContextChange$);
|
|
145
|
+
assert.strictEqual(activeContext, "initial");
|
|
226
146
|
manager.destroy();
|
|
227
147
|
});
|
|
228
|
-
it("should emit when context changes", () => {
|
|
148
|
+
it("should emit when the active context changes via enter/leave", () => {
|
|
229
149
|
const spy = createMockFn();
|
|
230
|
-
const sub = keyManager.onContextChange$.subscribe(spy);
|
|
231
|
-
spy.
|
|
232
|
-
assert.
|
|
233
|
-
|
|
150
|
+
const sub = keyManager.onContextChange$.subscribe(spy);
|
|
151
|
+
assert.strictEqual(spy.calledCount, 1, "Did not emit initial context");
|
|
152
|
+
assert.strictEqual(spy.lastArgs[0], null);
|
|
153
|
+
keyManager.enterContext("newContext");
|
|
154
|
+
assert.strictEqual(spy.calledCount, 2, "Did not emit on enterContext");
|
|
234
155
|
assert.strictEqual(spy.lastArgs[0], "newContext");
|
|
235
|
-
|
|
236
|
-
assert.strictEqual(spy.calledCount,
|
|
156
|
+
keyManager.enterContext("anotherContext");
|
|
157
|
+
assert.strictEqual(spy.calledCount, 3, "Did not emit on second enterContext");
|
|
237
158
|
assert.strictEqual(spy.lastArgs[0], "anotherContext");
|
|
238
|
-
|
|
239
|
-
assert.strictEqual(spy.calledCount,
|
|
159
|
+
keyManager.leaveContext();
|
|
160
|
+
assert.strictEqual(spy.calledCount, 4, "Did not emit on leaveContext");
|
|
161
|
+
assert.strictEqual(spy.lastArgs[0], "newContext");
|
|
162
|
+
keyManager.leaveContext();
|
|
163
|
+
assert.strictEqual(spy.calledCount, 5, "Did not emit on final leaveContext");
|
|
240
164
|
assert.strictEqual(spy.lastArgs[0], null);
|
|
241
165
|
sub.unsubscribe();
|
|
242
166
|
});
|
|
243
|
-
it("should not emit if context
|
|
244
|
-
keyManager.setContext("testContext");
|
|
167
|
+
it("should not emit if the active context does not change (e.g. attempting to leave base context)", () => {
|
|
245
168
|
const spy = createMockFn();
|
|
246
|
-
const sub = keyManager.onContextChange$.subscribe(spy); //
|
|
169
|
+
const sub = keyManager.onContextChange$.subscribe(spy); // Emits initial `null`
|
|
247
170
|
spy.mockClear(); // Clear initial emission
|
|
248
|
-
|
|
249
|
-
assert.strictEqual(spy.calledCount, 0, "
|
|
171
|
+
keyManager.leaveContext();
|
|
172
|
+
assert.strictEqual(spy.calledCount, 0, "Should not emit when leaving base context");
|
|
250
173
|
sub.unsubscribe();
|
|
251
174
|
});
|
|
252
175
|
});
|
|
176
|
+
describe("Deprecated context methods", () => {
|
|
177
|
+
it("getContext should warn and return active context", () => {
|
|
178
|
+
keyManager.enterContext("test");
|
|
179
|
+
const ctx = keyManager.getContext();
|
|
180
|
+
assert.strictEqual(ctx, "test");
|
|
181
|
+
assert.ok(consoleWarnMock.mock.calls.some(c => c.arguments[0].includes(`"getContext" is deprecated`)));
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
describe("Context Management (Stack and Override)", () => {
|
|
186
|
+
describe("Context Stack (enter/leave)", () => {
|
|
187
|
+
it("should enter and leave contexts on the stack correctly", () => {
|
|
188
|
+
assert.strictEqual(keyManager.getActiveContext(), null);
|
|
189
|
+
keyManager.enterContext("modal");
|
|
190
|
+
assert.strictEqual(keyManager.getActiveContext(), "modal");
|
|
191
|
+
keyManager.enterContext("dropdown");
|
|
192
|
+
assert.strictEqual(keyManager.getActiveContext(), "dropdown");
|
|
193
|
+
const left1 = keyManager.leaveContext();
|
|
194
|
+
assert.strictEqual(left1, "dropdown");
|
|
195
|
+
assert.strictEqual(keyManager.getActiveContext(), "modal");
|
|
196
|
+
const left2 = keyManager.leaveContext();
|
|
197
|
+
assert.strictEqual(left2, "modal");
|
|
198
|
+
assert.strictEqual(keyManager.getActiveContext(), null);
|
|
199
|
+
const left3 = keyManager.leaveContext();
|
|
200
|
+
assert.strictEqual(left3, undefined);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
describe("Context Override (setContext/restore)", () => {
|
|
204
|
+
it("setContext should set an override context and return a restore function", () => {
|
|
205
|
+
keyManager.enterContext("parent");
|
|
206
|
+
assert.strictEqual(keyManager.getActiveContext(), "parent");
|
|
207
|
+
const restore = keyManager.setContext("override");
|
|
208
|
+
assert.strictEqual(keyManager.getActiveContext(), "override");
|
|
209
|
+
restore();
|
|
210
|
+
assert.strictEqual(keyManager.getActiveContext(), "parent");
|
|
211
|
+
});
|
|
212
|
+
it("the override context should take precedence over the stack context", () => {
|
|
213
|
+
keyManager.enterContext("parent");
|
|
214
|
+
keyManager.setContext("override");
|
|
215
|
+
// Even if we change the stack, the override should remain active
|
|
216
|
+
keyManager.enterContext("child");
|
|
217
|
+
assert.strictEqual(keyManager.getActiveContext(), "override");
|
|
218
|
+
keyManager.leaveContext(); // leaves "child"
|
|
219
|
+
assert.strictEqual(keyManager.getActiveContext(), "override");
|
|
220
|
+
});
|
|
221
|
+
it("restoring the override should reveal the correct, current stack context", () => {
|
|
222
|
+
keyManager.enterContext("parent");
|
|
223
|
+
const restore = keyManager.setContext("override");
|
|
224
|
+
keyManager.enterContext("child");
|
|
225
|
+
assert.strictEqual(keyManager.getActiveContext(), "override");
|
|
226
|
+
restore();
|
|
227
|
+
assert.strictEqual(keyManager.getActiveContext(), "child", `Should reveal "child" which is top of the stack`);
|
|
228
|
+
});
|
|
229
|
+
it("should distinguish between restoring an override and setting an override to null", () => {
|
|
230
|
+
keyManager.enterContext("parent");
|
|
231
|
+
assert.strictEqual(keyManager.getActiveContext(), "parent");
|
|
232
|
+
// 1. Set an override to "null". The active context should be null.
|
|
233
|
+
const restoreNull = keyManager.setContext(null);
|
|
234
|
+
assert.strictEqual(keyManager.getActiveContext(), null, "Active context should be null due to override.");
|
|
235
|
+
// Leaving the stack context should have no effect on the active context, since the override is active.
|
|
236
|
+
keyManager.leaveContext(); // Tries to leave "parent"
|
|
237
|
+
assert.strictEqual(keyManager.getActiveContext(), null, "Active context should still be null after stack change.");
|
|
238
|
+
// Restore from the null override. Active context should revert to the top of the stack.
|
|
239
|
+
restoreNull();
|
|
240
|
+
assert.strictEqual(keyManager.getActiveContext(), null, `Active context should now be from the stack, which is "null".`);
|
|
241
|
+
// 2. Test restore from a non-null override
|
|
242
|
+
keyManager.enterContext("parent"); // Stack is [null, parent]
|
|
243
|
+
const restoreEditor = keyManager.setContext("editor");
|
|
244
|
+
assert.strictEqual(keyManager.getActiveContext(), "editor");
|
|
245
|
+
restoreEditor();
|
|
246
|
+
assert.strictEqual(keyManager.getActiveContext(), "parent");
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
describe("Active Context Logic and Shortcuts", () => {
|
|
250
|
+
it("should trigger shortcuts based on the override context", () => {
|
|
251
|
+
const overrideCb = createMockFn();
|
|
252
|
+
keyManager.addCombination({ id: "o", keys: "o", context: "override" }).subscribe(overrideCb);
|
|
253
|
+
const parentCb = createMockFn();
|
|
254
|
+
keyManager.addCombination({ id: "p", keys: "p", context: "parent" }).subscribe(parentCb);
|
|
255
|
+
keyManager.enterContext("parent");
|
|
256
|
+
keyManager.setContext("override");
|
|
257
|
+
dispatchKeyEvent(document, "p");
|
|
258
|
+
assert.strictEqual(parentCb.calledCount, 0);
|
|
259
|
+
dispatchKeyEvent(document, "o");
|
|
260
|
+
assert.strictEqual(overrideCb.calledCount, 1);
|
|
261
|
+
});
|
|
262
|
+
it("should trigger stack context shortcuts after override is restored", () => {
|
|
263
|
+
const overrideCb = createMockFn();
|
|
264
|
+
keyManager.addCombination({ id: "o", keys: "o", context: "override" }).subscribe(overrideCb);
|
|
265
|
+
const parentCb = createMockFn();
|
|
266
|
+
keyManager.addCombination({ id: "p", keys: "p", context: "parent" }).subscribe(parentCb);
|
|
267
|
+
keyManager.enterContext("parent");
|
|
268
|
+
const restore = keyManager.setContext("override");
|
|
269
|
+
dispatchKeyEvent(document, "p");
|
|
270
|
+
assert.strictEqual(parentCb.calledCount, 0);
|
|
271
|
+
restore(); // Restore context back to "parent"
|
|
272
|
+
dispatchKeyEvent(document, "p");
|
|
273
|
+
assert.strictEqual(parentCb.calledCount, 1);
|
|
274
|
+
dispatchKeyEvent(document, "o");
|
|
275
|
+
assert.strictEqual(overrideCb.calledCount, 0);
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
describe("Contextual Shortcut Triggering", () => {
|
|
280
|
+
it("should only trigger shortcut in its active context", () => {
|
|
281
|
+
keyManager.addCombination({ id: "test", keys: "a", context: "editor" }).subscribe(mockCallback);
|
|
282
|
+
// 1. Wrong context
|
|
283
|
+
keyManager.enterContext("modal");
|
|
284
|
+
dispatchKeyEvent(document, "a");
|
|
285
|
+
assert.strictEqual(mockCallback.calledCount, 0, `Should not trigger in "modal" context`);
|
|
286
|
+
// 2. No context (should not trigger)
|
|
287
|
+
keyManager.leaveContext(); // back to "null"
|
|
288
|
+
dispatchKeyEvent(document, "a");
|
|
289
|
+
assert.strictEqual(mockCallback.calledCount, 0, `Should not trigger in "null" context`);
|
|
290
|
+
// 3. Correct context
|
|
291
|
+
keyManager.enterContext("editor");
|
|
292
|
+
dispatchKeyEvent(document, "a");
|
|
293
|
+
assert.strictEqual(mockCallback.calledCount, 1, `Should trigger in "editor" context`);
|
|
294
|
+
});
|
|
295
|
+
it("global shortcut should trigger if no specific context is active", () => {
|
|
296
|
+
keyManager.addCombination({ id: "global", keys: "g" }).subscribe(mockCallback);
|
|
297
|
+
dispatchKeyEvent(document, "g");
|
|
298
|
+
assert.strictEqual(mockCallback.calledCount, 1, `Global shortcut should trigger in "null" context`);
|
|
299
|
+
});
|
|
300
|
+
it("global shortcut should trigger if a different specific context is active", () => {
|
|
301
|
+
keyManager.addCombination({ id: "global", keys: "g" }).subscribe(mockCallback);
|
|
302
|
+
keyManager.enterContext("editor");
|
|
303
|
+
dispatchKeyEvent(document, "g");
|
|
304
|
+
assert.strictEqual(mockCallback.calledCount, 1, "Global shortcut should trigger in any active context");
|
|
305
|
+
});
|
|
253
306
|
});
|
|
254
307
|
describe("addCombination", () => {
|
|
255
308
|
it(`should return an Observable that emits on a simple key combination (e.g., "A")`, () => {
|
|
@@ -637,18 +690,20 @@ describe("Hotkeys Library (Node.js Test Runner)", () => {
|
|
|
637
690
|
}).subscribe(specificCallback);
|
|
638
691
|
});
|
|
639
692
|
it("should only trigger specific context callback when specific context is active", () => {
|
|
640
|
-
keyManager.
|
|
693
|
+
keyManager.enterContext("editor");
|
|
641
694
|
dispatchKeyEvent(document, Keys.G, "keydown", { ctrlKey: true });
|
|
642
695
|
assert.strictEqual(specificCallback.calledCount, 1, "Specific callback should have been called");
|
|
643
696
|
assert.strictEqual(globalCallback.calledCount, 0, "Global callback should NOT have been called");
|
|
644
697
|
});
|
|
645
|
-
it("should only trigger global callback when no specific context is active
|
|
646
|
-
|
|
698
|
+
it("should only trigger global callback when no specific context is active", () => {
|
|
699
|
+
// Initial context is null
|
|
647
700
|
dispatchKeyEvent(document, Keys.G, "keydown", { ctrlKey: true });
|
|
648
701
|
assert.strictEqual(specificCallback.calledCount, 0, "Specific callback should NOT have been called");
|
|
649
702
|
assert.strictEqual(globalCallback.calledCount, 1, "Global callback should have been called");
|
|
703
|
+
});
|
|
704
|
+
it("should trigger global callback when a non-matching specific context is active", () => {
|
|
650
705
|
globalCallback.mockClear();
|
|
651
|
-
keyManager.
|
|
706
|
+
keyManager.enterContext("anotherContext"); // Different specific context
|
|
652
707
|
dispatchKeyEvent(document, Keys.G, "keydown", { ctrlKey: true });
|
|
653
708
|
assert.strictEqual(specificCallback.calledCount, 0, `Specific callback should NOT have been called for "anotherContext"`);
|
|
654
709
|
assert.strictEqual(globalCallback.calledCount, 1, `Global callback should have been called when in "anotherContext"`);
|