flowchart.test.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. import { KEYS, reseed } from "@excalidraw/common";
  2. import { Excalidraw } from "@excalidraw/excalidraw";
  3. import { API } from "@excalidraw/excalidraw/tests/helpers/api";
  4. import { UI, Keyboard, Pointer } from "@excalidraw/excalidraw/tests/helpers/ui";
  5. import {
  6. render,
  7. unmountComponent,
  8. } from "@excalidraw/excalidraw/tests/test-utils";
  9. unmountComponent();
  10. const { h } = window;
  11. const mouse = new Pointer("mouse");
  12. beforeEach(async () => {
  13. localStorage.clear();
  14. reseed(7);
  15. mouse.reset();
  16. await render(<Excalidraw handleKeyboardGlobally={true} />);
  17. h.state.width = 1000;
  18. h.state.height = 1000;
  19. // The bounds of hand-drawn linear elements may change after flipping, so
  20. // removing this style for testing
  21. UI.clickTool("arrow");
  22. UI.clickByTitle("Architect");
  23. UI.clickTool("selection");
  24. });
  25. describe("flow chart creation", () => {
  26. beforeEach(() => {
  27. API.clearSelection();
  28. const rectangle = API.createElement({
  29. type: "rectangle",
  30. width: 200,
  31. height: 100,
  32. });
  33. API.setElements([rectangle]);
  34. API.setSelectedElements([rectangle]);
  35. });
  36. // multiple at once
  37. it("create multiple successor nodes at once", () => {
  38. Keyboard.withModifierKeys({ ctrl: true }, () => {
  39. Keyboard.keyPress(KEYS.ARROW_RIGHT);
  40. Keyboard.keyPress(KEYS.ARROW_RIGHT);
  41. });
  42. Keyboard.keyUp(KEYS.CTRL_OR_CMD);
  43. expect(h.elements.length).toBe(5);
  44. expect(h.elements.filter((el) => el.type === "rectangle").length).toBe(3);
  45. expect(h.elements.filter((el) => el.type === "arrow").length).toBe(2);
  46. });
  47. it("when directions are changed, only the last same directions will apply", () => {
  48. Keyboard.withModifierKeys({ ctrl: true }, () => {
  49. Keyboard.keyPress(KEYS.ARROW_RIGHT);
  50. Keyboard.keyPress(KEYS.ARROW_RIGHT);
  51. Keyboard.keyPress(KEYS.ARROW_RIGHT);
  52. Keyboard.keyPress(KEYS.ARROW_LEFT);
  53. Keyboard.keyPress(KEYS.ARROW_UP);
  54. Keyboard.keyPress(KEYS.ARROW_UP);
  55. Keyboard.keyPress(KEYS.ARROW_UP);
  56. });
  57. Keyboard.keyUp(KEYS.CTRL_OR_CMD);
  58. expect(h.elements.length).toBe(7);
  59. expect(h.elements.filter((el) => el.type === "rectangle").length).toBe(4);
  60. expect(h.elements.filter((el) => el.type === "arrow").length).toBe(3);
  61. });
  62. it("when escaped, no nodes will be created", () => {
  63. Keyboard.withModifierKeys({ ctrl: true }, () => {
  64. Keyboard.keyPress(KEYS.ARROW_RIGHT);
  65. Keyboard.keyPress(KEYS.ARROW_LEFT);
  66. Keyboard.keyPress(KEYS.ARROW_UP);
  67. Keyboard.keyPress(KEYS.ARROW_DOWN);
  68. });
  69. Keyboard.keyPress(KEYS.ESCAPE);
  70. Keyboard.keyUp(KEYS.CTRL_OR_CMD);
  71. expect(h.elements.length).toBe(1);
  72. });
  73. it("create nodes one at a time", () => {
  74. const initialNode = h.elements[0];
  75. Keyboard.withModifierKeys({ ctrl: true }, () => {
  76. Keyboard.keyPress(KEYS.ARROW_RIGHT);
  77. });
  78. Keyboard.keyUp(KEYS.CTRL_OR_CMD);
  79. expect(h.elements.length).toBe(3);
  80. expect(h.elements.filter((el) => el.type === "rectangle").length).toBe(2);
  81. expect(h.elements.filter((el) => el.type === "arrow").length).toBe(1);
  82. const firstChildNode = h.elements.filter(
  83. (el) => el.type === "rectangle" && el.id !== initialNode.id,
  84. )[0];
  85. expect(firstChildNode).not.toBe(null);
  86. expect(firstChildNode.id).toBe(Object.keys(h.state.selectedElementIds)[0]);
  87. API.setSelectedElements([initialNode]);
  88. Keyboard.withModifierKeys({ ctrl: true }, () => {
  89. Keyboard.keyPress(KEYS.ARROW_RIGHT);
  90. });
  91. Keyboard.keyUp(KEYS.CTRL_OR_CMD);
  92. expect(h.elements.length).toBe(5);
  93. expect(h.elements.filter((el) => el.type === "rectangle").length).toBe(3);
  94. expect(h.elements.filter((el) => el.type === "arrow").length).toBe(2);
  95. const secondChildNode = h.elements.filter(
  96. (el) =>
  97. el.type === "rectangle" &&
  98. el.id !== initialNode.id &&
  99. el.id !== firstChildNode.id,
  100. )[0];
  101. expect(secondChildNode).not.toBe(null);
  102. expect(secondChildNode.id).toBe(Object.keys(h.state.selectedElementIds)[0]);
  103. API.setSelectedElements([initialNode]);
  104. Keyboard.withModifierKeys({ ctrl: true }, () => {
  105. Keyboard.keyPress(KEYS.ARROW_RIGHT);
  106. });
  107. Keyboard.keyUp(KEYS.CTRL_OR_CMD);
  108. expect(h.elements.length).toBe(7);
  109. expect(h.elements.filter((el) => el.type === "rectangle").length).toBe(4);
  110. expect(h.elements.filter((el) => el.type === "arrow").length).toBe(3);
  111. const thirdChildNode = h.elements.filter(
  112. (el) =>
  113. el.type === "rectangle" &&
  114. el.id !== initialNode.id &&
  115. el.id !== firstChildNode.id &&
  116. el.id !== secondChildNode.id,
  117. )[0];
  118. expect(thirdChildNode).not.toBe(null);
  119. expect(thirdChildNode.id).toBe(Object.keys(h.state.selectedElementIds)[0]);
  120. expect(firstChildNode.x).toBe(secondChildNode.x);
  121. expect(secondChildNode.x).toBe(thirdChildNode.x);
  122. });
  123. });
  124. describe("flow chart navigation", () => {
  125. it("single node at each level", () => {
  126. /**
  127. * ▨ -> ▨ -> ▨ -> ▨ -> ▨
  128. */
  129. API.clearSelection();
  130. const rectangle = API.createElement({
  131. type: "rectangle",
  132. width: 200,
  133. height: 100,
  134. });
  135. API.setElements([rectangle]);
  136. API.setSelectedElements([rectangle]);
  137. Keyboard.withModifierKeys({ ctrl: true }, () => {
  138. Keyboard.keyPress(KEYS.ARROW_RIGHT);
  139. });
  140. Keyboard.keyUp(KEYS.CTRL_OR_CMD);
  141. Keyboard.withModifierKeys({ ctrl: true }, () => {
  142. Keyboard.keyPress(KEYS.ARROW_RIGHT);
  143. });
  144. Keyboard.keyUp(KEYS.CTRL_OR_CMD);
  145. Keyboard.withModifierKeys({ ctrl: true }, () => {
  146. Keyboard.keyPress(KEYS.ARROW_RIGHT);
  147. });
  148. Keyboard.keyUp(KEYS.CTRL_OR_CMD);
  149. Keyboard.withModifierKeys({ ctrl: true }, () => {
  150. Keyboard.keyPress(KEYS.ARROW_RIGHT);
  151. });
  152. Keyboard.keyUp(KEYS.CTRL_OR_CMD);
  153. expect(h.elements.filter((el) => el.type === "rectangle").length).toBe(5);
  154. expect(h.elements.filter((el) => el.type === "arrow").length).toBe(4);
  155. // all the way to the left, gets us to the first node
  156. Keyboard.withModifierKeys({ alt: true }, () => {
  157. Keyboard.keyPress(KEYS.ARROW_LEFT);
  158. Keyboard.keyPress(KEYS.ARROW_LEFT);
  159. Keyboard.keyPress(KEYS.ARROW_LEFT);
  160. Keyboard.keyPress(KEYS.ARROW_LEFT);
  161. });
  162. Keyboard.keyUp(KEYS.ALT);
  163. expect(h.state.selectedElementIds[rectangle.id]).toBe(true);
  164. // all the way to the right, gets us to the last node
  165. const rightMostNode = h.elements[h.elements.length - 2];
  166. expect(rightMostNode);
  167. expect(rightMostNode.type).toBe("rectangle");
  168. Keyboard.withModifierKeys({ alt: true }, () => {
  169. Keyboard.keyPress(KEYS.ARROW_RIGHT);
  170. Keyboard.keyPress(KEYS.ARROW_RIGHT);
  171. Keyboard.keyPress(KEYS.ARROW_RIGHT);
  172. Keyboard.keyPress(KEYS.ARROW_RIGHT);
  173. });
  174. Keyboard.keyUp(KEYS.ALT);
  175. expect(h.state.selectedElementIds[rightMostNode.id]).toBe(true);
  176. });
  177. it("multiple nodes at each level", () => {
  178. /**
  179. * from the perspective of the first node, there're four layers, and
  180. * there are four nodes at the second layer
  181. *
  182. * -> ▨
  183. * ▨ -> ▨ -> ▨ -> ▨ -> ▨
  184. * -> ▨
  185. * -> ▨
  186. */
  187. API.clearSelection();
  188. const rectangle = API.createElement({
  189. type: "rectangle",
  190. width: 200,
  191. height: 100,
  192. });
  193. API.setElements([rectangle]);
  194. API.setSelectedElements([rectangle]);
  195. Keyboard.withModifierKeys({ ctrl: true }, () => {
  196. Keyboard.keyPress(KEYS.ARROW_RIGHT);
  197. });
  198. Keyboard.keyUp(KEYS.CTRL_OR_CMD);
  199. Keyboard.withModifierKeys({ ctrl: true }, () => {
  200. Keyboard.keyPress(KEYS.ARROW_RIGHT);
  201. });
  202. Keyboard.keyUp(KEYS.CTRL_OR_CMD);
  203. Keyboard.withModifierKeys({ ctrl: true }, () => {
  204. Keyboard.keyPress(KEYS.ARROW_RIGHT);
  205. });
  206. Keyboard.keyUp(KEYS.CTRL_OR_CMD);
  207. Keyboard.withModifierKeys({ ctrl: true }, () => {
  208. Keyboard.keyPress(KEYS.ARROW_RIGHT);
  209. });
  210. Keyboard.keyUp(KEYS.CTRL_OR_CMD);
  211. const secondNode = h.elements[1];
  212. const rightMostNode = h.elements[h.elements.length - 2];
  213. API.setSelectedElements([rectangle]);
  214. Keyboard.withModifierKeys({ ctrl: true }, () => {
  215. Keyboard.keyPress(KEYS.ARROW_RIGHT);
  216. });
  217. Keyboard.keyUp(KEYS.CTRL_OR_CMD);
  218. API.setSelectedElements([rectangle]);
  219. Keyboard.withModifierKeys({ ctrl: true }, () => {
  220. Keyboard.keyPress(KEYS.ARROW_RIGHT);
  221. });
  222. Keyboard.keyUp(KEYS.CTRL_OR_CMD);
  223. API.setSelectedElements([rectangle]);
  224. Keyboard.withModifierKeys({ ctrl: true }, () => {
  225. Keyboard.keyPress(KEYS.ARROW_RIGHT);
  226. });
  227. Keyboard.keyUp(KEYS.CTRL_OR_CMD);
  228. API.setSelectedElements([rectangle]);
  229. // because of same level cycling,
  230. // going right five times should take us back to the second node again
  231. Keyboard.withModifierKeys({ alt: true }, () => {
  232. Keyboard.keyPress(KEYS.ARROW_RIGHT);
  233. Keyboard.keyPress(KEYS.ARROW_RIGHT);
  234. Keyboard.keyPress(KEYS.ARROW_RIGHT);
  235. Keyboard.keyPress(KEYS.ARROW_RIGHT);
  236. Keyboard.keyPress(KEYS.ARROW_RIGHT);
  237. });
  238. Keyboard.keyUp(KEYS.ALT);
  239. expect(h.state.selectedElementIds[secondNode.id]).toBe(true);
  240. // from the second node, going right three times should take us to the rightmost node
  241. Keyboard.withModifierKeys({ alt: true }, () => {
  242. Keyboard.keyPress(KEYS.ARROW_RIGHT);
  243. Keyboard.keyPress(KEYS.ARROW_RIGHT);
  244. Keyboard.keyPress(KEYS.ARROW_RIGHT);
  245. });
  246. Keyboard.keyUp(KEYS.ALT);
  247. expect(h.state.selectedElementIds[rightMostNode.id]).toBe(true);
  248. Keyboard.withModifierKeys({ alt: true }, () => {
  249. Keyboard.keyPress(KEYS.ARROW_LEFT);
  250. Keyboard.keyPress(KEYS.ARROW_LEFT);
  251. Keyboard.keyPress(KEYS.ARROW_LEFT);
  252. Keyboard.keyPress(KEYS.ARROW_LEFT);
  253. });
  254. Keyboard.keyUp(KEYS.ALT);
  255. expect(h.state.selectedElementIds[rectangle.id]).toBe(true);
  256. });
  257. it("take the most obvious link when possible", () => {
  258. /**
  259. * ▨ → ▨ ▨ → ▨
  260. * ↓ ↑
  261. * ▨ → ▨
  262. */
  263. API.clearSelection();
  264. const rectangle = API.createElement({
  265. type: "rectangle",
  266. width: 200,
  267. height: 100,
  268. });
  269. API.setElements([rectangle]);
  270. API.setSelectedElements([rectangle]);
  271. Keyboard.withModifierKeys({ ctrl: true }, () => {
  272. Keyboard.keyPress(KEYS.ARROW_RIGHT);
  273. });
  274. Keyboard.keyUp(KEYS.CTRL_OR_CMD);
  275. Keyboard.withModifierKeys({ ctrl: true }, () => {
  276. Keyboard.keyPress(KEYS.ARROW_DOWN);
  277. });
  278. Keyboard.keyUp(KEYS.CTRL_OR_CMD);
  279. Keyboard.withModifierKeys({ ctrl: true }, () => {
  280. Keyboard.keyPress(KEYS.ARROW_RIGHT);
  281. });
  282. Keyboard.keyUp(KEYS.CTRL_OR_CMD);
  283. Keyboard.withModifierKeys({ ctrl: true }, () => {
  284. Keyboard.keyPress(KEYS.ARROW_UP);
  285. });
  286. Keyboard.keyUp(KEYS.CTRL_OR_CMD);
  287. Keyboard.withModifierKeys({ ctrl: true }, () => {
  288. Keyboard.keyPress(KEYS.ARROW_RIGHT);
  289. });
  290. Keyboard.keyUp(KEYS.CTRL_OR_CMD);
  291. // last node should be the one that's selected
  292. const rightMostNode = h.elements[h.elements.length - 2];
  293. expect(rightMostNode.type).toBe("rectangle");
  294. expect(h.state.selectedElementIds[rightMostNode.id]).toBe(true);
  295. Keyboard.withModifierKeys({ alt: true }, () => {
  296. Keyboard.keyPress(KEYS.ARROW_LEFT);
  297. Keyboard.keyPress(KEYS.ARROW_LEFT);
  298. Keyboard.keyPress(KEYS.ARROW_LEFT);
  299. Keyboard.keyPress(KEYS.ARROW_LEFT);
  300. Keyboard.keyPress(KEYS.ARROW_LEFT);
  301. });
  302. Keyboard.keyUp(KEYS.ALT);
  303. expect(h.state.selectedElementIds[rectangle.id]).toBe(true);
  304. // going any direction takes us to the predecessor as well
  305. const predecessorToRightMostNode = h.elements[h.elements.length - 4];
  306. expect(predecessorToRightMostNode.type).toBe("rectangle");
  307. API.setSelectedElements([rightMostNode]);
  308. Keyboard.withModifierKeys({ alt: true }, () => {
  309. Keyboard.keyPress(KEYS.ARROW_RIGHT);
  310. });
  311. Keyboard.keyUp(KEYS.ALT);
  312. expect(h.state.selectedElementIds[rightMostNode.id]).not.toBe(true);
  313. expect(h.state.selectedElementIds[predecessorToRightMostNode.id]).toBe(
  314. true,
  315. );
  316. API.setSelectedElements([rightMostNode]);
  317. Keyboard.withModifierKeys({ alt: true }, () => {
  318. Keyboard.keyPress(KEYS.ARROW_UP);
  319. });
  320. Keyboard.keyUp(KEYS.ALT);
  321. expect(h.state.selectedElementIds[rightMostNode.id]).not.toBe(true);
  322. expect(h.state.selectedElementIds[predecessorToRightMostNode.id]).toBe(
  323. true,
  324. );
  325. API.setSelectedElements([rightMostNode]);
  326. Keyboard.withModifierKeys({ alt: true }, () => {
  327. Keyboard.keyPress(KEYS.ARROW_DOWN);
  328. });
  329. Keyboard.keyUp(KEYS.ALT);
  330. expect(h.state.selectedElementIds[rightMostNode.id]).not.toBe(true);
  331. expect(h.state.selectedElementIds[predecessorToRightMostNode.id]).toBe(
  332. true,
  333. );
  334. });
  335. });