Parcourir la source

fix group selection (#2092)

David Luzar il y a 4 ans
Parent
commit
b8f8bc2e32

+ 8 - 26
src/components/App.tsx

@@ -3384,33 +3384,15 @@ class App extends React.Component<ExcalidrawProps, AppState> {
             }));
           }
         } else {
-          if (isSelectedViaGroup(this.state, hitElement)) {
-            /*
-            We want to select the group(s) the hit element is in not the particular element.
-            That means we have to deselect elements that are not part of the groups of the
-             hit element, while keeping the elements that are.
-            */
-            const idsOfSelectedElementsThatAreInGroups = hitElement.groupIds
-              .flatMap((groupId) =>
-                getElementsInGroup(this.scene.getElements(), groupId),
-              )
-              .map((element) => ({ [element.id]: true }))
-              .reduce((prevId, acc) => ({ ...prevId, ...acc }), {});
-
-            this.setState((_prevState) => ({
-              selectedGroupIds: {
-                ...hitElement.groupIds
-                  .map((gId) => ({ [gId]: true }))
-                  .reduce((prevId, acc) => ({ ...prevId, ...acc }), {}),
+          this.setState((prevState) => ({
+            ...selectGroupsForSelectedElements(
+              {
+                ...prevState,
+                selectedElementIds: { [hitElement.id]: true },
               },
-              selectedElementIds: { ...idsOfSelectedElementsThatAreInGroups },
-            }));
-          } else {
-            this.setState((_prevState) => ({
-              selectedGroupIds: {},
-              selectedElementIds: { [hitElement!.id]: true },
-            }));
-          }
+              this.scene.getElements(),
+            ),
+          }));
         }
       }
 

+ 848 - 50
src/tests/__snapshots__/regressionTests.test.tsx.snap

@@ -46,7 +46,8 @@ Object {
     "id1": true,
     "id2": true,
     "id3": true,
-    "id5": true,
+    "id4": true,
+    "id6": true,
   },
   "resizingElement": null,
   "scrollX": 0,
