From 42d336baa4342458f9623a13ea37272660d4021a Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Sat, 29 Aug 2026 11:20:40 +0000 Subject: [PATCH] test: stop the paired-mousemove test racing a 16ms wall-clock window `usePointerLock` suppresses the mousemove that Chromium pairs with a pointermove by checking `performance.now() - lastPointerMoveAt < 16`. The test dispatches the pair on consecutive lines and expects the second to be dropped. That makes real elapsed time part of the assertion. On a loaded CI runner more than 16ms can pass between those two dispatches, the mousemove is taken as a second movement, and the pointer reads 0.7 instead of 0.6 -- a red build with nothing wrong in the code. It failed exactly this way on PR #87, which touches only apps/desktop, while the identical desktop copy of the test passed in the same run. Freezes performance.now across the paired dispatch in both copies. Confirmed by running the same test with a clock stepping 20ms per call, which reproduces the CI diff exactly (0.7 vs 0.6). Nothing that ships changes -- the 16ms window is still the shipped behaviour, it just is not a race in the test any more. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_013FGWJHRL6B6ExLg25UenBS --- .../components/control/InputCapture.test.tsx | 28 +++++++++++++------ .../components/control/InputCapture.test.tsx | 28 +++++++++++++------ 2 files changed, 40 insertions(+), 16 deletions(-) diff --git a/apps/desktop/src/renderer/components/control/InputCapture.test.tsx b/apps/desktop/src/renderer/components/control/InputCapture.test.tsx index 0be006a8..3e735d18 100644 --- a/apps/desktop/src/renderer/components/control/InputCapture.test.tsx +++ b/apps/desktop/src/renderer/components/control/InputCapture.test.tsx @@ -198,6 +198,13 @@ describe('InputCapture pointer lock', () => { ); }); + // The paired mousemove is suppressed by a 16ms window on performance.now(), + // so the real wall-clock gap between the two dispatches below decides the + // outcome. On a loaded machine that gap can exceed 16ms, the mousemove lands + // as a second movement, and the pointer reads 0.7 instead of 0.6 -- a flaky + // test rather than a regression. Freeze the clock across the pair so the + // window is not a race. Only these two dispatches are covered; everything + // else in the test still runs on real time. it('does not count a paired mousemove twice after pointermove', () => { const browser = installBrowserApis(); const onInputEvent = vi.fn(); @@ -208,14 +215,19 @@ describe('InputCapture pointer lock', () => { browser.grantLock(container); }); - act(() => { - document.dispatchEvent( - Object.assign(new MouseEvent('pointermove'), { movementX: 100, movementY: 50 }) - ); - document.dispatchEvent( - Object.assign(new MouseEvent('mousemove'), { movementX: 100, movementY: 50 }) - ); - }); + const now = vi.spyOn(performance, 'now').mockReturnValue(1000); + try { + act(() => { + document.dispatchEvent( + Object.assign(new MouseEvent('pointermove'), { movementX: 100, movementY: 50 }) + ); + document.dispatchEvent( + Object.assign(new MouseEvent('mousemove'), { movementX: 100, movementY: 50 }) + ); + }); + } finally { + now.mockRestore(); + } onInputEvent.mockClear(); act(() => { diff --git a/apps/web/src/components/control/InputCapture.test.tsx b/apps/web/src/components/control/InputCapture.test.tsx index 0be006a8..3e735d18 100644 --- a/apps/web/src/components/control/InputCapture.test.tsx +++ b/apps/web/src/components/control/InputCapture.test.tsx @@ -198,6 +198,13 @@ describe('InputCapture pointer lock', () => { ); }); + // The paired mousemove is suppressed by a 16ms window on performance.now(), + // so the real wall-clock gap between the two dispatches below decides the + // outcome. On a loaded machine that gap can exceed 16ms, the mousemove lands + // as a second movement, and the pointer reads 0.7 instead of 0.6 -- a flaky + // test rather than a regression. Freeze the clock across the pair so the + // window is not a race. Only these two dispatches are covered; everything + // else in the test still runs on real time. it('does not count a paired mousemove twice after pointermove', () => { const browser = installBrowserApis(); const onInputEvent = vi.fn(); @@ -208,14 +215,19 @@ describe('InputCapture pointer lock', () => { browser.grantLock(container); }); - act(() => { - document.dispatchEvent( - Object.assign(new MouseEvent('pointermove'), { movementX: 100, movementY: 50 }) - ); - document.dispatchEvent( - Object.assign(new MouseEvent('mousemove'), { movementX: 100, movementY: 50 }) - ); - }); + const now = vi.spyOn(performance, 'now').mockReturnValue(1000); + try { + act(() => { + document.dispatchEvent( + Object.assign(new MouseEvent('pointermove'), { movementX: 100, movementY: 50 }) + ); + document.dispatchEvent( + Object.assign(new MouseEvent('mousemove'), { movementX: 100, movementY: 50 }) + ); + }); + } finally { + now.mockRestore(); + } onInputEvent.mockClear(); act(() => {