diff --git a/frontend/__tests__/components/AnimatedModal.spec.tsx b/frontend/__tests__/components/AnimatedModal.spec.tsx new file mode 100644 index 000000000000..1c10d9f573fe --- /dev/null +++ b/frontend/__tests__/components/AnimatedModal.spec.tsx @@ -0,0 +1,97 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { render } from "@solidjs/testing-library"; +import { AnimatedModal } from "../../src/ts/components/AnimatedModal"; + +describe("AnimatedModal", () => { + beforeEach(() => { + vi.clearAllMocks(); + + // Mock dialog methods that don't exist in jsdom + HTMLDialogElement.prototype.showModal = vi.fn(); + HTMLDialogElement.prototype.show = vi.fn(); + HTMLDialogElement.prototype.close = vi.fn(); + }); + + function renderModal(props: { + isOpen: boolean; + onClose: () => void; + onEscape?: (e: KeyboardEvent) => void; + onBackdropClick?: (e: MouseEvent) => void; + class?: string; + beforeShow?: () => void | Promise; + afterShow?: () => void | Promise; + beforeHide?: () => void | Promise; + afterHide?: () => void | Promise; + animationMode?: "none" | "both" | "modalOnly"; + }): { + container: HTMLElement; + dialog: HTMLDialogElement; + modalDiv: HTMLDivElement; + } { + const { container } = render(() => ( + +
Test Content
+
+ )); + + return { + // oxlint-disable-next-line no-non-null-assertion + container: container.children[0]! as HTMLElement, + // oxlint-disable-next-line no-non-null-assertion + dialog: container.querySelector("dialog")!, + // oxlint-disable-next-line no-non-null-assertion + modalDiv: container.querySelector(".modal")!, + }; + } + + it("renders dialog with correct id and class", () => { + const { dialog } = renderModal({ isOpen: false, onClose: vi.fn() }); + + expect(dialog).toHaveAttribute("id", "TestModal"); + expect(dialog).toHaveClass("modalWrapper", "hidden"); + }); + + it("renders children inside modal div", () => { + const { modalDiv } = renderModal({ isOpen: false, onClose: vi.fn() }); + + expect( + modalDiv.querySelector("[data-testid='modal-content']"), + ).toHaveTextContent("Test Content"); + }); + + it("has escape handler attached", () => { + const onClose = vi.fn(); + + const { dialog } = renderModal({ isOpen: true, onClose }); + + expect(dialog.onkeydown).toBeDefined(); + }); + + it("has backdrop click handler attached", () => { + const onClose = vi.fn(); + + const { dialog } = renderModal({ isOpen: true, onClose }); + + expect(dialog.onmousedown).toBeDefined(); + }); + + it("applies custom class to dialog", () => { + const { dialog } = renderModal({ + isOpen: false, + onClose: vi.fn(), + class: "customClass", + }); + + expect(dialog).toHaveClass("modalWrapper", "hidden", "customClass"); + }); + + it("renders with animationMode none", () => { + const { dialog } = renderModal({ + isOpen: false, + onClose: vi.fn(), + animationMode: "none", + }); + + expect(dialog).toHaveAttribute("id", "TestModal"); + }); +}); diff --git a/frontend/__tests__/components/AsyncContent.spec.tsx b/frontend/__tests__/components/AsyncContent.spec.tsx new file mode 100644 index 000000000000..1868b8c7c1ca --- /dev/null +++ b/frontend/__tests__/components/AsyncContent.spec.tsx @@ -0,0 +1,78 @@ +import { describe, it, expect } from "vitest"; +import { render, screen, waitFor } from "@solidjs/testing-library"; +import { createResource, Resource } from "solid-js"; +import AsyncContent from "../../src/ts/components/AsyncContent"; + +describe("AsyncContent", () => { + function renderWithResource( + resource: Resource, + errorMessage?: string, + ): { + container: HTMLElement; + } { + const { container } = render(() => ( + + {(data) =>
{String(data)}
} +
+ )); + + return { + container, + }; + } + + it("renders loading state while resource is pending", () => { + const [resource] = createResource(async () => { + await new Promise((resolve) => setTimeout(resolve, 100)); + return "data"; + }); + + const { container } = renderWithResource(resource); + + const preloader = container.querySelector(".preloader"); + expect(preloader).toBeInTheDocument(); + expect(preloader).toHaveClass("preloader"); + expect(preloader?.querySelector("i")).toHaveClass( + "fas", + "fa-fw", + "fa-spin", + "fa-circle-notch", + ); + }); + + it("renders data when resource resolves", async () => { + const [resource] = createResource(async () => { + return "Test Data"; + }); + + renderWithResource(resource); + + await waitFor(() => { + expect(screen.getByTestId("content")).toHaveTextContent("Test Data"); + }); + }); + + it("renders error message when resource fails", async () => { + const [resource] = createResource(async () => { + throw new Error("Test error"); + }); + + renderWithResource(resource, "Custom error message"); + + await waitFor(() => { + expect(screen.getByText(/Custom error message/)).toBeInTheDocument(); + }); + }); + + it("renders default error message when no custom message provided", async () => { + const [resource] = createResource(async () => { + throw new Error("Test error"); + }); + + renderWithResource(resource); + + await waitFor(() => { + expect(screen.getByText(/An error occurred/)).toBeInTheDocument(); + }); + }); +}); diff --git a/frontend/src/html/footer.html b/frontend/src/html/footer.html index 25ff8906347b..fb49d6b6126a 100644 --- a/frontend/src/html/footer.html +++ b/frontend/src/html/footer.html @@ -80,15 +80,7 @@
serika dark
- - + diff --git a/frontend/src/html/popups.html b/frontend/src/html/popups.html index 7dff29cceec5..205958504d9c 100644 --- a/frontend/src/html/popups.html +++ b/frontend/src/html/popups.html @@ -1,3 +1,5 @@ + + - +