visionA/visionA-frontend/src/lib/camera.test.ts
jim800121chen ba8fd454ea feat(workspace): Camera tab 接真 MJPEG stream + start/stop 狀態機(前端塊1)
推論工作區前端塊1:Camera tab 從 placeholder 換成真正接 MJPEG stream
的即時畫面。照 edge-ai-platform POC camera-feed.tsx 移植(唯讀參考、
重寫為 visionA 慣例)。

- camera-feed.tsx:MJPEG <img> + ResizeObserver + overlay slot(塊2 用)+
  角落標籤
- camera.ts buildStreamUrl:cache-bust 走 _t= query(encode)、token 絕不
  放 URL(same-origin cookie 認證、對齊 use-websocket §10)
- workspace-client Camera tab:接 CameraFeed + 串流狀態機、掉線清串流採
  render 期衍生(非 effect setState)
- use-websocket.ts:修 same-origin WS URL bug——getWsBaseUrl 回 "" 時
  new WebSocket() 需絕對 URL,補 window.location wss/ws(塊2 WS 用)
- types/camera.ts + types/inference.ts(後者鏡射 local-tool driver.InferenceResult)
- start/stop 契約改打 /api/camera/start|stop
- i18n 9 key 雙語齊全

Reviewer 0C/0M 通過。13 test(camera-feed 8 + camera 5)PASS、tsc/eslint clean。
(全套 11 failed 全在 conversion-store.test.ts 既有 flaky、本次未觸碰、經 grep 證實無關)

follow-up(交塊2/3):掉線→回線 stream 卡舊 URL,建議掉線一併 clearStream。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 04:42:41 +08:00

53 lines
1.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* lib/camera 單元測試(塊 1
*
* 驗證 buildStreamUrl
* - jsdomwindow 存在)+ 無 NEXT_PUBLIC_API_BASE → base = ""(同 origin回相對路徑
* - cacheBust 以 `_t=` query 附加,且做 URL encode
* - 已含 query 的 URL 用 `&` 串接
* - 相對路徑補開頭斜線
*/
import { afterEach, describe, expect, it } from "vitest";
import { buildStreamUrl, CAMERA_STREAM_PATH } from "./camera";
const ORIGINAL = process.env.NEXT_PUBLIC_API_BASE;
afterEach(() => {
if (ORIGINAL === undefined) delete process.env.NEXT_PUBLIC_API_BASE;
else process.env.NEXT_PUBLIC_API_BASE = ORIGINAL;
});
describe("buildStreamUrl", () => {
it("同 origin無 env回相對路徑不含 cacheBust 時無 query", () => {
delete process.env.NEXT_PUBLIC_API_BASE;
expect(buildStreamUrl(CAMERA_STREAM_PATH)).toBe("/api/camera/stream");
});
it("附加 cacheBust 為 _t query 並 encode", () => {
delete process.env.NEXT_PUBLIC_API_BASE;
expect(buildStreamUrl("/api/camera/stream", "dev 1")).toBe(
"/api/camera/stream?_t=dev%201",
);
});
it("已含 query 時用 & 串接 cacheBust", () => {
delete process.env.NEXT_PUBLIC_API_BASE;
expect(buildStreamUrl("/api/camera/stream?x=1", "abc")).toBe(
"/api/camera/stream?x=1&_t=abc",
);
});
it("跨 origin有 env前綴 base URL", () => {
process.env.NEXT_PUBLIC_API_BASE = "https://api.example.com";
expect(buildStreamUrl("/api/camera/stream")).toBe(
"https://api.example.com/api/camera/stream",
);
});
it("相對路徑無開頭斜線時自動補上", () => {
delete process.env.NEXT_PUBLIC_API_BASE;
expect(buildStreamUrl("api/camera/stream")).toBe("/api/camera/stream");
});
});