From 9f684850362f3a3dd4a80b2695f541330949061d Mon Sep 17 00:00:00 2001 From: Badcuban <108198679+badcuban@users.noreply.github.com> Date: Thu, 22 Oct 2026 02:29:56 -0400 Subject: [PATCH] test(web): the text-selection list tests scroll the way a reader does The two text-selection tests on the real chat list wrote scrollTop directly to bring a word into view. The list hears a scroll a frame later; a row it measured in between (common on a busy CI runner) let its follow-the-end logic snap back to the bottom, the word left view and its selection dropped, so the Copy button never appeared. The helper now sends a wheel event before each scroll, as a real wheel does, which tells the list the reader left its end. --- .../chat/MessagesTimeline.real-list.browser.tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/apps/web/src/components/chat/MessagesTimeline.real-list.browser.tsx b/apps/web/src/components/chat/MessagesTimeline.real-list.browser.tsx index 17b390b22..30c352dd3 100644 --- a/apps/web/src/components/chat/MessagesTimeline.real-list.browser.tsx +++ b/apps/web/src/components/chat/MessagesTimeline.real-list.browser.tsx @@ -1427,17 +1427,26 @@ function dragSelectWord(messageId: string, word: string) { throw new Error(`"${word}" is not in ${messageId}`); } +// Scrolls the list by `delta` the way a reader's wheel does: the wheel comes +// first and tells the list the reader left its end. A bare scrollTop write +// does not. The list hears that scroll a frame later (longer on a busy +// machine), and a row it measures in between snaps it back to the end. +function wheelScroll(list: HTMLElement, delta: number) { + list.dispatchEvent(new WheelEvent("wheel", { bubbles: true, deltaY: delta })); + list.scrollTop += delta; +} + // Scrolls `step` pixels at a time until the list draws message-51, then puts // its word in the middle of the list. A saved scroll position cannot stand in // for this: the list measures rows as they come into view, and every row it // measures moves the ones below it. async function scrollPelicanIntoView(list: HTMLElement, step: number) { for (let count = 0; count < 40 && !wordRect("message-51", "pelican"); count += 1) { - list.scrollTop += step; + wheelScroll(list, step); await new Promise((resolve) => setTimeout(resolve, 50)); } const listTop = list.getBoundingClientRect().top; - list.scrollTop += wordRect("message-51", "pelican")!.top - listTop - 180; + wheelScroll(list, wordRect("message-51", "pelican")!.top - listTop - 180); await new Promise((resolve) => setTimeout(resolve, 200)); }