authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-03-04 22:31:44-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-03-05 10:20:34-08:00
log1100d5a6f7e00ca41aa795ad601f78c670f5e5ab
treef54bb915d598b65dac3a3fcb2160ce2adcfbdce9
parent9abf96770044612038eb37740a1dfb4c38e1d272

Use vi.waitFor in ChatView browser measurement helpers

- Replace manual timeout polling loops with `vi.waitFor` in test helpers - Improve reliability of locating and measuring virtualized user rows in attachment-height tests

1 files changed, 48 insertions(+), 42 deletions(-)

apps/web/src/components/ChatView.browser.tsx+48-42
...@@ -15,7 +15,7 @@ import {...@@ -15,7 +15,7 @@ import {
15import { RouterProvider, createMemoryHistory } from "@tanstack/react-router";15import { RouterProvider, createMemoryHistory } from "@tanstack/react-router";
16import { HttpResponse, http, ws } from "msw";16import { HttpResponse, http, ws } from "msw";
17import { setupWorker } from "msw/browser";17import { setupWorker } from "msw/browser";
18import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";18import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
19import { render } from "vitest-browser-react";19import { render } from "vitest-browser-react";
2020
21import { getRouter } from "../router";21import { getRouter } from "../router";
...@@ -301,13 +301,21 @@ async function waitForElement<T extends Element>(...@@ -301,13 +301,21 @@ async function waitForElement<T extends Element>(
301 query: () => T | null,301 query: () => T | null,
302 errorMessage: string,302 errorMessage: string,
303): Promise<T> {303): Promise<T> {
304 const timeoutAt = performance.now() + 8_000;304 let element: T | null = null;
305 while (performance.now() < timeoutAt) {305 await vi.waitFor(
306 const element = query();306 () => {
307 if (element) return element;307 element = query();
308 await nextFrame();308 expect(element, errorMessage).toBeTruthy();
309 },
310 {
311 timeout: 8_000,
312 interval: 16,
313 },
314 );
315 if (!element) {
316 throw new Error(errorMessage);
309 }317 }
310 throw new Error(errorMessage);318 return element;
311}319}
312320
313async function waitForImagesToLoad(scope: ParentNode): Promise<void> {321async function waitForImagesToLoad(scope: ParentNode): Promise<void> {
...@@ -368,29 +376,29 @@ async function renderAndMeasureUserRow(options: {...@@ -368,29 +376,29 @@ async function renderAndMeasureUserRow(options: {
368 );376 );
369377
370 let row: HTMLElement | null = null;378 let row: HTMLElement | null = null;
371 const timeoutAt = performance.now() + 8_000;379 await vi.waitFor(
372 while (performance.now() < timeoutAt) {380 async () => {
373 scrollContainer.scrollTop = 0;381 scrollContainer.scrollTop = 0;
374 scrollContainer.dispatchEvent(new Event("scroll"));382 scrollContainer.dispatchEvent(new Event("scroll"));
375 await waitForLayout();383 await waitForLayout();
376 row = host.querySelector<HTMLElement>(384 row = host.querySelector<HTMLElement>(
377 `[data-message-id="${options.targetMessageId}"][data-message-role="user"]`,385 `[data-message-id="${options.targetMessageId}"][data-message-role="user"]`,
378 );386 );
379 if (row) {387 expect(row, "Unable to locate targeted user message row.").toBeTruthy();
380 break;388 },
381 }389 {
382 }390 timeout: 8_000,
383 if (!row) {391 interval: 16,
384 throw new Error("Unable to locate targeted user message row.");392 },
385 }393 );
386394
387 await waitForImagesToLoad(row);395 await waitForImagesToLoad(row!);
388 scrollContainer.scrollTop = 0;396 scrollContainer.scrollTop = 0;
389 scrollContainer.dispatchEvent(new Event("scroll"));397 scrollContainer.dispatchEvent(new Event("scroll"));
390 await nextFrame();398 await nextFrame();
391399
392 const timelineRoot =400 const timelineRoot =
393 row.closest<HTMLElement>('[data-timeline-root="true"]') ??401 row!.closest<HTMLElement>('[data-timeline-root="true"]') ??
394 host.querySelector<HTMLElement>('[data-timeline-root="true"]');402 host.querySelector<HTMLElement>('[data-timeline-root="true"]');
395 if (!(timelineRoot instanceof HTMLElement)) {403 if (!(timelineRoot instanceof HTMLElement)) {
396 throw new Error("Unable to locate timeline root container.");404 throw new Error("Unable to locate timeline root container.");
...@@ -400,24 +408,22 @@ async function renderAndMeasureUserRow(options: {...@@ -400,24 +408,22 @@ async function renderAndMeasureUserRow(options: {
400 let measuredRowHeightPx = 0;408 let measuredRowHeightPx = 0;
401 let renderedInVirtualizedRegion = false;409 let renderedInVirtualizedRegion = false;
402 const rowSelector = `[data-message-id="${options.targetMessageId}"][data-message-role="user"]`;410 const rowSelector = `[data-message-id="${options.targetMessageId}"][data-message-role="user"]`;
403 const measureTimeoutAt = performance.now() + 4_000;411 await vi.waitFor(
404 while (performance.now() < measureTimeoutAt) {412 async () => {
405 scrollContainer.scrollTop = 0;413 scrollContainer.scrollTop = 0;
406 scrollContainer.dispatchEvent(new Event("scroll"));414 scrollContainer.dispatchEvent(new Event("scroll"));
407 await nextFrame();415 await nextFrame();
408 const measuredRow = host.querySelector<HTMLElement>(rowSelector);416 const measuredRow = host.querySelector<HTMLElement>(rowSelector);
409 if (!measuredRow) {417 expect(measuredRow, "Unable to measure targeted user row height.").toBeTruthy();
410 continue;418 measuredRowHeightPx = measuredRow!.getBoundingClientRect().height;
411 }419 renderedInVirtualizedRegion = measuredRow!.closest("[data-index]") instanceof HTMLElement;
412 measuredRowHeightPx = measuredRow.getBoundingClientRect().height;420 expect(measuredRowHeightPx, "Unable to measure targeted user row height.").toBeGreaterThan(0);
413 renderedInVirtualizedRegion = measuredRow.closest("[data-index]") instanceof HTMLElement;421 },
414 if (measuredRowHeightPx > 0) {422 {
415 break;423 timeout: 4_000,
416 }424 interval: 16,
417 }425 },
418 if (measuredRowHeightPx <= 0) {426 );
419 throw new Error("Unable to measure targeted user row height.");
420 }
421427
422 return { measuredRowHeightPx, timelineWidthMeasuredPx, renderedInVirtualizedRegion };428 return { measuredRowHeightPx, timelineWidthMeasuredPx, renderedInVirtualizedRegion };
423 } finally {429 } finally {