Compare commits

...

12 Commits

Author SHA1 Message Date
Terry Jia
e5bee1b72a test 2025-09-07 21:28:01 -04:00
github-actions
4ef46f8bc3 Update test expectations [skip ci] 2025-09-08 01:26:39 +00:00
Terry Jia
8b8aaffe02 fix cases on Logi M705 2025-09-07 20:41:01 -04:00
Terry Jia
3c7d4202e4 console.log 2025-09-05 15:32:27 -04:00
github-actions
48fe4a648b Update test expectations [skip ci] 2025-09-05 19:31:16 +00:00
Terry Jia
f22383eb57 remove console log 2025-09-05 14:51:33 -04:00
Terry Jia
233c306431 bug fix 2025-09-04 23:09:44 -04:00
Terry Jia
780f1936ed test fix 2025-09-04 22:50:26 -04:00
Terry Jia
bbb9d47724 bug fix 2025-09-04 22:16:55 -04:00
github-actions
705d0565dd Update test expectations [skip ci] 2025-09-05 01:56:33 +00:00
Terry Jia
935587571c fix playwright test 2025-09-04 21:15:10 -04:00
Terry Jia
63d97a6303 improve standard canvas navigation 2025-09-04 20:39:22 -04:00
6 changed files with 1119 additions and 147 deletions

View File

