aboutsummaryrefslogtreecommitdiffstats
path: root/packages/excalidraw/actions/actionDuplicateSelection.test.tsx
diff options
context:
space:
mode:
authorkj_sh6042026-03-15 16:19:35 -0400
committerkj_sh6042026-03-15 16:19:35 -0400
commit6ec259a0e71174651bae95d4628138bf6fd68742 (patch)
tree5e33c6a5ec091ecabfcb257fdc7b6a88ed8754ac /packages/excalidraw/actions/actionDuplicateSelection.test.tsx
parent16c8578b15c727f22921f8a80a56ee4d4e7f2272 (diff)
refactor: packages/
Diffstat (limited to 'packages/excalidraw/actions/actionDuplicateSelection.test.tsx')
-rw-r--r--packages/excalidraw/actions/actionDuplicateSelection.test.tsx530
1 files changed, 530 insertions, 0 deletions
diff --git a/packages/excalidraw/actions/actionDuplicateSelection.test.tsx b/packages/excalidraw/actions/actionDuplicateSelection.test.tsx
new file mode 100644
index 0000000..dc471df
--- /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(<Excalidraw />);
+ });
+
+ 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"] },
+ ]);
+ });
+ });
+});