From ab50691f8a3126c657b512cdf2c4d1322aa4aa09 Mon Sep 17 00:00:00 2001 From: jim800121chen Date: Wed, 1 Jul 2026 00:30:18 +0800 Subject: [PATCH] =?UTF-8?q?fix(conversion):=20=E8=BD=89=E6=AA=94=E5=AE=8C?= =?UTF-8?q?=E6=88=90=E9=A0=81=E3=80=8C=E5=B7=B2=E5=8A=A0=E5=85=A5=E3=80=8D?= =?UTF-8?q?=E6=8C=89=E9=88=95=E4=B8=8D=E5=86=8D=E9=87=8D=E8=A4=87=20promot?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SuccessView「加到模型庫」按鈕文字依 importedModelId 切換(已加入/加到模型庫), 但 onClick 永遠是 handleImportClick(開 PromoteDialog)→ 已 promote 後再點又 開 dialog 又 promote。 - 按鈕 onClick 改 handleImportButtonClick 分流:importedModelId 有值→ router.push(/models/{id}) 導航(不再 promote);空→開 PromoteDialog(現狀) - aria-label 同步切換(import.aria.done)對齊實際行為 - 後端 PromoteToModels 本有冪等(同 jobID 不重複建),前端 onClick 為防禦層 Co-Authored-By: Claude Opus 4.8 (1M context) --- .../components/SuccessView.test.tsx | 77 +++++++++++++++++++ .../app/conversion/components/SuccessView.tsx | 22 +++++- .../src/lib/i18n/dictionaries/en.ts | 2 + .../src/lib/i18n/dictionaries/zh-Hant.ts | 1 + 4 files changed, 100 insertions(+), 2 deletions(-) diff --git a/visionA-frontend/src/app/conversion/components/SuccessView.test.tsx b/visionA-frontend/src/app/conversion/components/SuccessView.test.tsx index 37dbb08..d467d14 100644 --- a/visionA-frontend/src/app/conversion/components/SuccessView.test.tsx +++ b/visionA-frontend/src/app/conversion/components/SuccessView.test.tsx @@ -447,6 +447,83 @@ describe(" — 加到模型庫", () => { const text = btn.textContent ?? ""; expect(/已加入|Added/.test(text)).toBe(true); }); + + it("尚未 promote 時點按鈕 → 開 PromoteDialog、不導航", () => { + setSucceeded(); + renderView(); + + // 初始無 dialog、無導航 + expect(screen.queryByTestId("promote-dialog")).toBeNull(); + + fireEvent.click(screen.getByTestId("success-import-button")); + + // 開 dialog + expect(screen.getByTestId("promote-dialog")).toBeTruthy(); + // 不導航 + expect(mockRouterPush).not.toHaveBeenCalled(); + }); + + it("已 promote 後再點按鈕 → 導向 /models/{id}、不再開 dialog、不再 promote", async () => { + setSucceeded(); + const promoteSpy = vi + .spyOn(useConversionStore.getState(), "promoteToModels") + .mockResolvedValue({ model_id: "m-dup-guard" }); + + renderView(); + + // 第一次:開 dialog → 確認 → promote 成功 + fireEvent.click(screen.getByTestId("success-import-button")); + fireEvent.click(screen.getByTestId("promote-dialog-confirm")); + + await waitFor(() => { + expect(screen.queryByTestId("promote-dialog")).toBeNull(); + }); + await waitFor(() => { + expect(promoteSpy).toHaveBeenCalledTimes(1); + }); + + // 按鈕已切「已加入」狀態 + const btn = screen.getByTestId("success-import-button"); + expect(/已加入|Added/.test(btn.textContent ?? "")).toBe(true); + + // 第二次點同一顆按鈕 → 應只導航、不再開 dialog、不再 promote + fireEvent.click(btn); + + // 不再開 PromoteDialog + expect(screen.queryByTestId("promote-dialog")).toBeNull(); + // promote 仍然只被呼叫過一次(沒有重複 promote) + expect(promoteSpy).toHaveBeenCalledTimes(1); + // 導向模型詳細頁 + expect(mockRouterPush).toHaveBeenCalledWith("/models/m-dup-guard"); + }); + + it("已 promote 後按鈕 aria-label 切成 done 文案(語意為前往查看)", async () => { + setSucceeded(); + vi.spyOn(useConversionStore.getState(), "promoteToModels").mockResolvedValue( + { model_id: "m-aria" }, + ); + + renderView(); + + // promote 前:aria-label 為 cta(含「對話框 / dialog」語意) + const btnBefore = screen.getByTestId("success-import-button"); + const ariaBefore = btnBefore.getAttribute("aria-label") ?? ""; + expect(/對話框|dialog/i.test(ariaBefore)).toBe(true); + + // 執行 promote + fireEvent.click(btnBefore); + fireEvent.click(screen.getByTestId("promote-dialog-confirm")); + await waitFor(() => { + expect(screen.queryByTestId("promote-dialog")).toBeNull(); + }); + + // promote 後:aria-label 改為 done(語意為前往查看、不再是開對話框) + const ariaAfter = + screen.getByTestId("success-import-button").getAttribute("aria-label") ?? + ""; + expect(/前往查看|open the model|detail/i.test(ariaAfter)).toBe(true); + expect(/對話框|dialog/i.test(ariaAfter)).toBe(false); + }); }); /* -------------------------------------------------------------------------- */ diff --git a/visionA-frontend/src/app/conversion/components/SuccessView.tsx b/visionA-frontend/src/app/conversion/components/SuccessView.tsx index 533f10c..909c7f1 100644 --- a/visionA-frontend/src/app/conversion/components/SuccessView.tsx +++ b/visionA-frontend/src/app/conversion/components/SuccessView.tsx @@ -292,6 +292,20 @@ function SuccessViewInner({ setPromoteOpen(true); }; + /** + * 「加到模型庫」按鈕的點擊行為依 importedModelId 分流: + * - 尚未 promote(importedModelId 為空)→ 開 PromoteDialog(handleImportClick) + * - 已 promote(importedModelId 有值,文字顯示「已加入(前往查看)」)→ 直接導向 + * /models/{id},**不再開 dialog、不再 promote**(修:重複點會重複 promote 的 bug) + */ + const handleImportButtonClick = () => { + if (importedModelId) { + router.push(`/models/${importedModelId}`); + return; + } + handleImportClick(); + }; + const handlePromoteSuccess = (modelId: string) => { setImportedModelId(modelId); // 對齊 wireframe §7.1:toast「已加入模型庫」+「前往模型庫」連結 @@ -419,9 +433,13 @@ function SuccessViewInner({ type="button" variant="default" size="lg" - onClick={handleImportClick} + onClick={handleImportButtonClick} disabled={isExpired} - aria-label={t("conversion.success.import.aria.cta")} + aria-label={ + importedModelId + ? t("conversion.success.import.aria.done") + : t("conversion.success.import.aria.cta") + } data-testid="success-import-button" className="h-auto justify-start gap-3 py-4" > diff --git a/visionA-frontend/src/lib/i18n/dictionaries/en.ts b/visionA-frontend/src/lib/i18n/dictionaries/en.ts index f099782..de199bf 100644 --- a/visionA-frontend/src/lib/i18n/dictionaries/en.ts +++ b/visionA-frontend/src/lib/i18n/dictionaries/en.ts @@ -579,6 +579,8 @@ export const en: Dictionary = { "Couldn't add to model library; please try again.", "conversion.success.import.aria.cta": "Add to model library (opens a confirmation dialog)", + "conversion.success.import.aria.done": + "Added to model library; open the model detail page", "conversion.success.download.title": "Download .nef", "conversion.success.description": "{source} has been converted into a .nef compatible with {chip}.", diff --git a/visionA-frontend/src/lib/i18n/dictionaries/zh-Hant.ts b/visionA-frontend/src/lib/i18n/dictionaries/zh-Hant.ts index c1e3bbd..df55c6f 100644 --- a/visionA-frontend/src/lib/i18n/dictionaries/zh-Hant.ts +++ b/visionA-frontend/src/lib/i18n/dictionaries/zh-Hant.ts @@ -548,6 +548,7 @@ export const zhHant: Dictionary = { "加到模型庫失敗,請稍後再試", "conversion.success.import.aria.cta": "加到模型庫(會開啟確認對話框)", + "conversion.success.import.aria.done": "已加入模型庫,前往查看模型詳細頁", "conversion.success.download.title": "下載 .nef", "conversion.success.description": "{source} 已成功轉成 {chip} 可用的 .nef 檔",