// @vitest-environment jsdom import { mount } from "@vue/test-utils"; import { describe, expect, it } from "vitest"; import WorkbenchMessageCard from "./WorkbenchMessageCard.vue"; function componentStubs() { return { TraceTimeline: { template: "
" }, LoadingState: { template: "
" }, MessageMarkdown: { template: "
" }, ApiErrorDiagnostic: { template: "
" }, StatusBadge: { template: "
" } }; } describe("WorkbenchMessageCard startup visibility", () => { it("shows one startup label before the first Kafka event", () => { const wrapper = mount(WorkbenchMessageCard, { props: { detailEnabled: false, message: { id: "msg_starting_agent", messageId: "msg_starting_agent", role: "agent", title: "Code Agent", text: "", status: "running", traceId: "trc_starting", sessionId: "ses_starting", createdAt: "2026-07-20T10:00:00.000Z", } }, global: { stubs: { ...componentStubs(), LoadingState: { props: ["label"], template: "
{{ label }}
" }, } } }); expect(wrapper.text()).toContain("启动中..."); expect(wrapper.text()).not.toContain("思考中..."); expect(wrapper.findAll(".loading-state")).toHaveLength(1); wrapper.unmount(); }); }); describe("WorkbenchMessageCard metadata layout", () => { it("renders recent activity at the footer left and elapsed time at the footer right", () => { const now = Date.now(); const wrapper = mount(WorkbenchMessageCard, { props: { detailEnabled: false, message: { id: "msg_timing_layout", messageId: "msg_timing_layout", role: "agent", title: "Code Agent", text: "", status: "running", traceId: "trc_timing_layout", sessionId: "ses_timing_layout", createdAt: new Date(now - 10_000).toISOString(), timing: { startedAt: new Date(now - 10_000).toISOString(), lastEventAt: new Date(now - 4_000).toISOString() } } }, global: { stubs: componentStubs() } }); const footer = wrapper.get(".message-meta-footer"); expect(wrapper.find("article > header").exists()).toBe(false); expect(footer.get(".message-activity-meta").text()).toMatch(/^最近更新:\d+ 秒前$/u); expect(footer.get(".message-duration-meta").text()).toMatch(/^耗时 \d+ 秒$/u); expect(footer.get(".message-footer-timing").element.firstElementChild).toBe(footer.get(".message-activity-meta").element); expect(footer.get(".message-footer-timing").element.lastElementChild).toBe(footer.get(".message-duration-meta").element); wrapper.unmount(); }); }); describe("WorkbenchMessageCard semantic retry visibility", () => { it("shows Git mirror clone progress as runtime preparation", () => { const wrapper = mount(WorkbenchMessageCard, { props: { detailEnabled: false, message: { id: "msg_fetch_agent", messageId: "msg_fetch_agent", role: "agent", title: "Code Agent", text: "", status: "running", traceId: "trc_fetch_banner", sessionId: "ses_fetch_banner", createdAt: "2026-07-20T10:00:00.000Z", runnerTrace: { traceId: "trc_fetch_banner", eventSource: "hwlab-kafka-sse", status: "running", eventCount: 1, events: [{ sourceEventId: "evt_fetch_banner", sourceSeq: 5, type: "backend", status: "running", failureDomain: "infrastructure", failureCode: "git-mirror-fetch-in-progress", summary: "正在从 Git mirror 获取 Runner 源码", retryPhase: "initialAttempt", retryAttempt: 1, retryMaxAttempts: 3, createdAt: new Date().toISOString() }] } } }, global: { stubs: componentStubs() } }); expect(wrapper.text()).toContain("运行环境准备:正在从 Git mirror 获取 Runner 源码"); expect(wrapper.text()).toContain("第 1/3 次源码获取进行中"); expect(wrapper.text()).not.toContain("基础设施故障"); expect(wrapper.get(".semantic-failure-banner").attributes("data-emphasis")).toBe("quiet"); expect(wrapper.find(".semantic-failure-banner code").exists()).toBe(false); wrapper.unmount(); }); it("shows an infrastructure retry banner without opening Trace details", () => { const wrapper = mount(WorkbenchMessageCard, { props: { detailEnabled: false, message: { id: "msg_retry_agent", messageId: "msg_retry_agent", role: "agent", title: "Code Agent", text: "", status: "running", traceId: "trc_retry_banner", sessionId: "ses_retry_banner", createdAt: "2026-07-20T10:00:00.000Z", runnerTrace: { traceId: "trc_retry_banner", eventSource: "hwlab-kafka-sse", status: "running", eventCount: 1, events: [{ sourceEventId: "evt_retry_banner", sourceSeq: 5, type: "backend", status: "retrying", failureDomain: "infrastructure", failureCode: "runner-image-registry-unreachable", summary: "Runner 镜像仓库暂时不可达", retryPhase: "retryScheduled", retryAttempt: 2, retryMaxAttempts: 5, nextRetryAt: new Date(Date.now() + 12_000).toISOString(), createdAt: new Date().toISOString() }] } } }, global: { stubs: componentStubs() } }); const banner = wrapper.get(".semantic-failure-banner"); expect(banner.attributes("data-emphasis")).toBe("alert"); expect(banner.text()).toContain("基础设施故障:Runner 镜像仓库暂时不可达"); expect(banner.text()).toContain("第 2/5 次重试已安排"); expect(banner.text()).toMatch(/预计 \d+ 秒后开始/u); expect(banner.text()).toContain("runner-image-registry-unreachable"); wrapper.unmount(); }); });