@marimo-team/islands 0.25.2 → 0.25.3-dev100
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-CNSxdKyJ.js → ConnectedDataExplorerComponent-Dhf5EGs5.js} +14 -14
- package/dist/{ErrorBoundary-HusVNio-.js → ErrorBoundary-GScQVnJ8.js} +8 -8
- package/dist/{abnfDiagram-VCTEODGH-DQDjNXt3.js → abnfDiagram-VCTEODGH-Di400kYu.js} +2 -2
- package/dist/any-language-editor-k2B8YI3Y.js +75 -0
- package/dist/{architectureDiagram-5GKGNRK7-Nmls7Wxn.js → architectureDiagram-5GKGNRK7-BdTJcsNJ.js} +1 -1
- package/dist/{arrays-CN1djC4k.js → arrays-R6JsImiT.js} +8 -0
- package/dist/assets/__vite-browser-external-35VXYmnw.js +1 -0
- package/dist/assets/{worker-HPY9fbhz.js → worker-Dq8y-WM5.js} +2 -2
- package/dist/{button-CvjkwdSk.js → button-BxB3RDjN.js} +1 -1
- package/dist/{capabilities-CQ5IjhzL.js → capabilities-BgR0osfN.js} +2 -2
- package/dist/{chat-ui-BDpymeoe.js → chat-ui-BIgPxfPP.js} +1193 -1194
- package/dist/{check-EDwSwP89.js → check-P35dc2dH.js} +1 -1
- package/dist/{chunk-IMKFNOWR-CoIFEo-1.js → chunk-IMKFNOWR-mtD7aJBw.js} +1 -1
- package/dist/{chunk-SHT3W25Y-lHiGx3ZC.js → chunk-SHT3W25Y-DybB1ShH.js} +1 -1
- package/dist/{chunk-SVP7TREG-BvsHIrY_.js → chunk-SVP7TREG-DNhhdCex.js} +1 -1
- package/dist/{chunk-TICWLB2K-BC8YIm_n.js → chunk-TICWLB2K-NhAQhlDf.js} +1 -1
- package/dist/{chunk-YOKDWASO-DPZk8vhS.js → chunk-YOKDWASO-DKplTrBY.js} +20 -20
- package/dist/{classDiagram-ZZMXUADV-Bt98lkkv.js → classDiagram-ZZMXUADV-GTL0l5xS.js} +2 -2
- package/dist/{classDiagram-v2-VYDZK3BY-D9bfbcC3.js → classDiagram-v2-VYDZK3BY-BWrM2ruI.js} +2 -2
- package/dist/{code-visibility-4aW5326K.js → code-visibility-DnqM5oW_.js} +17 -17
- package/dist/{common-Cjo7bjRK.js → common-D2HSo8Gy.js} +6091 -5697
- package/dist/{copy-icon-Ft7t9bqz.js → copy-icon-CPoxUG7G.js} +3 -3
- package/dist/{cynefinDiagram-5FMLGOSQ-CgWra7wt.js → cynefinDiagram-5FMLGOSQ-CX22wa7c.js} +1 -1
- package/dist/{dagre-GXQ25YYZ-CFZ8-35c.js → dagre-GXQ25YYZ-Df6Pp-cl.js} +1 -1
- package/dist/{data-grid-overlay-editor-CvdQXfSk.js → data-grid-overlay-editor-BgIMQs9G.js} +5 -5
- package/dist/{data-grid-types-BH_r7g7l.js → data-grid-types-0PJ-9N5n.js} +1 -1
- package/dist/{diagram-S7CK7UJ4-lCNQrW7d.js → diagram-S7CK7UJ4-CctU9kVx.js} +1 -1
- package/dist/{diagram-UQ7AKVKN-CQPQ6pP_.js → diagram-UQ7AKVKN-FVHjCF9j.js} +1 -1
- package/dist/{diagram-VX7I27RA-Cf3b0llz.js → diagram-VX7I27RA-BSDbwFFv.js} +1 -1
- package/dist/{diagram-Z3DM3KII-Bt4QLrpn.js → diagram-Z3DM3KII-BX8kW7Nc.js} +1 -1
- package/dist/{dialog-CEBFZNyc.js → dialog-C1PcpCqX.js} +3 -3
- package/dist/{dist-DpBpA_n1.js → dist-Lm-D-Vr6.js} +3 -3
- package/dist/{dropdown-menu-CP_qCYHC.js → dropdown-menu-B_7xD2X4.js} +177 -169
- package/dist/{ebnfDiagram-PWID7BFC-D3NAyPFY.js → ebnfDiagram-PWID7BFC-DtapdLet.js} +2 -2
- package/dist/{erDiagram-RLTQ6QDP-Tlu09_Nm.js → erDiagram-RLTQ6QDP-BAru9jHt.js} +1 -1
- package/dist/{error-banner-Da600cwz.js → error-banner-Sl2FlSJ4.js} +12 -7
- package/dist/{cells-C8iCYvLU.js → error-entries-TAO6kEhF.js} +1573 -1504
- package/dist/{feedback-button-ZuLnINhC.js → feedback-button-BXcB6llx.js} +122 -123
- package/dist/{flowDiagram-HODETNUW-CAYbpJmV.js → flowDiagram-HODETNUW-CjaeFeea.js} +2 -2
- package/dist/{formats-BGh2thRc.js → formats-V8ifA0Bk.js} +3 -3
- package/dist/{ganttDiagram-EL5Y4UJY-BQT16JSc.js → ganttDiagram-EL5Y4UJY-Dh_4cBJZ.js} +2 -2
- package/dist/{glide-data-editor-qM9FSJA9.js → glide-data-editor-S-_J-7SO.js} +370 -368
- package/dist/{glide-utils-DJyqh0EM.js → glide-utils-Bt8_pBa2.js} +16 -16
- package/dist/{globals-Ci7B9t6C.js → globals-D4vBwcD6.js} +3 -3
- package/dist/{highlighted-body-KPVGNVTW-zwjq21tf.js → highlighted-body-KPVGNVTW-3Ziwfz-V.js} +2 -2
- package/dist/{html-to-image-CZY1pIZH.js → html-to-image-CWdayGZU.js} +35 -35
- package/dist/{infoDiagram-27XIBGKW-XrDxhr0x.js → infoDiagram-27XIBGKW-B1hmUggC.js} +1 -1
- package/dist/{ishikawaDiagram-5VMMS53U-CD1muzov.js → ishikawaDiagram-5VMMS53U-DUeT3t-d.js} +1 -1
- package/dist/{kanban-definition-UXKFOSKX-Dg9W142_.js → kanban-definition-UXKFOSKX-DEUnNNgc.js} +1 -1
- package/dist/{label-BDKF3Afc.js → label-BRnUC0NX.js} +2 -2
- package/dist/{linear-Cnaq3Tfb.js → linear-DWERh7eI.js} +121 -73
- package/dist/{loader-BQOfjTIJ.js → loader-Bm8cy3e8.js} +12 -12
- package/dist/main.js +3262 -3044
- package/dist/{mermaid-CiAXjLpA.js → mermaid-B0fXpsNP.js} +38 -38
- package/dist/mermaid-HWGCJPDP-Du3-HPQA.js +2 -0
- package/dist/{mindmap-definition-YA3MSWOX-DgCidgYJ.js → mindmap-definition-YA3MSWOX-CPexlhDQ.js} +1 -1
- package/dist/{number-overlay-editor-DunDxAzh.js → number-overlay-editor-CUrUm7Np.js} +3 -3
- package/dist/{objects-CzUgoMIF.js → objects-DCmMvZtn.js} +1 -3
- package/dist/{once-BtE-wKRL.js → once-eG8Vmvjd.js} +1 -1
- package/dist/{pegDiagram-XKGWAZYB-coOYYKB6.js → pegDiagram-XKGWAZYB-CMpp1OOQ.js} +2 -2
- package/dist/{pieDiagram-E7YTZNPT-BnXTc3bj.js → pieDiagram-E7YTZNPT-DlMWg5t1.js} +1 -1
- package/dist/precisionRound-DhAzeRcm.js +49 -0
- package/dist/{process-output-e4jj9Ijt.js → process-output-DD-oSvZk.js} +5 -5
- package/dist/{quadrantDiagram-AXDQQJYC-Ccw0xMjN.js → quadrantDiagram-AXDQQJYC-Blrm7V7_.js} +1 -1
- package/dist/{railroadDiagram-O6MQD6OU-B28-u83n.js → railroadDiagram-O6MQD6OU-DBJfseeO.js} +2 -2
- package/dist/{react-vega-BED0DjiB.js → react-vega-C4T9Rb_w.js} +12284 -12270
- package/dist/{requirementDiagram-BXWQKSXE-CdYCTMu0.js → requirementDiagram-BXWQKSXE-BC7V639r.js} +1 -1
- package/dist/{reveal-component-A7HedxmE.js → reveal-component-CFzdsOg8.js} +23 -23
- package/dist/{select-BButFi-d.js → select-B0LRDH0g.js} +4 -4
- package/dist/{sequenceDiagram-WJ2MYXX4-CM3aRAe_.js → sequenceDiagram-WJ2MYXX4-CgesU6F-.js} +1 -1
- package/dist/{spec-BwarBpUJ.js → spec-CcT0slVc.js} +4 -4
- package/dist/{stateDiagram-D77RDMKH-DOZUMVj8.js → stateDiagram-D77RDMKH-lH_BUhs3.js} +2 -2
- package/dist/{stateDiagram-v2-MP3YSRHH-CISYxma6.js → stateDiagram-v2-MP3YSRHH-8LL6FrBe.js} +2 -2
- package/dist/style.css +1 -1
- package/dist/{swimlanes-42K2YHIH-BkSppxp8.js → swimlanes-42K2YHIH-Cuf9B9kE.js} +1 -1
- package/dist/{swimlanesDiagram-VR7AAH4N-c8G5pGxC.js → swimlanesDiagram-VR7AAH4N-BoMMtOPh.js} +2 -2
- package/dist/{swiper-component-DKntyn8p.js → swiper-component-DfhRvAFi.js} +2 -2
- package/dist/{time-BieKwdYS.js → time-BaMbJs4z.js} +18 -18
- package/dist/{timeline-definition-24CTP7MA-CaMQayqK.js → timeline-definition-24CTP7MA-yzzw4TF4.js} +1 -1
- package/dist/{toDate-BNJf01u1.js → toDate-DKH0_kMo.js} +53 -54
- package/dist/{toast-B9UMEi8u.js → toast-CFWpihJU.js} +4 -4
- package/dist/{tooltip-BO1REbqs.js → tooltip-CKO1pHJo.js} +2 -2
- package/dist/{use-restore-focus-BEM7sFWl.js → use-restore-focus-DBCITLeF.js} +1 -1
- package/dist/{useAsyncData-Deh5kszT.js → useAsyncData-D0mLJstx.js} +15 -14
- package/dist/{useDateFormatter-D8XruNGG.js → useDateFormatter-c_yJtkAe.js} +2 -2
- package/dist/{useDeepCompareMemoize-DXwzRINI.js → useDeepCompareMemoize-BpPWi3G3.js} +1 -1
- package/dist/{useIframeCapabilities-DBETvvPl.js → useIframeCapabilities-BkdeVdXC.js} +1 -1
- package/dist/{useLifecycle-j7D9uD1o.js → useLifecycle-Dum8WSXq.js} +3 -3
- package/dist/{useTheme-CRnjkku0.js → useTheme-CfeaVw5D.js} +6 -6
- package/dist/{vega-component-DG29TAB7.js → vega-component-DgytCKrL.js} +147 -147
- package/dist/{vega-embed-container-BNHE18G9.js → vega-embed-container--LYZqguO.js} +2 -2
- package/dist/{vega-loader.browser-DNST6uVf.js → vega-loader.browser-B9Pz0EGP.js} +298 -507
- package/dist/{vennDiagram-4TSXK5OY-CF8pfaK3.js → vennDiagram-4TSXK5OY-eJOrTqqC.js} +1 -1
- package/dist/{wardleyDiagram-VM6X3IG4-dGPu39d-.js → wardleyDiagram-VM6X3IG4-TILPvee6.js} +1 -1
- package/dist/{xychartDiagram-S5SC5T6Z-CP6-9q56.js → xychartDiagram-S5SC5T6Z-DUPvRwwJ.js} +2 -2
- package/package.json +8 -5
- package/src/__tests__/fixtures/{pair-preview.ts → pair-config.ts} +3 -2
- package/src/__tests__/host-imports.test.ts +101 -0
- package/src/__tests__/mount.test.ts +8 -8
- package/src/components/app-config/ai-config.tsx +7 -2
- package/src/components/chat/__tests__/message-queue.test.tsx +17 -0
- package/src/components/chat/acp/__tests__/state.test.ts +2 -2
- package/src/components/chat/acp/blocks.tsx +2 -2
- package/src/components/chat/chat-components.tsx +2 -0
- package/src/components/chat/chat-panel.tsx +9 -8
- package/src/components/chat/chat-utils.ts +4 -8
- package/src/components/data-table/__tests__/column-explorer.test.tsx +27 -1
- package/src/components/data-table/__tests__/columns.test.tsx +105 -0
- package/src/components/data-table/__tests__/context-menu-interaction.test.tsx +130 -0
- package/src/components/data-table/__tests__/context-menu.test.ts +176 -0
- package/src/components/data-table/__tests__/date-filter-inputs.test.tsx +24 -1
- package/src/components/data-table/__tests__/date-popover.test.tsx +72 -0
- package/src/components/data-table/__tests__/export-actions.test.tsx +434 -6
- package/src/components/data-table/__tests__/search-highlight.test.tsx +109 -0
- package/src/components/data-table/__tests__/url-detector.test.tsx +74 -0
- package/src/components/data-table/cell-selection/feature.ts +2 -2
- package/src/components/data-table/cell-selection/types.ts +2 -2
- package/src/components/data-table/charts/__tests__/chart-panel.test.tsx +74 -1
- package/src/components/data-table/charts/__tests__/storage.test.ts +4 -1
- package/src/components/data-table/charts/charts.tsx +35 -25
- package/src/components/data-table/charts/storage.ts +16 -5
- package/src/components/data-table/column-explorer-panel/column-explorer.tsx +1 -1
- package/src/components/data-table/column-summary/column-summary.tsx +2 -4
- package/src/components/data-table/column-visibility-dropdown.tsx +8 -13
- package/src/components/data-table/columns.tsx +45 -46
- package/src/components/data-table/context-menu.tsx +161 -46
- package/src/components/data-table/data-table.tsx +7 -1
- package/src/components/data-table/date-filter-inputs.tsx +15 -42
- package/src/components/data-table/date-popover.tsx +6 -4
- package/src/components/data-table/export-actions.tsx +267 -163
- package/src/components/data-table/row-viewer-panel/row-viewer.tsx +2 -2
- package/src/components/data-table/schemas.ts +14 -36
- package/src/components/data-table/search-highlight.tsx +79 -0
- package/src/components/data-table/types.ts +1 -0
- package/src/components/data-table/url-detector.tsx +49 -9
- package/src/components/data-table/utils.ts +18 -0
- package/src/components/datasources/__tests__/column-actions.test.tsx +97 -0
- package/src/components/datasources/__tests__/column-preview.test.tsx +63 -2
- package/src/components/datasources/__tests__/table-list.test.tsx +118 -0
- package/src/components/datasources/column-preview.tsx +8 -2
- package/src/components/datasources/datasources.tsx +47 -35
- package/src/components/debugger/debugger-code.tsx +5 -0
- package/src/components/dependency-graph/panels.tsx +2 -1
- package/src/components/editor/Output.tsx +9 -2
- package/src/components/editor/SortableCell.tsx +1 -3
- package/src/components/editor/__tests__/dynamic-favicon.test.tsx +111 -18
- package/src/components/editor/actions/__tests__/pair-with-agent-commands.test.ts +80 -252
- package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +210 -34
- package/src/components/editor/actions/pair-with-agent-commands.ts +42 -51
- package/src/components/editor/actions/pair-with-agent-modal.tsx +100 -37
- package/src/components/editor/ai/add-cell-with-ai.tsx +3 -3
- package/src/components/editor/ai/ai-completion-editor.tsx +1 -0
- package/src/components/editor/alerts/startup-logs-alert.tsx +1 -0
- package/src/components/editor/cell/CreateCellButton.tsx +8 -10
- package/src/components/editor/cell/DeleteButton.tsx +1 -0
- package/src/components/editor/cell/__tests__/useShouldShowInterrupt.test.ts +39 -0
- package/src/components/editor/cell/code/__tests__/language-toggle.test.tsx +54 -0
- package/src/components/editor/cell/code/cell-editor.tsx +1 -1
- package/src/components/editor/cell/code/language-toggle.tsx +8 -7
- package/src/components/editor/cell/useShouldShowInterrupt.ts +2 -12
- package/src/components/editor/chrome/panels/__tests__/packages-panel.test.tsx +30 -1
- package/src/components/editor/chrome/panels/cache-panel.tsx +1 -0
- package/src/components/editor/chrome/panels/error-panel.tsx +1 -0
- package/src/components/editor/chrome/panels/packages-panel.tsx +4 -7
- package/src/components/editor/chrome/wrapper/__tests__/attachment-status.test.tsx +366 -0
- package/src/components/editor/chrome/wrapper/__tests__/pending-ai-cells.test.tsx +58 -1
- package/src/components/editor/chrome/wrapper/footer-item.tsx +32 -24
- package/src/components/editor/chrome/wrapper/footer-items/attachment-status.tsx +237 -0
- package/src/components/editor/chrome/wrapper/footer-items/runtime-settings.tsx +1 -7
- package/src/components/editor/chrome/wrapper/footer.tsx +2 -0
- package/src/components/editor/chrome/wrapper/pending-ai-cells.tsx +12 -2
- package/src/components/editor/chrome/wrapper/storage.ts +1 -1
- package/src/components/editor/connections/components.tsx +1 -1
- package/src/components/editor/controls/__tests__/command-palette.test.tsx +53 -0
- package/src/components/editor/controls/command-palette.tsx +22 -10
- package/src/components/editor/dynamic-favicon.tsx +21 -40
- package/src/components/editor/errors/__tests__/send-error-report-button.test.tsx +537 -0
- package/src/components/editor/errors/send-error-report-button.tsx +69 -0
- package/src/components/editor/file-tree/__tests__/file-explorer-interactions.test.tsx +6 -0
- package/src/components/editor/file-tree/__tests__/use-file-search.test.tsx +91 -0
- package/src/components/editor/file-tree/file-explorer.tsx +10 -5
- package/src/components/editor/file-tree/file-viewer.tsx +4 -2
- package/src/components/editor/file-tree/use-file-search.ts +35 -21
- package/src/components/editor/notebook-banner.tsx +1 -0
- package/src/components/editor/output/MarimoErrorOutput.tsx +21 -5
- package/src/components/editor/output/MarimoTracebackOutput.tsx +37 -22
- package/src/components/editor/output/__tests__/ansi-reduce.test.ts +18 -0
- package/src/components/editor/output/ansi-reduce.ts +12 -0
- package/src/components/editor/output/console/ConsoleOutput.tsx +19 -25
- package/src/components/editor/package-alert.tsx +6 -4
- package/src/components/editor/renderers/vertical-layout/sidebar/toggle.tsx +7 -1
- package/src/components/home/components.tsx +17 -31
- package/src/components/icons/__tests__/large-spinner.test.tsx +39 -0
- package/src/components/icons/large-spinner.tsx +3 -14
- package/src/components/icons/spinner.tsx +1 -1
- package/src/components/mcp/mcp-status-indicator.tsx +1 -1
- package/src/components/pages/__tests__/home-page.test.tsx +116 -0
- package/src/components/pages/home-page.tsx +164 -91
- package/src/components/scratchpad/scratchpad.tsx +2 -2
- package/src/components/slides/reveal-component.tsx +4 -2
- package/src/components/slides/slide-cell-view.tsx +1 -1
- package/src/components/slides/slide-form.tsx +1 -0
- package/src/components/tracing/tracing.tsx +2 -2
- package/src/components/ui/alert-dialog.tsx +1 -1
- package/src/core/ai/context/providers/datasource.ts +6 -2
- package/src/core/attachments/__tests__/toast.test.tsx +58 -0
- package/src/core/attachments/state.ts +18 -0
- package/src/core/attachments/toast.ts +15 -0
- package/src/core/cells/__tests__/__snapshots__/cells.test.ts.snap +4 -0
- package/src/core/cells/__tests__/cell.test.ts +39 -0
- package/src/core/cells/__tests__/cells.test.ts +55 -1
- package/src/core/cells/__tests__/collapseConsoleOutputs.test.ts +54 -0
- package/src/core/cells/__tests__/error-report.test.ts +139 -0
- package/src/core/cells/__tests__/notebook-outline.test.ts +113 -0
- package/src/core/cells/__tests__/session.test.ts +50 -1
- package/src/core/cells/cell.ts +14 -3
- package/src/core/cells/cells.ts +3 -1
- package/src/core/cells/error-report.ts +63 -0
- package/src/core/cells/pending-delete-service.ts +2 -6
- package/src/core/cells/session.ts +29 -2
- package/src/core/cells/types.ts +10 -1
- package/src/core/codemirror/__tests__/setup.test.ts +1 -1
- package/src/core/codemirror/keymaps/vim.ts +2 -6
- package/src/core/codemirror/language/languages/markdown.ts +1 -1
- package/src/core/codemirror/language/languages/python.ts +3 -3
- package/src/core/codemirror/language/panel/panel.tsx +1 -0
- package/src/core/config/pair.ts +5 -3
- package/src/core/datasets/__tests__/data-source-discovery.test.ts +4 -4
- package/src/core/datasets/data-source-discovery.ts +4 -6
- package/src/core/document/__tests__/draft.test.ts +322 -0
- package/src/core/document/__tests__/fixtures.ts +92 -0
- package/src/core/document/__tests__/model.test.ts +483 -0
- package/src/core/document/__tests__/state.test.ts +446 -0
- package/src/core/document/__tests__/submission.test.ts +346 -0
- package/src/core/document/draft.ts +472 -0
- package/src/core/document/index.ts +10 -0
- package/src/core/document/model.ts +53 -0
- package/src/core/document/state.ts +401 -0
- package/src/core/document/sync.ts +359 -0
- package/src/core/edit-app.tsx +2 -2
- package/src/core/host/AGENTS.md +16 -0
- package/src/core/host/__tests__/attachments.test.ts +56 -0
- package/src/core/host/__tests__/document.test.ts +126 -0
- package/src/core/host/__tests__/fixtures.ts +41 -0
- package/src/core/host/__tests__/notebook-client.test.ts +570 -0
- package/src/core/host/connection.ts +42 -0
- package/src/core/host/notebook-client.ts +297 -0
- package/src/core/host/resources/attachments.ts +22 -0
- package/src/core/host/resources/document.ts +101 -0
- package/src/core/hotkeys/actions.ts +2 -4
- package/src/core/kernel/__tests__/RuntimeState.test.ts +1 -1
- package/src/core/network/connection.ts +4 -2
- package/src/core/network/useConnectionNotice.ts +8 -2
- package/src/core/run-app.tsx +2 -2
- package/src/core/runtime/__tests__/createWsUrl.test.ts +77 -7
- package/src/core/runtime/__tests__/runtime.test.ts +18 -17
- package/src/core/runtime/runtime.ts +26 -30
- package/src/core/state/__tests__/jotai.test.tsx +108 -0
- package/src/core/state/jotai.ts +1 -1
- package/src/core/static/files.ts +2 -0
- package/src/core/wasm/bridge.ts +2 -10
- package/src/core/wasm/store.ts +3 -3
- package/src/core/wasm/worker/bootstrap.ts +1 -1
- package/src/core/wasm/worker/getMarimoWheel.ts +1 -1
- package/src/core/websocket/__tests__/{useMarimoKernelConnection.test.ts → connection-state.test.ts} +15 -29
- package/src/core/websocket/__tests__/useNotebookClient.hook.test.tsx +223 -0
- package/src/core/websocket/__tests__/{useMarimoKernelConnection.hook.test.tsx → useNotebookSession.hook.test.tsx} +194 -184
- package/src/core/websocket/connection-state.ts +60 -0
- package/src/core/websocket/state.ts +7 -0
- package/src/core/websocket/transports/__tests__/notebook.test.ts +502 -0
- package/src/core/websocket/transports/notebook.ts +213 -0
- package/src/core/websocket/types.ts +9 -39
- package/src/core/websocket/useNotebookClient.ts +89 -0
- package/src/core/websocket/{useMarimoKernelConnection.tsx → useNotebookSession.tsx} +53 -224
- package/src/css/globals.css +5 -0
- package/src/hooks/__tests__/useAsyncData.test.tsx +148 -0
- package/src/hooks/__tests__/useDebounce.test.tsx +51 -0
- package/src/hooks/__tests__/useDeepCompareMemoize.test.tsx +39 -0
- package/src/hooks/__tests__/useDelayElapsed.test.tsx +28 -0
- package/src/hooks/__tests__/useInternalStateWithSync.test.tsx +20 -0
- package/src/hooks/__tests__/useScript.test.tsx +77 -0
- package/src/hooks/__tests__/useStateForKey.test.tsx +257 -0
- package/src/hooks/useAsyncData.ts +3 -3
- package/src/hooks/useDataSourceDiscovery.ts +6 -4
- package/src/hooks/useDebounce.ts +5 -8
- package/src/hooks/useDeepCompareMemoize.ts +1 -1
- package/src/hooks/useDelayElapsed.ts +14 -10
- package/src/hooks/useElapsedTime.ts +3 -3
- package/src/hooks/useInputHistory.ts +4 -7
- package/src/hooks/useInternalStateWithSync.ts +6 -6
- package/src/hooks/useResizeObserver.ts +4 -7
- package/src/hooks/useScript.ts +67 -70
- package/src/hooks/useStateForKey.ts +93 -0
- package/src/mount.tsx +3 -3
- package/src/plugins/core/registerReactComponent.tsx +1 -1
- package/src/plugins/impl/CodeEditorPlugin.tsx +1 -1
- package/src/plugins/impl/DataTablePlugin.tsx +14 -15
- package/src/plugins/impl/DropdownPlugin.tsx +2 -6
- package/src/plugins/impl/FileBrowserPlugin.tsx +4 -16
- package/src/plugins/impl/FormPlugin.tsx +1 -1
- package/src/plugins/impl/RangeSliderPlugin.tsx +0 -1
- package/src/plugins/impl/RefreshPlugin.tsx +1 -0
- package/src/plugins/impl/SliderPlugin.tsx +6 -8
- package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +201 -1
- package/src/plugins/impl/__tests__/FileBrowserPlugin.test.tsx +45 -0
- package/src/plugins/impl/__tests__/SliderPlugin.test.tsx +24 -3
- package/src/plugins/impl/anywidget/__tests__/registry.test.ts +2 -0
- package/src/plugins/impl/anywidget/__tests__/widget-binding.test.ts +241 -2
- package/src/plugins/impl/anywidget/runtime.ts +21 -9
- package/src/plugins/impl/anywidget/widget-binding.ts +160 -82
- package/src/plugins/impl/chat/__tests__/chat-prompts.test.tsx +93 -0
- package/src/plugins/impl/chat/chat-ui.tsx +12 -10
- package/src/plugins/impl/code/any-language-editor.tsx +2 -2
- package/src/plugins/impl/data-editor/glide-data-editor.tsx +8 -5
- package/src/plugins/impl/data-editor/glide-utils.ts +3 -1
- package/src/plugins/impl/data-explorer/ConnectedDataExplorerComponent.tsx +1 -1
- package/src/plugins/impl/data-explorer/state/reducer.ts +1 -1
- package/src/plugins/impl/data-frames/DataFramePlugin.tsx +0 -1
- package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +5 -3
- package/src/plugins/impl/panel/PanelPlugin.tsx +16 -17
- package/src/plugins/impl/panel/__tests__/utils.test.ts +39 -1
- package/src/plugins/impl/panel/utils.ts +17 -0
- package/src/plugins/impl/vega/make-selectable.ts +4 -7
- package/src/plugins/layout/RoutesPlugin.tsx +1 -1
- package/src/stories/__tests__/editor-story.test.tsx +33 -0
- package/src/stories/button.stories.tsx +1 -0
- package/src/stories/editor.stories.tsx +24 -17
- package/src/stories/form-wrapper.stories.tsx +2 -2
- package/src/stories/select.stories.tsx +2 -2
- package/src/stories/switchable-multi-select.stories.tsx +1 -1
- package/src/utils/__tests__/arrays.test.ts +35 -0
- package/src/utils/__tests__/batch-requests.test.ts +3 -3
- package/src/utils/__tests__/dates.test.ts +116 -0
- package/src/utils/__tests__/numbers.test.ts +48 -1
- package/src/utils/__tests__/objects.test.ts +21 -0
- package/src/utils/arrays.ts +10 -0
- package/src/utils/blob.ts +1 -1
- package/src/utils/dates.ts +94 -0
- package/src/utils/numbers.ts +28 -17
- package/src/utils/objects.ts +3 -7
- package/src/utils/once.ts +19 -19
- package/src/utils/zod-utils.ts +1 -3
- package/dist/any-language-editor-B9REGlD9.js +0 -76
- package/dist/assets/__vite-browser-external-BXYkiFsk.js +0 -1
- package/dist/error-entries-CzoYkIQZ.js +0 -56
- package/dist/mermaid-HWGCJPDP-ByCsZnYF.js +0 -2
- package/dist/precisionRound-DMIVQrt2.js +0 -97
- package/src/core/state/__tests__/jotai.test.ts +0 -25
- package/src/core/websocket/__tests__/useWebSocket.test.ts +0 -49
- package/src/core/websocket/transports/__tests__/basic.test.ts +0 -275
- package/src/core/websocket/transports/__tests__/sse.test.ts +0 -338
- package/src/core/websocket/transports/__tests__/transport.test.ts +0 -308
- package/src/core/websocket/transports/__tests__/ws.test.ts +0 -125
- package/src/core/websocket/transports/basic.ts +0 -102
- package/src/core/websocket/transports/sse.ts +0 -308
- package/src/core/websocket/transports/transport.ts +0 -61
- package/src/core/websocket/transports/ws.ts +0 -98
- package/src/core/websocket/useWebSocket.tsx +0 -88
|
@@ -54,6 +54,7 @@ function renderExportActions(
|
|
|
54
54
|
props: Partial<ExportActionProps> = {},
|
|
55
55
|
sizeLimit: { limitBytes: number; unavailableMessage: string } | null = null,
|
|
56
56
|
mode: AppMode = "edit",
|
|
57
|
+
strictMode = false,
|
|
57
58
|
) {
|
|
58
59
|
const store = createStore();
|
|
59
60
|
store.set(downloadSizeLimitAtom, sizeLimit);
|
|
@@ -66,7 +67,7 @@ function renderExportActions(
|
|
|
66
67
|
</TooltipProvider>
|
|
67
68
|
</Provider>
|
|
68
69
|
);
|
|
69
|
-
const result = render(view(props));
|
|
70
|
+
const result = render(view(props), { reactStrictMode: strictMode });
|
|
70
71
|
return {
|
|
71
72
|
...result,
|
|
72
73
|
rerenderExportActions: (nextProps: Partial<ExportActionProps>) =>
|
|
@@ -284,7 +285,7 @@ describe("ExportActions dialog", () => {
|
|
|
284
285
|
await waitFor(() => {
|
|
285
286
|
expect(downloadByURL).toHaveBeenCalled();
|
|
286
287
|
});
|
|
287
|
-
expect(getExportMetadata).toHaveBeenCalledTimes(
|
|
288
|
+
expect(getExportMetadata).toHaveBeenCalledTimes(3);
|
|
288
289
|
expect(
|
|
289
290
|
screen.queryByText("GeoParquet export requires pyarrow."),
|
|
290
291
|
).not.toBeInTheDocument();
|
|
@@ -310,10 +311,10 @@ describe("ExportActions dialog", () => {
|
|
|
310
311
|
mocks.handleInstallPackages.mock.calls[0][1]();
|
|
311
312
|
});
|
|
312
313
|
await waitFor(() => {
|
|
313
|
-
expect(getExportMetadata).toHaveBeenCalledTimes(
|
|
314
|
+
expect(getExportMetadata).toHaveBeenCalledTimes(3);
|
|
314
315
|
});
|
|
315
316
|
expect(
|
|
316
|
-
screen.
|
|
317
|
+
await screen.findByRole("button", { name: "Install pyarrow" }),
|
|
317
318
|
).toBeInTheDocument();
|
|
318
319
|
expect(
|
|
319
320
|
screen.getByRole("button", { name: "Download GeoParquet" }),
|
|
@@ -530,6 +531,345 @@ describe("ExportActions dialog", () => {
|
|
|
530
531
|
expect(downloadByURL).not.toHaveBeenCalled();
|
|
531
532
|
});
|
|
532
533
|
|
|
534
|
+
it("does not revive an earlier source failure when switching back", async () => {
|
|
535
|
+
downloadAs.mockRejectedValueOnce(new Error("First source failed"));
|
|
536
|
+
const { rerenderExportActions } = renderExportActions({
|
|
537
|
+
metadataSource: "first",
|
|
538
|
+
});
|
|
539
|
+
await openDialog();
|
|
540
|
+
fireEvent.click(screen.getByRole("button", { name: "Download CSV" }));
|
|
541
|
+
expect(await screen.findByRole("alert")).toHaveTextContent(
|
|
542
|
+
"First source failed",
|
|
543
|
+
);
|
|
544
|
+
|
|
545
|
+
rerenderExportActions({ metadataSource: "second" });
|
|
546
|
+
expect(screen.queryByRole("alert")).not.toBeInTheDocument();
|
|
547
|
+
rerenderExportActions({ metadataSource: "first" });
|
|
548
|
+
expect(screen.queryByRole("alert")).not.toBeInTheDocument();
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
async function startGeoParquetInstall(strictMode = false) {
|
|
552
|
+
const getExportMetadata = vi.fn().mockResolvedValue(geometryMetadata);
|
|
553
|
+
downloadAs.mockResolvedValueOnce({
|
|
554
|
+
url: "",
|
|
555
|
+
filename: "",
|
|
556
|
+
error: "GeoParquet export requires pyarrow.",
|
|
557
|
+
missing_packages: ["pyarrow"],
|
|
558
|
+
});
|
|
559
|
+
const { rerenderExportActions, unmount } = renderExportActions(
|
|
560
|
+
{ getExportMetadata, metadataSource: "first" },
|
|
561
|
+
null,
|
|
562
|
+
"edit",
|
|
563
|
+
strictMode,
|
|
564
|
+
);
|
|
565
|
+
const dialog = await openDialog();
|
|
566
|
+
fireEvent.click(
|
|
567
|
+
await screen.findByRole("button", { name: "Download GeoParquet" }),
|
|
568
|
+
);
|
|
569
|
+
fireEvent.click(
|
|
570
|
+
await screen.findByRole("button", { name: "Install pyarrow" }),
|
|
571
|
+
);
|
|
572
|
+
return { dialog, getExportMetadata, rerenderExportActions, unmount };
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
it("does not retry with a geometry choice after the final geometry disappears", async () => {
|
|
576
|
+
const { getExportMetadata } = await startGeoParquetInstall();
|
|
577
|
+
getExportMetadata.mockResolvedValue({
|
|
578
|
+
geometry_columns: [],
|
|
579
|
+
primary_geometry_column: null,
|
|
580
|
+
default_geometry_column: null,
|
|
581
|
+
formats: {},
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
await act(async () => mocks.handleInstallPackages.mock.calls[0][1]());
|
|
585
|
+
|
|
586
|
+
expect(downloadAs).toHaveBeenCalledTimes(1);
|
|
587
|
+
expect(downloadByURL).not.toHaveBeenCalled();
|
|
588
|
+
expect(
|
|
589
|
+
await screen.findByText(
|
|
590
|
+
"The selected geometry is no longer available. Choose another geometry to export.",
|
|
591
|
+
),
|
|
592
|
+
).toBeInTheDocument();
|
|
593
|
+
fireEvent.click(screen.getByRole("button", { name: "Download Parquet" }));
|
|
594
|
+
await waitFor(() => expect(downloadByURL).toHaveBeenCalledTimes(1));
|
|
595
|
+
expect(downloadAs).toHaveBeenLastCalledWith({ format: "parquet" });
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
it("does not export when installation finishes after the widget unmounts", async () => {
|
|
599
|
+
const { getExportMetadata, unmount } = await startGeoParquetInstall();
|
|
600
|
+
unmount();
|
|
601
|
+
|
|
602
|
+
await act(async () => mocks.handleInstallPackages.mock.calls[0][1]());
|
|
603
|
+
|
|
604
|
+
expect(getExportMetadata).toHaveBeenCalledTimes(1);
|
|
605
|
+
expect(downloadAs).toHaveBeenCalledTimes(1);
|
|
606
|
+
expect(downloadByURL).not.toHaveBeenCalled();
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
it("does not export when post-install metadata resolves after unmount", async () => {
|
|
610
|
+
const { getExportMetadata, unmount } = await startGeoParquetInstall();
|
|
611
|
+
let resolveCheck: (value: ExportMetadata) => void = () => undefined;
|
|
612
|
+
getExportMetadata.mockImplementationOnce(
|
|
613
|
+
() =>
|
|
614
|
+
new Promise<ExportMetadata>((resolve) => {
|
|
615
|
+
resolveCheck = resolve;
|
|
616
|
+
}),
|
|
617
|
+
);
|
|
618
|
+
await act(async () => mocks.handleInstallPackages.mock.calls[0][1]());
|
|
619
|
+
expect(getExportMetadata).toHaveBeenCalledTimes(2);
|
|
620
|
+
unmount();
|
|
621
|
+
|
|
622
|
+
await act(async () => resolveCheck(geometryMetadata));
|
|
623
|
+
|
|
624
|
+
expect(downloadAs).toHaveBeenCalledTimes(1);
|
|
625
|
+
expect(downloadByURL).not.toHaveBeenCalled();
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
it("does not deliver a retried export that resolves after unmount", async () => {
|
|
629
|
+
const { unmount } = await startGeoParquetInstall();
|
|
630
|
+
let resolveDownload: (value: {
|
|
631
|
+
url: string;
|
|
632
|
+
filename: string;
|
|
633
|
+
}) => void = () => undefined;
|
|
634
|
+
downloadAs.mockImplementationOnce(
|
|
635
|
+
() =>
|
|
636
|
+
new Promise((resolve) => {
|
|
637
|
+
resolveDownload = resolve;
|
|
638
|
+
}),
|
|
639
|
+
);
|
|
640
|
+
await act(async () => mocks.handleInstallPackages.mock.calls[0][1]());
|
|
641
|
+
expect(downloadAs).toHaveBeenCalledTimes(2);
|
|
642
|
+
unmount();
|
|
643
|
+
|
|
644
|
+
await act(async () =>
|
|
645
|
+
resolveDownload({
|
|
646
|
+
url: "https://example.test/export",
|
|
647
|
+
filename: "table",
|
|
648
|
+
}),
|
|
649
|
+
);
|
|
650
|
+
|
|
651
|
+
expect(downloadByURL).not.toHaveBeenCalled();
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
it("retries after installation under StrictMode effect replay", async () => {
|
|
655
|
+
await startGeoParquetInstall(true);
|
|
656
|
+
|
|
657
|
+
await act(async () => mocks.handleInstallPackages.mock.calls[0][1]());
|
|
658
|
+
|
|
659
|
+
await waitFor(() => expect(downloadByURL).toHaveBeenCalledTimes(1));
|
|
660
|
+
expect(downloadAs).toHaveBeenLastCalledWith({
|
|
661
|
+
format: "parquet",
|
|
662
|
+
geometry_column: "location",
|
|
663
|
+
});
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
it("does not export when installation finishes after the dialog closes", async () => {
|
|
667
|
+
const { dialog, getExportMetadata } = await startGeoParquetInstall();
|
|
668
|
+
fireEvent.click(within(dialog).getByRole("button", { name: "Close" }));
|
|
669
|
+
await waitFor(() => {
|
|
670
|
+
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
await act(async () => mocks.handleInstallPackages.mock.calls[0][1]());
|
|
674
|
+
|
|
675
|
+
expect(getExportMetadata).toHaveBeenCalledTimes(1);
|
|
676
|
+
expect(downloadAs).toHaveBeenCalledTimes(1);
|
|
677
|
+
expect(downloadByURL).not.toHaveBeenCalled();
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
it("does not export when installation finishes after the source changes", async () => {
|
|
681
|
+
const { getExportMetadata, rerenderExportActions } =
|
|
682
|
+
await startGeoParquetInstall();
|
|
683
|
+
rerenderExportActions({ getExportMetadata, metadataSource: "second" });
|
|
684
|
+
await waitFor(() => {
|
|
685
|
+
expect(getExportMetadata).toHaveBeenCalledTimes(2);
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
await act(async () => mocks.handleInstallPackages.mock.calls[0][1]());
|
|
689
|
+
|
|
690
|
+
expect(getExportMetadata).toHaveBeenCalledTimes(2);
|
|
691
|
+
expect(downloadAs).toHaveBeenCalledTimes(1);
|
|
692
|
+
expect(downloadByURL).not.toHaveBeenCalled();
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
it("does not export a non-geographic retry after the dialog closes", async () => {
|
|
696
|
+
downloadAs.mockResolvedValueOnce({
|
|
697
|
+
url: "",
|
|
698
|
+
filename: "",
|
|
699
|
+
error: "Parquet export requires pyarrow.",
|
|
700
|
+
missing_packages: ["pyarrow"],
|
|
701
|
+
});
|
|
702
|
+
renderExportActions();
|
|
703
|
+
const dialog = await openDialog();
|
|
704
|
+
fireEvent.click(screen.getByRole("button", { name: "Download Parquet" }));
|
|
705
|
+
fireEvent.click(
|
|
706
|
+
await screen.findByRole("button", { name: "Install pyarrow" }),
|
|
707
|
+
);
|
|
708
|
+
fireEvent.click(within(dialog).getByRole("button", { name: "Close" }));
|
|
709
|
+
|
|
710
|
+
await act(async () => mocks.handleInstallPackages.mock.calls[0][1]());
|
|
711
|
+
|
|
712
|
+
expect(downloadAs).toHaveBeenCalledTimes(1);
|
|
713
|
+
expect(downloadByURL).not.toHaveBeenCalled();
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
it("retries after installation when an earlier export succeeded", async () => {
|
|
717
|
+
const getExportMetadata = vi.fn().mockResolvedValue({
|
|
718
|
+
...geometryMetadata,
|
|
719
|
+
formats: {
|
|
720
|
+
parquet: {
|
|
721
|
+
available: false,
|
|
722
|
+
reason: "GeoParquet export requires pyarrow.",
|
|
723
|
+
missing_packages: ["pyarrow"],
|
|
724
|
+
},
|
|
725
|
+
},
|
|
726
|
+
});
|
|
727
|
+
renderExportActions({ getExportMetadata });
|
|
728
|
+
await openDialog();
|
|
729
|
+
fireEvent.click(screen.getByRole("button", { name: "Download CSV" }));
|
|
730
|
+
await waitFor(() => expect(downloadByURL).toHaveBeenCalledTimes(1));
|
|
731
|
+
fireEvent.click(
|
|
732
|
+
await screen.findByRole("button", { name: "Install pyarrow" }),
|
|
733
|
+
);
|
|
734
|
+
getExportMetadata.mockResolvedValue(geometryMetadata);
|
|
735
|
+
|
|
736
|
+
await act(async () => mocks.handleInstallPackages.mock.calls[0][1]());
|
|
737
|
+
|
|
738
|
+
await waitFor(() => expect(downloadByURL).toHaveBeenCalledTimes(2));
|
|
739
|
+
expect(downloadAs).toHaveBeenLastCalledWith({
|
|
740
|
+
format: "parquet",
|
|
741
|
+
geometry_column: "location",
|
|
742
|
+
});
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
it("waits for the post-install metadata check before exporting", async () => {
|
|
746
|
+
let resolveCheck: (value: ExportMetadata) => void = () => undefined;
|
|
747
|
+
const getExportMetadata = vi.fn().mockResolvedValue({
|
|
748
|
+
...geometryMetadata,
|
|
749
|
+
formats: {
|
|
750
|
+
parquet: {
|
|
751
|
+
available: false,
|
|
752
|
+
reason: "GeoParquet export requires pyarrow.",
|
|
753
|
+
missing_packages: ["pyarrow"],
|
|
754
|
+
},
|
|
755
|
+
},
|
|
756
|
+
});
|
|
757
|
+
renderExportActions({ getExportMetadata });
|
|
758
|
+
await openDialog();
|
|
759
|
+
fireEvent.click(
|
|
760
|
+
await screen.findByRole("button", { name: "Install pyarrow" }),
|
|
761
|
+
);
|
|
762
|
+
getExportMetadata.mockImplementationOnce(
|
|
763
|
+
() =>
|
|
764
|
+
new Promise((resolve) => {
|
|
765
|
+
resolveCheck = resolve;
|
|
766
|
+
}),
|
|
767
|
+
);
|
|
768
|
+
|
|
769
|
+
await act(async () => mocks.handleInstallPackages.mock.calls[0][1]());
|
|
770
|
+
expect(downloadAs).not.toHaveBeenCalled();
|
|
771
|
+
|
|
772
|
+
getExportMetadata.mockResolvedValue(geometryMetadata);
|
|
773
|
+
await act(async () => resolveCheck(geometryMetadata));
|
|
774
|
+
await waitFor(() => expect(downloadByURL).toHaveBeenCalled());
|
|
775
|
+
expect(downloadAs).toHaveBeenCalledTimes(1);
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
it("uses the new source eligibility after a settled package failure", async () => {
|
|
779
|
+
const getExportMetadata = vi.fn().mockResolvedValue(geometryMetadata);
|
|
780
|
+
downloadAs.mockResolvedValueOnce({
|
|
781
|
+
url: "",
|
|
782
|
+
filename: "",
|
|
783
|
+
error: "GeoParquet export requires pyarrow.",
|
|
784
|
+
missing_packages: ["pyarrow"],
|
|
785
|
+
});
|
|
786
|
+
const { rerenderExportActions } = renderExportActions({
|
|
787
|
+
getExportMetadata,
|
|
788
|
+
metadataSource: "first",
|
|
789
|
+
});
|
|
790
|
+
await openDialog();
|
|
791
|
+
fireEvent.click(
|
|
792
|
+
await screen.findByRole("button", { name: "Download GeoParquet" }),
|
|
793
|
+
);
|
|
794
|
+
await screen.findByRole("button", { name: "Install pyarrow" });
|
|
795
|
+
|
|
796
|
+
rerenderExportActions({ getExportMetadata, metadataSource: "second" });
|
|
797
|
+
|
|
798
|
+
await waitFor(() =>
|
|
799
|
+
expect(
|
|
800
|
+
screen.getByRole("button", { name: "Download GeoParquet" }),
|
|
801
|
+
).toBeEnabled(),
|
|
802
|
+
);
|
|
803
|
+
expect(
|
|
804
|
+
screen.queryByRole("button", { name: "Install pyarrow" }),
|
|
805
|
+
).not.toBeInTheDocument();
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
it("ignores metadata for an earlier source that resolves late", async () => {
|
|
809
|
+
let resolveFirst: (value: ExportMetadata) => void = () => undefined;
|
|
810
|
+
const first = vi.fn(
|
|
811
|
+
() =>
|
|
812
|
+
new Promise<ExportMetadata>((resolve) => {
|
|
813
|
+
resolveFirst = resolve;
|
|
814
|
+
}),
|
|
815
|
+
);
|
|
816
|
+
const second = vi.fn().mockResolvedValue({
|
|
817
|
+
...geometryMetadata,
|
|
818
|
+
geometry_columns: [
|
|
819
|
+
{ name: "region", encoding: "objects", crs: "EPSG:3857" },
|
|
820
|
+
],
|
|
821
|
+
primary_geometry_column: "region",
|
|
822
|
+
default_geometry_column: "region",
|
|
823
|
+
});
|
|
824
|
+
const { rerenderExportActions } = renderExportActions({
|
|
825
|
+
getExportMetadata: first,
|
|
826
|
+
metadataSource: "first",
|
|
827
|
+
});
|
|
828
|
+
await openDialog();
|
|
829
|
+
rerenderExportActions({
|
|
830
|
+
getExportMetadata: second,
|
|
831
|
+
metadataSource: "second",
|
|
832
|
+
});
|
|
833
|
+
await waitFor(() =>
|
|
834
|
+
expect(screen.getByTestId("export-summary-parquet")).toHaveTextContent(
|
|
835
|
+
"region",
|
|
836
|
+
),
|
|
837
|
+
);
|
|
838
|
+
|
|
839
|
+
await act(async () => resolveFirst(geometryMetadata));
|
|
840
|
+
|
|
841
|
+
expect(screen.getByTestId("export-summary-parquet")).toHaveTextContent(
|
|
842
|
+
"region",
|
|
843
|
+
);
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
it("does not use earlier metadata when the new source request fails", async () => {
|
|
847
|
+
const getExportMetadata = vi.fn().mockResolvedValue(geometryMetadata);
|
|
848
|
+
const { rerenderExportActions } = renderExportActions({
|
|
849
|
+
getExportMetadata,
|
|
850
|
+
metadataSource: "first",
|
|
851
|
+
});
|
|
852
|
+
await openDialog();
|
|
853
|
+
await waitFor(() =>
|
|
854
|
+
expect(
|
|
855
|
+
screen.getByRole("button", { name: "Download GeoParquet" }),
|
|
856
|
+
).toBeEnabled(),
|
|
857
|
+
);
|
|
858
|
+
getExportMetadata.mockRejectedValueOnce(new Error("Connection lost"));
|
|
859
|
+
|
|
860
|
+
rerenderExportActions({ getExportMetadata, metadataSource: "second" });
|
|
861
|
+
|
|
862
|
+
expect(
|
|
863
|
+
await screen.findByText(
|
|
864
|
+
"Could not load geometry options: Connection lost",
|
|
865
|
+
),
|
|
866
|
+
).toBeInTheDocument();
|
|
867
|
+
expect(
|
|
868
|
+
screen.getByRole("button", { name: "Download Parquet" }),
|
|
869
|
+
).toBeDisabled();
|
|
870
|
+
expect(screen.getByRole("button", { name: "Download CSV" })).toBeEnabled();
|
|
871
|
+
});
|
|
872
|
+
|
|
533
873
|
it("shows formats in the approved order with an options toggle where supported", async () => {
|
|
534
874
|
renderExportActions();
|
|
535
875
|
const dialog = await openDialog();
|
|
@@ -591,6 +931,94 @@ describe("ExportActions dialog", () => {
|
|
|
591
931
|
expect(within(dialog).queryByRole("group")).not.toBeInTheDocument();
|
|
592
932
|
});
|
|
593
933
|
|
|
934
|
+
it("describes format options with their description and current settings", async () => {
|
|
935
|
+
renderExportActions({
|
|
936
|
+
getExportMetadata: vi.fn().mockResolvedValue(geometryMetadata),
|
|
937
|
+
});
|
|
938
|
+
await openDialog();
|
|
939
|
+
await screen.findByRole("button", { name: "GeoParquet options" });
|
|
940
|
+
|
|
941
|
+
for (const [label, description] of [
|
|
942
|
+
["CSV", "Comma-separated values Comma · UTF-8"],
|
|
943
|
+
["TSV", "Best for Excel and Google Sheets UTF-8"],
|
|
944
|
+
["JSON", "Raw JSON data Escapes non-ASCII"],
|
|
945
|
+
["GeoParquet", "Parquet with geometry and CRS metadata location"],
|
|
946
|
+
["GeoJSON", "Geometry in longitude and latitude location"],
|
|
947
|
+
]) {
|
|
948
|
+
expect(
|
|
949
|
+
screen.getByRole("button", { name: `${label} options` }),
|
|
950
|
+
).toHaveAccessibleDescription(description);
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
const csvOptions = screen.getByRole("button", { name: "CSV options" });
|
|
954
|
+
fireEvent.click(csvOptions);
|
|
955
|
+
fireEvent.change(screen.getByRole("combobox", { name: "Delimiter" }), {
|
|
956
|
+
target: { value: ";" },
|
|
957
|
+
});
|
|
958
|
+
expect(csvOptions).toHaveAccessibleDescription(
|
|
959
|
+
"Comma-separated values Semicolon · UTF-8",
|
|
960
|
+
);
|
|
961
|
+
fireEvent.click(csvOptions);
|
|
962
|
+
expect(csvOptions).toHaveAccessibleDescription(
|
|
963
|
+
"Comma-separated values Semicolon · UTF-8",
|
|
964
|
+
);
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
it.each([
|
|
968
|
+
["CSV", "Comma-separated values"],
|
|
969
|
+
["TSV", "Best for Excel and Google Sheets"],
|
|
970
|
+
["JSON", "Raw JSON data"],
|
|
971
|
+
["GeoParquet", "Parquet with geometry and CRS metadata"],
|
|
972
|
+
["GeoJSON", "Geometry in longitude and latitude"],
|
|
973
|
+
])(
|
|
974
|
+
"toggles %s options from the row label and description",
|
|
975
|
+
async (label, description) => {
|
|
976
|
+
renderExportActions({
|
|
977
|
+
getExportMetadata: vi.fn().mockResolvedValue(geometryMetadata),
|
|
978
|
+
});
|
|
979
|
+
await openDialog();
|
|
980
|
+
const toggle = await screen.findByRole("button", {
|
|
981
|
+
name: `${label} options`,
|
|
982
|
+
});
|
|
983
|
+
|
|
984
|
+
fireEvent.click(screen.getByText(label));
|
|
985
|
+
expect(toggle).toHaveAttribute("aria-expanded", "true");
|
|
986
|
+
expect(
|
|
987
|
+
screen.getByRole("group", { name: `${label} options` }),
|
|
988
|
+
).toBeInTheDocument();
|
|
989
|
+
|
|
990
|
+
fireEvent.click(screen.getByText(description));
|
|
991
|
+
expect(toggle).toHaveAttribute("aria-expanded", "false");
|
|
992
|
+
expect(
|
|
993
|
+
screen.queryByRole("group", { name: `${label} options` }),
|
|
994
|
+
).not.toBeInTheDocument();
|
|
995
|
+
},
|
|
996
|
+
);
|
|
997
|
+
|
|
998
|
+
it("keeps download and copy independent of row expansion", async () => {
|
|
999
|
+
vi.stubGlobal(
|
|
1000
|
+
"fetch",
|
|
1001
|
+
vi.fn().mockResolvedValue({
|
|
1002
|
+
ok: true,
|
|
1003
|
+
text: vi.fn().mockResolvedValue("name,value\n"),
|
|
1004
|
+
}),
|
|
1005
|
+
);
|
|
1006
|
+
renderExportActions();
|
|
1007
|
+
await openDialog();
|
|
1008
|
+
const toggle = screen.getByRole("button", { name: "CSV options" });
|
|
1009
|
+
|
|
1010
|
+
fireEvent.click(screen.getByRole("button", { name: "Download CSV" }));
|
|
1011
|
+
await waitFor(() => expect(downloadByURL).toHaveBeenCalled());
|
|
1012
|
+
expect(toggle).toHaveAttribute("aria-expanded", "false");
|
|
1013
|
+
|
|
1014
|
+
fireEvent.click(screen.getByText("CSV"));
|
|
1015
|
+
fireEvent.click(screen.getByRole("button", { name: "Copy CSV" }));
|
|
1016
|
+
expect(toggle).toHaveAttribute("aria-expanded", "true");
|
|
1017
|
+
await waitFor(() => {
|
|
1018
|
+
expect(copyToClipboard).toHaveBeenCalledWith("name,value\n");
|
|
1019
|
+
});
|
|
1020
|
+
});
|
|
1021
|
+
|
|
594
1022
|
it("sends only changed CSV options with a download", async () => {
|
|
595
1023
|
renderExportActions();
|
|
596
1024
|
await openDialog();
|
|
@@ -1412,7 +1840,7 @@ describe("ExportActions GeoJSON", () => {
|
|
|
1412
1840
|
);
|
|
1413
1841
|
await act(async () => mocks.handleInstallPackages.mock.calls[0][1]());
|
|
1414
1842
|
await waitFor(() => expect(downloadByURL).toHaveBeenCalled());
|
|
1415
|
-
expect(getExportMetadata).toHaveBeenCalledTimes(
|
|
1843
|
+
expect(getExportMetadata).toHaveBeenCalledTimes(3);
|
|
1416
1844
|
expect(downloadAs).toHaveBeenLastCalledWith({
|
|
1417
1845
|
format: "geojson",
|
|
1418
1846
|
geometry_column: "boundary",
|
|
@@ -1685,7 +2113,7 @@ describe("ExportActions GeoJSON clipboard", () => {
|
|
|
1685
2113
|
format: "geojson",
|
|
1686
2114
|
geometry_column: "boundary",
|
|
1687
2115
|
});
|
|
1688
|
-
expect(getExportMetadata).toHaveBeenCalledTimes(
|
|
2116
|
+
expect(getExportMetadata).toHaveBeenCalledTimes(3);
|
|
1689
2117
|
expect(downloadByURL).not.toHaveBeenCalled();
|
|
1690
2118
|
});
|
|
1691
2119
|
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
import { render } from "@testing-library/react";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import {
|
|
5
|
+
buildHighlightPattern,
|
|
6
|
+
findMatchRanges,
|
|
7
|
+
highlightMatches,
|
|
8
|
+
highlightRanges,
|
|
9
|
+
} from "../search-highlight";
|
|
10
|
+
|
|
11
|
+
function highlight(text: string, query: string) {
|
|
12
|
+
return highlightMatches(text, buildHighlightPattern(query));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function marksOf(node: React.ReactNode) {
|
|
16
|
+
const { container } = render(<span>{node}</span>);
|
|
17
|
+
return {
|
|
18
|
+
container,
|
|
19
|
+
marks: [...container.querySelectorAll("mark")].map((m) => m.textContent),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe("buildHighlightPattern", () => {
|
|
24
|
+
it("returns undefined for an empty or missing query", () => {
|
|
25
|
+
expect(buildHighlightPattern("")).toBeUndefined();
|
|
26
|
+
expect(buildHighlightPattern(undefined)).toBeUndefined();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("escapes regex metacharacters so the query matches literally", () => {
|
|
30
|
+
const { marks } = marksOf(highlight("a.b a-b (x)", ".b"));
|
|
31
|
+
expect(marks).toEqual([".b"]);
|
|
32
|
+
expect(marksOf(highlight("f(x)", "(x)")).marks).toEqual(["(x)"]);
|
|
33
|
+
expect(marksOf(highlight("C and C++", "c++")).marks).toEqual(["C++"]);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe("highlightMatches", () => {
|
|
38
|
+
it("returns the input unchanged without a pattern", () => {
|
|
39
|
+
expect(highlightMatches("Alice Smith", undefined)).toBe("Alice Smith");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("returns the input unchanged when there is no match", () => {
|
|
43
|
+
expect(highlight("Alice Smith", "zzz")).toBe("Alice Smith");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("returns the input unchanged when the query is longer than the text", () => {
|
|
47
|
+
expect(highlight("ab", "abc")).toBe("ab");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("wraps a single match and keeps the surrounding text", () => {
|
|
51
|
+
const { container, marks } = marksOf(highlight("Alice Smith", "ce S"));
|
|
52
|
+
expect(marks).toEqual(["ce S"]);
|
|
53
|
+
expect(container.textContent).toBe("Alice Smith");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("matches case-insensitively and keeps the original casing", () => {
|
|
57
|
+
const { marks } = marksOf(highlight("Alice Smith", "smi"));
|
|
58
|
+
expect(marks).toEqual(["Smi"]);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("wraps every occurrence", () => {
|
|
62
|
+
const { container, marks } = marksOf(highlight("a-A-a", "a"));
|
|
63
|
+
expect(marks).toEqual(["a", "A", "a"]);
|
|
64
|
+
expect(container.textContent).toBe("a-A-a");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("produces no empty segments for matches at the edges", () => {
|
|
68
|
+
const segments = highlight("xax", "x") as React.ReactNode[];
|
|
69
|
+
expect(Array.isArray(segments)).toBe(true);
|
|
70
|
+
expect(segments).toHaveLength(3);
|
|
71
|
+
expect(segments[1]).toBe("a");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("is safe to reuse one global pattern across calls", () => {
|
|
75
|
+
const pattern = buildHighlightPattern("a");
|
|
76
|
+
expect(marksOf(highlightMatches("aa", pattern)).marks).toEqual(["a", "a"]);
|
|
77
|
+
expect(marksOf(highlightMatches("aa", pattern)).marks).toEqual(["a", "a"]);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe("Unicode case folding", () => {
|
|
82
|
+
it("folds the Kelvin sign to k like the backend engines", () => {
|
|
83
|
+
expect(marksOf(highlight("K", "k")).marks).toEqual(["K"]);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("folds every Greek sigma form", () => {
|
|
87
|
+
expect(marksOf(highlight("ΟΣ ος οσ", "σ")).marks).toEqual(["Σ", "ς", "σ"]);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe("highlightRanges", () => {
|
|
92
|
+
it("applies shared ranges to a segment at an offset", () => {
|
|
93
|
+
const ranges = findMatchRanges(
|
|
94
|
+
"see https://x",
|
|
95
|
+
buildHighlightPattern("see https"),
|
|
96
|
+
);
|
|
97
|
+
expect(ranges).toEqual([[0, 9]]);
|
|
98
|
+
const { marks: textMarks } = marksOf(highlightRanges("see ", ranges, 0));
|
|
99
|
+
expect(textMarks).toEqual(["see "]);
|
|
100
|
+
const { marks: linkMarks } = marksOf(
|
|
101
|
+
highlightRanges("https://x", ranges, 4),
|
|
102
|
+
);
|
|
103
|
+
expect(linkMarks).toEqual(["https"]);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("returns the segment unchanged when no range overlaps it", () => {
|
|
107
|
+
expect(highlightRanges("zzz", [[10, 12]], 0)).toBe("zzz");
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { fireEvent, render, screen } from "@testing-library/react";
|
|
3
3
|
import { describe, expect, it, vi } from "vitest";
|
|
4
4
|
import { parseContent } from "@/utils/url-parser";
|
|
5
|
+
import { buildHighlightPattern } from "../search-highlight";
|
|
5
6
|
import { isMarkdown, UrlDetector } from "../url-detector";
|
|
6
7
|
|
|
7
8
|
describe("UrlDetector", () => {
|
|
@@ -201,3 +202,76 @@ describe("isMarkdown", () => {
|
|
|
201
202
|
expect(isMarkdown("Visit [marimo](https://marimo.io)")).toBe(true);
|
|
202
203
|
});
|
|
203
204
|
});
|
|
205
|
+
|
|
206
|
+
describe("UrlDetector with highlightPattern", () => {
|
|
207
|
+
it("wraps a text match in a mark", () => {
|
|
208
|
+
const { container } = render(
|
|
209
|
+
<UrlDetector
|
|
210
|
+
parts={parseContent("Alice Smith")}
|
|
211
|
+
highlightPattern={buildHighlightPattern("smi")}
|
|
212
|
+
/>,
|
|
213
|
+
);
|
|
214
|
+
const marks = container.querySelectorAll("mark");
|
|
215
|
+
expect(marks).toHaveLength(1);
|
|
216
|
+
expect(marks[0].textContent).toBe("Smi");
|
|
217
|
+
expect(container.textContent).toBe("Alice Smith");
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it("wraps a match inside a link label", () => {
|
|
221
|
+
const { container } = render(
|
|
222
|
+
<UrlDetector
|
|
223
|
+
parts={parseContent("see https://marimo.io/docs")}
|
|
224
|
+
highlightPattern={buildHighlightPattern("marimo")}
|
|
225
|
+
/>,
|
|
226
|
+
);
|
|
227
|
+
const link = container.querySelector("a");
|
|
228
|
+
expect(link).not.toBeNull();
|
|
229
|
+
const mark = link?.querySelector("mark");
|
|
230
|
+
expect(mark?.textContent).toBe("marimo");
|
|
231
|
+
expect(link).toHaveAttribute("href", "https://marimo.io/docs");
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("renders no mark for an empty query", () => {
|
|
235
|
+
const { container } = render(
|
|
236
|
+
<UrlDetector
|
|
237
|
+
parts={parseContent("Alice Smith")}
|
|
238
|
+
highlightPattern={buildHighlightPattern("")}
|
|
239
|
+
/>,
|
|
240
|
+
);
|
|
241
|
+
expect(container.querySelector("mark")).toBeNull();
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
describe("UrlDetector highlight across part boundaries", () => {
|
|
246
|
+
it("marks a query that spans a text part and a link", () => {
|
|
247
|
+
const { container } = render(
|
|
248
|
+
<UrlDetector
|
|
249
|
+
parts={parseContent("see https://marimo.io/docs")}
|
|
250
|
+
highlightPattern={buildHighlightPattern("see https")}
|
|
251
|
+
/>,
|
|
252
|
+
);
|
|
253
|
+
const marks = [...container.querySelectorAll("mark")].map(
|
|
254
|
+
(m) => m.textContent,
|
|
255
|
+
);
|
|
256
|
+
expect(marks).toEqual(["see ", "https"]);
|
|
257
|
+
const link = container.querySelector("a");
|
|
258
|
+
expect(link?.querySelector("mark")?.textContent).toBe("https");
|
|
259
|
+
expect(link).toHaveAttribute("href", "https://marimo.io/docs");
|
|
260
|
+
expect(container.textContent).toBe("see https://marimo.io/docs");
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it("marks the fallback link when an image fails to load", () => {
|
|
264
|
+
const { container } = render(
|
|
265
|
+
<UrlDetector
|
|
266
|
+
parts={parseContent("https://example.com/image.png")}
|
|
267
|
+
highlightPattern={buildHighlightPattern("image")}
|
|
268
|
+
/>,
|
|
269
|
+
);
|
|
270
|
+
const img = container.querySelector("img");
|
|
271
|
+
expect(img).not.toBeNull();
|
|
272
|
+
fireEvent.error(img as HTMLImageElement);
|
|
273
|
+
const link = container.querySelector("a");
|
|
274
|
+
expect(link).toHaveAttribute("href", "https://example.com/image.png");
|
|
275
|
+
expect(link?.querySelector("mark")?.textContent).toBe("image");
|
|
276
|
+
});
|
|
277
|
+
});
|