@marimo-team/islands 0.25.3-dev173 → 0.25.3-dev176

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 (134) hide show
  1. package/dist/{ErrorBoundary-wT3f3iJ1.js → ErrorBoundary-D66f-xAE.js} +1 -1
  2. package/dist/{any-language-editor-B92WMzoC.js → any-language-editor-BVFHDDQ3.js} +2 -2
  3. package/dist/{chat-ui-CuORr6fL.js → chat-ui-X-cWrL9u.js} +1222 -1222
  4. package/dist/{code-visibility-ZReaeMaF.js → code-visibility-CVVeTAwi.js} +2 -2
  5. package/dist/{common-DCevHg1m.js → common-DNlm9Cdj.js} +1074 -1078
  6. package/dist/{data-grid-overlay-editor-CTC-bJus.js → data-grid-overlay-editor-BArE3_zi.js} +1 -1
  7. package/dist/{dist-F8Ep36rk.js → dist-B1M0bGO3.js} +1 -1
  8. package/dist/{dist-D6d86eNJ.js → dist-BA13RJj5.js} +1 -1
  9. package/dist/{dist-Bvs24R_E.js → dist-BJtmSgcE.js} +48 -48
  10. package/dist/{dist-CN_BeG2I.js → dist-Ba2VIn1K.js} +53 -53
  11. package/dist/{dist-PXYI_yuP.js → dist-Bj4bmwUm.js} +12 -12
  12. package/dist/{dist-DoE4XJBC.js → dist-BmS-ZGjl.js} +8626 -8626
  13. package/dist/{dist-BorMD-uX.js → dist-Bzogh7CW.js} +87 -87
  14. package/dist/{dist-CN8zI-XC.js → dist-CThPVKaP.js} +1385 -1382
  15. package/dist/{dist-DiDibOYr.js → dist-CTi5FTiV.js} +87 -87
  16. package/dist/{dist-diIx5qJW.js → dist-Ck_C32ye.js} +2 -2
  17. package/dist/{dist-BLfKmR-l.js → dist-D1fnFp1J.js} +1 -1
  18. package/dist/{dist-OR1OnN-y.js → dist-D3x9__Fu.js} +46 -46
  19. package/dist/{dist-BMQf5zIS.js → dist-D7mrh5sO.js} +36 -36
  20. package/dist/{dist-CSJ0MDRw.js → dist-D9Q3xjiP.js} +1 -1
  21. package/dist/{dist-BuaRHxOc.js → dist-DP60Ubh8.js} +2 -2
  22. package/dist/{dist-CSbh1PNP.js → dist-DTv9KkpW.js} +12 -12
  23. package/dist/{dist-D-wwqlzJ.js → dist-DVVwOfrD.js} +109 -109
  24. package/dist/{dist-C8fAKSH1.js → dist-DYXM73-k.js} +36 -36
  25. package/dist/{dist-DBtSl3sf.js → dist-DjeybPOb.js} +1 -1
  26. package/dist/{dist-CrSP_H1M.js → dist-DtGn9N4h.js} +197 -197
  27. package/dist/{dist-CLdpGQVr.js → dist-KN80qWCP.js} +2 -2
  28. package/dist/{dist-Cg7gzw-a.js → dist-O0qFpEJh.js} +56 -56
  29. package/dist/{dist-CkTTIdBe.js → dist-OUle2RT7.js} +207 -207
  30. package/dist/{dist-BK4Oj99o.js → dist-T5rh6JvL.js} +1 -1
  31. package/dist/{error-entries-CmWMEsY_.js → error-entries-mpoD5t79.js} +4939 -4983
  32. package/dist/{esm-D2gTsXEV.js → esm-Bbw_Njjx.js} +575 -575
  33. package/dist/{esm-AMFssoNp.js → esm-mDQNEFe_.js} +354 -354
  34. package/dist/{feedback-button-DwXkmewo.js → feedback-button-CyvqdxXN.js} +21 -21
  35. package/dist/{glide-data-editor-BlmcP-lw.js → glide-data-editor-BxvYRMC3.js} +3 -3
  36. package/dist/{globals-BdRitZ4p.js → globals-BJla9q8e.js} +1 -1
  37. package/dist/{html-to-image-bpiQh7ND.js → html-to-image-BoZthB4P.js} +454 -445
  38. package/dist/main.js +3587 -3587
  39. package/dist/{number-overlay-editor-DQG-hFHT.js → number-overlay-editor-BWg7xiRF.js} +1 -1
  40. package/dist/{process-output-BzloOI7F.js → process-output-DLS4Mqg_.js} +2 -2
  41. package/dist/{reveal-component-D3r3vSTK.js → reveal-component-feAltY24.js} +3 -3
  42. package/package.json +1 -1
  43. package/src/__mocks__/document.tsx +54 -0
  44. package/src/__mocks__/notebook.ts +2 -1
  45. package/src/__mocks__/requests.ts +0 -2
  46. package/src/components/chat/chat-panel.tsx +3 -3
  47. package/src/components/datasources/__tests__/column-preview.test.tsx +6 -8
  48. package/src/components/datasources/column-preview.tsx +4 -8
  49. package/src/components/datasources/datasources.tsx +4 -3
  50. package/src/components/dependency-graph/minimap-content.tsx +2 -6
  51. package/src/components/dependency-graph/panels.tsx +2 -1
  52. package/src/components/editor/__tests__/Output.test.tsx +50 -32
  53. package/src/components/editor/__tests__/data-attributes.test.tsx +22 -11
  54. package/src/components/editor/__tests__/output-persistence.test.tsx +6 -1
  55. package/src/components/editor/actions/__tests__/name-cell-input.test.tsx +69 -0
  56. package/src/components/editor/actions/name-cell-input.tsx +4 -3
  57. package/src/components/editor/actions/useCellActionButton.tsx +20 -26
  58. package/src/components/editor/actions/useNotebookActions.tsx +13 -18
  59. package/src/components/editor/actions/useSetCodeVisibility.ts +10 -11
  60. package/src/components/editor/actions/useSetOutputsExpanded.ts +13 -18
  61. package/src/components/editor/cell/CreateCellButton.tsx +5 -9
  62. package/src/components/editor/cell/__tests__/StagedAICell.test.tsx +3 -1
  63. package/src/components/editor/cell/code/cell-editor.tsx +13 -20
  64. package/src/components/editor/cell/useAddCell.ts +4 -8
  65. package/src/components/editor/cell/useDeleteCell.tsx +10 -36
  66. package/src/components/editor/cell/useRunCells.ts +2 -5
  67. package/src/components/editor/cell/useSplitCell.tsx +2 -1
  68. package/src/components/editor/chrome/panels/logs-panel.tsx +2 -1
  69. package/src/components/editor/chrome/panels/snippets-panel.tsx +5 -11
  70. package/src/components/editor/chrome/wrapper/__tests__/pending-ai-cells.test.tsx +3 -1
  71. package/src/components/editor/columns/sortable-column.tsx +1 -1
  72. package/src/components/editor/connections/components.tsx +4 -9
  73. package/src/components/editor/controls/Controls.tsx +1 -1
  74. package/src/components/editor/controls/__tests__/notebook-menu-dropdown.test.tsx +9 -3
  75. package/src/components/editor/documentation.css +34 -11
  76. package/src/components/editor/errors/auto-fix.tsx +4 -8
  77. package/src/components/editor/file-tree/file-explorer.tsx +3 -7
  78. package/src/components/editor/links/__tests__/cell-link.test.tsx +3 -1
  79. package/src/components/editor/links/cell-link.tsx +2 -1
  80. package/src/components/editor/navigation/__tests__/clipboard.test.ts +60 -38
  81. package/src/components/editor/navigation/__tests__/navigation.test.ts +73 -64
  82. package/src/components/editor/navigation/clipboard.ts +12 -11
  83. package/src/components/editor/navigation/multi-cell-action-toolbar.tsx +11 -20
  84. package/src/components/editor/navigation/navigation.ts +20 -28
  85. package/src/components/editor/notebook-cell.tsx +7 -10
  86. package/src/components/editor/output/console/__tests__/ConsoleOutput.test.tsx +125 -69
  87. package/src/components/editor/renderers/cell-array.tsx +10 -17
  88. package/src/components/markdown/markdown-renderer.tsx +4 -8
  89. package/src/components/pages/edit-page.tsx +32 -17
  90. package/src/components/scratchpad/scratchpad.test.tsx +3 -1
  91. package/src/components/scratchpad/scratchpad.tsx +4 -7
  92. package/src/components/slides/__tests__/minimap-actions.test.tsx +9 -25
  93. package/src/components/slides/minimap.tsx +7 -3
  94. package/src/components/slides/slide-cell-view.tsx +3 -3
  95. package/src/components/sort/SortableCellsProvider.tsx +2 -1
  96. package/src/core/ai/__tests__/staged-cells.test.ts +18 -20
  97. package/src/core/ai/staged-cells.ts +8 -16
  98. package/src/core/ai/tools/__tests__/edit-notebook-tool.test.ts +2 -2
  99. package/src/core/ai/tools/__tests__/run-cells-tool.test.ts +2 -2
  100. package/src/core/ai/tools/base.ts +2 -2
  101. package/src/core/ai/tools/edit-notebook-tool.ts +13 -21
  102. package/src/core/cells/__tests__/actions.test.ts +474 -34
  103. package/src/core/cells/__tests__/add-missing-import.test.ts +7 -8
  104. package/src/core/cells/__tests__/cells.test.ts +171 -813
  105. package/src/core/cells/__tests__/document.test.ts +5 -35
  106. package/src/core/cells/__tests__/notebook-outline.test.ts +7 -1
  107. package/src/core/cells/__tests__/outputs.test.tsx +71 -53
  108. package/src/core/cells/actions.ts +391 -3
  109. package/src/core/cells/add-missing-import.ts +46 -42
  110. package/src/core/cells/cells.ts +38 -413
  111. package/src/core/cells/document.ts +7 -4
  112. package/src/core/cells/outputs.ts +12 -28
  113. package/src/core/codemirror/cells/extensions.ts +6 -6
  114. package/src/core/codemirror/cells/state.ts +1 -1
  115. package/src/core/codemirror/keymaps/vim.ts +2 -2
  116. package/src/core/edit-app.tsx +19 -27
  117. package/src/core/islands/bridge.ts +0 -2
  118. package/src/core/kernel/handlers.ts +3 -3
  119. package/src/core/network/__tests__/requests-lazy.test.ts +1 -3
  120. package/src/core/network/requests-lazy.ts +0 -2
  121. package/src/core/network/requests-network.ts +0 -16
  122. package/src/core/network/requests-static.ts +0 -2
  123. package/src/core/network/requests-toasting.tsx +0 -2
  124. package/src/core/network/types.ts +0 -7
  125. package/src/core/notebook/document/__tests__/codemirror.test.ts +5 -0
  126. package/src/core/notebook/document/__tests__/draft.test.ts +41 -0
  127. package/src/core/notebook/document/__tests__/state.test.ts +21 -0
  128. package/src/core/notebook/document/draft.ts +35 -5
  129. package/src/core/notebook/document/index.ts +1 -0
  130. package/src/core/notebook/document/state.ts +6 -2
  131. package/src/core/session/useNotebookSession.tsx +6 -2
  132. package/src/core/wasm/bridge.ts +0 -16
  133. package/src/css/globals.css +56 -22
  134. package/src/stories/runtime-story.tsx +22 -15
