frame.test.tsx 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563
  1. import {
  2. convertToExcalidrawElements,
  3. Excalidraw,
  4. } from "@excalidraw/excalidraw";
  5. import { API } from "@excalidraw/excalidraw/tests/helpers/api";
  6. import { Keyboard, Pointer } from "@excalidraw/excalidraw/tests/helpers/ui";
  7. import {
  8. getCloneByOrigId,
  9. render,
  10. } from "@excalidraw/excalidraw/tests/test-utils";
  11. import type { ExcalidrawElement } from "../src/types";
  12. const { h } = window;
  13. const mouse = new Pointer("mouse");
  14. describe("adding elements to frames", () => {
  15. type ElementType = string;
  16. const assertOrder = (
  17. els: readonly { type: ElementType }[],
  18. order: ElementType[],
  19. ) => {
  20. expect(els.map((el) => el.type)).toEqual(order);
  21. };
  22. const reorderElements = <T extends { type: ElementType }>(
  23. els: readonly T[],
  24. order: ElementType[],
  25. ) => {
  26. return order.reduce((acc: T[], el) => {
  27. acc.push(els.find((e) => e.type === el)!);
  28. return acc;
  29. }, []);
  30. };
  31. function resizeFrameOverElement(
  32. frame: ExcalidrawElement,
  33. element: ExcalidrawElement,
  34. ) {
  35. mouse.clickAt(0, 0);
  36. mouse.downAt(frame.x + frame.width, frame.y + frame.height);
  37. mouse.moveTo(
  38. element.x + element.width + 50,
  39. element.y + element.height + 50,
  40. );
  41. mouse.up();
  42. }
  43. function dragElementIntoFrame(
  44. frame: ExcalidrawElement,
  45. element: ExcalidrawElement,
  46. ) {
  47. mouse.clickAt(element.x, element.y);
  48. mouse.downAt(element.x + element.width / 2, element.y + element.height / 2);
  49. mouse.moveTo(frame.x + frame.width / 2, frame.y + frame.height / 2);
  50. mouse.up();
  51. }
  52. function selectElementAndDuplicate(
  53. element: ExcalidrawElement,
  54. moveTo: [number, number] = [element.x + 25, element.y + 25],
  55. ) {
  56. const [x, y] = [
  57. element.x + element.width / 2,
  58. element.y + element.height / 2,
  59. ];
  60. Keyboard.withModifierKeys({ alt: true }, () => {
  61. mouse.downAt(x, y);
  62. mouse.moveTo(moveTo[0], moveTo[1]);
  63. mouse.up();
  64. });
  65. }
  66. function expectEqualIds(expected: ExcalidrawElement[]) {
  67. expect(h.elements.map((x) => x.id)).toEqual(expected.map((x) => x.id));
  68. }
  69. let frame: ExcalidrawElement;
  70. let rect1: ExcalidrawElement;
  71. let rect2: ExcalidrawElement;
  72. let rect3: ExcalidrawElement;
  73. let rect4: ExcalidrawElement;
  74. let text: ExcalidrawElement;
  75. let arrow: ExcalidrawElement;
  76. beforeEach(async () => {
  77. await render(<Excalidraw />);
  78. frame = API.createElement({ id: "id0", type: "frame", x: 0, width: 150 });
  79. rect1 = API.createElement({
  80. id: "id1",
  81. type: "rectangle",
  82. x: -1000,
  83. });
  84. rect2 = API.createElement({
  85. id: "id2",
  86. type: "rectangle",
  87. x: 200,
  88. width: 50,
  89. });
  90. rect3 = API.createElement({
  91. id: "id3",
  92. type: "rectangle",
  93. x: 400,
  94. width: 50,
  95. });
  96. rect4 = API.createElement({
  97. id: "id4",
  98. type: "rectangle",
  99. x: 1000,
  100. width: 50,
  101. });
  102. text = API.createElement({
  103. id: "id5",
  104. type: "text",
  105. x: 100,
  106. });
  107. arrow = API.createElement({
  108. id: "id6",
  109. type: "arrow",
  110. x: 100,
  111. boundElements: [{ id: text.id, type: "text" }],
  112. });
  113. });
  114. const commonTestCases = async (
  115. func: typeof resizeFrameOverElement | typeof dragElementIntoFrame,
  116. ) => {
  117. describe.skip("when frame is in a layer below", async () => {
  118. it("should add an element", async () => {
  119. API.setElements([frame, rect2]);
  120. func(frame, rect2);
  121. expect(h.elements[0].frameId).toBe(frame.id);
  122. expectEqualIds([rect2, frame]);
  123. });
  124. it("should add elements", async () => {
  125. API.setElements([frame, rect2, rect3]);
  126. func(frame, rect2);
  127. func(frame, rect3);
  128. expect(rect2.frameId).toBe(frame.id);
  129. expect(rect3.frameId).toBe(frame.id);
  130. expectEqualIds([rect2, rect3, frame]);
  131. });
  132. it("should add elements when there are other other elements in between", async () => {
  133. API.setElements([frame, rect1, rect2, rect4, rect3]);
  134. func(frame, rect2);
  135. func(frame, rect3);
  136. expect(rect2.frameId).toBe(frame.id);
  137. expect(rect3.frameId).toBe(frame.id);
  138. expectEqualIds([rect2, rect3, frame, rect1, rect4]);
  139. });
  140. it("should add elements when there are other elements in between and the order is reversed", async () => {
  141. API.setElements([frame, rect3, rect4, rect2, rect1]);
  142. func(frame, rect2);
  143. func(frame, rect3);
  144. expect(rect2.frameId).toBe(frame.id);
  145. expect(rect3.frameId).toBe(frame.id);
  146. expectEqualIds([rect2, rect3, frame, rect4, rect1]);
  147. });
  148. });
  149. describe.skip("when frame is in a layer above", async () => {
  150. it("should add an element", async () => {
  151. API.setElements([rect2, frame]);
  152. func(frame, rect2);
  153. expect(h.elements[0].frameId).toBe(frame.id);
  154. expectEqualIds([rect2, frame]);
  155. });
  156. it("should add elements", async () => {
  157. API.setElements([rect2, rect3, frame]);
  158. func(frame, rect2);
  159. func(frame, rect3);
  160. expect(rect2.frameId).toBe(frame.id);
  161. expect(rect3.frameId).toBe(frame.id);
  162. expectEqualIds([rect3, rect2, frame]);
  163. });
  164. it("should add elements when there are other other elements in between", async () => {
  165. API.setElements([rect1, rect2, rect4, rect3, frame]);
  166. func(frame, rect2);
  167. func(frame, rect3);
  168. expect(rect2.frameId).toBe(frame.id);
  169. expect(rect3.frameId).toBe(frame.id);
  170. expectEqualIds([rect1, rect4, rect3, rect2, frame]);
  171. });
  172. it("should add elements when there are other elements in between and the order is reversed", async () => {
  173. API.setElements([rect3, rect4, rect2, rect1, frame]);
  174. func(frame, rect2);
  175. func(frame, rect3);
  176. expect(rect2.frameId).toBe(frame.id);
  177. expect(rect3.frameId).toBe(frame.id);
  178. expectEqualIds([rect4, rect1, rect3, rect2, frame]);
  179. });
  180. });
  181. describe("when frame is in an inner layer", async () => {
  182. it.skip("should add elements", async () => {
  183. API.setElements([rect2, frame, rect3]);
  184. func(frame, rect2);
  185. func(frame, rect3);
  186. expect(rect2.frameId).toBe(frame.id);
  187. expect(rect3.frameId).toBe(frame.id);
  188. expectEqualIds([rect2, rect3, frame]);
  189. });
  190. it.skip("should add elements when there are other other elements in between", async () => {
  191. API.setElements([rect2, rect1, frame, rect4, rect3]);
  192. func(frame, rect2);
  193. func(frame, rect3);
  194. expect(rect2.frameId).toBe(frame.id);
  195. expect(rect3.frameId).toBe(frame.id);
  196. expectEqualIds([rect1, rect2, rect3, frame, rect4]);
  197. });
  198. it.skip("should add elements when there are other elements in between and the order is reversed", async () => {
  199. API.setElements([rect3, rect4, frame, rect2, rect1]);
  200. func(frame, rect2);
  201. func(frame, rect3);
  202. expect(rect2.frameId).toBe(frame.id);
  203. expect(rect3.frameId).toBe(frame.id);
  204. expectEqualIds([rect4, rect3, rect2, frame, rect1]);
  205. });
  206. });
  207. };
  208. const resizingTest = async (
  209. containerType: "arrow" | "rectangle",
  210. initialOrder: ElementType[],
  211. expectedOrder: ElementType[],
  212. ) => {
  213. await render(<Excalidraw />);
  214. const frame = API.createElement({ type: "frame", x: 0, y: 0 });
  215. API.setElements(
  216. reorderElements(
  217. [
  218. frame,
  219. ...convertToExcalidrawElements([
  220. {
  221. type: containerType,
  222. x: 100,
  223. y: 100,
  224. height: 10,
  225. label: { text: "xx" },
  226. },
  227. ]),
  228. ],
  229. initialOrder,
  230. ),
  231. );
  232. assertOrder(h.elements, initialOrder);
  233. expect(h.elements[1].frameId).toBe(null);
  234. expect(h.elements[2].frameId).toBe(null);
  235. const container = h.elements[1];
  236. resizeFrameOverElement(frame, container);
  237. assertOrder(h.elements, expectedOrder);
  238. expect(h.elements[0].frameId).toBe(frame.id);
  239. expect(h.elements[1].frameId).toBe(frame.id);
  240. };
  241. describe("resizing frame over elements", async () => {
  242. await commonTestCases(resizeFrameOverElement);
  243. it.skip("resizing over text containers and labelled arrows", async () => {
  244. await resizingTest(
  245. "rectangle",
  246. ["frame", "rectangle", "text"],
  247. ["rectangle", "text", "frame"],
  248. );
  249. await resizingTest(
  250. "rectangle",
  251. ["frame", "text", "rectangle"],
  252. ["rectangle", "text", "frame"],
  253. );
  254. await resizingTest(
  255. "rectangle",
  256. ["rectangle", "text", "frame"],
  257. ["rectangle", "text", "frame"],
  258. );
  259. await resizingTest(
  260. "rectangle",
  261. ["text", "rectangle", "frame"],
  262. ["rectangle", "text", "frame"],
  263. );
  264. await resizingTest(
  265. "arrow",
  266. ["frame", "arrow", "text"],
  267. ["arrow", "text", "frame"],
  268. );
  269. await resizingTest(
  270. "arrow",
  271. ["text", "arrow", "frame"],
  272. ["arrow", "text", "frame"],
  273. );
  274. await resizingTest(
  275. "arrow",
  276. ["frame", "arrow", "text"],
  277. ["arrow", "text", "frame"],
  278. );
  279. // FIXME failing in tests (it fails to add elements to frame for some
  280. // reason) but works in browser. (╯°□°)╯︵ ┻━┻
  281. //
  282. // Looks like the `getElementsCompletelyInFrame()` doesn't work
  283. // in these cases.
  284. //
  285. // await testElements(
  286. // "arrow",
  287. // ["arrow", "text", "frame"],
  288. // ["arrow", "text", "frame"],
  289. // );
  290. });
  291. it.skip("should add arrow bound with text when frame is in a layer below", async () => {
  292. API.setElements([frame, arrow, text]);
  293. resizeFrameOverElement(frame, arrow);
  294. expect(arrow.frameId).toBe(frame.id);
  295. expect(text.frameId).toBe(frame.id);
  296. expectEqualIds([arrow, text, frame]);
  297. });
  298. it("should add arrow bound with text when frame is in a layer above", async () => {
  299. API.setElements([arrow, text, frame]);
  300. resizeFrameOverElement(frame, arrow);
  301. expect(arrow.frameId).toBe(frame.id);
  302. expect(text.frameId).toBe(frame.id);
  303. expectEqualIds([arrow, text, frame]);
  304. });
  305. it.skip("should add arrow bound with text when frame is in an inner layer", async () => {
  306. API.setElements([arrow, frame, text]);
  307. resizeFrameOverElement(frame, arrow);
  308. expect(arrow.frameId).toBe(frame.id);
  309. expect(text.frameId).toBe(frame.id);
  310. expectEqualIds([arrow, text, frame]);
  311. });
  312. });
  313. describe("resizing frame over elements but downwards", async () => {
  314. it.skip("should add elements when frame is in a layer below", async () => {
  315. API.setElements([frame, rect1, rect2, rect3, rect4]);
  316. resizeFrameOverElement(frame, rect4);
  317. resizeFrameOverElement(frame, rect3);
  318. expect(rect2.frameId).toBe(frame.id);
  319. expect(rect3.frameId).toBe(frame.id);
  320. expectEqualIds([rect2, rect3, frame, rect4, rect1]);
  321. });
  322. it.skip("should add elements when frame is in a layer above", async () => {
  323. API.setElements([rect1, rect2, rect3, rect4, frame]);
  324. resizeFrameOverElement(frame, rect4);
  325. resizeFrameOverElement(frame, rect3);
  326. expect(rect2.frameId).toBe(frame.id);
  327. expect(rect3.frameId).toBe(frame.id);
  328. expectEqualIds([rect1, rect2, rect3, frame, rect4]);
  329. });
  330. it.skip("should add elements when frame is in an inner layer", async () => {
  331. API.setElements([rect1, rect2, frame, rect3, rect4]);
  332. resizeFrameOverElement(frame, rect4);
  333. resizeFrameOverElement(frame, rect3);
  334. expect(rect2.frameId).toBe(frame.id);
  335. expect(rect3.frameId).toBe(frame.id);
  336. expectEqualIds([rect1, rect2, rect3, frame, rect4]);
  337. });
  338. });
  339. describe("dragging elements into the frame", async () => {
  340. await commonTestCases(dragElementIntoFrame);
  341. it.skip("should drag element inside, duplicate it and keep it in frame", () => {
  342. API.setElements([frame, rect2]);
  343. dragElementIntoFrame(frame, rect2);
  344. selectElementAndDuplicate(rect2);
  345. const rect2_copy = getCloneByOrigId(rect2.id);
  346. expect(rect2_copy.frameId).toBe(frame.id);
  347. expect(rect2.frameId).toBe(frame.id);
  348. expectEqualIds([rect2_copy, rect2, frame]);
  349. });
  350. it.skip("should drag element inside, duplicate it and remove it from frame", () => {
  351. API.setElements([frame, rect2]);
  352. dragElementIntoFrame(frame, rect2);
  353. // move the rect2 outside the frame
  354. selectElementAndDuplicate(rect2, [-1000, -1000]);
  355. const rect2_copy = getCloneByOrigId(rect2.id);
  356. expect(rect2_copy.frameId).toBe(frame.id);
  357. expect(rect2.frameId).toBe(null);
  358. expectEqualIds([rect2_copy, frame, rect2]);
  359. });
  360. it("random order 01", () => {
  361. const frame1 = API.createElement({
  362. type: "frame",
  363. x: 0,
  364. y: 0,
  365. width: 100,
  366. height: 100,
  367. });
  368. const frame2 = API.createElement({
  369. type: "frame",
  370. x: 200,
  371. y: 0,
  372. width: 100,
  373. height: 100,
  374. });
  375. const frame3 = API.createElement({
  376. type: "frame",
  377. x: 300,
  378. y: 0,
  379. width: 100,
  380. height: 100,
  381. });
  382. const rectangle1 = API.createElement({
  383. type: "rectangle",
  384. x: 25,
  385. y: 25,
  386. width: 50,
  387. height: 50,
  388. frameId: frame1.id,
  389. });
  390. const rectangle2 = API.createElement({
  391. type: "rectangle",
  392. x: 225,
  393. y: 25,
  394. width: 50,
  395. height: 50,
  396. frameId: frame2.id,
  397. });
  398. const rectangle3 = API.createElement({
  399. type: "rectangle",
  400. x: 325,
  401. y: 25,
  402. width: 50,
  403. height: 50,
  404. frameId: frame3.id,
  405. });
  406. const rectangle4 = API.createElement({
  407. type: "rectangle",
  408. x: 350,
  409. y: 25,
  410. width: 50,
  411. height: 50,
  412. frameId: frame3.id,
  413. });
  414. API.setElements([
  415. frame1,
  416. rectangle4,
  417. rectangle1,
  418. rectangle3,
  419. frame3,
  420. rectangle2,
  421. frame2,
  422. ]);
  423. API.setSelectedElements([rectangle2]);
  424. const origSize = h.elements.length;
  425. expect(h.elements.length).toBe(origSize);
  426. dragElementIntoFrame(frame3, rectangle2);
  427. expect(h.elements.length).toBe(origSize);
  428. });
  429. it("random order 02", () => {
  430. const frame1 = API.createElement({
  431. type: "frame",
  432. x: 0,
  433. y: 0,
  434. width: 100,
  435. height: 100,
  436. });
  437. const frame2 = API.createElement({
  438. type: "frame",
  439. x: 200,
  440. y: 0,
  441. width: 100,
  442. height: 100,
  443. });
  444. const rectangle1 = API.createElement({
  445. type: "rectangle",
  446. x: 25,
  447. y: 25,
  448. width: 50,
  449. height: 50,
  450. frameId: frame1.id,
  451. });
  452. const rectangle2 = API.createElement({
  453. type: "rectangle",
  454. x: 225,
  455. y: 25,
  456. width: 50,
  457. height: 50,
  458. frameId: frame2.id,
  459. });
  460. API.setElements([rectangle1, rectangle2, frame1, frame2]);
  461. API.setSelectedElements([rectangle2]);
  462. expect(h.elements.length).toBe(4);
  463. dragElementIntoFrame(frame2, rectangle1);
  464. expect(h.elements.length).toBe(4);
  465. });
  466. });
  467. });