improve standard canvas navigation

This commit is contained in:
Terry Jia
2025-09-04 20:36:42 -04:00
parent 58313ea05b
commit 63d97a6303
4 changed files with 1008 additions and 144 deletions

View File

@@ -357,16 +357,47 @@ export class CanvasPointer {
* Updates the device mode based on event patterns.
*/
#updateDeviceMode(event: WheelEvent, now: number): void {
if (this.#isTrackpadPattern(event)) {
console.log('event.deltaX:', event.deltaX)
console.log('event.deltaY:', event.deltaY)
const wheelDeltaY = (event as any).wheelDeltaY
console.log('wheelDeltaY: ', wheelDeltaY)
// if deltaX is non-zero, it's definitely a trackpad
if (Math.abs(event.deltaX) !== 0) {
this.detectedDevice = 'trackpad'
} else if (this.#isMousePattern(event)) {
this.detectedDevice = 'mouse'
} else if (
} else if (wheelDeltaY !== undefined) {
const absWheelDeltaY = Math.abs(wheelDeltaY)
// get this wheelDelta from real world testing
const wheelDeltaYThreshold = navigator.platform.includes('Mac') ? 30 : 75
if (absWheelDeltaY > wheelDeltaYThreshold) {
if (this.#isTrackpadPattern(event)) {
this.detectedDevice = 'trackpad'
} else {
this.detectedDevice = 'mouse'
}
} else if (absWheelDeltaY > 0) {
this.detectedDevice = 'trackpad'
}
} else {
// in case wheelDeltaY is undefined (e.g. Firefox), fall back to original pattern detection
if (this.#isTrackpadPattern(event)) {
this.detectedDevice = 'trackpad'
} else if (this.#isMousePattern(event)) {
this.detectedDevice = 'mouse'
}
}
if (
this.detectedDevice === 'trackpad' &&
this.#shouldBufferLinuxEvent(event)
) {
this.#bufferLinuxEvent(event, now)
}
console.log('Detected device:', this.detectedDevice)
}
/**
@@ -392,6 +423,27 @@ export class CanvasPointer {
// Pinch-to-zoom: ctrlKey with small deltaY
if (event.ctrlKey && Math.abs(event.deltaY) < 10) return true
// Two-finger panning vertically: zero deltaX AND small deltaY, only check this on non-Mac
if (
!navigator.platform.includes('Mac') &&
event.deltaX === 0 &&
Math.abs(event.deltaY) < 70
)
return true
const wheelDeltaY = (event as any).wheelDeltaY
if (
wheelDeltaY !== undefined &&
!navigator.platform.includes('Mac') &&
event.deltaX === 0
) {
// As tested in real world, on non-Mac, trackpad wheelDeltaY is usually very close to deltaY while two-finger panning vertically
if (Math.abs(Math.abs(event.deltaY) - Math.abs(wheelDeltaY)) < 2) {
return true
}
}
return false
}

View File

@@ -3548,17 +3548,33 @@ export class LGraphCanvas
// Detect if this is a trackpad gesture or mouse wheel
const isTrackpad = this.pointer.isTrackpadGesture(e)
const isCtrlOrMacMeta =
e.ctrlKey || (e.metaKey && navigator.platform.includes('Mac'))
const isZoomModifier = isCtrlOrMacMeta && !e.altKey && !e.shiftKey
if (isZoomModifier || LiteGraph.canvasNavigationMode === 'legacy') {
// Legacy mode or standard mode with ctrl - use wheel for zoom
if (isTrackpad) {
// Trackpad gesture - use smooth scaling
scale *= 1 + e.deltaY * (1 - this.zoom_speed) * 0.18
this.ds.changeScale(scale, [e.clientX, e.clientY], false)
// we need to separate trackpad from mouse because trackpad has different logic on standard
// mouse:
// 1. wheel scroll without ctrl = zoom in/out canvas
// 2. wheel scroll with ctrl = zoom in/out canvas
// trackpad:
// 1. two finger scroll = pan canvas
// 2. two finger pitch to zoom (included ctrl) = zoom in/out canvas
if (isTrackpad) {
const factor = 0.18
if (LiteGraph.canvasNavigationMode === 'standard') {
if (e.ctrlKey || e.metaKey) {
scale *= 1 + e.deltaY * (1 - this.zoom_speed) * factor
this.ds.changeScale(scale, [e.clientX, e.clientY], false)
} else {
this.ds.offset[0] -= e.deltaX * (1 + factor) * (1 / scale)
this.ds.offset[1] -= e.deltaY * (1 + factor) * (1 / scale)
}
} else {
scale *= 1 + e.deltaY * (1 - this.zoom_speed) * factor
this.ds.changeScale(scale, [e.clientX, e.clientY], false)
}
} else {
const isZoomModifier = !e.altKey && !e.shiftKey
if (isZoomModifier || LiteGraph.canvasNavigationMode === 'legacy') {
// Mouse wheel - use stepped scaling
if (e.deltaY < 0) {
scale *= this.zoom_speed
@@ -3566,17 +3582,16 @@ export class LGraphCanvas
scale *= 1 / this.zoom_speed
}
this.ds.changeScale(scale, [e.clientX, e.clientY])
}
} else {
// Standard mode without ctrl - use wheel / gestures to pan
// Trackpads and mice work on significantly different scales
const factor = isTrackpad ? 0.18 : 0.008_333
if (!isTrackpad && e.shiftKey && e.deltaX === 0) {
this.ds.offset[0] -= e.deltaY * (1 + factor) * (1 / scale)
} else {
this.ds.offset[0] -= e.deltaX * (1 + factor) * (1 / scale)
this.ds.offset[1] -= e.deltaY * (1 + factor) * (1 / scale)
// Standard mode without ctrl - use wheel to pan
const factor = 0.008_333
if (e.shiftKey && e.deltaX === 0) {
this.ds.offset[0] -= e.deltaY * (1 + factor) * (1 / scale)
} else {
this.ds.offset[0] -= e.deltaX * (1 + factor) * (1 / scale)
this.ds.offset[1] -= e.deltaY * (1 + factor) * (1 / scale)
}
}
}
@@ -3609,13 +3624,15 @@ export class LGraphCanvas
if (e.type == 'keydown') {
// TODO: Switch
if (e.key === ' ') {
// space
this.read_only = true
if (this._previously_dragging_canvas === null) {
this._previously_dragging_canvas = this.dragging_canvas
// space - only switch to pan mode if we're in standard mode and not already in pan/read_only mode
if (LiteGraph.canvasNavigationMode === 'standard' && !this.read_only) {
this.read_only = true
if (this._previously_dragging_canvas === null) {
this._previously_dragging_canvas = this.dragging_canvas
}
this.dragging_canvas = this.pointer.isDown
block_default = true
}
this.dragging_canvas = this.pointer.isDown
block_default = true
} else if (e.key === 'Escape') {
// esc
if (this.linkConnector.isConnecting) {
@@ -3659,11 +3676,16 @@ export class LGraphCanvas
}
} else if (e.type == 'keyup') {
if (e.key === ' ') {
// space
this.read_only = false
this.dragging_canvas =
(this._previously_dragging_canvas ?? false) && this.pointer.isDown
this._previously_dragging_canvas = null
// space - only revert if we had temporarily switched to pan mode
if (
LiteGraph.canvasNavigationMode === 'standard' &&
this._previously_dragging_canvas !== null
) {
this.read_only = false
this.dragging_canvas =
(this._previously_dragging_canvas ?? false) && this.pointer.isDown
this._previously_dragging_canvas = null
}
}
for (const node of Object.values(this.selected_nodes)) {

View File

@@ -0,0 +1,896 @@
/**
* Real QA Data Tests for CanvasPointer Device Detection
*
* This file contains tests based on actual device data collected from QA testing.
* Each test represents real-world behavior from specific devices and platforms.
*
* Test Structure:
* - Platform: The operating system (Mac, Windows, Linux)
* - Device: The specific input device (mouse, trackpad, precision touchpad)
* - Gesture: The type of interaction (scroll, pinch-to-zoom, two-finger pan)
* - Data: Exact event sequences as captured from real devices
*
* @vitest-environment jsdom
*/
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { CanvasPointer } from '../src/CanvasPointer'
describe('CanvasPointer Device Detection - Real QA Data Tests', () => {
let element: HTMLDivElement
let pointer: CanvasPointer
let originalPlatform: string
beforeEach(() => {
element = document.createElement('div')
pointer = new CanvasPointer(element)
vi.spyOn(performance, 'now').mockReturnValue(0)
vi.spyOn(global, 'setTimeout')
vi.spyOn(global, 'clearTimeout')
originalPlatform = navigator.platform
})
afterEach(() => {
vi.restoreAllMocks()
vi.clearAllTimers()
Object.defineProperty(navigator, 'platform', {
value: originalPlatform,
writable: true,
configurable: true
})
})
function mockPlatform(platform: 'Mac' | 'Windows' | 'Linux') {
const platformMap = {
Mac: 'MacIntel',
Windows: 'Win32',
Linux: 'Linux x86_64'
}
Object.defineProperty(navigator, 'platform', {
value: platformMap[platform],
writable: true,
configurable: true
})
}
describe('Mouse wheel detection from real devices', () => {
it('should detect mouse from QA data: Mac mouse with negative wheelDelta pattern', () => {
// Platform: macOS (Mac)
// Device: Mouse
// Expected: All events should be detected as mouse
mockPlatform('Mac')
const testSequence = [
{ deltaX: 0, deltaY: 12, wheelDeltaY: -36 },
{ deltaX: 0, deltaY: 13, wheelDeltaY: -39 },
{ deltaX: 0, deltaY: 12, wheelDeltaY: -36 },
{ deltaX: 0, deltaY: 13, wheelDeltaY: -39 }
]
pointer = new CanvasPointer(element)
testSequence.forEach((eventData, index) => {
// Mock time progression (16ms between events for ~60fps)
vi.spyOn(performance, 'now').mockReturnValue(index * 16)
const event = new WheelEvent('wheel', {
deltaX: eventData.deltaX,
deltaY: eventData.deltaY
})
Object.defineProperty(event, 'wheelDeltaY', {
value: eventData.wheelDeltaY,
writable: false
})
const result = pointer.isTrackpadGesture(event)
expect(pointer.detectedDevice).toBe('mouse')
expect(result).toBe(false)
})
})
it('should detect mouse from QA data: Mac mouse events with varying deltaY values', () => {
// This test captures the pattern where deltaY varies slightly (12, 13, 12, 13)
// but wheelDeltaY maintains the 3x ratio (-36, -39, -36, -39)
// Platform: macOS (Mac)
mockPlatform('Mac')
const testSequence = [
{ deltaX: 0, deltaY: 12, wheelDeltaY: -36, expectedDevice: 'mouse' },
{ deltaX: 0, deltaY: 13, wheelDeltaY: -39, expectedDevice: 'mouse' },
{ deltaX: 0, deltaY: 12, wheelDeltaY: -36, expectedDevice: 'mouse' },
{ deltaX: 0, deltaY: 13, wheelDeltaY: -39, expectedDevice: 'mouse' }
]
pointer = new CanvasPointer(element)
testSequence.forEach((eventData, index) => {
vi.spyOn(performance, 'now').mockReturnValue(index * 20)
const event = new WheelEvent('wheel', {
deltaX: eventData.deltaX,
deltaY: eventData.deltaY
})
Object.defineProperty(event, 'wheelDeltaY', {
value: eventData.wheelDeltaY,
writable: false
})
pointer.isTrackpadGesture(event)
expect(pointer.detectedDevice).toBe(eventData.expectedDevice)
})
})
it('should detect mouse from QA data: Mac mouse with varying scroll speeds', () => {
// Real data from QA testing showing mouse wheel behavior with different scroll speeds
// Platform: macOS (Mac)
// Device: Mouse (with varying scroll speeds)
// Expected: All events should be detected as mouse
mockPlatform('Mac')
const testSequence = [
{ deltaX: 0, deltaY: 13, wheelDeltaY: -39, expectedDevice: 'mouse' },
{ deltaX: 0, deltaY: 13, wheelDeltaY: -39, expectedDevice: 'mouse' },
{ deltaX: 0, deltaY: 26, wheelDeltaY: -78, expectedDevice: 'mouse' }, // Double speed scroll
{ deltaX: 0, deltaY: -13, wheelDeltaY: 39, expectedDevice: 'mouse' }, // Reverse direction
{ deltaX: 0, deltaY: 12, wheelDeltaY: -36, expectedDevice: 'mouse' }
]
pointer = new CanvasPointer(element)
testSequence.forEach((eventData, index) => {
vi.spyOn(performance, 'now').mockReturnValue(index * 16)
const event = new WheelEvent('wheel', {
deltaX: eventData.deltaX,
deltaY: eventData.deltaY
})
Object.defineProperty(event, 'wheelDeltaY', {
value: eventData.wheelDeltaY,
writable: false
})
const result = pointer.isTrackpadGesture(event)
expect(pointer.detectedDevice).toBe(eventData.expectedDevice)
expect(result).toBe(false) // Not trackpad
})
})
it('should detect mouse from QA data: Mac mouse with small deltaY pattern', () => {
// Real data from QA testing showing Mac mouse with smaller deltaY values
// Platform: macOS (Mac)
// Device: Mouse (slower/precise scrolling)
// Expected: All events should be detected as mouse
// Note: deltaY is 4.000244140625 with wheelDeltaY of ±120 (30x ratio)
mockPlatform('Mac')
const testSequence = [
{
deltaX: 0,
deltaY: 4.000244140625,
wheelDeltaY: -120,
expectedDevice: 'mouse'
},
{
deltaX: 0,
deltaY: -4.000244140625,
wheelDeltaY: 120,
expectedDevice: 'mouse'
},
{
deltaX: 0,
deltaY: 4.000244140625,
wheelDeltaY: -120,
expectedDevice: 'mouse'
},
{
deltaX: 0,
deltaY: -4.000244140625,
wheelDeltaY: 120,
expectedDevice: 'mouse'
},
{
deltaX: 0,
deltaY: 4.000244140625,
wheelDeltaY: -120,
expectedDevice: 'mouse'
}
]
pointer = new CanvasPointer(element)
testSequence.forEach((eventData, index) => {
vi.spyOn(performance, 'now').mockReturnValue(index * 16)
const event = new WheelEvent('wheel', {
deltaX: eventData.deltaX,
deltaY: eventData.deltaY
})
Object.defineProperty(event, 'wheelDeltaY', {
value: eventData.wheelDeltaY,
writable: false
})
const result = pointer.isTrackpadGesture(event)
expect(pointer.detectedDevice).toBe(eventData.expectedDevice)
expect(result).toBe(false) // Not trackpad
})
})
it('should detect mouse from QA data: Windows mouse with high precision values', () => {
// Real data from QA testing showing Windows mouse wheel behavior
// Platform: Windows
// Device: Mouse with high-precision scrolling
// Expected: All events should be detected as mouse
// Note: Windows has characteristic fractional deltaY values like 111.111...
mockPlatform('Windows')
const testSequence = [
{
deltaX: 0,
deltaY: -111.11111615700719,
wheelDeltaY: 133,
expectedDevice: 'mouse'
},
{
deltaX: 0,
deltaY: 111.11111615700719,
wheelDeltaY: -133,
expectedDevice: 'mouse'
},
{
deltaX: 0,
deltaY: -111.11111615700719,
wheelDeltaY: 133,
expectedDevice: 'mouse'
},
{
deltaX: 0,
deltaY: -111.11111615700719,
wheelDeltaY: 133,
expectedDevice: 'mouse'
}
]
pointer = new CanvasPointer(element)
testSequence.forEach((eventData, index) => {
vi.spyOn(performance, 'now').mockReturnValue(index * 16)
const event = new WheelEvent('wheel', {
deltaX: eventData.deltaX,
deltaY: eventData.deltaY
})
Object.defineProperty(event, 'wheelDeltaY', {
value: eventData.wheelDeltaY,
writable: false
})
const result = pointer.isTrackpadGesture(event)
expect(pointer.detectedDevice).toBe(eventData.expectedDevice)
expect(result).toBe(false) // Not trackpad
})
})
it('should detect mouse from QA data: Windows mouse with integer deltaY pattern', () => {
// Real data from QA testing showing Windows mouse wheel behavior
// Platform: Windows
// Device: Mouse with standard scrolling
// Expected: All events should be detected as mouse
// Note: Windows mouse with clean integer deltaY of 100 and wheelDeltaY of ±120
mockPlatform('Windows')
const testSequence = [
{ deltaX: 0, deltaY: 100, wheelDeltaY: -120, expectedDevice: 'mouse' },
{ deltaX: 0, deltaY: -100, wheelDeltaY: 120, expectedDevice: 'mouse' },
{ deltaX: 0, deltaY: 100, wheelDeltaY: -120, expectedDevice: 'mouse' },
{ deltaX: 0, deltaY: 100, wheelDeltaY: -120, expectedDevice: 'mouse' },
{ deltaX: 0, deltaY: -100, wheelDeltaY: 120, expectedDevice: 'mouse' },
{ deltaX: 0, deltaY: 100, wheelDeltaY: -120, expectedDevice: 'mouse' },
{ deltaX: 0, deltaY: 100, wheelDeltaY: -120, expectedDevice: 'mouse' },
{ deltaX: 0, deltaY: -100, wheelDeltaY: 120, expectedDevice: 'mouse' }
]
pointer = new CanvasPointer(element)
testSequence.forEach((eventData, index) => {
vi.spyOn(performance, 'now').mockReturnValue(index * 16)
const event = new WheelEvent('wheel', {
deltaX: eventData.deltaX,
deltaY: eventData.deltaY
})
Object.defineProperty(event, 'wheelDeltaY', {
value: eventData.wheelDeltaY,
writable: false
})
const result = pointer.isTrackpadGesture(event)
expect(pointer.detectedDevice).toBe(eventData.expectedDevice)
expect(result).toBe(false) // Not trackpad
})
})
})
describe('Trackpad detection from real devices', () => {
it('should detect trackpad from QA data: Windows trackpad pinch-to-zoom', () => {
// Platform: Windows
// Device: Precision Touchpad (pinch-to-zoom gesture)
// Expected: All events should be detected as trackpad
// Note: Windows trackpad has small deltaY values but constant wheelDeltaY
mockPlatform('Windows')
const testSequence = [
{
deltaX: 0,
deltaY: -3.3135088654249674,
wheelDeltaY: 133,
ctrlKey: true,
expectedDevice: 'trackpad'
},
{
deltaX: 0,
deltaY: 8.94965318420894,
wheelDeltaY: -133,
ctrlKey: true,
expectedDevice: 'trackpad'
},
{
deltaX: 0,
deltaY: -3.654589743292812,
wheelDeltaY: 133,
ctrlKey: true,
expectedDevice: 'trackpad'
},
{
deltaX: 0,
deltaY: -23.625750933408778,
wheelDeltaY: 133,
ctrlKey: true,
expectedDevice: 'trackpad'
},
{
deltaX: 0,
deltaY: -3.616658329584863,
wheelDeltaY: 133,
ctrlKey: true,
expectedDevice: 'trackpad'
},
{
deltaX: 0,
deltaY: -1.999075238624841,
wheelDeltaY: 133,
ctrlKey: true,
expectedDevice: 'trackpad'
}
]
pointer = new CanvasPointer(element)
testSequence.forEach((eventData, index) => {
vi.spyOn(performance, 'now').mockReturnValue(index * 16)
const event = new WheelEvent('wheel', {
deltaX: eventData.deltaX,
deltaY: eventData.deltaY,
ctrlKey: eventData.ctrlKey
})
Object.defineProperty(event, 'wheelDeltaY', {
value: eventData.wheelDeltaY,
writable: false
})
const result = pointer.isTrackpadGesture(event)
expect(pointer.detectedDevice).toBe(eventData.expectedDevice)
expect(result).toBe(true) // Is trackpad
})
})
it('should detect trackpad from QA data: Windows trackpad two-finger scroll', () => {
// Platform: Windows
// Device: Precision Touchpad (two-finger vertical scroll)
// Expected: All events should be detected as trackpad
// Note: Windows trackpad has small deltaY values with matching small wheelDeltaY
mockPlatform('Windows')
const testSequence = [
{
deltaX: 0,
deltaY: -2.222222323140144,
wheelDeltaY: 2,
expectedDevice: 'trackpad'
},
{
deltaX: 0,
deltaY: 1.111111161570072,
wheelDeltaY: -1,
expectedDevice: 'trackpad'
},
{
deltaX: 0,
deltaY: 16.66666742355108,
wheelDeltaY: -16,
expectedDevice: 'trackpad'
},
{
deltaX: 0,
deltaY: 4.444444646280288,
wheelDeltaY: -4,
expectedDevice: 'trackpad'
},
{
deltaX: 0,
deltaY: 4.444444646280288,
wheelDeltaY: -4,
expectedDevice: 'trackpad'
},
{
deltaX: 0,
deltaY: 5.55555580785036,
wheelDeltaY: -5,
expectedDevice: 'trackpad'
}
]
pointer = new CanvasPointer(element)
testSequence.forEach((eventData, index) => {
vi.spyOn(performance, 'now').mockReturnValue(index * 16)
const event = new WheelEvent('wheel', {
deltaX: eventData.deltaX,
deltaY: eventData.deltaY
})
Object.defineProperty(event, 'wheelDeltaY', {
value: eventData.wheelDeltaY,
writable: false
})
const result = pointer.isTrackpadGesture(event)
expect(pointer.detectedDevice).toBe(eventData.expectedDevice)
expect(result).toBe(true) // Is trackpad
})
})
it('should detect trackpad from QA data: Windows trackpad horizontal scroll', () => {
// Real data from QA testing showing Windows trackpad horizontal scroll behavior
// Platform: Windows
// Device: Precision Touchpad (two-finger horizontal scroll)
// Expected: All events should be detected as trackpad
// Note: Windows trackpad horizontal scroll has deltaX only, no deltaY or wheelDeltaY
mockPlatform('Windows')
const testSequence = [
{
deltaX: -33.33333484710216,
deltaY: 0,
wheelDeltaY: 0,
expectedDevice: 'trackpad'
},
{
deltaX: -37.77777949338245,
deltaY: 0,
wheelDeltaY: 0,
expectedDevice: 'trackpad'
},
{
deltaX: 73.33333666362475,
deltaY: 0,
wheelDeltaY: 0,
expectedDevice: 'trackpad'
}
]
pointer = new CanvasPointer(element)
testSequence.forEach((eventData, index) => {
vi.spyOn(performance, 'now').mockReturnValue(index * 16)
const event = new WheelEvent('wheel', {
deltaX: eventData.deltaX,
deltaY: eventData.deltaY
})
Object.defineProperty(event, 'wheelDeltaY', {
value: eventData.wheelDeltaY,
writable: false
})
const result = pointer.isTrackpadGesture(event)
expect(pointer.detectedDevice).toBe(eventData.expectedDevice)
expect(result).toBe(true) // Is trackpad
})
})
it('should detect trackpad from QA data: Mac trackpad pinch-to-zoom', () => {
// Real data from QA testing showing trackpad pinch-to-zoom behavior
// Platform: macOS (Mac)
// Device: MacBook Trackpad (pinch-to-zoom gesture)
// Expected: All events should be detected as trackpad
// Note: ctrlKey is true for pinch-to-zoom on Mac
mockPlatform('Mac')
const testSequence = [
{
deltaX: 0,
deltaY: 1.206591010093689,
wheelDeltaY: -120,
ctrlKey: true,
expectedDevice: 'trackpad'
},
{
deltaX: 0,
deltaY: -1.3895320892333984,
wheelDeltaY: 120,
ctrlKey: true,
expectedDevice: 'trackpad'
},
{
deltaX: 0,
deltaY: -0.5978795289993286,
wheelDeltaY: 120,
ctrlKey: true,
expectedDevice: 'trackpad'
}
]
pointer = new CanvasPointer(element)
testSequence.forEach((eventData, index) => {
vi.spyOn(performance, 'now').mockReturnValue(index * 16)
const event = new WheelEvent('wheel', {
deltaX: eventData.deltaX,
deltaY: eventData.deltaY,
ctrlKey: eventData.ctrlKey
})
Object.defineProperty(event, 'wheelDeltaY', {
value: eventData.wheelDeltaY,
writable: false
})
const result = pointer.isTrackpadGesture(event)
expect(pointer.detectedDevice).toBe(eventData.expectedDevice)
expect(result).toBe(true) // Is trackpad
})
})
it('should detect trackpad from QA data: Mac trackpad pinch-to-zoom (set 2)', () => {
// Real data from QA testing showing another Mac trackpad pinch-to-zoom pattern
// Platform: macOS (Mac)
// Device: MacBook Trackpad (pinch-to-zoom gesture)
// Expected: All events should be detected as trackpad
// Note: ctrlKey is true for pinch-to-zoom on Mac, deltaY values vary more in this set
mockPlatform('Mac')
const testSequence = [
{
deltaX: 0,
deltaY: 1.9179956912994385,
wheelDeltaY: -120,
ctrlKey: true,
expectedDevice: 'trackpad'
},
{
deltaX: 0,
deltaY: -1.9791855812072754,
wheelDeltaY: 120,
ctrlKey: true,
expectedDevice: 'trackpad'
},
{
deltaX: 0,
deltaY: -0.8947280049324036,
wheelDeltaY: 120,
ctrlKey: true,
expectedDevice: 'trackpad'
},
{
deltaX: 0,
deltaY: -0.8947280049324036,
wheelDeltaY: 120,
ctrlKey: true,
expectedDevice: 'trackpad'
},
{
deltaX: 0,
deltaY: 0.80277419090271,
wheelDeltaY: -120,
ctrlKey: true,
expectedDevice: 'trackpad'
}
]
pointer = new CanvasPointer(element)
testSequence.forEach((eventData, index) => {
vi.spyOn(performance, 'now').mockReturnValue(index * 16)
const event = new WheelEvent('wheel', {
deltaX: eventData.deltaX,
deltaY: eventData.deltaY,
ctrlKey: eventData.ctrlKey
})
Object.defineProperty(event, 'wheelDeltaY', {
value: eventData.wheelDeltaY,
writable: false
})
const result = pointer.isTrackpadGesture(event)
expect(pointer.detectedDevice).toBe(eventData.expectedDevice)
expect(result).toBe(true) // Is trackpad
})
})
it('should detect trackpad from QA data: Mac trackpad horizontal scroll', () => {
// Real data from QA testing showing Mac trackpad horizontal scroll behavior
// Platform: macOS (Mac)
// Device: MacBook Trackpad (two-finger horizontal scroll)
// Expected: All events should be detected as trackpad
// Note: Mac trackpad horizontal scroll has integer deltaX values, no deltaY or wheelDeltaY
mockPlatform('Mac')
const testSequence = [
{ deltaX: 9, deltaY: 0, wheelDeltaY: 0, expectedDevice: 'trackpad' },
{ deltaX: -6, deltaY: 0, wheelDeltaY: 0, expectedDevice: 'trackpad' },
{ deltaX: 2, deltaY: 0, wheelDeltaY: 0, expectedDevice: 'trackpad' },
{ deltaX: -3, deltaY: 0, wheelDeltaY: 0, expectedDevice: 'trackpad' },
{ deltaX: 2, deltaY: 0, wheelDeltaY: 0, expectedDevice: 'trackpad' },
{ deltaX: -2, deltaY: 0, wheelDeltaY: 0, expectedDevice: 'trackpad' }
]
pointer = new CanvasPointer(element)
testSequence.forEach((eventData, index) => {
vi.spyOn(performance, 'now').mockReturnValue(index * 16)
const event = new WheelEvent('wheel', {
deltaX: eventData.deltaX,
deltaY: eventData.deltaY
})
Object.defineProperty(event, 'wheelDeltaY', {
value: eventData.wheelDeltaY,
writable: false
})
const result = pointer.isTrackpadGesture(event)
expect(pointer.detectedDevice).toBe(eventData.expectedDevice)
expect(result).toBe(true) // Is trackpad
})
})
it('should detect trackpad from QA data: Mac trackpad vertical scroll', () => {
// Real data from QA testing showing Mac trackpad vertical scroll behavior
// Platform: macOS (Mac)
// Device: MacBook Trackpad (two-finger vertical scroll)
// Expected: All events should be detected as trackpad
// Note: Mac trackpad vertical scroll has very small integer deltaY (±1) with wheelDeltaY (±3)
mockPlatform('Mac')
const testSequence = [
{ deltaX: 0, deltaY: 1, wheelDeltaY: -3, expectedDevice: 'trackpad' },
{ deltaX: 0, deltaY: -1, wheelDeltaY: 3, expectedDevice: 'trackpad' },
{ deltaX: 0, deltaY: 1, wheelDeltaY: -3, expectedDevice: 'trackpad' },
{ deltaX: 0, deltaY: 1, wheelDeltaY: -3, expectedDevice: 'trackpad' },
{ deltaX: 0, deltaY: 1, wheelDeltaY: -3, expectedDevice: 'trackpad' },
{ deltaX: 0, deltaY: -1, wheelDeltaY: 3, expectedDevice: 'trackpad' }
]
pointer = new CanvasPointer(element)
testSequence.forEach((eventData, index) => {
vi.spyOn(performance, 'now').mockReturnValue(index * 16)
const event = new WheelEvent('wheel', {
deltaX: eventData.deltaX,
deltaY: eventData.deltaY
})
Object.defineProperty(event, 'wheelDeltaY', {
value: eventData.wheelDeltaY,
writable: false
})
const result = pointer.isTrackpadGesture(event)
expect(pointer.detectedDevice).toBe(eventData.expectedDevice)
expect(result).toBe(true) // Is trackpad
})
})
it('should detect trackpad from QA data: Windows trackpad mixed gestures', () => {
// Real data from QA testing showing Windows trackpad with various gestures
// Platform: Windows
// Device: Precision Touchpad (mixed two-finger scrolling)
// Expected: All events should be detected as trackpad
// Note: This sequence shows both horizontal and vertical scrolling patterns
mockPlatform('Windows')
const testSequence = [
{
deltaX: 34.4444453,
deltaY: 31.1111119,
wheelDeltaY: -31,
expectedDevice: 'trackpad'
},
{
deltaX: -3.3333334,
deltaY: -1.1111114,
wheelDeltaY: 1,
expectedDevice: 'trackpad'
},
{
deltaX: 0,
deltaY: -37.77777877854715,
wheelDeltaY: 37,
expectedDevice: 'trackpad'
},
{
deltaX: 0,
deltaY: -28.88888965,
wheelDeltaY: 28,
expectedDevice: 'trackpad'
},
{
deltaX: 0,
deltaY: -62.22222387054825,
wheelDeltaY: 62,
expectedDevice: 'trackpad'
},
{
deltaX: 0,
deltaY: 6.666666843,
wheelDeltaY: -6,
expectedDevice: 'trackpad'
}
]
pointer = new CanvasPointer(element)
testSequence.forEach((eventData, index) => {
// Allow some time between events to avoid cooldown period
vi.spyOn(performance, 'now').mockReturnValue(index * 100)
const event = new WheelEvent('wheel', {
deltaX: eventData.deltaX,
deltaY: eventData.deltaY
})
Object.defineProperty(event, 'wheelDeltaY', {
value: eventData.wheelDeltaY,
writable: false
})
const result = pointer.isTrackpadGesture(event)
expect(pointer.detectedDevice).toBe(eventData.expectedDevice)
expect(result).toBe(true) // Is trackpad
})
})
it('should detect trackpad from QA data: Windows trackpad vertical scroll', () => {
// Real data from QA testing showing Windows trackpad two-finger vertical scrolling
// Platform: Windows
// Device: Precision Touchpad (two-finger vertical scroll)
// Expected: All events should be detected as trackpad
// Note: Windows trackpad shows small deltaY values with matching wheelDeltaY (opposite sign)
mockPlatform('Windows')
const testSequence = [
{ deltaX: 0, deltaY: -6, wheelDeltaY: 6, expectedDevice: 'trackpad' },
{ deltaX: 0, deltaY: 1, wheelDeltaY: -1, expectedDevice: 'trackpad' },
{ deltaX: 0, deltaY: -19, wheelDeltaY: 18, expectedDevice: 'trackpad' },
{ deltaX: 0, deltaY: 38, wheelDeltaY: -37, expectedDevice: 'trackpad' },
{ deltaX: 0, deltaY: -4, wheelDeltaY: 4, expectedDevice: 'trackpad' },
{ deltaX: 0, deltaY: -21, wheelDeltaY: 21, expectedDevice: 'trackpad' }
]
pointer = new CanvasPointer(element)
testSequence.forEach((eventData, index) => {
vi.spyOn(performance, 'now').mockReturnValue(index * 16)
const event = new WheelEvent('wheel', {
deltaX: eventData.deltaX,
deltaY: eventData.deltaY
})
Object.defineProperty(event, 'wheelDeltaY', {
value: eventData.wheelDeltaY,
writable: false
})
const result = pointer.isTrackpadGesture(event)
expect(pointer.detectedDevice).toBe(eventData.expectedDevice)
expect(result).toBe(true) // Is trackpad
})
})
it('should detect trackpad from QA data: Windows trackpad pinch gesture', () => {
// Real data from QA testing showing Windows trackpad pinch-to-zoom behavior
// Platform: Windows
// Device: Precision Touchpad (pinch gesture)
// Expected: All events should be detected as trackpad
// Note: Windows trackpad shows small decimal deltaY values with standard wheelDeltaY (-120/120)
mockPlatform('Windows')
const testSequence = [
{
deltaX: 0,
deltaY: 0.7864023208,
wheelDeltaY: -120,
ctrlKey: true,
expectedDevice: 'trackpad'
},
{
deltaX: 0,
deltaY: -1.8231786727,
wheelDeltaY: 120,
ctrlKey: true,
expectedDevice: 'trackpad'
},
{
deltaX: 0,
deltaY: -5.795222473,
wheelDeltaY: 120,
ctrlKey: true,
expectedDevice: 'trackpad'
},
{
deltaX: 0,
deltaY: -2.065727996,
wheelDeltaY: 120,
ctrlKey: true,
expectedDevice: 'trackpad'
}
]
pointer = new CanvasPointer(element)
testSequence.forEach((eventData, index) => {
vi.spyOn(performance, 'now').mockReturnValue(index * 16)
const event = new WheelEvent('wheel', {
deltaX: eventData.deltaX,
deltaY: eventData.deltaY,
ctrlKey: eventData.ctrlKey
})
Object.defineProperty(event, 'wheelDeltaY', {
value: eventData.wheelDeltaY,
writable: false
})
const result = pointer.isTrackpadGesture(event)
expect(pointer.detectedDevice).toBe(eventData.expectedDevice)
expect(result).toBe(true)
})
})
})
})

View File

@@ -89,17 +89,6 @@ describe('CanvasPointer Device Detection - Efficient Timestamp-Based TDD Tests',
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,
@@ -135,17 +124,6 @@ describe('CanvasPointer Device Detection - Efficient Timestamp-Based TDD Tests',
})
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,
@@ -156,17 +134,6 @@ describe('CanvasPointer Device Detection - Efficient Timestamp-Based TDD Tests',
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')
})
})
})
@@ -193,32 +160,6 @@ describe('CanvasPointer Device Detection - Efficient Timestamp-Based TDD Tests',
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)
@@ -244,32 +185,6 @@ describe('CanvasPointer Device Detection - Efficient Timestamp-Based TDD Tests',
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', () => {
@@ -360,7 +275,7 @@ describe('CanvasPointer Device Detection - Efficient Timestamp-Based TDD Tests',
})
describe('500ms Cooldown Period', () => {
it('should NOT allow switching from mouse to trackpad within 500ms', () => {
it('should allow switching from mouse to trackpad within 500ms', () => {
pointer.detectedDevice = 'mouse'
// First event at time 0
@@ -381,7 +296,7 @@ describe('CanvasPointer Device Detection - Efficient Timestamp-Based TDD Tests',
deltaX: 0
})
pointer.isTrackpadGesture(event2)
expect(pointer.detectedDevice).toBe('mouse')
expect(pointer.detectedDevice).toBe('trackpad')
})
it('should allow switching from mouse to trackpad after 500ms', () => {
@@ -443,7 +358,7 @@ describe('CanvasPointer Device Detection - Efficient Timestamp-Based TDD Tests',
// Send first mouse event at time 0
vi.spyOn(performance, 'now').mockReturnValue(0)
pointer.isTrackpadGesture(
new WheelEvent('wheel', { deltaY: 60, deltaX: 0 })
new WheelEvent('wheel', { deltaY: 75, deltaX: 0 })
)
// Send trackpad events within 500ms window
@@ -977,7 +892,7 @@ describe('CanvasPointer Device Detection - Efficient Timestamp-Based TDD Tests',
for (let i = 0; i < 10; i++) {
vi.spyOn(performance, 'now').mockReturnValue(i * 30) // 30ms between events
const event = new WheelEvent('wheel', {
deltaY: 60,
deltaY: 75,
deltaX: 0
})
pointer.isTrackpadGesture(event)
@@ -985,27 +900,6 @@ describe('CanvasPointer Device Detection - Efficient Timestamp-Based TDD Tests',
}
})
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