Files
healer-man/src/game/keybindPreferences.test.ts
T
2026-09-11 14:26:15 -04:00

85 lines
5.9 KiB
TypeScript

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);
});
});