@@ -55,10 +56,10 @@ Object {
   "selectedElementIds": Object {
     "id0": true,
     "id2": true,
-    "id6": true,
+    "id7": true,
   },
   "selectedGroupIds": Object {
-    "id4": true,
+    "id5": true,
   },
   "selectionElement": null,
   "shouldAddWatermark": false,
@@ -81,7 +82,7 @@ Object {
   "boundElementIds": null,
   "fillStyle": "hachure",
   "groupIds": Array [],
-  "height": 100,
+  "height": 10,
   "id": "id1",
   "isDeleted": false,
   "opacity": 100,
@@ -94,9 +95,9 @@ Object {
   "type": "rectangle",
   "version": 2,
   "versionNonce": 453191,
-  "width": 100,
-  "x": 110,
-  "y": 110,
+  "width": 10,
+  "x": 0,
+  "y": 30,
 }
 `;
 
@@ -107,9 +108,9 @@ Object {
   "boundElementIds": null,
   "fillStyle": "hachure",
   "groupIds": Array [
-    "id4",
+    "id5",
   ],
-  "height": 100,
+  "height": 10,
   "id": "id0",
   "isDeleted": false,
   "opacity": 100,
@@ -121,8 +122,8 @@ Object {
   "strokeWidth": 1,
   "type": "rectangle",
   "version": 3,
-  "versionNonce": 1116226695,
-  "width": 100,
+  "versionNonce": 1014066025,
+  "width": 10,
   "x": 0,
   "y": 0,
 }
@@ -135,9 +136,9 @@ Object {
   "boundElementIds": null,
   "fillStyle": "hachure",
   "groupIds": Array [
-    "id4",
+    "id5",
   ],
-  "height": 100,
+  "height": 10,
   "id": "id2",
   "isDeleted": false,
   "opacity": 100,
@@ -149,10 +150,10 @@ Object {
   "strokeWidth": 1,
   "type": "rectangle",
   "version": 3,
-  "versionNonce": 1014066025,
-  "width": 100,
-  "x": 220,
-  "y": 220,
+  "versionNonce": 238820263,
+  "width": 10,
+  "x": 0,
+  "y": 60,
 }
 `;
 
@@ -178,7 +179,7 @@ Object {
           "boundElementIds": null,
           "fillStyle": "hachure",
           "groupIds": Array [],
-          "height": 100,
+          "height": 10,
           "id": "id0",
           "isDeleted": false,
           "opacity": 100,
@@ -191,7 +192,7 @@ Object {
           "type": "rectangle",
           "version": 2,
           "versionNonce": 1278240551,
-          "width": 100,
+          "width": 10,
           "x": 0,
           "y": 0,
         },
@@ -214,7 +215,7 @@ Object {
           "boundElementIds": null,
           "fillStyle": "hachure",
           "groupIds": Array [],
-          "height": 100,
+          "height": 10,
           "id": "id0",
           "isDeleted": false,
           "opacity": 100,
@@ -227,7 +228,7 @@ Object {
           "type": "rectangle",
           "version": 2,
           "versionNonce": 1278240551,
-          "width": 100,
+          "width": 10,
           "x": 0,
           "y": 0,
         },
@@ -237,7 +238,7 @@ Object {
           "boundElementIds": null,
           "fillStyle": "hachure",
           "groupIds": Array [],
-          "height": 100,
+          "height": 10,
           "id": "id1",
           "isDeleted": false,
           "opacity": 100,
@@ -250,9 +251,9 @@ Object {
           "type": "rectangle",
           "version": 2,
           "versionNonce": 453191,
-          "width": 100,
-          "x": 110,
-          "y": 110,
+          "width": 10,
+          "x": 0,
+          "y": 30,
         },
       ],
     },
@@ -273,7 +274,7 @@ Object {
           "boundElementIds": null,
           "fillStyle": "hachure",
           "groupIds": Array [],
-          "height": 100,
+          "height": 10,
           "id": "id0",
           "isDeleted": false,
           "opacity": 100,
@@ -286,7 +287,7 @@ Object {
           "type": "rectangle",
           "version": 2,
           "versionNonce": 1278240551,
-          "width": 100,
+          "width": 10,
           "x": 0,
           "y": 0,
         },
@@ -296,7 +297,7 @@ Object {
           "boundElementIds": null,
           "fillStyle": "hachure",
           "groupIds": Array [],
-          "height": 100,
+          "height": 10,
           "id": "id1",
           "isDeleted": false,
           "opacity": 100,
@@ -309,9 +310,9 @@ Object {
           "type": "rectangle",
           "version": 2,
           "versionNonce": 453191,
-          "width": 100,
-          "x": 110,
-          "y": 110,
+          "width": 10,
+          "x": 0,
+          "y": 30,
         },
         Object {
           "angle": 0,
@@ -319,7 +320,7 @@ Object {
           "boundElementIds": null,
           "fillStyle": "hachure",
           "groupIds": Array [],
-          "height": 100,
+          "height": 10,
           "id": "id2",
           "isDeleted": false,
           "opacity": 100,
@@ -332,9 +333,9 @@ Object {
           "type": "rectangle",
           "version": 2,
           "versionNonce": 2019559783,
-          "width": 100,
-          "x": 220,
-          "y": 220,
+          "width": 10,
+          "x": 0,
+          "y": 60,
         },
       ],
     },
@@ -347,6 +348,7 @@ Object {
           "id0": true,
           "id2": true,
           "id3": true,
+          "id4": true,
         },
         "viewBackgroundColor": "#ffffff",
       },
@@ -357,7 +359,7 @@ Object {
           "boundElementIds": null,
           "fillStyle": "hachure",
           "groupIds": Array [],
-          "height": 100,
+          "height": 10,
           "id": "id1",
           "isDeleted": false,
           "opacity": 100,
@@ -370,9 +372,9 @@ Object {
           "type": "rectangle",
           "version": 2,
           "versionNonce": 453191,
-          "width": 100,
-          "x": 110,
-          "y": 110,
+          "width": 10,
+          "x": 0,
+          "y": 30,
         },
         Object {
           "angle": 0,
@@ -380,9 +382,9 @@ Object {
           "boundElementIds": null,
           "fillStyle": "hachure",
           "groupIds": Array [
-            "id4",
+            "id5",
           ],
-          "height": 100,
+          "height": 10,
           "id": "id0",
           "isDeleted": false,
           "opacity": 100,
@@ -394,8 +396,8 @@ Object {
           "strokeWidth": 1,
           "type": "rectangle",
           "version": 3,
-          "versionNonce": 1116226695,
-          "width": 100,
+          "versionNonce": 1014066025,
+          "width": 10,
           "x": 0,
           "y": 0,
         },
@@ -405,9 +407,9 @@ Object {
           "boundElementIds": null,
           "fillStyle": "hachure",
           "groupIds": Array [
-            "id4",
+            "id5",
           ],
-          "height": 100,
+          "height": 10,
           "id": "id2",
           "isDeleted": false,
           "opacity": 100,
@@ -419,10 +421,10 @@ Object {
           "strokeWidth": 1,
           "type": "rectangle",
           "version": 3,
-          "versionNonce": 1014066025,
-          "width": 100,
-          "x": 220,
-          "y": 220,
+          "versionNonce": 238820263,
+          "width": 10,
+          "x": 0,
+          "y": 60,
         },
       ],
     },
@@ -432,7 +434,7 @@ Object {
 
 exports[`given element A and group of elements B and given both are selected when user clicks on B, on pointer up only elements from B should be selected: [end of test] number of elements 1`] = `3`;
 
-exports[`given element A and group of elements B and given both are selected when user clicks on B, on pointer up only elements from B should be selected: [end of test] number of renders 1`] = `22`;
+exports[`given element A and group of elements B and given both are selected when user clicks on B, on pointer up only elements from B should be selected: [end of test] number of renders 1`] = `26`;
 
 exports[`given element A and group of elements B and given both are selected when user shift-clicks on B, on pointer up only element A should be selected: [end of test] appState 1`] = `
 Object {
@@ -24495,6 +24497,802 @@ exports[`regression tests shows context menu for element: [end of test] number o
 
 exports[`regression tests shows context menu for element: [end of test] number of renders 1`] = `7`;
 
+exports[`regression tests single-clicking on a subgroup of a selected group should not alter selection: [end of test] appState 1`] = `
+Object {
+  "appearance": "light",
+  "collaborators": Map {},
+  "currentItemBackgroundColor": "transparent",
+  "currentItemFillStyle": "hachure",
+  "currentItemFontFamily": 1,
+  "currentItemFontSize": 20,
+  "currentItemLinearStrokeSharpness": "round",
+  "currentItemOpacity": 100,
+  "currentItemRoughness": 1,
+  "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,
+  "gridSize": null,
+  "height": 768,
+  "isBindingEnabled": true,
+  "isCollaborating": false,
+  "isLibraryOpen": false,
+  "isLoading": false,
+  "isResizing": false,
+  "isRotating": false,
+  "lastPointerDownWith": "mouse",
+  "multiElement": null,
+  "name": "Untitled-201933152653",
+  "offsetLeft": 0,
+  "offsetTop": 0,
+  "openMenu": null,
+  "previousSelectedElementIds": Object {
+    "id0": true,
+    "id1": true,
+    "id5": true,
+    "id6": true,
+  },
+  "resizingElement": null,
+  "scrollX": 0,
+  "scrollY": 0,
+  "scrolledOutside": false,
+  "selectedElementIds": Object {
+    "id0": true,
+    "id1": true,
+    "id11": true,
+    "id5": true,
+    "id6": true,
+  },
+  "selectedGroupIds": Object {
+    "id10": true,
+  },
+  "selectionElement": null,
+  "shouldAddWatermark": false,
+  "shouldCacheIgnoreZoom": false,
+  "showShortcutsDialog": false,
+  "startBoundElement": null,
+  "suggestedBindings": Array [],
+  "username": "",
+  "viewBackgroundColor": "#ffffff",
+  "width": 1024,
+  "zenModeEnabled": false,
+  "zoom": 1,
+}
+`;
+
+exports[`regression tests single-clicking on a subgroup of a selected group should not alter selection: [end of test] element 0 1`] = `
+Object {
+  "angle": 0,
+  "backgroundColor": "transparent",
+  "boundElementIds": null,
+  "fillStyle": "hachure",
+  "groupIds": Array [
+    "id4",
+    "id10",
+  ],
+  "height": 10,
+  "id": "id0",
+  "isDeleted": false,
+  "opacity": 100,
+  "roughness": 1,
+  "seed": 337897,
+  "strokeColor": "#000000",
+  "strokeSharpness": "sharp",
+  "strokeStyle": "solid",
+  "strokeWidth": 1,
+  "type": "rectangle",
+  "version": 4,
+  "versionNonce": 81784553,
+  "width": 10,
+  "x": 10,
+  "y": 10,
+}
+`;
+
+exports[`regression tests single-clicking on a subgroup of a selected group should not alter selection: [end of test] element 1 1`] = `
+Object {
+  "angle": 0,
+  "backgroundColor": "transparent",
+  "boundElementIds": null,
+  "fillStyle": "hachure",
+  "groupIds": Array [
+    "id4",
+    "id10",
+  ],
+  "height": 10,
+  "id": "id1",
+  "isDeleted": false,
+  "opacity": 100,
+  "roughness": 1,
+  "seed": 449462985,
+  "strokeColor": "#000000",
+  "strokeSharpness": "sharp",
+  "strokeStyle": "solid",
+  "strokeWidth": 1,
+  "type": "rectangle",
+  "version": 4,
+  "versionNonce": 747212839,
+  "width": 10,
+  "x": 50,
+  "y": 50,
+}
+`;
+
+exports[`regression tests single-clicking on a subgroup of a selected group should not alter selection: [end of test] element 2 1`] = `
+Object {
+  "angle": 0,
+  "backgroundColor": "transparent",
+  "boundElementIds": null,
+  "fillStyle": "hachure",
+  "groupIds": Array [
+    "id9",
+    "id10",
+  ],
+  "height": 10,
+  "id": "id5",
+  "isDeleted": false,
+  "opacity": 100,
+  "roughness": 1,
+  "seed": 1014066025,
+  "strokeColor": "#000000",
+  "strokeSharpness": "sharp",
+  "strokeStyle": "solid",
+  "strokeWidth": 1,
+  "type": "rectangle",
+  "version": 4,
+  "versionNonce": 1723083209,
+  "width": 10,
+  "x": 10,
+  "y": 50,
+}
+`;
+
+exports[`regression tests single-clicking on a subgroup of a selected group should not alter selection: [end of test] element 3 1`] = `
+Object {
+  "angle": 0,
+  "backgroundColor": "transparent",
+  "boundElementIds": null,
+  "fillStyle": "hachure",
+  "groupIds": Array [
+    "id9",
+    "id10",
+  ],
+  "height": 10,
+  "id": "id6",
+  "isDeleted": false,
+  "opacity": 100,
+  "roughness": 1,
+  "seed": 400692809,
+  "strokeColor": "#000000",
+  "strokeSharpness": "sharp",
+  "strokeStyle": "solid",
+  "strokeWidth": 1,
+  "type": "rectangle",
+  "version": 4,
+  "versionNonce": 760410951,
+  "width": 10,
+  "x": 50,
+  "y": 50,
+}
+`;
+
+exports[`regression tests single-clicking on a subgroup of a selected group should not alter selection: [end of test] history 1`] = `
+Object {
+  "recording": false,
+  "redoStack": Array [],
+  "stateHistory": 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": 10,
+          "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": 10,
+          "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": 10,
+          "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": 10,
+          "x": 10,
+          "y": 10,
+        },
+        Object {
+          "angle": 0,
+          "backgroundColor": "transparent",
+          "boundElementIds": null,
+          "fillStyle": "hachure",
+          "groupIds": Array [],
+          "height": 10,
+          "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": 10,
+          "x": 50,
+          "y": 50,
+        },
+      ],
+    },
+    Object {
+      "appState": Object {
+        "editingGroupId": null,
+        "editingLinearElement": null,
+        "name": "Untitled-201933152653",
+        "selectedElementIds": Object {
+          "id0": true,
+          "id1": true,
+          "id2": true,
+          "id3": true,
+        },
+        "viewBackgroundColor": "#ffffff",
+      },
+      "elements": Array [
+        Object {
+          "angle": 0,
+          "backgroundColor": "transparent",
+          "boundElementIds": null,
+          "fillStyle": "hachure",
+          "groupIds": Array [
+            "id4",
+          ],
+          "height": 10,
+          "id": "id0",
+          "isDeleted": false,
+          "opacity": 100,
+          "roughness": 1,
+          "seed": 337897,
+          "strokeColor": "#000000",
+          "strokeSharpness": "sharp",
+          "strokeStyle": "solid",
+          "strokeWidth": 1,
+          "type": "rectangle",
+          "version": 3,
+          "versionNonce": 1150084233,
+          "width": 10,
+          "x": 10,
+          "y": 10,
+        },
+        Object {
+          "angle": 0,
+          "backgroundColor": "transparent",
+          "boundElementIds": null,
+          "fillStyle": "hachure",
+          "groupIds": Array [
+            "id4",
+          ],
+          "height": 10,
+          "id": "id1",
+          "isDeleted": false,
+          "opacity": 100,
+          "roughness": 1,
+          "seed": 449462985,
+          "strokeColor": "#000000",
+          "strokeSharpness": "sharp",
+          "strokeStyle": "solid",
+          "strokeWidth": 1,
+          "type": "rectangle",
+          "version": 3,
+          "versionNonce": 1116226695,
+          "width": 10,
+          "x": 50,
+          "y": 50,
+        },
+      ],
+    },
+    Object {
+      "appState": Object {
+        "editingGroupId": null,
+        "editingLinearElement": null,
+        "name": "Untitled-201933152653",
+        "selectedElementIds": Object {
+          "id5": true,
+        },
+        "viewBackgroundColor": "#ffffff",
+      },
+      "elements": Array [
+        Object {
+          "angle": 0,
+          "backgroundColor": "transparent",
+          "boundElementIds": null,
+          "fillStyle": "hachure",
+          "groupIds": Array [
+            "id4",
+          ],
+          "height": 10,
+          "id": "id0",
+          "isDeleted": false,
+          "opacity": 100,
+          "roughness": 1,
+          "seed": 337897,
+          "strokeColor": "#000000",
+          "strokeSharpness": "sharp",
+          "strokeStyle": "solid",
+          "strokeWidth": 1,
+          "type": "rectangle",
+          "version": 3,
+          "versionNonce": 1150084233,
+          "width": 10,
+          "x": 10,
+          "y": 10,
+        },
+        Object {
+          "angle": 0,
+          "backgroundColor": "transparent",
+          "boundElementIds": null,
+          "fillStyle": "hachure",
+          "groupIds": Array [
+            "id4",
+          ],
+          "height": 10,
+          "id": "id1",
+          "isDeleted": false,
+          "opacity": 100,
+          "roughness": 1,
+          "seed": 449462985,
+          "strokeColor": "#000000",
+          "strokeSharpness": "sharp",
+          "strokeStyle": "solid",
+          "strokeWidth": 1,
+          "type": "rectangle",
+          "version": 3,
+          "versionNonce": 1116226695,
+          "width": 10,
+          "x": 50,
+          "y": 50,
+        },
+        Object {
+          "angle": 0,
+          "backgroundColor": "transparent",
+          "boundElementIds": null,
+          "fillStyle": "hachure",
+          "groupIds": Array [],
+          "height": 10,
+          "id": "id5",
+          "isDeleted": false,
+          "opacity": 100,
+          "roughness": 1,
+          "seed": 1014066025,
+          "strokeColor": "#000000",
+          "strokeSharpness": "sharp",
+          "strokeStyle": "solid",
+          "strokeWidth": 1,
+          "type": "rectangle",
+          "version": 2,
+          "versionNonce": 238820263,
+          "width": 10,
+          "x": 10,
+          "y": 50,
+        },
+      ],
+    },
+    Object {
+      "appState": Object {
+        "editingGroupId": null,
+        "editingLinearElement": null,
+        "name": "Untitled-201933152653",
+        "selectedElementIds": Object {
+          "id6": true,
+        },
+        "viewBackgroundColor": "#ffffff",
+      },
+      "elements": Array [
+        Object {
+          "angle": 0,
+          "backgroundColor": "transparent",
+          "boundElementIds": null,
+          "fillStyle": "hachure",
+          "groupIds": Array [
+            "id4",
+          ],
+          "height": 10,
+          "id": "id0",
+          "isDeleted": false,
+          "opacity": 100,
+          "roughness": 1,
+          "seed": 337897,
+          "strokeColor": "#000000",
+          "strokeSharpness": "sharp",
+          "strokeStyle": "solid",
+          "strokeWidth": 1,
+          "type": "rectangle",
+          "version": 3,
+          "versionNonce": 1150084233,
+          "width": 10,
+          "x": 10,
+          "y": 10,
+        },
+        Object {
+          "angle": 0,
+          "backgroundColor": "transparent",
+          "boundElementIds": null,
+          "fillStyle": "hachure",
+          "groupIds": Array [
+            "id4",
+          ],
+          "height": 10,
+          "id": "id1",
+          "isDeleted": false,
+          "opacity": 100,
+          "roughness": 1,
+          "seed": 449462985,
+          "strokeColor": "#000000",
+          "strokeSharpness": "sharp",
+          "strokeStyle": "solid",
+          "strokeWidth": 1,
+          "type": "rectangle",
+          "version": 3,
+          "versionNonce": 1116226695,
+          "width": 10,
+          "x": 50,
+          "y": 50,
+        },
+        Object {
+          "angle": 0,
+          "backgroundColor": "transparent",
+          "boundElementIds": null,
+          "fillStyle": "hachure",
+          "groupIds": Array [],
+          "height": 10,
+          "id": "id5",
+          "isDeleted": false,
+          "opacity": 100,
+          "roughness": 1,
+          "seed": 1014066025,
+          "strokeColor": "#000000",
+          "strokeSharpness": "sharp",
+          "strokeStyle": "solid",
+          "strokeWidth": 1,
+          "type": "rectangle",
+          "version": 2,
+          "versionNonce": 238820263,
+          "width": 10,
+          "x": 10,
+          "y": 50,
+        },
+        Object {
+          "angle": 0,
+          "backgroundColor": "transparent",
+          "boundElementIds": null,
+          "fillStyle": "hachure",
+          "groupIds": Array [],
+          "height": 10,
+          "id": "id6",
+          "isDeleted": false,
+          "opacity": 100,
+          "roughness": 1,
+          "seed": 400692809,
+          "strokeColor": "#000000",
+          "strokeSharpness": "sharp",
+          "strokeStyle": "solid",
+          "strokeWidth": 1,
+          "type": "rectangle",
+          "version": 2,
+          "versionNonce": 1604849351,
+          "width": 10,
+          "x": 50,
+          "y": 50,
+        },
+      ],
+    },
+    Object {
+      "appState": Object {
+        "editingGroupId": null,
+        "editingLinearElement": null,
+        "name": "Untitled-201933152653",
+        "selectedElementIds": Object {
+          "id5": true,
+          "id6": true,
+          "id7": true,
+          "id8": true,
+        },
+        "viewBackgroundColor": "#ffffff",
+      },
+      "elements": Array [
+        Object {
+          "angle": 0,
+          "backgroundColor": "transparent",
+          "boundElementIds": null,
+          "fillStyle": "hachure",
+          "groupIds": Array [
+            "id4",
+          ],
+          "height": 10,
+          "id": "id0",
+          "isDeleted": false,
+          "opacity": 100,
+          "roughness": 1,
+          "seed": 337897,
+          "strokeColor": "#000000",
+          "strokeSharpness": "sharp",
+          "strokeStyle": "solid",
+          "strokeWidth": 1,
+          "type": "rectangle",
+          "version": 3,
+          "versionNonce": 1150084233,
+          "width": 10,
+          "x": 10,
+          "y": 10,
+        },
+        Object {
+          "angle": 0,
+          "backgroundColor": "transparent",
+          "boundElementIds": null,
+          "fillStyle": "hachure",
+          "groupIds": Array [
+            "id4",
+          ],
+          "height": 10,
+          "id": "id1",
+          "isDeleted": false,
+          "opacity": 100,
+          "roughness": 1,
+          "seed": 449462985,
+          "strokeColor": "#000000",
+          "strokeSharpness": "sharp",
+          "strokeStyle": "solid",
+          "strokeWidth": 1,
+          "type": "rectangle",
+          "version": 3,
+          "versionNonce": 1116226695,
+          "width": 10,
+          "x": 50,
+          "y": 50,
+        },
+        Object {
+          "angle": 0,
+          "backgroundColor": "transparent",
+          "boundElementIds": null,
+          "fillStyle": "hachure",
+          "groupIds": Array [
+            "id9",
+          ],
+          "height": 10,
+          "id": "id5",
+          "isDeleted": false,
+          "opacity": 100,
+          "roughness": 1,
+          "seed": 1014066025,
+          "strokeColor": "#000000",
+          "strokeSharpness": "sharp",
+          "strokeStyle": "solid",
+          "strokeWidth": 1,
+          "type": "rectangle",
+          "version": 3,
+          "versionNonce": 493213705,
+          "width": 10,
+          "x": 10,
+          "y": 50,
+        },
+        Object {
+          "angle": 0,
+          "backgroundColor": "transparent",
+          "boundElementIds": null,
+          "fillStyle": "hachure",
+          "groupIds": Array [
+            "id9",
+          ],
+          "height": 10,
+          "id": "id6",
+          "isDeleted": false,
+          "opacity": 100,
+          "roughness": 1,
+          "seed": 400692809,
+          "strokeColor": "#000000",
+          "strokeSharpness": "sharp",
+          "strokeStyle": "solid",
+          "strokeWidth": 1,
+          "type": "rectangle",
+          "version": 3,
+          "versionNonce": 915032327,
+          "width": 10,
+          "x": 50,
+          "y": 50,
+        },
+      ],
+    },
+    Object {
+      "appState": Object {
+        "editingGroupId": null,
+        "editingLinearElement": null,
+        "name": "Untitled-201933152653",
+        "selectedElementIds": Object {
+          "id0": true,
+          "id1": true,
+          "id5": true,
+          "id6": true,
+        },
+        "viewBackgroundColor": "#ffffff",
+      },
+      "elements": Array [
+        Object {
+          "angle": 0,
+          "backgroundColor": "transparent",
+          "boundElementIds": null,
+          "fillStyle": "hachure",
+          "groupIds": Array [
+            "id4",
+            "id10",
+          ],
+          "height": 10,
+          "id": "id0",
+          "isDeleted": false,
+          "opacity": 100,
+          "roughness": 1,
+          "seed": 337897,
+          "strokeColor": "#000000",
+          "strokeSharpness": "sharp",
+          "strokeStyle": "solid",
+          "strokeWidth": 1,
+          "type": "rectangle",
+          "version": 4,
+          "versionNonce": 81784553,
+          "width": 10,
+          "x": 10,
+          "y": 10,
+        },
+        Object {
+          "angle": 0,
+          "backgroundColor": "transparent",
+          "boundElementIds": null,
+          "fillStyle": "hachure",
+          "groupIds": Array [
+            "id4",
+            "id10",
+          ],
+          "height": 10,
+          "id": "id1",
+          "isDeleted": false,
+          "opacity": 100,
+          "roughness": 1,
+          "seed": 449462985,
+          "strokeColor": "#000000",
+          "strokeSharpness": "sharp",
+          "strokeStyle": "solid",
+          "strokeWidth": 1,
+          "type": "rectangle",
+          "version": 4,
+          "versionNonce": 747212839,
+          "width": 10,
+          "x": 50,
+          "y": 50,
+        },
+        Object {
+          "angle": 0,
+          "backgroundColor": "transparent",
+          "boundElementIds": null,
+          "fillStyle": "hachure",
+          "groupIds": Array [
+            "id9",
+            "id10",
+          ],
+          "height": 10,
+          "id": "id5",
+          "isDeleted": false,
+          "opacity": 100,
+          "roughness": 1,
+          "seed": 1014066025,
+          "strokeColor": "#000000",
+          "strokeSharpness": "sharp",
+          "strokeStyle": "solid",
+          "strokeWidth": 1,
+          "type": "rectangle",
+          "version": 4,
+          "versionNonce": 1723083209,
+          "width": 10,
+          "x": 10,
+          "y": 50,
+        },
+        Object {
+          "angle": 0,
+          "backgroundColor": "transparent",
+          "boundElementIds": null,
+          "fillStyle": "hachure",
+          "groupIds": Array [
+            "id9",
+            "id10",
+          ],
+          "height": 10,
+          "id": "id6",
+          "isDeleted": false,
+          "opacity": 100,
+          "roughness": 1,
+          "seed": 400692809,
+          "strokeColor": "#000000",
+          "strokeSharpness": "sharp",
+          "strokeStyle": "solid",
+          "strokeWidth": 1,
+          "type": "rectangle",
+          "version": 4,
+          "versionNonce": 760410951,
+          "width": 10,
+          "x": 50,
+          "y": 50,
+        },
+      ],
+    },
+  ],
+}
+`;
+
+exports[`regression tests single-clicking on a subgroup of a selected group should not alter selection: [end of test] number of elements 1`] = `4`;
+
+exports[`regression tests single-clicking on a subgroup of a selected group should not alter selection: [end of test] number of renders 1`] = `37`;
+
 exports[`regression tests spacebar + drag scrolls the canvas: [end of test] appState 1`] = `
 Object {
   "appearance": "light",

+ 80 - 36
src/tests/regressionTests.test.tsx

@@ -23,6 +23,33 @@ const clickTool = (toolName: ToolName) => {
   fireEvent.click(getByToolName(toolName));
 };
 
+const createElement = (
+  type: ToolName,
+  {
+    x = 0,
+    y = x,
+    size = 10,
+  }: {
+    x?: number;
+    y?: number;
+    size?: number;
+  },
+) => {
+  clickTool(type);
+  mouse.reset();
+  mouse.down(x, y);
+  mouse.reset();
+  mouse.up(x + size, y + size);
+  return h.elements[h.elements.length - 1];
+};
+
+const group = (elements: ExcalidrawElement[]) => {
+  mouse.select(elements);
+  withModifierKeys({ ctrl: true }, () => {
+    keyPress("g");
+  });
+};
+
 let altKey = false;
 let shiftKey = false;
 let ctrlKey = false;
@@ -159,6 +186,28 @@ class Pointer {
     this.move(dx, dy);
     fireEvent.doubleClick(canvas, this.getEvent());
   }
+
+  select(
+    /** if multiple elements supplied, they're shift-selected */
+    elements: ExcalidrawElement | ExcalidrawElement[],
+  ) {
+    // @ts-ignore
+    h.app.clearSelection(null);
+    withModifierKeys({ shift: true }, () => {
+      elements = Array.isArray(elements) ? elements : [elements];
+      elements.forEach((element) => {
+        mouse.reset();
+        mouse.click(element.x, element.y);
+      });
+    });
+    mouse.reset();
+  }
+
+  clickOn(element: ExcalidrawElement) {
+    mouse.reset();
+    mouse.click(element.x, element.y);
+    mouse.reset();
+  }
 }
 
 const mouse = new Pointer("mouse");
@@ -1579,6 +1628,27 @@ describe("regression tests", () => {
     });
     expect(getSelectedElements().length).toBe(0);
   });
+
+  it("single-clicking on a subgroup of a selected group should not alter selection", () => {
+    const rect1 = createElement("rectangle", { x: 10 });
+    const rect2 = createElement("rectangle", { x: 50 });
+    group([rect1, rect2]);
+
+    const rect3 = createElement("rectangle", { x: 10, y: 50 });
+    const rect4 = createElement("rectangle", { x: 50, y: 50 });
+    group([rect3, rect4]);
+
+    withModifierKeys({ ctrl: true }, () => {
+      keyPress("a");
+      keyPress("g");
+    });
+
+    const selectedGroupIds_prev = h.state.selectedGroupIds;
+    const selectedElements_prev = getSelectedElements();
+    mouse.clickOn(rect3);
+    expect(h.state.selectedGroupIds).toEqual(selectedGroupIds_prev);
+    expect(getSelectedElements()).toEqual(selectedElements_prev);
+  });
 });
 
 it(
@@ -1586,52 +1656,26 @@ it(
     "when user clicks on B, on pointer up " +
     "only elements from B should be selected",
   () => {
-    clickTool("rectangle");
-    mouse.down();
-    mouse.up(100, 100);
+    const rect1 = createElement("rectangle", { y: 0 });
+    const rect2 = createElement("rectangle", { y: 30 });
+    const rect3 = createElement("rectangle", { y: 60 });
 
-    clickTool("rectangle");
-    mouse.down(10, 10);
-    mouse.up(100, 100);
-
-    clickTool("rectangle");
-    mouse.down(10, 10);
-    mouse.up(100, 100);
-
-    // Select first rectangle while keeping third one selected.
-    // Third rectangle is selected because it was the last element
-    //  to be created.
-    mouse.reset();
-    withModifierKeys({ shift: true }, () => {
-      mouse.click();
-    });
-
-    // Create group with first and third rectangle
-    withModifierKeys({ ctrl: true }, () => {
-      keyPress("g");
-    });
+    group([rect1, rect3]);
 
     expect(getSelectedElements().length).toBe(2);
-    const selectedGroupIds = Object.keys(h.state.selectedGroupIds);
-    expect(selectedGroupIds.length).toBe(1);
+    expect(Object.keys(h.state.selectedGroupIds).length).toBe(1);
 
     // Select second rectangle without deselecting group
     withModifierKeys({ shift: true }, () => {
-      mouse.click(110, 110);
+      mouse.clickOn(rect2);
     });
     expect(getSelectedElements().length).toBe(3);
 
-    // pointer down on first rectangle that is
-    // part of the group
-    mouse.reset();
-    mouse.down();
-    expect(getSelectedElements().length).toBe(3);
-
-    // should only deselect on pointer up
-    mouse.up();
+    // clicking on first rectangle that is part of the group should select
+    //  that group (exclusively)
+    mouse.clickOn(rect1);
     expect(getSelectedElements().length).toBe(2);
-    const newSelectedGroupIds = Object.keys(h.state.selectedGroupIds);
-    expect(newSelectedGroupIds.length).toBe(1);
+    expect(Object.keys(h.state.selectedGroupIds).length).toBe(1);
   },
 );