summaryrefslogtreecommitdiff
path: root/apps/web/src/actions/keybindings/__tests__/persistence.test.ts
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web/src/actions/keybindings/__tests__/persistence.test.ts')
-rw-r--r--apps/web/src/actions/keybindings/__tests__/persistence.test.ts144
1 files changed, 144 insertions, 0 deletions
diff --git a/apps/web/src/actions/keybindings/__tests__/persistence.test.ts b/apps/web/src/actions/keybindings/__tests__/persistence.test.ts
new file mode 100644
index 00000000..bcccc6a4
--- /dev/null
+++ b/apps/web/src/actions/keybindings/__tests__/persistence.test.ts
@@ -0,0 +1,144 @@
+import {
+ afterEach,
+ beforeEach,
+ describe,
+ expect,
+ mock,
+ test,
+} from "bun:test";
+import {
+ decodePersistedKeybindingsState,
+ migratePersistedKeybindingsState,
+ parseImportedKeybindings,
+ serializeKeybindingsState,
+} from "../persistence";
+
+describe("keybinding persistence", () => {
+ let warnSpy: ReturnType<typeof mock>;
+ let originalWarn: typeof console.warn;
+
+ beforeEach(() => {
+ originalWarn = console.warn;
+ warnSpy = mock(() => {});
+ console.warn = warnSpy;
+ });
+
+ afterEach(() => {
+ console.warn = originalWarn;
+ });
+
+ test("migrates legacy persisted keybindings before decoding them", () => {
+ const migrated = migratePersistedKeybindingsState({
+ state: {
+ keybindings: {
+ s: "split-selected",
+ "ctrl+v": "paste-selected",
+ },
+ isCustomized: true,
+ },
+ fromVersion: 2,
+ });
+
+ const decoded = decodePersistedKeybindingsState({ state: migrated });
+ expect(decoded).not.toBeNull();
+ if (!decoded) throw new Error("Expected migrated keybindings to decode");
+
+ expect(decoded.isCustomized).toBe(true);
+ expect(decoded.keybindings.get("s")).toBe("split");
+ expect(decoded.keybindings.get("ctrl+v")).toBe("paste-copied");
+ expect(decoded.keybindings.get("escape")).toBe("cancel-interaction");
+ expect(warnSpy).not.toHaveBeenCalled();
+ });
+
+ test("filters invalid persisted entries at the boundary and warns", () => {
+ const decoded = decodePersistedKeybindingsState({
+ state: {
+ keybindings: {
+ space: "toggle-play",
+ "shift+bogus": "toggle-play",
+ "ctrl+v": "not-an-action",
+ },
+ isCustomized: false,
+ },
+ });
+
+ expect(decoded).not.toBeNull();
+ if (!decoded) throw new Error("Expected persisted keybindings to decode");
+
+ expect(Array.from(decoded.keybindings.entries())).toEqual([
+ ["space", "toggle-play"],
+ ]);
+ expect(warnSpy).toHaveBeenCalledTimes(1);
+ });
+
+ test("returns null and warns when persisted shape is unrecognizable", () => {
+ const decoded = decodePersistedKeybindingsState({ state: "garbage" });
+ expect(decoded).toBeNull();
+ expect(warnSpy).toHaveBeenCalledTimes(1);
+ });
+
+ test("round-trips actions that have no default shortcut", () => {
+ // `stop-playback` is a valid `TActionWithOptionalArgs` but is not in the
+ // defaults table; the validator must still accept it.
+ const serialized = serializeKeybindingsState({
+ keybindings: new Map([
+ ["space", "toggle-play"],
+ ["x", "stop-playback"],
+ ["b", "toggle-bookmark"],
+ ]),
+ isCustomized: true,
+ });
+
+ const decoded = decodePersistedKeybindingsState({ state: serialized });
+ expect(decoded).not.toBeNull();
+ if (!decoded) throw new Error("Expected round-tripped keybindings");
+
+ expect(decoded.keybindings.get("space")).toBe("toggle-play");
+ expect(decoded.keybindings.get("x")).toBe("stop-playback");
+ expect(decoded.keybindings.get("b")).toBe("toggle-bookmark");
+ expect(warnSpy).not.toHaveBeenCalled();
+ });
+});
+
+describe("parseImportedKeybindings", () => {
+ test("accepts a valid configuration", () => {
+ const result = parseImportedKeybindings({
+ config: {
+ space: "toggle-play",
+ x: "stop-playback",
+ },
+ });
+
+ expect(result.get("space")).toBe("toggle-play");
+ expect(result.get("x")).toBe("stop-playback");
+ });
+
+ test("throws on non-object input", () => {
+ expect(() => parseImportedKeybindings({ config: null })).toThrow(
+ /JSON object/,
+ );
+ expect(() => parseImportedKeybindings({ config: [] })).toThrow(
+ /JSON object/,
+ );
+ });
+
+ test("throws on non-string action values", () => {
+ expect(() =>
+ parseImportedKeybindings({ config: { space: 42 } }),
+ ).toThrow(/expected string/);
+ });
+
+ test("throws on invalid shortcut keys", () => {
+ expect(() =>
+ parseImportedKeybindings({
+ config: { "shift+bogus": "toggle-play" },
+ }),
+ ).toThrow(/shift\+bogus/);
+ });
+
+ test("throws on invalid actions", () => {
+ expect(() =>
+ parseImportedKeybindings({ config: { space: "not-an-action" } }),
+ ).toThrow(/not-an-action/);
+ });
+});