chore: восстановление репозитория из снапшота v0.3.1

Прежняя git-история утрачена при переносе проекта на машину владельца
(снапшот без .git). Хэши коммитов в docs/reports/* относятся к утраченной
истории.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
sab.code.lab 2026-08-07 09:34:02 +02:00
commit 1c85091186
184 changed files with 33303 additions and 0 deletions

View file

@ -0,0 +1,79 @@
/**
* Тесты редьюсера тач-ввода: вся таблица переходов.
* 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();
});
});