dragCreate.test.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. import ReactDOM from "react-dom";
  2. import { Excalidraw } from "../index";
  3. import * as StaticScene from "../renderer/staticScene";
  4. import * as InteractiveScene from "../renderer/interactiveScene";
  5. import { KEYS } from "../keys";
  6. import {
  7. render,
  8. fireEvent,
  9. mockBoundingClientRect,
  10. restoreOriginalGetBoundingClientRect,
  11. } from "./test-utils";
  12. import type { ExcalidrawLinearElement } from "../element/types";
  13. import { reseed } from "../random";
  14. import { vi } from "vitest";
  15. // Unmount ReactDOM from root
  16. ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
  17. const renderInteractiveScene = vi.spyOn(
  18. InteractiveScene,
  19. "renderInteractiveScene",
  20. );
  21. const renderStaticScene = vi.spyOn(StaticScene, "renderStaticScene");
  22. beforeEach(() => {
  23. localStorage.clear();
  24. renderInteractiveScene.mockClear();
  25. renderStaticScene.mockClear();
  26. reseed(7);
  27. });
  28. const { h } = window;
  29. describe("Test dragCreate", () => {
  30. describe("add element to the scene when pointer dragging long enough", () => {
  31. it("rectangle", async () => {
  32. const { getByToolName, container } = await render(<Excalidraw />);
  33. // select tool
  34. const tool = getByToolName("rectangle");
  35. fireEvent.click(tool);
  36. const canvas = container.querySelector("canvas.interactive")!;
  37. // start from (30, 20)
  38. fireEvent.pointerDown(canvas, { clientX: 30, clientY: 20 });
  39. // move to (60,70)
  40. fireEvent.pointerMove(canvas, { clientX: 60, clientY: 70 });
  41. // finish (position does not matter)
  42. fireEvent.pointerUp(canvas);
  43. expect(renderInteractiveScene).toHaveBeenCalledTimes(6);
  44. expect(renderStaticScene).toHaveBeenCalledTimes(6);
  45. expect(h.state.selectionElement).toBeNull();
  46. expect(h.elements.length).toEqual(1);
  47. expect(h.elements[0].type).toEqual("rectangle");
  48. expect(h.elements[0].x).toEqual(30);
  49. expect(h.elements[0].y).toEqual(20);
  50. expect(h.elements[0].width).toEqual(30); // 60 - 30
  51. expect(h.elements[0].height).toEqual(50); // 70 - 20
  52. expect(h.elements.length).toMatchSnapshot();
  53. h.elements.forEach((element) => expect(element).toMatchSnapshot());
  54. });
  55. it("ellipse", async () => {
  56. const { getByToolName, container } = await render(<Excalidraw />);
  57. // select tool
  58. const tool = getByToolName("ellipse");
  59. fireEvent.click(tool);
  60. const canvas = container.querySelector("canvas.interactive")!;
  61. // start from (30, 20)
  62. fireEvent.pointerDown(canvas, { clientX: 30, clientY: 20 });
  63. // move to (60,70)
  64. fireEvent.pointerMove(canvas, { clientX: 60, clientY: 70 });
  65. // finish (position does not matter)
  66. fireEvent.pointerUp(canvas);
  67. expect(renderInteractiveScene).toHaveBeenCalledTimes(6);
  68. expect(renderStaticScene).toHaveBeenCalledTimes(6);
  69. expect(h.state.selectionElement).toBeNull();
  70. expect(h.elements.length).toEqual(1);
  71. expect(h.elements[0].type).toEqual("ellipse");
  72. expect(h.elements[0].x).toEqual(30);
  73. expect(h.elements[0].y).toEqual(20);
  74. expect(h.elements[0].width).toEqual(30); // 60 - 30
  75. expect(h.elements[0].height).toEqual(50); // 70 - 20
  76. expect(h.elements.length).toMatchSnapshot();
  77. h.elements.forEach((element) => expect(element).toMatchSnapshot());
  78. });
  79. it("diamond", async () => {
  80. const { getByToolName, container } = await render(<Excalidraw />);
  81. // select tool
  82. const tool = getByToolName("diamond");
  83. fireEvent.click(tool);
  84. const canvas = container.querySelector("canvas.interactive")!;
  85. // start from (30, 20)
  86. fireEvent.pointerDown(canvas, { clientX: 30, clientY: 20 });
  87. // move to (60,70)
  88. fireEvent.pointerMove(canvas, { clientX: 60, clientY: 70 });
  89. // finish (position does not matter)
  90. fireEvent.pointerUp(canvas);
  91. expect(renderInteractiveScene).toHaveBeenCalledTimes(6);
  92. expect(renderStaticScene).toHaveBeenCalledTimes(6);
  93. expect(h.state.selectionElement).toBeNull();
  94. expect(h.elements.length).toEqual(1);
  95. expect(h.elements[0].type).toEqual("diamond");
  96. expect(h.elements[0].x).toEqual(30);
  97. expect(h.elements[0].y).toEqual(20);
  98. expect(h.elements[0].width).toEqual(30); // 60 - 30
  99. expect(h.elements[0].height).toEqual(50); // 70 - 20
  100. expect(h.elements.length).toMatchSnapshot();
  101. h.elements.forEach((element) => expect(element).toMatchSnapshot());
  102. });
  103. it("arrow", async () => {
  104. const { getByToolName, container } = await render(<Excalidraw />);
  105. // select tool
  106. const tool = getByToolName("arrow");
  107. fireEvent.click(tool);
  108. const canvas = container.querySelector("canvas.interactive")!;
  109. // start from (30, 20)
  110. fireEvent.pointerDown(canvas, { clientX: 30, clientY: 20 });
  111. // move to (60,70)
  112. fireEvent.pointerMove(canvas, { clientX: 60, clientY: 70 });
  113. // finish (position does not matter)
  114. fireEvent.pointerUp(canvas);
  115. expect(renderInteractiveScene).toHaveBeenCalledTimes(6);
  116. expect(renderStaticScene).toHaveBeenCalledTimes(6);
  117. expect(h.state.selectionElement).toBeNull();
  118. expect(h.elements.length).toEqual(1);
  119. const element = h.elements[0] as ExcalidrawLinearElement;
  120. expect(element.type).toEqual("arrow");
  121. expect(element.x).toEqual(30);
  122. expect(element.y).toEqual(20);
  123. expect(element.points.length).toEqual(2);
  124. expect(element.points[0]).toEqual([0, 0]);
  125. expect(element.points[1]).toEqual([30, 50]); // (60 - 30, 70 - 20)
  126. expect(h.elements.length).toMatchSnapshot();
  127. h.elements.forEach((element) => expect(element).toMatchSnapshot());
  128. });
  129. it("line", async () => {
  130. const { getByToolName, container } = await render(<Excalidraw />);
  131. // select tool
  132. const tool = getByToolName("line");
  133. fireEvent.click(tool);
  134. const canvas = container.querySelector("canvas.interactive")!;
  135. // start from (30, 20)
  136. fireEvent.pointerDown(canvas, { clientX: 30, clientY: 20 });
  137. // move to (60,70)
  138. fireEvent.pointerMove(canvas, { clientX: 60, clientY: 70 });
  139. // finish (position does not matter)
  140. fireEvent.pointerUp(canvas);
  141. expect(renderInteractiveScene).toHaveBeenCalledTimes(6);
  142. expect(renderStaticScene).toHaveBeenCalledTimes(6);
  143. expect(h.state.selectionElement).toBeNull();
  144. expect(h.elements.length).toEqual(1);
  145. const element = h.elements[0] as ExcalidrawLinearElement;
  146. expect(element.type).toEqual("line");
  147. expect(element.x).toEqual(30);
  148. expect(element.y).toEqual(20);
  149. expect(element.points.length).toEqual(2);
  150. expect(element.points[0]).toEqual([0, 0]);
  151. expect(element.points[1]).toEqual([30, 50]); // (60 - 30, 70 - 20)
  152. h.elements.forEach((element) => expect(element).toMatchSnapshot());
  153. });
  154. });
  155. describe("do not add element to the scene if size is too small", () => {
  156. beforeAll(() => {
  157. mockBoundingClientRect();
  158. });
  159. afterAll(() => {
  160. restoreOriginalGetBoundingClientRect();
  161. });
  162. it("rectangle", async () => {
  163. const { getByToolName, container } = await render(<Excalidraw />);
  164. // select tool
  165. const tool = getByToolName("rectangle");
  166. fireEvent.click(tool);
  167. const canvas = container.querySelector("canvas.interactive")!;
  168. // start from (30, 20)
  169. fireEvent.pointerDown(canvas, { clientX: 30, clientY: 20 });
  170. // finish (position does not matter)
  171. fireEvent.pointerUp(canvas);
  172. expect(renderInteractiveScene).toHaveBeenCalledTimes(5);
  173. expect(renderStaticScene).toHaveBeenCalledTimes(5);
  174. expect(h.state.selectionElement).toBeNull();
  175. expect(h.elements.length).toEqual(0);
  176. });
  177. it("ellipse", async () => {
  178. const { getByToolName, container } = await render(<Excalidraw />);
  179. // select tool
  180. const tool = getByToolName("ellipse");
  181. fireEvent.click(tool);
  182. const canvas = container.querySelector("canvas.interactive")!;
  183. // start from (30, 20)
  184. fireEvent.pointerDown(canvas, { clientX: 30, clientY: 20 });
  185. // finish (position does not matter)
  186. fireEvent.pointerUp(canvas);
  187. expect(renderInteractiveScene).toHaveBeenCalledTimes(5);
  188. expect(renderStaticScene).toHaveBeenCalledTimes(5);
  189. expect(h.state.selectionElement).toBeNull();
  190. expect(h.elements.length).toEqual(0);
  191. });
  192. it("diamond", async () => {
  193. const { getByToolName, container } = await render(<Excalidraw />);
  194. // select tool
  195. const tool = getByToolName("diamond");
  196. fireEvent.click(tool);
  197. const canvas = container.querySelector("canvas.interactive")!;
  198. // start from (30, 20)
  199. fireEvent.pointerDown(canvas, { clientX: 30, clientY: 20 });
  200. // finish (position does not matter)
  201. fireEvent.pointerUp(canvas);
  202. expect(renderInteractiveScene).toHaveBeenCalledTimes(5);
  203. expect(renderStaticScene).toHaveBeenCalledTimes(5);
  204. expect(h.state.selectionElement).toBeNull();
  205. expect(h.elements.length).toEqual(0);
  206. });
  207. it("arrow", async () => {
  208. const { getByToolName, container } = await render(
  209. <Excalidraw handleKeyboardGlobally={true} />,
  210. );
  211. // select tool
  212. const tool = getByToolName("arrow");
  213. fireEvent.click(tool);
  214. const canvas = container.querySelector("canvas.interactive")!;
  215. // start from (30, 20)
  216. fireEvent.pointerDown(canvas, { clientX: 30, clientY: 20 });
  217. // finish (position does not matter)
  218. fireEvent.pointerUp(canvas);
  219. // we need to finalize it because arrows and lines enter multi-mode
  220. fireEvent.keyDown(document, {
  221. key: KEYS.ENTER,
  222. });
  223. expect(renderInteractiveScene).toHaveBeenCalledTimes(6);
  224. expect(renderStaticScene).toHaveBeenCalledTimes(6);
  225. expect(h.state.selectionElement).toBeNull();
  226. expect(h.elements.length).toEqual(0);
  227. });
  228. it("line", async () => {
  229. const { getByToolName, container } = await render(
  230. <Excalidraw handleKeyboardGlobally={true} />,
  231. );
  232. // select tool
  233. const tool = getByToolName("line");
  234. fireEvent.click(tool);
  235. const canvas = container.querySelector("canvas.interactive")!;
  236. // start from (30, 20)
  237. fireEvent.pointerDown(canvas, { clientX: 30, clientY: 20 });
  238. // finish (position does not matter)
  239. fireEvent.pointerUp(canvas);
  240. // we need to finalize it because arrows and lines enter multi-mode
  241. fireEvent.keyDown(document, {
  242. key: KEYS.ENTER,
  243. });
  244. expect(renderInteractiveScene).toHaveBeenCalledTimes(6);
  245. expect(renderStaticScene).toHaveBeenCalledTimes(6);
  246. expect(h.state.selectionElement).toBeNull();
  247. expect(h.elements.length).toEqual(0);
  248. });
  249. });
  250. });