From 29dd20db1af38ca2a4ce5e97c5adfa4c170d3985 Mon Sep 17 00:00:00 2001 From: Hassan Abdel-Rahman Date: Fri, 24 Jul 2026 12:19:40 -0400 Subject: [PATCH] Drain the gesture-less scroll before the gesture in persist-scroll-position test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `records the offset only after a genuine user scroll gesture` flaked: a programmatic `scrollTop` assignment fires its `scroll` event on the next rendering frame, not synchronously, and `settled()` does not await that frame. The gesture-less scroll set to 90 could therefore land during the subsequent `await triggerEvent(scroller, 'wheel')` settle — after the wheel flipped the modifier from restoring into recording — and be recorded as a spurious user offset, so `recorded` became `[90, 120, ...]` and the `every(v === 120)` check failed. Confirmed by logging: `onChange 90` fired between the pre-wheel and post-wheel markers. Await one animation frame after the gesture-less scroll so its event drains while there is still no gesture (and is correctly ignored). Also surface the recorded array in the assertion message so any future miss is diagnosable. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../modifiers/persist-scroll-position-test.gts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/host/tests/integration/modifiers/persist-scroll-position-test.gts b/packages/host/tests/integration/modifiers/persist-scroll-position-test.gts index d01f4551f8..316395adef 100644 --- a/packages/host/tests/integration/modifiers/persist-scroll-position-test.gts +++ b/packages/host/tests/integration/modifiers/persist-scroll-position-test.gts @@ -128,6 +128,15 @@ module('Integration | modifier | persist-scroll-position', function (hooks) { await triggerEvent(scroller, 'scroll'); assert.deepEqual(recorded, [], 'a gesture-less scroll is ignored'); + // A programmatic `scrollTop` assignment fires its `scroll` event on the + // next rendering, not synchronously, and `settled()` does not await that + // frame. Drain it here — while there is still no gesture, so the modifier + // ignores it — otherwise it can land during the wheel below, after the + // gesture has flipped the modifier into recording mode, and be recorded as + // a spurious user offset (90). + // eslint-disable-next-line @cardstack/boxel/no-raf-for-state -- must await the browser's post-assignment scroll frame + await new Promise((resolve) => requestAnimationFrame(resolve)); + // After a real gesture, the user's scrolling is recorded. (A single move // can emit more than one `scroll` — the native one plus the synthetic test // event — so assert the value rather than the call count.) @@ -138,7 +147,9 @@ module('Integration | modifier | persist-scroll-position', function (hooks) { recorded.length >= 1 && recorded.every((v) => v === 120); assert.ok( recordedOnlyTheUserOffset, - 'the user scroll offset (120) is recorded', + `the user scroll offset (120) is recorded (recorded: ${JSON.stringify( + recorded, + )})`, ); });