// Production CSS is part of the behavior under test because row height depends on it.import "../index.css"; import { ORCHESTRATION_WS_METHODS, type MessageId, type OrchestrationReadModel, type ProjectId, type ServerConfig, type ThreadId, type WsWelcomePayload, WS_CHANNELS, WS_METHODS,} from "@t3tools/contracts";import { RouterProvider, createMemoryHistory } from "@tanstack/react-router";import { HttpResponse, http, ws } from "msw";import { setupWorker } from "msw/browser";import { page } from "vitest/browser";import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";import { render } from "vitest-browser-react"; import { useComposerDraftStore } from "../composerDraftStore";import { getRouter } from "../router";import { useStore } from "../store";import { estimateTimelineMessageHeight } from "./timelineHeight"; const THREAD_ID = "thread-browser-test" as ThreadId;const PROJECT_ID = "project-1" as ProjectId;const NOW_ISO = "2026-03-04T12:00:00.000Z";const BASE_TIME_MS = Date.parse(NOW_ISO);const ATTACHMENT_SVG = "<svg xmlns='http://www.w3.org/2000/svg' width='120' height='300'></svg>"; interface WsRequestEnvelope { id: string; body: { _tag: string; [key: string]: unknown; };} interface TestFixture { snapshot: OrchestrationReadModel; serverConfig: ServerConfig; welcome: WsWelcomePayload;} let fixture: TestFixture;const wsRequests: WsRequestEnvelope["body"][] = [];const wsLink = ws.link(/ws(s)?:\/\/.*/); interface ViewportSpec { name: string; width: number; height: number; textTolerancePx: number; attachmentTolerancePx: number;} const DEFAULT_VIEWPORT: ViewportSpec = { name: "desktop", width: 960, height: 1_100, textTolerancePx: 44, attachmentTolerancePx: 56,};const TEXT_VIEWPORT_MATRIX = [ DEFAULT_VIEWPORT, { name: "tablet", width: 720, height: 1_024, textTolerancePx: 44, attachmentTolerancePx: 56 }, { name: "mobile", width: 430, height: 932, textTolerancePx: 56, attachmentTolerancePx: 56 }, { name: "narrow", width: 320, height: 700, textTolerancePx: 84, attachmentTolerancePx: 56 },] as const satisfies readonly ViewportSpec[];const ATTACHMENT_VIEWPORT_MATRIX = [ DEFAULT_VIEWPORT, { name: "mobile", width: 430, height: 932, textTolerancePx: 56, attachmentTolerancePx: 56 }, { name: "narrow", width: 320, height: 700, textTolerancePx: 84, attachmentTolerancePx: 56 },] as const satisfies readonly ViewportSpec[]; interface UserRowMeasurement { measuredRowHeightPx: number; timelineWidthMeasuredPx: number; renderedInVirtualizedRegion: boolean;} interface MountedChatView { cleanup: () => Promise<void>; measureUserRow: (targetMessageId: MessageId) => Promise<UserRowMeasurement>; setViewport: (viewport: ViewportSpec) => Promise<void>;} function isoAt(offsetSeconds: number): string { return new Date(BASE_TIME_MS + offsetSeconds * 1_000).toISOString();} function createBaseServerConfig(): ServerConfig { return { cwd: "/repo/project", keybindingsConfigPath: "/repo/project/.t3code-keybindings.json", keybindings: [], issues: [], providers: [ { provider: "codex", status: "ready", available: true, authStatus: "authenticated", checkedAt: NOW_ISO, }, ], availableEditors: [], };} function createUserMessage(options: { id: MessageId; text: string; offsetSeconds: number; attachments?: Array<{ type: "image"; id: string; name: string; mimeType: string; sizeBytes: number; }>;}) { return { id: options.id, role: "user" as const, text: options.text, ...(options.attachments ? { attachments: options.attachments } : {}), turnId: null, streaming: false, createdAt: isoAt(options.offsetSeconds), updatedAt: isoAt(options.offsetSeconds + 1), };} function createAssistantMessage(options: { id: MessageId; text: string; offsetSeconds: number }) { return { id: options.id, role: "assistant" as const, text: options.text, turnId: null, streaming: false, createdAt: isoAt(options.offsetSeconds), updatedAt: isoAt(options.offsetSeconds + 1), };} function createSnapshotForTargetUser(options: { targetMessageId: MessageId; targetText: string; targetAttachmentCount?: number;}): OrchestrationReadModel { const messages: Array<OrchestrationReadModel["threads"][number]["messages"][number]> = []; for (let index = 0; index < 22; index += 1) { const isTarget = index === 3; const userId = `msg-user-${index}` as MessageId; const assistantId = `msg-assistant-${index}` as MessageId; const attachments = isTarget && (options.targetAttachmentCount ?? 0) > 0 ? Array.from({ length: options.targetAttachmentCount ?? 0 }, (_, attachmentIndex) => ({ type: "image" as const, id: `attachment-${attachmentIndex + 1}`, name: `attachment-${attachmentIndex + 1}.png`, mimeType: "image/png", sizeBytes: 128, })) : undefined; messages.push( createUserMessage({ id: isTarget ? options.targetMessageId : userId, text: isTarget ? options.targetText : `filler user message ${index}`, offsetSeconds: messages.length * 3, ...(attachments ? { attachments } : {}), }), ); messages.push( createAssistantMessage({ id: assistantId, text: `assistant filler ${index}`, offsetSeconds: messages.length * 3, }), ); } return { snapshotSequence: 1, projects: [ { id: PROJECT_ID, title: "Project", workspaceRoot: "/repo/project", defaultModel: "gpt-5", scripts: [], createdAt: NOW_ISO, updatedAt: NOW_ISO, deletedAt: null, }, ], threads: [ { id: THREAD_ID, projectId: PROJECT_ID, title: "Browser test thread", model: "gpt-5", interactionMode: "default", runtimeMode: "full-access", branch: "main", worktreePath: null, latestTurn: null, createdAt: NOW_ISO, updatedAt: NOW_ISO, deletedAt: null, messages, activities: [], proposedPlans: [], checkpoints: [], session: { threadId: THREAD_ID, status: "ready", providerName: "codex", runtimeMode: "full-access", activeTurnId: null, lastError: null, updatedAt: NOW_ISO, }, }, ], updatedAt: NOW_ISO, };} function buildFixture(snapshot: OrchestrationReadModel): TestFixture { return { snapshot, serverConfig: createBaseServerConfig(), welcome: { cwd: "/repo/project", projectName: "Project", bootstrapProjectId: PROJECT_ID, bootstrapThreadId: THREAD_ID, }, };} function createDraftOnlySnapshot(): OrchestrationReadModel { const snapshot = createSnapshotForTargetUser({ targetMessageId: "msg-user-draft-target" as MessageId, targetText: "draft thread", }); return { ...snapshot, threads: [], };} function resolveWsRpc(tag: string): unknown { if (tag === ORCHESTRATION_WS_METHODS.getSnapshot) { return fixture.snapshot; } if (tag === WS_METHODS.serverGetConfig) { return fixture.serverConfig; } if (tag === WS_METHODS.gitListBranches) { return { isRepo: true, branches: [ { name: "main", current: true, isDefault: true, worktreePath: null, }, ], }; } if (tag === WS_METHODS.gitStatus) { return { branch: "main", hasWorkingTreeChanges: false, workingTree: { files: [], insertions: 0, deletions: 0, }, hasUpstream: true, aheadCount: 0, behindCount: 0, pr: null, }; } if (tag === WS_METHODS.projectsSearchEntries) { return { entries: [], truncated: false, }; } return {};} const worker = setupWorker( wsLink.addEventListener("connection", ({ client }) => { client.send( JSON.stringify({ type: "push", sequence: 1, channel: WS_CHANNELS.serverWelcome, data: fixture.welcome, }), ); client.addEventListener("message", (event) => { const rawData = event.data; if (typeof rawData !== "string") return; let request: WsRequestEnvelope; try { request = JSON.parse(rawData) as WsRequestEnvelope; } catch { return; } const method = request.body?._tag; if (typeof method !== "string") return; wsRequests.push(request.body); client.send( JSON.stringify({ id: request.id, result: resolveWsRpc(method), }), ); }); }), http.get("*/attachments/:attachmentId", () => HttpResponse.text(ATTACHMENT_SVG, { headers: { "Content-Type": "image/svg+xml", }, }), ), http.get("*/api/project-favicon", () => new HttpResponse(null, { status: 204 })),); async function nextFrame(): Promise<void> { await new Promise<void>((resolve) => { window.requestAnimationFrame(() => resolve()); });} async function waitForLayout(): Promise<void> { await nextFrame(); await nextFrame(); await nextFrame();} async function setViewport(viewport: ViewportSpec): Promise<void> { await page.viewport(viewport.width, viewport.height); await waitForLayout();} async function waitForProductionStyles(): Promise<void> { await vi.waitFor( () => { expect(getComputedStyle(document.documentElement).getPropertyValue("--background").trim()).not.toBe( "", ); expect(getComputedStyle(document.body).marginTop).toBe("0px"); }, { timeout: 4_000, interval: 16, }, );} async function waitForElement<T extends Element>( query: () => T | null, errorMessage: string,): Promise<T> { let element: T | null = null; await vi.waitFor( () => { element = query(); expect(element, errorMessage).toBeTruthy(); }, { timeout: 8_000, interval: 16, }, ); if (!element) { throw new Error(errorMessage); } return element;} async function waitForComposerEditor(): Promise<HTMLElement> { return waitForElement( () => document.querySelector<HTMLElement>('[contenteditable="true"]'), "Unable to find composer editor.", );} async function waitForInteractionModeButton(expectedLabel: "Chat" | "Plan"): Promise<HTMLButtonElement> { return waitForElement( () => Array.from(document.querySelectorAll("button")).find( (button) => button.textContent?.trim() === expectedLabel, ) as HTMLButtonElement | null, `Unable to find ${expectedLabel} interaction mode button.`, );} async function waitForImagesToLoad(scope: ParentNode): Promise<void> { const images = Array.from(scope.querySelectorAll("img")); if (images.length === 0) { return; } await Promise.all( images.map( (image) => new Promise<void>((resolve) => { if (image.complete) { resolve(); return; } image.addEventListener("load", () => resolve(), { once: true }); image.addEventListener("error", () => resolve(), { once: true }); }), ), ); await waitForLayout();} async function measureUserRow(options: { host: HTMLElement; targetMessageId: MessageId;}): Promise<UserRowMeasurement> { const { host, targetMessageId } = options; const rowSelector = `[data-message-id="${targetMessageId}"][data-message-role="user"]`; const scrollContainer = await waitForElement( () => host.querySelector<HTMLDivElement>("div.overflow-y-auto.overscroll-y-contain"), "Unable to find ChatView message scroll container.", ); let row: HTMLElement | null = null; await vi.waitFor( async () => { scrollContainer.scrollTop = 0; scrollContainer.dispatchEvent(new Event("scroll")); await waitForLayout(); row = host.querySelector<HTMLElement>(rowSelector); expect(row, "Unable to locate targeted user message row.").toBeTruthy(); }, { timeout: 8_000, interval: 16, }, ); await waitForImagesToLoad(row!); scrollContainer.scrollTop = 0; scrollContainer.dispatchEvent(new Event("scroll")); await nextFrame(); const timelineRoot = row!.closest<HTMLElement>('[data-timeline-root="true"]') ?? host.querySelector<HTMLElement>('[data-timeline-root="true"]'); if (!(timelineRoot instanceof HTMLElement)) { throw new Error("Unable to locate timeline root container."); } let timelineWidthMeasuredPx = 0; let measuredRowHeightPx = 0; let renderedInVirtualizedRegion = false; await vi.waitFor( async () => { scrollContainer.scrollTop = 0; scrollContainer.dispatchEvent(new Event("scroll")); await nextFrame(); const measuredRow = host.querySelector<HTMLElement>(rowSelector); expect(measuredRow, "Unable to measure targeted user row height.").toBeTruthy(); timelineWidthMeasuredPx = timelineRoot.getBoundingClientRect().width; measuredRowHeightPx = measuredRow!.getBoundingClientRect().height; renderedInVirtualizedRegion = measuredRow!.closest("[data-index]") instanceof HTMLElement; expect(timelineWidthMeasuredPx, "Unable to measure timeline width.").toBeGreaterThan(0); expect(measuredRowHeightPx, "Unable to measure targeted user row height.").toBeGreaterThan(0); }, { timeout: 4_000, interval: 16, }, ); return { measuredRowHeightPx, timelineWidthMeasuredPx, renderedInVirtualizedRegion };} async function mountChatView(options: { viewport: ViewportSpec; snapshot: OrchestrationReadModel;Lines 1–500 of 871. Load more
// Production CSS is part of the behavior under test because row height depends on it. import "../index.css"; import { ORCHESTRATION_WS_METHODS, type MessageId, type OrchestrationReadModel, type ProjectId, type ServerConfig, type ThreadId, type WsWelcomePayload, WS_CHANNELS, WS_METHODS, } from "@t3tools/contracts"; import { RouterProvider, createMemoryHistory } from "@tanstack/react-router"; import { HttpResponse, http, ws } from "msw"; import { setupWorker } from "msw/browser"; import { page } from "vitest/browser"; import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { render } from "vitest-browser-react"; import { useComposerDraftStore } from "../composerDraftStore"; import { getRouter } from "../router"; import { useStore } from "../store"; import { estimateTimelineMessageHeight } from "./timelineHeight"; const THREAD_ID = "thread-browser-test" as ThreadId; const PROJECT_ID = "project-1" as ProjectId; const NOW_ISO = "2026-03-04T12:00:00.000Z"; const BASE_TIME_MS = Date.parse(NOW_ISO); const ATTACHMENT_SVG = "<svg xmlns='http://www.w3.org/2000/svg' width='120' height='300'></svg>"; interface WsRequestEnvelope { id: string; body: { _tag: string; [key: string]: unknown; }; } interface TestFixture { snapshot: OrchestrationReadModel; serverConfig: ServerConfig; welcome: WsWelcomePayload; } let fixture: TestFixture; const wsRequests: WsRequestEnvelope["body"][] = []; const wsLink = ws.link(/ws(s)?:\/\/.*/); interface ViewportSpec { name: string; width: number; height: number; textTolerancePx: number; attachmentTolerancePx: number; } const DEFAULT_VIEWPORT: ViewportSpec = { name: "desktop", width: 960, height: 1_100, textTolerancePx: 44, attachmentTolerancePx: 56, }; const TEXT_VIEWPORT_MATRIX = [ DEFAULT_VIEWPORT, { name: "tablet", width: 720, height: 1_024, textTolerancePx: 44, attachmentTolerancePx: 56 }, { name: "mobile", width: 430, height: 932, textTolerancePx: 56, attachmentTolerancePx: 56 }, { name: "narrow", width: 320, height: 700, textTolerancePx: 84, attachmentTolerancePx: 56 }, ] as const satisfies readonly ViewportSpec[]; const ATTACHMENT_VIEWPORT_MATRIX = [ DEFAULT_VIEWPORT, { name: "mobile", width: 430, height: 932, textTolerancePx: 56, attachmentTolerancePx: 56 }, { name: "narrow", width: 320, height: 700, textTolerancePx: 84, attachmentTolerancePx: 56 }, ] as const satisfies readonly ViewportSpec[]; interface UserRowMeasurement { measuredRowHeightPx: number; timelineWidthMeasuredPx: number; renderedInVirtualizedRegion: boolean; } interface MountedChatView { cleanup: () => Promise<void>; measureUserRow: (targetMessageId: MessageId) => Promise<UserRowMeasurement>; setViewport: (viewport: ViewportSpec) => Promise<void>; } function isoAt(offsetSeconds: number): string { return new Date(BASE_TIME_MS + offsetSeconds * 1_000).toISOString(); } function createBaseServerConfig(): ServerConfig { return { cwd: "/repo/project", keybindingsConfigPath: "/repo/project/.t3code-keybindings.json", keybindings: [], issues: [], providers: [ { provider: "codex", status: "ready", available: true, authStatus: "authenticated", checkedAt: NOW_ISO, }, ], availableEditors: [], }; } function createUserMessage(options: { id: MessageId; text: string; offsetSeconds: number; attachments?: Array<{ type: "image"; id: string; name: string; mimeType: string; sizeBytes: number; }>; }) { return { id: options.id, role: "user" as const, text: options.text, ...(options.attachments ? { attachments: options.attachments } : {}), turnId: null, streaming: false, createdAt: isoAt(options.offsetSeconds), updatedAt: isoAt(options.offsetSeconds + 1), }; } function createAssistantMessage(options: { id: MessageId; text: string; offsetSeconds: number }) { return { id: options.id, role: "assistant" as const, text: options.text, turnId: null, streaming: false, createdAt: isoAt(options.offsetSeconds), updatedAt: isoAt(options.offsetSeconds + 1), }; } function createSnapshotForTargetUser(options: { targetMessageId: MessageId; targetText: string; targetAttachmentCount?: number; }): OrchestrationReadModel { const messages: Array<OrchestrationReadModel["threads"][number]["messages"][number]> = []; for (let index = 0; index < 22; index += 1) { const isTarget = index === 3; const userId = `msg-user-${index}` as MessageId; const assistantId = `msg-assistant-${index}` as MessageId; const attachments = isTarget && (options.targetAttachmentCount ?? 0) > 0 ? Array.from({ length: options.targetAttachmentCount ?? 0 }, (_, attachmentIndex) => ({ type: "image" as const, id: `attachment-${attachmentIndex + 1}`, name: `attachment-${attachmentIndex + 1}.png`, mimeType: "image/png", sizeBytes: 128, })) : undefined; messages.push( createUserMessage({ id: isTarget ? options.targetMessageId : userId, text: isTarget ? options.targetText : `filler user message ${index}`, offsetSeconds: messages.length * 3, ...(attachments ? { attachments } : {}), }), ); messages.push( createAssistantMessage({ id: assistantId, text: `assistant filler ${index}`, offsetSeconds: messages.length * 3, }), ); } return { snapshotSequence: 1, projects: [ { id: PROJECT_ID, title: "Project", workspaceRoot: "/repo/project", defaultModel: "gpt-5", scripts: [], createdAt: NOW_ISO, updatedAt: NOW_ISO, deletedAt: null, }, ], threads: [ { id: THREAD_ID, projectId: PROJECT_ID, title: "Browser test thread", model: "gpt-5", interactionMode: "default", runtimeMode: "full-access", branch: "main", worktreePath: null, latestTurn: null, createdAt: NOW_ISO, updatedAt: NOW_ISO, deletedAt: null, messages, activities: [], proposedPlans: [], checkpoints: [], session: { threadId: THREAD_ID, status: "ready", providerName: "codex", runtimeMode: "full-access", activeTurnId: null, lastError: null, updatedAt: NOW_ISO, }, }, ], updatedAt: NOW_ISO, }; } function buildFixture(snapshot: OrchestrationReadModel): TestFixture { return { snapshot, serverConfig: createBaseServerConfig(), welcome: { cwd: "/repo/project", projectName: "Project", bootstrapProjectId: PROJECT_ID, bootstrapThreadId: THREAD_ID, }, }; } function createDraftOnlySnapshot(): OrchestrationReadModel { const snapshot = createSnapshotForTargetUser({ targetMessageId: "msg-user-draft-target" as MessageId, targetText: "draft thread", }); return { ...snapshot, threads: [], }; } function resolveWsRpc(tag: string): unknown { if (tag === ORCHESTRATION_WS_METHODS.getSnapshot) { return fixture.snapshot; } if (tag === WS_METHODS.serverGetConfig) { return fixture.serverConfig; } if (tag === WS_METHODS.gitListBranches) { return { isRepo: true, branches: [ { name: "main", current: true, isDefault: true, worktreePath: null, }, ], }; } if (tag === WS_METHODS.gitStatus) { return { branch: "main", hasWorkingTreeChanges: false, workingTree: { files: [], insertions: 0, deletions: 0, }, hasUpstream: true, aheadCount: 0, behindCount: 0, pr: null, }; } if (tag === WS_METHODS.projectsSearchEntries) { return { entries: [], truncated: false, }; } return {}; } const worker = setupWorker( wsLink.addEventListener("connection", ({ client }) => { client.send( JSON.stringify({ type: "push", sequence: 1, channel: WS_CHANNELS.serverWelcome, data: fixture.welcome, }), ); client.addEventListener("message", (event) => { const rawData = event.data; if (typeof rawData !== "string") return; let request: WsRequestEnvelope; try { request = JSON.parse(rawData) as WsRequestEnvelope; } catch { return; } const method = request.body?._tag; if (typeof method !== "string") return; wsRequests.push(request.body); client.send( JSON.stringify({ id: request.id, result: resolveWsRpc(method), }), ); }); }), http.get("*/attachments/:attachmentId", () => HttpResponse.text(ATTACHMENT_SVG, { headers: { "Content-Type": "image/svg+xml", }, }), ), http.get("*/api/project-favicon", () => new HttpResponse(null, { status: 204 })), ); async function nextFrame(): Promise<void> { await new Promise<void>((resolve) => { window.requestAnimationFrame(() => resolve()); }); } async function waitForLayout(): Promise<void> { await nextFrame(); await nextFrame(); await nextFrame(); } async function setViewport(viewport: ViewportSpec): Promise<void> { await page.viewport(viewport.width, viewport.height); await waitForLayout(); } async function waitForProductionStyles(): Promise<void> { await vi.waitFor( () => { expect(getComputedStyle(document.documentElement).getPropertyValue("--background").trim()).not.toBe( "", ); expect(getComputedStyle(document.body).marginTop).toBe("0px"); }, { timeout: 4_000, interval: 16, }, ); } async function waitForElement<T extends Element>( query: () => T | null, errorMessage: string, ): Promise<T> { let element: T | null = null; await vi.waitFor( () => { element = query(); expect(element, errorMessage).toBeTruthy(); }, { timeout: 8_000, interval: 16, }, ); if (!element) { throw new Error(errorMessage); } return element; } async function waitForComposerEditor(): Promise<HTMLElement> { return waitForElement( () => document.querySelector<HTMLElement>('[contenteditable="true"]'), "Unable to find composer editor.", ); } async function waitForInteractionModeButton(expectedLabel: "Chat" | "Plan"): Promise<HTMLButtonElement> { return waitForElement( () => Array.from(document.querySelectorAll("button")).find( (button) => button.textContent?.trim() === expectedLabel, ) as HTMLButtonElement | null, `Unable to find ${expectedLabel} interaction mode button.`, ); } async function waitForImagesToLoad(scope: ParentNode): Promise<void> { const images = Array.from(scope.querySelectorAll("img")); if (images.length === 0) { return; } await Promise.all( images.map( (image) => new Promise<void>((resolve) => { if (image.complete) { resolve(); return; } image.addEventListener("load", () => resolve(), { once: true }); image.addEventListener("error", () => resolve(), { once: true }); }), ), ); await waitForLayout(); } async function measureUserRow(options: { host: HTMLElement; targetMessageId: MessageId; }): Promise<UserRowMeasurement> { const { host, targetMessageId } = options; const rowSelector = `[data-message-id="${targetMessageId}"][data-message-role="user"]`; const scrollContainer = await waitForElement( () => host.querySelector<HTMLDivElement>("div.overflow-y-auto.overscroll-y-contain"), "Unable to find ChatView message scroll container.", ); let row: HTMLElement | null = null; await vi.waitFor( async () => { scrollContainer.scrollTop = 0; scrollContainer.dispatchEvent(new Event("scroll")); await waitForLayout(); row = host.querySelector<HTMLElement>(rowSelector); expect(row, "Unable to locate targeted user message row.").toBeTruthy(); }, { timeout: 8_000, interval: 16, }, ); await waitForImagesToLoad(row!); scrollContainer.scrollTop = 0; scrollContainer.dispatchEvent(new Event("scroll")); await nextFrame(); const timelineRoot = row!.closest<HTMLElement>('[data-timeline-root="true"]') ?? host.querySelector<HTMLElement>('[data-timeline-root="true"]'); if (!(timelineRoot instanceof HTMLElement)) { throw new Error("Unable to locate timeline root container."); } let timelineWidthMeasuredPx = 0; let measuredRowHeightPx = 0; let renderedInVirtualizedRegion = false; await vi.waitFor( async () => { scrollContainer.scrollTop = 0; scrollContainer.dispatchEvent(new Event("scroll")); await nextFrame(); const measuredRow = host.querySelector<HTMLElement>(rowSelector); expect(measuredRow, "Unable to measure targeted user row height.").toBeTruthy(); timelineWidthMeasuredPx = timelineRoot.getBoundingClientRect().width; measuredRowHeightPx = measuredRow!.getBoundingClientRect().height; renderedInVirtualizedRegion = measuredRow!.closest("[data-index]") instanceof HTMLElement; expect(timelineWidthMeasuredPx, "Unable to measure timeline width.").toBeGreaterThan(0); expect(measuredRowHeightPx, "Unable to measure targeted user row height.").toBeGreaterThan(0); }, { timeout: 4_000, interval: 16, }, ); return { measuredRowHeightPx, timelineWidthMeasuredPx, renderedInVirtualizedRegion }; } async function mountChatView(options: { viewport: ViewportSpec; snapshot: OrchestrationReadModel;