summaryrefslogtreecommitdiffhomepage
path: root/src/adapters/portal.test.ts
blob: a5624d5ace2716c3104266490cf63803ff98bc2d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import { afterEach, describe, expect, it } from "vitest";
import { portal } from "./portal";

describe("portal action", () => {
  afterEach(() => {
    // Strip any leftover teleported nodes between tests.
    document.querySelectorAll("body > :not(script)").forEach((n) => {
      if (n instanceof HTMLElement) n.remove();
    });
  });

  it("teleports the node to document.body (escaping an ancestor with transform)", () => {
    // Simulate the sidebar: a transformed ancestor establishes a containing
    // block for `position: fixed`.
    const ancestor = document.createElement("div");
    ancestor.style.transform = "translateX(0)";
    document.body.appendChild(ancestor);

    const node = document.createElement("div");
    node.setAttribute("data-testid", "modal");
    ancestor.appendChild(node);
    expect(node.parentNode).toBe(ancestor);

    const action = portal(node);

    // After the action, the node is a direct child of <body>, not the ancestor.
    expect(node.parentNode).toBe(document.body);
    expect(ancestor.contains(node)).toBe(false);

    action.destroy();

    // On destroy the node is removed from <body>.
    expect(document.body.contains(node)).toBe(false);
  });

  it("is a no-op (does not throw) when document is unavailable (SSR guard)", () => {
    const originalDocument = globalThis.document;
    // @ts-expect-error — deliberately undefined to exercise the SSR guard.
    globalThis.document = undefined;
    try {
      const stub = {} as HTMLElement;
      const action = portal(stub);
      // Must not throw, and returns a destroy that is safe to call.
      action.destroy();
    } finally {
      globalThis.document = originalDocument;
    }
  });
});