mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 12:51:30 +00:00
26f5d40819
- Replace Webpack with Vite for faster client bundling and HMR. - Migrate tests from Jest to Vitest and update configuration. - Update Web Worker instantiation to standard ESM syntax. - Implement Env utility in `src/core` for safe, hybrid environment variable access (Vite vs Node). - Refactor configuration loaders to remove direct `process.env` dependencies in shared code. - Update TypeScript environment definitions and project scripts for the new toolchain. - Remove the [depracated usage of the husky](https://github.com/typicode/husky/releases/tag/v9.0.1). ## Description: migrate build system to Vite and test runner to Vitest & Remove depracated husky usage ## Please complete the following: - [X] I have added screenshots for all UI updates - [X] I process any text displayed to the user through translateText() and I've added it to the en.json file - [ ] I have added relevant tests to the test directory - [X] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: wraith4081 --------- Co-authored-by: evanpelle <evanpelle@gmail.com>
456 lines
12 KiB
TypeScript
456 lines
12 KiB
TypeScript
import { AutoUpgradeEvent, InputHandler } from "../src/client/InputHandler";
|
|
import { EventBus } from "../src/core/EventBus";
|
|
|
|
class MockPointerEvent {
|
|
button: number;
|
|
clientX: number;
|
|
clientY: number;
|
|
pointerId: number;
|
|
type: string;
|
|
preventDefault: () => void;
|
|
|
|
constructor(type: string, init: any) {
|
|
this.type = type;
|
|
this.button = init.button;
|
|
this.clientX = init.clientX;
|
|
this.clientY = init.clientY;
|
|
this.pointerId = init.pointerId;
|
|
this.preventDefault = vi.fn();
|
|
}
|
|
}
|
|
|
|
global.PointerEvent = MockPointerEvent as any;
|
|
|
|
describe("InputHandler AutoUpgrade", () => {
|
|
let inputHandler: InputHandler;
|
|
let eventBus: EventBus;
|
|
let mockCanvas: HTMLCanvasElement;
|
|
|
|
beforeEach(() => {
|
|
mockCanvas = document.createElement("canvas");
|
|
mockCanvas.width = 800;
|
|
mockCanvas.height = 600;
|
|
|
|
eventBus = new EventBus();
|
|
|
|
inputHandler = new InputHandler(
|
|
{ attackRatio: 20, ghostStructure: null },
|
|
mockCanvas,
|
|
eventBus,
|
|
);
|
|
});
|
|
|
|
describe("Middle Mouse Button Handling", () => {
|
|
test("should emit AutoUpgradeEvent on middle mouse button press", () => {
|
|
const mockEmit = vi.spyOn(eventBus, "emit");
|
|
|
|
const pointerEvent = new PointerEvent("pointerdown", {
|
|
button: 1,
|
|
clientX: 150,
|
|
clientY: 250,
|
|
pointerId: 1,
|
|
});
|
|
|
|
inputHandler["onPointerDown"](pointerEvent);
|
|
|
|
expect(mockEmit).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
x: 150,
|
|
y: 250,
|
|
}),
|
|
);
|
|
});
|
|
|
|
test("should emit MouseDownEvent on left mouse button press instead of AutoUpgradeEvent", () => {
|
|
const mockEmit = vi.spyOn(eventBus, "emit");
|
|
|
|
const pointerEvent = new PointerEvent("pointerdown", {
|
|
button: 0,
|
|
clientX: 150,
|
|
clientY: 250,
|
|
pointerId: 1,
|
|
});
|
|
|
|
inputHandler["onPointerDown"](pointerEvent);
|
|
|
|
expect(mockEmit).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
x: 150,
|
|
y: 250,
|
|
}),
|
|
);
|
|
|
|
const calls = mockEmit.mock.calls;
|
|
const lastCall = calls[calls.length - 1];
|
|
expect(lastCall[0]).not.toBeInstanceOf(AutoUpgradeEvent);
|
|
});
|
|
|
|
test("should not emit AutoUpgradeEvent on right mouse button press", () => {
|
|
const mockEmit = vi.spyOn(eventBus, "emit");
|
|
|
|
const pointerEvent = new PointerEvent("pointerdown", {
|
|
button: 2,
|
|
clientX: 150,
|
|
clientY: 250,
|
|
pointerId: 1,
|
|
});
|
|
|
|
inputHandler["onPointerDown"](pointerEvent);
|
|
|
|
expect(mockEmit).not.toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
x: 150,
|
|
y: 250,
|
|
}),
|
|
);
|
|
});
|
|
|
|
test("should handle multiple middle mouse button presses", () => {
|
|
const mockEmit = vi.spyOn(eventBus, "emit");
|
|
|
|
const pointerEvent1 = new PointerEvent("pointerdown", {
|
|
button: 1,
|
|
clientX: 100,
|
|
clientY: 200,
|
|
pointerId: 1,
|
|
});
|
|
inputHandler["onPointerDown"](pointerEvent1);
|
|
|
|
const pointerEvent2 = new PointerEvent("pointerdown", {
|
|
button: 1,
|
|
clientX: 300,
|
|
clientY: 400,
|
|
pointerId: 2,
|
|
});
|
|
inputHandler["onPointerDown"](pointerEvent2);
|
|
|
|
expect(mockEmit).toHaveBeenCalledTimes(2);
|
|
expect(mockEmit).toHaveBeenNthCalledWith(
|
|
1,
|
|
expect.objectContaining({
|
|
x: 100,
|
|
y: 200,
|
|
}),
|
|
);
|
|
expect(mockEmit).toHaveBeenNthCalledWith(
|
|
2,
|
|
expect.objectContaining({
|
|
x: 300,
|
|
y: 400,
|
|
}),
|
|
);
|
|
});
|
|
|
|
test("should handle middle mouse button press with zero coordinates", () => {
|
|
const mockEmit = vi.spyOn(eventBus, "emit");
|
|
|
|
const pointerEvent = new PointerEvent("pointerdown", {
|
|
button: 1,
|
|
clientX: 0,
|
|
clientY: 0,
|
|
pointerId: 1,
|
|
});
|
|
|
|
inputHandler["onPointerDown"](pointerEvent);
|
|
|
|
expect(mockEmit).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
x: 0,
|
|
y: 0,
|
|
}),
|
|
);
|
|
});
|
|
|
|
test("should handle middle mouse button press with negative coordinates", () => {
|
|
const mockEmit = vi.spyOn(eventBus, "emit");
|
|
|
|
const pointerEvent = new PointerEvent("pointerdown", {
|
|
button: 1,
|
|
clientX: -100,
|
|
clientY: -200,
|
|
pointerId: 1,
|
|
});
|
|
|
|
inputHandler["onPointerDown"](pointerEvent);
|
|
|
|
expect(mockEmit).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
x: -100,
|
|
y: -200,
|
|
}),
|
|
);
|
|
});
|
|
|
|
test("should handle middle mouse button press with decimal coordinates", () => {
|
|
const mockEmit = vi.spyOn(eventBus, "emit");
|
|
|
|
const pointerEvent = new PointerEvent("pointerdown", {
|
|
button: 1,
|
|
clientX: 100.5,
|
|
clientY: 200.7,
|
|
pointerId: 1,
|
|
});
|
|
|
|
inputHandler["onPointerDown"](pointerEvent);
|
|
|
|
expect(mockEmit).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
x: 100.5,
|
|
y: 200.7,
|
|
}),
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("Pointer Event Handling", () => {
|
|
test("should handle pointer events with different pointer IDs", () => {
|
|
const mockEmit = vi.spyOn(eventBus, "emit");
|
|
|
|
const pointerEvent1 = new PointerEvent("pointerdown", {
|
|
button: 1,
|
|
clientX: 100,
|
|
clientY: 200,
|
|
pointerId: 1,
|
|
});
|
|
inputHandler["onPointerDown"](pointerEvent1);
|
|
|
|
const pointerEvent2 = new PointerEvent("pointerdown", {
|
|
button: 1,
|
|
clientX: 300,
|
|
clientY: 400,
|
|
pointerId: 2,
|
|
});
|
|
inputHandler["onPointerDown"](pointerEvent2);
|
|
|
|
expect(mockEmit).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
test("should handle pointer events with same pointer ID", () => {
|
|
const mockEmit = vi.spyOn(eventBus, "emit");
|
|
|
|
const pointerEvent1 = new PointerEvent("pointerdown", {
|
|
button: 1,
|
|
clientX: 100,
|
|
clientY: 200,
|
|
pointerId: 1,
|
|
});
|
|
inputHandler["onPointerDown"](pointerEvent1);
|
|
|
|
const pointerEvent2 = new PointerEvent("pointerdown", {
|
|
button: 1,
|
|
clientX: 300,
|
|
clientY: 400,
|
|
pointerId: 1,
|
|
});
|
|
inputHandler["onPointerDown"](pointerEvent2);
|
|
|
|
expect(mockEmit).toHaveBeenCalledTimes(2);
|
|
});
|
|
});
|
|
|
|
describe("Edge Cases", () => {
|
|
test("should handle very large coordinates", () => {
|
|
const mockEmit = vi.spyOn(eventBus, "emit");
|
|
|
|
const pointerEvent = new PointerEvent("pointerdown", {
|
|
button: 1,
|
|
clientX: Number.MAX_SAFE_INTEGER,
|
|
clientY: Number.MAX_SAFE_INTEGER,
|
|
pointerId: 1,
|
|
});
|
|
|
|
inputHandler["onPointerDown"](pointerEvent);
|
|
|
|
expect(mockEmit).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
x: Number.MAX_SAFE_INTEGER,
|
|
y: Number.MAX_SAFE_INTEGER,
|
|
}),
|
|
);
|
|
});
|
|
|
|
test("should handle very small coordinates", () => {
|
|
const mockEmit = vi.spyOn(eventBus, "emit");
|
|
|
|
const pointerEvent = new PointerEvent("pointerdown", {
|
|
button: 1,
|
|
clientX: Number.MIN_SAFE_INTEGER,
|
|
clientY: Number.MIN_SAFE_INTEGER,
|
|
pointerId: 1,
|
|
});
|
|
|
|
inputHandler["onPointerDown"](pointerEvent);
|
|
|
|
expect(mockEmit).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
x: Number.MIN_SAFE_INTEGER,
|
|
y: Number.MIN_SAFE_INTEGER,
|
|
}),
|
|
);
|
|
});
|
|
|
|
test("should handle NaN coordinates", () => {
|
|
const mockEmit = vi.spyOn(eventBus, "emit");
|
|
|
|
const pointerEvent = new PointerEvent("pointerdown", {
|
|
button: 1,
|
|
clientX: NaN,
|
|
clientY: NaN,
|
|
pointerId: 1,
|
|
});
|
|
|
|
inputHandler["onPointerDown"](pointerEvent);
|
|
|
|
expect(mockEmit).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
x: NaN,
|
|
y: NaN,
|
|
}),
|
|
);
|
|
});
|
|
|
|
test("should handle Infinity coordinates", () => {
|
|
const mockEmit = vi.spyOn(eventBus, "emit");
|
|
|
|
const pointerEvent = new PointerEvent("pointerdown", {
|
|
button: 1,
|
|
clientX: Infinity,
|
|
clientY: -Infinity,
|
|
pointerId: 1,
|
|
});
|
|
|
|
inputHandler["onPointerDown"](pointerEvent);
|
|
|
|
expect(mockEmit).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
x: Infinity,
|
|
y: -Infinity,
|
|
}),
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("Integration with Event Bus", () => {
|
|
test("should allow event listeners to receive AutoUpgradeEvents", () => {
|
|
const mockListener = vi.fn();
|
|
|
|
eventBus.on(AutoUpgradeEvent, mockListener);
|
|
|
|
const pointerEvent = new PointerEvent("pointerdown", {
|
|
button: 1,
|
|
clientX: 150,
|
|
clientY: 250,
|
|
pointerId: 1,
|
|
});
|
|
inputHandler["onPointerDown"](pointerEvent);
|
|
|
|
expect(mockListener).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
x: 150,
|
|
y: 250,
|
|
}),
|
|
);
|
|
});
|
|
|
|
test("should allow multiple listeners for AutoUpgradeEvent", () => {
|
|
const mockListener1 = vi.fn();
|
|
const mockListener2 = vi.fn();
|
|
|
|
eventBus.on(AutoUpgradeEvent, mockListener1);
|
|
eventBus.on(AutoUpgradeEvent, mockListener2);
|
|
|
|
const pointerEvent = new PointerEvent("pointerdown", {
|
|
button: 1,
|
|
clientX: 150,
|
|
clientY: 250,
|
|
pointerId: 1,
|
|
});
|
|
inputHandler["onPointerDown"](pointerEvent);
|
|
|
|
expect(mockListener1).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
x: 150,
|
|
y: 250,
|
|
}),
|
|
);
|
|
expect(mockListener2).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
x: 150,
|
|
y: 250,
|
|
}),
|
|
);
|
|
});
|
|
|
|
test("should not call unsubscribed listeners", () => {
|
|
const mockListener = vi.fn();
|
|
|
|
eventBus.on(AutoUpgradeEvent, mockListener);
|
|
eventBus.off(AutoUpgradeEvent, mockListener);
|
|
|
|
const pointerEvent = new PointerEvent("pointerdown", {
|
|
button: 1,
|
|
clientX: 150,
|
|
clientY: 250,
|
|
pointerId: 1,
|
|
});
|
|
inputHandler["onPointerDown"](pointerEvent);
|
|
|
|
expect(mockListener).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe("Keybinds JSON parsing", () => {
|
|
beforeEach(() => {
|
|
localStorage.removeItem("settings.keybinds");
|
|
});
|
|
|
|
test("parses nested object values and flattens them to strings", () => {
|
|
const nested = {
|
|
moveUp: { key: "moveUp", value: "KeyZ" },
|
|
};
|
|
localStorage.setItem("settings.keybinds", JSON.stringify(nested));
|
|
|
|
inputHandler.initialize();
|
|
|
|
expect((inputHandler as any).keybinds.moveUp).toBe("KeyZ");
|
|
});
|
|
|
|
test("accepts legacy string values", () => {
|
|
localStorage.setItem(
|
|
"settings.keybinds",
|
|
JSON.stringify({ moveUp: "KeyX" }),
|
|
);
|
|
|
|
inputHandler.initialize();
|
|
|
|
expect((inputHandler as any).keybinds.moveUp).toBe("KeyX");
|
|
});
|
|
|
|
test("ignores non-string and 'Null' values and preserves defaults", () => {
|
|
const mixed = {
|
|
moveUp: { key: "moveUp", value: null },
|
|
moveLeft: "Null",
|
|
};
|
|
localStorage.setItem("settings.keybinds", JSON.stringify(mixed));
|
|
|
|
inputHandler.initialize();
|
|
|
|
// defaults from InputHandler should remain
|
|
expect((inputHandler as any).keybinds.moveUp).toBe("KeyW");
|
|
expect((inputHandler as any).keybinds.moveLeft).toBe("KeyA");
|
|
});
|
|
|
|
test("handles invalid JSON gracefully and warns", () => {
|
|
const spy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
localStorage.setItem("settings.keybinds", "not a json");
|
|
|
|
inputHandler.initialize();
|
|
|
|
expect(spy).toHaveBeenCalled();
|
|
// default remains when parsing fails
|
|
expect((inputHandler as any).keybinds.moveUp).toBe("KeyW");
|
|
spy.mockRestore();
|
|
});
|
|
});
|
|
});
|