Ship file viewer, editor improvements, and private package releases
This commit is contained in:
1 parent
645914955c
commit
a4a31032b9
158 files changed
+10523
-137
No files matched your search
@@ -0,0 +1,182 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user