@marimo-team/islands 0.25.3-dev191 → 0.25.3-dev193

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/dist/{ConnectedDataExplorerComponent-BBbOElk3.js → ConnectedDataExplorerComponent-BRuwLOIx.js} +9 -9
  2. package/dist/{ErrorBoundary--X0z83Yx.js → ErrorBoundary-BVu_xT-p.js} +2 -2
  3. package/dist/{abnfDiagram-VCTEODGH-DhMT6Uhd.js → abnfDiagram-VCTEODGH-LlJTOOU_.js} +2 -2
  4. package/dist/{architectureDiagram-5GKGNRK7-BVbEiJLq.js → architectureDiagram-5GKGNRK7-9PQtGLLZ.js} +1 -1
  5. package/dist/{arrays-R6JsImiT.js → arrays-DA6KkLfq.js} +6 -12
  6. package/dist/assets/__vite-browser-external-BCJsx8QS.js +1 -0
  7. package/dist/assets/{worker-C6e9ERyd.js → worker-DodjWT_Z.js} +2 -2
  8. package/dist/{capabilities-CI4xUPsp.js → capabilities-CvWU-ey_.js} +1 -1
  9. package/dist/{chat-ui-BguxTQEL.js → chat-ui-Bl2euMBD.js} +93 -93
  10. package/dist/{chunk-IMKFNOWR-By5hSbJJ.js → chunk-IMKFNOWR-DcXv-BD5.js} +1 -1
  11. package/dist/{chunk-SHT3W25Y-DFrEYG54.js → chunk-SHT3W25Y-VwnELN23.js} +1 -1
  12. package/dist/{chunk-SVP7TREG-DFXGc9ew.js → chunk-SVP7TREG-DgQpI5Zi.js} +1 -1
  13. package/dist/{chunk-TICWLB2K-DZdNIpoZ.js → chunk-TICWLB2K-CTg6VTJT.js} +1 -1
  14. package/dist/{classDiagram-ZZMXUADV-Cgj_eEQA.js → classDiagram-ZZMXUADV-UzmcFrnW.js} +2 -2
  15. package/dist/{classDiagram-v2-VYDZK3BY-DLAUrSqj.js → classDiagram-v2-VYDZK3BY-DhjiI6IQ.js} +2 -2
  16. package/dist/{code-visibility-uDyLo9_i.js → code-visibility-BTVimCUw.js} +38 -42
  17. package/dist/{common-DWKY45Md.js → common-fTgq7gO-.js} +849 -849
  18. package/dist/{cynefinDiagram-5FMLGOSQ-Cikf9AKa.js → cynefinDiagram-5FMLGOSQ-O4ShIbJX.js} +1 -1
  19. package/dist/{dagre-GXQ25YYZ-C5KEi5_l.js → dagre-GXQ25YYZ-dtHhhcPu.js} +1 -1
  20. package/dist/{data-grid-overlay-editor-CRnYWD1L.js → data-grid-overlay-editor-DAZZkLWg.js} +1 -1
  21. package/dist/{diagram-S7CK7UJ4-BaoBTQjR.js → diagram-S7CK7UJ4-ppPYVZ6W.js} +1 -1
  22. package/dist/{diagram-UQ7AKVKN-DDufBsUA.js → diagram-UQ7AKVKN-CMZDf--k.js} +1 -1
  23. package/dist/{diagram-VX7I27RA-CY6RALJ8.js → diagram-VX7I27RA-CFvJdx-C.js} +1 -1
  24. package/dist/{diagram-Z3DM3KII-Bc-LHsD6.js → diagram-Z3DM3KII-Dt6_uFbv.js} +1 -1
  25. package/dist/{dropdown-menu-drDjxJyV.js → dropdown-menu-CeaOsjWZ.js} +2 -2
  26. package/dist/{ebnfDiagram-PWID7BFC-Cz0J-tcC.js → ebnfDiagram-PWID7BFC-DpRFrKK-.js} +2 -2
  27. package/dist/{erDiagram-RLTQ6QDP-DTXTDgFu.js → erDiagram-RLTQ6QDP-CqAU4b0u.js} +1 -1
  28. package/dist/{error-entries-CZ1NQ6Kv.js → error-entries-CYGwr5-O.js} +5596 -6012
  29. package/dist/feedback-button-DzFFn-n4.js +374 -0
  30. package/dist/{flowDiagram-HODETNUW-RbwWf2zc.js → flowDiagram-HODETNUW-i8-Gw8HO.js} +2 -2
  31. package/dist/{formats-DbXnijtH.js → formats-BtoHHy42.js} +1 -1
  32. package/dist/{glide-data-editor-CvRiA49N.js → glide-data-editor-DFsqVbWR.js} +5 -5
  33. package/dist/{globals-BR4CmGOS.js → globals-Dr0MRANu.js} +2 -2
  34. package/dist/{html-to-image-D_4_hQ8U.js → html-to-image-Cy5UDpQJ.js} +56 -56
  35. package/dist/{infoDiagram-27XIBGKW-DlmzZjmI.js → infoDiagram-27XIBGKW-DpfOKdLL.js} +1 -1
  36. package/dist/{ishikawaDiagram-5VMMS53U-EN3TYP_q.js → ishikawaDiagram-5VMMS53U-Dg7mrMG7.js} +1 -1
  37. package/dist/{kanban-definition-UXKFOSKX-CK_-Ru7Q.js → kanban-definition-UXKFOSKX-QB6e3Jrj.js} +1 -1
  38. package/dist/main.js +5875 -5876
  39. package/dist/{mermaid-DU9ZRmkG.js → mermaid-CeWG4Egr.js} +48 -48
  40. package/dist/{mindmap-definition-YA3MSWOX-C-hqgUvF.js → mindmap-definition-YA3MSWOX-BTz3uTGe.js} +1 -1
  41. package/dist/{number-overlay-editor-DiYGbxNa.js → number-overlay-editor-DupOSYHx.js} +1 -1
  42. package/dist/{once-eG8Vmvjd.js → once-DWGO2mQ2.js} +1 -1
  43. package/dist/{pegDiagram-XKGWAZYB-BiMejYPB.js → pegDiagram-XKGWAZYB-3ZIEa3mB.js} +2 -2
  44. package/dist/{pieDiagram-E7YTZNPT-B1zCZ1eq.js → pieDiagram-E7YTZNPT-BJAlk046.js} +1 -1
  45. package/dist/{process-output-DxSCHBWp.js → process-output-q9MTAIMX.js} +2 -2
  46. package/dist/{railroadDiagram-O6MQD6OU-BLcfaASl.js → railroadDiagram-O6MQD6OU-Dn3bsSRa.js} +2 -2
  47. package/dist/{requirementDiagram-BXWQKSXE-DtBOf52-.js → requirementDiagram-BXWQKSXE-wcOmQQCL.js} +1 -1
  48. package/dist/{reveal-component-D3DsXPIm.js → reveal-component-Bg8rP_Uo.js} +17 -17
  49. package/dist/{sequenceDiagram-WJ2MYXX4-DIwF19_n.js → sequenceDiagram-WJ2MYXX4-DSh_qxoF.js} +1 -1
  50. package/dist/{spec-DZDnnlgT.js → spec-C7Fos-If.js} +1 -1
  51. package/dist/{stateDiagram-D77RDMKH-sWk1AdYL.js → stateDiagram-D77RDMKH-DH_H8NMH.js} +2 -2
  52. package/dist/{stateDiagram-v2-MP3YSRHH-CiNo-OI7.js → stateDiagram-v2-MP3YSRHH-D_JM7MEK.js} +2 -2
  53. package/dist/style.css +1 -1
  54. package/dist/{swimlanes-42K2YHIH-Bj3hiOY5.js → swimlanes-42K2YHIH-CfoTJO-9.js} +1 -1
  55. package/dist/{swimlanesDiagram-VR7AAH4N-BzEo2r_N.js → swimlanesDiagram-VR7AAH4N-Z_QhvK0G.js} +2 -2
  56. package/dist/{swiper-component-BkCm-kLg.js → swiper-component-D2geL1Ku.js} +1 -1
  57. package/dist/{timeline-definition-24CTP7MA-CrPQk42H.js → timeline-definition-24CTP7MA-BFtdScaL.js} +1 -1
  58. package/dist/{toDate-Cu2pIZcC.js → toDate-CQAFSwos.js} +24 -24
  59. package/dist/{useAsyncData-DEhLdRIu.js → useAsyncData-BeyZyjso.js} +1 -1
  60. package/dist/{useDeepCompareMemoize-RjZGVhBV.js → useDeepCompareMemoize-DOQgae0W.js} +1 -1
  61. package/dist/{useIframeCapabilities-CSdWn6NP.js → useIframeCapabilities-DNvaj9wM.js} +1 -1
  62. package/dist/{useLifecycle-DtcB2s1z.js → useLifecycle-n2lnCFS8.js} +1 -1
  63. package/dist/{useTheme-CoEtr4mK.js → useTheme-CY777LXN.js} +303 -99
  64. package/dist/{vega-component-BhjTrP9-.js → vega-component-0nQzA-I8.js} +6 -6
  65. package/dist/{vennDiagram-4TSXK5OY-BaYMoIpj.js → vennDiagram-4TSXK5OY-BXY8IGCA.js} +1 -1
  66. package/dist/{wardleyDiagram-VM6X3IG4-DjHeuj8p.js → wardleyDiagram-VM6X3IG4-D0tFe6eC.js} +1 -1
  67. package/dist/{xychartDiagram-S5SC5T6Z-Bc_hcuXS.js → xychartDiagram-S5SC5T6Z-Dx8wFM-y.js} +1 -1
  68. package/package.json +1 -1
  69. package/src/__mocks__/notebook.ts +0 -1
  70. package/src/__mocks__/partial.ts +26 -0
  71. package/src/__tests__/main.test.tsx +24 -1
  72. package/src/components/app-config/app-config-form.tsx +5 -9
  73. package/src/components/editor/__tests__/data-attributes.test.tsx +0 -1
  74. package/src/components/editor/__tests__/output-persistence.test.tsx +0 -1
  75. package/src/components/editor/actions/useCellActionButton.tsx +6 -5
  76. package/src/components/editor/actions/useConfigActions.tsx +3 -4
  77. package/src/components/editor/actions/useNotebookActions.tsx +5 -4
  78. package/src/components/editor/cell/code/cell-editor.tsx +13 -6
  79. package/src/components/editor/cell/collapse.tsx +6 -1
  80. package/src/components/editor/chrome/components/feedback-button.tsx +1 -7
  81. package/src/components/editor/controls/Controls.tsx +1 -1
  82. package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +8 -6
  83. package/src/components/editor/navigation/__tests__/navigation.test.ts +15 -15
  84. package/src/components/editor/navigation/__tests__/selection.test.ts +44 -21
  85. package/src/components/editor/navigation/focus-utils.ts +19 -0
  86. package/src/components/editor/navigation/multi-cell-action-toolbar.tsx +6 -13
  87. package/src/components/editor/navigation/navigation.ts +52 -43
  88. package/src/components/editor/navigation/selection.ts +30 -29
  89. package/src/components/editor/notebook-cell.tsx +10 -18
  90. package/src/components/editor/renderers/cell-array.tsx +6 -5
  91. package/src/components/editor/renderers/slides-layout/__tests__/plugin.test.ts +0 -1
  92. package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +1 -1
  93. package/src/components/pages/edit-page.tsx +0 -13
  94. package/src/components/scratchpad/scratchpad.tsx +0 -1
  95. package/src/components/slides/reveal-component.tsx +1 -1
  96. package/src/components/slides/slide-cell-view.tsx +0 -1
  97. package/src/components/sort/SortableCellsProvider.tsx +6 -5
  98. package/src/core/MarimoApp.tsx +1 -1
  99. package/src/core/cells/__tests__/__snapshots__/cells.test.ts.snap +0 -37
  100. package/src/core/cells/__tests__/actions.test.ts +190 -80
  101. package/src/core/cells/__tests__/arrange.test.ts +263 -0
  102. package/src/core/cells/__tests__/cells.test.ts +11 -1101
  103. package/src/core/cells/__tests__/document.test.ts +3 -32
  104. package/src/core/cells/__tests__/editor.test.ts +57 -0
  105. package/src/core/cells/__tests__/harness.ts +82 -0
  106. package/src/core/cells/__tests__/layout.test.ts +189 -0
  107. package/src/core/cells/__tests__/notebook-outline.test.ts +7 -1
  108. package/src/core/cells/__tests__/session.test.ts +329 -174
  109. package/src/core/cells/__tests__/utils.test.ts +0 -1
  110. package/src/core/cells/actions.ts +247 -82
  111. package/src/core/cells/arrange.ts +341 -0
  112. package/src/core/cells/cells.ts +27 -516
  113. package/src/core/cells/document.ts +6 -5
  114. package/src/core/cells/edit.ts +30 -0
  115. package/src/core/cells/editor.ts +82 -0
  116. package/src/core/cells/layout.ts +152 -0
  117. package/src/core/cells/session.ts +75 -52
  118. package/src/core/cells/types.ts +0 -5
  119. package/src/core/cells/utils.ts +6 -4
  120. package/src/core/config/__tests__/app-config.test.ts +64 -0
  121. package/src/core/config/__tests__/config-schema.test.ts +1 -1
  122. package/src/core/config/config-schema.ts +6 -2
  123. package/src/core/config/config.ts +26 -12
  124. package/src/core/islands/bootstrap.ts +0 -1
  125. package/src/core/kernel/handlers.ts +1 -13
  126. package/src/core/meta/__tests__/code-visibility.test.tsx +78 -70
  127. package/src/core/meta/code-visibility.ts +18 -9
  128. package/src/core/notebook/__tests__/operations.test.ts +51 -0
  129. package/src/core/notebook/document/__tests__/codemirror.test.ts +15 -0
  130. package/src/core/notebook/index.ts +2 -1
  131. package/src/core/notebook/operations.ts +30 -0
  132. package/src/core/saving/__tests__/state.test.ts +111 -0
  133. package/src/core/saving/save-component.tsx +19 -33
  134. package/src/core/saving/state.ts +38 -39
  135. package/src/core/saving/useAutoSave.ts +8 -35
  136. package/src/core/session/__tests__/backend.test.ts +1 -0
  137. package/src/core/session/__tests__/useNotebookReplica.hook.test.tsx +1 -0
  138. package/src/core/session/active-notebook.ts +11 -0
  139. package/src/core/session/backend.ts +17 -19
  140. package/src/core/session/embedded.ts +7 -0
  141. package/src/core/session/useNotebookReplica.ts +3 -1
  142. package/src/core/session/useNotebookSession.tsx +7 -8
  143. package/src/core/transports/__tests__/static.test.ts +139 -8
  144. package/src/core/transports/static.ts +98 -16
  145. package/src/core/wasm/__tests__/bridge.test.ts +1 -0
  146. package/src/mount.tsx +19 -10
  147. package/src/stories/cell.stories.tsx +0 -2
  148. package/src/stories/layout/vertical/one-column.stories.tsx +0 -2
  149. package/src/utils/__tests__/id-tree.test.ts +20 -1548
  150. package/src/utils/id-tree.tsx +12 -537
  151. package/dist/assets/__vite-browser-external-C8gQho07.js +0 -1
  152. package/dist/feedback-button-CDf1yRm_.js +0 -379
  153. package/src/components/editor/navigation/__tests__/state.test.ts +0 -81
  154. package/src/components/editor/navigation/state.ts +0 -65
