mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-03 06:47:33 +00:00
## Summary Merges latest changes from `main` as of 10-06-2025. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-5965-Merge-main-as-of-10-06-2025-into-rh-test-2856d73d3650812cb95fd8917278a770) by [Unito](https://www.unito.io) --------- Signed-off-by: Marcel Petrick <mail@marcelpetrick.it> Co-authored-by: filtered <176114999+webfiltered@users.noreply.github.com> Co-authored-by: Christian Byrne <cbyrne@comfy.org> Co-authored-by: github-actions <github-actions@github.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Alexander Brown <drjkl@comfy.org> Co-authored-by: Benjamin Lu <benceruleanlu@proton.me> Co-authored-by: Terry Jia <terryjia88@gmail.com> Co-authored-by: snomiao <snomiao@gmail.com> Co-authored-by: Simula_r <18093452+simula-r@users.noreply.github.com> Co-authored-by: Jake Schroeder <jake.schroeder@isophex.com> Co-authored-by: Comfy Org PR Bot <snomiao+comfy-pr@gmail.com> Co-authored-by: AustinMroz <4284322+AustinMroz@users.noreply.github.com> Co-authored-by: GitHub Action <action@github.com> Co-authored-by: Johnpaul Chiwetelu <49923152+Myestery@users.noreply.github.com> Co-authored-by: Marcel Petrick <mail@marcelpetrick.it> Co-authored-by: Alexander Brown <DrJKL0424@gmail.com> Co-authored-by: Benjamin Lu <benjaminlu1107@gmail.com> Co-authored-by: Alexander Piskun <13381981+bigcat88@users.noreply.github.com> Co-authored-by: Rizumu Ayaka <rizumu@ayaka.moe> Co-authored-by: JakeSchroeder <jake@axiom.co> Co-authored-by: AustinMroz <austin@comfy.org> Co-authored-by: DrJKL <DrJKL@users.noreply.github.com> Co-authored-by: ComfyUI Wiki <contact@comfyui-wiki.com>
1221 lines
38 KiB
TypeScript
1221 lines
38 KiB
TypeScript
/**
|
|
* Test-Driven Design (TDD) tests for device detection functionality.
|
|
*
|
|
* These tests describe the expected behavior for device detection between
|
|
* mouse and trackpad inputs using an efficient timestamp-based approach.
|
|
*
|
|
* Design Philosophy:
|
|
* - Uses timestamps (performance.now()) instead of creating timers for every event
|
|
* - Creates at most ONE timer (for Linux buffer timeout), not one per wheel event
|
|
* - Handles potentially thousands of wheel events per second efficiently
|
|
*
|
|
* Expected new properties on CanvasPointer:
|
|
* - detectedDevice: 'mouse' | 'trackpad'
|
|
* - lastWheelEventTime: number // timestamp, not the event itself
|
|
* - bufferedLinuxEvent: WheelEvent | undefined
|
|
* - bufferedLinuxEventTime: number
|
|
* - linuxBufferTimeoutId: number | undefined // single timer handle
|
|
*
|
|
* Expected new methods on CanvasPointer:
|
|
* - detectDevice(event: WheelEvent): void
|
|
* - clearLinuxBuffer(): void
|
|
*
|
|
* Performance: This design can handle 10,000+ events without creating any timers
|
|
* (except one for Linux detection), ensuring smooth scrolling performance.
|
|
*
|
|
* @vitest-environment jsdom
|
|
*/
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
import { CanvasPointer } from '@/lib/litegraph/src/CanvasPointer'
|
|
|
|
describe('CanvasPointer Device Detection - Efficient Timestamp-Based TDD Tests', () => {
|
|
let element: HTMLDivElement
|
|
let pointer: CanvasPointer
|
|
|
|
beforeEach(() => {
|
|
element = document.createElement('div')
|
|
pointer = new CanvasPointer(element)
|
|
// Mock performance.now() for timestamp-based testing
|
|
vi.spyOn(performance, 'now').mockReturnValue(0)
|
|
vi.spyOn(global, 'setTimeout')
|
|
vi.spyOn(global, 'clearTimeout')
|
|
})
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks()
|
|
vi.clearAllTimers()
|
|
})
|
|
|
|
describe('Initial State', () => {
|
|
it('should start in mouse detected mode immediately after loading', () => {
|
|
expect(pointer.detectedDevice).toBe('mouse')
|
|
})
|
|
|
|
it('should have no last wheel event time immediately after loading', () => {
|
|
expect(pointer.lastWheelEventTime).toBe(0)
|
|
expect(pointer.hasReceivedWheelEvent).toBe(false)
|
|
})
|
|
|
|
it('should have no buffered Linux event immediately after loading', () => {
|
|
expect(pointer.bufferedLinuxEvent).toBeUndefined()
|
|
expect(pointer.bufferedLinuxEventTime).toBe(0)
|
|
expect(pointer.linuxBufferTimeoutId).toBeUndefined()
|
|
})
|
|
})
|
|
|
|
describe('First Event Detection', () => {
|
|
describe('switching to trackpad on first event', () => {
|
|
it('should switch to trackpad if first event is pinch-to-zoom with deltaY < 10', () => {
|
|
const event = new WheelEvent('wheel', {
|
|
ctrlKey: true,
|
|
deltaY: 9.5,
|
|
deltaX: 0
|
|
})
|
|
|
|
pointer.isTrackpadGesture(event)
|
|
expect(pointer.detectedDevice).toBe('trackpad')
|
|
expect(pointer.lastWheelEventTime).toBe(0) // Records current time
|
|
})
|
|
|
|
it('should switch to trackpad if first event is pinch-to-zoom with deltaY = 9.999', () => {
|
|
const event = new WheelEvent('wheel', {
|
|
ctrlKey: true,
|
|
deltaY: 9.999,
|
|
deltaX: 0
|
|
})
|
|
|
|
pointer.isTrackpadGesture(event)
|
|
expect(pointer.detectedDevice).toBe('trackpad')
|
|
})
|
|
|
|
it('should NOT switch to trackpad if first event is pinch-to-zoom with deltaY = 10', () => {
|
|
const event = new WheelEvent('wheel', {
|
|
ctrlKey: true,
|
|
deltaY: 10,
|
|
deltaX: 0
|
|
})
|
|
|
|
pointer.isTrackpadGesture(event)
|
|
expect(pointer.detectedDevice).toBe('mouse')
|
|
})
|
|
|
|
it('should switch to trackpad if first event is two-finger panning with integer values', () => {
|
|
const event = new WheelEvent('wheel', {
|
|
ctrlKey: false,
|
|
deltaY: 5,
|
|
deltaX: -3
|
|
})
|
|
|
|
pointer.isTrackpadGesture(event)
|
|
expect(pointer.detectedDevice).toBe('trackpad')
|
|
})
|
|
|
|
it('should switch to trackpad if first event is two-finger panning with ctrlKey true', () => {
|
|
const event = new WheelEvent('wheel', {
|
|
ctrlKey: true,
|
|
deltaY: 7,
|
|
deltaX: 4
|
|
})
|
|
|
|
pointer.isTrackpadGesture(event)
|
|
expect(pointer.detectedDevice).toBe('trackpad')
|
|
})
|
|
|
|
it('should switch to trackpad if first event is negative pinch-to-zoom with deltaY > -10', () => {
|
|
const event = new WheelEvent('wheel', {
|
|
ctrlKey: true,
|
|
deltaY: -9.5,
|
|
deltaX: 0
|
|
})
|
|
|
|
pointer.isTrackpadGesture(event)
|
|
expect(pointer.detectedDevice).toBe('trackpad')
|
|
})
|
|
})
|
|
|
|
describe('remaining in mouse mode on first event', () => {
|
|
it('should remain in mouse mode if first event is pinch-to-zoom with deltaY >= 10', () => {
|
|
const event = new WheelEvent('wheel', {
|
|
ctrlKey: true,
|
|
deltaY: 10.1,
|
|
deltaX: 0
|
|
})
|
|
|
|
pointer.isTrackpadGesture(event)
|
|
expect(pointer.detectedDevice).toBe('mouse')
|
|
})
|
|
|
|
it('should remain in mouse mode if first event is mouse wheel with deltaY = 120', () => {
|
|
const event = new WheelEvent('wheel', {
|
|
ctrlKey: false,
|
|
deltaY: 120,
|
|
deltaX: 0
|
|
})
|
|
|
|
pointer.isTrackpadGesture(event)
|
|
expect(pointer.detectedDevice).toBe('mouse')
|
|
})
|
|
|
|
it('should remain in mouse mode if first event has only deltaY (no deltaX)', () => {
|
|
const event = new WheelEvent('wheel', {
|
|
ctrlKey: false,
|
|
deltaY: 30,
|
|
deltaX: 0
|
|
})
|
|
|
|
pointer.isTrackpadGesture(event)
|
|
expect(pointer.detectedDevice).toBe('mouse')
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('Mode Switching from Mouse to Trackpad', () => {
|
|
beforeEach(() => {
|
|
// Ensure we start in mouse mode
|
|
pointer.detectedDevice = 'mouse'
|
|
// Simulate a previous event to establish timing
|
|
pointer.lastWheelEventTime = 0
|
|
pointer.hasReceivedWheelEvent = true
|
|
})
|
|
|
|
it('should switch to trackpad on two-finger panning with non-zero deltaX and deltaY', () => {
|
|
// Simulate 500ms has passed since last event
|
|
vi.spyOn(performance, 'now').mockReturnValue(500)
|
|
|
|
const event = new WheelEvent('wheel', {
|
|
ctrlKey: false,
|
|
deltaY: 15,
|
|
deltaX: 8
|
|
})
|
|
|
|
pointer.isTrackpadGesture(event)
|
|
expect(pointer.detectedDevice).toBe('trackpad')
|
|
})
|
|
|
|
it('should NOT switch to trackpad on two-finger panning with zero deltaX', () => {
|
|
vi.spyOn(performance, 'now').mockReturnValue(500)
|
|
|
|
const event = new WheelEvent('wheel', {
|
|
ctrlKey: false,
|
|
deltaY: 15,
|
|
deltaX: 0
|
|
})
|
|
|
|
pointer.isTrackpadGesture(event)
|
|
expect(pointer.detectedDevice).toBe('mouse')
|
|
})
|
|
|
|
it('should NOT switch to trackpad on two-finger panning with zero deltaY', () => {
|
|
vi.spyOn(performance, 'now').mockReturnValue(500)
|
|
|
|
const event = new WheelEvent('wheel', {
|
|
ctrlKey: false,
|
|
deltaY: 0,
|
|
deltaX: 15
|
|
})
|
|
|
|
pointer.isTrackpadGesture(event)
|
|
expect(pointer.detectedDevice).toBe('mouse')
|
|
})
|
|
|
|
it('should switch to trackpad on pinch-to-zoom with deltaY < 10', () => {
|
|
vi.spyOn(performance, 'now').mockReturnValue(500)
|
|
|
|
const event = new WheelEvent('wheel', {
|
|
ctrlKey: true,
|
|
deltaY: 9.99,
|
|
deltaX: 0
|
|
})
|
|
|
|
pointer.isTrackpadGesture(event)
|
|
expect(pointer.detectedDevice).toBe('trackpad')
|
|
})
|
|
|
|
it('should switch to trackpad on pinch-to-zoom with deltaY = -5.5', () => {
|
|
vi.spyOn(performance, 'now').mockReturnValue(500)
|
|
|
|
const event = new WheelEvent('wheel', {
|
|
ctrlKey: true,
|
|
deltaY: -5.5,
|
|
deltaX: 0
|
|
})
|
|
|
|
pointer.isTrackpadGesture(event)
|
|
expect(pointer.detectedDevice).toBe('trackpad')
|
|
})
|
|
|
|
it('should NOT switch to trackpad on pinch-to-zoom with deltaY = 10', () => {
|
|
vi.spyOn(performance, 'now').mockReturnValue(500)
|
|
|
|
const event = new WheelEvent('wheel', {
|
|
ctrlKey: true,
|
|
deltaY: 10,
|
|
deltaX: 0
|
|
})
|
|
|
|
pointer.isTrackpadGesture(event)
|
|
expect(pointer.detectedDevice).toBe('mouse')
|
|
})
|
|
|
|
it('should NOT switch to trackpad on pinch-to-zoom with deltaY = -10', () => {
|
|
vi.spyOn(performance, 'now').mockReturnValue(500)
|
|
|
|
const event = new WheelEvent('wheel', {
|
|
ctrlKey: true,
|
|
deltaY: -10,
|
|
deltaX: 0
|
|
})
|
|
|
|
pointer.isTrackpadGesture(event)
|
|
expect(pointer.detectedDevice).toBe('mouse')
|
|
})
|
|
})
|
|
|
|
describe('Mode Switching from Trackpad to Mouse', () => {
|
|
beforeEach(() => {
|
|
// Set to trackpad mode
|
|
pointer.detectedDevice = 'trackpad'
|
|
pointer.lastWheelEventTime = 0
|
|
pointer.hasReceivedWheelEvent = true
|
|
})
|
|
|
|
it('should switch to mouse on clear mouse wheel event with deltaY > 80', () => {
|
|
vi.spyOn(performance, 'now').mockReturnValue(500)
|
|
|
|
const event = new WheelEvent('wheel', {
|
|
ctrlKey: false,
|
|
deltaY: 80.1,
|
|
deltaX: 0
|
|
})
|
|
|
|
pointer.isTrackpadGesture(event)
|
|
expect(pointer.detectedDevice).toBe('mouse')
|
|
})
|
|
|
|
it('should switch to mouse on clear mouse wheel event with deltaY = 120', () => {
|
|
vi.spyOn(performance, 'now').mockReturnValue(500)
|
|
|
|
const event = new WheelEvent('wheel', {
|
|
ctrlKey: false,
|
|
deltaY: 120,
|
|
deltaX: 0
|
|
})
|
|
|
|
pointer.isTrackpadGesture(event)
|
|
expect(pointer.detectedDevice).toBe('mouse')
|
|
})
|
|
|
|
it('should switch to mouse on clear mouse wheel event with negative deltaY < -80', () => {
|
|
vi.spyOn(performance, 'now').mockReturnValue(500)
|
|
|
|
const event = new WheelEvent('wheel', {
|
|
ctrlKey: false,
|
|
deltaY: -90,
|
|
deltaX: 0
|
|
})
|
|
|
|
pointer.isTrackpadGesture(event)
|
|
expect(pointer.detectedDevice).toBe('mouse')
|
|
})
|
|
|
|
it('should NOT switch to mouse with deltaY = 80', () => {
|
|
vi.spyOn(performance, 'now').mockReturnValue(500)
|
|
|
|
const event = new WheelEvent('wheel', {
|
|
ctrlKey: false,
|
|
deltaY: 80,
|
|
deltaX: 0
|
|
})
|
|
|
|
pointer.isTrackpadGesture(event)
|
|
expect(pointer.detectedDevice).toBe('trackpad')
|
|
})
|
|
|
|
it('should NOT switch to mouse with deltaY = -80', () => {
|
|
vi.spyOn(performance, 'now').mockReturnValue(500)
|
|
|
|
const event = new WheelEvent('wheel', {
|
|
ctrlKey: false,
|
|
deltaY: -80,
|
|
deltaX: 0
|
|
})
|
|
|
|
pointer.isTrackpadGesture(event)
|
|
expect(pointer.detectedDevice).toBe('trackpad')
|
|
})
|
|
|
|
it('should NOT switch to mouse with deltaY = 79.999', () => {
|
|
vi.spyOn(performance, 'now').mockReturnValue(500)
|
|
|
|
const event = new WheelEvent('wheel', {
|
|
ctrlKey: false,
|
|
deltaY: 79.999,
|
|
deltaX: 0
|
|
})
|
|
|
|
pointer.isTrackpadGesture(event)
|
|
expect(pointer.detectedDevice).toBe('trackpad')
|
|
})
|
|
})
|
|
|
|
describe('500ms Cooldown Period', () => {
|
|
it('should NOT allow switching from mouse to trackpad within 500ms', () => {
|
|
pointer.detectedDevice = 'mouse'
|
|
|
|
// First event at time 0
|
|
vi.spyOn(performance, 'now').mockReturnValue(0)
|
|
const event1 = new WheelEvent('wheel', {
|
|
ctrlKey: false,
|
|
deltaY: 60,
|
|
deltaX: 0
|
|
})
|
|
pointer.isTrackpadGesture(event1)
|
|
expect(pointer.lastWheelEventTime).toBe(0)
|
|
|
|
// Try to switch after 499ms - should fail
|
|
vi.spyOn(performance, 'now').mockReturnValue(499)
|
|
const event2 = new WheelEvent('wheel', {
|
|
ctrlKey: true,
|
|
deltaY: 5,
|
|
deltaX: 0
|
|
})
|
|
pointer.isTrackpadGesture(event2)
|
|
expect(pointer.detectedDevice).toBe('mouse')
|
|
})
|
|
|
|
it('should allow switching from mouse to trackpad after 500ms', () => {
|
|
pointer.detectedDevice = 'mouse'
|
|
|
|
// First event at time 0
|
|
vi.spyOn(performance, 'now').mockReturnValue(0)
|
|
const event1 = new WheelEvent('wheel', {
|
|
ctrlKey: false,
|
|
deltaY: 60,
|
|
deltaX: 0
|
|
})
|
|
pointer.isTrackpadGesture(event1)
|
|
|
|
// Try to switch after 500ms - should succeed
|
|
vi.spyOn(performance, 'now').mockReturnValue(500)
|
|
const event2 = new WheelEvent('wheel', {
|
|
ctrlKey: true,
|
|
deltaY: 5,
|
|
deltaX: 0
|
|
})
|
|
pointer.isTrackpadGesture(event2)
|
|
expect(pointer.detectedDevice).toBe('trackpad')
|
|
})
|
|
|
|
it('should NOT allow switching from trackpad to mouse within 500ms', () => {
|
|
pointer.detectedDevice = 'trackpad'
|
|
|
|
// First event at time 0
|
|
vi.spyOn(performance, 'now').mockReturnValue(0)
|
|
const event1 = new WheelEvent('wheel', {
|
|
ctrlKey: true,
|
|
deltaY: 5,
|
|
deltaX: 0
|
|
})
|
|
pointer.isTrackpadGesture(event1)
|
|
|
|
// Try to switch after 400ms - should fail
|
|
vi.spyOn(performance, 'now').mockReturnValue(400)
|
|
const event2 = new WheelEvent('wheel', {
|
|
ctrlKey: false,
|
|
deltaY: 120,
|
|
deltaX: 0
|
|
})
|
|
pointer.isTrackpadGesture(event2)
|
|
expect(pointer.detectedDevice).toBe('trackpad')
|
|
})
|
|
|
|
it('should maintain cooldown even with multiple events', () => {
|
|
pointer.detectedDevice = 'mouse'
|
|
|
|
// Series of events that would normally trigger trackpad
|
|
const trackpadEvents = [
|
|
{ deltaY: 5, deltaX: 3 },
|
|
{ deltaY: -7, deltaX: 2 },
|
|
{ deltaY: 8, deltaX: -4 }
|
|
]
|
|
|
|
// Send first mouse event at time 0
|
|
vi.spyOn(performance, 'now').mockReturnValue(0)
|
|
pointer.isTrackpadGesture(
|
|
new WheelEvent('wheel', { deltaY: 60, deltaX: 0 })
|
|
)
|
|
|
|
// Send trackpad events within 500ms window
|
|
trackpadEvents.forEach((eventData, index) => {
|
|
vi.spyOn(performance, 'now').mockReturnValue((index + 1) * 100) // 100ms, 200ms, 300ms
|
|
const event = new WheelEvent('wheel', eventData)
|
|
pointer.isTrackpadGesture(event)
|
|
expect(pointer.detectedDevice).toBe('mouse') // Should remain mouse
|
|
})
|
|
|
|
// After 500ms from last event (300ms + 500ms = 800ms), should be able to switch
|
|
vi.spyOn(performance, 'now').mockReturnValue(800)
|
|
const switchEvent = new WheelEvent('wheel', { deltaY: 5, deltaX: 3 })
|
|
pointer.isTrackpadGesture(switchEvent)
|
|
expect(pointer.detectedDevice).toBe('trackpad')
|
|
})
|
|
})
|
|
|
|
describe('Linux Wheel Event Buffering', () => {
|
|
beforeEach(() => {
|
|
pointer.detectedDevice = 'trackpad'
|
|
pointer.lastWheelEventTime = 0
|
|
pointer.hasReceivedWheelEvent = true
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
it('should buffer possible Linux wheel event and create single timeout', () => {
|
|
const setTimeoutSpy = vi.spyOn(global, 'setTimeout')
|
|
vi.spyOn(performance, 'now').mockReturnValue(500) // Allow mode switching
|
|
|
|
const event = new WheelEvent('wheel', {
|
|
deltaY: 10,
|
|
deltaX: 0
|
|
})
|
|
|
|
pointer.isTrackpadGesture(event)
|
|
expect(pointer.bufferedLinuxEvent).toBe(event)
|
|
expect(pointer.bufferedLinuxEventTime).toBe(500)
|
|
expect(pointer.detectedDevice).toBe('trackpad') // No immediate switch
|
|
|
|
// Should create exactly ONE timeout for buffer clearing
|
|
expect(setTimeoutSpy).toHaveBeenCalledTimes(1)
|
|
expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), 10)
|
|
})
|
|
|
|
it('should reuse timer when buffering new Linux event', () => {
|
|
const setTimeoutSpy = vi.spyOn(global, 'setTimeout')
|
|
const clearTimeoutSpy = vi.spyOn(global, 'clearTimeout')
|
|
|
|
// First Linux event
|
|
vi.spyOn(performance, 'now').mockReturnValue(500)
|
|
const event1 = new WheelEvent('wheel', { deltaY: 15, deltaX: 0 })
|
|
pointer.isTrackpadGesture(event1)
|
|
const firstTimeoutId = pointer.linuxBufferTimeoutId
|
|
|
|
// Second Linux event before timeout
|
|
vi.spyOn(performance, 'now').mockReturnValue(505)
|
|
const event2 = new WheelEvent('wheel', { deltaY: 10, deltaX: 0 })
|
|
pointer.isTrackpadGesture(event2)
|
|
|
|
// Should clear the first timeout and create a new one
|
|
expect(clearTimeoutSpy).toHaveBeenCalledWith(firstTimeoutId)
|
|
expect(setTimeoutSpy).toHaveBeenCalledTimes(2)
|
|
expect(pointer.bufferedLinuxEvent).toBe(event2)
|
|
})
|
|
|
|
it('should buffer negative Linux wheel values', () => {
|
|
const setTimeoutSpy = vi.spyOn(global, 'setTimeout')
|
|
vi.spyOn(performance, 'now').mockReturnValue(500)
|
|
|
|
const event = new WheelEvent('wheel', {
|
|
deltaY: -10,
|
|
deltaX: 0
|
|
})
|
|
|
|
pointer.isTrackpadGesture(event)
|
|
expect(pointer.bufferedLinuxEvent).toBe(event)
|
|
expect(pointer.detectedDevice).toBe('trackpad')
|
|
expect(setTimeoutSpy).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('should NOT buffer event with deltaY < 10', () => {
|
|
const setTimeoutSpy = vi.spyOn(global, 'setTimeout')
|
|
vi.spyOn(performance, 'now').mockReturnValue(500)
|
|
|
|
const event = new WheelEvent('wheel', {
|
|
deltaY: 9,
|
|
deltaX: 0
|
|
})
|
|
|
|
pointer.isTrackpadGesture(event)
|
|
expect(pointer.bufferedLinuxEvent).toBeUndefined()
|
|
expect(pointer.detectedDevice).toBe('trackpad')
|
|
expect(setTimeoutSpy).not.toHaveBeenCalled() // No timer created
|
|
})
|
|
|
|
it('should NOT buffer event with non-zero deltaX', () => {
|
|
const setTimeoutSpy = vi.spyOn(global, 'setTimeout')
|
|
vi.spyOn(performance, 'now').mockReturnValue(500)
|
|
|
|
const event = new WheelEvent('wheel', {
|
|
deltaY: 10,
|
|
deltaX: 1
|
|
})
|
|
|
|
pointer.isTrackpadGesture(event)
|
|
expect(pointer.bufferedLinuxEvent).toBeUndefined()
|
|
expect(pointer.detectedDevice).toBe('trackpad')
|
|
expect(setTimeoutSpy).not.toHaveBeenCalled() // No timer created
|
|
})
|
|
|
|
it('should switch to mouse if follow-up event has same deltaY within 10ms', () => {
|
|
const clearTimeoutSpy = vi.spyOn(global, 'clearTimeout')
|
|
|
|
// First event - buffered at time 500
|
|
vi.spyOn(performance, 'now').mockReturnValue(500)
|
|
const event1 = new WheelEvent('wheel', {
|
|
deltaY: 10,
|
|
deltaX: 0
|
|
})
|
|
pointer.isTrackpadGesture(event1)
|
|
expect(pointer.bufferedLinuxEvent).toBe(event1)
|
|
const timeoutId = pointer.linuxBufferTimeoutId
|
|
|
|
// Follow-up within 10ms with same deltaY
|
|
vi.spyOn(performance, 'now').mockReturnValue(509)
|
|
const event2 = new WheelEvent('wheel', {
|
|
deltaY: 10,
|
|
deltaX: 0
|
|
})
|
|
pointer.isTrackpadGesture(event2)
|
|
|
|
expect(pointer.detectedDevice).toBe('mouse')
|
|
expect(pointer.bufferedLinuxEvent).toBeUndefined()
|
|
expect(clearTimeoutSpy).toHaveBeenCalledWith(timeoutId) // Timer cleared
|
|
})
|
|
|
|
it('should switch to mouse if follow-up event is divisible by original deltaY within 10ms', () => {
|
|
vi.spyOn(performance, 'now').mockReturnValue(500)
|
|
|
|
// First event - buffered
|
|
const event1 = new WheelEvent('wheel', {
|
|
deltaY: 10,
|
|
deltaX: 0
|
|
})
|
|
pointer.isTrackpadGesture(event1)
|
|
|
|
// Follow-up within 10ms with deltaY divisible by 10
|
|
vi.spyOn(performance, 'now').mockReturnValue(505)
|
|
const event2 = new WheelEvent('wheel', {
|
|
deltaY: 30,
|
|
deltaX: 0
|
|
})
|
|
pointer.isTrackpadGesture(event2)
|
|
|
|
expect(pointer.detectedDevice).toBe('mouse')
|
|
expect(pointer.bufferedLinuxEvent).toBeUndefined()
|
|
})
|
|
|
|
it('should switch to mouse if follow-up deltaY is divisible by original (base 15)', () => {
|
|
vi.spyOn(performance, 'now').mockReturnValue(500)
|
|
|
|
// First event with base 15
|
|
const event1 = new WheelEvent('wheel', {
|
|
deltaY: 15,
|
|
deltaX: 0
|
|
})
|
|
pointer.isTrackpadGesture(event1)
|
|
|
|
// Follow-up with multiple of 15
|
|
vi.spyOn(performance, 'now').mockReturnValue(508)
|
|
const event2 = new WheelEvent('wheel', {
|
|
deltaY: 45,
|
|
deltaX: 0
|
|
})
|
|
pointer.isTrackpadGesture(event2)
|
|
|
|
expect(pointer.detectedDevice).toBe('mouse')
|
|
})
|
|
|
|
it('should switch to mouse if original deltaY is divisible by follow-up', () => {
|
|
vi.spyOn(performance, 'now').mockReturnValue(500)
|
|
|
|
// First event with larger value
|
|
const event1 = new WheelEvent('wheel', {
|
|
deltaY: 30,
|
|
deltaX: 0
|
|
})
|
|
pointer.isTrackpadGesture(event1)
|
|
|
|
// Follow-up with divisor
|
|
vi.spyOn(performance, 'now').mockReturnValue(507)
|
|
const event2 = new WheelEvent('wheel', {
|
|
deltaY: 10,
|
|
deltaX: 0
|
|
})
|
|
pointer.isTrackpadGesture(event2)
|
|
|
|
expect(pointer.detectedDevice).toBe('mouse')
|
|
})
|
|
|
|
it('should NOT switch to mouse if follow-up is not divisible', () => {
|
|
vi.spyOn(performance, 'now').mockReturnValue(500)
|
|
|
|
// First event
|
|
const event1 = new WheelEvent('wheel', {
|
|
deltaY: 10,
|
|
deltaX: 0
|
|
})
|
|
pointer.isTrackpadGesture(event1)
|
|
|
|
// Follow-up with non-divisible value
|
|
vi.spyOn(performance, 'now').mockReturnValue(505)
|
|
const event2 = new WheelEvent('wheel', {
|
|
deltaY: 13,
|
|
deltaX: 0
|
|
})
|
|
pointer.isTrackpadGesture(event2)
|
|
|
|
expect(pointer.detectedDevice).toBe('trackpad')
|
|
})
|
|
|
|
it('should NOT switch to mouse if follow-up comes after 10ms', () => {
|
|
vi.spyOn(performance, 'now').mockReturnValue(500)
|
|
|
|
// First event
|
|
const event1 = new WheelEvent('wheel', {
|
|
deltaY: 10,
|
|
deltaX: 0
|
|
})
|
|
pointer.isTrackpadGesture(event1)
|
|
|
|
// Follow-up after 10ms
|
|
vi.spyOn(performance, 'now').mockReturnValue(511)
|
|
const event2 = new WheelEvent('wheel', {
|
|
deltaY: 10,
|
|
deltaX: 0
|
|
})
|
|
pointer.isTrackpadGesture(event2)
|
|
|
|
expect(pointer.detectedDevice).toBe('trackpad')
|
|
})
|
|
|
|
it('should call clearLinuxBuffer method after 10ms timeout', () => {
|
|
vi.spyOn(performance, 'now').mockReturnValue(500)
|
|
vi.useFakeTimers() // Use fake timers just for this test
|
|
|
|
const event = new WheelEvent('wheel', {
|
|
deltaY: 10,
|
|
deltaX: 0
|
|
})
|
|
pointer.isTrackpadGesture(event)
|
|
expect(pointer.bufferedLinuxEvent).toBe(event)
|
|
|
|
// Simulate timeout firing
|
|
vi.runOnlyPendingTimers()
|
|
expect(pointer.bufferedLinuxEvent).toBeUndefined()
|
|
expect(pointer.linuxBufferTimeoutId).toBeUndefined()
|
|
|
|
vi.useRealTimers() // Restore for other tests
|
|
})
|
|
|
|
it('should handle negative Linux wheel values', () => {
|
|
vi.spyOn(performance, 'now').mockReturnValue(500)
|
|
|
|
// First negative event
|
|
const event1 = new WheelEvent('wheel', {
|
|
deltaY: -15,
|
|
deltaX: 0
|
|
})
|
|
pointer.isTrackpadGesture(event1)
|
|
|
|
// Follow-up with same negative value
|
|
vi.spyOn(performance, 'now').mockReturnValue(505)
|
|
const event2 = new WheelEvent('wheel', {
|
|
deltaY: -15,
|
|
deltaX: 0
|
|
})
|
|
pointer.isTrackpadGesture(event2)
|
|
|
|
expect(pointer.detectedDevice).toBe('mouse')
|
|
})
|
|
|
|
it('should handle mixed sign Linux wheel values if divisible', () => {
|
|
vi.spyOn(performance, 'now').mockReturnValue(500)
|
|
|
|
// First positive event
|
|
const event1 = new WheelEvent('wheel', {
|
|
deltaY: 10,
|
|
deltaX: 0
|
|
})
|
|
pointer.isTrackpadGesture(event1)
|
|
|
|
// Follow-up with negative multiple
|
|
vi.spyOn(performance, 'now').mockReturnValue(505)
|
|
const event2 = new WheelEvent('wheel', {
|
|
deltaY: -30,
|
|
deltaX: 0
|
|
})
|
|
pointer.isTrackpadGesture(event2)
|
|
|
|
expect(pointer.detectedDevice).toBe('mouse')
|
|
})
|
|
|
|
it('should allow buffering during 500ms cooldown as exception', () => {
|
|
pointer.detectedDevice = 'trackpad'
|
|
|
|
// Send initial event at time 0
|
|
vi.spyOn(performance, 'now').mockReturnValue(0)
|
|
const event1 = new WheelEvent('wheel', {
|
|
deltaY: 5,
|
|
deltaX: 2
|
|
})
|
|
pointer.isTrackpadGesture(event1)
|
|
|
|
// Within cooldown at 100ms, but Linux buffering should still work
|
|
vi.spyOn(performance, 'now').mockReturnValue(100)
|
|
const event2 = new WheelEvent('wheel', {
|
|
deltaY: 10,
|
|
deltaX: 0
|
|
})
|
|
pointer.isTrackpadGesture(event2)
|
|
expect(pointer.bufferedLinuxEvent).toBe(event2)
|
|
|
|
// Follow-up for Linux detection at 105ms
|
|
vi.spyOn(performance, 'now').mockReturnValue(105)
|
|
const event3 = new WheelEvent('wheel', {
|
|
deltaY: 20,
|
|
deltaX: 0
|
|
})
|
|
pointer.isTrackpadGesture(event3)
|
|
|
|
// Should switch despite being within original 500ms window
|
|
expect(pointer.detectedDevice).toBe('mouse')
|
|
})
|
|
})
|
|
|
|
describe('Performance and Efficiency', () => {
|
|
it('should not create timers for regular wheel events', () => {
|
|
const setTimeoutSpy = vi.spyOn(global, 'setTimeout')
|
|
pointer.detectedDevice = 'mouse'
|
|
|
|
// Simulate rapid scrolling without Linux-like patterns
|
|
for (let i = 0; i < 100; i++) {
|
|
vi.spyOn(performance, 'now').mockReturnValue(i * 16) // 60fps scrolling
|
|
const event = new WheelEvent('wheel', {
|
|
deltaY: 120, // Clear mouse wheel value
|
|
deltaX: 0
|
|
})
|
|
pointer.isTrackpadGesture(event)
|
|
}
|
|
|
|
// Should create NO timers for regular mouse wheel events
|
|
expect(setTimeoutSpy).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('should create at most one timer for Linux detection', () => {
|
|
const setTimeoutSpy = vi.spyOn(global, 'setTimeout')
|
|
pointer.detectedDevice = 'trackpad'
|
|
|
|
// Send a Linux-like event that requires buffering
|
|
vi.spyOn(performance, 'now').mockReturnValue(500)
|
|
const event1 = new WheelEvent('wheel', { deltaY: 10, deltaX: 0 })
|
|
pointer.isTrackpadGesture(event1)
|
|
|
|
// Should create exactly one timer
|
|
expect(setTimeoutSpy).toHaveBeenCalledTimes(1)
|
|
|
|
// Send more regular events
|
|
for (let i = 1; i <= 10; i++) {
|
|
vi.spyOn(performance, 'now').mockReturnValue(500 + i * 100)
|
|
const event = new WheelEvent('wheel', { deltaY: 5, deltaX: 3 })
|
|
pointer.isTrackpadGesture(event)
|
|
}
|
|
|
|
// Still only one timer (the Linux buffer timeout)
|
|
expect(setTimeoutSpy).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('should handle thousands of events efficiently', () => {
|
|
const setTimeoutSpy = vi.spyOn(global, 'setTimeout')
|
|
let maxTimersCreated = 0
|
|
|
|
// Simulate extended scrolling session with mixed inputs
|
|
for (let i = 0; i < 10000; i++) {
|
|
vi.spyOn(performance, 'now').mockReturnValue(i)
|
|
|
|
// Mix of different event types
|
|
const eventType = i % 3
|
|
let event: WheelEvent
|
|
|
|
if (eventType === 0) {
|
|
// Mouse wheel
|
|
event = new WheelEvent('wheel', {
|
|
deltaY: 120,
|
|
deltaX: 0
|
|
})
|
|
} else if (eventType === 1) {
|
|
// Trackpad two-finger
|
|
event = new WheelEvent('wheel', {
|
|
deltaY: Math.floor(Math.random() * 20),
|
|
deltaX: Math.floor(Math.random() * 20)
|
|
})
|
|
} else {
|
|
// Pinch to zoom
|
|
event = new WheelEvent('wheel', {
|
|
ctrlKey: true,
|
|
deltaY: Math.random() * 10,
|
|
deltaX: 0
|
|
})
|
|
}
|
|
|
|
pointer.isTrackpadGesture(event)
|
|
|
|
// Track maximum timers created
|
|
maxTimersCreated = Math.max(
|
|
maxTimersCreated,
|
|
setTimeoutSpy.mock.calls.length
|
|
)
|
|
}
|
|
|
|
// Should create at most a few timers for Linux detection, not thousands
|
|
expect(maxTimersCreated).toBeLessThan(10)
|
|
})
|
|
|
|
it('should use minimal memory with timestamp approach', () => {
|
|
// This test verifies the implementation uses timestamps, not stored events
|
|
const initialProps = Object.keys(pointer).length
|
|
|
|
// Process many events
|
|
for (let i = 0; i < 1000; i++) {
|
|
vi.spyOn(performance, 'now').mockReturnValue(i * 10)
|
|
const event = new WheelEvent('wheel', {
|
|
deltaY: 60 + Math.random() * 100,
|
|
deltaX: Math.random() * 50
|
|
})
|
|
pointer.isTrackpadGesture(event)
|
|
}
|
|
|
|
// Should only have a few properties for tracking state
|
|
const finalProps = Object.keys(pointer).length
|
|
expect(finalProps - initialProps).toBeLessThanOrEqual(5) // Only added minimal tracking properties
|
|
|
|
// Verify we store timestamps, not events (except Linux buffer)
|
|
expect(typeof pointer.lastWheelEventTime).toBe('number')
|
|
expect(typeof pointer.bufferedLinuxEventTime).toBe('number')
|
|
})
|
|
|
|
it('should handle rapid mode switching efficiently', () => {
|
|
const setTimeoutSpy = vi.spyOn(global, 'setTimeout')
|
|
|
|
// Rapidly switch between mouse and trackpad modes
|
|
for (let i = 0; i < 100; i++) {
|
|
const baseTime = i * 600 // Every 600ms to allow switching
|
|
|
|
// Mouse event
|
|
vi.spyOn(performance, 'now').mockReturnValue(baseTime)
|
|
pointer.isTrackpadGesture(
|
|
new WheelEvent('wheel', { deltaY: 120, deltaX: 0 })
|
|
)
|
|
|
|
// Trackpad event
|
|
vi.spyOn(performance, 'now').mockReturnValue(baseTime + 500)
|
|
pointer.isTrackpadGesture(
|
|
new WheelEvent('wheel', { deltaY: 5, deltaX: 3 })
|
|
)
|
|
}
|
|
|
|
// Should create minimal or no timers despite 200 events
|
|
expect(setTimeoutSpy.mock.calls.length).toBeLessThan(5)
|
|
})
|
|
})
|
|
|
|
describe('Edge Cases and Complex Scenarios', () => {
|
|
it('should handle float values correctly for mouse detection', () => {
|
|
pointer.detectedDevice = 'trackpad'
|
|
pointer.lastWheelEventTime = 0
|
|
pointer.hasReceivedWheelEvent = true
|
|
vi.spyOn(performance, 'now').mockReturnValue(500)
|
|
|
|
// Float value <= 80 should NOT switch from trackpad
|
|
const event1 = new WheelEvent('wheel', {
|
|
deltaY: 60.5,
|
|
deltaX: 0
|
|
})
|
|
pointer.isTrackpadGesture(event1)
|
|
expect(pointer.detectedDevice).toBe('trackpad')
|
|
|
|
// Float value > 80 should switch to mouse
|
|
vi.spyOn(performance, 'now').mockReturnValue(1000) // 500ms later
|
|
const event2 = new WheelEvent('wheel', {
|
|
deltaY: 80.1,
|
|
deltaX: 0
|
|
})
|
|
pointer.isTrackpadGesture(event2)
|
|
expect(pointer.detectedDevice).toBe('mouse')
|
|
})
|
|
|
|
it('should handle integer values correctly for trackpad detection', () => {
|
|
pointer.detectedDevice = 'mouse'
|
|
pointer.lastWheelEventTime = 0
|
|
pointer.hasReceivedWheelEvent = true
|
|
vi.spyOn(performance, 'now').mockReturnValue(500)
|
|
|
|
// Integer values in two-finger panning
|
|
const event = new WheelEvent('wheel', {
|
|
deltaY: 5,
|
|
deltaX: 3
|
|
})
|
|
pointer.isTrackpadGesture(event)
|
|
expect(pointer.detectedDevice).toBe('trackpad')
|
|
})
|
|
|
|
it('should correctly identify pinch-to-zoom with ctrlKey', () => {
|
|
const event = new WheelEvent('wheel', {
|
|
ctrlKey: true,
|
|
deltaY: 250.5,
|
|
deltaX: 0
|
|
})
|
|
|
|
// This is pinch-to-zoom but deltaY > 10, so stays as mouse on first event
|
|
pointer.isTrackpadGesture(event)
|
|
expect(pointer.detectedDevice).toBe('mouse')
|
|
})
|
|
|
|
it('should handle rapid event sequences', () => {
|
|
pointer.detectedDevice = 'mouse'
|
|
pointer.lastWheelEventTime = 0
|
|
|
|
// Simulate rapid scrolling
|
|
for (let i = 0; i < 10; i++) {
|
|
vi.spyOn(performance, 'now').mockReturnValue(i * 30) // 30ms between events
|
|
const event = new WheelEvent('wheel', {
|
|
deltaY: 60,
|
|
deltaX: 0
|
|
})
|
|
pointer.isTrackpadGesture(event)
|
|
expect(pointer.detectedDevice).toBe('mouse')
|
|
}
|
|
})
|
|
|
|
it('should handle boundary values for pinch-to-zoom detection', () => {
|
|
// Test deltaY = 10 (boundary)
|
|
const event1 = new WheelEvent('wheel', {
|
|
ctrlKey: true,
|
|
deltaY: 10,
|
|
deltaX: 0
|
|
})
|
|
pointer.isTrackpadGesture(event1)
|
|
expect(pointer.detectedDevice).toBe('mouse')
|
|
|
|
// Reset and test deltaY = 9.999999
|
|
pointer = new CanvasPointer(element)
|
|
const event2 = new WheelEvent('wheel', {
|
|
ctrlKey: true,
|
|
deltaY: 9.999999,
|
|
deltaX: 0
|
|
})
|
|
pointer.isTrackpadGesture(event2)
|
|
expect(pointer.detectedDevice).toBe('trackpad')
|
|
})
|
|
|
|
it('should handle boundary values for mouse wheel detection', () => {
|
|
pointer.detectedDevice = 'trackpad'
|
|
pointer.lastWheelEventTime = 0
|
|
pointer.hasReceivedWheelEvent = true
|
|
vi.spyOn(performance, 'now').mockReturnValue(500)
|
|
|
|
// Test deltaY = 80 (boundary)
|
|
const event1 = new WheelEvent('wheel', {
|
|
deltaY: 80,
|
|
deltaX: 0
|
|
})
|
|
pointer.isTrackpadGesture(event1)
|
|
expect(pointer.detectedDevice).toBe('trackpad')
|
|
|
|
// Test deltaY = 80.000001
|
|
vi.spyOn(performance, 'now').mockReturnValue(1000) // 500ms later
|
|
const event2 = new WheelEvent('wheel', {
|
|
deltaY: 80.000001,
|
|
deltaX: 0
|
|
})
|
|
pointer.isTrackpadGesture(event2)
|
|
expect(pointer.detectedDevice).toBe('mouse')
|
|
})
|
|
|
|
it('should handle Linux wheel detection with various multiples', () => {
|
|
pointer.detectedDevice = 'trackpad'
|
|
pointer.lastWheelEventTime = 0
|
|
pointer.hasReceivedWheelEvent = true
|
|
vi.spyOn(performance, 'now').mockReturnValue(500)
|
|
|
|
// Test with base 10 and multiple 50
|
|
const event1 = new WheelEvent('wheel', {
|
|
deltaY: 10,
|
|
deltaX: 0
|
|
})
|
|
pointer.isTrackpadGesture(event1)
|
|
|
|
vi.spyOn(performance, 'now').mockReturnValue(505) // 5ms later
|
|
const event2 = new WheelEvent('wheel', {
|
|
deltaY: 50,
|
|
deltaX: 0
|
|
})
|
|
pointer.isTrackpadGesture(event2)
|
|
expect(pointer.detectedDevice).toBe('mouse')
|
|
})
|
|
|
|
it('should not confuse trackpad integers with Linux wheel', () => {
|
|
pointer.detectedDevice = 'trackpad'
|
|
pointer.lastWheelEventTime = 0
|
|
pointer.hasReceivedWheelEvent = true
|
|
vi.spyOn(performance, 'now').mockReturnValue(500)
|
|
|
|
// Trackpad two-finger panning with integers
|
|
const event1 = new WheelEvent('wheel', {
|
|
deltaY: 10,
|
|
deltaX: 5 // Non-zero deltaX
|
|
})
|
|
pointer.isTrackpadGesture(event1)
|
|
|
|
// Should not buffer this as Linux event
|
|
expect(pointer.bufferedLinuxEvent).toBeUndefined()
|
|
expect(pointer.detectedDevice).toBe('trackpad')
|
|
})
|
|
})
|
|
|
|
describe('Input Type Validation', () => {
|
|
describe('Two-finger panning validation', () => {
|
|
it('should accept integer deltaY values', () => {
|
|
const values = [0, 1, -1, 100, -100, 999, -999]
|
|
values.forEach((deltaY) => {
|
|
const event = new WheelEvent('wheel', {
|
|
ctrlKey: false,
|
|
deltaY,
|
|
deltaX: 5
|
|
})
|
|
expect(Number.isInteger(event.deltaY)).toBe(true)
|
|
})
|
|
})
|
|
|
|
it('should accept integer deltaX values', () => {
|
|
const values = [0, 1, -1, 100, -100, 999, -999]
|
|
values.forEach((deltaX) => {
|
|
const event = new WheelEvent('wheel', {
|
|
ctrlKey: false,
|
|
deltaY: 5,
|
|
deltaX
|
|
})
|
|
expect(Number.isInteger(event.deltaX)).toBe(true)
|
|
})
|
|
})
|
|
|
|
it('should handle ctrlKey true or false', () => {
|
|
;[true, false].forEach((ctrlKey) => {
|
|
const event = new WheelEvent('wheel', {
|
|
ctrlKey,
|
|
deltaY: 5,
|
|
deltaX: 3
|
|
})
|
|
expect(typeof event.ctrlKey).toBe('boolean')
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('Pinch-to-zoom validation', () => {
|
|
it('should always have ctrlKey true', () => {
|
|
const event = new WheelEvent('wheel', {
|
|
ctrlKey: true,
|
|
deltaY: 5.5,
|
|
deltaX: 0
|
|
})
|
|
expect(event.ctrlKey).toBe(true)
|
|
})
|
|
|
|
it('should accept float deltaY values in range -1000 to 1000', () => {
|
|
const values = [-1000, -999.99, -0.1, 0, 0.1, 999.99, 1000]
|
|
values.forEach((deltaY) => {
|
|
const event = new WheelEvent('wheel', {
|
|
ctrlKey: true,
|
|
deltaY,
|
|
deltaX: 0
|
|
})
|
|
expect(event.deltaY).toBeGreaterThanOrEqual(-1000)
|
|
expect(event.deltaY).toBeLessThanOrEqual(1000)
|
|
})
|
|
})
|
|
|
|
it('should always have deltaX = 0', () => {
|
|
const event = new WheelEvent('wheel', {
|
|
ctrlKey: true,
|
|
deltaY: 5.5,
|
|
deltaX: 0
|
|
})
|
|
expect(event.deltaX).toBe(0)
|
|
})
|
|
})
|
|
|
|
describe('Mouse input validation', () => {
|
|
it('should accept float deltaX values in range -1000 to 1000', () => {
|
|
const values = [-1000, -500.5, 0, 500.5, 1000]
|
|
values.forEach((deltaX) => {
|
|
const event = new WheelEvent('wheel', {
|
|
deltaY: 120,
|
|
deltaX
|
|
})
|
|
expect(event.deltaX).toBeGreaterThanOrEqual(-1000)
|
|
expect(event.deltaX).toBeLessThanOrEqual(1000)
|
|
})
|
|
})
|
|
|
|
it('should have deltaY >= 60 for Windows/Mac mouse', () => {
|
|
const values = [60, 60.1, 80, 120, 240]
|
|
values.forEach((deltaY) => {
|
|
const event = new WheelEvent('wheel', {
|
|
deltaY,
|
|
deltaX: 0
|
|
})
|
|
expect(event.deltaY).toBeGreaterThanOrEqual(60)
|
|
})
|
|
})
|
|
|
|
it('should have integer deltaY as multiples of 10 or 15 for Linux', () => {
|
|
// Base 10 multiples
|
|
const base10Values = [10, 20, 30, 40, 50, -10, -20, -30]
|
|
base10Values.forEach((deltaY) => {
|
|
expect(Number.isInteger(deltaY)).toBe(true)
|
|
// Use Math.abs to avoid JavaScript's -0 vs 0 issue with modulo on negative numbers
|
|
expect(Math.abs(deltaY) % 10).toBe(0)
|
|
})
|
|
|
|
// Base 15 multiples
|
|
const base15Values = [15, 30, 45, 60, -15, -30, -45]
|
|
base15Values.forEach((deltaY) => {
|
|
expect(Number.isInteger(deltaY)).toBe(true)
|
|
// Use Math.abs to avoid JavaScript's -0 vs 0 issue with modulo on negative numbers
|
|
expect(Math.abs(deltaY) % 15).toBe(0)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('Float vs Integer understanding', () => {
|
|
it('should recognize that integers are valid float values', () => {
|
|
const integerValues = [0, 1, -1, 10, -10, 100]
|
|
integerValues.forEach((value) => {
|
|
expect(Number.isInteger(value)).toBe(true)
|
|
expect(typeof value === 'number').toBe(true) // Valid as float
|
|
})
|
|
})
|
|
|
|
it('should recognize that decimals are NOT valid integer values', () => {
|
|
const decimalValues = [0.1, -0.1, 10.5, -10.5, 99.99]
|
|
decimalValues.forEach((value) => {
|
|
expect(Number.isInteger(value)).toBe(false)
|
|
expect(typeof value === 'number').toBe(true) // Still valid as float
|
|
})
|
|
})
|
|
|
|
it('should correctly validate pinch-to-zoom deltaY as float', () => {
|
|
// These are all valid float values for pinch-to-zoom
|
|
const validValues = [0, 1, -1, 0.5, -0.5, 999, -999, 500.123]
|
|
validValues.forEach((value) => {
|
|
const event = new WheelEvent('wheel', {
|
|
ctrlKey: true,
|
|
deltaY: value,
|
|
deltaX: 0
|
|
})
|
|
expect(typeof event.deltaY === 'number').toBe(true)
|
|
expect(event.deltaY >= -1000 && event.deltaY <= 1000).toBe(true)
|
|
})
|
|
})
|
|
})
|
|
})
|
|
})
|