zindex.test.tsx 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512
  1. import { reseed } from "@excalidraw/common";
  2. import {
  3. actionSendBackward,
  4. actionBringForward,
  5. actionBringToFront,
  6. actionSendToBack,
  7. actionDuplicateSelection,
  8. } from "@excalidraw/excalidraw/actions";
  9. import { Excalidraw } from "@excalidraw/excalidraw";
  10. import { API } from "@excalidraw/excalidraw/tests/helpers/api";
  11. import {
  12. act,
  13. getCloneByOrigId,
  14. render,
  15. unmountComponent,
  16. } from "@excalidraw/excalidraw/tests/test-utils";
  17. import type { AppState } from "@excalidraw/excalidraw/types";
  18. import { selectGroupsForSelectedElements } from "../src/groups";
  19. import type {
  20. ExcalidrawElement,
  21. ExcalidrawFrameElement,
  22. ExcalidrawSelectionElement,
  23. } from "../src/types";
  24. unmountComponent();
  25. beforeEach(() => {
  26. localStorage.clear();
  27. reseed(7);
  28. });
  29. const { h } = window;
  30. type ExcalidrawElementType = Exclude<
  31. ExcalidrawElement,
  32. ExcalidrawSelectionElement
  33. >["type"];
  34. const populateElements = (
  35. elements: {
  36. id: string;
  37. type?: ExcalidrawElementType;
  38. isDeleted?: boolean;
  39. isSelected?: boolean;
  40. groupIds?: string[];
  41. y?: number;
  42. x?: number;
  43. width?: number;
  44. height?: number;
  45. containerId?: string;
  46. frameId?: ExcalidrawFrameElement["id"];
  47. index?: ExcalidrawElement["index"];
  48. }[],
  49. appState?: Partial<AppState>,
  50. ) => {
  51. const selectedElementIds: any = {};
  52. const newElements = elements.map(
  53. ({
  54. id,
  55. isDeleted = false,
  56. isSelected = false,
  57. groupIds = [],
  58. y = 100,
  59. x = 100,
  60. width = 100,
  61. height = 100,
  62. containerId = null,
  63. frameId = null,
  64. type,
  65. }) => {
  66. const element = API.createElement({
  67. type: type ?? (containerId ? "text" : "rectangle"),
  68. id,
  69. isDeleted,
  70. x,
  71. y,
  72. width,
  73. height,
  74. groupIds,
  75. containerId,
  76. frameId: frameId || null,
  77. });
  78. if (isSelected) {
  79. selectedElementIds[element.id] = true;
  80. }
  81. return element;
  82. },
  83. );
  84. // initialize `boundElements` on containers, if applicable
  85. API.setElements(
  86. newElements.map((element, index, elements) => {
  87. const nextElement = elements[index + 1];
  88. if (
  89. nextElement &&
  90. "containerId" in nextElement &&
  91. element.id === nextElement.containerId
  92. ) {
  93. return {
  94. ...element,
  95. boundElements: [{ type: "text", id: nextElement.id }],
  96. };
  97. }
  98. return element;
  99. }),
  100. );
  101. act(() => {
  102. h.setState({
  103. ...selectGroupsForSelectedElements(
  104. { ...h.state, ...appState, selectedElementIds },
  105. h.elements,
  106. h.state,
  107. null,
  108. ),
  109. ...appState,
  110. selectedElementIds,
  111. } as AppState);
  112. });
  113. return selectedElementIds;
  114. };
  115. type Actions =
  116. | typeof actionBringForward
  117. | typeof actionSendBackward
  118. | typeof actionBringToFront
  119. | typeof actionSendToBack;
  120. const assertZindex = ({
  121. elements,
  122. appState,
  123. operations,
  124. }: {
  125. elements: {
  126. id: string;
  127. isDeleted?: true;
  128. isSelected?: true;
  129. groupIds?: string[];
  130. containerId?: string;
  131. frameId?: ExcalidrawFrameElement["id"];
  132. type?: ExcalidrawElementType;
  133. }[];
  134. appState?: Partial<AppState>;
  135. operations: [Actions, string[]][];
  136. }) => {
  137. const selectedElementIds = populateElements(elements, appState);
  138. operations.forEach(([action, expected]) => {
  139. API.executeAction(action);
  140. expect(h.elements.map((element) => element.id)).toEqual(expected);
  141. expect(h.state.selectedElementIds).toEqual(selectedElementIds);
  142. });
  143. };
  144. describe("z-index manipulation", () => {
  145. beforeEach(async () => {
  146. await render(<Excalidraw />);
  147. });
  148. it("send back", () => {
  149. assertZindex({
  150. elements: [
  151. { id: "A" },
  152. { id: "B", isDeleted: true },
  153. { id: "C", isDeleted: true },
  154. { id: "D", isSelected: true },
  155. ],
  156. operations: [
  157. [actionSendBackward, ["D", "A", "B", "C"]],
  158. // noop
  159. [actionSendBackward, ["D", "A", "B", "C"]],
  160. ],
  161. });
  162. assertZindex({
  163. elements: [
  164. { id: "A", isSelected: true },
  165. { id: "B", isSelected: true },
  166. { id: "C", isSelected: true },
  167. ],
  168. operations: [
  169. // noop
  170. [actionSendBackward, ["A", "B", "C"]],
  171. ],
  172. });
  173. assertZindex({
  174. elements: [
  175. { id: "A", isDeleted: true },
  176. { id: "B" },
  177. { id: "C", isDeleted: true },
  178. { id: "D", isSelected: true },
  179. ],
  180. operations: [[actionSendBackward, ["A", "D", "B", "C"]]],
  181. });
  182. assertZindex({
  183. elements: [
  184. { id: "A" },
  185. { id: "B", isDeleted: true },
  186. { id: "C", isDeleted: true },
  187. { id: "D", isSelected: true },
  188. { id: "E", isSelected: true },
  189. { id: "F" },
  190. ],
  191. operations: [
  192. [actionSendBackward, ["D", "E", "A", "B", "C", "F"]],
  193. // noop
  194. [actionSendBackward, ["D", "E", "A", "B", "C", "F"]],
  195. ],
  196. });
  197. assertZindex({
  198. elements: [
  199. { id: "A" },
  200. { id: "B" },
  201. { id: "C", isDeleted: true },
  202. { id: "D", isDeleted: true },
  203. { id: "E", isSelected: true },
  204. { id: "F" },
  205. { id: "G", isSelected: true },
  206. ],
  207. operations: [
  208. [actionSendBackward, ["A", "E", "B", "C", "D", "G", "F"]],
  209. [actionSendBackward, ["E", "A", "G", "B", "C", "D", "F"]],
  210. [actionSendBackward, ["E", "G", "A", "B", "C", "D", "F"]],
  211. // noop
  212. [actionSendBackward, ["E", "G", "A", "B", "C", "D", "F"]],
  213. ],
  214. });
  215. assertZindex({
  216. elements: [
  217. { id: "A" },
  218. { id: "B" },
  219. { id: "C", isDeleted: true },
  220. { id: "D", isSelected: true },
  221. { id: "E", isDeleted: true },
  222. { id: "F", isSelected: true },
  223. { id: "G" },
  224. ],
  225. operations: [
  226. [actionSendBackward, ["A", "D", "E", "F", "B", "C", "G"]],
  227. [actionSendBackward, ["D", "E", "F", "A", "B", "C", "G"]],
  228. // noop
  229. [actionSendBackward, ["D", "E", "F", "A", "B", "C", "G"]],
  230. ],
  231. });
  232. // elements should not duplicate
  233. assertZindex({
  234. elements: [
  235. { id: "A", containerId: "C" },
  236. { id: "B" },
  237. { id: "C", isSelected: true },
  238. ],
  239. operations: [
  240. [actionSendBackward, ["A", "C", "B"]],
  241. // noop
  242. [actionSendBackward, ["A", "C", "B"]],
  243. ],
  244. });
  245. // grouped elements should be atomic
  246. // -------------------------------------------------------------------------
  247. assertZindex({
  248. elements: [
  249. { id: "A" },
  250. { id: "B", groupIds: ["g1"] },
  251. { id: "C", groupIds: ["g1"] },
  252. { id: "D", isDeleted: true },
  253. { id: "E", isDeleted: true },
  254. { id: "F", isSelected: true },
  255. ],
  256. operations: [
  257. [actionSendBackward, ["A", "F", "B", "C", "D", "E"]],
  258. [actionSendBackward, ["F", "A", "B", "C", "D", "E"]],
  259. // noop
  260. [actionSendBackward, ["F", "A", "B", "C", "D", "E"]],
  261. ],
  262. });
  263. assertZindex({
  264. elements: [
  265. { id: "A" },
  266. { id: "B", groupIds: ["g2", "g1"] },
  267. { id: "C", groupIds: ["g2", "g1"] },
  268. { id: "D", groupIds: ["g1"] },
  269. { id: "E", isDeleted: true },
  270. { id: "F", isSelected: true },
  271. ],
  272. operations: [
  273. [actionSendBackward, ["A", "F", "B", "C", "D", "E"]],
  274. [actionSendBackward, ["F", "A", "B", "C", "D", "E"]],
  275. // noop
  276. [actionSendBackward, ["F", "A", "B", "C", "D", "E"]],
  277. ],
  278. });
  279. assertZindex({
  280. elements: [
  281. { id: "A" },
  282. { id: "B", groupIds: ["g1"] },
  283. { id: "C", groupIds: ["g2", "g1"] },
  284. { id: "D", groupIds: ["g2", "g1"] },
  285. { id: "E", isDeleted: true },
  286. { id: "F", isSelected: true },
  287. ],
  288. operations: [
  289. [actionSendBackward, ["A", "F", "B", "C", "D", "E"]],
  290. [actionSendBackward, ["F", "A", "B", "C", "D", "E"]],
  291. // noop
  292. [actionSendBackward, ["F", "A", "B", "C", "D", "E"]],
  293. ],
  294. });
  295. assertZindex({
  296. elements: [
  297. { id: "A" },
  298. { id: "B1", groupIds: ["g1"] },
  299. { id: "C1", groupIds: ["g1"] },
  300. { id: "D2", groupIds: ["g2"], isSelected: true },
  301. { id: "E2", groupIds: ["g2"], isSelected: true },
  302. ],
  303. appState: {
  304. editingGroupId: null,
  305. },
  306. operations: [[actionSendBackward, ["A", "D2", "E2", "B1", "C1"]]],
  307. });
  308. // in-group siblings
  309. // -------------------------------------------------------------------------
  310. assertZindex({
  311. elements: [
  312. { id: "A" },
  313. { id: "B", groupIds: ["g1"] },
  314. { id: "C", groupIds: ["g2", "g1"] },
  315. { id: "D", groupIds: ["g2", "g1"], isSelected: true },
  316. ],
  317. appState: {
  318. editingGroupId: "g2",
  319. },
  320. operations: [
  321. [actionSendBackward, ["A", "B", "D", "C"]],
  322. // noop (prevented)
  323. [actionSendBackward, ["A", "B", "D", "C"]],
  324. ],
  325. });
  326. assertZindex({
  327. elements: [
  328. { id: "A" },
  329. { id: "B", groupIds: ["g2", "g1"] },
  330. { id: "C", groupIds: ["g2", "g1"] },
  331. { id: "D", groupIds: ["g1"], isSelected: true },
  332. ],
  333. appState: {
  334. editingGroupId: "g1",
  335. },
  336. operations: [
  337. [actionSendBackward, ["A", "D", "B", "C"]],
  338. // noop (prevented)
  339. [actionSendBackward, ["A", "D", "B", "C"]],
  340. ],
  341. });
  342. assertZindex({
  343. elements: [
  344. { id: "A" },
  345. { id: "B", groupIds: ["g1"] },
  346. { id: "C", groupIds: ["g2", "g1"], isSelected: true },
  347. { id: "D", groupIds: ["g2", "g1"], isDeleted: true },
  348. { id: "E", groupIds: ["g2", "g1"], isSelected: true },
  349. ],
  350. appState: {
  351. editingGroupId: "g1",
  352. },
  353. operations: [
  354. [actionSendBackward, ["A", "C", "D", "E", "B"]],
  355. // noop (prevented)
  356. [actionSendBackward, ["A", "C", "D", "E", "B"]],
  357. ],
  358. });
  359. assertZindex({
  360. elements: [
  361. { id: "A" },
  362. { id: "B", groupIds: ["g1"] },
  363. { id: "C", groupIds: ["g2", "g1"] },
  364. { id: "D", groupIds: ["g2", "g1"] },
  365. { id: "E", groupIds: ["g3", "g1"], isSelected: true },
  366. { id: "F", groupIds: ["g3", "g1"], isSelected: true },
  367. ],
  368. appState: {
  369. editingGroupId: "g1",
  370. },
  371. operations: [
  372. [actionSendBackward, ["A", "B", "E", "F", "C", "D"]],
  373. [actionSendBackward, ["A", "E", "F", "B", "C", "D"]],
  374. // noop (prevented)
  375. [actionSendBackward, ["A", "E", "F", "B", "C", "D"]],
  376. ],
  377. });
  378. // invalid z-indexes across groups (legacy) → allow to sort to next sibling
  379. assertZindex({
  380. elements: [
  381. { id: "A", groupIds: ["g1"] },
  382. { id: "B", groupIds: ["g2"] },
  383. { id: "C", groupIds: ["g1"] },
  384. { id: "D", groupIds: ["g2"], isSelected: true },
  385. { id: "E", groupIds: ["g2"], isSelected: true },
  386. ],
  387. appState: {
  388. editingGroupId: "g2",
  389. },
  390. operations: [
  391. [actionSendBackward, ["A", "D", "E", "B", "C"]],
  392. // noop
  393. [actionSendBackward, ["A", "D", "E", "B", "C"]],
  394. ],
  395. });
  396. // invalid z-indexes across groups (legacy) → allow to sort to next sibling
  397. assertZindex({
  398. elements: [
  399. { id: "A", groupIds: ["g1"] },
  400. { id: "B", groupIds: ["g2"] },
  401. { id: "C", groupIds: ["g1"] },
  402. { id: "D", groupIds: ["g2"], isSelected: true },
  403. { id: "F" },
  404. { id: "G", groupIds: ["g2"], isSelected: true },
  405. ],
  406. appState: {
  407. editingGroupId: "g2",
  408. },
  409. operations: [
  410. [actionSendBackward, ["A", "D", "G", "B", "C", "F"]],
  411. // noop
  412. [actionSendBackward, ["A", "D", "G", "B", "C", "F"]],
  413. ],
  414. });
  415. });
  416. it("bring forward", () => {
  417. assertZindex({
  418. elements: [
  419. { id: "A" },
  420. { id: "B", isSelected: true },
  421. { id: "C", isSelected: true },
  422. { id: "D", isDeleted: true },
  423. { id: "E" },
  424. ],
  425. operations: [
  426. [actionBringForward, ["A", "D", "E", "B", "C"]],
  427. // noop
  428. [actionBringForward, ["A", "D", "E", "B", "C"]],
  429. ],
  430. });
  431. assertZindex({
  432. elements: [
  433. { id: "A", isSelected: true },
  434. { id: "B", isSelected: true },
  435. { id: "C", isSelected: true },
  436. ],
  437. operations: [
  438. // noop
  439. [actionBringForward, ["A", "B", "C"]],
  440. ],
  441. });
  442. assertZindex({
  443. elements: [
  444. { id: "A", isSelected: true },
  445. { id: "B", isDeleted: true },
  446. { id: "C", isDeleted: true },
  447. { id: "D" },
  448. { id: "E", isSelected: true },
  449. { id: "F", isDeleted: true },
  450. { id: "G" },
  451. ],
  452. operations: [
  453. [actionBringForward, ["B", "C", "D", "A", "F", "G", "E"]],
  454. [actionBringForward, ["B", "C", "D", "F", "G", "A", "E"]],
  455. // noop
  456. [actionBringForward, ["B", "C", "D", "F", "G", "A", "E"]],
  457. ],
  458. });
  459. // grouped elements should be atomic
  460. // -------------------------------------------------------------------------
  461. assertZindex({
  462. elements: [
  463. { id: "A", isSelected: true },
  464. { id: "B", isDeleted: true },
  465. { id: "C", isDeleted: true },
  466. { id: "D", groupIds: ["g1"] },
  467. { id: "E", groupIds: ["g1"] },
  468. { id: "F" },
  469. ],
  470. operations: [
  471. [actionBringForward, ["B", "C", "D", "E", "A", "F"]],
  472. [actionBringForward, ["B", "C", "D", "E", "F", "A"]],
  473. // noop
  474. [actionBringForward, ["B", "C", "D", "E", "F", "A"]],
  475. ],
  476. });
  477. assertZindex({
  478. elements: [
  479. { id: "A" },
  480. { id: "B", isSelected: true },
  481. { id: "C", groupIds: ["g2", "g1"] },
  482. { id: "D", groupIds: ["g2", "g1"] },
  483. { id: "E", groupIds: ["g1"] },
  484. { id: "F" },
  485. ],
  486. operations: [
  487. [actionBringForward, ["A", "C", "D", "E", "B", "F"]],
  488. [actionBringForward, ["A", "C", "D", "E", "F", "B"]],
  489. // noop
  490. [actionBringForward, ["A", "C", "D", "E", "F", "B"]],
  491. ],
  492. });
  493. assertZindex({
  494. elements: [
  495. { id: "A" },
  496. { id: "B", isSelected: true },
  497. { id: "C", groupIds: ["g1"] },
  498. { id: "D", groupIds: ["g2", "g1"] },
  499. { id: "E", groupIds: ["g2", "g1"] },
  500. { id: "F" },
  501. ],
  502. operations: [
  503. [actionBringForward, ["A", "C", "D", "E", "B", "F"]],
  504. [actionBringForward, ["A", "C", "D", "E", "F", "B"]],
  505. // noop
  506. [actionBringForward, ["A", "C", "D", "E", "F", "B"]],
  507. ],
  508. });
  509. // in-group siblings
  510. // -------------------------------------------------------------------------
  511. assertZindex({
  512. elements: [
  513. { id: "A" },
  514. { id: "B", groupIds: ["g2", "g1"], isSelected: true },
  515. { id: "C", groupIds: ["g2", "g1"] },
  516. { id: "D", groupIds: ["g1"] },
  517. ],
  518. appState: {
  519. editingGroupId: "g2",
  520. },
  521. operations: [
  522. [actionBringForward, ["A", "C", "B", "D"]],
  523. // noop (prevented)
  524. [actionBringForward, ["A", "C", "B", "D"]],
  525. ],
  526. });
  527. assertZindex({
  528. elements: [
  529. { id: "A", groupIds: ["g1"], isSelected: true },
  530. { id: "B", groupIds: ["g2", "g1"] },
  531. { id: "C", groupIds: ["g2", "g1"] },
  532. { id: "D" },
  533. ],
  534. appState: {
  535. editingGroupId: "g1",
  536. },
  537. operations: [
  538. [actionBringForward, ["B", "C", "A", "D"]],
  539. // noop (prevented)
  540. [actionBringForward, ["B", "C", "A", "D"]],
  541. ],
  542. });
  543. assertZindex({
  544. elements: [
  545. { id: "A", groupIds: ["g2", "g1"], isSelected: true },
  546. { id: "B", groupIds: ["g2", "g1"], isSelected: true },
  547. { id: "C", groupIds: ["g1"] },
  548. { id: "D" },
  549. ],
  550. appState: {
  551. editingGroupId: "g1",
  552. },
  553. operations: [
  554. [actionBringForward, ["C", "A", "B", "D"]],
  555. // noop (prevented)
  556. [actionBringForward, ["C", "A", "B", "D"]],
  557. ],
  558. });
  559. // invalid z-indexes across groups (legacy) → allow to sort to next sibling
  560. assertZindex({
  561. elements: [
  562. { id: "A", groupIds: ["g2"], isSelected: true },
  563. { id: "B", groupIds: ["g2"], isSelected: true },
  564. { id: "C", groupIds: ["g1"] },
  565. { id: "D", groupIds: ["g2"] },
  566. { id: "E", groupIds: ["g1"] },
  567. ],
  568. appState: {
  569. editingGroupId: "g2",
  570. },
  571. operations: [
  572. [actionBringForward, ["C", "D", "A", "B", "E"]],
  573. // noop
  574. [actionBringForward, ["C", "D", "A", "B", "E"]],
  575. ],
  576. });
  577. // invalid z-indexes across groups (legacy) → allow to sort to next sibling
  578. assertZindex({
  579. elements: [
  580. { id: "A", groupIds: ["g2"], isSelected: true },
  581. { id: "B" },
  582. { id: "C", groupIds: ["g2"], isSelected: true },
  583. { id: "D", groupIds: ["g1"] },
  584. { id: "E", groupIds: ["g2"] },
  585. { id: "F", groupIds: ["g1"] },
  586. ],
  587. appState: {
  588. editingGroupId: "g2",
  589. },
  590. operations: [
  591. [actionBringForward, ["B", "D", "E", "A", "C", "F"]],
  592. // noop
  593. [actionBringForward, ["B", "D", "E", "A", "C", "F"]],
  594. ],
  595. });
  596. });
  597. it("bring to front", () => {
  598. assertZindex({
  599. elements: [
  600. { id: "0" },
  601. { id: "A", isSelected: true },
  602. { id: "B", isDeleted: true },
  603. { id: "C", isDeleted: true },
  604. { id: "D" },
  605. { id: "E", isSelected: true },
  606. { id: "F", isDeleted: true },
  607. { id: "G" },
  608. ],
  609. operations: [
  610. [actionBringToFront, ["0", "B", "C", "D", "F", "G", "A", "E"]],
  611. // noop
  612. [actionBringToFront, ["0", "B", "C", "D", "F", "G", "A", "E"]],
  613. ],
  614. });
  615. assertZindex({
  616. elements: [
  617. { id: "A", isSelected: true },
  618. { id: "B", isSelected: true },
  619. { id: "C", isSelected: true },
  620. ],
  621. operations: [
  622. // noop
  623. [actionBringToFront, ["A", "B", "C"]],
  624. ],
  625. });
  626. assertZindex({
  627. elements: [
  628. { id: "A" },
  629. { id: "B", isSelected: true },
  630. { id: "C", isSelected: true },
  631. ],
  632. operations: [
  633. // noop
  634. [actionBringToFront, ["A", "B", "C"]],
  635. ],
  636. });
  637. assertZindex({
  638. elements: [
  639. { id: "A", isSelected: true },
  640. { id: "B", isSelected: true },
  641. { id: "C" },
  642. ],
  643. operations: [
  644. [actionBringToFront, ["C", "A", "B"]],
  645. // noop
  646. [actionBringToFront, ["C", "A", "B"]],
  647. ],
  648. });
  649. // in-group sorting
  650. // -------------------------------------------------------------------------
  651. assertZindex({
  652. elements: [
  653. { id: "A" },
  654. { id: "B", groupIds: ["g1"] },
  655. { id: "C", groupIds: ["g1"], isSelected: true },
  656. { id: "D", groupIds: ["g1"] },
  657. { id: "E", groupIds: ["g1"], isSelected: true },
  658. { id: "F", groupIds: ["g2", "g1"] },
  659. { id: "G", groupIds: ["g2", "g1"] },
  660. { id: "H", groupIds: ["g3", "g1"] },
  661. { id: "I", groupIds: ["g3", "g1"] },
  662. ],
  663. appState: {
  664. editingGroupId: "g1",
  665. },
  666. operations: [
  667. [actionBringToFront, ["A", "B", "D", "F", "G", "H", "I", "C", "E"]],
  668. // noop (prevented)
  669. [actionBringToFront, ["A", "B", "D", "F", "G", "H", "I", "C", "E"]],
  670. ],
  671. });
  672. assertZindex({
  673. elements: [
  674. { id: "A" },
  675. { id: "B", groupIds: ["g2", "g1"], isSelected: true },
  676. { id: "D", groupIds: ["g2", "g1"] },
  677. { id: "C", groupIds: ["g1"] },
  678. ],
  679. appState: {
  680. editingGroupId: "g2",
  681. },
  682. operations: [
  683. [actionBringToFront, ["A", "D", "B", "C"]],
  684. // noop (prevented)
  685. [actionBringToFront, ["A", "D", "B", "C"]],
  686. ],
  687. });
  688. // invalid z-indexes across groups (legacy) → allow to sort to next sibling
  689. assertZindex({
  690. elements: [
  691. { id: "A", groupIds: ["g2", "g3"], isSelected: true },
  692. { id: "B", groupIds: ["g1", "g3"] },
  693. { id: "C", groupIds: ["g2", "g3"] },
  694. { id: "D", groupIds: ["g1", "g3"] },
  695. ],
  696. appState: {
  697. editingGroupId: "g2",
  698. },
  699. operations: [
  700. [actionBringToFront, ["B", "C", "A", "D"]],
  701. // noop
  702. [actionBringToFront, ["B", "C", "A", "D"]],
  703. ],
  704. });
  705. // invalid z-indexes across groups (legacy) → allow to sort to next sibling
  706. assertZindex({
  707. elements: [
  708. { id: "A", groupIds: ["g2"], isSelected: true },
  709. { id: "B", groupIds: ["g1"] },
  710. { id: "C", groupIds: ["g2"] },
  711. { id: "D", groupIds: ["g1"] },
  712. ],
  713. appState: {
  714. editingGroupId: "g2",
  715. },
  716. operations: [
  717. [actionBringToFront, ["B", "C", "A", "D"]],
  718. // noop
  719. [actionBringToFront, ["B", "C", "A", "D"]],
  720. ],
  721. });
  722. });
  723. it("send to back", () => {
  724. assertZindex({
  725. elements: [
  726. { id: "A" },
  727. { id: "B", isDeleted: true },
  728. { id: "C" },
  729. { id: "D", isDeleted: true },
  730. { id: "E", isSelected: true },
  731. { id: "F", isDeleted: true },
  732. { id: "G" },
  733. { id: "H", isSelected: true },
  734. { id: "I" },
  735. ],
  736. operations: [
  737. [actionSendToBack, ["E", "H", "A", "B", "C", "D", "F", "G", "I"]],
  738. // noop
  739. [actionSendToBack, ["E", "H", "A", "B", "C", "D", "F", "G", "I"]],
  740. ],
  741. });
  742. assertZindex({
  743. elements: [
  744. { id: "A", isSelected: true },
  745. { id: "B", isSelected: true },
  746. { id: "C", isSelected: true },
  747. ],
  748. operations: [
  749. // noop
  750. [actionSendToBack, ["A", "B", "C"]],
  751. ],
  752. });
  753. assertZindex({
  754. elements: [
  755. { id: "A", isSelected: true },
  756. { id: "B", isSelected: true },
  757. { id: "C" },
  758. ],
  759. operations: [
  760. // noop
  761. [actionSendToBack, ["A", "B", "C"]],
  762. ],
  763. });
  764. assertZindex({
  765. elements: [
  766. { id: "A" },
  767. { id: "B", isSelected: true },
  768. { id: "C", isSelected: true },
  769. ],
  770. operations: [
  771. [actionSendToBack, ["B", "C", "A"]],
  772. // noop
  773. [actionSendToBack, ["B", "C", "A"]],
  774. ],
  775. });
  776. // in-group sorting
  777. // -------------------------------------------------------------------------
  778. assertZindex({
  779. elements: [
  780. { id: "A" },
  781. { id: "B", groupIds: ["g2", "g1"] },
  782. { id: "C", groupIds: ["g2", "g1"] },
  783. { id: "D", groupIds: ["g3", "g1"] },
  784. { id: "E", groupIds: ["g3", "g1"] },
  785. { id: "F", groupIds: ["g1"], isSelected: true },
  786. { id: "G", groupIds: ["g1"] },
  787. { id: "H", groupIds: ["g1"], isSelected: true },
  788. { id: "I", groupIds: ["g1"] },
  789. ],
  790. appState: {
  791. editingGroupId: "g1",
  792. },
  793. operations: [
  794. [actionSendToBack, ["A", "F", "H", "B", "C", "D", "E", "G", "I"]],
  795. // noop (prevented)
  796. [actionSendToBack, ["A", "F", "H", "B", "C", "D", "E", "G", "I"]],
  797. ],
  798. });
  799. assertZindex({
  800. elements: [
  801. { id: "A" },
  802. { id: "B", groupIds: ["g1"] },
  803. { id: "C", groupIds: ["g2", "g1"] },
  804. { id: "D", groupIds: ["g2", "g1"], isSelected: true },
  805. ],
  806. appState: {
  807. editingGroupId: "g2",
  808. },
  809. operations: [
  810. [actionSendToBack, ["A", "B", "D", "C"]],
  811. // noop (prevented)
  812. [actionSendToBack, ["A", "B", "D", "C"]],
  813. ],
  814. });
  815. // invalid z-indexes across groups (legacy) → allow to sort to next sibling
  816. assertZindex({
  817. elements: [
  818. { id: "A", groupIds: ["g1", "g3"] },
  819. { id: "B", groupIds: ["g2", "g3"] },
  820. { id: "C", groupIds: ["g1", "g3"] },
  821. { id: "D", groupIds: ["g2", "g3"], isSelected: true },
  822. ],
  823. appState: {
  824. editingGroupId: "g2",
  825. },
  826. operations: [
  827. [actionSendToBack, ["A", "D", "B", "C"]],
  828. // noop
  829. [actionSendToBack, ["A", "D", "B", "C"]],
  830. ],
  831. });
  832. // invalid z-indexes across groups (legacy) → allow to sort to next sibling
  833. assertZindex({
  834. elements: [
  835. { id: "A", groupIds: ["g1"] },
  836. { id: "B", groupIds: ["g2"] },
  837. { id: "C", groupIds: ["g1"] },
  838. { id: "D", groupIds: ["g2"], isSelected: true },
  839. ],
  840. appState: {
  841. editingGroupId: "g2",
  842. },
  843. operations: [
  844. [actionSendToBack, ["A", "D", "B", "C"]],
  845. // noop
  846. [actionSendToBack, ["A", "D", "B", "C"]],
  847. ],
  848. });
  849. });
  850. it("duplicating elements should retain zindex integrity", () => {
  851. populateElements([
  852. { id: "A", isSelected: true },
  853. { id: "B", isSelected: true },
  854. ]);
  855. API.executeAction(actionDuplicateSelection);
  856. expect(h.elements).toMatchObject([
  857. { id: "A" },
  858. { id: getCloneByOrigId("A").id },
  859. { id: "B" },
  860. { id: getCloneByOrigId("B").id },
  861. ]);
  862. populateElements([
  863. { id: "A", groupIds: ["g1"], isSelected: true },
  864. { id: "B", groupIds: ["g1"], isSelected: true },
  865. ]);
  866. API.executeAction(actionDuplicateSelection);
  867. expect(h.elements).toMatchObject([
  868. { id: "A" },
  869. { id: "B" },
  870. {
  871. id: getCloneByOrigId("A").id,
  872. groupIds: [expect.stringMatching(/.{3,}/)],
  873. },
  874. {
  875. id: getCloneByOrigId("B").id,
  876. groupIds: [expect.stringMatching(/.{3,}/)],
  877. },
  878. ]);
  879. populateElements([
  880. { id: "A", groupIds: ["g1"], isSelected: true },
  881. { id: "B", groupIds: ["g1"], isSelected: true },
  882. { id: "C" },
  883. ]);
  884. API.executeAction(actionDuplicateSelection);
  885. expect(h.elements).toMatchObject([
  886. { id: "A" },
  887. { id: "B" },
  888. {
  889. id: getCloneByOrigId("A").id,
  890. groupIds: [expect.stringMatching(/.{3,}/)],
  891. },
  892. {
  893. id: getCloneByOrigId("B").id,
  894. groupIds: [expect.stringMatching(/.{3,}/)],
  895. },
  896. { id: "C" },
  897. ]);
  898. populateElements([
  899. { id: "A", groupIds: ["g1"], isSelected: true },
  900. { id: "B", groupIds: ["g1"], isSelected: true },
  901. { id: "C", isSelected: true },
  902. ]);
  903. API.executeAction(actionDuplicateSelection);
  904. expect(h.elements.map((element) => element.id)).toEqual([
  905. "A",
  906. "B",
  907. getCloneByOrigId("A").id,
  908. getCloneByOrigId("B").id,
  909. "C",
  910. getCloneByOrigId("C").id,
  911. ]);
  912. populateElements([
  913. { id: "A", groupIds: ["g1"], isSelected: true },
  914. { id: "B", groupIds: ["g1"], isSelected: true },
  915. { id: "C", groupIds: ["g2"], isSelected: true },
  916. { id: "D", groupIds: ["g2"], isSelected: true },
  917. ]);
  918. API.executeAction(actionDuplicateSelection);
  919. expect(h.elements.map((element) => element.id)).toEqual([
  920. "A",
  921. "B",
  922. getCloneByOrigId("A").id,
  923. getCloneByOrigId("B").id,
  924. "C",
  925. "D",
  926. getCloneByOrigId("C").id,
  927. getCloneByOrigId("D").id,
  928. ]);
  929. populateElements(
  930. [
  931. { id: "A", groupIds: ["g1", "g2"], isSelected: true },
  932. { id: "B", groupIds: ["g1", "g2"], isSelected: true },
  933. { id: "C", groupIds: ["g2"], isSelected: true },
  934. ],
  935. {
  936. selectedGroupIds: { g1: true },
  937. },
  938. );
  939. API.executeAction(actionDuplicateSelection);
  940. expect(h.elements.map((element) => element.id)).toEqual([
  941. "A",
  942. "B",
  943. getCloneByOrigId("A").id,
  944. getCloneByOrigId("B").id,
  945. "C",
  946. getCloneByOrigId("C").id,
  947. ]);
  948. populateElements(
  949. [
  950. { id: "A", groupIds: ["g1", "g2"], isSelected: true },
  951. { id: "B", groupIds: ["g1", "g2"], isSelected: true },
  952. { id: "C", groupIds: ["g2"], isSelected: true },
  953. ],
  954. {
  955. selectedGroupIds: { g2: true },
  956. },
  957. );
  958. API.executeAction(actionDuplicateSelection);
  959. expect(h.elements.map((element) => element.id)).toEqual([
  960. "A",
  961. "B",
  962. "C",
  963. getCloneByOrigId("A").id,
  964. getCloneByOrigId("B").id,
  965. getCloneByOrigId("C").id,
  966. ]);
  967. populateElements(
  968. [
  969. { id: "A", groupIds: ["g1", "g2"], isSelected: true },
  970. { id: "B", groupIds: ["g1", "g2"], isSelected: true },
  971. { id: "C", groupIds: ["g2"], isSelected: true },
  972. { id: "D", groupIds: ["g3", "g4"], isSelected: true },
  973. { id: "E", groupIds: ["g3", "g4"], isSelected: true },
  974. { id: "F", groupIds: ["g4"], isSelected: true },
  975. ],
  976. {
  977. selectedGroupIds: { g2: true, g4: true },
  978. },
  979. );
  980. API.executeAction(actionDuplicateSelection);
  981. expect(h.elements.map((element) => element.id)).toEqual([
  982. "A",
  983. "B",
  984. "C",
  985. getCloneByOrigId("A").id,
  986. getCloneByOrigId("B").id,
  987. getCloneByOrigId("C").id,
  988. "D",
  989. "E",
  990. "F",
  991. getCloneByOrigId("D").id,
  992. getCloneByOrigId("E").id,
  993. getCloneByOrigId("F").id,
  994. ]);
  995. populateElements(
  996. [
  997. { id: "A", groupIds: ["g1", "g2"], isSelected: true },
  998. { id: "B", groupIds: ["g1", "g2"] },
  999. { id: "C", groupIds: ["g2"] },
  1000. ],
  1001. { editingGroupId: "g1" },
  1002. );
  1003. API.executeAction(actionDuplicateSelection);
  1004. expect(h.elements.map((element) => element.id)).toEqual([
  1005. "A",
  1006. getCloneByOrigId("A").id,
  1007. "B",
  1008. "C",
  1009. ]);
  1010. populateElements(
  1011. [
  1012. { id: "A", groupIds: ["g1", "g2"] },
  1013. { id: "B", groupIds: ["g1", "g2"], isSelected: true },
  1014. { id: "C", groupIds: ["g2"] },
  1015. ],
  1016. { editingGroupId: "g1" },
  1017. );
  1018. API.executeAction(actionDuplicateSelection);
  1019. expect(h.elements.map((element) => element.id)).toEqual([
  1020. "A",
  1021. "B",
  1022. getCloneByOrigId("B").id,
  1023. "C",
  1024. ]);
  1025. populateElements(
  1026. [
  1027. { id: "A", groupIds: ["g1", "g2"], isSelected: true },
  1028. { id: "B", groupIds: ["g1", "g2"], isSelected: true },
  1029. { id: "C", groupIds: ["g2"] },
  1030. ],
  1031. { editingGroupId: "g1" },
  1032. );
  1033. API.executeAction(actionDuplicateSelection);
  1034. expect(h.elements.map((element) => element.id)).toEqual([
  1035. "A",
  1036. getCloneByOrigId("A").id,
  1037. "B",
  1038. getCloneByOrigId("B").id,
  1039. "C",
  1040. ]);
  1041. });
  1042. it("duplicating incorrectly interleaved elements (group elements should be together) should still produce reasonable result", () => {
  1043. populateElements([
  1044. { id: "A", groupIds: ["g1"], isSelected: true },
  1045. { id: "B" },
  1046. { id: "C", groupIds: ["g1"], isSelected: true },
  1047. ]);
  1048. API.executeAction(actionDuplicateSelection);
  1049. expect(h.elements.map((element) => element.id)).toEqual([
  1050. "A",
  1051. "C",
  1052. getCloneByOrigId("A").id,
  1053. getCloneByOrigId("C").id,
  1054. "B",
  1055. ]);
  1056. });
  1057. it("group-selected duplication should includes deleted elements that weren't selected on account of being deleted", () => {
  1058. populateElements([
  1059. { id: "A", groupIds: ["g1"], isDeleted: true },
  1060. { id: "B", groupIds: ["g1"], isSelected: true },
  1061. { id: "C", groupIds: ["g1"], isSelected: true },
  1062. { id: "D" },
  1063. ]);
  1064. expect(h.state.selectedGroupIds).toEqual({ g1: true });
  1065. API.executeAction(actionDuplicateSelection);
  1066. expect(h.elements.map((element) => element.id)).toEqual([
  1067. "A",
  1068. "B",
  1069. "C",
  1070. getCloneByOrigId("A").id,
  1071. getCloneByOrigId("B").id,
  1072. getCloneByOrigId("C").id,
  1073. "D",
  1074. ]);
  1075. });
  1076. it("text-container binding should be atomic", () => {
  1077. assertZindex({
  1078. elements: [
  1079. { id: "A", isSelected: true },
  1080. { id: "B" },
  1081. { id: "C", containerId: "B" },
  1082. ],
  1083. operations: [
  1084. [actionBringForward, ["B", "C", "A"]],
  1085. [actionSendBackward, ["A", "B", "C"]],
  1086. ],
  1087. });
  1088. assertZindex({
  1089. elements: [
  1090. { id: "A" },
  1091. { id: "B", isSelected: true },
  1092. { id: "C", containerId: "B" },
  1093. ],
  1094. operations: [
  1095. [actionSendBackward, ["B", "C", "A"]],
  1096. [actionBringForward, ["A", "B", "C"]],
  1097. ],
  1098. });
  1099. assertZindex({
  1100. elements: [
  1101. { id: "A", isSelected: true, groupIds: ["g1"] },
  1102. { id: "B", groupIds: ["g1"] },
  1103. { id: "C", containerId: "B", groupIds: ["g1"] },
  1104. ],
  1105. appState: {
  1106. editingGroupId: "g1",
  1107. },
  1108. operations: [
  1109. [actionBringForward, ["B", "C", "A"]],
  1110. [actionSendBackward, ["A", "B", "C"]],
  1111. ],
  1112. });
  1113. assertZindex({
  1114. elements: [
  1115. { id: "A", groupIds: ["g1"] },
  1116. { id: "B", groupIds: ["g1"], isSelected: true },
  1117. { id: "C", containerId: "B", groupIds: ["g1"] },
  1118. ],
  1119. appState: {
  1120. editingGroupId: "g1",
  1121. },
  1122. operations: [
  1123. [actionSendBackward, ["B", "C", "A"]],
  1124. [actionBringForward, ["A", "B", "C"]],
  1125. ],
  1126. });
  1127. assertZindex({
  1128. elements: [
  1129. { id: "A", groupIds: ["g1"] },
  1130. { id: "B", isSelected: true, groupIds: ["g1"] },
  1131. { id: "C" },
  1132. { id: "D", containerId: "C" },
  1133. ],
  1134. appState: {
  1135. editingGroupId: "g1",
  1136. },
  1137. operations: [[actionBringForward, ["A", "B", "C", "D"]]],
  1138. });
  1139. });
  1140. });
  1141. describe("z-indexing with frames", () => {
  1142. beforeEach(async () => {
  1143. await render(<Excalidraw />);
  1144. });
  1145. // naming scheme:
  1146. // F# ... frame element
  1147. // F#_# ... frame child of F# (rectangle)
  1148. // R# ... unrelated element (rectangle)
  1149. it("moving whole frame by one (normalized)", () => {
  1150. // normalized frame order
  1151. assertZindex({
  1152. elements: [
  1153. { id: "F1_1", frameId: "F1" },
  1154. { id: "F1_2", frameId: "F1" },
  1155. { id: "F1", type: "frame", isSelected: true },
  1156. { id: "R1" },
  1157. { id: "R2" },
  1158. ],
  1159. operations: [
  1160. // +1
  1161. [actionBringForward, ["R1", "F1_1", "F1_2", "F1", "R2"]],
  1162. // +1
  1163. [actionBringForward, ["R1", "R2", "F1_1", "F1_2", "F1"]],
  1164. // noop
  1165. [actionBringForward, ["R1", "R2", "F1_1", "F1_2", "F1"]],
  1166. // -1
  1167. [actionSendBackward, ["R1", "F1_1", "F1_2", "F1", "R2"]],
  1168. // -1
  1169. [actionSendBackward, ["F1_1", "F1_2", "F1", "R1", "R2"]],
  1170. // noop
  1171. [actionSendBackward, ["F1_1", "F1_2", "F1", "R1", "R2"]],
  1172. ],
  1173. });
  1174. });
  1175. it("moving whole frame by one (DENORMALIZED)", () => {
  1176. // DENORMALIZED FRAME ORDER
  1177. assertZindex({
  1178. elements: [
  1179. { id: "F1_1", frameId: "F1" },
  1180. { id: "F1", type: "frame", isSelected: true },
  1181. { id: "F1_2", frameId: "F1" },
  1182. { id: "R1" },
  1183. { id: "R2" },
  1184. ],
  1185. operations: [
  1186. // +1
  1187. [actionBringForward, ["R1", "F1_1", "F1", "F1_2", "R2"]],
  1188. // +1
  1189. [actionBringForward, ["R1", "R2", "F1_1", "F1", "F1_2"]],
  1190. // noop
  1191. [actionBringForward, ["R1", "R2", "F1_1", "F1", "F1_2"]],
  1192. ],
  1193. });
  1194. // DENORMALIZED FRAME ORDER
  1195. assertZindex({
  1196. elements: [
  1197. { id: "F1_1", frameId: "F1" },
  1198. { id: "F1", type: "frame", isSelected: true },
  1199. { id: "R1" },
  1200. { id: "F1_2", frameId: "F1" },
  1201. { id: "R2" },
  1202. ],
  1203. operations: [
  1204. // +1
  1205. [actionBringForward, ["R1", "F1_1", "F1", "R2", "F1_2"]],
  1206. // +1
  1207. [actionBringForward, ["R1", "R2", "F1_1", "F1", "F1_2"]],
  1208. // noop
  1209. [actionBringForward, ["R1", "R2", "F1_1", "F1", "F1_2"]],
  1210. ],
  1211. });
  1212. // DENORMALIZED FRAME ORDER
  1213. assertZindex({
  1214. elements: [
  1215. { id: "F1_1", frameId: "F1" },
  1216. { id: "R1" },
  1217. { id: "F1", type: "frame", isSelected: true },
  1218. { id: "R2" },
  1219. { id: "F1_2", frameId: "F1" },
  1220. { id: "R3" },
  1221. ],
  1222. operations: [
  1223. // +1
  1224. [actionBringForward, ["R1", "F1_1", "R2", "F1", "R3", "F1_2"]],
  1225. // +1
  1226. // FIXME incorrect, should put F1_1 after R3
  1227. [actionBringForward, ["R1", "R2", "F1_1", "R3", "F1", "F1_2"]],
  1228. // +1
  1229. // FIXME should be noop from previous step after it's fixed
  1230. [actionBringForward, ["R1", "R2", "R3", "F1_1", "F1", "F1_2"]],
  1231. ],
  1232. });
  1233. // DENORMALIZED FRAME ORDER
  1234. assertZindex({
  1235. elements: [
  1236. { id: "F1_1", frameId: "F1" },
  1237. { id: "R1" },
  1238. { id: "F1", type: "frame", isSelected: true },
  1239. { id: "R2" },
  1240. { id: "F1_2", frameId: "F1" },
  1241. { id: "R3" },
  1242. ],
  1243. operations: [
  1244. // -1
  1245. [actionSendBackward, ["F1_1", "F1", "R1", "F1_2", "R2", "R3"]],
  1246. // -1
  1247. [actionSendBackward, ["F1_1", "F1", "F1_2", "R1", "R2", "R3"]],
  1248. ],
  1249. });
  1250. });
  1251. it("moving selected frame children by one (normalized)", () => {
  1252. // normalized frame order
  1253. assertZindex({
  1254. elements: [
  1255. { id: "F1_1", frameId: "F1", isSelected: true },
  1256. { id: "F1_2", frameId: "F1" },
  1257. { id: "F1", type: "frame" },
  1258. { id: "R1" },
  1259. ],
  1260. operations: [
  1261. // +1
  1262. [actionBringForward, ["F1_2", "F1_1", "F1", "R1"]],
  1263. // noop
  1264. [actionBringForward, ["F1_2", "F1_1", "F1", "R1"]],
  1265. ],
  1266. });
  1267. // normalized frame order, multiple frames
  1268. assertZindex({
  1269. elements: [
  1270. { id: "F1_1", frameId: "F1", isSelected: true },
  1271. { id: "F1_2", frameId: "F1" },
  1272. { id: "F1", type: "frame" },
  1273. { id: "R1" },
  1274. { id: "F2_1", frameId: "F2", isSelected: true },
  1275. { id: "F2_2", frameId: "F2" },
  1276. { id: "F2", type: "frame" },
  1277. { id: "R2" },
  1278. ],
  1279. operations: [
  1280. // +1
  1281. [
  1282. actionBringForward,
  1283. ["F1_2", "F1_1", "F1", "R1", "F2_2", "F2_1", "F2", "R2"],
  1284. ],
  1285. // noop
  1286. [
  1287. actionBringForward,
  1288. ["F1_2", "F1_1", "F1", "R1", "F2_2", "F2_1", "F2", "R2"],
  1289. ],
  1290. ],
  1291. });
  1292. });
  1293. it("moving selected frame children by one (DENORMALIZED)", () => {
  1294. // DENORMALIZED FRAME ORDER
  1295. assertZindex({
  1296. elements: [
  1297. { id: "F1_1", frameId: "F1", isSelected: true },
  1298. { id: "F1", type: "frame" },
  1299. { id: "F1_2", frameId: "F1" },
  1300. { id: "R1" },
  1301. ],
  1302. operations: [
  1303. // +1
  1304. // NOTE not sure what we wanna do here
  1305. [actionBringForward, ["F1", "F1_2", "F1_1", "R1"]],
  1306. // noop
  1307. [actionBringForward, ["F1", "F1_2", "F1_1", "R1"]],
  1308. // -1
  1309. [actionSendBackward, ["F1", "F1_1", "F1_2", "R1"]],
  1310. // noop
  1311. [actionSendBackward, ["F1", "F1_1", "F1_2", "R1"]],
  1312. ],
  1313. });
  1314. // DENORMALIZED FRAME ORDER
  1315. assertZindex({
  1316. elements: [
  1317. { id: "F1_1", frameId: "F1", isSelected: true },
  1318. { id: "R1" },
  1319. { id: "F1", type: "frame" },
  1320. { id: "F1_2", frameId: "F1" },
  1321. { id: "R2" },
  1322. ],
  1323. operations: [
  1324. // +1
  1325. // NOTE not sure what we wanna do here
  1326. [actionBringForward, ["R1", "F1", "F1_2", "F1_1", "R2"]],
  1327. // noop
  1328. [actionBringForward, ["R1", "F1", "F1_2", "F1_1", "R2"]],
  1329. // -1
  1330. [actionSendBackward, ["R1", "F1", "F1_1", "F1_2", "R2"]],
  1331. // noop
  1332. [actionSendBackward, ["R1", "F1", "F1_1", "F1_2", "R2"]],
  1333. ],
  1334. });
  1335. });
  1336. it("moving whole frame to front/end", () => {
  1337. // normalized frame order
  1338. assertZindex({
  1339. elements: [
  1340. { id: "F1_1", frameId: "F1" },
  1341. { id: "F1_2", frameId: "F1" },
  1342. { id: "F1", type: "frame", isSelected: true },
  1343. { id: "R1" },
  1344. { id: "R2" },
  1345. ],
  1346. operations: [
  1347. // +∞
  1348. [actionBringToFront, ["R1", "R2", "F1_1", "F1_2", "F1"]],
  1349. // noop
  1350. [actionBringToFront, ["R1", "R2", "F1_1", "F1_2", "F1"]],
  1351. // -∞
  1352. [actionSendToBack, ["F1_1", "F1_2", "F1", "R1", "R2"]],
  1353. // noop
  1354. [actionSendToBack, ["F1_1", "F1_2", "F1", "R1", "R2"]],
  1355. ],
  1356. });
  1357. // DENORMALIZED FRAME ORDER
  1358. assertZindex({
  1359. elements: [
  1360. { id: "F1_1", frameId: "F1" },
  1361. { id: "F1", type: "frame", isSelected: true },
  1362. { id: "F1_2", frameId: "F1" },
  1363. { id: "R1" },
  1364. { id: "R2" },
  1365. ],
  1366. operations: [
  1367. // +∞
  1368. [actionBringToFront, ["R1", "R2", "F1_1", "F1", "F1_2"]],
  1369. // noop
  1370. [actionBringToFront, ["R1", "R2", "F1_1", "F1", "F1_2"]],
  1371. // -∞
  1372. [actionSendToBack, ["F1_1", "F1", "F1_2", "R1", "R2"]],
  1373. // noop
  1374. [actionSendToBack, ["F1_1", "F1", "F1_2", "R1", "R2"]],
  1375. ],
  1376. });
  1377. // DENORMALIZED FRAME ORDER
  1378. assertZindex({
  1379. elements: [
  1380. { id: "F1_1", frameId: "F1" },
  1381. { id: "F1", type: "frame", isSelected: true },
  1382. { id: "R1" },
  1383. { id: "F1_2", frameId: "F1" },
  1384. { id: "R2" },
  1385. ],
  1386. operations: [
  1387. // +∞
  1388. [actionBringToFront, ["R1", "R2", "F1_1", "F1", "F1_2"]],
  1389. ],
  1390. });
  1391. // DENORMALIZED FRAME ORDER
  1392. assertZindex({
  1393. elements: [
  1394. { id: "F1_1", frameId: "F1" },
  1395. { id: "R1" },
  1396. { id: "F1", type: "frame", isSelected: true },
  1397. { id: "R2" },
  1398. { id: "F1_2", frameId: "F1" },
  1399. { id: "R3" },
  1400. ],
  1401. operations: [
  1402. // +1
  1403. [actionBringToFront, ["R1", "R2", "R3", "F1_1", "F1", "F1_2"]],
  1404. ],
  1405. });
  1406. });
  1407. });