import { describe, it, expect, beforeEach } from 'vitest';
import { InputManager, type PointerState } from '../InputManager';
/**
* Фейковые canvas/window: достаточно, чтобы InputManager работал без браузера.
* События диспатчим напрямую в сохранённые обработчики.
*/
type Handler = (e: unknown) => void;
function makeFakeEnv() {
const canvasListeners = new Map<string, Handler>();
const windowListeners = new Map<string, Handler>();
const canvas = {
addEventListener: (type: string, fn: Handler) => canvasListeners.set(type, fn),
removeEventListener: (type: string) => canvasListeners.delete(type),
getBoundingClientRect: () => ({ left: 0, top: 0, width: 480, height: 270 })
};
(globalThis as Record<string, unknown>).window = {
addEventListener: (type: string, fn: Handler) => windowListeners.set(type, fn),
removeEventListener: (type: string) => windowListeners.delete(type)
};
return { canvas, canvasListeners, windowListeners } as const;
}
function keyEvent(code: string) {
return { code, pointerType: 'mouse', clientX: 0, clientY: 0 };
}
function pointerEvent(x: number, y: number, type = 'mouse') {
return { clientX: x, clientY: y, pointerType: type };
}
describe('InputManager (фейковый canvas/window)', () => {
let input: InputManager;
let env: ReturnType<typeof makeFakeEnv>;
beforeEach(() => {
env = makeFakeEnv();
input = new InputManager(env.canvas as unknown as HTMLCanvasElement, 480, 270);
input.bindActions({ attack: ['Space'], advance: ['Enter'] });
});
it('justPressed/justReleased по клавиатуре, endTick очищает', () => {
const kd = env.windowListeners.get('keydown')!;
const ku = env.windowListeners.get('keyup')!;
kd(keyEvent('Space'));
expect(input.isActionJustPressed('attack')).toBe(true);
expect(input.isActionActive('attack')).toBe(true);
input.endTick();
expect(input.isActionJustPressed('attack')).toBe(false);
expect(input.isActionActive('attack')).toBe(true);
ku(keyEvent('Space'));
expect(input.wasKeyReleased('Space')).toBe(true);
expect(input.isActionJustReleased('attack')).toBe(true);
expect(input.isActionActive('attack')).toBe(false);
input.endTick();
expect(input.isActionJustReleased('attack')).toBe(false);
});
it('isActionJustReleased не путает действия', () => {
const kd = env.windowListeners.get('keydown')!;
const ku = env.windowListeners.get('keyup')!;
kd(keyEvent('Space'));
input.endTick();
ku(keyEvent('Space'));
expect(input.isActionJustReleased('attack')).toBe(true);
expect(input.isActionJustReleased('advance')).toBe(false);
});
it('указатель: downTicks копится, justPressed сбрасывается в endTick', () => {
const pd = env.canvasListeners.get('pointerdown')!;
const pu = env.canvasListeners.get('pointerup')!;
pd(pointerEvent(240, 135));
const p = input.getPointer() as PointerState;
expect(p.justPressed).toBe(true);
expect(p.downTicks).toBe(0);
expect(p.x).toBe(240);
input.endTick();
expect(p.justPressed).toBe(false);
expect(p.down).toBe(true);
input.endTick();
expect(p.downTicks).toBe(2); // накопился за два тика удержания
pu(pointerEvent(240, 135));
expect(p.justReleased).toBe(true);
expect(p.down).toBe(false);
input.endTick();
expect(p.downTicks).toBe(0);
});
it('координаты переводятся в виртуальное разрешение', () => {
const pm = env.canvasListeners.get('pointermove')!;
// фейковый rect: 0..480x270 1:1, проверяем смещение
pm(pointerEvent(96, 54));
const p = input.getPointer();
expect(p.x).toBe(96);
expect(p.y).toBe(54);
});
it('isTouch помечает тач-события', () => {
const pd = env.canvasListeners.get('pointerdown')!;
pd(pointerEvent(0, 0, 'touch'));
expect(input.getPointer().isTouch).toBe(true);
});
});