@marimo-team/frontend 0.23.1-dev9 → 0.23.1
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/assets/{JsonOutput-BY31ccA7.js → JsonOutput-CavtrueA.js} +1 -1
- package/dist/assets/{MarimoErrorOutput--Yd2Aw0J.js → MarimoErrorOutput-Bmp8DLLo.js} +1 -1
- package/dist/assets/RenderHTML-CM3WMmA8.js +1 -0
- package/dist/assets/{add-connection-dialog-CjvNOKgb.js → add-connection-dialog-BGZvJkor.js} +1 -1
- package/dist/assets/{agent-panel-C24uwabG.js → agent-panel-BvL9Lu9c.js} +1 -1
- package/dist/assets/{cell-editor-zW0u82sK.js → cell-editor-B40o_zx_.js} +1 -1
- package/dist/assets/{chat-display-DsHMZa9F.js → chat-display-M_nvYuHH.js} +1 -1
- package/dist/assets/{chat-panel-o9D3upnX.js → chat-panel-BMOW93uQ.js} +1 -1
- package/dist/assets/{chat-ui-BYS03y86.js → chat-ui-DyeimpVh.js} +1 -1
- package/dist/assets/{column-preview-Dwv5a_zE.js → column-preview-AfcgbFG_.js} +1 -1
- package/dist/assets/{command-palette-BYbKGSF3.js → command-palette-BgvdyU3B.js} +1 -1
- package/dist/assets/{documentation-panel-CA2pWMgB.js → documentation-panel-DUPcsi8P.js} +1 -1
- package/dist/assets/{edit-page-CMUN3ESy.js → edit-page-DD4uEDmX.js} +4 -4
- package/dist/assets/{error-panel-CbqfK1HJ.js → error-panel-DQOeSv5-.js} +1 -1
- package/dist/assets/{file-explorer-panel-CbS8z-JR.js → file-explorer-panel-B67zjs2X.js} +1 -1
- package/dist/assets/{form-DLyXacSF.js → form-BJ6VFU8l.js} +1 -1
- package/dist/assets/{hooks-kZJc1iBf.js → hooks-DvwShzDb.js} +1 -1
- package/dist/assets/index-y6osgSWB.js +42 -0
- package/dist/assets/{layout-tmN-U1zs.js → layout-erv8pLIP.js} +1 -1
- package/dist/assets/{panels-CLfdzLPR.js → panels-1u-RE72f.js} +1 -1
- package/dist/assets/{run-page-DPuH6QY4.js → run-page-DfWH_1mz.js} +1 -1
- package/dist/assets/{scratchpad-panel-BsMm0GQP.js → scratchpad-panel-CnaiXtoJ.js} +1 -1
- package/dist/assets/{session-panel-CTDzGShO.js → session-panel-C68GBFwH.js} +1 -1
- package/dist/assets/{snippets-panel-CWof0wHk.js → snippets-panel-BmIdR0lc.js} +1 -1
- package/dist/assets/state-D1n-olwf.js +3 -0
- package/dist/assets/{useNotebookActions-DHBEqrc_.js → useNotebookActions-Ch1o32Jw.js} +1 -1
- package/dist/index.html +7 -7
- package/package.json +4 -4
- package/src/core/islands/__tests__/bridge.test.ts +2 -12
- package/src/core/islands/__tests__/islands-harness.test.ts +348 -0
- package/src/core/islands/__tests__/parse.test.ts +466 -24
- package/src/core/islands/__tests__/test-utils.tsx +263 -0
- package/src/core/islands/bootstrap.ts +265 -0
- package/src/core/islands/bridge.ts +154 -75
- package/src/core/islands/components/IslandControls.tsx +103 -0
- package/src/core/islands/components/__tests__/IslandControls.test.tsx +185 -0
- package/src/core/islands/components/__tests__/useIslandControls.test.ts +208 -0
- package/src/core/islands/components/output-wrapper.tsx +76 -93
- package/src/core/islands/components/useIslandControls.ts +60 -0
- package/src/core/islands/components/web-components.tsx +168 -40
- package/src/core/islands/constants.ts +28 -0
- package/src/core/islands/main.ts +7 -205
- package/src/core/islands/parse.ts +73 -26
- package/src/core/islands/worker-factory.ts +86 -0
- package/src/plugins/core/RenderHTML.tsx +9 -0
- package/src/plugins/core/__test__/RenderHTML.test.ts +27 -0
- package/src/plugins/core/__test__/trusted-url.test.ts +48 -0
- package/src/plugins/core/registerReactComponent.tsx +11 -8
- package/src/plugins/core/trusted-url.ts +20 -0
- package/src/plugins/impl/ButtonPlugin.tsx +4 -6
- package/src/plugins/impl/CodeEditorPlugin.tsx +15 -18
- package/src/plugins/impl/DataEditorPlugin.tsx +8 -14
- package/src/plugins/impl/DataTablePlugin.tsx +8 -9
- package/src/plugins/impl/FileUploadPlugin.tsx +39 -43
- package/src/plugins/impl/FormPlugin.tsx +2 -6
- package/src/plugins/impl/anywidget/__tests__/widget-binding.test.ts +27 -1
- package/src/plugins/impl/anywidget/widget-binding.ts +13 -0
- package/src/plugins/impl/chat/ChatPlugin.tsx +17 -20
- package/src/plugins/impl/data-explorer/DataExplorerPlugin.tsx +5 -8
- package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +21 -0
- package/src/plugins/impl/mpl-interactive/__tests__/MplInteractivePlugin.test.tsx +119 -0
- package/src/plugins/impl/panel/PanelPlugin.tsx +31 -10
- package/src/plugins/impl/panel/__tests__/PanelPlugin.test.ts +60 -0
- package/src/plugins/impl/vega/VegaPlugin.tsx +5 -8
- package/src/plugins/layout/NavigationMenuPlugin.tsx +2 -6
- package/dist/assets/RenderHTML-CbuarQqA.js +0 -1
- package/dist/assets/index-Bm25ctN7.js +0 -42
- package/dist/assets/state-BvnlMKdT.js +0 -3
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
3
|
+
import { ISLAND_DATA_ATTRIBUTES } from "@/core/islands/constants";
|
|
4
|
+
import {
|
|
5
|
+
extractIslandCodeFromEmbed,
|
|
6
|
+
parseIslandElement,
|
|
7
|
+
parseIslandElementsIntoApps,
|
|
8
|
+
parseMarimoIslandApps,
|
|
9
|
+
} from "../parse";
|
|
10
|
+
import {
|
|
11
|
+
buildIslandHTML,
|
|
12
|
+
createIslandHarness,
|
|
13
|
+
type IslandHarness,
|
|
14
|
+
} from "./test-utils.tsx";
|
|
15
|
+
|
|
16
|
+
let harness: IslandHarness;
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
harness?.cleanup();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// Reactive vs Non-Reactive Parsing
|
|
24
|
+
// ============================================================================
|
|
25
|
+
|
|
26
|
+
describe("reactive vs non-reactive islands", () => {
|
|
27
|
+
it("should parse reactive islands into apps with code", () => {
|
|
28
|
+
harness = createIslandHarness(
|
|
29
|
+
buildIslandHTML([
|
|
30
|
+
{ reactive: true, code: "x = 1", output: "<div>1</div>" },
|
|
31
|
+
{ reactive: true, code: "y = 2", output: "<div>2</div>" },
|
|
32
|
+
]),
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const apps = parseMarimoIslandApps(harness.container);
|
|
36
|
+
expect(apps).toHaveLength(1);
|
|
37
|
+
expect(apps[0].cells).toHaveLength(2);
|
|
38
|
+
expect(apps[0].cells[0].code).toBe("x = 1");
|
|
39
|
+
expect(apps[0].cells[1].code).toBe("y = 2");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("should skip non-reactive islands during parsing (no code sent to kernel)", () => {
|
|
43
|
+
harness = createIslandHarness(
|
|
44
|
+
buildIslandHTML([
|
|
45
|
+
{ reactive: true, code: "x = 1", output: "<div>1</div>" },
|
|
46
|
+
{ reactive: false, output: "<div>static content</div>" },
|
|
47
|
+
{ reactive: true, code: "y = 2", output: "<div>2</div>" },
|
|
48
|
+
]),
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const apps = parseMarimoIslandApps(harness.container);
|
|
52
|
+
expect(apps).toHaveLength(1);
|
|
53
|
+
// Only the 2 reactive islands become cells
|
|
54
|
+
expect(apps[0].cells).toHaveLength(2);
|
|
55
|
+
expect(apps[0].cells[0].code).toBe("x = 1");
|
|
56
|
+
expect(apps[0].cells[1].code).toBe("y = 2");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("should not set data-cell-idx on non-reactive islands", () => {
|
|
60
|
+
harness = createIslandHarness(
|
|
61
|
+
buildIslandHTML([
|
|
62
|
+
{ reactive: true, code: "x = 1", output: "<div>1</div>" },
|
|
63
|
+
{ reactive: false, output: "<div>static</div>" },
|
|
64
|
+
]),
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
parseMarimoIslandApps(harness.container);
|
|
68
|
+
|
|
69
|
+
const [reactiveIsland, nonReactiveIsland] = harness.islands;
|
|
70
|
+
|
|
71
|
+
// Reactive island gets a cell index
|
|
72
|
+
expect(reactiveIsland.getAttribute(ISLAND_DATA_ATTRIBUTES.CELL_IDX)).toBe(
|
|
73
|
+
"0",
|
|
74
|
+
);
|
|
75
|
+
// Non-reactive island does NOT get a cell index
|
|
76
|
+
expect(
|
|
77
|
+
nonReactiveIsland.getAttribute(ISLAND_DATA_ATTRIBUTES.CELL_IDX),
|
|
78
|
+
).toBeNull();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("should handle all-non-reactive islands (empty app list)", () => {
|
|
82
|
+
harness = createIslandHarness(
|
|
83
|
+
buildIslandHTML([
|
|
84
|
+
{ reactive: false, output: "<div>static 1</div>" },
|
|
85
|
+
{ reactive: false, output: "<div>static 2</div>" },
|
|
86
|
+
]),
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const apps = parseMarimoIslandApps(harness.container);
|
|
90
|
+
expect(apps).toHaveLength(0);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// ============================================================================
|
|
95
|
+
// extractIslandCodeFromEmbed
|
|
96
|
+
// ============================================================================
|
|
97
|
+
|
|
98
|
+
describe("extractIslandCodeFromEmbed with harness", () => {
|
|
99
|
+
it("should return code for reactive islands", () => {
|
|
100
|
+
harness = createIslandHarness(
|
|
101
|
+
buildIslandHTML([
|
|
102
|
+
{ reactive: true, code: 'mo.md("hello")', output: "<div>hello</div>" },
|
|
103
|
+
]),
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const code = extractIslandCodeFromEmbed(harness.islands[0]);
|
|
107
|
+
expect(code).toBe('mo.md("hello")');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("should return empty string for non-reactive islands", () => {
|
|
111
|
+
harness = createIslandHarness(
|
|
112
|
+
buildIslandHTML([
|
|
113
|
+
{
|
|
114
|
+
reactive: false,
|
|
115
|
+
code: 'mo.md("hello")',
|
|
116
|
+
output: "<div>hello</div>",
|
|
117
|
+
},
|
|
118
|
+
]),
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
const code = extractIslandCodeFromEmbed(harness.islands[0]);
|
|
122
|
+
expect(code).toBe("");
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// ============================================================================
|
|
127
|
+
// parseIslandElement
|
|
128
|
+
// ============================================================================
|
|
129
|
+
|
|
130
|
+
describe("parseIslandElement with harness", () => {
|
|
131
|
+
it("should return cell data for reactive island with output and code", () => {
|
|
132
|
+
harness = createIslandHarness(
|
|
133
|
+
buildIslandHTML([
|
|
134
|
+
{ reactive: true, code: "x = 1", output: "<div>1</div>" },
|
|
135
|
+
]),
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
const result = parseIslandElement(harness.islands[0]);
|
|
139
|
+
expect(result).not.toBeNull();
|
|
140
|
+
expect(result!.code).toBe("x = 1");
|
|
141
|
+
expect(result!.output).toBe("<div>1</div>");
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("should return null for non-reactive island (code is empty)", () => {
|
|
145
|
+
harness = createIslandHarness(
|
|
146
|
+
buildIslandHTML([{ reactive: false, output: "<div>static</div>" }]),
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
const result = parseIslandElement(harness.islands[0]);
|
|
150
|
+
expect(result).toBeNull();
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// ============================================================================
|
|
155
|
+
// Multi-app parsing
|
|
156
|
+
// ============================================================================
|
|
157
|
+
|
|
158
|
+
describe("multi-app parsing with harness", () => {
|
|
159
|
+
it("should group islands by app-id", () => {
|
|
160
|
+
harness = createIslandHarness(
|
|
161
|
+
buildIslandHTML([
|
|
162
|
+
{ appId: "app-1", reactive: true, code: "a = 1", output: "<div/>" },
|
|
163
|
+
{ appId: "app-2", reactive: true, code: "b = 2", output: "<div/>" },
|
|
164
|
+
{ appId: "app-1", reactive: true, code: "c = 3", output: "<div/>" },
|
|
165
|
+
]),
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
const apps = parseMarimoIslandApps(harness.container);
|
|
169
|
+
expect(apps).toHaveLength(2);
|
|
170
|
+
|
|
171
|
+
const app1 = apps.find((a) => a.id === "app-1")!;
|
|
172
|
+
const app2 = apps.find((a) => a.id === "app-2")!;
|
|
173
|
+
|
|
174
|
+
expect(app1.cells).toHaveLength(2);
|
|
175
|
+
expect(app1.cells[0].code).toBe("a = 1");
|
|
176
|
+
expect(app1.cells[1].code).toBe("c = 3");
|
|
177
|
+
|
|
178
|
+
expect(app2.cells).toHaveLength(1);
|
|
179
|
+
expect(app2.cells[0].code).toBe("b = 2");
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("should assign sequential cell indices within each app", () => {
|
|
183
|
+
harness = createIslandHarness(
|
|
184
|
+
buildIslandHTML([
|
|
185
|
+
{ appId: "app-1", reactive: true, code: "a", output: "<div/>" },
|
|
186
|
+
{ appId: "app-1", reactive: true, code: "b", output: "<div/>" },
|
|
187
|
+
{ appId: "app-1", reactive: true, code: "c", output: "<div/>" },
|
|
188
|
+
]),
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
const apps = parseMarimoIslandApps(harness.container);
|
|
192
|
+
expect(apps[0].cells.map((c) => c.idx)).toEqual([0, 1, 2]);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("should skip non-reactive islands in cell index assignment", () => {
|
|
196
|
+
harness = createIslandHarness(
|
|
197
|
+
buildIslandHTML([
|
|
198
|
+
{ reactive: true, code: "a = 1", output: "<div/>" },
|
|
199
|
+
{ reactive: false, output: "<div>static</div>" },
|
|
200
|
+
{ reactive: true, code: "b = 2", output: "<div/>" },
|
|
201
|
+
]),
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
const apps = parseMarimoIslandApps(harness.container);
|
|
205
|
+
expect(apps[0].cells).toHaveLength(2);
|
|
206
|
+
expect(apps[0].cells[0].idx).toBe(0);
|
|
207
|
+
expect(apps[0].cells[1].idx).toBe(1);
|
|
208
|
+
|
|
209
|
+
// Verify DOM: reactive islands get indices, non-reactive does not
|
|
210
|
+
expect(
|
|
211
|
+
harness.islands[0].getAttribute(ISLAND_DATA_ATTRIBUTES.CELL_IDX),
|
|
212
|
+
).toBe("0");
|
|
213
|
+
expect(
|
|
214
|
+
harness.islands[1].getAttribute(ISLAND_DATA_ATTRIBUTES.CELL_IDX),
|
|
215
|
+
).toBeNull();
|
|
216
|
+
expect(
|
|
217
|
+
harness.islands[2].getAttribute(ISLAND_DATA_ATTRIBUTES.CELL_IDX),
|
|
218
|
+
).toBe("1");
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// ============================================================================
|
|
223
|
+
// Mixed reactive/non-reactive scenarios (regression tests)
|
|
224
|
+
// ============================================================================
|
|
225
|
+
|
|
226
|
+
describe("mixed reactive/non-reactive island scenarios", () => {
|
|
227
|
+
it("should handle the generate.py demo pattern: reactive + non-reactive + display_code", () => {
|
|
228
|
+
// Mirrors the "Island Features" section of generate.py
|
|
229
|
+
harness = createIslandHarness(
|
|
230
|
+
buildIslandHTML([
|
|
231
|
+
// Section header (reactive)
|
|
232
|
+
{
|
|
233
|
+
reactive: true,
|
|
234
|
+
code: 'mo.md("## Display Code")',
|
|
235
|
+
output: "<div><h2>Display Code</h2></div>",
|
|
236
|
+
},
|
|
237
|
+
// display_code island (reactive)
|
|
238
|
+
{
|
|
239
|
+
reactive: true,
|
|
240
|
+
code: 'mo.md("You can show the code")',
|
|
241
|
+
output: "<div>You can show the code</div>",
|
|
242
|
+
displayCode: true,
|
|
243
|
+
},
|
|
244
|
+
// Non-reactive section header
|
|
245
|
+
{
|
|
246
|
+
reactive: true,
|
|
247
|
+
code: 'mo.md("## Non-Reactive Islands")',
|
|
248
|
+
output: "<div><h2>Non-Reactive Islands</h2></div>",
|
|
249
|
+
},
|
|
250
|
+
// Non-reactive island — the one that was crashing
|
|
251
|
+
{
|
|
252
|
+
reactive: false,
|
|
253
|
+
code: 'mo.md("This island is non-reactive")',
|
|
254
|
+
output:
|
|
255
|
+
"<div>This island is non-reactive - it runs once and doesn't update</div>",
|
|
256
|
+
},
|
|
257
|
+
]),
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
const apps = parseMarimoIslandApps(harness.container);
|
|
261
|
+
|
|
262
|
+
// Only 3 reactive islands become cells
|
|
263
|
+
expect(apps).toHaveLength(1);
|
|
264
|
+
expect(apps[0].cells).toHaveLength(3);
|
|
265
|
+
|
|
266
|
+
// Non-reactive island (index 3) has no cell-idx
|
|
267
|
+
expect(
|
|
268
|
+
harness.islands[3].getAttribute(ISLAND_DATA_ATTRIBUTES.CELL_IDX),
|
|
269
|
+
).toBeNull();
|
|
270
|
+
expect(
|
|
271
|
+
harness.islands[3].getAttribute(ISLAND_DATA_ATTRIBUTES.REACTIVE),
|
|
272
|
+
).toBe("false");
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it("should handle non-reactive island at the start", () => {
|
|
276
|
+
harness = createIslandHarness(
|
|
277
|
+
buildIslandHTML([
|
|
278
|
+
{ reactive: false, output: "<div>static header</div>" },
|
|
279
|
+
{ reactive: true, code: "x = 1", output: "<div>1</div>" },
|
|
280
|
+
]),
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
const apps = parseMarimoIslandApps(harness.container);
|
|
284
|
+
expect(apps).toHaveLength(1);
|
|
285
|
+
expect(apps[0].cells).toHaveLength(1);
|
|
286
|
+
expect(apps[0].cells[0].code).toBe("x = 1");
|
|
287
|
+
expect(apps[0].cells[0].idx).toBe(0);
|
|
288
|
+
|
|
289
|
+
// First island (non-reactive) has no index
|
|
290
|
+
expect(
|
|
291
|
+
harness.islands[0].getAttribute(ISLAND_DATA_ATTRIBUTES.CELL_IDX),
|
|
292
|
+
).toBeNull();
|
|
293
|
+
// Second island (reactive) gets index 0
|
|
294
|
+
expect(
|
|
295
|
+
harness.islands[1].getAttribute(ISLAND_DATA_ATTRIBUTES.CELL_IDX),
|
|
296
|
+
).toBe("0");
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it("should handle alternating reactive and non-reactive islands", () => {
|
|
300
|
+
harness = createIslandHarness(
|
|
301
|
+
buildIslandHTML([
|
|
302
|
+
{ reactive: true, code: "a = 1", output: "<div/>" },
|
|
303
|
+
{ reactive: false, output: "<div>static</div>" },
|
|
304
|
+
{ reactive: true, code: "b = 2", output: "<div/>" },
|
|
305
|
+
{ reactive: false, output: "<div>static</div>" },
|
|
306
|
+
{ reactive: true, code: "c = 3", output: "<div/>" },
|
|
307
|
+
]),
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
const apps = parseMarimoIslandApps(harness.container);
|
|
311
|
+
expect(apps[0].cells).toHaveLength(3);
|
|
312
|
+
expect(apps[0].cells.map((c) => c.code)).toEqual([
|
|
313
|
+
"a = 1",
|
|
314
|
+
"b = 2",
|
|
315
|
+
"c = 3",
|
|
316
|
+
]);
|
|
317
|
+
expect(apps[0].cells.map((c) => c.idx)).toEqual([0, 1, 2]);
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
// ============================================================================
|
|
322
|
+
// parseIslandElementsIntoApps (direct element-level tests)
|
|
323
|
+
// ============================================================================
|
|
324
|
+
|
|
325
|
+
describe("parseIslandElementsIntoApps with mixed elements", () => {
|
|
326
|
+
it("should preserve DOM order for cell indices", () => {
|
|
327
|
+
harness = createIslandHarness(
|
|
328
|
+
buildIslandHTML([
|
|
329
|
+
{ reactive: true, code: "first", output: "<div/>" },
|
|
330
|
+
{ reactive: true, code: "second", output: "<div/>" },
|
|
331
|
+
{ reactive: true, code: "third", output: "<div/>" },
|
|
332
|
+
]),
|
|
333
|
+
);
|
|
334
|
+
|
|
335
|
+
const apps = parseIslandElementsIntoApps(harness.islands);
|
|
336
|
+
expect(apps[0].cells.map((c) => c.code)).toEqual([
|
|
337
|
+
"first",
|
|
338
|
+
"second",
|
|
339
|
+
"third",
|
|
340
|
+
]);
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it("should handle empty container", () => {
|
|
344
|
+
harness = createIslandHarness("");
|
|
345
|
+
const apps = parseMarimoIslandApps(harness.container);
|
|
346
|
+
expect(apps).toHaveLength(0);
|
|
347
|
+
});
|
|
348
|
+
});
|