binding.test.tsx 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697
  1. import { KEYS, arrayToMap } from "@excalidraw/common";
  2. import { pointFrom } from "@excalidraw/math";
  3. import { actionWrapTextInContainer } from "@excalidraw/excalidraw/actions/actionBoundText";
  4. import { Excalidraw, isLinearElement } from "@excalidraw/excalidraw";
  5. import { API } from "@excalidraw/excalidraw/tests/helpers/api";
  6. import { UI, Pointer, Keyboard } from "@excalidraw/excalidraw/tests/helpers/ui";
  7. import {
  8. act,
  9. fireEvent,
  10. render,
  11. } from "@excalidraw/excalidraw/tests/test-utils";
  12. import { defaultLang, setLanguage } from "@excalidraw/excalidraw/i18n";
  13. import { getTransformHandles } from "../src/transformHandles";
  14. import {
  15. getTextEditor,
  16. TEXT_EDITOR_SELECTOR,
  17. } from "../../excalidraw/tests/queries/dom";
  18. import type {
  19. ExcalidrawArrowElement,
  20. ExcalidrawLinearElement,
  21. FixedPointBinding,
  22. } from "../src/types";
  23. const { h } = window;
  24. const mouse = new Pointer("mouse");
  25. describe("binding for simple arrows", () => {
  26. describe("when both endpoints are bound inside the same element", () => {
  27. beforeEach(async () => {
  28. mouse.reset();
  29. await act(() => {
  30. return setLanguage(defaultLang);
  31. });
  32. await render(<Excalidraw handleKeyboardGlobally={true} />);
  33. });
  34. it("should create an `inside` binding", () => {
  35. // Create a rectangle
  36. UI.clickTool("rectangle");
  37. mouse.reset();
  38. mouse.downAt(100, 100);
  39. mouse.moveTo(200, 200);
  40. mouse.up();
  41. const rect = API.getSelectedElement();
  42. // Draw arrow with endpoint inside the filled rectangle
  43. UI.clickTool("arrow");
  44. mouse.downAt(110, 110);
  45. mouse.moveTo(160, 160);
  46. mouse.up();
  47. const arrow = API.getSelectedElement() as ExcalidrawLinearElement;
  48. expect(arrow.x).toBe(110);
  49. expect(arrow.y).toBe(110);
  50. // Should bind to the rectangle since endpoint is inside
  51. expect(arrow.startBinding?.elementId).toBe(rect.id);
  52. expect(arrow.endBinding?.elementId).toBe(rect.id);
  53. const startBinding = arrow.startBinding as FixedPointBinding;
  54. expect(startBinding.fixedPoint[0]).toBeGreaterThanOrEqual(0);
  55. expect(startBinding.fixedPoint[0]).toBeLessThanOrEqual(1);
  56. expect(startBinding.fixedPoint[1]).toBeGreaterThanOrEqual(0);
  57. expect(startBinding.fixedPoint[1]).toBeLessThanOrEqual(1);
  58. expect(startBinding.mode).toBe("inside");
  59. const endBinding = arrow.endBinding as FixedPointBinding;
  60. expect(endBinding.fixedPoint[0]).toBeGreaterThanOrEqual(0);
  61. expect(endBinding.fixedPoint[0]).toBeLessThanOrEqual(1);
  62. expect(endBinding.fixedPoint[1]).toBeGreaterThanOrEqual(0);
  63. expect(endBinding.fixedPoint[1]).toBeLessThanOrEqual(1);
  64. expect(endBinding.mode).toBe("inside");
  65. // Move the bindable
  66. mouse.downAt(100, 150);
  67. mouse.moveTo(280, 110);
  68. mouse.up();
  69. // Check if the arrow moved
  70. expect(arrow.x).toBe(290);
  71. expect(arrow.y).toBe(70);
  72. // Restore bindable
  73. mouse.reset();
  74. mouse.downAt(280, 110);
  75. mouse.moveTo(130, 110);
  76. mouse.up();
  77. // Move the start point of the arrow to check if
  78. // the behavior remains the same for old arrows
  79. mouse.reset();
  80. mouse.downAt(110, 110);
  81. mouse.moveTo(120, 120);
  82. mouse.up();
  83. // Move the bindable again
  84. mouse.reset();
  85. mouse.downAt(130, 110);
  86. mouse.moveTo(280, 110);
  87. mouse.up();
  88. // Check if the arrow moved
  89. expect(arrow.x).toBe(290);
  90. expect(arrow.y).toBe(70);
  91. });
  92. it("3+ point arrow should be dragged along with the bindable", () => {
  93. // Create two rectangles as binding targets
  94. const rectLeft = API.createElement({
  95. type: "rectangle",
  96. x: 0,
  97. y: 0,
  98. width: 100,
  99. height: 100,
  100. });
  101. const rectRight = API.createElement({
  102. type: "rectangle",
  103. x: 300,
  104. y: 0,
  105. width: 100,
  106. height: 100,
  107. });
  108. // Create a non-elbowed arrow with inner points bound to different elements
  109. const arrow = API.createElement({
  110. type: "arrow",
  111. x: 100,
  112. y: 50,
  113. width: 200,
  114. height: 0,
  115. points: [
  116. pointFrom(0, 0), // start point
  117. pointFrom(50, -20), // first inner point
  118. pointFrom(150, 20), // second inner point
  119. pointFrom(200, 0), // end point
  120. ],
  121. startBinding: {
  122. elementId: rectLeft.id,
  123. fixedPoint: [0.5, 0.5],
  124. mode: "orbit",
  125. },
  126. endBinding: {
  127. elementId: rectRight.id,
  128. fixedPoint: [0.5, 0.5],
  129. mode: "orbit",
  130. },
  131. });
  132. API.setElements([rectLeft, rectRight, arrow]);
  133. // Store original inner point positions
  134. const originalInnerPoint1 = [...arrow.points[1]];
  135. const originalInnerPoint2 = [...arrow.points[2]];
  136. // Move the right rectangle down by 50 pixels
  137. mouse.reset();
  138. mouse.downAt(350, 50); // Click on the right rectangle
  139. mouse.moveTo(350, 100); // Move it down
  140. mouse.up();
  141. // Verify that inner points did NOT move when bound to different elements
  142. // The arrow should NOT translate inner points proportionally when only one end moves
  143. expect(arrow.points[1][0]).toBe(originalInnerPoint1[0]);
  144. expect(arrow.points[1][1]).toBe(originalInnerPoint1[1]);
  145. expect(arrow.points[2][0]).toBe(originalInnerPoint2[0]);
  146. expect(arrow.points[2][1]).toBe(originalInnerPoint2[1]);
  147. });
  148. });
  149. describe("when arrow is outside of shape", () => {
  150. beforeEach(async () => {
  151. mouse.reset();
  152. await act(() => {
  153. return setLanguage(defaultLang);
  154. });
  155. await render(<Excalidraw handleKeyboardGlobally={true} />);
  156. });
  157. it("should handle new arrow start point binding", () => {
  158. // Create a rectangle
  159. UI.clickTool("rectangle");
  160. mouse.downAt(100, 100);
  161. mouse.moveTo(200, 200);
  162. mouse.up();
  163. const rectangle = API.getSelectedElement();
  164. // Create arrow with arrow tool
  165. UI.clickTool("arrow");
  166. mouse.downAt(205, 150); // Start close to rectangle
  167. mouse.moveTo(250, 150); // End outside
  168. mouse.up();
  169. const arrow = API.getSelectedElement() as ExcalidrawLinearElement;
  170. // Arrow should have start binding to rectangle
  171. expect(arrow.startBinding?.elementId).toBe(rectangle.id);
  172. expect(arrow.startBinding?.mode).toBe("orbit"); // Default is orbit, not inside
  173. expect(arrow.endBinding).toBeNull();
  174. });
  175. it("should handle new arrow end point binding", () => {
  176. // Create a rectangle
  177. UI.clickTool("rectangle");
  178. mouse.downAt(100, 100);
  179. mouse.moveTo(200, 200);
  180. mouse.up();
  181. const rectangle = API.getSelectedElement();
  182. // Create arrow with end point in binding zone
  183. UI.clickTool("arrow");
  184. mouse.downAt(50, 150); // Start outside
  185. mouse.moveTo(95, 95); // End near rectangle edge (should bind as orbit)
  186. mouse.up();
  187. const arrow = API.getSelectedElement() as ExcalidrawLinearElement;
  188. // Arrow should have end binding to rectangle
  189. expect(arrow.endBinding?.elementId).toBe(rectangle.id);
  190. expect(arrow.endBinding?.mode).toBe("orbit");
  191. expect(arrow.startBinding).toBeNull();
  192. });
  193. it.skip("should create orbit binding when one of the cursor is inside rectangle", () => {
  194. // Create a filled solid rectangle
  195. UI.clickTool("rectangle");
  196. mouse.downAt(100, 100);
  197. mouse.moveTo(200, 200);
  198. mouse.up();
  199. const rect = API.getSelectedElement();
  200. API.updateElement(rect, {
  201. fillStyle: "solid",
  202. backgroundColor: "#a5d8ff",
  203. });
  204. // Draw arrow with endpoint inside the filled rectangle, since only
  205. // filled bindables bind inside the shape
  206. UI.clickTool("arrow");
  207. mouse.downAt(10, 10);
  208. mouse.moveTo(160, 160);
  209. mouse.up();
  210. const arrow = API.getSelectedElement() as ExcalidrawLinearElement;
  211. expect(arrow.x).toBe(10);
  212. expect(arrow.y).toBe(10);
  213. expect(arrow.width).toBeCloseTo(85.75985931287957);
  214. expect(arrow.height).toBeCloseTo(85.75985931288186);
  215. // Should bind to the rectangle since endpoint is inside
  216. expect(arrow.startBinding).toBe(null);
  217. expect(arrow.endBinding?.elementId).toBe(rect.id);
  218. const endBinding = arrow.endBinding as FixedPointBinding;
  219. expect(endBinding.fixedPoint[0]).toBeGreaterThanOrEqual(0);
  220. expect(endBinding.fixedPoint[0]).toBeLessThanOrEqual(1);
  221. expect(endBinding.fixedPoint[1]).toBeGreaterThanOrEqual(0);
  222. expect(endBinding.fixedPoint[1]).toBeLessThanOrEqual(1);
  223. mouse.reset();
  224. // Move the bindable
  225. mouse.downAt(130, 110);
  226. mouse.moveTo(280, 110);
  227. mouse.up();
  228. // Check if the arrow moved
  229. expect(arrow.x).toBe(10);
  230. expect(arrow.y).toBe(10);
  231. expect(arrow.width).toBeCloseTo(234);
  232. expect(arrow.height).toBeCloseTo(117);
  233. // Restore bindable
  234. mouse.reset();
  235. mouse.downAt(280, 110);
  236. mouse.moveTo(130, 110);
  237. mouse.up();
  238. // Move the arrow out
  239. mouse.reset();
  240. mouse.click(10, 10);
  241. mouse.downAt(96.466, 96.466);
  242. mouse.moveTo(50, 50);
  243. mouse.up();
  244. expect(arrow.startBinding).toBe(null);
  245. expect(arrow.endBinding).toBe(null);
  246. // Re-bind the arrow by moving the cursor inside the rectangle
  247. mouse.reset();
  248. mouse.downAt(50, 50);
  249. mouse.moveTo(150, 150);
  250. mouse.up();
  251. // Check if the arrow is still on the outside
  252. expect(arrow.width).toBeCloseTo(86, 0);
  253. expect(arrow.height).toBeCloseTo(86, 0);
  254. });
  255. });
  256. describe("additional binding behavior", () => {
  257. beforeEach(async () => {
  258. mouse.reset();
  259. await act(() => {
  260. return setLanguage(defaultLang);
  261. });
  262. await render(<Excalidraw handleKeyboardGlobally={true} />);
  263. });
  264. it(
  265. "editing arrow and moving its head to bind it to element A, finalizing the" +
  266. "editing by clicking on element A should end up selecting A",
  267. async () => {
  268. UI.createElement("rectangle", {
  269. y: 0,
  270. size: 100,
  271. });
  272. // Create arrow bound to rectangle
  273. UI.clickTool("arrow");
  274. mouse.down(50, -100);
  275. mouse.up(0, 80);
  276. // Edit arrow
  277. Keyboard.withModifierKeys({ ctrl: true }, () => {
  278. Keyboard.keyPress(KEYS.ENTER);
  279. });
  280. // move arrow head
  281. mouse.down();
  282. mouse.up(0, 10);
  283. expect(API.getSelectedElement().type).toBe("arrow");
  284. expect(h.state.selectedLinearElement?.isEditing).toBe(true);
  285. mouse.reset();
  286. mouse.clickAt(-50, -50);
  287. expect(h.state.selectedLinearElement?.isEditing).toBe(false);
  288. expect(API.getSelectedElement().type).toBe("arrow");
  289. // Edit arrow
  290. Keyboard.withModifierKeys({ ctrl: true }, () => {
  291. Keyboard.keyPress(KEYS.ENTER);
  292. });
  293. expect(h.state.selectedLinearElement?.isEditing).toBe(true);
  294. mouse.reset();
  295. mouse.clickAt(0, 0);
  296. expect(h.state.selectedLinearElement).toBeNull();
  297. expect(API.getSelectedElement().type).toBe("rectangle");
  298. },
  299. );
  300. it("should unbind on bound element deletion", () => {
  301. const rectangle = UI.createElement("rectangle", {
  302. x: 60,
  303. y: 0,
  304. size: 100,
  305. });
  306. const arrow = UI.createElement("arrow", {
  307. x: 0,
  308. y: 5,
  309. size: 70,
  310. });
  311. expect(arrow.endBinding?.elementId).toBe(rectangle.id);
  312. mouse.select(rectangle);
  313. expect(API.getSelectedElement().type).toBe("rectangle");
  314. Keyboard.keyDown(KEYS.DELETE);
  315. expect(arrow.endBinding).toBe(null);
  316. });
  317. it("should unbind arrow when arrow is resized", () => {
  318. const rectLeft = UI.createElement("rectangle", {
  319. x: 0,
  320. width: 200,
  321. height: 500,
  322. });
  323. const rectRight = UI.createElement("rectangle", {
  324. x: 400,
  325. width: 200,
  326. height: 500,
  327. });
  328. UI.clickTool("arrow");
  329. mouse.reset();
  330. mouse.clickAt(190, 250);
  331. mouse.moveTo(220, 200);
  332. mouse.moveTo(300, 200);
  333. mouse.clickAt(300, 200);
  334. mouse.moveTo(340, 251);
  335. mouse.moveTo(410, 251);
  336. mouse.clickAt(410, 251);
  337. const arrow = h.elements[h.elements.length - 1] as any;
  338. expect(arrow.startBinding?.elementId).toBe(rectLeft.id);
  339. expect(arrow.endBinding?.elementId).toBe(rectRight.id);
  340. // Drag arrow off of bound rectangle range
  341. const handles = getTransformHandles(
  342. arrow,
  343. h.state.zoom,
  344. arrayToMap(h.elements),
  345. "mouse",
  346. ).se!;
  347. const elX = handles[0] + handles[2] / 2;
  348. const elY = handles[1] + handles[3] / 2;
  349. mouse.downAt(elX, elY);
  350. mouse.moveTo(300, 400);
  351. mouse.up();
  352. expect(arrow.startBinding).toBe(null);
  353. expect(arrow.endBinding).toBe(null);
  354. });
  355. it("should unbind arrow when arrow is rotated", () => {
  356. const rectLeft = UI.createElement("rectangle", {
  357. x: 0,
  358. width: 200,
  359. height: 500,
  360. });
  361. const rectRight = UI.createElement("rectangle", {
  362. x: 400,
  363. width: 200,
  364. height: 500,
  365. });
  366. UI.clickTool("arrow");
  367. mouse.reset();
  368. mouse.clickAt(190, 250);
  369. mouse.moveTo(220, 200);
  370. mouse.moveTo(300, 200);
  371. mouse.clickAt(300, 200);
  372. mouse.moveTo(350, 251);
  373. mouse.moveTo(410, 251);
  374. mouse.clickAt(410, 251);
  375. const arrow = API.getSelectedElement() as ExcalidrawArrowElement;
  376. expect(arrow.startBinding?.elementId).toBe(rectLeft.id);
  377. expect(arrow.endBinding?.elementId).toBe(rectRight.id);
  378. const rotation = getTransformHandles(
  379. arrow,
  380. h.state.zoom,
  381. arrayToMap(h.elements),
  382. "mouse",
  383. ).rotation!;
  384. const rotationHandleX = rotation[0] + rotation[2] / 2;
  385. const rotationHandleY = rotation[1] + rotation[3] / 2;
  386. mouse.reset();
  387. mouse.down(rotationHandleX, rotationHandleY);
  388. mouse.move(300, 400);
  389. mouse.up();
  390. expect(arrow.angle).toBeGreaterThan(0.7 * Math.PI);
  391. expect(arrow.angle).toBeLessThan(1.3 * Math.PI);
  392. expect(arrow.startBinding).toBeNull();
  393. expect(arrow.endBinding).toBeNull();
  394. });
  395. it("should not unbind when duplicating via selection group", () => {
  396. const rectLeft = UI.createElement("rectangle", {
  397. x: 0,
  398. width: 200,
  399. height: 500,
  400. });
  401. const rectRight = UI.createElement("rectangle", {
  402. x: 400,
  403. y: 200,
  404. width: 200,
  405. height: 500,
  406. });
  407. const arrow = UI.createElement("arrow", {
  408. x: 190,
  409. y: 250,
  410. width: 217,
  411. height: 1,
  412. });
  413. expect(arrow.startBinding?.elementId).toBe(rectLeft.id);
  414. expect(arrow.endBinding?.elementId).toBe(rectRight.id);
  415. mouse.downAt(-100, -100);
  416. mouse.moveTo(0, 0);
  417. mouse.moveTo(650, 750);
  418. mouse.up(0, 0);
  419. expect(API.getSelectedElements().length).toBe(3);
  420. mouse.moveTo(5, 5);
  421. Keyboard.withModifierKeys({ alt: true }, () => {
  422. mouse.downAt(5, 5);
  423. mouse.moveTo(1000, 1000);
  424. mouse.up(0, 0);
  425. expect(window.h.elements.length).toBe(6);
  426. window.h.elements.forEach((element) => {
  427. if (isLinearElement(element)) {
  428. expect(element.startBinding).not.toBe(null);
  429. expect(element.endBinding).not.toBe(null);
  430. } else {
  431. expect(element.boundElements).not.toBe(null);
  432. }
  433. });
  434. });
  435. });
  436. });
  437. describe("to text elements", () => {
  438. beforeEach(async () => {
  439. mouse.reset();
  440. await act(() => {
  441. return setLanguage(defaultLang);
  442. });
  443. await render(<Excalidraw handleKeyboardGlobally={true} />);
  444. });
  445. it("should update binding when text containerized", async () => {
  446. const rectangle1 = API.createElement({
  447. type: "rectangle",
  448. id: "rectangle1",
  449. width: 100,
  450. height: 100,
  451. boundElements: [
  452. { id: "arrow1", type: "arrow" },
  453. { id: "arrow2", type: "arrow" },
  454. ],
  455. });
  456. const arrow1 = API.createElement({
  457. type: "arrow",
  458. id: "arrow1",
  459. points: [pointFrom(0, 0), pointFrom(0, -87.45777932247563)],
  460. startBinding: {
  461. elementId: "rectangle1",
  462. fixedPoint: [0.5, 1],
  463. mode: "orbit",
  464. },
  465. endBinding: {
  466. elementId: "text1",
  467. fixedPoint: [1, 0.5],
  468. mode: "orbit",
  469. },
  470. });
  471. const arrow2 = API.createElement({
  472. type: "arrow",
  473. id: "arrow2",
  474. points: [pointFrom(0, 0), pointFrom(0, -87.45777932247563)],
  475. startBinding: {
  476. elementId: "text1",
  477. fixedPoint: [0.5, 1],
  478. mode: "orbit",
  479. },
  480. endBinding: {
  481. elementId: "rectangle1",
  482. fixedPoint: [1, 0.5],
  483. mode: "orbit",
  484. },
  485. });
  486. const text1 = API.createElement({
  487. type: "text",
  488. id: "text1",
  489. text: "ola",
  490. boundElements: [
  491. { id: "arrow1", type: "arrow" },
  492. { id: "arrow2", type: "arrow" },
  493. ],
  494. });
  495. API.setElements([rectangle1, arrow1, arrow2, text1]);
  496. API.setSelectedElements([text1]);
  497. expect(h.state.selectedElementIds[text1.id]).toBe(true);
  498. API.executeAction(actionWrapTextInContainer);
  499. // new text container will be placed before the text element
  500. const container = h.elements.at(-2)!;
  501. expect(container.type).toBe("rectangle");
  502. expect(container.id).not.toBe(rectangle1.id);
  503. expect(container).toEqual(
  504. expect.objectContaining({
  505. boundElements: expect.arrayContaining([
  506. {
  507. type: "text",
  508. id: text1.id,
  509. },
  510. {
  511. type: "arrow",
  512. id: arrow1.id,
  513. },
  514. {
  515. type: "arrow",
  516. id: arrow2.id,
  517. },
  518. ]),
  519. }),
  520. );
  521. expect(arrow1.startBinding?.elementId).toBe(rectangle1.id);
  522. expect(arrow1.endBinding?.elementId).toBe(container.id);
  523. expect(arrow2.startBinding?.elementId).toBe(container.id);
  524. expect(arrow2.endBinding?.elementId).toBe(rectangle1.id);
  525. });
  526. it("should keep binding on text update", async () => {
  527. const text = API.createElement({
  528. type: "text",
  529. text: "ola",
  530. x: 60,
  531. y: 0,
  532. width: 100,
  533. height: 100,
  534. });
  535. API.setElements([text]);
  536. const arrow = UI.createElement("arrow", {
  537. x: 0,
  538. y: 0,
  539. size: 65,
  540. });
  541. expect(arrow.endBinding?.elementId).toBe(text.id);
  542. // delete text element by submitting empty text
  543. // -------------------------------------------------------------------------
  544. UI.clickTool("text");
  545. mouse.clickAt(text.x + 50, text.y + 50);
  546. const editor = await getTextEditor();
  547. expect(editor).not.toBe(null);
  548. fireEvent.change(editor, { target: { value: "asdasdasdasdas" } });
  549. fireEvent.keyDown(editor, { key: KEYS.ESCAPE });
  550. expect(document.querySelector(TEXT_EDITOR_SELECTOR)).toBe(null);
  551. expect(arrow.endBinding?.elementId).toBe(text.id);
  552. });
  553. it("should unbind on text element deletion by submitting empty text", async () => {
  554. const text = API.createElement({
  555. type: "text",
  556. text: "¡olá!",
  557. x: 60,
  558. y: 0,
  559. width: 100,
  560. height: 100,
  561. });
  562. API.setElements([text]);
  563. const arrow = UI.createElement("arrow", {
  564. x: 0,
  565. y: 0,
  566. size: 65,
  567. });
  568. expect(arrow.endBinding?.elementId).toBe(text.id);
  569. // edit text element and submit
  570. // -------------------------------------------------------------------------
  571. UI.clickTool("text");
  572. mouse.clickAt(text.x + 50, text.y + 50);
  573. const editor = await getTextEditor();
  574. fireEvent.change(editor, { target: { value: "" } });
  575. fireEvent.keyDown(editor, { key: KEYS.ESCAPE });
  576. expect(document.querySelector(TEXT_EDITOR_SELECTOR)).toBe(null);
  577. expect(arrow.endBinding).toBe(null);
  578. });
  579. });
  580. });