magus-cli 7.3.1 → 7.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.
Files changed (127) hide show
  1. package/package.json +4 -4
  2. package/src/__tests__/blind/c1-npm-package.test.ts +206 -0
  3. package/src/__tests__/blind/c10-colour-roles.test.ts +265 -0
  4. package/src/__tests__/blind/c11-cli-parse-refusal.test.ts +454 -0
  5. package/src/__tests__/blind/c12-skill-frontmatter.test.ts +323 -0
  6. package/src/__tests__/blind/c13-wrong-shape.test.ts +603 -0
  7. package/src/__tests__/blind/c14-cli-row-label.test.ts +259 -0
  8. package/src/__tests__/blind/c15-probe-ownership.test.ts +533 -0
  9. package/src/__tests__/blind/c16-nested-wrong-shape.test.ts +297 -0
  10. package/src/__tests__/blind/c17-enter-label.test.ts +625 -0
  11. package/src/__tests__/blind/c18-hook-shape.test.ts +354 -0
  12. package/src/__tests__/blind/c19-paste-routing.test.ts +252 -0
  13. package/src/__tests__/blind/c2-settings-parse.test.ts +350 -0
  14. package/src/__tests__/blind/c20-registry-and-file-safety.test.ts +560 -0
  15. package/src/__tests__/blind/c4-key-dispatch.test.ts +397 -0
  16. package/src/__tests__/blind/c5-keymap.test.ts +461 -0
  17. package/src/__tests__/blind/c6-request-gate.test.ts +133 -0
  18. package/src/__tests__/blind/c7-tab-tiers.test.ts +320 -0
  19. package/src/__tests__/blind/c8-text-width.test.ts +137 -0
  20. package/src/__tests__/blind/c9-cli-tool-version.test.ts +239 -0
  21. package/src/__tests__/blind/child-run.ts +114 -0
  22. package/src/__tests__/cli-tools-adapter.test.ts +5 -2
  23. package/src/__tests__/component-badge.test.ts +20 -36
  24. package/src/__tests__/footer-hints.test.ts +1 -1
  25. package/src/__tests__/manifest.test.ts +25 -17
  26. package/src/__tests__/marketplace-badge.test.ts +4 -4
  27. package/src/__tests__/models-screen-state.test.ts +36 -13
  28. package/src/__tests__/scope-squares.test.tsx +28 -114
  29. package/src/__tests__/shutdown.test.ts +578 -0
  30. package/src/__tests__/stale-load-repair.test.ts +5 -4
  31. package/src/__tests__/styles-manager.test.ts +4 -4
  32. package/src/__tests__/styles-screen-state.test.ts +26 -12
  33. package/src/__tests__/tabbar-layout.test.ts +44 -32
  34. package/src/__tests__/theme-adaptive-colors.test.ts +6 -59
  35. package/src/__tests__/tool-probe.test.ts +47 -22
  36. package/src/__tests__/uppercase-keybindings.test.ts +25 -12
  37. package/src/cli/ansi.ts +2 -2
  38. package/src/cli/bootstrap.ts +33 -2
  39. package/src/cli/doctor.ts +120 -15
  40. package/src/cli/install.ts +35 -4
  41. package/src/cli/profile.ts +18 -1
  42. package/src/cli/router.ts +31 -18
  43. package/src/cli/update-view.ts +1 -1
  44. package/src/data/cli-tools.ts +9 -1
  45. package/src/services/claude-settings.ts +434 -198
  46. package/src/services/cli-tool-commands.ts +45 -5
  47. package/src/services/cli-tool-version.ts +101 -0
  48. package/src/services/gitignore-service.ts +39 -22
  49. package/src/services/hook-registration.ts +23 -6
  50. package/src/services/manifest.ts +117 -52
  51. package/src/services/plugin-manager.ts +19 -5
  52. package/src/services/profile-adopt.ts +7 -2
  53. package/src/services/profile-startup.ts +87 -7
  54. package/src/services/profiles.ts +36 -23
  55. package/src/services/settings-file.ts +164 -4
  56. package/src/services/settings-manager.ts +19 -5
  57. package/src/services/skills-manager.ts +46 -0
  58. package/src/services/tool-probe.ts +316 -44
  59. package/src/services/toolchain.ts +39 -0
  60. package/src/services/update-engine.ts +5 -0
  61. package/src/services/update-plan.ts +9 -0
  62. package/src/tui.tsx +48 -21
  63. package/src/ui/App.tsx +58 -197
  64. package/src/ui/adapters/cliToolsAdapter.ts +141 -12
  65. package/src/ui/adapters/pluginsAdapter.ts +10 -7
  66. package/src/ui/adapters/settingsAdapter.ts +0 -21
  67. package/src/ui/adapters/skillsAdapter.ts +27 -8
  68. package/src/ui/adapters/stylesAdapter.ts +15 -11
  69. package/src/ui/components/CategoryHeader.tsx +7 -1
  70. package/src/ui/components/EmptyFilterState.tsx +2 -2
  71. package/src/ui/components/FlagDetailEditor.tsx +0 -0
  72. package/src/ui/components/ScreenErrorBoundary.tsx +105 -0
  73. package/src/ui/components/TabBar.tsx +93 -21
  74. package/src/ui/components/layout/FooterHints.tsx +6 -27
  75. package/src/ui/components/layout/ScreenLayout.tsx +68 -42
  76. package/src/ui/components/layout/index.ts +1 -5
  77. package/src/ui/components/modals/ConfirmModal.tsx +2 -19
  78. package/src/ui/components/modals/HelpOverlay.tsx +186 -0
  79. package/src/ui/components/modals/MessageModal.tsx +3 -15
  80. package/src/ui/components/modals/ModalContainer.tsx +107 -60
  81. package/src/ui/components/primitives/ErrorNotice.tsx +38 -0
  82. package/src/ui/components/primitives/ListCategoryRow.tsx +18 -7
  83. package/src/ui/components/primitives/MetaText.tsx +1 -1
  84. package/src/ui/components/primitives/ScopeDetail.tsx +31 -26
  85. package/src/ui/components/primitives/ScopeSquares.tsx +23 -28
  86. package/src/ui/components/primitives/SelectableRow.tsx +24 -1
  87. package/src/ui/components/primitives/index.ts +4 -3
  88. package/src/ui/hooks/useProfileStartupModal.ts +15 -3
  89. package/src/ui/input/KeyRouter.tsx +304 -0
  90. package/src/ui/input/key-dispatch.ts +197 -0
  91. package/src/ui/keymap.ts +905 -0
  92. package/src/ui/renderers/cliToolRenderers.tsx +192 -92
  93. package/src/ui/renderers/gitignoreRenderers.tsx +41 -43
  94. package/src/ui/renderers/markdownRenderer.tsx +2 -2
  95. package/src/ui/renderers/mcpRenderers.tsx +28 -14
  96. package/src/ui/renderers/modelRenderers.tsx +12 -13
  97. package/src/ui/renderers/modelVisuals.tsx +0 -2
  98. package/src/ui/renderers/pluginRenderers.tsx +47 -54
  99. package/src/ui/renderers/profileRenderers.tsx +32 -37
  100. package/src/ui/renderers/settingsRenderers.tsx +84 -55
  101. package/src/ui/renderers/skillRenderers.tsx +237 -190
  102. package/src/ui/renderers/styleRenderers.tsx +13 -2
  103. package/src/ui/runtime/shutdown.ts +268 -0
  104. package/src/ui/screens/AliasScreen.tsx +118 -100
  105. package/src/ui/screens/CliToolsScreen.tsx +173 -101
  106. package/src/ui/screens/EnvVarsScreen.tsx +44 -42
  107. package/src/ui/screens/GitignoreScreen.tsx +50 -55
  108. package/src/ui/screens/McpRegistryScreen.tsx +52 -86
  109. package/src/ui/screens/McpScreen.tsx +47 -33
  110. package/src/ui/screens/ModelsScreen.tsx +59 -104
  111. package/src/ui/screens/PluginsScreen.tsx +129 -129
  112. package/src/ui/screens/ProfilesScreen.tsx +49 -46
  113. package/src/ui/screens/SkillsScreen.tsx +184 -204
  114. package/src/ui/screens/StylesScreen.tsx +104 -157
  115. package/src/ui/state/AppContext.tsx +6 -0
  116. package/src/ui/state/DimensionsContext.tsx +1 -10
  117. package/src/ui/state/reducer.ts +5 -8
  118. package/src/ui/state/types.ts +11 -9
  119. package/src/ui/text-width.ts +341 -0
  120. package/src/ui/theme-mode.ts +34 -71
  121. package/src/ui/theme.ts +50 -28
  122. package/src/utils/request-gate.ts +37 -0
  123. package/src/ui/components/ScopeIndicator.tsx +0 -63
  124. package/src/ui/components/SearchInput.tsx +0 -57
  125. package/src/ui/hooks/index.ts +0 -2
  126. package/src/ui/hooks/useKeyboard.ts +0 -24
  127. package/src/ui/hooks/useKeyboardHandler.ts +0 -63
