瀏覽代碼

feat: add undo/redo buttons & tweak footer (#3832)

Co-authored-by: Aakansha Doshi <aakansha1216@gmail.com>
David Luzar 3 年之前
父節點
當前提交
99623334d1

+ 19 - 11
src/actions/actionCanvas.tsx

@@ -1,7 +1,7 @@
 import React from "react";
 import { getDefaultAppState } from "../appState";
 import { ColorPicker } from "../components/ColorPicker";
-import { resetZoom, trash, zoomIn, zoomOut } from "../components/icons";
+import { trash, zoomIn, zoomOut } from "../components/icons";
 import { ToolButton } from "../components/ToolButton";
 import { DarkModeToggle } from "../components/DarkModeToggle";
 import { ZOOM_STEP } from "../constants";
@@ -17,6 +17,7 @@ import { getNewZoom } from "../scene/zoom";
 import { AppState, NormalizedZoomValue } from "../types";
 import { getShortcutKey } from "../utils";
 import { register } from "./register";
+import { Tooltip } from "../components/Tooltip";
 
 export const actionChangeViewBackgroundColor = register({
   name: "changeViewBackgroundColor",
@@ -108,6 +109,7 @@ export const actionZoomIn = register({
       onClick={() => {
         updateData(null);
       }}
+      size="small"
     />
   ),
   keyTest: (event) =>
@@ -142,6 +144,7 @@ export const actionZoomOut = register({
       onClick={() => {
         updateData(null);
       }}
+      size="small"
     />
   ),
   keyTest: (event) =>
@@ -168,16 +171,21 @@ export const actionResetZoom = register({
       commitToHistory: false,
     };
   },
-  PanelComponent: ({ updateData }) => (
-    <ToolButton
-      type="button"
-      icon={resetZoom}
-      title={t("buttons.resetZoom")}
-      aria-label={t("buttons.resetZoom")}
-      onClick={() => {
-        updateData(null);
-      }}
-    />
+  PanelComponent: ({ updateData, appState }) => (
+    <Tooltip label={t("buttons.resetZoom")}>
+      <ToolButton
+        type="button"
+        className="reset-zoom-button"
+        title={t("buttons.resetZoom")}
+        aria-label={t("buttons.resetZoom")}
+        onClick={() => {
+          updateData(null);
+        }}
+        size="small"
+      >
+        {(appState.zoom.value * 100).toFixed(0)}%
+      </ToolButton>
+    </Tooltip>
   ),
   keyTest: (event) =>
     (event.code === CODES.ZERO || event.code === CODES.NUM_ZERO) &&

+ 2 - 2
src/actions/actionExport.tsx

@@ -70,7 +70,7 @@ export const actionChangeExportScale = register({
           return (
             <ToolButton
               key={s}
-              size="s"
+              size="small"
               type="radio"
               icon={`${s}x`}
               name="export-canvas-scale"
@@ -120,7 +120,7 @@ export const actionChangeExportEmbedScene = register({
     >
       {t("labels.exportEmbedScene")}
       <Tooltip label={t("labels.exportEmbedScene_details")} long={true}>
-        <div className="Tooltip-icon">{questionCircle}</div>
+        <div className="excalidraw-tooltip-icon">{questionCircle}</div>
       </Tooltip>
     </CheckboxItem>
   ),

+ 4 - 2
src/actions/actionHistory.tsx

@@ -69,12 +69,13 @@ export const createUndoAction: ActionCreator = (history) => ({
     event[KEYS.CTRL_OR_CMD] &&
     event.key.toLowerCase() === KEYS.Z &&
     !event.shiftKey,
-  PanelComponent: ({ updateData }) => (
+  PanelComponent: ({ updateData, data }) => (
     <ToolButton
       type="button"
       icon={undo}
       aria-label={t("buttons.undo")}
       onClick={updateData}
+      size={data?.size || "medium"}
     />
   ),
   commitToHistory: () => false,
@@ -89,12 +90,13 @@ export const createRedoAction: ActionCreator = (history) => ({
       event.shiftKey &&
       event.key.toLowerCase() === KEYS.Z) ||
     (isWindows && event.ctrlKey && !event.shiftKey && event.key === KEYS.Y),
-  PanelComponent: ({ updateData }) => (
+  PanelComponent: ({ updateData, data }) => (
     <ToolButton
       type="button"
       icon={redo}
       aria-label={t("buttons.redo")}
       onClick={updateData}
+      size={data?.size || "medium"}
     />
   ),
   commitToHistory: () => false,

+ 2 - 2
src/actions/actionNavigate.tsx

@@ -30,8 +30,8 @@ export const actionGoToCollaborator = register({
       commitToHistory: false,
     };
   },
-  PanelComponent: ({ appState, updateData, id }) => {
-    const clientId = id;
+  PanelComponent: ({ appState, updateData, data }) => {
+    const clientId: string | undefined = data?.id;
     if (!clientId) {
       return null;
     }

+ 6 - 6
src/actions/manager.tsx

@@ -5,6 +5,7 @@ import {
   UpdaterFn,
   ActionName,
   ActionResult,
+  PanelComponentProps,
 } from "./types";
 import { ExcalidrawElement } from "../element/types";
 import { AppProps, AppState } from "../types";
@@ -107,11 +108,10 @@ export class ActionManager implements ActionsManagerInterface {
     );
   }
 
-  // Id is an attribute that we can use to pass in data like keys.
-  // This is needed for dynamically generated action components
-  // like the user list. We can use this key to extract more
-  // data from app state. This is an alternative to generic prop hell!
-  renderAction = (name: ActionName, id?: string) => {
+  /**
+   * @param data additional data sent to the PanelComponent
+   */
+  renderAction = (name: ActionName, data?: PanelComponentProps["data"]) => {
     const canvasActions = this.app.props.UIOptions.canvasActions;
 
     if (
@@ -139,8 +139,8 @@ export class ActionManager implements ActionsManagerInterface {
           elements={this.getElementsIncludingDeleted()}
           appState={this.getAppState()}
           updateData={updateData}
-          id={id}
           appProps={this.app.props}
+          data={data}
         />
       );
     }

+ 10 - 7
src/actions/types.ts

@@ -2,6 +2,7 @@ import React from "react";
 import { ExcalidrawElement } from "../element/types";
 import { AppState, ExcalidrawProps } from "../types";
 import Library from "../data/library";
+import { ToolButtonSize } from "../components/ToolButton";
 
 /** if false, the action should be prevented */
 export type ActionResult =
@@ -102,15 +103,17 @@ export type ActionName =
   | "exportWithDarkMode"
   | "toggleTheme";
 
+export type PanelComponentProps = {
+  elements: readonly ExcalidrawElement[];
+  appState: AppState;
+  updateData: (formData?: any) => void;
+  appProps: ExcalidrawProps;
+  data?: Partial<{ id: string; size: ToolButtonSize }>;
+};
+
 export interface Action {
   name: ActionName;
-  PanelComponent?: React.FC<{
-    elements: readonly ExcalidrawElement[];
-    appState: AppState;
-    updateData: (formData?: any) => void;
-    appProps: ExcalidrawProps;
-    id?: string;
-  }>;
+  PanelComponent?: React.FC<PanelComponentProps>;
   perform: ActionFn;
   keyPriority?: number;
   keyTest?: (

+ 0 - 3
src/components/Actions.tsx

@@ -207,9 +207,6 @@ export const ZoomActions = ({
       {renderAction("zoomIn")}
       {renderAction("zoomOut")}
       {renderAction("resetZoom")}
-      <div style={{ marginInlineStart: 4 }}>
-        {(zoom.value * 100).toFixed(0)}%
-      </div>
     </Stack.Row>
   </Stack.Col>
 );

+ 1 - 1
src/components/CheckboxItem.scss

@@ -81,7 +81,7 @@
       align-items: center;
     }
 
-    .Tooltip-icon {
+    .excalidraw-tooltip-icon {
       width: 1em;
       height: 1em;
     }

+ 12 - 2
src/components/LayerUI.scss

@@ -73,10 +73,10 @@
       }
 
       :root[dir="ltr"] &.layer-ui__wrapper__footer-left--transition-left {
-        transform: translate(-92px, 0);
+        transform: translate(-76px, 0);
       }
       :root[dir="rtl"] &.layer-ui__wrapper__footer-left--transition-left {
-        transform: translate(92px, 0);
+        transform: translate(76px, 0);
       }
 
       &.layer-ui__wrapper__footer-left--transition-bottom {
@@ -120,5 +120,15 @@
     .disable-zen-mode--visible {
       pointer-events: all;
     }
+
+    .layer-ui__wrapper__footer-left {
+      margin-bottom: 0.2em;
+    }
+
+    .layer-ui__wrapper__footer-right {
+      margin-top: auto;
+      margin-bottom: auto;
+      margin-inline-end: 1em;
+    }
   }
 }

+ 13 - 1
src/components/LayerUI.tsx

@@ -632,7 +632,9 @@ const LayerUI = ({
                       label={client.username || "Unknown user"}
                       key={clientId}
                     >
-                      {actionManager.renderAction("goToCollaborator", clientId)}
+                      {actionManager.renderAction("goToCollaborator", {
+                        id: clientId,
+                      })}
                     </Tooltip>
                   ))}
             </UserList>
@@ -665,6 +667,16 @@ const LayerUI = ({
                   zoom={appState.zoom}
                 />
               </Island>
+              {!viewModeEnabled && (
+                <div
+                  className={clsx("undo-redo-buttons zen-mode-transition", {
+                    "layer-ui__wrapper__footer-left--transition-bottom": zenModeEnabled,
+                  })}
+                >
+                  {actionManager.renderAction("undo", { size: "small" })}
+                  {actionManager.renderAction("redo", { size: "small" })}
+                </div>
+              )}
             </Section>
           </Stack.Col>
         </div>

+ 1 - 1
src/components/LibraryButton.tsx

@@ -21,7 +21,7 @@ export const LibraryButton: React.FC<{
     <label
       className={clsx(
         "ToolIcon ToolIcon_type_floating ToolIcon__library zen-mode-visibility",
-        `ToolIcon_size_m`,
+        `ToolIcon_size_medium`,
         {
           "zen-mode-visibility--hidden": appState.zenModeEnabled,
         },

+ 2 - 3
src/components/LockButton.tsx

@@ -2,8 +2,7 @@ import "./ToolIcon.scss";
 
 import React from "react";
 import clsx from "clsx";
-
-type LockIconSize = "s" | "m";
+import { ToolButtonSize } from "./ToolButton";
 
 type LockIconProps = {
   title?: string;
@@ -13,7 +12,7 @@ type LockIconProps = {
   zenModeEnabled?: boolean;
 };
 
-const DEFAULT_SIZE: LockIconSize = "m";
+const DEFAULT_SIZE: ToolButtonSize = "medium";
 
 const ICONS = {
   CHECKED: (

+ 3 - 4
src/components/MobileMenu.tsx

@@ -168,10 +168,9 @@ export const MobileMenu = ({
                           )
                           .map(([clientId, client]) => (
                             <React.Fragment key={clientId}>
-                              {actionManager.renderAction(
-                                "goToCollaborator",
-                                clientId,
-                              )}
+                              {actionManager.renderAction("goToCollaborator", {
+                                id: clientId,
+                              })}
                             </React.Fragment>
                           ))}
                       </UserList>

+ 4 - 5
src/components/ToolButton.tsx

@@ -4,7 +4,7 @@ import React from "react";
 import clsx from "clsx";
 import { useExcalidrawContainer } from "./App";
 
-type ToolIconSize = "s" | "m";
+export type ToolButtonSize = "small" | "medium";
 
 type ToolButtonBaseProps = {
   icon?: React.ReactNode;
@@ -15,7 +15,7 @@ type ToolButtonBaseProps = {
   title?: string;
   name?: string;
   id?: string;
-  size?: ToolIconSize;
+  size?: ToolButtonSize;
   keyBindingLabel?: string;
   showAriaLabel?: boolean;
   hidden?: boolean;
@@ -41,13 +41,11 @@ type ToolButtonProps =
       onChange?(): void;
     });
 
-const DEFAULT_SIZE: ToolIconSize = "m";
-
 export const ToolButton = React.forwardRef((props: ToolButtonProps, ref) => {
   const { id: excalId } = useExcalidrawContainer();
   const innerRef = React.useRef(null);
   React.useImperativeHandle(ref, () => innerRef.current);
-  const sizeCn = `ToolIcon_size_${props.size || DEFAULT_SIZE}`;
+  const sizeCn = `ToolIcon_size_${props.size}`;
 
   if (props.type === "button" || props.type === "icon") {
     return (
@@ -118,4 +116,5 @@ export const ToolButton = React.forwardRef((props: ToolButtonProps, ref) => {
 ToolButton.defaultProps = {
   visible: true,
   className: "",
+  size: "medium",
 };

+ 3 - 3
src/components/ToolIcon.scss

@@ -60,9 +60,9 @@
     text-overflow: ellipsis;
   }
 
-  .ToolIcon_size_s .ToolIcon__icon {
-    width: 1.4rem;
-    height: 1.4rem;
+  .ToolIcon_size_small .ToolIcon__icon {
+    width: 2rem;
+    height: 2rem;
     font-size: 0.8em;
   }
 

+ 17 - 11
src/components/Tooltip.scss

@@ -1,4 +1,6 @@
 @import "../css/variables.module";
+
+// container in body where the actual tooltip is appended to
 .excalidraw-tooltip {
   position: absolute;
   z-index: 1000;
@@ -24,16 +26,20 @@
   }
 }
 
-.excalidraw {
-  .Tooltip-icon {
-    width: 0.9em;
-    height: 0.9em;
-    margin-left: 5px;
-    margin-top: 1px;
-    display: flex;
-
-    @include isMobile {
-      display: none;
-    }
+// wraps the element we want to apply the tooltip to
+.excalidraw-tooltip-wrapper {
+  display: flex;
+  height: 100%;
+}
+
+.excalidraw-tooltip-icon {
+  width: 0.9em;
+  height: 0.9em;
+  margin-left: 5px;
+  margin-top: 1px;
+  display: flex;
+
+  @include isMobile {
+    display: none;
   }
 }

+ 1 - 0
src/components/Tooltip.tsx

@@ -74,6 +74,7 @@ export const Tooltip = ({ children, label, long = false }: TooltipProps) => {
 
   return (
     <div
+      className="excalidraw-tooltip-wrapper"
       onPointerEnter={(event) =>
         updateTooltip(
           event.currentTarget as HTMLDivElement,

+ 14 - 24
src/css/styles.scss

@@ -414,22 +414,6 @@
     &:active {
       background-color: var(--button-gray-2);
     }
-
-    &.dropdown-select--floating {
-      margin: 0.5em;
-    }
-  }
-
-  .dropdown-select__language.dropdown-select--floating {
-    bottom: 10px;
-
-    :root[dir="ltr"] & {
-      right: 44px;
-    }
-
-    :root[dir="rtl"] & {
-      left: 44px;
-    }
   }
 
   .zIndexButton {
@@ -456,26 +440,32 @@
   }
 
   .help-icon {
+    display: flex;
     cursor: pointer;
     fill: $oc-gray-6;
     width: 1.5rem;
     padding: 0;
     margin: 0;
-    margin-top: 5px;
     background: none;
     color: var(--icon-fill-color);
-
     &:hover {
       background: none;
     }
+  }
 
-    :root[dir="ltr"] & {
-      margin-right: 14px;
-    }
+  .reset-zoom-button {
+    padding: 0.2em;
+    background: transparent;
+    color: var(--text-primary-color);
+    font-family: var(--ui-font);
+  }
 
-    :root[dir="rtl"] & {
-      margin-left: 14px;
-    }
+  .undo-redo-buttons {
+    display: flex;
+    gap: 0.4em;
+    margin-top: auto;
+    margin-bottom: auto;
+    margin-inline-start: 0.6em;
   }
 
   @include isMobile {

+ 1 - 6
src/excalidraw-app/components/LanguageList.tsx

@@ -1,23 +1,18 @@
 import React from "react";
-import clsx from "clsx";
 import * as i18n from "../../i18n";
 
 export const LanguageList = ({
   onChange,
   languages = i18n.languages,
   currentLangCode = i18n.getLanguage().code,
-  floating,
 }: {
   languages?: { code: string; label: string }[];
   onChange: (langCode: i18n.Language["code"]) => void;
   currentLangCode?: i18n.Language["code"];
-  floating?: boolean;
 }) => (
   <React.Fragment>
     <select
-      className={clsx("dropdown-select dropdown-select__language", {
-        "dropdown-select--floating": floating,
-      })}
+      className="dropdown-select dropdown-select__language"
       onChange={({ target }) => onChange(target.value)}
       value={currentLangCode}
       aria-label={i18n.t("buttons.selectLanguage")}

+ 7 - 1
src/excalidraw-app/index.scss

@@ -2,12 +2,18 @@
   .layer-ui__wrapper__footer-center {
     display: flex;
     justify-content: space-between;
+    margin-top: auto;
+    margin-bottom: auto;
+    margin-inline-start: auto;
   }
 
   .encrypted-icon {
     border-radius: var(--space-factor);
     color: var(--icon-green-fill-color);
-    margin-top: 13px;
+    margin-top: auto;
+    margin-bottom: auto;
+    margin-inline-start: auto;
+    margin-inline-end: 0.6em;
 
     svg {
       width: 1.2rem;

+ 1 - 4
src/excalidraw-app/index.tsx

@@ -348,11 +348,8 @@ const ExcalidrawWrapper = () => {
 
       const renderLanguageList = () => (
         <LanguageList
-          onChange={(langCode) => {
-            setLangCode(langCode);
-          }}
+          onChange={(langCode) => setLangCode(langCode)}
           languages={languages}
-          floating={!isMobile}
           currentLangCode={langCode}
         />
       );

+ 1 - 1
src/tests/flip.test.tsx

@@ -17,7 +17,7 @@ beforeEach(async () => {
   mouse.reset();
 
   await setLanguage(defaultLang);
-  render(<App />);
+  await render(<App />);
 });
 
 const createAndSelectOneRectangle = (angle: number = 0) => {

+ 10 - 10
src/tests/packages/__snapshots__/excalidraw.test.tsx.snap

@@ -25,7 +25,7 @@ exports[`<Excalidraw/> Test UIOptions prop Test canvasActions should not hide an
       >
         <button
           aria-label="Reset the canvas"
-          class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon"
+          class="ToolIcon_type_button ToolIcon_size_medium ToolIcon_type_button--show ToolIcon"
           data-testid="clear-canvas-button"
           title="Reset the canvas"
           type="button"
@@ -53,7 +53,7 @@ exports[`<Excalidraw/> Test UIOptions prop Test canvasActions should not hide an
         />
         <button
           aria-label="Load"
-          class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon"
+          class="ToolIcon_type_button ToolIcon_size_medium ToolIcon_type_button--show ToolIcon"
           data-testid="load-button"
           title="Load"
           type="button"
@@ -78,7 +78,7 @@ exports[`<Excalidraw/> Test UIOptions prop Test canvasActions should not hide an
         </button>
         <button
           aria-label="Export"
-          class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon"
+          class="ToolIcon_type_button ToolIcon_size_medium ToolIcon_type_button--show ToolIcon"
           data-testid="json-export-button"
           title="Export"
           type="button"
@@ -103,7 +103,7 @@ exports[`<Excalidraw/> Test UIOptions prop Test canvasActions should not hide an
         </button>
         <button
           aria-label="Save as image"
-          class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon"
+          class="ToolIcon_type_button ToolIcon_size_medium ToolIcon_type_button--show ToolIcon"
           data-testid="image-export-button"
           title="Save as image"
           type="button"
@@ -170,7 +170,7 @@ exports[`<Excalidraw/> Test UIOptions prop Test canvasActions should not hide an
         >
           <button
             aria-label="Dark mode"
-            class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon ToolIcon--plain"
+            class="ToolIcon_type_button ToolIcon_size_medium ToolIcon_type_button--show ToolIcon ToolIcon--plain"
             data-testid="toggle-dark-mode"
             title="Dark mode"
             type="button"
@@ -224,7 +224,7 @@ exports[`<Excalidraw/> Test UIOptions prop should not hide any UI element when t
       >
         <button
           aria-label="Reset the canvas"
-          class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon"
+          class="ToolIcon_type_button ToolIcon_size_medium ToolIcon_type_button--show ToolIcon"
           data-testid="clear-canvas-button"
           title="Reset the canvas"
           type="button"
@@ -252,7 +252,7 @@ exports[`<Excalidraw/> Test UIOptions prop should not hide any UI element when t
         />
         <button
           aria-label="Load"
-          class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon"
+          class="ToolIcon_type_button ToolIcon_size_medium ToolIcon_type_button--show ToolIcon"
           data-testid="load-button"
           title="Load"
           type="button"
@@ -277,7 +277,7 @@ exports[`<Excalidraw/> Test UIOptions prop should not hide any UI element when t
         </button>
         <button
           aria-label="Export"
-          class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon"
+          class="ToolIcon_type_button ToolIcon_size_medium ToolIcon_type_button--show ToolIcon"
           data-testid="json-export-button"
           title="Export"
           type="button"
@@ -302,7 +302,7 @@ exports[`<Excalidraw/> Test UIOptions prop should not hide any UI element when t
         </button>
         <button
           aria-label="Save as image"
-          class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon"
+          class="ToolIcon_type_button ToolIcon_size_medium ToolIcon_type_button--show ToolIcon"
           data-testid="image-export-button"
           title="Save as image"
           type="button"
@@ -369,7 +369,7 @@ exports[`<Excalidraw/> Test UIOptions prop should not hide any UI element when t
         >
           <button
             aria-label="Dark mode"
-            class="ToolIcon_type_button ToolIcon_size_m ToolIcon_type_button--show ToolIcon ToolIcon--plain"
+            class="ToolIcon_type_button ToolIcon_size_medium ToolIcon_type_button--show ToolIcon ToolIcon--plain"
             data-testid="toggle-dark-mode"
             title="Dark mode"
             type="button"

+ 19 - 16
src/tests/resize.test.tsx

@@ -45,14 +45,17 @@ describe("resize rectangle ellipses and diamond elements", () => {
     ${"se"} | ${[-30, -10]}   | ${[70, 90]}   | ${[elemData.x, elemData.y]}
     ${"nw"} | ${[-300, -200]} | ${[400, 300]} | ${[-300, -200]}
     ${"sw"} | ${[40, -20]}    | ${[60, 80]}   | ${[40, 0]}
-  `("resizes with handle $handle", ({ handle, move, dimensions, topLeft }) => {
-    render(<App />);
-    const rectangle = UI.createElement("rectangle", elemData);
-    resize(rectangle, handle, move);
-    const element = h.elements[0];
-    expect([element.width, element.height]).toEqual(dimensions);
-    expect([element.x, element.y]).toEqual(topLeft);
-  });
+  `(
+    "resizes with handle $handle",
+    async ({ handle, move, dimensions, topLeft }) => {
+      await render(<App />);
+      const rectangle = UI.createElement("rectangle", elemData);
+      resize(rectangle, handle, move);
+      const element = h.elements[0];
+      expect([element.width, element.height]).toEqual(dimensions);
+      expect([element.x, element.y]).toEqual(topLeft);
+    },
+  );
 
   it.each`
     handle  | move            | dimensions    | topLeft
@@ -61,8 +64,8 @@ describe("resize rectangle ellipses and diamond elements", () => {
     ${"sw"} | ${[40, -20]}    | ${[80, 80]}   | ${[20, 0]}
   `(
     "resizes with fixed side ratios on handle $handle",
-    ({ handle, move, dimensions, topLeft }) => {
-      render(<App />);
+    async ({ handle, move, dimensions, topLeft }) => {
+      await render(<App />);
       const rectangle = UI.createElement("rectangle", elemData);
       resize(rectangle, handle, move, { shift: true });
       const element = h.elements[0];
@@ -79,8 +82,8 @@ describe("resize rectangle ellipses and diamond elements", () => {
     ${"n"}  | ${[_, 150]}    | ${[50, 50]}   | ${[25, 100]}
   `(
     "Flips while resizing and keeping side ratios on handle $handle",
-    ({ handle, move, dimensions, topLeft }) => {
-      render(<App />);
+    async ({ handle, move, dimensions, topLeft }) => {
+      await render(<App />);
       const rectangle = UI.createElement("rectangle", elemData);
       resize(rectangle, handle, move, { shift: true });
       const element = h.elements[0];
@@ -95,8 +98,8 @@ describe("resize rectangle ellipses and diamond elements", () => {
     ${"s"}  | ${[_, -20]}   | ${[100, 60]}  | ${[0, 20]}
   `(
     "Resizes from center on handle $handle",
-    ({ handle, move, dimensions, topLeft }) => {
-      render(<App />);
+    async ({ handle, move, dimensions, topLeft }) => {
+      await render(<App />);
       const rectangle = UI.createElement("rectangle", elemData);
       resize(rectangle, handle, move, { alt: true });
       const element = h.elements[0];
@@ -111,8 +114,8 @@ describe("resize rectangle ellipses and diamond elements", () => {
     ${"e"}  | ${[-130, _]}  | ${[160, 160]} | ${[-30, -30]}
   `(
     "Resizes from center, flips and keeps side rations on handle $handle",
-    ({ handle, move, dimensions, topLeft }) => {
-      render(<App />);
+    async ({ handle, move, dimensions, topLeft }) => {
+      await render(<App />);
       const rectangle = UI.createElement("rectangle", elemData);
       resize(rectangle, handle, move, { alt: true, shift: true });
       const element = h.elements[0];