sortElements.test.ts 9.6 KB

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