zindex.test.tsx 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056
  1. import React from "react";
  2. import ReactDOM from "react-dom";
  3. import { render } from "./test-utils";
  4. import App from "../components/App";
  5. import { reseed } from "../random";
  6. import {
  7. actionSendBackward,
  8. actionBringForward,
  9. actionBringToFront,
  10. actionSendToBack,
  11. actionDuplicateSelection,
  12. } from "../actions";
  13. import { AppState } from "../types";
  14. import { API } from "./helpers/api";
  15. // Unmount ReactDOM from root
  16. ReactDOM.unmountComponentAtNode(document.getElementById("root")!);
  17. beforeEach(() => {
  18. localStorage.clear();
  19. reseed(7);
  20. });
  21. const { h } = window;
  22. const populateElements = (
  23. elements: {
  24. id: string;
  25. isDeleted?: boolean;
  26. isSelected?: boolean;
  27. groupIds?: string[];
  28. y?: number;
  29. x?: number;
  30. width?: number;
  31. height?: number;
  32. }[],
  33. ) => {
  34. const selectedElementIds: any = {};
  35. h.elements = elements.map(
  36. ({
  37. id,
  38. isDeleted = false,
  39. isSelected = false,
  40. groupIds = [],
  41. y = 100,
  42. x = 100,
  43. width = 100,
  44. height = 100,
  45. }) => {
  46. const element = API.createElement({
  47. type: "rectangle",
  48. id,
  49. isDeleted,
  50. x,
  51. y,
  52. width,
  53. height,
  54. });
  55. // @ts-ignore
  56. element.groupIds = groupIds;
  57. if (isSelected) {
  58. selectedElementIds[element.id] = true;
  59. }
  60. return element;
  61. },
  62. );
  63. h.setState({
  64. selectedElementIds,
  65. });
  66. return selectedElementIds;
  67. };
  68. type Actions =
  69. | typeof actionBringForward
  70. | typeof actionSendBackward
  71. | typeof actionBringToFront
  72. | typeof actionSendToBack;
  73. const assertZindex = ({
  74. elements,
  75. appState,
  76. operations,
  77. }: {
  78. elements: {
  79. id: string;
  80. isDeleted?: true;
  81. isSelected?: true;
  82. groupIds?: string[];
  83. }[];
  84. appState?: Partial<AppState>;
  85. operations: [Actions, string[]][];
  86. }) => {
  87. const selectedElementIds = populateElements(elements);
  88. h.setState({
  89. editingGroupId: appState?.editingGroupId || null,
  90. });
  91. operations.forEach(([action, expected]) => {
  92. h.app.actionManager.executeAction(action);
  93. expect(h.elements.map((element) => element.id)).toEqual(expected);
  94. expect(h.state.selectedElementIds).toEqual(selectedElementIds);
  95. });
  96. };
  97. describe("z-index manipulation", () => {
  98. beforeEach(() => {
  99. render(<App />);
  100. });
  101. it("send back", () => {
  102. assertZindex({
  103. elements: [
  104. { id: "A" },
  105. { id: "B", isDeleted: true },
  106. { id: "C", isDeleted: true },
  107. { id: "D", isSelected: true },
  108. ],
  109. operations: [
  110. [actionSendBackward, ["D", "A", "B", "C"]],
  111. // noop
  112. [actionSendBackward, ["D", "A", "B", "C"]],
  113. ],
  114. });
  115. assertZindex({
  116. elements: [
  117. { id: "A", isSelected: true },
  118. { id: "B", isSelected: true },
  119. { id: "C", isSelected: true },
  120. ],
  121. operations: [
  122. // noop
  123. [actionSendBackward, ["A", "B", "C"]],
  124. ],
  125. });
  126. assertZindex({
  127. elements: [
  128. { id: "A", isDeleted: true },
  129. { id: "B" },
  130. { id: "C", isDeleted: true },
  131. { id: "D", isSelected: true },
  132. ],
  133. operations: [[actionSendBackward, ["A", "D", "B", "C"]]],
  134. });
  135. assertZindex({
  136. elements: [
  137. { id: "A" },
  138. { id: "B", isDeleted: true },
  139. { id: "C", isDeleted: true },
  140. { id: "D", isSelected: true },
  141. { id: "E", isSelected: true },
  142. { id: "F" },
  143. ],
  144. operations: [
  145. [actionSendBackward, ["D", "E", "A", "B", "C", "F"]],
  146. // noop
  147. [actionSendBackward, ["D", "E", "A", "B", "C", "F"]],
  148. ],
  149. });
  150. assertZindex({
  151. elements: [
  152. { id: "A" },
  153. { id: "B" },
  154. { id: "C", isDeleted: true },
  155. { id: "D", isDeleted: true },
  156. { id: "E", isSelected: true },
  157. { id: "F" },
  158. { id: "G", isSelected: true },
  159. ],
  160. operations: [
  161. [actionSendBackward, ["A", "E", "B", "C", "D", "G", "F"]],
  162. [actionSendBackward, ["E", "A", "G", "B", "C", "D", "F"]],
  163. [actionSendBackward, ["E", "G", "A", "B", "C", "D", "F"]],
  164. // noop
  165. [actionSendBackward, ["E", "G", "A", "B", "C", "D", "F"]],
  166. ],
  167. });
  168. assertZindex({
  169. elements: [
  170. { id: "A" },
  171. { id: "B" },
  172. { id: "C", isDeleted: true },
  173. { id: "D", isSelected: true },
  174. { id: "E", isDeleted: true },
  175. { id: "F", isSelected: true },
  176. { id: "G" },
  177. ],
  178. operations: [
  179. [actionSendBackward, ["A", "D", "E", "F", "B", "C", "G"]],
  180. [actionSendBackward, ["D", "E", "F", "A", "B", "C", "G"]],
  181. // noop
  182. [actionSendBackward, ["D", "E", "F", "A", "B", "C", "G"]],
  183. ],
  184. });
  185. // grouped elements should be atomic
  186. // -------------------------------------------------------------------------
  187. assertZindex({
  188. elements: [
  189. { id: "A" },
  190. { id: "B", groupIds: ["g1"] },
  191. { id: "C", groupIds: ["g1"] },
  192. { id: "D", isDeleted: true },
  193. { id: "E", isDeleted: true },
  194. { id: "F", isSelected: true },
  195. ],
  196. operations: [
  197. [actionSendBackward, ["A", "F", "B", "C", "D", "E"]],
  198. [actionSendBackward, ["F", "A", "B", "C", "D", "E"]],
  199. // noop
  200. [actionSendBackward, ["F", "A", "B", "C", "D", "E"]],
  201. ],
  202. });
  203. assertZindex({
  204. elements: [
  205. { id: "A" },
  206. { id: "B", groupIds: ["g2", "g1"] },
  207. { id: "C", groupIds: ["g2", "g1"] },
  208. { id: "D", groupIds: ["g1"] },
  209. { id: "E", isDeleted: true },
  210. { id: "F", isSelected: true },
  211. ],
  212. operations: [
  213. [actionSendBackward, ["A", "F", "B", "C", "D", "E"]],
  214. [actionSendBackward, ["F", "A", "B", "C", "D", "E"]],
  215. // noop
  216. [actionSendBackward, ["F", "A", "B", "C", "D", "E"]],
  217. ],
  218. });
  219. assertZindex({
  220. elements: [
  221. { id: "A" },
  222. { id: "B", groupIds: ["g1"] },
  223. { id: "C", groupIds: ["g2", "g1"] },
  224. { id: "D", groupIds: ["g2", "g1"] },
  225. { id: "E", isDeleted: true },
  226. { id: "F", isSelected: true },
  227. ],
  228. operations: [
  229. [actionSendBackward, ["A", "F", "B", "C", "D", "E"]],
  230. [actionSendBackward, ["F", "A", "B", "C", "D", "E"]],
  231. // noop
  232. [actionSendBackward, ["F", "A", "B", "C", "D", "E"]],
  233. ],
  234. });
  235. assertZindex({
  236. elements: [
  237. { id: "A" },
  238. { id: "B1", groupIds: ["g1"] },
  239. { id: "C1", groupIds: ["g1"] },
  240. { id: "D2", groupIds: ["g2"], isSelected: true },
  241. { id: "E2", groupIds: ["g2"], isSelected: true },
  242. ],
  243. appState: {
  244. editingGroupId: null,
  245. },
  246. operations: [[actionSendBackward, ["A", "D2", "E2", "B1", "C1"]]],
  247. });
  248. // in-group siblings
  249. // -------------------------------------------------------------------------
  250. assertZindex({
  251. elements: [
  252. { id: "A" },
  253. { id: "B", groupIds: ["g1"] },
  254. { id: "C", groupIds: ["g2", "g1"] },
  255. { id: "D", groupIds: ["g2", "g1"], isSelected: true },
  256. ],
  257. appState: {
  258. editingGroupId: "g2",
  259. },
  260. operations: [
  261. [actionSendBackward, ["A", "B", "D", "C"]],
  262. // noop (prevented)
  263. [actionSendBackward, ["A", "B", "D", "C"]],
  264. ],
  265. });
  266. assertZindex({
  267. elements: [
  268. { id: "A" },
  269. { id: "B", groupIds: ["g2", "g1"] },
  270. { id: "C", groupIds: ["g2", "g1"] },
  271. { id: "D", groupIds: ["g1"], isSelected: true },
  272. ],
  273. appState: {
  274. editingGroupId: "g1",
  275. },
  276. operations: [
  277. [actionSendBackward, ["A", "D", "B", "C"]],
  278. // noop (prevented)
  279. [actionSendBackward, ["A", "D", "B", "C"]],
  280. ],
  281. });
  282. assertZindex({
  283. elements: [
  284. { id: "A" },
  285. { id: "B", groupIds: ["g1"] },
  286. { id: "C", groupIds: ["g2", "g1"], isSelected: true },
  287. { id: "D", groupIds: ["g2", "g1"], isDeleted: true },
  288. { id: "E", groupIds: ["g2", "g1"], isSelected: true },
  289. ],
  290. appState: {
  291. editingGroupId: "g1",
  292. },
  293. operations: [
  294. [actionSendBackward, ["A", "C", "D", "E", "B"]],
  295. // noop (prevented)
  296. [actionSendBackward, ["A", "C", "D", "E", "B"]],
  297. ],
  298. });
  299. assertZindex({
  300. elements: [
  301. { id: "A" },
  302. { id: "B", groupIds: ["g1"] },
  303. { id: "C", groupIds: ["g2", "g1"] },
  304. { id: "D", groupIds: ["g2", "g1"] },
  305. { id: "E", groupIds: ["g3", "g1"], isSelected: true },
  306. { id: "F", groupIds: ["g3", "g1"], isSelected: true },
  307. ],
  308. appState: {
  309. editingGroupId: "g1",
  310. },
  311. operations: [
  312. [actionSendBackward, ["A", "B", "E", "F", "C", "D"]],
  313. [actionSendBackward, ["A", "E", "F", "B", "C", "D"]],
  314. // noop (prevented)
  315. [actionSendBackward, ["A", "E", "F", "B", "C", "D"]],
  316. ],
  317. });
  318. // invalid z-indexes across groups (legacy) → allow to sort to next sibling
  319. assertZindex({
  320. elements: [
  321. { id: "A", groupIds: ["g1"] },
  322. { id: "B", groupIds: ["g2"] },
  323. { id: "C", groupIds: ["g1"] },
  324. { id: "D", groupIds: ["g2"], isSelected: true },
  325. { id: "E", groupIds: ["g2"], isSelected: true },
  326. ],
  327. appState: {
  328. editingGroupId: "g2",
  329. },
  330. operations: [
  331. [actionSendBackward, ["A", "D", "E", "B", "C"]],
  332. // noop
  333. [actionSendBackward, ["A", "D", "E", "B", "C"]],
  334. ],
  335. });
  336. // invalid z-indexes across groups (legacy) → allow to sort to next sibling
  337. assertZindex({
  338. elements: [
  339. { id: "A", groupIds: ["g1"] },
  340. { id: "B", groupIds: ["g2"] },
  341. { id: "C", groupIds: ["g1"] },
  342. { id: "D", groupIds: ["g2"], isSelected: true },
  343. { id: "F" },
  344. { id: "G", groupIds: ["g2"], isSelected: true },
  345. ],
  346. appState: {
  347. editingGroupId: "g2",
  348. },
  349. operations: [
  350. [actionSendBackward, ["A", "D", "G", "B", "C", "F"]],
  351. // noop
  352. [actionSendBackward, ["A", "D", "G", "B", "C", "F"]],
  353. ],
  354. });
  355. });
  356. it("bring forward", () => {
  357. assertZindex({
  358. elements: [
  359. { id: "A" },
  360. { id: "B", isSelected: true },
  361. { id: "C", isSelected: true },
  362. { id: "D", isDeleted: true },
  363. { id: "E" },
  364. ],
  365. operations: [
  366. [actionBringForward, ["A", "D", "E", "B", "C"]],
  367. // noop
  368. [actionBringForward, ["A", "D", "E", "B", "C"]],
  369. ],
  370. });
  371. assertZindex({
  372. elements: [
  373. { id: "A", isSelected: true },
  374. { id: "B", isSelected: true },
  375. { id: "C", isSelected: true },
  376. ],
  377. operations: [
  378. // noop
  379. [actionBringForward, ["A", "B", "C"]],
  380. ],
  381. });
  382. assertZindex({
  383. elements: [
  384. { id: "A", isSelected: true },
  385. { id: "B", isDeleted: true },
  386. { id: "C", isDeleted: true },
  387. { id: "D" },
  388. { id: "E", isSelected: true },
  389. { id: "F", isDeleted: true },
  390. { id: "G" },
  391. ],
  392. operations: [
  393. [actionBringForward, ["B", "C", "D", "A", "F", "G", "E"]],
  394. [actionBringForward, ["B", "C", "D", "F", "G", "A", "E"]],
  395. // noop
  396. [actionBringForward, ["B", "C", "D", "F", "G", "A", "E"]],
  397. ],
  398. });
  399. // grouped elements should be atomic
  400. // -------------------------------------------------------------------------
  401. assertZindex({
  402. elements: [
  403. { id: "A", isSelected: true },
  404. { id: "B", isDeleted: true },
  405. { id: "C", isDeleted: true },
  406. { id: "D", groupIds: ["g1"] },
  407. { id: "E", groupIds: ["g1"] },
  408. { id: "F" },
  409. ],
  410. operations: [
  411. [actionBringForward, ["B", "C", "D", "E", "A", "F"]],
  412. [actionBringForward, ["B", "C", "D", "E", "F", "A"]],
  413. // noop
  414. [actionBringForward, ["B", "C", "D", "E", "F", "A"]],
  415. ],
  416. });
  417. assertZindex({
  418. elements: [
  419. { id: "A" },
  420. { id: "B", isSelected: true },
  421. { id: "C", groupIds: ["g2", "g1"] },
  422. { id: "D", groupIds: ["g2", "g1"] },
  423. { id: "E", groupIds: ["g1"] },
  424. { id: "F" },
  425. ],
  426. operations: [
  427. [actionBringForward, ["A", "C", "D", "E", "B", "F"]],
  428. [actionBringForward, ["A", "C", "D", "E", "F", "B"]],
  429. // noop
  430. [actionBringForward, ["A", "C", "D", "E", "F", "B"]],
  431. ],
  432. });
  433. assertZindex({
  434. elements: [
  435. { id: "A" },
  436. { id: "B", isSelected: true },
  437. { id: "C", groupIds: ["g1"] },
  438. { id: "D", groupIds: ["g2", "g1"] },
  439. { id: "E", groupIds: ["g2", "g1"] },
  440. { id: "F" },
  441. ],
  442. operations: [
  443. [actionBringForward, ["A", "C", "D", "E", "B", "F"]],
  444. [actionBringForward, ["A", "C", "D", "E", "F", "B"]],
  445. // noop
  446. [actionBringForward, ["A", "C", "D", "E", "F", "B"]],
  447. ],
  448. });
  449. // in-group siblings
  450. // -------------------------------------------------------------------------
  451. assertZindex({
  452. elements: [
  453. { id: "A" },
  454. { id: "B", groupIds: ["g2", "g1"], isSelected: true },
  455. { id: "C", groupIds: ["g2", "g1"] },
  456. { id: "D", groupIds: ["g1"] },
  457. ],
  458. appState: {
  459. editingGroupId: "g2",
  460. },
  461. operations: [
  462. [actionBringForward, ["A", "C", "B", "D"]],
  463. // noop (prevented)
  464. [actionBringForward, ["A", "C", "B", "D"]],
  465. ],
  466. });
  467. assertZindex({
  468. elements: [
  469. { id: "A", groupIds: ["g1"], isSelected: true },
  470. { id: "B", groupIds: ["g2", "g1"] },
  471. { id: "C", groupIds: ["g2", "g1"] },
  472. { id: "D" },
  473. ],
  474. appState: {
  475. editingGroupId: "g1",
  476. },
  477. operations: [
  478. [actionBringForward, ["B", "C", "A", "D"]],
  479. // noop (prevented)
  480. [actionBringForward, ["B", "C", "A", "D"]],
  481. ],
  482. });
  483. assertZindex({
  484. elements: [
  485. { id: "A", groupIds: ["g2", "g1"], isSelected: true },
  486. { id: "B", groupIds: ["g2", "g1"], isSelected: true },
  487. { id: "C", groupIds: ["g1"] },
  488. { id: "D" },
  489. ],
  490. appState: {
  491. editingGroupId: "g1",
  492. },
  493. operations: [
  494. [actionBringForward, ["C", "A", "B", "D"]],
  495. // noop (prevented)
  496. [actionBringForward, ["C", "A", "B", "D"]],
  497. ],
  498. });
  499. // invalid z-indexes across groups (legacy) → allow to sort to next sibling
  500. assertZindex({
  501. elements: [
  502. { id: "A", groupIds: ["g2"], isSelected: true },
  503. { id: "B", groupIds: ["g2"], isSelected: true },
  504. { id: "C", groupIds: ["g1"] },
  505. { id: "D", groupIds: ["g2"] },
  506. { id: "E", groupIds: ["g1"] },
  507. ],
  508. appState: {
  509. editingGroupId: "g2",
  510. },
  511. operations: [
  512. [actionBringForward, ["C", "D", "A", "B", "E"]],
  513. // noop
  514. [actionBringForward, ["C", "D", "A", "B", "E"]],
  515. ],
  516. });
  517. // invalid z-indexes across groups (legacy) → allow to sort to next sibling
  518. assertZindex({
  519. elements: [
  520. { id: "A", groupIds: ["g2"], isSelected: true },
  521. { id: "B" },
  522. { id: "C", groupIds: ["g2"], isSelected: true },
  523. { id: "D", groupIds: ["g1"] },
  524. { id: "E", groupIds: ["g2"] },
  525. { id: "F", groupIds: ["g1"] },
  526. ],
  527. appState: {
  528. editingGroupId: "g2",
  529. },
  530. operations: [
  531. [actionBringForward, ["B", "D", "E", "A", "C", "F"]],
  532. // noop
  533. [actionBringForward, ["B", "D", "E", "A", "C", "F"]],
  534. ],
  535. });
  536. });
  537. it("bring to front", () => {
  538. assertZindex({
  539. elements: [
  540. { id: "0" },
  541. { id: "A", isSelected: true },
  542. { id: "B", isDeleted: true },
  543. { id: "C", isDeleted: true },
  544. { id: "D" },
  545. { id: "E", isSelected: true },
  546. { id: "F", isDeleted: true },
  547. { id: "G" },
  548. ],
  549. operations: [
  550. [actionBringToFront, ["0", "B", "C", "D", "F", "G", "A", "E"]],
  551. // noop
  552. [actionBringToFront, ["0", "B", "C", "D", "F", "G", "A", "E"]],
  553. ],
  554. });
  555. assertZindex({
  556. elements: [
  557. { id: "A", isSelected: true },
  558. { id: "B", isSelected: true },
  559. { id: "C", isSelected: true },
  560. ],
  561. operations: [
  562. // noop
  563. [actionBringToFront, ["A", "B", "C"]],
  564. ],
  565. });
  566. assertZindex({
  567. elements: [
  568. { id: "A" },
  569. { id: "B", isSelected: true },
  570. { id: "C", isSelected: true },
  571. ],
  572. operations: [
  573. // noop
  574. [actionBringToFront, ["A", "B", "C"]],
  575. ],
  576. });
  577. assertZindex({
  578. elements: [
  579. { id: "A", isSelected: true },
  580. { id: "B", isSelected: true },
  581. { id: "C" },
  582. ],
  583. operations: [
  584. [actionBringToFront, ["C", "A", "B"]],
  585. // noop
  586. [actionBringToFront, ["C", "A", "B"]],
  587. ],
  588. });
  589. // in-group sorting
  590. // -------------------------------------------------------------------------
  591. assertZindex({
  592. elements: [
  593. { id: "A" },
  594. { id: "B", groupIds: ["g1"] },
  595. { id: "C", groupIds: ["g1"], isSelected: true },
  596. { id: "D", groupIds: ["g1"] },
  597. { id: "E", groupIds: ["g1"], isSelected: true },
  598. { id: "F", groupIds: ["g2", "g1"] },
  599. { id: "G", groupIds: ["g2", "g1"] },
  600. { id: "H", groupIds: ["g3", "g1"] },
  601. { id: "I", groupIds: ["g3", "g1"] },
  602. ],
  603. appState: {
  604. editingGroupId: "g1",
  605. },
  606. operations: [
  607. [actionBringToFront, ["A", "B", "D", "F", "G", "H", "I", "C", "E"]],
  608. // noop (prevented)
  609. [actionBringToFront, ["A", "B", "D", "F", "G", "H", "I", "C", "E"]],
  610. ],
  611. });
  612. assertZindex({
  613. elements: [
  614. { id: "A" },
  615. { id: "B", groupIds: ["g2", "g1"], isSelected: true },
  616. { id: "D", groupIds: ["g2", "g1"] },
  617. { id: "C", groupIds: ["g1"] },
  618. ],
  619. appState: {
  620. editingGroupId: "g2",
  621. },
  622. operations: [
  623. [actionBringToFront, ["A", "D", "B", "C"]],
  624. // noop (prevented)
  625. [actionBringToFront, ["A", "D", "B", "C"]],
  626. ],
  627. });
  628. // invalid z-indexes across groups (legacy) → allow to sort to next sibling
  629. assertZindex({
  630. elements: [
  631. { id: "A", groupIds: ["g2", "g3"], isSelected: true },
  632. { id: "B", groupIds: ["g1", "g3"] },
  633. { id: "C", groupIds: ["g2", "g3"] },
  634. { id: "D", groupIds: ["g1", "g3"] },
  635. ],
  636. appState: {
  637. editingGroupId: "g2",
  638. },
  639. operations: [
  640. [actionBringToFront, ["B", "C", "A", "D"]],
  641. // noop
  642. [actionBringToFront, ["B", "C", "A", "D"]],
  643. ],
  644. });
  645. // invalid z-indexes across groups (legacy) → allow to sort to next sibling
  646. assertZindex({
  647. elements: [
  648. { id: "A", groupIds: ["g2"], isSelected: true },
  649. { id: "B", groupIds: ["g1"] },
  650. { id: "C", groupIds: ["g2"] },
  651. { id: "D", groupIds: ["g1"] },
  652. ],
  653. appState: {
  654. editingGroupId: "g2",
  655. },
  656. operations: [
  657. [actionBringToFront, ["B", "C", "A", "D"]],
  658. // noop
  659. [actionBringToFront, ["B", "C", "A", "D"]],
  660. ],
  661. });
  662. });
  663. it("send to back", () => {
  664. assertZindex({
  665. elements: [
  666. { id: "A" },
  667. { id: "B", isDeleted: true },
  668. { id: "C" },
  669. { id: "D", isDeleted: true },
  670. { id: "E", isSelected: true },
  671. { id: "F", isDeleted: true },
  672. { id: "G" },
  673. { id: "H", isSelected: true },
  674. { id: "I" },
  675. ],
  676. operations: [
  677. [actionSendToBack, ["E", "H", "A", "B", "C", "D", "F", "G", "I"]],
  678. // noop
  679. [actionSendToBack, ["E", "H", "A", "B", "C", "D", "F", "G", "I"]],
  680. ],
  681. });
  682. assertZindex({
  683. elements: [
  684. { id: "A", isSelected: true },
  685. { id: "B", isSelected: true },
  686. { id: "C", isSelected: true },
  687. ],
  688. operations: [
  689. // noop
  690. [actionSendToBack, ["A", "B", "C"]],
  691. ],
  692. });
  693. assertZindex({
  694. elements: [
  695. { id: "A", isSelected: true },
  696. { id: "B", isSelected: true },
  697. { id: "C" },
  698. ],
  699. operations: [
  700. // noop
  701. [actionSendToBack, ["A", "B", "C"]],
  702. ],
  703. });
  704. assertZindex({
  705. elements: [
  706. { id: "A" },
  707. { id: "B", isSelected: true },
  708. { id: "C", isSelected: true },
  709. ],
  710. operations: [
  711. [actionSendToBack, ["B", "C", "A"]],
  712. // noop
  713. [actionSendToBack, ["B", "C", "A"]],
  714. ],
  715. });
  716. // in-group sorting
  717. // -------------------------------------------------------------------------
  718. assertZindex({
  719. elements: [
  720. { id: "A" },
  721. { id: "B", groupIds: ["g2", "g1"] },
  722. { id: "C", groupIds: ["g2", "g1"] },
  723. { id: "D", groupIds: ["g3", "g1"] },
  724. { id: "E", groupIds: ["g3", "g1"] },
  725. { id: "F", groupIds: ["g1"], isSelected: true },
  726. { id: "G", groupIds: ["g1"] },
  727. { id: "H", groupIds: ["g1"], isSelected: true },
  728. { id: "I", groupIds: ["g1"] },
  729. ],
  730. appState: {
  731. editingGroupId: "g1",
  732. },
  733. operations: [
  734. [actionSendToBack, ["A", "F", "H", "B", "C", "D", "E", "G", "I"]],
  735. // noop (prevented)
  736. [actionSendToBack, ["A", "F", "H", "B", "C", "D", "E", "G", "I"]],
  737. ],
  738. });
  739. assertZindex({
  740. elements: [
  741. { id: "A" },
  742. { id: "B", groupIds: ["g1"] },
  743. { id: "C", groupIds: ["g2", "g1"] },
  744. { id: "D", groupIds: ["g2", "g1"], isSelected: true },
  745. ],
  746. appState: {
  747. editingGroupId: "g2",
  748. },
  749. operations: [
  750. [actionSendToBack, ["A", "B", "D", "C"]],
  751. // noop (prevented)
  752. [actionSendToBack, ["A", "B", "D", "C"]],
  753. ],
  754. });
  755. // invalid z-indexes across groups (legacy) → allow to sort to next sibling
  756. assertZindex({
  757. elements: [
  758. { id: "A", groupIds: ["g1", "g3"] },
  759. { id: "B", groupIds: ["g2", "g3"] },
  760. { id: "C", groupIds: ["g1", "g3"] },
  761. { id: "D", groupIds: ["g2", "g3"], isSelected: true },
  762. ],
  763. appState: {
  764. editingGroupId: "g2",
  765. },
  766. operations: [
  767. [actionSendToBack, ["A", "D", "B", "C"]],
  768. // noop
  769. [actionSendToBack, ["A", "D", "B", "C"]],
  770. ],
  771. });
  772. // invalid z-indexes across groups (legacy) → allow to sort to next sibling
  773. assertZindex({
  774. elements: [
  775. { id: "A", groupIds: ["g1"] },
  776. { id: "B", groupIds: ["g2"] },
  777. { id: "C", groupIds: ["g1"] },
  778. { id: "D", groupIds: ["g2"], isSelected: true },
  779. ],
  780. appState: {
  781. editingGroupId: "g2",
  782. },
  783. operations: [
  784. [actionSendToBack, ["A", "D", "B", "C"]],
  785. // noop
  786. [actionSendToBack, ["A", "D", "B", "C"]],
  787. ],
  788. });
  789. });
  790. it("duplicating elements should retain zindex integrity", () => {
  791. populateElements([
  792. { id: "A", isSelected: true },
  793. { id: "B", isSelected: true },
  794. ]);
  795. h.app.actionManager.executeAction(actionDuplicateSelection);
  796. expect(h.elements).toMatchObject([
  797. { id: "A" },
  798. { id: "A_copy" },
  799. { id: "B" },
  800. { id: "B_copy" },
  801. ]);
  802. populateElements([
  803. { id: "A", groupIds: ["g1"], isSelected: true },
  804. { id: "B", groupIds: ["g1"], isSelected: true },
  805. ]);
  806. h.setState({
  807. selectedGroupIds: { g1: true },
  808. });
  809. h.app.actionManager.executeAction(actionDuplicateSelection);
  810. expect(h.elements).toMatchObject([
  811. { id: "A" },
  812. { id: "B" },
  813. {
  814. id: "A_copy",
  815. groupIds: [expect.stringMatching(/.{3,}/)],
  816. },
  817. {
  818. id: "B_copy",
  819. groupIds: [expect.stringMatching(/.{3,}/)],
  820. },
  821. ]);
  822. populateElements([
  823. { id: "A", groupIds: ["g1"], isSelected: true },
  824. { id: "B", groupIds: ["g1"], isSelected: true },
  825. { id: "C" },
  826. ]);
  827. h.setState({
  828. selectedGroupIds: { g1: true },
  829. });
  830. h.app.actionManager.executeAction(actionDuplicateSelection);
  831. expect(h.elements).toMatchObject([
  832. { id: "A" },
  833. { id: "B" },
  834. {
  835. id: "A_copy",
  836. groupIds: [expect.stringMatching(/.{3,}/)],
  837. },
  838. {
  839. id: "B_copy",
  840. groupIds: [expect.stringMatching(/.{3,}/)],
  841. },
  842. { id: "C" },
  843. ]);
  844. populateElements([
  845. { id: "A", groupIds: ["g1"], isSelected: true },
  846. { id: "B", groupIds: ["g1"], isSelected: true },
  847. { id: "C", isSelected: true },
  848. ]);
  849. h.setState({
  850. selectedGroupIds: { g1: true },
  851. });
  852. h.app.actionManager.executeAction(actionDuplicateSelection);
  853. expect(h.elements.map((element) => element.id)).toEqual([
  854. "A",
  855. "B",
  856. "A_copy",
  857. "B_copy",
  858. "C",
  859. "C_copy",
  860. ]);
  861. populateElements([
  862. { id: "A", groupIds: ["g1"], isSelected: true },
  863. { id: "B", groupIds: ["g1"], isSelected: true },
  864. { id: "C", groupIds: ["g2"], isSelected: true },
  865. { id: "D", groupIds: ["g2"], isSelected: true },
  866. ]);
  867. h.setState({
  868. selectedGroupIds: { g1: true, g2: true },
  869. });
  870. h.app.actionManager.executeAction(actionDuplicateSelection);
  871. expect(h.elements.map((element) => element.id)).toEqual([
  872. "A",
  873. "B",
  874. "A_copy",
  875. "B_copy",
  876. "C",
  877. "D",
  878. "C_copy",
  879. "D_copy",
  880. ]);
  881. populateElements([
  882. { id: "A", groupIds: ["g1", "g2"], isSelected: true },
  883. { id: "B", groupIds: ["g1", "g2"], isSelected: true },
  884. { id: "C", groupIds: ["g2"], isSelected: true },
  885. ]);
  886. h.setState({
  887. selectedGroupIds: { g1: true },
  888. });
  889. h.app.actionManager.executeAction(actionDuplicateSelection);
  890. expect(h.elements.map((element) => element.id)).toEqual([
  891. "A",
  892. "B",
  893. "A_copy",
  894. "B_copy",
  895. "C",
  896. "C_copy",
  897. ]);
  898. populateElements([
  899. { id: "A", groupIds: ["g1", "g2"], isSelected: true },
  900. { id: "B", groupIds: ["g1", "g2"], isSelected: true },
  901. { id: "C", groupIds: ["g2"], isSelected: true },
  902. ]);
  903. h.setState({
  904. selectedGroupIds: { g2: true },
  905. });
  906. h.app.actionManager.executeAction(actionDuplicateSelection);
  907. expect(h.elements.map((element) => element.id)).toEqual([
  908. "A",
  909. "B",
  910. "C",
  911. "A_copy",
  912. "B_copy",
  913. "C_copy",
  914. ]);
  915. populateElements([
  916. { id: "A", groupIds: ["g1", "g2"], isSelected: true },
  917. { id: "B", groupIds: ["g1", "g2"], isSelected: true },
  918. { id: "C", groupIds: ["g2"], isSelected: true },
  919. { id: "D", groupIds: ["g3", "g4"], isSelected: true },
  920. { id: "E", groupIds: ["g3", "g4"], isSelected: true },
  921. { id: "F", groupIds: ["g4"], isSelected: true },
  922. ]);
  923. h.setState({
  924. selectedGroupIds: { g2: true, g4: true },
  925. });
  926. h.app.actionManager.executeAction(actionDuplicateSelection);
  927. expect(h.elements.map((element) => element.id)).toEqual([
  928. "A",
  929. "B",
  930. "C",
  931. "A_copy",
  932. "B_copy",
  933. "C_copy",
  934. "D",
  935. "E",
  936. "F",
  937. "D_copy",
  938. "E_copy",
  939. "F_copy",
  940. ]);
  941. populateElements([
  942. { id: "A", groupIds: ["g1", "g2"], isSelected: true },
  943. { id: "B", groupIds: ["g1", "g2"] },
  944. { id: "C", groupIds: ["g2"] },
  945. ]);
  946. h.app.actionManager.executeAction(actionDuplicateSelection);
  947. expect(h.elements.map((element) => element.id)).toEqual([
  948. "A",
  949. "A_copy",
  950. "B",
  951. "C",
  952. ]);
  953. populateElements([
  954. { id: "A", groupIds: ["g1", "g2"] },
  955. { id: "B", groupIds: ["g1", "g2"], isSelected: true },
  956. { id: "C", groupIds: ["g2"] },
  957. ]);
  958. h.app.actionManager.executeAction(actionDuplicateSelection);
  959. expect(h.elements.map((element) => element.id)).toEqual([
  960. "A",
  961. "B",
  962. "B_copy",
  963. "C",
  964. ]);
  965. populateElements([
  966. { id: "A", groupIds: ["g1", "g2"], isSelected: true },
  967. { id: "B", groupIds: ["g1", "g2"], isSelected: true },
  968. { id: "C", groupIds: ["g2"], isSelected: true },
  969. ]);
  970. h.app.actionManager.executeAction(actionDuplicateSelection);
  971. expect(h.elements.map((element) => element.id)).toEqual([
  972. "A",
  973. "A_copy",
  974. "B",
  975. "B_copy",
  976. "C",
  977. "C_copy",
  978. ]);
  979. });
  980. });