jim800121chen 5c1c37d151 feat(frontend): classification 結果呈現與推論期控制項
detection 畫框、classification 改在影像右上角疊標籤(不畫框),
並在推論面板提供即時切換解析方式與上傳 label 檔。

- 新增 InferenceOverlay 依 taskType 分派;ClassificationOverlay 用
  DOM 而非 canvas,讓 CJK 排版與 aria-live 交給瀏覽器處理
- 標籤防閃爍:挑戰者需連續 3 幀居冠才切換,或信心度領先 15% 直接切;
  低於門檻立即清空(顯示過期標籤比空白更糟)
- 身分比對優先用 classIndex,避免換 label 檔時被誤判為換類別
- 推論設定卡片:即時切 detection/classification、上傳 .txt label、
  清除 label,皆不需重燒
- classification-result 原本無條件渲染,導致 detection 模式下側欄
  永遠顯示「分類結果」與空圖表,改為依 taskType 切換
- 清掉 camera-overlay 每幀執行的 debug console.log
- 補 ResizeObserver stub,原本任何渲染 InferencePanel 的測試都會
  在 jsdom 直接拋錯

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 19:27:28 +08:00

47 lines
1.5 KiB
TypeScript

import '@testing-library/jest-dom';
import { afterEach, vi } from 'vitest';
import { cleanup } from '@testing-library/react';
afterEach(() => {
cleanup();
});
// jsdom does not implement ResizeObserver, which Radix primitives (Slider) and
// CameraFeed rely on. A no-op stub is enough: tests assert on rendered output,
// not on resize-driven behaviour.
if (!('ResizeObserver' in globalThis)) {
class ResizeObserverStub implements ResizeObserver {
observe() {}
unobserve() {}
disconnect() {}
}
globalThis.ResizeObserver = ResizeObserverStub;
}
// jsdom implements neither the Pointer Capture API nor scrollIntoView, both of
// which Radix Select calls while opening its dropdown. Without these stubs the
// trigger throws `target.hasPointerCapture is not a function` and the listbox
// never mounts, so any test that opens a <Select> is untestable.
if (!Element.prototype.hasPointerCapture) {
Element.prototype.hasPointerCapture = () => false;
Element.prototype.setPointerCapture = () => {};
Element.prototype.releasePointerCapture = () => {};
}
if (!Element.prototype.scrollIntoView) {
Element.prototype.scrollIntoView = () => {};
}
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: vi.fn().mockImplementation((query: string) => ({
matches: false,
media: query,
onchange: null,
addListener: vi.fn(),
removeListener: vi.fn(),
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
dispatchEvent: vi.fn(),
})),
});