Skip to content
File

Blob: tests/e2e/specs/30-image-brightness.spec.ts

typescript141 lines
1import * as Y from "yjs";
2import {
3 test,
4 expect,
5 createTestPage,
6 createTestWorkspace,
7 createShareLink,
8 waitForDocEditorReady,
9} from "../fixtures/bland-test";
10 
11test("image brightness supports keyboard input, reset, persistence, sharing, and publishing", async ({
12 authenticatedPage: { page, accessToken },
13 e2eContext,
14 browser,
15}) => {
16 test.setTimeout(120_000);
17 const workspace = await createTestWorkspace(page, accessToken, "Image Brightness");
18 const testPage = await createTestPage(page, accessToken, "Image Brightness", workspace);
19 await page.goto(`/${testPage.workspaceSlug}/${testPage.pageId}`);
20 const editor = await waitForDocEditorReady(page, { editable: true, connected: true });
21 await editor.click();
22 await editor.evaluate(async (element) => {
23 const canvas = document.createElement("canvas");
24 canvas.width = 640;
25 canvas.height = 400;
26 const context = canvas.getContext("2d")!;
27 context.fillStyle = "#f4f2eb";
28 context.fillRect(0, 0, canvas.width, canvas.height);
29 context.fillStyle = "#243129";
30 context.font = "32px sans-serif";
31 context.fillText("Image brightness", 40, 80);
32 const blob = await new Promise<Blob>((resolve, reject) => {
33 canvas.toBlob((result) => (result ? resolve(result) : reject(new Error("PNG fixture failed"))), "image/png");
34 });
35 const clipboard = new DataTransfer();
36 clipboard.items.add(new File([blob], "first.png", { type: "image/png" }));
37 clipboard.items.add(new File([blob], "second.png", { type: "image/png" }));
38 element.dispatchEvent(new ClipboardEvent("paste", { bubbles: true, cancelable: true, clipboardData: clipboard }));
39 });
40 
41 const images = editor.locator("img.tiptap-image");
42 await expect(images).toHaveCount(2);
43 await expect(images.first()).toBeVisible();
44 await expect(images.last()).toBeVisible();
45 const originalSrc = await images.first().getAttribute("src");
46 await images.first().click();
47 const slider = page.getByRole("slider", { name: "Image brightness" });
48 await expect(slider).toHaveValue("100");
49 await slider.focus();
50 await slider.press("ArrowLeft");
51 await expect(slider).toHaveValue("99");
52 await expect(slider).toBeFocused();
53 await expect(images.first()).toHaveCSS("filter", "brightness(0.99)");
54 
55 const reset = page.getByRole("button", { name: "Reset brightness" });
56 await reset.focus();
57 await reset.press("Enter");
58 await expect(slider).toHaveValue("100");
59 await expect(images.first()).toHaveCSS("filter", "none");
60 
61 await page.setViewportSize({ width: 390, height: 844 });
62 await images.first().scrollIntoViewIfNeeded();
63 await images.first().click();
64 await slider.focus();
65 for (let step = 0; step < 25; step++) await slider.press("ArrowLeft");
66 await expect(slider).toHaveValue("75");
67 await expect(images.first()).toHaveCSS("filter", "brightness(0.75)");
68 await expect(images.last()).toHaveCSS("filter", "none");
69 await expect(images.first()).toHaveAttribute("src", originalSrc!);
70 const bounds = await page.locator(".tiptap-image-toolbar").boundingBox();
71 expect(bounds).not.toBeNull();
72 expect(bounds!.x).toBeGreaterThanOrEqual(0);
73 expect(bounds!.x + bounds!.width).toBeLessThanOrEqual(390);
74 expect(bounds!.y).toBeGreaterThanOrEqual(0);
75 expect(bounds!.y + bounds!.height).toBeLessThanOrEqual(844);
76 await page.screenshot({ path: test.info().outputPath("image-brightness-mobile.png") });
77 
78 const headers = { Authorization: `Bearer ${accessToken}` };
79 await expect
80 .poll(
81 async () => {
82 const response = await page.request.get(
83 `/api/v1/workspaces/${workspace.workspaceId}/pages/${testPage.pageId}/snapshot`,
84 { headers },
85 );
86 if (response.status() !== 200) return [];
87 const document = new Y.Doc();
88 try {
89 Y.applyUpdate(document, new Uint8Array(await response.body()));
90 return document
91 .getXmlFragment("document-store")
92 .toArray()
93 .filter((node): node is Y.XmlElement => node instanceof Y.XmlElement && node.nodeName === "image")
94 .map((node) => Number(node.getAttribute("brightness") ?? 100));
95 } finally {
96 document.destroy();
97 }
98 },
99 { timeout: 15_000 },
100 )
101 .toEqual([75, 100]);
102 
103 await page.reload();
104 await waitForDocEditorReady(page, { editable: true, connected: true });
105 await expect(images.first()).toHaveCSS("filter", "brightness(0.75)");
106 await expect(images.last()).toHaveCSS("filter", "none");
107 
108 const share = await createShareLink(page, accessToken, testPage.pageId, "view");
109 const anonymous = await browser.newContext();
110 try {
111 const viewer = await anonymous.newPage();
112 await viewer.goto(`${e2eContext.baseUrl}/s/${share.token}`);
113 await waitForDocEditorReady(viewer, { editable: false });
114 await expect(viewer.locator(".tiptap img.tiptap-image").first()).toHaveCSS("filter", "brightness(0.75)");
115 await expect(viewer.getByRole("slider", { name: "Image brightness" })).toHaveCount(0);
116 
117 const slug = `brightness-${Date.now().toString(36)}`;
118 const site = await page.request.patch(`/api/v1/workspaces/${workspace.workspaceId}/site`, {
119 headers,
120 data: { slug, published: true },
121 });
122 expect(site.ok()).toBe(true);
123 const publish = await page.request.post(
124 `/api/v1/workspaces/${workspace.workspaceId}/site/pages/${testPage.pageId}`,
125 { headers },
126 );
127 expect(publish.ok()).toBe(true);
128 // Allow the save projection to pass through the configured five-second queue batch.
129 await page.waitForTimeout(6500);
130 const { port } = new URL(e2eContext.baseUrl);
131 const publicUrl = `http://${slug}.bland.localhost${port ? `:${port}` : ""}/image-brightness-${testPage.pageId.toLowerCase()}`;
132 const response = await viewer.goto(publicUrl);
133 expect(response?.status()).toBe(200);
134 await expect(viewer.locator(".tiptap img.tiptap-image").first()).toHaveCSS("filter", "brightness(0.75)");
135 await expect(viewer.locator(".tiptap img.tiptap-image").last()).toHaveCSS("filter", "none");
136 await expect(viewer.getByRole("slider", { name: "Image brightness" })).toHaveCount(0);
137 } finally {
138 await anonymous.close();
139 }
140});