yukey

testkit: key() rejects unlisted multi-char names and shift on printables

commit 2203cbd09dd4ee69b067f88736524d4284baf99c · parent 63181701b8 · browse this tree
Ximianth, 2026-10-10 05:48:25 UTC

key("F1") or key("Up") used to type the letters; key("Backspace") threw a
raw CDP error about the text parameter.  Both now reject up front naming
KEYDEFS, which is where a new named key would go.

shift on a printable character sent the unshifted text with the shift bit
held, which no keyboard produces.  Rather than silently ignore the option,
reject it: the caller should pass the shifted character.  shift stays for
named keys (shift+Tab is what the scenarios use).

Both tests watched failing (resolved undefined) before the fix.
diff --git a/packages/yukey-testkit/src/cdp.test.ts b/packages/yukey-testkit/src/cdp.test.ts
index 9eb2b2f..fbb59f5 100644
--- a/packages/yukey-testkit/src/cdp.test.ts
+++ b/packages/yukey-testkit/src/cdp.test.ts
@@ -90,6 +90,18 @@ describe("Cdp.key", () => {
 			{ type: "keyUp", windowsVirtualKeyCode: 65, code: "KeyA", key: "a", modifiers: 0 },
 		]);
 	});
+
+	it("rejects a multi-character name that isn't a known key, sending nothing", async () => {
+		const sock = new FakeSocket();
+		await expect(Cdp.fromSocket(sock).key("F1")).rejects.toThrow(/KEYDEFS/);
+		expect(sock.sent).toEqual([]);
+	});
+
+	it("rejects shift on a printable character (type the shifted character instead)", async () => {
+		const sock = new FakeSocket();
+		await expect(Cdp.fromSocket(sock).key("a", { shift: true })).rejects.toThrow(/shift/);
+		expect(sock.sent).toEqual([]);
+	});
 });
 
 describe("Cdp.cmd", () => {
diff --git a/packages/yukey-testkit/src/cdp.ts b/packages/yukey-testkit/src/cdp.ts
index 4f00277..ee47470 100644
--- a/packages/yukey-testkit/src/cdp.ts
+++ b/packages/yukey-testkit/src/cdp.ts
@@ -125,13 +125,21 @@ export class Cdp {
 		return sent;
 	}
 
-	// Dispatch a trusted key press (down + up).  Named keys come from KEYDEFS;
-	// anything else is treated as a printable character.
+	// Dispatch a trusted key press (down + up).  `key` is a DOM KeyboardEvent.key
+	// value: a named key from KEYDEFS, or a single printable character.
+	// Shift is only meaningful on named keys (shift+Tab); for a printable
+	// character, pass the shifted character itself.
 	// Enter and Space carry their text payload on a full keyDown
 	// (rawKeyDown skips text insertion; native widgets need the text to act).
 	async key(key: string, opts: { shift?: boolean } = {}): Promise<void> {
 		const mods = opts.shift === true ? 8 : 0;
 		const def = KEYDEFS[key];
+		if (def === undefined && key.length !== 1) {
+			throw new Error(`key ${JSON.stringify(key)} is not a single character and not in KEYDEFS`);
+		}
+		if (def === undefined && mods !== 0) {
+			throw new Error(`key ${JSON.stringify(key)}: shift applies to named keys only; pass the shifted character`);
+		}
 		if (def !== undefined) {
 			const [vk, code] = def;
 			const text = key === " " ? " " : key === "Enter" ? "\r" : "";