feat(chart): refine viewport interaction and rendering
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { ViewportInteractionStateMachine } from './viewportInteractionState'
|
||||
import {
|
||||
reduceViewportInteraction,
|
||||
transitionViewportInteraction,
|
||||
type ViewportInteractionEvent,
|
||||
} from './viewportInteractionState'
|
||||
|
||||
const gesture = {
|
||||
trackIndex: 2,
|
||||
@@ -13,64 +17,85 @@ const gesture = {
|
||||
yDomains: { track: [-1, 1] as [number, number] },
|
||||
}
|
||||
|
||||
describe('ViewportInteractionStateMachine', () => {
|
||||
it('accepts one gesture and rejects a conflicting start', () => {
|
||||
const machine = new ViewportInteractionStateMachine()
|
||||
|
||||
expect(machine.begin(gesture)).toBe(true)
|
||||
expect(machine.begin({ ...gesture, pointerId: 8 })).toBe(false)
|
||||
expect(machine.state).toMatchObject({ kind: 'box', pointerId: 7 })
|
||||
})
|
||||
|
||||
it('rejects moves and completion from a different pointer', () => {
|
||||
const machine = new ViewportInteractionStateMachine()
|
||||
machine.begin(gesture)
|
||||
|
||||
expect(machine.move(8, { currentX: 30, currentY: 40 })).toBeNull()
|
||||
expect(machine.finish(8, { currentX: 30, currentY: 40 })).toBeNull()
|
||||
expect(machine.state?.currentX).toBe(10)
|
||||
})
|
||||
|
||||
it('updates a valid pointer, completes it, and returns to idle', () => {
|
||||
const machine = new ViewportInteractionStateMachine()
|
||||
machine.begin({ ...gesture, kind: 'pan' })
|
||||
|
||||
expect(machine.move(7, { currentX: 30, currentY: 40 })).toMatchObject({
|
||||
kind: 'pan',
|
||||
currentX: 30,
|
||||
currentY: 40,
|
||||
describe('viewport interaction reducer', () => {
|
||||
it('accepts one begin and rejects a conflicting begin', () => {
|
||||
const started = transitionViewportInteraction(null, { type: 'begin', gesture })
|
||||
const conflicting = transitionViewportInteraction(started.state, {
|
||||
type: 'begin',
|
||||
gesture: { ...gesture, pointerId: 8 },
|
||||
})
|
||||
expect(machine.finish(7, { currentX: 50, currentY: 60 })).toMatchObject({
|
||||
kind: 'pan',
|
||||
currentX: 50,
|
||||
currentY: 60,
|
||||
|
||||
expect(started.accepted).toBe(true)
|
||||
expect(started.state).toMatchObject({ kind: 'box', pointerId: 7 })
|
||||
expect(conflicting.accepted).toBe(false)
|
||||
expect(conflicting.state).toMatchObject({ kind: 'box', pointerId: 7 })
|
||||
})
|
||||
|
||||
it('rejects move and finish from a different pointer without changing state', () => {
|
||||
const state = reduceViewportInteraction(null, { type: 'begin', gesture })
|
||||
const move = transitionViewportInteraction(state, {
|
||||
type: 'move',
|
||||
pointerId: 8,
|
||||
position: { currentX: 30, currentY: 40 },
|
||||
})
|
||||
expect(machine.state).toBeNull()
|
||||
const finish = transitionViewportInteraction(move.state, {
|
||||
type: 'finish',
|
||||
pointerId: 8,
|
||||
position: { currentX: 30, currentY: 40 },
|
||||
})
|
||||
|
||||
expect(move.accepted).toBe(false)
|
||||
expect(finish.accepted).toBe(false)
|
||||
expect(finish.state).toMatchObject({ currentX: 10, currentY: 20 })
|
||||
})
|
||||
|
||||
it('only cancels the owning pointer and supports explicit reset', () => {
|
||||
const machine = new ViewportInteractionStateMachine()
|
||||
machine.begin(gesture)
|
||||
it('updates a valid pointer and returns the completed snapshot on finish', () => {
|
||||
const state = reduceViewportInteraction(null, {
|
||||
type: 'begin',
|
||||
gesture: { ...gesture, kind: 'pan' },
|
||||
})
|
||||
const moved = transitionViewportInteraction(state, {
|
||||
type: 'move',
|
||||
pointerId: 7,
|
||||
position: { currentX: 30, currentY: 40 },
|
||||
})
|
||||
const finished = transitionViewportInteraction(moved.state, {
|
||||
type: 'finish',
|
||||
pointerId: 7,
|
||||
position: { currentX: 50, currentY: 60 },
|
||||
})
|
||||
|
||||
expect(machine.cancel(8)).toBe(false)
|
||||
expect(machine.state).not.toBeNull()
|
||||
expect(machine.cancel(7)).toBe(true)
|
||||
expect(machine.state).toBeNull()
|
||||
|
||||
machine.begin(gesture)
|
||||
machine.reset()
|
||||
expect(machine.state).toBeNull()
|
||||
expect(moved.state).toMatchObject({ kind: 'pan', currentX: 30, currentY: 40 })
|
||||
expect(finished.completed).toMatchObject({ kind: 'pan', currentX: 50, currentY: 60 })
|
||||
expect(finished.state).toBeNull()
|
||||
})
|
||||
|
||||
it('returns defensive copies from its state getter', () => {
|
||||
const machine = new ViewportInteractionStateMachine()
|
||||
machine.begin(gesture)
|
||||
const snapshot = machine.state!
|
||||
snapshot.currentX = 99
|
||||
snapshot.xDomain[0] = 50
|
||||
snapshot.yDomains.track![0] = 50
|
||||
it('cancels only the owning pointer and supports cancel/reset events', () => {
|
||||
const state = reduceViewportInteraction(null, { type: 'begin', gesture })
|
||||
const rejectedCancel = transitionViewportInteraction(state, { type: 'cancel', pointerId: 8 })
|
||||
const cancelled = transitionViewportInteraction(rejectedCancel.state, {
|
||||
type: 'cancel',
|
||||
pointerId: 7,
|
||||
})
|
||||
const restarted = reduceViewportInteraction(null, { type: 'begin', gesture })
|
||||
const reset = transitionViewportInteraction(restarted, { type: 'reset' })
|
||||
|
||||
expect(machine.state).toMatchObject({ currentX: 10, xDomain: [0, 100] })
|
||||
expect(machine.state?.yDomains.track).toEqual([-1, 1])
|
||||
expect(rejectedCancel.accepted).toBe(false)
|
||||
expect(rejectedCancel.state).not.toBeNull()
|
||||
expect(cancelled.accepted).toBe(true)
|
||||
expect(cancelled.state).toBeNull()
|
||||
expect(reset.accepted).toBe(true)
|
||||
expect(reset.state).toBeNull()
|
||||
})
|
||||
|
||||
it('keeps events discriminated and does not mutate the begin input', () => {
|
||||
const event: ViewportInteractionEvent = { type: 'begin', gesture }
|
||||
const state = reduceViewportInteraction(null, event)
|
||||
|
||||
expect(state).not.toBeNull()
|
||||
expect(state?.xDomain).toEqual([0, 100])
|
||||
expect(state?.yDomains.track).toEqual([-1, 1])
|
||||
expect(event.gesture.xDomain).toEqual([0, 100])
|
||||
expect(event.gesture.yDomains.track).toEqual([-1, 1])
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user