+
+ Click the card to enter fullscreen. Click again or press Esc to exit.
+
+ {
+ attach(el);
+ const onChange = () => setIsActive(document.fullscreenElement === el);
+ document.addEventListener("fullscreenchange", onChange);
+ onCleanup(() => document.removeEventListener("fullscreenchange", onChange));
+ }}
+ style={{
+ padding: "2rem 1rem",
+ background: isActive() ? "#dbeafe" : "#f1f5f9",
+ "border-radius": "8px",
+ cursor: "pointer",
+ "text-align": "center",
+ border: `2px dashed ${isActive() ? "#3b82f6" : "#cbd5e1"}`,
+ color: isActive() ? "#1e40af" : "#475569",
+ "font-size": "0.875rem",
+ transition: "background 200ms, border-color 200ms, color 200ms",
+ }}
+ >
+ {isActive() ? "Fullscreen — click to exit" : "Click to enter fullscreen"}
+
+
+
+ );
+ },
+});
diff --git a/packages/fullscreen/test/index.test.ts b/packages/fullscreen/test/index.test.ts
index 2426725d1..cbefb050c 100644
--- a/packages/fullscreen/test/index.test.ts
+++ b/packages/fullscreen/test/index.test.ts
@@ -1,78 +1,196 @@
+import "./setup.js";
import { setup_state } from "./setup.js";
-import { createRoot, createEffect, createSignal } from "solid-js";
-import { createFullscreen } from "../src/index.js";
-import { describe, it, expect } from "vitest";
+import { createRoot, flush } from "solid-js";
+import { makeFullscreen, createFullscreen, fullscreen } from "../src/index.js";
+import { describe, it, expect, beforeEach } from "vitest";
-describe("createFullscreen", () => {
+beforeEach(() => {
+ setup_state.current_el = undefined;
+ setup_state.current_options = undefined;
+});
+
+describe("makeFullscreen", () => {
const ref = document.createElement("div");
- it("will call the fullscreen method", async () => {
- let captured: unknown;
+ it("enter() calls requestFullscreen on the element", async () => {
+ const [enter] = makeFullscreen(ref);
+ await enter();
+ expect(setup_state.current_el).toBe(ref);
+ });
+
+ it("exit() calls document.exitFullscreen", async () => {
+ const [enter, exit] = makeFullscreen(ref);
+ await enter();
+ await exit();
+ expect(setup_state.current_el).toBeUndefined();
+ });
+
+ it("enter() uses creation-time options by default", async () => {
+ const options: FullscreenOptions = { navigationUI: "hide" };
+ const [enter] = makeFullscreen(ref, options);
+ await enter();
+ expect(setup_state.current_options).toBe(options);
+ });
+
+ it("enter() call-time options override creation-time options", async () => {
+ const creation: FullscreenOptions = { navigationUI: "hide" };
+ const callTime: FullscreenOptions = { navigationUI: "show" };
+ const [enter] = makeFullscreen(ref, creation);
+ await enter(callTime);
+ expect(setup_state.current_options).toBe(callTime);
+ });
+});
- const dispose = createRoot(dispose => {
- const active = createFullscreen(ref);
+describe("createFullscreen", () => {
+ const ref = document.createElement("div");
- createEffect(() => {
- captured = active();
- });
+ it("isActive starts false when element is not fullscreen", () => {
+ createRoot(dispose => {
+ const { isActive } = createFullscreen(ref);
+ expect(isActive()).toBe(false);
+ dispose();
+ });
+ });
- return dispose;
+ it("isActive starts true when element is already fullscreen", () => {
+ setup_state.current_el = ref;
+
+ createRoot(dispose => {
+ const { isActive } = createFullscreen(ref);
+ expect(isActive()).toBe(true);
+ dispose();
});
+ });
- expect(captured).toEqual(false);
+ it("enter() enters fullscreen and isActive becomes true", async () => {
+ await createRoot(async dispose => {
+ const { enter, isActive } = createFullscreen(ref);
- await Promise.resolve();
- expect(captured).toEqual(true);
+ await enter();
+ flush();
+ expect(isActive()).toBe(true);
- dispose();
+ dispose();
+ });
});
- it("will exit fullscreen on reactive change", async () => {
- const [fs, setFs] = createSignal(true);
- let captured: unknown;
+ it("exit() leaves fullscreen and isActive becomes false", async () => {
+ await createRoot(async dispose => {
+ const { enter, exit, isActive } = createFullscreen(ref);
- const dispose = createRoot(dispose => {
- const active = createFullscreen(ref, fs);
+ await enter();
+ flush();
+ expect(isActive()).toBe(true);
- createEffect(() => {
- captured = active();
- });
+ await exit();
+ flush();
+ expect(isActive()).toBe(false);
- return dispose;
+ dispose();
});
+ });
- expect(captured).toEqual(false);
+ it("isActive updates when OS dismisses fullscreen", async () => {
+ await createRoot(async dispose => {
+ const { enter, isActive } = createFullscreen(ref);
- await Promise.resolve();
- expect(captured).toEqual(true);
+ await enter();
+ flush();
+ expect(isActive()).toBe(true);
- setFs(false);
- expect(captured).toEqual(false);
+ setup_state.current_el = undefined;
+ document.dispatchEvent(new Event("fullscreenchange"));
+ flush();
+ expect(isActive()).toBe(false);
+ dispose();
+ });
+ });
+
+ it("exits fullscreen on cleanup by default", async () => {
+ let dispose!: () => void;
+ await createRoot(async d => {
+ dispose = d;
+ const { enter } = createFullscreen(ref);
+ await enter();
+ flush();
+ });
dispose();
+ expect(setup_state.current_el).toBeUndefined();
});
- it("will open fullscreen with options", async () => {
- const options: FullscreenOptions = { navigationUI: "hide" };
- let captured: unknown;
+ it("does not exit fullscreen on cleanup when exitOnCleanup is false", async () => {
+ let dispose!: () => void;
+ await createRoot(async d => {
+ dispose = d;
+ const { enter } = createFullscreen(ref, { exitOnCleanup: false });
+ await enter();
+ flush();
+ });
+ dispose();
+ expect(setup_state.current_el).toBe(ref);
- const dispose = createRoot(dispose => {
- const active = createFullscreen(ref, undefined, options);
+ // reset state
+ setup_state.current_el = undefined;
+ });
- createEffect(() => {
- captured = active();
- });
+ it("enter() passes options to requestFullscreen", async () => {
+ await createRoot(async dispose => {
+ const { enter } = createFullscreen(ref, { navigationUI: "hide" });
+ await enter();
+ expect(setup_state.current_options).toStrictEqual({ navigationUI: "hide" });
+ dispose();
+ });
+ });
+});
- return dispose;
+describe("fullscreen (directive)", () => {
+ const el = document.createElement("div");
+
+ it("clicking the element enters fullscreen", () => {
+ createRoot(dispose => {
+ const attach = fullscreen();
+ attach(el);
+ el.click();
+ expect(setup_state.current_el).toBe(el);
+ dispose();
});
+ });
- expect(captured).toEqual(false);
+ it("clicking again while fullscreen exits", () => {
+ createRoot(dispose => {
+ const attach = fullscreen();
+ attach(el);
+ el.click();
+ expect(setup_state.current_el).toBe(el);
+ el.click();
+ expect(setup_state.current_el).toBeUndefined();
+ dispose();
+ });
+ });
- await Promise.resolve();
- expect(captured).toEqual(true);
- expect(setup_state.current_options).toBe(options);
+ it("passes options to requestFullscreen", () => {
+ const options: FullscreenOptions = { navigationUI: "hide" };
+ createRoot(dispose => {
+ const attach = fullscreen(options);
+ attach(el);
+ el.click();
+ expect(setup_state.current_options).toBe(options);
+ dispose();
+ });
+ });
+
+ it("removes click listener on cleanup", () => {
+ let dispose!: () => void;
+ createRoot(d => {
+ dispose = d;
+ const attach = fullscreen();
+ attach(el);
+ });
dispose();
+ el.click();
+ expect(setup_state.current_el).toBeUndefined();
});
});
diff --git a/packages/fullscreen/test/server.test.ts b/packages/fullscreen/test/server.test.ts
index d667024a0..d6f629fb2 100644
--- a/packages/fullscreen/test/server.test.ts
+++ b/packages/fullscreen/test/server.test.ts
@@ -1,9 +1,16 @@
import { describe, test, expect } from "vitest";
-import { createFullscreen } from "../src/index.js";
+import { createFullscreen, fullscreen } from "../src/index.js";
describe("API doesn't break in SSR", () => {
- // check if the API doesn't throw when calling it in SSR
- test("createFullScreen() - SSR", () => {
- expect(createFullscreen(undefined, () => false)()).toEqual(false);
+ test("createFullscreen() returns safe defaults", async () => {
+ const { enter, exit, isActive } = createFullscreen(undefined as any);
+ expect(isActive()).toBe(false);
+ await expect(enter()).resolves.toBeUndefined();
+ await expect(exit()).resolves.toBeUndefined();
+ });
+
+ test("fullscreen() returns a no-op ref callback", () => {
+ const attach = fullscreen();
+ expect(attach).toBeTypeOf("function");
});
});
diff --git a/packages/fullscreen/test/setup.ts b/packages/fullscreen/test/setup.ts
index 19b89e9d5..8a20a3acf 100644
--- a/packages/fullscreen/test/setup.ts
+++ b/packages/fullscreen/test/setup.ts
@@ -6,21 +6,26 @@ export const setup_state = {
HTMLElement.prototype.requestFullscreen = function (
this: HTMLElement,
options?: FullscreenOptions,
-) {
+): Promise