diff --git a/visionA-frontend/src/components/dashboard/activity-timeline.test.tsx b/visionA-frontend/src/components/dashboard/activity-timeline.test.tsx new file mode 100644 index 0000000..53a527c --- /dev/null +++ b/visionA-frontend/src/components/dashboard/activity-timeline.test.tsx @@ -0,0 +1,75 @@ +/** + * ActivityTimeline 測試 + * + * 回歸重點: + * F7/F8 接 WS 事件後,後端可能推入 activityIcons/activityColors map 尚未涵蓋的 + * 未知 type → lookup 回 undefined → render undefined 會 crash。修法加 + * fallback(Circle / text-muted-foreground)。這裡塞一筆未知 type 的 activity, + * 確認 render 不 throw。 + * + * 另驗:已知 type 正常渲染、空狀態。 + */ + +import { render, screen } from "@testing-library/react"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; + +import { LocaleProvider } from "@/lib/i18n/context"; +import { + useActivityStore, + type ActivityEntry, + type ActivityType, +} from "@/stores/activity-store"; + +import { ActivityTimeline } from "./activity-timeline"; + +function renderTimeline() { + return render( + + + , + ); +} + +beforeEach(() => { + useActivityStore.setState({ activities: [] }); +}); + +afterEach(() => { + useActivityStore.setState({ activities: [] }); +}); + +describe("ActivityTimeline", () => { + it("空狀態:顯示空文案", () => { + expect(() => renderTimeline()).not.toThrow(); + expect(screen.getByText(/還沒有任何活動/)).toBeInTheDocument(); + }); + + it("已知 type 正常渲染", () => { + const entry: ActivityEntry = { + id: "a1", + type: "device_paired", + message: "配對成功", + timestamp: Date.now(), + }; + useActivityStore.setState({ activities: [entry] }); + + expect(() => renderTimeline()).not.toThrow(); + expect(screen.getByText("配對成功")).toBeInTheDocument(); + }); + + it("未知 type(後端推未涵蓋事件)→ fallback icon/色,不 crash", () => { + // 模擬 F7/F8 接 WS 後推入 map 尚未涵蓋的 type。 + const entry: ActivityEntry = { + id: "a-unknown", + type: "some_future_event" as ActivityType, + message: "未知事件", + timestamp: Date.now(), + }; + useActivityStore.setState({ activities: [entry] }); + + expect(() => renderTimeline()).not.toThrow(); + expect(screen.getByText("未知事件")).toBeInTheDocument(); + // 列表有渲染(fallback icon 沒讓整列消失) + expect(screen.getByTestId("activity-list")).toBeInTheDocument(); + }); +}); diff --git a/visionA-frontend/src/components/dashboard/activity-timeline.tsx b/visionA-frontend/src/components/dashboard/activity-timeline.tsx index 9ef7e95..35e9bc8 100644 --- a/visionA-frontend/src/components/dashboard/activity-timeline.tsx +++ b/visionA-frontend/src/components/dashboard/activity-timeline.tsx @@ -21,6 +21,7 @@ import { useEffect, useState } from "react"; import { AlertTriangle, CheckCircle, + Circle, Link2, RefreshCw, Trash2, @@ -104,8 +105,11 @@ export function ActivityTimeline() { ) : (