推論工作區前端塊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>
53 lines
1.7 KiB
TypeScript
53 lines
1.7 KiB
TypeScript
/**
|
||
* lib/camera 單元測試(塊 1)
|
||
*
|
||
* 驗證 buildStreamUrl:
|
||
* - jsdom(window 存在)+ 無 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");
|
||
});
|
||
});
|