// 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(); }); }); });