Sfoglia il codice sorgente

chore: upgrade to react@19 (#9182)

David Luzar 6 mesi fa
parent
commit
31e8476c78
58 ha cambiato i file con 703 aggiunte e 813 eliminazioni
  1. 2 2
      dev-docs/package.json
  2. 2 2
      examples/excalidraw/package.json
  3. 4 4
      examples/excalidraw/with-nextjs/package.json
  4. 2 2
      examples/excalidraw/with-script-in-browser/package.json
  5. 3 3
      excalidraw-app/collab/CollabError.tsx
  6. 27 28
      excalidraw-app/components/DebugCanvas.tsx
  7. 2 2
      excalidraw-app/package.json
  8. 5 6
      package.json
  9. 1 0
      packages/excalidraw/components/ButtonIcon.tsx
  10. 1 0
      packages/excalidraw/components/ButtonIconCycle.tsx
  11. 1 0
      packages/excalidraw/components/ButtonIconSelect.tsx
  12. 1 0
      packages/excalidraw/components/FontPicker/FontPickerList.tsx
  13. 1 1
      packages/excalidraw/components/FontPicker/keyboardNavHandlers.ts
  14. 1 0
      packages/excalidraw/components/HelpDialog.tsx
  15. 1 0
      packages/excalidraw/components/IconPicker.tsx
  16. 3 2
      packages/excalidraw/components/MagicButton.tsx
  17. 1 0
      packages/excalidraw/components/MobileMenu.tsx
  18. 1 0
      packages/excalidraw/components/Sidebar/common.ts
  19. 0 1
      packages/excalidraw/components/Stack.tsx
  20. 1 1
      packages/excalidraw/components/TTDDialog/TTDDialogOutput.tsx
  21. 1 0
      packages/excalidraw/components/TTDDialog/TTDDialogTrigger.tsx
  22. 2 2
      packages/excalidraw/components/TTDDialog/common.ts
  23. 1 1
      packages/excalidraw/components/canvases/InteractiveCanvas.tsx
  24. 1 0
      packages/excalidraw/components/dropdownMenu/DropdownMenuItem.tsx
  25. 1 0
      packages/excalidraw/components/dropdownMenu/DropdownMenuItemContent.tsx
  26. 1 0
      packages/excalidraw/components/dropdownMenu/DropdownMenuItemLink.tsx
  27. 1 0
      packages/excalidraw/components/welcome-screen/WelcomeScreen.Center.tsx
  28. 2 3
      packages/excalidraw/element/flowchart.test.tsx
  29. 7 4
      packages/excalidraw/element/textWysiwyg.test.tsx
  30. 1 0
      packages/excalidraw/fonts/FontMetadata.ts
  31. 7 3
      packages/excalidraw/global.d.ts
  32. 1 1
      packages/excalidraw/hooks/useOutsideClick.ts
  33. 1 1
      packages/excalidraw/hooks/useScrollPosition.ts
  34. 4 4
      packages/excalidraw/package.json
  35. 2 4
      packages/excalidraw/tests/App.test.tsx
  36. 2 4
      packages/excalidraw/tests/align.test.tsx
  37. 7 3
      packages/excalidraw/tests/clipboard.test.tsx
  38. 2 3
      packages/excalidraw/tests/contextmenu.test.tsx
  39. 2 4
      packages/excalidraw/tests/cropElement.test.tsx
  40. 12 3
      packages/excalidraw/tests/data/__snapshots__/restore.test.ts.snap
  41. 18 8
      packages/excalidraw/tests/data/reconcile.test.ts
  42. 2 0
      packages/excalidraw/tests/data/restore.test.ts
  43. 2 3
      packages/excalidraw/tests/dragCreate.test.tsx
  44. 2 3
      packages/excalidraw/tests/elementLocking.test.tsx
  45. 2 3
      packages/excalidraw/tests/flip.test.tsx
  46. 4 5
      packages/excalidraw/tests/helpers/api.ts
  47. 8 4
      packages/excalidraw/tests/linearElementEditor.test.tsx
  48. 2 4
      packages/excalidraw/tests/move.test.tsx
  49. 2 3
      packages/excalidraw/tests/multiPointCreate.test.tsx
  50. 2 3
      packages/excalidraw/tests/regressionTests.test.tsx
  51. 2 3
      packages/excalidraw/tests/resize.test.tsx
  52. 2 3
      packages/excalidraw/tests/rotate.test.tsx
  53. 2 3
      packages/excalidraw/tests/selection.test.tsx
  54. 9 1
      packages/excalidraw/tests/test-utils.ts
  55. 2 4
      packages/excalidraw/tests/zindex.test.tsx
  56. 1 0
      packages/excalidraw/types.ts
  57. 1 0
      tsconfig.json
  58. 522 669
      yarn.lock

+ 2 - 2
dev-docs/package.json

@@ -23,8 +23,8 @@
     "clsx": "^1.2.1",
     "docusaurus-plugin-sass": "0.2.3",
     "prism-react-renderer": "^1.3.5",
-    "react": "18.2.0",
-    "react-dom": "18.2.0",
+    "react": "19.0.0",
+    "react-dom": "19.0.0",
     "sass": "1.57.1"
   },
   "devDependencies": {

+ 2 - 2
examples/excalidraw/package.json

@@ -3,8 +3,8 @@
   "version": "1.0.0",
   "private": true,
   "dependencies": {
-    "react": "18.2.0",
-    "react-dom": "18.2.0",
+    "react": "19.0.0",
+    "react-dom": "19.0.0",
     "@excalidraw/excalidraw": "*"
   },
   "devDependencies": {

+ 4 - 4
examples/excalidraw/with-nextjs/package.json

@@ -13,13 +13,13 @@
   "dependencies": {
     "@excalidraw/excalidraw": "*",
     "next": "14.1",
-    "react": "18.2.0",
-    "react-dom": "18.2.0"
+    "react": "19.0.0",
+    "react-dom": "19.0.0"
   },
   "devDependencies": {
     "@types/node": "^20",
-    "@types/react": "18.2.0",
-    "@types/react-dom": "18.2.0",
+    "@types/react": "19.0.10",
+    "@types/react-dom": "19.0.4",
     "path2d-polyfill": "2.0.1",
     "typescript": "^5"
   }

+ 2 - 2
examples/excalidraw/with-script-in-browser/package.json

@@ -3,8 +3,8 @@
   "version": "1.0.0",
   "private": true,
   "dependencies": {
-    "react": "18.2.0",
-    "react-dom": "18.2.0",
+    "react": "19.0.0",
+    "react-dom": "19.0.0",
     "@excalidraw/excalidraw": "*"
   },
   "devDependencies": {

+ 3 - 3
excalidraw-app/collab/CollabError.tsx

@@ -19,16 +19,16 @@ export const collabErrorIndicatorAtom = atom<ErrorIndicator>({
 
 const CollabError = ({ collabError }: { collabError: ErrorIndicator }) => {
   const [isAnimating, setIsAnimating] = useState(false);
-  const clearAnimationRef = useRef<string | number | NodeJS.Timeout>();
+  const clearAnimationRef = useRef<string | number>(0);
 
   useEffect(() => {
     setIsAnimating(true);
-    clearAnimationRef.current = setTimeout(() => {
+    clearAnimationRef.current = window.setTimeout(() => {
       setIsAnimating(false);
     }, 1000);
 
     return () => {
-      clearTimeout(clearAnimationRef.current);
+      window.clearTimeout(clearAnimationRef.current);
     };
   }, [collabError.message, collabError.nonce]);
 

+ 27 - 28
excalidraw-app/components/DebugCanvas.tsx

@@ -1,4 +1,4 @@
-import { forwardRef, useCallback, useImperativeHandle, useRef } from "react";
+import { useCallback, useImperativeHandle, useRef } from "react";
 import { type AppState } from "../../packages/excalidraw/types";
 import { throttleRAF } from "../../packages/excalidraw/utils";
 import {
@@ -276,36 +276,35 @@ export const DebugFooter = ({ onChange }: { onChange: () => void }) => {
 interface DebugCanvasProps {
   appState: AppState;
   scale: number;
+  ref?: React.Ref<HTMLCanvasElement>;
 }
 
-const DebugCanvas = forwardRef<HTMLCanvasElement, DebugCanvasProps>(
-  ({ appState, scale }, ref) => {
-    const { width, height } = appState;
+const DebugCanvas = ({ appState, scale, ref }: DebugCanvasProps) => {
+  const { width, height } = appState;
 
-    const canvasRef = useRef<HTMLCanvasElement>(null);
-    useImperativeHandle<HTMLCanvasElement | null, HTMLCanvasElement | null>(
-      ref,
-      () => canvasRef.current,
-      [canvasRef],
-    );
+  const canvasRef = useRef<HTMLCanvasElement>(null);
+  useImperativeHandle<HTMLCanvasElement | null, HTMLCanvasElement | null>(
+    ref,
+    () => canvasRef.current,
+    [canvasRef],
+  );
 
-    return (
-      <canvas
-        style={{
-          width,
-          height,
-          position: "absolute",
-          zIndex: 2,
-          pointerEvents: "none",
-        }}
-        width={width * scale}
-        height={height * scale}
-        ref={canvasRef}
-      >
-        Debug Canvas
-      </canvas>
-    );
-  },
-);
+  return (
+    <canvas
+      style={{
+        width,
+        height,
+        position: "absolute",
+        zIndex: 2,
+        pointerEvents: "none",
+      }}
+      width={width * scale}
+      height={height * scale}
+      ref={canvasRef}
+    >
+      Debug Canvas
+    </canvas>
+  );
+};
 
 export default DebugCanvas;

+ 2 - 2
excalidraw-app/package.json

@@ -33,8 +33,8 @@
     "i18next-browser-languagedetector": "6.1.4",
     "idb-keyval": "6.0.3",
     "jotai": "2.11.0",
-    "react": "18.2.0",
-    "react-dom": "18.2.0",
+    "react": "19.0.0",
+    "react-dom": "19.0.0",
     "socket.io-client": "4.7.2",
     "vite-plugin-html": "3.2.2"
   },

+ 5 - 6
package.json

@@ -17,11 +17,11 @@
     "@types/chai": "4.3.0",
     "@types/jest": "27.4.0",
     "@types/lodash.throttle": "4.1.7",
-    "@types/react": "18.2.0",
-    "@types/react-dom": "18.2.0",
+    "@types/react": "19.0.10",
+    "@types/react-dom": "19.0.4",
     "@types/socket.io-client": "3.0.0",
     "@vitejs/plugin-react": "3.1.0",
-    "@vitest/coverage-v8": "2.0.5",
+    "@vitest/coverage-v8": "3.0.7",
     "@vitest/ui": "2.0.5",
     "chai": "4.3.6",
     "dotenv": "16.0.1",
@@ -39,9 +39,9 @@
     "vite": "5.0.12",
     "vite-plugin-checker": "0.7.2",
     "vite-plugin-ejs": "1.7.0",
-    "vite-plugin-pwa": "0.17.4",
+    "vite-plugin-pwa": "0.21.1",
     "vite-plugin-svgr": "4.2.0",
-    "vitest": "2.0.5",
+    "vitest": "3.0.6",
     "vitest-canvas-mock": "0.3.3"
   },
   "engines": {
@@ -83,7 +83,6 @@
     "clean-install": "yarn rm:node_modules && yarn install"
   },
   "resolutions": {
-    "@types/react": "18.2.0",
     "strip-ansi": "6.0.1"
   }
 }

+ 1 - 0
packages/excalidraw/components/ButtonIcon.tsx

@@ -1,4 +1,5 @@
 import { forwardRef } from "react";
+import type { JSX } from "react";
 import clsx from "clsx";
 
 import "./ButtonIcon.scss";

+ 1 - 0
packages/excalidraw/components/ButtonIconCycle.tsx

@@ -1,3 +1,4 @@
+import type { JSX } from "react";
 import clsx from "clsx";
 
 export const ButtonIconCycle = <T extends any>({

+ 1 - 0
packages/excalidraw/components/ButtonIconSelect.tsx

@@ -1,3 +1,4 @@
+import type { JSX } from "react";
 import clsx from "clsx";
 import { ButtonIcon } from "./ButtonIcon";
 

+ 1 - 0
packages/excalidraw/components/FontPicker/FontPickerList.tsx

@@ -1,3 +1,4 @@
+import type { JSX } from "react";
 import React, {
   useMemo,
   useState,

+ 1 - 1
packages/excalidraw/components/FontPicker/keyboardNavHandlers.ts

@@ -4,7 +4,7 @@ import { type FontDescriptor } from "./FontPickerList";
 
 interface FontPickerKeyNavHandlerProps {
   event: React.KeyboardEvent<HTMLDivElement>;
-  inputRef: React.RefObject<HTMLInputElement>;
+  inputRef: React.RefObject<HTMLInputElement | null>;
   hoveredFont: Node<FontDescriptor> | undefined;
   filteredFonts: Node<FontDescriptor>[];
   onClose: () => void;

+ 1 - 0
packages/excalidraw/components/HelpDialog.tsx

@@ -1,3 +1,4 @@
+import type { JSX } from "react";
 import React from "react";
 import { t } from "../i18n";
 import { KEYS } from "../keys";

+ 1 - 0
packages/excalidraw/components/IconPicker.tsx

@@ -1,3 +1,4 @@
+import type { JSX } from "react";
 import React, { useEffect } from "react";
 import * as Popover from "@radix-ui/react-popover";
 import { isArrowKey, KEYS } from "../keys";

+ 3 - 2
packages/excalidraw/components/MagicButton.tsx

@@ -1,8 +1,9 @@
-import "./ToolIcon.scss";
-
+import type { JSX } from "react";
 import clsx from "clsx";
 import type { ToolButtonSize } from "./ToolButton";
 
+import "./ToolIcon.scss";
+
 const DEFAULT_SIZE: ToolButtonSize = "small";
 
 export const ElementCanvasButton = (props: {

+ 1 - 0
packages/excalidraw/components/MobileMenu.tsx

@@ -1,3 +1,4 @@
+import type { JSX } from "react";
 import React from "react";
 import type {
   AppClassProperties,

+ 1 - 0
packages/excalidraw/components/Sidebar/common.ts

@@ -1,3 +1,4 @@
+import type { JSX } from "react";
 import React from "react";
 import type { AppState, SidebarName, SidebarTabName } from "../../types";
 

+ 0 - 1
packages/excalidraw/components/Stack.tsx

@@ -10,7 +10,6 @@ type StackProps = {
   justifyContent?: "center" | "space-around" | "space-between";
   className?: string | boolean;
   style?: React.CSSProperties;
-  ref: React.RefObject<HTMLDivElement>;
 };
 
 const RowStack = forwardRef(

+ 1 - 1
packages/excalidraw/components/TTDDialog/TTDDialogOutput.tsx

@@ -13,7 +13,7 @@ const ErrorComp = ({ error }: { error: string }) => {
 
 interface TTDDialogOutputProps {
   error: Error | null;
-  canvasRef: React.RefObject<HTMLDivElement>;
+  canvasRef: React.RefObject<HTMLDivElement | null>;
   loaded: boolean;
 }
 

+ 1 - 0
packages/excalidraw/components/TTDDialog/TTDDialogTrigger.tsx

@@ -1,3 +1,4 @@
+import type { JSX } from "react";
 import type { ReactNode } from "react";
 import { useTunnels } from "../../context/tunnels";
 import DropdownMenu from "../dropdownMenu/DropdownMenu";

+ 2 - 2
packages/excalidraw/components/TTDDialog/common.ts

@@ -12,7 +12,7 @@ const resetPreview = ({
   canvasRef,
   setError,
 }: {
-  canvasRef: React.RefObject<HTMLDivElement>;
+  canvasRef: React.RefObject<HTMLDivElement | null>;
   setError: (error: Error | null) => void;
 }) => {
   const canvasNode = canvasRef.current;
@@ -40,7 +40,7 @@ export interface MermaidToExcalidrawLibProps {
 }
 
 interface ConvertMermaidToExcalidrawFormatProps {
-  canvasRef: React.RefObject<HTMLDivElement>;
+  canvasRef: React.RefObject<HTMLDivElement | null>;
   mermaidToExcalidrawLib: MermaidToExcalidrawLibProps;
   mermaidDefinition: string;
   setError: (error: Error | null) => void;

+ 1 - 1
packages/excalidraw/components/canvases/InteractiveCanvas.tsx

@@ -17,7 +17,7 @@ import { isRenderThrottlingEnabled } from "../../reactUtils";
 import { renderInteractiveScene } from "../../renderer/interactiveScene";
 
 type InteractiveCanvasProps = {
-  containerRef: React.RefObject<HTMLDivElement>;
+  containerRef: React.RefObject<HTMLDivElement | null>;
   canvas: HTMLCanvasElement | null;
   elementsMap: RenderableElementsMap;
   visibleElements: readonly NonDeletedExcalidrawElement[];

+ 1 - 0
packages/excalidraw/components/dropdownMenu/DropdownMenuItem.tsx

@@ -1,3 +1,4 @@
+import type { JSX } from "react";
 import React, { useEffect, useRef } from "react";
 import {
   getDropdownMenuItemClassName,

+ 1 - 0
packages/excalidraw/components/dropdownMenu/DropdownMenuItemContent.tsx

@@ -1,3 +1,4 @@
+import type { JSX } from "react";
 import { useDevice } from "../App";
 
 const MenuItemContent = ({

+ 1 - 0
packages/excalidraw/components/dropdownMenu/DropdownMenuItemLink.tsx

@@ -1,4 +1,5 @@
 import MenuItemContent from "./DropdownMenuItemContent";
+import type { JSX } from "react";
 import React from "react";
 import {
   getDropdownMenuItemClassName,

+ 1 - 0
packages/excalidraw/components/welcome-screen/WelcomeScreen.Center.tsx

@@ -1,3 +1,4 @@
+import type { JSX } from "react";
 import { actionLoadScene, actionShortcuts } from "../../actions";
 import { getShortcutFromShortcutName } from "../../actions/shortcuts";
 import { t, useI18n } from "../../i18n";

+ 2 - 3
packages/excalidraw/element/flowchart.test.tsx

@@ -1,12 +1,11 @@
-import ReactDOM from "react-dom";
-import { render } from "../tests/test-utils";
+import { render, unmountComponent } from "../tests/test-utils";
 import { reseed } from "../random";
 import { UI, Keyboard, Pointer } from "../tests/helpers/ui";
 import { Excalidraw } from "../index";
 import { API } from "../tests/helpers/api";
 import { KEYS } from "../keys";
 
-ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
+unmountComponent();
 
 const { h } = window;
 const mouse = new Pointer("mouse");

+ 7 - 4
packages/excalidraw/element/textWysiwyg.test.tsx

@@ -1,7 +1,11 @@
 import React from "react";
-import ReactDOM from "react-dom";
 import { Excalidraw } from "../index";
-import { GlobalTestState, render, screen } from "../tests/test-utils";
+import {
+  GlobalTestState,
+  render,
+  screen,
+  unmountComponent,
+} from "../tests/test-utils";
 import { Keyboard, Pointer, UI } from "../tests/helpers/ui";
 import { CODES, KEYS } from "../keys";
 import {
@@ -21,8 +25,7 @@ import { getOriginalContainerHeightFromCache } from "./containerCache";
 import { getTextEditor, updateTextEditor } from "../tests/queries/dom";
 import { pointFrom } from "../../math";
 
-// Unmount ReactDOM from root
-ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
+unmountComponent();
 
 const tab = "    ";
 const mouse = new Pointer("mouse");

+ 1 - 0
packages/excalidraw/fonts/FontMetadata.ts

@@ -1,3 +1,4 @@
+import type { JSX } from "react";
 import {
   FreedrawIcon,
   FontFamilyNormalIcon,

+ 7 - 3
packages/excalidraw/global.d.ts

@@ -99,8 +99,12 @@ declare module "image-blob-reduce" {
   export = reduce;
 }
 
+interface CustomMatchers {
+  toBeNonNaNNumber(): void;
+  toCloselyEqualPoints(points: readonly [number, number][]): void;
+}
+
 declare namespace jest {
-  interface Expect {
-    toBeNonNaNNumber(): void;
-  }
+  interface Expect extends CustomMatchers {}
+  interface Matchers extends CustomMatchers {}
 }

+ 1 - 1
packages/excalidraw/hooks/useOutsideClick.ts

@@ -2,7 +2,7 @@ import { useEffect } from "react";
 import { EVENT } from "../constants";
 
 export function useOutsideClick<T extends HTMLElement>(
-  ref: React.RefObject<T>,
+  ref: React.RefObject<T | null>,
   /** if performance is of concern, memoize the callback */
   callback: (event: Event) => void,
   /**

+ 1 - 1
packages/excalidraw/hooks/useScrollPosition.ts

@@ -5,7 +5,7 @@ import throttle from "lodash.throttle";
 const scrollPositionAtom = atom<number>(0);
 
 export const useScrollPosition = <T extends HTMLElement>(
-  elementRef: React.RefObject<T>,
+  elementRef: React.RefObject<T | null>,
 ) => {
   const [scrollPosition, setScrollPosition] = useAtom(scrollPositionAtom);
 

+ 4 - 4
packages/excalidraw/package.json

@@ -52,8 +52,8 @@
     ]
   },
   "peerDependencies": {
-    "react": "^17.0.2 || ^18.2.0",
-    "react-dom": "^17.0.2 || ^18.2.0"
+    "react": "^17.0.2 || ^18.2.0 || ^19.0.0",
+    "react-dom": "^17.0.2 || ^18.2.0 || ^19.0.0"
   },
   "dependencies": {
     "@braintree/sanitize-url": "6.0.2",
@@ -98,8 +98,8 @@
     "@babel/preset-typescript": "7.24.1",
     "@size-limit/preset-big-lib": "9.0.0",
     "@testing-library/dom": "10.4.0",
-    "@testing-library/jest-dom": "5.16.2",
-    "@testing-library/react": "16.0.0",
+    "@testing-library/jest-dom": "6.6.3",
+    "@testing-library/react": "16.2.0",
     "@types/pako": "1.0.3",
     "@types/pica": "5.1.3",
     "@types/resize-observer-browser": "0.1.7",

+ 2 - 4
packages/excalidraw/tests/App.test.tsx

@@ -1,8 +1,7 @@
 import React from "react";
-import ReactDOM from "react-dom";
 import * as StaticScene from "../renderer/staticScene";
 import { reseed } from "../random";
-import { render, queryByTestId } from "../tests/test-utils";
+import { render, queryByTestId, unmountComponent } from "../tests/test-utils";
 
 import { Excalidraw } from "../index";
 import { vi } from "vitest";
@@ -11,8 +10,7 @@ const renderStaticScene = vi.spyOn(StaticScene, "renderStaticScene");
 
 describe("Test <App/>", () => {
   beforeEach(async () => {
-    // Unmount ReactDOM from root
-    ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
+    unmountComponent();
     localStorage.clear();
     renderStaticScene.mockClear();
     reseed(7);

+ 2 - 4
packages/excalidraw/tests/align.test.tsx

@@ -1,6 +1,5 @@
 import React from "react";
-import ReactDOM from "react-dom";
-import { act, render } from "./test-utils";
+import { act, unmountComponent, render } from "./test-utils";
 import { Excalidraw } from "../index";
 import { defaultLang, setLanguage } from "../i18n";
 import { UI, Pointer, Keyboard } from "./helpers/ui";
@@ -54,8 +53,7 @@ const createAndSelectTwoRectanglesWithDifferentSizes = () => {
 
 describe("aligning", () => {
   beforeEach(async () => {
-    // Unmount ReactDOM from root
-    ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
+    unmountComponent();
     mouse.reset();
 
     await act(() => {

+ 7 - 3
packages/excalidraw/tests/clipboard.test.tsx

@@ -1,7 +1,11 @@
 import React from "react";
 import { vi } from "vitest";
-import ReactDOM from "react-dom";
-import { render, waitFor, GlobalTestState } from "./test-utils";
+import {
+  render,
+  waitFor,
+  GlobalTestState,
+  unmountComponent,
+} from "./test-utils";
 import { Pointer, Keyboard } from "./helpers/ui";
 import { Excalidraw } from "../index";
 import { KEYS } from "../keys";
@@ -63,7 +67,7 @@ const sleep = (ms: number) => {
 };
 
 beforeEach(async () => {
-  ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
+  unmountComponent();
 
   localStorage.clear();
 

+ 2 - 3
packages/excalidraw/tests/contextmenu.test.tsx

@@ -1,5 +1,4 @@
 import React from "react";
-import ReactDOM from "react-dom";
 import {
   render,
   fireEvent,
@@ -11,6 +10,7 @@ import {
   queryAllByText,
   waitFor,
   togglePopover,
+  unmountComponent,
 } from "./test-utils";
 import { Excalidraw } from "../index";
 import * as StaticScene from "../renderer/staticScene";
@@ -38,8 +38,7 @@ const checkpoint = (name: string) => {
 
 const mouse = new Pointer("mouse");
 
-// Unmount ReactDOM from root
-ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
+unmountComponent();
 
 const renderStaticScene = vi.spyOn(StaticScene, "renderStaticScene");
 beforeEach(() => {

+ 2 - 4
packages/excalidraw/tests/cropElement.test.tsx

@@ -1,9 +1,8 @@
 import React from "react";
-import ReactDOM from "react-dom";
 import { vi } from "vitest";
 import { Keyboard, Pointer, UI } from "./helpers/ui";
 import type { ExcalidrawImageElement, ImageCrop } from "../element/types";
-import { act, GlobalTestState, render } from "./test-utils";
+import { act, GlobalTestState, render, unmountComponent } from "./test-utils";
 import { Excalidraw, exportToCanvas, exportToSvg } from "..";
 import { API } from "./helpers/api";
 import type { NormalizedZoomValue } from "../types";
@@ -16,8 +15,7 @@ const { h } = window;
 const mouse = new Pointer("mouse");
 
 beforeEach(async () => {
-  // Unmount ReactDOM from root
-  ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
+  unmountComponent();
 
   mouse.reset();
   localStorage.clear();

+ 12 - 3
packages/excalidraw/tests/data/__snapshots__/restore.test.ts.snap

@@ -173,7 +173,7 @@ exports[`restoreElements > should restore freedraw element correctly 1`] = `
   "fillStyle": "solid",
   "frameId": null,
   "groupIds": [],
-  "height": 0,
+  "height": 100,
   "id": "id-freedraw01",
   "index": "a0",
   "isDeleted": false,
@@ -181,7 +181,16 @@ exports[`restoreElements > should restore freedraw element correctly 1`] = `
   "link": null,
   "locked": false,
   "opacity": 100,
-  "points": [],
+  "points": [
+    [
+      0,
+      0,
+    ],
+    [
+      10,
+      10,
+    ],
+  ],
   "pressures": [],
   "roughness": 1,
   "roundness": {
@@ -196,7 +205,7 @@ exports[`restoreElements > should restore freedraw element correctly 1`] = `
   "updated": 1,
   "version": 2,
   "versionNonce": Any<Number>,
-  "width": 0,
+  "width": 100,
   "x": 0,
   "y": 0,
 }

+ 18 - 8
packages/excalidraw/tests/data/reconcile.test.ts

@@ -80,9 +80,12 @@ const test = <U extends `${string}:${"L" | "R"}`>(
   const reconciledIds = reconciled.map((x) => x.id);
   const reconciledIndices = reconciled.map((x) => x.index);
 
-  expect(target.length).equal(reconciled.length);
-  expect(reconciledIndices.length).equal(new Set([...reconciledIndices]).size); // expect no duplicated indices
-  expect(reconciledIds).deep.equal(
+  expect(target.length).toEqual(reconciled.length);
+  expect(reconciledIndices.length).toEqual(
+    new Set([...reconciledIndices]).size,
+  ); // expect no duplicated indices
+  assert.deepEqual(
+    reconciledIds,
     target.map((uid) => {
       const [, id, source] = uid.match(/^(\w+):([LR])$/)!;
       const element = (source === "L" ? _local : _remote).find(
@@ -96,13 +99,15 @@ const test = <U extends `${string}:${"L" | "R"}`>(
 
   // convergent reconciliation on the remote client
   try {
-    expect(
+    assert.deepEqual(
       reconcileElements(
         cloneJSON(_remote),
         cloneJSON(_local as RemoteExcalidrawElement[]),
         {} as AppState,
       ).map((x) => x.id),
-    ).deep.equal(reconciledIds, "convergent reconciliation");
+      reconciledIds,
+      "convergent reconciliation",
+    );
   } catch (error: any) {
     console.error("local original", _remote);
     console.error("remote original", _local);
@@ -111,13 +116,15 @@ const test = <U extends `${string}:${"L" | "R"}`>(
 
   // bidirectional re-reconciliation on remote client
   try {
-    expect(
+    assert.deepEqual(
       reconcileElements(
         cloneJSON(_remote),
         cloneJSON(reconciled as unknown as RemoteExcalidrawElement[]),
         {} as AppState,
       ).map((x) => x.id),
-    ).deep.equal(reconciledIds, "local re-reconciliation");
+      reconciledIds,
+      "local re-reconciliation",
+    );
   } catch (error: any) {
     console.error("local original", _remote);
     console.error("remote reconciled", reconciled);
@@ -309,7 +316,10 @@ describe("elements reconciliation", () => {
         throw new Error("reconcileElements: duplicate elements found");
       }
 
-      expect(ret.map((x) => x.id)).to.deep.equal(expected);
+      assert.deepEqual(
+        ret.map((x) => x.id),
+        expected,
+      );
     };
 
     // identical id/version/versionNonce/index

+ 2 - 0
packages/excalidraw/tests/data/restore.test.ts

@@ -13,6 +13,7 @@ import type { NormalizedZoomValue } from "../../types";
 import { DEFAULT_SIDEBAR, FONT_FAMILY, ROUNDNESS } from "../../constants";
 import { newElementWith } from "../../element/mutateElement";
 import { vi } from "vitest";
+import { pointFrom } from "../../../math";
 
 describe("restoreElements", () => {
   const mockSizeHelper = vi.spyOn(sizeHelpers, "isInvisiblySmallElement");
@@ -103,6 +104,7 @@ describe("restoreElements", () => {
     const freedrawElement = API.createElement({
       type: "freedraw",
       id: "id-freedraw01",
+      points: [pointFrom(0, 0), pointFrom(10, 10)],
     });
 
     const restoredFreedraw = restore.restoreElements(

+ 2 - 3
packages/excalidraw/tests/dragCreate.test.tsx

@@ -1,5 +1,4 @@
 import React from "react";
-import ReactDOM from "react-dom";
 import { Excalidraw } from "../index";
 import * as StaticScene from "../renderer/staticScene";
 import * as InteractiveScene from "../renderer/interactiveScene";
@@ -9,13 +8,13 @@ import {
   fireEvent,
   mockBoundingClientRect,
   restoreOriginalGetBoundingClientRect,
+  unmountComponent,
 } from "./test-utils";
 import type { ExcalidrawLinearElement } from "../element/types";
 import { reseed } from "../random";
 import { vi } from "vitest";
 
-// Unmount ReactDOM from root
-ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
+unmountComponent();
 
 const renderInteractiveScene = vi.spyOn(
   InteractiveScene,

+ 2 - 3
packages/excalidraw/tests/elementLocking.test.tsx

@@ -1,7 +1,6 @@
 import React from "react";
-import ReactDOM from "react-dom";
 import { Excalidraw } from "../index";
-import { render } from "../tests/test-utils";
+import { render, unmountComponent } from "../tests/test-utils";
 import { Keyboard, Pointer, UI } from "../tests/helpers/ui";
 import { KEYS } from "../keys";
 import { API } from "../tests/helpers/api";
@@ -9,7 +8,7 @@ import { actionSelectAll } from "../actions";
 import { t } from "../i18n";
 import { mutateElement } from "../element/mutateElement";
 
-ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
+unmountComponent();
 
 const mouse = new Pointer("mouse");
 const h = window.h;

+ 2 - 3
packages/excalidraw/tests/flip.test.tsx

@@ -1,10 +1,10 @@
 import React from "react";
-import ReactDOM from "react-dom";
 import {
   fireEvent,
   GlobalTestState,
   render,
   screen,
+  unmountComponent,
   waitFor,
 } from "./test-utils";
 import { UI, Pointer, Keyboard } from "./helpers/ui";
@@ -43,8 +43,7 @@ vi.mock("../data/blob", async (actual) => {
 });
 
 beforeEach(async () => {
-  // Unmount ReactDOM from root
-  ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
+  unmountComponent();
 
   mouse.reset();
   localStorage.clear();

+ 4 - 5
packages/excalidraw/tests/helpers/api.ts

@@ -189,7 +189,7 @@ export class API {
     containerId?: T extends "text"
       ? ExcalidrawTextElement["containerId"]
       : never;
-    points?: T extends "arrow" | "line" ? readonly LocalPoint[] : never;
+    points?: T extends "arrow" | "line" | "freedraw" ? readonly LocalPoint[] : never;
     locked?: boolean;
     fileId?: T extends "image" ? string : never;
     scale?: T extends "image" ? ExcalidrawImageElement["scale"] : never;
@@ -228,8 +228,6 @@ export class API {
     const base: Omit<
       ExcalidrawGenericElement,
       | "id"
-      | "width"
-      | "height"
       | "type"
       | "version"
       | "versionNonce"
@@ -241,6 +239,8 @@ export class API {
       seed: 1,
       x,
       y,
+      width,
+      height,
       frameId: rest.frameId ?? null,
       index: rest.index ?? null,
       angle: (rest.angle ?? 0) as Radians,
@@ -272,8 +272,6 @@ export class API {
       case "ellipse":
         element = newElement({
           type: type as "rectangle" | "diamond" | "ellipse",
-          width,
-          height,
           ...base,
         });
         break;
@@ -308,6 +306,7 @@ export class API {
         element = newFreeDrawElement({
           type: type as "freedraw",
           simulatePressure: true,
+          points: rest.points,
           ...base,
         });
         break;

+ 8 - 4
packages/excalidraw/tests/linearElementEditor.test.tsx

@@ -1,5 +1,4 @@
 import React from "react";
-import ReactDOM from "react-dom";
 import type {
   ExcalidrawElement,
   ExcalidrawLinearElement,
@@ -12,7 +11,13 @@ import * as StaticScene from "../renderer/staticScene";
 import * as InteractiveCanvas from "../renderer/interactiveScene";
 
 import { Keyboard, Pointer, UI } from "./helpers/ui";
-import { screen, render, fireEvent, GlobalTestState } from "./test-utils";
+import {
+  screen,
+  render,
+  fireEvent,
+  GlobalTestState,
+  unmountComponent,
+} from "./test-utils";
 import { API } from "../tests/helpers/api";
 import { KEYS } from "../keys";
 import { LinearElementEditor } from "../element/linearElementEditor";
@@ -43,8 +48,7 @@ describe("Test Linear Elements", () => {
   let interactiveCanvas: HTMLCanvasElement;
 
   beforeEach(async () => {
-    // Unmount ReactDOM from root
-    ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
+    unmountComponent();
     localStorage.clear();
     renderInteractiveScene.mockClear();
     renderStaticScene.mockClear();

+ 2 - 4
packages/excalidraw/tests/move.test.tsx

@@ -1,6 +1,5 @@
 import React from "react";
-import ReactDOM from "react-dom";
-import { render, fireEvent, act } from "./test-utils";
+import { render, fireEvent, act, unmountComponent } from "./test-utils";
 import { Excalidraw } from "../index";
 import * as StaticScene from "../renderer/staticScene";
 import * as InteractiveCanvas from "../renderer/interactiveScene";
@@ -16,8 +15,7 @@ import { KEYS } from "../keys";
 import { vi } from "vitest";
 import type Scene from "../scene/Scene";
 
-// Unmount ReactDOM from root
-ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
+unmountComponent();
 
 const renderInteractiveScene = vi.spyOn(
   InteractiveCanvas,

+ 2 - 3
packages/excalidraw/tests/multiPointCreate.test.tsx

@@ -1,10 +1,10 @@
 import React from "react";
-import ReactDOM from "react-dom";
 import {
   render,
   fireEvent,
   mockBoundingClientRect,
   restoreOriginalGetBoundingClientRect,
+  unmountComponent,
 } from "./test-utils";
 import { Excalidraw } from "../index";
 import * as StaticScene from "../renderer/staticScene";
@@ -14,8 +14,7 @@ import type { ExcalidrawLinearElement } from "../element/types";
 import { reseed } from "../random";
 import { vi } from "vitest";
 
-// Unmount ReactDOM from root
-ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
+unmountComponent();
 
 const renderInteractiveScene = vi.spyOn(
   InteractiveCanvas,

+ 2 - 3
packages/excalidraw/tests/regressionTests.test.tsx

@@ -1,5 +1,4 @@
 import React from "react";
-import ReactDOM from "react-dom";
 import type { ExcalidrawElement } from "../element/types";
 import { CODES, KEYS } from "../keys";
 import { Excalidraw } from "../index";
@@ -14,6 +13,7 @@ import {
   render,
   screen,
   togglePopover,
+  unmountComponent,
 } from "./test-utils";
 import { FONT_FAMILY } from "../constants";
 import { vi } from "vitest";
@@ -43,8 +43,7 @@ const checkpoint = (name: string) => {
   );
 };
 beforeEach(async () => {
-  // Unmount ReactDOM from root
-  ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
+  unmountComponent();
 
   localStorage.clear();
   renderStaticScene.mockClear();

+ 2 - 3
packages/excalidraw/tests/resize.test.tsx

@@ -1,6 +1,5 @@
 import React from "react";
-import ReactDOM from "react-dom";
-import { render } from "./test-utils";
+import { render, unmountComponent } from "./test-utils";
 import { reseed } from "../random";
 import { UI, Keyboard, Pointer } from "./helpers/ui";
 import type {
@@ -21,7 +20,7 @@ import { pointFrom } from "../../math";
 import { resizeSingleElement } from "../element/resizeElements";
 import { getSizeFromPoints } from "../points";
 
-ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
+unmountComponent();
 
 const { h } = window;
 const mouse = new Pointer("mouse");

+ 2 - 3
packages/excalidraw/tests/rotate.test.tsx

@@ -1,12 +1,11 @@
 import React from "react";
-import ReactDOM from "react-dom";
-import { render } from "./test-utils";
+import { render, unmountComponent } from "./test-utils";
 import { reseed } from "../random";
 import { UI } from "./helpers/ui";
 import { Excalidraw } from "../index";
 import { expect } from "vitest";
 
-ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
+unmountComponent();
 
 beforeEach(() => {
   localStorage.clear();

+ 2 - 3
packages/excalidraw/tests/selection.test.tsx

@@ -1,11 +1,11 @@
 import React from "react";
-import ReactDOM from "react-dom";
 import {
   render,
   fireEvent,
   mockBoundingClientRect,
   restoreOriginalGetBoundingClientRect,
   assertSelectedElements,
+  unmountComponent,
 } from "./test-utils";
 import { Excalidraw } from "../index";
 import * as StaticScene from "../renderer/staticScene";
@@ -17,8 +17,7 @@ import { Keyboard, Pointer, UI } from "./helpers/ui";
 import { SHAPES } from "../shapes";
 import { vi } from "vitest";
 
-// Unmount ReactDOM from root
-ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
+unmountComponent();
 
 const renderInteractiveScene = vi.spyOn(
   InteractiveCanvas,

+ 9 - 1
packages/excalidraw/tests/test-utils.ts

@@ -2,7 +2,13 @@ import "pepjs";
 
 import type { RenderResult, RenderOptions } from "@testing-library/react";
 import { act } from "@testing-library/react";
-import { render, queries, waitFor, fireEvent } from "@testing-library/react";
+import {
+  render,
+  queries,
+  waitFor,
+  fireEvent,
+  cleanup,
+} from "@testing-library/react";
 
 import * as toolQueries from "./queries/toolQueries";
 import type { ImportedDataState } from "../data/types";
@@ -16,6 +22,8 @@ import { ORIG_ID } from "../constants";
 import { arrayToMap } from "../utils";
 import type { AllPossibleKeys } from "../utility-types";
 
+export { cleanup as unmountComponent };
+
 const customQueries = {
   ...queries,
   ...toolQueries,

+ 2 - 4
packages/excalidraw/tests/zindex.test.tsx

@@ -1,6 +1,5 @@
 import React from "react";
-import ReactDOM from "react-dom";
-import { act, getCloneByOrigId, render } from "./test-utils";
+import { act, getCloneByOrigId, render, unmountComponent } from "./test-utils";
 import { Excalidraw } from "../index";
 import { reseed } from "../random";
 import {
@@ -19,8 +18,7 @@ import type {
   ExcalidrawSelectionElement,
 } from "../element/types";
 
-// Unmount ReactDOM from root
-ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
+unmountComponent();
 
 beforeEach(() => {
   localStorage.clear();

+ 1 - 0
packages/excalidraw/types.ts

@@ -1,3 +1,4 @@
+import type { JSX } from "react";
 import type React from "react";
 import type {
   PointerType,

+ 1 - 0
tsconfig.json

@@ -1,5 +1,6 @@
 {
   "compilerOptions": {
+    "types": ["vitest/globals", "@testing-library/jest-dom"],
     "target": "ESNext",
     "lib": ["dom", "dom.iterable", "esnext"],
     "allowJs": true,

File diff suppressed because it is too large
+ 522 - 669
yarn.lock


Some files were not shown because too many files changed in this diff