@marimo-team/islands 0.21.2-dev56 → 0.21.2-dev58
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/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/css/app/fonts.css +6 -6
- package/src/fonts/Fira_Mono/FiraMono-Bold.woff2 +0 -0
- package/src/fonts/Fira_Mono/FiraMono-Medium.woff2 +0 -0
- package/src/fonts/Fira_Mono/FiraMono-Regular.woff2 +0 -0
- package/src/fonts/Lora/Lora-VariableFont_wght.woff2 +0 -0
- package/src/fonts/PT_Sans/PTSans-Bold.woff2 +0 -0
- package/src/fonts/PT_Sans/PTSans-Regular.woff2 +0 -0
- package/src/utils/createReducer.ts +26 -11
- package/src/fonts/Fira_Mono/FiraMono-Bold.ttf +0 -0
- package/src/fonts/Fira_Mono/FiraMono-Medium.ttf +0 -0
- package/src/fonts/Fira_Mono/FiraMono-Regular.ttf +0 -0
- package/src/fonts/Lora/Lora-Italic-VariableFont_wght.ttf +0 -0
- package/src/fonts/Lora/Lora-VariableFont_wght.ttf +0 -0
- package/src/fonts/Lora/static/Lora-Bold.ttf +0 -0
- package/src/fonts/Lora/static/Lora-BoldItalic.ttf +0 -0
- package/src/fonts/Lora/static/Lora-Italic.ttf +0 -0
- package/src/fonts/Lora/static/Lora-Medium.ttf +0 -0
- package/src/fonts/Lora/static/Lora-MediumItalic.ttf +0 -0
- package/src/fonts/Lora/static/Lora-Regular.ttf +0 -0
- package/src/fonts/Lora/static/Lora-SemiBold.ttf +0 -0
- package/src/fonts/Lora/static/Lora-SemiBoldItalic.ttf +0 -0
- package/src/fonts/PT_Sans/PTSans-Bold.ttf +0 -0
- package/src/fonts/PT_Sans/PTSans-BoldItalic.ttf +0 -0
- package/src/fonts/PT_Sans/PTSans-Italic.ttf +0 -0
- package/src/fonts/PT_Sans/PTSans-Regular.ttf +0 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Tests for fromDocumentChanges / applyTransactionChanges in isolation.
|
|
5
|
+
*
|
|
6
|
+
* These test the change→action mapping for edge cases and error paths that
|
|
7
|
+
* can't be exercised via the round-trip tests (since toDocumentChanges would
|
|
8
|
+
* never produce malformed or conflicting changes). Basic correctness is
|
|
9
|
+
* covered by document-roundtrip.test.ts. This file focuses on:
|
|
10
|
+
*
|
|
11
|
+
* - Multi-change transactions (create+move, create+set-code, set-code+delete)
|
|
12
|
+
* - Cancelled changes (create+delete same cell)
|
|
13
|
+
* - Missing/nonexistent anchors and cells
|
|
14
|
+
* - Config propagation on create-cell (disabled, column)
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { python } from "@codemirror/lang-python";
|
|
18
|
+
import { EditorState } from "@codemirror/state";
|
|
19
|
+
import { EditorView } from "@codemirror/view";
|
|
20
|
+
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
|
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 {
|
|
27
|
+
applyTransactionChanges,
|
|
28
|
+
exportedForTesting as middlewareExports,
|
|
29
|
+
} from "../document-changes";
|
|
30
|
+
import { CellId } from "../ids";
|
|
31
|
+
|
|
32
|
+
const { initialNotebookState, reducer, createActions } = exportedForTesting;
|
|
33
|
+
|
|
34
|
+
function createEditor(code: string) {
|
|
35
|
+
const state = EditorState.create({
|
|
36
|
+
doc: code,
|
|
37
|
+
extensions: [
|
|
38
|
+
python(),
|
|
39
|
+
adaptiveLanguageConfiguration({
|
|
40
|
+
cellId: "cell1" as CellId,
|
|
41
|
+
completionConfig: {
|
|
42
|
+
activate_on_typing: true,
|
|
43
|
+
signature_hint_on_typing: false,
|
|
44
|
+
copilot: false,
|
|
45
|
+
codeium_api_key: null,
|
|
46
|
+
},
|
|
47
|
+
hotkeys: new OverridingHotkeyProvider({}),
|
|
48
|
+
placeholderType: "marimo-import",
|
|
49
|
+
lspConfig: {},
|
|
50
|
+
}),
|
|
51
|
+
],
|
|
52
|
+
});
|
|
53
|
+
return new EditorView({ state, parent: document.body });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let state: NotebookState;
|
|
57
|
+
let actions: ReturnType<typeof createActions>;
|
|
58
|
+
|
|
59
|
+
function setup(...codes: string[]) {
|
|
60
|
+
state = initialNotebookState();
|
|
61
|
+
state.cellIds = MultiColumn.from([]);
|
|
62
|
+
actions = createActions((action) => {
|
|
63
|
+
state = reducer(state, action);
|
|
64
|
+
for (const [cellId, handle] of Object.entries(state.cellHandles)) {
|
|
65
|
+
if (!handle.current) {
|
|
66
|
+
const view = createEditor(state.cellData[cellId as CellId].code);
|
|
67
|
+
const h: CellHandle = { editorView: view, editorViewOrNull: view };
|
|
68
|
+
state.cellHandles[cellId as CellId] = { current: h };
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
for (const code of codes) {
|
|
73
|
+
actions.createNewCell({ cellId: "__end__", before: false, code });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
afterEach(() => {
|
|
78
|
+
middlewareExports.cancelPendingChanges();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
function apply(changes: Parameters<typeof applyTransactionChanges>[0]) {
|
|
82
|
+
applyTransactionChanges(changes, actions, () => state.cellIds.inOrderIds);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Snapshot of document state: ordering, code, name, config. */
|
|
86
|
+
function pretty(s: NotebookState): string {
|
|
87
|
+
const lines = s.cellIds.inOrderIds.map((id) => {
|
|
88
|
+
const cell = s.cellData[id];
|
|
89
|
+
const flags: string[] = [];
|
|
90
|
+
if (cell.name && cell.name !== "_") {
|
|
91
|
+
flags.push(`name=${cell.name}`);
|
|
92
|
+
}
|
|
93
|
+
if (cell.config.hide_code) {
|
|
94
|
+
flags.push("hide_code");
|
|
95
|
+
}
|
|
96
|
+
if (cell.config.disabled) {
|
|
97
|
+
flags.push("disabled");
|
|
98
|
+
}
|
|
99
|
+
if (cell.config.column != null) {
|
|
100
|
+
flags.push(`col=${cell.config.column}`);
|
|
101
|
+
}
|
|
102
|
+
const suffix = flags.length > 0 ? ` [${flags.join(", ")}]` : "";
|
|
103
|
+
return `${id}: '${cell.code}'${suffix}`;
|
|
104
|
+
});
|
|
105
|
+
return `\n${lines.join("\n")}\n`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let i = 0;
|
|
109
|
+
|
|
110
|
+
beforeAll(() => {
|
|
111
|
+
CellId.create = () => `${i++}` as CellId;
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
beforeEach(() => {
|
|
115
|
+
i = 0;
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe("applyTransactionChanges edge cases", () => {
|
|
119
|
+
it("create-cell applies disabled and column config", () => {
|
|
120
|
+
setup("a");
|
|
121
|
+
apply([
|
|
122
|
+
{
|
|
123
|
+
type: "create-cell",
|
|
124
|
+
cellId: "new-cell",
|
|
125
|
+
code: "configured",
|
|
126
|
+
name: "",
|
|
127
|
+
config: { hide_code: true, disabled: true, column: 1 },
|
|
128
|
+
},
|
|
129
|
+
]);
|
|
130
|
+
expect(pretty(state)).toMatchInlineSnapshot(`
|
|
131
|
+
"
|
|
132
|
+
0: 'a'
|
|
133
|
+
new-cell: 'configured' [hide_code, disabled, col=1]
|
|
134
|
+
"
|
|
135
|
+
`);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("create-cell then move-cell in same transaction", () => {
|
|
139
|
+
setup("a", "b");
|
|
140
|
+
const [a] = state.cellIds.inOrderIds;
|
|
141
|
+
apply([
|
|
142
|
+
{
|
|
143
|
+
type: "create-cell",
|
|
144
|
+
cellId: "new-cell",
|
|
145
|
+
code: "new",
|
|
146
|
+
name: "",
|
|
147
|
+
config: {},
|
|
148
|
+
},
|
|
149
|
+
{ type: "move-cell", cellId: "new-cell", before: a },
|
|
150
|
+
]);
|
|
151
|
+
expect(pretty(state)).toMatchInlineSnapshot(`
|
|
152
|
+
"
|
|
153
|
+
new-cell: 'new'
|
|
154
|
+
0: 'a'
|
|
155
|
+
1: 'b'
|
|
156
|
+
"
|
|
157
|
+
`);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("create-cell then set-code in same transaction", () => {
|
|
161
|
+
setup("a");
|
|
162
|
+
apply([
|
|
163
|
+
{
|
|
164
|
+
type: "create-cell",
|
|
165
|
+
cellId: "new-cell",
|
|
166
|
+
code: "initial",
|
|
167
|
+
name: "",
|
|
168
|
+
config: {},
|
|
169
|
+
},
|
|
170
|
+
{ type: "set-code", cellId: "new-cell", code: "updated" },
|
|
171
|
+
]);
|
|
172
|
+
expect(pretty(state)).toMatchInlineSnapshot(`
|
|
173
|
+
"
|
|
174
|
+
0: 'a'
|
|
175
|
+
new-cell: 'updated'
|
|
176
|
+
"
|
|
177
|
+
`);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("create-cell then delete-cell same cell cancels out", () => {
|
|
181
|
+
setup("a");
|
|
182
|
+
apply([
|
|
183
|
+
{
|
|
184
|
+
type: "create-cell",
|
|
185
|
+
cellId: "ephemeral",
|
|
186
|
+
code: "tmp",
|
|
187
|
+
name: "",
|
|
188
|
+
config: {},
|
|
189
|
+
},
|
|
190
|
+
{ type: "delete-cell", cellId: "ephemeral" },
|
|
191
|
+
]);
|
|
192
|
+
expect(pretty(state)).toMatchInlineSnapshot(`
|
|
193
|
+
"
|
|
194
|
+
0: 'a'
|
|
195
|
+
"
|
|
196
|
+
`);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("multiple changes in one transaction", () => {
|
|
200
|
+
setup("a", "b", "c");
|
|
201
|
+
const [a, b, c] = state.cellIds.inOrderIds;
|
|
202
|
+
apply([
|
|
203
|
+
{ type: "set-code", cellId: a, code: "x = 1" },
|
|
204
|
+
{ type: "set-name", cellId: b, name: "middle" },
|
|
205
|
+
{ type: "delete-cell", cellId: c },
|
|
206
|
+
]);
|
|
207
|
+
expect(pretty(state)).toMatchInlineSnapshot(`
|
|
208
|
+
"
|
|
209
|
+
0: 'x = 1'
|
|
210
|
+
1: 'b' [name=middle]
|
|
211
|
+
"
|
|
212
|
+
`);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it("move-cell with no anchor appends to end", () => {
|
|
216
|
+
setup("a", "b", "c");
|
|
217
|
+
const [a] = state.cellIds.inOrderIds;
|
|
218
|
+
apply([{ type: "move-cell", cellId: a }]);
|
|
219
|
+
expect(pretty(state)).toMatchInlineSnapshot(`
|
|
220
|
+
"
|
|
221
|
+
1: 'b'
|
|
222
|
+
2: 'c'
|
|
223
|
+
0: 'a'
|
|
224
|
+
"
|
|
225
|
+
`);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it("move-cell with missing after anchor falls back to end", () => {
|
|
229
|
+
setup("a", "b");
|
|
230
|
+
const [a] = state.cellIds.inOrderIds;
|
|
231
|
+
apply([{ type: "move-cell", cellId: a, after: "nonexistent" as CellId }]);
|
|
232
|
+
expect(pretty(state)).toMatchInlineSnapshot(`
|
|
233
|
+
"
|
|
234
|
+
1: 'b'
|
|
235
|
+
0: 'a'
|
|
236
|
+
"
|
|
237
|
+
`);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it("move-cell with missing before anchor falls back to start", () => {
|
|
241
|
+
setup("a", "b");
|
|
242
|
+
const [, b] = state.cellIds.inOrderIds;
|
|
243
|
+
apply([{ type: "move-cell", cellId: b, before: "nonexistent" as CellId }]);
|
|
244
|
+
expect(pretty(state)).toMatchInlineSnapshot(`
|
|
245
|
+
"
|
|
246
|
+
1: 'b'
|
|
247
|
+
0: 'a'
|
|
248
|
+
"
|
|
249
|
+
`);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it("move-cell on nonexistent cell is a no-op", () => {
|
|
253
|
+
setup("a", "b");
|
|
254
|
+
apply([
|
|
255
|
+
{
|
|
256
|
+
type: "move-cell",
|
|
257
|
+
cellId: "nonexistent" as CellId,
|
|
258
|
+
after: "0" as CellId,
|
|
259
|
+
},
|
|
260
|
+
]);
|
|
261
|
+
expect(pretty(state)).toMatchInlineSnapshot(`
|
|
262
|
+
"
|
|
263
|
+
0: 'a'
|
|
264
|
+
1: 'b'
|
|
265
|
+
"
|
|
266
|
+
`);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it("empty changes is a no-op", () => {
|
|
270
|
+
setup("a", "b");
|
|
271
|
+
apply([]);
|
|
272
|
+
expect(pretty(state)).toMatchInlineSnapshot(`
|
|
273
|
+
"
|
|
274
|
+
0: 'a'
|
|
275
|
+
1: 'b'
|
|
276
|
+
"
|
|
277
|
+
`);
|
|
278
|
+
});
|
|
279
|
+
});
|
|
@@ -6,6 +6,7 @@ import { EditorView } from "@codemirror/view";
|
|
|
6
6
|
import { createStore } from "jotai";
|
|
7
7
|
import {
|
|
8
8
|
afterAll,
|
|
9
|
+
afterEach,
|
|
9
10
|
beforeAll,
|
|
10
11
|
beforeEach,
|
|
11
12
|
describe,
|
|
@@ -29,6 +30,7 @@ import {
|
|
|
29
30
|
type NotebookState,
|
|
30
31
|
notebookAtom,
|
|
31
32
|
} from "../cells";
|
|
33
|
+
import { exportedForTesting as documentTransactionTestExports } from "../document-changes";
|
|
32
34
|
import {
|
|
33
35
|
focusAndScrollCellIntoView,
|
|
34
36
|
scrollToBottom,
|
|
@@ -147,6 +149,10 @@ describe("cell reducer", () => {
|
|
|
147
149
|
firstCellId = state.cellIds.inOrderIds[0];
|
|
148
150
|
});
|
|
149
151
|
|
|
152
|
+
afterEach(() => {
|
|
153
|
+
documentTransactionTestExports.cancelPendingChanges();
|
|
154
|
+
});
|
|
155
|
+
|
|
150
156
|
afterAll(() => {
|
|
151
157
|
CellId.create = originalCreate;
|
|
152
158
|
});
|