@@ -4,6 +4,7 @@ import { python } from "@codemirror/lang-python";
4
4
  import { EditorState } from "@codemirror/state";
5
5
  import { EditorView } from "@codemirror/view";
6
6
  import { createStore } from "jotai";
7
+ import { createRef } from "react";
7
8
  import {
8
9
  afterAll,
9
10
  beforeAll,
@@ -17,11 +18,12 @@ import {
17
18
  import { MockNotebook } from "@/__mocks__/notebook";
18
19
  import { cellId } from "@/__tests__/branded";
19
20
  import type { CellHandle } from "@/components/editor/notebook-cell";
20
- import { CellId, SCRATCH_CELL_ID, SETUP_CELL_ID } from "@/core/cells/ids";
21
+ import { CellId, SCRATCH_CELL_ID } from "@/core/cells/ids";
21
22
  import { foldAllBulk, unfoldAllBulk } from "@/core/codemirror/editing/commands";
22
23
  import { adaptiveLanguageConfiguration } from "@/core/codemirror/language/extension";
23
24
  import { OverridingHotkeyProvider } from "@/core/hotkeys/hotkeys";
24
25
  import type { OutputMessage } from "@/core/kernel/messages";
26
+ import type { CellConfig } from "@/core/network/types";
25
27
  import {
26
28
  type CollapsibleTree,
27
29
  MultiColumn,
@@ -39,7 +41,12 @@ import {
39
41
  scrollToBottom,
40
42
  scrollToTop,
41
43
  } from "../scrollCellIntoView";
42
- import type { CellData } from "../types";
44
+ import {
45
+ type CellData,
46
+ createCell,
47
+ createCellConfig,
48
+ createCellRuntimeState,
49
+ } from "../types";
43
50
 
44
51
  vi.mock("@/core/codemirror/editing/commands", () => ({
45
52
  foldAllBulk: vi.fn(),
@@ -120,8 +127,7 @@ describe("cell reducer", () => {
120
127
  let cells: ReturnType<typeof flattenTopLevelNotebookCells>;
121
128
  let firstCellId: CellId;
122
129
 
123
- const actions = createActions((action) => {
124
- state = reducer(state, action);
130
+ const mountEditors = () => {
125
131
  for (const [cellIdString, handle] of Object.entries(state.cellHandles)) {
126
132
  // @ts-expect-error - Typescript Object.entries doesn't know that keys are CellId
127
133
  const cid: CellId = cellIdString;
@@ -135,7 +141,76 @@ describe("cell reducer", () => {
135
141
  }
136
142
  }
137
143
  cells = flattenTopLevelNotebookCells(state);
138
- });
144
+ };
145
+ const actions = createActions((action) => {
146
+ state = reducer(state, action);
147
+ mountEditors();
148
+ });
149
+
150
+ /** Put a cell into the notebook the way the document would. */
151
+ function seedCell({
152
+ cellId,
153
+ before,
154
+ code = "",
155
+ name,
156
+ config,
157
+ newCellId,
158
+ hideCode,
159
+ autoFocus = true,
160
+ }: {
161
+ cellId: CellId | "__end__" | { type: "__end__"; columnId: CellColumnId };
162
+ before: boolean;
163
+ code?: string;
164
+ name?: string;
165
+ config?: CellConfig;
166
+ newCellId?: CellId;
167
+ hideCode?: boolean;
168
+ autoFocus?: boolean;
169
+ }): CellId {
170
+ let column: CollapsibleTree<CellId>;
171
+ let cellIndex: number;
172
+ if (cellId === "__end__") {
173
+ column = state.cellIds.atOrThrow(0);
174
+ cellIndex = column.length;
175
+ } else if (typeof cellId === "string") {
176
+ column = state.cellIds.findWithId(cellId);
177
+ cellIndex = column.topLevelIds.indexOf(cellId);
178
+ } else {
179
+ column = state.cellIds.get(cellId.columnId) ?? state.cellIds.atOrThrow(0);
180
+ cellIndex = column.length;
181
+ }
182
+ const id = newCellId ?? CellId.create();
183
+ state = {
184
+ ...state,
185
+ cellIds: state.cellIds.insertId(
186
+ id,
187
+ column.id,
188
+ before ? cellIndex : cellIndex + 1,
189
+ ),
190
+ cellData: {
191
+ ...state.cellData,
192
+ [id]: createCell({
193
+ id,
194
+ code,
195
+ name,
196
+ config: createCellConfig({
197
+ ...config,
198
+ ...(hideCode != null && { hide_code: hideCode }),
199
+ }),
200
+ edited: Boolean(code),
201
+ }),
202
+ },
203
+ cellRuntime: { ...state.cellRuntime, [id]: createCellRuntimeState() },
204
+ cellHandles: { ...state.cellHandles, [id]: createRef() },
205
+ scrollKey: autoFocus ? id : null,
206
+ untouchedNewCells:
207
+ hideCode && autoFocus
208
+ ? new Set([...state.untouchedNewCells, id])
209
+ : state.untouchedNewCells,
210
+ };
211
+ mountEditors();
212
+ return id;
213
+ }
139
214
 
140
215
  let i = 0;
141
216
  const originalCreate = CellId.create.bind(CellId);
@@ -151,7 +226,7 @@ describe("cell reducer", () => {
151
226
 
152
227
  state = initialNotebookState();
153
228
  state.cellIds = MultiColumn.from([]);
154
- actions.createNewCell({ cellId: "__end__", before: false });
229
+ seedCell({ cellId: "__end__", before: false });
155
230
  firstCellId = state.cellIds.inOrderIds[0];
156
231
  });
157
232
 
@@ -159,181 +234,6 @@ describe("cell reducer", () => {
159
234
  CellId.create = originalCreate;
160
235
  });
161
236
 
162
- it("can add a cell after another cell", () => {
163
- actions.createNewCell({
164
- cellId: firstCellId,
165
- before: false,
166
- });
167
- expect(formatCells(state)).toMatchInlineSnapshot(`
168
- "
169
- [0] ''
170
-
171
- [1] ''
172
- "
173
- `);
174
- });
175
-
176
- it("can add a cell to the end with code", () => {
177
- actions.createNewCell({
178
- cellId: "__end__",
179
- code: "import numpy as np",
180
- before: false,
181
- });
182
- expect(formatCells(state)).toMatchInlineSnapshot(`
183
- "
184
- [0] ''
185
-
186
- [1] 'import numpy as np'
187
- "
188
- `);
189
-
190
- // Cell should be added to the end and edited
191
- expect(cells[1].code).toBe("import numpy as np");
192
- expect(cells[1].edited).toBe(true);
193
- expect(cells[1].lastCodeRun).toBe(null);
194
- });
195
-
196
- it("can add a cell before another cell", () => {
197
- actions.createNewCell({
198
- cellId: firstCellId,
199
- before: true,
200
- });
201
- expect(formatCells(state)).toMatchInlineSnapshot(`
202
- "
203
- [1] ''
204
-
205
- [0] ''
206
- "
207
- `);
208
- });
209
-
210
- it("can add a cell with name and config", () => {
211
- actions.createNewCell({
212
- cellId: firstCellId,
213
- before: false,
214
- code: "x = 1",
215
- name: "My Cell",
216
- config: { hide_code: true, disabled: false },
217
- });
218
- const newCellId = state.cellIds.inOrderIds[1];
219
- expect(state.cellData[newCellId].name).toBe("My Cell");
220
- expect(state.cellData[newCellId].config.hide_code).toBe(true);
221
- expect(state.cellData[newCellId].config.disabled).toBe(false);
222
- });
223
-
224
- it("can delete a Python cell and undo delete", () => {
225
- actions.createNewCell({
226
- cellId: firstCellId,
227
- before: false,
228
- });
229
- actions.deleteCell({
230
- cellId: firstCellId,
231
- });
232
- expect(formatCells(state)).toMatchInlineSnapshot(`
233
- "
234
- [1] ''
235
- "
236
- `);
237
-
238
- // undo
239
- actions.undoDeleteCell();
240
- expect(formatCells(state)).toMatchInlineSnapshot(`
241
- "
242
- [2] ''
243
-
244
- [1] ''
245
- "
246
- `);
247
-
248
- // Verify scrollKey is set to the restored cell
249
- expect(state.scrollKey).toBe("2");
250
- });
251
-
252
- it("can delete a SQL cell and undo delete", () => {
253
- actions.createNewCell({
254
- cellId: firstCellId,
255
- before: false,
256
- code: `df = mo.sql("""SELECT * FROM table""")`,
257
- });
258
- const newCellId = state.cellIds.getColumns()[0].atOrThrow(1);
259
- actions.deleteCell({
260
- cellId: newCellId,
261
- });
262
- expect(formatCells(state)).toMatchInlineSnapshot(`
263
- "
264
- [0] ''
265
- "
266
- `);
267
-
268
- // undo
269
- actions.undoDeleteCell();
270
- expect(formatCells(state)).toMatchInlineSnapshot(`
271
- "
272
- [0] ''
273
-
274
- [2] 'df = mo.sql("""SELECT * FROM table""")'
275
- "
276
- `);
277
- });
278
-
279
- it("can delete a Markdown cell and undo delete", () => {
280
- const text = "The quick brown fox jumps over the lazy dog.";
281
- actions.createNewCell({
282
- cellId: firstCellId,
283
- before: false,
284
- code: `mo.md(r"""${text}""")`,
285
- });
286
- const newCellId = state.cellIds.getColumns()[0].atOrThrow(1);
287
- actions.deleteCell({
288
- cellId: newCellId,
289
- });
290
- expect(formatCells(state)).toMatchInlineSnapshot(`
291
- "
292
- [0] ''
293
- "
294
- `);
295
-
296
- // undo
297
- actions.undoDeleteCell();
298
- expect(formatCells(state)).toMatchInlineSnapshot(`
299
- "
300
- [0] ''
301
-
302
- [2] 'mo.md(r"""The quick brown fox jumps over the lazy dog.""")'
303
- "
304
- `);
305
- });
306
-
307
- it("undo delete restores cell config (hide_code, disabled)", () => {
308
- actions.createNewCell({
309
- cellId: firstCellId,
310
- before: false,
311
- code: "x = 1",
312
- });
313
- const newCellId = state.cellIds.getColumns()[0].atOrThrow(1);
314
-
315
- // Set config to hidden and disabled
316
- actions.updateCellConfig({
317
- cellId: newCellId,
318
- config: { hide_code: true, disabled: true },
319
- });
320
- expect(state.cellData[newCellId].config.hide_code).toBe(true);
321
- expect(state.cellData[newCellId].config.disabled).toBe(true);
322
-
323
- // Delete the cell
324
- actions.deleteCell({ cellId: newCellId });
325
- expect(state.cellIds.inOrderIds).not.toContain(newCellId);
326
-
327
- // Undo delete
328
- actions.undoDeleteCell();
329
- const restoredCellId =
330
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
331
-
332
- // Config should be preserved
333
- expect(state.cellData[restoredCellId].config.hide_code).toBe(true);
334
- expect(state.cellData[restoredCellId].config.disabled).toBe(true);
335
- });
336
-
337
237
  it("can update a cell", () => {
338
238
  actions.updateCellCode({
339
239
  cellId: firstCellId,
@@ -348,7 +248,7 @@ describe("cell reducer", () => {
348
248
  });
349
249
 
350
250
  it("can move a cell", () => {
351
- actions.createNewCell({
251
+ seedCell({
352
252
  cellId: firstCellId,
353
253
  before: false,
354
254
  });
@@ -462,11 +362,11 @@ describe("cell reducer", () => {
462
362
  });
463
363
 
464
364
  it("can drag and drop a cell", () => {
465
- actions.createNewCell({
365
+ seedCell({
466
366
  cellId: firstCellId,
467
367
  before: false,
468
368
  });
469
- actions.createNewCell({
369
+ seedCell({
470
370
  cellId: cellId("1"),
471
371
  before: false,
472
372
  });
@@ -512,11 +412,11 @@ describe("cell reducer", () => {
512
412
  });
513
413
 
514
414
  it("can move a cell to an exact index within a column", () => {
515
- actions.createNewCell({
415
+ seedCell({
516
416
  cellId: firstCellId,
517
417
  before: false,
518
418
  });
519
- actions.createNewCell({
419
+ seedCell({
520
420
  cellId: cellId("1"),
521
421
  before: false,
522
422
  });
@@ -540,11 +440,11 @@ describe("cell reducer", () => {
540
440
  });
541
441
 
542
442
  it("can move a cell to an exact index across columns", () => {
543
- actions.createNewCell({
443
+ seedCell({
544
444
  cellId: firstCellId,
545
445
  before: false,
546
446
  });
547
- actions.createNewCell({
447
+ seedCell({
548
448
  cellId: cellId("1"),
549
449
  before: false,
550
450
  });
@@ -573,11 +473,11 @@ describe("cell reducer", () => {
573
473
  });
574
474
 
575
475
  it("moveCellToIndex is a no-op when moving to the same position", () => {
576
- actions.createNewCell({
476
+ seedCell({
577
477
  cellId: firstCellId,
578
478
  before: false,
579
479
  });
580
- actions.createNewCell({
480
+ seedCell({
581
481
  cellId: cellId("1"),
582
482
  before: false,
583
483
  });
@@ -595,7 +495,7 @@ describe("cell reducer", () => {
595
495
  });
596
496
 
597
497
  it("moveCellToIndex is a no-op for an invalid columnId", () => {
598
- actions.createNewCell({
498
+ seedCell({
599
499
  cellId: firstCellId,
600
500
  before: false,
601
501
  });
@@ -612,11 +512,11 @@ describe("cell reducer", () => {
612
512
  });
613
513
 
614
514
  it("can move multiple cells relative to target", () => {
615
- actions.createNewCell({
515
+ seedCell({
616
516
  cellId: firstCellId,
617
517
  before: false,
618
518
  });
619
- actions.createNewCell({
519
+ seedCell({
620
520
  cellId: cellId("1"),
621
521
  before: false,
622
522
  });
@@ -647,143 +547,6 @@ describe("cell reducer", () => {
647
547
  `);
648
548
  });
649
549
 
650
- it("can undo cut-paste (move with previousPlacements)", () => {
651
- actions.createNewCell({
652
- cellId: firstCellId,
653
- before: false,
654
- });
655
- actions.createNewCell({
656
- cellId: cellId("1"),
657
- before: false,
658
- });
659
- expect(formatCells(state)).toMatchInlineSnapshot(`
660
- "
661
- [0] ''
662
-
663
- [1] ''
664
-
665
- [2] ''
666
- "
667
- `);
668
-
669
- const col = state.cellIds.findWithId(firstCellId);
670
- const previousPlacements = [
671
- {
672
- columnId: col.id,
673
- index: col.indexOfOrThrow(
674
- firstCellId,
675
- ) as import("@/utils/id-tree").CellIndex,
676
- },
677
- {
678
- columnId: col.id,
679
- index: col.indexOfOrThrow(
680
- cellId("1"),
681
- ) as import("@/utils/id-tree").CellIndex,
682
- },
683
- ];
684
-
685
- actions.moveCellsRelativeTo({
686
- cellIds: [firstCellId, cellId("1")],
687
- targetCellId: cellId("2"),
688
- position: "after",
689
- previousPlacements,
690
- });
691
- expect(formatCells(state)).toMatchInlineSnapshot(`
692
- "
693
- [2] ''
694
-
695
- [0] ''
696
-
697
- [1] ''
698
- "
699
- `);
700
-
701
- actions.undoDeleteCell();
702
- expect(formatCells(state)).toMatchInlineSnapshot(`
703
- "
704
- [0] ''
705
-
706
- [1] ''
707
-
708
- [2] ''
709
- "
710
- `);
711
- });
712
-
713
- it("undo order: cut-paste then delete — first undo restores delete, second undo undoes move", () => {
714
- actions.createNewCell({
715
- cellId: firstCellId,
716
- before: false,
717
- });
718
- actions.createNewCell({
719
- cellId: cellId("1"),
720
- before: false,
721
- });
722
-
723
- const col = state.cellIds.findWithId(firstCellId);
724
- const previousPlacements = [
725
- {
726
- columnId: col.id,
727
- index: col.indexOfOrThrow(
728
- firstCellId,
729
- ) as import("@/utils/id-tree").CellIndex,
730
- },
731
- {
732
- columnId: col.id,
733
- index: col.indexOfOrThrow(
734
- cellId("1"),
735
- ) as import("@/utils/id-tree").CellIndex,
736
- },
737
- ];
738
-
739
- actions.moveCellsRelativeTo({
740
- cellIds: [firstCellId, cellId("1")],
741
- targetCellId: cellId("2"),
742
- position: "after",
743
- previousPlacements,
744
- });
745
- expect(formatCells(state)).toMatchInlineSnapshot(`
746
- "
747
- [2] ''
748
-
749
- [0] ''
750
-
751
- [1] ''
752
- "
753
- `);
754
-
755
- actions.deleteCell({ cellId: cellId("2") });
756
- expect(formatCells(state)).toMatchInlineSnapshot(`
757
- "
758
- [0] ''
759
-
760
- [1] ''
761
- "
762
- `);
763
-
764
- actions.undoDeleteCell();
765
- expect(formatCells(state)).toMatchInlineSnapshot(`
766
- "
767
- [3] ''
768
-
769
- [0] ''
770
-
771
- [1] ''
772
- "
773
- `);
774
-
775
- actions.undoDeleteCell();
776
- expect(formatCells(state)).toMatchInlineSnapshot(`
777
- "
778
- [0] ''
779
-
780
- [1] ''
781
-
782
- [3] ''
783
- "
784
- `);
785
- });
786
-
787
550
  it("keeps error evidence after an edit and a new queued run", () => {
788
551
  const code = "raise ValueError('old failure')";
789
552
  const output: OutputMessage = {
@@ -1167,7 +930,7 @@ describe("cell reducer", () => {
1167
930
  formattingChange: false,
1168
931
  });
1169
932
  // Add cell
1170
- actions.createNewCell({
933
+ seedCell({
1171
934
  cellId: firstCellId,
1172
935
  before: false,
1173
936
  });
@@ -1272,33 +1035,6 @@ describe("cell reducer", () => {
1272
1035
  expect(cell.edited).toBe(false);
1273
1036
  });
1274
1037
 
1275
- it("can update a cells config", () => {
1276
- const firstCellId = cells[0].id;
1277
- let cell = cells[0];
1278
- // Starts empty
1279
- expect(cell.config).toEqual({
1280
- disabled: false,
1281
- hide_code: false,
1282
- column: null,
1283
- expand_output: false,
1284
- });
1285
-
1286
- actions.updateCellConfig({
1287
- cellId: firstCellId,
1288
- config: { disabled: true },
1289
- });
1290
- cell = cells[0];
1291
- expect(cell.config.disabled).toBe(true);
1292
-
1293
- // Revert
1294
- actions.updateCellConfig({
1295
- cellId: firstCellId,
1296
- config: { disabled: false },
1297
- });
1298
- cell = cells[0];
1299
- expect(cell.config.disabled).toBe(false);
1300
- });
1301
-
1302
1038
  it("can run a stopped cell", () => {
1303
1039
  // Update code of first cell
1304
1040
  actions.updateCellCode({
@@ -1704,8 +1440,8 @@ describe("cell reducer", () => {
1704
1440
  });
1705
1441
 
1706
1442
  it("can send a cell to the top", () => {
1707
- actions.createNewCell({ cellId: firstCellId, before: false });
1708
- actions.createNewCell({ cellId: cellId("1"), before: false });
1443
+ seedCell({ cellId: firstCellId, before: false });
1444
+ seedCell({ cellId: cellId("1"), before: false });
1709
1445
  actions.sendToTop({ cellId: cellId("2") });
1710
1446
  expect(formatCells(state)).toMatchInlineSnapshot(`
1711
1447
  "
@@ -1719,8 +1455,8 @@ describe("cell reducer", () => {
1719
1455
  });
1720
1456
 
1721
1457
  it("can send a cell to the bottom", () => {
1722
- actions.createNewCell({ cellId: firstCellId, before: false });
1723
- actions.createNewCell({ cellId: cellId("1"), before: false });
1458
+ seedCell({ cellId: firstCellId, before: false });
1459
+ seedCell({ cellId: cellId("1"), before: false });
1724
1460
  actions.sendToBottom({ cellId: firstCellId });
1725
1461
  expect(formatCells(state)).toMatchInlineSnapshot(`
1726
1462
  "
@@ -1734,8 +1470,8 @@ describe("cell reducer", () => {
1734
1470
  });
1735
1471
 
1736
1472
  it("can focus cells", () => {
1737
- actions.createNewCell({ cellId: firstCellId, before: false });
1738
- actions.createNewCell({ cellId: cellId("1"), before: false });
1473
+ seedCell({ cellId: firstCellId, before: false });
1474
+ seedCell({ cellId: cellId("1"), before: false });
1739
1475
 
1740
1476
  actions.focusCell({ cellId: cellId("1"), where: "before" });
1741
1477
  expect(focusAndScrollCellIntoView).toHaveBeenCalledWith(
@@ -1751,11 +1487,6 @@ describe("cell reducer", () => {
1751
1487
  expect(scrollToBottom).toHaveBeenCalled();
1752
1488
  });
1753
1489
 
1754
- it("can update cell name", () => {
1755
- actions.updateCellName({ cellId: firstCellId, name: "Test Cell" });
1756
- expect(state.cellData[firstCellId].name).toBe("Test Cell");
1757
- });
1758
-
1759
1490
  it("can fold and unfold all cells", () => {
1760
1491
  actions.foldAll();
1761
1492
  expect(foldAllBulk).toHaveBeenCalled();
@@ -1784,13 +1515,13 @@ describe("cell reducer", () => {
1784
1515
  });
1785
1516
 
1786
1517
  it("can collapse and expand cells", () => {
1787
- actions.createNewCell({ cellId: firstCellId, before: false });
1788
- actions.createNewCell({
1518
+ seedCell({ cellId: firstCellId, before: false });
1519
+ seedCell({
1789
1520
  cellId: cellId("1"),
1790
1521
  before: false,
1791
1522
  code: "# Header",
1792
1523
  });
1793
- actions.createNewCell({
1524
+ seedCell({
1794
1525
  cellId: cellId("2"),
1795
1526
  before: false,
1796
1527
  code: "## Subheader",
@@ -1812,13 +1543,13 @@ describe("cell reducer", () => {
1812
1543
  });
1813
1544
 
1814
1545
  it("can collapse and expand all cells in multiple columns", () => {
1815
- actions.createNewCell({ cellId: firstCellId, before: false });
1816
- actions.createNewCell({
1546
+ seedCell({ cellId: firstCellId, before: false });
1547
+ seedCell({
1817
1548
  cellId: cellId("1"),
1818
1549
  before: false,
1819
1550
  code: "# First Column Header",
1820
1551
  });
1821
- actions.createNewCell({
1552
+ seedCell({
1822
1553
  cellId: cellId("2"),
1823
1554
  before: false,
1824
1555
  code: "## First Column Subheader",
@@ -1826,12 +1557,12 @@ describe("cell reducer", () => {
1826
1557
 
1827
1558
  actions.addColumnBreakpoint({ cellId: cellId("2") });
1828
1559
 
1829
- actions.createNewCell({
1560
+ seedCell({
1830
1561
  cellId: cellId("3"),
1831
1562
  before: false,
1832
1563
  code: "# Second Column Header",
1833
1564
  });
1834
- actions.createNewCell({
1565
+ seedCell({
1835
1566
  cellId: cellId("4"),
1836
1567
  before: false,
1837
1568
  code: "## Second Column Subheader",
@@ -1879,18 +1610,18 @@ describe("cell reducer", () => {
1879
1610
  });
1880
1611
 
1881
1612
  it("can collapse and expand nested cells in one call", () => {
1882
- actions.createNewCell({ cellId: firstCellId, before: false });
1883
- actions.createNewCell({
1613
+ seedCell({ cellId: firstCellId, before: false });
1614
+ seedCell({
1884
1615
  cellId: cellId("1"),
1885
1616
  before: false,
1886
1617
  code: "# Header",
1887
1618
  });
1888
- actions.createNewCell({
1619
+ seedCell({
1889
1620
  cellId: cellId("2"),
1890
1621
  before: false,
1891
1622
  code: "## Subheader",
1892
1623
  });
1893
- actions.createNewCell({
1624
+ seedCell({
1894
1625
  cellId: cellId("3"),
1895
1626
  before: false,
1896
1627
  code: "### Subsubheader",
@@ -1934,18 +1665,18 @@ describe("cell reducer", () => {
1934
1665
  });
1935
1666
 
1936
1667
  it("can collapse all cells when nested heading ranges end at the same cell", () => {
1937
- actions.createNewCell({ cellId: firstCellId, before: false });
1938
- actions.createNewCell({
1668
+ seedCell({ cellId: firstCellId, before: false });
1669
+ seedCell({
1939
1670
  cellId: cellId("1"),
1940
1671
  before: false,
1941
1672
  code: "# Header",
1942
1673
  });
1943
- actions.createNewCell({
1674
+ seedCell({
1944
1675
  cellId: cellId("2"),
1945
1676
  before: false,
1946
1677
  code: "## Subheader",
1947
1678
  });
1948
- actions.createNewCell({
1679
+ seedCell({
1949
1680
  cellId: cellId("3"),
1950
1681
  before: false,
1951
1682
  code: "### Subsubheader",
@@ -1999,23 +1730,23 @@ describe("cell reducer", () => {
1999
1730
  });
2000
1731
 
2001
1732
  it("can collapse all cells when a deep chain is followed by a later section", () => {
2002
- actions.createNewCell({ cellId: firstCellId, before: false });
2003
- actions.createNewCell({
1733
+ seedCell({ cellId: firstCellId, before: false });
1734
+ seedCell({
2004
1735
  cellId: cellId("1"),
2005
1736
  before: false,
2006
1737
  code: "# Header",
2007
1738
  });
2008
- actions.createNewCell({
1739
+ seedCell({
2009
1740
  cellId: cellId("2"),
2010
1741
  before: false,
2011
1742
  code: "## Section A",
2012
1743
  });
2013
- actions.createNewCell({
1744
+ seedCell({
2014
1745
  cellId: cellId("3"),
2015
1746
  before: false,
2016
1747
  code: "### Sub of A",
2017
1748
  });
2018
- actions.createNewCell({
1749
+ seedCell({
2019
1750
  cellId: cellId("4"),
2020
1751
  before: false,
2021
1752
  code: "## Section B",
@@ -2078,8 +1809,8 @@ describe("cell reducer", () => {
2078
1809
  });
2079
1810
 
2080
1811
  it("can show hidden cells", () => {
2081
- actions.createNewCell({ cellId: firstCellId, before: false });
2082
- actions.createNewCell({ cellId: cellId("1"), before: false });
1812
+ seedCell({ cellId: firstCellId, before: false });
1813
+ seedCell({ cellId: cellId("1"), before: false });
2083
1814
  actions.collapseCell({ cellId: firstCellId });
2084
1815
 
2085
1816
  actions.showCellIfHidden({ cellId: cellId("1") });
@@ -2089,7 +1820,7 @@ describe("cell reducer", () => {
2089
1820
  });
2090
1821
 
2091
1822
  it("can split and undo split cells", () => {
2092
- actions.createNewCell({
1823
+ seedCell({
2093
1824
  cellId: firstCellId,
2094
1825
  before: false,
2095
1826
  code: "line1\nline2",
@@ -2156,9 +1887,9 @@ describe("cell reducer", () => {
2156
1887
  });
2157
1888
 
2158
1889
  it("can add a column breakpoint", () => {
2159
- actions.createNewCell({ cellId: firstCellId, before: false });
2160
- actions.createNewCell({ cellId: cellId("1"), before: false });
2161
- actions.createNewCell({ cellId: cellId("2"), before: false });
1890
+ seedCell({ cellId: firstCellId, before: false });
1891
+ seedCell({ cellId: cellId("1"), before: false });
1892
+ seedCell({ cellId: cellId("2"), before: false });
2162
1893
 
2163
1894
  expect(state.cellIds.getColumns().length).toBe(1);
2164
1895
  expect(formatCells(state)).toMatchInlineSnapshot(`
@@ -2203,16 +1934,16 @@ describe("cell reducer", () => {
2203
1934
 
2204
1935
  it("cannot add a column breakpoint before the first cell", () => {
2205
1936
  expect(state.cellIds.getColumns().length).toBe(1);
2206
- actions.createNewCell({ cellId: firstCellId, before: false });
2207
- actions.createNewCell({ cellId: cellId("1"), before: false });
1937
+ seedCell({ cellId: firstCellId, before: false });
1938
+ seedCell({ cellId: cellId("1"), before: false });
2208
1939
  actions.addColumnBreakpoint({ cellId: firstCellId });
2209
1940
  expect(state.cellIds.getColumns().length).toBe(1);
2210
1941
  });
2211
1942
 
2212
1943
  it("can delete a column", () => {
2213
- actions.createNewCell({ cellId: firstCellId, before: false });
2214
- actions.createNewCell({ cellId: cellId("1"), before: false });
2215
- actions.createNewCell({ cellId: cellId("2"), before: false });
1944
+ seedCell({ cellId: firstCellId, before: false });
1945
+ seedCell({ cellId: cellId("1"), before: false });
1946
+ seedCell({ cellId: cellId("2"), before: false });
2216
1947
  actions.addColumnBreakpoint({ cellId: cellId("2") });
2217
1948
 
2218
1949
  expect(state.cellIds.getColumns().length).toBe(2);
@@ -2256,8 +1987,8 @@ describe("cell reducer", () => {
2256
1987
  });
2257
1988
 
2258
1989
  it("deleting the last column does nothing", () => {
2259
- actions.createNewCell({ cellId: firstCellId, before: false });
2260
- actions.createNewCell({ cellId: cellId("1"), before: false });
1990
+ seedCell({ cellId: firstCellId, before: false });
1991
+ seedCell({ cellId: cellId("1"), before: false });
2261
1992
 
2262
1993
  const initialState = { ...state };
2263
1994
 
@@ -2268,9 +1999,9 @@ describe("cell reducer", () => {
2268
1999
  });
2269
2000
 
2270
2001
  it("can drop a cell over another cell", () => {
2271
- actions.createNewCell({ cellId: firstCellId, before: false });
2272
- actions.createNewCell({ cellId: cellId("1"), before: false });
2273
- actions.createNewCell({ cellId: cellId("2"), before: false });
2002
+ seedCell({ cellId: firstCellId, before: false });
2003
+ seedCell({ cellId: cellId("1"), before: false });
2004
+ seedCell({ cellId: cellId("2"), before: false });
2274
2005
 
2275
2006
  expect(formatCells(state)).toMatchInlineSnapshot(`
2276
2007
  "
@@ -2303,8 +2034,8 @@ describe("cell reducer", () => {
2303
2034
  });
2304
2035
 
2305
2036
  it("can drop a cell over a new column", () => {
2306
- actions.createNewCell({ cellId: firstCellId, before: false });
2307
- actions.createNewCell({ cellId: cellId("1"), before: false });
2037
+ seedCell({ cellId: firstCellId, before: false });
2038
+ seedCell({ cellId: cellId("1"), before: false });
2308
2039
 
2309
2040
  expect(state.cellIds.getColumns().length).toBe(1);
2310
2041
  expect(formatCells(state)).toMatchInlineSnapshot(`
@@ -2341,9 +2072,9 @@ describe("cell reducer", () => {
2341
2072
  });
2342
2073
 
2343
2074
  it("can drop a column over another column", () => {
2344
- actions.createNewCell({ cellId: firstCellId, before: false });
2345
- actions.createNewCell({ cellId: cellId("1"), before: false });
2346
- actions.createNewCell({ cellId: cellId("2"), before: false });
2075
+ seedCell({ cellId: firstCellId, before: false });
2076
+ seedCell({ cellId: cellId("1"), before: false });
2077
+ seedCell({ cellId: cellId("2"), before: false });
2347
2078
  actions.addColumnBreakpoint({ cellId: cellId("2") });
2348
2079
 
2349
2080
  expect(state.cellIds.getColumns().length).toBe(2);
@@ -2394,8 +2125,8 @@ describe("cell reducer", () => {
2394
2125
 
2395
2126
  it("can compact columns", () => {
2396
2127
  // Create initial state with 3 columns, including an empty one
2397
- actions.createNewCell({ cellId: firstCellId, before: false });
2398
- actions.createNewCell({ cellId: cellId("1"), before: false });
2128
+ seedCell({ cellId: firstCellId, before: false });
2129
+ seedCell({ cellId: cellId("1"), before: false });
2399
2130
  actions.addColumnBreakpoint({ cellId: cellId("1") });
2400
2131
  actions.addColumnBreakpoint({ cellId: cellId("2") });
2401
2132
  actions.dropOverNewColumn({ cellId: cellId("2") });
@@ -2534,7 +2265,7 @@ describe("cell reducer", () => {
2534
2265
 
2535
2266
  it("can clear output of all cells", () => {
2536
2267
  // Create multiple cells with output
2537
- actions.createNewCell({ cellId: firstCellId, before: false });
2268
+ seedCell({ cellId: firstCellId, before: false });
2538
2269
  const secondCellId = state.cellIds.atOrThrow(FIRST_COLUMN).atOrThrow(1);
2539
2270
 
2540
2271
  // Add output to both cells
@@ -2581,121 +2312,9 @@ describe("cell reducer", () => {
2581
2312
  expect(state.cellRuntime[secondCellId].consoleOutputs).toEqual([]);
2582
2313
  });
2583
2314
 
2584
- it("skips creating new cell if code exists and skipIfCodeExists is true", () => {
2585
- // Add initial cell with code
2586
- actions.updateCellCode({
2587
- cellId: firstCellId,
2588
- code: "import numpy as np",
2589
- formattingChange: false,
2590
- });
2591
-
2592
- // Try to create new cell with same code and skipIfCodeExists
2593
- actions.createNewCell({
2594
- cellId: "__end__",
2595
- code: "import numpy as np",
2596
- before: false,
2597
- skipIfCodeExists: true,
2598
- });
2599
-
2600
- // Should still only have one cell
2601
- expect(state.cellIds.inOrderIds.length).toBe(1);
2602
- expect(formatCells(state)).toMatchInlineSnapshot(`
2603
- "
2604
- [0] 'import numpy as np'
2605
- "
2606
- `);
2607
-
2608
- // Verify we can still add cell with different code
2609
- actions.createNewCell({
2610
- cellId: "__end__",
2611
- code: "import pandas as pd",
2612
- before: false,
2613
- skipIfCodeExists: true,
2614
- });
2615
-
2616
- expect(state.cellIds.inOrderIds.length).toBe(2);
2617
- expect(formatCells(state)).toMatchInlineSnapshot(`
2618
- "
2619
- [0] 'import numpy as np'
2620
-
2621
- [1] 'import pandas as pd'
2622
- "
2623
- `);
2624
- });
2625
-
2626
- it("can create and noop-update a setup cell", () => {
2627
- // Create the setup cell
2628
- actions.addSetupCellIfDoesntExist({ code: "# Setup code" });
2629
-
2630
- // Check that setup cell was created
2631
- expect(state.cellData[SETUP_CELL_ID].id).toBe(SETUP_CELL_ID);
2632
- expect(state.cellData[SETUP_CELL_ID].name).toBe("setup");
2633
- expect(state.cellData[SETUP_CELL_ID].code).toBe("# Setup code");
2634
- expect(state.cellData[SETUP_CELL_ID].edited).toBe(true);
2635
- expect(state.cellIds.inOrderIds).toContain(SETUP_CELL_ID);
2636
-
2637
- // Update the setup cell
2638
- actions.addSetupCellIfDoesntExist({ code: "# Updated setup code" });
2639
-
2640
- // Check that the setup cell did not change, since it already exists
2641
- expect(state.cellData[SETUP_CELL_ID].code).toBe("# Setup code");
2642
- expect(state.cellData[SETUP_CELL_ID].edited).toBe(true);
2643
- expect(state.cellIds.inOrderIds).toContain(SETUP_CELL_ID);
2644
- });
2645
-
2646
- it("can delete and undelete the setup cell", () => {
2647
- // Create the setup cell
2648
- actions.addSetupCellIfDoesntExist({ code: "# Setup code" });
2649
-
2650
- // Check that setup cell was created
2651
- expect(state.cellData[SETUP_CELL_ID].id).toBe(SETUP_CELL_ID);
2652
- expect(state.cellData[SETUP_CELL_ID].name).toBe("setup");
2653
- expect(state.cellData[SETUP_CELL_ID].code).toBe("# Setup code");
2654
- expect(state.cellData[SETUP_CELL_ID].edited).toBe(true);
2655
- expect(state.cellIds.inOrderIds).toContain(SETUP_CELL_ID);
2656
-
2657
- // Delete the setup cell
2658
- actions.deleteCell({ cellId: SETUP_CELL_ID });
2659
-
2660
- // Check that setup cell was deleted
2661
- expect(state.cellData[SETUP_CELL_ID]).toBeDefined(); // we keep old state
2662
- expect(state.cellIds.inOrderIds).not.toContain(SETUP_CELL_ID);
2663
-
2664
- // Undo delete the setup cell
2665
- actions.undoDeleteCell();
2666
-
2667
- // Check that setup cell was restored
2668
- expect(state.cellData[SETUP_CELL_ID].id).toBe(SETUP_CELL_ID);
2669
- expect(state.cellData[SETUP_CELL_ID].name).toBe("setup");
2670
- expect(state.cellData[SETUP_CELL_ID].code).toBe("# Setup code");
2671
- expect(state.cellData[SETUP_CELL_ID].edited).toBe(true);
2672
- expect(state.cellIds.inOrderIds).toContain(SETUP_CELL_ID);
2673
-
2674
- // Verify scrollKey is set to the restored setup cell
2675
- expect(state.scrollKey).toBe(SETUP_CELL_ID);
2676
- });
2677
-
2678
- it("can delete and then create a new setup cell", () => {
2679
- // Create the setup cell
2680
- actions.addSetupCellIfDoesntExist({ code: "# Setup code" });
2681
-
2682
- // Delete the setup cell
2683
- actions.deleteCell({ cellId: SETUP_CELL_ID });
2684
-
2685
- // Create a new setup cell
2686
- actions.addSetupCellIfDoesntExist({ code: "# New code" });
2687
-
2688
- // Check that the new setup cell was created
2689
- expect(state.cellData[SETUP_CELL_ID].id).toBe(SETUP_CELL_ID);
2690
- expect(state.cellData[SETUP_CELL_ID].name).toBe("setup");
2691
- expect(state.cellData[SETUP_CELL_ID].code).toBe("# New code");
2692
- expect(state.cellData[SETUP_CELL_ID].edited).toBe(true);
2693
- expect(state.cellIds.inOrderIds).toContain(SETUP_CELL_ID);
2694
- });
2695
-
2696
2315
  it("can clear all outputs", () => {
2697
2316
  // Add a cell and give it output
2698
- actions.createNewCell({
2317
+ seedCell({
2699
2318
  cellId: firstCellId,
2700
2319
  before: false,
2701
2320
  });
@@ -2723,202 +2342,14 @@ describe("cell reducer", () => {
2723
2342
  expect(state.cellRuntime[cell2Id].output).toBeNull();
2724
2343
  });
2725
2344
 
2726
- describe("moveToNextCell", () => {
2727
- let cell1Id: CellId;
2728
- let cell2Id: CellId;
2729
- let cell3Id: CellId;
2730
-
2731
- beforeEach(() => {
2732
- // Create a few cells to work with
2733
- actions.createNewCell({ cellId: "__end__", before: false });
2734
- actions.createNewCell({ cellId: "__end__", before: false });
2735
-
2736
- cell1Id = state.cellIds.inOrderIds[0];
2737
- cell2Id = state.cellIds.inOrderIds[1];
2738
- cell3Id = state.cellIds.inOrderIds[2];
2739
- });
2740
-
2741
- it("creates new cell when moving after last cell with noCreate=false", () => {
2742
- const initialCellCount = state.cellIds.inOrderIds.length;
2743
-
2744
- actions.moveToNextCell({
2745
- cellId: cell3Id,
2746
- before: false,
2747
- noCreate: false,
2748
- });
2749
-
2750
- expect(state.cellIds.inOrderIds.length).toBe(initialCellCount + 1);
2751
- });
2752
-
2753
- it("creates new cell when moving before first cell with noCreate=false", () => {
2754
- const initialCellCount = state.cellIds.inOrderIds.length;
2755
-
2756
- actions.moveToNextCell({
2757
- cellId: cell1Id,
2758
- before: true,
2759
- noCreate: false,
2760
- });
2761
-
2762
- expect(state.cellIds.inOrderIds.length).toBe(initialCellCount + 1);
2763
- });
2764
-
2765
- it("does not create new cell when moving after last cell with noCreate=true", () => {
2766
- const initialCellCount = state.cellIds.inOrderIds.length;
2767
- const initialState = { ...state };
2768
-
2769
- actions.moveToNextCell({
2770
- cellId: cell3Id,
2771
- before: false,
2772
- noCreate: true,
2773
- });
2774
-
2775
- // Should not create a new cell
2776
- expect(state.cellIds.inOrderIds.length).toBe(initialCellCount);
2777
- // Should not crash or throw an error
2778
- expect(state.cellIds.inOrderIds).toEqual(initialState.cellIds.inOrderIds);
2779
- });
2780
-
2781
- it("does not create new cell when moving before first cell with noCreate=true", () => {
2782
- const initialCellCount = state.cellIds.inOrderIds.length;
2783
- const initialState = { ...state };
2784
-
2785
- actions.moveToNextCell({ cellId: cell1Id, before: true, noCreate: true });
2786
-
2787
- // Should not create a new cell
2788
- expect(state.cellIds.inOrderIds.length).toBe(initialCellCount);
2789
- // Should not crash or throw an error
2790
- expect(state.cellIds.inOrderIds).toEqual(initialState.cellIds.inOrderIds);
2791
- });
2792
-
2793
- it("focuses next cell when moving within bounds", () => {
2794
- const focusSpy = vi.mocked(focusAndScrollCellIntoView);
2795
- focusSpy.mockClear();
2796
-
2797
- actions.moveToNextCell({
2798
- cellId: cell1Id,
2799
- before: false,
2800
- noCreate: true,
2801
- });
2802
-
2803
- expect(focusSpy).toHaveBeenCalledWith({
2804
- cellId: cell2Id,
2805
- cell: state.cellHandles[cell2Id],
2806
- isCodeHidden: false,
2807
- codeFocus: "top",
2808
- variableName: undefined,
2809
- });
2810
- });
2811
-
2812
- it("focuses previous cell when moving backward within bounds", () => {
2813
- const focusSpy = vi.mocked(focusAndScrollCellIntoView);
2814
- focusSpy.mockClear();
2815
-
2816
- actions.moveToNextCell({ cellId: cell2Id, before: true, noCreate: true });
2817
-
2818
- expect(focusSpy).toHaveBeenCalledWith({
2819
- cellId: cell1Id,
2820
- cell: state.cellHandles[cell1Id],
2821
- isCodeHidden: false,
2822
- codeFocus: "bottom",
2823
- variableName: undefined,
2824
- });
2825
- });
2826
-
2827
- it("does not move focus from scratch cell", () => {
2828
- const focusSpy = vi.mocked(focusAndScrollCellIntoView);
2829
- focusSpy.mockClear();
2830
- const initialState = { ...state };
2831
-
2832
- actions.moveToNextCell({
2833
- cellId: SCRATCH_CELL_ID,
2834
- before: false,
2835
- noCreate: false,
2836
- });
2837
-
2838
- expect(focusSpy).not.toHaveBeenCalled();
2839
- expect(state.cellIds.inOrderIds).toEqual(initialState.cellIds.inOrderIds);
2840
- });
2841
- });
2842
-
2843
2345
  describe("untouched cells functionality", () => {
2844
2346
  it("starts with empty untouchedNewCells set", () => {
2845
2347
  expect(state.untouchedNewCells).toEqual(new Set());
2846
2348
  });
2847
2349
 
2848
- it("can create a new cell with hideCode option", () => {
2849
- const initialCellCount = state.cellIds.inOrderIds.length;
2850
-
2851
- actions.createNewCell({
2852
- cellId: "__end__",
2853
- before: false,
2854
- hideCode: true,
2855
- });
2856
-
2857
- expect(state.cellIds.inOrderIds.length).toBe(initialCellCount + 1);
2858
- const newCellId =
2859
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
2860
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
2861
- });
2862
-
2863
- it("adds interactively-created hidden cell with boilerplate code to untouchedNewCells", () => {
2864
- // Markdown cells are created with hideCode and non-empty default code
2865
- // (e.g. `mo.md(r"""\n""")`). They are user-initiated, so their editor
2866
- // should be shown until first blur.
2867
- actions.createNewCell({
2868
- cellId: "__end__",
2869
- before: false,
2870
- code: 'mo.md(r"""\n""")',
2871
- hideCode: true,
2872
- autoFocus: true,
2873
- });
2874
-
2875
- const newCellId =
2876
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
2877
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
2878
- });
2879
-
2880
- it("does not add programmatically-created hidden cell with code to untouchedNewCells", () => {
2881
- // Cells created by the kernel (e.g. via code_mode) carry code and
2882
- // autoFocus=false; their hide_code must take effect immediately.
2883
- actions.createNewCell({
2884
- cellId: "__end__",
2885
- before: false,
2886
- code: "x = 1",
2887
- hideCode: true,
2888
- autoFocus: false,
2889
- });
2890
-
2891
- const newCellId =
2892
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
2893
- expect(state.untouchedNewCells.has(newCellId)).toBe(false);
2894
- });
2895
-
2896
- it("does not add cell to untouchedNewCells when hideCode is false", () => {
2897
- actions.createNewCell({
2898
- cellId: "__end__",
2899
- before: false,
2900
- hideCode: false,
2901
- });
2902
-
2903
- const newCellId =
2904
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
2905
- expect(state.untouchedNewCells.has(newCellId)).toBe(false);
2906
- });
2907
-
2908
- it("does not add cell to untouchedNewCells when hideCode is undefined", () => {
2909
- actions.createNewCell({
2910
- cellId: "__end__",
2911
- before: false,
2912
- });
2913
-
2914
- const newCellId =
2915
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
2916
- expect(state.untouchedNewCells.has(newCellId)).toBe(false);
2917
- });
2918
-
2919
2350
  it("can mark a cell as touched", () => {
2920
2351
  // Create a cell with hideCode
2921
- actions.createNewCell({
2352
+ seedCell({
2922
2353
  cellId: "__end__",
2923
2354
  before: false,
2924
2355
  hideCode: true,
@@ -2936,7 +2367,7 @@ describe("cell reducer", () => {
2936
2367
 
2937
2368
  it("markTouched is idempotent", () => {
2938
2369
  // Create a cell with hideCode
2939
- actions.createNewCell({
2370
+ seedCell({
2940
2371
  cellId: "__end__",
2941
2372
  before: false,
2942
2373
  hideCode: true,
@@ -2966,17 +2397,17 @@ describe("cell reducer", () => {
2966
2397
 
2967
2398
  it("can handle multiple untouched cells", () => {
2968
2399
  // Create multiple cells with hideCode
2969
- actions.createNewCell({
2400
+ seedCell({
2970
2401
  cellId: "__end__",
2971
2402
  before: false,
2972
2403
  hideCode: true,
2973
2404
  });
2974
- actions.createNewCell({
2405
+ seedCell({
2975
2406
  cellId: "__end__",
2976
2407
  before: false,
2977
2408
  hideCode: true,
2978
2409
  });
2979
- actions.createNewCell({
2410
+ seedCell({
2980
2411
  cellId: "__end__",
2981
2412
  before: false,
2982
2413
  hideCode: false,
@@ -3001,12 +2432,12 @@ describe("cell reducer", () => {
3001
2432
 
3002
2433
  it("preserves untouched state when cells are moved", () => {
3003
2434
  // Create cells with hideCode
3004
- actions.createNewCell({
2435
+ seedCell({
3005
2436
  cellId: "__end__",
3006
2437
  before: false,
3007
2438
  hideCode: true,
3008
2439
  });
3009
- actions.createNewCell({
2440
+ seedCell({
3010
2441
  cellId: "__end__",
3011
2442
  before: false,
3012
2443
  hideCode: true,
@@ -3030,58 +2461,9 @@ describe("cell reducer", () => {
3030
2461
  expect(state.untouchedNewCells.has(cell2Id)).toBe(true);
3031
2462
  });
3032
2463
 
3033
- it("does not remove untouched state when cell is deleted", () => {
3034
- // We could implement this, but it doesn't actually affect downstream behavior.
3035
- // It is easier to leave it, so `undo` works as expected.
3036
-
3037
- // Create a cell with hideCode
3038
- actions.createNewCell({
3039
- cellId: "__end__",
3040
- before: false,
3041
- hideCode: true,
3042
- });
3043
-
3044
- const newCellId =
3045
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3046
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3047
-
3048
- // Delete the cell
3049
- actions.deleteCell({ cellId: newCellId });
3050
-
3051
- // Does not actually remove untouched state
3052
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3053
- });
3054
-
3055
- it("restores untouched state when cell deletion is undone", () => {
3056
- // Create a cell with hideCode
3057
- actions.createNewCell({
3058
- cellId: "__end__",
3059
- before: false,
3060
- hideCode: true,
3061
- });
3062
-
3063
- const newCellId =
3064
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3065
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3066
-
3067
- // Delete the cell
3068
- actions.deleteCell({ cellId: newCellId });
3069
- // Still exists in untouchedNewCells
3070
- expect(state.untouchedNewCells.has(newCellId)).toBe(true);
3071
-
3072
- // Undo the deletion
3073
- actions.undoDeleteCell();
3074
-
3075
- // The cell should be restored but no longer untouched
3076
- // (this is expected behavior - undoing doesn't restore untouched state)
3077
- const restoredCellId =
3078
- state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3079
- expect(state.untouchedNewCells.has(restoredCellId)).toBe(false);
3080
- });
3081
-
3082
2464
  it("untouched cell behavior with hide_code config", () => {
3083
2465
  // Create a cell with hideCode and hide_code config
3084
- actions.createNewCell({
2466
+ seedCell({
3085
2467
  cellId: "__end__",
3086
2468
  before: false,
3087
2469
  hideCode: true,
@@ -3090,11 +2472,11 @@ describe("cell reducer", () => {
3090
2472
  const newCellId =
3091
2473
  state.cellIds.inOrderIds[state.cellIds.inOrderIds.length - 1];
3092
2474
 
3093
- // Update the cell config to hide code
3094
- actions.updateCellConfig({
3095
- cellId: newCellId,
3096
- config: { hide_code: true },
3097
- });
2475
+ // The document hides the cell's code
2476
+ state.cellData[newCellId] = {
2477
+ ...state.cellData[newCellId],
2478
+ config: { ...state.cellData[newCellId].config, hide_code: true },
2479
+ };
3098
2480
 
3099
2481
  // Cell should be untouched and have hide_code config
3100
2482
  expect(state.untouchedNewCells.has(newCellId)).toBe(true);
@@ -3113,7 +2495,7 @@ describe("cell reducer", () => {
3113
2495
 
3114
2496
  it("can mark an existing cell as untouched", () => {
3115
2497
  // Create a cell without hideCode (not in untouchedNewCells)
3116
- actions.createNewCell({
2498
+ seedCell({
3117
2499
  cellId: "__end__",
3118
2500
  before: false,
3119
2501
  hideCode: false,
@@ -3131,7 +2513,7 @@ describe("cell reducer", () => {
3131
2513
 
3132
2514
  it("markUntouched is idempotent", () => {
3133
2515
  // Create a cell without hideCode
3134
- actions.createNewCell({
2516
+ seedCell({
3135
2517
  cellId: "__end__",
3136
2518
  before: false,
3137
2519
  hideCode: false,
@@ -3151,7 +2533,7 @@ describe("cell reducer", () => {
3151
2533
 
3152
2534
  it("markUntouched does not affect already untouched cells", () => {
3153
2535
  // Create a cell with hideCode (already in untouchedNewCells)
3154
- actions.createNewCell({
2536
+ seedCell({
3155
2537
  cellId: "__end__",
3156
2538
  before: false,
3157
2539
  hideCode: true,
@@ -3169,7 +2551,7 @@ describe("cell reducer", () => {
3169
2551
 
3170
2552
  it("markTouched and markUntouched can toggle cell state", () => {
3171
2553
  // Create a cell without hideCode
3172
- actions.createNewCell({
2554
+ seedCell({
3173
2555
  cellId: "__end__",
3174
2556
  before: false,
3175
2557
  hideCode: false,
@@ -3197,7 +2579,7 @@ describe("cell reducer", () => {
3197
2579
  it("markUntouched works for markdown cell conversion scenario", () => {
3198
2580
  // Simulates converting a Python cell to Markdown
3199
2581
  // 1. Create a regular cell (no hideCode)
3200
- actions.createNewCell({
2582
+ seedCell({
3201
2583
  cellId: "__end__",
3202
2584
  before: false,
3203
2585
  hideCode: false,
@@ -3211,11 +2593,12 @@ describe("cell reducer", () => {
3211
2593
  expect(state.untouchedNewCells.has(cellId)).toBe(false);
3212
2594
  expect(exportedForTesting.isCellCodeHidden(state, cellId)).toBe(false);
3213
2595
 
3214
- // 2. Convert to markdown: set hide_code and mark as untouched
3215
- actions.updateCellConfig({
3216
- cellId,
3217
- config: { hide_code: true },
3218
- });
2596
+ // 2. Convert to markdown: the document hides the code, and the cell
2597
+ // is marked untouched
2598
+ state.cellData[cellId] = {
2599
+ ...state.cellData[cellId],
2600
+ config: { ...state.cellData[cellId].config, hide_code: true },
2601
+ };
3219
2602
  actions.markUntouched({ cellId });
3220
2603
 
3221
2604
  // Code should NOT be hidden because cell is untouched (user can edit)
@@ -3237,7 +2620,7 @@ describe("cell reducer", () => {
3237
2620
  const { cellDataAtom, cellRuntimeAtom, cellHandleAtom } =
3238
2621
  exportedForTesting;
3239
2622
 
3240
- actions.createNewCell({ cellId: firstCellId, before: false });
2623
+ seedCell({ cellId: firstCellId, before: false });
3241
2624
  const newCellId = state.cellIds.inOrderIds[1];
3242
2625
 
3243
2626
  const dataAtom1 = cellDataAtom(newCellId);
@@ -3253,31 +2636,6 @@ describe("cell reducer", () => {
3253
2636
  expect(runtimeAtom2).toBe(runtimeAtom1);
3254
2637
  expect(handleAtom2).toBe(handleAtom1);
3255
2638
  });
3256
-
3257
- it("cleans up atom family cache when cells are deleted", () => {
3258
- const { cellDataAtom, cellRuntimeAtom, cellHandleAtom } =
3259
- exportedForTesting;
3260
-
3261
- actions.createNewCell({ cellId: firstCellId, before: false });
3262
- const newCellId = state.cellIds.inOrderIds[1];
3263
-
3264
- // Access to create cache entries
3265
- const dataAtom1 = cellDataAtom(newCellId);
3266
- const runtimeAtom1 = cellRuntimeAtom(newCellId);
3267
- const handleAtom1 = cellHandleAtom(newCellId);
3268
-
3269
- // Triggers purge
3270
- actions.deleteCell({ cellId: newCellId });
3271
-
3272
- // Access again (should be new instances after cleanup)
3273
- const dataAtom2 = cellDataAtom(newCellId);
3274
- const runtimeAtom2 = cellRuntimeAtom(newCellId);
3275
- const handleAtom2 = cellHandleAtom(newCellId);
3276
-
3277
- expect(dataAtom2).not.toBe(dataAtom1);
3278
- expect(runtimeAtom2).not.toBe(runtimeAtom1);
3279
- expect(handleAtom2).not.toBe(handleAtom1);
3280
- });
3281
2639
  });
3282
2640
  });
3283
2641