85 lines
5.9 KiB
TypeScript
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);
|
|
});
|
|
});
|