@marimo-team/islands 0.25.3-dev5 → 0.25.3-dev51
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-CNMliVzw.js → ConnectedDataExplorerComponent-Da5OoEUL.js} +14 -14
- package/dist/{ErrorBoundary-BiZWOrv4.js → ErrorBoundary-CbxCC2kk.js} +8 -8
- package/dist/{abnfDiagram-VCTEODGH-D-Ez91gV.js → abnfDiagram-VCTEODGH-Bz3mrprX.js} +2 -2
- package/dist/any-language-editor-k2B8YI3Y.js +75 -0
- package/dist/{architectureDiagram-5GKGNRK7-CTpcNtO8.js → architectureDiagram-5GKGNRK7-Cs5ujb9K.js} +1 -1
- package/dist/{arrays-CN1djC4k.js → arrays-R6JsImiT.js} +8 -0
- package/dist/{button-CvjkwdSk.js → button-BxB3RDjN.js} +1 -1
- package/dist/{capabilities-CQ5IjhzL.js → capabilities-BgR0osfN.js} +2 -2
- package/dist/{chat-ui-0lKdOoQ3.js → chat-ui-C1Gjgokv.js} +1103 -1103
- package/dist/{check-EDwSwP89.js → check-P35dc2dH.js} +1 -1
- package/dist/{chunk-IMKFNOWR-DGxi-zeo.js → chunk-IMKFNOWR-CcTSaxZt.js} +1 -1
- package/dist/{chunk-SHT3W25Y-CV9J3rAB.js → chunk-SHT3W25Y-DcJcjOaO.js} +1 -1
- package/dist/{chunk-SVP7TREG-BL-9Fqgg.js → chunk-SVP7TREG-BVtDTjaF.js} +1 -1
- package/dist/{chunk-TICWLB2K-Cu_XMjDM.js → chunk-TICWLB2K-BkDFkh8q.js} +1 -1
- package/dist/{chunk-YOKDWASO-DPZk8vhS.js → chunk-YOKDWASO-DKplTrBY.js} +20 -20
- package/dist/{classDiagram-ZZMXUADV-BXXV13Hq.js → classDiagram-ZZMXUADV-CfAt7HT5.js} +2 -2
- package/dist/{classDiagram-v2-VYDZK3BY-B7FXJ1Cp.js → classDiagram-v2-VYDZK3BY-CGEjNYa2.js} +2 -2
- package/dist/{code-visibility-CW5_8ZOI.js → code-visibility-DoZ9lxzx.js} +6 -6
- package/dist/{common-mzWw4FdI.js → common-D1PacyXO.js} +3112 -3015
- package/dist/{copy-icon-Ft7t9bqz.js → copy-icon-CPoxUG7G.js} +3 -3
- package/dist/{cynefinDiagram-5FMLGOSQ-CH96rwba.js → cynefinDiagram-5FMLGOSQ-BW6NmfJi.js} +1 -1
- package/dist/{dagre-GXQ25YYZ-BD3PmpFY.js → dagre-GXQ25YYZ-CWg3CTuF.js} +1 -1
- package/dist/{data-grid-overlay-editor-CJmehSdc.js → data-grid-overlay-editor-B50UpkXd.js} +5 -5
- package/dist/{data-grid-types-BH_r7g7l.js → data-grid-types-0PJ-9N5n.js} +1 -1
- package/dist/{diagram-S7CK7UJ4-BsSJJldB.js → diagram-S7CK7UJ4-BPxtoS-z.js} +1 -1
- package/dist/{diagram-UQ7AKVKN-muQtNZc4.js → diagram-UQ7AKVKN-DA-vJdFL.js} +1 -1
- package/dist/{diagram-VX7I27RA-CdhuZAqR.js → diagram-VX7I27RA-D6bkR84S.js} +1 -1
- package/dist/{diagram-Z3DM3KII-BD8mXjOj.js → diagram-Z3DM3KII-m60g4cAT.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-MG5hiCIa.js} +76 -76
- package/dist/{ebnfDiagram-PWID7BFC-BIN1sweE.js → ebnfDiagram-PWID7BFC-CK99sho2.js} +2 -2
- package/dist/{erDiagram-RLTQ6QDP-Ctp5cfoN.js → erDiagram-RLTQ6QDP-Bryq0tdE.js} +1 -1
- package/dist/{error-banner-D-WCIiMt.js → error-banner-Sl2FlSJ4.js} +2 -2
- package/dist/{error-entries-DSvFWT_M.js → error-entries-CMNtA8NK.js} +959 -959
- package/dist/{feedback-button-BcT1FBud.js → feedback-button-DqPqwryB.js} +29 -29
- package/dist/{flowDiagram-HODETNUW-3EfLrlFb.js → flowDiagram-HODETNUW-D-3aRpfS.js} +2 -2
- package/dist/{formats-CeOmU1MU.js → formats-6pLWWrY2.js} +3 -3
- package/dist/{ganttDiagram-EL5Y4UJY-BQT16JSc.js → ganttDiagram-EL5Y4UJY-Dh_4cBJZ.js} +2 -2
- package/dist/{glide-data-editor-XckatD0j.js → glide-data-editor-BxMhUd3K.js} +370 -368
- package/dist/{glide-utils-DJyqh0EM.js → glide-utils-Bt8_pBa2.js} +16 -16
- package/dist/{globals-jRrLCkrP.js → globals-DTJa_doJ.js} +3 -3
- package/dist/{highlighted-body-KPVGNVTW-zwjq21tf.js → highlighted-body-KPVGNVTW-3Ziwfz-V.js} +2 -2
- package/dist/{html-to-image-B8s5SgGm.js → html-to-image-By72ALyI.js} +29 -29
- package/dist/{infoDiagram-27XIBGKW-DipQoHxl.js → infoDiagram-27XIBGKW-s1C1kgfU.js} +1 -1
- package/dist/{ishikawaDiagram-5VMMS53U-DgFMjSGz.js → ishikawaDiagram-5VMMS53U-CCsRgolY.js} +1 -1
- package/dist/{kanban-definition-UXKFOSKX-w1ACVjgl.js → kanban-definition-UXKFOSKX-BrmxDlcc.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 +2358 -2221
- package/dist/{mermaid-CHo49Yv-.js → mermaid-DKI0869I.js} +38 -38
- package/dist/mermaid-HWGCJPDP-Du3-HPQA.js +2 -0
- package/dist/{mindmap-definition-YA3MSWOX-CrT0KSfX.js → mindmap-definition-YA3MSWOX-641xJysF.js} +1 -1
- package/dist/{number-overlay-editor-pZx8dUDV.js → number-overlay-editor-Z_H6x5b-.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-BqvywNJA.js → pegDiagram-XKGWAZYB-keVUAUeS.js} +2 -2
- package/dist/{pieDiagram-E7YTZNPT-BfxJ_XCc.js → pieDiagram-E7YTZNPT-DHktXPO_.js} +1 -1
- package/dist/precisionRound-DhAzeRcm.js +49 -0
- package/dist/{process-output-CKRXyszb.js → process-output-D-OMW5sV.js} +4 -4
- package/dist/{quadrantDiagram-AXDQQJYC-Ccw0xMjN.js → quadrantDiagram-AXDQQJYC-Blrm7V7_.js} +1 -1
- package/dist/{railroadDiagram-O6MQD6OU-8vFLVTPu.js → railroadDiagram-O6MQD6OU-BVEF4OV_.js} +2 -2
- package/dist/{react-vega-BED0DjiB.js → react-vega-C4T9Rb_w.js} +12284 -12270
- package/dist/{requirementDiagram-BXWQKSXE-a_MxGvxe.js → requirementDiagram-BXWQKSXE-CWo-UAKV.js} +1 -1
- package/dist/{reveal-component-79ez8-D5.js → reveal-component-tAB5K3QE.js} +10 -10
- package/dist/{select-BButFi-d.js → select-B0LRDH0g.js} +4 -4
- package/dist/{sequenceDiagram-WJ2MYXX4-CXFMnxGh.js → sequenceDiagram-WJ2MYXX4-CHQvLnq4.js} +1 -1
- package/dist/{spec-BwarBpUJ.js → spec-C-LRKGtT.js} +4 -4
- package/dist/{stateDiagram-D77RDMKH-DQeA_CwI.js → stateDiagram-D77RDMKH-BOJZgbUJ.js} +2 -2
- package/dist/{stateDiagram-v2-MP3YSRHH-BxxQucm1.js → stateDiagram-v2-MP3YSRHH-C53XXdYH.js} +2 -2
- package/dist/style.css +1 -1
- package/dist/{swimlanes-42K2YHIH-Do6L-cYg.js → swimlanes-42K2YHIH-D2o1gOtw.js} +1 -1
- package/dist/{swimlanesDiagram-VR7AAH4N-DUzPvSVU.js → swimlanesDiagram-VR7AAH4N-BHFzN1Wk.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-D3c46WM5.js → timeline-definition-24CTP7MA-rv7QUa46.js} +1 -1
- package/dist/{toDate-vIXljnsr.js → toDate-CjPuEPou.js} +21 -20
- 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-ChgNtdmE.js} +15 -14
- package/dist/{useDateFormatter-D8XruNGG.js → useDateFormatter-c_yJtkAe.js} +2 -2
- package/dist/{useDeepCompareMemoize-DXwzRINI.js → useDeepCompareMemoize-Bet7kSsI.js} +1 -1
- package/dist/{useIframeCapabilities-DBETvvPl.js → useIframeCapabilities-BkdeVdXC.js} +1 -1
- package/dist/{useLifecycle-j7D9uD1o.js → useLifecycle-VVmz_24o.js} +3 -3
- package/dist/{useTheme-CRnjkku0.js → useTheme-DympCsQg.js} +5 -5
- package/dist/{vega-component-DTG6zxaY.js → vega-component-DnfQeoyt.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-BSZD8JcZ.js → vennDiagram-4TSXK5OY-D36Zk7t-.js} +1 -1
- package/dist/{wardleyDiagram-VM6X3IG4-48cUWkfA.js → wardleyDiagram-VM6X3IG4-BLBolvyQ.js} +1 -1
- package/dist/{xychartDiagram-S5SC5T6Z-CswYgYeu.js → xychartDiagram-S5SC5T6Z-CfH57l0v.js} +2 -2
- package/package.json +4 -4
- package/src/__tests__/fixtures/{pair-preview.ts → pair-config.ts} +3 -2
- package/src/__tests__/host-imports.test.ts +1 -0
- package/src/__tests__/mount.test.ts +8 -8
- package/src/components/app-config/ai-config.tsx +5 -1
- package/src/components/chat/__tests__/message-queue.test.tsx +17 -0
- package/src/components/chat/chat-panel.tsx +3 -7
- package/src/components/chat/chat-utils.ts +4 -8
- package/src/components/data-table/__tests__/context-menu-interaction.test.tsx +130 -0
- package/src/components/data-table/__tests__/date-popover.test.tsx +72 -0
- package/src/components/data-table/__tests__/export-actions.test.tsx +417 -6
- package/src/components/data-table/column-visibility-dropdown.tsx +8 -13
- package/src/components/data-table/columns.tsx +2 -1
- package/src/components/data-table/context-menu.tsx +36 -20
- package/src/components/data-table/date-popover.tsx +6 -4
- package/src/components/data-table/export-actions.tsx +263 -161
- 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/editor/Output.tsx +5 -1
- 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 +186 -34
- package/src/components/editor/actions/pair-with-agent-commands.ts +42 -51
- package/src/components/editor/actions/pair-with-agent-modal.tsx +96 -36
- package/src/components/editor/ai/add-cell-with-ai.tsx +3 -3
- package/src/components/editor/cell/CreateCellButton.tsx +8 -10
- 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/chrome/wrapper/__tests__/attachment-status.test.tsx +277 -78
- package/src/components/editor/chrome/wrapper/footer-items/attachment-status.tsx +196 -41
- 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 +139 -36
- package/src/components/editor/errors/send-error-report-button.tsx +10 -7
- package/src/components/editor/file-tree/file-viewer.tsx +4 -2
- package/src/components/editor/output/MarimoTracebackOutput.tsx +28 -22
- package/src/components/editor/output/console/ConsoleOutput.tsx +8 -18
- package/src/components/pages/home-page.tsx +4 -4
- package/src/components/slides/slide-cell-view.tsx +1 -1
- package/src/components/tracing/tracing.tsx +2 -2
- package/src/core/attachments/__tests__/toast.test.tsx +58 -0
- package/src/core/attachments/state.ts +5 -8
- package/src/core/attachments/toast.ts +15 -0
- package/src/core/cells/__tests__/error-report.test.ts +53 -3
- package/src/core/cells/__tests__/notebook-outline.test.ts +113 -0
- package/src/core/cells/__tests__/session.test.ts +18 -3
- package/src/core/cells/cells.ts +2 -1
- package/src/core/cells/error-report.ts +14 -6
- package/src/core/cells/pending-delete-service.ts +2 -6
- package/src/core/cells/session.ts +19 -1
- 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/edit-app.tsx +2 -2
- package/src/core/host/AGENTS.md +11 -2
- 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__/events.test.ts +49 -0
- package/src/core/host/__tests__/fixtures.ts +41 -0
- package/src/core/host/__tests__/notebook-client.test.ts +496 -0
- package/src/core/host/connection.ts +81 -0
- package/src/core/host/events.ts +68 -0
- package/src/core/host/notebook-client.ts +316 -0
- package/src/core/host/resources/attachments.ts +19 -0
- package/src/core/host/resources/document.ts +101 -0
- package/src/core/hotkeys/actions.ts +2 -4
- package/src/core/islands/__tests__/bootstrap.test.ts +0 -26
- package/src/core/islands/bootstrap.ts +0 -1
- package/src/core/network/connection.ts +4 -2
- package/src/core/run-app.tsx +2 -2
- package/src/core/runtime/__tests__/runtime.test.ts +5 -12
- package/src/core/runtime/runtime.ts +5 -5
- package/src/core/static/files.ts +2 -0
- package/src/core/websocket/__tests__/{useMarimoKernelConnection.test.ts → connection-state.test.ts} +15 -17
- package/src/core/websocket/__tests__/{useWebSocket.test.ts → createConnectionTransport.test.ts} +1 -1
- package/src/core/websocket/__tests__/useNotebookClient.hook.test.tsx +149 -0
- package/src/core/websocket/__tests__/{useMarimoKernelConnection.hook.test.tsx → useNotebookSession.hook.test.tsx} +168 -253
- package/src/core/websocket/connection-state.ts +70 -0
- package/src/core/websocket/state.ts +7 -0
- package/src/core/websocket/transports/__tests__/sse.test.ts +18 -4
- package/src/core/websocket/transports/create.ts +31 -0
- package/src/core/websocket/transports/sse.ts +14 -2
- package/src/core/websocket/transports/transport.ts +12 -23
- package/src/core/websocket/types.ts +9 -39
- package/src/core/websocket/useNotebookClient.ts +78 -0
- package/src/core/websocket/{useMarimoKernelConnection.tsx → useNotebookSession.tsx} +53 -233
- package/src/hooks/__tests__/useAsyncData.test.tsx +148 -0
- package/src/hooks/useAsyncData.ts +3 -3
- package/src/hooks/useDataSourceDiscovery.ts +6 -4
- package/src/hooks/useElapsedTime.ts +3 -3
- package/src/hooks/useInputHistory.ts +4 -7
- package/src/hooks/useResizeObserver.ts +4 -7
- package/src/mount.tsx +3 -3
- package/src/plugins/core/registerReactComponent.tsx +1 -1
- package/src/plugins/impl/DropdownPlugin.tsx +2 -6
- package/src/plugins/impl/FormPlugin.tsx +1 -1
- package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +84 -0
- 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/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/mpl-interactive/MplInteractivePlugin.tsx +5 -3
- package/src/plugins/impl/panel/PanelPlugin.tsx +15 -16
- 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 +3 -6
- package/src/stories/__tests__/editor-story.test.tsx +33 -0
- package/src/stories/editor.stories.tsx +24 -17
- package/src/utils/__tests__/arrays.test.ts +35 -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/numbers.ts +28 -17
- package/src/utils/objects.ts +3 -7
- package/src/utils/once.ts +19 -19
- package/dist/any-language-editor-DZ_YTVus.js +0 -76
- package/dist/mermaid-HWGCJPDP-ByCsZnYF.js +0 -2
- package/dist/precisionRound-DMIVQrt2.js +0 -97
- package/src/core/host/__tests__/protocol.test.ts +0 -214
- package/src/core/host/protocol.ts +0 -1943
- package/src/core/websocket/useWebSocket.tsx +0 -88
|
@@ -1,17 +1,41 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
act,
|
|
5
|
+
render as renderComponent,
|
|
6
|
+
waitFor,
|
|
7
|
+
} from "@testing-library/react";
|
|
4
8
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
|
-
import {
|
|
9
|
+
import { Provider, createStore } from "jotai";
|
|
10
|
+
import type { ReactNode } from "react";
|
|
11
|
+
import { MockNotebook } from "@/__mocks__/notebook";
|
|
12
|
+
import { cellId } from "@/__tests__/branded";
|
|
13
|
+
import { notebookAtom } from "@/core/cells/cells";
|
|
6
14
|
import { DynamicFavicon } from "../dynamic-favicon";
|
|
7
15
|
|
|
8
|
-
// Mock useCellErrors hook
|
|
9
|
-
vi.mock("@/core/cells/cells", () => ({
|
|
10
|
-
useCellErrors: vi.fn(),
|
|
11
|
-
}));
|
|
12
|
-
|
|
13
16
|
describe("DynamicFavicon", () => {
|
|
14
17
|
let favicon: HTMLLinkElement;
|
|
18
|
+
let store: ReturnType<typeof createStore>;
|
|
19
|
+
const id = cellId("cell");
|
|
20
|
+
|
|
21
|
+
function render(ui: ReactNode) {
|
|
22
|
+
return renderComponent(ui, {
|
|
23
|
+
wrapper: ({ children }) => <Provider store={store}>{children}</Provider>,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function setErrors() {
|
|
28
|
+
store.set(
|
|
29
|
+
notebookAtom,
|
|
30
|
+
MockNotebook.notebookStateWithErrors([
|
|
31
|
+
{
|
|
32
|
+
cellId: id,
|
|
33
|
+
cellName: "cell",
|
|
34
|
+
errorData: [MockNotebook.errors.syntax("Invalid syntax")],
|
|
35
|
+
},
|
|
36
|
+
]),
|
|
37
|
+
);
|
|
38
|
+
}
|
|
15
39
|
|
|
16
40
|
beforeEach(() => {
|
|
17
41
|
document
|
|
@@ -27,15 +51,20 @@ describe("DynamicFavicon", () => {
|
|
|
27
51
|
// Mock document.hasFocus
|
|
28
52
|
vi.spyOn(document, "hasFocus").mockReturnValue(true);
|
|
29
53
|
|
|
30
|
-
|
|
31
|
-
|
|
54
|
+
store = createStore();
|
|
55
|
+
store.set(
|
|
56
|
+
notebookAtom,
|
|
57
|
+
MockNotebook.notebookState({
|
|
58
|
+
cellData: { [id]: { code: "x = 1" } },
|
|
59
|
+
}),
|
|
60
|
+
);
|
|
32
61
|
});
|
|
33
62
|
|
|
34
63
|
afterEach(() => {
|
|
35
64
|
document
|
|
36
65
|
.querySelectorAll("link[rel~='icon']")
|
|
37
66
|
.forEach((element) => element.remove());
|
|
38
|
-
vi.
|
|
67
|
+
vi.restoreAllMocks();
|
|
39
68
|
vi.useRealTimers();
|
|
40
69
|
});
|
|
41
70
|
|
|
@@ -67,9 +96,7 @@ describe("DynamicFavicon", () => {
|
|
|
67
96
|
});
|
|
68
97
|
|
|
69
98
|
it("should show error favicon when run completes with errors", async () => {
|
|
70
|
-
(
|
|
71
|
-
{ error: "mock error" },
|
|
72
|
-
]);
|
|
99
|
+
setErrors();
|
|
73
100
|
|
|
74
101
|
const { rerender } = render(<DynamicFavicon isRunning={true} />);
|
|
75
102
|
|
|
@@ -147,6 +174,72 @@ describe("DynamicFavicon", () => {
|
|
|
147
174
|
});
|
|
148
175
|
});
|
|
149
176
|
|
|
177
|
+
it("does not look up or rewrite the favicon when code changes", async () => {
|
|
178
|
+
const { rerender } = render(<DynamicFavicon isRunning={true} />);
|
|
179
|
+
await waitFor(() => expect(favicon.href).toContain("circle-play.ico"));
|
|
180
|
+
rerender(<DynamicFavicon isRunning={false} />);
|
|
181
|
+
await waitFor(() => expect(favicon.href).toContain("circle-check.ico"));
|
|
182
|
+
const lookup = vi.spyOn(document, "querySelector");
|
|
183
|
+
const write = vi.spyOn(favicon, "href", "set");
|
|
184
|
+
|
|
185
|
+
await act(async () => {
|
|
186
|
+
store.set(notebookAtom, (state) => ({
|
|
187
|
+
...state,
|
|
188
|
+
cellData: {
|
|
189
|
+
...state.cellData,
|
|
190
|
+
[id]: { ...state.cellData[id], code: "x = 2" },
|
|
191
|
+
},
|
|
192
|
+
}));
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
expect(
|
|
196
|
+
lookup.mock.calls.filter(
|
|
197
|
+
([selector]) => selector === "link[rel~='icon']",
|
|
198
|
+
),
|
|
199
|
+
).toEqual([]);
|
|
200
|
+
expect(write).not.toHaveBeenCalled();
|
|
201
|
+
lookup.mockRestore();
|
|
202
|
+
write.mockRestore();
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it("updates the favicon when the error count changes", async () => {
|
|
206
|
+
const { rerender } = render(<DynamicFavicon isRunning={true} />);
|
|
207
|
+
await waitFor(() => expect(favicon.href).toContain("circle-play.ico"));
|
|
208
|
+
rerender(<DynamicFavicon isRunning={false} />);
|
|
209
|
+
await waitFor(() => expect(favicon.href).toContain("circle-check.ico"));
|
|
210
|
+
|
|
211
|
+
act(() => setErrors());
|
|
212
|
+
await waitFor(() => expect(favicon.href).toContain("circle-x.ico"));
|
|
213
|
+
|
|
214
|
+
act(() =>
|
|
215
|
+
store.set(
|
|
216
|
+
notebookAtom,
|
|
217
|
+
MockNotebook.notebookState({ cellData: { [id]: {} } }),
|
|
218
|
+
),
|
|
219
|
+
);
|
|
220
|
+
await waitFor(() => expect(favicon.href).toContain("circle-check.ico"));
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("keeps the completion icon until its own reset timer expires", async () => {
|
|
224
|
+
vi.useFakeTimers();
|
|
225
|
+
const { rerender } = render(<DynamicFavicon isRunning={true} />);
|
|
226
|
+
await act(() => vi.advanceTimersByTimeAsync(1000));
|
|
227
|
+
rerender(<DynamicFavicon isRunning={false} />);
|
|
228
|
+
await act(() => vi.advanceTimersByTimeAsync(2000));
|
|
229
|
+
expect(favicon.href).toContain("circle-check.ico");
|
|
230
|
+
await act(() => vi.advanceTimersByTimeAsync(1000));
|
|
231
|
+
expect(favicon.href).toContain("favicon.ico");
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("cancels the reset timer when unmounted", async () => {
|
|
235
|
+
vi.useFakeTimers();
|
|
236
|
+
const { unmount } = render(<DynamicFavicon isRunning={true} />);
|
|
237
|
+
await act(() => vi.advanceTimersByTimeAsync(0));
|
|
238
|
+
expect(vi.getTimerCount()).toBe(1);
|
|
239
|
+
unmount();
|
|
240
|
+
expect(vi.getTimerCount()).toBe(0);
|
|
241
|
+
});
|
|
242
|
+
|
|
150
243
|
describe("notifications", () => {
|
|
151
244
|
beforeEach(() => {
|
|
152
245
|
vi.spyOn(document, "visibilityState", "get").mockReturnValue("hidden");
|
|
@@ -156,7 +249,7 @@ describe("DynamicFavicon", () => {
|
|
|
156
249
|
global.Notification.permission = "granted";
|
|
157
250
|
});
|
|
158
251
|
|
|
159
|
-
it("should send success notification when run completes without errors", () => {
|
|
252
|
+
it("should send success notification when run completes without errors", async () => {
|
|
160
253
|
// @ts-expect-error ok in tests
|
|
161
254
|
global.Notification = vi
|
|
162
255
|
.fn()
|
|
@@ -172,12 +265,11 @@ describe("DynamicFavicon", () => {
|
|
|
172
265
|
|
|
173
266
|
const { rerender } = render(<DynamicFavicon isRunning={true} />);
|
|
174
267
|
rerender(<DynamicFavicon isRunning={false} />);
|
|
268
|
+
await waitFor(() => expect(Notification).toHaveBeenCalledOnce());
|
|
175
269
|
});
|
|
176
270
|
|
|
177
|
-
it("should send error notification when run completes with errors", () => {
|
|
178
|
-
(
|
|
179
|
-
{ error: "mock error" },
|
|
180
|
-
]);
|
|
271
|
+
it("should send error notification when run completes with errors", async () => {
|
|
272
|
+
setErrors();
|
|
181
273
|
|
|
182
274
|
// @ts-expect-error ok in tests
|
|
183
275
|
global.Notification = vi
|
|
@@ -194,6 +286,7 @@ describe("DynamicFavicon", () => {
|
|
|
194
286
|
|
|
195
287
|
const { rerender } = render(<DynamicFavicon isRunning={true} />);
|
|
196
288
|
rerender(<DynamicFavicon isRunning={false} />);
|
|
289
|
+
await waitFor(() => expect(Notification).toHaveBeenCalledOnce());
|
|
197
290
|
});
|
|
198
291
|
|
|
199
292
|
it("should not send notification when document is visible", () => {
|
|
@@ -1,50 +1,34 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
3
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { PAIR_CONFIG } from "@/__tests__/fixtures/pair-config";
|
|
5
|
+
import { shellQuote } from "@/utils/shell";
|
|
4
6
|
import {
|
|
5
7
|
type ConnectionInfo,
|
|
6
|
-
getFileFromURL,
|
|
7
8
|
getMarimoCommand,
|
|
8
9
|
getRawPrompt,
|
|
9
10
|
getTerminalCommand,
|
|
10
11
|
maskToken,
|
|
11
12
|
} from "../pair-with-agent-commands";
|
|
12
|
-
import { shellQuote } from "@/utils/shell";
|
|
13
|
-
import { PAIR_PREVIEW } from "@/__tests__/fixtures/pair-preview";
|
|
14
13
|
|
|
15
14
|
const CONNECTION: ConnectionInfo = {
|
|
16
15
|
url: "http://localhost:8000",
|
|
17
|
-
|
|
18
|
-
session: "s_ab12cd",
|
|
16
|
+
notebook: "nb-one",
|
|
19
17
|
};
|
|
20
|
-
|
|
21
|
-
const CONNECTION_WITHOUT_FILE: ConnectionInfo = {
|
|
18
|
+
const CONNECTION_WITHOUT_TARGET: ConnectionInfo = {
|
|
22
19
|
url: "http://localhost:8000",
|
|
23
20
|
};
|
|
24
21
|
|
|
25
22
|
describe("shellQuote", () => {
|
|
26
|
-
it("quotes
|
|
23
|
+
it("quotes empty and unsafe arguments", () => {
|
|
27
24
|
expect(shellQuote("")).toBe("''");
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it("leaves shell-safe values untouched", () => {
|
|
31
25
|
expect(shellQuote("http://localhost:8000")).toBe("http://localhost:8000");
|
|
32
|
-
expect(shellQuote("notebooks/example.py")).toBe("notebooks/example.py");
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it("quotes values with shell metacharacters", () => {
|
|
36
26
|
expect(shellQuote("http://host:8000?a=1&b=2")).toBe(
|
|
37
27
|
"'http://host:8000?a=1&b=2'",
|
|
38
28
|
);
|
|
39
|
-
expect(shellQuote("has space")).toBe("'has space'");
|
|
40
29
|
expect(shellQuote("$(rm -rf /)")).toBe("'$(rm -rf /)'");
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it("escapes embedded single quotes without breaking out", () => {
|
|
44
|
-
// Closes the quote, emits a literal ' via "'", then reopens: '"'"'
|
|
45
30
|
expect(shellQuote("a'b")).toBe(`'a'"'"'b'`);
|
|
46
31
|
});
|
|
47
|
-
|
|
48
32
|
it.each([
|
|
49
33
|
["/tmp/my notebook.py", "'/tmp/my notebook.py'"],
|
|
50
34
|
[
|
|
@@ -55,267 +39,115 @@ describe("shellQuote", () => {
|
|
|
55
39
|
String.raw`\\server\share\my notebook.py`,
|
|
56
40
|
String.raw`'\\server\share\my notebook.py'`,
|
|
57
41
|
],
|
|
58
|
-
])("quotes
|
|
42
|
+
])("quotes path %s as one argument", (path, expected) => {
|
|
59
43
|
expect(shellQuote(path)).toBe(expected);
|
|
60
44
|
});
|
|
61
45
|
});
|
|
62
46
|
|
|
63
|
-
describe("getFileFromURL", () => {
|
|
64
|
-
it("returns undefined when the file query parameter is absent or empty", () => {
|
|
65
|
-
expect(getFileFromURL("http://localhost:8000")).toBeUndefined();
|
|
66
|
-
expect(getFileFromURL("http://localhost:8000?file=")).toBeUndefined();
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it("decodes spaces and literal plus signs", () => {
|
|
70
|
-
expect(
|
|
71
|
-
getFileFromURL(
|
|
72
|
-
"http://localhost:8000?file=relative%2Fmy%20notebook%2Bdata.py",
|
|
73
|
-
),
|
|
74
|
-
).toBe("relative/my notebook+data.py");
|
|
75
|
-
expect(getFileFromURL("http://localhost:8000?file=my+notebook.py")).toBe(
|
|
76
|
-
"my notebook.py",
|
|
77
|
-
);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it("preserves decoded Windows paths and uses the first duplicate value", () => {
|
|
81
|
-
expect(
|
|
82
|
-
getFileFromURL(
|
|
83
|
-
"http://localhost:8000?file=C%3A%5CUsers%5CJane%20Doe%5Cnotebook.py",
|
|
84
|
-
),
|
|
85
|
-
).toBe(String.raw`C:\Users\Jane Doe\notebook.py`);
|
|
86
|
-
expect(
|
|
87
|
-
getFileFromURL("http://localhost:8000?file=first.py&file=second.py"),
|
|
88
|
-
).toBe("first.py");
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
|
|
92
47
|
describe("getMarimoCommand", () => {
|
|
93
|
-
afterEach(() =>
|
|
94
|
-
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it("uses the local checkout in dev", () => {
|
|
48
|
+
afterEach(() => vi.unstubAllEnvs());
|
|
49
|
+
it("uses the checkout command in development", () => {
|
|
98
50
|
vi.stubEnv("DEV", true);
|
|
99
51
|
expect(getMarimoCommand()).toBe("uv run marimo");
|
|
100
52
|
});
|
|
101
|
-
|
|
102
|
-
it("uses uvx outside of dev", () => {
|
|
53
|
+
it("uses uvx outside development", () => {
|
|
103
54
|
vi.stubEnv("DEV", false);
|
|
104
55
|
expect(getMarimoCommand()).toBe("uvx marimo@latest");
|
|
105
56
|
});
|
|
106
57
|
});
|
|
107
58
|
|
|
108
59
|
describe("getTerminalCommand", () => {
|
|
109
|
-
it("uses the preview launcher in production and quotes session-only targets", () => {
|
|
110
|
-
vi.stubEnv("DEV", false);
|
|
111
|
-
try {
|
|
112
|
-
expect(
|
|
113
|
-
getTerminalCommand(
|
|
114
|
-
"claude",
|
|
115
|
-
{ url: "http://host:8000?a=1&b=2", session: "s_' {file}" },
|
|
116
|
-
false,
|
|
117
|
-
{ ...PAIR_PREVIEW, command: "uvx marimo@latest" },
|
|
118
|
-
),
|
|
119
|
-
).toBe(
|
|
120
|
-
String.raw`claude "$(MARIMO_PAIR_NEXT=1 uvx marimo@latest pair prompt \
|
|
121
|
-
--url 'http://host:8000?a=1&b=2' \
|
|
122
|
-
--session 's_'"'"' {file}')"`,
|
|
123
|
-
);
|
|
124
|
-
expect(
|
|
125
|
-
getTerminalCommand(
|
|
126
|
-
"codex",
|
|
127
|
-
CONNECTION_WITHOUT_FILE,
|
|
128
|
-
false,
|
|
129
|
-
PAIR_PREVIEW,
|
|
130
|
-
),
|
|
131
|
-
).toBe(
|
|
132
|
-
String.raw`codex "$(MARIMO_PAIR_NEXT=1 uvx marimo@latest pair prompt \
|
|
133
|
-
--url http://localhost:8000)"`,
|
|
134
|
-
);
|
|
135
|
-
} finally {
|
|
136
|
-
vi.unstubAllEnvs();
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
|
|
140
60
|
it.each([
|
|
141
61
|
["claude", "claude"],
|
|
142
62
|
["codex", "codex"],
|
|
143
63
|
["opencode", "opencode --prompt"],
|
|
144
|
-
] as const)(
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
expect(
|
|
148
|
-
getTerminalCommand(
|
|
149
|
-
agent,
|
|
150
|
-
{ ...CONNECTION, session: "s_ab12cd" },
|
|
151
|
-
true,
|
|
152
|
-
PAIR_PREVIEW,
|
|
153
|
-
),
|
|
154
|
-
).toBe(
|
|
155
|
-
String.raw`${cli} "$(MARIMO_PAIR_NEXT=1 uvx marimo@latest pair prompt \
|
|
156
|
-
--url http://localhost:8000 \
|
|
157
|
-
--session s_ab12cd \
|
|
158
|
-
--with-token)"`,
|
|
159
|
-
);
|
|
160
|
-
},
|
|
161
|
-
);
|
|
162
|
-
|
|
163
|
-
it("includes the url and agent flag for each agent", () => {
|
|
164
|
-
expect(getTerminalCommand("claude", CONNECTION, false)).toBe(
|
|
165
|
-
`claude "$(uv run marimo pair prompt --url http://localhost:8000 --claude)"`,
|
|
166
|
-
);
|
|
167
|
-
expect(getTerminalCommand("codex", CONNECTION, false)).toBe(
|
|
168
|
-
`codex "$(uv run marimo pair prompt --url http://localhost:8000 --codex)"`,
|
|
169
|
-
);
|
|
170
|
-
expect(getTerminalCommand("opencode", CONNECTION, false)).toBe(
|
|
171
|
-
`opencode --prompt "$(uv run marimo pair prompt --url http://localhost:8000 --opencode)"`,
|
|
64
|
+
] as const)("launches %s through the public CLI", (agent, cli) => {
|
|
65
|
+
expect(getTerminalCommand(agent, CONNECTION, true, PAIR_CONFIG)).toBe(
|
|
66
|
+
`${cli} "$(uvx marimo@latest pair prompt \\\n --url http://localhost:8000 \\\n --notebook nb-one \\\n --with-token)"`,
|
|
172
67
|
);
|
|
173
68
|
});
|
|
174
|
-
|
|
175
|
-
it("never passes a file to marimo pair prompt", () => {
|
|
176
|
-
const command = getTerminalCommand("claude", CONNECTION, false);
|
|
177
|
-
expect(command).not.toContain("--file");
|
|
178
|
-
expect(command).not.toContain("notebooks/example.py");
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
it("omits the session outside the preview", () => {
|
|
182
|
-
expect(getTerminalCommand("claude", CONNECTION, false)).not.toContain(
|
|
183
|
-
"--session",
|
|
184
|
-
);
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
it("omits the session when it is unknown", () => {
|
|
188
|
-
expect(
|
|
189
|
-
getTerminalCommand(
|
|
190
|
-
"claude",
|
|
191
|
-
CONNECTION_WITHOUT_FILE,
|
|
192
|
-
false,
|
|
193
|
-
PAIR_PREVIEW,
|
|
194
|
-
),
|
|
195
|
-
).not.toContain("--session");
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
it("shell-escapes a url containing metacharacters", () => {
|
|
69
|
+
it("uses a stable Session ID for editor execution", () => {
|
|
199
70
|
const command = getTerminalCommand(
|
|
200
71
|
"claude",
|
|
201
|
-
{ url: "http://host:8000?
|
|
72
|
+
{ url: "http://host:8000?a=1&b=2", session: "stable_' {file}" },
|
|
202
73
|
false,
|
|
74
|
+
PAIR_CONFIG,
|
|
203
75
|
);
|
|
204
|
-
expect(command).toContain("--url 'http://host:8000?
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
it("adds --with-token before the agent flag when requested", () => {
|
|
208
|
-
const command = getTerminalCommand("claude", CONNECTION, true);
|
|
209
|
-
expect(command).toContain("--with-token --claude");
|
|
76
|
+
expect(command).toContain("--url 'http://host:8000?a=1&b=2'");
|
|
77
|
+
expect(command).toContain(`--session 'stable_'"'"' {file}'`);
|
|
78
|
+
expect(command).not.toContain("--notebook");
|
|
210
79
|
});
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
"
|
|
80
|
+
it("prefers the verified Notebook ID to a Session ID", () => {
|
|
81
|
+
const command = getTerminalCommand(
|
|
82
|
+
"codex",
|
|
83
|
+
{ ...CONNECTION, session: "stable-one" },
|
|
84
|
+
false,
|
|
85
|
+
PAIR_CONFIG,
|
|
215
86
|
);
|
|
87
|
+
expect(command).toContain("--notebook nb-one");
|
|
88
|
+
expect(command).not.toContain("stable-one");
|
|
89
|
+
expect(command).not.toContain("--with-token");
|
|
90
|
+
});
|
|
91
|
+
it("uses the CLI without injected configuration", () => {
|
|
92
|
+
vi.stubEnv("DEV", true);
|
|
93
|
+
try {
|
|
94
|
+
const command = getTerminalCommand(
|
|
95
|
+
"claude",
|
|
96
|
+
CONNECTION_WITHOUT_TARGET,
|
|
97
|
+
false,
|
|
98
|
+
);
|
|
99
|
+
expect(command).toContain("uv run marimo pair prompt");
|
|
100
|
+
expect(command).not.toMatch(
|
|
101
|
+
/--claude|MARIMO_PAIR_NEXT|--file|--session|--notebook/,
|
|
102
|
+
);
|
|
103
|
+
} finally {
|
|
104
|
+
vi.unstubAllEnvs();
|
|
105
|
+
}
|
|
216
106
|
});
|
|
217
107
|
});
|
|
218
108
|
|
|
219
109
|
describe("getRawPrompt", () => {
|
|
220
|
-
it("
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
command: "custom marimo",
|
|
231
|
-
templates: {
|
|
232
|
-
...PAIR_PREVIEW.templates,
|
|
233
|
-
prompt: "{command}\n{url}\n{session}{authentication}",
|
|
234
|
-
},
|
|
110
|
+
it("does not interpret placeholders in inserted values", () => {
|
|
111
|
+
const prompt = getRawPrompt(
|
|
112
|
+
{ url: "http://host/{session}", notebook: "{command}/it's" },
|
|
113
|
+
"tok'en {session} $&",
|
|
114
|
+
{
|
|
115
|
+
...PAIR_CONFIG,
|
|
116
|
+
command: "custom marimo",
|
|
117
|
+
templates: {
|
|
118
|
+
...PAIR_CONFIG.templates,
|
|
119
|
+
prompt: "{command}\n{url}\n{notebook}{authentication}",
|
|
235
120
|
},
|
|
236
|
-
|
|
237
|
-
)
|
|
238
|
-
|
|
239
|
-
|
|
121
|
+
},
|
|
122
|
+
);
|
|
123
|
+
expect(prompt).toBe(
|
|
124
|
+
"custom marimo\nhttp://host/{session}\nNotebook: {command}/it's\n" +
|
|
125
|
+
`\n\nFor authenticated notebook discovery, set \`export MARIMO_TOKEN='tok'"'"'en {session} $&'\` in the shell that runs marimo.`,
|
|
240
126
|
);
|
|
241
127
|
});
|
|
242
|
-
|
|
243
128
|
it.each([
|
|
244
|
-
[
|
|
129
|
+
[CONNECTION_WITHOUT_TARGET, ""],
|
|
130
|
+
[CONNECTION, "Notebook: nb-one\n"],
|
|
245
131
|
[
|
|
246
|
-
{ ...
|
|
247
|
-
"Session:
|
|
132
|
+
{ ...CONNECTION_WITHOUT_TARGET, session: "stable-one" },
|
|
133
|
+
"Session: stable-one\n",
|
|
248
134
|
],
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
expect(getRawPrompt(connection, null,
|
|
252
|
-
"Pair with me on this running marimo notebook.\n\n" +
|
|
253
|
-
`URL: http://localhost:8000\n${fields}\n` +
|
|
254
|
-
"Run `uvx marimo@latest pair --help` first.\n" +
|
|
255
|
-
"Use `uvx marimo@latest` for all marimo commands.\n\n" +
|
|
256
|
-
"Once connected, send a fun toast using `mo.status.toast(...)` (`import marimo as mo`).",
|
|
257
|
-
);
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
it("renders the shared preview template with the session and no file", () => {
|
|
135
|
+
])("renders the shared CLI prompt for %j", (connection, target) => {
|
|
136
|
+
const expected = `Pair with me on this running marimo notebook.\n\nURL: http://localhost:8000\n${target}\nRun \`uvx marimo@latest pair --help\` first.\nUse \`uvx marimo@latest\` for all marimo commands.`;
|
|
137
|
+
expect(getRawPrompt(connection, null, PAIR_CONFIG)).toBe(expected);
|
|
261
138
|
expect(
|
|
262
|
-
getRawPrompt({ ...
|
|
263
|
-
).toBe(
|
|
264
|
-
"Pair with me on this running marimo notebook.\n\n" +
|
|
265
|
-
"URL: http://localhost:8000\n" +
|
|
266
|
-
"Session: s_ab12cd\n\n" +
|
|
267
|
-
"Run `uvx marimo@latest pair --help` first.\n" +
|
|
268
|
-
"Use `uvx marimo@latest` for all marimo commands.\n\n" +
|
|
269
|
-
"Once connected, send a fun toast using `mo.status.toast(...)` (`import marimo as mo`).",
|
|
270
|
-
);
|
|
139
|
+
getRawPrompt(connection, null, { ...PAIR_CONFIG, hostEnabled: false }),
|
|
140
|
+
).toBe(expected);
|
|
271
141
|
});
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
it("omits file targeting when the page URL has no file", () => {
|
|
284
|
-
const prompt = getRawPrompt(CONNECTION_WITHOUT_FILE, null);
|
|
285
|
-
expect(prompt).toContain("execute-code.sh --url http://localhost:8000");
|
|
286
|
-
expect(prompt).not.toContain("--file");
|
|
287
|
-
expect(prompt).not.toContain("--session");
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
it("omits the token hint when there is no token", () => {
|
|
291
|
-
const prompt = getRawPrompt(CONNECTION, null);
|
|
292
|
-
expect(prompt).not.toContain("--token");
|
|
293
|
-
expect(prompt).not.toContain("auth token");
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
it("includes a file-scoped token hint when a token is present", () => {
|
|
297
|
-
const prompt = getRawPrompt(CONNECTION, "secret-token");
|
|
298
|
-
expect(prompt).toContain(
|
|
299
|
-
"execute-code.sh --url http://localhost:8000 --file notebooks/example.py --token secret-token",
|
|
300
|
-
);
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
it("shell-escapes a token containing a single quote", () => {
|
|
304
|
-
const prompt = getRawPrompt(CONNECTION, "tok'en");
|
|
305
|
-
expect(prompt).toContain(`--token 'tok'"'"'en'`);
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
it("matches the CLI prompt shape", () => {
|
|
309
|
-
const prompt = getRawPrompt(CONNECTION, null);
|
|
310
|
-
expect(prompt).toMatchInlineSnapshot(`
|
|
311
|
-
"Use the /marimo-pair skill to pair-program on a running marimo notebook.
|
|
312
|
-
|
|
313
|
-
Connect to the notebook at: http://localhost:8000 (file notebooks/example.py)
|
|
314
|
-
|
|
315
|
-
Use \`execute-code.sh --url http://localhost:8000 --file notebooks/example.py\` from the marimo-pair skill to execute code in the notebook.
|
|
316
|
-
|
|
317
|
-
Once you are connected, send a fun toast (mo.status.toast(...)) to the user inside marimo letting them know you're ready to pair."
|
|
318
|
-
`);
|
|
142
|
+
it("matches the shared prompt without injected templates", () => {
|
|
143
|
+
vi.stubEnv("DEV", false);
|
|
144
|
+
try {
|
|
145
|
+
expect(getRawPrompt(CONNECTION, "secret-token")).toBe(
|
|
146
|
+
getRawPrompt(CONNECTION, "secret-token", PAIR_CONFIG),
|
|
147
|
+
);
|
|
148
|
+
} finally {
|
|
149
|
+
vi.unstubAllEnvs();
|
|
150
|
+
}
|
|
319
151
|
});
|
|
320
152
|
});
|
|
321
153
|
|
|
@@ -324,12 +156,8 @@ describe("maskToken", () => {
|
|
|
324
156
|
expect(maskToken("ab")).toBe("****");
|
|
325
157
|
expect(maskToken("abcd")).toBe("****");
|
|
326
158
|
});
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
it("caps the number of mask characters at eight", () => {
|
|
333
|
-
expect(maskToken("0123456789abcdef")).toBe("********cdef");
|
|
334
|
-
});
|
|
159
|
+
it("reveals only the last four characters", () =>
|
|
160
|
+
expect(maskToken("abcdefgh")).toBe("****efgh"));
|
|
161
|
+
it("caps mask characters at eight", () =>
|
|
162
|
+
expect(maskToken("0123456789abcdef")).toBe("********cdef"));
|
|
335
163
|
});
|