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:
+134
-160
@@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user