Files

183 lines
6.9 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import { Window } from "happy-dom";
import { openViewer, sourceFromTrigger, startViewerTriggers } from "../src/overlay.ts";
import { mountViewer, startViewerRuntime } from "../src/runtime.ts";
async function settled(ticks = 30) {
for (let i = 0; i < ticks; i++) await new Promise((done) => setTimeout(done, 5));
}
function page() {
const window = new Window({ url: "https://app.test/" });
return { window, doc: window.document as unknown as Document };
}
const csv = { name: "data.csv", blob: new Blob(["a,b\n1,2\n"]) };
describe("opening a preview over the page", () => {
test("the overlay mounts a viewer and offers a way out", async () => {
const { window, doc } = page();
const opened = openViewer(csv, { document: doc });
await settled();
const dialog = doc.querySelector(".wrn-viewer-dialog")!;
expect(dialog).not.toBeNull();
expect(dialog.querySelector(".wrn-viewer-content")!.getAttribute("data-viewer-state")).toBe(
"ready",
);
expect(dialog.querySelector('[data-viewer-action="close"]')).not.toBeNull();
opened.close();
expect(doc.querySelector(".wrn-viewer-dialog")).toBeNull();
await window.happyDOM.close();
});
test("closing disposes the viewer rather than leaving it behind the overlay", async () => {
const { window, doc } = page();
const opened = openViewer(csv, { document: doc });
await settled();
const host = doc.querySelector(".wrn-viewer-dialog .wrn-viewer")!;
expect(host.querySelector("table")).not.toBeNull();
opened.close();
// The host went with the dialog, so nothing keeps running off-screen.
expect(host.isConnected).toBe(false);
await window.happyDOM.close();
});
test("closing twice, or from the dialog's own close event, is safe", async () => {
const { window, doc } = page();
let closes = 0;
const opened = openViewer(csv, { document: doc, onClose: () => closes++ });
await settled();
doc.querySelector(".wrn-viewer-dialog")!.dispatchEvent(new window.Event("close") as never);
opened.close();
expect(closes).toBe(1);
await window.happyDOM.close();
});
test("a click on the dialog itself is a backdrop click", async () => {
const { window, doc } = page();
openViewer(csv, { document: doc });
await settled();
const dialog = doc.querySelector(".wrn-viewer-dialog")!;
// A click inside the viewer must not close it.
dialog
.querySelector(".wrn-viewer")!
.dispatchEvent(new window.MouseEvent("click", { bubbles: true }) as never);
expect(doc.querySelector(".wrn-viewer-dialog")).not.toBeNull();
dialog.dispatchEvent(new window.MouseEvent("click", { bubbles: true }) as never);
expect(doc.querySelector(".wrn-viewer-dialog")).toBeNull();
await window.happyDOM.close();
});
});
describe("triggers", () => {
test("a data attribute is read into a source", () => {
const { window, doc } = page();
const button = doc.createElement("button");
button.dataset.viewerOpen = "";
button.dataset.src = "/files/a.pdf";
button.dataset.name = "a.pdf";
button.dataset.size = "120";
expect(sourceFromTrigger(button)).toEqual({
src: "/files/a.pdf",
key: undefined,
store: undefined,
name: "a.pdf",
mime: undefined,
size: 120,
});
// Nothing to open is not an error; the click is simply ignored.
expect(sourceFromTrigger(doc.createElement("button"))).toBeUndefined();
void window.happyDOM.close();
});
test("clicking an element with data-viewer-open opens the preview", async () => {
const { window, doc } = page();
const stop = startViewerTriggers(doc);
const button = doc.createElement("button");
button.dataset.viewerOpen = "";
button.dataset.src = "https://app.test/notes.csv";
button.dataset.name = "notes.csv";
doc.body.append(button);
const original = globalThis.fetch;
globalThis.fetch = (async () =>
new Response("a,b\n1,2\n", { headers: { "content-type": "text/csv" } })) as never;
try {
button.dispatchEvent(new window.MouseEvent("click", { bubbles: true }) as never);
await settled();
expect(doc.querySelector(".wrn-viewer-dialog")).not.toBeNull();
expect(doc.querySelector(".wrn-viewer-dialog table")).not.toBeNull();
} finally {
globalThis.fetch = original;
stop();
}
await window.happyDOM.close();
});
});
describe("layout modes", () => {
test("fullScreen mounts covering the page, and Close returns it inline", async () => {
const { window, doc } = page();
const host = doc.createElement("section");
doc.body.append(host);
const dispose = mountViewer(host as never, csv, { fullScreen: true });
await settled();
expect(host.classList.contains("wrn-viewer-expanded")).toBe(true);
const close = host.querySelector('[data-viewer-action="close"]') as HTMLButtonElement;
expect(close).not.toBeNull();
close.click();
expect(host.classList.contains("wrn-viewer-expanded")).toBe(false);
// The control goes with the state it exits.
expect(host.querySelector('[data-viewer-action="close"]')).toBeNull();
dispose();
await window.happyDOM.close();
});
test("an inline viewer offers no Close, because there is nothing to close", async () => {
const { window, doc } = page();
const host = doc.createElement("section");
doc.body.append(host);
const dispose = mountViewer(host as never, csv);
await settled();
expect(host.querySelector('[data-viewer-action="close"]')).toBeNull();
dispose();
await window.happyDOM.close();
});
test("layout=page drops the card chrome", async () => {
const { window, doc } = page();
const host = doc.createElement("section");
doc.body.append(host);
const dispose = mountViewer(host as never, csv, { layout: "page" });
await settled();
expect(host.classList.contains("wrn-viewer-as-page")).toBe(true);
dispose();
await window.happyDOM.close();
});
test("the runtime re-mounts when the layout attributes change", async () => {
const { window, doc } = page();
const host = doc.createElement("section");
host.dataset.wrnexusRuntime = "viewer";
host.dataset.name = "data.csv";
host.dataset.src = "https://app.test/data.csv";
host.dataset.fullScreen = "false";
doc.body.append(host);
const original = globalThis.fetch;
globalThis.fetch = (async () =>
new Response("a,b\n1,2\n", { headers: { "content-type": "text/csv" } })) as never;
const stop = startViewerRuntime(doc);
try {
await settled();
expect(host.classList.contains("wrn-viewer-expanded")).toBe(false);
// Without this in the remount signature the attribute change is ignored.
host.dataset.fullScreen = "true";
await settled();
expect(host.classList.contains("wrn-viewer-expanded")).toBe(true);
} finally {
globalThis.fetch = original;
stop();
}
await window.happyDOM.close();
});
});