fix(conversion): 轉檔完成頁「已加入」按鈕不再重複 promote
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) <noreply@anthropic.com>
This commit is contained in:
parent
7d8ad4857e
commit
ab50691f8a
@ -447,6 +447,83 @@ describe("<SuccessView /> — 加到模型庫", () => {
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
@ -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"
|
||||
>
|
||||
|
||||
@ -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}.",
|
||||
|
||||
@ -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 檔",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user