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 {
1515import { RouterProvider, createMemoryHistory } from "@tanstack/react-router";
1616import { HttpResponse, http, ws } from "msw";
1717import { 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";
1919import { render } from "vitest-browser-react";
2020
2121import { getRouter } from "../router";
......@@ -301,13 +301,21 @@ async function waitForElement<T extends Element>(
301301 query: () => T | null,
302302 errorMessage: string,
303303): Promise<T> {
304 const timeoutAt = performance.now() + 8_000;
305 while (performance.now() < timeoutAt) {
306 const element = query();
307 if (element) return element;
308 await nextFrame();
304 let element: T | null = null;
305 await vi.waitFor(
306 () => {
307 element = query();
308 expect(element, errorMessage).toBeTruthy();
309 },
310 {
311 timeout: 8_000,
312 interval: 16,
313 },
314 );
315 if (!element) {
316 throw new Error(errorMessage);
309317 }
310 throw new Error(errorMessage);
318 return element;
311319}
312320
313321async function waitForImagesToLoad(scope: ParentNode): Promise<void> {
......@@ -368,29 +376,29 @@ async function renderAndMeasureUserRow(options: {
368376 );
369377
370378 let row: HTMLElement | null = null;
371 const timeoutAt = performance.now() + 8_000;
372 while (performance.now() < timeoutAt) {
373 scrollContainer.scrollTop = 0;
374 scrollContainer.dispatchEvent(new Event("scroll"));
375 await waitForLayout();
376 row = host.querySelector<HTMLElement>(
377 `[data-message-id="${options.targetMessageId}"][data-message-role="user"]`,
378 );
379 if (row) {
380 break;
381 }
382 }
383 if (!row) {
384 throw new Error("Unable to locate targeted user message row.");
385 }
379 await vi.waitFor(
380 async () => {
381 scrollContainer.scrollTop = 0;
382 scrollContainer.dispatchEvent(new Event("scroll"));
383 await waitForLayout();
384 row = host.querySelector<HTMLElement>(
385 `[data-message-id="${options.targetMessageId}"][data-message-role="user"]`,
386 );
387 expect(row, "Unable to locate targeted user message row.").toBeTruthy();
388 },
389 {
390 timeout: 8_000,
391 interval: 16,
392 },
393 );
386394
387 await waitForImagesToLoad(row);
395 await waitForImagesToLoad(row!);
388396 scrollContainer.scrollTop = 0;
389397 scrollContainer.dispatchEvent(new Event("scroll"));
390398 await nextFrame();
391399
392400 const timelineRoot =
393 row.closest<HTMLElement>('[data-timeline-root="true"]') ??
401 row!.closest<HTMLElement>('[data-timeline-root="true"]') ??
394402 host.querySelector<HTMLElement>('[data-timeline-root="true"]');
395403 if (!(timelineRoot instanceof HTMLElement)) {
396404 throw new Error("Unable to locate timeline root container.");
......@@ -400,24 +408,22 @@ async function renderAndMeasureUserRow(options: {
400408 let measuredRowHeightPx = 0;
401409 let renderedInVirtualizedRegion = false;
402410 const rowSelector = `[data-message-id="${options.targetMessageId}"][data-message-role="user"]`;
403 const measureTimeoutAt = performance.now() + 4_000;
404 while (performance.now() < measureTimeoutAt) {
405 scrollContainer.scrollTop = 0;
406 scrollContainer.dispatchEvent(new Event("scroll"));
407 await nextFrame();
408 const measuredRow = host.querySelector<HTMLElement>(rowSelector);
409 if (!measuredRow) {
410 continue;
411 }
412 measuredRowHeightPx = measuredRow.getBoundingClientRect().height;
413 renderedInVirtualizedRegion = measuredRow.closest("[data-index]") instanceof HTMLElement;
414 if (measuredRowHeightPx > 0) {
415 break;
416 }
417 }
418 if (measuredRowHeightPx <= 0) {
419 throw new Error("Unable to measure targeted user row height.");
420 }
411 await vi.waitFor(
412 async () => {
413 scrollContainer.scrollTop = 0;
414 scrollContainer.dispatchEvent(new Event("scroll"));
415 await nextFrame();
416 const measuredRow = host.querySelector<HTMLElement>(rowSelector);
417 expect(measuredRow, "Unable to measure targeted user row height.").toBeTruthy();
418 measuredRowHeightPx = measuredRow!.getBoundingClientRect().height;
419 renderedInVirtualizedRegion = measuredRow!.closest("[data-index]") instanceof HTMLElement;
420 expect(measuredRowHeightPx, "Unable to measure targeted user row height.").toBeGreaterThan(0);
421 },
422 {
423 timeout: 4_000,
424 interval: 16,
425 },
426 );
421427
422428 return { measuredRowHeightPx, timelineWidthMeasuredPx, renderedInVirtualizedRegion };
423429 } finally {