feat: [cont.] support inserting multiple images (#9875)
* feat: support inserting multiple images * Initial * handleAppOnDrop, onImageToolbarButtonClick, pasteFromClipboard * Initial get history working * insertMultipleImages -> insertImages * Bug fixes, improvements * Remove redundant branch * Refactor addElementsFromMixedContentPaste * History, drag & drop bug fixes * Update snapshots * Remove redundant try-catch * Refactor pasteFromClipboard * Plain paste check in mermaid paste * Move comment * processClipboardData -> insertClipboardContent * Redundant variable * Redundant variable * Refactor insertImages * createImagePlaceholder -> newImagePlaceholder * Get rid of unneeded NEVER schedule, filter out failed images * Trigger CI * Position placeholders before initializing * Don't mutate scene with positionElementsOnGrid, captureUpdate: CaptureUpdateAction.IMMEDIATELY * Comment * Move positionOnGrid out of file * Rename file * Get rid of generic * Initial tests * More asserts, test paste * Test image tool * De-duplicate * Stricter assert, move rest of logic outside of waitFor * Modify history tests * De-duplicate update snapshots * Trigger CI * Fix package build * Make setupImageTest more explicit * Re-introduce generic to use latest placeholder versions * newElementWith instead of mutateElement to delete failed placeholder * Insert failed images separately with CaptureUpdateAction.NEVER * Refactor * Don't re-order elements * WIP * Get rid of 'never' for failed * refactor type check * align max file size constant * make grid padding scale to zoom --------- Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
import { 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";
|
||||
|
||||
const { h } = window;
|
||||
|
||||
export const setupImageTest = async (
|
||||
sizes: { width: number; height: number }[],
|
||||
) => {
|
||||
await render(<Excalidraw autoFocus={true} handleKeyboardGlobally={true} />);
|
||||
|
||||
h.state.height = 1000;
|
||||
|
||||
mockMultipleHTMLImageElements(sizes.map((size) => [size.width, size.height]));
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
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.clickTool("image");
|
||||
|
||||
await assert();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user