All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m59s
On desktop, tauri-plugin-dialog 2.6.0 replaces window.confirm with an async function: confirm(...) returned a Promise, always truthy, so every guard `if (!confirm(...)) return;` let the action run before the user answered. Deleting a secondary profile removed its database even on "Cancel". - confirmAction() (src/services/confirmService.ts) calls the plugin's ask() with a warning icon and buttons labelled in the app language (common.confirm / common.cancel); a failed call resolves false and is logged (fail closed). @tauri-apps/plugin-dialog 2.6.0 added, the crate's version. - The six sites await it: profile, category, adjustment, budget template and Balance category deletions, and both confirmations of the license key removal (performRemoveLicense takes an async confirm). - native-dialog-guard.test.ts fails on any native confirm/alert in src/ and on any confirmation not awaited (TypeScript does not flag `!promise`). - Tests: confirmService, the deletion of a profile in ProfileFormModal, and the license removal tests with an async confirm (test: reproduces #421: six of them were red on the old code). Refs #421 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
79 lines
2.3 KiB
TypeScript
79 lines
2.3 KiB
TypeScript
/**
|
|
* confirmAction (#421): the plugin's `ask`, mocked, answers; the shared i18next instance,
|
|
* initialised by `src/i18n/config.ts` as in the app, gives the button labels.
|
|
*/
|
|
|
|
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
|
|
const ask = vi.fn();
|
|
vi.mock("@tauri-apps/plugin-dialog", () => ({
|
|
ask: (...args: unknown[]) => ask(...args),
|
|
}));
|
|
const logWarn = vi.fn();
|
|
vi.mock("./logService", () => ({
|
|
logWarn: (...args: unknown[]) => logWarn(...args),
|
|
}));
|
|
|
|
import i18n from "../i18n/config";
|
|
import { confirmAction } from "./confirmService";
|
|
|
|
beforeEach(async () => {
|
|
ask.mockReset();
|
|
logWarn.mockReset();
|
|
await i18n.changeLanguage("fr");
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await i18n.changeLanguage("fr");
|
|
});
|
|
|
|
describe("confirmAction", () => {
|
|
it("resolves true when the user confirms", async () => {
|
|
ask.mockResolvedValue(true);
|
|
|
|
await expect(confirmAction("Supprimer ce profil ?")).resolves.toBe(true);
|
|
expect(logWarn).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("resolves false when the user cancels or closes the dialog", async () => {
|
|
ask.mockResolvedValue(false);
|
|
|
|
await expect(confirmAction("Supprimer ce profil ?")).resolves.toBe(false);
|
|
});
|
|
|
|
it("resolves false and logs a warning when the dialog fails", async () => {
|
|
ask.mockRejectedValue(new Error("dialog unavailable"));
|
|
|
|
await expect(confirmAction("Supprimer ce profil ?")).resolves.toBe(false);
|
|
expect(logWarn).toHaveBeenCalledTimes(1);
|
|
expect(logWarn).toHaveBeenCalledWith(
|
|
"Confirmation dialog failed; the action was not carried out",
|
|
"Error: dialog unavailable",
|
|
);
|
|
});
|
|
|
|
it("asks with the message, a warning icon and French buttons in French", async () => {
|
|
ask.mockResolvedValue(true);
|
|
|
|
await confirmAction("Supprimer ce profil ?");
|
|
|
|
expect(ask).toHaveBeenCalledWith("Supprimer ce profil ?", {
|
|
kind: "warning",
|
|
okLabel: "Confirmer",
|
|
cancelLabel: "Annuler",
|
|
});
|
|
});
|
|
|
|
it("labels the buttons in English once the app is in English", async () => {
|
|
ask.mockResolvedValue(true);
|
|
await i18n.changeLanguage("en");
|
|
|
|
await confirmAction("Delete this profile?");
|
|
|
|
expect(ask).toHaveBeenCalledWith("Delete this profile?", {
|
|
kind: "warning",
|
|
okLabel: "Confirm",
|
|
cancelLabel: "Cancel",
|
|
});
|
|
});
|
|
});
|