aboutsummaryrefslogtreecommitdiffstats
path: root/packages/excalidraw/components/Stats/stats.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/components/Stats/stats.test.tsx
parent16c8578b15c727f22921f8a80a56ee4d4e7f2272 (diff)
refactor: packages/
Diffstat (limited to 'packages/excalidraw/components/Stats/stats.test.tsx')
-rw-r--r--packages/excalidraw/components/Stats/stats.test.tsx724
1 files changed, 724 insertions, 0 deletions
diff --git a/packages/excalidraw/components/Stats/stats.test.tsx b/packages/excalidraw/components/Stats/stats.test.tsx
new file mode 100644
index 0000000..49d6a62
--- /dev/null
+++ b/packages/excalidraw/components/Stats/stats.test.tsx
@@ -0,0 +1,724 @@
+import React from "react";
+import { act, fireEvent, queryByTestId } from "@testing-library/react";
+import { Keyboard, Pointer, UI } from "../../tests/helpers/ui";
+import { getStepSizedValue } from "./utils";
+import {
+ GlobalTestState,
+ mockBoundingClientRect,
+ render,
+ restoreOriginalGetBoundingClientRect,
+} from "../../tests/test-utils";
+import * as StaticScene from "../../renderer/staticScene";
+import { vi } from "vitest";
+import { reseed } from "../../random";
+import { setDateTimeForTests } from "../../utils";
+import { Excalidraw, mutateElement } from "../..";
+import { t } from "../../i18n";
+import type {
+ ExcalidrawElement,
+ ExcalidrawLinearElement,
+ ExcalidrawTextElement,
+} from "../../element/types";
+import { getTextEditor, updateTextEditor } from "../../tests/queries/dom";
+import { getCommonBounds, isTextElement } from "../../element";
+import { API } from "../../tests/helpers/api";
+import { actionGroup } from "../../actions";
+import { isInGroup } from "../../groups";
+import type { Degrees } from "@excalidraw/math";
+import { degreesToRadians, pointFrom, pointRotateRads } from "@excalidraw/math";
+
+const { h } = window;
+const mouse = new Pointer("mouse");
+const renderStaticScene = vi.spyOn(StaticScene, "renderStaticScene");
+let stats: HTMLElement | null = null;
+let elementStats: HTMLElement | null | undefined = null;
+
+const testInputProperty = (
+ element: ExcalidrawElement,
+ property: "x" | "y" | "width" | "height" | "angle" | "fontSize",
+ label: string,
+ initialValue: number,
+ nextValue: number,
+) => {
+ const input = UI.queryStatsProperty(label)?.querySelector(
+ ".drag-input",
+ ) as HTMLInputElement;
+ expect(input).toBeDefined();
+ expect(input.value).toBe(initialValue.toString());
+ UI.updateInput(input, String(nextValue));
+ if (property === "angle") {
+ expect(element[property]).toBe(
+ degreesToRadians(Number(nextValue) as Degrees),
+ );
+ } else if (property === "fontSize" && isTextElement(element)) {
+ expect(element[property]).toBe(Number(nextValue));
+ } else if (property !== "fontSize") {
+ expect(element[property]).toBe(Number(nextValue));
+ }
+};
+
+describe("step sized value", () => {
+ it("should return edge values correctly", () => {
+ const steps = [10, 15, 20, 25, 30];
+ const values = [10, 15, 20, 25, 30];
+
+ steps.forEach((step, idx) => {
+ expect(getStepSizedValue(values[idx], step)).toEqual(values[idx]);
+ });
+ });
+
+ it("step sized value lies in the middle", () => {
+ let stepSize = 15;
+ let values = [7.5, 9, 12, 14.99, 15, 22.49];
+
+ values.forEach((value) => {
+ expect(getStepSizedValue(value, stepSize)).toEqual(15);
+ });
+
+ stepSize = 10;
+ values = [-5, 4.99, 0, 1.23];
+ values.forEach((value) => {
+ expect(getStepSizedValue(value, stepSize)).toEqual(0);
+ });
+ });
+});
+
+describe("binding with linear elements", () => {
+ beforeEach(async () => {
+ localStorage.clear();
+ renderStaticScene.mockClear();
+ reseed(19);
+ setDateTimeForTests("201933152653");
+
+ await render(<Excalidraw handleKeyboardGlobally={true} />);
+
+ API.setElements([]);
+
+ fireEvent.contextMenu(GlobalTestState.interactiveCanvas, {
+ button: 2,
+ clientX: 1,
+ clientY: 1,
+ });
+ const contextMenu = UI.queryContextMenu();
+ fireEvent.click(queryByTestId(contextMenu!, "stats")!);
+ stats = UI.queryStats();
+
+ UI.clickTool("rectangle");
+ mouse.down();
+ mouse.up(200, 100);
+
+ UI.clickTool("arrow");
+ mouse.down(5, 0);
+ mouse.up(300, 50);
+
+ elementStats = stats?.querySelector("#elementStats");
+ });
+
+ beforeAll(() => {
+ mockBoundingClientRect();
+ });
+
+ afterAll(() => {
+ restoreOriginalGetBoundingClientRect();
+ });
+
+ it("should remain bound to linear element on small position change", async () => {
+ const linear = h.elements[1] as ExcalidrawLinearElement;
+ const inputX = UI.queryStatsProperty("X")?.querySelector(
+ ".drag-input",
+ ) as HTMLInputElement;
+
+ expect(linear.startBinding).not.toBe(null);
+ expect(inputX).not.toBeNull();
+ UI.updateInput(inputX, String("204"));
+ expect(linear.startBinding).not.toBe(null);
+ });
+
+ it("should remain bound to linear element on small angle change", async () => {
+ const linear = h.elements[1] as ExcalidrawLinearElement;
+ const inputAngle = UI.queryStatsProperty("A")?.querySelector(
+ ".drag-input",
+ ) as HTMLInputElement;
+
+ expect(linear.startBinding).not.toBe(null);
+ UI.updateInput(inputAngle, String("1"));
+ expect(linear.startBinding).not.toBe(null);
+ });
+
+ it("should unbind linear element on large position change", async () => {
+ const linear = h.elements[1] as ExcalidrawLinearElement;
+ const inputX = UI.queryStatsProperty("X")?.querySelector(
+ ".drag-input",
+ ) as HTMLInputElement;
+
+ expect(linear.startBinding).not.toBe(null);
+ expect(inputX).not.toBeNull();
+ UI.updateInput(inputX, String("254"));
+ expect(linear.startBinding).toBe(null);
+ });
+
+ it("should remain bound to linear element on small angle change", async () => {
+ const linear = h.elements[1] as ExcalidrawLinearElement;
+ const inputAngle = UI.queryStatsProperty("A")?.querySelector(
+ ".drag-input",
+ ) as HTMLInputElement;
+
+ expect(linear.startBinding).not.toBe(null);
+ UI.updateInput(inputAngle, String("45"));
+ expect(linear.startBinding).toBe(null);
+ });
+});
+
+// single element
+describe("stats for a generic element", () => {
+ beforeEach(async () => {
+ localStorage.clear();
+ renderStaticScene.mockClear();
+ reseed(7);
+ setDateTimeForTests("201933152653");
+
+ await render(<Excalidraw handleKeyboardGlobally={true} />);
+
+ API.setElements([]);
+
+ fireEvent.contextMenu(GlobalTestState.interactiveCanvas, {
+ button: 2,
+ clientX: 1,
+ clientY: 1,
+ });
+ const contextMenu = UI.queryContextMenu();
+ fireEvent.click(queryByTestId(contextMenu!, "stats")!);
+ stats = UI.queryStats();
+
+ UI.clickTool("rectangle");
+ mouse.down();
+ mouse.up(200, 100);
+ elementStats = stats?.querySelector("#elementStats");
+ });
+
+ beforeAll(() => {
+ mockBoundingClientRect();
+ });
+
+ afterAll(() => {
+ restoreOriginalGetBoundingClientRect();
+ });
+
+ it("should open stats", () => {
+ expect(stats).toBeDefined();
+ expect(elementStats).toBeDefined();
+
+ // title
+ const title = elementStats?.querySelector("h3");
+ expect(title?.lastChild?.nodeValue)?.toBe(t("stats.elementProperties"));
+
+ // element type
+ const elementType = queryByTestId(elementStats!, "stats-element-type");
+ expect(elementType).toBeDefined();
+ expect(elementType?.lastChild?.nodeValue).toBe(t("element.rectangle"));
+
+ // properties
+ ["X", "Y", "W", "H", "A"].forEach((label) => () => {
+ expect(
+ stats!.querySelector?.(`.drag-input-container[data-testid="${label}"]`),
+ ).toBeDefined();
+ });
+ });
+
+ it("should be able to edit all properties for a general element", () => {
+ const rectangle = h.elements[0];
+ const initialX = rectangle.x;
+ const initialY = rectangle.y;
+
+ testInputProperty(rectangle, "width", "W", 200, 100);
+ testInputProperty(rectangle, "height", "H", 100, 200);
+ testInputProperty(rectangle, "x", "X", initialX, 230);
+ testInputProperty(rectangle, "y", "Y", initialY, 220);
+ testInputProperty(rectangle, "angle", "A", 0, 45);
+ });
+
+ it("should keep only two decimal places", () => {
+ const rectangle = h.elements[0];
+ const rectangleId = rectangle.id;
+
+ const input = UI.queryStatsProperty("W")?.querySelector(
+ ".drag-input",
+ ) as HTMLInputElement;
+ expect(input).toBeDefined();
+ expect(input.value).toBe(rectangle.width.toString());
+ UI.updateInput(input, "123.123");
+ expect(h.elements.length).toBe(1);
+ expect(rectangle.id).toBe(rectangleId);
+ expect(input.value).toBe("123.12");
+ expect(rectangle.width).toBe(123.12);
+
+ UI.updateInput(input, "88.98766");
+ expect(input.value).toBe("88.99");
+ expect(rectangle.width).toBe(88.99);
+ });
+
+ it("should update input x and y when angle is changed", () => {
+ const rectangle = h.elements[0];
+ const [cx, cy] = [
+ rectangle.x + rectangle.width / 2,
+ rectangle.y + rectangle.height / 2,
+ ];
+ const [topLeftX, topLeftY] = pointRotateRads(
+ pointFrom(rectangle.x, rectangle.y),
+ pointFrom(cx, cy),
+ rectangle.angle,
+ );
+
+ const xInput = UI.queryStatsProperty("X")?.querySelector(
+ ".drag-input",
+ ) as HTMLInputElement;
+
+ const yInput = UI.queryStatsProperty("Y")?.querySelector(
+ ".drag-input",
+ ) as HTMLInputElement;
+
+ expect(xInput.value).toBe(topLeftX.toString());
+ expect(yInput.value).toBe(topLeftY.toString());
+
+ testInputProperty(rectangle, "angle", "A", 0, 45);
+
+ let [newTopLeftX, newTopLeftY] = pointRotateRads(
+ pointFrom(rectangle.x, rectangle.y),
+ pointFrom(cx, cy),
+ rectangle.angle,
+ );
+
+ expect(newTopLeftX.toString()).not.toEqual(xInput.value);
+ expect(newTopLeftY.toString()).not.toEqual(yInput.value);
+
+ testInputProperty(rectangle, "angle", "A", 45, 66);
+
+ [newTopLeftX, newTopLeftY] = pointRotateRads(
+ pointFrom(rectangle.x, rectangle.y),
+ pointFrom(cx, cy),
+ rectangle.angle,
+ );
+ expect(newTopLeftX.toString()).not.toEqual(xInput.value);
+ expect(newTopLeftY.toString()).not.toEqual(yInput.value);
+ });
+
+ it("should fix top left corner when width or height is changed", () => {
+ const rectangle = h.elements[0];
+
+ testInputProperty(rectangle, "angle", "A", 0, 45);
+ let [cx, cy] = [
+ rectangle.x + rectangle.width / 2,
+ rectangle.y + rectangle.height / 2,
+ ];
+ const [topLeftX, topLeftY] = pointRotateRads(
+ pointFrom(rectangle.x, rectangle.y),
+ pointFrom(cx, cy),
+ rectangle.angle,
+ );
+ testInputProperty(rectangle, "width", "W", rectangle.width, 400);
+ [cx, cy] = [
+ rectangle.x + rectangle.width / 2,
+ rectangle.y + rectangle.height / 2,
+ ];
+ let [currentTopLeftX, currentTopLeftY] = pointRotateRads(
+ pointFrom(rectangle.x, rectangle.y),
+ pointFrom(cx, cy),
+ rectangle.angle,
+ );
+ expect(currentTopLeftX).toBeCloseTo(topLeftX, 4);
+ expect(currentTopLeftY).toBeCloseTo(topLeftY, 4);
+
+ testInputProperty(rectangle, "height", "H", rectangle.height, 400);
+ [cx, cy] = [
+ rectangle.x + rectangle.width / 2,
+ rectangle.y + rectangle.height / 2,
+ ];
+ [currentTopLeftX, currentTopLeftY] = pointRotateRads(
+ pointFrom(rectangle.x, rectangle.y),
+ pointFrom(cx, cy),
+ rectangle.angle,
+ );
+
+ expect(currentTopLeftX).toBeCloseTo(topLeftX, 4);
+ expect(currentTopLeftY).toBeCloseTo(topLeftY, 4);
+ });
+});
+
+describe("stats for a non-generic element", () => {
+ beforeEach(async () => {
+ localStorage.clear();
+ renderStaticScene.mockClear();
+ reseed(7);
+ setDateTimeForTests("201933152653");
+
+ await render(<Excalidraw handleKeyboardGlobally={true} />);
+
+ API.setElements([]);
+
+ fireEvent.contextMenu(GlobalTestState.interactiveCanvas, {
+ button: 2,
+ clientX: 1,
+ clientY: 1,
+ });
+ const contextMenu = UI.queryContextMenu();
+ fireEvent.click(queryByTestId(contextMenu!, "stats")!);
+ stats = UI.queryStats();
+ });
+
+ beforeAll(() => {
+ mockBoundingClientRect();
+ });
+
+ afterAll(() => {
+ restoreOriginalGetBoundingClientRect();
+ });
+
+ it("text element", async () => {
+ UI.clickTool("text");
+ mouse.clickAt(20, 30);
+ const textEditorSelector = ".excalidraw-textEditorContainer > textarea";
+ const editor = await getTextEditor(textEditorSelector, true);
+ updateTextEditor(editor, "Hello!");
+ act(() => {
+ editor.blur();
+ });
+
+ const text = h.elements[0] as ExcalidrawTextElement;
+ mouse.clickOn(text);
+
+ elementStats = stats?.querySelector("#elementStats");
+
+ // can change font size
+ const input = UI.queryStatsProperty("F")?.querySelector(
+ ".drag-input",
+ ) as HTMLInputElement;
+ expect(input).toBeDefined();
+ expect(input.value).toBe(text.fontSize.toString());
+ UI.updateInput(input, "36");
+ expect(text.fontSize).toBe(36);
+
+ // cannot change width or height
+ const width = UI.queryStatsProperty("W")?.querySelector(".drag-input");
+ expect(width).toBeUndefined();
+ const height = UI.queryStatsProperty("H")?.querySelector(".drag-input");
+ expect(height).toBeUndefined();
+
+ // min font size is 4
+ UI.updateInput(input, "0");
+ expect(text.fontSize).not.toBe(0);
+ expect(text.fontSize).toBe(4);
+ });
+
+ it("frame element", () => {
+ const frame = API.createElement({
+ id: "id0",
+ type: "frame",
+ x: 150,
+ width: 150,
+ });
+ API.setElements([frame]);
+ API.setAppState({
+ selectedElementIds: {
+ [frame.id]: true,
+ },
+ });
+
+ elementStats = stats?.querySelector("#elementStats");
+
+ expect(elementStats).toBeDefined();
+
+ // cannot change angle
+ const angle = UI.queryStatsProperty("A")?.querySelector(".drag-input");
+ expect(angle).toBeUndefined();
+
+ // can change width or height
+ testInputProperty(frame, "width", "W", frame.width, 250);
+ testInputProperty(frame, "height", "H", frame.height, 500);
+ });
+
+ it("image element", () => {
+ const image = API.createElement({ type: "image", width: 200, height: 100 });
+ API.setElements([image]);
+ mouse.clickOn(image);
+ API.setAppState({
+ selectedElementIds: {
+ [image.id]: true,
+ },
+ });
+ elementStats = stats?.querySelector("#elementStats");
+ expect(elementStats).toBeDefined();
+ const widthToHeight = image.width / image.height;
+
+ // when width or height is changed, the aspect ratio is preserved
+ testInputProperty(image, "width", "W", image.width, 400);
+ expect(image.width).toBe(400);
+ expect(image.width / image.height).toBe(widthToHeight);
+
+ testInputProperty(image, "height", "H", image.height, 80);
+ expect(image.height).toBe(80);
+ expect(image.width / image.height).toBe(widthToHeight);
+ });
+
+ it("should display fontSize for bound text", () => {
+ const container = API.createElement({
+ type: "rectangle",
+ width: 200,
+ height: 100,
+ });
+ const text = API.createElement({
+ type: "text",
+ width: 200,
+ height: 100,
+ containerId: container.id,
+ fontSize: 20,
+ });
+ mutateElement(container, {
+ boundElements: [{ type: "text", id: text.id }],
+ });
+ API.setElements([container, text]);
+
+ API.setSelectedElements([container]);
+ const fontSize = UI.queryStatsProperty("F")?.querySelector(
+ ".drag-input",
+ ) as HTMLInputElement;
+ expect(fontSize).toBeDefined();
+
+ UI.updateInput(fontSize, "40");
+
+ expect(text.fontSize).toBe(40);
+ });
+});
+
+// multiple elements
+describe("stats for multiple elements", () => {
+ beforeEach(async () => {
+ mouse.reset();
+ localStorage.clear();
+ renderStaticScene.mockClear();
+ reseed(7);
+ setDateTimeForTests("201933152653");
+
+ await render(<Excalidraw handleKeyboardGlobally={true} />);
+
+ API.setElements([]);
+
+ fireEvent.contextMenu(GlobalTestState.interactiveCanvas, {
+ button: 2,
+ clientX: 1,
+ clientY: 1,
+ });
+ const contextMenu = UI.queryContextMenu();
+ fireEvent.click(queryByTestId(contextMenu!, "stats")!);
+ stats = UI.queryStats();
+ });
+
+ beforeAll(() => {
+ mockBoundingClientRect();
+ });
+
+ afterAll(() => {
+ restoreOriginalGetBoundingClientRect();
+ });
+
+ it("should display MIXED for elements with different values", () => {
+ UI.clickTool("rectangle");
+ mouse.down();
+ mouse.up(200, 100);
+
+ UI.clickTool("ellipse");
+ mouse.down(50, 50);
+ mouse.up(100, 100);
+
+ UI.clickTool("diamond");
+ mouse.down(-100, -100);
+ mouse.up(125, 145);
+
+ API.setAppState({
+ selectedElementIds: h.elements.reduce((acc, el) => {
+ acc[el.id] = true;
+ return acc;
+ }, {} as Record<string, true>),
+ });
+
+ elementStats = stats?.querySelector("#elementStats");
+
+ const width = UI.queryStatsProperty("W")?.querySelector(
+ ".drag-input",
+ ) as HTMLInputElement;
+ expect(width?.value).toBe("Mixed");
+ const height = UI.queryStatsProperty("H")?.querySelector(
+ ".drag-input",
+ ) as HTMLInputElement;
+ expect(height?.value).toBe("Mixed");
+ const angle = UI.queryStatsProperty("A")?.querySelector(
+ ".drag-input",
+ ) as HTMLInputElement;
+ expect(angle.value).toBe("0");
+
+ UI.updateInput(width, "250");
+ h.elements.forEach((el) => {
+ expect(el.width).toBe(250);
+ });
+
+ UI.updateInput(height, "450");
+ h.elements.forEach((el) => {
+ expect(el.height).toBe(450);
+ });
+ });
+
+ it("should display a property when one of the elements is editable for that property", async () => {
+ // text, rectangle, frame
+ UI.clickTool("text");
+ mouse.clickAt(20, 30);
+ const textEditorSelector = ".excalidraw-textEditorContainer > textarea";
+ const editor = await getTextEditor(textEditorSelector, true);
+ updateTextEditor(editor, "Hello!");
+ act(() => {
+ editor.blur();
+ });
+
+ UI.clickTool("rectangle");
+ mouse.down();
+ mouse.up(200, 100);
+
+ const frame = API.createElement({
+ type: "frame",
+ x: 150,
+ width: 150,
+ });
+
+ API.setElements([...h.elements, frame]);
+
+ const text = h.elements.find((el) => el.type === "text");
+ const rectangle = h.elements.find((el) => el.type === "rectangle");
+
+ API.setAppState({
+ selectedElementIds: h.elements.reduce((acc, el) => {
+ acc[el.id] = true;
+ return acc;
+ }, {} as Record<string, true>),
+ });
+
+ elementStats = stats?.querySelector("#elementStats");
+
+ const width = UI.queryStatsProperty("W")?.querySelector(
+ ".drag-input",
+ ) as HTMLInputElement;
+ expect(width).toBeDefined();
+ expect(width.value).toBe("Mixed");
+
+ const height = UI.queryStatsProperty("H")?.querySelector(
+ ".drag-input",
+ ) as HTMLInputElement;
+ expect(height).toBeDefined();
+ expect(height.value).toBe("Mixed");
+
+ const angle = UI.queryStatsProperty("A")?.querySelector(
+ ".drag-input",
+ ) as HTMLInputElement;
+ expect(angle).toBeDefined();
+ expect(angle.value).toBe("0");
+
+ const fontSize = UI.queryStatsProperty("F")?.querySelector(
+ ".drag-input",
+ ) as HTMLInputElement;
+ expect(fontSize).toBeDefined();
+
+ // changing width does not affect text
+ UI.updateInput(width, "200");
+
+ expect(rectangle?.width).toBe(200);
+ expect(frame.width).toBe(200);
+ expect(text?.width).not.toBe(200);
+
+ UI.updateInput(angle, "40");
+
+ const angleInRadian = degreesToRadians(40 as Degrees);
+ expect(rectangle?.angle).toBeCloseTo(angleInRadian, 4);
+ expect(text?.angle).toBeCloseTo(angleInRadian, 4);
+ expect(frame.angle).toBe(0);
+ });
+
+ it("should treat groups as single units", () => {
+ const createAndSelectGroup = () => {
+ UI.clickTool("rectangle");
+ mouse.down();
+ mouse.up(100, 100);
+
+ UI.clickTool("rectangle");
+ mouse.down(0, 0);
+ mouse.up(100, 100);
+
+ mouse.reset();
+ Keyboard.withModifierKeys({ shift: true }, () => {
+ mouse.click();
+ });
+
+ API.executeAction(actionGroup);
+ };
+
+ createAndSelectGroup();
+
+ const elementsInGroup = h.elements.filter((el) => isInGroup(el));
+ let [x1, y1, x2, y2] = getCommonBounds(elementsInGroup);
+
+ elementStats = stats?.querySelector("#elementStats");
+
+ const x = UI.queryStatsProperty("X")?.querySelector(
+ ".drag-input",
+ ) as HTMLInputElement;
+
+ expect(x).toBeDefined();
+ expect(Number(x.value)).toBe(x1);
+
+ UI.updateInput(x, "300");
+
+ expect(h.elements[0].x).toBe(300);
+ expect(h.elements[1].x).toBe(400);
+ expect(x.value).toBe("300");
+
+ const y = UI.queryStatsProperty("Y")?.querySelector(
+ ".drag-input",
+ ) as HTMLInputElement;
+
+ expect(y).toBeDefined();
+ expect(Number(y.value)).toBe(y1);
+
+ UI.updateInput(y, "200");
+
+ expect(h.elements[0].y).toBe(200);
+ expect(h.elements[1].y).toBe(300);
+ expect(y.value).toBe("200");
+
+ const width = UI.queryStatsProperty("W")?.querySelector(
+ ".drag-input",
+ ) as HTMLInputElement;
+ expect(width).toBeDefined();
+ expect(Number(width.value)).toBe(200);
+
+ const height = UI.queryStatsProperty("H")?.querySelector(
+ ".drag-input",
+ ) as HTMLInputElement;
+ expect(height).toBeDefined();
+ expect(Number(height.value)).toBe(200);
+
+ UI.updateInput(width, "400");
+
+ [x1, y1, x2, y2] = getCommonBounds(elementsInGroup);
+ let newGroupWidth = x2 - x1;
+
+ expect(newGroupWidth).toBeCloseTo(400, 4);
+
+ UI.updateInput(width, "300");
+
+ [x1, y1, x2, y2] = getCommonBounds(elementsInGroup);
+ newGroupWidth = x2 - x1;
+ expect(newGroupWidth).toBeCloseTo(300, 4);
+
+ UI.updateInput(height, "500");
+
+ [x1, y1, x2, y2] = getCommonBounds(elementsInGroup);
+ const newGroupHeight = y2 - y1;
+ expect(newGroupHeight).toBeCloseTo(500, 4);
+ });
+});