import { cleanup, render } from "@testing-library/react"; import React from "react"; import Portal from "../portal"; describe("Portal", () => { afterEach(() => { const portals = document.querySelectorAll('[id^="test-portal"]'); portals.forEach((portal) => portal.remove()); cleanup(); }); it("renders children into a portal", () => { const { container } = render(
Portal Content
, ); expect( container.querySelector('[data-testid="portal-content"]'), ).toBeNull(); const portalRoot = document.getElementById("test-portal-1"); expect(portalRoot).toBeTruthy(); expect( portalRoot?.querySelector('[data-testid="portal-content"]'), ).toBeTruthy(); }); it("creates portal root if it doesn't exist", () => { expect(document.getElementById("test-portal-2")).toBeNull(); render(
Content
, ); const portalRoot = document.getElementById("test-portal-2"); expect(portalRoot).not.toBeNull(); expect(portalRoot?.parentElement).toBe(document.body); }); it("uses existing portal root if it exists", () => { const existingRoot = document.createElement("div"); existingRoot.id = "test-portal-3"; document.body.appendChild(existingRoot); render(
Using Existing
, ); const portalRoot = document.getElementById("test-portal-3"); expect(portalRoot).toBe(existingRoot); expect( portalRoot?.querySelector('[data-testid="existing-content"]'), ).toBeTruthy(); existingRoot.remove(); }); it("removes portal content on unmount", () => { const { unmount } = render(
Cleanup Test
, ); const portalRoot = document.getElementById("test-portal-4"); expect( portalRoot?.querySelector('[data-testid="portal-content"]'), ).toBeTruthy(); unmount(); const stillExists = document.getElementById("test-portal-4"); expect(stillExists).toBeTruthy(); expect( stillExists?.querySelector('[data-testid="portal-content"]'), ).toBeNull(); }); it("renders multiple children correctly", () => { render(
Child 1
Child 2
Child 3
, ); const portalRoot = document.getElementById("test-portal-5"); expect(portalRoot?.querySelector('[data-testid="child-1"]')).toBeTruthy(); expect(portalRoot?.querySelector('[data-testid="child-2"]')).toBeTruthy(); expect(portalRoot?.querySelector('[data-testid="child-3"]')).toBeTruthy(); }); it("handles multiple portals", () => { render(
Portal A
, ); render(
Portal B
, ); expect(document.getElementById("test-portal-6a")).not.toBeNull(); expect(document.getElementById("test-portal-6b")).not.toBeNull(); }); it("works with shadow DOM when portalHost is provided", () => { const shadowHost = document.createElement("div"); document.body.appendChild(shadowHost); const shadowRoot = shadowHost.attachShadow({ mode: "open" }); render(
Shadow Content
, ); const portalRoot = shadowRoot.getElementById("test-portal-shadow"); expect(portalRoot).toBeTruthy(); expect( portalRoot?.querySelector('[data-testid="shadow-content"]'), ).toBeTruthy(); shadowHost.remove(); }); it("appends to portalHost instead of document.body when provided", () => { const customHost = document.createElement("div"); document.body.appendChild(customHost); const shadowRoot = customHost.attachShadow({ mode: "open" }); render(
Custom Host Content
, ); const portalRoot = shadowRoot.getElementById("test-portal-custom-host"); expect(portalRoot).toBeTruthy(); expect(portalRoot?.parentNode).toBe(shadowRoot); customHost.remove(); }); it("creates portal root in portalHost when it doesn't exist", () => { const shadowHost = document.createElement("div"); document.body.appendChild(shadowHost); const shadowRoot = shadowHost.attachShadow({ mode: "open" }); render(
Shadow Portal
, ); const portalRoot = shadowRoot.getElementById("test-portal-7"); expect(portalRoot).not.toBeNull(); expect(shadowRoot.contains(portalRoot!)).toBe(true); shadowHost.remove(); }); it("handles re-renders correctly", () => { const { rerender } = render(
Content 1
, ); const portalRoot = document.getElementById("test-portal-8"); expect(portalRoot?.querySelector('[data-testid="content-1"]')).toBeTruthy(); rerender(
Content 2
, ); expect(portalRoot?.querySelector('[data-testid="content-1"]')).toBeNull(); expect(portalRoot?.querySelector('[data-testid="content-2"]')).toBeTruthy(); }); });