@marimo-team/islands 0.23.15-dev32 → 0.23.15-dev4
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-7p7rt9iw.js → ConnectedDataExplorerComponent-CU4fZJzG.js} +4 -4
- package/dist/{ErrorBoundary-B_CAG7_e.js → ErrorBoundary-DE6tzZf-.js} +1 -1
- package/dist/{ImageComparisonComponent-1i5IBhon.js → ImageComparisonComponent-CHrI72em.js} +129 -173
- package/dist/assets/__vite-browser-external-BQCLNwri.js +1 -0
- package/dist/assets/worker-DAWRHcPq.js +73 -0
- package/dist/{chat-ui-C0wkSwxB.js → chat-ui-DinOvbWu.js} +3111 -3111
- package/dist/{code-visibility-CEHWVGz1.js → code-visibility-Dk8cVOny.js} +864 -946
- package/dist/{debounce-BOD3DbfP.js → constants-T20xxyNf.js} +24 -1
- package/dist/{formats-BX3uDQbB.js → formats-Dzx4J_z1.js} +1 -1
- package/dist/{glide-data-editor-CwZz71BD.js → glide-data-editor-CjTu7ukN.js} +3 -3
- package/dist/{html-to-image-D8Q5SLSJ.js → html-to-image-_wGfk8V-.js} +2241 -2277
- package/dist/{input-BGPrFH3g.js → input-DtsN7xm-.js} +2 -2
- package/dist/main.js +1201 -1297
- package/dist/{mermaid-UdmxG2PZ.js → mermaid-BYqXy_NE.js} +2 -2
- package/dist/{process-output-C_A12w9J.js → process-output-Mh4UrjwM.js} +1 -1
- package/dist/{reveal-component-kLDhMoa2.js → reveal-component-B5DXLyO7.js} +605 -618
- package/dist/{spec-DSs9v0xx.js → spec-Cz-Bj1JI.js} +1 -1
- package/dist/style.css +1 -1
- package/dist/{toDate-CKpRx4TS.js → toDate-CWNNlFEX.js} +4 -3
- package/dist/{useAsyncData-BSOyAbac.js → useAsyncData-KfHB8wQR.js} +1 -1
- package/dist/{useDeepCompareMemoize-BTLeWzuR.js → useDeepCompareMemoize-nJMtxhm4.js} +1 -1
- package/dist/{useLifecycle-C6wHjkhW.js → useLifecycle-DegSo0lV.js} +1 -1
- package/dist/{useTheme-Df_vGflw.js → useTheme-6eZ3GOTS.js} +27 -62
- package/dist/{vega-component-DNHEV0u0.js → vega-component-DzyyM9fc.js} +6 -6
- package/package.json +1 -1
- package/src/components/chat/__tests__/chat-utils.test.ts +1 -244
- package/src/components/chat/chat-panel.tsx +67 -196
- package/src/components/chat/chat-utils.ts +2 -111
- package/src/components/editor/SortableCell.tsx +2 -6
- package/src/components/editor/actions/pair-with-agent-modal.tsx +64 -20
- package/src/components/editor/ai/add-cell-with-ai.tsx +5 -14
- package/src/components/editor/ai/ai-completion-editor.tsx +1 -4
- package/src/components/editor/cell/cell-context-menu.tsx +0 -7
- package/src/components/editor/chrome/panels/packages-panel.tsx +1 -11
- package/src/components/editor/columns/cell-column.tsx +4 -34
- package/src/components/editor/columns/sortable-column.tsx +4 -24
- package/src/components/editor/navigation/__tests__/navigation.test.ts +0 -33
- package/src/components/editor/navigation/navigation.ts +1 -8
- package/src/components/editor/notebook-cell.tsx +106 -203
- package/src/components/editor/renderers/cell-array.tsx +13 -26
- package/src/components/editor/renderers/cells-renderer.tsx +2 -8
- package/src/components/editor/renderers/slides-layout/__tests__/plugin.test.ts +0 -29
- package/src/components/editor/renderers/slides-layout/types.ts +0 -4
- package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +19 -19
- package/src/components/slides/reveal-component.tsx +4 -37
- package/src/components/slides/slide-form.tsx +0 -72
- package/src/core/cells/__tests__/utils.test.ts +0 -59
- package/src/core/cells/utils.ts +0 -51
- package/src/core/codemirror/completion/__tests__/signature-hint.test.ts +3 -97
- package/src/core/codemirror/completion/signature-hint.ts +11 -78
- package/src/core/constants.ts +0 -6
- package/src/core/islands/__tests__/bridge.test.ts +46 -341
- package/src/core/islands/__tests__/parse.test.ts +0 -105
- package/src/core/islands/bootstrap.ts +3 -8
- package/src/core/islands/bridge.ts +23 -116
- package/src/core/islands/components/web-components.tsx +15 -76
- package/src/core/islands/constants.ts +0 -1
- package/src/core/islands/main.ts +3 -35
- package/src/core/islands/parse.ts +23 -70
- package/src/core/islands/worker/worker.tsx +57 -145
- package/src/core/wasm/worker/bootstrap.ts +20 -113
- package/src/css/app/Cell.css +0 -10
- package/src/hooks/useHotkey.ts +4 -29
- package/src/plugins/impl/anywidget/__tests__/host.test.ts +9 -6
- package/src/plugins/impl/anywidget/__tests__/widget-binding.test.ts +61 -22
- package/src/plugins/impl/anywidget/model-proxy.ts +13 -0
- package/src/plugins/impl/anywidget/widget-binding.ts +34 -4
- package/src/plugins/impl/image-comparison/ImageComparisonComponent.tsx +2 -53
- package/src/plugins/impl/matplotlib/__tests__/matplotlib-renderer.test.ts +3 -71
- package/src/plugins/impl/matplotlib/matplotlib-renderer.ts +0 -1
- package/src/theme/useTheme.ts +1 -16
- package/dist/assets/__vite-browser-external-CjNAy01L.js +0 -1
- package/dist/assets/worker-B3XPCb6y.js +0 -98
- package/src/components/chat/__tests__/message-queue.test.tsx +0 -121
- package/src/components/editor/__tests__/output-persistence.test.tsx +0 -241
- package/src/components/editor/actions/__tests__/pair-with-agent-commands.test.ts +0 -153
- package/src/components/editor/actions/pair-with-agent-commands.ts +0 -109
- package/src/components/editor/columns/__tests__/cell-column.test.tsx +0 -105
- package/src/components/editor/renderers/__tests__/cells-renderer.test.tsx +0 -66
- package/src/core/islands/components/__tests__/web-components.test.tsx +0 -214
- package/src/core/islands/worker/__tests__/controller.test.ts +0 -173
- package/src/hooks/__tests__/useHotkey.test.tsx +0 -88
- package/src/plugins/impl/image-comparison/__tests__/ImageComparisonComponent.test.tsx +0 -71
- package/src/theme/__tests__/useTheme.test.ts +0 -68
|
@@ -45,22 +45,14 @@ vi.stubGlobal("URL", MockURL);
|
|
|
45
45
|
const {
|
|
46
46
|
mockBridge,
|
|
47
47
|
mockLoadPackages,
|
|
48
|
-
mockReplaceSessionRequest,
|
|
49
|
-
mockStopSessionRequest,
|
|
50
48
|
mockStartSessionRequest,
|
|
51
|
-
mockMessageListeners,
|
|
52
|
-
mockStoreSet,
|
|
53
49
|
mockParseMarimoIslandApps,
|
|
54
50
|
mockCreateMarimoFile,
|
|
55
51
|
mockGetMarimoExportContext,
|
|
56
52
|
} = vi.hoisted(() => ({
|
|
57
53
|
mockBridge: vi.fn(),
|
|
58
54
|
mockLoadPackages: vi.fn(),
|
|
59
|
-
mockReplaceSessionRequest: vi.fn(),
|
|
60
|
-
mockStopSessionRequest: vi.fn(),
|
|
61
55
|
mockStartSessionRequest: vi.fn(),
|
|
62
|
-
mockMessageListeners: new Map<string, (payload: never) => void>(),
|
|
63
|
-
mockStoreSet: vi.fn(),
|
|
64
56
|
mockParseMarimoIslandApps: vi.fn<() => TestIslandApp[]>(() => []),
|
|
65
57
|
mockCreateMarimoFile: vi.fn(),
|
|
66
58
|
mockGetMarimoExportContext: vi.fn<() => TestExportContext | undefined>(
|
|
@@ -74,19 +66,13 @@ vi.mock("@/core/wasm/rpc", () => ({
|
|
|
74
66
|
request: {
|
|
75
67
|
bridge: mockBridge,
|
|
76
68
|
loadPackages: mockLoadPackages,
|
|
77
|
-
replaceSession: mockReplaceSessionRequest,
|
|
78
69
|
startSession: mockStartSessionRequest,
|
|
79
|
-
stopSession: mockStopSessionRequest,
|
|
80
70
|
},
|
|
81
71
|
send: {
|
|
82
72
|
consumerReady: vi.fn(),
|
|
83
73
|
},
|
|
84
74
|
},
|
|
85
|
-
addMessageListener: vi.fn(
|
|
86
|
-
(name: string, listener: (payload: never) => void) => {
|
|
87
|
-
mockMessageListeners.set(name, listener);
|
|
88
|
-
},
|
|
89
|
-
),
|
|
75
|
+
addMessageListener: vi.fn(),
|
|
90
76
|
}),
|
|
91
77
|
}));
|
|
92
78
|
|
|
@@ -111,11 +97,10 @@ vi.mock("@/core/meta/globals", () => ({
|
|
|
111
97
|
}));
|
|
112
98
|
|
|
113
99
|
// Mock the jotai store
|
|
114
|
-
vi.mock("@/core/state/jotai",
|
|
115
|
-
...(await importOriginal<typeof import("@/core/state/jotai")>()),
|
|
100
|
+
vi.mock("@/core/state/jotai", () => ({
|
|
116
101
|
store: {
|
|
117
102
|
get: vi.fn(),
|
|
118
|
-
set:
|
|
103
|
+
set: vi.fn(),
|
|
119
104
|
},
|
|
120
105
|
}));
|
|
121
106
|
|
|
@@ -130,69 +115,40 @@ describe("IslandsPyodideBridge", () => {
|
|
|
130
115
|
mockParseMarimoIslandApps.mockReturnValue([]);
|
|
131
116
|
mockCreateMarimoFile.mockReset();
|
|
132
117
|
mockGetMarimoExportContext.mockReturnValue(undefined);
|
|
133
|
-
|
|
134
|
-
bridge = new IslandsPyodideBridge();
|
|
118
|
+
bridge = new IslandsPyodideBridge({ autoStartSessions: false });
|
|
135
119
|
});
|
|
136
120
|
|
|
137
|
-
|
|
138
|
-
const listener = mockMessageListeners.get("ready");
|
|
139
|
-
if (!listener) {
|
|
140
|
-
throw new Error("Missing worker ready listener");
|
|
141
|
-
}
|
|
142
|
-
listener({} as never);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function app(id = "app-1"): TestIslandApp {
|
|
146
|
-
return {
|
|
147
|
-
id,
|
|
148
|
-
cells: [{ code: "x = 1", idx: 0, output: "<div>1</div>" }],
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function setSingleApp(file = "generated app 1", appId = "app-1") {
|
|
153
|
-
mockParseMarimoIslandApps.mockReturnValue([app(appId)]);
|
|
154
|
-
mockCreateMarimoFile.mockReturnValue(file);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function mockSingleApp(file = "generated app 1", appId = "app-1") {
|
|
158
|
-
setSingleApp(file, appId);
|
|
159
|
-
signalWorkerReady();
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function sendSlider() {
|
|
163
|
-
return bridge.sendComponentValues({
|
|
164
|
-
objectIds: [uiElementId("slider-1")],
|
|
165
|
-
values: [2],
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
async function initializeSingleApp() {
|
|
170
|
-
mockSingleApp();
|
|
171
|
-
await bridge.initializeApps();
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
describe("app initialization", () => {
|
|
121
|
+
describe("startSessionsForAllApps", () => {
|
|
175
122
|
it("should prefer trusted export notebook code when there is exactly one reactive app", async () => {
|
|
176
|
-
mockParseMarimoIslandApps.mockReturnValue([
|
|
123
|
+
mockParseMarimoIslandApps.mockReturnValue([
|
|
124
|
+
{
|
|
125
|
+
id: "app-1",
|
|
126
|
+
cells: [{ code: "x = 1", idx: 0, output: "<div>1</div>" }],
|
|
127
|
+
},
|
|
128
|
+
]);
|
|
177
129
|
mockGetMarimoExportContext.mockReturnValue({
|
|
178
130
|
trusted: true,
|
|
179
131
|
notebookCode:
|
|
180
132
|
"import marimo\napp = marimo.App()\n@app.cell\ndef __():\n x = 1\n return",
|
|
181
133
|
});
|
|
182
134
|
|
|
183
|
-
|
|
184
|
-
|
|
135
|
+
await (
|
|
136
|
+
bridge as unknown as { startSessionsForAllApps(): Promise<void> }
|
|
137
|
+
).startSessionsForAllApps();
|
|
185
138
|
|
|
186
139
|
expect(mockCreateMarimoFile).not.toHaveBeenCalled();
|
|
187
140
|
expect(mockStartSessionRequest).toHaveBeenCalledWith({
|
|
188
141
|
appId: "app-1",
|
|
189
142
|
code: "import marimo\napp = marimo.App()\n@app.cell\ndef __():\n x = 1\n return",
|
|
190
|
-
sessionGeneration: 1,
|
|
191
143
|
});
|
|
192
144
|
});
|
|
193
145
|
|
|
194
146
|
it("should ignore trusted export notebook code for a payload-backed app", async () => {
|
|
195
|
-
const payloadApp = {
|
|
147
|
+
const payloadApp = {
|
|
148
|
+
id: "app-1",
|
|
149
|
+
payloadBacked: true,
|
|
150
|
+
cells: [{ code: "x = 1", idx: 0, output: "<div>1</div>" }],
|
|
151
|
+
};
|
|
196
152
|
mockParseMarimoIslandApps.mockReturnValue([payloadApp]);
|
|
197
153
|
mockGetMarimoExportContext.mockReturnValue({
|
|
198
154
|
trusted: true,
|
|
@@ -200,19 +156,28 @@ describe("IslandsPyodideBridge", () => {
|
|
|
200
156
|
});
|
|
201
157
|
mockCreateMarimoFile.mockReturnValue("generated payload app");
|
|
202
158
|
|
|
203
|
-
|
|
204
|
-
|
|
159
|
+
await (
|
|
160
|
+
bridge as unknown as { startSessionsForAllApps(): Promise<void> }
|
|
161
|
+
).startSessionsForAllApps();
|
|
205
162
|
|
|
206
163
|
expect(mockCreateMarimoFile).toHaveBeenCalledWith(payloadApp);
|
|
207
164
|
expect(mockStartSessionRequest).toHaveBeenCalledWith({
|
|
208
165
|
appId: "app-1",
|
|
209
166
|
code: "generated payload app",
|
|
210
|
-
sessionGeneration: 1,
|
|
211
167
|
});
|
|
212
168
|
});
|
|
213
169
|
|
|
214
170
|
it("should keep synthesized per-app files for multiple reactive apps even when export context exists", async () => {
|
|
215
|
-
mockParseMarimoIslandApps.mockReturnValue([
|
|
171
|
+
mockParseMarimoIslandApps.mockReturnValue([
|
|
172
|
+
{
|
|
173
|
+
id: "app-1",
|
|
174
|
+
cells: [{ code: "x = 1", idx: 0, output: "<div>1</div>" }],
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
id: "app-2",
|
|
178
|
+
cells: [{ code: "y = 2", idx: 0, output: "<div>2</div>" }],
|
|
179
|
+
},
|
|
180
|
+
]);
|
|
216
181
|
mockGetMarimoExportContext.mockReturnValue({
|
|
217
182
|
trusted: true,
|
|
218
183
|
notebookCode: "full notebook should be ignored",
|
|
@@ -221,281 +186,43 @@ describe("IslandsPyodideBridge", () => {
|
|
|
221
186
|
.mockReturnValueOnce("generated app 1")
|
|
222
187
|
.mockReturnValueOnce("generated app 2");
|
|
223
188
|
|
|
224
|
-
|
|
225
|
-
|
|
189
|
+
await (
|
|
190
|
+
bridge as unknown as { startSessionsForAllApps(): Promise<void> }
|
|
191
|
+
).startSessionsForAllApps();
|
|
226
192
|
|
|
227
193
|
expect(mockCreateMarimoFile).toHaveBeenCalledTimes(2);
|
|
228
194
|
expect(mockStartSessionRequest).toHaveBeenNthCalledWith(1, {
|
|
229
195
|
appId: "app-1",
|
|
230
196
|
code: "generated app 1",
|
|
231
|
-
sessionGeneration: 1,
|
|
232
197
|
});
|
|
233
198
|
expect(mockStartSessionRequest).toHaveBeenNthCalledWith(2, {
|
|
234
199
|
appId: "app-2",
|
|
235
200
|
code: "generated app 2",
|
|
236
|
-
sessionGeneration: 2,
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
await sendSlider();
|
|
240
|
-
await bridge.stopSession();
|
|
241
|
-
|
|
242
|
-
expect(mockReplaceSessionRequest).not.toHaveBeenCalled();
|
|
243
|
-
expect(mockStopSessionRequest).not.toHaveBeenCalled();
|
|
244
|
-
expect(mockBridge).toHaveBeenCalledWith({
|
|
245
|
-
appId: "app-1",
|
|
246
|
-
functionName: "put_control_request",
|
|
247
|
-
payload: {
|
|
248
|
-
objectIds: ["slider-1"],
|
|
249
|
-
token: "test-uuid-12345",
|
|
250
|
-
type: "update-ui-element",
|
|
251
|
-
values: [2],
|
|
252
|
-
},
|
|
253
|
-
sessionGeneration: 1,
|
|
254
201
|
});
|
|
255
202
|
});
|
|
256
203
|
|
|
257
204
|
it("should synthesize a file for a single app when no trusted export context is present", async () => {
|
|
258
|
-
|
|
205
|
+
mockParseMarimoIslandApps.mockReturnValue([
|
|
206
|
+
{
|
|
207
|
+
id: "app-1",
|
|
208
|
+
cells: [{ code: "x = 1", idx: 0, output: "<div>1</div>" }],
|
|
209
|
+
},
|
|
210
|
+
]);
|
|
211
|
+
mockCreateMarimoFile.mockReturnValue("generated app 1");
|
|
259
212
|
|
|
260
|
-
|
|
261
|
-
|
|
213
|
+
await (
|
|
214
|
+
bridge as unknown as { startSessionsForAllApps(): Promise<void> }
|
|
215
|
+
).startSessionsForAllApps();
|
|
262
216
|
|
|
263
217
|
expect(mockCreateMarimoFile).toHaveBeenCalledTimes(1);
|
|
264
218
|
expect(mockStartSessionRequest).toHaveBeenCalledWith({
|
|
265
219
|
appId: "app-1",
|
|
266
220
|
code: "generated app 1",
|
|
267
|
-
sessionGeneration: 1,
|
|
268
|
-
});
|
|
269
|
-
});
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
describe("app lifecycle", () => {
|
|
273
|
-
it("holds controls until the first app session is ready", async () => {
|
|
274
|
-
setSingleApp();
|
|
275
|
-
|
|
276
|
-
const initialization = bridge.initializeApps();
|
|
277
|
-
const control = sendSlider();
|
|
278
|
-
await Promise.resolve();
|
|
279
|
-
|
|
280
|
-
expect(mockBridge).not.toHaveBeenCalled();
|
|
281
|
-
|
|
282
|
-
signalWorkerReady();
|
|
283
|
-
await Promise.all([initialization, control]);
|
|
284
|
-
expect(mockBridge).toHaveBeenCalledWith(
|
|
285
|
-
expect.objectContaining({
|
|
286
|
-
appId: "app-1",
|
|
287
|
-
sessionGeneration: 1,
|
|
288
|
-
}),
|
|
289
|
-
);
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
it("starts the first app, skips duplicates, and replaces changed source", async () => {
|
|
293
|
-
mockSingleApp();
|
|
294
|
-
|
|
295
|
-
await bridge.initializeApps();
|
|
296
|
-
await bridge.initializeApps();
|
|
297
|
-
await sendSlider();
|
|
298
|
-
|
|
299
|
-
expect(mockStartSessionRequest).toHaveBeenCalledOnce();
|
|
300
|
-
expect(mockStartSessionRequest).toHaveBeenCalledWith({
|
|
301
|
-
appId: "app-1",
|
|
302
|
-
code: "generated app 1",
|
|
303
|
-
sessionGeneration: 1,
|
|
304
|
-
});
|
|
305
|
-
expect(mockReplaceSessionRequest).not.toHaveBeenCalled();
|
|
306
|
-
expect(mockBridge).toHaveBeenCalledWith(
|
|
307
|
-
expect.objectContaining({
|
|
308
|
-
appId: "app-1",
|
|
309
|
-
sessionGeneration: 1,
|
|
310
|
-
}),
|
|
311
|
-
);
|
|
312
|
-
mockCreateMarimoFile.mockReturnValue("generated app 2");
|
|
313
|
-
await bridge.initializeApps();
|
|
314
|
-
|
|
315
|
-
expect(mockReplaceSessionRequest).toHaveBeenCalledOnce();
|
|
316
|
-
expect(mockReplaceSessionRequest).toHaveBeenCalledWith({
|
|
317
|
-
appId: "app-1",
|
|
318
|
-
code: "generated app 2",
|
|
319
|
-
sessionGeneration: 2,
|
|
320
|
-
});
|
|
321
|
-
expect(mockStoreSet).toHaveBeenCalledOnce();
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
it("stops the matching active app", async () => {
|
|
325
|
-
mockSingleApp();
|
|
326
|
-
await bridge.initializeApps();
|
|
327
|
-
|
|
328
|
-
await bridge.stopSession("other-app");
|
|
329
|
-
await bridge.stopSession("app-1");
|
|
330
|
-
|
|
331
|
-
expect(mockStopSessionRequest).toHaveBeenCalledOnce();
|
|
332
|
-
expect(mockStopSessionRequest).toHaveBeenCalledWith({
|
|
333
|
-
appId: "app-1",
|
|
334
|
-
sessionGeneration: 1,
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
mockBridge.mockClear();
|
|
338
|
-
const control = sendSlider();
|
|
339
|
-
await Promise.resolve();
|
|
340
|
-
expect(mockBridge).not.toHaveBeenCalled();
|
|
341
|
-
|
|
342
|
-
mockSingleApp("generated app 2");
|
|
343
|
-
await bridge.initializeApps();
|
|
344
|
-
await control;
|
|
345
|
-
|
|
346
|
-
expect(mockBridge).toHaveBeenCalledWith(
|
|
347
|
-
expect.objectContaining({ sessionGeneration: 2 }),
|
|
348
|
-
);
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
it("replaces the active session after stop fails", async () => {
|
|
352
|
-
mockSingleApp();
|
|
353
|
-
await bridge.initializeApps();
|
|
354
|
-
mockStopSessionRequest.mockRejectedValueOnce(new Error("stop failed"));
|
|
355
|
-
|
|
356
|
-
await expect(bridge.stopSession("app-1")).rejects.toThrow("stop failed");
|
|
357
|
-
|
|
358
|
-
mockSingleApp("generated app 2", "app-2");
|
|
359
|
-
|
|
360
|
-
await bridge.initializeApps();
|
|
361
|
-
|
|
362
|
-
expect(mockStartSessionRequest).toHaveBeenCalledOnce();
|
|
363
|
-
expect(mockReplaceSessionRequest).toHaveBeenCalledWith({
|
|
364
|
-
appId: "app-2",
|
|
365
|
-
code: "generated app 2",
|
|
366
|
-
sessionGeneration: 2,
|
|
367
|
-
});
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
it("keeps controls sent during stop scoped to the stopped app", async () => {
|
|
371
|
-
mockSingleApp();
|
|
372
|
-
await bridge.initializeApps();
|
|
373
|
-
mockBridge.mockClear();
|
|
374
|
-
let finishStop!: () => void;
|
|
375
|
-
mockStopSessionRequest.mockReturnValueOnce(
|
|
376
|
-
new Promise<void>((resolve) => {
|
|
377
|
-
finishStop = resolve;
|
|
378
|
-
}),
|
|
379
|
-
);
|
|
380
|
-
|
|
381
|
-
const stop = bridge.stopSession("app-1");
|
|
382
|
-
await vi.waitFor(() =>
|
|
383
|
-
expect(mockStopSessionRequest).toHaveBeenCalledOnce(),
|
|
384
|
-
);
|
|
385
|
-
const control = sendSlider();
|
|
386
|
-
mockSingleApp("generated app 2", "app-2");
|
|
387
|
-
const nextInitialization = bridge.initializeApps();
|
|
388
|
-
|
|
389
|
-
finishStop();
|
|
390
|
-
await Promise.all([stop, control, nextInitialization]);
|
|
391
|
-
|
|
392
|
-
expect(mockBridge).toHaveBeenCalledWith(
|
|
393
|
-
expect.objectContaining({
|
|
394
|
-
appId: "app-1",
|
|
395
|
-
sessionGeneration: 1,
|
|
396
|
-
}),
|
|
397
|
-
);
|
|
398
|
-
});
|
|
399
|
-
|
|
400
|
-
it("allows initialization to retry after replacement fails", async () => {
|
|
401
|
-
mockSingleApp();
|
|
402
|
-
await bridge.initializeApps();
|
|
403
|
-
mockCreateMarimoFile.mockReturnValue("generated app 2");
|
|
404
|
-
mockReplaceSessionRequest.mockRejectedValueOnce(
|
|
405
|
-
new Error("replacement failed"),
|
|
406
|
-
);
|
|
407
|
-
|
|
408
|
-
await expect(bridge.initializeApps()).rejects.toThrow(
|
|
409
|
-
"replacement failed",
|
|
410
|
-
);
|
|
411
|
-
await bridge.initializeApps();
|
|
412
|
-
|
|
413
|
-
expect(mockStartSessionRequest).toHaveBeenCalledOnce();
|
|
414
|
-
expect(mockReplaceSessionRequest).toHaveBeenCalledTimes(2);
|
|
415
|
-
expect(mockReplaceSessionRequest).toHaveBeenLastCalledWith({
|
|
416
|
-
appId: "app-1",
|
|
417
|
-
code: "generated app 2",
|
|
418
|
-
sessionGeneration: 3,
|
|
419
221
|
});
|
|
420
222
|
});
|
|
421
|
-
|
|
422
|
-
it("scopes controls to the replacement active when they were sent", async () => {
|
|
423
|
-
mockSingleApp();
|
|
424
|
-
await bridge.initializeApps();
|
|
425
|
-
mockBridge.mockClear();
|
|
426
|
-
mockCreateMarimoFile.mockReturnValue("generated app 2");
|
|
427
|
-
let finishSecondApp!: () => void;
|
|
428
|
-
let finishThirdApp!: () => void;
|
|
429
|
-
mockReplaceSessionRequest
|
|
430
|
-
.mockReturnValueOnce(
|
|
431
|
-
new Promise<void>((resolve) => {
|
|
432
|
-
finishSecondApp = resolve;
|
|
433
|
-
}),
|
|
434
|
-
)
|
|
435
|
-
.mockReturnValueOnce(
|
|
436
|
-
new Promise<void>((resolve) => {
|
|
437
|
-
finishThirdApp = resolve;
|
|
438
|
-
}),
|
|
439
|
-
);
|
|
440
|
-
|
|
441
|
-
const secondInitialization = bridge.initializeApps();
|
|
442
|
-
await vi.waitFor(() =>
|
|
443
|
-
expect(mockReplaceSessionRequest).toHaveBeenCalledOnce(),
|
|
444
|
-
);
|
|
445
|
-
const control = sendSlider();
|
|
446
|
-
mockSingleApp("generated app 3", "app-2");
|
|
447
|
-
const thirdInitialization = bridge.initializeApps();
|
|
448
|
-
|
|
449
|
-
expect(mockBridge).not.toHaveBeenCalled();
|
|
450
|
-
|
|
451
|
-
finishSecondApp();
|
|
452
|
-
await vi.waitFor(() => expect(mockBridge).toHaveBeenCalledOnce());
|
|
453
|
-
expect(mockBridge).toHaveBeenCalledWith(
|
|
454
|
-
expect.objectContaining({
|
|
455
|
-
appId: "app-1",
|
|
456
|
-
sessionGeneration: 2,
|
|
457
|
-
}),
|
|
458
|
-
);
|
|
459
|
-
|
|
460
|
-
finishThirdApp();
|
|
461
|
-
await Promise.all([secondInitialization, thirdInitialization, control]);
|
|
462
|
-
});
|
|
463
|
-
|
|
464
|
-
it("keeps controls from a failed replacement out of the next app", async () => {
|
|
465
|
-
mockSingleApp();
|
|
466
|
-
await bridge.initializeApps();
|
|
467
|
-
mockBridge.mockClear();
|
|
468
|
-
mockCreateMarimoFile.mockReturnValue("generated app 2");
|
|
469
|
-
let failReplacement!: (error: Error) => void;
|
|
470
|
-
mockReplaceSessionRequest.mockReturnValueOnce(
|
|
471
|
-
new Promise<void>((_resolve, reject) => {
|
|
472
|
-
failReplacement = reject;
|
|
473
|
-
}),
|
|
474
|
-
);
|
|
475
|
-
|
|
476
|
-
const failedInitialization = bridge.initializeApps();
|
|
477
|
-
await vi.waitFor(() =>
|
|
478
|
-
expect(mockReplaceSessionRequest).toHaveBeenCalledOnce(),
|
|
479
|
-
);
|
|
480
|
-
const control = sendSlider();
|
|
481
|
-
failReplacement(new Error("replacement failed"));
|
|
482
|
-
await expect(failedInitialization).rejects.toThrow("replacement failed");
|
|
483
|
-
|
|
484
|
-
mockSingleApp("generated app 3", "app-2");
|
|
485
|
-
await Promise.all([bridge.initializeApps(), control]);
|
|
486
|
-
|
|
487
|
-
expect(mockBridge).toHaveBeenCalledWith(
|
|
488
|
-
expect.objectContaining({
|
|
489
|
-
appId: "app-1",
|
|
490
|
-
sessionGeneration: 2,
|
|
491
|
-
}),
|
|
492
|
-
);
|
|
493
|
-
});
|
|
494
223
|
});
|
|
495
224
|
|
|
496
225
|
describe("sendComponentValues", () => {
|
|
497
|
-
beforeEach(initializeSingleApp);
|
|
498
|
-
|
|
499
226
|
it("should include type field and token in control request", async () => {
|
|
500
227
|
const request = {
|
|
501
228
|
objectIds: [uiElementId("Hbol-0")],
|
|
@@ -505,7 +232,6 @@ describe("IslandsPyodideBridge", () => {
|
|
|
505
232
|
await bridge.sendComponentValues(request);
|
|
506
233
|
|
|
507
234
|
expect(mockBridge).toHaveBeenCalledWith({
|
|
508
|
-
appId: "app-1",
|
|
509
235
|
functionName: "put_control_request",
|
|
510
236
|
payload: {
|
|
511
237
|
type: "update-ui-element",
|
|
@@ -513,7 +239,6 @@ describe("IslandsPyodideBridge", () => {
|
|
|
513
239
|
values: [58],
|
|
514
240
|
token: "test-uuid-12345",
|
|
515
241
|
},
|
|
516
|
-
sessionGeneration: 1,
|
|
517
242
|
});
|
|
518
243
|
});
|
|
519
244
|
|
|
@@ -526,21 +251,17 @@ describe("IslandsPyodideBridge", () => {
|
|
|
526
251
|
await bridge.sendComponentValues(request);
|
|
527
252
|
|
|
528
253
|
expect(mockBridge).toHaveBeenCalledWith({
|
|
529
|
-
appId: "app-1",
|
|
530
254
|
functionName: "put_control_request",
|
|
531
255
|
payload: expect.objectContaining({
|
|
532
256
|
type: "update-ui-element",
|
|
533
257
|
objectIds: ["slider-1", "slider-2"],
|
|
534
258
|
values: [10, 20],
|
|
535
259
|
}),
|
|
536
|
-
sessionGeneration: 1,
|
|
537
260
|
});
|
|
538
261
|
});
|
|
539
262
|
});
|
|
540
263
|
|
|
541
264
|
describe("sendFunctionRequest", () => {
|
|
542
|
-
beforeEach(initializeSingleApp);
|
|
543
|
-
|
|
544
265
|
it("should include type field in control request", async () => {
|
|
545
266
|
const request = {
|
|
546
267
|
functionCallId: requestId("call-123"),
|
|
@@ -552,7 +273,6 @@ describe("IslandsPyodideBridge", () => {
|
|
|
552
273
|
await bridge.sendFunctionRequest(request);
|
|
553
274
|
|
|
554
275
|
expect(mockBridge).toHaveBeenCalledWith({
|
|
555
|
-
appId: "app-1",
|
|
556
276
|
functionName: "put_control_request",
|
|
557
277
|
payload: {
|
|
558
278
|
type: "invoke-function",
|
|
@@ -561,14 +281,11 @@ describe("IslandsPyodideBridge", () => {
|
|
|
561
281
|
functionName: "my_function",
|
|
562
282
|
args: { x: 1, y: 2 },
|
|
563
283
|
},
|
|
564
|
-
sessionGeneration: 1,
|
|
565
284
|
});
|
|
566
285
|
});
|
|
567
286
|
});
|
|
568
287
|
|
|
569
288
|
describe("sendRun", () => {
|
|
570
|
-
beforeEach(initializeSingleApp);
|
|
571
|
-
|
|
572
289
|
it("should include type field in control request", async () => {
|
|
573
290
|
const request = {
|
|
574
291
|
cellIds: [cellId("cell-1"), cellId("cell-2")],
|
|
@@ -578,14 +295,12 @@ describe("IslandsPyodideBridge", () => {
|
|
|
578
295
|
await bridge.sendRun(request);
|
|
579
296
|
|
|
580
297
|
expect(mockBridge).toHaveBeenCalledWith({
|
|
581
|
-
appId: "app-1",
|
|
582
298
|
functionName: "put_control_request",
|
|
583
299
|
payload: {
|
|
584
300
|
type: "execute-cells",
|
|
585
301
|
cellIds: ["cell-1", "cell-2"],
|
|
586
302
|
codes: ["print('hello')", "print('world')"],
|
|
587
303
|
},
|
|
588
|
-
sessionGeneration: 1,
|
|
589
304
|
});
|
|
590
305
|
});
|
|
591
306
|
|
|
@@ -598,11 +313,7 @@ describe("IslandsPyodideBridge", () => {
|
|
|
598
313
|
await bridge.sendRun(request);
|
|
599
314
|
|
|
600
315
|
// Verify loadPackages was called with joined codes
|
|
601
|
-
expect(mockLoadPackages).toHaveBeenCalledWith(
|
|
602
|
-
appId: "app-1",
|
|
603
|
-
code: "import pandas",
|
|
604
|
-
sessionGeneration: 1,
|
|
605
|
-
});
|
|
316
|
+
expect(mockLoadPackages).toHaveBeenCalledWith("import pandas");
|
|
606
317
|
|
|
607
318
|
// Verify order: loadPackages should be called before bridge
|
|
608
319
|
const loadPackagesCallOrder =
|
|
@@ -613,8 +324,6 @@ describe("IslandsPyodideBridge", () => {
|
|
|
613
324
|
});
|
|
614
325
|
|
|
615
326
|
describe("sendModelValue", () => {
|
|
616
|
-
beforeEach(initializeSingleApp);
|
|
617
|
-
|
|
618
327
|
it("should include type field in control request", async () => {
|
|
619
328
|
const request = {
|
|
620
329
|
modelId: widgetModelId("widget-1"),
|
|
@@ -629,7 +338,6 @@ describe("IslandsPyodideBridge", () => {
|
|
|
629
338
|
await bridge.sendModelValue(request);
|
|
630
339
|
|
|
631
340
|
expect(mockBridge).toHaveBeenCalledWith({
|
|
632
|
-
appId: "app-1",
|
|
633
341
|
functionName: "put_control_request",
|
|
634
342
|
payload: {
|
|
635
343
|
type: "model",
|
|
@@ -641,14 +349,11 @@ describe("IslandsPyodideBridge", () => {
|
|
|
641
349
|
},
|
|
642
350
|
buffers: [],
|
|
643
351
|
},
|
|
644
|
-
sessionGeneration: 1,
|
|
645
352
|
});
|
|
646
353
|
});
|
|
647
354
|
});
|
|
648
355
|
|
|
649
356
|
describe("control request message format", () => {
|
|
650
|
-
beforeEach(initializeSingleApp);
|
|
651
|
-
|
|
652
357
|
it("should always include the type field required by msgspec", async () => {
|
|
653
358
|
// Test all methods to ensure they include the type field
|
|
654
359
|
await bridge.sendComponentValues({ objectIds: [], values: [] });
|