Skip to content
Source
Sign inSign up

BrowserDocumentFrame.browser.test.tsx

166 lines6.9 KB

1import { injectHtmlRenderBootstrap } from "@t3tools/shared/htmlRender";
2import { describe, expect, it, vi } from "vite-plus/test";
3import { page } from "vite-plus/test/browser";
4​
5import "~/index.css";
6import { render } from "../../../test/render";
7​
8import { HtmlRenderDocument } from "./BrowserDocumentFrame";
9​
10const sizeChanged = (height: number) => ({
11 jsonrpc: "2.0",
12 method: "ui/notifications/size-changed",
13 params: { height },
14});
15​
16describe("HtmlRenderDocument", () => {
17 it("reports the page's first height and ignores messages from other windows", async () => {
18 using pageUrl = (() => {
19 const page = `<script>parent.postMessage(${JSON.stringify(sizeChanged(321))}, "*");</script>`;
20 const url = URL.createObjectURL(new Blob([page], { type: "text/html" }));
21 return { url, [Symbol.dispose]: () => URL.revokeObjectURL(url) };
22 })();
23 const onContentHeight = vi.fn();
24 await using _screen = await render(
25 <HtmlRenderDocument src={pageUrl.url} title="Report" onContentHeight={onContentHeight} />,
26 );
27​
28 await vi.waitFor(() => expect(onContentHeight).toHaveBeenCalledWith(321));
29​
30 // Listeners run in registration order, so once this one sees the message the frame's has too.
31 const delivered = new Promise<void>((resolve) =>
32 window.addEventListener("message", () => resolve(), { once: true }),
33 );
34 window.postMessage(sizeChanged(999), "*");
35 await delivered;
36 expect(onContentHeight).toHaveBeenCalledTimes(1);
37 });
38​
39 it("opens a clicked image with the other images of its block in the viewer", async () => {
40 const swatch = (color: string) =>
41 `data:image/svg+xml,${encodeURIComponent(
42 `<svg xmlns="http://www.w3.org/2000/svg" width="80" height="50"><rect width="80" height="50" fill="${color}"/></svg>`,
43 )}`;
44 const html = injectHtmlRenderBootstrap(
45 `<!doctype html><html><body><div>${["Red", "Green", "Blue"]
46 .map((name) => `<img alt="${name}" src="${swatch(name.toLowerCase())}">`)
47 .join("")}</div></body></html>`,
48 );
49 const src = URL.createObjectURL(new Blob([html], { type: "text/html" }));
50 using _url = { [Symbol.dispose]: () => URL.revokeObjectURL(src) };
51 await using screen = await render(<HtmlRenderDocument src={src} title="Swatches" />);
52​
53 await page.frameLocator(screen.getByTitle("Swatches")).getByAltText("Green").click();
54​
55 const viewer = page.getByRole("dialog", { name: "Expanded image preview" });
56 await expect.element(viewer.getByAltText("Green")).toBeVisible();
57 await expect.element(viewer.getByRole("button", { name: "Next media" })).toBeVisible();
58 await expect.element(viewer.getByRole("button", { name: "Previous media" })).toBeVisible();
59 });
60});
61​
62it("keeps the first and last selected card rings inside the fitted renderer frame", async () => {
63 const html = injectHtmlRenderBootstrap(`<!doctype html><html><head><style>
64 main { display: grid; gap: 12px; }
65 button { box-sizing: border-box; width: 100%; height: 72px; border: 0; }
66 button[aria-pressed="true"] { outline: 3px solid blue; outline-offset: 2px; }
67 </style></head><body><main>
68 <button id="first" aria-pressed="false">Meadow</button>
69 <button id="middle" aria-pressed="false">Harbor</button>
70 <button id="last" aria-pressed="false">Orchid</button>
71 </main><script>
72 document.querySelectorAll("button").forEach(function(button) {
73 button.addEventListener("click", function() {
74 document.querySelectorAll("button").forEach(function(card) {
75 card.setAttribute("aria-pressed", String(card === button));
76 });
77 });
78 });
79 window.addEventListener("message", function(event) {
80 if (event.source !== window.parent || event.data.type !== "select-card") return;
81 var button = document.getElementById(event.data.id);
82 button.click();
83 var rect = button.getBoundingClientRect();
84 var style = getComputedStyle(button);
85 var ring = parseFloat(style.outlineWidth) + parseFloat(style.outlineOffset);
86 window.parent.postMessage({
87 type: "selected-card-bounds",
88 id: button.id,
89 selected: button.getAttribute("aria-pressed") === "true",
90 ring: ring,
91 left: rect.left - ring,
92 right: rect.right + ring,
93 top: rect.top - ring,
94 bottom: rect.bottom + ring,
95 pageHeight: document.documentElement.getBoundingClientRect().height,
96 viewportHeight: window.innerHeight,
97 viewportWidth: window.innerWidth,
98 scrollWidth: document.documentElement.scrollWidth
99 }, "*");
100 });
101 </script></body></html>`);
102 const src = URL.createObjectURL(new Blob([html], { type: "text/html" }));
103 using _url = { [Symbol.dispose]: () => URL.revokeObjectURL(src) };
104 const container = document.createElement("div");
105 container.style.width = "728px";
106 const reports = new Map<
107 string,
108 {
109 selected: boolean;
110 ring: number;
111 left: number;
112 right: number;
113 top: number;
114 bottom: number;
115 pageHeight: number;
116 viewportHeight: number;
117 viewportWidth: number;
118 scrollWidth: number;
119 }
120 >();
121 const messages = new AbortController();
122 using _messages = { [Symbol.dispose]: () => messages.abort() };
123 window.addEventListener(
124 "message",
125 (event) => {
126 const frame = container.querySelector("iframe");
127 if (event.source === frame?.contentWindow && event.data?.type === "selected-card-bounds") {
128 reports.set(event.data.id, event.data);
129 }
130 },
131 { signal: messages.signal },
132 );
133 const contentHeight = vi.fn((height: number) => {
134 container.style.setProperty("--content-height", `${height}px`);
135 });
136 await using screen = await render(
137 <>
138 <style>{`iframe[title="Edge cards"] {
139 display: block; width: 100%; height: var(--content-height, 120px); border: 0;
140 }`}</style>
141 <HtmlRenderDocument src={src} title="Edge cards" onContentHeight={contentHeight} />
142 </>,
143 { container },
144 );
145​
146 await vi.waitFor(() => expect(contentHeight).toHaveBeenCalled());
147 const frame = screen.getByTitle("Edge cards").element() as HTMLIFrameElement;
148 await vi.waitFor(() => {
149 expect(frame.getBoundingClientRect().height).toBe(contentHeight.mock.lastCall?.[0]);
150 });
151​
152 for (const id of ["first", "last"]) {
153 frame.contentWindow?.postMessage({ type: "select-card", id }, "*");
154 await vi.waitFor(() => expect(reports.get(id)?.selected).toBe(true));
155 const bounds = reports.get(id)!;
156 expect(bounds.ring).toBe(5);
157 expect(bounds.viewportWidth).toBe(728);
158 expect(bounds.pageHeight).toBe(contentHeight.mock.lastCall?.[0]);
159 expect(bounds.viewportHeight).toBe(bounds.pageHeight);
160 expect(bounds.left).toBeGreaterThanOrEqual(0);
161 expect(bounds.top).toBeGreaterThanOrEqual(0);
162 expect(bounds.right).toBeLessThanOrEqual(bounds.viewportWidth);
163 expect(bounds.bottom).toBeLessThanOrEqual(bounds.viewportHeight);
164 expect(bounds.scrollWidth).toBeLessThanOrEqual(bounds.viewportWidth);
165 }
166});