The native <dialog> wrapper rendered its children unconditionally and only called dialog.close() on the underlying element when open went false. The React subtree stayed mounted, so component state (e.g. a ConfirmButton mid-confirm with a red checkmark showing) survived a close/reopen cycle and reappeared the next time the user opened the same dialog. Gate children on open so the subtree unmounts on close. Next open gets a fresh tree with default state.
80 lines
2.0 KiB
TypeScript
80 lines
2.0 KiB
TypeScript
// @vitest-environment jsdom
|
|
import { cleanup, render, screen } from "@testing-library/react";
|
|
import { userEvent } from "@testing-library/user-event";
|
|
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
|
|
import { polyfillDialog } from "../../__tests__/polyfill-dialog.js";
|
|
import { Dialog, DialogHeader } from "../ui/dialog.js";
|
|
|
|
beforeAll(() => {
|
|
polyfillDialog();
|
|
});
|
|
|
|
afterEach(cleanup);
|
|
|
|
describe("Dialog", () => {
|
|
it("opens when open=true", () => {
|
|
render(
|
|
<Dialog open={true} onClose={() => {}}>
|
|
Content
|
|
</Dialog>,
|
|
);
|
|
expect(screen.getByText("Content")).toBeDefined();
|
|
});
|
|
|
|
it("closes when open changes from true to false", () => {
|
|
const { rerender } = render(
|
|
<Dialog open={true} onClose={() => {}}>
|
|
Content
|
|
</Dialog>,
|
|
);
|
|
const dialog = document.querySelector("dialog");
|
|
expect(dialog?.hasAttribute("open")).toBe(true);
|
|
|
|
rerender(
|
|
<Dialog open={false} onClose={() => {}}>
|
|
Content
|
|
</Dialog>,
|
|
);
|
|
expect(dialog?.hasAttribute("open")).toBe(false);
|
|
});
|
|
|
|
it("unmounts children when closed so internal state does not persist", () => {
|
|
const { rerender } = render(
|
|
<Dialog open={true} onClose={() => {}}>
|
|
<span>Body</span>
|
|
</Dialog>,
|
|
);
|
|
expect(screen.queryByText("Body")).not.toBeNull();
|
|
|
|
rerender(
|
|
<Dialog open={false} onClose={() => {}}>
|
|
<span>Body</span>
|
|
</Dialog>,
|
|
);
|
|
expect(screen.queryByText("Body")).toBeNull();
|
|
});
|
|
|
|
it("calls onClose on cancel event", () => {
|
|
const onClose = vi.fn();
|
|
render(
|
|
<Dialog open={true} onClose={onClose}>
|
|
Content
|
|
</Dialog>,
|
|
);
|
|
const dialog = document.querySelector("dialog");
|
|
dialog?.dispatchEvent(new Event("cancel"));
|
|
expect(onClose).toHaveBeenCalledOnce();
|
|
});
|
|
});
|
|
|
|
describe("DialogHeader", () => {
|
|
it("renders title and close button", async () => {
|
|
const onClose = vi.fn();
|
|
render(<DialogHeader title="Test Title" onClose={onClose} />);
|
|
|
|
expect(screen.getByText("Test Title")).toBeDefined();
|
|
await userEvent.click(screen.getByRole("button"));
|
|
expect(onClose).toHaveBeenCalledOnce();
|
|
});
|
|
});
|