diff --git a/src/actions/actionFlip.ts b/src/actions/actionFlip.ts index 0dadc23b..8edbbc4a 100644 --- a/src/actions/actionFlip.ts +++ b/src/actions/actionFlip.ts @@ -1,42 +1,17 @@ import { register } from "./register"; import { getSelectedElements } from "../scene"; import { getNonDeletedElements } from "../element"; -import { mutateElement } from "../element/mutateElement"; import { ExcalidrawElement, NonDeleted } from "../element/types"; -import { normalizeAngle, resizeSingleElement } from "../element/resizeElements"; -import { AppState } from "../types"; -import { getTransformHandles } from "../element/transformHandles"; -import { updateBoundElements } from "../element/binding"; +import { resizeMultipleElements } from "../element/resizeElements"; +import { AppState, PointerDownState } from "../types"; import { arrayToMap } from "../utils"; -import { - getElementAbsoluteCoords, - getElementPointsCoords, -} from "../element/bounds"; -import { isLinearElement } from "../element/typeChecks"; -import { LinearElementEditor } from "../element/linearElementEditor"; import { CODES, KEYS } from "../keys"; - -const enableActionFlipHorizontal = ( - elements: readonly ExcalidrawElement[], - appState: AppState, -) => { - const eligibleElements = getSelectedElements( - getNonDeletedElements(elements), - appState, - ); - return eligibleElements.length === 1 && eligibleElements[0].type !== "text"; -}; - -const enableActionFlipVertical = ( - elements: readonly ExcalidrawElement[], - appState: AppState, -) => { - const eligibleElements = getSelectedElements( - getNonDeletedElements(elements), - appState, - ); - return eligibleElements.length === 1; -}; +import { getCommonBoundingBox } from "../element/bounds"; +import { + bindOrUnbindSelectedElements, + isBindingEnabled, + unbindLinearElements, +} from "../element/binding"; export const actionFlipHorizontal = register({ name: "flipHorizontal", @@ -50,8 +25,6 @@ export const actionFlipHorizontal = register({ }, keyTest: (event) => event.shiftKey && event.code === CODES.H, contextItemLabel: "labels.flipHorizontal", - predicate: (elements, appState) => - enableActionFlipHorizontal(elements, appState), }); export const actionFlipVertical = register({ @@ -67,8 +40,6 @@ export const actionFlipVertical = register({ keyTest: (event) => event.shiftKey && event.code === CODES.V && !event[KEYS.CTRL_OR_CMD], contextItemLabel: "labels.flipVertical", - predicate: (elements, appState) => - enableActionFlipVertical(elements, appState), }); const flipSelectedElements = ( @@ -81,11 +52,6 @@ const flipSelectedElements = ( appState, ); - // remove once we allow for groups of elements to be flipped - if (selectedElements.length > 1) { - return elements; - } - const updatedElements = flipElements( selectedElements, appState, @@ -104,144 +70,20 @@ const flipElements = ( appState: AppState, flipDirection: "horizontal" | "vertical", ): ExcalidrawElement[] => { - elements.forEach((element) => { - flipElement(element, appState); - // If vertical flip, rotate an extra 180 - if (flipDirection === "vertical") { - rotateElement(element, Math.PI); - } - }); + const { minX, minY, maxX, maxY } = getCommonBoundingBox(elements); + + resizeMultipleElements( + { originalElements: arrayToMap(elements) } as PointerDownState, + elements, + "nw", + true, + flipDirection === "horizontal" ? maxX : minX, + flipDirection === "horizontal" ? minY : maxY, + ); + + (isBindingEnabled(appState) + ? bindOrUnbindSelectedElements + : unbindLinearElements)(elements); + return elements; }; - -const flipElement = ( - element: NonDeleted, - appState: AppState, -) => { - const originalX = element.x; - const originalY = element.y; - const width = element.width; - const height = element.height; - const originalAngle = normalizeAngle(element.angle); - - // Rotate back to zero, if necessary - mutateElement(element, { - angle: normalizeAngle(0), - }); - // Flip unrotated by pulling TransformHandle to opposite side - const transformHandles = getTransformHandles(element, appState.zoom); - let usingNWHandle = true; - let nHandle = transformHandles.nw; - if (!nHandle) { - // Use ne handle instead - usingNWHandle = false; - nHandle = transformHandles.ne; - if (!nHandle) { - mutateElement(element, { - angle: originalAngle, - }); - return; - } - } - - let finalOffsetX = 0; - if (isLinearElement(element) && element.points.length < 3) { - finalOffsetX = - element.points.reduce((max, point) => Math.max(max, point[0]), 0) * 2 - - element.width; - } - - let initialPointsCoords; - if (isLinearElement(element)) { - initialPointsCoords = getElementPointsCoords(element, element.points); - } - const initialElementAbsoluteCoords = getElementAbsoluteCoords(element); - - if (isLinearElement(element) && element.points.length < 3) { - for (let index = 1; index < element.points.length; index++) { - LinearElementEditor.movePoints(element, [ - { - index, - point: [-element.points[index][0], element.points[index][1]], - }, - ]); - } - LinearElementEditor.normalizePoints(element); - } else { - const elWidth = initialPointsCoords - ? initialPointsCoords[2] - initialPointsCoords[0] - : initialElementAbsoluteCoords[2] - initialElementAbsoluteCoords[0]; - - const startPoint = initialPointsCoords - ? [initialPointsCoords[0], initialPointsCoords[1]] - : [initialElementAbsoluteCoords[0], initialElementAbsoluteCoords[1]]; - - resizeSingleElement( - new Map().set(element.id, element), - false, - element, - usingNWHandle ? "nw" : "ne", - true, - usingNWHandle ? startPoint[0] + elWidth : startPoint[0] - elWidth, - startPoint[1], - ); - } - - // Rotate by (360 degrees - original angle) - let angle = normalizeAngle(2 * Math.PI - originalAngle); - if (angle < 0) { - // check, probably unnecessary - angle = normalizeAngle(angle + 2 * Math.PI); - } - mutateElement(element, { - angle, - }); - - // Move back to original spot to appear "flipped in place" - mutateElement(element, { - x: originalX + finalOffsetX, - y: originalY, - width, - height, - }); - - updateBoundElements(element); - - if (initialPointsCoords && isLinearElement(element)) { - // Adjusting origin because when a beizer curve path exceeds min/max points it offsets the origin. - // There's still room for improvement since when the line roughness is > 1 - // we still have a small offset of the origin when fliipping the element. - const finalPointsCoords = getElementPointsCoords(element, element.points); - - const topLeftCoordsDiff = initialPointsCoords[0] - finalPointsCoords[0]; - const topRightCoordDiff = initialPointsCoords[2] - finalPointsCoords[2]; - - const coordsDiff = topLeftCoordsDiff + topRightCoordDiff; - - mutateElement(element, { - x: element.x + coordsDiff * 0.5, - y: element.y, - width, - height, - }); - } -}; - -const rotateElement = (element: ExcalidrawElement, rotationAngle: number) => { - const originalX = element.x; - const originalY = element.y; - let angle = normalizeAngle(element.angle + rotationAngle); - if (angle < 0) { - // check, probably unnecessary - angle = normalizeAngle(2 * Math.PI + angle); - } - mutateElement(element, { - angle, - }); - - // Move back to original spot - mutateElement(element, { - x: originalX, - y: originalY, - }); -}; diff --git a/src/element/resizeElements.ts b/src/element/resizeElements.ts index 67a6346b..3610d757 100644 --- a/src/element/resizeElements.ts +++ b/src/element/resizeElements.ts @@ -14,17 +14,21 @@ import { NonDeleted, ExcalidrawElement, ExcalidrawTextElementWithContainer, + ExcalidrawImageElement, } from "./types"; +import type { Mutable } from "../utility-types"; import { getElementAbsoluteCoords, getCommonBounds, getResizedElementAbsoluteCoords, getCommonBoundingBox, + getElementPointsCoords, } from "./bounds"; import { isArrowElement, isBoundToContainer, isFreeDrawElement, + isImageElement, isLinearElement, isTextElement, } from "./typeChecks"; @@ -49,8 +53,12 @@ import { measureText, getBoundTextMaxHeight, } from "./textElement"; +import { LinearElementEditor } from "./linearElementEditor"; export const normalizeAngle = (angle: number): number => { + if (angle < 0) { + return angle + 2 * Math.PI; + } if (angle >= 2 * Math.PI) { return angle - 2 * Math.PI; } @@ -596,7 +604,7 @@ export const resizeSingleElement = ( } }; -const resizeMultipleElements = ( +export const resizeMultipleElements = ( pointerDownState: PointerDownState, selectedElements: readonly NonDeletedExcalidrawElement[], transformHandleType: "nw" | "ne" | "sw" | "se", @@ -627,8 +635,28 @@ const resizeMultipleElements = ( [], ); + // getCommonBoundingBox() uses getBoundTextElement() which returns null for + // original elements from pointerDownState, so we have to find and add these + // bound text elements manually. Additionally, the coordinates of bound text + // elements aren't always up to date. + const boundTextElements = targetElements.reduce((acc, { orig }) => { + if (!isLinearElement(orig)) { + return acc; + } + const textId = getBoundTextElementId(orig); + if (!textId) { + return acc; + } + const text = pointerDownState.originalElements.get(textId) ?? null; + if (!isBoundToContainer(text)) { + return acc; + } + const xy = LinearElementEditor.getBoundTextElementPosition(orig, text); + return [...acc, { ...text, ...xy }]; + }, [] as ExcalidrawTextElementWithContainer[]); + const { minX, minY, maxX, maxY, midX, midY } = getCommonBoundingBox( - targetElements.map(({ orig }) => orig), + targetElements.map(({ orig }) => orig).concat(boundTextElements), ); const direction = transformHandleType; @@ -640,12 +668,22 @@ const resizeMultipleElements = ( }; // anchor point must be on the opposite side of the dragged selection handle - // or be the center of the selection if alt is pressed + // or be the center of the selection if shouldResizeFromCenter const [anchorX, anchorY]: Point = shouldResizeFromCenter ? [midX, midY] : mapDirectionsToAnchors[direction]; - const mapDirectionsToPointerSides: Record< + const scale = + Math.max( + Math.abs(pointerX - anchorX) / (maxX - minX) || 0, + Math.abs(pointerY - anchorY) / (maxY - minY) || 0, + ) * (shouldResizeFromCenter ? 2 : 1); + + if (scale === 0) { + return; + } + + const mapDirectionsToPointerPositions: Record< typeof direction, [x: boolean, y: boolean] > = { @@ -655,68 +693,117 @@ const resizeMultipleElements = ( nw: [pointerX <= anchorX, pointerY <= anchorY], }; - // pointer side relative to anchor - const [pointerSideX, pointerSideY] = mapDirectionsToPointerSides[ + /** + * to flip an element: + * 1. determine over which axis is the element being flipped + * (could be x, y, or both) indicated by `flipFactorX` & `flipFactorY` + * 2. shift element's position by the amount of width or height (or both) or + * mirror points in the case of linear & freedraw elemenets + * 3. adjust element angle + */ + const [flipFactorX, flipFactorY] = mapDirectionsToPointerPositions[ direction ].map((condition) => (condition ? 1 : -1)); + const isFlippedByX = flipFactorX < 0; + const isFlippedByY = flipFactorY < 0; - // stop resizing if a pointer is on the other side of selection - if (pointerSideX < 0 && pointerSideY < 0) { - return; - } + const elementsAndUpdates: { + element: NonDeletedExcalidrawElement; + update: Mutable< + Pick + > & { + points?: ExcalidrawLinearElement["points"]; + fontSize?: ExcalidrawTextElement["fontSize"]; + baseline?: ExcalidrawTextElement["baseline"]; + scale?: ExcalidrawImageElement["scale"]; + }; + boundText: { + element: ExcalidrawTextElementWithContainer; + fontSize: ExcalidrawTextElement["fontSize"]; + baseline: ExcalidrawTextElement["baseline"]; + } | null; + }[] = []; - const scale = - Math.max( - (pointerSideX * Math.abs(pointerX - anchorX)) / (maxX - minX), - (pointerSideY * Math.abs(pointerY - anchorY)) / (maxY - minY), - ) * (shouldResizeFromCenter ? 2 : 1); + for (const { orig, latest } of targetElements) { + // bounded text elements are updated along with their container elements + if (isTextElement(orig) && isBoundToContainer(orig)) { + continue; + } - if (scale === 0) { - return; - } + const width = orig.width * scale; + const height = orig.height * scale; + const angle = normalizeAngle(orig.angle * flipFactorX * flipFactorY); - targetElements.forEach((element) => { - const width = element.orig.width * scale; - const height = element.orig.height * scale; - const x = anchorX + (element.orig.x - anchorX) * scale; - const y = anchorY + (element.orig.y - anchorY) * scale; + const isLinearOrFreeDraw = isLinearElement(orig) || isFreeDrawElement(orig); + const offsetX = orig.x - anchorX; + const offsetY = orig.y - anchorY; + const shiftX = isFlippedByX && !isLinearOrFreeDraw ? width : 0; + const shiftY = isFlippedByY && !isLinearOrFreeDraw ? height : 0; + const x = anchorX + flipFactorX * (offsetX * scale + shiftX); + const y = anchorY + flipFactorY * (offsetY * scale + shiftY); - // readjust points for linear & free draw elements const rescaledPoints = rescalePointsInElement( - element.orig, - width, - height, + orig, + width * flipFactorX, + height * flipFactorY, false, ); - const update: { - width: number; - height: number; - x: number; - y: number; - points?: Point[]; - fontSize?: number; - baseline?: number; - } = { - width, - height, + const update: typeof elementsAndUpdates[0]["update"] = { x, y, + width, + height, + angle, ...rescaledPoints, }; - let boundTextUpdates: { fontSize: number; baseline: number } | null = null; + if (isImageElement(orig) && targetElements.length === 1) { + update.scale = [orig.scale[0] * flipFactorX, orig.scale[1] * flipFactorY]; + } - const boundTextElement = getBoundTextElement(element.latest); + if (isLinearElement(orig) && (isFlippedByX || isFlippedByY)) { + const origBounds = getElementPointsCoords(orig, orig.points); + const newBounds = getElementPointsCoords( + { ...orig, x, y }, + rescaledPoints.points!, + ); + const origXY = [orig.x, orig.y]; + const newXY = [x, y]; - if (boundTextElement || isTextElement(element.orig)) { + const linearShift = (axis: "x" | "y") => { + const i = axis === "x" ? 0 : 1; + return ( + (newBounds[i + 2] - + newXY[i] - + (origXY[i] - origBounds[i]) * scale + + (origBounds[i + 2] - origXY[i]) * scale - + (newXY[i] - newBounds[i])) / + 2 + ); + }; + + if (isFlippedByX) { + update.x -= linearShift("x"); + } + + if (isFlippedByY) { + update.y -= linearShift("y"); + } + } + + let boundText: typeof elementsAndUpdates[0]["boundText"] = null; + + const boundTextElement = getBoundTextElement(latest); + + if (boundTextElement || isTextElement(orig)) { const updatedElement = { - ...element.latest, + ...latest, width, height, }; const metrics = measureFontSizeFromWidth( - boundTextElement ?? (element.orig as ExcalidrawTextElement), + boundTextElement ?? (orig as ExcalidrawTextElement), boundTextElement ? getBoundTextMaxWidth(updatedElement) : updatedElement.width, @@ -729,29 +816,50 @@ const resizeMultipleElements = ( return; } - if (isTextElement(element.orig)) { + if (isTextElement(orig)) { update.fontSize = metrics.size; update.baseline = metrics.baseline; } if (boundTextElement) { - boundTextUpdates = { + boundText = { + element: boundTextElement, fontSize: metrics.size, baseline: metrics.baseline, }; } } - updateBoundElements(element.latest, { newSize: { width, height } }); + elementsAndUpdates.push({ element: latest, update, boundText }); + } - mutateElement(element.latest, update); + const elementsToUpdate = elementsAndUpdates.map(({ element }) => element); - if (boundTextElement && boundTextUpdates) { - mutateElement(boundTextElement, boundTextUpdates); + for (const { element, update, boundText } of elementsAndUpdates) { + const { width, height, angle } = update; - handleBindTextResize(element.latest, transformHandleType); + mutateElement(element, update, false); + + updateBoundElements(element, { + simultaneouslyUpdated: elementsToUpdate, + newSize: { width, height }, + }); + + if (boundText) { + const { element: boundTextElement, ...boundTextUpdates } = boundText; + mutateElement( + boundTextElement, + { + ...boundTextUpdates, + angle: isLinearElement(element) ? undefined : angle, + }, + false, + ); + handleBindTextResize(element, transformHandleType); } - }); + } + + Scene.getScene(elementsAndUpdates[0].element)?.informMutation(); }; const rotateMultipleElements = ( diff --git a/src/tests/__snapshots__/contextmenu.test.tsx.snap b/src/tests/__snapshots__/contextmenu.test.tsx.snap index 5e3a5b22..6103c2f5 100644 --- a/src/tests/__snapshots__/contextmenu.test.tsx.snap +++ b/src/tests/__snapshots__/contextmenu.test.tsx.snap @@ -197,7 +197,6 @@ Object { "keyTest": [Function], "name": "flipHorizontal", "perform": [Function], - "predicate": [Function], "trackEvent": Object { "category": "element", }, @@ -207,7 +206,6 @@ Object { "keyTest": [Function], "name": "flipVertical", "perform": [Function], - "predicate": [Function], "trackEvent": Object { "category": "element", }, @@ -4594,7 +4592,6 @@ Object { "keyTest": [Function], "name": "flipHorizontal", "perform": [Function], - "predicate": [Function], "trackEvent": Object { "category": "element", }, @@ -4604,7 +4601,6 @@ Object { "keyTest": [Function], "name": "flipVertical", "perform": [Function], - "predicate": [Function], "trackEvent": Object { "category": "element", }, @@ -5144,7 +5140,6 @@ Object { "keyTest": [Function], "name": "flipHorizontal", "perform": [Function], - "predicate": [Function], "trackEvent": Object { "category": "element", }, @@ -5154,7 +5149,6 @@ Object { "keyTest": [Function], "name": "flipVertical", "perform": [Function], - "predicate": [Function], "trackEvent": Object { "category": "element", }, @@ -6003,7 +5997,6 @@ Object { "keyTest": [Function], "name": "flipHorizontal", "perform": [Function], - "predicate": [Function], "trackEvent": Object { "category": "element", }, @@ -6013,7 +6006,6 @@ Object { "keyTest": [Function], "name": "flipVertical", "perform": [Function], - "predicate": [Function], "trackEvent": Object { "category": "element", }, @@ -6349,7 +6341,6 @@ Object { "keyTest": [Function], "name": "flipHorizontal", "perform": [Function], - "predicate": [Function], "trackEvent": Object { "category": "element", }, @@ -6359,7 +6350,6 @@ Object { "keyTest": [Function], "name": "flipVertical", "perform": [Function], - "predicate": [Function], "trackEvent": Object { "category": "element", }, diff --git a/src/tests/__snapshots__/regressionTests.test.tsx.snap b/src/tests/__snapshots__/regressionTests.test.tsx.snap index 68d87ef3..931a2db5 100644 --- a/src/tests/__snapshots__/regressionTests.test.tsx.snap +++ b/src/tests/__snapshots__/regressionTests.test.tsx.snap @@ -15332,7 +15332,10 @@ Object { "penMode": false, "pendingImageElementId": null, "previousSelectedElementIds": Object { + "id0": true, + "id1": true, "id2": true, + "id3": true, }, "resizingElement": null, "scrollX": 0, @@ -15342,7 +15345,6 @@ Object { "id0": true, "id1": true, "id2": true, - "id3": true, "id5": true, }, "selectedGroupIds": Object {}, @@ -15390,7 +15392,7 @@ Object { "type": "rectangle", "updated": 1, "version": 4, - "versionNonce": 1505387817, + "versionNonce": 23633383, "width": 10, "x": 10, "y": 10, @@ -15421,7 +15423,7 @@ Object { "type": "rectangle", "updated": 1, "version": 4, - "versionNonce": 23633383, + "versionNonce": 493213705, "width": 10, "x": 30, "y": 10, @@ -15452,7 +15454,7 @@ Object { "type": "rectangle", "updated": 1, "version": 4, - "versionNonce": 493213705, + "versionNonce": 915032327, "width": 10, "x": 50, "y": 10, @@ -15803,7 +15805,6 @@ Object { "id0": true, "id1": true, "id2": true, - "id3": true, "id5": true, }, "selectedGroupIds": Object {}, @@ -15833,7 +15834,7 @@ Object { "type": "rectangle", "updated": 1, "version": 4, - "versionNonce": 1505387817, + "versionNonce": 23633383, "width": 10, "x": 10, "y": 10, @@ -15861,7 +15862,7 @@ Object { "type": "rectangle", "updated": 1, "version": 4, - "versionNonce": 23633383, + "versionNonce": 493213705, "width": 10, "x": 30, "y": 10, @@ -15889,7 +15890,7 @@ Object { "type": "rectangle", "updated": 1, "version": 4, - "versionNonce": 493213705, + "versionNonce": 915032327, "width": 10, "x": 50, "y": 10, diff --git a/src/tests/contextmenu.test.tsx b/src/tests/contextmenu.test.tsx index 9b4007b0..9e89996a 100644 --- a/src/tests/contextmenu.test.tsx +++ b/src/tests/contextmenu.test.tsx @@ -207,6 +207,8 @@ describe("contextMenu element", () => { "deleteSelectedElements", "group", "addToLibrary", + "flipHorizontal", + "flipVertical", "sendBackward", "bringForward", "sendToBack", @@ -258,6 +260,8 @@ describe("contextMenu element", () => { "deleteSelectedElements", "ungroup", "addToLibrary", + "flipHorizontal", + "flipVertical", "sendBackward", "bringForward", "sendToBack", diff --git a/src/tests/flip.test.tsx b/src/tests/flip.test.tsx index c1469bc8..091d1c73 100644 --- a/src/tests/flip.test.tsx +++ b/src/tests/flip.test.tsx @@ -195,10 +195,8 @@ const checkElementsBoundingBox = async ( debugger; await waitFor(() => { // Check if width and height did not change - expect(x1 - toleranceInPx <= x12 && x12 <= x1 + toleranceInPx).toBeTruthy(); - expect(y1 - toleranceInPx <= y12 && y12 <= y1 + toleranceInPx).toBeTruthy(); - expect(x2 - toleranceInPx <= x22 && x22 <= x2 + toleranceInPx).toBeTruthy(); - expect(y2 - toleranceInPx <= y22 && y22 <= y2 + toleranceInPx).toBeTruthy(); + expect(x2 - x1).toBeCloseTo(x22 - x12, -1); + expect(y2 - y1).toBeCloseTo(y22 - y12, -1); }); }; @@ -216,14 +214,22 @@ const checkTwoPointsLineHorizontalFlip = async () => { h.app.actionManager.executeAction(actionFlipHorizontal); const newElement = h.elements[0] as ExcalidrawLinearElement; await waitFor(() => { - expect(originalElement.points[0][0]).toEqual( - newElement.points[0][0] !== 0 ? -newElement.points[0][0] : 0, + expect(originalElement.points[0][0]).toBeCloseTo( + -newElement.points[0][0], + 5, ); - expect(originalElement.points[0][1]).toEqual(newElement.points[0][1]); - expect(originalElement.points[1][0]).toEqual( - newElement.points[1][0] !== 0 ? -newElement.points[1][0] : 0, + expect(originalElement.points[0][1]).toBeCloseTo( + newElement.points[0][1], + 5, + ); + expect(originalElement.points[1][0]).toBeCloseTo( + -newElement.points[1][0], + 5, + ); + expect(originalElement.points[1][1]).toBeCloseTo( + newElement.points[1][1], + 5, ); - expect(originalElement.points[1][1]).toEqual(newElement.points[1][1]); }); }; @@ -234,14 +240,22 @@ const checkTwoPointsLineVerticalFlip = async () => { h.app.actionManager.executeAction(actionFlipVertical); const newElement = h.elements[0] as ExcalidrawLinearElement; await waitFor(() => { - expect(originalElement.points[0][0]).toEqual( - newElement.points[0][0] !== 0 ? -newElement.points[0][0] : 0, + expect(originalElement.points[0][0]).toBeCloseTo( + newElement.points[0][0], + 5, ); - expect(originalElement.points[0][1]).toEqual(newElement.points[0][1]); - expect(originalElement.points[1][0]).toEqual( - newElement.points[1][0] !== 0 ? -newElement.points[1][0] : 0, + expect(originalElement.points[0][1]).toBeCloseTo( + -newElement.points[0][1], + 5, + ); + expect(originalElement.points[1][0]).toBeCloseTo( + newElement.points[1][0], + 5, + ); + expect(originalElement.points[1][1]).toBeCloseTo( + -newElement.points[1][1], + 5, ); - expect(originalElement.points[1][1]).toEqual(newElement.points[1][1]); }); }; @@ -318,7 +332,7 @@ describe("rectangle", () => { it("flips a rotated rectangle vertically correctly", async () => { const originalAngle = (3 * Math.PI) / 4; - const expectedAgnle = Math.PI / 4; + const expectedAgnle = (5 * Math.PI) / 4; createAndSelectOneRectangle(originalAngle); @@ -351,7 +365,7 @@ describe("diamond", () => { it("flips a rotated diamond vertically correctly", async () => { const originalAngle = (5 * Math.PI) / 4; - const expectedAngle = (7 * Math.PI) / 4; + const expectedAngle = (3 * Math.PI) / 4; createAndSelectOneDiamond(originalAngle); @@ -384,7 +398,7 @@ describe("ellipse", () => { it("flips a rotated ellipse vertically correctly", async () => { const originalAngle = (7 * Math.PI) / 4; - const expectedAngle = (5 * Math.PI) / 4; + const expectedAngle = Math.PI / 4; createAndSelectOneEllipse(originalAngle); @@ -429,7 +443,7 @@ describe("arrow", () => { it("flips a rotated arrow vertically with line inside min/max points bounds", async () => { const originalAngle = Math.PI / 4; - const expectedAngle = (3 * Math.PI) / 4; + const expectedAngle = (7 * Math.PI) / 4; const line = createLinearElementWithCurveInsideMinMaxPoints("arrow"); h.app.scene.replaceAllElements([line]); h.app.state.selectedElementIds[line.id] = true; @@ -481,7 +495,7 @@ describe("arrow", () => { //TODO: elements with curve outside minMax points have a wrong bounding box!!! it.skip("flips a rotated arrow vertically with line outside min/max points bounds", async () => { const originalAngle = Math.PI / 4; - const expectedAngle = (3 * Math.PI) / 4; + const expectedAngle = (7 * Math.PI) / 4; const line = createLinearElementsWithCurveOutsideMinMaxPoints("arrow"); mutateElement(line, { angle: originalAngle }); h.app.scene.replaceAllElements([line]); @@ -512,7 +526,6 @@ describe("arrow", () => { it("flips a two points arrow vertically correctly", async () => { createAndSelectOneArrow(); - await checkTwoPointsLineVerticalFlip(); }); }); @@ -581,7 +594,7 @@ describe("line", () => { //TODO: elements with curve outside minMax points have a wrong bounding box it.skip("flips a rotated line vertically with line outside min/max points bounds", async () => { const originalAngle = Math.PI / 4; - const expectedAngle = (3 * Math.PI) / 4; + const expectedAngle = (7 * Math.PI) / 4; const line = createLinearElementsWithCurveOutsideMinMaxPoints("line"); mutateElement(line, { angle: originalAngle }); h.app.scene.replaceAllElements([line]); @@ -616,7 +629,7 @@ describe("line", () => { it("flips a rotated line vertically with line inside min/max points bounds", async () => { const originalAngle = Math.PI / 4; - const expectedAngle = (3 * Math.PI) / 4; + const expectedAngle = (7 * Math.PI) / 4; const line = createLinearElementWithCurveInsideMinMaxPoints("line"); h.app.scene.replaceAllElements([line]); h.app.state.selectedElementIds[line.id] = true; @@ -670,7 +683,7 @@ describe("freedraw", () => { it("flips a rotated drawing vertically correctly", async () => { const originalAngle = Math.PI / 4; - const expectedAngle = (3 * Math.PI) / 4; + const expectedAngle = (7 * Math.PI) / 4; const draw = createAndReturnOneDraw(originalAngle); // select draw, since not done automatically @@ -718,8 +731,8 @@ describe("image", () => { }); await checkVerticalFlip(); - expect((h.elements[0] as ExcalidrawImageElement).scale).toEqual([-1, 1]); - expect(h.elements[0].angle).toBeCloseTo(Math.PI); + expect((h.elements[0] as ExcalidrawImageElement).scale).toEqual([1, -1]); + expect(h.elements[0].angle).toBeCloseTo(0); }); it("flips an rotated image horizontally correctly", async () => { @@ -742,7 +755,7 @@ describe("image", () => { it("flips an rotated image vertically correctly", async () => { const originalAngle = Math.PI / 4; - const expectedAngle = (3 * Math.PI) / 4; + const expectedAngle = (7 * Math.PI) / 4; //paste image await createImage(); await waitFor(() => { @@ -757,7 +770,7 @@ describe("image", () => { }); await checkRotatedVerticalFlip(expectedAngle); - expect((h.elements[0] as ExcalidrawImageElement).scale).toEqual([-1, 1]); + expect((h.elements[0] as ExcalidrawImageElement).scale).toEqual([1, -1]); expect(h.elements[0].angle).toBeCloseTo(expectedAngle); }); @@ -772,7 +785,7 @@ describe("image", () => { }); await checkVerticalHorizontalFlip(); - expect((h.elements[0] as ExcalidrawImageElement).scale).toEqual([1, 1]); - expect(h.elements[0].angle).toBeCloseTo(Math.PI); + expect((h.elements[0] as ExcalidrawImageElement).scale).toEqual([-1, -1]); + expect(h.elements[0].angle).toBeCloseTo(0); }); }); diff --git a/src/tests/regressionTests.test.tsx b/src/tests/regressionTests.test.tsx index 9ecbfb42..2b909488 100644 --- a/src/tests/regressionTests.test.tsx +++ b/src/tests/regressionTests.test.tsx @@ -542,7 +542,7 @@ describe("regression tests", () => { expect(element.groupIds.length).toBe(1); } - mouse.reset(); + mouse.moveTo(-10, -10); // the NW resizing handle is at [0, 0], so moving further mouse.down(); mouse.restorePosition(...end); mouse.up();