Files
lucia-frontend/tests/unit/module/alertModal.test.js
T

354 lines
13 KiB
JavaScript

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