Deduplicate save and leave dialog logic in alertModal

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RaRBJFZKSTA7naHGuQHfnQ
This commit is contained in:
2026-07-05 08:22:15 +08:00
co-authored by Claude Fable 5
parent 8b06094978
commit 00ff034e0e
2 changed files with 487 additions and 160 deletions
+134 -160
View File
@@ -29,20 +29,23 @@ const customClass = {
"!inline-block !rounded-full !text-sm !font-medium !text-center !align-middle !transition-colors !duration-300 !px-5 !py-2 !w-[100px] !h-[40px] ", "!inline-block !rounded-full !text-sm !font-medium !text-center !align-middle !transition-colors !duration-300 !px-5 !py-2 !w-[100px] !h-[40px] ",
}; };
/** /**
* Shows a modal dialog to save a new filter with a user-provided name. * Shows a modal dialog prompting for a name and saves through the
* given backend API function.
* *
* @param {Function} addFilterId - Backend API function to create the filter. * @param {string} title - The dialog title.
* @param {Function|null} [next=null] - Vue Router next() guard callback. * @param {string} inputPlaceholder - The name input placeholder text.
* @returns {Promise<boolean>} True if the filter was saved, false otherwise. * @param {Function} save - Backend API function receiving the name.
* @param {Function|null} [onCancel=null] - Callback invoked when the
* dialog is cancelled or dismissed.
* @returns {Promise<boolean>} True if saved, false otherwise.
*/ */
export async function saveFilter(addFilterId, next = null) { async function promptSaveName(title, inputPlaceholder, save, onCancel = null) {
let fileName = ""; let fileName = "";
const pageAdminStore = usePageAdminStore();
const { value, isConfirmed } = await Swal.fire({ const { value, isConfirmed } = await Swal.fire({
title: "SAVE NEW FILTER", title: title,
input: "text", input: "text",
inputPlaceholder: "Enter Filter Name.", inputPlaceholder: inputPlaceholder,
inputValue: fileName, inputValue: fileName,
inputAttributes: { inputAttributes: {
maxlength: 200, maxlength: 200,
@@ -64,7 +67,7 @@ export async function saveFilter(addFilterId, next = null) {
// Determine whether to redirect based on the return value // Determine whether to redirect based on the return value
if (isConfirmed) { if (isConfirmed) {
// Save succeeded // Save succeeded
await addFilterId(fileName); await save(fileName);
// Show save complete notification // Show save complete notification
if (value) { if (value) {
// Example of value: yes // Example of value: yes
@@ -73,16 +76,34 @@ export async function saveFilter(addFilterId, next = null) {
// Clear the input field // Clear the input field
fileName = ""; fileName = "";
return true; return true;
} else {
// Clicked cancel or outside the dialog; save failed.
pageAdminStore.keepPreviousPage();
// Not every time we have nontrivial next value
if (next !== null) {
next(false);
}
return false;
} }
// Clicked cancel or outside the dialog; save failed.
if (onCancel !== null) onCancel();
return false;
}
/**
* Shows a modal dialog to save a new filter with a user-provided name.
*
* @param {Function} addFilterId - Backend API function to create the filter.
* @param {Function|null} [next=null] - Vue Router next() guard callback.
* @returns {Promise<boolean>} True if the filter was saved, false otherwise.
*/
export async function saveFilter(addFilterId, next = null) {
const pageAdminStore = usePageAdminStore();
return promptSaveName(
"SAVE NEW FILTER",
"Enter Filter Name.",
addFilterId,
() => {
pageAdminStore.keepPreviousPage();
// Not every time we have nontrivial next value
if (next !== null) {
next(false);
}
},
);
} }
/** /**
* Shows a timed success notification after a file has been saved. * Shows a timed success notification after a file has been saved.
@@ -117,9 +138,67 @@ export async function leaveFilter(next, addFilterId, toPath, logOut) {
const allMapDataStore = useAllMapDataStore(); const allMapDataStore = useAllMapDataStore();
const pageAdminStore = usePageAdminStore(); const pageAdminStore = usePageAdminStore();
const result = await Swal.fire({ await confirmLeave({
title: "SAVE YOUR FILTER?", title: "SAVE YOUR FILTER?",
html: "If you want to continue using this filter in any other page, please select [Yes].", html: "If you want to continue using this filter in any other page, please select [Yes].",
saveChanges: async () => {
if (allMapDataStore.createFilterId) {
await allMapDataStore.updateFilter();
if (allMapDataStore.isUpdateFilter) {
await savedSuccessfully(allMapDataStore.filterName);
}
} else {
// Dangerous, here shows a modal
await saveFilter(addFilterId, next);
}
},
navigateAfterSave: true,
// Handle page admin issue
beforeDismiss: () => pageAdminStore.keepPreviousPage(),
discardTemp: () => {
allMapDataStore.tempFilterId = null;
},
next: next,
toPath: toPath,
logOut: logOut,
});
}
/**
* Shows the "save before leaving" confirmation dialog and runs the
* confirm, cancel, and backdrop flows shared by the filter and
* conformance pages.
*
* @param {Object} options - The dialog and flow options.
* @param {string} options.title - The dialog title.
* @param {string} [options.html] - Optional dialog body HTML.
* @param {Function} options.saveChanges - Async callback saving the
* unsaved changes on confirm.
* @param {boolean} options.navigateAfterSave - Whether to navigate (or
* log out) after saving on confirm.
* @param {Function|null} options.beforeDismiss - Callback invoked on
* cancel or backdrop dismiss before anything else, or null.
* @param {Function} options.discardTemp - Callback discarding the
* temporary unsaved data on cancel.
* @param {Function} options.next - Vue Router next() guard callback.
* @param {string} options.toPath - The destination route path.
* @param {Function} [options.logOut] - Optional logout function to call
* instead of navigating.
* @returns {Promise<void>}
*/
async function confirmLeave({
title,
html,
saveChanges,
navigateAfterSave,
beforeDismiss,
discardTemp,
next,
toPath,
logOut,
}) {
const result = await Swal.fire({
title: title,
...(html !== undefined && { html: html }),
icon: "warning", icon: "warning",
iconColor: "#FF3366", iconColor: "#FF3366",
reverseButtons: true, reverseButtons: true,
@@ -132,31 +211,16 @@ export async function leaveFilter(next, addFilterId, toPath, logOut) {
}); });
if (result.isConfirmed) { if (result.isConfirmed) {
if (allMapDataStore.createFilterId) { await saveChanges();
await allMapDataStore.updateFilter(); if (navigateAfterSave) {
if (allMapDataStore.isUpdateFilter) { logOut ? logOut() : next(toPath);
await savedSuccessfully(allMapDataStore.filterName);
}
} else {
// Dangerous, here shows a modal
await saveFilter(addFilterId, next);
} }
logOut ? logOut() : next(toPath);
} else if (result.dismiss === "cancel") { } else if (result.dismiss === "cancel") {
// console.log('popup cancel case', ); if (beforeDismiss) beforeDismiss();
// Handle page admin issue discardTemp();
// console.log("usePageAdminStore.activePage", usePageAdminStore.activePage);
pageAdminStore.keepPreviousPage();
allMapDataStore.tempFilterId = null;
logOut ? logOut() : next(toPath); logOut ? logOut() : next(toPath);
} else if (result.dismiss === "backdrop") { } else if (result.dismiss === "backdrop") {
// console.log('popup backdrop case', ); if (beforeDismiss) beforeDismiss();
// Handle page admin issue
// console.log("usePageAdminStore.activePage", usePageAdminStore.activePage);
pageAdminStore.keepPreviousPage();
if (!logOut) { if (!logOut) {
next(false); next(false);
} }
@@ -171,47 +235,16 @@ export async function leaveFilter(next, addFilterId, toPath, logOut) {
* @returns {Promise<boolean>} True if the rule was saved, false otherwise. * @returns {Promise<boolean>} True if the rule was saved, false otherwise.
*/ */
export async function saveConformance(addConformanceCreateCheckId) { export async function saveConformance(addConformanceCreateCheckId) {
let fileName = ""; return promptSaveName(
const { value, isConfirmed } = await Swal.fire({ "SAVE NEW RULE",
title: "SAVE NEW RULE", "Enter Rule Name.",
input: "text", addConformanceCreateCheckId,
inputPlaceholder: "Enter Rule Name.", );
inputValue: fileName,
inputAttributes: {
maxlength: 200,
},
inputValidator: (value) => {
if (!value) return "You need to write something!";
fileName = value;
},
icon: "info",
iconHtml:
'<span class="material-symbols-outlined !text-[58px]">cloud_upload</span>',
iconColor: "#0099FF",
reverseButtons: true,
confirmButtonColor: "#0099FF",
showCancelButton: true,
cancelButtonColor: "#94a3b8",
customClass: customClass,
});
// Determine whether to redirect based on the return value
if (isConfirmed) {
// Save succeeded
await addConformanceCreateCheckId(fileName);
// Show save complete notification
if (value) savedSuccessfully(value);
// Clear the input field
fileName = "";
return true;
} else {
// Clicked cancel or outside the dialog; save failed.
return false;
}
} }
/** /**
* Prompts the user to save unsaved conformance rule changes before * Prompts the user to save unsaved conformance rule changes before
* leaving the Conformance page. Delegates to helper functions for * leaving the Conformance page. Handles confirm (save), cancel
* confirm, cancel, and backdrop scenarios. * (discard), and backdrop (stay) scenarios.
* *
* @param {Function} next - Vue Router next() guard callback. * @param {Function} next - Vue Router next() guard callback.
* @param {Function} addConformanceCreateCheckId - Backend API function * @param {Function} addConformanceCreateCheckId - Backend API function
@@ -227,91 +260,32 @@ export async function leaveConformance(
logOut, logOut,
) { ) {
const conformanceStore = useConformanceStore(); const conformanceStore = useConformanceStore();
const result = await showConfirmationDialog();
if (result.isConfirmed) { await confirmLeave({
await handleConfirmed(conformanceStore, addConformanceCreateCheckId);
} else {
await handleDismiss(result.dismiss, conformanceStore, next, toPath, logOut);
}
}
/**
* Displays the "SAVE YOUR RULE?" confirmation dialog.
* @returns {Promise<Object>} The SweetAlert2 result object.
*/
async function showConfirmationDialog() {
return Swal.fire({
title: "SAVE YOUR RULE?", title: "SAVE YOUR RULE?",
icon: "warning", saveChanges: async () => {
iconColor: "#FF3366", if (
reverseButtons: true, conformanceStore.conformanceFilterCreateCheckId ||
confirmButtonText: "Yes", conformanceStore.conformanceLogCreateCheckId
confirmButtonColor: "#FF3366", ) {
showCancelButton: true, await conformanceStore.updateConformance();
cancelButtonText: "No", if (conformanceStore.isUpdateConformance) {
cancelButtonColor: "#94a3b8", await savedSuccessfully(conformanceStore.conformanceFileName);
customClass: customClass, }
}); } else {
} await saveConformance(addConformanceCreateCheckId);
/**
* Handles the confirmed save action for conformance rules.
* @param {Object} conformanceStore - The conformance Pinia store.
* @param {Function} addConformanceCreateCheckId - API function to create check.
* @returns {Promise<void>}
*/
async function handleConfirmed(conformanceStore, addConformanceCreateCheckId) {
if (
conformanceStore.conformanceFilterCreateCheckId ||
conformanceStore.conformanceLogCreateCheckId
) {
await conformanceStore.updateConformance();
if (conformanceStore.isUpdateConformance) {
await savedSuccessfully(conformanceStore.conformanceFileName);
}
} else {
await saveConformance(addConformanceCreateCheckId);
}
}
/**
* Handles dismiss actions (cancel or backdrop click) for conformance modals.
* @param {string} dismissType - The SweetAlert2 dismiss reason.
* @param {Object} conformanceStore - The conformance Pinia store.
* @param {Function} next - Vue Router next() guard callback.
* @param {string} toPath - The destination route path.
* @param {Function} [logOut] - Optional logout function.
* @returns {Promise<void>}
*/
async function handleDismiss(
dismissType,
conformanceStore,
next,
toPath,
logOut,
) {
switch (dismissType) {
case "cancel":
resetTempCheckId(conformanceStore);
logOut ? logOut() : next(toPath);
break;
case "backdrop":
if (!logOut) {
next(false);
} }
break; },
default: navigateAfterSave: false,
break; beforeDismiss: null,
} discardTemp: () => {
} conformanceStore.conformanceFilterTempCheckId = null;
conformanceStore.conformanceLogTempCheckId = null;
/** },
* Resets temporary conformance check IDs to null. next: next,
* @param {Object} conformanceStore - The conformance Pinia store. toPath: toPath,
*/ logOut: logOut,
function resetTempCheckId(conformanceStore) { });
conformanceStore.conformanceFilterTempCheckId = null;
conformanceStore.conformanceLogTempCheckId = null;
} }
/** /**
+353
View File
@@ -0,0 +1,353 @@
// The Lucia project.
// Copyright 2026-2026 DSP, inc. All rights reserved.
// Authors:
// imacat.yang@dsp.im (imacat), 2026/7/5
// AI assistance: Claude Code (Anthropic)
import { describe, it, expect, beforeEach, vi } from "vitest";
import { setActivePinia, createPinia } from "pinia";
import { http, HttpResponse } from "msw";
import { server } from "@/mocks/node.js";
import { findRequest, captureRequest } from "@/mocks/request-log.js";
vi.mock("@/module/apiError.js", () => ({
default: vi.fn(),
}));
vi.mock("sweetalert2", () => ({
default: { close: vi.fn(), fire: vi.fn() },
}));
import Swal from "sweetalert2";
import {
saveFilter,
saveConformance,
leaveFilter,
leaveConformance,
} from "@/module/alertModal.js";
import { useAllMapDataStore } from "@/stores/allMapData";
import { useConformanceStore } from "@/stores/conformance";
import { usePageAdminStore } from "@/stores/pageAdmin";
/**
* Makes Swal.fire simulate a confirmed dialog. If the dialog config
* has an input validator, it is invoked with the value first, the
* same way SweetAlert2 validates the input before resolving.
*
* @param {string} value - The input value the user typed.
*/
function fireConfirmedWithInput(value) {
Swal.fire.mockImplementation(async (config) => {
if (config.inputValidator) config.inputValidator(value);
return { value, isConfirmed: true };
});
}
describe("alertModal", () => {
let pageAdminStore;
beforeEach(() => {
setActivePinia(createPinia());
pageAdminStore = usePageAdminStore();
vi.spyOn(pageAdminStore, "keepPreviousPage");
Swal.fire.mockReset();
document.cookie = "luciaToken=fake-test-token";
});
describe("saveFilter", () => {
it("prompts with the filter save dialog config", async () => {
fireConfirmedWithInput("My Filter");
await saveFilter(vi.fn());
expect(Swal.fire).toHaveBeenCalledWith(
expect.objectContaining({
title: "SAVE NEW FILTER",
input: "text",
inputPlaceholder: "Enter Filter Name.",
icon: "info",
confirmButtonColor: "#0099FF",
showCancelButton: true,
cancelButtonColor: "#94a3b8",
}),
);
});
it("rejects an empty input value", async () => {
fireConfirmedWithInput("My Filter");
await saveFilter(vi.fn());
const config = Swal.fire.mock.calls[0][0];
expect(config.inputValidator("")).toBe("You need to write something!");
expect(config.inputValidator("ok")).toBeUndefined();
});
it("saves via the API function and notifies on confirm", async () => {
fireConfirmedWithInput("My Filter");
const addFilterId = vi.fn();
const saved = await saveFilter(addFilterId);
expect(saved).toBe(true);
expect(addFilterId).toHaveBeenCalledWith("My Filter");
expect(Swal.fire).toHaveBeenCalledTimes(2);
expect(Swal.fire.mock.calls[1][0]).toMatchObject({
title: "SAVE COMPLETE",
icon: "success",
});
expect(Swal.fire.mock.calls[1][0].html).toContain("My Filter");
expect(pageAdminStore.keepPreviousPage).not.toHaveBeenCalled();
});
it("keeps the previous page and stops navigation on cancel", async () => {
Swal.fire.mockResolvedValue({ isConfirmed: false, dismiss: "cancel" });
const addFilterId = vi.fn();
const next = vi.fn();
const saved = await saveFilter(addFilterId, next);
expect(saved).toBe(false);
expect(addFilterId).not.toHaveBeenCalled();
expect(pageAdminStore.keepPreviousPage).toHaveBeenCalledTimes(1);
expect(next).toHaveBeenCalledWith(false);
});
it("cancels without a next() callback", async () => {
Swal.fire.mockResolvedValue({ isConfirmed: false, dismiss: "cancel" });
const saved = await saveFilter(vi.fn());
expect(saved).toBe(false);
expect(pageAdminStore.keepPreviousPage).toHaveBeenCalledTimes(1);
});
});
describe("saveConformance", () => {
it("prompts with the rule save dialog config", async () => {
fireConfirmedWithInput("My Rule");
await saveConformance(vi.fn());
expect(Swal.fire).toHaveBeenCalledWith(
expect.objectContaining({
title: "SAVE NEW RULE",
input: "text",
inputPlaceholder: "Enter Rule Name.",
icon: "info",
confirmButtonColor: "#0099FF",
showCancelButton: true,
cancelButtonColor: "#94a3b8",
}),
);
});
it("saves via the API function and notifies on confirm", async () => {
fireConfirmedWithInput("My Rule");
const addCheckId = vi.fn();
const saved = await saveConformance(addCheckId);
expect(saved).toBe(true);
expect(addCheckId).toHaveBeenCalledWith("My Rule");
expect(Swal.fire).toHaveBeenCalledTimes(2);
expect(Swal.fire.mock.calls[1][0]).toMatchObject({
title: "SAVE COMPLETE",
icon: "success",
});
expect(Swal.fire.mock.calls[1][0].html).toContain("My Rule");
});
it("returns false on cancel without touching the page admin", async () => {
Swal.fire.mockResolvedValue({ isConfirmed: false, dismiss: "cancel" });
const addCheckId = vi.fn();
const saved = await saveConformance(addCheckId);
expect(saved).toBe(false);
expect(addCheckId).not.toHaveBeenCalled();
expect(pageAdminStore.keepPreviousPage).not.toHaveBeenCalled();
});
});
describe("leaveFilter", () => {
let allMapDataStore;
beforeEach(() => {
allMapDataStore = useAllMapDataStore();
});
it("shows the leave confirmation dialog config", async () => {
Swal.fire.mockResolvedValue({ isConfirmed: false, dismiss: "esc" });
await leaveFilter(vi.fn(), vi.fn(), "/files");
expect(Swal.fire).toHaveBeenCalledWith(
expect.objectContaining({
title: "SAVE YOUR FILTER?",
html: "If you want to continue using this filter in any other page, please select [Yes].",
icon: "warning",
confirmButtonText: "Yes",
cancelButtonText: "No",
confirmButtonColor: "#FF3366",
showCancelButton: true,
}),
);
});
it("updates an existing filter and navigates on confirm", async () => {
server.use(
http.put("/api/filters/:id", ({ params }) => {
captureRequest("PUT", `/api/filters/${params.id}`);
return HttpResponse.json({});
}),
);
allMapDataStore.createFilterId = 5;
allMapDataStore.filterName = "My Filter";
Swal.fire.mockResolvedValue({ isConfirmed: true });
const next = vi.fn();
await leaveFilter(next, vi.fn(), "/files");
expect(findRequest("PUT", "/api/filters/5")).toHaveLength(1);
expect(Swal.fire.mock.calls[1][0]).toMatchObject({
title: "SAVE COMPLETE",
});
expect(Swal.fire.mock.calls[1][0].html).toContain("My Filter");
expect(next).toHaveBeenCalledWith("/files");
});
it("prompts to save a new filter and navigates on confirm", async () => {
Swal.fire.mockImplementationOnce(async () => ({ isConfirmed: true }));
Swal.fire.mockImplementationOnce(async (config) => {
config.inputValidator("Fresh Filter");
return { value: "Fresh Filter", isConfirmed: true };
});
const addFilterId = vi.fn();
const next = vi.fn();
await leaveFilter(next, addFilterId, "/files");
expect(Swal.fire.mock.calls[1][0]).toMatchObject({
title: "SAVE NEW FILTER",
});
expect(addFilterId).toHaveBeenCalledWith("Fresh Filter");
expect(next).toHaveBeenCalledWith("/files");
});
it("calls logOut instead of navigating on confirm", async () => {
server.use(http.put("/api/filters/:id", () => HttpResponse.json({})));
allMapDataStore.createFilterId = 5;
allMapDataStore.filterName = "My Filter";
Swal.fire.mockResolvedValue({ isConfirmed: true });
const next = vi.fn();
const logOut = vi.fn();
await leaveFilter(next, vi.fn(), "/files", logOut);
expect(logOut).toHaveBeenCalledTimes(1);
expect(next).not.toHaveBeenCalled();
});
it("discards the filter and navigates on cancel", async () => {
allMapDataStore.tempFilterId = 42;
Swal.fire.mockResolvedValue({ isConfirmed: false, dismiss: "cancel" });
const next = vi.fn();
await leaveFilter(next, vi.fn(), "/files");
expect(pageAdminStore.keepPreviousPage).toHaveBeenCalledTimes(1);
expect(allMapDataStore.tempFilterId).toBeNull();
expect(next).toHaveBeenCalledWith("/files");
});
it("stays on the page on backdrop dismiss", async () => {
Swal.fire.mockResolvedValue({ isConfirmed: false, dismiss: "backdrop" });
const next = vi.fn();
await leaveFilter(next, vi.fn(), "/files");
expect(pageAdminStore.keepPreviousPage).toHaveBeenCalledTimes(1);
expect(next).toHaveBeenCalledWith(false);
});
it("does nothing extra on backdrop dismiss when logging out", async () => {
Swal.fire.mockResolvedValue({ isConfirmed: false, dismiss: "backdrop" });
const next = vi.fn();
const logOut = vi.fn();
await leaveFilter(next, vi.fn(), "/files", logOut);
expect(pageAdminStore.keepPreviousPage).toHaveBeenCalledTimes(1);
expect(next).not.toHaveBeenCalled();
expect(logOut).not.toHaveBeenCalled();
});
});
describe("leaveConformance", () => {
let conformanceStore;
beforeEach(() => {
conformanceStore = useConformanceStore();
});
it("shows the leave confirmation dialog config", async () => {
Swal.fire.mockResolvedValue({ isConfirmed: false, dismiss: "esc" });
await leaveConformance(vi.fn(), vi.fn(), "/files");
expect(Swal.fire).toHaveBeenCalledWith(
expect.objectContaining({
title: "SAVE YOUR RULE?",
icon: "warning",
confirmButtonText: "Yes",
cancelButtonText: "No",
confirmButtonColor: "#FF3366",
showCancelButton: true,
}),
);
expect(Swal.fire.mock.calls[0][0].html).toBeUndefined();
});
it("updates an existing rule without navigating on confirm", async () => {
server.use(
http.put("/api/filter-checks/:id", ({ params }) => {
captureRequest("PUT", `/api/filter-checks/${params.id}`);
return HttpResponse.json({});
}),
);
conformanceStore.conformanceFilterCreateCheckId = 7;
conformanceStore.conformanceFileName = "My Rule";
Swal.fire.mockResolvedValue({ isConfirmed: true });
const next = vi.fn();
await leaveConformance(next, vi.fn(), "/files");
expect(findRequest("PUT", "/api/filter-checks/7")).toHaveLength(1);
expect(Swal.fire.mock.calls[1][0]).toMatchObject({
title: "SAVE COMPLETE",
});
expect(Swal.fire.mock.calls[1][0].html).toContain("My Rule");
expect(next).not.toHaveBeenCalled();
});
it("prompts to save a new rule without navigating on confirm", async () => {
Swal.fire.mockImplementationOnce(async () => ({ isConfirmed: true }));
Swal.fire.mockImplementationOnce(async (config) => {
config.inputValidator("Fresh Rule");
return { value: "Fresh Rule", isConfirmed: true };
});
const addCheckId = vi.fn();
const next = vi.fn();
await leaveConformance(next, addCheckId, "/files");
expect(Swal.fire.mock.calls[1][0]).toMatchObject({
title: "SAVE NEW RULE",
});
expect(addCheckId).toHaveBeenCalledWith("Fresh Rule");
expect(next).not.toHaveBeenCalled();
});
it("resets temporary check IDs and navigates on cancel", async () => {
conformanceStore.conformanceFilterTempCheckId = 3;
conformanceStore.conformanceLogTempCheckId = 4;
Swal.fire.mockResolvedValue({ isConfirmed: false, dismiss: "cancel" });
const next = vi.fn();
await leaveConformance(next, vi.fn(), "/files");
expect(conformanceStore.conformanceFilterTempCheckId).toBeNull();
expect(conformanceStore.conformanceLogTempCheckId).toBeNull();
expect(next).toHaveBeenCalledWith("/files");
expect(pageAdminStore.keepPreviousPage).not.toHaveBeenCalled();
});
it("calls logOut instead of navigating on cancel", async () => {
Swal.fire.mockResolvedValue({ isConfirmed: false, dismiss: "cancel" });
const next = vi.fn();
const logOut = vi.fn();
await leaveConformance(next, vi.fn(), "/files", logOut);
expect(logOut).toHaveBeenCalledTimes(1);
expect(next).not.toHaveBeenCalled();
});
it("stays on the page on backdrop dismiss", async () => {
Swal.fire.mockResolvedValue({ isConfirmed: false, dismiss: "backdrop" });
const next = vi.fn();
await leaveConformance(next, vi.fn(), "/files");
expect(next).toHaveBeenCalledWith(false);
expect(pageAdminStore.keepPreviousPage).not.toHaveBeenCalled();
});
it("does nothing on backdrop dismiss when logging out", async () => {
Swal.fire.mockResolvedValue({ isConfirmed: false, dismiss: "backdrop" });
const next = vi.fn();
const logOut = vi.fn();
await leaveConformance(next, vi.fn(), "/files", logOut);
expect(next).not.toHaveBeenCalled();
expect(logOut).not.toHaveBeenCalled();
});
});
});