|  | @@ -2,8 +2,12 @@ import { render, waitFor } from "./test-utils";
 | 
	
		
			
				|  |  |  import ExcalidrawApp from "../excalidraw-app";
 | 
	
		
			
				|  |  |  import { API } from "./helpers/api";
 | 
	
		
			
				|  |  |  import { MIME_TYPES } from "../constants";
 | 
	
		
			
				|  |  | -import { LibraryItem } from "../types";
 | 
	
		
			
				|  |  | +import { LibraryItem, LibraryItems } from "../types";
 | 
	
		
			
				|  |  |  import { UI } from "./helpers/ui";
 | 
	
		
			
				|  |  | +import { serializeLibraryAsJSON } from "../data/json";
 | 
	
		
			
				|  |  | +import { distributeLibraryItemsOnSquareGrid } from "../data/library";
 | 
	
		
			
				|  |  | +import { ExcalidrawGenericElement } from "../element/types";
 | 
	
		
			
				|  |  | +import { getCommonBoundingBox } from "../element/bounds";
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  const { h } = window;
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -37,7 +41,7 @@ describe("library", () => {
 | 
	
		
			
				|  |  |        await API.readFile("./fixtures/fixture_library.excalidrawlib", "utf8"),
 | 
	
		
			
				|  |  |      ).library[0];
 | 
	
		
			
				|  |  |      await API.drop(
 | 
	
		
			
				|  |  | -      new Blob([JSON.stringify(libraryItems)], {
 | 
	
		
			
				|  |  | +      new Blob([serializeLibraryAsJSON([libraryItems])], {
 | 
	
		
			
				|  |  |          type: MIME_TYPES.excalidrawlib,
 | 
	
		
			
				|  |  |        }),
 | 
	
		
			
				|  |  |      );
 | 
	
	
		
			
				|  | @@ -53,7 +57,7 @@ describe("library", () => {
 | 
	
		
			
				|  |  |        await API.readFile("./fixtures/fixture_library.excalidrawlib", "utf8"),
 | 
	
		
			
				|  |  |      ).library[0];
 | 
	
		
			
				|  |  |      await API.drop(
 | 
	
		
			
				|  |  | -      new Blob([JSON.stringify(libraryItems)], {
 | 
	
		
			
				|  |  | +      new Blob([serializeLibraryAsJSON([libraryItems])], {
 | 
	
		
			
				|  |  |          type: MIME_TYPES.excalidrawlib,
 | 
	
		
			
				|  |  |        }),
 | 
	
		
			
				|  |  |      );
 | 
	
	
		
			
				|  | @@ -63,3 +67,111 @@ describe("library", () => {
 | 
	
		
			
				|  |  |      expect(h.state.activeTool.type).toBe("selection");
 | 
	
		
			
				|  |  |    });
 | 
	
		
			
				|  |  |  });
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +describe("distributeLibraryItemsOnSquareGrid()", () => {
 | 
	
		
			
				|  |  | +  it("should distribute items on a grid", async () => {
 | 
	
		
			
				|  |  | +    const createLibraryItem = (
 | 
	
		
			
				|  |  | +      elements: ExcalidrawGenericElement[],
 | 
	
		
			
				|  |  | +    ): LibraryItem => {
 | 
	
		
			
				|  |  | +      return {
 | 
	
		
			
				|  |  | +        id: `id-${Date.now()}`,
 | 
	
		
			
				|  |  | +        elements,
 | 
	
		
			
				|  |  | +        status: "unpublished",
 | 
	
		
			
				|  |  | +        created: Date.now(),
 | 
	
		
			
				|  |  | +      };
 | 
	
		
			
				|  |  | +    };
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    const PADDING = 50;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    const el1 = API.createElement({
 | 
	
		
			
				|  |  | +      id: "id1",
 | 
	
		
			
				|  |  | +      width: 100,
 | 
	
		
			
				|  |  | +      height: 100,
 | 
	
		
			
				|  |  | +      x: 0,
 | 
	
		
			
				|  |  | +      y: 0,
 | 
	
		
			
				|  |  | +    });
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    const el2 = API.createElement({
 | 
	
		
			
				|  |  | +      id: "id2",
 | 
	
		
			
				|  |  | +      width: 100,
 | 
	
		
			
				|  |  | +      height: 80,
 | 
	
		
			
				|  |  | +      x: -100,
 | 
	
		
			
				|  |  | +      y: -50,
 | 
	
		
			
				|  |  | +    });
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    const el3 = API.createElement({
 | 
	
		
			
				|  |  | +      id: "id3",
 | 
	
		
			
				|  |  | +      width: 40,
 | 
	
		
			
				|  |  | +      height: 50,
 | 
	
		
			
				|  |  | +      x: -100,
 | 
	
		
			
				|  |  | +      y: -50,
 | 
	
		
			
				|  |  | +    });
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    const el4 = API.createElement({
 | 
	
		
			
				|  |  | +      id: "id4",
 | 
	
		
			
				|  |  | +      width: 50,
 | 
	
		
			
				|  |  | +      height: 50,
 | 
	
		
			
				|  |  | +      x: 0,
 | 
	
		
			
				|  |  | +      y: 0,
 | 
	
		
			
				|  |  | +    });
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    const el5 = API.createElement({
 | 
	
		
			
				|  |  | +      id: "id5",
 | 
	
		
			
				|  |  | +      width: 70,
 | 
	
		
			
				|  |  | +      height: 100,
 | 
	
		
			
				|  |  | +      x: 40,
 | 
	
		
			
				|  |  | +      y: 0,
 | 
	
		
			
				|  |  | +    });
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    const libraryItems: LibraryItems = [
 | 
	
		
			
				|  |  | +      createLibraryItem([el1]),
 | 
	
		
			
				|  |  | +      createLibraryItem([el2]),
 | 
	
		
			
				|  |  | +      createLibraryItem([el3]),
 | 
	
		
			
				|  |  | +      createLibraryItem([el4, el5]),
 | 
	
		
			
				|  |  | +    ];
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    const distributed = distributeLibraryItemsOnSquareGrid(libraryItems);
 | 
	
		
			
				|  |  | +    // assert the returned library items are flattened to elements
 | 
	
		
			
				|  |  | +    expect(distributed.length).toEqual(
 | 
	
		
			
				|  |  | +      libraryItems.map((x) => x.elements).flat().length,
 | 
	
		
			
				|  |  | +    );
 | 
	
		
			
				|  |  | +    expect(distributed).toEqual(
 | 
	
		
			
				|  |  | +      expect.arrayContaining([
 | 
	
		
			
				|  |  | +        expect.objectContaining({
 | 
	
		
			
				|  |  | +          id: el1.id,
 | 
	
		
			
				|  |  | +          x: 0,
 | 
	
		
			
				|  |  | +          y: 0,
 | 
	
		
			
				|  |  | +        }),
 | 
	
		
			
				|  |  | +        expect.objectContaining({
 | 
	
		
			
				|  |  | +          id: el2.id,
 | 
	
		
			
				|  |  | +          x:
 | 
	
		
			
				|  |  | +            el1.width +
 | 
	
		
			
				|  |  | +            PADDING +
 | 
	
		
			
				|  |  | +            (getCommonBoundingBox([el4, el5]).width - el2.width) / 2,
 | 
	
		
			
				|  |  | +          y: Math.abs(el1.height - el2.height) / 2,
 | 
	
		
			
				|  |  | +        }),
 | 
	
		
			
				|  |  | +        expect.objectContaining({
 | 
	
		
			
				|  |  | +          id: el3.id,
 | 
	
		
			
				|  |  | +          x: Math.abs(el1.width - el3.width) / 2,
 | 
	
		
			
				|  |  | +          y:
 | 
	
		
			
				|  |  | +            Math.max(el1.height, el2.height) +
 | 
	
		
			
				|  |  | +            PADDING +
 | 
	
		
			
				|  |  | +            Math.abs(el3.height - Math.max(el4.height, el5.height)) / 2,
 | 
	
		
			
				|  |  | +        }),
 | 
	
		
			
				|  |  | +        expect.objectContaining({
 | 
	
		
			
				|  |  | +          id: el4.id,
 | 
	
		
			
				|  |  | +          x: Math.max(el1.width, el2.width) + PADDING,
 | 
	
		
			
				|  |  | +          y: Math.max(el1.height, el2.height) + PADDING,
 | 
	
		
			
				|  |  | +        }),
 | 
	
		
			
				|  |  | +        expect.objectContaining({
 | 
	
		
			
				|  |  | +          id: el5.id,
 | 
	
		
			
				|  |  | +          x: Math.max(el1.width, el2.width) + PADDING + Math.abs(el5.x - el4.x),
 | 
	
		
			
				|  |  | +          y:
 | 
	
		
			
				|  |  | +            Math.max(el1.height, el2.height) +
 | 
	
		
			
				|  |  | +            PADDING +
 | 
	
		
			
				|  |  | +            Math.abs(el5.y - el4.y),
 | 
	
		
			
				|  |  | +        }),
 | 
	
		
			
				|  |  | +      ]),
 | 
	
		
			
				|  |  | +    );
 | 
	
		
			
				|  |  | +  });
 | 
	
		
			
				|  |  | +});
 |