1442 lines · 42.1 KB · TypeScript
RawDownload
// 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,  OrchestrationSessionStatus,} 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 {  INLINE_TERMINAL_CONTEXT_PLACEHOLDER,  type TerminalContextDraft,} from "../lib/terminalContext";import { isMacPlatform } from "../lib/utils";import { getRouter } from "../router";import { useStore } from "../store";import { estimateTimelineMessageHeight } from "./timelineHeight"; const THREAD_ID = "thread-browser-test" as ThreadId;const UUID_ROUTE_RE = /^\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;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>;  router: ReturnType<typeof getRouter>;} 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 createTerminalContext(input: {  id: string;  terminalLabel: string;  lineStart: number;  lineEnd: number;  text: string;}): TerminalContextDraft {  return {    id: input.id,    threadId: THREAD_ID,    terminalId: `terminal-${input.id}`,    terminalLabel: input.terminalLabel,    lineStart: input.lineStart,    lineEnd: input.lineEnd,    text: input.text,    createdAt: NOW_ISO,  };} function createSnapshotForTargetUser(options: {  targetMessageId: MessageId;  targetText: string;  targetAttachmentCount?: number;  sessionStatus?: OrchestrationSessionStatus;}): 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: options.sessionStatus ?? "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 addThreadToSnapshot(  snapshot: OrchestrationReadModel,  threadId: ThreadId,): OrchestrationReadModel {  return {    ...snapshot,    snapshotSequence: snapshot.snapshotSequence + 1,    threads: [      ...snapshot.threads,      {        id: threadId,        projectId: PROJECT_ID,        title: "New 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,          status: "ready",          providerName: "codex",          runtimeMode: "full-access",          activeTurnId: null,          lastError: null,          updatedAt: NOW_ISO,        },      },    ],  };} function createDraftOnlySnapshot(): OrchestrationReadModel {  const snapshot = createSnapshotForTargetUser({    targetMessageId: "msg-user-draft-target" as MessageId,    targetText: "draft thread",  });  return {    ...snapshot,    threads: [],  };} function createSnapshotWithLongProposedPlan(): OrchestrationReadModel {  const snapshot = createSnapshotForTargetUser({    targetMessageId: "msg-user-plan-target" as MessageId,    targetText: "plan thread",  });  const planMarkdown = [    "# Ship plan mode follow-up",    "",    "- Step 1: capture the thread-open trace",    "- Step 2: identify the main-thread bottleneck",    "- Step 3: keep collapsed cards cheap",    "- Step 4: render the full markdown only on demand",    "- Step 5: preserve export and save actions",    "- Step 6: add regression coverage",    "- Step 7: verify route transitions stay responsive",    "- Step 8: confirm no server-side work changed",    "- Step 9: confirm short plans still render normally",    "- Step 10: confirm long plans stay collapsed by default",    "- Step 11: confirm preview text is still useful",    "- Step 12: confirm plan follow-up flow still works",    "- Step 13: confirm timeline virtualization still behaves",    "- Step 14: confirm theme styling still looks correct",    "- Step 15: confirm save dialog behavior is unchanged",    "- Step 16: confirm download behavior is unchanged",    "- Step 17: confirm code fences do not parse until expand",    "- Step 18: confirm preview truncation ends cleanly",    "- Step 19: confirm markdown links still open in editor after expand",    "- Step 20: confirm deep hidden detail only appears after expand",    "",    "```ts",    "export const hiddenPlanImplementationDetail = 'deep hidden detail only after expand';",    "```",  ].join("\n");   return {    ...snapshot,    threads: snapshot.threads.map((thread) =>      thread.id === THREAD_ID        ? Object.assign({}, thread, {            proposedPlans: [              {                id: "plan-browser-test",                turnId: null,                planMarkdown,                implementedAt: null,                implementationThreadId: null,                createdAt: isoAt(1_000),                updatedAt: isoAt(1_001),              },            ],            updatedAt: isoAt(1_001),          })        : thread,    ),  };} function resolveWsRpc(body: WsRequestEnvelope["body"]): unknown {  const tag = body._tag;  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,      hasOriginRemote: 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,    };  }  if (tag === WS_METHODS.terminalOpen) {    return {      threadId: typeof body.threadId === "string" ? body.threadId : THREAD_ID,      terminalId: typeof body.terminalId === "string" ? body.terminalId : "default",      cwd: typeof body.cwd === "string" ? body.cwd : "/repo/project",      status: "running",      pid: 123,      history: "",      exitCode: null,      exitSignal: null,      updatedAt: NOW_ISO,    };  }  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(request.body),        }),      );    });  }),  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> {

Lines 1–500 of 1442.