From ef82e15ee8ade1c9fca1fc28c6fa6eb2ce63f3ff Mon Sep 17 00:00:00 2001 From: dwelle Date: Sat, 12 Dec 2020 21:24:52 +0100 Subject: [PATCH] update appState on copy-styles & improve paste --- src/actions/actionStyles.ts | 156 +- src/element/types.ts | 13 + src/global.d.ts | 9 + .../regressionTests.test.tsx.snap | 1634 +++++++---------- src/tests/helpers/api.ts | 10 +- src/tests/regressionTests.test.tsx | 256 ++- 6 files changed, 1000 insertions(+), 1078 deletions(-) diff --git a/src/actions/actionStyles.ts b/src/actions/actionStyles.ts index 991f62b95..7e74674b7 100644 --- a/src/actions/actionStyles.ts +++ b/src/actions/actionStyles.ts @@ -1,28 +1,110 @@ import { isTextElement, - isExcalidrawElement, redrawTextBoundingBox, + getNonDeletedElements, } from "../element"; import { CODES, KEYS } from "../keys"; import { register } from "./register"; -import { mutateElement, newElementWith } from "../element/mutateElement"; +import { newElementWith } from "../element/mutateElement"; import { - DEFAULT_FONT_SIZE, - DEFAULT_FONT_FAMILY, - DEFAULT_TEXT_ALIGN, -} from "../constants"; + ExcalidrawElement, + ExcalidrawElementPossibleProps, +} from "../element/types"; +import { AppState } from "../types"; +import { + canChangeSharpness, + getSelectedElements, + hasBackground, + hasStroke, + hasText, +} from "../scene"; +import { isLinearElement, isLinearElementType } from "../element/typeChecks"; + +type AppStateStyles = { + [K in AssertSubset< + keyof AppState, + typeof copyableStyles[number][0] + >]: AppState[K]; +}; + +type ElementStyles = { + [K in AssertSubset< + keyof ExcalidrawElementPossibleProps, + typeof copyableStyles[number][1] + >]: ExcalidrawElementPossibleProps[K]; +}; + +type ElemelementStylesByType = Record; // `copiedStyles` is exported only for tests. -export let copiedStyles: string = "{}"; +let COPIED_STYLES: { + appStateStyles: Partial; + elementStyles: Partial; + elementStylesByType: Partial; +} | null = null; + +/* [AppState prop, ExcalidrawElement prop, predicate] */ +const copyableStyles = [ + ["currentItemOpacity", "opacity", () => true], + ["currentItemStrokeColor", "strokeColor", () => true], + ["currentItemStrokeStyle", "strokeStyle", hasStroke], + ["currentItemStrokeWidth", "strokeWidth", hasStroke], + ["currentItemRoughness", "roughness", hasStroke], + ["currentItemBackgroundColor", "backgroundColor", hasBackground], + ["currentItemFillStyle", "fillStyle", hasBackground], + ["currentItemStrokeSharpness", "strokeSharpness", canChangeSharpness], + ["currentItemLinearStrokeSharpness", "strokeSharpness", isLinearElementType], + ["currentItemStartArrowhead", "startArrowhead", isLinearElementType], + ["currentItemEndArrowhead", "endArrowhead", isLinearElementType], + ["currentItemFontFamily", "fontFamily", hasText], + ["currentItemFontSize", "fontSize", hasText], + ["currentItemTextAlign", "textAlign", hasText], +] as const; + +const getCommonStyleProps = ( + elements: readonly ExcalidrawElement[], +): Exclude => { + const appStateStyles = {} as AppStateStyles; + const elementStyles = {} as ElementStyles; + + const elementStylesByType = elements.reduce((acc, element) => { + // only use the first element of given type + if (!acc[element.type]) { + acc[element.type] = {} as ElementStyles; + copyableStyles.forEach(([appStateProp, prop, predicate]) => { + const value = (element as any)[prop]; + if (value !== undefined && predicate(element.type)) { + if (appStateStyles[appStateProp] === undefined) { + (appStateStyles as any)[appStateProp] = value; + } + if (elementStyles[prop] === undefined) { + (elementStyles as any)[prop] = value; + } + (acc as any)[element.type][prop] = value; + } + }); + } + return acc; + }, {} as ElemelementStylesByType); + + // clone in case we ever make some of the props into non-primitives + return JSON.parse( + JSON.stringify({ appStateStyles, elementStyles, elementStylesByType }), + ); +}; export const actionCopyStyles = register({ name: "copyStyles", perform: (elements, appState) => { - const element = elements.find((el) => appState.selectedElementIds[el.id]); - if (element) { - copiedStyles = JSON.stringify(element); - } + COPIED_STYLES = getCommonStyleProps( + getSelectedElements(getNonDeletedElements(elements), appState), + ); + return { + appState: { + ...appState, + ...COPIED_STYLES.appStateStyles, + }, commitToHistory: false, }; }, @@ -35,31 +117,49 @@ export const actionCopyStyles = register({ export const actionPasteStyles = register({ name: "pasteStyles", perform: (elements, appState) => { - const pastedElement = JSON.parse(copiedStyles); - if (!isExcalidrawElement(pastedElement)) { + if (!COPIED_STYLES) { return { elements, commitToHistory: false }; } + const getStyle = ( + element: T, + prop: K, + ) => { + return (COPIED_STYLES?.elementStylesByType[element.type]?.[ + prop as keyof ElementStyles + ] ?? + COPIED_STYLES?.elementStyles[prop as keyof ElementStyles] ?? + element[prop]) as T[K]; + }; return { elements: elements.map((element) => { if (appState.selectedElementIds[element.id]) { - const newElement = newElementWith(element, { - backgroundColor: pastedElement?.backgroundColor, - strokeWidth: pastedElement?.strokeWidth, - strokeColor: pastedElement?.strokeColor, - strokeStyle: pastedElement?.strokeStyle, - fillStyle: pastedElement?.fillStyle, - opacity: pastedElement?.opacity, - roughness: pastedElement?.roughness, - }); - if (isTextElement(newElement)) { - mutateElement(newElement, { - fontSize: pastedElement?.fontSize || DEFAULT_FONT_SIZE, - fontFamily: pastedElement?.fontFamily || DEFAULT_FONT_FAMILY, - textAlign: pastedElement?.textAlign || DEFAULT_TEXT_ALIGN, + const commonProps = { + backgroundColor: getStyle(element, "backgroundColor"), + strokeWidth: getStyle(element, "strokeWidth"), + strokeColor: getStyle(element, "strokeColor"), + strokeStyle: getStyle(element, "strokeStyle"), + fillStyle: getStyle(element, "fillStyle"), + opacity: getStyle(element, "opacity"), + roughness: getStyle(element, "roughness"), + strokeSharpness: getStyle(element, "strokeSharpness"), + }; + if (isTextElement(element)) { + const newElement = newElementWith(element, { + ...commonProps, + fontSize: getStyle(element, "fontSize"), + fontFamily: getStyle(element, "fontFamily"), + textAlign: getStyle(element, "textAlign"), }); redrawTextBoundingBox(newElement); + return newElement; + } else if (isLinearElement(element)) { + return newElementWith(element, { + ...commonProps, + startArrowhead: getStyle(element, "startArrowhead"), + endArrowhead: getStyle(element, "endArrowhead"), + }); } - return newElement; + return newElementWith(element, commonProps); } return element; }), diff --git a/src/element/types.ts b/src/element/types.ts index c718504cf..b02f95ff9 100644 --- a/src/element/types.ts +++ b/src/element/types.ts @@ -110,3 +110,16 @@ export type ExcalidrawLinearElement = _ExcalidrawElementBase & startArrowhead: Arrowhead | null; endArrowhead: Arrowhead | null; }>; + +export type ExcalidrawElementTypes = Pick["type"]; + +/** @private */ +type __ExcalidrawElementPossibleProps_withoutType = T extends any + ? { [K in keyof Omit]: T[K] } + : never; + +/** Do not use for anything unless you really need it for some abstract + API types */ +export type ExcalidrawElementPossibleProps = UnionToIntersection< + __ExcalidrawElementPossibleProps_withoutType +> & { type: ExcalidrawElementTypes }; diff --git a/src/global.d.ts b/src/global.d.ts index 8bce07a71..b3a754c3b 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -46,6 +46,15 @@ type MarkOptional = Omit & Partial>; type MarkRequired = Exclude & Required>; +type UnionToIntersection = (T extends any ? (x: T) => any : never) extends ( + x: infer R, +) => any + ? R + : never; + +/** Assert K is a subset of T, and returns K */ +type AssertSubset = K; + // PNG encoding/decoding // ----------------------------------------------------------------------------- type TEXtChunk = { name: "tEXt"; data: Uint8Array }; diff --git a/src/tests/__snapshots__/regressionTests.test.tsx.snap b/src/tests/__snapshots__/regressionTests.test.tsx.snap index 8ffa26a57..ec8e427ce 100644 --- a/src/tests/__snapshots__/regressionTests.test.tsx.snap +++ b/src/tests/__snapshots__/regressionTests.test.tsx.snap @@ -4551,6 +4551,194 @@ exports[`regression tests click-drag to select a group: [end of test] number of exports[`regression tests click-drag to select a group: [end of test] number of renders 1`] = `17`; +exports[`regression tests copy-styles updates appState defaults: [end of test] appState 1`] = ` +Object { + "appearance": "light", + "collaborators": Map {}, + "currentItemBackgroundColor": "#00FF00", + "currentItemEndArrowhead": "bar", + "currentItemFillStyle": "solid", + "currentItemFontFamily": 3, + "currentItemFontSize": 200, + "currentItemLinearStrokeSharpness": "sharp", + "currentItemOpacity": 90, + "currentItemRoughness": 2, + "currentItemStartArrowhead": "bar", + "currentItemStrokeColor": "#FF0000", + "currentItemStrokeSharpness": "sharp", + "currentItemStrokeStyle": "solid", + "currentItemStrokeWidth": 10, + "currentItemTextAlign": "center", + "cursorButton": "up", + "cursorX": 0, + "cursorY": 0, + "draggingElement": null, + "editingElement": null, + "editingGroupId": null, + "editingLinearElement": null, + "elementLocked": false, + "elementType": "selection", + "errorMessage": null, + "exportBackground": true, + "exportEmbedScene": false, + "fileHandle": null, + "gridSize": null, + "height": 768, + "isBindingEnabled": true, + "isLibraryOpen": false, + "isLoading": false, + "isResizing": false, + "isRotating": false, + "lastPointerDownWith": "mouse", + "multiElement": null, + "name": "Untitled-201933152653", + "offsetLeft": 0, + "offsetTop": 0, + "openMenu": null, + "previousSelectedElementIds": Object {}, + "resizingElement": null, + "scrollX": 0, + "scrollY": 0, + "scrolledOutside": false, + "selectedElementIds": Object { + "A": true, + "B": true, + "C": true, + }, + "selectedGroupIds": Object {}, + "selectionElement": null, + "shouldAddWatermark": false, + "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, + "showStats": false, + "startBoundElement": null, + "suggestedBindings": Array [], + "viewBackgroundColor": "#ffffff", + "width": 1024, + "zenModeEnabled": false, + "zoom": Object { + "translation": Object { + "x": 0, + "y": 0, + }, + "value": 1, + }, +} +`; + +exports[`regression tests copy-styles updates appState defaults: [end of test] element 0 1`] = ` +Object { + "angle": 0, + "backgroundColor": "#00FF00", + "boundElementIds": null, + "fillStyle": "solid", + "groupIds": Array [], + "height": 100, + "id": "A", + "isDeleted": false, + "opacity": 90, + "roughness": 2, + "seed": 337897, + "strokeColor": "#FF0000", + "strokeSharpness": "sharp", + "strokeStyle": "solid", + "strokeWidth": 10, + "type": "rectangle", + "version": 1, + "versionNonce": 0, + "width": 100, + "x": 0, + "y": 0, +} +`; + +exports[`regression tests copy-styles updates appState defaults: [end of test] element 1 1`] = ` +Object { + "angle": 0, + "backgroundColor": "transparent", + "boundElementIds": null, + "endArrowhead": "bar", + "endBinding": null, + "fillStyle": "hachure", + "groupIds": Array [], + "height": 0, + "id": "B", + "isDeleted": false, + "lastCommittedPoint": null, + "opacity": 100, + "points": Array [], + "roughness": 1, + "seed": 1278240551, + "startArrowhead": "bar", + "startBinding": null, + "strokeColor": "#000000", + "strokeSharpness": "sharp", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "arrow", + "version": 1, + "versionNonce": 0, + "width": 0, + "x": 200, + "y": 200, +} +`; + +exports[`regression tests copy-styles updates appState defaults: [end of test] element 2 1`] = ` +Object { + "angle": 0, + "backgroundColor": "transparent", + "baseline": 0, + "boundElementIds": null, + "fillStyle": "hachure", + "fontFamily": 3, + "fontSize": 200, + "groupIds": Array [], + "height": 0, + "id": "C", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 449462985, + "strokeColor": "#000000", + "strokeSharpness": "sharp", + "strokeStyle": "solid", + "strokeWidth": 1, + "text": "test", + "textAlign": "center", + "type": "text", + "version": 2, + "versionNonce": 453191, + "verticalAlign": "top", + "width": 0, + "x": 200, + "y": 200, +} +`; + +exports[`regression tests copy-styles updates appState defaults: [end of test] history 1`] = ` +Object { + "recording": false, + "redoStack": Array [], + "stateHistory": Array [ + Object { + "appState": Object { + "editingGroupId": null, + "editingLinearElement": null, + "name": "Untitled-201933152653", + "selectedElementIds": Object {}, + "viewBackgroundColor": "#ffffff", + }, + "elements": Array [], + }, + ], +} +`; + +exports[`regression tests copy-styles updates appState defaults: [end of test] number of elements 1`] = `3`; + +exports[`regression tests copy-styles updates appState defaults: [end of test] number of renders 1`] = `4`; + exports[`regression tests deselects group of selected elements on pointer down when pointer doesn't hit any element: [end of test] appState 1`] = ` Object { "appearance": "light", @@ -12504,6 +12692,461 @@ exports[`regression tests noop interaction after undo shouldn't create history e exports[`regression tests noop interaction after undo shouldn't create history entry: [end of test] number of renders 1`] = `17`; +exports[`regression tests paste-styles action: [end of test] appState 1`] = ` +Object { + "appearance": "light", + "collaborators": Map {}, + "currentItemBackgroundColor": "#00FF00", + "currentItemEndArrowhead": "bar", + "currentItemFillStyle": "solid", + "currentItemFontFamily": 3, + "currentItemFontSize": 200, + "currentItemLinearStrokeSharpness": "sharp", + "currentItemOpacity": 90, + "currentItemRoughness": 2, + "currentItemStartArrowhead": "bar", + "currentItemStrokeColor": "#FF0000", + "currentItemStrokeSharpness": "sharp", + "currentItemStrokeStyle": "solid", + "currentItemStrokeWidth": 10, + "currentItemTextAlign": "center", + "cursorButton": "up", + "cursorX": 0, + "cursorY": 0, + "draggingElement": null, + "editingElement": null, + "editingGroupId": null, + "editingLinearElement": null, + "elementLocked": false, + "elementType": "selection", + "errorMessage": null, + "exportBackground": true, + "exportEmbedScene": false, + "fileHandle": null, + "gridSize": null, + "height": 768, + "isBindingEnabled": true, + "isLibraryOpen": false, + "isLoading": false, + "isResizing": false, + "isRotating": false, + "lastPointerDownWith": "mouse", + "multiElement": null, + "name": "Untitled-201933152653", + "offsetLeft": 0, + "offsetTop": 0, + "openMenu": null, + "previousSelectedElementIds": Object {}, + "resizingElement": null, + "scrollX": 0, + "scrollY": 0, + "scrolledOutside": false, + "selectedElementIds": Object { + "D": true, + "E": true, + "F": true, + }, + "selectedGroupIds": Object {}, + "selectionElement": null, + "shouldAddWatermark": false, + "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, + "showStats": false, + "startBoundElement": null, + "suggestedBindings": Array [], + "viewBackgroundColor": "#ffffff", + "width": 1024, + "zenModeEnabled": false, + "zoom": Object { + "translation": Object { + "x": 0, + "y": 0, + }, + "value": 1, + }, +} +`; + +exports[`regression tests paste-styles action: [end of test] element 0 1`] = ` +Object { + "angle": 0, + "backgroundColor": "#00FF00", + "boundElementIds": null, + "fillStyle": "solid", + "groupIds": Array [], + "height": 100, + "id": "A", + "isDeleted": false, + "opacity": 90, + "roughness": 2, + "seed": 337897, + "strokeColor": "#FF0000", + "strokeSharpness": "sharp", + "strokeStyle": "solid", + "strokeWidth": 10, + "type": "rectangle", + "version": 1, + "versionNonce": 0, + "width": 100, + "x": 0, + "y": 0, +} +`; + +exports[`regression tests paste-styles action: [end of test] element 1 1`] = ` +Object { + "angle": 0, + "backgroundColor": "transparent", + "boundElementIds": null, + "endArrowhead": "bar", + "endBinding": null, + "fillStyle": "hachure", + "groupIds": Array [], + "height": 0, + "id": "B", + "isDeleted": false, + "lastCommittedPoint": null, + "opacity": 100, + "points": Array [], + "roughness": 1, + "seed": 1278240551, + "startArrowhead": "bar", + "startBinding": null, + "strokeColor": "#000000", + "strokeSharpness": "sharp", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "arrow", + "version": 1, + "versionNonce": 0, + "width": 0, + "x": 0, + "y": 0, +} +`; + +exports[`regression tests paste-styles action: [end of test] element 2 1`] = ` +Object { + "angle": 0, + "backgroundColor": "transparent", + "baseline": 0, + "boundElementIds": null, + "fillStyle": "hachure", + "fontFamily": 3, + "fontSize": 200, + "groupIds": Array [], + "height": 0, + "id": "C", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 449462985, + "strokeColor": "#000000", + "strokeSharpness": "sharp", + "strokeStyle": "solid", + "strokeWidth": 1, + "text": "test", + "textAlign": "center", + "type": "text", + "version": 2, + "versionNonce": 453191, + "verticalAlign": "top", + "width": 0, + "x": 0, + "y": 0, +} +`; + +exports[`regression tests paste-styles action: [end of test] element 3 1`] = ` +Object { + "angle": 0, + "backgroundColor": "#00FF00", + "boundElementIds": null, + "fillStyle": "solid", + "groupIds": Array [], + "height": 100, + "id": "D", + "isDeleted": false, + "opacity": 90, + "roughness": 2, + "seed": 401146281, + "strokeColor": "#FF0000", + "strokeSharpness": "sharp", + "strokeStyle": "solid", + "strokeWidth": 10, + "type": "rectangle", + "version": 2, + "versionNonce": 1014066025, + "width": 100, + "x": 200, + "y": 200, +} +`; + +exports[`regression tests paste-styles action: [end of test] element 4 1`] = ` +Object { + "angle": 0, + "backgroundColor": "#00FF00", + "boundElementIds": null, + "endArrowhead": "bar", + "endBinding": null, + "fillStyle": "solid", + "groupIds": Array [], + "height": 0, + "id": "E", + "isDeleted": false, + "lastCommittedPoint": null, + "opacity": 100, + "points": Array [], + "roughness": 1, + "seed": 2019559783, + "startArrowhead": "bar", + "startBinding": null, + "strokeColor": "#000000", + "strokeSharpness": "sharp", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "arrow", + "version": 2, + "versionNonce": 238820263, + "width": 0, + "x": 200, + "y": 200, +} +`; + +exports[`regression tests paste-styles action: [end of test] element 5 1`] = ` +Object { + "angle": 0, + "backgroundColor": "#00FF00", + "baseline": 0, + "boundElementIds": null, + "fillStyle": "solid", + "fontFamily": 3, + "fontSize": 200, + "groupIds": Array [], + "height": 0, + "id": "F", + "isDeleted": false, + "opacity": 100, + "roughness": 2, + "seed": 1150084233, + "strokeColor": "#000000", + "strokeSharpness": "sharp", + "strokeStyle": "solid", + "strokeWidth": 10, + "text": "test", + "textAlign": "center", + "type": "text", + "version": 3, + "versionNonce": 400692809, + "verticalAlign": "top", + "width": 0, + "x": 200, + "y": 200, +} +`; + +exports[`regression tests paste-styles action: [end of test] history 1`] = ` +Object { + "recording": false, + "redoStack": Array [], + "stateHistory": Array [ + Object { + "appState": Object { + "editingGroupId": null, + "editingLinearElement": null, + "name": "Untitled-201933152653", + "selectedElementIds": Object {}, + "viewBackgroundColor": "#ffffff", + }, + "elements": Array [], + }, + Object { + "appState": Object { + "editingGroupId": null, + "editingLinearElement": null, + "name": "Untitled-201933152653", + "selectedElementIds": Object { + "D": true, + "E": true, + "F": true, + }, + "viewBackgroundColor": "#ffffff", + }, + "elements": Array [ + Object { + "angle": 0, + "backgroundColor": "#00FF00", + "boundElementIds": null, + "fillStyle": "solid", + "groupIds": Array [], + "height": 100, + "id": "A", + "isDeleted": false, + "opacity": 90, + "roughness": 2, + "seed": 337897, + "strokeColor": "#FF0000", + "strokeSharpness": "sharp", + "strokeStyle": "solid", + "strokeWidth": 10, + "type": "rectangle", + "version": 1, + "versionNonce": 0, + "width": 100, + "x": 0, + "y": 0, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "boundElementIds": null, + "endArrowhead": "bar", + "endBinding": null, + "fillStyle": "hachure", + "groupIds": Array [], + "height": 0, + "id": "B", + "isDeleted": false, + "lastCommittedPoint": null, + "opacity": 100, + "points": Array [], + "roughness": 1, + "seed": 1278240551, + "startArrowhead": "bar", + "startBinding": null, + "strokeColor": "#000000", + "strokeSharpness": "sharp", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "arrow", + "version": 1, + "versionNonce": 0, + "width": 0, + "x": 0, + "y": 0, + }, + Object { + "angle": 0, + "backgroundColor": "transparent", + "baseline": 0, + "boundElementIds": null, + "fillStyle": "hachure", + "fontFamily": 3, + "fontSize": 200, + "groupIds": Array [], + "height": 0, + "id": "C", + "isDeleted": false, + "opacity": 100, + "roughness": 1, + "seed": 449462985, + "strokeColor": "#000000", + "strokeSharpness": "sharp", + "strokeStyle": "solid", + "strokeWidth": 1, + "text": "test", + "textAlign": "center", + "type": "text", + "version": 2, + "versionNonce": 453191, + "verticalAlign": "top", + "width": 0, + "x": 0, + "y": 0, + }, + Object { + "angle": 0, + "backgroundColor": "#00FF00", + "boundElementIds": null, + "fillStyle": "solid", + "groupIds": Array [], + "height": 100, + "id": "D", + "isDeleted": false, + "opacity": 90, + "roughness": 2, + "seed": 401146281, + "strokeColor": "#FF0000", + "strokeSharpness": "sharp", + "strokeStyle": "solid", + "strokeWidth": 10, + "type": "rectangle", + "version": 2, + "versionNonce": 1014066025, + "width": 100, + "x": 200, + "y": 200, + }, + Object { + "angle": 0, + "backgroundColor": "#00FF00", + "boundElementIds": null, + "endArrowhead": "bar", + "endBinding": null, + "fillStyle": "solid", + "groupIds": Array [], + "height": 0, + "id": "E", + "isDeleted": false, + "lastCommittedPoint": null, + "opacity": 100, + "points": Array [], + "roughness": 1, + "seed": 2019559783, + "startArrowhead": "bar", + "startBinding": null, + "strokeColor": "#000000", + "strokeSharpness": "sharp", + "strokeStyle": "solid", + "strokeWidth": 1, + "type": "arrow", + "version": 2, + "versionNonce": 238820263, + "width": 0, + "x": 200, + "y": 200, + }, + Object { + "angle": 0, + "backgroundColor": "#00FF00", + "baseline": 0, + "boundElementIds": null, + "fillStyle": "solid", + "fontFamily": 3, + "fontSize": 200, + "groupIds": Array [], + "height": 0, + "id": "F", + "isDeleted": false, + "opacity": 100, + "roughness": 2, + "seed": 1150084233, + "strokeColor": "#000000", + "strokeSharpness": "sharp", + "strokeStyle": "solid", + "strokeWidth": 10, + "text": "test", + "textAlign": "center", + "type": "text", + "version": 3, + "versionNonce": 400692809, + "verticalAlign": "top", + "width": 0, + "x": 200, + "y": 200, + }, + ], + }, + ], +} +`; + +exports[`regression tests paste-styles action: [end of test] number of elements 1`] = `6`; + +exports[`regression tests paste-styles action: [end of test] number of renders 1`] = `6`; + exports[`regression tests pinch-to-zoom works: [end of test] appState 1`] = ` Object { "appearance": "light", @@ -13456,164 +14099,6 @@ exports[`regression tests selecting 'Bring to front' in context menu brings elem exports[`regression tests selecting 'Bring to front' in context menu brings element to front: [end of test] number of renders 1`] = `11`; -exports[`regression tests selecting 'Copy styles' in context menu copies styles: [end of test] appState 1`] = ` -Object { - "appearance": "light", - "collaborators": Map {}, - "currentItemBackgroundColor": "transparent", - "currentItemEndArrowhead": "arrow", - "currentItemFillStyle": "hachure", - "currentItemFontFamily": 1, - "currentItemFontSize": 20, - "currentItemLinearStrokeSharpness": "round", - "currentItemOpacity": 100, - "currentItemRoughness": 1, - "currentItemStartArrowhead": null, - "currentItemStrokeColor": "#000000", - "currentItemStrokeSharpness": "sharp", - "currentItemStrokeStyle": "solid", - "currentItemStrokeWidth": 1, - "currentItemTextAlign": "left", - "cursorButton": "up", - "cursorX": 0, - "cursorY": 0, - "draggingElement": null, - "editingElement": null, - "editingGroupId": null, - "editingLinearElement": null, - "elementLocked": false, - "elementType": "selection", - "errorMessage": null, - "exportBackground": true, - "exportEmbedScene": false, - "fileHandle": null, - "gridSize": null, - "height": 768, - "isBindingEnabled": true, - "isLibraryOpen": false, - "isLoading": false, - "isResizing": false, - "isRotating": false, - "lastPointerDownWith": "mouse", - "multiElement": null, - "name": "Untitled-201933152653", - "offsetLeft": 0, - "offsetTop": 0, - "openMenu": null, - "previousSelectedElementIds": Object {}, - "resizingElement": null, - "scrollX": 0, - "scrollY": 0, - "scrolledOutside": false, - "selectedElementIds": Object { - "id0": true, - }, - "selectedGroupIds": Object {}, - "selectionElement": null, - "shouldAddWatermark": false, - "shouldCacheIgnoreZoom": false, - "showShortcutsDialog": false, - "showStats": false, - "startBoundElement": null, - "suggestedBindings": Array [], - "viewBackgroundColor": "#ffffff", - "width": 1024, - "zenModeEnabled": false, - "zoom": Object { - "translation": Object { - "x": 0, - "y": 0, - }, - "value": 1, - }, -} -`; - -exports[`regression tests selecting 'Copy styles' in context menu copies styles: [end of test] element 0 1`] = ` -Object { - "angle": 0, - "backgroundColor": "transparent", - "boundElementIds": null, - "fillStyle": "hachure", - "groupIds": Array [], - "height": 20, - "id": "id0", - "isDeleted": false, - "opacity": 100, - "roughness": 1, - "seed": 337897, - "strokeColor": "#000000", - "strokeSharpness": "sharp", - "strokeStyle": "solid", - "strokeWidth": 1, - "type": "rectangle", - "version": 2, - "versionNonce": 1278240551, - "width": 20, - "x": 10, - "y": 10, -} -`; - -exports[`regression tests selecting 'Copy styles' in context menu copies styles: [end of test] history 1`] = ` -Object { - "recording": false, - "redoStack": Array [], - "stateHistory": Array [ - Object { - "appState": Object { - "editingGroupId": null, - "editingLinearElement": null, - "name": "Untitled-201933152653", - "selectedElementIds": Object {}, - "viewBackgroundColor": "#ffffff", - }, - "elements": Array [], - }, - Object { - "appState": Object { - "editingGroupId": null, - "editingLinearElement": null, - "name": "Untitled-201933152653", - "selectedElementIds": Object { - "id0": true, - }, - "viewBackgroundColor": "#ffffff", - }, - "elements": Array [ - Object { - "angle": 0, - "backgroundColor": "transparent", - "boundElementIds": null, - "fillStyle": "hachure", - "groupIds": Array [], - "height": 20, - "id": "id0", - "isDeleted": false, - "opacity": 100, - "roughness": 1, - "seed": 337897, - "strokeColor": "#000000", - "strokeSharpness": "sharp", - "strokeStyle": "solid", - "strokeWidth": 1, - "type": "rectangle", - "version": 2, - "versionNonce": 1278240551, - "width": 20, - "x": 10, - "y": 10, - }, - ], - }, - ], -} -`; - -exports[`regression tests selecting 'Copy styles' in context menu copies styles: [end of test] number of elements 1`] = `1`; - -exports[`regression tests selecting 'Copy styles' in context menu copies styles: [end of test] number of renders 1`] = `5`; - exports[`regression tests selecting 'Delete' in context menu deletes element: [end of test] appState 1`] = ` Object { "appearance": "light", @@ -14365,839 +14850,6 @@ exports[`regression tests selecting 'Group selection' in context menu groups sel exports[`regression tests selecting 'Group selection' in context menu groups selected elements: [end of test] number of renders 1`] = `12`; -exports[`regression tests selecting 'Paste styles' in context menu pastes styles: [end of test] appState 1`] = ` -Object { - "appearance": "light", - "collaborators": Map {}, - "currentItemBackgroundColor": "#e64980", - "currentItemEndArrowhead": "arrow", - "currentItemFillStyle": "cross-hatch", - "currentItemFontFamily": 1, - "currentItemFontSize": 20, - "currentItemLinearStrokeSharpness": "round", - "currentItemOpacity": 60, - "currentItemRoughness": 2, - "currentItemStartArrowhead": null, - "currentItemStrokeColor": "#c92a2a", - "currentItemStrokeSharpness": "sharp", - "currentItemStrokeStyle": "dotted", - "currentItemStrokeWidth": 2, - "currentItemTextAlign": "left", - "cursorButton": "up", - "cursorX": 0, - "cursorY": 0, - "draggingElement": null, - "editingElement": null, - "editingGroupId": null, - "editingLinearElement": null, - "elementLocked": false, - "elementType": "selection", - "errorMessage": null, - "exportBackground": true, - "exportEmbedScene": false, - "fileHandle": null, - "gridSize": null, - "height": 768, - "isBindingEnabled": true, - "isLibraryOpen": false, - "isLoading": false, - "isResizing": false, - "isRotating": false, - "lastPointerDownWith": "mouse", - "multiElement": null, - "name": "Untitled-201933152653", - "offsetLeft": 0, - "offsetTop": 0, - "openMenu": null, - "previousSelectedElementIds": Object {}, - "resizingElement": null, - "scrollX": 0, - "scrollY": 0, - "scrolledOutside": false, - "selectedElementIds": Object { - "id0": true, - }, - "selectedGroupIds": Object {}, - "selectionElement": null, - "shouldAddWatermark": false, - "shouldCacheIgnoreZoom": false, - "showShortcutsDialog": false, - "showStats": false, - "startBoundElement": null, - "suggestedBindings": Array [], - "viewBackgroundColor": "#ffffff", - "width": 1024, - "zenModeEnabled": false, - "zoom": Object { - "translation": Object { - "x": 0, - "y": 0, - }, - "value": 1, - }, -} -`; - -exports[`regression tests selecting 'Paste styles' in context menu pastes styles: [end of test] element 0 1`] = ` -Object { - "angle": 0, - "backgroundColor": "#e64980", - "boundElementIds": null, - "fillStyle": "cross-hatch", - "groupIds": Array [], - "height": 20, - "id": "id0", - "isDeleted": false, - "opacity": 60, - "roughness": 2, - "seed": 337897, - "strokeColor": "#c92a2a", - "strokeSharpness": "sharp", - "strokeStyle": "dotted", - "strokeWidth": 2, - "type": "rectangle", - "version": 3, - "versionNonce": 81784553, - "width": 20, - "x": 10, - "y": 10, -} -`; - -exports[`regression tests selecting 'Paste styles' in context menu pastes styles: [end of test] element 1 1`] = ` -Object { - "angle": 0, - "backgroundColor": "#e64980", - "boundElementIds": null, - "fillStyle": "cross-hatch", - "groupIds": Array [], - "height": 20, - "id": "id1", - "isDeleted": false, - "opacity": 60, - "roughness": 2, - "seed": 23633383, - "strokeColor": "#c92a2a", - "strokeSharpness": "sharp", - "strokeStyle": "dotted", - "strokeWidth": 2, - "type": "rectangle", - "version": 13, - "versionNonce": 915032327, - "width": 20, - "x": 40, - "y": 40, -} -`; - -exports[`regression tests selecting 'Paste styles' in context menu pastes styles: [end of test] history 1`] = ` -Object { - "recording": false, - "redoStack": Array [], - "stateHistory": Array [ - Object { - "appState": Object { - "editingGroupId": null, - "editingLinearElement": null, - "name": "Untitled-201933152653", - "selectedElementIds": Object {}, - "viewBackgroundColor": "#ffffff", - }, - "elements": Array [], - }, - Object { - "appState": Object { - "editingGroupId": null, - "editingLinearElement": null, - "name": "Untitled-201933152653", - "selectedElementIds": Object { - "id0": true, - }, - "viewBackgroundColor": "#ffffff", - }, - "elements": Array [ - Object { - "angle": 0, - "backgroundColor": "transparent", - "boundElementIds": null, - "fillStyle": "hachure", - "groupIds": Array [], - "height": 20, - "id": "id0", - "isDeleted": false, - "opacity": 100, - "roughness": 1, - "seed": 337897, - "strokeColor": "#000000", - "strokeSharpness": "sharp", - "strokeStyle": "solid", - "strokeWidth": 1, - "type": "rectangle", - "version": 2, - "versionNonce": 1278240551, - "width": 20, - "x": 10, - "y": 10, - }, - ], - }, - Object { - "appState": Object { - "editingGroupId": null, - "editingLinearElement": null, - "name": "Untitled-201933152653", - "selectedElementIds": Object { - "id1": true, - }, - "viewBackgroundColor": "#ffffff", - }, - "elements": Array [ - Object { - "angle": 0, - "backgroundColor": "transparent", - "boundElementIds": null, - "fillStyle": "hachure", - "groupIds": Array [], - "height": 20, - "id": "id0", - "isDeleted": false, - "opacity": 100, - "roughness": 1, - "seed": 337897, - "strokeColor": "#000000", - "strokeSharpness": "sharp", - "strokeStyle": "solid", - "strokeWidth": 1, - "type": "rectangle", - "version": 2, - "versionNonce": 1278240551, - "width": 20, - "x": 10, - "y": 10, - }, - Object { - "angle": 0, - "backgroundColor": "transparent", - "boundElementIds": null, - "fillStyle": "hachure", - "groupIds": Array [], - "height": 20, - "id": "id1", - "isDeleted": false, - "opacity": 100, - "roughness": 1, - "seed": 449462985, - "strokeColor": "#000000", - "strokeSharpness": "sharp", - "strokeStyle": "solid", - "strokeWidth": 1, - "type": "rectangle", - "version": 2, - "versionNonce": 453191, - "width": 20, - "x": 40, - "y": 40, - }, - ], - }, - Object { - "appState": Object { - "editingGroupId": null, - "editingLinearElement": null, - "name": "Untitled-201933152653", - "selectedElementIds": Object { - "id1": true, - }, - "viewBackgroundColor": "#ffffff", - }, - "elements": Array [ - Object { - "angle": 0, - "backgroundColor": "transparent", - "boundElementIds": null, - "fillStyle": "hachure", - "groupIds": Array [], - "height": 20, - "id": "id0", - "isDeleted": false, - "opacity": 100, - "roughness": 1, - "seed": 337897, - "strokeColor": "#000000", - "strokeSharpness": "sharp", - "strokeStyle": "solid", - "strokeWidth": 1, - "type": "rectangle", - "version": 2, - "versionNonce": 1278240551, - "width": 20, - "x": 10, - "y": 10, - }, - Object { - "angle": 0, - "backgroundColor": "transparent", - "boundElementIds": null, - "fillStyle": "hachure", - "groupIds": Array [], - "height": 20, - "id": "id1", - "isDeleted": false, - "opacity": 100, - "roughness": 1, - "seed": 449462985, - "strokeColor": "#000000", - "strokeSharpness": "sharp", - "strokeStyle": "solid", - "strokeWidth": 1, - "type": "rectangle", - "version": 3, - "versionNonce": 401146281, - "width": 20, - "x": 40, - "y": 40, - }, - ], - }, - Object { - "appState": Object { - "editingGroupId": null, - "editingLinearElement": null, - "name": "Untitled-201933152653", - "selectedElementIds": Object { - "id1": true, - }, - "viewBackgroundColor": "#ffffff", - }, - "elements": Array [ - Object { - "angle": 0, - "backgroundColor": "transparent", - "boundElementIds": null, - "fillStyle": "hachure", - "groupIds": Array [], - "height": 20, - "id": "id0", - "isDeleted": false, - "opacity": 100, - "roughness": 1, - "seed": 337897, - "strokeColor": "#000000", - "strokeSharpness": "sharp", - "strokeStyle": "solid", - "strokeWidth": 1, - "type": "rectangle", - "version": 2, - "versionNonce": 1278240551, - "width": 20, - "x": 10, - "y": 10, - }, - Object { - "angle": 0, - "backgroundColor": "transparent", - "boundElementIds": null, - "fillStyle": "hachure", - "groupIds": Array [], - "height": 20, - "id": "id1", - "isDeleted": false, - "opacity": 100, - "roughness": 1, - "seed": 449462985, - "strokeColor": "#c92a2a", - "strokeSharpness": "sharp", - "strokeStyle": "solid", - "strokeWidth": 1, - "type": "rectangle", - "version": 5, - "versionNonce": 1150084233, - "width": 20, - "x": 40, - "y": 40, - }, - ], - }, - Object { - "appState": Object { - "editingGroupId": null, - "editingLinearElement": null, - "name": "Untitled-201933152653", - "selectedElementIds": Object { - "id1": true, - }, - "viewBackgroundColor": "#ffffff", - }, - "elements": Array [ - Object { - "angle": 0, - "backgroundColor": "transparent", - "boundElementIds": null, - "fillStyle": "hachure", - "groupIds": Array [], - "height": 20, - "id": "id0", - "isDeleted": false, - "opacity": 100, - "roughness": 1, - "seed": 337897, - "strokeColor": "#000000", - "strokeSharpness": "sharp", - "strokeStyle": "solid", - "strokeWidth": 1, - "type": "rectangle", - "version": 2, - "versionNonce": 1278240551, - "width": 20, - "x": 10, - "y": 10, - }, - Object { - "angle": 0, - "backgroundColor": "transparent", - "boundElementIds": null, - "fillStyle": "hachure", - "groupIds": Array [], - "height": 20, - "id": "id1", - "isDeleted": false, - "opacity": 100, - "roughness": 1, - "seed": 449462985, - "strokeColor": "#c92a2a", - "strokeSharpness": "sharp", - "strokeStyle": "solid", - "strokeWidth": 1, - "type": "rectangle", - "version": 6, - "versionNonce": 1116226695, - "width": 20, - "x": 40, - "y": 40, - }, - ], - }, - Object { - "appState": Object { - "editingGroupId": null, - "editingLinearElement": null, - "name": "Untitled-201933152653", - "selectedElementIds": Object { - "id1": true, - }, - "viewBackgroundColor": "#ffffff", - }, - "elements": Array [ - Object { - "angle": 0, - "backgroundColor": "transparent", - "boundElementIds": null, - "fillStyle": "hachure", - "groupIds": Array [], - "height": 20, - "id": "id0", - "isDeleted": false, - "opacity": 100, - "roughness": 1, - "seed": 337897, - "strokeColor": "#000000", - "strokeSharpness": "sharp", - "strokeStyle": "solid", - "strokeWidth": 1, - "type": "rectangle", - "version": 2, - "versionNonce": 1278240551, - "width": 20, - "x": 10, - "y": 10, - }, - Object { - "angle": 0, - "backgroundColor": "#e64980", - "boundElementIds": null, - "fillStyle": "hachure", - "groupIds": Array [], - "height": 20, - "id": "id1", - "isDeleted": false, - "opacity": 100, - "roughness": 1, - "seed": 449462985, - "strokeColor": "#c92a2a", - "strokeSharpness": "sharp", - "strokeStyle": "solid", - "strokeWidth": 1, - "type": "rectangle", - "version": 8, - "versionNonce": 238820263, - "width": 20, - "x": 40, - "y": 40, - }, - ], - }, - Object { - "appState": Object { - "editingGroupId": null, - "editingLinearElement": null, - "name": "Untitled-201933152653", - "selectedElementIds": Object { - "id1": true, - }, - "viewBackgroundColor": "#ffffff", - }, - "elements": Array [ - Object { - "angle": 0, - "backgroundColor": "transparent", - "boundElementIds": null, - "fillStyle": "hachure", - "groupIds": Array [], - "height": 20, - "id": "id0", - "isDeleted": false, - "opacity": 100, - "roughness": 1, - "seed": 337897, - "strokeColor": "#000000", - "strokeSharpness": "sharp", - "strokeStyle": "solid", - "strokeWidth": 1, - "type": "rectangle", - "version": 2, - "versionNonce": 1278240551, - "width": 20, - "x": 10, - "y": 10, - }, - Object { - "angle": 0, - "backgroundColor": "#e64980", - "boundElementIds": null, - "fillStyle": "cross-hatch", - "groupIds": Array [], - "height": 20, - "id": "id1", - "isDeleted": false, - "opacity": 100, - "roughness": 1, - "seed": 449462985, - "strokeColor": "#c92a2a", - "strokeSharpness": "sharp", - "strokeStyle": "solid", - "strokeWidth": 1, - "type": "rectangle", - "version": 9, - "versionNonce": 400692809, - "width": 20, - "x": 40, - "y": 40, - }, - ], - }, - Object { - "appState": Object { - "editingGroupId": null, - "editingLinearElement": null, - "name": "Untitled-201933152653", - "selectedElementIds": Object { - "id1": true, - }, - "viewBackgroundColor": "#ffffff", - }, - "elements": Array [ - Object { - "angle": 0, - "backgroundColor": "transparent", - "boundElementIds": null, - "fillStyle": "hachure", - "groupIds": Array [], - "height": 20, - "id": "id0", - "isDeleted": false, - "opacity": 100, - "roughness": 1, - "seed": 337897, - "strokeColor": "#000000", - "strokeSharpness": "sharp", - "strokeStyle": "solid", - "strokeWidth": 1, - "type": "rectangle", - "version": 2, - "versionNonce": 1278240551, - "width": 20, - "x": 10, - "y": 10, - }, - Object { - "angle": 0, - "backgroundColor": "#e64980", - "boundElementIds": null, - "fillStyle": "cross-hatch", - "groupIds": Array [], - "height": 20, - "id": "id1", - "isDeleted": false, - "opacity": 100, - "roughness": 1, - "seed": 449462985, - "strokeColor": "#c92a2a", - "strokeSharpness": "sharp", - "strokeStyle": "solid", - "strokeWidth": 2, - "type": "rectangle", - "version": 10, - "versionNonce": 1604849351, - "width": 20, - "x": 40, - "y": 40, - }, - ], - }, - Object { - "appState": Object { - "editingGroupId": null, - "editingLinearElement": null, - "name": "Untitled-201933152653", - "selectedElementIds": Object { - "id1": true, - }, - "viewBackgroundColor": "#ffffff", - }, - "elements": Array [ - Object { - "angle": 0, - "backgroundColor": "transparent", - "boundElementIds": null, - "fillStyle": "hachure", - "groupIds": Array [], - "height": 20, - "id": "id0", - "isDeleted": false, - "opacity": 100, - "roughness": 1, - "seed": 337897, - "strokeColor": "#000000", - "strokeSharpness": "sharp", - "strokeStyle": "solid", - "strokeWidth": 1, - "type": "rectangle", - "version": 2, - "versionNonce": 1278240551, - "width": 20, - "x": 10, - "y": 10, - }, - Object { - "angle": 0, - "backgroundColor": "#e64980", - "boundElementIds": null, - "fillStyle": "cross-hatch", - "groupIds": Array [], - "height": 20, - "id": "id1", - "isDeleted": false, - "opacity": 100, - "roughness": 1, - "seed": 449462985, - "strokeColor": "#c92a2a", - "strokeSharpness": "sharp", - "strokeStyle": "dotted", - "strokeWidth": 2, - "type": "rectangle", - "version": 11, - "versionNonce": 1505387817, - "width": 20, - "x": 40, - "y": 40, - }, - ], - }, - Object { - "appState": Object { - "editingGroupId": null, - "editingLinearElement": null, - "name": "Untitled-201933152653", - "selectedElementIds": Object { - "id1": true, - }, - "viewBackgroundColor": "#ffffff", - }, - "elements": Array [ - Object { - "angle": 0, - "backgroundColor": "transparent", - "boundElementIds": null, - "fillStyle": "hachure", - "groupIds": Array [], - "height": 20, - "id": "id0", - "isDeleted": false, - "opacity": 100, - "roughness": 1, - "seed": 337897, - "strokeColor": "#000000", - "strokeSharpness": "sharp", - "strokeStyle": "solid", - "strokeWidth": 1, - "type": "rectangle", - "version": 2, - "versionNonce": 1278240551, - "width": 20, - "x": 10, - "y": 10, - }, - Object { - "angle": 0, - "backgroundColor": "#e64980", - "boundElementIds": null, - "fillStyle": "cross-hatch", - "groupIds": Array [], - "height": 20, - "id": "id1", - "isDeleted": false, - "opacity": 100, - "roughness": 2, - "seed": 23633383, - "strokeColor": "#c92a2a", - "strokeSharpness": "sharp", - "strokeStyle": "dotted", - "strokeWidth": 2, - "type": "rectangle", - "version": 12, - "versionNonce": 493213705, - "width": 20, - "x": 40, - "y": 40, - }, - ], - }, - Object { - "appState": Object { - "editingGroupId": null, - "editingLinearElement": null, - "name": "Untitled-201933152653", - "selectedElementIds": Object { - "id1": true, - }, - "viewBackgroundColor": "#ffffff", - }, - "elements": Array [ - Object { - "angle": 0, - "backgroundColor": "transparent", - "boundElementIds": null, - "fillStyle": "hachure", - "groupIds": Array [], - "height": 20, - "id": "id0", - "isDeleted": false, - "opacity": 100, - "roughness": 1, - "seed": 337897, - "strokeColor": "#000000", - "strokeSharpness": "sharp", - "strokeStyle": "solid", - "strokeWidth": 1, - "type": "rectangle", - "version": 2, - "versionNonce": 1278240551, - "width": 20, - "x": 10, - "y": 10, - }, - Object { - "angle": 0, - "backgroundColor": "#e64980", - "boundElementIds": null, - "fillStyle": "cross-hatch", - "groupIds": Array [], - "height": 20, - "id": "id1", - "isDeleted": false, - "opacity": 60, - "roughness": 2, - "seed": 23633383, - "strokeColor": "#c92a2a", - "strokeSharpness": "sharp", - "strokeStyle": "dotted", - "strokeWidth": 2, - "type": "rectangle", - "version": 13, - "versionNonce": 915032327, - "width": 20, - "x": 40, - "y": 40, - }, - ], - }, - Object { - "appState": Object { - "editingGroupId": null, - "editingLinearElement": null, - "name": "Untitled-201933152653", - "selectedElementIds": Object { - "id0": true, - }, - "viewBackgroundColor": "#ffffff", - }, - "elements": Array [ - Object { - "angle": 0, - "backgroundColor": "#e64980", - "boundElementIds": null, - "fillStyle": "cross-hatch", - "groupIds": Array [], - "height": 20, - "id": "id0", - "isDeleted": false, - "opacity": 60, - "roughness": 2, - "seed": 337897, - "strokeColor": "#c92a2a", - "strokeSharpness": "sharp", - "strokeStyle": "dotted", - "strokeWidth": 2, - "type": "rectangle", - "version": 3, - "versionNonce": 81784553, - "width": 20, - "x": 10, - "y": 10, - }, - Object { - "angle": 0, - "backgroundColor": "#e64980", - "boundElementIds": null, - "fillStyle": "cross-hatch", - "groupIds": Array [], - "height": 20, - "id": "id1", - "isDeleted": false, - "opacity": 60, - "roughness": 2, - "seed": 23633383, - "strokeColor": "#c92a2a", - "strokeSharpness": "sharp", - "strokeStyle": "dotted", - "strokeWidth": 2, - "type": "rectangle", - "version": 13, - "versionNonce": 915032327, - "width": 20, - "x": 40, - "y": 40, - }, - ], - }, - ], -} -`; - -exports[`regression tests selecting 'Paste styles' in context menu pastes styles: [end of test] number of elements 1`] = `2`; - -exports[`regression tests selecting 'Paste styles' in context menu pastes styles: [end of test] number of renders 1`] = `20`; - exports[`regression tests selecting 'Send backward' in context menu sends element backward: [end of test] appState 1`] = ` Object { "appearance": "light", diff --git a/src/tests/helpers/api.ts b/src/tests/helpers/api.ts index 37e9f08c2..cb3e4db16 100644 --- a/src/tests/helpers/api.ts +++ b/src/tests/helpers/api.ts @@ -81,6 +81,12 @@ export class API { verticalAlign?: T extends "text" ? ExcalidrawTextElement["verticalAlign"] : never; + startArrowhead?: T extends "arrow" | "line" | "draw" + ? ExcalidrawLinearElement["startArrowhead"] + : never; + endArrowhead?: T extends "arrow" | "line" | "draw" + ? ExcalidrawLinearElement["endArrowhead"] + : never; }): T extends "arrow" | "line" | "draw" ? ExcalidrawLinearElement : T extends "text" @@ -130,8 +136,8 @@ export class API { case "draw": element = newLinearElement({ type: type as "arrow" | "line" | "draw", - startArrowhead: null, - endArrowhead: null, + startArrowhead: rest.startArrowhead ?? null, + endArrowhead: rest.endArrowhead ?? null, ...base, }); break; diff --git a/src/tests/regressionTests.test.tsx b/src/tests/regressionTests.test.tsx index 02d78c470..9bf5e99a5 100644 --- a/src/tests/regressionTests.test.tsx +++ b/src/tests/regressionTests.test.tsx @@ -1,7 +1,7 @@ import { queryByText } from "@testing-library/react"; import React from "react"; import ReactDOM from "react-dom"; -import { copiedStyles } from "../actions/actionStyles"; +import { getDefaultAppState } from "../appState"; import { ExcalidrawElement } from "../element/types"; import { setLanguage, t } from "../i18n"; import { CODES, KEYS } from "../keys"; @@ -768,82 +768,224 @@ describe("regression tests", () => { }); }); - it("selecting 'Copy styles' in context menu copies styles", () => { - UI.clickTool("rectangle"); - mouse.down(10, 10); - mouse.up(20, 20); + it("copy-styles updates appState defaults", () => { + h.app.updateScene({ + elements: [ + API.createElement({ + type: "rectangle", + id: "A", + x: 0, + y: 0, + opacity: 90, + strokeColor: "#FF0000", + strokeStyle: "solid", + strokeWidth: 10, + roughness: 2, + backgroundColor: "#00FF00", + fillStyle: "solid", + strokeSharpness: "sharp", + }), + API.createElement({ + type: "arrow", + id: "B", + x: 200, + y: 200, + startArrowhead: "bar", + endArrowhead: "bar", + }), + API.createElement({ + type: "text", + id: "C", + x: 200, + y: 200, + fontFamily: 3, + fontSize: 200, + textAlign: "center", + }), + ], + }); + + h.app.setState({ + selectedElementIds: { A: true, B: true, C: true }, + }); + + const defaultAppState = getDefaultAppState(); + + expect(h.state).toEqual( + expect.objectContaining({ + currentItemOpacity: defaultAppState.currentItemOpacity, + currentItemStrokeColor: defaultAppState.currentItemStrokeColor, + currentItemStrokeStyle: defaultAppState.currentItemStrokeStyle, + currentItemStrokeWidth: defaultAppState.currentItemStrokeWidth, + currentItemRoughness: defaultAppState.currentItemRoughness, + currentItemBackgroundColor: defaultAppState.currentItemBackgroundColor, + currentItemFillStyle: defaultAppState.currentItemFillStyle, + currentItemStrokeSharpness: defaultAppState.currentItemStrokeSharpness, + currentItemStartArrowhead: defaultAppState.currentItemStartArrowhead, + currentItemEndArrowhead: defaultAppState.currentItemEndArrowhead, + currentItemFontFamily: defaultAppState.currentItemFontFamily, + currentItemFontSize: defaultAppState.currentItemFontSize, + currentItemTextAlign: defaultAppState.currentItemTextAlign, + }), + ); fireEvent.contextMenu(GlobalTestState.canvas, { button: 2, clientX: 1, clientY: 1, }); + const contextMenu = document.querySelector(".context-menu"); - expect(copiedStyles).toBe("{}"); fireEvent.click(queryByText(contextMenu as HTMLElement, "Copy styles")!); - expect(copiedStyles).not.toBe("{}"); - const element = JSON.parse(copiedStyles); - expect(element).toEqual(API.getSelectedElement()); + + expect(h.state).toEqual( + expect.objectContaining({ + currentItemOpacity: 90, + currentItemStrokeColor: "#FF0000", + currentItemStrokeStyle: "solid", + currentItemStrokeWidth: 10, + currentItemRoughness: 2, + currentItemBackgroundColor: "#00FF00", + currentItemFillStyle: "solid", + currentItemStrokeSharpness: "sharp", + currentItemStartArrowhead: "bar", + currentItemEndArrowhead: "bar", + currentItemFontFamily: 3, + currentItemFontSize: 200, + currentItemTextAlign: "center", + }), + ); }); - it("selecting 'Paste styles' in context menu pastes styles", () => { - UI.clickTool("rectangle"); - mouse.down(10, 10); - mouse.up(20, 20); - - UI.clickTool("rectangle"); - mouse.down(10, 10); - mouse.up(20, 20); - - // Change some styles of second rectangle - clickLabeledElement("Stroke"); - clickLabeledElement("#c92a2a"); - clickLabeledElement("Background"); - clickLabeledElement("#e64980"); - // Fill style - fireEvent.click(screen.getByTitle("Cross-hatch")); - // Stroke width - fireEvent.click(screen.getByTitle("Bold")); - // Stroke style - fireEvent.click(screen.getByTitle("Dotted")); - // Roughness - fireEvent.click(screen.getByTitle("Cartoonist")); - // Opacity - fireEvent.change(screen.getByLabelText("Opacity"), { - target: { value: "60" }, + it("paste-styles action", () => { + h.app.updateScene({ + elements: [ + API.createElement({ + type: "rectangle", + id: "A", + x: 0, + y: 0, + opacity: 90, + strokeColor: "#FF0000", + strokeStyle: "solid", + strokeWidth: 10, + roughness: 2, + backgroundColor: "#00FF00", + fillStyle: "solid", + strokeSharpness: "sharp", + }), + API.createElement({ + type: "arrow", + id: "B", + x: 0, + y: 0, + startArrowhead: "bar", + endArrowhead: "bar", + }), + API.createElement({ + type: "text", + id: "C", + x: 0, + y: 0, + fontFamily: 3, + fontSize: 200, + textAlign: "center", + }), + API.createElement({ + type: "rectangle", + id: "D", + x: 200, + y: 200, + }), + API.createElement({ + type: "arrow", + id: "E", + x: 200, + y: 200, + }), + API.createElement({ + type: "text", + id: "F", + x: 200, + y: 200, + }), + ], + }); + + h.app.setState({ + selectedElementIds: { A: true, B: true, C: true }, }); - mouse.reset(); - // Copy styles of second rectangle fireEvent.contextMenu(GlobalTestState.canvas, { button: 2, - clientX: 40, - clientY: 40, + clientX: 1, + clientY: 1, }); - let contextMenu = document.querySelector(".context-menu"); - fireEvent.click(queryByText(contextMenu as HTMLElement, "Copy styles")!); - const secondRect = JSON.parse(copiedStyles); - expect(secondRect.id).toBe(h.elements[1].id); + fireEvent.click( + queryByText( + document.querySelector(".context-menu") as HTMLElement, + "Copy styles", + )!, + ); - mouse.reset(); - // Paste styles to first rectangle + h.app.setState({ + selectedElementIds: { D: true, E: true, F: true }, + }); fireEvent.contextMenu(GlobalTestState.canvas, { button: 2, - clientX: 10, - clientY: 10, + clientX: 201, + clientY: 201, }); - contextMenu = document.querySelector(".context-menu"); - fireEvent.click(queryByText(contextMenu as HTMLElement, "Paste styles")!); + fireEvent.click( + queryByText( + document.querySelector(".context-menu") as HTMLElement, + "Paste styles", + )!, + ); - const firstRect = API.getSelectedElement(); - expect(firstRect.id).toBe(h.elements[0].id); - expect(firstRect.strokeColor).toBe("#c92a2a"); - expect(firstRect.backgroundColor).toBe("#e64980"); - expect(firstRect.fillStyle).toBe("cross-hatch"); - expect(firstRect.strokeWidth).toBe(2); // Bold: 2 - expect(firstRect.strokeStyle).toBe("dotted"); - expect(firstRect.roughness).toBe(2); // Cartoonist: 2 - expect(firstRect.opacity).toBe(60); + const defaultAppState = getDefaultAppState(); + + expect(h.elements.find((element) => element.id === "D")).toEqual( + expect.objectContaining({ + opacity: 90, + strokeColor: "#FF0000", + strokeStyle: "solid", + strokeWidth: 10, + roughness: 2, + backgroundColor: "#00FF00", + fillStyle: "solid", + strokeSharpness: "sharp", + }), + ); + expect(h.elements.find((element) => element.id === "E")).toEqual( + expect.objectContaining({ + opacity: defaultAppState.currentItemOpacity, + strokeColor: defaultAppState.currentItemStrokeColor, + strokeStyle: defaultAppState.currentItemStrokeStyle, + strokeWidth: defaultAppState.currentItemStrokeWidth, + roughness: defaultAppState.currentItemRoughness, + backgroundColor: "#00FF00", + fillStyle: "solid", + strokeSharpness: "sharp", + startArrowhead: "bar", + endArrowhead: "bar", + }), + ); + expect(h.elements.find((element) => element.id === "F")).toEqual( + expect.objectContaining({ + opacity: defaultAppState.currentItemOpacity, + strokeColor: defaultAppState.currentItemStrokeColor, + strokeStyle: defaultAppState.currentItemStrokeStyle, + strokeWidth: 10, + roughness: 2, + backgroundColor: "#00FF00", + fillStyle: "solid", + strokeSharpness: "sharp", + fontFamily: 3, + fontSize: 200, + textAlign: "center", + }), + ); }); it("selecting 'Delete' in context menu deletes element", () => {