162 lines
4.2 KiB
TypeScript
162 lines
4.2 KiB
TypeScript
import { describe, expect, test, vi } from 'vitest';
|
|
|
|
import {
|
|
createRuntimeDragInputController,
|
|
createRuntimeInputPointFromNormalized,
|
|
resolveRuntimeInputGridCell,
|
|
} from './index';
|
|
|
|
describe('runtime drag input controller', () => {
|
|
test('pointer-like short press remains a tap', () => {
|
|
const onTap = vi.fn();
|
|
const onDrop = vi.fn();
|
|
const controller = createRuntimeDragInputController({
|
|
dragThresholdPx: 12,
|
|
onTap,
|
|
onDrop,
|
|
});
|
|
|
|
controller.press({
|
|
targetId: 'piece-1',
|
|
inputId: 'pointer:1',
|
|
deviceKind: 'pointer',
|
|
point: { clientX: 10, clientY: 10 },
|
|
});
|
|
controller.move({
|
|
inputId: 'pointer:1',
|
|
point: { clientX: 14, clientY: 14 },
|
|
});
|
|
controller.release({
|
|
inputId: 'pointer:1',
|
|
point: { clientX: 14, clientY: 14 },
|
|
});
|
|
|
|
expect(onTap).toHaveBeenCalledTimes(1);
|
|
expect(onTap).toHaveBeenCalledWith(
|
|
expect.objectContaining({ targetId: 'piece-1', dragging: false }),
|
|
);
|
|
expect(onDrop).not.toHaveBeenCalled();
|
|
});
|
|
|
|
test('device adapters can force continuous drag semantics', () => {
|
|
const onDragStart = vi.fn();
|
|
const onDragMove = vi.fn();
|
|
const onDrop = vi.fn();
|
|
const controller = createRuntimeDragInputController({
|
|
dragThresholdPx: 100,
|
|
onDragStart,
|
|
onDragMove,
|
|
onDrop,
|
|
});
|
|
|
|
controller.press({
|
|
targetId: 'piece-1',
|
|
inputId: 'mocap:hand',
|
|
deviceKind: 'mocap',
|
|
point: { clientX: 10, clientY: 10 },
|
|
});
|
|
controller.move({
|
|
inputId: 'mocap:hand',
|
|
point: { clientX: 11, clientY: 11 },
|
|
forceDragging: true,
|
|
});
|
|
controller.release({
|
|
inputId: 'mocap:hand',
|
|
point: { clientX: 12, clientY: 12 },
|
|
});
|
|
|
|
expect(onDragStart).toHaveBeenCalledTimes(1);
|
|
expect(onDragMove).toHaveBeenCalledTimes(1);
|
|
expect(onDrop).toHaveBeenCalledTimes(1);
|
|
expect(onDrop).toHaveBeenCalledWith(
|
|
expect.objectContaining({ deviceKind: 'mocap', dragging: true }),
|
|
);
|
|
});
|
|
|
|
test('device adapters can force drop on release without converting to tap', () => {
|
|
const onTap = vi.fn();
|
|
const onDrop = vi.fn();
|
|
const controller = createRuntimeDragInputController({
|
|
dragThresholdPx: 100,
|
|
onTap,
|
|
onDrop,
|
|
});
|
|
|
|
controller.press({
|
|
targetId: 'piece-1',
|
|
inputId: 'mocap:hand',
|
|
deviceKind: 'mocap',
|
|
point: { clientX: 10, clientY: 10 },
|
|
});
|
|
controller.release({
|
|
inputId: 'mocap:hand',
|
|
point: { clientX: 10, clientY: 10 },
|
|
forceDrop: true,
|
|
});
|
|
|
|
expect(onDrop).toHaveBeenCalledTimes(1);
|
|
expect(onDrop).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
targetId: 'piece-1',
|
|
forceDrop: true,
|
|
dragging: false,
|
|
}),
|
|
);
|
|
expect(onTap).not.toHaveBeenCalled();
|
|
});
|
|
|
|
test('input-scoped cancel keeps unrelated active sessions alive', () => {
|
|
const onCancel = vi.fn();
|
|
const onDrop = vi.fn();
|
|
const controller = createRuntimeDragInputController({
|
|
dragThresholdPx: 1,
|
|
onCancel,
|
|
onDrop,
|
|
});
|
|
|
|
controller.press({
|
|
targetId: 'piece-1',
|
|
inputId: 'pointer:1',
|
|
deviceKind: 'pointer',
|
|
point: { clientX: 10, clientY: 10 },
|
|
});
|
|
controller.cancel('mocap:hand');
|
|
controller.move({
|
|
inputId: 'pointer:1',
|
|
point: { clientX: 20, clientY: 20 },
|
|
});
|
|
controller.release({
|
|
inputId: 'pointer:1',
|
|
point: { clientX: 20, clientY: 20 },
|
|
});
|
|
|
|
expect(onCancel).not.toHaveBeenCalled();
|
|
expect(onDrop).toHaveBeenCalledTimes(1);
|
|
expect(onDrop).toHaveBeenCalledWith(
|
|
expect.objectContaining({ inputId: 'pointer:1', targetId: 'piece-1' }),
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('runtime input geometry', () => {
|
|
test('normalised device coordinates map into client coordinates and grid cells', () => {
|
|
const point = createRuntimeInputPointFromNormalized(0.75, 0.25, {
|
|
left: 20,
|
|
top: 10,
|
|
width: 200,
|
|
height: 100,
|
|
});
|
|
|
|
expect(point).toEqual({
|
|
clientX: 170,
|
|
clientY: 35,
|
|
normalizedX: 0.75,
|
|
normalizedY: 0.25,
|
|
});
|
|
expect(resolveRuntimeInputGridCell(point, { rows: 4, cols: 4 })).toEqual({
|
|
row: 1,
|
|
col: 3,
|
|
});
|
|
});
|
|
});
|