/** * @file RunHistory.test.tsx * @description Pins the props-only boundary of `ActiveRunsSwitcher` / `RunsModal` * after their move out of `pages/Run.tsx`: the switcher counts live runs and * opens the list, the list merges live in-memory handles with persistent history * (live entries winning on a shared id) newest first, marks the live and current * rows, filters by status / mode / free text, and fires attach / resume / view * with the right run — attach by run id, resume and view with the matching * history item. * * @author Nguyễn Ngọc Trí Vĩ */ import { MemoryRouter } from "react-router-dom"; import { fireEvent, render, screen } from "@testing-library/react"; import { beforeEach, describe, expect, it, vi } from "vitest"; import i18n from "i18next"; import { ActiveRunsSwitcher, RunsModal, type UnifiedRunRow } from "../RunHistory"; import type { DashboardRunHistoryItem, RunListResponse } from "../../../lib/api"; import type { Session } from "../../../lib/types"; const LIVE_ID = "run-live"; const PAST_ID = "run-past"; const HEADLESS_ID = "run-headless"; const activeRuns = { activeCount: 1, maxConcurrent: 2, items: [ { id: LIVE_ID, sessionId: "sess-live", cwd: "/tmp/live", model: "claude-opus-5", status: "running", startedAt: "2000-01-01T00:50:00Z", promptPreview: "the live prompt", endedAt: null, }, ], } as unknown as RunListResponse; function historyItem(over: Partial): DashboardRunHistoryItem { return { id: PAST_ID, session_id: "sess-past", cwd: "/tmp/past", model: "sonnet", status: "completed", prompt_preview: "the past prompt", started_at: new Date(2000).toISOString(), ended_at: new Date(2500).toISOString(), exit_code: 0, permission_mode: "acceptEdits", effort: "", resume_session_id: null, isLive: false, ...over, } as DashboardRunHistoryItem; } const PAST = historyItem({}); const HEADLESS = historyItem({ id: HEADLESS_ID, session_id: "sess-headless", cwd: "/tmp/headless", prompt_preview: "the headless prompt", started_at: new Date(1000).toISOString(), }); function externalSession(over: Partial = {}): Session { return { id: "sess-external", name: "the external prompt", status: "active", cwd: "/tmp/external", model: "claude-opus-5", started_at: new Date(3000).toISOString(), ended_at: null, updated_at: new Date(3000).toISOString(), source: "local", ...over, } as unknown as Session; } function renderSwitcher(overrides: Partial> = {}) { const spies = { onAttach: vi.fn(), onResumeFromHistory: vi.fn(), onViewFromHistory: vi.fn(), onRefresh: vi.fn(), }; const utils = render( ); return { ...utils, spies }; } function row(id: string, over: Partial = {}): UnifiedRunRow { return { id, sessionId: `sess-${id}`, cwd: `/tmp/${id}`, model: "sonnet", status: "abandoned", promptPreview: `prompt of ${id}`, startedAt: 1000, endedAt: 2000, isLive: false, ...over, }; } function renderModal( rows: UnifiedRunRow[], overrides: Partial> = {} ) { const spies = { onAttach: vi.fn(), onResume: vi.fn(), onView: vi.fn(), onClose: vi.fn(), onRefresh: vi.fn(), }; const utils = render( ); return { ...utils, spies }; } /** The nth "All" chip — index 0 is the status group, 1 is the mode group. */ function allChip(nth: number): HTMLElement { const hits = screen.getAllByText(i18n.t("run:runs.allLabel", "All")); const hit = hits[nth]; if (!hit) throw new Error(`no "All" chip at index ${nth}`); return hit; } /** A filter chip, told apart from the same word appearing in a row's status * pill or mode badge by being a `