diff --git a/packages/excalidraw/actions/actionDeleteSelected.test.tsx b/packages/excalidraw/actions/actionDeleteSelected.test.tsx new file mode 100644 index 0000000000..d48646fac7 --- /dev/null +++ b/packages/excalidraw/actions/actionDeleteSelected.test.tsx @@ -0,0 +1,211 @@ +import React from "react"; +import { Excalidraw, mutateElement } from "../index"; +import { act, assertElements, render } from "../tests/test-utils"; +import { API } from "../tests/helpers/api"; +import { actionDeleteSelected } from "./actionDeleteSelected"; + +const { h } = window; + +describe("deleting selected elements when frame selected should keep children + select them", () => { + beforeEach(async () => { + await render(); + }); + + it("frame only", async () => { + const f1 = API.createElement({ + type: "frame", + }); + + const r1 = API.createElement({ + type: "rectangle", + frameId: f1.id, + }); + + API.setElements([f1, r1]); + + API.setSelectedElements([f1]); + + act(() => { + h.app.actionManager.executeAction(actionDeleteSelected); + }); + + assertElements(h.elements, [ + { id: f1.id, isDeleted: true }, + { id: r1.id, isDeleted: false, selected: true }, + ]); + }); + + it("frame + text container (text's frameId set)", async () => { + const f1 = API.createElement({ + type: "frame", + }); + + const r1 = API.createElement({ + type: "rectangle", + frameId: f1.id, + }); + + const t1 = API.createElement({ + type: "text", + width: 200, + height: 100, + fontSize: 20, + containerId: r1.id, + frameId: f1.id, + }); + + mutateElement(r1, { + boundElements: [{ type: "text", id: t1.id }], + }); + + API.setElements([f1, r1, t1]); + + API.setSelectedElements([f1]); + + act(() => { + h.app.actionManager.executeAction(actionDeleteSelected); + }); + + assertElements(h.elements, [ + { id: f1.id, isDeleted: true }, + { id: r1.id, isDeleted: false, selected: true }, + { id: t1.id, isDeleted: false }, + ]); + }); + + it("frame + text container (text's frameId not set)", async () => { + const f1 = API.createElement({ + type: "frame", + }); + + const r1 = API.createElement({ + type: "rectangle", + frameId: f1.id, + }); + + const t1 = API.createElement({ + type: "text", + width: 200, + height: 100, + fontSize: 20, + containerId: r1.id, + frameId: null, + }); + + mutateElement(r1, { + boundElements: [{ type: "text", id: t1.id }], + }); + + API.setElements([f1, r1, t1]); + + API.setSelectedElements([f1]); + + act(() => { + h.app.actionManager.executeAction(actionDeleteSelected); + }); + + assertElements(h.elements, [ + { id: f1.id, isDeleted: true }, + { id: r1.id, isDeleted: false, selected: true }, + { id: t1.id, isDeleted: false }, + ]); + }); + + it("frame + text container (text selected too)", async () => { + const f1 = API.createElement({ + type: "frame", + }); + + const r1 = API.createElement({ + type: "rectangle", + frameId: f1.id, + }); + + const t1 = API.createElement({ + type: "text", + width: 200, + height: 100, + fontSize: 20, + containerId: r1.id, + frameId: null, + }); + + mutateElement(r1, { + boundElements: [{ type: "text", id: t1.id }], + }); + + API.setElements([f1, r1, t1]); + + API.setSelectedElements([f1, t1]); + + act(() => { + h.app.actionManager.executeAction(actionDeleteSelected); + }); + + assertElements(h.elements, [ + { id: f1.id, isDeleted: true }, + { id: r1.id, isDeleted: false, selected: true }, + { id: t1.id, isDeleted: false }, + ]); + }); + + it("frame + labeled arrow", async () => { + const f1 = API.createElement({ + type: "frame", + }); + + const a1 = API.createElement({ + type: "arrow", + frameId: f1.id, + }); + + const t1 = API.createElement({ + type: "text", + width: 200, + height: 100, + fontSize: 20, + containerId: a1.id, + frameId: null, + }); + + mutateElement(a1, { + boundElements: [{ type: "text", id: t1.id }], + }); + + API.setElements([f1, a1, t1]); + + API.setSelectedElements([f1, t1]); + + act(() => { + h.app.actionManager.executeAction(actionDeleteSelected); + }); + + assertElements(h.elements, [ + { id: f1.id, isDeleted: true }, + { id: a1.id, isDeleted: false, selected: true }, + { id: t1.id, isDeleted: false }, + ]); + }); + + it("frame + children selected", async () => { + const f1 = API.createElement({ + type: "frame", + }); + const r1 = API.createElement({ + type: "rectangle", + frameId: f1.id, + }); + API.setElements([f1, r1]); + + API.setSelectedElements([f1, r1]); + + act(() => { + h.app.actionManager.executeAction(actionDeleteSelected); + }); + + assertElements(h.elements, [ + { id: f1.id, isDeleted: true }, + { id: r1.id, isDeleted: false, selected: true }, + ]); + }); +}); diff --git a/packages/excalidraw/actions/actionDeleteSelected.tsx b/packages/excalidraw/actions/actionDeleteSelected.tsx index 882c8b3f33..c66b2c0248 100644 --- a/packages/excalidraw/actions/actionDeleteSelected.tsx +++ b/packages/excalidraw/actions/actionDeleteSelected.tsx @@ -18,6 +18,8 @@ import { import { updateActiveTool } from "../utils"; import { TrashIcon } from "../components/icons"; import { StoreAction } from "../store"; +import { getContainerElement } from "../element/textElement"; +import { getFrameChildren } from "../frame"; const deleteSelectedElements = ( elements: readonly ExcalidrawElement[], @@ -33,10 +35,50 @@ const deleteSelectedElements = ( const selectedElementIds: Record = {}; + const elementsMap = app.scene.getNonDeletedElementsMap(); + + const processedElements = new Set(); + + for (const frameId of framesToBeDeleted) { + const frameChildren = getFrameChildren(elements, frameId); + for (const el of frameChildren) { + if (processedElements.has(el.id)) { + continue; + } + + if (isBoundToContainer(el)) { + const containerElement = getContainerElement(el, elementsMap); + if (containerElement) { + selectedElementIds[containerElement.id] = true; + } + } else { + selectedElementIds[el.id] = true; + } + processedElements.add(el.id); + } + } + let shouldSelectEditingGroup = true; const nextElements = elements.map((el) => { if (appState.selectedElementIds[el.id]) { + const boundElement = isBoundToContainer(el) + ? getContainerElement(el, elementsMap) + : null; + + if (el.frameId && framesToBeDeleted.has(el.frameId)) { + shouldSelectEditingGroup = false; + selectedElementIds[el.id] = true; + return el; + } + + if ( + boundElement?.frameId && + framesToBeDeleted.has(boundElement?.frameId) + ) { + return el; + } + if (el.boundElements) { el.boundElements.forEach((candidate) => { const bound = app.scene.getNonDeletedElementsMap().get(candidate.id); @@ -59,7 +101,9 @@ const deleteSelectedElements = ( // if deleting a frame, remove the children from it and select them if (el.frameId && framesToBeDeleted.has(el.frameId)) { shouldSelectEditingGroup = false; - selectedElementIds[el.id] = true; + if (!isBoundToContainer(el)) { + selectedElementIds[el.id] = true; + } return newElementWith(el, { frameId: null }); } @@ -224,11 +268,13 @@ export const actionDeleteSelected = register({ storeAction: StoreAction.CAPTURE, }; } + let { elements: nextElements, appState: nextAppState } = deleteSelectedElements(elements, appState, app); + fixBindingsAfterDeletion( nextElements, - elements.filter(({ id }) => appState.selectedElementIds[id]), + nextElements.filter((el) => el.isDeleted), ); nextAppState = handleGroupEditingState(nextAppState, nextElements); diff --git a/packages/excalidraw/actions/actionDuplicateSelection.test.tsx b/packages/excalidraw/actions/actionDuplicateSelection.test.tsx new file mode 100644 index 0000000000..dc471df371 --- /dev/null +++ b/packages/excalidraw/actions/actionDuplicateSelection.test.tsx @@ -0,0 +1,530 @@ +import { Excalidraw } from "../index"; +import { + act, + assertElements, + getCloneByOrigId, + render, +} from "../tests/test-utils"; +import { API } from "../tests/helpers/api"; +import { actionDuplicateSelection } from "./actionDuplicateSelection"; +import React from "react"; +import { ORIG_ID } from "../constants"; + +const { h } = window; + +describe("actionDuplicateSelection", () => { + beforeEach(async () => { + await render(); + }); + + describe("duplicating frames", () => { + it("frame selected only", async () => { + const frame = API.createElement({ + type: "frame", + }); + + const rectangle = API.createElement({ + type: "rectangle", + frameId: frame.id, + }); + + API.setElements([frame, rectangle]); + API.setSelectedElements([frame]); + + act(() => { + h.app.actionManager.executeAction(actionDuplicateSelection); + }); + + assertElements(h.elements, [ + { id: frame.id }, + { id: rectangle.id, frameId: frame.id }, + { [ORIG_ID]: rectangle.id, frameId: getCloneByOrigId(frame.id)?.id }, + { [ORIG_ID]: frame.id, selected: true }, + ]); + }); + + it("frame selected only (with text container)", async () => { + const frame = API.createElement({ + type: "frame", + }); + + const [rectangle, text] = API.createTextContainer({ frameId: frame.id }); + + API.setElements([frame, rectangle, text]); + API.setSelectedElements([frame]); + + act(() => { + h.app.actionManager.executeAction(actionDuplicateSelection); + }); + + assertElements(h.elements, [ + { id: frame.id }, + { id: rectangle.id, frameId: frame.id }, + { id: text.id, containerId: rectangle.id, frameId: frame.id }, + { [ORIG_ID]: rectangle.id, frameId: getCloneByOrigId(frame.id)?.id }, + { + [ORIG_ID]: text.id, + containerId: getCloneByOrigId(rectangle.id)?.id, + frameId: getCloneByOrigId(frame.id)?.id, + }, + { [ORIG_ID]: frame.id, selected: true }, + ]); + }); + + it("frame + text container selected (order A)", async () => { + const frame = API.createElement({ + type: "frame", + }); + + const [rectangle, text] = API.createTextContainer({ frameId: frame.id }); + + API.setElements([frame, rectangle, text]); + API.setSelectedElements([frame, rectangle]); + + act(() => { + h.app.actionManager.executeAction(actionDuplicateSelection); + }); + + assertElements(h.elements, [ + { id: frame.id }, + { id: rectangle.id, frameId: frame.id }, + { id: text.id, containerId: rectangle.id, frameId: frame.id }, + { + [ORIG_ID]: rectangle.id, + frameId: getCloneByOrigId(frame.id)?.id, + }, + { + [ORIG_ID]: text.id, + containerId: getCloneByOrigId(rectangle.id)?.id, + frameId: getCloneByOrigId(frame.id)?.id, + }, + { + [ORIG_ID]: frame.id, + selected: true, + }, + ]); + }); + + it("frame + text container selected (order B)", async () => { + const frame = API.createElement({ + type: "frame", + }); + + const [rectangle, text] = API.createTextContainer({ frameId: frame.id }); + + API.setElements([text, rectangle, frame]); + API.setSelectedElements([rectangle, frame]); + + act(() => { + h.app.actionManager.executeAction(actionDuplicateSelection); + }); + + assertElements(h.elements, [ + { id: rectangle.id, frameId: frame.id }, + { id: text.id, containerId: rectangle.id, frameId: frame.id }, + { id: frame.id }, + { + type: "rectangle", + [ORIG_ID]: `${rectangle.id}`, + }, + { + [ORIG_ID]: `${text.id}`, + type: "text", + containerId: getCloneByOrigId(rectangle.id)?.id, + frameId: getCloneByOrigId(frame.id)?.id, + }, + { [ORIG_ID]: `${frame.id}`, type: "frame", selected: true }, + ]); + }); + }); + + describe("duplicating frame children", () => { + it("frame child selected", () => { + const frame = API.createElement({ + type: "frame", + }); + + const rectangle = API.createElement({ + type: "rectangle", + frameId: frame.id, + }); + + API.setElements([frame, rectangle]); + API.setSelectedElements([rectangle]); + + act(() => { + h.app.actionManager.executeAction(actionDuplicateSelection); + }); + + assertElements(h.elements, [ + { id: frame.id }, + { id: rectangle.id, frameId: frame.id }, + { [ORIG_ID]: rectangle.id, frameId: frame.id, selected: true }, + ]); + }); + + it("frame text container selected (rectangle selected)", () => { + const frame = API.createElement({ + type: "frame", + }); + + const [rectangle, text] = API.createTextContainer({ frameId: frame.id }); + + API.setElements([frame, rectangle, text]); + API.setSelectedElements([rectangle]); + + act(() => { + h.app.actionManager.executeAction(actionDuplicateSelection); + }); + + assertElements(h.elements, [ + { id: frame.id }, + { id: rectangle.id, frameId: frame.id }, + { id: text.id, containerId: rectangle.id, frameId: frame.id }, + { [ORIG_ID]: rectangle.id, frameId: frame.id, selected: true }, + { + [ORIG_ID]: text.id, + containerId: getCloneByOrigId(rectangle.id).id, + frameId: frame.id, + }, + ]); + }); + + it("frame bound text selected (container not selected)", () => { + const frame = API.createElement({ + type: "frame", + }); + + const [rectangle, text] = API.createTextContainer({ frameId: frame.id }); + + API.setElements([frame, rectangle, text]); + API.setSelectedElements([text]); + + act(() => { + h.app.actionManager.executeAction(actionDuplicateSelection); + }); + + assertElements(h.elements, [ + { id: frame.id }, + { id: rectangle.id, frameId: frame.id }, + { id: text.id, containerId: rectangle.id, frameId: frame.id }, + { [ORIG_ID]: rectangle.id, frameId: frame.id, selected: true }, + { + [ORIG_ID]: text.id, + containerId: getCloneByOrigId(rectangle.id).id, + frameId: frame.id, + }, + ]); + }); + + it("frame text container selected (text not exists)", () => { + const frame = API.createElement({ + type: "frame", + }); + + const [rectangle] = API.createTextContainer({ frameId: frame.id }); + + API.setElements([frame, rectangle]); + API.setSelectedElements([rectangle]); + + act(() => { + h.app.actionManager.executeAction(actionDuplicateSelection); + }); + + assertElements(h.elements, [ + { id: frame.id }, + { id: rectangle.id, frameId: frame.id }, + { [ORIG_ID]: rectangle.id, frameId: frame.id, selected: true }, + ]); + }); + + // shouldn't happen + it("frame bound text selected (container not exists)", () => { + const frame = API.createElement({ + type: "frame", + }); + + const [, text] = API.createTextContainer({ frameId: frame.id }); + + API.setElements([frame, text]); + API.setSelectedElements([text]); + + act(() => { + h.app.actionManager.executeAction(actionDuplicateSelection); + }); + + assertElements(h.elements, [ + { id: frame.id }, + { id: text.id, frameId: frame.id }, + { [ORIG_ID]: text.id, frameId: frame.id }, + ]); + }); + + it("frame bound container selected (text has no frameId)", () => { + const frame = API.createElement({ + type: "frame", + }); + + const [rectangle, text] = API.createTextContainer({ + frameId: frame.id, + label: { frameId: null }, + }); + + API.setElements([frame, rectangle, text]); + API.setSelectedElements([rectangle]); + + act(() => { + h.app.actionManager.executeAction(actionDuplicateSelection); + }); + + assertElements(h.elements, [ + { id: frame.id }, + { id: rectangle.id, frameId: frame.id }, + { id: text.id, containerId: rectangle.id }, + { [ORIG_ID]: rectangle.id, frameId: frame.id, selected: true }, + { + [ORIG_ID]: text.id, + containerId: getCloneByOrigId(rectangle.id).id, + }, + ]); + }); + }); + + describe("duplicating multiple frames", () => { + it("multiple frames selected (no children)", () => { + const frame1 = API.createElement({ + type: "frame", + }); + + const rect1 = API.createElement({ + type: "rectangle", + frameId: frame1.id, + }); + + const frame2 = API.createElement({ + type: "frame", + }); + + const rect2 = API.createElement({ + type: "rectangle", + frameId: frame2.id, + }); + + const ellipse = API.createElement({ + type: "ellipse", + }); + + API.setElements([rect1, frame1, ellipse, rect2, frame2]); + API.setSelectedElements([frame1, frame2]); + + act(() => { + h.app.actionManager.executeAction(actionDuplicateSelection); + }); + + assertElements(h.elements, [ + { id: rect1.id, frameId: frame1.id }, + { id: frame1.id }, + { [ORIG_ID]: rect1.id, frameId: getCloneByOrigId(frame1.id)?.id }, + { [ORIG_ID]: frame1.id, selected: true }, + { id: ellipse.id }, + { id: rect2.id, frameId: frame2.id }, + { id: frame2.id }, + { [ORIG_ID]: rect2.id, frameId: getCloneByOrigId(frame2.id)?.id }, + { [ORIG_ID]: frame2.id, selected: true }, + ]); + }); + + it("multiple frames selected (no children) + unrelated element", () => { + const frame1 = API.createElement({ + type: "frame", + }); + + const rect1 = API.createElement({ + type: "rectangle", + frameId: frame1.id, + }); + + const frame2 = API.createElement({ + type: "frame", + }); + + const rect2 = API.createElement({ + type: "rectangle", + frameId: frame2.id, + }); + + const ellipse = API.createElement({ + type: "ellipse", + }); + + API.setElements([rect1, frame1, ellipse, rect2, frame2]); + API.setSelectedElements([frame1, ellipse, frame2]); + + act(() => { + h.app.actionManager.executeAction(actionDuplicateSelection); + }); + + assertElements(h.elements, [ + { id: rect1.id, frameId: frame1.id }, + { id: frame1.id }, + { [ORIG_ID]: rect1.id, frameId: getCloneByOrigId(frame1.id)?.id }, + { [ORIG_ID]: frame1.id, selected: true }, + { id: ellipse.id }, + { [ORIG_ID]: ellipse.id, selected: true }, + { id: rect2.id, frameId: frame2.id }, + { id: frame2.id }, + { [ORIG_ID]: rect2.id, frameId: getCloneByOrigId(frame2.id)?.id }, + { [ORIG_ID]: frame2.id, selected: true }, + ]); + }); + }); + + describe("duplicating containers/bound elements", () => { + it("labeled arrow (arrow selected)", () => { + const [arrow, text] = API.createLabeledArrow(); + + API.setElements([arrow, text]); + API.setSelectedElements([arrow]); + + act(() => { + h.app.actionManager.executeAction(actionDuplicateSelection); + }); + + assertElements(h.elements, [ + { id: arrow.id }, + { id: text.id, containerId: arrow.id }, + { [ORIG_ID]: arrow.id, selected: true }, + { [ORIG_ID]: text.id, containerId: getCloneByOrigId(arrow.id)?.id }, + ]); + }); + + // shouldn't happen + it("labeled arrow (text selected)", () => { + const [arrow, text] = API.createLabeledArrow(); + + API.setElements([arrow, text]); + API.setSelectedElements([text]); + + act(() => { + h.app.actionManager.executeAction(actionDuplicateSelection); + }); + + assertElements(h.elements, [ + { id: arrow.id }, + { id: text.id, containerId: arrow.id }, + { [ORIG_ID]: arrow.id, selected: true }, + { [ORIG_ID]: text.id, containerId: getCloneByOrigId(arrow.id)?.id }, + ]); + }); + }); + + describe("duplicating groups", () => { + it("duplicate group containing frame (children don't have groupIds set)", () => { + const frame = API.createElement({ + type: "frame", + groupIds: ["A"], + }); + + const [rectangle, text] = API.createTextContainer({ + frameId: frame.id, + }); + + const ellipse = API.createElement({ + type: "ellipse", + groupIds: ["A"], + }); + + API.setElements([rectangle, text, frame, ellipse]); + API.setSelectedElements([frame, ellipse]); + + act(() => { + h.app.actionManager.executeAction(actionDuplicateSelection); + }); + + assertElements(h.elements, [ + { id: rectangle.id, frameId: frame.id }, + { id: text.id, frameId: frame.id }, + { id: frame.id }, + { id: ellipse.id }, + { [ORIG_ID]: rectangle.id, frameId: getCloneByOrigId(frame.id)?.id }, + { [ORIG_ID]: text.id, frameId: getCloneByOrigId(frame.id)?.id }, + { [ORIG_ID]: frame.id, selected: true }, + { [ORIG_ID]: ellipse.id, selected: true }, + ]); + }); + + it("duplicate group containing frame (children have groupIds)", () => { + const frame = API.createElement({ + type: "frame", + groupIds: ["A"], + }); + + const [rectangle, text] = API.createTextContainer({ + frameId: frame.id, + groupIds: ["A"], + }); + + const ellipse = API.createElement({ + type: "ellipse", + groupIds: ["A"], + }); + + API.setElements([rectangle, text, frame, ellipse]); + API.setSelectedElements([frame, ellipse]); + + act(() => { + h.app.actionManager.executeAction(actionDuplicateSelection); + }); + + assertElements(h.elements, [ + { id: rectangle.id, frameId: frame.id }, + { id: text.id, frameId: frame.id }, + { id: frame.id }, + { id: ellipse.id }, + { + [ORIG_ID]: rectangle.id, + frameId: getCloneByOrigId(frame.id)?.id, + // FIXME shouldn't be selected (in selectGroupsForSelectedElements) + selected: true, + }, + { + [ORIG_ID]: text.id, + frameId: getCloneByOrigId(frame.id)?.id, + // FIXME shouldn't be selected (in selectGroupsForSelectedElements) + selected: true, + }, + { [ORIG_ID]: frame.id, selected: true }, + { [ORIG_ID]: ellipse.id, selected: true }, + ]); + }); + + it("duplicating element nested in group", () => { + const ellipse = API.createElement({ + type: "ellipse", + groupIds: ["B"], + }); + const rect1 = API.createElement({ + type: "rectangle", + groupIds: ["A", "B"], + }); + const rect2 = API.createElement({ + type: "rectangle", + groupIds: ["A", "B"], + }); + + API.setElements([ellipse, rect1, rect2]); + API.setSelectedElements([ellipse], "B"); + + act(() => { + h.app.actionManager.executeAction(actionDuplicateSelection); + }); + + assertElements(h.elements, [ + { id: ellipse.id }, + { [ORIG_ID]: ellipse.id, groupIds: ["B"], selected: true }, + { id: rect1.id, groupIds: ["A", "B"] }, + { id: rect2.id, groupIds: ["A", "B"] }, + ]); + }); + }); +}); diff --git a/packages/excalidraw/actions/actionDuplicateSelection.tsx b/packages/excalidraw/actions/actionDuplicateSelection.tsx index d19bfa59d7..735c5cf233 100644 --- a/packages/excalidraw/actions/actionDuplicateSelection.tsx +++ b/packages/excalidraw/actions/actionDuplicateSelection.tsx @@ -5,7 +5,13 @@ import { duplicateElement, getNonDeletedElements } from "../element"; import { isSomeElementSelected } from "../scene"; import { ToolButton } from "../components/ToolButton"; import { t } from "../i18n"; -import { arrayToMap, getShortcutKey } from "../utils"; +import { + arrayToMap, + castArray, + findLastIndex, + getShortcutKey, + invariant, +} from "../utils"; import { LinearElementEditor } from "../element/linearElementEditor"; import { selectGroupsForSelectedElements, @@ -19,8 +25,13 @@ import { DEFAULT_GRID_SIZE } from "../constants"; import { bindTextToShapeAfterDuplication, getBoundTextElement, + getContainerElement, } from "../element/textElement"; -import { isBoundToContainer, isFrameLikeElement } from "../element/typeChecks"; +import { + hasBoundTextElement, + isBoundToContainer, + isFrameLikeElement, +} from "../element/typeChecks"; import { normalizeElementOrder } from "../element/sortElements"; import { DuplicateIcon } from "../components/icons"; import { @@ -31,7 +42,6 @@ import { excludeElementsInFramesFromSelection, getSelectedElements, } from "../scene/selection"; -import { syncMovedIndices } from "../fractionalIndex"; import { StoreAction } from "../store"; export const actionDuplicateSelection = register({ @@ -85,34 +95,66 @@ const duplicateElements = ( ): Partial => { // --------------------------------------------------------------------------- - // step (1) - - const sortedElements = normalizeElementOrder(elements); const groupIdMap = new Map(); const newElements: ExcalidrawElement[] = []; const oldElements: ExcalidrawElement[] = []; const oldIdToDuplicatedId = new Map(); const duplicatedElementsMap = new Map(); - const duplicateAndOffsetElement = (element: ExcalidrawElement) => { - const newElement = duplicateElement( - appState.editingGroupId, - groupIdMap, - element, - { - x: element.x + DEFAULT_GRID_SIZE / 2, - y: element.y + DEFAULT_GRID_SIZE / 2, + const elementsMap = arrayToMap(elements); + + const duplicateAndOffsetElement = < + T extends ExcalidrawElement | ExcalidrawElement[], + >( + element: T, + ): T extends ExcalidrawElement[] + ? ExcalidrawElement[] + : ExcalidrawElement | null => { + const elements = castArray(element); + + const _newElements = elements.reduce( + (acc: ExcalidrawElement[], element) => { + if (processedIds.has(element.id)) { + return acc; + } + + processedIds.set(element.id, true); + + const newElement = duplicateElement( + appState.editingGroupId, + groupIdMap, + element, + { + x: element.x + DEFAULT_GRID_SIZE / 2, + y: element.y + DEFAULT_GRID_SIZE / 2, + }, + ); + + processedIds.set(newElement.id, true); + + duplicatedElementsMap.set(newElement.id, newElement); + oldIdToDuplicatedId.set(element.id, newElement.id); + + oldElements.push(element); + newElements.push(newElement); + + acc.push(newElement); + return acc; }, + [], ); - duplicatedElementsMap.set(newElement.id, newElement); - oldIdToDuplicatedId.set(element.id, newElement.id); - oldElements.push(element); - newElements.push(newElement); - return newElement; + + return ( + Array.isArray(element) ? _newElements : _newElements[0] || null + ) as T extends ExcalidrawElement[] + ? ExcalidrawElement[] + : ExcalidrawElement | null; }; + elements = normalizeElementOrder(elements); + const idsOfElementsToDuplicate = arrayToMap( - getSelectedElements(sortedElements, appState, { + getSelectedElements(elements, appState, { includeBoundTextElement: true, includeElementsInFrames: true, }), @@ -130,122 +172,133 @@ const duplicateElements = ( // loop over them. const processedIds = new Map(); - const markAsProcessed = (elements: ExcalidrawElement[]) => { - for (const element of elements) { - processedIds.set(element.id, true); + const elementsWithClones: ExcalidrawElement[] = elements.slice(); + + const insertAfterIndex = ( + index: number, + elements: ExcalidrawElement | null | ExcalidrawElement[], + ) => { + invariant(index !== -1, "targetIndex === -1 "); + + if (!Array.isArray(elements) && !elements) { + return; } - return elements; + + elementsWithClones.splice(index + 1, 0, ...castArray(elements)); }; - const elementsWithClones: ExcalidrawElement[] = []; + const frameIdsToDuplicate = new Set( + elements + .filter( + (el) => idsOfElementsToDuplicate.has(el.id) && isFrameLikeElement(el), + ) + .map((el) => el.id), + ); - let index = -1; - - while (++index < sortedElements.length) { - const element = sortedElements[index]; - - if (processedIds.get(element.id)) { + for (const element of elements) { + if (processedIds.has(element.id)) { continue; } - const boundTextElement = getBoundTextElement(element, arrayToMap(elements)); - const isElementAFrameLike = isFrameLikeElement(element); + if (!idsOfElementsToDuplicate.has(element.id)) { + continue; + } - if (idsOfElementsToDuplicate.get(element.id)) { - // if a group or a container/bound-text or frame, duplicate atomically - if (element.groupIds.length || boundTextElement || isElementAFrameLike) { - const groupId = getSelectedGroupForElement(appState, element); - if (groupId) { - // TODO: - // remove `.flatMap...` - // if the elements in a frame are grouped when the frame is grouped - const groupElements = getElementsInGroup( - sortedElements, - groupId, - ).flatMap((element) => - isFrameLikeElement(element) - ? [...getFrameChildren(elements, element.id), element] - : [element], - ); + // groups + // ------------------------------------------------------------------------- - elementsWithClones.push( - ...markAsProcessed([ - ...groupElements, - ...groupElements.map((element) => - duplicateAndOffsetElement(element), - ), - ]), - ); - continue; - } - if (boundTextElement) { - elementsWithClones.push( - ...markAsProcessed([ - element, - boundTextElement, - duplicateAndOffsetElement(element), - duplicateAndOffsetElement(boundTextElement), - ]), - ); - continue; - } - if (isElementAFrameLike) { - const elementsInFrame = getFrameChildren(sortedElements, element.id); + const groupId = getSelectedGroupForElement(appState, element); + if (groupId) { + const groupElements = getElementsInGroup(elements, groupId).flatMap( + (element) => + isFrameLikeElement(element) + ? [...getFrameChildren(elements, element.id), element] + : [element], + ); - elementsWithClones.push( - ...markAsProcessed([ - ...elementsInFrame, - element, - ...elementsInFrame.map((e) => duplicateAndOffsetElement(e)), - duplicateAndOffsetElement(element), - ]), - ); + const targetIndex = findLastIndex(elementsWithClones, (el) => { + return el.groupIds?.includes(groupId); + }); - continue; - } - } - // since elements in frames have a lower z-index than the frame itself, - // they will be looped first and if their frames are selected as well, - // they will have been copied along with the frame atomically in the - // above branch, so we must skip those elements here - // - // now, for elements do not belong any frames or elements whose frames - // are selected (or elements that are left out from the above - // steps for whatever reason) we (should at least) duplicate them here - if (!element.frameId || !idsOfElementsToDuplicate.has(element.frameId)) { - elementsWithClones.push( - ...markAsProcessed([element, duplicateAndOffsetElement(element)]), + insertAfterIndex(targetIndex, duplicateAndOffsetElement(groupElements)); + continue; + } + + // frame duplication + // ------------------------------------------------------------------------- + + if (element.frameId && frameIdsToDuplicate.has(element.frameId)) { + continue; + } + + if (isFrameLikeElement(element)) { + const frameId = element.id; + + const frameChildren = getFrameChildren(elements, frameId); + + const targetIndex = findLastIndex(elementsWithClones, (el) => { + return el.frameId === frameId || el.id === frameId; + }); + + insertAfterIndex( + targetIndex, + duplicateAndOffsetElement([...frameChildren, element]), + ); + continue; + } + + // text container + // ------------------------------------------------------------------------- + + if (hasBoundTextElement(element)) { + const boundTextElement = getBoundTextElement(element, elementsMap); + + const targetIndex = findLastIndex(elementsWithClones, (el) => { + return ( + el.id === element.id || + ("containerId" in el && el.containerId === element.id) ); + }); + + if (boundTextElement) { + insertAfterIndex( + targetIndex, + duplicateAndOffsetElement([element, boundTextElement]), + ); + } else { + insertAfterIndex(targetIndex, duplicateAndOffsetElement(element)); } - } else { - elementsWithClones.push(...markAsProcessed([element])); + + continue; } - } - // step (2) + if (isBoundToContainer(element)) { + const container = getContainerElement(element, elementsMap); - // second pass to remove duplicates. We loop from the end as it's likelier - // that the last elements are in the correct order (contiguous or otherwise). - // Thus we need to reverse as the last step (3). + const targetIndex = findLastIndex(elementsWithClones, (el) => { + return el.id === element.id || el.id === container?.id; + }); - const finalElementsReversed: ExcalidrawElement[] = []; + if (container) { + insertAfterIndex( + targetIndex, + duplicateAndOffsetElement([container, element]), + ); + } else { + insertAfterIndex(targetIndex, duplicateAndOffsetElement(element)); + } - const finalElementIds = new Map(); - index = elementsWithClones.length; - - while (--index >= 0) { - const element = elementsWithClones[index]; - if (!finalElementIds.get(element.id)) { - finalElementIds.set(element.id, true); - finalElementsReversed.push(element); + continue; } - } - // step (3) - const finalElements = syncMovedIndices( - finalElementsReversed.reverse(), - arrayToMap(newElements), - ); + // default duplication (regular elements) + // ------------------------------------------------------------------------- + + insertAfterIndex( + findLastIndex(elementsWithClones, (el) => el.id === element.id), + duplicateAndOffsetElement(element), + ); + } // --------------------------------------------------------------------------- @@ -260,7 +313,7 @@ const duplicateElements = ( oldIdToDuplicatedId, ); bindElementsToFramesAfterDuplication( - finalElements, + elementsWithClones, oldElements, oldIdToDuplicatedId, ); @@ -269,7 +322,7 @@ const duplicateElements = ( excludeElementsInFramesFromSelection(newElements); return { - elements: finalElements, + elements: elementsWithClones, appState: { ...appState, ...selectGroupsForSelectedElements( @@ -285,7 +338,7 @@ const duplicateElements = ( {}, ), }, - getNonDeletedElements(finalElements), + getNonDeletedElements(elementsWithClones), appState, null, ), diff --git a/packages/excalidraw/constants.ts b/packages/excalidraw/constants.ts index b8f7e1b83e..cb32190b22 100644 --- a/packages/excalidraw/constants.ts +++ b/packages/excalidraw/constants.ts @@ -458,3 +458,6 @@ export const ARROW_TYPE: { [T in AppState["currentItemArrowType"]]: T } = { export const DEFAULT_REDUCED_GLOBAL_ALPHA = 0.3; export const ELEMENT_LINK_KEY = "element"; + +/** used in tests */ +export const ORIG_ID = Symbol.for("__test__originalId__"); diff --git a/packages/excalidraw/element/newElement.ts b/packages/excalidraw/element/newElement.ts index a79f077aa6..58b5ec43fb 100644 --- a/packages/excalidraw/element/newElement.ts +++ b/packages/excalidraw/element/newElement.ts @@ -45,6 +45,7 @@ import { DEFAULT_FONT_SIZE, DEFAULT_TEXT_ALIGN, DEFAULT_VERTICAL_ALIGN, + ORIG_ID, VERTICAL_ALIGN, } from "../constants"; import type { MarkOptional, Merge, Mutable } from "../utility-types"; @@ -592,26 +593,18 @@ export const deepCopyElement = ( return _deepCopyElement(val); }; +const __test__defineOrigId = (clonedObj: object, origId: string) => { + Object.defineProperty(clonedObj, ORIG_ID, { + value: origId, + writable: false, + enumerable: false, + }); +}; + /** - * utility wrapper to generate new id. In test env it reuses the old + postfix - * for test assertions. + * utility wrapper to generate new id. */ -export const regenerateId = ( - /** supply null if no previous id exists */ - previousId: string | null, -) => { - if (isTestEnv() && previousId) { - let nextId = `${previousId}_copy`; - // `window.h` may not be defined in some unit tests - if ( - window.h?.app - ?.getSceneElementsIncludingDeleted() - .find((el: ExcalidrawElement) => el.id === nextId) - ) { - nextId += "_copy"; - } - return nextId; - } +const regenerateId = () => { return randomId(); }; @@ -637,7 +630,11 @@ export const duplicateElement = ( ): Readonly => { let copy = deepCopyElement(element); - copy.id = regenerateId(copy.id); + if (isTestEnv()) { + __test__defineOrigId(copy, element.id); + } + + copy.id = regenerateId(); copy.boundElements = null; copy.updated = getUpdatedTimestamp(); copy.seed = randomInteger(); @@ -646,7 +643,7 @@ export const duplicateElement = ( editingGroupId, (groupId) => { if (!groupIdMapForOperation.has(groupId)) { - groupIdMapForOperation.set(groupId, regenerateId(groupId)); + groupIdMapForOperation.set(groupId, regenerateId()); } return groupIdMapForOperation.get(groupId)!; }, @@ -692,7 +689,7 @@ export const duplicateElements = ( // if we haven't migrated the element id, but an old element with the same // id exists, generate a new id for it and return it if (origElementsMap.has(id)) { - const newId = regenerateId(id); + const newId = regenerateId(); elementNewIdsMap.set(id, newId); return newId; } @@ -706,6 +703,9 @@ export const duplicateElements = ( const clonedElement: Mutable = _deepCopyElement(element); clonedElement.id = maybeGetNewId(element.id)!; + if (isTestEnv()) { + __test__defineOrigId(clonedElement, element.id); + } if (opts?.randomizeSeed) { clonedElement.seed = randomInteger(); @@ -715,7 +715,7 @@ export const duplicateElements = ( if (clonedElement.groupIds) { clonedElement.groupIds = clonedElement.groupIds.map((groupId) => { if (!groupNewIdsMap.has(groupId)) { - groupNewIdsMap.set(groupId, regenerateId(groupId)); + groupNewIdsMap.set(groupId, regenerateId()); } return groupNewIdsMap.get(groupId)!; }); diff --git a/packages/excalidraw/element/sortElements.ts b/packages/excalidraw/element/sortElements.ts index bb758370ad..3078a6827a 100644 --- a/packages/excalidraw/element/sortElements.ts +++ b/packages/excalidraw/element/sortElements.ts @@ -116,8 +116,5 @@ const normalizeBoundElementsOrder = ( export const normalizeElementOrder = ( elements: readonly ExcalidrawElement[], ) => { - // console.time(); - const ret = normalizeBoundElementsOrder(normalizeGroupElementOrder(elements)); - // console.timeEnd(); - return ret; + return normalizeBoundElementsOrder(normalizeGroupElementOrder(elements)); }; diff --git a/packages/excalidraw/frame.test.tsx b/packages/excalidraw/frame.test.tsx index 010ed93434..6a5045b2cb 100644 --- a/packages/excalidraw/frame.test.tsx +++ b/packages/excalidraw/frame.test.tsx @@ -1,9 +1,8 @@ -import React from "react"; 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 { render } from "./tests/test-utils"; +import { getCloneByOrigId, render } from "./tests/test-utils"; const { h } = window; const mouse = new Pointer("mouse"); @@ -413,10 +412,10 @@ describe("adding elements to frames", () => { dragElementIntoFrame(frame, rect2); - const rect2_copy = { ...rect2, id: `${rect2.id}_copy` }; - 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]); @@ -427,11 +426,11 @@ describe("adding elements to frames", () => { dragElementIntoFrame(frame, rect2); - const rect2_copy = { ...rect2, id: `${rect2.id}_copy` }; - // 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]); diff --git a/packages/excalidraw/package.json b/packages/excalidraw/package.json index fdbda46b81..b1f3e691bb 100644 --- a/packages/excalidraw/package.json +++ b/packages/excalidraw/package.json @@ -103,6 +103,7 @@ "@types/pako": "1.0.3", "@types/pica": "5.1.3", "@types/resize-observer-browser": "0.1.7", + "ansicolor": "2.0.3", "autoprefixer": "10.4.7", "babel-loader": "8.2.5", "babel-plugin-transform-class-properties": "6.24.1", diff --git a/packages/excalidraw/tests/__snapshots__/contextmenu.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/contextmenu.test.tsx.snap index 36666faec8..f77eb8ddbd 100644 --- a/packages/excalidraw/tests/__snapshots__/contextmenu.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/contextmenu.test.tsx.snap @@ -2517,7 +2517,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates "scrolledOutside": false, "searchMatches": [], "selectedElementIds": { - "id0_copy": true, + "id1": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": {}, @@ -2590,7 +2590,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates "frameId": null, "groupIds": [], "height": 20, - "id": "id0_copy", + "id": "id1", "index": "a1", "isDeleted": false, "link": null, @@ -2680,7 +2680,7 @@ History { "delta": Delta { "deleted": { "selectedElementIds": { - "id0_copy": true, + "id1": true, }, }, "inserted": { @@ -2693,7 +2693,7 @@ History { "elementsChange": ElementsChange { "added": Map {}, "removed": Map { - "id0_copy" => Delta { + "id1" => Delta { "deleted": { "angle": 0, "backgroundColor": "transparent", diff --git a/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap index 58bc9303e5..9e353c06fb 100644 --- a/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/history.test.tsx.snap @@ -78,14 +78,14 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "penMode": false, "pendingImageElementId": null, "previousSelectedElementIds": { - "id166": true, + "id172": true, }, "resizingElement": null, "scrollX": 0, "scrollY": 0, "searchMatches": [], "selectedElementIds": { - "id166": true, + "id172": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": {}, @@ -123,7 +123,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "frameId": null, "groupIds": [], "height": 100, - "id": "id164", + "id": "id170", "index": "a0", "isDeleted": false, "link": null, @@ -155,7 +155,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "frameId": null, "groupIds": [], "height": 100, - "id": "id165", + "id": "id171", "index": "a1", "isDeleted": false, "link": null, @@ -186,7 +186,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "elbowed": false, "endArrowhead": "arrow", "endBinding": { - "elementId": "id169", + "elementId": "id175", "fixedPoint": [ "0.50000", 1, @@ -198,7 +198,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "frameId": null, "groupIds": [], "height": 99, - "id": "id166", + "id": "id172", "index": "a2", "isDeleted": false, "lastCommittedPoint": null, @@ -239,7 +239,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "backgroundColor": "transparent", "boundElements": [ { - "id": "id166", + "id": "id172", "type": "arrow", }, ], @@ -248,7 +248,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "frameId": null, "groupIds": [], "height": 50, - "id": "id169", + "id": "id175", "index": "a3", "isDeleted": false, "link": null, @@ -290,10 +290,10 @@ History { "added": Map {}, "removed": Map {}, "updated": Map { - "id166" => Delta { + "id172" => Delta { "deleted": { "endBinding": { - "elementId": "id165", + "elementId": "id171", "fixedPoint": null, "focus": "0.00990", "gap": 1, @@ -310,7 +310,7 @@ History { ], ], "startBinding": { - "elementId": "id164", + "elementId": "id170", "fixedPoint": null, "focus": "0.02970", "gap": 1, @@ -318,7 +318,7 @@ History { }, "inserted": { "endBinding": { - "elementId": "id165", + "elementId": "id171", "fixedPoint": null, "focus": "-0.02000", "gap": 1, @@ -335,7 +335,7 @@ History { ], ], "startBinding": { - "elementId": "id164", + "elementId": "id170", "fixedPoint": null, "focus": "0.02000", "gap": 1, @@ -356,36 +356,36 @@ History { "added": Map {}, "removed": Map {}, "updated": Map { - "id164" => Delta { + "id170" => Delta { "deleted": { "boundElements": [], }, "inserted": { "boundElements": [ { - "id": "id166", + "id": "id172", "type": "arrow", }, ], }, }, - "id165" => Delta { + "id171" => Delta { "deleted": { "boundElements": [], }, "inserted": { "boundElements": [ { - "id": "id166", + "id": "id172", "type": "arrow", }, ], }, }, - "id166" => Delta { + "id172" => Delta { "deleted": { "endBinding": { - "elementId": "id169", + "elementId": "id175", "fixedPoint": [ "0.50000", 1, @@ -409,7 +409,7 @@ History { }, "inserted": { "endBinding": { - "elementId": "id165", + "elementId": "id171", "fixedPoint": null, "focus": "0.00990", "gap": 1, @@ -426,7 +426,7 @@ History { ], ], "startBinding": { - "elementId": "id164", + "elementId": "id170", "fixedPoint": null, "focus": "0.02970", "gap": 1, @@ -434,11 +434,11 @@ History { "y": "0.99245", }, }, - "id169" => Delta { + "id175" => Delta { "deleted": { "boundElements": [ { - "id": "id166", + "id": "id172", "type": "arrow", }, ], @@ -462,7 +462,7 @@ History { "elementsChange": ElementsChange { "added": Map {}, "removed": Map { - "id164" => Delta { + "id170" => Delta { "deleted": { "angle": 0, "backgroundColor": "transparent", @@ -493,7 +493,7 @@ History { "isDeleted": true, }, }, - "id165" => Delta { + "id171" => Delta { "deleted": { "angle": 0, "backgroundColor": "transparent", @@ -533,9 +533,9 @@ History { "delta": Delta { "deleted": { "selectedElementIds": { - "id166": true, + "id172": true, }, - "selectedLinearElementId": "id166", + "selectedLinearElementId": "id172", }, "inserted": { "selectedElementIds": {}, @@ -546,7 +546,7 @@ History { "elementsChange": ElementsChange { "added": Map {}, "removed": Map { - "id166" => Delta { + "id172" => Delta { "deleted": { "angle": 0, "backgroundColor": "transparent", @@ -683,14 +683,14 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "penMode": false, "pendingImageElementId": null, "previousSelectedElementIds": { - "id161": true, + "id167": true, }, "resizingElement": null, "scrollX": 0, "scrollY": 0, "searchMatches": [], "selectedElementIds": { - "id161": true, + "id167": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": {}, @@ -728,7 +728,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "frameId": null, "groupIds": [], "height": 100, - "id": "id159", + "id": "id165", "index": "a0", "isDeleted": false, "link": null, @@ -760,7 +760,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "frameId": null, "groupIds": [], "height": 100, - "id": "id160", + "id": "id166", "index": "a1", "isDeleted": false, "link": null, @@ -795,7 +795,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "frameId": null, "groupIds": [], "height": 0, - "id": "id161", + "id": "id167", "index": "a2", "isDeleted": false, "lastCommittedPoint": null, @@ -850,7 +850,7 @@ History { "added": Map {}, "removed": Map {}, "updated": Map { - "id161" => Delta { + "id167" => Delta { "deleted": { "points": [ [ @@ -890,33 +890,33 @@ History { "added": Map {}, "removed": Map {}, "updated": Map { - "id159" => Delta { + "id165" => Delta { "deleted": { "boundElements": [], }, "inserted": { "boundElements": [ { - "id": "id161", + "id": "id167", "type": "arrow", }, ], }, }, - "id160" => Delta { + "id166" => Delta { "deleted": { "boundElements": [], }, "inserted": { "boundElements": [ { - "id": "id161", + "id": "id167", "type": "arrow", }, ], }, }, - "id161" => Delta { + "id167" => Delta { "deleted": { "endBinding": null, "points": [ @@ -933,7 +933,7 @@ History { }, "inserted": { "endBinding": { - "elementId": "id160", + "elementId": "id166", "fixedPoint": null, "focus": 0, "gap": 1, @@ -949,7 +949,7 @@ History { ], ], "startBinding": { - "elementId": "id159", + "elementId": "id165", "fixedPoint": null, "focus": 0, "gap": 1, @@ -971,7 +971,7 @@ History { "elementsChange": ElementsChange { "added": Map {}, "removed": Map { - "id159" => Delta { + "id165" => Delta { "deleted": { "angle": 0, "backgroundColor": "transparent", @@ -1002,7 +1002,7 @@ History { "isDeleted": true, }, }, - "id160" => Delta { + "id166" => Delta { "deleted": { "angle": 0, "backgroundColor": "transparent", @@ -1042,9 +1042,9 @@ History { "delta": Delta { "deleted": { "selectedElementIds": { - "id161": true, + "id167": true, }, - "selectedLinearElementId": "id161", + "selectedLinearElementId": "id167", }, "inserted": { "selectedElementIds": {}, @@ -1055,7 +1055,7 @@ History { "elementsChange": ElementsChange { "added": Map {}, "removed": Map { - "id161" => Delta { + "id167" => Delta { "deleted": { "angle": 0, "backgroundColor": "transparent", @@ -1235,7 +1235,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "elbowed": false, "endArrowhead": null, "endBinding": { - "elementId": "id171", + "elementId": "id177", "fixedPoint": [ "0.50000", 1, @@ -1247,7 +1247,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "frameId": null, "groupIds": [], "height": "2.61991", - "id": "id172", + "id": "id178", "index": "Zz", "isDeleted": false, "lastCommittedPoint": null, @@ -1270,7 +1270,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl }, "startArrowhead": null, "startBinding": { - "elementId": "id170", + "elementId": "id176", "fixedPoint": [ 1, "0.50000", @@ -1296,7 +1296,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "backgroundColor": "transparent", "boundElements": [ { - "id": "id172", + "id": "id178", "type": "arrow", }, ], @@ -1305,7 +1305,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "frameId": null, "groupIds": [], "height": 100, - "id": "id170", + "id": "id176", "index": "a0", "isDeleted": false, "link": null, @@ -1333,7 +1333,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "backgroundColor": "transparent", "boundElements": [ { - "id": "id172", + "id": "id178", "type": "arrow", }, ], @@ -1342,7 +1342,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "frameId": null, "groupIds": [], "height": 100, - "id": "id171", + "id": "id177", "index": "a1", "isDeleted": false, "link": null, @@ -1384,7 +1384,7 @@ History { "elementsChange": ElementsChange { "added": Map {}, "removed": Map { - "id170" => Delta { + "id176" => Delta { "deleted": { "angle": 0, "backgroundColor": "transparent", @@ -1415,7 +1415,7 @@ History { "isDeleted": true, }, }, - "id171" => Delta { + "id177" => Delta { "deleted": { "angle": 0, "backgroundColor": "transparent", @@ -1448,10 +1448,10 @@ History { }, }, "updated": Map { - "id172" => Delta { + "id178" => Delta { "deleted": { "endBinding": { - "elementId": "id171", + "elementId": "id177", "fixedPoint": [ "0.50000", 1, @@ -1460,7 +1460,7 @@ History { "gap": 1, }, "startBinding": { - "elementId": "id170", + "elementId": "id176", "fixedPoint": [ 1, "0.50000", @@ -1606,7 +1606,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "elbowed": false, "endArrowhead": null, "endBinding": { - "elementId": "id174", + "elementId": "id180", "fixedPoint": [ 1, "0.50000", @@ -1618,7 +1618,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "frameId": null, "groupIds": [], "height": "2.61991", - "id": "id175", + "id": "id181", "index": "a0", "isDeleted": false, "lastCommittedPoint": null, @@ -1641,7 +1641,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl }, "startArrowhead": null, "startBinding": { - "elementId": "id173", + "elementId": "id179", "fixedPoint": [ "0.50000", 1, @@ -1667,7 +1667,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "backgroundColor": "transparent", "boundElements": [ { - "id": "id175", + "id": "id181", "type": "arrow", }, ], @@ -1676,7 +1676,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "frameId": null, "groupIds": [], "height": 100, - "id": "id173", + "id": "id179", "index": "a0V", "isDeleted": false, "link": null, @@ -1704,7 +1704,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "backgroundColor": "transparent", "boundElements": [ { - "id": "id175", + "id": "id181", "type": "arrow", }, ], @@ -1713,7 +1713,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "frameId": null, "groupIds": [], "height": 100, - "id": "id174", + "id": "id180", "index": "a1", "isDeleted": false, "link": null, @@ -1755,7 +1755,7 @@ History { "elementsChange": ElementsChange { "added": Map {}, "removed": Map { - "id175" => Delta { + "id181" => Delta { "deleted": { "angle": 0, "backgroundColor": "transparent", @@ -1764,7 +1764,7 @@ History { "elbowed": false, "endArrowhead": null, "endBinding": { - "elementId": "id174", + "elementId": "id180", "fixedPoint": [ 1, "0.50000", @@ -1798,7 +1798,7 @@ History { }, "startArrowhead": null, "startBinding": { - "elementId": "id173", + "elementId": "id179", "fixedPoint": [ "0.50000", 1, @@ -1820,11 +1820,11 @@ History { }, }, "updated": Map { - "id173" => Delta { + "id179" => Delta { "deleted": { "boundElements": [ { - "id": "id175", + "id": "id181", "type": "arrow", }, ], @@ -1833,11 +1833,11 @@ History { "boundElements": [], }, }, - "id174" => Delta { + "id180" => Delta { "deleted": { "boundElements": [ { - "id": "id175", + "id": "id181", "type": "arrow", }, ], @@ -1979,7 +1979,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "frameId": null, "groupIds": [], "height": 100, - "id": "id176", + "id": "id182", "index": "a0", "isDeleted": false, "link": null, @@ -2011,7 +2011,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "frameId": null, "groupIds": [], "height": 100, - "id": "id177", + "id": "id183", "index": "a1", "isDeleted": false, "link": null, @@ -2053,7 +2053,7 @@ History { "elementsChange": ElementsChange { "added": Map {}, "removed": Map { - "id176" => Delta { + "id182" => Delta { "deleted": { "angle": 0, "backgroundColor": "transparent", @@ -2084,7 +2084,7 @@ History { "isDeleted": true, }, }, - "id177" => Delta { + "id183" => Delta { "deleted": { "angle": 0, "backgroundColor": "transparent", @@ -2210,7 +2210,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "scrollY": 0, "searchMatches": [], "selectedElementIds": { - "id180": true, + "id186": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": {}, @@ -2244,7 +2244,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "backgroundColor": "transparent", "boundElements": [ { - "id": "id180", + "id": "id186", "type": "arrow", }, ], @@ -2253,7 +2253,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "frameId": null, "groupIds": [], "height": 100, - "id": "id178", + "id": "id184", "index": "a0", "isDeleted": false, "link": null, @@ -2281,7 +2281,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "backgroundColor": "transparent", "boundElements": [ { - "id": "id180", + "id": "id186", "type": "arrow", }, ], @@ -2290,7 +2290,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "frameId": null, "groupIds": [], "height": 100, - "id": "id179", + "id": "id185", "index": "a1", "isDeleted": false, "link": null, @@ -2321,7 +2321,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "elbowed": false, "endArrowhead": "arrow", "endBinding": { - "elementId": "id179", + "elementId": "id185", "fixedPoint": null, "focus": 0, "gap": 1, @@ -2330,7 +2330,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl "frameId": null, "groupIds": [], "height": "408.19672", - "id": "id180", + "id": "id186", "index": "a2", "isDeleted": false, "lastCommittedPoint": null, @@ -2353,7 +2353,7 @@ exports[`history > multiplayer undo/redo > conflicts in arrows and their bindabl }, "startArrowhead": null, "startBinding": { - "elementId": "id178", + "elementId": "id184", "fixedPoint": null, "focus": 0, "gap": 1, @@ -2390,7 +2390,7 @@ History { "elementsChange": ElementsChange { "added": Map {}, "removed": Map { - "id178" => Delta { + "id184" => Delta { "deleted": { "angle": 0, "backgroundColor": "transparent", @@ -2421,7 +2421,7 @@ History { "isDeleted": true, }, }, - "id179" => Delta { + "id185" => Delta { "deleted": { "angle": 0, "backgroundColor": "transparent", @@ -2461,9 +2461,9 @@ History { "delta": Delta { "deleted": { "selectedElementIds": { - "id180": true, + "id186": true, }, - "selectedLinearElementId": "id180", + "selectedLinearElementId": "id186", }, "inserted": { "selectedElementIds": {}, @@ -2474,7 +2474,7 @@ History { "elementsChange": ElementsChange { "added": Map {}, "removed": Map { - "id180" => Delta { + "id186" => Delta { "deleted": { "angle": 0, "backgroundColor": "transparent", @@ -2483,7 +2483,7 @@ History { "elbowed": false, "endArrowhead": "arrow", "endBinding": { - "elementId": "id179", + "elementId": "id185", "fixedPoint": null, "focus": 0, "gap": 1, @@ -2514,7 +2514,7 @@ History { }, "startArrowhead": null, "startBinding": { - "elementId": "id178", + "elementId": "id184", "fixedPoint": null, "focus": 0, "gap": 1, @@ -2533,11 +2533,11 @@ History { }, }, "updated": Map { - "id178" => Delta { + "id184" => Delta { "deleted": { "boundElements": [ { - "id": "id180", + "id": "id186", "type": "arrow", }, ], @@ -2546,11 +2546,11 @@ History { "boundElements": [], }, }, - "id179" => Delta { + "id185" => Delta { "deleted": { "boundElements": [ { - "id": "id180", + "id": "id186", "type": "arrow", }, ], @@ -2688,7 +2688,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "backgroundColor": "transparent", "boundElements": [ { - "id": "id147", + "id": "id153", "type": "text", }, ], @@ -2697,7 +2697,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 100, - "id": "id145", + "id": "id151", "index": "a0", "isDeleted": false, "link": null, @@ -2733,7 +2733,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 100, - "id": "id146", + "id": "id152", "index": "a1", "isDeleted": false, "lineHeight": "1.25000", @@ -2766,7 +2766,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "autoResize": true, "backgroundColor": "transparent", "boundElements": null, - "containerId": "id145", + "containerId": "id151", "customData": undefined, "fillStyle": "solid", "fontFamily": 5, @@ -2774,7 +2774,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 25, - "id": "id147", + "id": "id153", "index": "a2", "isDeleted": false, "lineHeight": "1.25000", @@ -2821,7 +2821,7 @@ History { "added": Map {}, "removed": Map {}, "updated": Map { - "id145" => Delta { + "id151" => Delta { "deleted": { "isDeleted": false, }, @@ -2852,7 +2852,7 @@ History { "y": 10, }, }, - "id146" => Delta { + "id152" => Delta { "deleted": { "containerId": null, }, @@ -2990,7 +2990,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "backgroundColor": "transparent", "boundElements": [ { - "id": "id150", + "id": "id156", "type": "text", }, ], @@ -2999,7 +2999,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 100, - "id": "id148", + "id": "id154", "index": "Zz", "isDeleted": false, "link": null, @@ -3027,7 +3027,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "autoResize": true, "backgroundColor": "transparent", "boundElements": null, - "containerId": "id148", + "containerId": "id154", "customData": undefined, "fillStyle": "solid", "fontFamily": 5, @@ -3035,7 +3035,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 100, - "id": "id149", + "id": "id155", "index": "a0", "isDeleted": true, "lineHeight": "1.25000", @@ -3068,7 +3068,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "autoResize": true, "backgroundColor": "transparent", "boundElements": null, - "containerId": "id148", + "containerId": "id154", "customData": undefined, "fillStyle": "solid", "fontFamily": 5, @@ -3076,7 +3076,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 25, - "id": "id150", + "id": "id156", "index": "a1", "isDeleted": false, "lineHeight": "1.25000", @@ -3121,9 +3121,9 @@ History { }, "elementsChange": ElementsChange { "added": Map { - "id149" => Delta { + "id155" => Delta { "deleted": { - "containerId": "id148", + "containerId": "id154", "isDeleted": true, }, "inserted": { @@ -3134,14 +3134,14 @@ History { }, "removed": Map {}, "updated": Map { - "id148" => Delta { + "id154" => Delta { "deleted": { "boundElements": [], }, "inserted": { "boundElements": [ { - "id": "id149", + "id": "id155", "type": "text", }, ], @@ -3277,7 +3277,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "backgroundColor": "transparent", "boundElements": [ { - "id": "id137", + "id": "id143", "type": "text", }, ], @@ -3286,7 +3286,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 100, - "id": "id135", + "id": "id141", "index": "a0", "isDeleted": false, "link": null, @@ -3314,7 +3314,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "autoResize": true, "backgroundColor": "transparent", "boundElements": null, - "containerId": "id135", + "containerId": "id141", "customData": undefined, "fillStyle": "solid", "fontFamily": 5, @@ -3322,7 +3322,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 25, - "id": "id137", + "id": "id143", "index": "a0V", "isDeleted": false, "lineHeight": "1.25000", @@ -3363,7 +3363,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 25, - "id": "id136", + "id": "id142", "index": "a1", "isDeleted": false, "lineHeight": "1.25000", @@ -3410,11 +3410,11 @@ History { "added": Map {}, "removed": Map {}, "updated": Map { - "id135" => Delta { + "id141" => Delta { "deleted": { "boundElements": [ { - "id": "id137", + "id": "id143", "type": "text", }, ], @@ -3422,23 +3422,23 @@ History { "inserted": { "boundElements": [ { - "id": "id136", + "id": "id142", "type": "text", }, ], }, }, - "id136" => Delta { + "id142" => Delta { "deleted": { "containerId": null, }, "inserted": { - "containerId": "id135", + "containerId": "id141", }, }, - "id137" => Delta { + "id143" => Delta { "deleted": { - "containerId": "id135", + "containerId": "id141", }, "inserted": { "containerId": null, @@ -3578,7 +3578,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 100, - "id": "id138", + "id": "id144", "index": "a0", "isDeleted": false, "link": null, @@ -3606,7 +3606,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "backgroundColor": "transparent", "boundElements": [ { - "id": "id139", + "id": "id145", "type": "text", }, ], @@ -3615,7 +3615,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 60, - "id": "id140", + "id": "id146", "index": "a0V", "isDeleted": false, "link": null, @@ -3643,7 +3643,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "autoResize": true, "backgroundColor": "transparent", "boundElements": null, - "containerId": "id140", + "containerId": "id146", "customData": undefined, "fillStyle": "solid", "fontFamily": 5, @@ -3651,7 +3651,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 50, - "id": "id139", + "id": "id145", "index": "a1", "isDeleted": false, "lineHeight": "1.25000", @@ -3699,32 +3699,32 @@ History { "added": Map {}, "removed": Map {}, "updated": Map { - "id138" => Delta { + "id144" => Delta { "deleted": { "boundElements": [], }, "inserted": { "boundElements": [ { - "id": "id139", + "id": "id145", "type": "text", }, ], }, }, - "id139" => Delta { + "id145" => Delta { "deleted": { - "containerId": "id140", + "containerId": "id146", }, "inserted": { - "containerId": "id138", + "containerId": "id144", }, }, - "id140" => Delta { + "id146" => Delta { "deleted": { "boundElements": [ { - "id": "id139", + "id": "id145", "type": "text", }, ], @@ -3867,7 +3867,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 100, - "id": "id133", + "id": "id139", "index": "a0", "isDeleted": false, "link": null, @@ -3903,7 +3903,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 25, - "id": "id134", + "id": "id140", "index": "a1", "isDeleted": false, "lineHeight": "1.25000", @@ -3950,25 +3950,25 @@ History { "added": Map {}, "removed": Map {}, "updated": Map { - "id133" => Delta { + "id139" => Delta { "deleted": { "boundElements": [], }, "inserted": { "boundElements": [ { - "id": "id134", + "id": "id140", "type": "text", }, ], }, }, - "id134" => Delta { + "id140" => Delta { "deleted": { "containerId": null, }, "inserted": { - "containerId": "id133", + "containerId": "id139", }, }, }, @@ -4101,7 +4101,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "backgroundColor": "transparent", "boundElements": [ { - "id": "id142", + "id": "id148", "type": "text", }, ], @@ -4110,7 +4110,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 100, - "id": "id141", + "id": "id147", "index": "a0", "isDeleted": false, "link": null, @@ -4138,7 +4138,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "autoResize": true, "backgroundColor": "transparent", "boundElements": null, - "containerId": "id141", + "containerId": "id147", "customData": undefined, "fillStyle": "solid", "fontFamily": 5, @@ -4146,7 +4146,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 25, - "id": "id142", + "id": "id148", "index": "a1", "isDeleted": false, "lineHeight": "1.25000", @@ -4193,7 +4193,7 @@ History { "elementsChange": ElementsChange { "added": Map {}, "removed": Map { - "id141" => Delta { + "id147" => Delta { "deleted": { "angle": 0, "backgroundColor": "transparent", @@ -4226,9 +4226,9 @@ History { }, }, "updated": Map { - "id142" => Delta { + "id148" => Delta { "deleted": { - "containerId": "id141", + "containerId": "id147", }, "inserted": { "containerId": null, @@ -4363,7 +4363,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "backgroundColor": "transparent", "boundElements": [ { - "id": "id144", + "id": "id150", "type": "text", }, ], @@ -4372,7 +4372,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 100, - "id": "id143", + "id": "id149", "index": "Zz", "isDeleted": false, "link": null, @@ -4400,7 +4400,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "autoResize": true, "backgroundColor": "transparent", "boundElements": null, - "containerId": "id143", + "containerId": "id149", "customData": undefined, "fillStyle": "solid", "fontFamily": 5, @@ -4408,7 +4408,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 25, - "id": "id144", + "id": "id150", "index": "a0", "isDeleted": false, "lineHeight": "1.25000", @@ -4455,13 +4455,13 @@ History { "elementsChange": ElementsChange { "added": Map {}, "removed": Map { - "id144" => Delta { + "id150" => Delta { "deleted": { "angle": 0, "autoResize": true, "backgroundColor": "transparent", "boundElements": null, - "containerId": "id143", + "containerId": "id149", "customData": undefined, "fillStyle": "solid", "fontFamily": 5, @@ -4497,11 +4497,11 @@ History { }, }, "updated": Map { - "id143" => Delta { + "id149" => Delta { "deleted": { "boundElements": [ { - "id": "id144", + "id": "id150", "type": "text", }, ], @@ -4639,7 +4639,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "backgroundColor": "transparent", "boundElements": [ { - "id": "id158", + "id": "id164", "type": "text", }, ], @@ -4648,7 +4648,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 100, - "id": "id157", + "id": "id163", "index": "Zz", "isDeleted": false, "link": null, @@ -4676,7 +4676,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "autoResize": true, "backgroundColor": "transparent", "boundElements": null, - "containerId": "id157", + "containerId": "id163", "customData": undefined, "fillStyle": "solid", "fontFamily": 5, @@ -4684,7 +4684,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 25, - "id": "id158", + "id": "id164", "index": "a0", "isDeleted": false, "lineHeight": "1.25000", @@ -4731,7 +4731,7 @@ History { "added": Map {}, "removed": Map {}, "updated": Map { - "id158" => Delta { + "id164" => Delta { "deleted": { "angle": 0, "x": 15, @@ -4873,7 +4873,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "backgroundColor": "transparent", "boundElements": [ { - "id": "id156", + "id": "id162", "type": "text", }, ], @@ -4882,7 +4882,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 100, - "id": "id155", + "id": "id161", "index": "a0", "isDeleted": false, "link": null, @@ -4910,7 +4910,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "autoResize": true, "backgroundColor": "transparent", "boundElements": null, - "containerId": "id155", + "containerId": "id161", "customData": undefined, "fillStyle": "solid", "fontFamily": 5, @@ -4918,7 +4918,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 25, - "id": "id156", + "id": "id162", "index": "a1", "isDeleted": false, "lineHeight": "1.25000", @@ -4966,7 +4966,7 @@ History { "added": Map {}, "removed": Map {}, "updated": Map { - "id155" => Delta { + "id161" => Delta { "deleted": { "angle": 90, "x": 200, @@ -5111,7 +5111,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 100, - "id": "id151", + "id": "id157", "index": "a0", "isDeleted": false, "link": null, @@ -5139,7 +5139,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "autoResize": true, "backgroundColor": "transparent", "boundElements": null, - "containerId": "id151", + "containerId": "id157", "customData": undefined, "fillStyle": "solid", "fontFamily": 5, @@ -5147,7 +5147,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 100, - "id": "id152", + "id": "id158", "index": "a1", "isDeleted": true, "lineHeight": "1.25000", @@ -5194,7 +5194,7 @@ History { "elementsChange": ElementsChange { "added": Map {}, "removed": Map { - "id151" => Delta { + "id157" => Delta { "deleted": { "boundElements": [], "isDeleted": false, @@ -5202,7 +5202,7 @@ History { "inserted": { "boundElements": [ { - "id": "id152", + "id": "id158", "type": "text", }, ], @@ -5339,7 +5339,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "backgroundColor": "transparent", "boundElements": [ { - "id": "id154", + "id": "id160", "type": "text", }, ], @@ -5348,7 +5348,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 100, - "id": "id153", + "id": "id159", "index": "Zz", "isDeleted": true, "link": null, @@ -5384,7 +5384,7 @@ exports[`history > multiplayer undo/redo > conflicts in bound text elements and "frameId": null, "groupIds": [], "height": 100, - "id": "id154", + "id": "id160", "index": "a0", "isDeleted": false, "lineHeight": "1.25000", @@ -5431,13 +5431,13 @@ History { "elementsChange": ElementsChange { "added": Map {}, "removed": Map { - "id154" => Delta { + "id160" => Delta { "deleted": { "containerId": null, "isDeleted": false, }, "inserted": { - "containerId": "id153", + "containerId": "id159", "isDeleted": true, }, }, @@ -5575,7 +5575,7 @@ exports[`history > multiplayer undo/redo > conflicts in frames and their childre "frameId": null, "groupIds": [], "height": 100, - "id": "id182", + "id": "id188", "index": "Zz", "isDeleted": false, "link": null, @@ -5607,7 +5607,7 @@ exports[`history > multiplayer undo/redo > conflicts in frames and their childre "frameId": null, "groupIds": [], "height": 500, - "id": "id181", + "id": "id187", "index": "a0", "isDeleted": true, "link": null, @@ -5650,7 +5650,7 @@ History { "elementsChange": ElementsChange { "added": Map {}, "removed": Map { - "id182" => Delta { + "id188" => Delta { "deleted": { "angle": 0, "backgroundColor": "transparent", @@ -5696,9 +5696,9 @@ History { "added": Map {}, "removed": Map {}, "updated": Map { - "id182" => Delta { + "id188" => Delta { "deleted": { - "frameId": "id181", + "frameId": "id187", }, "inserted": { "frameId": null, @@ -5793,7 +5793,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "penMode": false, "pendingImageElementId": null, "previousSelectedElementIds": { - "id110": true, + "id116": true, }, "resizingElement": null, "scrollX": 0, @@ -5838,7 +5838,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "A", ], "height": 100, - "id": "id109", + "id": "id115", "index": "a0", "isDeleted": false, "link": null, @@ -5872,7 +5872,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "A", ], "height": 100, - "id": "id110", + "id": "id116", "index": "a1", "isDeleted": true, "link": null, @@ -5909,8 +5909,8 @@ History { "delta": Delta { "deleted": { "selectedElementIds": { - "id109": true, - "id110": true, + "id115": true, + "id116": true, }, "selectedGroupIds": { "A": true, @@ -5926,7 +5926,7 @@ History { "added": Map {}, "removed": Map {}, "updated": Map { - "id109" => Delta { + "id115" => Delta { "deleted": { "angle": 0, "backgroundColor": "transparent", @@ -5959,7 +5959,7 @@ History { "isDeleted": true, }, }, - "id110" => Delta { + "id116" => Delta { "deleted": { "angle": 0, "backgroundColor": "transparent", @@ -6006,7 +6006,7 @@ History { "inserted": { "editingGroupId": null, "selectedElementIds": { - "id109": true, + "id115": true, }, "selectedGroupIds": { "A": true, @@ -6030,7 +6030,7 @@ History { "inserted": { "editingGroupId": "A", "selectedElementIds": { - "id110": true, + "id116": true, }, }, }, @@ -6127,7 +6127,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "penMode": false, "pendingImageElementId": null, "previousSelectedElementIds": { - "id97": true, + "id103": true, }, "resizingElement": null, "scrollX": 0, @@ -6170,7 +6170,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "frameId": null, "groupIds": [], "height": 10, - "id": "id95", + "id": "id101", "index": "a0", "isDeleted": false, "link": null, @@ -6202,7 +6202,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "frameId": null, "groupIds": [], "height": 10, - "id": "id96", + "id": "id102", "index": "a1", "isDeleted": true, "link": null, @@ -6234,7 +6234,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "frameId": null, "groupIds": [], "height": 10, - "id": "id97", + "id": "id103", "index": "a2", "isDeleted": true, "link": null, @@ -6271,7 +6271,7 @@ History { "delta": Delta { "deleted": { "selectedElementIds": { - "id95": true, + "id101": true, }, }, "inserted": { @@ -6282,7 +6282,7 @@ History { "elementsChange": ElementsChange { "added": Map {}, "removed": Map { - "id95" => Delta { + "id101" => Delta { "deleted": { "angle": 0, "backgroundColor": "transparent", @@ -6322,12 +6322,12 @@ History { "delta": Delta { "deleted": { "selectedElementIds": { - "id96": true, + "id102": true, }, }, "inserted": { "selectedElementIds": { - "id95": true, + "id101": true, }, }, }, @@ -6336,7 +6336,7 @@ History { "added": Map {}, "removed": Map {}, "updated": Map { - "id96" => Delta { + "id102" => Delta { "deleted": { "angle": 0, "backgroundColor": "#ffc9c9", @@ -6381,7 +6381,7 @@ History { "added": Map {}, "removed": Map {}, "updated": Map { - "id96" => Delta { + "id102" => Delta { "deleted": { "backgroundColor": "#ffc9c9", }, @@ -6397,12 +6397,12 @@ History { "delta": Delta { "deleted": { "selectedElementIds": { - "id97": true, + "id103": true, }, }, "inserted": { "selectedElementIds": { - "id96": true, + "id102": true, }, }, }, @@ -6411,7 +6411,7 @@ History { "added": Map {}, "removed": Map {}, "updated": Map { - "id97" => Delta { + "id103" => Delta { "deleted": { "angle": 0, "backgroundColor": "#ffc9c9", @@ -6456,7 +6456,7 @@ History { "added": Map {}, "removed": Map {}, "updated": Map { - "id97" => Delta { + "id103" => Delta { "deleted": { "x": 50, "y": 50, @@ -6555,15 +6555,15 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "penMode": false, "pendingImageElementId": null, "previousSelectedElementIds": { - "id100": true, + "id106": true, }, "resizingElement": null, "scrollX": 0, "scrollY": 0, "searchMatches": [], "selectedElementIds": { - "id100": true, - "id101": true, + "id106": true, + "id107": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": {}, @@ -6601,7 +6601,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "frameId": null, "groupIds": [], "height": 100, - "id": "id99", + "id": "id105", "index": "a0", "isDeleted": false, "link": null, @@ -6633,7 +6633,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "frameId": null, "groupIds": [], "height": 100, - "id": "id100", + "id": "id106", "index": "a1", "isDeleted": false, "link": null, @@ -6665,7 +6665,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "frameId": null, "groupIds": [], "height": 100, - "id": "id101", + "id": "id107", "index": "a2", "isDeleted": false, "link": null, @@ -6702,7 +6702,7 @@ History { "delta": Delta { "deleted": { "selectedElementIds": { - "id99": true, + "id105": true, }, }, "inserted": { @@ -6713,7 +6713,7 @@ History { "elementsChange": ElementsChange { "added": Map {}, "removed": Map { - "id99" => Delta { + "id105" => Delta { "deleted": { "angle": 0, "backgroundColor": "transparent", @@ -6744,7 +6744,7 @@ History { "isDeleted": true, }, }, - "id100" => Delta { + "id106" => Delta { "deleted": { "angle": 0, "backgroundColor": "transparent", @@ -6775,7 +6775,7 @@ History { "isDeleted": true, }, }, - "id101" => Delta { + "id107" => Delta { "deleted": { "angle": 0, "backgroundColor": "transparent", @@ -6815,12 +6815,12 @@ History { "delta": Delta { "deleted": { "selectedElementIds": { - "id100": true, + "id106": true, }, }, "inserted": { "selectedElementIds": { - "id99": true, + "id105": true, }, }, }, @@ -6836,7 +6836,7 @@ History { "delta": Delta { "deleted": { "selectedElementIds": { - "id101": true, + "id107": true, }, }, "inserted": { @@ -6944,10 +6944,10 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "scrollY": 0, "searchMatches": [], "selectedElementIds": { - "id105": true, - "id106": true, - "id107": true, - "id108": true, + "id111": true, + "id112": true, + "id113": true, + "id114": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": { @@ -6990,7 +6990,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "A", ], "height": 100, - "id": "id105", + "id": "id111", "index": "a0", "isDeleted": false, "link": null, @@ -7024,7 +7024,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "A", ], "height": 100, - "id": "id106", + "id": "id112", "index": "a1", "isDeleted": false, "link": null, @@ -7058,7 +7058,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "B", ], "height": 100, - "id": "id107", + "id": "id113", "index": "a2", "isDeleted": false, "link": null, @@ -7092,7 +7092,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "B", ], "height": 100, - "id": "id108", + "id": "id114", "index": "a3", "isDeleted": false, "link": null, @@ -7129,8 +7129,8 @@ History { "delta": Delta { "deleted": { "selectedElementIds": { - "id105": true, - "id106": true, + "id111": true, + "id112": true, }, "selectedGroupIds": { "A": true, @@ -7153,8 +7153,8 @@ History { "delta": Delta { "deleted": { "selectedElementIds": { - "id107": true, - "id108": true, + "id113": true, + "id114": true, }, "selectedGroupIds": { "B": true, @@ -7263,7 +7263,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "scrollY": 0, "searchMatches": [], "selectedElementIds": { - "id113": true, + "id119": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": {}, @@ -7304,7 +7304,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "frameId": null, "groupIds": [], "height": 10, - "id": "id113", + "id": "id119", "index": "a0", "isDeleted": true, "lastCommittedPoint": [ @@ -7357,7 +7357,7 @@ History { "delta": Delta { "deleted": { "selectedElementIds": { - "id113": true, + "id119": true, }, }, "inserted": { @@ -7368,7 +7368,7 @@ History { "elementsChange": ElementsChange { "added": Map {}, "removed": Map { - "id113" => Delta { + "id119" => Delta { "deleted": { "angle": 0, "backgroundColor": "transparent", @@ -7426,7 +7426,7 @@ History { "appStateChange": AppStateChange { "delta": Delta { "deleted": { - "selectedLinearElementId": "id113", + "selectedLinearElementId": "id119", }, "inserted": { "selectedLinearElementId": null, @@ -7443,7 +7443,7 @@ History { "appStateChange": AppStateChange { "delta": Delta { "deleted": { - "editingLinearElementId": "id113", + "editingLinearElementId": "id119", }, "inserted": { "editingLinearElementId": null, @@ -7463,7 +7463,7 @@ History { "editingLinearElementId": null, }, "inserted": { - "editingLinearElementId": "id113", + "editingLinearElementId": "id119", }, }, }, @@ -7600,7 +7600,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "frameId": null, "groupIds": [], "height": 10, - "id": "id94", + "id": "id100", "index": "a0", "isDeleted": true, "link": null, @@ -7637,7 +7637,7 @@ History { "delta": Delta { "deleted": { "selectedElementIds": { - "id94": true, + "id100": true, }, }, "inserted": { @@ -7649,7 +7649,7 @@ History { "added": Map {}, "removed": Map {}, "updated": Map { - "id94" => Delta { + "id100" => Delta { "deleted": { "angle": 0, "backgroundColor": "#ffec99", @@ -7694,7 +7694,7 @@ History { "added": Map {}, "removed": Map {}, "updated": Map { - "id94" => Delta { + "id100" => Delta { "deleted": { "backgroundColor": "#ffec99", }, @@ -7832,7 +7832,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "frameId": null, "groupIds": [], "height": 100, - "id": "id119", + "id": "id125", "index": "a1", "isDeleted": true, "link": null, @@ -7864,7 +7864,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "frameId": null, "groupIds": [], "height": 100, - "id": "id120", + "id": "id126", "index": "a3V", "isDeleted": true, "link": null, @@ -7896,7 +7896,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "frameId": null, "groupIds": [], "height": 100, - "id": "id118", + "id": "id124", "index": "a4", "isDeleted": true, "link": null, @@ -7938,7 +7938,7 @@ History { "added": Map {}, "removed": Map {}, "updated": Map { - "id119" => Delta { + "id125" => Delta { "deleted": { "index": "a1", }, @@ -7957,14 +7957,14 @@ History { }, "inserted": { "selectedElementIds": { - "id119": true, + "id125": true, }, }, }, }, "elementsChange": ElementsChange { "added": Map { - "id118" => Delta { + "id124" => Delta { "deleted": { "isDeleted": true, }, @@ -7995,7 +7995,7 @@ History { "y": 10, }, }, - "id119" => Delta { + "id125" => Delta { "deleted": { "isDeleted": true, }, @@ -8026,7 +8026,7 @@ History { "y": 20, }, }, - "id120" => Delta { + "id126" => Delta { "deleted": { "isDeleted": true, }, @@ -8190,7 +8190,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "frameId": null, "groupIds": [], "height": 100, - "id": "id114", + "id": "id120", "index": "Zx", "isDeleted": true, "link": null, @@ -8222,7 +8222,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "frameId": null, "groupIds": [], "height": 100, - "id": "id116", + "id": "id122", "index": "Zy", "isDeleted": true, "link": null, @@ -8254,7 +8254,7 @@ exports[`history > multiplayer undo/redo > should iterate through the history wh "frameId": null, "groupIds": [], "height": 100, - "id": "id115", + "id": "id121", "index": "a1", "isDeleted": true, "link": null, @@ -8296,7 +8296,7 @@ History { "added": Map {}, "removed": Map {}, "updated": Map { - "id115" => Delta { + "id121" => Delta { "deleted": { "index": "a1", }, @@ -8315,14 +8315,14 @@ History { }, "inserted": { "selectedElementIds": { - "id115": true, + "id121": true, }, }, }, }, "elementsChange": ElementsChange { "added": Map { - "id114" => Delta { + "id120" => Delta { "deleted": { "isDeleted": true, }, @@ -8353,7 +8353,7 @@ History { "y": 10, }, }, - "id115" => Delta { + "id121" => Delta { "deleted": { "isDeleted": true, }, @@ -8384,7 +8384,7 @@ History { "y": 20, }, }, - "id116" => Delta { + "id122" => Delta { "deleted": { "isDeleted": true, }, @@ -8507,16 +8507,16 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte "penMode": false, "pendingImageElementId": null, "previousSelectedElementIds": { - "id127": true, - "id128": true, + "id133": true, + "id134": true, }, "resizingElement": null, "scrollX": 0, "scrollY": 0, "searchMatches": [], "selectedElementIds": { - "id127": true, - "id128": true, + "id133": true, + "id134": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": {}, @@ -8554,7 +8554,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte "frameId": null, "groupIds": [], "height": 10, - "id": "id127", + "id": "id133", "index": "a0", "isDeleted": false, "link": null, @@ -8586,7 +8586,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte "frameId": null, "groupIds": [], "height": 10, - "id": "id128", + "id": "id134", "index": "a1", "isDeleted": false, "link": null, @@ -8618,7 +8618,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte "frameId": null, "groupIds": [], "height": 100, - "id": "id132", + "id": "id138", "index": "a2", "isDeleted": false, "link": null, @@ -8655,7 +8655,7 @@ History { "delta": Delta { "deleted": { "selectedElementIds": { - "id127": true, + "id133": true, }, }, "inserted": { @@ -8666,7 +8666,7 @@ History { "elementsChange": ElementsChange { "added": Map {}, "removed": Map { - "id127" => Delta { + "id133" => Delta { "deleted": { "angle": 0, "backgroundColor": "transparent", @@ -8706,12 +8706,12 @@ History { "delta": Delta { "deleted": { "selectedElementIds": { - "id128": true, + "id134": true, }, }, "inserted": { "selectedElementIds": { - "id127": true, + "id133": true, }, }, }, @@ -8719,7 +8719,7 @@ History { "elementsChange": ElementsChange { "added": Map {}, "removed": Map { - "id128" => Delta { + "id134" => Delta { "deleted": { "angle": 0, "backgroundColor": "transparent", @@ -8759,12 +8759,12 @@ History { "delta": Delta { "deleted": { "selectedElementIds": { - "id127": true, + "id133": true, }, }, "inserted": { "selectedElementIds": { - "id128": true, + "id134": true, }, }, }, @@ -8780,7 +8780,7 @@ History { "delta": Delta { "deleted": { "selectedElementIds": { - "id128": true, + "id134": true, }, }, "inserted": { @@ -8805,7 +8805,7 @@ History { "added": Map {}, "removed": Map {}, "updated": Map { - "id127" => Delta { + "id133" => Delta { "deleted": { "x": 90, "y": 90, @@ -8815,7 +8815,7 @@ History { "y": 10, }, }, - "id128" => Delta { + "id134" => Delta { "deleted": { "x": 110, "y": 110, @@ -8955,7 +8955,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte "frameId": null, "groupIds": [], "height": 50, - "id": "id122", + "id": "id128", "index": "a0", "isDeleted": false, "lastCommittedPoint": [ @@ -9014,7 +9014,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte "frameId": null, "groupIds": [], "height": 100, - "id": "id123", + "id": "id129", "index": "a1", "isDeleted": false, "link": null, @@ -9056,7 +9056,7 @@ History { "elementsChange": ElementsChange { "added": Map {}, "removed": Map { - "id122" => Delta { + "id128" => Delta { "deleted": { "angle": 0, "backgroundColor": "transparent", @@ -9209,7 +9209,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte "scrollY": 0, "searchMatches": [], "selectedElementIds": { - "id124": true, + "id130": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": {}, @@ -9247,7 +9247,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte "frameId": null, "groupIds": [], "height": 90, - "id": "id124", + "id": "id130", "index": "a0", "isDeleted": false, "link": null, @@ -9279,7 +9279,7 @@ exports[`history > multiplayer undo/redo > should not let remote changes to inte "frameId": null, "groupIds": [], "height": 100, - "id": "id126", + "id": "id132", "index": "a1", "isDeleted": false, "link": null, @@ -9316,7 +9316,7 @@ History { "delta": Delta { "deleted": { "selectedElementIds": { - "id124": true, + "id130": true, }, }, "inserted": { @@ -9327,7 +9327,7 @@ History { "elementsChange": ElementsChange { "added": Map {}, "removed": Map { - "id124" => Delta { + "id130" => Delta { "deleted": { "angle": 0, "backgroundColor": "transparent", @@ -9373,7 +9373,7 @@ History { "added": Map {}, "removed": Map {}, "updated": Map { - "id124" => Delta { + "id130" => Delta { "deleted": { "height": 90, "width": 90, @@ -9477,7 +9477,7 @@ exports[`history > multiplayer undo/redo > should not override remote changes on "scrollY": 0, "searchMatches": [], "selectedElementIds": { - "id81": true, + "id87": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": {}, @@ -9515,7 +9515,7 @@ exports[`history > multiplayer undo/redo > should not override remote changes on "frameId": null, "groupIds": [], "height": 10, - "id": "id81", + "id": "id87", "index": "a0", "isDeleted": false, "link": null, @@ -9547,7 +9547,7 @@ exports[`history > multiplayer undo/redo > should not override remote changes on "frameId": null, "groupIds": [], "height": 100, - "id": "id82", + "id": "id88", "index": "a1", "isDeleted": false, "link": null, @@ -9589,7 +9589,7 @@ History { "added": Map {}, "removed": Map {}, "updated": Map { - "id81" => Delta { + "id87" => Delta { "deleted": { "backgroundColor": "transparent", }, @@ -9607,7 +9607,7 @@ History { "delta": Delta { "deleted": { "selectedElementIds": { - "id81": true, + "id87": true, }, }, "inserted": { @@ -9618,7 +9618,7 @@ History { "elementsChange": ElementsChange { "added": Map {}, "removed": Map { - "id81" => Delta { + "id87" => Delta { "deleted": { "angle": 0, "backgroundColor": "transparent", @@ -9744,7 +9744,7 @@ exports[`history > multiplayer undo/redo > should not override remote changes on "scrollY": 0, "searchMatches": [], "selectedElementIds": { - "id83": true, + "id89": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": {}, @@ -9782,7 +9782,7 @@ exports[`history > multiplayer undo/redo > should not override remote changes on "frameId": null, "groupIds": [], "height": 10, - "id": "id83", + "id": "id89", "index": "a0", "isDeleted": false, "link": null, @@ -9819,7 +9819,7 @@ History { "delta": Delta { "deleted": { "selectedElementIds": { - "id83": true, + "id89": true, }, }, "inserted": { @@ -9830,7 +9830,7 @@ History { "elementsChange": ElementsChange { "added": Map {}, "removed": Map { - "id83" => Delta { + "id89" => Delta { "deleted": { "angle": 0, "backgroundColor": "transparent", @@ -9876,7 +9876,7 @@ History { "added": Map {}, "removed": Map {}, "updated": Map { - "id83" => Delta { + "id89" => Delta { "deleted": { "backgroundColor": "#ffc9c9", }, @@ -10020,7 +10020,7 @@ exports[`history > multiplayer undo/redo > should override remotely added groups "B", ], "height": 100, - "id": "id88", + "id": "id94", "index": "a0", "isDeleted": false, "link": null, @@ -10055,7 +10055,7 @@ exports[`history > multiplayer undo/redo > should override remotely added groups "B", ], "height": 100, - "id": "id89", + "id": "id95", "index": "a1", "isDeleted": false, "link": null, @@ -10089,7 +10089,7 @@ exports[`history > multiplayer undo/redo > should override remotely added groups "B", ], "height": 100, - "id": "id90", + "id": "id96", "index": "a2", "isDeleted": false, "link": null, @@ -10123,7 +10123,7 @@ exports[`history > multiplayer undo/redo > should override remotely added groups "B", ], "height": 100, - "id": "id91", + "id": "id97", "index": "a3", "isDeleted": false, "link": null, @@ -10166,7 +10166,7 @@ History { "added": Map {}, "removed": Map {}, "updated": Map { - "id88" => Delta { + "id94" => Delta { "deleted": { "groupIds": [ "A", @@ -10177,7 +10177,7 @@ History { "groupIds": [], }, }, - "id89" => Delta { + "id95" => Delta { "deleted": { "groupIds": [ "A", @@ -10282,7 +10282,7 @@ exports[`history > multiplayer undo/redo > should override remotely added points "scrollY": 0, "searchMatches": [], "selectedElementIds": { - "id92": true, + "id98": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": {}, @@ -10323,7 +10323,7 @@ exports[`history > multiplayer undo/redo > should override remotely added points "frameId": null, "groupIds": [], "height": 30, - "id": "id92", + "id": "id98", "index": "a0", "isDeleted": false, "lastCommittedPoint": [ @@ -10388,7 +10388,7 @@ History { "delta": Delta { "deleted": { "selectedElementIds": { - "id92": true, + "id98": true, }, }, "inserted": { @@ -10399,7 +10399,7 @@ History { "elementsChange": ElementsChange { "added": Map {}, "removed": Map { - "id92" => Delta { + "id98" => Delta { "deleted": { "angle": 0, "backgroundColor": "transparent", @@ -10464,7 +10464,7 @@ History { "added": Map {}, "removed": Map {}, "updated": Map { - "id92" => Delta { + "id98" => Delta { "deleted": { "height": 30, "lastCommittedPoint": [ @@ -10521,7 +10521,7 @@ History { "appStateChange": AppStateChange { "delta": Delta { "deleted": { - "selectedLinearElementId": "id92", + "selectedLinearElementId": "id98", }, "inserted": { "selectedLinearElementId": null, @@ -10661,7 +10661,7 @@ exports[`history > multiplayer undo/redo > should redistribute deltas when eleme "frameId": null, "groupIds": [], "height": 10, - "id": "id93", + "id": "id99", "index": "a0", "isDeleted": false, "link": null, @@ -10698,7 +10698,7 @@ History { "delta": Delta { "deleted": { "selectedElementIds": { - "id93": true, + "id99": true, }, }, "inserted": { @@ -10709,7 +10709,7 @@ History { "elementsChange": ElementsChange { "added": Map {}, "removed": Map { - "id93" => Delta { + "id99" => Delta { "deleted": { "angle": 0, "backgroundColor": "#ffec99", @@ -10752,7 +10752,7 @@ History { }, "inserted": { "selectedElementIds": { - "id93": true, + "id99": true, }, }, }, @@ -10761,7 +10761,7 @@ History { "added": Map {}, "removed": Map {}, "updated": Map { - "id93" => Delta { + "id99" => Delta { "deleted": { "isDeleted": false, }, @@ -11315,7 +11315,7 @@ exports[`history > multiplayer undo/redo > should update history entries after r "scrollY": 0, "searchMatches": [], "selectedElementIds": { - "id84": true, + "id90": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": {}, @@ -11353,7 +11353,7 @@ exports[`history > multiplayer undo/redo > should update history entries after r "frameId": null, "groupIds": [], "height": 10, - "id": "id84", + "id": "id90", "index": "a0", "isDeleted": false, "link": null, @@ -11395,7 +11395,7 @@ History { "added": Map {}, "removed": Map {}, "updated": Map { - "id84" => Delta { + "id90" => Delta { "deleted": { "backgroundColor": "#d0bfff", }, @@ -11417,7 +11417,7 @@ History { "added": Map {}, "removed": Map {}, "updated": Map { - "id84" => Delta { + "id90" => Delta { "deleted": { "backgroundColor": "transparent", }, @@ -11435,7 +11435,7 @@ History { "delta": Delta { "deleted": { "selectedElementIds": { - "id84": true, + "id90": true, }, }, "inserted": { @@ -11446,7 +11446,7 @@ History { "elementsChange": ElementsChange { "added": Map {}, "removed": Map { - "id84" => Delta { + "id90" => Delta { "deleted": { "angle": 0, "backgroundColor": "transparent", @@ -11640,7 +11640,7 @@ exports[`history > singleplayer undo/redo > remounting undo/redo buttons should "frameId": null, "groupIds": [], "height": 10, - "id": "id80", + "id": "id86", "index": "a1", "isDeleted": true, "link": null, @@ -11679,14 +11679,14 @@ History { }, "inserted": { "selectedElementIds": { - "id80": true, + "id86": true, }, }, }, }, "elementsChange": ElementsChange { "added": Map { - "id80" => Delta { + "id86" => Delta { "deleted": { "isDeleted": true, }, @@ -12712,7 +12712,7 @@ exports[`history > singleplayer undo/redo > should disable undo/redo buttons whe "scrollY": 0, "searchMatches": [], "selectedElementIds": { - "id78": true, + "id84": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": {}, @@ -12782,7 +12782,7 @@ exports[`history > singleplayer undo/redo > should disable undo/redo buttons whe "frameId": null, "groupIds": [], "height": 10, - "id": "id78", + "id": "id84", "index": "a1", "isDeleted": false, "link": null, @@ -12819,7 +12819,7 @@ History { "delta": Delta { "deleted": { "selectedElementIds": { - "id78": true, + "id84": true, }, }, "inserted": { @@ -12830,7 +12830,7 @@ History { "elementsChange": ElementsChange { "added": Map {}, "removed": Map { - "id78" => Delta { + "id84" => Delta { "deleted": { "angle": 0, "backgroundColor": "transparent", @@ -14881,1328 +14881,6 @@ exports[`history > singleplayer undo/redo > should support appstate name or view exports[`history > singleplayer undo/redo > should support appstate name or viewBackgroundColor change > [end of test] number of renders 1`] = `8`; exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind arrow from non deleted bindable elements on deletion and rebind on undo > [end of test] appState 1`] = ` -{ - "activeEmbeddable": null, - "activeTool": { - "customType": null, - "lastActiveTool": null, - "locked": false, - "type": "selection", - }, - "collaborators": Map {}, - "contextMenu": null, - "croppingElementId": null, - "currentChartType": "bar", - "currentHoveredFontFamily": null, - "currentItemArrowType": "round", - "currentItemBackgroundColor": "transparent", - "currentItemEndArrowhead": "arrow", - "currentItemFillStyle": "solid", - "currentItemFontFamily": 5, - "currentItemFontSize": 20, - "currentItemOpacity": 100, - "currentItemRoughness": 1, - "currentItemRoundness": "round", - "currentItemStartArrowhead": null, - "currentItemStrokeColor": "#1e1e1e", - "currentItemStrokeStyle": "solid", - "currentItemStrokeWidth": 2, - "currentItemTextAlign": "left", - "cursorButton": "up", - "defaultSidebarDockedPreference": false, - "editingFrame": null, - "editingGroupId": null, - "editingLinearElement": null, - "editingTextElement": null, - "elementsToHighlight": null, - "errorMessage": null, - "exportBackground": true, - "exportEmbedScene": false, - "exportScale": 1, - "exportWithDarkMode": false, - "fileHandle": null, - "followedBy": Set {}, - "frameRendering": { - "clip": true, - "enabled": true, - "name": true, - "outline": true, - }, - "frameToHighlight": null, - "gridModeEnabled": false, - "gridSize": 20, - "gridStep": 5, - "height": 0, - "hoveredElementIds": {}, - "isBindingEnabled": true, - "isCropping": false, - "isLoading": false, - "isResizing": false, - "isRotating": false, - "lastPointerDownWith": "mouse", - "multiElement": null, - "newElement": null, - "objectsSnapModeEnabled": false, - "offsetLeft": 0, - "offsetTop": 0, - "openDialog": null, - "openMenu": null, - "openPopup": null, - "openSidebar": null, - "originSnapOffset": null, - "pasteDialog": { - "data": null, - "shown": false, - }, - "penDetected": false, - "penMode": false, - "pendingImageElementId": null, - "previousSelectedElementIds": { - "id50": true, - }, - "resizingElement": null, - "scrollX": 0, - "scrollY": 0, - "searchMatches": [], - "selectedElementIds": { - "id55": true, - }, - "selectedElementsAreBeingDragged": false, - "selectedGroupIds": {}, - "selectionElement": null, - "shouldCacheIgnoreZoom": false, - "showHyperlinkPopup": false, - "showWelcomeScreen": true, - "snapLines": [], - "startBoundElement": null, - "stats": { - "open": false, - "panels": 3, - }, - "suggestedBindings": [], - "theme": "light", - "toast": null, - "userToFollow": null, - "viewBackgroundColor": "#ffffff", - "viewModeEnabled": false, - "width": 0, - "zenModeEnabled": false, - "zoom": { - "value": 1, - }, -} -`; - -exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind arrow from non deleted bindable elements on deletion and rebind on undo > [end of test] element 0 1`] = ` -{ - "angle": 0, - "backgroundColor": "transparent", - "boundElements": [ - { - "id": "id51", - "type": "text", - }, - { - "id": "id55", - "type": "arrow", - }, - ], - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 100, - "id": "id50", - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "updated": 1, - "version": 8, - "width": 100, - "x": -100, - "y": -50, -} -`; - -exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind arrow from non deleted bindable elements on deletion and rebind on undo > [end of test] element 1 1`] = ` -{ - "angle": 0, - "autoResize": true, - "backgroundColor": "transparent", - "boundElements": null, - "containerId": "id50", - "customData": undefined, - "fillStyle": "solid", - "fontFamily": 5, - "fontSize": 20, - "frameId": null, - "groupIds": [], - "height": 25, - "id": "id51", - "index": "a1", - "isDeleted": false, - "lineHeight": "1.25000", - "link": null, - "locked": false, - "opacity": 100, - "originalText": "ola", - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "text": "ola", - "textAlign": "center", - "type": "text", - "updated": 1, - "version": 6, - "verticalAlign": "middle", - "width": 30, - "x": -65, - "y": "-12.50000", -} -`; - -exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind arrow from non deleted bindable elements on deletion and rebind on undo > [end of test] element 2 1`] = ` -{ - "angle": 0, - "backgroundColor": "transparent", - "boundElements": [ - { - "id": "id55", - "type": "arrow", - }, - ], - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 100, - "id": "id52", - "index": "a2", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "updated": 1, - "version": 7, - "width": 100, - "x": 100, - "y": -50, -} -`; - -exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind arrow from non deleted bindable elements on deletion and rebind on undo > [end of test] element 3 1`] = ` -{ - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "elbowed": false, - "endArrowhead": "arrow", - "endBinding": { - "elementId": "id52", - "fixedPoint": null, - "focus": 0, - "gap": 1, - }, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 0, - "id": "id55", - "index": "a3", - "isDeleted": false, - "lastCommittedPoint": null, - "link": null, - "locked": false, - "opacity": 100, - "points": [ - [ - 0, - 0, - ], - [ - "98.00000", - 0, - ], - ], - "roughness": 1, - "roundness": { - "type": 2, - }, - "startArrowhead": null, - "startBinding": { - "elementId": "id50", - "fixedPoint": null, - "focus": 0, - "gap": 1, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "arrow", - "updated": 1, - "version": 10, - "width": "98.00000", - "x": 1, - "y": 0, -} -`; - -exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind arrow from non deleted bindable elements on deletion and rebind on undo > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id55": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id55" => Delta { - "deleted": { - "isDeleted": false, - "points": [ - [ - 0, - 0, - ], - [ - 100, - 0, - ], - ], - }, - "inserted": { - "isDeleted": true, - "points": [ - [ - 0, - 0, - ], - [ - 100, - 0, - ], - ], - }, - }, - }, - "updated": Map { - "id50" => Delta { - "deleted": { - "boundElements": [ - { - "id": "id55", - "type": "arrow", - }, - ], - }, - "inserted": { - "boundElements": [], - }, - }, - "id52" => Delta { - "deleted": { - "boundElements": [ - { - "id": "id55", - "type": "arrow", - }, - ], - }, - "inserted": { - "boundElements": [], - }, - }, - }, - }, - }, - ], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id50" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 100, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": -100, - "y": -50, - }, - "inserted": { - "isDeleted": true, - }, - }, - "id51" => Delta { - "deleted": { - "angle": 0, - "autoResize": true, - "backgroundColor": "transparent", - "boundElements": null, - "containerId": null, - "customData": undefined, - "fillStyle": "solid", - "fontFamily": 5, - "fontSize": 20, - "frameId": null, - "groupIds": [], - "height": 100, - "index": "a1", - "isDeleted": false, - "lineHeight": "1.25000", - "link": null, - "locked": false, - "opacity": 100, - "originalText": "ola", - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "text": "ola", - "textAlign": "left", - "type": "text", - "verticalAlign": "top", - "width": 100, - "x": -200, - "y": -200, - }, - "inserted": { - "isDeleted": true, - }, - }, - "id52" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 100, - "index": "a2", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": 100, - "y": -50, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id50": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id51": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": {}, - }, - "inserted": { - "selectedElementIds": { - "id51": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id50" => Delta { - "deleted": { - "boundElements": [ - { - "id": "id51", - "type": "text", - }, - ], - }, - "inserted": { - "boundElements": [], - }, - }, - "id51" => Delta { - "deleted": { - "containerId": "id50", - "height": 25, - "textAlign": "center", - "verticalAlign": "middle", - "width": 30, - "x": -65, - "y": "-12.50000", - }, - "inserted": { - "containerId": null, - "height": 100, - "textAlign": "left", - "verticalAlign": "top", - "width": 100, - "x": -200, - "y": -200, - }, - }, - }, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id55": true, - }, - "selectedLinearElementId": "id55", - }, - "inserted": { - "selectedElementIds": { - "id50": true, - }, - "selectedLinearElementId": null, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id55" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "elbowed": false, - "endArrowhead": "arrow", - "endBinding": { - "elementId": "id52", - "fixedPoint": null, - "focus": 0, - "gap": 1, - }, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 0, - "index": "a3", - "isDeleted": false, - "lastCommittedPoint": null, - "link": null, - "locked": false, - "opacity": 100, - "points": [ - [ - 0, - 0, - ], - [ - 100, - 0, - ], - ], - "roughness": 1, - "roundness": { - "type": 2, - }, - "startArrowhead": null, - "startBinding": { - "elementId": "id50", - "fixedPoint": null, - "focus": 0, - "gap": 1, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "arrow", - "width": 100, - "x": 0, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map { - "id50" => Delta { - "deleted": { - "boundElements": [ - { - "id": "id55", - "type": "arrow", - }, - ], - }, - "inserted": { - "boundElements": [], - }, - }, - "id52" => Delta { - "deleted": { - "boundElements": [ - { - "id": "id55", - "type": "arrow", - }, - ], - }, - "inserted": { - "boundElements": [], - }, - }, - }, - }, - }, - ], -} -`; - -exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind arrow from non deleted bindable elements on deletion and rebind on undo > [end of test] number of elements 1`] = `4`; - -exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind arrow from non deleted bindable elements on deletion and rebind on undo > [end of test] number of renders 1`] = `12`; - -exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind arrow from non deleted bindable elements on undo and rebind on redo > [end of test] appState 1`] = ` -{ - "activeEmbeddable": null, - "activeTool": { - "customType": null, - "lastActiveTool": null, - "locked": false, - "type": "selection", - }, - "collaborators": Map {}, - "contextMenu": null, - "croppingElementId": null, - "currentChartType": "bar", - "currentHoveredFontFamily": null, - "currentItemArrowType": "round", - "currentItemBackgroundColor": "transparent", - "currentItemEndArrowhead": "arrow", - "currentItemFillStyle": "solid", - "currentItemFontFamily": 5, - "currentItemFontSize": 20, - "currentItemOpacity": 100, - "currentItemRoughness": 1, - "currentItemRoundness": "round", - "currentItemStartArrowhead": null, - "currentItemStrokeColor": "#1e1e1e", - "currentItemStrokeStyle": "solid", - "currentItemStrokeWidth": 2, - "currentItemTextAlign": "left", - "cursorButton": "up", - "defaultSidebarDockedPreference": false, - "editingFrame": null, - "editingGroupId": null, - "editingLinearElement": null, - "editingTextElement": null, - "elementsToHighlight": null, - "errorMessage": null, - "exportBackground": true, - "exportEmbedScene": false, - "exportScale": 1, - "exportWithDarkMode": false, - "fileHandle": null, - "followedBy": Set {}, - "frameRendering": { - "clip": true, - "enabled": true, - "name": true, - "outline": true, - }, - "frameToHighlight": null, - "gridModeEnabled": false, - "gridSize": 20, - "gridStep": 5, - "height": 0, - "hoveredElementIds": {}, - "isBindingEnabled": true, - "isCropping": false, - "isLoading": false, - "isResizing": false, - "isRotating": false, - "lastPointerDownWith": "mouse", - "multiElement": null, - "newElement": null, - "objectsSnapModeEnabled": false, - "offsetLeft": 0, - "offsetTop": 0, - "openDialog": null, - "openMenu": null, - "openPopup": null, - "openSidebar": null, - "originSnapOffset": null, - "pasteDialog": { - "data": null, - "shown": false, - }, - "penDetected": false, - "penMode": false, - "pendingImageElementId": null, - "previousSelectedElementIds": { - "id44": true, - }, - "resizingElement": null, - "scrollX": 0, - "scrollY": 0, - "searchMatches": [], - "selectedElementIds": { - "id49": true, - }, - "selectedElementsAreBeingDragged": false, - "selectedGroupIds": {}, - "selectionElement": null, - "shouldCacheIgnoreZoom": false, - "showHyperlinkPopup": false, - "showWelcomeScreen": true, - "snapLines": [], - "startBoundElement": null, - "stats": { - "open": false, - "panels": 3, - }, - "suggestedBindings": [], - "theme": "light", - "toast": null, - "userToFollow": null, - "viewBackgroundColor": "#ffffff", - "viewModeEnabled": false, - "width": 0, - "zenModeEnabled": false, - "zoom": { - "value": 1, - }, -} -`; - -exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind arrow from non deleted bindable elements on undo and rebind on redo > [end of test] element 0 1`] = ` -{ - "angle": 0, - "backgroundColor": "transparent", - "boundElements": [ - { - "id": "id45", - "type": "text", - }, - { - "id": "id49", - "type": "arrow", - }, - ], - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 100, - "id": "id44", - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "updated": 1, - "version": 8, - "width": 100, - "x": -100, - "y": -50, -} -`; - -exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind arrow from non deleted bindable elements on undo and rebind on redo > [end of test] element 1 1`] = ` -{ - "angle": 0, - "autoResize": true, - "backgroundColor": "transparent", - "boundElements": null, - "containerId": "id44", - "customData": undefined, - "fillStyle": "solid", - "fontFamily": 5, - "fontSize": 20, - "frameId": null, - "groupIds": [], - "height": 25, - "id": "id45", - "index": "a1", - "isDeleted": false, - "lineHeight": "1.25000", - "link": null, - "locked": false, - "opacity": 100, - "originalText": "ola", - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "text": "ola", - "textAlign": "center", - "type": "text", - "updated": 1, - "version": 8, - "verticalAlign": "middle", - "width": 30, - "x": -65, - "y": "-12.50000", -} -`; - -exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind arrow from non deleted bindable elements on undo and rebind on redo > [end of test] element 2 1`] = ` -{ - "angle": 0, - "backgroundColor": "transparent", - "boundElements": [ - { - "id": "id49", - "type": "arrow", - }, - ], - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 100, - "id": "id46", - "index": "a2", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "updated": 1, - "version": 7, - "width": 100, - "x": 100, - "y": -50, -} -`; - -exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind arrow from non deleted bindable elements on undo and rebind on redo > [end of test] element 3 1`] = ` -{ - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "elbowed": false, - "endArrowhead": "arrow", - "endBinding": { - "elementId": "id46", - "fixedPoint": null, - "focus": 0, - "gap": 1, - }, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 0, - "id": "id49", - "index": "a3", - "isDeleted": false, - "lastCommittedPoint": null, - "link": null, - "locked": false, - "opacity": 100, - "points": [ - [ - 0, - 0, - ], - [ - "98.00000", - 0, - ], - ], - "roughness": 1, - "roundness": { - "type": 2, - }, - "startArrowhead": null, - "startBinding": { - "elementId": "id44", - "fixedPoint": null, - "focus": 0, - "gap": 1, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "arrow", - "updated": 1, - "version": 10, - "width": "98.00000", - "x": 1, - "y": 0, -} -`; - -exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind arrow from non deleted bindable elements on undo and rebind on redo > [end of test] history 1`] = ` -History { - "onHistoryChangedEmitter": Emitter { - "subscribers": [ - [Function], - [Function], - ], - }, - "redoStack": [], - "undoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": {}, - "inserted": {}, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id44" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 100, - "index": "a0", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": -100, - "y": -50, - }, - "inserted": { - "isDeleted": true, - }, - }, - "id45" => Delta { - "deleted": { - "angle": 0, - "autoResize": true, - "backgroundColor": "transparent", - "boundElements": null, - "containerId": null, - "customData": undefined, - "fillStyle": "solid", - "fontFamily": 5, - "fontSize": 20, - "frameId": null, - "groupIds": [], - "height": 100, - "index": "a1", - "isDeleted": false, - "lineHeight": "1.25000", - "link": null, - "locked": false, - "opacity": 100, - "originalText": "ola", - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "text": "ola", - "textAlign": "left", - "type": "text", - "verticalAlign": "top", - "width": 100, - "x": -200, - "y": -200, - }, - "inserted": { - "isDeleted": true, - }, - }, - "id46" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 100, - "index": "a2", - "isDeleted": false, - "link": null, - "locked": false, - "opacity": 100, - "roughness": 1, - "roundness": { - "type": 3, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "width": 100, - "x": 100, - "y": -50, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id44": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id45": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map {}, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": {}, - }, - "inserted": { - "selectedElementIds": { - "id45": true, - }, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map {}, - "updated": Map { - "id44" => Delta { - "deleted": { - "boundElements": [ - { - "id": "id45", - "type": "text", - }, - ], - }, - "inserted": { - "boundElements": [], - }, - }, - "id45" => Delta { - "deleted": { - "containerId": "id44", - "height": 25, - "textAlign": "center", - "verticalAlign": "middle", - "width": 30, - "x": -65, - "y": "-12.50000", - }, - "inserted": { - "containerId": null, - "height": 100, - "textAlign": "left", - "verticalAlign": "top", - "width": 100, - "x": -200, - "y": -200, - }, - }, - }, - }, - }, - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id49": true, - }, - "selectedLinearElementId": "id49", - }, - "inserted": { - "selectedElementIds": { - "id44": true, - }, - "selectedLinearElementId": null, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id49" => Delta { - "deleted": { - "angle": 0, - "backgroundColor": "transparent", - "boundElements": null, - "customData": undefined, - "elbowed": false, - "endArrowhead": "arrow", - "endBinding": { - "elementId": "id46", - "fixedPoint": null, - "focus": 0, - "gap": 1, - }, - "fillStyle": "solid", - "frameId": null, - "groupIds": [], - "height": 0, - "index": "a3", - "isDeleted": false, - "lastCommittedPoint": null, - "link": null, - "locked": false, - "opacity": 100, - "points": [ - [ - 0, - 0, - ], - [ - 100, - 0, - ], - ], - "roughness": 1, - "roundness": { - "type": 2, - }, - "startArrowhead": null, - "startBinding": { - "elementId": "id44", - "fixedPoint": null, - "focus": 0, - "gap": 1, - }, - "strokeColor": "#1e1e1e", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "arrow", - "width": 100, - "x": 0, - "y": 0, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map { - "id44" => Delta { - "deleted": { - "boundElements": [ - { - "id": "id49", - "type": "arrow", - }, - ], - }, - "inserted": { - "boundElements": [], - }, - }, - "id46" => Delta { - "deleted": { - "boundElements": [ - { - "id": "id49", - "type": "arrow", - }, - ], - }, - "inserted": { - "boundElements": [], - }, - }, - }, - }, - }, - ], -} -`; - -exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind arrow from non deleted bindable elements on undo and rebind on redo > [end of test] number of elements 1`] = `4`; - -exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind arrow from non deleted bindable elements on undo and rebind on redo > [end of test] number of renders 1`] = `12`; - -exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind everything from non deleted elements when iterating through the whole undo stack and vice versa rebind everything on redo > [end of test] appState 1`] = ` { "activeEmbeddable": null, "activeTool": { @@ -16315,7 +14993,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding } `; -exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind everything from non deleted elements when iterating through the whole undo stack and vice versa rebind everything on redo > [end of test] element 0 1`] = ` +exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind arrow from non deleted bindable elements on deletion and rebind on undo > [end of test] element 0 1`] = ` { "angle": 0, "backgroundColor": "transparent", @@ -16349,14 +15027,14 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "strokeWidth": 2, "type": "rectangle", "updated": 1, - "version": 12, + "version": 8, "width": 100, "x": -100, "y": -50, } `; -exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind everything from non deleted elements when iterating through the whole undo stack and vice versa rebind everything on redo > [end of test] element 1 1`] = ` +exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind arrow from non deleted bindable elements on deletion and rebind on undo > [end of test] element 1 1`] = ` { "angle": 0, "autoResize": true, @@ -16389,7 +15067,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "textAlign": "center", "type": "text", "updated": 1, - "version": 12, + "version": 6, "verticalAlign": "middle", "width": 30, "x": -65, @@ -16397,7 +15075,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding } `; -exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind everything from non deleted elements when iterating through the whole undo stack and vice versa rebind everything on redo > [end of test] element 2 1`] = ` +exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind arrow from non deleted bindable elements on deletion and rebind on undo > [end of test] element 2 1`] = ` { "angle": 0, "backgroundColor": "transparent", @@ -16427,14 +15105,14 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "strokeWidth": 2, "type": "rectangle", "updated": 1, - "version": 9, + "version": 7, "width": 100, "x": 100, "y": -50, } `; -exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind everything from non deleted elements when iterating through the whole undo stack and vice versa rebind everything on redo > [end of test] element 3 1`] = ` +exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind arrow from non deleted bindable elements on deletion and rebind on undo > [end of test] element 3 1`] = ` { "angle": 0, "backgroundColor": "transparent", @@ -16492,7 +15170,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding } `; -exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind everything from non deleted elements when iterating through the whole undo stack and vice versa rebind everything on redo > [end of test] history 1`] = ` +exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind arrow from non deleted bindable elements on deletion and rebind on undo > [end of test] history 1`] = ` History { "onHistoryChangedEmitter": Emitter { "subscribers": [ @@ -16500,7 +15178,83 @@ History { [Function], ], }, - "redoStack": [], + "redoStack": [ + HistoryEntry { + "appStateChange": AppStateChange { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id61": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elementsChange": ElementsChange { + "added": Map {}, + "removed": Map { + "id61" => Delta { + "deleted": { + "isDeleted": false, + "points": [ + [ + 0, + 0, + ], + [ + 100, + 0, + ], + ], + }, + "inserted": { + "isDeleted": true, + "points": [ + [ + 0, + 0, + ], + [ + 100, + 0, + ], + ], + }, + }, + }, + "updated": Map { + "id56" => Delta { + "deleted": { + "boundElements": [ + { + "id": "id61", + "type": "arrow", + }, + ], + }, + "inserted": { + "boundElements": [], + }, + }, + "id58" => Delta { + "deleted": { + "boundElements": [ + { + "id": "id61", + "type": "arrow", + }, + ], + }, + "inserted": { + "boundElements": [], + }, + }, + }, + }, + }, + ], "undoStack": [ HistoryEntry { "appStateChange": AppStateChange { @@ -16821,11 +15575,11 @@ History { } `; -exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind everything from non deleted elements when iterating through the whole undo stack and vice versa rebind everything on redo > [end of test] number of elements 1`] = `4`; +exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind arrow from non deleted bindable elements on deletion and rebind on undo > [end of test] number of elements 1`] = `4`; -exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind everything from non deleted elements when iterating through the whole undo stack and vice versa rebind everything on redo > [end of test] number of renders 1`] = `20`; +exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind arrow from non deleted bindable elements on deletion and rebind on undo > [end of test] number of renders 1`] = `12`; -exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind rectangle from arrow on deletion and rebind on undo > [end of test] appState 1`] = ` +exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind arrow from non deleted bindable elements on undo and rebind on redo > [end of test] appState 1`] = ` { "activeEmbeddable": null, "activeTool": { @@ -16902,13 +15656,15 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "penDetected": false, "penMode": false, "pendingImageElementId": null, - "previousSelectedElementIds": {}, + "previousSelectedElementIds": { + "id50": true, + }, "resizingElement": null, "scrollX": 0, "scrollY": 0, "searchMatches": [], "selectedElementIds": { - "id62": true, + "id55": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": {}, @@ -16936,19 +15692,642 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding } `; -exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind rectangle from arrow on deletion and rebind on undo > [end of test] element 0 1`] = ` +exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind arrow from non deleted bindable elements on undo and rebind on redo > [end of test] element 0 1`] = ` { "angle": 0, "backgroundColor": "transparent", "boundElements": [ { - "id": "id67", + "id": "id51", + "type": "text", + }, + { + "id": "id55", "type": "arrow", }, + ], + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 100, + "id": "id50", + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "updated": 1, + "version": 8, + "width": 100, + "x": -100, + "y": -50, +} +`; + +exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind arrow from non deleted bindable elements on undo and rebind on redo > [end of test] element 1 1`] = ` +{ + "angle": 0, + "autoResize": true, + "backgroundColor": "transparent", + "boundElements": null, + "containerId": "id50", + "customData": undefined, + "fillStyle": "solid", + "fontFamily": 5, + "fontSize": 20, + "frameId": null, + "groupIds": [], + "height": 25, + "id": "id51", + "index": "a1", + "isDeleted": false, + "lineHeight": "1.25000", + "link": null, + "locked": false, + "opacity": 100, + "originalText": "ola", + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "text": "ola", + "textAlign": "center", + "type": "text", + "updated": 1, + "version": 8, + "verticalAlign": "middle", + "width": 30, + "x": -65, + "y": "-12.50000", +} +`; + +exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind arrow from non deleted bindable elements on undo and rebind on redo > [end of test] element 2 1`] = ` +{ + "angle": 0, + "backgroundColor": "transparent", + "boundElements": [ + { + "id": "id55", + "type": "arrow", + }, + ], + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 100, + "id": "id52", + "index": "a2", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "updated": 1, + "version": 7, + "width": 100, + "x": 100, + "y": -50, +} +`; + +exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind arrow from non deleted bindable elements on undo and rebind on redo > [end of test] element 3 1`] = ` +{ + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "elbowed": false, + "endArrowhead": "arrow", + "endBinding": { + "elementId": "id52", + "fixedPoint": null, + "focus": 0, + "gap": 1, + }, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 0, + "id": "id55", + "index": "a3", + "isDeleted": false, + "lastCommittedPoint": null, + "link": null, + "locked": false, + "opacity": 100, + "points": [ + [ + 0, + 0, + ], + [ + "98.00000", + 0, + ], + ], + "roughness": 1, + "roundness": { + "type": 2, + }, + "startArrowhead": null, + "startBinding": { + "elementId": "id50", + "fixedPoint": null, + "focus": 0, + "gap": 1, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "arrow", + "updated": 1, + "version": 10, + "width": "98.00000", + "x": 1, + "y": 0, +} +`; + +exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind arrow from non deleted bindable elements on undo and rebind on redo > [end of test] history 1`] = ` +History { + "onHistoryChangedEmitter": Emitter { + "subscribers": [ + [Function], + [Function], + ], + }, + "redoStack": [], + "undoStack": [ + HistoryEntry { + "appStateChange": AppStateChange { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elementsChange": ElementsChange { + "added": Map {}, + "removed": Map { + "id50" => Delta { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 100, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": -100, + "y": -50, + }, + "inserted": { + "isDeleted": true, + }, + }, + "id51" => Delta { + "deleted": { + "angle": 0, + "autoResize": true, + "backgroundColor": "transparent", + "boundElements": null, + "containerId": null, + "customData": undefined, + "fillStyle": "solid", + "fontFamily": 5, + "fontSize": 20, + "frameId": null, + "groupIds": [], + "height": 100, + "index": "a1", + "isDeleted": false, + "lineHeight": "1.25000", + "link": null, + "locked": false, + "opacity": 100, + "originalText": "ola", + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "text": "ola", + "textAlign": "left", + "type": "text", + "verticalAlign": "top", + "width": 100, + "x": -200, + "y": -200, + }, + "inserted": { + "isDeleted": true, + }, + }, + "id52" => Delta { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 100, + "index": "a2", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": 100, + "y": -50, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": Map {}, + }, + }, + HistoryEntry { + "appStateChange": AppStateChange { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id50": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elementsChange": ElementsChange { + "added": Map {}, + "removed": Map {}, + "updated": Map {}, + }, + }, + HistoryEntry { + "appStateChange": AppStateChange { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id51": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elementsChange": ElementsChange { + "added": Map {}, + "removed": Map {}, + "updated": Map {}, + }, + }, + HistoryEntry { + "appStateChange": AppStateChange { + "delta": Delta { + "deleted": { + "selectedElementIds": {}, + }, + "inserted": { + "selectedElementIds": { + "id51": true, + }, + }, + }, + }, + "elementsChange": ElementsChange { + "added": Map {}, + "removed": Map {}, + "updated": Map { + "id50" => Delta { + "deleted": { + "boundElements": [ + { + "id": "id51", + "type": "text", + }, + ], + }, + "inserted": { + "boundElements": [], + }, + }, + "id51" => Delta { + "deleted": { + "containerId": "id50", + "height": 25, + "textAlign": "center", + "verticalAlign": "middle", + "width": 30, + "x": -65, + "y": "-12.50000", + }, + "inserted": { + "containerId": null, + "height": 100, + "textAlign": "left", + "verticalAlign": "top", + "width": 100, + "x": -200, + "y": -200, + }, + }, + }, + }, + }, + HistoryEntry { + "appStateChange": AppStateChange { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id55": true, + }, + "selectedLinearElementId": "id55", + }, + "inserted": { + "selectedElementIds": { + "id50": true, + }, + "selectedLinearElementId": null, + }, + }, + }, + "elementsChange": ElementsChange { + "added": Map {}, + "removed": Map { + "id55" => Delta { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "elbowed": false, + "endArrowhead": "arrow", + "endBinding": { + "elementId": "id52", + "fixedPoint": null, + "focus": 0, + "gap": 1, + }, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 0, + "index": "a3", + "isDeleted": false, + "lastCommittedPoint": null, + "link": null, + "locked": false, + "opacity": 100, + "points": [ + [ + 0, + 0, + ], + [ + 100, + 0, + ], + ], + "roughness": 1, + "roundness": { + "type": 2, + }, + "startArrowhead": null, + "startBinding": { + "elementId": "id50", + "fixedPoint": null, + "focus": 0, + "gap": 1, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "arrow", + "width": 100, + "x": 0, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": Map { + "id50" => Delta { + "deleted": { + "boundElements": [ + { + "id": "id55", + "type": "arrow", + }, + ], + }, + "inserted": { + "boundElements": [], + }, + }, + "id52" => Delta { + "deleted": { + "boundElements": [ + { + "id": "id55", + "type": "arrow", + }, + ], + }, + "inserted": { + "boundElements": [], + }, + }, + }, + }, + }, + ], +} +`; + +exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind arrow from non deleted bindable elements on undo and rebind on redo > [end of test] number of elements 1`] = `4`; + +exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind arrow from non deleted bindable elements on undo and rebind on redo > [end of test] number of renders 1`] = `12`; + +exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind everything from non deleted elements when iterating through the whole undo stack and vice versa rebind everything on redo > [end of test] appState 1`] = ` +{ + "activeEmbeddable": null, + "activeTool": { + "customType": null, + "lastActiveTool": null, + "locked": false, + "type": "selection", + }, + "collaborators": Map {}, + "contextMenu": null, + "croppingElementId": null, + "currentChartType": "bar", + "currentHoveredFontFamily": null, + "currentItemArrowType": "round", + "currentItemBackgroundColor": "transparent", + "currentItemEndArrowhead": "arrow", + "currentItemFillStyle": "solid", + "currentItemFontFamily": 5, + "currentItemFontSize": 20, + "currentItemOpacity": 100, + "currentItemRoughness": 1, + "currentItemRoundness": "round", + "currentItemStartArrowhead": null, + "currentItemStrokeColor": "#1e1e1e", + "currentItemStrokeStyle": "solid", + "currentItemStrokeWidth": 2, + "currentItemTextAlign": "left", + "cursorButton": "up", + "defaultSidebarDockedPreference": false, + "editingFrame": null, + "editingGroupId": null, + "editingLinearElement": null, + "editingTextElement": null, + "elementsToHighlight": null, + "errorMessage": null, + "exportBackground": true, + "exportEmbedScene": false, + "exportScale": 1, + "exportWithDarkMode": false, + "fileHandle": null, + "followedBy": Set {}, + "frameRendering": { + "clip": true, + "enabled": true, + "name": true, + "outline": true, + }, + "frameToHighlight": null, + "gridModeEnabled": false, + "gridSize": 20, + "gridStep": 5, + "height": 0, + "hoveredElementIds": {}, + "isBindingEnabled": true, + "isCropping": false, + "isLoading": false, + "isResizing": false, + "isRotating": false, + "lastPointerDownWith": "mouse", + "multiElement": null, + "newElement": null, + "objectsSnapModeEnabled": false, + "offsetLeft": 0, + "offsetTop": 0, + "openDialog": null, + "openMenu": null, + "openPopup": null, + "openSidebar": null, + "originSnapOffset": null, + "pasteDialog": { + "data": null, + "shown": false, + }, + "penDetected": false, + "penMode": false, + "pendingImageElementId": null, + "previousSelectedElementIds": { + "id62": true, + }, + "resizingElement": null, + "scrollX": 0, + "scrollY": 0, + "searchMatches": [], + "selectedElementIds": { + "id67": true, + }, + "selectedElementsAreBeingDragged": false, + "selectedGroupIds": {}, + "selectionElement": null, + "shouldCacheIgnoreZoom": false, + "showHyperlinkPopup": false, + "showWelcomeScreen": true, + "snapLines": [], + "startBoundElement": null, + "stats": { + "open": false, + "panels": 3, + }, + "suggestedBindings": [], + "theme": "light", + "toast": null, + "userToFollow": null, + "viewBackgroundColor": "#ffffff", + "viewModeEnabled": false, + "width": 0, + "zenModeEnabled": false, + "zoom": { + "value": 1, + }, +} +`; + +exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind everything from non deleted elements when iterating through the whole undo stack and vice versa rebind everything on redo > [end of test] element 0 1`] = ` +{ + "angle": 0, + "backgroundColor": "transparent", + "boundElements": [ { "id": "id63", "type": "text", }, + { + "id": "id67", + "type": "arrow", + }, ], "customData": undefined, "fillStyle": "solid", @@ -16970,14 +16349,14 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "strokeWidth": 2, "type": "rectangle", "updated": 1, - "version": 8, + "version": 12, "width": 100, "x": -100, "y": -50, } `; -exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind rectangle from arrow on deletion and rebind on undo > [end of test] element 1 1`] = ` +exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind everything from non deleted elements when iterating through the whole undo stack and vice versa rebind everything on redo > [end of test] element 1 1`] = ` { "angle": 0, "autoResize": true, @@ -17010,7 +16389,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "textAlign": "center", "type": "text", "updated": 1, - "version": 8, + "version": 12, "verticalAlign": "middle", "width": 30, "x": -65, @@ -17018,7 +16397,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding } `; -exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind rectangle from arrow on deletion and rebind on undo > [end of test] element 2 1`] = ` +exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind everything from non deleted elements when iterating through the whole undo stack and vice versa rebind everything on redo > [end of test] element 2 1`] = ` { "angle": 0, "backgroundColor": "transparent", @@ -17048,14 +16427,14 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "strokeWidth": 2, "type": "rectangle", "updated": 1, - "version": 5, + "version": 9, "width": 100, "x": 100, "y": -50, } `; -exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind rectangle from arrow on deletion and rebind on undo > [end of test] element 3 1`] = ` +exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind everything from non deleted elements when iterating through the whole undo stack and vice versa rebind everything on redo > [end of test] element 3 1`] = ` { "angle": 0, "backgroundColor": "transparent", @@ -17113,7 +16492,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding } `; -exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind rectangle from arrow on deletion and rebind on undo > [end of test] history 1`] = ` +exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind everything from non deleted elements when iterating through the whole undo stack and vice versa rebind everything on redo > [end of test] history 1`] = ` History { "onHistoryChangedEmitter": Emitter { "subscribers": [ @@ -17121,78 +16500,7 @@ History { [Function], ], }, - "redoStack": [ - HistoryEntry { - "appStateChange": AppStateChange { - "delta": Delta { - "deleted": { - "selectedElementIds": { - "id62": true, - }, - }, - "inserted": { - "selectedElementIds": {}, - }, - }, - }, - "elementsChange": ElementsChange { - "added": Map {}, - "removed": Map { - "id62" => Delta { - "deleted": { - "isDeleted": false, - }, - "inserted": { - "isDeleted": true, - }, - }, - "id63" => Delta { - "deleted": { - "isDeleted": false, - }, - "inserted": { - "isDeleted": true, - }, - }, - }, - "updated": Map { - "id67" => Delta { - "deleted": { - "points": [ - [ - 0, - 0, - ], - [ - 100, - 0, - ], - ], - "startBinding": { - "elementId": "id62", - "fixedPoint": null, - "focus": 0, - "gap": 1, - }, - }, - "inserted": { - "points": [ - [ - 0, - 0, - ], - [ - 100, - 0, - ], - ], - "startBinding": null, - }, - }, - }, - }, - }, - ], + "redoStack": [], "undoStack": [ HistoryEntry { "appStateChange": AppStateChange { @@ -17509,20 +16817,712 @@ History { }, }, }, + ], +} +`; + +exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind everything from non deleted elements when iterating through the whole undo stack and vice versa rebind everything on redo > [end of test] number of elements 1`] = `4`; + +exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind everything from non deleted elements when iterating through the whole undo stack and vice versa rebind everything on redo > [end of test] number of renders 1`] = `20`; + +exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind rectangle from arrow on deletion and rebind on undo > [end of test] appState 1`] = ` +{ + "activeEmbeddable": null, + "activeTool": { + "customType": null, + "lastActiveTool": null, + "locked": false, + "type": "selection", + }, + "collaborators": Map {}, + "contextMenu": null, + "croppingElementId": null, + "currentChartType": "bar", + "currentHoveredFontFamily": null, + "currentItemArrowType": "round", + "currentItemBackgroundColor": "transparent", + "currentItemEndArrowhead": "arrow", + "currentItemFillStyle": "solid", + "currentItemFontFamily": 5, + "currentItemFontSize": 20, + "currentItemOpacity": 100, + "currentItemRoughness": 1, + "currentItemRoundness": "round", + "currentItemStartArrowhead": null, + "currentItemStrokeColor": "#1e1e1e", + "currentItemStrokeStyle": "solid", + "currentItemStrokeWidth": 2, + "currentItemTextAlign": "left", + "cursorButton": "up", + "defaultSidebarDockedPreference": false, + "editingFrame": null, + "editingGroupId": null, + "editingLinearElement": null, + "editingTextElement": null, + "elementsToHighlight": null, + "errorMessage": null, + "exportBackground": true, + "exportEmbedScene": false, + "exportScale": 1, + "exportWithDarkMode": false, + "fileHandle": null, + "followedBy": Set {}, + "frameRendering": { + "clip": true, + "enabled": true, + "name": true, + "outline": true, + }, + "frameToHighlight": null, + "gridModeEnabled": false, + "gridSize": 20, + "gridStep": 5, + "height": 0, + "hoveredElementIds": {}, + "isBindingEnabled": true, + "isCropping": false, + "isLoading": false, + "isResizing": false, + "isRotating": false, + "lastPointerDownWith": "mouse", + "multiElement": null, + "newElement": null, + "objectsSnapModeEnabled": false, + "offsetLeft": 0, + "offsetTop": 0, + "openDialog": null, + "openMenu": null, + "openPopup": null, + "openSidebar": null, + "originSnapOffset": null, + "pasteDialog": { + "data": null, + "shown": false, + }, + "penDetected": false, + "penMode": false, + "pendingImageElementId": null, + "previousSelectedElementIds": {}, + "resizingElement": null, + "scrollX": 0, + "scrollY": 0, + "searchMatches": [], + "selectedElementIds": { + "id68": true, + }, + "selectedElementsAreBeingDragged": false, + "selectedGroupIds": {}, + "selectionElement": null, + "shouldCacheIgnoreZoom": false, + "showHyperlinkPopup": false, + "showWelcomeScreen": true, + "snapLines": [], + "startBoundElement": null, + "stats": { + "open": false, + "panels": 3, + }, + "suggestedBindings": [], + "theme": "light", + "toast": null, + "userToFollow": null, + "viewBackgroundColor": "#ffffff", + "viewModeEnabled": false, + "width": 0, + "zenModeEnabled": false, + "zoom": { + "value": 1, + }, +} +`; + +exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind rectangle from arrow on deletion and rebind on undo > [end of test] element 0 1`] = ` +{ + "angle": 0, + "backgroundColor": "transparent", + "boundElements": [ + { + "id": "id73", + "type": "arrow", + }, + { + "id": "id69", + "type": "text", + }, + ], + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 100, + "id": "id68", + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "updated": 1, + "version": 8, + "width": 100, + "x": -100, + "y": -50, +} +`; + +exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind rectangle from arrow on deletion and rebind on undo > [end of test] element 1 1`] = ` +{ + "angle": 0, + "autoResize": true, + "backgroundColor": "transparent", + "boundElements": null, + "containerId": "id68", + "customData": undefined, + "fillStyle": "solid", + "fontFamily": 5, + "fontSize": 20, + "frameId": null, + "groupIds": [], + "height": 25, + "id": "id69", + "index": "a1", + "isDeleted": false, + "lineHeight": "1.25000", + "link": null, + "locked": false, + "opacity": 100, + "originalText": "ola", + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "text": "ola", + "textAlign": "center", + "type": "text", + "updated": 1, + "version": 8, + "verticalAlign": "middle", + "width": 30, + "x": -65, + "y": "-12.50000", +} +`; + +exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind rectangle from arrow on deletion and rebind on undo > [end of test] element 2 1`] = ` +{ + "angle": 0, + "backgroundColor": "transparent", + "boundElements": [ + { + "id": "id73", + "type": "arrow", + }, + ], + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 100, + "id": "id70", + "index": "a2", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "updated": 1, + "version": 5, + "width": 100, + "x": 100, + "y": -50, +} +`; + +exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind rectangle from arrow on deletion and rebind on undo > [end of test] element 3 1`] = ` +{ + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "elbowed": false, + "endArrowhead": "arrow", + "endBinding": { + "elementId": "id70", + "fixedPoint": null, + "focus": 0, + "gap": 1, + }, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 0, + "id": "id73", + "index": "a3", + "isDeleted": false, + "lastCommittedPoint": null, + "link": null, + "locked": false, + "opacity": 100, + "points": [ + [ + 0, + 0, + ], + [ + "98.00000", + 0, + ], + ], + "roughness": 1, + "roundness": { + "type": 2, + }, + "startArrowhead": null, + "startBinding": { + "elementId": "id68", + "fixedPoint": null, + "focus": 0, + "gap": 1, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "arrow", + "updated": 1, + "version": 10, + "width": "98.00000", + "x": 1, + "y": 0, +} +`; + +exports[`history > singleplayer undo/redo > should support bidirectional bindings > should unbind rectangle from arrow on deletion and rebind on undo > [end of test] history 1`] = ` +History { + "onHistoryChangedEmitter": Emitter { + "subscribers": [ + [Function], + [Function], + ], + }, + "redoStack": [ HistoryEntry { "appStateChange": AppStateChange { "delta": Delta { "deleted": { "selectedElementIds": { - "id62": true, + "id68": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elementsChange": ElementsChange { + "added": Map {}, + "removed": Map { + "id68" => Delta { + "deleted": { + "isDeleted": false, + }, + "inserted": { + "isDeleted": true, + }, + }, + "id69" => Delta { + "deleted": { + "isDeleted": false, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": Map { + "id73" => Delta { + "deleted": { + "points": [ + [ + 0, + 0, + ], + [ + 100, + 0, + ], + ], + "startBinding": { + "elementId": "id68", + "fixedPoint": null, + "focus": 0, + "gap": 1, + }, + }, + "inserted": { + "points": [ + [ + 0, + 0, + ], + [ + 100, + 0, + ], + ], + "startBinding": null, + }, + }, + }, + }, + }, + ], + "undoStack": [ + HistoryEntry { + "appStateChange": AppStateChange { + "delta": Delta { + "deleted": {}, + "inserted": {}, + }, + }, + "elementsChange": ElementsChange { + "added": Map {}, + "removed": Map { + "id68" => Delta { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 100, + "index": "a0", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": -100, + "y": -50, + }, + "inserted": { + "isDeleted": true, + }, + }, + "id69" => Delta { + "deleted": { + "angle": 0, + "autoResize": true, + "backgroundColor": "transparent", + "boundElements": null, + "containerId": null, + "customData": undefined, + "fillStyle": "solid", + "fontFamily": 5, + "fontSize": 20, + "frameId": null, + "groupIds": [], + "height": 100, + "index": "a1", + "isDeleted": false, + "lineHeight": "1.25000", + "link": null, + "locked": false, + "opacity": 100, + "originalText": "ola", + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "text": "ola", + "textAlign": "left", + "type": "text", + "verticalAlign": "top", + "width": 100, + "x": -200, + "y": -200, + }, + "inserted": { + "isDeleted": true, + }, + }, + "id70" => Delta { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 100, + "index": "a2", + "isDeleted": false, + "link": null, + "locked": false, + "opacity": 100, + "roughness": 1, + "roundness": { + "type": 3, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "rectangle", + "width": 100, + "x": 100, + "y": -50, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": Map {}, + }, + }, + HistoryEntry { + "appStateChange": AppStateChange { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id68": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elementsChange": ElementsChange { + "added": Map {}, + "removed": Map {}, + "updated": Map {}, + }, + }, + HistoryEntry { + "appStateChange": AppStateChange { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id69": true, + }, + }, + "inserted": { + "selectedElementIds": {}, + }, + }, + }, + "elementsChange": ElementsChange { + "added": Map {}, + "removed": Map {}, + "updated": Map {}, + }, + }, + HistoryEntry { + "appStateChange": AppStateChange { + "delta": Delta { + "deleted": { + "selectedElementIds": {}, + }, + "inserted": { + "selectedElementIds": { + "id69": true, + }, + }, + }, + }, + "elementsChange": ElementsChange { + "added": Map {}, + "removed": Map {}, + "updated": Map { + "id68" => Delta { + "deleted": { + "boundElements": [ + { + "id": "id69", + "type": "text", + }, + ], + }, + "inserted": { + "boundElements": [], + }, + }, + "id69" => Delta { + "deleted": { + "containerId": "id68", + "height": 25, + "textAlign": "center", + "verticalAlign": "middle", + "width": 30, + "x": -65, + "y": "-12.50000", + }, + "inserted": { + "containerId": null, + "height": 100, + "textAlign": "left", + "verticalAlign": "top", + "width": 100, + "x": -200, + "y": -200, + }, + }, + }, + }, + }, + HistoryEntry { + "appStateChange": AppStateChange { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id73": true, + }, + "selectedLinearElementId": "id73", + }, + "inserted": { + "selectedElementIds": { + "id68": true, + }, + "selectedLinearElementId": null, + }, + }, + }, + "elementsChange": ElementsChange { + "added": Map {}, + "removed": Map { + "id73" => Delta { + "deleted": { + "angle": 0, + "backgroundColor": "transparent", + "boundElements": null, + "customData": undefined, + "elbowed": false, + "endArrowhead": "arrow", + "endBinding": { + "elementId": "id70", + "fixedPoint": null, + "focus": 0, + "gap": 1, + }, + "fillStyle": "solid", + "frameId": null, + "groupIds": [], + "height": 0, + "index": "a3", + "isDeleted": false, + "lastCommittedPoint": null, + "link": null, + "locked": false, + "opacity": 100, + "points": [ + [ + 0, + 0, + ], + [ + 100, + 0, + ], + ], + "roughness": 1, + "roundness": { + "type": 2, + }, + "startArrowhead": null, + "startBinding": { + "elementId": "id68", + "fixedPoint": null, + "focus": 0, + "gap": 1, + }, + "strokeColor": "#1e1e1e", + "strokeStyle": "solid", + "strokeWidth": 2, + "type": "arrow", + "width": 100, + "x": 0, + "y": 0, + }, + "inserted": { + "isDeleted": true, + }, + }, + }, + "updated": Map { + "id68" => Delta { + "deleted": { + "boundElements": [ + { + "id": "id73", + "type": "arrow", + }, + ], + }, + "inserted": { + "boundElements": [], + }, + }, + "id70" => Delta { + "deleted": { + "boundElements": [ + { + "id": "id73", + "type": "arrow", + }, + ], + }, + "inserted": { + "boundElements": [], + }, + }, + }, + }, + }, + HistoryEntry { + "appStateChange": AppStateChange { + "delta": Delta { + "deleted": { + "selectedElementIds": { + "id68": true, }, "selectedLinearElementId": null, }, "inserted": { "selectedElementIds": { - "id67": true, + "id73": true, }, - "selectedLinearElementId": "id67", + "selectedLinearElementId": "id73", }, }, }, @@ -17618,15 +17618,15 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "penMode": false, "pendingImageElementId": null, "previousSelectedElementIds": { - "id69": true, + "id75": true, }, "resizingElement": null, "scrollX": 0, "scrollY": 0, "searchMatches": [], "selectedElementIds": { - "id69": true, - "id71": true, + "id75": true, + "id77": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": {}, @@ -17660,11 +17660,11 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "backgroundColor": "transparent", "boundElements": [ { - "id": "id74", + "id": "id80", "type": "arrow", }, { - "id": "id70", + "id": "id76", "type": "text", }, ], @@ -17673,7 +17673,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "frameId": null, "groupIds": [], "height": 100, - "id": "id69", + "id": "id75", "index": "a0", "isDeleted": false, "link": null, @@ -17701,7 +17701,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "autoResize": true, "backgroundColor": "transparent", "boundElements": null, - "containerId": "id69", + "containerId": "id75", "customData": undefined, "fillStyle": "solid", "fontFamily": 5, @@ -17709,7 +17709,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "frameId": null, "groupIds": [], "height": 25, - "id": "id70", + "id": "id76", "index": "a1", "isDeleted": false, "lineHeight": "1.25000", @@ -17742,7 +17742,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "backgroundColor": "transparent", "boundElements": [ { - "id": "id74", + "id": "id80", "type": "arrow", }, ], @@ -17751,7 +17751,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "frameId": null, "groupIds": [], "height": 100, - "id": "id71", + "id": "id77", "index": "a2", "isDeleted": false, "link": null, @@ -17782,7 +17782,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "elbowed": false, "endArrowhead": "arrow", "endBinding": { - "elementId": "id71", + "elementId": "id77", "fixedPoint": null, "focus": 0, "gap": 1, @@ -17791,7 +17791,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding "frameId": null, "groupIds": [], "height": 0, - "id": "id74", + "id": "id80", "index": "a3", "isDeleted": false, "lastCommittedPoint": null, @@ -17814,7 +17814,7 @@ exports[`history > singleplayer undo/redo > should support bidirectional binding }, "startArrowhead": null, "startBinding": { - "elementId": "id69", + "elementId": "id75", "fixedPoint": null, "focus": 0, "gap": 1, @@ -17845,8 +17845,8 @@ History { "delta": Delta { "deleted": { "selectedElementIds": { - "id69": true, - "id71": true, + "id75": true, + "id77": true, }, }, "inserted": { @@ -17857,7 +17857,7 @@ History { "elementsChange": ElementsChange { "added": Map {}, "removed": Map { - "id69" => Delta { + "id75" => Delta { "deleted": { "isDeleted": false, }, @@ -17865,7 +17865,7 @@ History { "isDeleted": true, }, }, - "id70" => Delta { + "id76" => Delta { "deleted": { "isDeleted": false, }, @@ -17873,7 +17873,7 @@ History { "isDeleted": true, }, }, - "id71" => Delta { + "id77" => Delta { "deleted": { "isDeleted": false, }, @@ -17883,10 +17883,10 @@ History { }, }, "updated": Map { - "id74" => Delta { + "id80" => Delta { "deleted": { "endBinding": { - "elementId": "id71", + "elementId": "id77", "fixedPoint": null, "focus": 0, "gap": 1, @@ -17902,7 +17902,7 @@ History { ], ], "startBinding": { - "elementId": "id69", + "elementId": "id75", "fixedPoint": null, "focus": 0, "gap": 1, @@ -17938,7 +17938,7 @@ History { "elementsChange": ElementsChange { "added": Map {}, "removed": Map { - "id69" => Delta { + "id75" => Delta { "deleted": { "angle": 0, "backgroundColor": "transparent", @@ -17969,7 +17969,7 @@ History { "isDeleted": true, }, }, - "id70" => Delta { + "id76" => Delta { "deleted": { "angle": 0, "autoResize": true, @@ -18009,7 +18009,7 @@ History { "isDeleted": true, }, }, - "id71" => Delta { + "id77" => Delta { "deleted": { "angle": 0, "backgroundColor": "transparent", @@ -18049,7 +18049,7 @@ History { "delta": Delta { "deleted": { "selectedElementIds": { - "id69": true, + "id75": true, }, }, "inserted": { @@ -18068,7 +18068,7 @@ History { "delta": Delta { "deleted": { "selectedElementIds": { - "id70": true, + "id76": true, }, }, "inserted": { @@ -18090,7 +18090,7 @@ History { }, "inserted": { "selectedElementIds": { - "id70": true, + "id76": true, }, }, }, @@ -18099,11 +18099,11 @@ History { "added": Map {}, "removed": Map {}, "updated": Map { - "id69" => Delta { + "id75" => Delta { "deleted": { "boundElements": [ { - "id": "id70", + "id": "id76", "type": "text", }, ], @@ -18112,9 +18112,9 @@ History { "boundElements": [], }, }, - "id70" => Delta { + "id76" => Delta { "deleted": { - "containerId": "id69", + "containerId": "id75", "height": 25, "textAlign": "center", "verticalAlign": "middle", @@ -18140,13 +18140,13 @@ History { "delta": Delta { "deleted": { "selectedElementIds": { - "id74": true, + "id80": true, }, - "selectedLinearElementId": "id74", + "selectedLinearElementId": "id80", }, "inserted": { "selectedElementIds": { - "id69": true, + "id75": true, }, "selectedLinearElementId": null, }, @@ -18155,7 +18155,7 @@ History { "elementsChange": ElementsChange { "added": Map {}, "removed": Map { - "id74" => Delta { + "id80" => Delta { "deleted": { "angle": 0, "backgroundColor": "transparent", @@ -18164,7 +18164,7 @@ History { "elbowed": false, "endArrowhead": "arrow", "endBinding": { - "elementId": "id71", + "elementId": "id77", "fixedPoint": null, "focus": 0, "gap": 1, @@ -18195,7 +18195,7 @@ History { }, "startArrowhead": null, "startBinding": { - "elementId": "id69", + "elementId": "id75", "fixedPoint": null, "focus": 0, "gap": 1, @@ -18214,11 +18214,11 @@ History { }, }, "updated": Map { - "id69" => Delta { + "id75" => Delta { "deleted": { "boundElements": [ { - "id": "id74", + "id": "id80", "type": "arrow", }, ], @@ -18227,11 +18227,11 @@ History { "boundElements": [], }, }, - "id71" => Delta { + "id77" => Delta { "deleted": { "boundElements": [ { - "id": "id74", + "id": "id80", "type": "arrow", }, ], @@ -18248,15 +18248,15 @@ History { "delta": Delta { "deleted": { "selectedElementIds": { - "id69": true, + "id75": true, }, "selectedLinearElementId": null, }, "inserted": { "selectedElementIds": { - "id74": true, + "id80": true, }, - "selectedLinearElementId": "id74", + "selectedLinearElementId": "id80", }, }, }, @@ -18271,7 +18271,7 @@ History { "delta": Delta { "deleted": { "selectedElementIds": { - "id71": true, + "id77": true, }, }, "inserted": { @@ -18371,15 +18371,15 @@ exports[`history > singleplayer undo/redo > should support changes in elements' "penMode": false, "pendingImageElementId": null, "previousSelectedElementIds": { - "id39": true, + "id45": true, }, "resizingElement": null, "scrollX": 0, "scrollY": 0, "searchMatches": [], "selectedElementIds": { - "id39": true, - "id41": true, + "id45": true, + "id47": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": {}, @@ -18417,7 +18417,7 @@ exports[`history > singleplayer undo/redo > should support changes in elements' "frameId": null, "groupIds": [], "height": 10, - "id": "id40", + "id": "id46", "index": "a1", "isDeleted": false, "link": null, @@ -18449,7 +18449,7 @@ exports[`history > singleplayer undo/redo > should support changes in elements' "frameId": null, "groupIds": [], "height": 10, - "id": "id39", + "id": "id45", "index": "a2", "isDeleted": false, "link": null, @@ -18481,7 +18481,7 @@ exports[`history > singleplayer undo/redo > should support changes in elements' "frameId": null, "groupIds": [], "height": 10, - "id": "id41", + "id": "id47", "index": "a3", "isDeleted": false, "link": null, @@ -18518,7 +18518,7 @@ History { "delta": Delta { "deleted": { "selectedElementIds": { - "id39": true, + "id45": true, }, }, "inserted": { @@ -18529,7 +18529,7 @@ History { "elementsChange": ElementsChange { "added": Map {}, "removed": Map { - "id39" => Delta { + "id45" => Delta { "deleted": { "angle": 0, "backgroundColor": "transparent", @@ -18569,12 +18569,12 @@ History { "delta": Delta { "deleted": { "selectedElementIds": { - "id40": true, + "id46": true, }, }, "inserted": { "selectedElementIds": { - "id39": true, + "id45": true, }, }, }, @@ -18582,7 +18582,7 @@ History { "elementsChange": ElementsChange { "added": Map {}, "removed": Map { - "id40" => Delta { + "id46" => Delta { "deleted": { "angle": 0, "backgroundColor": "transparent", @@ -18622,12 +18622,12 @@ History { "delta": Delta { "deleted": { "selectedElementIds": { - "id41": true, + "id47": true, }, }, "inserted": { "selectedElementIds": { - "id40": true, + "id46": true, }, }, }, @@ -18635,7 +18635,7 @@ History { "elementsChange": ElementsChange { "added": Map {}, "removed": Map { - "id41" => Delta { + "id47" => Delta { "deleted": { "angle": 0, "backgroundColor": "transparent", @@ -18681,7 +18681,7 @@ History { "added": Map {}, "removed": Map {}, "updated": Map { - "id41" => Delta { + "id47" => Delta { "deleted": { "index": "a0V", }, @@ -18697,12 +18697,12 @@ History { "delta": Delta { "deleted": { "selectedElementIds": { - "id39": true, + "id45": true, }, }, "inserted": { "selectedElementIds": { - "id41": true, + "id47": true, }, }, }, @@ -18718,7 +18718,7 @@ History { "delta": Delta { "deleted": { "selectedElementIds": { - "id41": true, + "id47": true, }, }, "inserted": { @@ -18743,7 +18743,7 @@ History { "added": Map {}, "removed": Map {}, "updated": Map { - "id39" => Delta { + "id45" => Delta { "deleted": { "index": "a2", }, @@ -18751,7 +18751,7 @@ History { "index": "Zz", }, }, - "id41" => Delta { + "id47" => Delta { "deleted": { "index": "a3", }, @@ -18855,12 +18855,12 @@ exports[`history > singleplayer undo/redo > should support duplication of groups "scrollY": 0, "searchMatches": [], "selectedElementIds": { - "id35_copy_copy": true, - "id36_copy_copy": true, + "id42": true, + "id44": true, }, "selectedElementsAreBeingDragged": false, "selectedGroupIds": { - "A_copy": true, + "id43": true, }, "selectionElement": null, "shouldCacheIgnoreZoom": false, @@ -18963,10 +18963,10 @@ exports[`history > singleplayer undo/redo > should support duplication of groups "fillStyle": "solid", "frameId": null, "groupIds": [ - "A_copy", + "id43", ], "height": 100, - "id": "id35_copy_copy", + "id": "id42", "index": "a1G", "isDeleted": false, "link": null, @@ -18997,10 +18997,10 @@ exports[`history > singleplayer undo/redo > should support duplication of groups "fillStyle": "solid", "frameId": null, "groupIds": [ - "A_copy", + "id43", ], "height": 100, - "id": "id36_copy_copy", + "id": "id44", "index": "a1V", "isDeleted": false, "link": null, @@ -19031,10 +19031,10 @@ exports[`history > singleplayer undo/redo > should support duplication of groups "fillStyle": "solid", "frameId": null, "groupIds": [ - "A_copy", + "id40", ], "height": 100, - "id": "id35_copy", + "id": "id39", "index": "a2", "isDeleted": true, "link": null, @@ -19065,10 +19065,10 @@ exports[`history > singleplayer undo/redo > should support duplication of groups "fillStyle": "solid", "frameId": null, "groupIds": [ - "A_copy", + "id40", ], "height": 100, - "id": "id36_copy", + "id": "id41", "index": "a3", "isDeleted": true, "link": null, @@ -19196,11 +19196,11 @@ History { "delta": Delta { "deleted": { "selectedElementIds": { - "id35_copy_copy": true, - "id36_copy_copy": true, + "id42": true, + "id44": true, }, "selectedGroupIds": { - "A_copy": true, + "id43": true, }, }, "inserted": { @@ -19217,7 +19217,7 @@ History { "elementsChange": ElementsChange { "added": Map {}, "removed": Map { - "id35_copy_copy" => Delta { + "id42" => Delta { "deleted": { "angle": 0, "backgroundColor": "transparent", @@ -19226,7 +19226,7 @@ History { "fillStyle": "solid", "frameId": null, "groupIds": [ - "A_copy", + "id43", ], "height": 100, "index": "a1G", @@ -19250,7 +19250,7 @@ History { "isDeleted": true, }, }, - "id36_copy_copy" => Delta { + "id44" => Delta { "deleted": { "angle": 0, "backgroundColor": "transparent", @@ -19259,7 +19259,7 @@ History { "fillStyle": "solid", "frameId": null, "groupIds": [ - "A_copy", + "id43", ], "height": 100, "index": "a1V", diff --git a/packages/excalidraw/tests/__snapshots__/move.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/move.test.tsx.snap index 1fc6156fe6..2e34ec6b2d 100644 --- a/packages/excalidraw/tests/__snapshots__/move.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/move.test.tsx.snap @@ -10,7 +10,7 @@ exports[`duplicate element on move when ALT is clicked > rectangle 5`] = ` "frameId": null, "groupIds": [], "height": 50, - "id": "id0_copy", + "id": "id2", "index": "a0", "isDeleted": false, "link": null, diff --git a/packages/excalidraw/tests/__snapshots__/regressionTests.test.tsx.snap b/packages/excalidraw/tests/__snapshots__/regressionTests.test.tsx.snap index 75795b251e..5d48ead6cf 100644 --- a/packages/excalidraw/tests/__snapshots__/regressionTests.test.tsx.snap +++ b/packages/excalidraw/tests/__snapshots__/regressionTests.test.tsx.snap @@ -2129,7 +2129,7 @@ History { "elementsChange": ElementsChange { "added": Map {}, "removed": Map { - "id0_copy" => Delta { + "id2" => Delta { "deleted": { "angle": 0, "backgroundColor": "transparent", @@ -10619,7 +10619,7 @@ History { "elementsChange": ElementsChange { "added": Map {}, "removed": Map { - "id0_copy" => Delta { + "id6" => Delta { "deleted": { "angle": 0, "backgroundColor": "transparent", @@ -10628,7 +10628,7 @@ History { "fillStyle": "solid", "frameId": null, "groupIds": [ - "id4_copy", + "id7", ], "height": 10, "index": "a0", @@ -10652,7 +10652,7 @@ History { "isDeleted": true, }, }, - "id1_copy" => Delta { + "id8" => Delta { "deleted": { "angle": 0, "backgroundColor": "transparent", @@ -10661,7 +10661,7 @@ History { "fillStyle": "solid", "frameId": null, "groupIds": [ - "id4_copy", + "id7", ], "height": 10, "index": "a1", @@ -10685,7 +10685,7 @@ History { "isDeleted": true, }, }, - "id2_copy" => Delta { + "id9" => Delta { "deleted": { "angle": 0, "backgroundColor": "transparent", @@ -10694,7 +10694,7 @@ History { "fillStyle": "solid", "frameId": null, "groupIds": [ - "id4_copy", + "id7", ], "height": 10, "index": "a2", diff --git a/packages/excalidraw/tests/helpers/api.ts b/packages/excalidraw/tests/helpers/api.ts index f0876611e2..a6432666a5 100644 --- a/packages/excalidraw/tests/helpers/api.ts +++ b/packages/excalidraw/tests/helpers/api.ts @@ -40,6 +40,7 @@ import { createTestHook } from "../../components/App"; import type { Action } from "../../actions/types"; import { mutateElement } from "../../element/mutateElement"; import { pointFrom, type LocalPoint, type Radians } from "../../../math"; +import { selectGroupsForSelectedElements } from "../../groups"; const readFile = util.promisify(fs.readFile); // so that window.h is available when App.tsx is not imported as well. @@ -68,13 +69,21 @@ export class API { }); }; - static setSelectedElements = (elements: ExcalidrawElement[]) => { + static setSelectedElements = (elements: ExcalidrawElement[], editingGroupId?: string | null) => { act(() => { h.setState({ - selectedElementIds: elements.reduce((acc, element) => { - acc[element.id] = true; - return acc; - }, {} as Record), + ...selectGroupsForSelectedElements( + { + editingGroupId: editingGroupId ?? null, + selectedElementIds: elements.reduce((acc, element) => { + acc[element.id] = true; + return acc; + }, {} as Record), + }, + elements, + h.state, + h.app, + ) }); }); }; @@ -158,7 +167,7 @@ export class API { isDeleted?: boolean; frameId?: ExcalidrawElement["id"] | null; index?: ExcalidrawElement["index"]; - groupIds?: string[]; + groupIds?: ExcalidrawElement["groupIds"]; // generic element props strokeColor?: ExcalidrawGenericElement["strokeColor"]; backgroundColor?: ExcalidrawGenericElement["backgroundColor"]; @@ -369,6 +378,84 @@ export class API { return element as any; }; + static createTextContainer = (opts?: { + frameId?: ExcalidrawElement["id"]; + groupIds?: ExcalidrawElement["groupIds"]; + label?: { + text?: string; + frameId?: ExcalidrawElement["id"] | null; + groupIds?: ExcalidrawElement["groupIds"]; + }; + }) => { + const rectangle = API.createElement({ + type: "rectangle", + frameId: opts?.frameId || null, + groupIds: opts?.groupIds, + }); + + const text = API.createElement({ + type: "text", + text: opts?.label?.text || "sample-text", + width: 50, + height: 20, + fontSize: 16, + containerId: rectangle.id, + frameId: + opts?.label?.frameId === undefined + ? opts?.frameId ?? null + : opts?.label?.frameId ?? null, + groupIds: opts?.label?.groupIds === undefined + ? opts?.groupIds + : opts?.label?.groupIds , + + }); + + mutateElement( + rectangle, + { + boundElements: [{ type: "text", id: text.id }], + }, + false, + ); + + return [rectangle, text]; + }; + + static createLabeledArrow = (opts?: { + frameId?: ExcalidrawElement["id"]; + label?: { + text?: string; + frameId?: ExcalidrawElement["id"] | null; + }; + }) => { + const arrow = API.createElement({ + type: "arrow", + frameId: opts?.frameId || null, + }); + + const text = API.createElement({ + type: "text", + id: "text2", + width: 50, + height: 20, + containerId: arrow.id, + frameId: + opts?.label?.frameId === undefined + ? opts?.frameId ?? null + : opts?.label?.frameId ?? null, + }); + + mutateElement( + arrow, + { + boundElements: [{ type: "text", id: text.id }], + }, + false, + ); + + return [arrow, text]; + }; + static readFile = async ( filepath: string, encoding?: T, diff --git a/packages/excalidraw/tests/history.test.tsx b/packages/excalidraw/tests/history.test.tsx index 9402095ee7..5000940ca4 100644 --- a/packages/excalidraw/tests/history.test.tsx +++ b/packages/excalidraw/tests/history.test.tsx @@ -7,6 +7,7 @@ import { assertSelectedElements, render, togglePopover, + getCloneByOrigId, } from "./test-utils"; import { Excalidraw } from "../index"; import { Keyboard, Pointer, UI } from "./helpers/ui"; @@ -15,7 +16,7 @@ import { getDefaultAppState } from "../appState"; import { fireEvent, queryByTestId, waitFor } from "@testing-library/react"; import { createUndoAction, createRedoAction } from "../actions/actionHistory"; import { actionToggleViewMode } from "../actions/actionToggleViewMode"; -import { EXPORT_DATA_TYPES, MIME_TYPES } from "../constants"; +import { EXPORT_DATA_TYPES, MIME_TYPES, ORIG_ID } from "../constants"; import type { AppState } from "../types"; import { arrayToMap } from "../utils"; import { @@ -1138,8 +1139,8 @@ describe("history", () => { expect(h.elements).toEqual([ expect.objectContaining({ id: rect1.id, isDeleted: false }), expect.objectContaining({ id: rect2.id, isDeleted: false }), - expect.objectContaining({ id: `${rect1.id}_copy`, isDeleted: true }), - expect.objectContaining({ id: `${rect2.id}_copy`, isDeleted: true }), + expect.objectContaining({ [ORIG_ID]: rect1.id, isDeleted: true }), + expect.objectContaining({ [ORIG_ID]: rect2.id, isDeleted: true }), ]); expect(h.state.editingGroupId).toBeNull(); expect(h.state.selectedGroupIds).toEqual({ A: true }); @@ -1151,8 +1152,8 @@ describe("history", () => { expect(h.elements).toEqual([ expect.objectContaining({ id: rect1.id, isDeleted: false }), expect.objectContaining({ id: rect2.id, isDeleted: false }), - expect.objectContaining({ id: `${rect1.id}_copy`, isDeleted: false }), - expect.objectContaining({ id: `${rect2.id}_copy`, isDeleted: false }), + expect.objectContaining({ [ORIG_ID]: rect1.id, isDeleted: false }), + expect.objectContaining({ [ORIG_ID]: rect2.id, isDeleted: false }), ]); expect(h.state.editingGroupId).toBeNull(); expect(h.state.selectedGroupIds).not.toEqual( @@ -1171,14 +1172,14 @@ describe("history", () => { expect.arrayContaining([ expect.objectContaining({ id: rect1.id, isDeleted: false }), expect.objectContaining({ id: rect2.id, isDeleted: false }), - expect.objectContaining({ id: `${rect1.id}_copy`, isDeleted: true }), - expect.objectContaining({ id: `${rect2.id}_copy`, isDeleted: true }), + expect.objectContaining({ [ORIG_ID]: rect1.id, isDeleted: true }), + expect.objectContaining({ [ORIG_ID]: rect2.id, isDeleted: true }), expect.objectContaining({ - id: `${rect1.id}_copy_copy`, + [ORIG_ID]: getCloneByOrigId(rect1.id)?.id, isDeleted: false, }), expect.objectContaining({ - id: `${rect2.id}_copy_copy`, + [ORIG_ID]: getCloneByOrigId(rect2.id)?.id, isDeleted: false, }), ]), diff --git a/packages/excalidraw/tests/library.test.tsx b/packages/excalidraw/tests/library.test.tsx index 51b139ce37..7b48407b72 100644 --- a/packages/excalidraw/tests/library.test.tsx +++ b/packages/excalidraw/tests/library.test.tsx @@ -1,11 +1,11 @@ import React from "react"; import { vi } from "vitest"; -import { fireEvent, render, waitFor } from "./test-utils"; +import { fireEvent, getCloneByOrigId, render, waitFor } from "./test-utils"; import { act, queryByTestId } from "@testing-library/react"; import { Excalidraw } from "../index"; import { API } from "./helpers/api"; -import { MIME_TYPES } from "../constants"; +import { MIME_TYPES, ORIG_ID } from "../constants"; import type { LibraryItem, LibraryItems } from "../types"; import { UI } from "./helpers/ui"; import { serializeLibraryAsJSON } from "../data/json"; @@ -76,7 +76,7 @@ describe("library", () => { }), ); await waitFor(() => { - expect(h.elements).toEqual([expect.objectContaining({ id: "A_copy" })]); + expect(h.elements).toEqual([expect.objectContaining({ [ORIG_ID]: "A" })]); }); }); @@ -125,23 +125,27 @@ describe("library", () => { ); await waitFor(() => { - expect(h.elements).toEqual([ - expect.objectContaining({ - id: "rectangle1_copy", - boundElements: expect.arrayContaining([ - { type: "text", id: "text1_copy" }, - { type: "arrow", id: "arrow1_copy" }, - ]), - }), - expect.objectContaining({ - id: "text1_copy", - containerId: "rectangle1_copy", - }), - expect.objectContaining({ - id: "arrow1_copy", - endBinding: expect.objectContaining({ elementId: "rectangle1_copy" }), - }), - ]); + expect(h.elements).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + [ORIG_ID]: "rectangle1", + boundElements: expect.arrayContaining([ + { type: "text", id: getCloneByOrigId("text1").id }, + { type: "arrow", id: getCloneByOrigId("arrow1").id }, + ]), + }), + expect.objectContaining({ + [ORIG_ID]: "text1", + containerId: getCloneByOrigId("rectangle1").id, + }), + expect.objectContaining({ + [ORIG_ID]: "arrow1", + endBinding: expect.objectContaining({ + elementId: getCloneByOrigId("rectangle1").id, + }), + }), + ]), + ); }); }); @@ -170,10 +174,11 @@ describe("library", () => { await waitFor(() => { expect(h.elements).toEqual([ expect.objectContaining({ - id: "elem1_copy", + [ORIG_ID]: "elem1", }), expect.objectContaining({ - id: expect.not.stringMatching(/^(elem1_copy|elem1)$/), + id: expect.not.stringMatching(/^elem1$/), + [ORIG_ID]: expect.not.stringMatching(/^\w+$/), }), ]); }); @@ -189,7 +194,7 @@ describe("library", () => { }), ); await waitFor(() => { - expect(h.elements).toEqual([expect.objectContaining({ id: "A_copy" })]); + expect(h.elements).toEqual([expect.objectContaining({ [ORIG_ID]: "A" })]); }); expect(h.state.activeTool.type).toBe("selection"); }); diff --git a/packages/excalidraw/tests/test-utils.ts b/packages/excalidraw/tests/test-utils.ts index 4c0eacee68..1c775c2214 100644 --- a/packages/excalidraw/tests/test-utils.ts +++ b/packages/excalidraw/tests/test-utils.ts @@ -11,6 +11,10 @@ import { getSelectedElements } from "../scene/selection"; import type { ExcalidrawElement } from "../element/types"; import { UI } from "./helpers/ui"; import { diffStringsUnified } from "jest-diff"; +import ansi from "ansicolor"; +import { ORIG_ID } from "../constants"; +import { arrayToMap } from "../utils"; +import type { AllPossibleKeys } from "../utility-types"; const customQueries = { ...queries, @@ -295,3 +299,150 @@ expect.addSnapshotSerializer({ ); }, }); + +export const getCloneByOrigId = ( + origId: ExcalidrawElement["id"], + returnNullIfNotExists: T = false as T, +): T extends true ? ExcalidrawElement | null : ExcalidrawElement => { + const clonedElement = window.h.elements?.find( + (el) => (el as any)[ORIG_ID] === origId, + ); + if (clonedElement) { + return clonedElement; + } + if (returnNullIfNotExists !== true) { + throw new Error(`cloned element not found for origId: ${origId}`); + } + return null as T extends true ? ExcalidrawElement | null : ExcalidrawElement; +}; + +/** + * Assertion helper that strips the actual elements of extra attributes + * so that diffs are easier to read in case of failure. + * + * Asserts element order as well, and selected element ids + * (when `selected: true` set for given element). + * + * If testing cloned elements, you can use { `[ORIG_ID]: origElement.id } + * If you need to refer to cloned element properties, you can use + * `getCloneByOrigId()`, e.g.: `{ frameId: getCloneByOrigId(origFrame.id)?.id }` + */ +export const assertElements = >( + actualElements: readonly ExcalidrawElement[], + /** array order matters */ + expectedElements: (Partial> & { + /** meta, will be stripped for element attribute checks */ + selected?: true; + } & ( + | { + id: ExcalidrawElement["id"]; + } + | { [ORIG_ID]?: string } + ))[], +) => { + const h = window.h; + + const expectedElementsWithIds: (typeof expectedElements[number] & { + id: ExcalidrawElement["id"]; + })[] = expectedElements.map((el) => { + if ("id" in el) { + return el; + } + const actualElement = actualElements.find( + (act) => (act as any)[ORIG_ID] === el[ORIG_ID], + ); + if (actualElement) { + return { ...el, id: actualElement.id }; + } + return { + ...el, + id: "UNKNOWN_ID", + }; + }); + + const map_expectedElements = arrayToMap(expectedElementsWithIds); + + const selectedElementIds = expectedElementsWithIds.reduce( + (acc: Record, el) => { + if (el.selected) { + acc[el.id] = true; + } + return acc; + }, + {}, + ); + + const mappedActualElements = actualElements.map((el) => { + const expectedElement = map_expectedElements.get(el.id); + if (expectedElement) { + const pickedAttrs: Record = {}; + + for (const key of Object.keys(expectedElement)) { + if (key === "selected") { + delete expectedElement.selected; + continue; + } + pickedAttrs[key] = (el as any)[key]; + } + + if (ORIG_ID in expectedElement) { + // @ts-ignore + pickedAttrs[ORIG_ID] = (el as any)[ORIG_ID]; + } + + return pickedAttrs; + } + return el; + }); + + try { + // testing order separately for even easier diffs + expect(actualElements.map((x) => x.id)).toEqual( + expectedElementsWithIds.map((x) => x.id), + ); + } catch (err: any) { + let errStr = "\n\nmismatched element order\n\n"; + + errStr += `actual: ${ansi.lightGray( + `[${err.actual + .map((id: string, index: number) => { + const act = actualElements[index]; + + return `${ + id === err.expected[index] ? ansi.green(id) : ansi.red(id) + } (${act.type.slice(0, 4)}${ + ORIG_ID in act ? ` ↳ ${(act as any)[ORIG_ID]}` : "" + })`; + }) + .join(", ")}]`, + )}\n${ansi.lightGray( + `expected: [${err.expected + .map((exp: string, index: number) => { + const expEl = actualElements.find((el) => el.id === exp); + const origEl = + expEl && + actualElements.find((el) => el.id === (expEl as any)[ORIG_ID]); + return expEl + ? `${ + exp === err.actual[index] + ? ansi.green(expEl.id) + : ansi.red(expEl.id) + } (${expEl.type.slice(0, 4)}${origEl ? ` ↳ ${origEl.id}` : ""})` + : exp; + }) + .join(", ")}]\n`, + )}`; + + const error = new Error(errStr); + const stack = err.stack.split("\n"); + stack.splice(1, 1); + error.stack = stack.join("\n"); + throw error; + } + + expect(mappedActualElements).toEqual( + expect.arrayContaining(expectedElementsWithIds), + ); + + expect(h.state.selectedElementIds).toEqual(selectedElementIds); +}; diff --git a/packages/excalidraw/tests/zindex.test.tsx b/packages/excalidraw/tests/zindex.test.tsx index 66599ac860..732644eb35 100644 --- a/packages/excalidraw/tests/zindex.test.tsx +++ b/packages/excalidraw/tests/zindex.test.tsx @@ -1,6 +1,6 @@ import React from "react"; import ReactDOM from "react-dom"; -import { act, render } from "./test-utils"; +import { act, getCloneByOrigId, render } from "./test-utils"; import { Excalidraw } from "../index"; import { reseed } from "../random"; import { @@ -916,9 +916,9 @@ describe("z-index manipulation", () => { API.executeAction(actionDuplicateSelection); expect(h.elements).toMatchObject([ { id: "A" }, - { id: "A_copy" }, + { id: getCloneByOrigId("A").id }, { id: "B" }, - { id: "B_copy" }, + { id: getCloneByOrigId("B").id }, ]); populateElements([ @@ -930,12 +930,12 @@ describe("z-index manipulation", () => { { id: "A" }, { id: "B" }, { - id: "A_copy", + id: getCloneByOrigId("A").id, groupIds: [expect.stringMatching(/.{3,}/)], }, { - id: "B_copy", + id: getCloneByOrigId("B").id, groupIds: [expect.stringMatching(/.{3,}/)], }, @@ -951,12 +951,12 @@ describe("z-index manipulation", () => { { id: "A" }, { id: "B" }, { - id: "A_copy", + id: getCloneByOrigId("A").id, groupIds: [expect.stringMatching(/.{3,}/)], }, { - id: "B_copy", + id: getCloneByOrigId("B").id, groupIds: [expect.stringMatching(/.{3,}/)], }, @@ -972,10 +972,10 @@ describe("z-index manipulation", () => { expect(h.elements.map((element) => element.id)).toEqual([ "A", "B", - "A_copy", - "B_copy", + getCloneByOrigId("A").id, + getCloneByOrigId("B").id, "C", - "C_copy", + getCloneByOrigId("C").id, ]); populateElements([ @@ -988,12 +988,12 @@ describe("z-index manipulation", () => { expect(h.elements.map((element) => element.id)).toEqual([ "A", "B", - "A_copy", - "B_copy", + getCloneByOrigId("A").id, + getCloneByOrigId("B").id, "C", "D", - "C_copy", - "D_copy", + getCloneByOrigId("C").id, + getCloneByOrigId("D").id, ]); populateElements( @@ -1010,10 +1010,10 @@ describe("z-index manipulation", () => { expect(h.elements.map((element) => element.id)).toEqual([ "A", "B", - "A_copy", - "B_copy", + getCloneByOrigId("A").id, + getCloneByOrigId("B").id, "C", - "C_copy", + getCloneByOrigId("C").id, ]); populateElements( @@ -1031,9 +1031,9 @@ describe("z-index manipulation", () => { "A", "B", "C", - "A_copy", - "B_copy", - "C_copy", + getCloneByOrigId("A").id, + getCloneByOrigId("B").id, + getCloneByOrigId("C").id, ]); populateElements( @@ -1054,15 +1054,15 @@ describe("z-index manipulation", () => { "A", "B", "C", - "A_copy", - "B_copy", - "C_copy", + getCloneByOrigId("A").id, + getCloneByOrigId("B").id, + getCloneByOrigId("C").id, "D", "E", "F", - "D_copy", - "E_copy", - "F_copy", + getCloneByOrigId("D").id, + getCloneByOrigId("E").id, + getCloneByOrigId("F").id, ]); populateElements( @@ -1076,7 +1076,7 @@ describe("z-index manipulation", () => { API.executeAction(actionDuplicateSelection); expect(h.elements.map((element) => element.id)).toEqual([ "A", - "A_copy", + getCloneByOrigId("A").id, "B", "C", ]); @@ -1093,7 +1093,7 @@ describe("z-index manipulation", () => { expect(h.elements.map((element) => element.id)).toEqual([ "A", "B", - "B_copy", + getCloneByOrigId("B").id, "C", ]); @@ -1108,9 +1108,9 @@ describe("z-index manipulation", () => { API.executeAction(actionDuplicateSelection); expect(h.elements.map((element) => element.id)).toEqual([ "A", - "A_copy", + getCloneByOrigId("A").id, "B", - "B_copy", + getCloneByOrigId("B").id, "C", ]); }); @@ -1125,8 +1125,8 @@ describe("z-index manipulation", () => { expect(h.elements.map((element) => element.id)).toEqual([ "A", "C", - "A_copy", - "C_copy", + getCloneByOrigId("A").id, + getCloneByOrigId("C").id, "B", ]); }); @@ -1144,9 +1144,9 @@ describe("z-index manipulation", () => { "A", "B", "C", - "A_copy", - "B_copy", - "C_copy", + getCloneByOrigId("A").id, + getCloneByOrigId("B").id, + getCloneByOrigId("C").id, "D", ]); }); diff --git a/packages/excalidraw/utility-types.ts b/packages/excalidraw/utility-types.ts index f7872393e5..d4804d1957 100644 --- a/packages/excalidraw/utility-types.ts +++ b/packages/excalidraw/utility-types.ts @@ -65,3 +65,6 @@ export type MakeBrand = { /** Maybe just promise or already fulfilled one! */ export type MaybePromise = T | Promise; + +// get union of all keys from the union of types +export type AllPossibleKeys = T extends any ? keyof T : never; diff --git a/packages/excalidraw/utils.ts b/packages/excalidraw/utils.ts index e4431ddd1f..6a682e9cfa 100644 --- a/packages/excalidraw/utils.ts +++ b/packages/excalidraw/utils.ts @@ -1240,3 +1240,6 @@ export class PromisePool { export const escapeDoubleQuotes = (str: string) => { return str.replace(/"/g, """); }; + +export const castArray = (value: T | T[]): T[] => + Array.isArray(value) ? value : [value]; diff --git a/yarn.lock b/yarn.lock index 79ad50f23e..a71ad5738d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3916,6 +3916,11 @@ ansi-styles@^6.0.0, ansi-styles@^6.1.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== +ansicolor@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/ansicolor/-/ansicolor-2.0.3.tgz#ec4448ae5baf8c2d62bf2dad52eac06ba0b5ea21" + integrity sha512-pzusTqk9VHrjgMCcTPDTTvfJfx6Q3+L5tQ6yKC8Diexmoit4YROTFIkxFvRTNL9y5s0Q8HrSrgerCD5bIC+Kiw== + anymatch@~3.1.2: version "3.1.3" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e"