sortElements.test.ts 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. import { API } from "@excalidraw/excalidraw/tests/helpers/api";
  2. import { mutateElement } from "@excalidraw/element";
  3. import { normalizeElementOrder } from "../src/sortElements";
  4. import type { ExcalidrawElement } from "../src/types";
  5. const { h } = window;
  6. const assertOrder = (
  7. elements: readonly ExcalidrawElement[],
  8. expectedOrder: string[],
  9. ) => {
  10. const actualOrder = elements.map((element) => element.id);
  11. expect(actualOrder).toEqual(expectedOrder);
  12. };
  13. describe("normalizeElementsOrder", () => {
  14. it("sort bound-text elements", () => {
  15. const container = API.createElement({
  16. id: "container",
  17. type: "rectangle",
  18. });
  19. const boundText = API.createElement({
  20. id: "boundText",
  21. type: "text",
  22. containerId: container.id,
  23. });
  24. const otherElement = API.createElement({
  25. id: "otherElement",
  26. type: "rectangle",
  27. boundElements: [],
  28. });
  29. const otherElement2 = API.createElement({
  30. id: "otherElement2",
  31. type: "rectangle",
  32. boundElements: [],
  33. });
  34. mutateElement(container, new Map(), {
  35. boundElements: [{ type: "text", id: boundText.id }],
  36. });
  37. assertOrder(normalizeElementOrder([container, boundText]), [
  38. "container",
  39. "boundText",
  40. ]);
  41. assertOrder(normalizeElementOrder([boundText, container]), [
  42. "container",
  43. "boundText",
  44. ]);
  45. assertOrder(
  46. normalizeElementOrder([
  47. boundText,
  48. container,
  49. otherElement,
  50. otherElement2,
  51. ]),
  52. ["container", "boundText", "otherElement", "otherElement2"],
  53. );
  54. assertOrder(normalizeElementOrder([container, otherElement, boundText]), [
  55. "container",
  56. "boundText",
  57. "otherElement",
  58. ]);
  59. assertOrder(
  60. normalizeElementOrder([
  61. container,
  62. otherElement,
  63. otherElement2,
  64. boundText,
  65. ]),
  66. ["container", "boundText", "otherElement", "otherElement2"],
  67. );
  68. assertOrder(
  69. normalizeElementOrder([
  70. boundText,
  71. otherElement,
  72. container,
  73. otherElement2,
  74. ]),
  75. ["otherElement", "container", "boundText", "otherElement2"],
  76. );
  77. // noop
  78. assertOrder(
  79. normalizeElementOrder([
  80. otherElement,
  81. container,
  82. boundText,
  83. otherElement2,
  84. ]),
  85. ["otherElement", "container", "boundText", "otherElement2"],
  86. );
  87. // text has existing containerId, but container doesn't list is
  88. // as a boundElement
  89. assertOrder(
  90. normalizeElementOrder([
  91. API.createElement({
  92. id: "boundText",
  93. type: "text",
  94. containerId: "container",
  95. }),
  96. API.createElement({
  97. id: "container",
  98. type: "rectangle",
  99. }),
  100. ]),
  101. ["boundText", "container"],
  102. );
  103. assertOrder(
  104. normalizeElementOrder([
  105. API.createElement({
  106. id: "boundText",
  107. type: "text",
  108. containerId: "container",
  109. }),
  110. ]),
  111. ["boundText"],
  112. );
  113. assertOrder(
  114. normalizeElementOrder([
  115. API.createElement({
  116. id: "container",
  117. type: "rectangle",
  118. boundElements: [],
  119. }),
  120. ]),
  121. ["container"],
  122. );
  123. assertOrder(
  124. normalizeElementOrder([
  125. API.createElement({
  126. id: "container",
  127. type: "rectangle",
  128. boundElements: [{ id: "x", type: "text" }],
  129. }),
  130. ]),
  131. ["container"],
  132. );
  133. assertOrder(
  134. normalizeElementOrder([
  135. API.createElement({
  136. id: "arrow",
  137. type: "arrow",
  138. }),
  139. API.createElement({
  140. id: "container",
  141. type: "rectangle",
  142. boundElements: [{ id: "arrow", type: "arrow" }],
  143. }),
  144. ]),
  145. ["arrow", "container"],
  146. );
  147. });
  148. it("normalize group order", () => {
  149. assertOrder(
  150. normalizeElementOrder([
  151. API.createElement({
  152. id: "A_rect1",
  153. type: "rectangle",
  154. groupIds: ["A"],
  155. }),
  156. API.createElement({
  157. id: "rect2",
  158. type: "rectangle",
  159. }),
  160. API.createElement({
  161. id: "rect3",
  162. type: "rectangle",
  163. }),
  164. API.createElement({
  165. id: "A_rect4",
  166. type: "rectangle",
  167. groupIds: ["A"],
  168. }),
  169. API.createElement({
  170. id: "A_rect5",
  171. type: "rectangle",
  172. groupIds: ["A"],
  173. }),
  174. API.createElement({
  175. id: "rect6",
  176. type: "rectangle",
  177. }),
  178. API.createElement({
  179. id: "A_rect7",
  180. type: "rectangle",
  181. groupIds: ["A"],
  182. }),
  183. ]),
  184. ["A_rect1", "A_rect4", "A_rect5", "A_rect7", "rect2", "rect3", "rect6"],
  185. );
  186. assertOrder(
  187. normalizeElementOrder([
  188. API.createElement({
  189. id: "A_rect1",
  190. type: "rectangle",
  191. groupIds: ["A"],
  192. }),
  193. API.createElement({
  194. id: "rect2",
  195. type: "rectangle",
  196. }),
  197. API.createElement({
  198. id: "B_rect3",
  199. type: "rectangle",
  200. groupIds: ["B"],
  201. }),
  202. API.createElement({
  203. id: "A_rect4",
  204. type: "rectangle",
  205. groupIds: ["A"],
  206. }),
  207. API.createElement({
  208. id: "B_rect5",
  209. type: "rectangle",
  210. groupIds: ["B"],
  211. }),
  212. API.createElement({
  213. id: "rect6",
  214. type: "rectangle",
  215. }),
  216. API.createElement({
  217. id: "A_rect7",
  218. type: "rectangle",
  219. groupIds: ["A"],
  220. }),
  221. ]),
  222. ["A_rect1", "A_rect4", "A_rect7", "rect2", "B_rect3", "B_rect5", "rect6"],
  223. );
  224. // nested groups
  225. assertOrder(
  226. normalizeElementOrder([
  227. API.createElement({
  228. id: "A_rect1",
  229. type: "rectangle",
  230. groupIds: ["A"],
  231. }),
  232. API.createElement({
  233. id: "BA_rect2",
  234. type: "rectangle",
  235. groupIds: ["B", "A"],
  236. }),
  237. ]),
  238. ["A_rect1", "BA_rect2"],
  239. );
  240. assertOrder(
  241. normalizeElementOrder([
  242. API.createElement({
  243. id: "BA_rect1",
  244. type: "rectangle",
  245. groupIds: ["B", "A"],
  246. }),
  247. API.createElement({
  248. id: "A_rect2",
  249. type: "rectangle",
  250. groupIds: ["A"],
  251. }),
  252. ]),
  253. ["BA_rect1", "A_rect2"],
  254. );
  255. assertOrder(
  256. normalizeElementOrder([
  257. API.createElement({
  258. id: "BA_rect1",
  259. type: "rectangle",
  260. groupIds: ["B", "A"],
  261. }),
  262. API.createElement({
  263. id: "A_rect2",
  264. type: "rectangle",
  265. groupIds: ["A"],
  266. }),
  267. API.createElement({
  268. id: "CBA_rect3",
  269. type: "rectangle",
  270. groupIds: ["C", "B", "A"],
  271. }),
  272. API.createElement({
  273. id: "rect4",
  274. type: "rectangle",
  275. }),
  276. API.createElement({
  277. id: "A_rect5",
  278. type: "rectangle",
  279. groupIds: ["A"],
  280. }),
  281. API.createElement({
  282. id: "BA_rect5",
  283. type: "rectangle",
  284. groupIds: ["B", "A"],
  285. }),
  286. API.createElement({
  287. id: "BA_rect6",
  288. type: "rectangle",
  289. groupIds: ["B", "A"],
  290. }),
  291. API.createElement({
  292. id: "CBA_rect7",
  293. type: "rectangle",
  294. groupIds: ["C", "B", "A"],
  295. }),
  296. API.createElement({
  297. id: "X_rect8",
  298. type: "rectangle",
  299. groupIds: ["X"],
  300. }),
  301. API.createElement({
  302. id: "rect9",
  303. type: "rectangle",
  304. }),
  305. API.createElement({
  306. id: "YX_rect10",
  307. type: "rectangle",
  308. groupIds: ["Y", "X"],
  309. }),
  310. API.createElement({
  311. id: "X_rect11",
  312. type: "rectangle",
  313. groupIds: ["X"],
  314. }),
  315. ]),
  316. [
  317. "BA_rect1",
  318. "BA_rect5",
  319. "BA_rect6",
  320. "A_rect2",
  321. "A_rect5",
  322. "CBA_rect3",
  323. "CBA_rect7",
  324. "rect4",
  325. "X_rect8",
  326. "X_rect11",
  327. "YX_rect10",
  328. "rect9",
  329. ],
  330. );
  331. });
  332. // TODO
  333. it.skip("normalize boundElements array", () => {
  334. const container = API.createElement({
  335. id: "container",
  336. type: "rectangle",
  337. boundElements: [],
  338. });
  339. const boundText = API.createElement({
  340. id: "boundText",
  341. type: "text",
  342. containerId: container.id,
  343. });
  344. h.app.scene.mutateElement(container, {
  345. boundElements: [
  346. { type: "text", id: boundText.id },
  347. { type: "text", id: "xxx" },
  348. ],
  349. });
  350. expect(normalizeElementOrder([container, boundText])).toEqual([
  351. expect.objectContaining({
  352. id: container.id,
  353. }),
  354. expect.objectContaining({ id: boundText.id }),
  355. ]);
  356. });
  357. // should take around <100ms for 10K iterations (@dwelle's PC 22-05-25)
  358. it.skip("normalizeElementsOrder() perf", () => {
  359. const makeElements = (iterations: number) => {
  360. const elements: ExcalidrawElement[] = [];
  361. while (iterations--) {
  362. const container = API.createElement({
  363. type: "rectangle",
  364. boundElements: [],
  365. groupIds: ["B", "A"],
  366. });
  367. const boundText = API.createElement({
  368. type: "text",
  369. containerId: container.id,
  370. groupIds: ["A"],
  371. });
  372. const otherElement = API.createElement({
  373. type: "rectangle",
  374. boundElements: [],
  375. groupIds: ["C", "A"],
  376. });
  377. h.app.scene.mutateElement(container, {
  378. boundElements: [{ type: "text", id: boundText.id }],
  379. });
  380. elements.push(boundText, otherElement, container);
  381. }
  382. return elements;
  383. };
  384. const elements = makeElements(10000);
  385. const t0 = Date.now();
  386. normalizeElementOrder(elements);
  387. console.info(`${Date.now() - t0}ms`);
  388. });
  389. });