From 51dfd0f2d83a230af4062c3301935aa3e630fd90 Mon Sep 17 00:00:00 2001 From: jim800121chen Date: Thu, 16 Jul 2026 11:40:35 +0800 Subject: [PATCH] =?UTF-8?q?fix(frontend):=20normalizeDevice=20serialNumber?= =?UTF-8?q?=20=E5=8A=A0=20trim=EF=BC=88WP-C=20Minor=20#1=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 純空白字串(" ")原判 truthy → 會以空白 serial 組出必失敗的請求路由。 改為先 trim 再判定:serial !== "" ? serial : null。順帶消除字面 "0" 被 falsy 誤判為 null 的隱性行為。補純空白 → null 邊界測試。 Reviewer 通過(0C/0M/0Mi)。tsc/eslint 0、18 test 綠。 Co-Authored-By: Claude Opus 4.8 (1M context) --- visionA-frontend/src/stores/device-store.test.ts | 5 ++++- visionA-frontend/src/stores/device-store.ts | 8 +++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/visionA-frontend/src/stores/device-store.test.ts b/visionA-frontend/src/stores/device-store.test.ts index b996fda..b9a316d 100644 --- a/visionA-frontend/src/stores/device-store.test.ts +++ b/visionA-frontend/src/stores/device-store.test.ts @@ -175,7 +175,7 @@ describe("useDeviceStore", () => { }); }); - it("serial_number 正規化:snake_case / camelCase 都吃、缺欄位與空字串 → null(WP-C)", async () => { + it("serial_number 正規化:snake_case / camelCase 都吃、缺欄位與空字串/純空白 → null(WP-C)", async () => { vi.spyOn(globalThis, "fetch").mockResolvedValueOnce( jsonResponse({ success: true, @@ -188,6 +188,8 @@ describe("useDeviceStore", () => { { id: "dev-3", name: "C", type: "kl520", status: "connected" }, // 空字串(防禦性;無法路由)→ null { id: "dev-4", name: "D", type: "kl520", status: "connected", serial_number: "" }, + // 純空白(防禦性;trim 後為空、無法路由)→ null + { id: "dev-5", name: "E", type: "kl520", status: "connected", serial_number: " " }, ], }), ); @@ -198,6 +200,7 @@ describe("useDeviceStore", () => { expect(devices[1]?.serialNumber).toBe("KN99887766"); expect(devices[2]?.serialNumber).toBeNull(); expect(devices[3]?.serialNumber).toBeNull(); + expect(devices[4]?.serialNumber).toBeNull(); }); it("fetchDevices 遇到 501 NOT_IMPLEMENTED 時視為空 list,不記錯誤", async () => { diff --git a/visionA-frontend/src/stores/device-store.ts b/visionA-frontend/src/stores/device-store.ts index ed03cf1..8114168 100644 --- a/visionA-frontend/src/stores/device-store.ts +++ b/visionA-frontend/src/stores/device-store.ts @@ -112,14 +112,16 @@ function normalizeDevice(raw: unknown): Device { const rawRemoteStatus = pick("remote_status", "remoteStatus") as | RemoteStatus | undefined; - // serial_number 為 omitempty:缺欄位 → null;防禦性把空字串也視為 null - // (空字串無法路由,語意上等同「未回報序號」)。 + // serial_number 為 omitempty:缺欄位 → null;防禦性把空字串/純空白也視為 null + // (空字串與空白皆無法路由,語意上等同「未回報序號」)。先 trim 再判定, + // 避免純空白(" ")誤判為 truthy 而以空白 serial 組出必失敗的請求路由。 const rawSerial = pick("serial_number", "serialNumber"); + const serial = rawSerial != null ? String(rawSerial).trim() : ""; return { id: String(pick("id") ?? ""), name: String(pick("name") ?? pick("device_name") ?? ""), alias: pick("alias") ?? undefined, - serialNumber: rawSerial ? String(rawSerial) : null, + serialNumber: serial !== "" ? serial : null, type: String(pick("type", "device_type") ?? ""), status: (pick("status") as DeviceHardwareStatus) ?? "disconnected", remoteStatus: