diff options
| author | kj_sh604 | 2026-03-15 16:19:35 -0400 |
|---|---|---|
| committer | kj_sh604 | 2026-03-15 16:19:35 -0400 |
| commit | 6ec259a0e71174651bae95d4628138bf6fd68742 (patch) | |
| tree | 5e33c6a5ec091ecabfcb257fdc7b6a88ed8754ac /packages/excalidraw/frame.test.tsx | |
| parent | 16c8578b15c727f22921f8a80a56ee4d4e7f2272 (diff) | |
refactor: packages/
Diffstat (limited to 'packages/excalidraw/frame.test.tsx')
| -rw-r--r-- | packages/excalidraw/frame.test.tsx | 555 |
1 files changed, 555 insertions, 0 deletions
diff --git a/packages/excalidraw/frame.test.tsx b/packages/excalidraw/frame.test.tsx new file mode 100644 index 0000000..6a5045b --- /dev/null +++ b/packages/excalidraw/frame.test.tsx @@ -0,0 +1,555 @@ +import type { ExcalidrawElement } from "./element/types"; +import { convertToExcalidrawElements, Excalidraw } from "./index"; +import { API } from "./tests/helpers/api"; +import { Keyboard, Pointer } from "./tests/helpers/ui"; +import { getCloneByOrigId, render } from "./tests/test-utils"; + +const { h } = window; +const mouse = new Pointer("mouse"); + +describe("adding elements to frames", () => { + type ElementType = string; + const assertOrder = ( + els: readonly { type: ElementType }[], + order: ElementType[], + ) => { + expect(els.map((el) => el.type)).toEqual(order); + }; + + const reorderElements = <T extends { type: ElementType }>( + els: readonly T[], + order: ElementType[], + ) => { + return order.reduce((acc: T[], el) => { + acc.push(els.find((e) => e.type === el)!); + return acc; + }, []); + }; + + function resizeFrameOverElement( + frame: ExcalidrawElement, + element: ExcalidrawElement, + ) { + mouse.clickAt(0, 0); + mouse.downAt(frame.x + frame.width, frame.y + frame.height); + mouse.moveTo( + element.x + element.width + 50, + element.y + element.height + 50, + ); + mouse.up(); + } + + function dragElementIntoFrame( + frame: ExcalidrawElement, + element: ExcalidrawElement, + ) { + mouse.clickAt(element.x, element.y); + mouse.downAt(element.x + element.width / 2, element.y + element.height / 2); + mouse.moveTo(frame.x + frame.width / 2, frame.y + frame.height / 2); + mouse.up(); + } + + function selectElementAndDuplicate( + element: ExcalidrawElement, + moveTo: [number, number] = [element.x + 25, element.y + 25], + ) { + const [x, y] = [ + element.x + element.width / 2, + element.y + element.height / 2, + ]; + + Keyboard.withModifierKeys({ alt: true }, () => { + mouse.downAt(x, y); + mouse.moveTo(moveTo[0], moveTo[1]); + mouse.up(); + }); + } + + function expectEqualIds(expected: ExcalidrawElement[]) { + expect(h.elements.map((x) => x.id)).toEqual(expected.map((x) => x.id)); + } + + let frame: ExcalidrawElement; + let rect1: ExcalidrawElement; + let rect2: ExcalidrawElement; + let rect3: ExcalidrawElement; + let rect4: ExcalidrawElement; + let text: ExcalidrawElement; + let arrow: ExcalidrawElement; + + beforeEach(async () => { + await render(<Excalidraw />); + + frame = API.createElement({ id: "id0", type: "frame", x: 0, width: 150 }); + rect1 = API.createElement({ + id: "id1", + type: "rectangle", + x: -1000, + }); + rect2 = API.createElement({ + id: "id2", + type: "rectangle", + x: 200, + width: 50, + }); + rect3 = API.createElement({ + id: "id3", + type: "rectangle", + x: 400, + width: 50, + }); + rect4 = API.createElement({ + id: "id4", + type: "rectangle", + x: 1000, + width: 50, + }); + text = API.createElement({ + id: "id5", + type: "text", + x: 100, + }); + arrow = API.createElement({ + id: "id6", + type: "arrow", + x: 100, + boundElements: [{ id: text.id, type: "text" }], + }); + }); + + const commonTestCases = async ( + func: typeof resizeFrameOverElement | typeof dragElementIntoFrame, + ) => { + describe.skip("when frame is in a layer below", async () => { + it("should add an element", async () => { + API.setElements([frame, rect2]); + + func(frame, rect2); + + expect(h.elements[0].frameId).toBe(frame.id); + expectEqualIds([rect2, frame]); + }); + + it("should add elements", async () => { + API.setElements([frame, rect2, rect3]); + + func(frame, rect2); + func(frame, rect3); + + expect(rect2.frameId).toBe(frame.id); + expect(rect3.frameId).toBe(frame.id); + expectEqualIds([rect2, rect3, frame]); + }); + + it("should add elements when there are other other elements in between", async () => { + API.setElements([frame, rect1, rect2, rect4, rect3]); + + func(frame, rect2); + func(frame, rect3); + + expect(rect2.frameId).toBe(frame.id); + expect(rect3.frameId).toBe(frame.id); + expectEqualIds([rect2, rect3, frame, rect1, rect4]); + }); + + it("should add elements when there are other elements in between and the order is reversed", async () => { + API.setElements([frame, rect3, rect4, rect2, rect1]); + + func(frame, rect2); + func(frame, rect3); + + expect(rect2.frameId).toBe(frame.id); + expect(rect3.frameId).toBe(frame.id); + expectEqualIds([rect2, rect3, frame, rect4, rect1]); + }); + }); + + describe.skip("when frame is in a layer above", async () => { + it("should add an element", async () => { + API.setElements([rect2, frame]); + + func(frame, rect2); + + expect(h.elements[0].frameId).toBe(frame.id); + expectEqualIds([rect2, frame]); + }); + + it("should add elements", async () => { + API.setElements([rect2, rect3, frame]); + + func(frame, rect2); + func(frame, rect3); + + expect(rect2.frameId).toBe(frame.id); + expect(rect3.frameId).toBe(frame.id); + expectEqualIds([rect3, rect2, frame]); + }); + + it("should add elements when there are other other elements in between", async () => { + API.setElements([rect1, rect2, rect4, rect3, frame]); + + func(frame, rect2); + func(frame, rect3); + + expect(rect2.frameId).toBe(frame.id); + expect(rect3.frameId).toBe(frame.id); + expectEqualIds([rect1, rect4, rect3, rect2, frame]); + }); + + it("should add elements when there are other elements in between and the order is reversed", async () => { + API.setElements([rect3, rect4, rect2, rect1, frame]); + + func(frame, rect2); + func(frame, rect3); + + expect(rect2.frameId).toBe(frame.id); + expect(rect3.frameId).toBe(frame.id); + expectEqualIds([rect4, rect1, rect3, rect2, frame]); + }); + }); + + describe("when frame is in an inner layer", async () => { + it.skip("should add elements", async () => { + API.setElements([rect2, frame, rect3]); + + func(frame, rect2); + func(frame, rect3); + + expect(rect2.frameId).toBe(frame.id); + expect(rect3.frameId).toBe(frame.id); + expectEqualIds([rect2, rect3, frame]); + }); + + it.skip("should add elements when there are other other elements in between", async () => { + API.setElements([rect2, rect1, frame, rect4, rect3]); + + func(frame, rect2); + func(frame, rect3); + + expect(rect2.frameId).toBe(frame.id); + expect(rect3.frameId).toBe(frame.id); + expectEqualIds([rect1, rect2, rect3, frame, rect4]); + }); + + it.skip("should add elements when there are other elements in between and the order is reversed", async () => { + API.setElements([rect3, rect4, frame, rect2, rect1]); + + func(frame, rect2); + func(frame, rect3); + + expect(rect2.frameId).toBe(frame.id); + expect(rect3.frameId).toBe(frame.id); + expectEqualIds([rect4, rect3, rect2, frame, rect1]); + }); + }); + }; + + const resizingTest = async ( + containerType: "arrow" | "rectangle", + initialOrder: ElementType[], + expectedOrder: ElementType[], + ) => { + await render(<Excalidraw />); + + const frame = API.createElement({ type: "frame", x: 0, y: 0 }); + + API.setElements( + reorderElements( + [ + frame, + ...convertToExcalidrawElements([ + { + type: containerType, + x: 100, + y: 100, + height: 10, + label: { text: "xx" }, + }, + ]), + ], + initialOrder, + ), + ); + + assertOrder(h.elements, initialOrder); + + expect(h.elements[1].frameId).toBe(null); + expect(h.elements[2].frameId).toBe(null); + + const container = h.elements[1]; + + resizeFrameOverElement(frame, container); + assertOrder(h.elements, expectedOrder); + + expect(h.elements[0].frameId).toBe(frame.id); + expect(h.elements[1].frameId).toBe(frame.id); + }; + + describe("resizing frame over elements", async () => { + await commonTestCases(resizeFrameOverElement); + + it.skip("resizing over text containers and labelled arrows", async () => { + await resizingTest( + "rectangle", + ["frame", "rectangle", "text"], + ["rectangle", "text", "frame"], + ); + await resizingTest( + "rectangle", + ["frame", "text", "rectangle"], + ["rectangle", "text", "frame"], + ); + await resizingTest( + "rectangle", + ["rectangle", "text", "frame"], + ["rectangle", "text", "frame"], + ); + await resizingTest( + "rectangle", + ["text", "rectangle", "frame"], + ["rectangle", "text", "frame"], + ); + await resizingTest( + "arrow", + ["frame", "arrow", "text"], + ["arrow", "text", "frame"], + ); + await resizingTest( + "arrow", + ["text", "arrow", "frame"], + ["arrow", "text", "frame"], + ); + await resizingTest( + "arrow", + ["frame", "arrow", "text"], + ["arrow", "text", "frame"], + ); + + // FIXME failing in tests (it fails to add elements to frame for some + // reason) but works in browser. (╯°□°)╯︵ ┻━┻ + // + // Looks like the `getElementsCompletelyInFrame()` doesn't work + // in these cases. + // + // await testElements( + // "arrow", + // ["arrow", "text", "frame"], + // ["arrow", "text", "frame"], + // ); + }); + + it.skip("should add arrow bound with text when frame is in a layer below", async () => { + API.setElements([frame, arrow, text]); + + resizeFrameOverElement(frame, arrow); + + expect(arrow.frameId).toBe(frame.id); + expect(text.frameId).toBe(frame.id); + expectEqualIds([arrow, text, frame]); + }); + + it("should add arrow bound with text when frame is in a layer above", async () => { + API.setElements([arrow, text, frame]); + + resizeFrameOverElement(frame, arrow); + + expect(arrow.frameId).toBe(frame.id); + expect(text.frameId).toBe(frame.id); + expectEqualIds([arrow, text, frame]); + }); + + it.skip("should add arrow bound with text when frame is in an inner layer", async () => { + API.setElements([arrow, frame, text]); + + resizeFrameOverElement(frame, arrow); + + expect(arrow.frameId).toBe(frame.id); + expect(text.frameId).toBe(frame.id); + expectEqualIds([arrow, text, frame]); + }); + }); + + describe("resizing frame over elements but downwards", async () => { + it.skip("should add elements when frame is in a layer below", async () => { + API.setElements([frame, rect1, rect2, rect3, rect4]); + + resizeFrameOverElement(frame, rect4); + resizeFrameOverElement(frame, rect3); + + expect(rect2.frameId).toBe(frame.id); + expect(rect3.frameId).toBe(frame.id); + expectEqualIds([rect2, rect3, frame, rect4, rect1]); + }); + + it.skip("should add elements when frame is in a layer above", async () => { + API.setElements([rect1, rect2, rect3, rect4, frame]); + + resizeFrameOverElement(frame, rect4); + resizeFrameOverElement(frame, rect3); + + expect(rect2.frameId).toBe(frame.id); + expect(rect3.frameId).toBe(frame.id); + expectEqualIds([rect1, rect2, rect3, frame, rect4]); + }); + + it.skip("should add elements when frame is in an inner layer", async () => { + API.setElements([rect1, rect2, frame, rect3, rect4]); + + resizeFrameOverElement(frame, rect4); + resizeFrameOverElement(frame, rect3); + + expect(rect2.frameId).toBe(frame.id); + expect(rect3.frameId).toBe(frame.id); + expectEqualIds([rect1, rect2, rect3, frame, rect4]); + }); + }); + + describe("dragging elements into the frame", async () => { + await commonTestCases(dragElementIntoFrame); + + it.skip("should drag element inside, duplicate it and keep it in frame", () => { + API.setElements([frame, rect2]); + + dragElementIntoFrame(frame, rect2); + + selectElementAndDuplicate(rect2); + + const rect2_copy = getCloneByOrigId(rect2.id); + + expect(rect2_copy.frameId).toBe(frame.id); + expect(rect2.frameId).toBe(frame.id); + expectEqualIds([rect2_copy, rect2, frame]); + }); + + it.skip("should drag element inside, duplicate it and remove it from frame", () => { + API.setElements([frame, rect2]); + + dragElementIntoFrame(frame, rect2); + + // move the rect2 outside the frame + selectElementAndDuplicate(rect2, [-1000, -1000]); + + const rect2_copy = getCloneByOrigId(rect2.id); + + expect(rect2_copy.frameId).toBe(frame.id); + expect(rect2.frameId).toBe(null); + expectEqualIds([rect2_copy, frame, rect2]); + }); + + it("random order 01", () => { + const frame1 = API.createElement({ + type: "frame", + x: 0, + y: 0, + width: 100, + height: 100, + }); + const frame2 = API.createElement({ + type: "frame", + x: 200, + y: 0, + width: 100, + height: 100, + }); + const frame3 = API.createElement({ + type: "frame", + x: 300, + y: 0, + width: 100, + height: 100, + }); + + const rectangle1 = API.createElement({ + type: "rectangle", + x: 25, + y: 25, + width: 50, + height: 50, + frameId: frame1.id, + }); + const rectangle2 = API.createElement({ + type: "rectangle", + x: 225, + y: 25, + width: 50, + height: 50, + frameId: frame2.id, + }); + const rectangle3 = API.createElement({ + type: "rectangle", + x: 325, + y: 25, + width: 50, + height: 50, + frameId: frame3.id, + }); + const rectangle4 = API.createElement({ + type: "rectangle", + x: 350, + y: 25, + width: 50, + height: 50, + frameId: frame3.id, + }); + + API.setElements([ + frame1, + rectangle4, + rectangle1, + rectangle3, + frame3, + rectangle2, + frame2, + ]); + + API.setSelectedElements([rectangle2]); + + const origSize = h.elements.length; + + expect(h.elements.length).toBe(origSize); + dragElementIntoFrame(frame3, rectangle2); + expect(h.elements.length).toBe(origSize); + }); + + it("random order 02", () => { + const frame1 = API.createElement({ + type: "frame", + x: 0, + y: 0, + width: 100, + height: 100, + }); + const frame2 = API.createElement({ + type: "frame", + x: 200, + y: 0, + width: 100, + height: 100, + }); + const rectangle1 = API.createElement({ + type: "rectangle", + x: 25, + y: 25, + width: 50, + height: 50, + frameId: frame1.id, + }); + const rectangle2 = API.createElement({ + type: "rectangle", + x: 225, + y: 25, + width: 50, + height: 50, + frameId: frame2.id, + }); + + API.setElements([rectangle1, rectangle2, frame1, frame2]); + + API.setSelectedElements([rectangle2]); + + expect(h.elements.length).toBe(4); + dragElementIntoFrame(frame2, rectangle1); + expect(h.elements.length).toBe(4); + }); + }); +}); |
