180 lines
6.3 KiB
TypeScript
180 lines
6.3 KiB
TypeScript
// @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: "<div />" },
|
|
LoadingState: { template: "<div />" },
|
|
MessageMarkdown: { template: "<div />" },
|
|
ApiErrorDiagnostic: { template: "<div />" },
|
|
StatusBadge: { template: "<div />" }
|
|
};
|
|
}
|
|
|
|
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: "<div class='loading-state'>{{ label }}</div>" },
|
|
}
|
|
}
|
|
});
|
|
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();
|
|
});
|
|
});
|