import { describe, expect, it } from "vitest"; import { INPUT_ACTIONS, conflictingInputActions, defaultInputDevice, normalizeInputPreferences, replaceInputBinding } from "../input/keybinds"; import { exportUiPreferences, importUiPreferences, normalizeUiPreferences } from "./uiLayout"; import { profileInputBindings, restoreMovementBindings, withProfileInputBindings } from "./keybindPreferences"; describe("profile keybinds", () => { it("defaults desktop browsers to PC and mobile/native apps to Controller", () => { expect(defaultInputDevice(false, false)).toBe("pc"); expect(defaultInputDevice(false, true)).toBe("controller"); expect(defaultInputDevice(true, false)).toBe("controller"); expect(defaultInputDevice(true, true)).toBe("controller"); }); it("migrates older profiles with every action, independent input types and 60 WoW slots", () => { const preferences = normalizeUiPreferences({ version: 1 }); expect(preferences.keybinds.pc["move-forward"]).toBe("KeyW"); expect(preferences.keybinds.controller["move-forward"]).toBe("Axis1-"); expect(INPUT_ACTIONS.filter((entry) => entry.id.startsWith("bar:"))).toHaveLength(60); expect(INPUT_ACTIONS.filter((entry) => entry.id.startsWith("ability:"))).toHaveLength(24); expect(Object.keys(preferences.keybinds.pc)).toHaveLength(INPUT_ACTIONS.length); preferences.keybinds.pc.jump = null; expect(preferences.keybinds.controller.jump).toBe("Button10"); }); it("finds an existing W skill conflict and repairs movement without losing assigned skills or templates", () => { const preferences = normalizeUiPreferences(null); preferences.current.pc.bars[4].slots[11] = { key: "KeyW", abilityId: "heal" }; preferences.keybinds.pc["move-left"] = null; preferences.slots.mobile[2] = { name: "Travel", layout: preferences.current.mobile }; const bindings = profileInputBindings(preferences, "pc"); expect(conflictingInputActions(bindings, "pc", "move-forward", "KeyW")).toEqual(["bar:4:11"]); const repaired = restoreMovementBindings(preferences, "pc"); expect(repaired.current.pc.bars[4].slots[11]).toEqual({ key: null, abilityId: "heal" }); expect(repaired.keybinds.pc["move-left"]).toBe("KeyA"); expect(repaired.slots).toEqual(preferences.slots); expect(preferences.current.pc.bars[4].slots[11].key).toBe("KeyW"); }); it("supports explicit conflict replacement and persistent unbinding", () => { const preferences = normalizeUiPreferences(null); const bindings = profileInputBindings(preferences, "pc"); expect(conflictingInputActions(bindings, "pc", "bar:0:0", "KeyW")).toEqual(["move-forward"]); const changed = withProfileInputBindings(preferences, "pc", replaceInputBinding(bindings, "pc", "bar:0:0", "KeyW")); const restored = importUiPreferences(exportUiPreferences(changed)); expect(restored.keybinds.pc["move-forward"]).toBeNull(); expect(restored.current.pc.bars[0].slots[0].key).toBe("KeyW"); expect(restoreMovementBindings(restored, "pc").current.pc.bars[0].slots[0].key).toBeNull(); }); it("uses the loaded template's bar keys and changes only the chosen input type", () => { let preferences = normalizeUiPreferences(null); const bindings = profileInputBindings(preferences, "pc"); preferences = withProfileInputBindings(preferences, "pc", replaceInputBinding(bindings, "pc", "bar:4:11", "Ctrl+Shift+KeyQ")); preferences.current.pc.bars[4].slots[11].key = "KeyZ"; expect(profileInputBindings(preferences, "pc")["bar:4:11"]).toBe("KeyZ"); const next = withProfileInputBindings(preferences, "controller", replaceInputBinding(preferences.keybinds.controller, "controller", "jump", "Button4&Button3")); expect(next.current.pc).toEqual(preferences.current.pc); expect(importUiPreferences(exportUiPreferences(next)).keybinds.controller.jump).toBe("Button4&Button3"); }); it("distinguishes contextual defaults and mutually exclusive PC layouts from real collisions", () => { const preferences = normalizeUiPreferences(null); for (const device of ["pc", "controller"] as const) { const bindings = profileInputBindings(preferences, device); expect(INPUT_ACTIONS.flatMap((entry) => conflictingInputActions(bindings, device, entry.id, bindings[entry.id]))).toEqual([]); } expect(conflictingInputActions(preferences.keybinds.controller, "controller", "menu-confirm", "Button2")).toContain("editor-clear"); expect(conflictingInputActions(preferences.keybinds.pc, "pc", "jump", "KeyC")).toContain("companion"); }); it("validates imported keybinds, normalizes chords and retains defaults for corrupt profile fields", () => { const normalized = normalizeInputPreferences({ pc: { jump: "Shift+Ctrl+KeyQ", inventory: "invalid", pause: null }, controller: { jump: "Button6&Button4&Button3" } }); expect(normalized.pc.jump).toBe("Ctrl+Shift+KeyQ"); expect(normalized.pc.inventory).toBe("KeyI"); expect(normalized.pc.pause).toBeNull(); expect(normalized.controller.jump).toBe("Button4&Button6&Button3"); const invalid = JSON.parse(exportUiPreferences(normalizeUiPreferences(null))); invalid.keybinds.controller.jump = "Axis99+"; expect(() => importUiPreferences(JSON.stringify(invalid))).toThrow("Invalid keybind"); }); it("requires clearing a modifier's standalone action before using it in a controller chord", () => { const bindings = normalizeInputPreferences(null).controller; expect(conflictingInputActions(bindings, "controller", "jump", "Button8&Button0")).toEqual(["companion"]); const changed = replaceInputBinding(bindings, "controller", "jump", "Button8&Button0"); expect(changed.companion).toBeNull(); expect(changed["ability:primary:face-bottom"]).toBe("Button0"); expect(conflictingInputActions(changed, "controller", "companion", "Button8")).toEqual(["jump"]); expect(conflictingInputActions(bindings, "controller", "jump", "Button4")).toHaveLength(8); }); });