@marimo-team/islands 0.15.2 → 0.15.4
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-C39nQwtD.js → ConnectedDataExplorerComponent-B68gXlbY.js} +323 -328
- package/dist/{ImageComparisonComponent-BhkiyswP.js → ImageComparisonComponent-Cw1oA8Tn.js} +13 -13
- package/dist/{_baseUniq-DdHL34FO.js → _baseUniq-CQrhBg_9.js} +67 -67
- package/dist/any-language-editor-pzUl6lxp.js +27 -0
- package/dist/{arc-BXrety1g.js → arc-BOhn-m2C.js} +1 -1
- package/dist/{architectureDiagram-KFL7JDKH-BMy6ywCF.js → architectureDiagram-W76B3OCA-DdYf2VnU.js} +144 -144
- package/dist/assets/{worker-COGufAQn.js → worker-BcG8m3h5.js} +33 -29
- package/dist/asterisk-DS281yxp.js +271 -0
- package/dist/{blockDiagram-ZYB65J3Q-DYT2-nlI.js → blockDiagram-QIGZ2CNN-DmmYotkP.js} +10 -10
- package/dist/{c4Diagram-AAMF2YG6-ZiQzioe6.js → c4Diagram-FPNF74CW-Dyz4zMHJ.js} +8 -8
- package/dist/{channel-CeuXqUAU.js → channel-CCL8jXAe.js} +1 -1
- package/dist/{chunk-ANTBXLJU-BvYnIrdq.js → chunk-4BX2VUAB-BfKwWLfJ.js} +1 -1
- package/dist/{chunk-WVR4S24B-DXj8yaUk.js → chunk-55IACEB6-CFQU7zSp.js} +1 -1
- package/dist/{chunk-GLLZNHP4-CyFsosAe.js → chunk-FMBD7UC4-DDjZzUcl.js} +1 -1
- package/dist/{chunk-JBRWN2VN-DA_EEhy2.js → chunk-K7UQS3LO-nBRjBU1H.js} +117 -117
- package/dist/{chunk-NRVI72HA-BYx2jMlI.js → chunk-QN33PNHL-B-g8sJzl.js} +1 -1
- package/dist/{chunk-FHKO5MBM-DfCztBk8.js → chunk-QZHKN3VN-B7QSJS3J.js} +1 -1
- package/dist/{chunk-LXBSTHXV-Se7vdY6J.js → chunk-TVAH2DTR-pGXll4d1.js} +7 -7
- package/dist/{chunk-OMD6QJNC-CqgcPMgL.js → chunk-TZMSLE5B-Dx9h-1mv.js} +1 -1
- package/dist/{classDiagram-v2-QTMF73CY-B19A3G1l.js → classDiagram-KNZD7YFC-BXryI7DY.js} +2 -2
- package/dist/{classDiagram-3BZAVTQC-B19A3G1l.js → classDiagram-v2-RKCZMP56-BXryI7DY.js} +2 -2
- package/dist/{clone-78au0tn1.js → clone-DqwV7ges.js} +1 -1
- package/dist/cose-bilkent-S5V4N54A-Di6FNMXz.js +2609 -0
- package/dist/{cytoscape.esm-BYnVVhJX.js → cytoscape.esm-DfdJODL8.js} +34 -34
- package/dist/{dagre-2BBEFEWP-BfEn3ZUV.js → dagre-5GWH7T2D-BTZPMTey.js} +6 -6
- package/dist/{data-grid-overlay-editor-CH_qLkV2.js → data-grid-overlay-editor-ryatXXby.js} +11 -11
- package/dist/{diagram-4IRLE6MV-CL8xidnG.js → diagram-N5W7TBWH-D79_zdOu.js} +59 -60
- package/dist/{diagram-RP2FKANI-B1BPcUew.js → diagram-QEK2KX5R-DX2A_SD0.js} +15 -15
- package/dist/{diagram-GUPCWM2R-CZ5cfqlq.js → diagram-S2PKOQOG-DM6VMTrJ.js} +10 -10
- package/dist/dockerfile-BoowzQlp.js +194 -0
- package/dist/ebnf-DUPDuY4r.js +78 -0
- package/dist/{erDiagram-HZWUO2LU-BEAIww50.js → erDiagram-AWTI2OKA-BBirxtlI.js} +8 -8
- package/dist/fcl-CPC2WYrI.js +103 -0
- package/dist/{flowDiagram-THRYKUMA-Czs2UAI2.js → flowDiagram-PVAE7QVJ-DyVweEMs.js} +9 -9
- package/dist/{ganttDiagram-WV7ZQ7D5-ByYIAVFO.js → ganttDiagram-OWAHRB6G-DTB7FX7r.js} +34 -34
- package/dist/{gitGraphDiagram-OJR772UL-BcpDsiyB.js → gitGraphDiagram-NY62KEGX-BrbIb5pD.js} +4 -4
- package/dist/{glide-data-editor-CmN6FVyi.js → glide-data-editor-DhMX4nmM.js} +33 -33
- package/dist/{graph-77W6heli.js → graph-CuLSrclI.js} +3 -3
- package/dist/http-D9LttvKF.js +44 -0
- package/dist/{index-BOojn38D.js → index-BNgdUQ2e.js} +7711 -7711
- package/dist/index-DIy6LHLJ.js +98 -0
- package/dist/{index-CmozKMxx.js → index-Df2dsx1t.js} +6 -6
- package/dist/{index-pBmAzQJl.js → index-MCx5v1x0.js} +2 -2
- package/dist/{index-Bfk9dnyS.js → index-cz_xaKvT.js} +33090 -32892
- package/dist/{infoDiagram-6WOFNB3A-CfzLHHVP.js → infoDiagram-STP46IZ2-CCBHc7-K.js} +2 -2
- package/dist/{journeyDiagram-FFXJYRFH-ndAcpkGn.js → journeyDiagram-BIP6EPQ6-LhGSj54j.js} +24 -26
- package/dist/{kanban-definition-KOZQBZVT-DcQYzNvc.js → kanban-definition-6OIFK2YF-aegTMFS6.js} +14 -14
- package/dist/{layout-XySVHJgD.js → layout-BEARWMhl.js} +81 -81
- package/dist/{linear-PbooOqg7.js → linear-fbJq6cdO.js} +35 -35
- package/dist/{main-B5yML0bw.js → main-HerZgEhd.js} +76533 -69945
- package/dist/main.js +1 -1
- package/dist/{mermaid-Cg5IX6Nv.js → mermaid-DxPYK0KX.js} +6160 -7493
- package/dist/min-DBJkhObB.js +80 -0
- package/dist/mindmap-definition-Q6HEUPPD-A3Fh5XDZ.js +785 -0
- package/dist/nginx-zDPm3Z74.js +89 -0
- package/dist/{number-overlay-editor-DUhfZqtP.js → number-overlay-editor-USMrY6k3.js} +19 -19
- package/dist/{pieDiagram-DBDJKBY4-DTOlNsja.js → pieDiagram-ADFJNKIX-Q9uFlCV0.js} +17 -17
- package/dist/{quadrantDiagram-YPSRARAO-BX2d8VS-.js → quadrantDiagram-LMRXKWRM-BuPh-qpK.js} +6 -6
- package/dist/{react-plotly-Dcyw-3Sa.js → react-plotly-HSqJPRfa.js} +3577 -3577
- package/dist/{requirementDiagram-EGVEC5DT-D1T5u-wG.js → requirementDiagram-4UW4RH46-CHROYNU_.js} +7 -7
- package/dist/{sankeyDiagram-HRAUVNP4-G6xDfnp-.js → sankeyDiagram-GR3RE2ED-DkUqHP2d.js} +5 -5
- package/dist/sequenceDiagram-C3RYC4MD-YoPTMplP.js +2519 -0
- package/dist/{slides-component-BJLlPJSr.js → slides-component-D7CHSR00.js} +66 -66
- package/dist/solr-BNlsLglM.js +41 -0
- package/dist/spreadsheet-C-cy4P5N.js +49 -0
- package/dist/{stateDiagram-UUKSUZ4H-CYXbjaom.js → stateDiagram-KXAO66HF-DEN00mVU.js} +5 -5
- package/dist/{stateDiagram-v2-EYPG3UTE-Br1HYKT6.js → stateDiagram-v2-UMBNRL4Z-DlQqSUAa.js} +2 -2
- package/dist/style.css +1 -1
- package/dist/tiddlywiki-5wqsXtSk.js +155 -0
- package/dist/tiki-__Kn3CeS.js +181 -0
- package/dist/{time-B9SZnSen.js → time-BtVcKqeD.js} +58 -58
- package/dist/{timeline-definition-3HZDQTIS-DeK_ZRD0.js → timeline-definition-XQNQX7LJ-DEteLt8D.js} +10 -12
- package/dist/{timer-BYwnU4DF.js → timer-B0-z63CM.js} +16 -16
- package/dist/{treemap-75Q7IDZK-CKP4vV_0.js → treemap-75Q7IDZK-8S6podme.js} +14 -14
- package/dist/{vega-component-CpgdqX2d.js → vega-component-D35L45kI.js} +30 -30
- package/dist/{xychartDiagram-FDP5SA34-AMEPsx_R.js → xychartDiagram-6GGTOJPD-DKwGThyy.js} +7 -7
- package/package.json +44 -41
- package/src/__mocks__/notebook.ts +3 -0
- package/src/__mocks__/requests.ts +3 -0
- package/src/__tests__/__snapshots__/CellStatus.test.tsx.snap +12 -12
- package/src/__tests__/chat-utils.test.ts +26 -211
- package/src/components/ai/ai-model-dropdown.tsx +25 -9
- package/src/components/ai/ai-provider-icon.tsx +5 -1
- package/src/components/app-config/ai-config.tsx +7 -0
- package/src/components/chat/acp/__tests__/__snapshots__/prompt.test.ts.snap +304 -0
- package/src/components/chat/acp/__tests__/atoms.test.ts +56 -0
- package/src/components/chat/acp/__tests__/prompt.test.ts +12 -0
- package/src/components/chat/acp/__tests__/state.test.ts +621 -0
- package/src/components/chat/acp/agent-docs.tsx +78 -0
- package/src/components/chat/acp/agent-panel.css +23 -0
- package/src/components/chat/acp/agent-panel.tsx +715 -0
- package/src/components/chat/acp/agent-selector.tsx +138 -0
- package/src/components/chat/acp/blocks.tsx +664 -0
- package/src/components/chat/acp/common.tsx +198 -0
- package/src/components/chat/acp/prompt.ts +284 -0
- package/src/components/chat/acp/scroll-to-bottom-button.tsx +50 -0
- package/src/components/chat/acp/session-tabs.tsx +138 -0
- package/src/components/chat/acp/state.ts +263 -0
- package/src/components/chat/acp/thread.tsx +121 -0
- package/src/components/chat/acp/types.ts +63 -0
- package/src/components/chat/acp/utils.ts +45 -0
- package/src/components/chat/chat-components.tsx +71 -0
- package/src/components/chat/chat-panel.tsx +481 -291
- package/src/components/chat/chat-utils.ts +50 -0
- package/src/components/chat/markdown-renderer.tsx +3 -7
- package/src/components/chat/tool-call-accordion.tsx +6 -6
- package/src/components/datasources/__tests__/utils.test.ts +6 -0
- package/src/components/datasources/column-preview.tsx +1 -3
- package/src/components/editor/actions/useNotebookActions.tsx +1 -1
- package/src/components/editor/ai/add-cell-with-ai.tsx +20 -15
- package/src/components/editor/ai/ai-completion-editor.tsx +22 -3
- package/src/components/editor/ai/completion-handlers.tsx +2 -4
- package/src/components/editor/ai/completion-utils.ts +85 -11
- package/src/components/editor/alerts/startup-logs-alert.tsx +72 -0
- package/src/components/editor/chrome/panels/datasources-panel.tsx +3 -1
- package/src/components/editor/chrome/panels/dependency-graph-panel.tsx +3 -1
- package/src/components/editor/chrome/panels/documentation-panel.tsx +3 -1
- package/src/components/editor/chrome/panels/error-panel.tsx +3 -1
- package/src/components/editor/chrome/panels/file-explorer-panel.tsx +3 -1
- package/src/components/editor/chrome/panels/logs-panel.tsx +3 -1
- package/src/components/editor/chrome/panels/outline-panel.tsx +3 -1
- package/src/components/editor/chrome/panels/packages-panel.tsx +4 -2
- package/src/components/editor/chrome/panels/scratchpad-panel.tsx +3 -1
- package/src/components/editor/chrome/panels/secrets-panel.tsx +3 -1
- package/src/components/editor/chrome/panels/snippets-panel.tsx +3 -1
- package/src/components/editor/chrome/panels/tracing-panel.tsx +3 -1
- package/src/components/editor/chrome/panels/variable-panel.tsx +3 -1
- package/src/components/editor/chrome/types.ts +10 -0
- package/src/components/editor/chrome/wrapper/app-chrome.tsx +55 -31
- package/src/components/editor/controls/command-palette-button.tsx +1 -1
- package/src/components/editor/controls/command-palette.tsx +5 -4
- package/src/components/editor/controls/state.ts +4 -0
- package/src/components/editor/package-alert.tsx +108 -58
- package/src/components/editor/renderers/CellArray.tsx +2 -0
- package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +0 -1
- package/src/components/pages/edit-page.tsx +7 -3
- package/src/core/ai/chat-utils.ts +26 -43
- package/src/core/ai/config.ts +1 -1
- package/src/core/ai/context/__tests__/registry.test.ts +277 -3
- package/src/core/ai/context/context.ts +11 -1
- package/src/core/ai/context/providers/__tests__/cell-output.test.ts +378 -0
- package/src/core/ai/context/providers/__tests__/error.test.ts +3 -2
- package/src/core/ai/context/providers/__tests__/file.test.ts +119 -0
- package/src/core/ai/context/providers/cell-output.ts +349 -0
- package/src/core/ai/context/providers/common.ts +5 -1
- package/src/core/ai/context/providers/file.ts +287 -0
- package/src/core/ai/context/registry.ts +79 -0
- package/src/core/ai/state.ts +22 -5
- package/src/core/alerts/state.ts +71 -3
- package/src/core/cells/cell.ts +2 -2
- package/src/core/cells/cells.ts +1 -1
- package/src/core/cells/logs.ts +1 -1
- package/src/core/cells/runs.ts +6 -5
- package/src/core/codemirror/ai/resources.ts +47 -5
- package/src/core/codemirror/ai/state.ts +12 -0
- package/src/core/codemirror/language/__tests__/sql.test.ts +45 -0
- package/src/core/codemirror/markdown/__tests__/commands.test.ts +1 -0
- package/src/core/codemirror/theme/dark.ts +1 -1
- package/src/core/config/capabilities.ts +1 -1
- package/src/core/config/feature-flag.tsx +2 -0
- package/src/core/datasets/__tests__/data-source.test.ts +24 -0
- package/src/core/errors/__tests__/errors.test.ts +2 -0
- package/src/core/islands/bridge.ts +1 -0
- package/src/core/islands/main.ts +1 -0
- package/src/core/kernel/messages.ts +12 -6
- package/src/core/layout/layout.ts +3 -3
- package/src/core/network/requests-network.ts +8 -0
- package/src/core/network/requests-static.ts +1 -0
- package/src/core/network/requests-toasting.ts +1 -0
- package/src/core/network/types.ts +4 -1
- package/src/core/wasm/bridge.ts +18 -2
- package/src/core/wasm/worker/bootstrap.ts +3 -1
- package/src/core/wasm/worker/getMarimoWheel.ts +3 -8
- package/src/core/wasm/worker/types.ts +3 -0
- package/src/core/websocket/useMarimoWebSocket.tsx +7 -1
- package/src/css/app/Cell.css +42 -21
- package/src/css/app/codemirror.css +5 -1
- package/src/css/globals.css +3 -0
- package/src/css/md.css +1 -1
- package/src/plugins/impl/MicrophonePlugin.tsx +2 -2
- package/src/plugins/impl/chat/ChatPlugin.tsx +2 -9
- package/src/plugins/impl/chat/chat-ui.tsx +129 -110
- package/src/plugins/impl/chat/types.ts +5 -8
- package/src/plugins/impl/code/__tests__/language.test.ts +15 -0
- package/src/plugins/impl/code/any-language-editor.tsx +11 -8
- package/src/plugins/impl/vega/vega.css +121 -0
- package/src/plugins/layout/MimeRenderPlugin.tsx +3 -6
- package/src/stories/cell.stories.tsx +6 -0
- package/src/stories/layout/vertical/one-column.stories.tsx +215 -0
- package/src/theme/useTheme.ts +11 -6
- package/src/utils/Logger.ts +5 -6
- package/src/utils/__tests__/blob.test.ts +37 -0
- package/src/utils/arrays.ts +13 -0
- package/src/utils/fileToBase64.ts +21 -6
- package/src/utils/json/base64.ts +5 -2
- package/src/utils/numbers.ts +9 -7
- package/dist/any-language-editor-DC5170DQ.js +0 -45
- package/dist/asn1-jKiBa2Ya.js +0 -95
- package/dist/clojure-CCKyeQKf.js +0 -800
- package/dist/css-BkF-NPzE.js +0 -1553
- package/dist/index-5ZH_qS8j.js +0 -288
- package/dist/index-U4yn89qO.js +0 -341
- package/dist/javascript-C2yteZeJ.js +0 -691
- package/dist/min-DS5Jz-hg.js +0 -80
- package/dist/mindmap-definition-LNHGMQRG-0aOVaMR8.js +0 -3234
- package/dist/mllike-BSnXJBGA.js +0 -272
- package/dist/pug-CwAQJzGR.js +0 -248
- package/dist/python-BkR3uSy8.js +0 -313
- package/dist/rpm-IznJm2Xc.js +0 -57
- package/dist/sequenceDiagram-WFGC7UMF-DMhHzllb.js +0 -2284
- package/dist/ttcn-cfg-Bac_acMi.js +0 -88
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { Mocks } from "@/__mocks__/common";
|
|
5
|
+
|
|
6
|
+
// Mock the external dependencies
|
|
7
|
+
vi.mock("html-to-image", () => ({
|
|
8
|
+
toPng: vi.fn().mockResolvedValue("data:image/png;base64,mockbase64data"),
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
vi.mock("@/utils/Logger", () => ({
|
|
12
|
+
Logger: Mocks.quietLogger(),
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
import type { NotebookState } from "@/core/cells/cells";
|
|
16
|
+
import type { CellId } from "@/core/cells/ids";
|
|
17
|
+
import type { OutputMessage } from "@/core/kernel/messages";
|
|
18
|
+
import type { JotaiStore } from "@/core/state/jotai";
|
|
19
|
+
import { CellOutputContextProvider } from "../cell-output";
|
|
20
|
+
|
|
21
|
+
// Test helper to create mock store
|
|
22
|
+
function createMockStore(notebook: NotebookState): JotaiStore {
|
|
23
|
+
const store = {
|
|
24
|
+
get: vi.fn().mockReturnValue(notebook),
|
|
25
|
+
} as unknown as JotaiStore;
|
|
26
|
+
return store;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe("CellOutputContextProvider", () => {
|
|
30
|
+
let provider: CellOutputContextProvider;
|
|
31
|
+
let mockStore: JotaiStore;
|
|
32
|
+
let mockNotebook: NotebookState;
|
|
33
|
+
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
// Create a basic mock notebook state
|
|
36
|
+
mockNotebook = {
|
|
37
|
+
cellIds: {
|
|
38
|
+
inOrderIds: ["cell1" as CellId, "cell2" as CellId, "cell3" as CellId],
|
|
39
|
+
},
|
|
40
|
+
cellData: {
|
|
41
|
+
cell1: {
|
|
42
|
+
id: "cell1" as CellId,
|
|
43
|
+
name: "My Cell",
|
|
44
|
+
code: "print('hello world')",
|
|
45
|
+
},
|
|
46
|
+
cell2: {
|
|
47
|
+
id: "cell2" as CellId,
|
|
48
|
+
name: "",
|
|
49
|
+
code: "import matplotlib.pyplot as plt\nplt.plot([1,2,3])",
|
|
50
|
+
},
|
|
51
|
+
cell3: {
|
|
52
|
+
id: "cell3" as CellId,
|
|
53
|
+
name: "Empty Cell",
|
|
54
|
+
code: "# no output",
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
cellRuntime: {
|
|
58
|
+
cell1: {
|
|
59
|
+
output: {
|
|
60
|
+
mimetype: "text/plain",
|
|
61
|
+
data: "hello world",
|
|
62
|
+
} as OutputMessage,
|
|
63
|
+
},
|
|
64
|
+
cell2: {
|
|
65
|
+
output: {
|
|
66
|
+
mimetype: "image/png",
|
|
67
|
+
data: "base64imagedata",
|
|
68
|
+
} as OutputMessage,
|
|
69
|
+
},
|
|
70
|
+
cell3: {
|
|
71
|
+
output: null,
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
} as unknown as NotebookState;
|
|
75
|
+
|
|
76
|
+
mockStore = createMockStore(mockNotebook);
|
|
77
|
+
provider = new CellOutputContextProvider(mockStore);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe("basic properties", () => {
|
|
81
|
+
it("should have correct title and context type", () => {
|
|
82
|
+
expect(provider.title).toBe("Cell Outputs");
|
|
83
|
+
expect(provider.contextType).toBe("cell-output");
|
|
84
|
+
expect(provider.mentionPrefix).toBe("@");
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe("getItems", () => {
|
|
89
|
+
it("should return only cells with outputs", () => {
|
|
90
|
+
const items = provider.getItems();
|
|
91
|
+
|
|
92
|
+
// Should have 2 items (cell1 and cell2, but not cell3 which has no output)
|
|
93
|
+
expect(items).toHaveLength(2);
|
|
94
|
+
|
|
95
|
+
const cellIds = items.map((item) => item.data.cellId);
|
|
96
|
+
expect(cellIds).toContain("cell1");
|
|
97
|
+
expect(cellIds).toContain("cell2");
|
|
98
|
+
expect(cellIds).not.toContain("cell3");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("should correctly identify text vs media outputs", () => {
|
|
102
|
+
const items = provider.getItems();
|
|
103
|
+
|
|
104
|
+
const textItem = items.find((item) => item.data.cellId === "cell1");
|
|
105
|
+
const mediaItem = items.find((item) => item.data.cellId === "cell2");
|
|
106
|
+
|
|
107
|
+
expect(textItem?.data.outputType).toBe("text");
|
|
108
|
+
expect(mediaItem?.data.outputType).toBe("media");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("should process text content correctly", () => {
|
|
112
|
+
const items = provider.getItems();
|
|
113
|
+
const textItem = items.find((item) => item.data.cellId === "cell1");
|
|
114
|
+
|
|
115
|
+
expect(textItem?.data.processedContent).toBe("hello world");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("should mark media items for download when no direct URL available", () => {
|
|
119
|
+
const items = provider.getItems();
|
|
120
|
+
const mediaItem = items.find((item) => item.data.cellId === "cell2");
|
|
121
|
+
|
|
122
|
+
expect(mediaItem?.data.shouldDownloadImage).toBe(true);
|
|
123
|
+
expect(mediaItem?.data.imageUrl).toBeUndefined();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("should include cell code and names", () => {
|
|
127
|
+
const items = provider.getItems();
|
|
128
|
+
|
|
129
|
+
const item1 = items.find((item) => item.data.cellId === "cell1");
|
|
130
|
+
const item2 = items.find((item) => item.data.cellId === "cell2");
|
|
131
|
+
|
|
132
|
+
expect(item1?.data.cellName).toBe("My Cell");
|
|
133
|
+
expect(item1?.data.cellCode).toBe("print('hello world')");
|
|
134
|
+
|
|
135
|
+
expect(item2?.data.cellName).toBe("cell-1");
|
|
136
|
+
expect(item2?.data.cellCode).toBe(
|
|
137
|
+
"import matplotlib.pyplot as plt\nplt.plot([1,2,3])",
|
|
138
|
+
);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe("formatContext", () => {
|
|
143
|
+
it("should format text output context correctly", () => {
|
|
144
|
+
const items = provider.getItems();
|
|
145
|
+
const textItem = items.find((item) => item.data.cellId === "cell1");
|
|
146
|
+
|
|
147
|
+
if (!textItem) {
|
|
148
|
+
throw new Error("Text item not found");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const context = provider.formatContext(textItem);
|
|
152
|
+
|
|
153
|
+
expect(context).toContain("cell-output");
|
|
154
|
+
expect(context).toContain("My Cell");
|
|
155
|
+
expect(context).toContain("Cell Code:");
|
|
156
|
+
expect(context).toContain("print('hello world')");
|
|
157
|
+
expect(context).toContain("Output:");
|
|
158
|
+
expect(context).toContain("hello world");
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("should format media output context correctly", () => {
|
|
162
|
+
const items = provider.getItems();
|
|
163
|
+
const mediaItem = items.find((item) => item.data.cellId === "cell2");
|
|
164
|
+
|
|
165
|
+
if (!mediaItem) {
|
|
166
|
+
throw new Error("Media item not found");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const context = provider.formatContext(mediaItem);
|
|
170
|
+
|
|
171
|
+
expect(context).toContain("cell-output");
|
|
172
|
+
expect(context).toContain("cell-1");
|
|
173
|
+
expect(context).toContain("Cell Code:");
|
|
174
|
+
expect(context).toContain("import matplotlib.pyplot as plt");
|
|
175
|
+
expect(context).toContain("Media Output: Contains image/png content");
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
describe("formatCompletion", () => {
|
|
180
|
+
it("should create proper completion object", () => {
|
|
181
|
+
const items = provider.getItems();
|
|
182
|
+
const item = items[0];
|
|
183
|
+
|
|
184
|
+
const completion = provider.formatCompletion(item);
|
|
185
|
+
|
|
186
|
+
expect(completion.label).toMatch(/@.+/);
|
|
187
|
+
expect(completion.displayLabel).toBe(item.data.cellName);
|
|
188
|
+
expect(completion.detail).toContain("output");
|
|
189
|
+
expect(completion.type).toBe("cell-output");
|
|
190
|
+
expect(completion.section).toBe("Cell Output");
|
|
191
|
+
expect(typeof completion.info).toBe("function");
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// Test utility functions separately
|
|
197
|
+
describe("Cell output utility functions", () => {
|
|
198
|
+
// We need to extract these functions to test them, or make them exported
|
|
199
|
+
// For now, let's test through the public interface
|
|
200
|
+
|
|
201
|
+
describe("media type detection", () => {
|
|
202
|
+
let provider: CellOutputContextProvider;
|
|
203
|
+
let mockStore: JotaiStore;
|
|
204
|
+
|
|
205
|
+
beforeEach(() => {
|
|
206
|
+
const mockNotebook = {
|
|
207
|
+
cellIds: { inOrderIds: [] },
|
|
208
|
+
cellData: {},
|
|
209
|
+
cellRuntime: {},
|
|
210
|
+
} as unknown as NotebookState;
|
|
211
|
+
|
|
212
|
+
mockStore = createMockStore(mockNotebook);
|
|
213
|
+
provider = new CellOutputContextProvider(mockStore);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it("should detect image mimetypes as media", () => {
|
|
217
|
+
const testCases = [
|
|
218
|
+
{ mimetype: "image/png", data: "test", expected: "media" },
|
|
219
|
+
{ mimetype: "image/jpeg", data: "test", expected: "media" },
|
|
220
|
+
{ mimetype: "image/gif", data: "test", expected: "media" },
|
|
221
|
+
{ mimetype: "image/svg+xml", data: "test", expected: "media" },
|
|
222
|
+
];
|
|
223
|
+
|
|
224
|
+
for (const testCase of testCases) {
|
|
225
|
+
mockStore = createMockStore({
|
|
226
|
+
cellIds: { inOrderIds: ["test" as CellId] },
|
|
227
|
+
cellData: {
|
|
228
|
+
test: { id: "test" as CellId, name: "", code: "" },
|
|
229
|
+
},
|
|
230
|
+
cellRuntime: {
|
|
231
|
+
test: {
|
|
232
|
+
output: {
|
|
233
|
+
mimetype: testCase.mimetype,
|
|
234
|
+
data: testCase.data,
|
|
235
|
+
} as OutputMessage,
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
} as unknown as NotebookState);
|
|
239
|
+
|
|
240
|
+
provider = new CellOutputContextProvider(mockStore);
|
|
241
|
+
const items = provider.getItems();
|
|
242
|
+
|
|
243
|
+
expect(items[0]?.data.outputType).toBe(testCase.expected);
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it("should detect HTML with media tags as media", () => {
|
|
248
|
+
const htmlWithImage = '<div><img src="test.png" /></div>';
|
|
249
|
+
const htmlWithCanvas = '<canvas width="100" height="100"></canvas>';
|
|
250
|
+
const htmlWithSvg = '<svg><circle r="50" /></svg>';
|
|
251
|
+
|
|
252
|
+
const testCases = [
|
|
253
|
+
{ data: htmlWithImage, expected: "media" },
|
|
254
|
+
{ data: htmlWithCanvas, expected: "media" },
|
|
255
|
+
{ data: htmlWithSvg, expected: "media" },
|
|
256
|
+
{ data: "<p>Just text</p>", expected: "text" },
|
|
257
|
+
];
|
|
258
|
+
|
|
259
|
+
for (const testCase of testCases) {
|
|
260
|
+
mockStore = createMockStore({
|
|
261
|
+
cellIds: { inOrderIds: ["test" as CellId] },
|
|
262
|
+
cellData: {
|
|
263
|
+
test: { id: "test" as CellId, name: "", code: "" },
|
|
264
|
+
},
|
|
265
|
+
cellRuntime: {
|
|
266
|
+
test: {
|
|
267
|
+
output: {
|
|
268
|
+
mimetype: "text/html",
|
|
269
|
+
data: testCase.data,
|
|
270
|
+
} as OutputMessage,
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
} as unknown as NotebookState);
|
|
274
|
+
|
|
275
|
+
provider = new CellOutputContextProvider(mockStore);
|
|
276
|
+
const items = provider.getItems();
|
|
277
|
+
|
|
278
|
+
expect(items[0]?.data.outputType).toBe(testCase.expected);
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it("should detect text mimetypes correctly", () => {
|
|
283
|
+
const testCases = [
|
|
284
|
+
{ mimetype: "text/plain", expected: "text" },
|
|
285
|
+
{
|
|
286
|
+
mimetype: "text/html",
|
|
287
|
+
data: "<p>No media tags</p>",
|
|
288
|
+
expected: "text",
|
|
289
|
+
},
|
|
290
|
+
{ mimetype: "application/json", expected: "text" },
|
|
291
|
+
];
|
|
292
|
+
|
|
293
|
+
for (const testCase of testCases) {
|
|
294
|
+
mockStore = createMockStore({
|
|
295
|
+
cellIds: { inOrderIds: ["test" as CellId] },
|
|
296
|
+
cellData: {
|
|
297
|
+
test: { id: "test" as CellId, name: "", code: "" },
|
|
298
|
+
},
|
|
299
|
+
cellRuntime: {
|
|
300
|
+
test: {
|
|
301
|
+
output: {
|
|
302
|
+
mimetype: testCase.mimetype,
|
|
303
|
+
data: testCase.data || "test data",
|
|
304
|
+
} as OutputMessage,
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
} as unknown as NotebookState);
|
|
308
|
+
|
|
309
|
+
provider = new CellOutputContextProvider(mockStore);
|
|
310
|
+
const items = provider.getItems();
|
|
311
|
+
|
|
312
|
+
expect(items[0]?.data.outputType).toBe(testCase.expected);
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
describe("HTML content parsing", () => {
|
|
318
|
+
let provider: CellOutputContextProvider;
|
|
319
|
+
let mockStore: JotaiStore;
|
|
320
|
+
|
|
321
|
+
beforeEach(() => {
|
|
322
|
+
// Mock DOM methods for HTML parsing
|
|
323
|
+
const mockDiv = {
|
|
324
|
+
innerHTML: "",
|
|
325
|
+
textContent: "",
|
|
326
|
+
innerText: "",
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
global.document = {
|
|
330
|
+
createElement: vi.fn().mockReturnValue(mockDiv),
|
|
331
|
+
} as unknown as Document;
|
|
332
|
+
|
|
333
|
+
const mockNotebook = {
|
|
334
|
+
cellIds: { inOrderIds: [] },
|
|
335
|
+
cellData: {},
|
|
336
|
+
cellRuntime: {},
|
|
337
|
+
} as unknown as NotebookState;
|
|
338
|
+
|
|
339
|
+
mockStore = createMockStore(mockNotebook);
|
|
340
|
+
provider = new CellOutputContextProvider(mockStore);
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it("should extract text content from HTML", () => {
|
|
344
|
+
const htmlContent = "<p>Hello <strong>world</strong>!</p>";
|
|
345
|
+
|
|
346
|
+
// Mock the DOM parsing result
|
|
347
|
+
const mockDiv = {
|
|
348
|
+
innerHTML: "",
|
|
349
|
+
textContent: "Hello world!",
|
|
350
|
+
innerText: "Hello world!",
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
(
|
|
354
|
+
global.document.createElement as ReturnType<typeof vi.fn>
|
|
355
|
+
).mockReturnValue(mockDiv);
|
|
356
|
+
|
|
357
|
+
mockStore = createMockStore({
|
|
358
|
+
cellIds: { inOrderIds: ["test" as CellId] },
|
|
359
|
+
cellData: {
|
|
360
|
+
test: { id: "test" as CellId, name: "", code: "" },
|
|
361
|
+
},
|
|
362
|
+
cellRuntime: {
|
|
363
|
+
test: {
|
|
364
|
+
output: {
|
|
365
|
+
mimetype: "text/html",
|
|
366
|
+
data: htmlContent,
|
|
367
|
+
} as OutputMessage,
|
|
368
|
+
},
|
|
369
|
+
},
|
|
370
|
+
} as unknown as NotebookState);
|
|
371
|
+
|
|
372
|
+
provider = new CellOutputContextProvider(mockStore);
|
|
373
|
+
const items = provider.getItems();
|
|
374
|
+
|
|
375
|
+
expect(items[0]?.data.processedContent).toBe("Hello world!");
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
});
|
|
@@ -6,6 +6,7 @@ import { beforeEach, describe, expect, it } from "vitest";
|
|
|
6
6
|
import { MockNotebook } from "@/__mocks__/notebook";
|
|
7
7
|
import { notebookAtom } from "@/core/cells/cells";
|
|
8
8
|
import { type CellId, CellId as CellIdClass } from "@/core/cells/ids";
|
|
9
|
+
import { Boosts } from "../common";
|
|
9
10
|
import { ErrorContextProvider } from "../error";
|
|
10
11
|
|
|
11
12
|
describe("ErrorContextProvider", () => {
|
|
@@ -121,7 +122,7 @@ describe("ErrorContextProvider", () => {
|
|
|
121
122
|
label: "@Errors",
|
|
122
123
|
displayLabel: "Errors",
|
|
123
124
|
detail: "2 errors",
|
|
124
|
-
boost:
|
|
125
|
+
boost: Boosts.ERROR,
|
|
125
126
|
type: "error",
|
|
126
127
|
apply: "@Errors",
|
|
127
128
|
section: "Errors",
|
|
@@ -181,7 +182,7 @@ describe("ErrorContextProvider", () => {
|
|
|
181
182
|
expect(completion).toMatchObject({
|
|
182
183
|
label: "Error",
|
|
183
184
|
displayLabel: "Error",
|
|
184
|
-
boost:
|
|
185
|
+
boost: Boosts.ERROR,
|
|
185
186
|
});
|
|
186
187
|
});
|
|
187
188
|
});
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import type { CompletionContext } from "@codemirror/autocomplete";
|
|
4
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
import type { EditRequests, FileInfo, RunRequests } from "@/core/network/types";
|
|
6
|
+
import { FileContextProvider } from "../file";
|
|
7
|
+
|
|
8
|
+
describe("FileContextProvider", () => {
|
|
9
|
+
let provider: FileContextProvider;
|
|
10
|
+
let mockApiRequests: Partial<EditRequests & RunRequests>;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
mockApiRequests = {
|
|
14
|
+
sendSearchFiles: vi.fn(),
|
|
15
|
+
};
|
|
16
|
+
provider = new FileContextProvider(
|
|
17
|
+
mockApiRequests as EditRequests & RunRequests,
|
|
18
|
+
);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("should have correct provider properties", () => {
|
|
22
|
+
expect(provider.title).toBe("Files");
|
|
23
|
+
expect(provider.mentionPrefix).toBe("#");
|
|
24
|
+
expect(provider.contextType).toBe("file");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("should return empty items array for static getItems", () => {
|
|
28
|
+
expect(provider.getItems()).toEqual([]);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("should format context item correctly", () => {
|
|
32
|
+
const item = {
|
|
33
|
+
uri: provider.asURI("/path/to/file.py"),
|
|
34
|
+
name: "file.py",
|
|
35
|
+
type: "file" as const,
|
|
36
|
+
description: "Python file",
|
|
37
|
+
data: {
|
|
38
|
+
path: "/path/to/file.py",
|
|
39
|
+
isDirectory: false,
|
|
40
|
+
isMarimoFile: false,
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const context = provider.formatContext(item);
|
|
45
|
+
expect(context).toContain("file");
|
|
46
|
+
expect(context).toContain("file.py");
|
|
47
|
+
expect(context).toContain("/path/to/file.py");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("should create completion source that returns null for no match", async () => {
|
|
51
|
+
const completionSource = provider.createCompletionSource();
|
|
52
|
+
const mockContext = {
|
|
53
|
+
matchBefore: vi.fn().mockReturnValue(null),
|
|
54
|
+
} as unknown as CompletionContext;
|
|
55
|
+
|
|
56
|
+
const result = await completionSource(mockContext);
|
|
57
|
+
expect(result).toBeNull();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("should create completion source that searches files for matches", async () => {
|
|
61
|
+
const mockFiles: FileInfo[] = [
|
|
62
|
+
{
|
|
63
|
+
id: "1",
|
|
64
|
+
path: "/src/app.py",
|
|
65
|
+
name: "app.py",
|
|
66
|
+
isDirectory: false,
|
|
67
|
+
isMarimoFile: false,
|
|
68
|
+
lastModified: Date.now(),
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
id: "2",
|
|
72
|
+
path: "/src/utils",
|
|
73
|
+
name: "utils",
|
|
74
|
+
isDirectory: true,
|
|
75
|
+
isMarimoFile: false,
|
|
76
|
+
lastModified: Date.now(),
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
vi.mocked(mockApiRequests.sendSearchFiles!).mockResolvedValue({
|
|
81
|
+
files: mockFiles,
|
|
82
|
+
query: "app",
|
|
83
|
+
totalFound: 2,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const completionSource = provider.createCompletionSource();
|
|
87
|
+
const mockContext = {
|
|
88
|
+
matchBefore: vi.fn().mockReturnValue({
|
|
89
|
+
text: "#app",
|
|
90
|
+
from: 0,
|
|
91
|
+
}),
|
|
92
|
+
} as unknown as CompletionContext;
|
|
93
|
+
|
|
94
|
+
const result = await completionSource(mockContext);
|
|
95
|
+
|
|
96
|
+
expect(result).not.toBeNull();
|
|
97
|
+
expect(result!.from).toBe(0);
|
|
98
|
+
expect(result!.options).toHaveLength(2);
|
|
99
|
+
expect(result!.options[0].label).toContain("app.py");
|
|
100
|
+
expect(result!.options[0].displayLabel).toContain("app.py");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("should handle search errors gracefully", async () => {
|
|
104
|
+
vi.mocked(mockApiRequests.sendSearchFiles!).mockRejectedValue(
|
|
105
|
+
new Error("Search failed"),
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const completionSource = provider.createCompletionSource();
|
|
109
|
+
const mockContext = {
|
|
110
|
+
matchBefore: vi.fn().mockReturnValue({
|
|
111
|
+
text: "#test",
|
|
112
|
+
from: 0,
|
|
113
|
+
}),
|
|
114
|
+
} as unknown as CompletionContext;
|
|
115
|
+
|
|
116
|
+
const result = await completionSource(mockContext);
|
|
117
|
+
expect(result).toBeNull();
|
|
118
|
+
});
|
|
119
|
+
});
|