@@ -25,240 +25,6 @@ describe("CollapsibleTree", () => {
25
25
  `);
26
26
  });
27
27
 
28
- it("collapses nodes correctly with no end", () => {
29
- const collapsedTree = tree.collapse("two", undefined);
30
- expect(collapsedTree.nodes[1].isCollapsed).toBe(true);
31
- expect(collapsedTree.isCollapsed("two")).toBe(true);
32
- expect(collapsedTree.toString()).toMatchInlineSnapshot(`
33
- "one
34
- two (collapsed)
35
- three
36
- four
37
- "
38
- `);
39
- });
40
-
41
- it("collapses nodes correctly with an 'until' in the middle", () => {
42
- const collapsedTree = tree.collapse("two", "three");
43
- expect(collapsedTree.nodes[1].isCollapsed).toBe(true);
44
- expect(collapsedTree.toString()).toMatchInlineSnapshot(`
45
- "one
46
- two (collapsed)
47
- three
48
- four
49
- "
50
- `);
51
- });
52
-
53
- it("collapses nodes correctly with an 'until' at the end", () => {
54
- const collapsedTree = tree.collapse("two", "four");
55
- expect(collapsedTree.nodes[1].isCollapsed).toBe(true);
56
- expect(collapsedTree.toString()).toMatchInlineSnapshot(`
57
- "one
58
- two (collapsed)
59
- three
60
- four
61
- "
62
- `);
63
- });
64
-
65
- it("collapses nodes correctly if just the last node", () => {
66
- const collapsedTree = tree.collapse("four", undefined);
67
- expect(collapsedTree.nodes[3].isCollapsed).toBe(true);
68
- expect(collapsedTree.toString()).toMatchInlineSnapshot(`
69
- "one
70
- two
71
- three
72
- four (collapsed)
73
- "
74
- `);
75
- });
76
-
77
- it("collapses nodes correctly if just the first node", () => {
78
- const collapsedTree = tree.collapse("one", undefined);
79
- expect(collapsedTree.nodes[0].isCollapsed).toBe(true);
80
- expect(collapsedTree.toString()).toMatchInlineSnapshot(`
81
- "one (collapsed)
82
- two
83
- three
84
- four
85
- "
86
- `);
87
- });
88
-
89
- it("can double collapse", () => {
90
- const collapsedTree = tree
91
- .collapse("three", undefined)
92
- .collapse("two", undefined);
93
- expect(collapsedTree.toString()).toMatchInlineSnapshot(`
94
- "one
95
- two (collapsed)
96
- three (collapsed)
97
- four
98
- "
99
- `);
100
- });
101
-
102
- it("failures to collapse", () => {
103
- expect(() =>
104
- tree.collapse("five", undefined),
105
- ).toThrowErrorMatchingInlineSnapshot(
106
- "[Error: Node five not found in tree. Valid ids: one,two,three,four]",
107
- );
108
- expect(() =>
109
- tree.collapse("one", "five"),
110
- ).toThrowErrorMatchingInlineSnapshot(
111
- "[Error: Node five not found in tree]",
112
- );
113
- expect(() =>
114
- tree.collapse("two", "one"),
115
- ).toThrowErrorMatchingInlineSnapshot(
116
- "[Error: Node one is before node two]",
117
- );
118
- expect(() => {
119
- tree = tree.collapse("two", undefined);
120
- tree = tree.collapse("two", undefined);
121
- }).toThrowErrorMatchingInlineSnapshot(
122
- "[Error: Node two is already collapsed]",
123
- );
124
- });
125
-
126
- it("collapses all nodes from leaves to root in one call", () => {
127
- const collapsedTree = tree.collapseAll([
128
- { id: "one", until: undefined },
129
- { id: "two", until: undefined },
130
- { id: "three", until: undefined },
131
- { id: "four", until: undefined },
132
- ]);
133
- expect(collapsedTree.toString()).toMatchInlineSnapshot(`
134
- "one (collapsed)
135
- two (collapsed)
136
- three (collapsed)
137
- four (collapsed)
138
- "
139
- `);
140
- });
141
-
142
- it("collapses some nodes from leaves to root in one call", () => {
143
- const collapsedTree = tree.collapseAll([
144
- { id: "one", until: undefined },
145
- null,
146
- { id: "three", until: "four" },
147
- null,
148
- ]);
149
- expect(collapsedTree.toString()).toMatchInlineSnapshot(`
150
- "one (collapsed)
151
- two
152
- three (collapsed)
153
- four
154
- "
155
- `);
156
- });
157
-
158
- it("failures to collapse all", () => {
159
- expect(() => tree.collapseAll([])).toThrowErrorMatchingInlineSnapshot(
160
- "[Error: No collapse ranges provided]",
161
- );
162
- expect(() =>
163
- tree.collapseAll([
164
- { id: "one", until: undefined },
165
- { id: "two", until: undefined },
166
- ]),
167
- ).toThrowErrorMatchingInlineSnapshot(
168
- "[Error: Collapse ranges length 2 does not match tree length 4]",
169
- );
170
- expect(() =>
171
- tree.collapseAll([null, { id: "one", until: undefined }, null, null]),
172
- ).toThrowErrorMatchingInlineSnapshot(
173
- "[Error: Node two does not match collapse range id one]",
174
- );
175
- expect(() =>
176
- tree.collapseAll([{ id: "one", until: "five" }, null, null, null]),
177
- ).toThrowErrorMatchingInlineSnapshot(
178
- "[Error: Node five not found in tree]",
179
- );
180
- expect(() =>
181
- tree.collapseAll([null, { id: "two", until: "one" }, null, null]),
182
- ).toThrowErrorMatchingInlineSnapshot(
183
- "[Error: Node one is before node two]",
184
- );
185
- });
186
-
187
- it("expands nodes correctly", () => {
188
- const collapsed = tree.collapse("two", undefined);
189
- expect(collapsed.nodes[1].isCollapsed).toBe(true);
190
- const expandedTree = collapsed.expand("two");
191
- expect(expandedTree.nodes[1].isCollapsed).toBe(false);
192
- expect(expandedTree.toString()).toMatchInlineSnapshot(`
193
- "one
194
- two
195
- three
196
- four
197
- "
198
- `);
199
- });
200
-
201
- it("expands all nested collapsed nodes correctly in one call", () => {
202
- let collapsed = tree.collapse("three", undefined);
203
- expect(collapsed.nodes[2].isCollapsed).toBe(true);
204
- collapsed = collapsed.collapse("two", undefined);
205
- expect(collapsed.nodes[1].isCollapsed).toBe(true);
206
- const expandedTree = collapsed.expandAll();
207
- expect(expandedTree.nodes[1].isCollapsed).toBe(false);
208
- expect(expandedTree.toString()).toMatchInlineSnapshot(`
209
- "one
210
- two
211
- three
212
- four
213
- "
214
- `);
215
- });
216
-
217
- it("fails to expand when node not found", () => {
218
- expect(() => tree.expand("five")).toThrowErrorMatchingInlineSnapshot(
219
- "[Error: Node five not found in tree. Valid ids: one,two,three,four]",
220
- );
221
- });
222
-
223
- it("expand on already expanded node is a no-op", () => {
224
- // Expanding an already expanded node should return the same tree (no-op)
225
- const result = tree.expand("one");
226
- expect(result).toBe(tree);
227
- // Can call multiple times without error
228
- const result2 = tree.expand("one");
229
- expect(result2).toBe(tree);
230
- });
231
-
232
- it("moves nodes correctly", () => {
233
- const movedTree = tree.move(0, 1);
234
- expect(movedTree.toString()).toMatchInlineSnapshot(`
235
- "two
236
- one
237
- three
238
- four
239
- "
240
- `);
241
- });
242
-
243
- it("moves a collapsed node correctly", () => {
244
- const collapsed = tree.collapse("two", "three");
245
- expect(collapsed.toString()).toMatchInlineSnapshot(`
246
- "one
247
- two (collapsed)
248
- three
249
- four
250
- "
251
- `);
252
- const movedTree = collapsed.move(1, 2);
253
- expect(movedTree.toString()).toMatchInlineSnapshot(`
254
- "one
255
- four
256
- two (collapsed)
257
- three
258
- "
259
- `);
260
- });
261
-
262
28
  it("inserts nodes correctly", () => {
263
29
  tree = tree.insert("newNode", 1);
264
30
  expect(tree.toString()).toMatchInlineSnapshot(`
@@ -294,93 +60,6 @@ describe("CollapsibleTree", () => {
294
60
  `);
295
61
  });
296
62
 
297
- it("counts children correctly", () => {
298
- expect(tree.getCount("who")).toBe(0);
299
- expect(tree.getCount("one")).toBe(0);
300
- // Collapse three and get count
301
- tree = tree.collapse("three", undefined);
302
- expect(tree.getCount("three")).toBe(1);
303
- // Collapse two and get count
304
- tree = tree.collapse("two", undefined);
305
- expect(tree.getCount("two")).toBe(2);
306
- });
307
-
308
- it("finds deep correctly", () => {
309
- expect(tree.find("three")).toEqual(["three"]);
310
- tree = tree.collapse("three", undefined).collapse("two", undefined);
311
- expect(tree.find("three")).toEqual(["two", "three"]);
312
- expect(tree.find("four")).toEqual(["two", "three", "four"]);
313
- expect(tree.toString()).toMatchInlineSnapshot(`
314
- "one
315
- two (collapsed)
316
- three (collapsed)
317
- four
318
- "
319
- `);
320
- });
321
-
322
- it("finds and expands correctly at a leaf", () => {
323
- tree = tree.collapse("three", undefined).collapse("two", undefined);
324
- expect(tree.toString()).toMatchInlineSnapshot(`
325
- "one
326
- two (collapsed)
327
- three (collapsed)
328
- four
329
- "
330
- `);
331
- const prevTree = tree;
332
- tree = tree.findAndExpandDeep("four");
333
- expect(tree.toString()).toMatchInlineSnapshot(`
334
- "one
335
- two
336
- three
337
- four
338
- "
339
- `);
340
- // doesn't mutate
341
- expect(prevTree.toString()).not.toEqual(tree.toString());
342
- });
343
-
344
- it("finds and expands correctly at a non-leaf", () => {
345
- tree = tree.collapse("three", undefined).collapse("two", undefined);
346
- expect(tree.toString()).toMatchInlineSnapshot(`
347
- "one
348
- two (collapsed)
349
- three (collapsed)
350
- four
351
- "
352
- `);
353
- const prevTree = tree;
354
- tree = tree.findAndExpandDeep("three");
355
- expect(tree.toString()).toMatchInlineSnapshot(`
356
- "one
357
- two
358
- three
359
- four
360
- "
361
- `);
362
- // doesn't mutate
363
- expect(prevTree.toString()).not.toEqual(tree.toString());
364
- });
365
-
366
- it("can delete nodes", () => {
367
- tree = tree.collapse("three", undefined).collapse("two", undefined);
368
- expect(tree.toString()).toMatchInlineSnapshot(`
369
- "one
370
- two (collapsed)
371
- three (collapsed)
372
- four
373
- "
374
- `);
375
- tree = tree.deleteAtIndex(1);
376
- expect(tree.toString()).toMatchInlineSnapshot(`
377
- "one
378
- three (collapsed)
379
- four
380
- "
381
- `);
382
- });
383
-
384
63
  it("can delete non-collapsed nodes without throwing", () => {
385
64
  // Deleting a non-collapsed node should not throw
386
65
  // (previously this would throw "Node is already expanded" internally)
@@ -399,50 +78,6 @@ describe("CollapsibleTree", () => {
399
78
  );
400
79
  });
401
80
 
402
- it("top-level ids are correct", () => {
403
- expect(tree.topLevelIds).toMatchInlineSnapshot(`
404
- [
405
- "one",
406
- "two",
407
- "three",
408
- "four",
409
- ]
410
- `);
411
-
412
- const collapsed = tree.collapse("two", undefined);
413
- expect(collapsed.topLevelIds).toMatchInlineSnapshot(`
414
- [
415
- "one",
416
- "two",
417
- ]
418
- `);
419
- });
420
-
421
- it("in order ids are correct", () => {
422
- expect(tree.inOrderIds).toMatchInlineSnapshot(`
423
- [
424
- "one",
425
- "two",
426
- "three",
427
- "four",
428
- ]
429
- `);
430
-
431
- const collapsed = tree.collapse("two", undefined);
432
-
433
- // Not mutable
434
- expect(tree.inOrderIds).toEqual(tree.inOrderIds);
435
-
436
- expect(collapsed.inOrderIds).toMatchInlineSnapshot(`
437
- [
438
- "one",
439
- "two",
440
- "three",
441
- "four",
442
- ]
443
- `);
444
- });
445
-
446
81
  it("atOrThrow throws if not found", () => {
447
82
  expect(() => tree.atOrThrow(5)).toThrowErrorMatchingInlineSnapshot(
448
83
  "[Error: Node at index 5 not found in tree]",
@@ -454,49 +89,6 @@ describe("CollapsibleTree", () => {
454
89
  expect(tree.last()).toBe("four");
455
90
  });
456
91
 
457
- it("length is correct", () => {
458
- expect(tree.length).toBe(4);
459
-
460
- const collapsed = tree.collapse("two", undefined);
461
- expect(collapsed.length).toBe(2);
462
- });
463
-
464
- it("moves to front and back correctly", () => {
465
- tree = tree.collapse("three", undefined).moveToBack("three");
466
- expect(tree.toString()).toMatchInlineSnapshot(`
467
- "one
468
- two
469
- three (collapsed)
470
- four
471
- "
472
- `);
473
-
474
- tree = tree.moveToFront("two");
475
- expect(tree.toString()).toMatchInlineSnapshot(`
476
- "two
477
- one
478
- three (collapsed)
479
- four
480
- "
481
- `);
482
- });
483
-
484
- it("getDescendants correctly", () => {
485
- expect(tree.getDescendants("two")).toEqual([]);
486
- expect(tree.getDescendants("who")).toEqual([]);
487
-
488
- tree = tree.collapse("three", undefined).collapse("two", undefined);
489
- expect(tree.getDescendants("two")).toMatchInlineSnapshot(`
490
- [
491
- "three",
492
- "four",
493
- ]
494
- `);
495
-
496
- // We only get descendants of the top level
497
- expect(tree.getDescendants("three")).toMatchInlineSnapshot("[]");
498
- });
499
-
500
92
  it("handles slice correctly", () => {
501
93
  // Normal slice in middle
502
94
  expect(tree.slice(1, 3)).toEqual(["two", "three"]);
@@ -526,14 +118,6 @@ describe("CollapsibleTree", () => {
526
118
  expect(tree.slice(1, 2)).toEqual(["two"]);
527
119
  });
528
120
 
529
- it("handles slice with collapsed nodes", () => {
530
- const collapsed = tree.collapse("two", "three");
531
-
532
- // Should only return top-level nodes, not descendants
533
- expect(collapsed.slice(0, 2)).toEqual(["one", "two"]);
534
- expect(collapsed.slice(1, 3)).toEqual(["two", "four"]);
535
- });
536
-
537
121
  it("handles after correctly", () => {
538
122
  // Normal case - get next node
539
123
  expect(tree.after("one")).toBe("two");
@@ -549,20 +133,6 @@ describe("CollapsibleTree", () => {
549
133
  );
550
134
  });
551
135
 
552
- it("handles after with collapsed nodes", () => {
553
- const collapsed = tree.collapse("two", "three");
554
-
555
- // Should return next top-level node
556
- expect(collapsed.after("one")).toBe("two");
557
- expect(collapsed.after("two")).toBe("four");
558
- expect(collapsed.after("four")).toBeUndefined();
559
-
560
- // Should throw for collapsed children that aren't top-level
561
- expect(() => collapsed.after("three")).toThrow(
562
- "Node three not found in tree. Valid ids: one,two,four",
563
- );
564
- });
565
-
566
136
  it("handles before correctly", () => {
567
137
  // Normal case - get previous node
568
138
  expect(tree.before("two")).toBe("one");
@@ -578,20 +148,6 @@ describe("CollapsibleTree", () => {
578
148
  );
579
149
  });
580
150
 
581
- it("handles before with collapsed nodes", () => {
582
- const collapsed = tree.collapse("two", "three");
583
-
584
- // Should return previous top-level node
585
- expect(collapsed.before("two")).toBe("one");
586
- expect(collapsed.before("four")).toBe("two");
587
- expect(collapsed.before("one")).toBeUndefined();
588
-
589
- // Should throw for collapsed children that aren't top-level
590
- expect(() => collapsed.before("three")).toThrow(
591
- "Node three not found in tree. Valid ids: one,two,four",
592
- );
593
- });
594
-
595
151
  it("handles slice on empty tree", () => {
596
152
  const emptyTree = CollapsibleTree.from([]);
597
153
  expect(emptyTree.slice(0, 1)).toEqual([]);
@@ -665,45 +221,6 @@ describe("CollapsibleTree edge cases", () => {
665
221
  expect(tree.topLevelIds).toEqual(["Z", "A", "B", "C", "D", "Y"]);
666
222
  });
667
223
 
668
- it("handles delete with expand", () => {
669
- tree = tree.collapse("B", "D");
670
- tree = tree.deleteAtIndex(1);
671
- expect(tree.topLevelIds).toEqual(["A", "C", "D"]);
672
- });
673
-
674
- it("finds nodes correctly", () => {
675
- tree = tree.collapse("B", "D");
676
- expect(tree.find("C")).toEqual(["B", "C"]);
677
- expect(tree.find("E")).toEqual([]);
678
- });
679
-
680
- it("handles equality check", () => {
681
- const tree2 = CollapsibleTree.from(["A", "B", "C", "D"]);
682
- expect(tree.equals(tree2)).toBe(true);
683
- tree2.collapse("B", "C");
684
- expect(tree.equals(tree2)).toBe(true);
685
- });
686
-
687
- it("handles getCount correctly", () => {
688
- tree = tree.collapse("B", "D");
689
- expect(tree.getCount("A")).toBe(0);
690
- expect(tree.getCount("B")).toBe(2);
691
- expect(tree.getCount("C")).toBe(0);
692
- });
693
-
694
- it("handles findAndExpandDeep", () => {
695
- tree = tree.collapse("B", "D");
696
- tree = tree.findAndExpandDeep("C");
697
- expect(tree.topLevelIds).toEqual(["A", "B", "C", "D"]);
698
- });
699
-
700
- it("handles multiple collapses and expands", () => {
701
- tree = tree.collapse("B", "C").collapse("A", "D");
702
- expect(tree.topLevelIds).toEqual(["A"]);
703
- tree = tree.expand("A").expand("B");
704
- expect(tree.topLevelIds).toEqual(["A", "B", "C", "D"]);
705
- });
706
-
707
224
  it("handles edge cases for first and last", () => {
708
225
  expect(tree.first()).toBe("A");
709
226
  expect(tree.last()).toBe("D");
@@ -711,21 +228,6 @@ describe("CollapsibleTree edge cases", () => {
711
228
  expect(() => tree.first()).toThrow("Node at index 0 not found in tree");
712
229
  expect(() => tree.last()).toThrow("Node at index -1 not found in tree");
713
230
  });
714
-
715
- it("handles inOrderIds with nested structure", () => {
716
- tree = tree.collapse("B", "C");
717
- expect(tree.inOrderIds).toEqual(["A", "B", "C", "D"]);
718
- });
719
-
720
- it("handles moving within bounds", () => {
721
- tree = tree.move(0, 2);
722
- expect(tree.topLevelIds).toEqual(["B", "C", "A", "D"]);
723
- });
724
-
725
- it("handles moving out of bounds", () => {
726
- tree = tree.move(0, 10);
727
- expect(tree.topLevelIds).toEqual(["B", "C", "D", "A"]);
728
- });
729
231
  });
730
232
 
731
233
  describe("MultiColumn", () => {
@@ -862,29 +364,6 @@ describe("MultiColumn", () => {
862
364
  ]);
863
365
  });
864
366
 
865
- it("moves within a column", () => {
866
- const moved = multiColumn.moveWithinColumn(
867
- columnIds[0],
868
- 0 as CellIndex,
869
- 2 as CellIndex,
870
- );
871
- expect(moved.topLevelIds[0]).toEqual(["A2", "A3", "A1"]);
872
- });
873
-
874
- it("moves across columns", () => {
875
- const moved = multiColumn.moveAcrossColumns(
876
- columnIds[0],
877
- "A1",
878
- columnIds[1],
879
- "B1",
880
- );
881
- expect(moved.topLevelIds).toEqual([
882
- ["A2", "A3"],
883
- ["A1", "B1", "B2"],
884
- ["C1", "C2", "C3", "C4"],
885
- ]);
886
- });
887
-
888
367
  it("moves a column", () => {
889
368
  const moved = multiColumn.moveColumn(columnIds[0], columnIds[2]);
890
369
  expect(moved.topLevelIds).toEqual([
@@ -921,17 +400,6 @@ describe("MultiColumn", () => {
921
400
  ]);
922
401
  });
923
402
 
924
- it("moves to a new column", () => {
925
- const newColumn = multiColumn.moveToNewColumn("B1");
926
- expect(newColumn.colLength).toBe(4);
927
- expect(newColumn.topLevelIds).toEqual([
928
- ["A1", "A2", "A3"],
929
- ["B2"],
930
- ["C1", "C2", "C3", "C4"],
931
- ["B1"],
932
- ]);
933
- });
934
-
935
403
  it("gets column with id", () => {
936
404
  const column = multiColumn.findWithId("B2");
937
405
  expect(multiColumn.indexOf(column)).toBe(1);
@@ -940,9 +408,9 @@ describe("MultiColumn", () => {
940
408
 
941
409
  it("transforms by id", () => {
942
410
  const transformed = multiColumn.transformWithCellId("B1", (tree) =>
943
- tree.moveToFront("B2"),
411
+ tree.delete("B1"),
944
412
  );
945
- expect(transformed.topLevelIds[1]).toEqual(["B2", "B1"]);
413
+ expect(transformed.topLevelIds[1]).toEqual(["B2"]);
946
414
  });
947
415
 
948
416
  it("transforms all columns", () => {
@@ -961,82 +429,6 @@ describe("MultiColumn", () => {
961
429
  expect(inserted.topLevelIds[1]).toEqual(["B1", "D1", "B2"]);
962
430
  });
963
431
 
964
- describe("moveCellsRelativeTo", () => {
965
- it("moves a single cell before target in same column", () => {
966
- const moved = multiColumn.moveCellsRelativeTo(["A3"], "A1", "before");
967
- expect(moved.topLevelIds).toEqual([
968
- ["A3", "A1", "A2"],
969
- ["B1", "B2"],
970
- ["C1", "C2", "C3", "C4"],
971
- ]);
972
- });
973
-
974
- it("moves a single cell after target in same column", () => {
975
- const moved = multiColumn.moveCellsRelativeTo(["A1"], "A3", "after");
976
- expect(moved.topLevelIds).toEqual([
977
- ["A2", "A3", "A1"],
978
- ["B1", "B2"],
979
- ["C1", "C2", "C3", "C4"],
980
- ]);
981
- });
982
-
983
- it("moves multiple cells before target", () => {
984
- const moved = multiColumn.moveCellsRelativeTo(
985
- ["A1", "A2"],
986
- "B2",
987
- "before",
988
- );
989
- expect(moved.topLevelIds).toEqual([
990
- ["A3"],
991
- ["B1", "A1", "A2", "B2"],
992
- ["C1", "C2", "C3", "C4"],
993
- ]);
994
- });
995
-
996
- it("moves multiple cells after target", () => {
997
- const moved = multiColumn.moveCellsRelativeTo(
998
- ["A2", "A3"],
999
- "B1",
1000
- "after",
1001
- );
1002
- expect(moved.topLevelIds).toEqual([
1003
- ["A1"],
1004
- ["B1", "A2", "A3", "B2"],
1005
- ["C1", "C2", "C3", "C4"],
1006
- ]);
1007
- });
1008
-
1009
- it("returns same MultiColumn when cellIds is empty", () => {
1010
- const moved = multiColumn.moveCellsRelativeTo([], "A1", "before");
1011
- expect(moved).toBe(multiColumn);
1012
- });
1013
-
1014
- it("inserts at end when target is among moved cells", () => {
1015
- const moved = multiColumn.moveCellsRelativeTo(
1016
- ["A1", "A2"],
1017
- "A2",
1018
- "after",
1019
- );
1020
- // Target A2 was removed; insert at end of column 0
1021
- expect(moved.topLevelIds).toEqual([
1022
- ["A3", "A1", "A2"],
1023
- ["B1", "B2"],
1024
- ["C1", "C2", "C3", "C4"],
1025
- ]);
1026
- });
1027
-
1028
- it("throws when node not found", () => {
1029
- expect(() =>
1030
- multiColumn.moveCellsRelativeTo(["Z1"], "A1", "before"),
1031
- ).toThrow("Cell Z1 not found in any column");
1032
- });
1033
- });
1034
-
1035
- it("deletes by id", () => {
1036
- const deleted = multiColumn.deleteById("B1");
1037
- expect(deleted.topLevelIds[1]).toEqual(["B2"]);
1038
- });
1039
-
1040
432
  it("compacts", () => {
1041
433
  const withEmpty = MultiColumn.from([["A1"], [], ["C1"], []]);
1042
434
  const compacted = withEmpty.compact();
@@ -1079,36 +471,6 @@ describe("MultiColumn", () => {
1079
471
  expect(() => multiColumn.atOrThrow(5)).toThrow("Column 5 not found");
1080
472
  });
1081
473
 
1082
- it("handles moving the last item in a column", () => {
1083
- const multiColumn = MultiColumn.from([["A1", "A2"], ["B1"]]);
1084
- const columnIds = multiColumn.getColumns().map((c) => c.id);
1085
- const moved = multiColumn.moveAcrossColumns(
1086
- columnIds[1],
1087
- "B1",
1088
- columnIds[0],
1089
- "A2",
1090
- );
1091
- expect(moved.topLevelIds).toEqual([["A1", "B1", "A2"], []]);
1092
- expect(moved.colLength).toBe(2);
1093
- });
1094
-
1095
- it("handles moving all items from a column", () => {
1096
- const multiColumn = MultiColumn.from([
1097
- ["A1", "A2"],
1098
- ["B1", "B2"],
1099
- ]);
1100
- const columnIds = multiColumn.getColumns().map((c) => c.id);
1101
- let moved = multiColumn.moveAcrossColumns(
1102
- columnIds[1],
1103
- "B1",
1104
- columnIds[0],
1105
- "A2",
1106
- );
1107
- moved = moved.moveAcrossColumns(columnIds[1], "B2", columnIds[0], "A2");
1108
- expect(moved.topLevelIds).toEqual([["A1", "B1", "B2", "A2"], []]);
1109
- expect(moved.colLength).toBe(2);
1110
- });
1111
-
1112
474
  it("handles inserting at out-of-bounds indices", () => {
1113
475
  const multiColumn = MultiColumn.from([["A1"], ["B1"]]);
1114
476
  const columnId = multiColumn.atOrThrow(1).id;
@@ -1116,24 +478,6 @@ describe("MultiColumn", () => {
1116
478
  expect(inserted.topLevelIds).toEqual([["A1"], ["B1", "C1"]]);
1117
479
  });
1118
480
 
1119
- it("handles collapsing and expanding in multi-column setup", () => {
1120
- let multiColumn = MultiColumn.from([
1121
- ["A1", "A2", "A3"],
1122
- ["B1", "B2"],
1123
- ]);
1124
- multiColumn = multiColumn.transformWithCellId("A1", (tree) =>
1125
- tree.collapse("A1", "A3"),
1126
- );
1127
- expect(multiColumn.topLevelIds).toEqual([["A1"], ["B1", "B2"]]);
1128
- multiColumn = multiColumn.transformWithCellId("A1", (tree) =>
1129
- tree.expand("A1"),
1130
- );
1131
- expect(multiColumn.topLevelIds).toEqual([
1132
- ["A1", "A2", "A3"],
1133
- ["B1", "B2"],
1134
- ]);
1135
- });
1136
-
1137
481
  it("handles moving a column to its own position", () => {
1138
482
  const multiColumn = MultiColumn.from([["A1"], ["B1"], ["C1"]]);
1139
483
  const moved = multiColumn.moveColumn(columnIds[1], columnIds[1]);
@@ -1155,39 +499,6 @@ describe("MultiColumn", () => {
1155
499
  expect(transformed).toBe(multiColumn);
1156
500
  });
1157
501
 
1158
- it("handles nested collapses and expands", () => {
1159
- let multiColumn = MultiColumn.from([["A1", "A2", "A3", "A4", "A5"]]);
1160
- multiColumn = multiColumn.transformWithCellId("A1", (tree) =>
1161
- tree.collapse("A2", "A4"),
1162
- );
1163
- multiColumn = multiColumn.transformWithCellId("A1", (tree) =>
1164
- tree.collapse("A1", undefined),
1165
- );
1166
- expect(multiColumn.topLevelIds).toEqual([["A1"]]);
1167
- multiColumn = multiColumn.transformWithCellId("A1", (tree) =>
1168
- tree.expand("A1"),
1169
- );
1170
- expect(multiColumn.topLevelIds).toEqual([["A1", "A2", "A5"]]);
1171
- multiColumn = multiColumn.transformWithCellId("A1", (tree) =>
1172
- tree.expand("A2"),
1173
- );
1174
- expect(multiColumn.topLevelIds).toEqual([["A1", "A2", "A3", "A4", "A5"]]);
1175
- });
1176
-
1177
- it("handles moving to a new column when it's the last item", () => {
1178
- let multiColumn = MultiColumn.from([["A1"], ["B1"]]);
1179
- multiColumn = multiColumn.moveToNewColumn("B1");
1180
- expect(multiColumn.colLength).toBe(3);
1181
- expect(multiColumn.topLevelIds).toEqual([["A1"], [], ["B1"]]);
1182
- });
1183
-
1184
- it("handles deleting the last item in a column", () => {
1185
- let multiColumn = MultiColumn.from([["A1"], ["B1"], ["C1"]]);
1186
- multiColumn = multiColumn.deleteById("B1");
1187
- expect(multiColumn.colLength).toBe(3);
1188
- expect(multiColumn.topLevelIds).toEqual([["A1"], [], ["C1"]]);
1189
- });
1190
-
1191
502
  it("handles multiple operations in sequence", () => {
1192
503
  let multiColumn = MultiColumn.from([
1193
504
  ["A1", "A2"],
@@ -1195,40 +506,31 @@ describe("MultiColumn", () => {
1195
506
  ["C1", "C2"],
1196
507
  ]);
1197
508
  const columnIds = multiColumn.getColumns().map((c) => c.id);
1198
- multiColumn = multiColumn.moveAcrossColumns(
1199
- columnIds[0],
1200
- "A2",
1201
- columnIds[1],
1202
- "B1",
1203
- );
1204
- expect(multiColumn.topLevelIds).toEqual([
1205
- ["A1"],
1206
- ["A2", "B1", "B2"],
1207
- ["C1", "C2"],
1208
- ]);
1209
509
  multiColumn = multiColumn.insertId("D1", columnIds[2], 1 as CellIndex);
1210
510
  expect(multiColumn.topLevelIds).toEqual([
1211
- ["A1"],
1212
- ["A2", "B1", "B2"],
511
+ ["A1", "A2"],
512
+ ["B1", "B2"],
1213
513
  ["C1", "D1", "C2"],
1214
514
  ]);
1215
- multiColumn = multiColumn.deleteById("B1");
515
+ multiColumn = multiColumn.transformWithCellId("B1", (tree) =>
516
+ tree.delete("B1"),
517
+ );
1216
518
  expect(multiColumn.topLevelIds).toEqual([
1217
- ["A1"],
1218
- ["A2", "B2"],
519
+ ["A1", "A2"],
520
+ ["B2"],
1219
521
  ["C1", "D1", "C2"],
1220
522
  ]);
1221
523
  multiColumn = multiColumn.moveColumn(columnIds[0], columnIds[2]);
1222
524
  expect(multiColumn.topLevelIds).toEqual([
1223
- ["A2", "B2"],
525
+ ["B2"],
1224
526
  ["C1", "D1", "C2"],
1225
- ["A1"],
527
+ ["A1", "A2"],
1226
528
  ]);
1227
529
  multiColumn = multiColumn.compact();
1228
530
  expect(multiColumn.topLevelIds).toEqual([
1229
- ["A2", "B2"],
531
+ ["B2"],
1230
532
  ["C1", "D1", "C2"],
1231
- ["A1"],
533
+ ["A1", "A2"],
1232
534
  ]);
1233
535
  });
1234
536
 
@@ -1238,7 +540,10 @@ describe("MultiColumn", () => {
1238
540
  expect(multiColumn.setupCellExists()).toBe(false);
1239
541
  const multiColumn2 = multiColumn.insertId(SETUP_CELL_ID, columnIds[0], 0);
1240
542
  expect(multiColumn2.setupCellExists()).toBe(true);
1241
- const multiColumn3 = multiColumn2.deleteById(SETUP_CELL_ID);
543
+ const multiColumn3 = multiColumn2.transformWithCellId(
544
+ SETUP_CELL_ID,
545
+ (tree) => tree.delete(SETUP_CELL_ID),
546
+ );
1242
547
  expect(multiColumn3.setupCellExists()).toBe(false);
1243
548
  });
1244
549
 
@@ -1264,15 +569,15 @@ describe("MultiColumn", () => {
1264
569
 
1265
570
  it("handles transform method", () => {
1266
571
  const transformed = multiColumn.transform(columnIds[1], (tree) =>
1267
- tree.moveToFront("B2"),
572
+ tree.delete("B1"),
1268
573
  );
1269
- expect(transformed.topLevelIds[1]).toEqual(["B2", "B1"]);
574
+ expect(transformed.topLevelIds[1]).toEqual(["B2"]);
1270
575
  });
1271
576
 
1272
577
  it("handles transform method with non-existent column id", () => {
1273
578
  const transformed = multiColumn.transform(
1274
579
  "non-existent-id" as CellColumnId,
1275
- (tree) => tree.moveToFront("B2"),
580
+ (tree) => tree.delete("B2"),
1276
581
  );
1277
582
  const columns1 = multiColumn.getColumns();
1278
583
  const columns2 = transformed.getColumns();
@@ -1281,37 +586,6 @@ describe("MultiColumn", () => {
1281
586
  expect(transformed).toBe(multiColumn);
1282
587
  });
1283
588
 
1284
- it("handles moving across columns with undefined toId", () => {
1285
- const moved = multiColumn.moveAcrossColumns(
1286
- columnIds[0],
1287
- "A1",
1288
- columnIds[1],
1289
- undefined,
1290
- );
1291
- expect(moved.topLevelIds).toEqual([
1292
- ["A2", "A3"],
1293
- ["A1", "B1", "B2"],
1294
- ["C1", "C2", "C3", "C4"],
1295
- ]);
1296
- });
1297
-
1298
- it("handles moving across columns to an empty column", () => {
1299
- const emptyColumnMultiColumn = multiColumn
1300
- .deleteById("B1")
1301
- .deleteById("B2");
1302
- const moved = emptyColumnMultiColumn.moveAcrossColumns(
1303
- columnIds[0],
1304
- "A1",
1305
- columnIds[1],
1306
- undefined,
1307
- );
1308
- expect(moved.topLevelIds).toEqual([
1309
- ["A2", "A3"],
1310
- ["A1"],
1311
- ["C1", "C2", "C3", "C4"],
1312
- ]);
1313
- });
1314
-
1315
589
  it("handles inserting a breakpoint at the start of a column", () => {
1316
590
  const withBreakpoint = multiColumn.insertBreakpoint("A1");
1317
591
  expect(withBreakpoint.colLength).toBe(4);
@@ -1341,805 +615,3 @@ describe("MultiColumn", () => {
1341
615
  expect(withBreakpoint.topLevelIds).toEqual([["A1"], [], ["B1"], ["C1"]]);
1342
616
  });
1343
617
  });
1344
-
1345
- describe("MultiColumn.fromWithPreviousShape", () => {
1346
- it("creates a new MultiColumn with a single column when no previous shape is provided", () => {
1347
- const ids = ["A1", "A2", "A3"];
1348
- const multiColumn = MultiColumn.fromWithPreviousShape(
1349
- ids,
1350
- MultiColumn.from([]),
1351
- );
1352
-
1353
- expect(multiColumn.colLength).toBe(1);
1354
- expect(multiColumn.topLevelIds).toEqual([ids]);
1355
- expect(multiColumn.inOrderIds).toEqual(ids);
1356
- });
1357
-
1358
- it("preserves column structure when ids exist in previous columns", () => {
1359
- const previousMultiColumn = MultiColumn.from([
1360
- ["A1", "A2"],
1361
- ["B1", "B2"],
1362
- ["C1", "C2"],
1363
- ]);
1364
-
1365
- // Subset of ids from different columns
1366
- const ids = ["A1", "B2", "C1"];
1367
- const multiColumn = MultiColumn.fromWithPreviousShape(
1368
- ids,
1369
- previousMultiColumn,
1370
- );
1371
-
1372
- expect(multiColumn.colLength).toBe(3);
1373
- expect(multiColumn.topLevelIds).toEqual([["A1"], ["B2"], ["C1"]]);
1374
- });
1375
-
1376
- it("preserves column structure with different ordering", () => {
1377
- const previousMultiColumn = MultiColumn.from([
1378
- ["A1", "A2", "A3"],
1379
- ["B1", "B2", "B3"],
1380
- ]);
1381
-
1382
- // Same ids but different order
1383
- const ids = ["A3", "B1", "A1", "B3"];
1384
- const multiColumn = MultiColumn.fromWithPreviousShape(
1385
- ids,
1386
- previousMultiColumn,
1387
- );
1388
-
1389
- expect(multiColumn.colLength).toBe(2);
1390
- expect(multiColumn.topLevelIds).toEqual([
1391
- ["A3", "A1"],
1392
- ["B1", "B3"],
1393
- ]);
1394
- });
1395
-
1396
- it("handles ids not present in previous shape", () => {
1397
- const previousMultiColumn = MultiColumn.from([
1398
- ["A1", "A2"],
1399
- ["B1", "B2"],
1400
- ]);
1401
-
1402
- // Include new ids not in previous shape
1403
- const ids = ["A1", "B1", "C1", "D1"];
1404
- const multiColumn = MultiColumn.fromWithPreviousShape(
1405
- ids,
1406
- previousMultiColumn,
1407
- );
1408
-
1409
- // New ids should be added to the last column
1410
- expect(multiColumn.colLength).toBe(2);
1411
- expect(multiColumn.topLevelIds).toEqual([["A1"], ["B1", "C1", "D1"]]);
1412
- expect(multiColumn.inOrderIds).toEqual(["A1", "B1", "C1", "D1"]);
1413
- });
1414
-
1415
- it("preserves collapsed state from previous shape", () => {
1416
- // Create a previous shape with collapsed nodes
1417
- let previousMultiColumn = MultiColumn.from([
1418
- ["A1", "A2", "A3"],
1419
- ["B1", "B2", "B3"],
1420
- ]);
1421
-
1422
- // Collapse A1 to include A2
1423
- const columnIds = previousMultiColumn.getColumnIds();
1424
- previousMultiColumn = previousMultiColumn.transform(columnIds[0], (tree) =>
1425
- tree.collapse("A1", "A2"),
1426
- );
1427
-
1428
- // Create new shape with same ids
1429
- const ids = ["A1", "A2", "A3", "B1", "B2", "B3"];
1430
- const multiColumn = MultiColumn.fromWithPreviousShape(
1431
- ids,
1432
- previousMultiColumn,
1433
- );
1434
-
1435
- // Check that A1 is still collapsed
1436
- const column = multiColumn.atOrThrow(0);
1437
- expect(column.isCollapsed("A1")).toBe(true);
1438
-
1439
- // Check that the structure is preserved
1440
- expect(multiColumn.colLength).toBe(2);
1441
- expect(multiColumn.topLevelIds).toEqual([
1442
- ["A1", "A3"],
1443
- ["B1", "B2", "B3"],
1444
- ]);
1445
- });
1446
-
1447
- it("handles empty columns in previous shape", () => {
1448
- // Create a previous shape with an empty column
1449
- const previousMultiColumn = MultiColumn.from([
1450
- ["A1", "A2"],
1451
- [],
1452
- ["C1", "C2"],
1453
- ]);
1454
-
1455
- const ids = ["A1", "C2"];
1456
- const multiColumn = MultiColumn.fromWithPreviousShape(
1457
- ids,
1458
- previousMultiColumn,
1459
- );
1460
-
1461
- // Should preserve the column structure
1462
- expect(multiColumn.colLength).toBe(3);
1463
- expect(multiColumn.topLevelIds).toEqual([["A1"], [], ["C2"]]);
1464
- });
1465
-
1466
- it("handles subset of ids with nested collapsed structure", () => {
1467
- // Create a previous shape with nested collapsed structure
1468
- let previousMultiColumn = MultiColumn.from([
1469
- ["A1", "A2", "A3", "A4", "A5"],
1470
- ["B1", "B2", "B3"],
1471
- ]);
1472
-
1473
- // Collapse A1 to include A2, A3
1474
- const columnIds = previousMultiColumn.getColumnIds();
1475
- previousMultiColumn = previousMultiColumn.transform(columnIds[0], (tree) =>
1476
- tree.collapse("A1", "A3"),
1477
- );
1478
-
1479
- // Create new shape with subset of ids
1480
- const ids = ["A1", "A2", "A3", "A4", "B1"];
1481
- const multiColumn = MultiColumn.fromWithPreviousShape(
1482
- ids,
1483
- previousMultiColumn,
1484
- );
1485
-
1486
- // Check that collapsed state is preserved
1487
- const column = multiColumn.atOrThrow(0);
1488
- expect(column.isCollapsed("A1")).toBe(true);
1489
-
1490
- // Check that the structure is preserved
1491
- expect(multiColumn.colLength).toBe(2);
1492
- expect(multiColumn.topLevelIds).toEqual([["A1", "A4"], ["B1"]]);
1493
- expect(multiColumn.inOrderIds).toEqual(["A1", "A2", "A3", "A4", "B1"]);
1494
- });
1495
-
1496
- it("handles reordering of columns", () => {
1497
- // Create a previous shape with specific column order
1498
- const previousMultiColumn = MultiColumn.from([
1499
- ["A1", "A2"],
1500
- ["B1", "B2"],
1501
- ["C1", "C2"],
1502
- ]);
1503
-
1504
- // Create new shape with ids in different order
1505
- const ids = ["C1", "A1", "B1"];
1506
- const multiColumn = MultiColumn.fromWithPreviousShape(
1507
- ids,
1508
- previousMultiColumn,
1509
- );
1510
-
1511
- // Column order should be preserved
1512
- expect(multiColumn.colLength).toBe(3);
1513
- expect(multiColumn.topLevelIds).toEqual([["A1"], ["B1"], ["C1"]]);
1514
- });
1515
-
1516
- it("handles completely different set of ids", () => {
1517
- const previousMultiColumn = MultiColumn.from([
1518
- ["A1", "A2"],
1519
- ["B1", "B2"],
1520
- ]);
1521
-
1522
- // Completely different set of ids
1523
- const ids = ["X1", "X2", "X3"];
1524
- const multiColumn = MultiColumn.fromWithPreviousShape(
1525
- ids,
1526
- previousMultiColumn,
1527
- );
1528
-
1529
- // New ids should be added to the last column
1530
- expect(multiColumn.colLength).toBe(2);
1531
- expect(multiColumn.topLevelIds).toEqual([["X1", "X2", "X3"], []]);
1532
- expect(multiColumn.inOrderIds).toEqual(["X1", "X2", "X3"]);
1533
- });
1534
-
1535
- it("preserves tree structure within columns", () => {
1536
- // Create a previous shape with complex tree structure
1537
- let previousMultiColumn = MultiColumn.from([
1538
- ["A1", "A2", "A3", "A4"],
1539
- ["B1", "B2", "B3"],
1540
- ]);
1541
-
1542
- // Create complex tree structure by collapsing nodes
1543
- const columnIds = previousMultiColumn.getColumnIds();
1544
- previousMultiColumn = previousMultiColumn.transform(columnIds[0], (tree) =>
1545
- tree.collapse("A1", "A2"),
1546
- );
1547
- previousMultiColumn = previousMultiColumn.transform(columnIds[1], (tree) =>
1548
- tree.collapse("B1", "B2"),
1549
- );
1550
-
1551
- // Create new shape with same ids
1552
- const ids = ["A1", "A2", "A3", "A4", "B1", "B2", "B3"];
1553
- const multiColumn = MultiColumn.fromWithPreviousShape(
1554
- ids,
1555
- previousMultiColumn,
1556
- );
1557
-
1558
- // Check that collapsed state is preserved in both columns
1559
- expect(multiColumn.atOrThrow(0).isCollapsed("A1")).toBe(true);
1560
- expect(multiColumn.atOrThrow(1).isCollapsed("B1")).toBe(true);
1561
-
1562
- // Check that the structure is preserved
1563
- expect(multiColumn.colLength).toBe(2);
1564
- expect(multiColumn.topLevelIds).toEqual([
1565
- ["A1", "A3", "A4"],
1566
- ["B1", "B3"],
1567
- ]);
1568
- });
1569
-
1570
- it("handles empty array of ids", () => {
1571
- const previousMultiColumn = MultiColumn.from([
1572
- ["A1", "A2"],
1573
- ["B1", "B2"],
1574
- ]);
1575
-
1576
- const multiColumn = MultiColumn.fromWithPreviousShape(
1577
- [],
1578
- previousMultiColumn,
1579
- );
1580
-
1581
- expect(multiColumn.colLength).toBe(2);
1582
- expect(multiColumn.topLevelIds).toEqual([[], []]);
1583
- expect(multiColumn.inOrderIds).toEqual([]);
1584
- });
1585
-
1586
- it("handles single id", () => {
1587
- const previousMultiColumn = MultiColumn.from([
1588
- ["A1", "A2"],
1589
- ["B1", "B2"],
1590
- ]);
1591
-
1592
- const multiColumn = MultiColumn.fromWithPreviousShape(
1593
- ["A1"],
1594
- previousMultiColumn,
1595
- );
1596
-
1597
- expect(multiColumn.colLength).toBe(2);
1598
- expect(multiColumn.topLevelIds).toEqual([["A1"], []]);
1599
- expect(multiColumn.inOrderIds).toEqual(["A1"]);
1600
- });
1601
-
1602
- it("preserves complex nested structure across multiple columns", () => {
1603
- // Create a complex structure with multiple columns and nested collapsed nodes
1604
- let previousMultiColumn = MultiColumn.from([
1605
- ["A1", "A2", "A3", "A4", "A5"],
1606
- ["B1", "B2", "B3", "B4"],
1607
- ["C1", "C2", "C3"],
1608
- ]);
1609
-
1610
- // Create complex nested structure
1611
- const columnIds = previousMultiColumn.getColumnIds();
1612
-
1613
- // Collapse in first column
1614
- previousMultiColumn = previousMultiColumn.transform(columnIds[0], (tree) =>
1615
- tree.collapse("A1", "A2"),
1616
- );
1617
- previousMultiColumn = previousMultiColumn.transform(columnIds[0], (tree) =>
1618
- tree.collapse("A3", "A4"),
1619
- );
1620
-
1621
- // Collapse in second column
1622
- previousMultiColumn = previousMultiColumn.transform(columnIds[1], (tree) =>
1623
- tree.collapse("B1", "B2"),
1624
- );
1625
-
1626
- // Collapse in third column
1627
- previousMultiColumn = previousMultiColumn.transform(columnIds[2], (tree) =>
1628
- tree.collapse("C1", "C2"),
1629
- );
1630
-
1631
- // Create new shape with all ids
1632
- const ids = [
1633
- "A1",
1634
- "A2",
1635
- "A3",
1636
- "A4",
1637
- "A5",
1638
- "B1",
1639
- "B2",
1640
- "B3",
1641
- "B4",
1642
- "C1",
1643
- "C2",
1644
- "C3",
1645
- ];
1646
-
1647
- const multiColumn = MultiColumn.fromWithPreviousShape(
1648
- ids,
1649
- previousMultiColumn,
1650
- );
1651
-
1652
- // Check that all collapsed states are preserved
1653
- expect(multiColumn.atOrThrow(0).isCollapsed("A1")).toBe(true);
1654
- expect(multiColumn.atOrThrow(0).isCollapsed("A3")).toBe(true);
1655
- expect(multiColumn.atOrThrow(1).isCollapsed("B1")).toBe(true);
1656
- expect(multiColumn.atOrThrow(2).isCollapsed("C1")).toBe(true);
1657
-
1658
- // Check that the structure is preserved
1659
- expect(multiColumn.colLength).toBe(3);
1660
- expect(multiColumn.topLevelIds).toEqual([
1661
- ["A1", "A3", "A5"],
1662
- ["B1", "B3", "B4"],
1663
- ["C1", "C3"],
1664
- ]);
1665
- });
1666
-
1667
- it("handles case where ids are distributed differently than in previous shape", () => {
1668
- const previousMultiColumn = MultiColumn.from([
1669
- ["A1", "A2"],
1670
- ["B1", "B2"],
1671
- ["C1", "C2"],
1672
- ]);
1673
-
1674
- // Create new shape with ids from different columns grouped together
1675
- const ids = ["A1", "B1", "C1", "A2", "B2", "C2"];
1676
- const multiColumn = MultiColumn.fromWithPreviousShape(
1677
- ids,
1678
- previousMultiColumn,
1679
- );
1680
-
1681
- // Should preserve the original column structure
1682
- expect(multiColumn.colLength).toBe(3);
1683
- expect(multiColumn.topLevelIds).toEqual([
1684
- ["A1", "A2"],
1685
- ["B1", "B2"],
1686
- ["C1", "C2"],
1687
- ]);
1688
- });
1689
-
1690
- it("handles case where previous shape has collapsed nodes but new ids don't include children", () => {
1691
- // Create a previous shape with collapsed nodes
1692
- let previousMultiColumn = MultiColumn.from([
1693
- ["A1", "A2", "A3"],
1694
- ["B1", "B2", "B3"],
1695
- ]);
1696
-
1697
- // Collapse nodes in both columns
1698
- const columnIds = previousMultiColumn.getColumnIds();
1699
- previousMultiColumn = previousMultiColumn.transform(columnIds[0], (tree) =>
1700
- tree.collapse("A1", "A2"),
1701
- );
1702
- previousMultiColumn = previousMultiColumn.transform(columnIds[1], (tree) =>
1703
- tree.collapse("B1", "B2"),
1704
- );
1705
-
1706
- // Create new shape without the collapsed children
1707
- const ids = ["A1", "A3", "B1", "B3"];
1708
- const multiColumn = MultiColumn.fromWithPreviousShape(
1709
- ids,
1710
- previousMultiColumn,
1711
- );
1712
-
1713
- // Nodes should not be collapsed since children are missing
1714
- expect(multiColumn.atOrThrow(0).isCollapsed("A1")).toBe(false);
1715
- expect(multiColumn.atOrThrow(1).isCollapsed("B1")).toBe(false);
1716
-
1717
- // Check that the structure is preserved
1718
- expect(multiColumn.colLength).toBe(2);
1719
- expect(multiColumn.topLevelIds).toEqual([
1720
- ["A1", "A3"],
1721
- ["B1", "B3"],
1722
- ]);
1723
- });
1724
-
1725
- it("handles deeply nested collapsed structure", () => {
1726
- // Create a tree with deeply nested collapsed structure
1727
- let previousTree = CollapsibleTree.from([
1728
- "one",
1729
- "two",
1730
- "three",
1731
- "four",
1732
- "five",
1733
- "six",
1734
- "seven",
1735
- "eight",
1736
- ]);
1737
-
1738
- // Create nested collapses
1739
- previousTree = previousTree.collapse("one", "two");
1740
-
1741
- // Get the collapsed node and collapse its children
1742
- const oneNode = previousTree._nodeMap.get("one");
1743
- expect(oneNode).toBeDefined();
1744
-
1745
- // Create a new tree with the same structure
1746
- const tree = CollapsibleTree.fromWithPreviousShape(
1747
- ["one", "two", "three", "four", "five", "six", "seven", "eight"],
1748
- previousTree,
1749
- );
1750
-
1751
- // Check that collapsed state is preserved
1752
- expect(tree.isCollapsed("one")).toBe(true);
1753
- expect(tree.topLevelIds).toEqual([
1754
- "one",
1755
- "three",
1756
- "four",
1757
- "five",
1758
- "six",
1759
- "seven",
1760
- "eight",
1761
- ]);
1762
- });
1763
-
1764
- it("handles case where collapsed node is at the end", () => {
1765
- // Create a tree with collapsed node at the end
1766
- let previousTree = CollapsibleTree.from([
1767
- "one",
1768
- "two",
1769
- "three",
1770
- "four",
1771
- "five",
1772
- ]);
1773
- previousTree = previousTree.collapse("four", "five");
1774
-
1775
- // Create new tree with same ids
1776
- const tree = CollapsibleTree.fromWithPreviousShape(
1777
- ["one", "two", "three", "four", "five"],
1778
- previousTree,
1779
- );
1780
-
1781
- // Check that collapsed state is preserved
1782
- expect(tree.isCollapsed("four")).toBe(true);
1783
- expect(tree.topLevelIds).toEqual(["one", "two", "three", "four"]);
1784
- });
1785
-
1786
- it("handles case with multiple collapsed nodes in the same column", () => {
1787
- // Create a previous shape with multiple collapsed nodes in the same column
1788
- let previousMultiColumn = MultiColumn.from([
1789
- ["A1", "A2", "A3", "A4", "A5", "A6"],
1790
- ["B1", "B2"],
1791
- ]);
1792
-
1793
- // Collapse multiple nodes in the first column
1794
- const columnIds = previousMultiColumn.getColumnIds();
1795
- previousMultiColumn = previousMultiColumn.transform(columnIds[0], (tree) =>
1796
- tree.collapse("A1", "A2"),
1797
- );
1798
- previousMultiColumn = previousMultiColumn.transform(columnIds[0], (tree) =>
1799
- tree.collapse("A3", "A4"),
1800
- );
1801
- previousMultiColumn = previousMultiColumn.transform(columnIds[0], (tree) =>
1802
- tree.collapse("A5", "A6"),
1803
- );
1804
-
1805
- // Create new shape with same ids
1806
- const ids = ["A1", "A2", "A3", "A4", "A5", "A6", "B1", "B2"];
1807
- const multiColumn = MultiColumn.fromWithPreviousShape(
1808
- ids,
1809
- previousMultiColumn,
1810
- );
1811
-
1812
- // Check that all collapsed states are preserved
1813
- expect(multiColumn.atOrThrow(0).isCollapsed("A1")).toBe(true);
1814
- expect(multiColumn.atOrThrow(0).isCollapsed("A3")).toBe(true);
1815
- expect(multiColumn.atOrThrow(0).isCollapsed("A5")).toBe(true);
1816
-
1817
- // Check that the structure is preserved
1818
- expect(multiColumn.colLength).toBe(2);
1819
- expect(multiColumn.topLevelIds).toEqual([
1820
- ["A1", "A3", "A5"],
1821
- ["B1", "B2"],
1822
- ]);
1823
- });
1824
-
1825
- it("handles case where all columns are empty in previous shape", () => {
1826
- // Create a previous shape with all empty columns
1827
- const previousMultiColumn = MultiColumn.from([[], [], []]);
1828
-
1829
- // Create new shape with new ids
1830
- const ids = ["A1", "B1", "C1"];
1831
- const multiColumn = MultiColumn.fromWithPreviousShape(
1832
- ids,
1833
- previousMultiColumn,
1834
- );
1835
-
1836
- // New ids should be added to the last column
1837
- expect(multiColumn.colLength).toBe(3);
1838
- expect(multiColumn.topLevelIds).toEqual([["A1", "B1", "C1"], [], []]);
1839
- expect(multiColumn.inOrderIds).toEqual(["A1", "B1", "C1"]);
1840
- });
1841
-
1842
- it("handles case with very large number of ids", () => {
1843
- // Create a previous shape with a reasonable number of ids
1844
- const previousIds1 = Array.from({ length: 50 }, (_, i) => `A${i}`);
1845
- const previousIds2 = Array.from({ length: 50 }, (_, i) => `B${i}`);
1846
- const previousMultiColumn = MultiColumn.from([previousIds1, previousIds2]);
1847
-
1848
- // Create a large set of ids
1849
- const ids = [
1850
- ...Array.from({ length: 50 }, (_, i) => `A${i}`),
1851
- ...Array.from({ length: 50 }, (_, i) => `B${i}`),
1852
- ...Array.from({ length: 50 }, (_, i) => `C${i}`),
1853
- ];
1854
-
1855
- const multiColumn = MultiColumn.fromWithPreviousShape(
1856
- ids,
1857
- previousMultiColumn,
1858
- );
1859
-
1860
- // Should preserve the column structure for existing ids and add new ids to last column
1861
- expect(multiColumn.colLength).toBe(2);
1862
- expect(multiColumn.topLevelIds[0].length).toBe(50);
1863
- expect(multiColumn.topLevelIds[1].length).toBe(100); // 50 B ids + 50 C ids
1864
- expect(multiColumn.inOrderIds.length).toBe(150); // All ids should be included
1865
- });
1866
- });
1867
-
1868
- describe("CollapsibleTree.fromWithPreviousShape", () => {
1869
- it("creates a new tree when no previous tree is provided", () => {
1870
- const ids = ["one", "two", "three"];
1871
- const tree = CollapsibleTree.fromWithPreviousShape(ids);
1872
-
1873
- expect(tree.topLevelIds).toEqual(ids);
1874
- expect(tree.inOrderIds).toEqual(ids);
1875
- expect(tree.nodes.length).toBe(3);
1876
- expect(tree.nodes.every((node) => !node.isCollapsed)).toBe(true);
1877
- });
1878
-
1879
- it("preserves the tree ID when previous tree is provided", () => {
1880
- const previousTree = CollapsibleTree.from(["one", "two", "three"]);
1881
- const tree = CollapsibleTree.fromWithPreviousShape(
1882
- ["one", "two"],
1883
- previousTree,
1884
- );
1885
-
1886
- expect(tree.id).toBe(previousTree.id);
1887
- });
1888
-
1889
- it("preserves collapsed state from previous tree", () => {
1890
- // Create a tree with collapsed nodes
1891
- let previousTree = CollapsibleTree.from(["one", "two", "three", "four"]);
1892
- previousTree = previousTree.collapse("one", "two");
1893
-
1894
- // Create new tree with same ids
1895
- const tree = CollapsibleTree.fromWithPreviousShape(
1896
- ["one", "two", "three", "four"],
1897
- previousTree,
1898
- );
1899
-
1900
- // Check that one is still collapsed
1901
- expect(tree.isCollapsed("one")).toBe(true);
1902
- expect(tree.topLevelIds).toEqual(["one", "three", "four"]);
1903
- });
1904
-
1905
- it("handles subset of ids from previous tree", () => {
1906
- // Create a tree with collapsed nodes
1907
- let previousTree = CollapsibleTree.from([
1908
- "one",
1909
- "two",
1910
- "three",
1911
- "four",
1912
- "five",
1913
- ]);
1914
- previousTree = previousTree.collapse("one", "two");
1915
- previousTree = previousTree.collapse("three", "four");
1916
-
1917
- // Create new tree with subset of ids
1918
- const tree = CollapsibleTree.fromWithPreviousShape(
1919
- ["one", "two", "three"],
1920
- previousTree,
1921
- );
1922
-
1923
- // Check that collapsed state is preserved for existing nodes
1924
- expect(tree.isCollapsed("one")).toBe(true);
1925
- expect(tree.topLevelIds).toEqual(["one", "three"]);
1926
- });
1927
-
1928
- it("handles different ordering of ids", () => {
1929
- // Create a tree with collapsed nodes
1930
- let previousTree = CollapsibleTree.from(["one", "two", "three", "four"]);
1931
- previousTree = previousTree.collapse("one", "two");
1932
-
1933
- // Create new tree with different order
1934
- const tree = CollapsibleTree.fromWithPreviousShape(
1935
- ["three", "one", "two", "four"],
1936
- previousTree,
1937
- );
1938
-
1939
- // Check that collapsed state is preserved
1940
- expect(tree.isCollapsed("one")).toBe(true);
1941
- expect(tree.topLevelIds).toEqual(["three", "one", "four"]);
1942
- });
1943
-
1944
- it("handles new ids not in previous tree", () => {
1945
- // Create a tree with collapsed nodes
1946
- let previousTree = CollapsibleTree.from(["one", "two", "three"]);
1947
- previousTree = previousTree.collapse("one", "two");
1948
-
1949
- // Create new tree with new ids
1950
- const tree = CollapsibleTree.fromWithPreviousShape(
1951
- ["one", "two", "three", "four", "five"],
1952
- previousTree,
1953
- );
1954
-
1955
- // Check that collapsed state is preserved for existing nodes
1956
- expect(tree.isCollapsed("one")).toBe(true);
1957
- expect(tree.topLevelIds).toEqual(["one", "three", "four", "five"]);
1958
- });
1959
-
1960
- it("handles completely different set of ids", () => {
1961
- // Create a tree with collapsed nodes
1962
- let previousTree = CollapsibleTree.from(["one", "two", "three"]);
1963
- previousTree = previousTree.collapse("one", "two");
1964
-
1965
- // Create new tree with completely different ids
1966
- const tree = CollapsibleTree.fromWithPreviousShape(
1967
- ["four", "five", "six"],
1968
- previousTree,
1969
- );
1970
-
1971
- // Should have no collapsed nodes
1972
- expect(tree.nodes.every((node) => !node.isCollapsed)).toBe(true);
1973
- expect(tree.topLevelIds).toEqual(["four", "five", "six"]);
1974
- });
1975
-
1976
- it("preserves nested collapsed structure", () => {
1977
- // Create a tree with nested collapsed structure
1978
- let previousTree = CollapsibleTree.from([
1979
- "one",
1980
- "two",
1981
- "three",
1982
- "four",
1983
- "five",
1984
- "six",
1985
- ]);
1986
- previousTree = previousTree.collapse("one", "two");
1987
-
1988
- // Collapse the node that contains the collapsed children
1989
- const oneNode = previousTree.nodes.find((n) => n.value === "one");
1990
- expect(oneNode).toBeDefined();
1991
- expect(oneNode?.children.length).toBeGreaterThan(0);
1992
-
1993
- // Create a new tree with the same structure
1994
- const tree = CollapsibleTree.fromWithPreviousShape(
1995
- ["one", "two", "three", "four", "five", "six"],
1996
- previousTree,
1997
- );
1998
-
1999
- // Check that collapsed state is preserved
2000
- expect(tree.isCollapsed("one")).toBe(true);
2001
- expect(tree.topLevelIds).toEqual(["one", "three", "four", "five", "six"]);
2002
- });
2003
-
2004
- it("collapses using the last available child when some children are missing", () => {
2005
- // Create a tree with collapsed nodes
2006
- let previousTree = CollapsibleTree.from(["one", "two", "three", "four"]);
2007
- previousTree = previousTree.collapse("one", "three");
2008
-
2009
- // Create new tree without "two" but with "three"
2010
- const tree = CollapsibleTree.fromWithPreviousShape(
2011
- ["one", "three", "four"],
2012
- previousTree,
2013
- );
2014
-
2015
- // Should still collapse "one" with "three" as the child
2016
- expect(tree.isCollapsed("one")).toBe(true);
2017
- expect(tree.topLevelIds).toEqual(["one", "four"]);
2018
- });
2019
-
2020
- it("preserves collapse state when children order changes", () => {
2021
- // Create a tree with collapsed nodes
2022
- let previousTree = CollapsibleTree.from(["one", "two", "three", "four"]);
2023
- previousTree = previousTree.collapse("one", "two");
2024
-
2025
- // Create new tree with children in different order
2026
- const tree = CollapsibleTree.fromWithPreviousShape(
2027
- ["one", "three", "two", "four"],
2028
- previousTree,
2029
- );
2030
-
2031
- // Should still be collapsed
2032
- expect(tree.isCollapsed("one")).toBe(true);
2033
- // But the topLevelIds will be different because the children order changed
2034
- expect(tree.topLevelIds).toEqual(["one", "four"]);
2035
- });
2036
-
2037
- it("handles empty arrays correctly", () => {
2038
- const previousTree = CollapsibleTree.from(["one", "two", "three"]);
2039
- const tree = CollapsibleTree.fromWithPreviousShape([], previousTree);
2040
-
2041
- expect(tree.nodes.length).toBe(0);
2042
- expect(tree.topLevelIds).toEqual([]);
2043
- expect(tree.inOrderIds).toEqual([]);
2044
- });
2045
-
2046
- it("handles single item arrays correctly", () => {
2047
- let previousTree = CollapsibleTree.from(["one", "two", "three"]);
2048
- previousTree = previousTree.collapse("one", "two");
2049
-
2050
- const tree = CollapsibleTree.fromWithPreviousShape(["one"], previousTree);
2051
-
2052
- expect(tree.nodes.length).toBe(1);
2053
- expect(tree.topLevelIds).toEqual(["one"]);
2054
- expect(tree.inOrderIds).toEqual(["one"]);
2055
- // Should not be collapsed since there are no children
2056
- expect(tree.isCollapsed("one")).toBe(false);
2057
- });
2058
-
2059
- it("handles multiple collapsed nodes in sequence", () => {
2060
- let previousTree = CollapsibleTree.from([
2061
- "one",
2062
- "two",
2063
- "three",
2064
- "four",
2065
- "five",
2066
- "six",
2067
- ]);
2068
- previousTree = previousTree.collapse("one", "two");
2069
- previousTree = previousTree.collapse("three", "four");
2070
- previousTree = previousTree.collapse("five", "six");
2071
-
2072
- const tree = CollapsibleTree.fromWithPreviousShape(
2073
- ["one", "two", "three", "four", "five", "six"],
2074
- previousTree,
2075
- );
2076
-
2077
- expect(tree.isCollapsed("one")).toBe(true);
2078
- expect(tree.isCollapsed("three")).toBe(true);
2079
- expect(tree.isCollapsed("five")).toBe(true);
2080
- expect(tree.topLevelIds).toEqual(["one", "three", "five"]);
2081
- });
2082
-
2083
- it("handles case where collapsed node's children are not in new ids", () => {
2084
- let previousTree = CollapsibleTree.from(["one", "two", "three", "four"]);
2085
- previousTree = previousTree.collapse("one", "two");
2086
-
2087
- // Create new tree without the collapsed child
2088
- const tree = CollapsibleTree.fromWithPreviousShape(
2089
- ["one", "three", "four"],
2090
- previousTree,
2091
- );
2092
-
2093
- // Should not be collapsed since the child is missing
2094
- expect(tree.isCollapsed("one")).toBe(false);
2095
- expect(tree.topLevelIds).toEqual(["one", "three", "four"]);
2096
- });
2097
-
2098
- it("reproduces GitHub issue #6188: inOrderIds with nested collapses", () => {
2099
- // Simplified reproduction case:
2100
- // Cell A (markdown): # Section 1 Header
2101
- // Cell B (markdown): ## Subsection 1.1 Header
2102
- // Cell C (python): print("This cell should be in Subsection 1.1")
2103
- // Cell D (python): print("This cell should also be in Subsection 1.1")
2104
- // Cell E (markdown): ## Subsection 1.2 Header
2105
- // Cell F (python): print("This cell should be in Subsection 1.2")
2106
-
2107
- const originalOrder = [
2108
- "CellA", // # Section 1 Header
2109
- "CellB", // ## Subsection 1.1 Header
2110
- "CellC", // print("This cell should be in Subsection 1.1")
2111
- "CellD", // print("This cell should also be in Subsection 1.1")
2112
- "CellE", // ## Subsection 1.2 Header
2113
- "CellF", // print("This cell should be in Subsection 1.2")
2114
- ];
2115
-
2116
- // Create notebook with one column
2117
- let notebook = MultiColumn.from([originalOrder]);
2118
-
2119
- // Before collapsing, order should be correct
2120
- expect(notebook.inOrderIds).toEqual(originalOrder);
2121
-
2122
- const columnId = notebook.getColumnIds()[0];
2123
-
2124
- // Follow the exact reproduction steps from the GitHub issue:
2125
- // 1. Collapse B (subsection 1.1) - includes C, D (stops before E)
2126
- notebook = notebook.transform(columnId, (tree) =>
2127
- tree.collapse("CellB", "CellD"),
2128
- );
2129
-
2130
- // 2. Collapse E (subsection 1.2) - includes F (to end)
2131
- notebook = notebook.transform(columnId, (tree) =>
2132
- tree.collapse("CellE", undefined),
2133
- );
2134
-
2135
- // 3. Collapse A (section 1) - includes all the rest (B, E and their children)
2136
- notebook = notebook.transform(columnId, (tree) =>
2137
- tree.collapse("CellA", undefined),
2138
- );
2139
-
2140
- const finalOrder = notebook.inOrderIds;
2141
-
2142
- // inOrderIds now correctly preserves logical order even with nested collapses
2143
- expect(finalOrder).toEqual(originalOrder);
2144
- });
2145
- });