/** * Тесты редьюсера тач-ввода: вся таблица переходов. * tap → pixelToPoint; pending/commit/cancel/перенос по контракту. */ import { describe, expect, it } from 'vitest'; import type { Point } from '@go-learn/core'; import { computeGeometry, pointToPixel } from './geometry.js'; import type { InputEvent, InputState } from './input.js'; import { reduceInput } from './input.js'; const geo = computeGeometry(9, 450, false); const IDLE: InputState = { pending: null }; function tap(point: Point): InputEvent { const { x, y } = pointToPixel(geo, point); return { type: 'tap', px: x, py: y }; } const A: Point = { x: 3, y: 3 }; const B: Point = { x: 5, y: 6 }; describe('reduceInput: tap', () => { it('тап при pending=null → pending=точка, commit=null', () => { const result = reduceInput(IDLE, tap(A), geo); expect(result.commit).toBeNull(); expect(result.state.pending).toEqual(A); }); it('повторный тап по той же точке → commit=точка, pending=null', () => { const result = reduceInput({ pending: A }, tap(A), geo); expect(result.commit).toEqual(A); expect(result.state.pending).toBeNull(); }); it('тап по другой точке → перенос pending, commit=null', () => { const result = reduceInput({ pending: A }, tap(B), geo); expect(result.commit).toBeNull(); expect(result.state.pending).toEqual(B); }); it('тап мимо доски → состояние не меняется, commit=null', () => { const miss: InputEvent = { type: 'tap', px: -20, py: 450 + 20 }; const fromIdle = reduceInput(IDLE, miss, geo); expect(fromIdle.state).toBe(IDLE); expect(fromIdle.commit).toBeNull(); const withPending: InputState = { pending: A }; const fromPending = reduceInput(withPending, miss, geo); expect(fromPending.state).toBe(withPending); expect(fromPending.commit).toBeNull(); }); }); describe('reduceInput: confirm', () => { it('confirm при pending → commit=pending, pending=null', () => { const result = reduceInput({ pending: A }, { type: 'confirm' }, geo); expect(result.commit).toEqual(A); expect(result.state.pending).toBeNull(); }); it('confirm без pending → commit=null, pending=null', () => { const result = reduceInput(IDLE, { type: 'confirm' }, geo); expect(result.commit).toBeNull(); expect(result.state.pending).toBeNull(); }); }); describe('reduceInput: cancel', () => { it('cancel при pending → сброс, commit=null', () => { const result = reduceInput({ pending: A }, { type: 'cancel' }, geo); expect(result.commit).toBeNull(); expect(result.state.pending).toBeNull(); }); it('cancel без pending → commit=null, pending=null', () => { const result = reduceInput(IDLE, { type: 'cancel' }, geo); expect(result.commit).toBeNull(); expect(result.state.pending).toBeNull(); }); });