@marimo-team/frontend 0.19.3-dev8 → 0.19.4-dev0
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/{CellStatus-BwPGnX3z.js → CellStatus--kUu6N2K.js} +1 -1
- package/dist/assets/{ConnectedDataExplorerComponent-KlUs_Sz3.js → ConnectedDataExplorerComponent-BKJwCHu7.js} +1 -1
- package/dist/assets/{ErrorBoundary-Drf1manw.js → ErrorBoundary-C7JBxSzd.js} +1 -1
- package/dist/assets/{ImperativeModal-q6QlC2aZ.js → ImperativeModal-DVhvP4lH.js} +1 -1
- package/dist/assets/{JsonOutput--AuyEErr.js → JsonOutput-BSGE-MRo.js} +5 -5
- package/dist/assets/{LazyAnyLanguageCodeMirror-jpEDlD0M.js → LazyAnyLanguageCodeMirror-Cp2punaU.js} +2 -2
- package/dist/assets/{MarimoErrorOutput-BZjY8e2w.js → MarimoErrorOutput-CX0SCJOZ.js} +2 -2
- package/dist/assets/{RenderHTML-BTLaM20B.js → RenderHTML-Do_PVqRy.js} +1 -1
- package/dist/assets/VisuallyHidden-B9t3FhTP.js +1 -0
- package/dist/assets/{add-cell-with-ai-BWWVs9qV.js → add-cell-with-ai-manh7kBT.js} +21 -21
- package/dist/assets/{add-database-form-Bw_YRH1r.js → add-database-form-CgkV0MRs.js} +2 -2
- package/dist/assets/agent-panel-D-OmT-rw.js +287 -0
- package/dist/assets/{ai-model-dropdown-BrUOgnWS.js → ai-model-dropdown-DzyBY5VA.js} +1 -1
- package/dist/assets/{alert-dialog-k5KxevGr.js → alert-dialog-jcHA5geR.js} +1 -1
- package/dist/assets/{any-language-editor-DQu1Tt2N.js → any-language-editor-Cm83E7D_.js} +1 -1
- package/dist/assets/{app-config-button-B8CXELx0.js → app-config-button-DC3alCuB.js} +1 -1
- package/dist/assets/button-B8cGZzP5.js +1 -0
- package/dist/assets/{cache-panel-C1So4Zu3.js → cache-panel-1FqnpB9y.js} +1 -1
- package/dist/assets/cell-editor-RHFZmO74.js +23 -0
- package/dist/assets/cell-link-Dqj_nfXA.js +1 -0
- package/dist/assets/{cells-DU3EySUd.js → cells-BNQUQiDS.js} +49 -49
- package/dist/assets/{chat-components-Bc9j9ls4.js → chat-components-CWiXtKu6.js} +1 -1
- package/dist/assets/{chat-display-BrTi6c8V.js → chat-display-CGnOamQG.js} +1 -1
- package/dist/assets/{chat-panel-8Dym5Gv3.js → chat-panel-Dh1M55c9.js} +2 -2
- package/dist/assets/client-CDjmJmVw.js +4 -0
- package/dist/assets/{column-preview-Ck6B_-sQ.js → column-preview-CKxT2s-S.js} +1 -1
- package/dist/assets/{command-B_minI8b.js → command-YPFTinLj.js} +1 -1
- package/dist/assets/{command-palette-BT3u6JBB.js → command-palette-7fVEhKGc.js} +1 -1
- package/dist/assets/common-DJkPpBxC.js +1 -0
- package/dist/assets/config-D6nhy4FA.js +1 -0
- package/dist/assets/context-DHfVoQfl.js +1 -0
- package/dist/assets/{copy-icon-B69c-352.js → copy-icon-jWsqdLn1.js} +1 -1
- package/dist/assets/{datasource-DCvPlnaJ.js → datasource-DerBLc6V.js} +2 -2
- package/dist/assets/{dependency-graph-panel-C9jYZ6pA.js → dependency-graph-panel-Vd-OsVLa.js} +4 -4
- package/dist/assets/{dialog-DUEuLcT2.js → dialog-CF5DtF1E.js} +1 -1
- package/dist/assets/{dist-DOFFh6Ii.js → dist-Dg7UO_Vw.js} +1 -1
- package/dist/assets/{documentation-panel-AsatrTfg.js → documentation-panel-xG2-zpwg.js} +1 -1
- package/dist/assets/{download-PR1bF3g_.js → download-B6EJS7Ar.js} +1 -1
- package/dist/assets/edit-page-7Hkti2j_.js +12 -0
- package/dist/assets/{error-banner-DU5Qb8a8.js → error-banner-DvT0IGDZ.js} +1 -1
- package/dist/assets/{error-panel-D_wVKV6I.js → error-panel-BxBpZYvt.js} +1 -1
- package/dist/assets/{es-CEE_7T0w.js → es-BoHEdemq.js} +1 -1
- package/dist/assets/{field-DDKGFzpC.js → field-Clr_fqUr.js} +1 -1
- package/dist/assets/{file-explorer-panel-DltK8JVp.js → file-explorer-panel-C9K0vIPl.js} +1 -1
- package/dist/assets/{floating-outline-BfdazXWm.js → floating-outline-DCrTuu2G.js} +1 -1
- package/dist/assets/{focus-CtlWIiQr.js → focus-DM53w5BH.js} +1 -1
- package/dist/assets/{form-Cy5TkLzh.js → form-BcKfhfZc.js} +2 -2
- package/dist/assets/{glide-data-editor-D_bRnWfy.js → glide-data-editor-CRb9AiCG.js} +1 -1
- package/dist/assets/{globals-BSLm1nlz.js → globals-Bf30kOQF.js} +1 -1
- package/dist/assets/{home-page-CEnaUutq.js → home-page-BRyNf7fl.js} +2 -2
- package/dist/assets/index-CBMqMxiq.js +43 -0
- package/dist/assets/index-DDc_1b-N.css +2 -0
- package/dist/assets/input-B80Yt1uu.js +1 -0
- package/dist/assets/{kiosk-mode-BnTZR6mM.js → kiosk-mode-P-NYHJID.js} +1 -1
- package/dist/assets/{label-qwandMoh.js → label-CNZLffHW.js} +1 -1
- package/dist/assets/{layout-BTiWDrbh.js → layout-DT91GUei.js} +4 -4
- package/dist/assets/links-D529u6GQ.js +1 -0
- package/dist/assets/{logs-panel-Cnp9tO_1.js → logs-panel-C2dfrRig.js} +1 -1
- package/dist/assets/{markdown-renderer-BSrbBHwX.js → markdown-renderer-BPnVa0ym.js} +2 -2
- package/dist/assets/{mermaid-BPkO79lo.js → mermaid--ZwxKP7u.js} +1 -1
- package/dist/assets/mode-Dq8MKjNR.js +1 -0
- package/dist/assets/{multi-map-fjX9ImVF.js → multi-map-CQd4MZr5.js} +1 -1
- package/dist/assets/name-cell-input-BaEPC7ON.js +1 -0
- package/dist/assets/{outline-panel-DCfj1bI-.js → outline-panel-Cca864H0.js} +1 -1
- package/dist/assets/{packages-panel-BiEckVdM.js → packages-panel-Cy_KAYmq.js} +1 -1
- package/dist/assets/{panels-BXRys72u.js → panels-BzlLZfye.js} +1 -1
- package/dist/assets/{process-output-wGlHkL-Q.js → process-output-Dn1rOp26.js} +1 -1
- package/dist/assets/{readonly-python-code-xbh7G2Y2.js → readonly-python-code-CXeF74Iq.js} +1 -1
- package/dist/assets/{renderShortcut-D0Pei-OA.js → renderShortcut-eU5Hsfml.js} +1 -1
- package/dist/assets/{run-page-BCwJRhCq.js → run-page-CM_n6pXD.js} +1 -1
- package/dist/assets/scratchpad-panel-XCkVY3Hp.js +1 -0
- package/dist/assets/{secrets-panel-CDWmmmBS.js → secrets-panel-BMY6PPth.js} +1 -1
- package/dist/assets/{select-D0g5GnIs.js → select-D9lTzMzP.js} +1 -1
- package/dist/assets/{session-panel-DuQl_oQp.js → session-panel-BDt6Y_mU.js} +1 -1
- package/dist/assets/{slides-component-MkPkpql1.js → slides-component-Dp0Yv5b0.js} +1 -1
- package/dist/assets/{snippets-panel-R_ql6HGu.js → snippets-panel-K-JKJQBf.js} +1 -1
- package/dist/assets/state-DWRZTH2y.js +1 -0
- package/dist/assets/state-JzO-Ni5T.js +1 -0
- package/dist/assets/{switch-CWzL-0WF.js → switch-RowEjq0T.js} +1 -1
- package/dist/assets/{terminal-BWM0fOMh.js → terminal-BhbNfCNw.js} +1 -1
- package/dist/assets/{textarea-CfvBt_Xm.js → textarea-Di1KKcL4.js} +1 -1
- package/dist/assets/{tracing-Kscqc1t3.js → tracing-nvbrZdpf.js} +1 -1
- package/dist/assets/{tracing-panel-BEzOflWc.js → tracing-panel-CTXJaO-A.js} +2 -2
- package/dist/assets/{types-DhuSHMNQ.js → types-CT2U5Ljy.js} +1 -1
- package/dist/assets/{useAddCell-C9lbOVO1.js → useAddCell-COb93CUl.js} +1 -1
- package/dist/assets/{useBoolean-B-A0dyIW.js → useBoolean-B_S7yTZz.js} +1 -1
- package/dist/assets/{useCellActionButton-fsh9MTAX.js → useCellActionButton-D5Zt1dDz.js} +1 -1
- package/dist/assets/{useDateFormatter-CV0QXb5P.js → useDateFormatter-DsANziQR.js} +1 -1
- package/dist/assets/useDeleteCell-DHF_xvAh.js +1 -0
- package/dist/assets/{useDependencyPanelTab-CngFbla0.js → useDependencyPanelTab-D59iW_MD.js} +1 -1
- package/dist/assets/useInterval-BGPIviJp.js +1 -0
- package/dist/assets/{useNotebookActions-D01w160c.js → useNotebookActions-DEl-rH-3.js} +1 -1
- package/dist/assets/{useNumberFormatter-D8ks3oPN.js → useNumberFormatter-FoXhpyAb.js} +1 -1
- package/dist/assets/usePress-DTwIUo40.js +7 -0
- package/dist/assets/useRunCells-CKEmgeKM.js +1 -0
- package/dist/assets/useSplitCell-D9YiO-z5.js +1 -0
- package/dist/assets/{useTheme-DfP1CWaW.js → useTheme-CNj0G_ol.js} +1 -1
- package/dist/assets/utilities.esm-DG4qccZc.js +3 -0
- package/dist/assets/utils-pfqq9IdB.js +1 -0
- package/dist/assets/{vega-component-B8ghmMYW.js → vega-component-C1voDf5W.js} +1 -1
- package/dist/assets/{write-secret-modal-CLm48gMe.js → write-secret-modal-hOetwavI.js} +1 -1
- package/dist/index.html +56 -56
- package/package.json +5 -5
- package/src/__mocks__/requests.ts +1 -0
- package/src/__tests__/mount.test.ts +128 -0
- package/src/components/app-config/__tests__/get-dirty-values.test.ts +1 -1
- package/src/components/app-config/ai-config.tsx +328 -28
- package/src/components/app-config/user-config-form.tsx +10 -3
- package/src/components/chat/acp/agent-panel.tsx +56 -43
- package/src/components/chat/chat-utils.ts +0 -19
- package/src/components/data-table/column-header.tsx +1 -1
- package/src/components/editor/KernelStartupErrorModal.tsx +2 -2
- package/src/components/editor/actions/name-cell-input.tsx +10 -4
- package/src/components/editor/ai/completion-handlers.tsx +1 -1
- package/src/components/editor/alerts/connecting-alert.tsx +33 -6
- package/src/components/editor/chrome/types.ts +2 -4
- package/src/components/editor/chrome/wrapper/app-chrome.tsx +55 -58
- package/src/components/editor/chrome/wrapper/footer-items/runtime-settings.tsx +150 -96
- package/src/components/editor/renderers/vertical-layout/__tests__/useFocusFirstEditor.test.ts +27 -0
- package/src/components/editor/renderers/vertical-layout/useFocusFirstEditor.ts +6 -0
- package/src/components/utils/lazy-mount.tsx +29 -8
- package/src/core/ai/ids/ids.ts +12 -4
- package/src/core/cells/cells.ts +2 -0
- package/src/core/cells/scrollCellIntoView.ts +3 -2
- package/src/core/codemirror/cm.ts +2 -0
- package/src/core/codemirror/lsp/__tests__/notebook-lsp.test.ts +123 -0
- package/src/core/codemirror/lsp/notebook-lsp.ts +44 -4
- package/src/core/codemirror/misc/__tests__/string-braces.test.ts +200 -0
- package/src/core/codemirror/misc/string-braces.ts +37 -0
- package/src/core/config/__tests__/config-schema.test.ts +36 -0
- package/src/core/config/config-schema.ts +1 -0
- package/src/core/export/__tests__/hooks.test.ts +504 -0
- package/src/core/export/hooks.ts +93 -4
- package/src/core/islands/bridge.ts +1 -0
- package/src/core/kernel/__tests__/handlers.test.ts +2 -2
- package/src/core/kernel/state.ts +1 -0
- package/src/core/network/__tests__/requests-lazy.test.ts +1 -1
- package/src/core/network/__tests__/requests-network.test.ts +0 -18
- package/src/core/network/requests-lazy.ts +3 -2
- package/src/core/network/requests-network.ts +10 -7
- package/src/core/network/requests-static.ts +1 -0
- package/src/core/network/requests-toasting.tsx +1 -0
- package/src/core/network/types.ts +2 -0
- package/src/core/wasm/bridge.ts +1 -0
- package/src/css/globals.css +2 -0
- package/src/hooks/__tests__/useInterval.test.tsx +104 -0
- package/src/hooks/useInterval.ts +32 -6
- package/src/mount.tsx +6 -0
- package/src/plugins/impl/chat/ChatPlugin.tsx +2 -4
- package/src/plugins/impl/chat/chat-ui.tsx +62 -191
- package/src/plugins/impl/chat/types.ts +5 -12
- package/src/plugins/impl/data-frames/DataFramePlugin.tsx +3 -1
- package/src/utils/events.ts +1 -0
- package/dist/assets/VisuallyHidden-BodIky8L.js +0 -1
- package/dist/assets/agent-panel-Bm-vW8YL.js +0 -287
- package/dist/assets/button-DuYGqRtX.js +0 -1
- package/dist/assets/cell-editor-Cdtc1m3g.js +0 -23
- package/dist/assets/cell-link-CFAPzUg5.js +0 -1
- package/dist/assets/client-DfkWorYM.js +0 -4
- package/dist/assets/common-jorbwXZC.js +0 -1
- package/dist/assets/config-Ba3eeYri.js +0 -1
- package/dist/assets/context-BAYdLMF_.js +0 -1
- package/dist/assets/edit-page-B1Ed6RKp.js +0 -12
- package/dist/assets/index-DNg7_e7t.js +0 -43
- package/dist/assets/index-__6MNWbe.css +0 -2
- package/dist/assets/input-CaEtLL8p.js +0 -1
- package/dist/assets/links-Bpd4gqTj.js +0 -1
- package/dist/assets/mode-yhfN-4ye.js +0 -1
- package/dist/assets/name-cell-input-CmuWqgFR.js +0 -1
- package/dist/assets/scratchpad-panel-C6PpCYtK.js +0 -1
- package/dist/assets/state-DEHWsmkM.js +0 -1
- package/dist/assets/state-DXAf-ejz.js +0 -1
- package/dist/assets/useDeleteCell-ByImoTpm.js +0 -1
- package/dist/assets/useInterval-DpipYmgs.js +0 -1
- package/dist/assets/usePress-C2LPFxyv.js +0 -7
- package/dist/assets/useRunCells-CmnSPQtM.js +0 -1
- package/dist/assets/useSplitCell-BTH64tve.js +0 -1
- package/dist/assets/utilities.esm-CMQs6YPp.js +0 -3
- package/dist/assets/utils-CJJIceVn.js +0 -1
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { renderHook } from "@testing-library/react";
|
|
4
|
+
import { createStore, Provider } from "jotai";
|
|
5
|
+
import type { ReactNode } from "react";
|
|
6
|
+
import * as React from "react";
|
|
7
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
8
|
+
import type { CellId } from "@/core/cells/ids";
|
|
9
|
+
import { CellOutputId } from "@/core/cells/ids";
|
|
10
|
+
import type { CellRuntimeState } from "@/core/cells/types";
|
|
11
|
+
import { useEnrichCellOutputs } from "../hooks";
|
|
12
|
+
|
|
13
|
+
// Mock html-to-image
|
|
14
|
+
vi.mock("html-to-image", () => ({
|
|
15
|
+
toPng: vi.fn(),
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
// Mock Logger
|
|
19
|
+
vi.mock("@/utils/Logger", () => ({
|
|
20
|
+
Logger: {
|
|
21
|
+
error: vi.fn(),
|
|
22
|
+
},
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
// Mock cellsRuntimeAtom - must be defined inline in the factory function
|
|
26
|
+
vi.mock("@/core/cells/cells", async () => {
|
|
27
|
+
const { atom } = await import("jotai");
|
|
28
|
+
return {
|
|
29
|
+
cellsRuntimeAtom: atom({}),
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
import { toPng } from "html-to-image";
|
|
34
|
+
import { cellsRuntimeAtom } from "@/core/cells/cells";
|
|
35
|
+
import { Logger } from "@/utils/Logger";
|
|
36
|
+
|
|
37
|
+
describe("useEnrichCellOutputs", () => {
|
|
38
|
+
let store: ReturnType<typeof createStore>;
|
|
39
|
+
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
vi.clearAllMocks();
|
|
42
|
+
store = createStore();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const wrapper = ({ children }: { children: ReactNode }) =>
|
|
46
|
+
React.createElement(Provider, { store }, children);
|
|
47
|
+
|
|
48
|
+
// Helper to set the mocked atom (cast to any to work around type mismatch)
|
|
49
|
+
const setCellsRuntime = (value: Record<CellId, CellRuntimeState>) => {
|
|
50
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
51
|
+
store.set(cellsRuntimeAtom as any, value);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const createMockCellRuntimes = (
|
|
55
|
+
cells: Record<string, Partial<CellRuntimeState>>,
|
|
56
|
+
): Record<CellId, CellRuntimeState> => {
|
|
57
|
+
return Object.fromEntries(
|
|
58
|
+
Object.entries(cells).map(([cellId, cell]) => [
|
|
59
|
+
cellId as CellId,
|
|
60
|
+
{
|
|
61
|
+
output: cell.output || null,
|
|
62
|
+
status: cell.status || "idle",
|
|
63
|
+
interrupted: false,
|
|
64
|
+
errored: false,
|
|
65
|
+
runStartTimestamp: null,
|
|
66
|
+
runElapsedTimeMs: null,
|
|
67
|
+
stallTime: null as unknown as number,
|
|
68
|
+
...cell,
|
|
69
|
+
} as CellRuntimeState,
|
|
70
|
+
]),
|
|
71
|
+
) as Record<CellId, CellRuntimeState>;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
it("should return empty object when no cells need screenshots", async () => {
|
|
75
|
+
vi.spyOn(document, "getElementById");
|
|
76
|
+
|
|
77
|
+
// Set up cell runtimes with no text/html outputs
|
|
78
|
+
setCellsRuntime(
|
|
79
|
+
createMockCellRuntimes({
|
|
80
|
+
"cell-1": {
|
|
81
|
+
output: {
|
|
82
|
+
channel: "output",
|
|
83
|
+
mimetype: "text/plain",
|
|
84
|
+
data: "Hello World",
|
|
85
|
+
timestamp: 0,
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
}),
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const { result } = renderHook(() => useEnrichCellOutputs(), { wrapper });
|
|
92
|
+
|
|
93
|
+
const enrichCellOutputs = result.current;
|
|
94
|
+
const output = await enrichCellOutputs();
|
|
95
|
+
|
|
96
|
+
expect(output).toEqual({});
|
|
97
|
+
expect(document.getElementById).not.toHaveBeenCalled();
|
|
98
|
+
expect(toPng).not.toHaveBeenCalled();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("should capture screenshots for cells with text/html output", async () => {
|
|
102
|
+
const cellId = "cell-1" as CellId;
|
|
103
|
+
const mockElement = document.createElement("div");
|
|
104
|
+
const mockDataUrl = "data:image/png;base64,mockImageData";
|
|
105
|
+
|
|
106
|
+
// Mock document.getElementById
|
|
107
|
+
vi.spyOn(document, "getElementById").mockReturnValue(mockElement);
|
|
108
|
+
vi.mocked(toPng).mockResolvedValue(mockDataUrl);
|
|
109
|
+
|
|
110
|
+
setCellsRuntime(
|
|
111
|
+
createMockCellRuntimes({
|
|
112
|
+
[cellId]: {
|
|
113
|
+
output: {
|
|
114
|
+
channel: "output",
|
|
115
|
+
mimetype: "text/html",
|
|
116
|
+
data: "<div>Chart</div>",
|
|
117
|
+
timestamp: 0,
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
}),
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
const { result } = renderHook(() => useEnrichCellOutputs(), { wrapper });
|
|
124
|
+
|
|
125
|
+
const enrichCellOutputs = result.current;
|
|
126
|
+
const output = await enrichCellOutputs();
|
|
127
|
+
|
|
128
|
+
expect(document.getElementById).toHaveBeenCalledWith(
|
|
129
|
+
CellOutputId.create(cellId),
|
|
130
|
+
);
|
|
131
|
+
expect(toPng).toHaveBeenCalledWith(mockElement);
|
|
132
|
+
expect(output).toEqual({
|
|
133
|
+
[cellId]: ["image/png", mockDataUrl],
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("should skip cells where output has not changed", async () => {
|
|
138
|
+
const cellId = "cell-1" as CellId;
|
|
139
|
+
const mockElement = document.createElement("div");
|
|
140
|
+
const mockDataUrl = "data:image/png;base64,mockImageData";
|
|
141
|
+
const htmlData = "<div>Chart</div>";
|
|
142
|
+
|
|
143
|
+
vi.spyOn(document, "getElementById").mockReturnValue(mockElement);
|
|
144
|
+
vi.mocked(toPng).mockResolvedValue(mockDataUrl);
|
|
145
|
+
|
|
146
|
+
setCellsRuntime(
|
|
147
|
+
createMockCellRuntimes({
|
|
148
|
+
[cellId]: {
|
|
149
|
+
output: {
|
|
150
|
+
channel: "output",
|
|
151
|
+
mimetype: "text/html",
|
|
152
|
+
data: htmlData,
|
|
153
|
+
timestamp: 0,
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
}),
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
const { result, rerender } = renderHook(() => useEnrichCellOutputs(), {
|
|
160
|
+
wrapper,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// First call - should capture
|
|
164
|
+
let enrichCellOutputs = result.current;
|
|
165
|
+
let output = await enrichCellOutputs();
|
|
166
|
+
expect(output).toEqual({ [cellId]: ["image/png", mockDataUrl] });
|
|
167
|
+
expect(toPng).toHaveBeenCalledTimes(1);
|
|
168
|
+
|
|
169
|
+
// Rerender to get updated atom state
|
|
170
|
+
rerender();
|
|
171
|
+
|
|
172
|
+
// Second call with same output - should not capture again
|
|
173
|
+
enrichCellOutputs = result.current;
|
|
174
|
+
output = await enrichCellOutputs();
|
|
175
|
+
expect(output).toEqual({}); // Empty because output hasn't changed
|
|
176
|
+
expect(toPng).toHaveBeenCalledTimes(1); // Still only 1 call
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("should handle screenshot errors gracefully", async () => {
|
|
180
|
+
const cellId = "cell-1" as CellId;
|
|
181
|
+
const mockElement = document.createElement("div");
|
|
182
|
+
const error = new Error("Screenshot failed");
|
|
183
|
+
|
|
184
|
+
vi.spyOn(document, "getElementById").mockReturnValue(mockElement);
|
|
185
|
+
vi.mocked(toPng).mockRejectedValue(error);
|
|
186
|
+
|
|
187
|
+
setCellsRuntime(
|
|
188
|
+
createMockCellRuntimes({
|
|
189
|
+
[cellId]: {
|
|
190
|
+
output: {
|
|
191
|
+
channel: "output",
|
|
192
|
+
mimetype: "text/html",
|
|
193
|
+
data: "<div>Chart</div>",
|
|
194
|
+
timestamp: 0,
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
}),
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
const { result } = renderHook(() => useEnrichCellOutputs(), { wrapper });
|
|
201
|
+
|
|
202
|
+
const enrichCellOutputs = result.current;
|
|
203
|
+
const output = await enrichCellOutputs();
|
|
204
|
+
|
|
205
|
+
expect(output).toEqual({}); // Failed screenshot should be filtered out
|
|
206
|
+
expect(Logger.error).toHaveBeenCalledWith(
|
|
207
|
+
`Error screenshotting cell ${cellId}:`,
|
|
208
|
+
error,
|
|
209
|
+
);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it("should handle missing DOM elements", async () => {
|
|
213
|
+
const cellId = "cell-1" as CellId;
|
|
214
|
+
|
|
215
|
+
vi.spyOn(document, "getElementById").mockReturnValue(null);
|
|
216
|
+
|
|
217
|
+
setCellsRuntime(
|
|
218
|
+
createMockCellRuntimes({
|
|
219
|
+
[cellId]: {
|
|
220
|
+
output: {
|
|
221
|
+
channel: "output",
|
|
222
|
+
mimetype: "text/html",
|
|
223
|
+
data: "<div>Chart</div>",
|
|
224
|
+
timestamp: 0,
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
}),
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
const { result } = renderHook(() => useEnrichCellOutputs(), { wrapper });
|
|
231
|
+
|
|
232
|
+
const enrichCellOutputs = result.current;
|
|
233
|
+
const output = await enrichCellOutputs();
|
|
234
|
+
|
|
235
|
+
expect(output).toEqual({});
|
|
236
|
+
expect(Logger.error).toHaveBeenCalledWith(
|
|
237
|
+
`Output element not found for cell ${cellId}`,
|
|
238
|
+
);
|
|
239
|
+
expect(toPng).not.toHaveBeenCalled();
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it("should process multiple cells in parallel", async () => {
|
|
243
|
+
const cell1 = "cell-1" as CellId;
|
|
244
|
+
const cell2 = "cell-2" as CellId;
|
|
245
|
+
const mockElement1 = document.createElement("div");
|
|
246
|
+
const mockElement2 = document.createElement("div");
|
|
247
|
+
const mockDataUrl1 = "data:image/png;base64,image1";
|
|
248
|
+
const mockDataUrl2 = "data:image/png;base64,image2";
|
|
249
|
+
|
|
250
|
+
vi.spyOn(document, "getElementById")
|
|
251
|
+
.mockReturnValueOnce(mockElement1)
|
|
252
|
+
.mockReturnValueOnce(mockElement2);
|
|
253
|
+
|
|
254
|
+
vi.mocked(toPng)
|
|
255
|
+
.mockResolvedValueOnce(mockDataUrl1)
|
|
256
|
+
.mockResolvedValueOnce(mockDataUrl2);
|
|
257
|
+
|
|
258
|
+
setCellsRuntime(
|
|
259
|
+
createMockCellRuntimes({
|
|
260
|
+
[cell1]: {
|
|
261
|
+
output: {
|
|
262
|
+
channel: "output",
|
|
263
|
+
mimetype: "text/html",
|
|
264
|
+
data: "<div>Chart 1</div>",
|
|
265
|
+
timestamp: 0,
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
[cell2]: {
|
|
269
|
+
output: {
|
|
270
|
+
channel: "output",
|
|
271
|
+
mimetype: "text/html",
|
|
272
|
+
data: "<div>Chart 2</div>",
|
|
273
|
+
timestamp: 0,
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
}),
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
const { result } = renderHook(() => useEnrichCellOutputs(), { wrapper });
|
|
280
|
+
|
|
281
|
+
const enrichCellOutputs = result.current;
|
|
282
|
+
const output = await enrichCellOutputs();
|
|
283
|
+
|
|
284
|
+
expect(output).toEqual({
|
|
285
|
+
[cell1]: ["image/png", mockDataUrl1],
|
|
286
|
+
[cell2]: ["image/png", mockDataUrl2],
|
|
287
|
+
});
|
|
288
|
+
expect(toPng).toHaveBeenCalledTimes(2);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it("should filter out null results from failed screenshots", async () => {
|
|
292
|
+
// Setup: one successful, one failed screenshot
|
|
293
|
+
const cell1 = "cell-1" as CellId;
|
|
294
|
+
const cell2 = "cell-2" as CellId;
|
|
295
|
+
const mockElement1 = document.createElement("div");
|
|
296
|
+
const mockDataUrl = "data:image/png;base64,image1";
|
|
297
|
+
|
|
298
|
+
vi.spyOn(document, "getElementById")
|
|
299
|
+
.mockReturnValueOnce(mockElement1)
|
|
300
|
+
.mockReturnValueOnce(null); // Second cell fails to find element
|
|
301
|
+
|
|
302
|
+
vi.mocked(toPng).mockResolvedValue(mockDataUrl);
|
|
303
|
+
|
|
304
|
+
setCellsRuntime(
|
|
305
|
+
createMockCellRuntimes({
|
|
306
|
+
[cell1]: {
|
|
307
|
+
output: {
|
|
308
|
+
channel: "output",
|
|
309
|
+
mimetype: "text/html",
|
|
310
|
+
data: "<div>Chart 1</div>",
|
|
311
|
+
timestamp: 0,
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
[cell2]: {
|
|
315
|
+
output: {
|
|
316
|
+
channel: "output",
|
|
317
|
+
mimetype: "text/html",
|
|
318
|
+
data: "<div>Chart 2</div>",
|
|
319
|
+
timestamp: 0,
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
}),
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
const { result } = renderHook(() => useEnrichCellOutputs(), { wrapper });
|
|
326
|
+
|
|
327
|
+
const enrichCellOutputs = result.current;
|
|
328
|
+
const output = await enrichCellOutputs();
|
|
329
|
+
|
|
330
|
+
// Only the successful screenshot should be in the result
|
|
331
|
+
expect(output).toEqual({
|
|
332
|
+
[cell1]: ["image/png", mockDataUrl],
|
|
333
|
+
});
|
|
334
|
+
expect(Logger.error).toHaveBeenCalledWith(
|
|
335
|
+
`Output element not found for cell ${cell2}`,
|
|
336
|
+
);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it("should only capture screenshots for cells with changed output", async () => {
|
|
340
|
+
const cellId = "cell-1" as CellId;
|
|
341
|
+
const mockElement = document.createElement("div");
|
|
342
|
+
const mockDataUrl1 = "data:image/png;base64,image1";
|
|
343
|
+
const mockDataUrl2 = "data:image/png;base64,image2";
|
|
344
|
+
|
|
345
|
+
vi.spyOn(document, "getElementById").mockReturnValue(mockElement);
|
|
346
|
+
vi.mocked(toPng)
|
|
347
|
+
.mockResolvedValueOnce(mockDataUrl1)
|
|
348
|
+
.mockResolvedValueOnce(mockDataUrl2);
|
|
349
|
+
|
|
350
|
+
// First call - cell should be captured
|
|
351
|
+
setCellsRuntime(
|
|
352
|
+
createMockCellRuntimes({
|
|
353
|
+
[cellId]: {
|
|
354
|
+
output: {
|
|
355
|
+
channel: "output",
|
|
356
|
+
mimetype: "text/html",
|
|
357
|
+
data: "<div>Chart v1</div>",
|
|
358
|
+
timestamp: 0,
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
}),
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
const { result, rerender } = renderHook(() => useEnrichCellOutputs(), {
|
|
365
|
+
wrapper,
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
// First screenshot
|
|
369
|
+
let enrichCellOutputs = result.current;
|
|
370
|
+
let output = await enrichCellOutputs();
|
|
371
|
+
expect(output).toEqual({ [cellId]: ["image/png", mockDataUrl1] });
|
|
372
|
+
|
|
373
|
+
// Second call - same output, should not be captured
|
|
374
|
+
rerender();
|
|
375
|
+
enrichCellOutputs = result.current;
|
|
376
|
+
output = await enrichCellOutputs();
|
|
377
|
+
expect(output).toEqual({});
|
|
378
|
+
|
|
379
|
+
// Third call - output changed, should be captured
|
|
380
|
+
setCellsRuntime(
|
|
381
|
+
createMockCellRuntimes({
|
|
382
|
+
[cellId]: {
|
|
383
|
+
output: {
|
|
384
|
+
channel: "output",
|
|
385
|
+
mimetype: "text/html",
|
|
386
|
+
data: "<div>Chart v2</div>", // Changed!
|
|
387
|
+
timestamp: 0,
|
|
388
|
+
},
|
|
389
|
+
},
|
|
390
|
+
}),
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
rerender();
|
|
394
|
+
enrichCellOutputs = result.current;
|
|
395
|
+
output = await enrichCellOutputs();
|
|
396
|
+
expect(output).toEqual({ [cellId]: ["image/png", mockDataUrl2] });
|
|
397
|
+
expect(toPng).toHaveBeenCalledTimes(2);
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
it("should ignore cells with non-text/html mimetype", async () => {
|
|
401
|
+
vi.spyOn(document, "getElementById");
|
|
402
|
+
|
|
403
|
+
setCellsRuntime(
|
|
404
|
+
createMockCellRuntimes({
|
|
405
|
+
"cell-1": {
|
|
406
|
+
output: {
|
|
407
|
+
channel: "output",
|
|
408
|
+
mimetype: "application/json",
|
|
409
|
+
data: '{"key": "value"}',
|
|
410
|
+
timestamp: 0,
|
|
411
|
+
},
|
|
412
|
+
},
|
|
413
|
+
"cell-2": {
|
|
414
|
+
output: {
|
|
415
|
+
channel: "output",
|
|
416
|
+
mimetype: "text/plain",
|
|
417
|
+
data: "Plain text",
|
|
418
|
+
timestamp: 0,
|
|
419
|
+
},
|
|
420
|
+
},
|
|
421
|
+
"cell-3": {
|
|
422
|
+
output: {
|
|
423
|
+
channel: "output",
|
|
424
|
+
mimetype: "image/png",
|
|
425
|
+
data: "data:image/png;base64,existing",
|
|
426
|
+
timestamp: 0,
|
|
427
|
+
},
|
|
428
|
+
},
|
|
429
|
+
}),
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
const { result } = renderHook(() => useEnrichCellOutputs(), { wrapper });
|
|
433
|
+
|
|
434
|
+
const enrichCellOutputs = result.current;
|
|
435
|
+
const output = await enrichCellOutputs();
|
|
436
|
+
|
|
437
|
+
// None of these should trigger screenshots
|
|
438
|
+
expect(output).toEqual({});
|
|
439
|
+
expect(document.getElementById).not.toHaveBeenCalled();
|
|
440
|
+
expect(toPng).not.toHaveBeenCalled();
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
it("should ignore cells with null or undefined output", async () => {
|
|
444
|
+
vi.spyOn(document, "getElementById");
|
|
445
|
+
|
|
446
|
+
setCellsRuntime(
|
|
447
|
+
createMockCellRuntimes({
|
|
448
|
+
"cell-1": {
|
|
449
|
+
output: null,
|
|
450
|
+
},
|
|
451
|
+
"cell-2": {
|
|
452
|
+
output: undefined,
|
|
453
|
+
},
|
|
454
|
+
}),
|
|
455
|
+
);
|
|
456
|
+
|
|
457
|
+
const { result } = renderHook(() => useEnrichCellOutputs(), { wrapper });
|
|
458
|
+
|
|
459
|
+
const enrichCellOutputs = result.current;
|
|
460
|
+
const output = await enrichCellOutputs();
|
|
461
|
+
|
|
462
|
+
expect(output).toEqual({});
|
|
463
|
+
expect(document.getElementById).not.toHaveBeenCalled();
|
|
464
|
+
expect(toPng).not.toHaveBeenCalled();
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
it("should return correctly formatted result with CellId and tuple", async () => {
|
|
468
|
+
// Expected format: Record<CellId, ["image/png", string]>
|
|
469
|
+
const cellId = "test-cell" as CellId;
|
|
470
|
+
const mockElement = document.createElement("div");
|
|
471
|
+
const mockDataUrl = "data:image/png;base64,testData";
|
|
472
|
+
|
|
473
|
+
vi.spyOn(document, "getElementById").mockReturnValue(mockElement);
|
|
474
|
+
vi.mocked(toPng).mockResolvedValue(mockDataUrl);
|
|
475
|
+
|
|
476
|
+
setCellsRuntime(
|
|
477
|
+
createMockCellRuntimes({
|
|
478
|
+
[cellId]: {
|
|
479
|
+
output: {
|
|
480
|
+
channel: "output",
|
|
481
|
+
mimetype: "text/html",
|
|
482
|
+
data: "<div>Content</div>",
|
|
483
|
+
timestamp: 0,
|
|
484
|
+
},
|
|
485
|
+
},
|
|
486
|
+
}),
|
|
487
|
+
);
|
|
488
|
+
|
|
489
|
+
const { result } = renderHook(() => useEnrichCellOutputs(), { wrapper });
|
|
490
|
+
|
|
491
|
+
const enrichCellOutputs = result.current;
|
|
492
|
+
const output = await enrichCellOutputs();
|
|
493
|
+
|
|
494
|
+
// Verify the exact return type structure
|
|
495
|
+
expect(output).toHaveProperty(cellId);
|
|
496
|
+
const cellOutput = output[cellId];
|
|
497
|
+
expect(cellOutput).toBeDefined();
|
|
498
|
+
expect(Array.isArray(cellOutput)).toBe(true);
|
|
499
|
+
if (cellOutput) {
|
|
500
|
+
expect(cellOutput[0]).toBe("image/png");
|
|
501
|
+
expect(cellOutput[1]).toBe(mockDataUrl);
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
});
|
package/src/core/export/hooks.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
-
import {
|
|
2
|
+
import { toPng } from "html-to-image";
|
|
3
|
+
import { atom, useAtom, useAtomValue } from "jotai";
|
|
3
4
|
import { appConfigAtom } from "@/core/config/config";
|
|
4
5
|
import { useInterval } from "@/hooks/useInterval";
|
|
6
|
+
import { Logger } from "@/utils/Logger";
|
|
7
|
+
import { Objects } from "@/utils/objects";
|
|
8
|
+
import { cellsRuntimeAtom } from "../cells/cells";
|
|
9
|
+
import { type CellId, CellOutputId } from "../cells/ids";
|
|
5
10
|
import { connectionAtom } from "../network/connection";
|
|
6
11
|
import { useRequestClient } from "../network/requests";
|
|
7
12
|
import { VirtualFileTracker } from "../static/virtual-file-tracker";
|
|
@@ -21,8 +26,13 @@ export function useAutoExport() {
|
|
|
21
26
|
const markdownDisabled = !markdownEnabled || !isConnected;
|
|
22
27
|
const htmlDisabled = !htmlEnabled || !isConnected;
|
|
23
28
|
const ipynbDisabled = !ipynbEnabled || !isConnected;
|
|
24
|
-
const {
|
|
25
|
-
|
|
29
|
+
const {
|
|
30
|
+
autoExportAsHTML,
|
|
31
|
+
autoExportAsIPYNB,
|
|
32
|
+
autoExportAsMarkdown,
|
|
33
|
+
updateCellOutputs,
|
|
34
|
+
} = useRequestClient();
|
|
35
|
+
const takeScreenshots = useEnrichCellOutputs();
|
|
26
36
|
|
|
27
37
|
useInterval(
|
|
28
38
|
async () => {
|
|
@@ -50,12 +60,91 @@ export function useAutoExport() {
|
|
|
50
60
|
|
|
51
61
|
useInterval(
|
|
52
62
|
async () => {
|
|
63
|
+
const cellsToOutput = await takeScreenshots();
|
|
64
|
+
if (Object.keys(cellsToOutput).length > 0) {
|
|
65
|
+
await updateCellOutputs({
|
|
66
|
+
cellIdsToOutput: cellsToOutput,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
53
69
|
await autoExportAsIPYNB({
|
|
54
70
|
download: false,
|
|
55
71
|
});
|
|
56
72
|
},
|
|
57
73
|
// Run every 5 seconds, or when the document becomes visible
|
|
58
74
|
// Ignore if the document is not visible
|
|
59
|
-
|
|
75
|
+
// Skip if running to ensure no race conditions between screenshot and export
|
|
76
|
+
{
|
|
77
|
+
delayMs: DELAY,
|
|
78
|
+
whenVisible: true,
|
|
79
|
+
disabled: ipynbDisabled,
|
|
80
|
+
skipIfRunning: true,
|
|
81
|
+
},
|
|
60
82
|
);
|
|
61
83
|
}
|
|
84
|
+
|
|
85
|
+
// We track cells that need screenshots, these will be exported to IPYNB
|
|
86
|
+
const richCellsToOutputAtom = atom<Record<CellId, unknown>>({});
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Take screenshots of cells with HTML outputs. These images will be sent to the backend to be exported to IPYNB.
|
|
90
|
+
* @returns A map of cell IDs to their screenshots data.
|
|
91
|
+
*/
|
|
92
|
+
export function useEnrichCellOutputs() {
|
|
93
|
+
const [richCellsOutput, setRichCellsOutput] = useAtom(richCellsToOutputAtom);
|
|
94
|
+
const cellRuntimes = useAtomValue(cellsRuntimeAtom);
|
|
95
|
+
|
|
96
|
+
return async (): Promise<Record<CellId, ["image/png", string]>> => {
|
|
97
|
+
const trackedCellsOutput: Record<CellId, unknown> = {};
|
|
98
|
+
|
|
99
|
+
const cellsToCaptureScreenshot: [CellId, unknown][] = [];
|
|
100
|
+
for (const [cellId, runtime] of Objects.entries(cellRuntimes)) {
|
|
101
|
+
const outputData = runtime.output?.data;
|
|
102
|
+
const outputHasChanged = richCellsOutput[cellId] !== outputData;
|
|
103
|
+
// Track latest output for this cell
|
|
104
|
+
trackedCellsOutput[cellId] = outputData;
|
|
105
|
+
if (
|
|
106
|
+
runtime.output?.mimetype === "text/html" &&
|
|
107
|
+
outputData &&
|
|
108
|
+
outputHasChanged
|
|
109
|
+
) {
|
|
110
|
+
cellsToCaptureScreenshot.push([cellId, runtime]);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// Always update tracked outputs, this ensures data is fresh for the next run
|
|
114
|
+
setRichCellsOutput(trackedCellsOutput);
|
|
115
|
+
|
|
116
|
+
if (cellsToCaptureScreenshot.length === 0) {
|
|
117
|
+
return {};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Capture screenshots
|
|
121
|
+
const results = await Promise.all(
|
|
122
|
+
cellsToCaptureScreenshot.map(async ([cellId]) => {
|
|
123
|
+
const outputElement = document.getElementById(
|
|
124
|
+
CellOutputId.create(cellId),
|
|
125
|
+
);
|
|
126
|
+
if (!outputElement) {
|
|
127
|
+
Logger.error(`Output element not found for cell ${cellId}`);
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
const dataUrl = await toPng(outputElement);
|
|
133
|
+
return [cellId, ["image/png", dataUrl]] as [
|
|
134
|
+
CellId,
|
|
135
|
+
["image/png", string],
|
|
136
|
+
];
|
|
137
|
+
} catch (error) {
|
|
138
|
+
Logger.error(`Error screenshotting cell ${cellId}:`, error);
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
}),
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
return Objects.fromEntries(
|
|
145
|
+
results.filter(
|
|
146
|
+
(result): result is [CellId, ["image/png", string]] => result !== null,
|
|
147
|
+
),
|
|
148
|
+
);
|
|
149
|
+
};
|
|
150
|
+
}
|
|
@@ -171,6 +171,7 @@ export class IslandsPyodideBridge implements RunRequests, EditRequests {
|
|
|
171
171
|
autoExportAsHTML = throwNotImplemented;
|
|
172
172
|
autoExportAsMarkdown = throwNotImplemented;
|
|
173
173
|
autoExportAsIPYNB = throwNotImplemented;
|
|
174
|
+
updateCellOutputs = throwNotImplemented;
|
|
174
175
|
addPackage = throwNotImplemented;
|
|
175
176
|
removePackage = throwNotImplemented;
|
|
176
177
|
getPackageList = throwNotImplemented;
|
package/src/core/kernel/state.ts
CHANGED
|
@@ -34,12 +34,6 @@ vi.mock("../connection", () => ({
|
|
|
34
34
|
waitForConnectionOpen: vi.fn().mockResolvedValue(undefined),
|
|
35
35
|
}));
|
|
36
36
|
|
|
37
|
-
vi.mock("../../state/jotai", () => ({
|
|
38
|
-
store: {
|
|
39
|
-
get: vi.fn(() => true),
|
|
40
|
-
},
|
|
41
|
-
}));
|
|
42
|
-
|
|
43
37
|
describe("createNetworkRequests", () => {
|
|
44
38
|
let mockClient: any;
|
|
45
39
|
let capturedCalls: Map<string, { hasParams: boolean; endpoint: string }>;
|
|
@@ -80,18 +74,6 @@ describe("createNetworkRequests", () => {
|
|
|
80
74
|
});
|
|
81
75
|
|
|
82
76
|
describe("special behavior", () => {
|
|
83
|
-
it("sendRun should drop requests if not connected", async () => {
|
|
84
|
-
const { store } = await import("../../state/jotai");
|
|
85
|
-
|
|
86
|
-
vi.mocked(store.get).mockReturnValue(false);
|
|
87
|
-
|
|
88
|
-
const requests = createNetworkRequests();
|
|
89
|
-
const result = await requests.sendRun({} as any);
|
|
90
|
-
|
|
91
|
-
expect(result).toBe(null);
|
|
92
|
-
expect(mockClient.POST).not.toHaveBeenCalled();
|
|
93
|
-
});
|
|
94
|
-
|
|
95
77
|
it("exportAsHTML should set assetUrl in dev/test mode", async () => {
|
|
96
78
|
const originalEnv = process.env.NODE_ENV;
|
|
97
79
|
process.env.NODE_ENV = "development";
|