@@ -0,0 +1,397 @@
1
+ /**
2
+ * Blind tests — contract C4 (requirements R4.1–R4.4, R4.6–R4.8).
3
+ *
4
+ * Written from requirements.md and architecture.md only. Layers are stubs that
5
+ * record every handle() call, so "a lower layer never sees the key" is
6
+ * observed directly rather than inferred.
7
+ */
8
+ import { describe, expect, test } from "bun:test";
9
+
10
+ const load = () => import("../../ui/input/key-dispatch.js");
11
+
12
+ type LayerId = "system" | "modal" | "input" | "screen" | "scroll" | "global";
13
+
14
+ interface Key {
15
+ name: string;
16
+ sequence: string;
17
+ ctrl: boolean;
18
+ meta: boolean;
19
+ shift: boolean;
20
+ }
21
+
22
+ const ORDER: LayerId[] = [
23
+ "system",
24
+ "modal",
25
+ "input",
26
+ "screen",
27
+ "scroll",
28
+ "global",
29
+ ];
30
+
31
+ const SEQ: Record<string, string> = {
32
+ space: " ",
33
+ return: "\r",
34
+ escape: "\x1b",
35
+ backspace: "\x7f",
36
+ up: "\x1b[A",
37
+ down: "\x1b[B",
38
+ pageup: "\x1b[5~",
39
+ pagedown: "\x1b[6~",
40
+ tab: "\t",
41
+ };
42
+
43
+ /** A KeyPress as OpenTUI names it. Printable keys use their own char as sequence. */
44
+ function key(name: string, mods: Partial<Omit<Key, "name">> = {}): Key {
45
+ let sequence = SEQ[name] ?? name;
46
+ if (mods.ctrl && /^[a-z]$/.test(name)) {
47
+ sequence = String.fromCharCode(name.charCodeAt(0) - 96);
48
+ }
49
+ if (name === "tab" && mods.shift) sequence = "\x1b[Z";
50
+ return { name, sequence, ctrl: false, meta: false, shift: false, ...mods };
51
+ }
52
+
53
+ const ctrl = (c: string) => key(c, { ctrl: true });
54
+ /** OpenTUI reports "U" as name "u", shift true, sequence "U". */
55
+ const upper = (c: string) =>
56
+ key(c.toLowerCase(), { shift: true, sequence: c.toUpperCase() });
57
+
58
+ const DIGITS = "1234567890".split("").map((d) => key(d));
59
+ const SCROLL_KEYS = [key("pageup"), key("pagedown"), ctrl("u"), ctrl("d")];
60
+
61
+ interface Recorder {
62
+ calls: LayerId[];
63
+ layers: { id: LayerId; active: boolean; handle(k: Key): boolean }[];
64
+ }
65
+
66
+ /** Build one layer per id. `consumes[id]` decides; absent means "never consumes". */
67
+ function stack(
68
+ consumes: Partial<Record<LayerId, (k: Key) => boolean>>,
69
+ active: Partial<Record<LayerId, boolean>> = {},
70
+ order: LayerId[] = ORDER,
71
+ ): Recorder {
72
+ const calls: LayerId[] = [];
73
+ const layers = order.map((id) => ({
74
+ id,
75
+ active: active[id] ?? true,
76
+ handle(k: Key): boolean {
77
+ calls.push(id);
78
+ return consumes[id]?.(k) ?? false;
79
+ },
80
+ }));
81
+ return { calls, layers };
82
+ }
83
+
84
+ const all = () => true;
85
+
86
+ describe("C4 — LAYER_ORDER", () => {
87
+ test("R4 layers run top first: system, modal, input, screen, scroll, global", async () => {
88
+ const { LAYER_ORDER } = await load();
89
+ expect([...LAYER_ORDER]).toEqual(ORDER);
90
+ });
91
+ });
92
+
93
+ describe("C4 — dispatchKey: one keypress, one handler", () => {
94
+ for (const [i, consumer] of ORDER.entries()) {
95
+ test(`R4.1 when ${consumer} consumes, dispatchKey returns "${consumer}" and no lower layer is called`, async () => {
96
+ const { dispatchKey } = await load();
97
+ // Every layer from the consumer down would also consume — only order
98
+ // decides who gets it.
99
+ const consumes: Partial<Record<LayerId, (k: Key) => boolean>> = {};
100
+ for (const id of ORDER.slice(i)) consumes[id] = all;
101
+ const r = stack(consumes);
102
+ expect(dispatchKey(r.layers, key("x"))).toBe(consumer);
103
+ expect(r.calls).toEqual(ORDER.slice(0, i + 1));
104
+ });
105
+ }
106
+
107
+ test("R4.1 layers are visited in LAYER_ORDER regardless of array order", async () => {
108
+ const { dispatchKey } = await load();
109
+ const shuffled: LayerId[] = [
110
+ "global",
111
+ "screen",
112
+ "system",
113
+ "scroll",
114
+ "input",
115
+ "modal",
116
+ ];
117
+ const r = stack({}, {}, shuffled);
118
+ expect(dispatchKey(r.layers, key("x"))).toBeNull();
119
+ expect(r.calls).toEqual(ORDER);
120
+ });
121
+
122
+ test("R4.1 with every layer consuming in reversed array order, system wins alone", async () => {
123
+ const { dispatchKey } = await load();
124
+ const r = stack(
125
+ {
126
+ system: all,
127
+ modal: all,
128
+ input: all,
129
+ screen: all,
130
+ scroll: all,
131
+ global: all,
132
+ },
133
+ {},
134
+ [...ORDER].reverse(),
135
+ );
136
+ expect(dispatchKey(r.layers, key("x"))).toBe("system");
137
+ expect(r.calls).toEqual(["system"]);
138
+ });
139
+
140
+ test("R4.1 inactive layers are skipped: their handle() is never called", async () => {
141
+ const { dispatchKey } = await load();
142
+ const r = stack(
143
+ { modal: all, input: all, global: all },
144
+ { modal: false, input: false },
145
+ );
146
+ expect(dispatchKey(r.layers, key("x"))).toBe("global");
147
+ expect(r.calls).toEqual(["system", "screen", "scroll", "global"]);
148
+ });
149
+
150
+ test("R4.1 dispatchKey returns null when no layer consumes, calling each active layer once", async () => {
151
+ const { dispatchKey } = await load();
152
+ const r = stack({});
153
+ expect(dispatchKey(r.layers, key("x"))).toBeNull();
154
+ expect(r.calls).toEqual(ORDER);
155
+ });
156
+
157
+ test("R4.1 missing layers are fine: a stack of only screen and global", async () => {
158
+ const { dispatchKey } = await load();
159
+ const calls: LayerId[] = [];
160
+ const layers = (["global", "screen"] as LayerId[]).map((id) => ({
161
+ id,
162
+ active: true,
163
+ handle: () => {
164
+ calls.push(id);
165
+ return id === "global";
166
+ },
167
+ }));
168
+ expect(dispatchKey(layers, key("1"))).toBe("global");
169
+ expect(calls).toEqual(["screen", "global"]);
170
+ });
171
+ });
172
+
173
+ describe("C4 — system and modal layers", () => {
174
+ test("R4.2 Ctrl+C is consumed by system even while a modal that eats every key is open", async () => {
175
+ const { dispatchKey } = await load();
176
+ const r = stack({
177
+ system: (k) => k.ctrl && k.name === "c",
178
+ modal: all,
179
+ global: all,
180
+ });
181
+ expect(dispatchKey(r.layers, ctrl("c"))).toBe("system");
182
+ expect(r.calls).toEqual(["system"]);
183
+ });
184
+
185
+ const MODAL_KEYS = [
186
+ ...DIGITS,
187
+ key("q"),
188
+ key("?"),
189
+ key("escape"),
190
+ key("return"),
191
+ key("tab"),
192
+ key("tab", { shift: true }),
193
+ key("up"),
194
+ key("down"),
195
+ key("j"),
196
+ key("k"),
197
+ ...SCROLL_KEYS,
198
+ ];
199
+
200
+ for (const k of MODAL_KEYS) {
201
+ const label = `${k.ctrl ? "ctrl+" : ""}${k.shift ? "shift+" : ""}${k.name}`;
202
+ test(`R4.3 with a modal open, ${label} reaches no input/screen/scroll/global handler`, async () => {
203
+ const { dispatchKey } = await load();
204
+ const r = stack({
205
+ modal: all,
206
+ input: all,
207
+ screen: all,
208
+ scroll: all,
209
+ global: all,
210
+ });
211
+ expect(dispatchKey(r.layers, k)).toBe("modal");
212
+ expect(r.calls).toEqual(["system", "modal"]);
213
+ });
214
+ }
215
+
216
+ test("R4.8 Enter in a confirm modal confirms the modal only; the screen never sees it", async () => {
217
+ const { dispatchKey } = await load();
218
+ let confirmed = 0;
219
+ let screenActed = 0;
220
+ const r = stack({
221
+ modal: (k) => {
222
+ if (k.name === "return") confirmed++;
223
+ return true;
224
+ },
225
+ screen: (k) => {
226
+ if (k.name === "return") screenActed++;
227
+ return true;
228
+ },
229
+ });
230
+ expect(dispatchKey(r.layers, key("return"))).toBe("modal");
231
+ expect(confirmed).toBe(1);
232
+ expect(screenActed).toBe(0);
233
+ });
234
+ });
235
+
236
+ describe("C4 — input layer (a focused search box) wired through inputLayerConsumes", () => {
237
+ /**
238
+ * A realistic stack: input consumes exactly what inputLayerConsumes says,
239
+ * the screen moves the selection on up/down, scroll takes isScrollKey, and
240
+ * global would act on anything that reaches it.
241
+ */
242
+ async function focusedStack() {
243
+ const { dispatchKey, inputLayerConsumes, isScrollKey } = await load();
244
+ const r = stack({
245
+ input: (k) => inputLayerConsumes(k),
246
+ screen: (k) => k.name === "up" || k.name === "down",
247
+ scroll: (k) => isScrollKey(k),
248
+ global: all,
249
+ });
250
+ return { dispatch: (k: Key) => dispatchKey(r.layers, k), calls: r.calls };
251
+ }
252
+
253
+ for (const d of DIGITS) {
254
+ test(`R4.4 digit ${d.name} typed into search does not switch tabs`, async () => {
255
+ const s = await focusedStack();
256
+ expect(s.dispatch(d)).toBe("input");
257
+ expect(s.calls).not.toContain("global");
258
+ });
259
+ }
260
+
261
+ for (const [name, k] of [
262
+ ["j", key("j")],
263
+ ["k", key("k")],
264
+ ["q", key("q")],
265
+ ["?", key("?")],
266
+ ["? (with shift)", key("?", { shift: true })],
267
+ ["space", key("space")],
268
+ ["backspace", key("backspace")],
269
+ ["Enter", key("return")],
270
+ ] as const) {
271
+ test(`R4.4 ${name} with search focused is handled by the input layer only`, async () => {
272
+ const s = await focusedStack();
273
+ expect(s.dispatch(k)).toBe("input");
274
+ expect(s.calls).toEqual(["system", "modal", "input"]);
275
+ });
276
+ }
277
+
278
+ test("R4.4 Esc with search focused never reaches global (never quits or changes screen)", async () => {
279
+ const s = await focusedStack();
280
+ expect(s.dispatch(key("escape"))).toBe("input");
281
+ expect(s.calls).not.toContain("global");
282
+ });
283
+
284
+ for (const dir of ["up", "down"]) {
285
+ test(`R4.4 ${dir} passes through the focused input to the screen (moves the selection)`, async () => {
286
+ const s = await focusedStack();
287
+ expect(s.dispatch(key(dir))).toBe("screen");
288
+ expect(s.calls).toEqual(["system", "modal", "input", "screen"]);
289
+ });
290
+ }
291
+
292
+ for (const k of SCROLL_KEYS) {
293
+ const label = `${k.ctrl ? "ctrl+" : ""}${k.name}`;
294
+ test(`R4.6 ${label} scrolls the panel while a search box has focus`, async () => {
295
+ const s = await focusedStack();
296
+ expect(s.dispatch(k)).toBe("scroll");
297
+ expect(s.calls).not.toContain("global");
298
+ });
299
+ }
300
+
301
+ test("R4.7 without a focused input, a digit reaches the global layer (switches tab)", async () => {
302
+ const { dispatchKey, inputLayerConsumes, isScrollKey } = await load();
303
+ const r = stack(
304
+ {
305
+ input: (k) => inputLayerConsumes(k),
306
+ screen: () => false,
307
+ scroll: (k) => isScrollKey(k),
308
+ global: all,
309
+ },
310
+ { input: false },
311
+ );
312
+ expect(dispatchKey(r.layers, key("3"))).toBe("global");
313
+ });
314
+
315
+ test("R4.7 Tab is not consumed by a focused input, so it can reach global", async () => {
316
+ const s = await focusedStack();
317
+ expect(s.dispatch(key("tab"))).toBe("global");
318
+ });
319
+ });
320
+
321
+ describe("C4 — inputLayerConsumes", () => {
322
+ const printable: Key[] = [
323
+ ..."abcdefghijklmnopqrstuvwxyz".split("").map((c) => key(c)),
324
+ ..."ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("").map((c) => upper(c)),
325
+ ...DIGITS,
326
+ ...["?", "/", "-", "_", ".", ",", ":", "@", "!", "#", "=", "+", "*"].map(
327
+ (c) => key(c),
328
+ ),
329
+ key("?", { shift: true }),
330
+ key("space"),
331
+ ];
332
+
333
+ for (const k of printable) {
334
+ test(`R4.4 printable ${JSON.stringify(k.sequence)} (name ${k.name}${k.shift ? ", shift" : ""}) is consumed by the input layer`, async () => {
335
+ const { inputLayerConsumes } = await load();
336
+ expect(inputLayerConsumes(k)).toBe(true);
337
+ });
338
+ }
339
+
340
+ for (const name of ["backspace", "return", "escape"]) {
341
+ test(`R4.4 ${name} is consumed by the input layer`, async () => {
342
+ const { inputLayerConsumes } = await load();
343
+ expect(inputLayerConsumes(key(name))).toBe(true);
344
+ });
345
+ }
346
+
347
+ const notConsumed: [string, Key][] = [
348
+ ["up", key("up")],
349
+ ["down", key("down")],
350
+ ["tab", key("tab")],
351
+ ["shift+tab", key("tab", { shift: true })],
352
+ ["pageup", key("pageup")],
353
+ ["pagedown", key("pagedown")],
354
+ ["ctrl+u", ctrl("u")],
355
+ ["ctrl+d", ctrl("d")],
356
+ ["ctrl+c", ctrl("c")],
357
+ ["ctrl+a", ctrl("a")],
358
+ ["ctrl+k", ctrl("k")],
359
+ ["meta+x", key("x", { meta: true, sequence: "\x1bx" })],
360
+ ["meta+1", key("1", { meta: true, sequence: "\x1b1" })],
361
+ ];
362
+
363
+ for (const [label, k] of notConsumed) {
364
+ test(`R4.4/R4.6 ${label} is NOT consumed by the input layer`, async () => {
365
+ const { inputLayerConsumes } = await load();
366
+ expect(inputLayerConsumes(k)).toBe(false);
367
+ });
368
+ }
369
+ });
370
+
371
+ describe("C4 — isScrollKey", () => {
372
+ for (const k of SCROLL_KEYS) {
373
+ const label = `${k.ctrl ? "ctrl+" : ""}${k.name}`;
374
+ test(`R4.6 ${label} is a scroll key`, async () => {
375
+ const { isScrollKey } = await load();
376
+ expect(isScrollKey(k)).toBe(true);
377
+ });
378
+ }
379
+
380
+ for (const [label, k] of [
381
+ ["u", key("u")],
382
+ ["d", key("d")],
383
+ ["U", upper("U")],
384
+ ["D", upper("D")],
385
+ ["up", key("up")],
386
+ ["down", key("down")],
387
+ ["space", key("space")],
388
+ ["return", key("return")],
389
+ ["ctrl+c", ctrl("c")],
390
+ ["tab", key("tab")],
391
+ ] as [string, Key][]) {
392
+ test(`R4.6 ${label} is not a scroll key`, async () => {
393
+ const { isScrollKey } = await load();
394
+ expect(isScrollKey(k)).toBe(false);
395
+ });
396
+ }
397
+ });