@marimo-team/frontend 0.23.16-dev4 → 0.23.16-dev40
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-BalYv0pK.js → CellStatus-C6Cj3oJc.js} +1 -1
- package/dist/assets/{ConnectedDataExplorerComponent-DI7mV-Gw.js → ConnectedDataExplorerComponent-CMaZapRd.js} +1 -1
- package/dist/assets/{ErrorBoundary-C7_5jjWi.js → ErrorBoundary-DzTD8LVq.js} +1 -1
- package/dist/assets/{ImperativeModal-BIavOiUv.js → ImperativeModal-fY-ZfT0b.js} +1 -1
- package/dist/assets/{JsonOutput-CkCD-FgM.js → JsonOutput-CyzvjIjx.js} +12 -12
- package/dist/assets/{LazyAnyLanguageCodeMirror-sVm_hz8A.js → LazyAnyLanguageCodeMirror-B2JvSXP-.js} +2 -2
- package/dist/assets/{MarimoErrorOutput-DO2k2fA5.js → MarimoErrorOutput-CIuzXFFo.js} +4 -4
- package/dist/assets/{RSPContexts-CgC81f0I.js → RSPContexts-WiCgGm__.js} +1 -1
- package/dist/assets/RenderHTML-Cd-PP5ZR.js +1 -0
- package/dist/assets/{RunButton-BKRupqr7.js → RunButton-CnYWedW4.js} +1 -1
- package/dist/assets/{add-cell-with-ai-CEZtaZCK.js → add-cell-with-ai-ByOBQT99.js} +8 -8
- package/dist/assets/add-connection-dialog-C97ylgk0.js +170 -0
- package/dist/assets/{agent-panel-CHB6t7Zy.js → agent-panel-Dxo9WUKV.js} +5 -5
- package/dist/assets/ai-model-dropdown-Bvqy98cI.js +5 -0
- package/dist/assets/{alert-dialog-BXegNC0I.js → alert-dialog-Bb8Lz8ko.js} +1 -1
- package/dist/assets/{any-language-editor-DCpZRrpx.js → any-language-editor--tRHs8C_.js} +1 -1
- package/dist/assets/{app-config-button-NrM9ywXA.js → app-config-button-v1lG63C2.js} +1 -1
- package/dist/assets/{button-eIihcbUw.js → button-DDGiD1NW.js} +1 -1
- package/dist/assets/{cache-panel-wllSTlbM.js → cache-panel-DcQUeUpg.js} +1 -1
- package/dist/assets/{capabilities-B09j1Eor.js → capabilities-DoEg0XwL.js} +1 -1
- package/dist/assets/{cell-editor-B5JVq7_q.js → cell-editor-BKrHbyX_.js} +16 -16
- package/dist/assets/cell-link-w5IbCQV4.js +1 -0
- package/dist/assets/cells-DzyDERZy.js +235 -0
- package/dist/assets/{chat-display-BG08ltQ3.js → chat-display-Ccgk4vbE.js} +1 -1
- package/dist/assets/{chat-panel-P2CzCtXJ.js → chat-panel--53TIc3t.js} +4 -4
- package/dist/assets/{chat-ui-fAJZAoTD.js → chat-ui-B5rBxgsw.js} +4 -4
- package/dist/assets/{column-preview-Dne1dIt0.js → column-preview-Cb8R9kfl.js} +1 -1
- package/dist/assets/{command-CAJLPEBq.js → command-Dqxie4gq.js} +1 -1
- package/dist/assets/{command-palette-iOCqUIs1.js → command-palette-B1S9PjZi.js} +1 -1
- package/dist/assets/{common-CD6Mj8n6.js → common-CJ3baa6D.js} +1 -1
- package/dist/assets/components-DpUIWGaj.js +1 -0
- package/dist/assets/config-B-Q7J_6d.js +1 -0
- package/dist/assets/{config-COUOIPEq.js → config-BJVx3eBW.js} +1 -1
- package/dist/assets/{copy-BtTOqPTS.js → copy-Bxb1_Gtw.js} +1 -1
- package/dist/assets/{copy-icon-C2pG0ZNK.js → copy-icon-neDQUo4Q.js} +1 -1
- package/dist/assets/{createReducer-C7kZ8zEe.js → createReducer-DGx5Af-C.js} +1 -1
- package/dist/assets/{datasource-ySAiC9gr.js → datasource-D_VcQMEn.js} +2 -2
- package/dist/assets/{dates-D-kIKDYA.js → dates-C2LWzsCZ.js} +1 -1
- package/dist/assets/{dependency-graph-panel-BF23EwcD.js → dependency-graph-panel-DY5lvPDx.js} +3 -3
- package/dist/assets/{dialog-D1DEsRZ2.js → dialog-BCVepWLJ.js} +1 -1
- package/dist/assets/{dist-ByVeMWkr.js → dist-BLN8PNJV.js} +1 -1
- package/dist/assets/dist-CiVShZKM.js +2 -0
- package/dist/assets/{documentation-panel-e9Fi9R7e.js → documentation-panel-CGhwxsXK.js} +1 -1
- package/dist/assets/download-DpS57jyt.js +9 -0
- package/dist/assets/{dropdown-menu-BFZLnWeO.js → dropdown-menu-dHmZR8bI.js} +1 -1
- package/dist/assets/{edit-page-7X3HYey7.js → edit-page-DA765yQV.js} +6 -6
- package/dist/assets/{error-banner-B6eIlxzg.js → error-banner-BSxEOIn2.js} +1 -1
- package/dist/assets/error-entries-CTYMwfsh.js +8 -0
- package/dist/assets/{error-panel-DdsUL6b7.js → error-panel-DGAkG5kW.js} +1 -1
- package/dist/assets/{es-1D83cgiu.js → es-gYgfF8y5.js} +1 -1
- package/dist/assets/{esm-BL7jQmzn.js → esm-B30N1GS0.js} +1 -1
- package/dist/assets/{field-BeZbVzXA.js → field-BsxD35VE.js} +1 -1
- package/dist/assets/{file-explorer-panel-Ntq1-VbF.js → file-explorer-panel-BKUo-MDh.js} +3 -3
- package/dist/assets/{file-icons-Cb54mIL8.js → file-icons-DfWENcb8.js} +1 -1
- package/dist/assets/{file-name-input-BWFcK1ey.js → file-name-input-CNVZtm5K.js} +1 -1
- package/dist/assets/floating-outline-LitP0wCD.js +1 -0
- package/dist/assets/focus-CNKbm-Xz.js +1 -0
- package/dist/assets/{form-B6BkSCJb.js → form-nHzLTU1m.js} +2 -2
- package/dist/assets/{formats-ClEFGPy-.js → formats-DzmLpc9A.js} +1 -1
- package/dist/assets/{formatting-B_41gSF5.js → formatting-CDf6w1Kt.js} +1 -1
- package/dist/assets/{fullscreen-dUjPYOmm.js → fullscreen-CTDqNQr6.js} +1 -1
- package/dist/assets/{gallery-page-CWQQAYpa.js → gallery-page-CJ3ms1Hn.js} +1 -1
- package/dist/assets/{glide-data-editor-lMARFWHS.js → glide-data-editor-j2igs9Y_.js} +1 -1
- package/dist/assets/home-page-DM9Z_IJ7.js +4 -0
- package/dist/assets/{html-to-image-CwmX28T9.js → html-to-image-JaxFFZcr.js} +2 -2
- package/dist/assets/index-C7_REKh1.js +38 -0
- package/dist/assets/index-D6Dp_PFj.css +2 -0
- package/dist/assets/{input-CqrUjynb.js → input-JxFwu-3G.js} +1 -1
- package/dist/assets/kiosk-mode-BumOFEtY.js +1 -0
- package/dist/assets/{label-BElwWzf0.js → label-CqjrpwRC.js} +1 -1
- package/dist/assets/{layout-CUlZijwG.js → layout-B29hqiiK.js} +5 -5
- package/dist/assets/{links-CToBjIIj.js → links-CZr5bjvo.js} +1 -1
- package/dist/assets/logs-panel-CQRPYS8j.js +1 -0
- package/dist/assets/{markdown-renderer-QvXG0CRk.js → markdown-renderer-sEayY9-m.js} +2 -2
- package/dist/assets/{menu-items-DbSLBfXR.js → menu-items-CCi6aOPm.js} +1 -1
- package/dist/assets/{mermaid-Bh0flEIb.js → mermaid-CYN0wOyG.js} +1 -1
- package/dist/assets/{name-cell-input-C6cukQ9k.js → name-cell-input-B-aj1KH3.js} +1 -1
- package/dist/assets/node-sql-parser-Si4oZ2P-.js +68 -0
- package/dist/assets/{numbers-CeeaFKqu.js → numbers-rmawRqQP.js} +1 -1
- package/dist/assets/{outline-panel-BPlg9_CE.js → outline-panel-BOryk60D.js} +1 -1
- package/dist/assets/{packages-panel-BzQF60rG.js → packages-panel-DoYDLty3.js} +1 -1
- package/dist/assets/pair-with-agent-modal-BCRSACei.js +4 -0
- package/dist/assets/panels-Bb4fIHgH.js +4 -0
- package/dist/assets/{popover-DrHQ3KEt.js → popover-Bcz1Zikb.js} +1 -1
- package/dist/assets/process-output-d-LEMtGI.js +1 -0
- package/dist/assets/{radio-group-CLBjGkt5.js → radio-group-DfioQthW.js} +1 -1
- package/dist/assets/{readonly-code-display-D63oRtBP.js → readonly-code-display-C9fu8tvK.js} +1 -1
- package/dist/assets/{readonly-python-code-CW_G5e4E.js → readonly-python-code-DHxOnQGj.js} +1 -1
- package/dist/assets/{renderShortcut-BZLr8D7H.js → renderShortcut-Bl8_FVn8.js} +1 -1
- package/dist/assets/{reveal-component-BLMTmd8T.js → reveal-component-5jf0W625.js} +60 -60
- package/dist/assets/{run-page-B6pgDYEJ.js → run-page-BMKx9ld_.js} +1 -1
- package/dist/assets/{runs-D51Xwrsk.js → runs-DBTXqJHS.js} +1 -1
- package/dist/assets/scratchpad-panel-C8z8vzBX.js +1 -0
- package/dist/assets/{scroll-to-bottom-button-Cgr10kaJ.js → scroll-to-bottom-button-C8yfT4wq.js} +1 -1
- package/dist/assets/{secrets-panel-Cc5KDBk6.js → secrets-panel-6J6wcuVR.js} +1 -1
- package/dist/assets/{select-B5TFNjmU.js → select-BP75_5jJ.js} +1 -1
- package/dist/assets/{session-CEFnN9Cq.js → session-BmukRjSi.js} +1 -1
- package/dist/assets/{session-panel-Y0Oon0sD.js → session-panel-BVVk-02h.js} +1 -1
- package/dist/assets/{share-CYpcX4N4.js → share-852X_cqy.js} +1 -1
- package/dist/assets/{skeleton-D3QPxqWC.js → skeleton-Bubr9pIj.js} +1 -1
- package/dist/assets/slides-DkEgzTJB.css +1 -0
- package/dist/assets/{snippets-panel-CXpGflMO.js → snippets-panel-U2-iok-c.js} +1 -1
- package/dist/assets/{spec-CzqcmYQk.js → spec-C7opZZRp.js} +1 -1
- package/dist/assets/{state-BvdK8eiw.js → state-D10U2MwA.js} +1 -1
- package/dist/assets/{state-WBgc7MiA.js → state-DDag0B_B.js} +1 -1
- package/dist/assets/state-DtziTd1T.js +3 -0
- package/dist/assets/{state-DBzEQtWz.js → state-DxQhUsG9.js} +1 -1
- package/dist/assets/{strings-Djnbwmfl.js → strings--BknHX9C.js} +1 -1
- package/dist/assets/{swiper-component-DInvCxhI.js → swiper-component-g8EXE911.js} +1 -1
- package/dist/assets/switch-OVajJBPR.js +1 -0
- package/dist/assets/{terminal-D3IQBBYo.js → terminal-217O8hlZ.js} +1 -1
- package/dist/assets/{textarea-CpFfEUet.js → textarea-Bmzmbbpo.js} +1 -1
- package/dist/assets/{tooltip-CFX4fYHU.js → tooltip-4bZlU7jl.js} +1 -1
- package/dist/assets/{tooltip-HLb6RxVp.js → tooltip-B7U4xkIN.js} +1 -1
- package/dist/assets/{tracing-CKn3gWHw.js → tracing-B5XFvMmV.js} +1 -1
- package/dist/assets/{tracing-panel-BU-o_LCd.js → tracing-panel-DnR2bYDe.js} +2 -2
- package/dist/assets/{tree-actions-CsC2XmGo.js → tree-actions-Dx9CDaxV.js} +1 -1
- package/dist/assets/{useBoolean-D89u30n-.js → useBoolean-CFj86iac.js} +1 -1
- package/dist/assets/useCellActionButton-DypR3yDt.js +1 -0
- package/dist/assets/{useDeleteCell-EPH4kcG7.js → useDeleteCell-vMXAr3rY.js} +1 -1
- package/dist/assets/{useDependencyPanelTab-CZnK7N2L.js → useDependencyPanelTab-CwdwKf7A.js} +1 -1
- package/dist/assets/useEventListener-C0195JsF.js +1 -0
- package/dist/assets/{useHotkey-DIuRH8Wu.js → useHotkey-DLZvrRZT.js} +1 -1
- package/dist/assets/{useIframeCapabilities-Ccb4fvKc.js → useIframeCapabilities-j6fR_dBA.js} +1 -1
- package/dist/assets/{useInstallPackage-BRbUAUFn.js → useInstallPackage-WiYBwhUO.js} +1 -1
- package/dist/assets/{useInterval-CC0vKpWm.js → useInterval-CWBTUy5W.js} +1 -1
- package/dist/assets/useNotebookActions-jWIcmpv0.js +12 -0
- package/dist/assets/{useRunCells-CSSF_mJ0.js → useRunCells-C2dvU2jA.js} +1 -1
- package/dist/assets/{useSplitCell-9Qz93yPZ.js → useSplitCell-C1tubdNi.js} +1 -1
- package/dist/assets/{useTheme-KXqAyDPC.js → useTheme-Bxv9B31g.js} +1 -1
- package/dist/assets/{utils-Z80SV4_d.js → utils-BWBBpCkT.js} +1 -1
- package/dist/assets/{vega-component-Cie3ag_T.js → vega-component-Db8KjZJG.js} +1 -1
- package/dist/assets/{write-secret-modal-DEEn7sZV.js → write-secret-modal-3-v-dHVj.js} +1 -1
- package/dist/index.html +68 -68
- package/package.json +3 -4
- package/src/__mocks__/requests.ts +45 -4
- package/src/components/data-table/__tests__/__snapshots__/chart-spec-model.test.ts.snap +174 -21
- package/src/components/data-table/__tests__/chart-spec-model.test.ts +102 -1
- package/src/components/data-table/column-summary/chart-spec-model.tsx +163 -103
- package/src/components/data-table/column-summary/legacy-chart-spec.ts +15 -7
- package/src/components/data-table/column-summary/utils.ts +26 -7
- package/src/components/data-table/types.ts +1 -1
- package/src/components/editor/actions/__tests__/pair-with-agent-commands.test.ts +99 -18
- package/src/components/editor/actions/__tests__/pdf-export.test.ts +0 -4
- package/src/components/editor/actions/pair-with-agent-commands.ts +21 -10
- package/src/components/editor/actions/pair-with-agent-modal.tsx +2 -2
- package/src/components/editor/actions/pdf-export.ts +1 -4
- package/src/components/editor/actions/useNotebookActions.tsx +7 -13
- package/src/components/editor/cell/code/__tests__/cell-editor-activity-lifecycle.test.ts +250 -0
- package/src/components/editor/cell/code/cell-editor.tsx +13 -7
- package/src/components/editor/chrome/panels/__tests__/logs-panel.test.tsx +25 -0
- package/src/components/editor/chrome/panels/logs-panel.tsx +12 -2
- package/src/components/editor/connections/storage/__tests__/__snapshots__/as-code.test.ts.snap +50 -0
- package/src/components/editor/connections/storage/__tests__/as-code.test.ts +87 -8
- package/src/components/editor/connections/storage/as-code.ts +40 -21
- package/src/components/editor/connections/storage/schemas.ts +83 -42
- package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +1 -1
- package/src/components/slides/__tests__/cell-output-block.test.tsx +40 -0
- package/src/components/slides/__tests__/slide-cell-view.test.tsx +33 -0
- package/src/components/slides/__tests__/slide-scroll-hint.test.tsx +198 -0
- package/src/components/slides/reveal-component.tsx +25 -19
- package/src/components/slides/slide-cell-view.tsx +1 -1
- package/src/components/slides/slide-scroll-hint.tsx +161 -0
- package/src/components/slides/slides.css +4 -2
- package/src/components/static-html/share-modal.tsx +1 -1
- package/src/components/ui/input.tsx +1 -1
- package/src/core/cells/__tests__/cells.test.ts +12 -2
- package/src/core/cells/__tests__/logs.test.ts +5 -5
- package/src/core/cells/cells.ts +17 -4
- package/src/core/cells/document-changes.ts +1 -0
- package/src/core/cells/logs.ts +14 -4
- package/src/core/codemirror/copilot/__tests__/language-server.test.ts +396 -100
- package/src/core/codemirror/copilot/__tests__/transport.test.ts +128 -540
- package/src/core/codemirror/copilot/language-server.ts +182 -70
- package/src/core/codemirror/copilot/transport.ts +15 -207
- package/src/core/codemirror/copilot/types.ts +9 -2
- package/src/core/codemirror/find-replace/__tests__/navigate.test.ts +42 -30
- package/src/core/codemirror/find-replace/navigate.ts +2 -0
- package/src/core/codemirror/go-to-definition/__tests__/commands.test.ts +24 -3
- package/src/core/codemirror/go-to-definition/commands.ts +3 -0
- package/src/core/codemirror/keymaps/__tests__/keymaps.test.ts +31 -2
- package/src/core/codemirror/keymaps/keymaps.ts +12 -0
- package/src/core/codemirror/language/__tests__/sql.test.ts +443 -61
- package/src/core/codemirror/language/languages/python.ts +23 -11
- package/src/core/codemirror/language/languages/sql/completion-store.ts +5 -1
- package/src/core/codemirror/language/languages/sql/sql.ts +210 -3
- package/src/core/codemirror/lsp/__tests__/federated-lsp.test.ts +161 -0
- package/src/core/codemirror/lsp/__tests__/log-messages.test.ts +53 -0
- package/src/core/codemirror/lsp/__tests__/normalize-markdown-math.test.ts +25 -0
- package/src/core/codemirror/lsp/__tests__/notebook-lsp.test.ts +598 -156
- package/src/core/codemirror/lsp/__tests__/transports.test.ts +112 -0
- package/src/core/codemirror/lsp/federated-lsp.ts +76 -23
- package/src/core/codemirror/lsp/log-messages.ts +37 -0
- package/src/core/codemirror/lsp/normalize-markdown-math.ts +8 -4
- package/src/core/codemirror/lsp/notebook-lsp.ts +220 -56
- package/src/core/codemirror/lsp/transports.ts +8 -1
- package/src/core/codemirror/lsp/types.ts +68 -8
- package/src/core/codemirror/lsp/utils.ts +14 -0
- package/src/core/codemirror/utils.ts +28 -0
- package/src/core/hotkeys/hotkeys.ts +0 -2
- package/src/core/islands/__tests__/bridge.test.ts +107 -0
- package/src/core/islands/bridge.ts +16 -0
- package/src/core/lsp/__tests__/transport.test.ts +509 -337
- package/src/core/lsp/transport.ts +304 -132
- package/src/core/mode.ts +10 -0
- package/src/core/network/__tests__/api.test.ts +42 -0
- package/src/core/network/__tests__/connection.test.ts +45 -0
- package/src/core/network/__tests__/requests-lazy.test.ts +32 -1
- package/src/core/network/__tests__/requests-network.test.ts +21 -0
- package/src/core/network/api.ts +27 -0
- package/src/core/network/connection.ts +15 -0
- package/src/core/network/requests-lazy.ts +1 -0
- package/src/core/network/requests-network.ts +14 -8
- package/src/core/network/requests-static.ts +1 -0
- package/src/core/network/requests-toasting.tsx +1 -0
- package/src/core/network/types.ts +22 -6
- package/src/core/rtc/state.ts +9 -0
- package/src/core/static/download-html.ts +5 -14
- package/src/core/wasm/bridge.ts +4 -2
- package/src/core/wasm/worker/types.ts +5 -2
- package/src/css/app/codemirror.css +16 -2
- package/src/plugins/impl/FormPlugin.tsx +1 -1
- package/src/plugins/impl/__tests__/FormPlugin.test.tsx +162 -0
- package/src/stories/log-viewer.stories.tsx +12 -6
- package/src/utils/__tests__/download.test.tsx +24 -1
- package/src/utils/download.ts +11 -6
- package/dist/assets/RenderHTML-BCvU9dYz.js +0 -1
- package/dist/assets/add-connection-dialog-B-jCRNTN.js +0 -170
- package/dist/assets/ai-model-dropdown-DXgLphFa.js +0 -5
- package/dist/assets/cell-link-DqBkyAFe.js +0 -1
- package/dist/assets/cells-Cf7bHHq3.js +0 -229
- package/dist/assets/components-BH6gDVv6.js +0 -1
- package/dist/assets/config-BbOTTCYY.js +0 -1
- package/dist/assets/dist-BCMBu0rO.js +0 -2
- package/dist/assets/download-BomYt3nl.js +0 -9
- package/dist/assets/error-entries-Birw-8VX.js +0 -8
- package/dist/assets/floating-outline-wz-WSRgP.js +0 -1
- package/dist/assets/focus-DiEyfiAG.js +0 -1
- package/dist/assets/home-page-DJ4jJsZj.js +0 -4
- package/dist/assets/index-A71LAt6h.css +0 -2
- package/dist/assets/index-CMZzy-dF.js +0 -38
- package/dist/assets/kiosk-mode-ABm3oYgq.js +0 -1
- package/dist/assets/logs-panel-iFV4ZJ9b.js +0 -1
- package/dist/assets/node-sql-parser-BWgUjcfV.js +0 -68
- package/dist/assets/pair-with-agent-modal-CKOYwOYw.js +0 -4
- package/dist/assets/panels-Dj45fW-v.js +0 -4
- package/dist/assets/process-output-BEhIGfkO.js +0 -1
- package/dist/assets/scratchpad-panel-B0mEqBNk.js +0 -1
- package/dist/assets/slides-MCbMDtlY.css +0 -1
- package/dist/assets/state-BZQc7xxL.js +0 -3
- package/dist/assets/switch-CvweaCXq.js +0 -1
- package/dist/assets/useCellActionButton-CRfIEVB5.js +0 -1
- package/dist/assets/useEventListener-B_w3A283.js +0 -1
- package/dist/assets/useNotebookActions-DTXAoukG.js +0 -12
|
@@ -22,6 +22,7 @@ vi.mock("../api", async () => {
|
|
|
22
22
|
return {
|
|
23
23
|
...actual,
|
|
24
24
|
API: {
|
|
25
|
+
handleExportResponse: vi.fn((response) => response.data),
|
|
25
26
|
handleResponse: vi.fn((response) => response.data),
|
|
26
27
|
handleResponseReturnNull: vi.fn(() => null),
|
|
27
28
|
},
|
|
@@ -32,6 +33,7 @@ vi.mock("../api", async () => {
|
|
|
32
33
|
vi.mock("../connection", () => ({
|
|
33
34
|
isConnectedAtom: { read: vi.fn(() => true) },
|
|
34
35
|
waitForConnectionOpen: vi.fn().mockResolvedValue(undefined),
|
|
36
|
+
waitForConnectionOpenIfNotebook: vi.fn().mockResolvedValue(undefined),
|
|
35
37
|
}));
|
|
36
38
|
|
|
37
39
|
describe("createNetworkRequests", () => {
|
|
@@ -121,6 +123,25 @@ describe("createNetworkRequests", () => {
|
|
|
121
123
|
expect(mockClient.GET).toHaveBeenCalledWith("/api/environment");
|
|
122
124
|
});
|
|
123
125
|
|
|
126
|
+
it("getExportAvailability should GET /api/export/availability", async () => {
|
|
127
|
+
const requests = createNetworkRequests();
|
|
128
|
+
await requests.getExportAvailability();
|
|
129
|
+
|
|
130
|
+
expect(mockClient.GET).toHaveBeenCalledWith("/api/export/availability");
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("getPackageList should not require a kernel connection", async () => {
|
|
134
|
+
const { waitForConnectionOpen, waitForConnectionOpenIfNotebook } =
|
|
135
|
+
await import("../connection");
|
|
136
|
+
|
|
137
|
+
const requests = createNetworkRequests();
|
|
138
|
+
await requests.getPackageList();
|
|
139
|
+
|
|
140
|
+
expect(mockClient.GET).toHaveBeenCalledWith("/api/packages/list");
|
|
141
|
+
expect(waitForConnectionOpenIfNotebook).toHaveBeenCalled();
|
|
142
|
+
expect(waitForConnectionOpen).not.toHaveBeenCalled();
|
|
143
|
+
});
|
|
144
|
+
|
|
124
145
|
it("exportAsIPYNB should call the new endpoint as text", async () => {
|
|
125
146
|
const requests = createNetworkRequests();
|
|
126
147
|
await requests.exportAsIPYNB({
|
package/src/core/network/api.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { Logger } from "../../utils/Logger";
|
|
|
6
6
|
import { Strings } from "../../utils/strings";
|
|
7
7
|
import { getRuntimeManager } from "../runtime/config";
|
|
8
8
|
import type { RuntimeManager } from "../runtime/runtime";
|
|
9
|
+
import type { ExportedFile } from "./types";
|
|
9
10
|
|
|
10
11
|
function getBaseUriWithoutQueryParams(): string {
|
|
11
12
|
// Remove query params and hash
|
|
@@ -15,6 +16,15 @@ function getBaseUriWithoutQueryParams(): string {
|
|
|
15
16
|
return url.toString();
|
|
16
17
|
}
|
|
17
18
|
|
|
19
|
+
function getExportFilename(response: Response): string {
|
|
20
|
+
const contentDisposition = response.headers.get("Content-Disposition");
|
|
21
|
+
const match = contentDisposition?.match(/filename\*=UTF-8''([^;]+)/i);
|
|
22
|
+
if (!match) {
|
|
23
|
+
throw new Error("Export response is missing a filename");
|
|
24
|
+
}
|
|
25
|
+
return decodeURIComponent(match[1]);
|
|
26
|
+
}
|
|
27
|
+
|
|
18
28
|
/**
|
|
19
29
|
* Wrapper around fetch that adds XSRF token and session ID to the request and
|
|
20
30
|
* strong types.
|
|
@@ -119,6 +129,23 @@ export const API = {
|
|
|
119
129
|
}
|
|
120
130
|
return Promise.resolve(response.data as T);
|
|
121
131
|
},
|
|
132
|
+
handleExportResponse: async <T extends BlobPart>(response: {
|
|
133
|
+
data?: T | undefined;
|
|
134
|
+
error?: Record<string, unknown>;
|
|
135
|
+
response: Response;
|
|
136
|
+
}): Promise<ExportedFile<T>> => {
|
|
137
|
+
const contents = await API.handleResponse<T>(response);
|
|
138
|
+
const mediaType = response.response.headers.get("Content-Type");
|
|
139
|
+
if (!mediaType) {
|
|
140
|
+
throw new Error("Export response is missing a media type");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
contents,
|
|
145
|
+
filename: getExportFilename(response.response),
|
|
146
|
+
mediaType,
|
|
147
|
+
};
|
|
148
|
+
},
|
|
122
149
|
handleResponseReturnNull: (response: {
|
|
123
150
|
error?: Record<string, unknown>;
|
|
124
151
|
response: Response;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
import { atom } from "jotai";
|
|
3
|
+
import { isNotebookPage } from "../mode";
|
|
3
4
|
import { waitFor } from "../state/jotai";
|
|
4
5
|
import { type ConnectionStatus, WebSocketState } from "../websocket/types";
|
|
5
6
|
|
|
@@ -17,6 +18,20 @@ export function waitForConnectionOpen() {
|
|
|
17
18
|
});
|
|
18
19
|
}
|
|
19
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Waits for the kernel connection, but only on pages that open one. Non-notebook
|
|
23
|
+
* pages (home, gallery) never connect, so waiting there would hang forever.
|
|
24
|
+
*
|
|
25
|
+
* Use for requests that the marimo server can serve without a session, but that
|
|
26
|
+
* should still wait for the kernel when a notebook is open.
|
|
27
|
+
*/
|
|
28
|
+
export function waitForConnectionOpenIfNotebook() {
|
|
29
|
+
if (!isNotebookPage()) {
|
|
30
|
+
return Promise.resolve();
|
|
31
|
+
}
|
|
32
|
+
return waitForConnectionOpen();
|
|
33
|
+
}
|
|
34
|
+
|
|
20
35
|
export const isConnectingAtom = atom((get) => {
|
|
21
36
|
const connection = get(connectionAtom);
|
|
22
37
|
return connection.state === WebSocketState.CONNECTING;
|
|
@@ -103,6 +103,7 @@ const ACTIONS: Record<keyof AllRequests, Action> = {
|
|
|
103
103
|
|
|
104
104
|
// Served by the marimo server without a kernel session
|
|
105
105
|
getEnvironmentInfo: "serverOnly",
|
|
106
|
+
getExportAvailability: "serverOnly",
|
|
106
107
|
|
|
107
108
|
// Home operations throw errors
|
|
108
109
|
getRecentFiles: "startConnection",
|
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
import { once } from "@/utils/once";
|
|
4
4
|
import { getRuntimeManager } from "../runtime/config";
|
|
5
5
|
import { API, createClientWithRuntimeManager } from "./api";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
waitForConnectionOpen,
|
|
8
|
+
waitForConnectionOpenIfNotebook,
|
|
9
|
+
} from "./connection";
|
|
7
10
|
import type { EditRequests, RunRequests } from "./types";
|
|
8
11
|
|
|
9
12
|
/**
|
|
@@ -19,7 +22,7 @@ function multipartInit(formData: FormData) {
|
|
|
19
22
|
};
|
|
20
23
|
}
|
|
21
24
|
|
|
22
|
-
const { handleResponse, handleResponseReturnNull } = API;
|
|
25
|
+
const { handleExportResponse, handleResponse, handleResponseReturnNull } = API;
|
|
23
26
|
|
|
24
27
|
export function createNetworkRequests(): EditRequests & RunRequests {
|
|
25
28
|
const getClient = once(() => {
|
|
@@ -399,6 +402,9 @@ export function createNetworkRequests(): EditRequests & RunRequests {
|
|
|
399
402
|
})
|
|
400
403
|
.then(handleResponse);
|
|
401
404
|
},
|
|
405
|
+
getExportAvailability: () => {
|
|
406
|
+
return getClient().GET("/api/export/availability").then(handleResponse);
|
|
407
|
+
},
|
|
402
408
|
exportAsHTML: async (request) => {
|
|
403
409
|
if (
|
|
404
410
|
process.env.NODE_ENV === "development" ||
|
|
@@ -412,7 +418,7 @@ export function createNetworkRequests(): EditRequests & RunRequests {
|
|
|
412
418
|
parseAs: "text",
|
|
413
419
|
params: getParams(),
|
|
414
420
|
})
|
|
415
|
-
.then(
|
|
421
|
+
.then(handleExportResponse);
|
|
416
422
|
},
|
|
417
423
|
exportAsMarkdown: async (request) => {
|
|
418
424
|
return getClient()
|
|
@@ -421,7 +427,7 @@ export function createNetworkRequests(): EditRequests & RunRequests {
|
|
|
421
427
|
parseAs: "text",
|
|
422
428
|
params: getParams(),
|
|
423
429
|
})
|
|
424
|
-
.then(
|
|
430
|
+
.then(handleExportResponse);
|
|
425
431
|
},
|
|
426
432
|
exportAsIPYNB: async (request) => {
|
|
427
433
|
return getClient()
|
|
@@ -430,7 +436,7 @@ export function createNetworkRequests(): EditRequests & RunRequests {
|
|
|
430
436
|
parseAs: "text",
|
|
431
437
|
params: getParams(),
|
|
432
438
|
})
|
|
433
|
-
.then(
|
|
439
|
+
.then(handleExportResponse);
|
|
434
440
|
},
|
|
435
441
|
exportAsPDF: async (request) => {
|
|
436
442
|
return getClient()
|
|
@@ -439,7 +445,7 @@ export function createNetworkRequests(): EditRequests & RunRequests {
|
|
|
439
445
|
parseAs: "blob",
|
|
440
446
|
params: getParams(),
|
|
441
447
|
})
|
|
442
|
-
.then(
|
|
448
|
+
.then(handleExportResponse);
|
|
443
449
|
},
|
|
444
450
|
autoExportAsHTML: async (request) => {
|
|
445
451
|
return getClient()
|
|
@@ -489,12 +495,12 @@ export function createNetworkRequests(): EditRequests & RunRequests {
|
|
|
489
495
|
},
|
|
490
496
|
getPackageList: async () => {
|
|
491
497
|
// If the sidebar is already open, it may try to load before the session has been initialized
|
|
492
|
-
await
|
|
498
|
+
await waitForConnectionOpenIfNotebook();
|
|
493
499
|
return getClient().GET("/api/packages/list").then(handleResponse);
|
|
494
500
|
},
|
|
495
501
|
getDependencyTree: async () => {
|
|
496
502
|
// If the sidebar is already open, it may try to load before the session has been initialized
|
|
497
|
-
await
|
|
503
|
+
await waitForConnectionOpenIfNotebook();
|
|
498
504
|
return getClient().GET("/api/packages/tree").then(handleResponse);
|
|
499
505
|
},
|
|
500
506
|
listSecretKeys: async (request) => {
|
|
@@ -82,6 +82,7 @@ export function createStaticRequests(): EditRequests & RunRequests {
|
|
|
82
82
|
getWorkspaceFiles: throwNotInEditMode,
|
|
83
83
|
getRunningNotebooks: throwNotInEditMode,
|
|
84
84
|
shutdownSession: throwNotInEditMode,
|
|
85
|
+
getExportAvailability: throwNotInEditMode,
|
|
85
86
|
exportAsHTML: throwNotInEditMode,
|
|
86
87
|
exportAsIPYNB: throwNotInEditMode,
|
|
87
88
|
exportAsMarkdown: throwNotInEditMode,
|
|
@@ -63,6 +63,7 @@ export function createErrorToastingRequests(
|
|
|
63
63
|
getWorkspaceFiles: "Failed to get workspace files",
|
|
64
64
|
getRunningNotebooks: "Failed to get running notebooks",
|
|
65
65
|
shutdownSession: "Failed to shutdown session",
|
|
66
|
+
getExportAvailability: "", // No toast
|
|
66
67
|
exportAsHTML: "Failed to export HTML",
|
|
67
68
|
exportAsIPYNB: "Failed to export ipynb",
|
|
68
69
|
exportAsMarkdown: "Failed to export Markdown",
|
|
@@ -17,12 +17,23 @@ export type CellConfig = schemas["CellConfig"];
|
|
|
17
17
|
export type RuntimeState = schemas["CellNotification"]["status"];
|
|
18
18
|
export type CodeCompletionRequest = schemas["CodeCompletionRequest"];
|
|
19
19
|
export type DeleteCellRequest = schemas["DeleteCellRequest"];
|
|
20
|
+
export type AutoExportAsIPYNBRequest = schemas["AutoExportAsIPYNBRequest"];
|
|
21
|
+
export type AutoExportAsMarkdownRequest =
|
|
22
|
+
schemas["AutoExportAsMarkdownRequest"];
|
|
20
23
|
export type ExportAsHTMLRequest = schemas["ExportAsHTMLRequest"];
|
|
21
24
|
export type ExportAsMarkdownRequest = schemas["ExportAsMarkdownRequest"];
|
|
22
25
|
export type ExportAsIPYNBRequest = schemas["ExportAsIPYNBRequest"];
|
|
23
26
|
export type ExportAsScriptRequest = schemas["ExportAsScriptRequest"];
|
|
24
27
|
export type ExportAsPDFRequest = schemas["ExportAsPDFRequest"];
|
|
28
|
+
export type ExportAvailabilityResponse = schemas["ExportAvailabilityResponse"];
|
|
25
29
|
export type UpdateCellOutputsRequest = schemas["UpdateCellOutputsRequest"];
|
|
30
|
+
|
|
31
|
+
export interface ExportedFile<T extends BlobPart = BlobPart> {
|
|
32
|
+
contents: T;
|
|
33
|
+
filename: string;
|
|
34
|
+
mediaType: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
26
37
|
export type FileCopyRequest = schemas["FileCopyRequest"];
|
|
27
38
|
export type FileCopyResponse = schemas["FileCopyResponse"];
|
|
28
39
|
export type FileCreateRequest = schemas["FileCreateRequest"];
|
|
@@ -205,13 +216,18 @@ export interface EditRequests {
|
|
|
205
216
|
request: ShutdownSessionRequest,
|
|
206
217
|
) => Promise<RunningNotebooksResponse>;
|
|
207
218
|
// Export requests
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
219
|
+
getExportAvailability: () => Promise<ExportAvailabilityResponse>;
|
|
220
|
+
exportAsHTML: (request: ExportAsHTMLRequest) => Promise<ExportedFile<string>>;
|
|
221
|
+
exportAsIPYNB: (
|
|
222
|
+
request: ExportAsIPYNBRequest,
|
|
223
|
+
) => Promise<ExportedFile<string>>;
|
|
224
|
+
exportAsMarkdown: (
|
|
225
|
+
request: ExportAsMarkdownRequest,
|
|
226
|
+
) => Promise<ExportedFile<string>>;
|
|
227
|
+
exportAsPDF: (request: ExportAsPDFRequest) => Promise<ExportedFile<Blob>>;
|
|
212
228
|
autoExportAsHTML: (request: ExportAsHTMLRequest) => Promise<null>;
|
|
213
|
-
autoExportAsMarkdown: (request:
|
|
214
|
-
autoExportAsIPYNB: (request:
|
|
229
|
+
autoExportAsMarkdown: (request: AutoExportAsMarkdownRequest) => Promise<null>;
|
|
230
|
+
autoExportAsIPYNB: (request: AutoExportAsIPYNBRequest) => Promise<null>;
|
|
215
231
|
updateCellOutputs: (request: UpdateCellOutputsRequest) => Promise<null>;
|
|
216
232
|
// Package requests
|
|
217
233
|
getPackageList: () => Promise<ListPackagesResponse>;
|
package/src/core/rtc/state.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
import { atomWithStorage } from "jotai/utils";
|
|
3
|
+
import { SCRATCH_CELL_ID, type CellId } from "@/core/cells/ids";
|
|
3
4
|
import { once } from "@/utils/once";
|
|
4
5
|
import { jotaiJsonStorage } from "@/utils/storage/jotai";
|
|
5
6
|
import { getFeatureFlag } from "../config/feature-flag";
|
|
@@ -23,3 +24,11 @@ export const usernameAtom = atomWithStorage<string>(
|
|
|
23
24
|
export const isRtcEnabled = once(() => {
|
|
24
25
|
return getFeatureFlag("rtc_v2");
|
|
25
26
|
});
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Whether this cell type can participate in real-time collaboration.
|
|
30
|
+
* Scratchpad cells are local-only.
|
|
31
|
+
*/
|
|
32
|
+
export function canUseRtc(cellId: CellId): boolean {
|
|
33
|
+
return cellId !== SCRATCH_CELL_ID;
|
|
34
|
+
}
|
|
@@ -1,31 +1,22 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import { Filenames } from "@/utils/filenames";
|
|
5
|
-
import { Paths } from "@/utils/paths";
|
|
3
|
+
import { downloadExportedFile } from "@/utils/download";
|
|
6
4
|
import { getRequestClient } from "../network/requests";
|
|
7
5
|
import { VirtualFileTracker } from "./virtual-file-tracker";
|
|
8
6
|
|
|
9
7
|
/**
|
|
10
8
|
* Downloads the current notebook as an HTML file.
|
|
11
9
|
*/
|
|
12
|
-
export async function downloadAsHTML(opts: {
|
|
13
|
-
filename: string;
|
|
14
|
-
includeCode: boolean;
|
|
15
|
-
}) {
|
|
10
|
+
export async function downloadAsHTML(opts: { includeCode: boolean }) {
|
|
16
11
|
const client = getRequestClient();
|
|
17
|
-
const {
|
|
18
|
-
const
|
|
12
|
+
const { includeCode } = opts;
|
|
13
|
+
const exportedFile = await client.exportAsHTML({
|
|
19
14
|
download: true,
|
|
20
15
|
includeCode: includeCode,
|
|
21
16
|
files: VirtualFileTracker.INSTANCE.filenames(),
|
|
22
17
|
});
|
|
23
|
-
const filenameWithoutPath = Paths.basename(filename) ?? "notebook.py";
|
|
24
18
|
|
|
25
|
-
|
|
26
|
-
new Blob([html], { type: "text/html" }),
|
|
27
|
-
Filenames.toHTML(filenameWithoutPath),
|
|
28
|
-
);
|
|
19
|
+
downloadExportedFile(exportedFile);
|
|
29
20
|
}
|
|
30
21
|
|
|
31
22
|
function updateAssetUrl(existingUrl: string, assetBaseUrl: string) {
|
package/src/core/wasm/bridge.ts
CHANGED
|
@@ -19,6 +19,7 @@ import type {
|
|
|
19
19
|
EnvironmentInfo,
|
|
20
20
|
ExportAsHTMLRequest,
|
|
21
21
|
ExportAsMarkdownRequest,
|
|
22
|
+
ExportedFile,
|
|
22
23
|
FileCopyResponse,
|
|
23
24
|
FileCreateResponse,
|
|
24
25
|
FileDeleteResponse,
|
|
@@ -520,7 +521,7 @@ export class PyodideBridge implements RunRequests, EditRequests {
|
|
|
520
521
|
functionName: "export_html",
|
|
521
522
|
payload: request,
|
|
522
523
|
});
|
|
523
|
-
return response as string
|
|
524
|
+
return response as ExportedFile<string>;
|
|
524
525
|
};
|
|
525
526
|
|
|
526
527
|
exportAsMarkdown: EditRequests["exportAsMarkdown"] = async (
|
|
@@ -530,7 +531,7 @@ export class PyodideBridge implements RunRequests, EditRequests {
|
|
|
530
531
|
functionName: "export_markdown",
|
|
531
532
|
payload: request,
|
|
532
533
|
});
|
|
533
|
-
return response as string
|
|
534
|
+
return response as ExportedFile<string>;
|
|
534
535
|
};
|
|
535
536
|
|
|
536
537
|
previewDatasetColumn: EditRequests["previewDatasetColumn"] = async (
|
|
@@ -642,6 +643,7 @@ export class PyodideBridge implements RunRequests, EditRequests {
|
|
|
642
643
|
getWorkspaceFiles = throwNotImplemented;
|
|
643
644
|
getRunningNotebooks = throwNotImplemented;
|
|
644
645
|
shutdownSession = throwNotImplemented;
|
|
646
|
+
getExportAvailability = throwNotImplemented;
|
|
645
647
|
exportAsIPYNB = throwNotImplemented;
|
|
646
648
|
exportAsPDF = throwNotImplemented;
|
|
647
649
|
autoExportAsHTML = throwNotImplemented;
|
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
EnvironmentInfo,
|
|
13
13
|
ExportAsHTMLRequest,
|
|
14
14
|
ExportAsMarkdownRequest,
|
|
15
|
+
ExportedFile,
|
|
15
16
|
FileCopyRequest,
|
|
16
17
|
FileCopyResponse,
|
|
17
18
|
FileCreateRequest,
|
|
@@ -96,8 +97,10 @@ export interface RawBridge {
|
|
|
96
97
|
load_packages(request: string): Promise<string>;
|
|
97
98
|
read_file(request: string): Promise<string>;
|
|
98
99
|
set_interrupt_buffer(request: Uint8Array): Promise<string>;
|
|
99
|
-
export_html(request: ExportAsHTMLRequest): Promise<string
|
|
100
|
-
export_markdown(
|
|
100
|
+
export_html(request: ExportAsHTMLRequest): Promise<ExportedFile<string>>;
|
|
101
|
+
export_markdown(
|
|
102
|
+
request: ExportAsMarkdownRequest,
|
|
103
|
+
): Promise<ExportedFile<string>>;
|
|
101
104
|
}
|
|
102
105
|
|
|
103
106
|
export type BridgePayload<T extends keyof RawBridge> = T extends keyof RawBridge
|
|
@@ -90,8 +90,22 @@
|
|
|
90
90
|
@apply bg-muted border border-border rounded-md px-1;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
/* The message, the docs link, and related info are siblings; stack them. */
|
|
94
|
+
.cm-lsp-diagnostic-message {
|
|
95
|
+
@apply flex flex-col items-start gap-1;
|
|
96
|
+
|
|
97
|
+
/* Rendered markdown, so the `pre-wrap` on `.cm-diagnostic` would only turn
|
|
98
|
+
the whitespace between block tags into blank lines. */
|
|
99
|
+
white-space: normal;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.cm-diagnostic-code-link {
|
|
103
|
+
@apply text-xs;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* The docs link already reads `source(code)`; don't repeat it below. */
|
|
107
|
+
.cm-diagnostic:has(.cm-diagnostic-code-link) .cm-diagnosticSource {
|
|
108
|
+
@apply hidden;
|
|
95
109
|
}
|
|
96
110
|
}
|
|
97
111
|
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
4
|
+
import { describe, expect, it, vi } from "vitest";
|
|
5
|
+
import { FormWrapper } from "../FormPlugin";
|
|
6
|
+
|
|
7
|
+
describe("FormPlugin", () => {
|
|
8
|
+
it("submits the form on Ctrl+Enter and calls setValue", async () => {
|
|
9
|
+
const setValue = vi.fn();
|
|
10
|
+
const validate = vi.fn().mockResolvedValue(null);
|
|
11
|
+
|
|
12
|
+
render(
|
|
13
|
+
<FormWrapper
|
|
14
|
+
currentValue="old-val"
|
|
15
|
+
newValue="new-val"
|
|
16
|
+
setValue={setValue}
|
|
17
|
+
validate={validate}
|
|
18
|
+
shouldValidate={false}
|
|
19
|
+
clearOnSubmit={false}
|
|
20
|
+
submitButtonLabel="Submit"
|
|
21
|
+
bordered={true}
|
|
22
|
+
loading={false}
|
|
23
|
+
submitButtonDisabled={false}
|
|
24
|
+
showClearButton={false}
|
|
25
|
+
clearButtonLabel="Clear"
|
|
26
|
+
label={null}
|
|
27
|
+
>
|
|
28
|
+
<input data-testid="input" />
|
|
29
|
+
</FormWrapper>,
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const input = screen.getByTestId("input");
|
|
33
|
+
|
|
34
|
+
// Simulate Ctrl+Enter on input
|
|
35
|
+
fireEvent.keyDown(input, { key: "Enter", ctrlKey: true });
|
|
36
|
+
|
|
37
|
+
await waitFor(() => {
|
|
38
|
+
expect(setValue).toHaveBeenCalledWith("new-val");
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("submits the form on Ctrl+Enter and runs validation when shouldValidate is true", async () => {
|
|
43
|
+
const setValue = vi.fn();
|
|
44
|
+
const validate = vi.fn().mockResolvedValue("Validation failed message");
|
|
45
|
+
|
|
46
|
+
render(
|
|
47
|
+
<FormWrapper
|
|
48
|
+
currentValue="old-val"
|
|
49
|
+
newValue="new-val"
|
|
50
|
+
setValue={setValue}
|
|
51
|
+
validate={validate}
|
|
52
|
+
shouldValidate={true}
|
|
53
|
+
clearOnSubmit={false}
|
|
54
|
+
submitButtonLabel="Submit"
|
|
55
|
+
bordered={true}
|
|
56
|
+
loading={false}
|
|
57
|
+
submitButtonDisabled={false}
|
|
58
|
+
showClearButton={false}
|
|
59
|
+
clearButtonLabel="Clear"
|
|
60
|
+
label={null}
|
|
61
|
+
>
|
|
62
|
+
<input data-testid="input" />
|
|
63
|
+
</FormWrapper>,
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const input = screen.getByTestId("input");
|
|
67
|
+
|
|
68
|
+
// Simulate Ctrl+Enter on input
|
|
69
|
+
fireEvent.keyDown(input, { key: "Enter", ctrlKey: true });
|
|
70
|
+
|
|
71
|
+
await waitFor(() => {
|
|
72
|
+
expect(validate).toHaveBeenCalledWith({ value: "new-val" });
|
|
73
|
+
// should not submit (setValue not called) due to validation failure
|
|
74
|
+
expect(setValue).not.toHaveBeenCalled();
|
|
75
|
+
// error message displayed
|
|
76
|
+
expect(screen.getByText("Validation failed message")).toBeInTheDocument();
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("clears inputs on submission when clearOnSubmit is true", async () => {
|
|
81
|
+
const setValue = vi.fn();
|
|
82
|
+
const validate = vi.fn().mockResolvedValue(null);
|
|
83
|
+
|
|
84
|
+
let mockUIElement: (HTMLElement & { reset?: () => void }) | null = null;
|
|
85
|
+
const mockReset = vi.fn();
|
|
86
|
+
|
|
87
|
+
render(
|
|
88
|
+
<FormWrapper
|
|
89
|
+
currentValue="old-val"
|
|
90
|
+
newValue="new-val"
|
|
91
|
+
setValue={setValue}
|
|
92
|
+
validate={validate}
|
|
93
|
+
shouldValidate={false}
|
|
94
|
+
clearOnSubmit={true}
|
|
95
|
+
submitButtonLabel="Submit"
|
|
96
|
+
bordered={true}
|
|
97
|
+
loading={false}
|
|
98
|
+
submitButtonDisabled={false}
|
|
99
|
+
showClearButton={false}
|
|
100
|
+
clearButtonLabel="Clear"
|
|
101
|
+
label={null}
|
|
102
|
+
>
|
|
103
|
+
<div
|
|
104
|
+
ref={(el) => {
|
|
105
|
+
if (el && !mockUIElement) {
|
|
106
|
+
mockUIElement = document.createElement("marimo-ui-element");
|
|
107
|
+
mockUIElement.setAttribute("data-testid", "mock-ui-element");
|
|
108
|
+
mockUIElement.reset = mockReset;
|
|
109
|
+
const inputEl = document.createElement("input");
|
|
110
|
+
inputEl.type = "text";
|
|
111
|
+
mockUIElement.appendChild(inputEl);
|
|
112
|
+
el.appendChild(mockUIElement);
|
|
113
|
+
}
|
|
114
|
+
}}
|
|
115
|
+
/>
|
|
116
|
+
</FormWrapper>,
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
const input = screen.getByRole("textbox");
|
|
120
|
+
|
|
121
|
+
// Simulate Ctrl+Enter on input
|
|
122
|
+
fireEvent.keyDown(input, { key: "Enter", ctrlKey: true });
|
|
123
|
+
|
|
124
|
+
await waitFor(() => {
|
|
125
|
+
expect(setValue).toHaveBeenCalledWith("new-val");
|
|
126
|
+
expect(mockReset).toHaveBeenCalled();
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("does not submit on Shift+Enter", async () => {
|
|
131
|
+
const setValue = vi.fn();
|
|
132
|
+
const validate = vi.fn().mockResolvedValue(null);
|
|
133
|
+
|
|
134
|
+
render(
|
|
135
|
+
<FormWrapper
|
|
136
|
+
currentValue="old-val"
|
|
137
|
+
newValue="new-val"
|
|
138
|
+
setValue={setValue}
|
|
139
|
+
validate={validate}
|
|
140
|
+
shouldValidate={false}
|
|
141
|
+
clearOnSubmit={false}
|
|
142
|
+
submitButtonLabel="Submit"
|
|
143
|
+
bordered={true}
|
|
144
|
+
loading={false}
|
|
145
|
+
submitButtonDisabled={false}
|
|
146
|
+
showClearButton={false}
|
|
147
|
+
clearButtonLabel="Clear"
|
|
148
|
+
label={null}
|
|
149
|
+
>
|
|
150
|
+
<input data-testid="input" />
|
|
151
|
+
</FormWrapper>,
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
const input = screen.getByTestId("input");
|
|
155
|
+
|
|
156
|
+
// Simulate Shift+Enter on input
|
|
157
|
+
fireEvent.keyDown(input, { key: "Enter", shiftKey: true });
|
|
158
|
+
|
|
159
|
+
expect(setValue).not.toHaveBeenCalled();
|
|
160
|
+
expect(validate).not.toHaveBeenCalled();
|
|
161
|
+
});
|
|
162
|
+
});
|
|
@@ -23,37 +23,43 @@ export const Primary: Story = {
|
|
|
23
23
|
{
|
|
24
24
|
timestamp: Date.now(),
|
|
25
25
|
level: "stdout",
|
|
26
|
-
cellId: cellId("cell1"),
|
|
26
|
+
source: { type: "cell", cellId: cellId("cell1") },
|
|
27
27
|
message: "Hello world!",
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
30
|
timestamp: Date.now(),
|
|
31
31
|
level: "stdout",
|
|
32
|
-
cellId: cellId("cell1"),
|
|
32
|
+
source: { type: "cell", cellId: cellId("cell1") },
|
|
33
33
|
message: "Running cell...",
|
|
34
34
|
},
|
|
35
35
|
{
|
|
36
36
|
timestamp: Date.now(),
|
|
37
37
|
level: "stdout",
|
|
38
|
-
cellId: cellId("cell1"),
|
|
38
|
+
source: { type: "cell", cellId: cellId("cell1") },
|
|
39
39
|
message: "Done!",
|
|
40
40
|
},
|
|
41
41
|
{
|
|
42
42
|
timestamp: Date.now(),
|
|
43
43
|
level: "stderr",
|
|
44
|
-
cellId: cellId("cell2"),
|
|
44
|
+
source: { type: "cell", cellId: cellId("cell2") },
|
|
45
45
|
message: "Output is too large!",
|
|
46
46
|
},
|
|
47
47
|
{
|
|
48
48
|
timestamp: Date.now(),
|
|
49
49
|
level: "stderr",
|
|
50
|
-
cellId: cellId("cell2"),
|
|
50
|
+
source: { type: "cell", cellId: cellId("cell2") },
|
|
51
51
|
message: "String length is too short.".repeat(100),
|
|
52
52
|
},
|
|
53
|
+
{
|
|
54
|
+
timestamp: Date.now(),
|
|
55
|
+
level: "stderr",
|
|
56
|
+
source: { type: "lsp", name: "pylsp" },
|
|
57
|
+
message: "Failed to load plugin 'ruff': not installed",
|
|
58
|
+
},
|
|
53
59
|
...Array.from({ length: 100 }).map(() => ({
|
|
54
60
|
timestamp: Date.now(),
|
|
55
61
|
level: "stdout" as const,
|
|
56
|
-
cellId: cellId("cell1"),
|
|
62
|
+
source: { type: "cell", cellId: cellId("cell1") } as const,
|
|
57
63
|
message: "Running cell...",
|
|
58
64
|
})),
|
|
59
65
|
]}
|