import { MIME_TYPES, randomId, reseed } from "@excalidraw/common"; import type { FileId } from "@excalidraw/element/types"; import * as blobModule from "../data/blob"; import * as filesystemModule from "../data/filesystem"; import { Excalidraw } from "../index"; import { createPasteEvent } from "../clipboard"; import { API } from "./helpers/api"; import { mockMultipleHTMLImageElements } from "./helpers/mocks"; import { UI } from "./helpers/ui"; import { GlobalTestState, render, waitFor } from "./test-utils"; import { DEER_IMAGE_DIMENSIONS, SMILEY_IMAGE_DIMENSIONS, } from "./fixtures/constants"; import { INITIALIZED_IMAGE_PROPS } from "./helpers/constants"; import type { ExcalidrawProps } from "../types"; const { h } = window; export const setupImageTest = async ( sizes: { width: number; height: number }[], props?: ExcalidrawProps, ) => { await render( , ); h.state.height = 1000; mockMultipleHTMLImageElements(sizes.map((size) => [size.width, size.height])); }; describe("resizeImageFile", () => { beforeEach(() => { vi.unstubAllGlobals(); }); it("returns the original file when it already fits the max dimensions", async () => { mockMultipleHTMLImageElements([[100, 100]]); const imageFile = new File([new Uint8Array([1, 2, 3])], "image.png", { type: MIME_TYPES.png, }); await expect( blobModule.resizeImageFile(imageFile, { maxWidthOrHeight: 200 }), ).resolves.toBe(imageFile); }); }); describe("image insertion", () => { beforeEach(() => { vi.clearAllMocks(); vi.unstubAllGlobals(); reseed(7); const generateIdSpy = vi.spyOn(blobModule, "generateIdFromFile"); const resizeFileSpy = vi.spyOn(blobModule, "resizeImageFile"); generateIdSpy.mockImplementation(() => Promise.resolve(randomId() as FileId), ); resizeFileSpy.mockImplementation((file: File) => Promise.resolve(file)); Object.assign(document, { elementFromPoint: () => GlobalTestState.canvas, }); }); const setup = () => setupImageTest([DEER_IMAGE_DIMENSIONS, SMILEY_IMAGE_DIMENSIONS]); const assert = async () => { await waitFor(() => { expect(h.elements).toEqual([ expect.objectContaining({ ...INITIALIZED_IMAGE_PROPS, ...DEER_IMAGE_DIMENSIONS, }), expect.objectContaining({ ...INITIALIZED_IMAGE_PROPS, ...SMILEY_IMAGE_DIMENSIONS, }), ]); }); // Not placed on top of each other const dimensionsSet = new Set(h.elements.map((el) => `${el.x}-${el.y}`)); expect(dimensionsSet.size).toEqual(h.elements.length); }; it("should eventually initialize all dropped images", async () => { await setup(); const files = await Promise.all([ API.loadFile("./fixtures/deer.png"), API.loadFile("./fixtures/smiley.png"), ]); await API.drop(files.map((file) => ({ kind: "file", file }))); await assert(); }); it("should eventually initialize all pasted images", async () => { await setup(); document.dispatchEvent( createPasteEvent({ files: await Promise.all([ API.loadFile("./fixtures/deer.png"), API.loadFile("./fixtures/smiley.png"), ]), }), ); await assert(); }); it("should eventually initialize all images added through image tool", async () => { await setup(); const fileOpenSpy = vi.spyOn(filesystemModule, "fileOpen"); fileOpenSpy.mockImplementation( async () => await Promise.all([ API.loadFile("./fixtures/deer.png"), API.loadFile("./fixtures/smiley.png"), ]), ); UI.clickExtraTool("image"); await assert(); }); it("passes host-configured max image dimensions to the resize helper", async () => { await setupImageTest([DEER_IMAGE_DIMENSIONS], { imageOptions: { maxWidthOrHeight: 2048 }, }); await API.drop([ { kind: "file", file: await API.loadFile("./fixtures/deer.png") }, ]); await waitFor(() => { expect(blobModule.resizeImageFile).toHaveBeenCalledWith( expect.any(File), { maxWidthOrHeight: 2048 }, ); }); }); it("enforces host-configured max image file size", async () => { await setupImageTest([DEER_IMAGE_DIMENSIONS], { imageOptions: { maxFileSizeBytes: 1024 * 1024 }, }); await API.drop([ { kind: "file", file: new File([new Uint8Array(2 * 1024 * 1024)], "image.png", { type: MIME_TYPES.png, }), }, ]); await waitFor(() => { expect(h.state.errorMessage).toBe( "File is too big. Maximum allowed size is 1MB.", ); }); }); });