@@ -734,10 +734,39 @@ export class ComfyPage {
await this.nextFrame()
}
async zoom(deltaY: number, steps: number = 1) {
await this.page.mouse.move(10, 10)
async zoom(
deltaY: number,
steps: number = 1,
wheelDeltaY?: number,
position?: Position
) {
const targetPos = position || { x: 10, y: 10 }
await this.page.mouse.move(targetPos.x, targetPos.y)
for (let i = 0; i < steps; i++) {
await this.page.mouse.wheel(0, deltaY)
if (wheelDeltaY !== undefined) {
// Dispatch a custom wheel event with wheelDeltaY property
await this.page.evaluate(
({ deltaY, wheelDeltaY, x, y }) => {
const canvas = document.getElementById('graph-canvas')
if (!canvas) return
const event = new WheelEvent('wheel', {
deltaY: deltaY,
deltaX: 0,
deltaMode: 0,
clientX: x,
clientY: y,
bubbles: true,
cancelable: true
})
// Add custom wheelDeltaY property
;(event as any).wheelDeltaY = wheelDeltaY
canvas.dispatchEvent(event)
},
{ deltaY, wheelDeltaY, x: targetPos.x, y: targetPos.y }
)
} else {
await this.page.mouse.wheel(0, deltaY)
}
}
await this.nextFrame()
}

View File

@@ -854,15 +854,14 @@ test.describe('Canvas Navigation', () => {
})
test('Mouse wheel should zoom in/out', async ({ comfyPage }) => {
await comfyPage.page.mouse.move(400, 300)
await comfyPage.page.mouse.wheel(0, -120)
await comfyPage.nextFrame()
const position = { x: 400, y: 300 }
// Use custom zoom method with wheelDeltaY to simulate mouse wheel
await comfyPage.zoom(-120, 1, 120, position)
await expect(comfyPage.canvas).toHaveScreenshot(
'legacy-wheel-zoom-in.png'
)
await comfyPage.page.mouse.wheel(0, 240)
await comfyPage.nextFrame()
await comfyPage.zoom(240, 1, -240, position)
await expect(comfyPage.canvas).toHaveScreenshot(
'legacy-wheel-zoom-out.png'
)
@@ -923,7 +922,23 @@ test.describe('Canvas Navigation', () => {
test('Ctrl + mouse wheel should zoom in/out', async ({ comfyPage }) => {
await comfyPage.page.mouse.move(400, 300)
await comfyPage.page.keyboard.down('Control')
await comfyPage.page.mouse.wheel(0, -120)
// Dispatch custom wheel event with wheelDeltaY and Control key
await comfyPage.page.evaluate(() => {
const canvas = document.getElementById('graph-canvas')
if (!canvas) return
const event = new WheelEvent('wheel', {
deltaY: -120,
deltaX: 0,
deltaMode: 0,
clientX: 400,
clientY: 300,
ctrlKey: true,
bubbles: true,
cancelable: true
})
;(event as any).wheelDeltaY = 120
canvas.dispatchEvent(event)
})
await comfyPage.page.keyboard.up('Control')
await comfyPage.nextFrame()
await expect(comfyPage.canvas).toHaveScreenshot(
@@ -931,7 +946,22 @@ test.describe('Canvas Navigation', () => {
)
await comfyPage.page.keyboard.down('Control')
await comfyPage.page.mouse.wheel(0, 240)
await comfyPage.page.evaluate(() => {
const canvas = document.getElementById('graph-canvas')
if (!canvas) return
const event = new WheelEvent('wheel', {
deltaY: 240,
deltaX: 0,
deltaMode: 0,
clientX: 400,
clientY: 300,
ctrlKey: true,
bubbles: true,
cancelable: true
})
;(event as any).wheelDeltaY = -240
canvas.dispatchEvent(event)
})
await comfyPage.page.keyboard.up('Control')
await comfyPage.nextFrame()
await expect(comfyPage.canvas).toHaveScreenshot(
@@ -1017,10 +1047,26 @@ test.describe('Canvas Navigation', () => {
await expect(comfyPage.canvas).toHaveScreenshot('standard-initial.png')
await comfyPage.page.mouse.move(400, 300)
const position = { x: 400, y: 300 }
// Pan right with Shift + wheel
await comfyPage.page.keyboard.down('Shift')
await comfyPage.page.mouse.wheel(0, 120)
await comfyPage.page.evaluate(({ x, y }) => {
const canvas = document.getElementById('graph-canvas')
if (!canvas) return
const event = new WheelEvent('wheel', {
deltaY: 120,
deltaX: 0,
deltaMode: 0,
clientX: x,
clientY: y,
shiftKey: true,
bubbles: true,
cancelable: true
})
;(event as any).wheelDeltaY = -120
canvas.dispatchEvent(event)
}, position)
await comfyPage.page.keyboard.up('Shift')
await comfyPage.nextFrame()
await expect(comfyPage.canvas).toHaveScreenshot(
@@ -1028,7 +1074,22 @@ test.describe('Canvas Navigation', () => {
)
await comfyPage.page.keyboard.down('Shift')
await comfyPage.page.mouse.wheel(0, -240)
await comfyPage.page.evaluate(({ x, y }) => {
const canvas = document.getElementById('graph-canvas')
if (!canvas) return
const event = new WheelEvent('wheel', {
deltaY: -240,
deltaX: 0,
deltaMode: 0,
clientX: x,
clientY: y,
shiftKey: true,
bubbles: true,
cancelable: true
})
;(event as any).wheelDeltaY = 240
canvas.dispatchEvent(event)
}, position)
await comfyPage.page.keyboard.up('Shift')
await comfyPage.nextFrame()
await expect(comfyPage.canvas).toHaveScreenshot(
@@ -1036,7 +1097,22 @@ test.describe('Canvas Navigation', () => {
)
await comfyPage.page.keyboard.down('Shift')
await comfyPage.page.mouse.wheel(0, 120)
await comfyPage.page.evaluate(({ x, y }) => {
const canvas = document.getElementById('graph-canvas')
if (!canvas) return
const event = new WheelEvent('wheel', {
deltaY: 120,
deltaX: 0,
deltaMode: 0,
clientX: x,
clientY: y,
shiftKey: true,
bubbles: true,
cancelable: true
})
;(event as any).wheelDeltaY = -120
canvas.dispatchEvent(event)
}, position)
await comfyPage.page.keyboard.up('Shift')
await comfyPage.nextFrame()
await expect(comfyPage.canvas).toHaveScreenshot(

View File

@@ -357,16 +357,56 @@ 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 (but except for some mouse models, for example Logitech M705 which its wheel can pan horizontally)
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)
// For some mouse models, for example Logitech M705, on Linux the wheelDeltaY and detaY are exactly the same when scrolling normally and value is 15 * 1/2, 1, 2, 3...8
const isMultipleOf15 = absWheelDeltaY % 15 === 0 || absWheelDeltaY === 7.5
if (absWheelDeltaY === Math.abs(event.deltaY) && isMultipleOf15) {
this.detectedDevice = 'mouse'
} else {
// 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 +432,28 @@ 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
// (except for some mouse models, for example Logitech M705, on Linux the wheelDeltaY and detaY are exactly the same when scrolling normally)
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)
}
}
}

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