@marimo-team/islands 0.21.2-dev57 → 0.21.2-dev59
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.
- package/dist/{ConnectedDataExplorerComponent-D0GoOd_c.js → ConnectedDataExplorerComponent-DrWDbHRV.js} +1 -1
- package/dist/main.js +243 -16
- package/dist/{spec-Bfvf9Hre.js → spec-oVDndBz4.js} +25 -16
- package/package.json +1 -1
- package/src/__mocks__/requests.ts +1 -0
- package/src/components/editor/file-tree/file-explorer.tsx +16 -2
- package/src/components/editor/file-tree/file-viewer.tsx +17 -3
- package/src/core/cells/__tests__/apply-transaction.test.ts +279 -0
- package/src/core/cells/__tests__/cells.test.ts +6 -0
- package/src/core/cells/__tests__/document-changes.test.ts +575 -0
- package/src/core/cells/__tests__/document-roundtrip.test.ts +376 -0
- package/src/core/cells/cells.ts +28 -3
- package/src/core/cells/document-changes.ts +644 -0
- package/src/core/islands/bridge.ts +1 -0
- package/src/core/islands/main.ts +2 -0
- package/src/core/network/requests-lazy.ts +1 -0
- package/src/core/network/requests-network.ts +9 -0
- package/src/core/network/requests-static.ts +1 -0
- package/src/core/network/requests-toasting.tsx +1 -0
- package/src/core/network/types.ts +5 -0
- package/src/core/wasm/bridge.ts +1 -0
- package/src/core/websocket/useMarimoKernelConnection.tsx +19 -1
- package/src/utils/createReducer.ts +26 -11
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Tests for toDocumentChanges (action → change mapping) as a pure function.
|
|
5
|
+
*
|
|
6
|
+
* Each test calls the reducer to produce prevState/newState, then calls
|
|
7
|
+
* toDocumentChanges directly — no middleware, no side effects. Basic round-trip
|
|
8
|
+
* correctness is covered by document-roundtrip.test.ts. This file focuses on:
|
|
9
|
+
*
|
|
10
|
+
* - anchorOf edge cases (before vs after, first cell, __end__)
|
|
11
|
+
* - Field name mapping (hide_code → hideCode)
|
|
12
|
+
* - Column structure actions emitting reorder-cells
|
|
13
|
+
* - Actions that should NOT emit changes
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { python } from "@codemirror/lang-python";
|
|
17
|
+
import { EditorState } from "@codemirror/state";
|
|
18
|
+
import { EditorView } from "@codemirror/view";
|
|
19
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
|
20
|
+
import { cellId } from "@/__tests__/branded";
|
|
21
|
+
import type { CellHandle } from "@/components/editor/notebook-cell";
|
|
22
|
+
import { adaptiveLanguageConfiguration } from "@/core/codemirror/language/extension";
|
|
23
|
+
import { OverridingHotkeyProvider } from "@/core/hotkeys/hotkeys";
|
|
24
|
+
import { MultiColumn } from "@/utils/id-tree";
|
|
25
|
+
import { exportedForTesting, type NotebookState } from "../cells";
|
|
26
|
+
import { type CellAction, toDocumentChanges } from "../document-changes";
|
|
27
|
+
import { CellId } from "../ids";
|
|
28
|
+
|
|
29
|
+
const { initialNotebookState, reducer } = exportedForTesting;
|
|
30
|
+
|
|
31
|
+
function createEditor(code: string) {
|
|
32
|
+
const state = EditorState.create({
|
|
33
|
+
doc: code,
|
|
34
|
+
extensions: [
|
|
35
|
+
python(),
|
|
36
|
+
adaptiveLanguageConfiguration({
|
|
37
|
+
cellId: cellId("cell1"),
|
|
38
|
+
completionConfig: {
|
|
39
|
+
activate_on_typing: true,
|
|
40
|
+
signature_hint_on_typing: false,
|
|
41
|
+
copilot: false,
|
|
42
|
+
codeium_api_key: null,
|
|
43
|
+
},
|
|
44
|
+
hotkeys: new OverridingHotkeyProvider({}),
|
|
45
|
+
placeholderType: "marimo-import",
|
|
46
|
+
lspConfig: {},
|
|
47
|
+
}),
|
|
48
|
+
],
|
|
49
|
+
});
|
|
50
|
+
return new EditorView({ state, parent: document.body });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Dispatch an action through the reducer and auto-create editor handles. */
|
|
54
|
+
function dispatch(
|
|
55
|
+
state: NotebookState,
|
|
56
|
+
action: { type: string; payload: unknown },
|
|
57
|
+
): NotebookState {
|
|
58
|
+
const next = reducer(state, action);
|
|
59
|
+
for (const [cellIdString, handle] of Object.entries(next.cellHandles)) {
|
|
60
|
+
const cid = cellIdString as CellId;
|
|
61
|
+
if (!handle.current) {
|
|
62
|
+
const view = createEditor(next.cellData[cid].code);
|
|
63
|
+
const h: CellHandle = { editorView: view, editorViewOrNull: view };
|
|
64
|
+
next.cellHandles[cid] = { current: h };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return next;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Dispatch an action and return the changes it produces. */
|
|
71
|
+
function resolve(state: NotebookState, action: CellAction) {
|
|
72
|
+
const next = dispatch(state, action);
|
|
73
|
+
const changes = toDocumentChanges(state, next, action);
|
|
74
|
+
return { next, changes };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let state: NotebookState;
|
|
78
|
+
|
|
79
|
+
let i = 0;
|
|
80
|
+
const originalCreate = CellId.create.bind(CellId);
|
|
81
|
+
|
|
82
|
+
beforeAll(() => {
|
|
83
|
+
CellId.create = () => cellId(`${i++}`);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
beforeEach(() => {
|
|
87
|
+
i = 0;
|
|
88
|
+
state = initialNotebookState();
|
|
89
|
+
state.cellIds = MultiColumn.from([]);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
afterAll(() => {
|
|
93
|
+
CellId.create = originalCreate;
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
function setup(...codes: string[]) {
|
|
97
|
+
for (const code of codes) {
|
|
98
|
+
state = dispatch(state, {
|
|
99
|
+
type: "createNewCell",
|
|
100
|
+
payload: {
|
|
101
|
+
cellId: "__end__",
|
|
102
|
+
before: false,
|
|
103
|
+
code,
|
|
104
|
+
newCellId: CellId.create(),
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
describe("toDocumentChanges", () => {
|
|
111
|
+
describe("anchorOf edge cases", () => {
|
|
112
|
+
it("uses before anchor when new cell is first", () => {
|
|
113
|
+
setup("a");
|
|
114
|
+
const [a] = state.cellIds.inOrderIds;
|
|
115
|
+
|
|
116
|
+
const { changes } = resolve(state, {
|
|
117
|
+
type: "createNewCell",
|
|
118
|
+
payload: {
|
|
119
|
+
cellId: a,
|
|
120
|
+
before: true,
|
|
121
|
+
code: "first",
|
|
122
|
+
newCellId: CellId.create(),
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
expect(changes).toMatchInlineSnapshot(`
|
|
127
|
+
[
|
|
128
|
+
{
|
|
129
|
+
"before": "0",
|
|
130
|
+
"cellId": "1",
|
|
131
|
+
"code": "first",
|
|
132
|
+
"config": {
|
|
133
|
+
"column": null,
|
|
134
|
+
"disabled": false,
|
|
135
|
+
"hide_code": false,
|
|
136
|
+
},
|
|
137
|
+
"name": "_",
|
|
138
|
+
"type": "create-cell",
|
|
139
|
+
},
|
|
140
|
+
]
|
|
141
|
+
`);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("uses after anchor for __end__ insertion", () => {
|
|
145
|
+
setup("a");
|
|
146
|
+
const { changes } = resolve(state, {
|
|
147
|
+
type: "createNewCell",
|
|
148
|
+
payload: {
|
|
149
|
+
cellId: "__end__",
|
|
150
|
+
before: false,
|
|
151
|
+
code: "last",
|
|
152
|
+
newCellId: CellId.create(),
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
expect(changes).toMatchInlineSnapshot(`
|
|
157
|
+
[
|
|
158
|
+
{
|
|
159
|
+
"after": "0",
|
|
160
|
+
"cellId": "1",
|
|
161
|
+
"code": "last",
|
|
162
|
+
"config": {
|
|
163
|
+
"column": null,
|
|
164
|
+
"disabled": false,
|
|
165
|
+
"hide_code": false,
|
|
166
|
+
},
|
|
167
|
+
"name": "_",
|
|
168
|
+
"type": "create-cell",
|
|
169
|
+
},
|
|
170
|
+
]
|
|
171
|
+
`);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("uses before when sendToTop moves cell to first position", () => {
|
|
175
|
+
setup("a", "b", "c");
|
|
176
|
+
const [, b] = state.cellIds.inOrderIds;
|
|
177
|
+
|
|
178
|
+
const { changes } = resolve(state, {
|
|
179
|
+
type: "sendToTop",
|
|
180
|
+
payload: { cellId: b },
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
expect(changes).toMatchInlineSnapshot(`
|
|
184
|
+
[
|
|
185
|
+
{
|
|
186
|
+
"before": "0",
|
|
187
|
+
"cellId": "1",
|
|
188
|
+
"type": "move-cell",
|
|
189
|
+
},
|
|
190
|
+
]
|
|
191
|
+
`);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
describe("field name mapping", () => {
|
|
196
|
+
it("maps hide_code to hideCode in set-config", () => {
|
|
197
|
+
setup("a");
|
|
198
|
+
const [a] = state.cellIds.inOrderIds;
|
|
199
|
+
|
|
200
|
+
const { changes } = resolve(state, {
|
|
201
|
+
type: "updateCellConfig",
|
|
202
|
+
payload: { cellId: a, config: { hide_code: true } },
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
expect(changes).toMatchInlineSnapshot(`
|
|
206
|
+
[
|
|
207
|
+
{
|
|
208
|
+
"cellId": "0",
|
|
209
|
+
"hideCode": true,
|
|
210
|
+
"type": "set-config",
|
|
211
|
+
},
|
|
212
|
+
]
|
|
213
|
+
`);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it("includes full CellConfig in create-cell", () => {
|
|
217
|
+
setup("a");
|
|
218
|
+
|
|
219
|
+
const { changes } = resolve(state, {
|
|
220
|
+
type: "createNewCell",
|
|
221
|
+
payload: {
|
|
222
|
+
cellId: "__end__",
|
|
223
|
+
before: false,
|
|
224
|
+
code: "hidden",
|
|
225
|
+
newCellId: CellId.create(),
|
|
226
|
+
hideCode: true,
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
expect(changes[0]).toMatchObject({
|
|
231
|
+
type: "create-cell",
|
|
232
|
+
config: { hide_code: true },
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
describe("column structure actions", () => {
|
|
238
|
+
it("dropOverNewColumn emits set-config + reorder-cells", () => {
|
|
239
|
+
setup("a", "b");
|
|
240
|
+
const [, b] = state.cellIds.inOrderIds;
|
|
241
|
+
|
|
242
|
+
const { changes } = resolve(state, {
|
|
243
|
+
type: "dropOverNewColumn",
|
|
244
|
+
payload: { cellId: b },
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
expect(changes).toMatchInlineSnapshot(`
|
|
248
|
+
[
|
|
249
|
+
{
|
|
250
|
+
"cellId": "1",
|
|
251
|
+
"column": 1,
|
|
252
|
+
"type": "set-config",
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
"cellIds": [
|
|
256
|
+
"0",
|
|
257
|
+
"1",
|
|
258
|
+
],
|
|
259
|
+
"type": "reorder-cells",
|
|
260
|
+
},
|
|
261
|
+
]
|
|
262
|
+
`);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it("addColumnBreakpoint emits set-config + reorder-cells", () => {
|
|
266
|
+
setup("a", "b", "c");
|
|
267
|
+
const [, b] = state.cellIds.inOrderIds;
|
|
268
|
+
|
|
269
|
+
const { changes } = resolve(state, {
|
|
270
|
+
type: "addColumnBreakpoint",
|
|
271
|
+
payload: { cellId: b },
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
expect(changes).toMatchInlineSnapshot(`
|
|
275
|
+
[
|
|
276
|
+
{
|
|
277
|
+
"cellId": "1",
|
|
278
|
+
"column": 1,
|
|
279
|
+
"type": "set-config",
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
"cellId": "2",
|
|
283
|
+
"column": 1,
|
|
284
|
+
"type": "set-config",
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
"cellIds": [
|
|
288
|
+
"0",
|
|
289
|
+
"1",
|
|
290
|
+
"2",
|
|
291
|
+
],
|
|
292
|
+
"type": "reorder-cells",
|
|
293
|
+
},
|
|
294
|
+
]
|
|
295
|
+
`);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it("mergeAllColumns emits set-config + reorder-cells", () => {
|
|
299
|
+
setup("a", "b", "c");
|
|
300
|
+
const [, b] = state.cellIds.inOrderIds;
|
|
301
|
+
state = dispatch(state, {
|
|
302
|
+
type: "addColumnBreakpoint",
|
|
303
|
+
payload: { cellId: b },
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
const { changes } = resolve(state, {
|
|
307
|
+
type: "mergeAllColumns",
|
|
308
|
+
payload: {},
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
expect(changes).toMatchInlineSnapshot(`
|
|
312
|
+
[
|
|
313
|
+
{
|
|
314
|
+
"cellId": "1",
|
|
315
|
+
"column": 0,
|
|
316
|
+
"type": "set-config",
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
"cellId": "2",
|
|
320
|
+
"column": 0,
|
|
321
|
+
"type": "set-config",
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
"cellIds": [
|
|
325
|
+
"0",
|
|
326
|
+
"1",
|
|
327
|
+
"2",
|
|
328
|
+
],
|
|
329
|
+
"type": "reorder-cells",
|
|
330
|
+
},
|
|
331
|
+
]
|
|
332
|
+
`);
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
describe("cell lifecycle actions", () => {
|
|
337
|
+
it("addColumn emits create-cell + column layout changes", () => {
|
|
338
|
+
setup("a");
|
|
339
|
+
const [a] = state.cellIds.inOrderIds;
|
|
340
|
+
const columnId = state.cellIds.findWithId(a).id;
|
|
341
|
+
|
|
342
|
+
const { changes } = resolve(state, {
|
|
343
|
+
type: "addColumn",
|
|
344
|
+
payload: { columnId },
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
expect(changes).toMatchInlineSnapshot(`
|
|
348
|
+
[
|
|
349
|
+
{
|
|
350
|
+
"after": "0",
|
|
351
|
+
"cellId": "1",
|
|
352
|
+
"code": "",
|
|
353
|
+
"config": {
|
|
354
|
+
"column": null,
|
|
355
|
+
"disabled": false,
|
|
356
|
+
"hide_code": false,
|
|
357
|
+
},
|
|
358
|
+
"name": "_",
|
|
359
|
+
"type": "create-cell",
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
"cellId": "1",
|
|
363
|
+
"column": 1,
|
|
364
|
+
"type": "set-config",
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
"cellIds": [
|
|
368
|
+
"0",
|
|
369
|
+
"1",
|
|
370
|
+
],
|
|
371
|
+
"type": "reorder-cells",
|
|
372
|
+
},
|
|
373
|
+
]
|
|
374
|
+
`);
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
it("undoDeleteCell emits create-cell for restored cell", () => {
|
|
378
|
+
setup("a", "b");
|
|
379
|
+
const [, b] = state.cellIds.inOrderIds;
|
|
380
|
+
|
|
381
|
+
// Delete cell b, then undo
|
|
382
|
+
state = dispatch(state, {
|
|
383
|
+
type: "deleteCell",
|
|
384
|
+
payload: { cellId: b },
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
const { changes } = resolve(state, {
|
|
388
|
+
type: "undoDeleteCell",
|
|
389
|
+
payload: {},
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// Restored cell should have original code
|
|
393
|
+
expect(changes[0]).toMatchObject({
|
|
394
|
+
type: "create-cell",
|
|
395
|
+
code: "b",
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it("splitCell emits set-code + create-cell", () => {
|
|
400
|
+
setup("line1\nline2");
|
|
401
|
+
const [a] = state.cellIds.inOrderIds;
|
|
402
|
+
|
|
403
|
+
// Position cursor at end of "line1" (position 5)
|
|
404
|
+
const view = state.cellHandles[a].current!.editorView;
|
|
405
|
+
view.dispatch({ selection: { anchor: 5 } });
|
|
406
|
+
|
|
407
|
+
const { changes } = resolve(state, {
|
|
408
|
+
type: "splitCell",
|
|
409
|
+
payload: { cellId: a },
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
expect(changes).toMatchInlineSnapshot(`
|
|
413
|
+
[
|
|
414
|
+
{
|
|
415
|
+
"cellId": "0",
|
|
416
|
+
"code": "line1",
|
|
417
|
+
"type": "set-code",
|
|
418
|
+
},
|
|
419
|
+
{
|
|
420
|
+
"after": "0",
|
|
421
|
+
"cellId": "1",
|
|
422
|
+
"code": "line2",
|
|
423
|
+
"config": {
|
|
424
|
+
"column": null,
|
|
425
|
+
"disabled": false,
|
|
426
|
+
"hide_code": false,
|
|
427
|
+
},
|
|
428
|
+
"name": "_",
|
|
429
|
+
"type": "create-cell",
|
|
430
|
+
},
|
|
431
|
+
]
|
|
432
|
+
`);
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
it("undoSplitCell emits set-code + delete-cell", () => {
|
|
436
|
+
setup("line1\nline2");
|
|
437
|
+
const [a] = state.cellIds.inOrderIds;
|
|
438
|
+
|
|
439
|
+
// Split the cell first
|
|
440
|
+
const view = state.cellHandles[a].current!.editorView;
|
|
441
|
+
view.dispatch({ selection: { anchor: 5 } });
|
|
442
|
+
const snapshot = view.state.doc.toString();
|
|
443
|
+
state = dispatch(state, {
|
|
444
|
+
type: "splitCell",
|
|
445
|
+
payload: { cellId: a },
|
|
446
|
+
});
|
|
447
|
+
const [, newCell] = state.cellIds.inOrderIds;
|
|
448
|
+
|
|
449
|
+
// Now undo the split
|
|
450
|
+
const { changes } = resolve(state, {
|
|
451
|
+
type: "undoSplitCell",
|
|
452
|
+
payload: { cellId: a, snapshot },
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
expect(changes).toMatchInlineSnapshot(`
|
|
456
|
+
[
|
|
457
|
+
{
|
|
458
|
+
"cellId": "0",
|
|
459
|
+
"code": "line1
|
|
460
|
+
line2",
|
|
461
|
+
"type": "set-code",
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
"cellId": "${newCell}",
|
|
465
|
+
"type": "delete-cell",
|
|
466
|
+
},
|
|
467
|
+
]
|
|
468
|
+
`);
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
it("moveToNextCell emits create-cell when past last cell", () => {
|
|
472
|
+
setup("a");
|
|
473
|
+
const [a] = state.cellIds.inOrderIds;
|
|
474
|
+
|
|
475
|
+
const { changes } = resolve(state, {
|
|
476
|
+
type: "moveToNextCell",
|
|
477
|
+
payload: { cellId: a, before: false },
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
expect(changes).toMatchInlineSnapshot(`
|
|
481
|
+
[
|
|
482
|
+
{
|
|
483
|
+
"after": "0",
|
|
484
|
+
"cellId": "1",
|
|
485
|
+
"code": "",
|
|
486
|
+
"config": {
|
|
487
|
+
"column": null,
|
|
488
|
+
"disabled": false,
|
|
489
|
+
"hide_code": false,
|
|
490
|
+
},
|
|
491
|
+
"name": "_",
|
|
492
|
+
"type": "create-cell",
|
|
493
|
+
},
|
|
494
|
+
]
|
|
495
|
+
`);
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
it("moveToNextCell emits nothing when moving within bounds", () => {
|
|
499
|
+
setup("a", "b");
|
|
500
|
+
const [a] = state.cellIds.inOrderIds;
|
|
501
|
+
|
|
502
|
+
const { changes } = resolve(state, {
|
|
503
|
+
type: "moveToNextCell",
|
|
504
|
+
payload: { cellId: a, before: false },
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
expect(changes).toHaveLength(0);
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
it("addSetupCellIfDoesntExist emits create-cell when new", () => {
|
|
511
|
+
setup("a");
|
|
512
|
+
|
|
513
|
+
const { changes } = resolve(state, {
|
|
514
|
+
type: "addSetupCellIfDoesntExist",
|
|
515
|
+
payload: { code: "import marimo as mo" },
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
expect(changes).toMatchInlineSnapshot(`
|
|
519
|
+
[
|
|
520
|
+
{
|
|
521
|
+
"before": "0",
|
|
522
|
+
"cellId": "setup",
|
|
523
|
+
"code": "import marimo as mo",
|
|
524
|
+
"config": {
|
|
525
|
+
"column": null,
|
|
526
|
+
"disabled": false,
|
|
527
|
+
"hide_code": false,
|
|
528
|
+
},
|
|
529
|
+
"name": "setup",
|
|
530
|
+
"type": "create-cell",
|
|
531
|
+
},
|
|
532
|
+
]
|
|
533
|
+
`);
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
it("addSetupCellIfDoesntExist emits nothing when already exists", () => {
|
|
537
|
+
setup("a");
|
|
538
|
+
// Add setup cell first
|
|
539
|
+
state = dispatch(state, {
|
|
540
|
+
type: "addSetupCellIfDoesntExist",
|
|
541
|
+
payload: { code: "import marimo as mo" },
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
// Try to add again — should just focus
|
|
545
|
+
const { changes } = resolve(state, {
|
|
546
|
+
type: "addSetupCellIfDoesntExist",
|
|
547
|
+
payload: {},
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
expect(changes).toHaveLength(0);
|
|
551
|
+
});
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
describe("actions that should NOT emit changes", () => {
|
|
555
|
+
it("focusCell returns empty", () => {
|
|
556
|
+
setup("a", "b");
|
|
557
|
+
const [a] = state.cellIds.inOrderIds;
|
|
558
|
+
const { changes } = resolve(state, {
|
|
559
|
+
type: "focusCell",
|
|
560
|
+
payload: { cellId: a, where: "after" },
|
|
561
|
+
});
|
|
562
|
+
expect(changes).toHaveLength(0);
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
it("prepareForRun returns empty", () => {
|
|
566
|
+
setup("a");
|
|
567
|
+
const [a] = state.cellIds.inOrderIds;
|
|
568
|
+
const { changes } = resolve(state, {
|
|
569
|
+
type: "prepareForRun",
|
|
570
|
+
payload: { cellId: a },
|
|
571
|
+
});
|
|
572
|
+
expect(changes).toHaveLength(0);
|
|
573
|
+
});
|
|
574
|
+
});
|
|
575
|
+
});
|