Simpl-Resultat/src/services/confirmService.test.ts
le king fu 155722775a
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m59s
fix(dialogs): wait for the confirmation before deleting or removing the key
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>
2026-10-18 13:44:02 -04:00

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",
});
});
});