@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.
Files changed (211) hide show
  1. package/dist/{ConnectedDataExplorerComponent-C39nQwtD.js → ConnectedDataExplorerComponent-B68gXlbY.js} +323 -328
  2. package/dist/{ImageComparisonComponent-BhkiyswP.js → ImageComparisonComponent-Cw1oA8Tn.js} +13 -13
  3. package/dist/{_baseUniq-DdHL34FO.js → _baseUniq-CQrhBg_9.js} +67 -67
  4. package/dist/any-language-editor-pzUl6lxp.js +27 -0
  5. package/dist/{arc-BXrety1g.js → arc-BOhn-m2C.js} +1 -1
  6. package/dist/{architectureDiagram-KFL7JDKH-BMy6ywCF.js → architectureDiagram-W76B3OCA-DdYf2VnU.js} +144 -144
  7. package/dist/assets/{worker-COGufAQn.js → worker-BcG8m3h5.js} +33 -29
  8. package/dist/asterisk-DS281yxp.js +271 -0
  9. package/dist/{blockDiagram-ZYB65J3Q-DYT2-nlI.js → blockDiagram-QIGZ2CNN-DmmYotkP.js} +10 -10
  10. package/dist/{c4Diagram-AAMF2YG6-ZiQzioe6.js → c4Diagram-FPNF74CW-Dyz4zMHJ.js} +8 -8
  11. package/dist/{channel-CeuXqUAU.js → channel-CCL8jXAe.js} +1 -1
  12. package/dist/{chunk-ANTBXLJU-BvYnIrdq.js → chunk-4BX2VUAB-BfKwWLfJ.js} +1 -1
  13. package/dist/{chunk-WVR4S24B-DXj8yaUk.js → chunk-55IACEB6-CFQU7zSp.js} +1 -1
  14. package/dist/{chunk-GLLZNHP4-CyFsosAe.js → chunk-FMBD7UC4-DDjZzUcl.js} +1 -1
  15. package/dist/{chunk-JBRWN2VN-DA_EEhy2.js → chunk-K7UQS3LO-nBRjBU1H.js} +117 -117
  16. package/dist/{chunk-NRVI72HA-BYx2jMlI.js → chunk-QN33PNHL-B-g8sJzl.js} +1 -1
  17. package/dist/{chunk-FHKO5MBM-DfCztBk8.js → chunk-QZHKN3VN-B7QSJS3J.js} +1 -1
  18. package/dist/{chunk-LXBSTHXV-Se7vdY6J.js → chunk-TVAH2DTR-pGXll4d1.js} +7 -7
  19. package/dist/{chunk-OMD6QJNC-CqgcPMgL.js → chunk-TZMSLE5B-Dx9h-1mv.js} +1 -1
  20. package/dist/{classDiagram-v2-QTMF73CY-B19A3G1l.js → classDiagram-KNZD7YFC-BXryI7DY.js} +2 -2
  21. package/dist/{classDiagram-3BZAVTQC-B19A3G1l.js → classDiagram-v2-RKCZMP56-BXryI7DY.js} +2 -2
  22. package/dist/{clone-78au0tn1.js → clone-DqwV7ges.js} +1 -1
  23. package/dist/cose-bilkent-S5V4N54A-Di6FNMXz.js +2609 -0
  24. package/dist/{cytoscape.esm-BYnVVhJX.js → cytoscape.esm-DfdJODL8.js} +34 -34
  25. package/dist/{dagre-2BBEFEWP-BfEn3ZUV.js → dagre-5GWH7T2D-BTZPMTey.js} +6 -6
  26. package/dist/{data-grid-overlay-editor-CH_qLkV2.js → data-grid-overlay-editor-ryatXXby.js} +11 -11
  27. package/dist/{diagram-4IRLE6MV-CL8xidnG.js → diagram-N5W7TBWH-D79_zdOu.js} +59 -60
  28. package/dist/{diagram-RP2FKANI-B1BPcUew.js → diagram-QEK2KX5R-DX2A_SD0.js} +15 -15
  29. package/dist/{diagram-GUPCWM2R-CZ5cfqlq.js → diagram-S2PKOQOG-DM6VMTrJ.js} +10 -10
  30. package/dist/dockerfile-BoowzQlp.js +194 -0
  31. package/dist/ebnf-DUPDuY4r.js +78 -0
  32. package/dist/{erDiagram-HZWUO2LU-BEAIww50.js → erDiagram-AWTI2OKA-BBirxtlI.js} +8 -8
  33. package/dist/fcl-CPC2WYrI.js +103 -0
  34. package/dist/{flowDiagram-THRYKUMA-Czs2UAI2.js → flowDiagram-PVAE7QVJ-DyVweEMs.js} +9 -9
  35. package/dist/{ganttDiagram-WV7ZQ7D5-ByYIAVFO.js → ganttDiagram-OWAHRB6G-DTB7FX7r.js} +34 -34
  36. package/dist/{gitGraphDiagram-OJR772UL-BcpDsiyB.js → gitGraphDiagram-NY62KEGX-BrbIb5pD.js} +4 -4
  37. package/dist/{glide-data-editor-CmN6FVyi.js → glide-data-editor-DhMX4nmM.js} +33 -33
  38. package/dist/{graph-77W6heli.js → graph-CuLSrclI.js} +3 -3
  39. package/dist/http-D9LttvKF.js +44 -0
  40. package/dist/{index-BOojn38D.js → index-BNgdUQ2e.js} +7711 -7711
  41. package/dist/index-DIy6LHLJ.js +98 -0
  42. package/dist/{index-CmozKMxx.js → index-Df2dsx1t.js} +6 -6
  43. package/dist/{index-pBmAzQJl.js → index-MCx5v1x0.js} +2 -2
  44. package/dist/{index-Bfk9dnyS.js → index-cz_xaKvT.js} +33090 -32892
  45. package/dist/{infoDiagram-6WOFNB3A-CfzLHHVP.js → infoDiagram-STP46IZ2-CCBHc7-K.js} +2 -2
  46. package/dist/{journeyDiagram-FFXJYRFH-ndAcpkGn.js → journeyDiagram-BIP6EPQ6-LhGSj54j.js} +24 -26
  47. package/dist/{kanban-definition-KOZQBZVT-DcQYzNvc.js → kanban-definition-6OIFK2YF-aegTMFS6.js} +14 -14
  48. package/dist/{layout-XySVHJgD.js → layout-BEARWMhl.js} +81 -81
  49. package/dist/{linear-PbooOqg7.js → linear-fbJq6cdO.js} +35 -35
  50. package/dist/{main-B5yML0bw.js → main-HerZgEhd.js} +76533 -69945
  51. package/dist/main.js +1 -1
  52. package/dist/{mermaid-Cg5IX6Nv.js → mermaid-DxPYK0KX.js} +6160 -7493
  53. package/dist/min-DBJkhObB.js +80 -0
  54. package/dist/mindmap-definition-Q6HEUPPD-A3Fh5XDZ.js +785 -0
  55. package/dist/nginx-zDPm3Z74.js +89 -0
  56. package/dist/{number-overlay-editor-DUhfZqtP.js → number-overlay-editor-USMrY6k3.js} +19 -19
  57. package/dist/{pieDiagram-DBDJKBY4-DTOlNsja.js → pieDiagram-ADFJNKIX-Q9uFlCV0.js} +17 -17
  58. package/dist/{quadrantDiagram-YPSRARAO-BX2d8VS-.js → quadrantDiagram-LMRXKWRM-BuPh-qpK.js} +6 -6
  59. package/dist/{react-plotly-Dcyw-3Sa.js → react-plotly-HSqJPRfa.js} +3577 -3577
  60. package/dist/{requirementDiagram-EGVEC5DT-D1T5u-wG.js → requirementDiagram-4UW4RH46-CHROYNU_.js} +7 -7
  61. package/dist/{sankeyDiagram-HRAUVNP4-G6xDfnp-.js → sankeyDiagram-GR3RE2ED-DkUqHP2d.js} +5 -5
  62. package/dist/sequenceDiagram-C3RYC4MD-YoPTMplP.js +2519 -0
  63. package/dist/{slides-component-BJLlPJSr.js → slides-component-D7CHSR00.js} +66 -66
  64. package/dist/solr-BNlsLglM.js +41 -0
  65. package/dist/spreadsheet-C-cy4P5N.js +49 -0
  66. package/dist/{stateDiagram-UUKSUZ4H-CYXbjaom.js → stateDiagram-KXAO66HF-DEN00mVU.js} +5 -5
  67. package/dist/{stateDiagram-v2-EYPG3UTE-Br1HYKT6.js → stateDiagram-v2-UMBNRL4Z-DlQqSUAa.js} +2 -2
  68. package/dist/style.css +1 -1
  69. package/dist/tiddlywiki-5wqsXtSk.js +155 -0
  70. package/dist/tiki-__Kn3CeS.js +181 -0
  71. package/dist/{time-B9SZnSen.js → time-BtVcKqeD.js} +58 -58
  72. package/dist/{timeline-definition-3HZDQTIS-DeK_ZRD0.js → timeline-definition-XQNQX7LJ-DEteLt8D.js} +10 -12
  73. package/dist/{timer-BYwnU4DF.js → timer-B0-z63CM.js} +16 -16
  74. package/dist/{treemap-75Q7IDZK-CKP4vV_0.js → treemap-75Q7IDZK-8S6podme.js} +14 -14
  75. package/dist/{vega-component-CpgdqX2d.js → vega-component-D35L45kI.js} +30 -30
  76. package/dist/{xychartDiagram-FDP5SA34-AMEPsx_R.js → xychartDiagram-6GGTOJPD-DKwGThyy.js} +7 -7
  77. package/package.json +44 -41
  78. package/src/__mocks__/notebook.ts +3 -0
  79. package/src/__mocks__/requests.ts +3 -0
  80. package/src/__tests__/__snapshots__/CellStatus.test.tsx.snap +12 -12
  81. package/src/__tests__/chat-utils.test.ts +26 -211
  82. package/src/components/ai/ai-model-dropdown.tsx +25 -9
  83. package/src/components/ai/ai-provider-icon.tsx +5 -1
  84. package/src/components/app-config/ai-config.tsx +7 -0
  85. package/src/components/chat/acp/__tests__/__snapshots__/prompt.test.ts.snap +304 -0
  86. package/src/components/chat/acp/__tests__/atoms.test.ts +56 -0
  87. package/src/components/chat/acp/__tests__/prompt.test.ts +12 -0
  88. package/src/components/chat/acp/__tests__/state.test.ts +621 -0
  89. package/src/components/chat/acp/agent-docs.tsx +78 -0
  90. package/src/components/chat/acp/agent-panel.css +23 -0
  91. package/src/components/chat/acp/agent-panel.tsx +715 -0
  92. package/src/components/chat/acp/agent-selector.tsx +138 -0
  93. package/src/components/chat/acp/blocks.tsx +664 -0
  94. package/src/components/chat/acp/common.tsx +198 -0
  95. package/src/components/chat/acp/prompt.ts +284 -0
  96. package/src/components/chat/acp/scroll-to-bottom-button.tsx +50 -0
  97. package/src/components/chat/acp/session-tabs.tsx +138 -0
  98. package/src/components/chat/acp/state.ts +263 -0
  99. package/src/components/chat/acp/thread.tsx +121 -0
  100. package/src/components/chat/acp/types.ts +63 -0
  101. package/src/components/chat/acp/utils.ts +45 -0
  102. package/src/components/chat/chat-components.tsx +71 -0
  103. package/src/components/chat/chat-panel.tsx +481 -291
  104. package/src/components/chat/chat-utils.ts +50 -0
  105. package/src/components/chat/markdown-renderer.tsx +3 -7
  106. package/src/components/chat/tool-call-accordion.tsx +6 -6
  107. package/src/components/datasources/__tests__/utils.test.ts +6 -0
  108. package/src/components/datasources/column-preview.tsx +1 -3
  109. package/src/components/editor/actions/useNotebookActions.tsx +1 -1
  110. package/src/components/editor/ai/add-cell-with-ai.tsx +20 -15
  111. package/src/components/editor/ai/ai-completion-editor.tsx +22 -3
  112. package/src/components/editor/ai/completion-handlers.tsx +2 -4
  113. package/src/components/editor/ai/completion-utils.ts +85 -11
  114. package/src/components/editor/alerts/startup-logs-alert.tsx +72 -0
  115. package/src/components/editor/chrome/panels/datasources-panel.tsx +3 -1
  116. package/src/components/editor/chrome/panels/dependency-graph-panel.tsx +3 -1
  117. package/src/components/editor/chrome/panels/documentation-panel.tsx +3 -1
  118. package/src/components/editor/chrome/panels/error-panel.tsx +3 -1
  119. package/src/components/editor/chrome/panels/file-explorer-panel.tsx +3 -1
  120. package/src/components/editor/chrome/panels/logs-panel.tsx +3 -1
  121. package/src/components/editor/chrome/panels/outline-panel.tsx +3 -1
  122. package/src/components/editor/chrome/panels/packages-panel.tsx +4 -2
  123. package/src/components/editor/chrome/panels/scratchpad-panel.tsx +3 -1
  124. package/src/components/editor/chrome/panels/secrets-panel.tsx +3 -1
  125. package/src/components/editor/chrome/panels/snippets-panel.tsx +3 -1
  126. package/src/components/editor/chrome/panels/tracing-panel.tsx +3 -1
  127. package/src/components/editor/chrome/panels/variable-panel.tsx +3 -1
  128. package/src/components/editor/chrome/types.ts +10 -0
  129. package/src/components/editor/chrome/wrapper/app-chrome.tsx +55 -31
  130. package/src/components/editor/controls/command-palette-button.tsx +1 -1
  131. package/src/components/editor/controls/command-palette.tsx +5 -4
  132. package/src/components/editor/controls/state.ts +4 -0
  133. package/src/components/editor/package-alert.tsx +108 -58
  134. package/src/components/editor/renderers/CellArray.tsx +2 -0
  135. package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +0 -1
  136. package/src/components/pages/edit-page.tsx +7 -3
  137. package/src/core/ai/chat-utils.ts +26 -43
  138. package/src/core/ai/config.ts +1 -1
  139. package/src/core/ai/context/__tests__/registry.test.ts +277 -3
  140. package/src/core/ai/context/context.ts +11 -1
  141. package/src/core/ai/context/providers/__tests__/cell-output.test.ts +378 -0
  142. package/src/core/ai/context/providers/__tests__/error.test.ts +3 -2
  143. package/src/core/ai/context/providers/__tests__/file.test.ts +119 -0
  144. package/src/core/ai/context/providers/cell-output.ts +349 -0
  145. package/src/core/ai/context/providers/common.ts +5 -1
  146. package/src/core/ai/context/providers/file.ts +287 -0
  147. package/src/core/ai/context/registry.ts +79 -0
  148. package/src/core/ai/state.ts +22 -5
  149. package/src/core/alerts/state.ts +71 -3
  150. package/src/core/cells/cell.ts +2 -2
  151. package/src/core/cells/cells.ts +1 -1
  152. package/src/core/cells/logs.ts +1 -1
  153. package/src/core/cells/runs.ts +6 -5
  154. package/src/core/codemirror/ai/resources.ts +47 -5
  155. package/src/core/codemirror/ai/state.ts +12 -0
  156. package/src/core/codemirror/language/__tests__/sql.test.ts +45 -0
  157. package/src/core/codemirror/markdown/__tests__/commands.test.ts +1 -0
  158. package/src/core/codemirror/theme/dark.ts +1 -1
  159. package/src/core/config/capabilities.ts +1 -1
  160. package/src/core/config/feature-flag.tsx +2 -0
  161. package/src/core/datasets/__tests__/data-source.test.ts +24 -0
  162. package/src/core/errors/__tests__/errors.test.ts +2 -0
  163. package/src/core/islands/bridge.ts +1 -0
  164. package/src/core/islands/main.ts +1 -0
  165. package/src/core/kernel/messages.ts +12 -6
  166. package/src/core/layout/layout.ts +3 -3
  167. package/src/core/network/requests-network.ts +8 -0
  168. package/src/core/network/requests-static.ts +1 -0
  169. package/src/core/network/requests-toasting.ts +1 -0
  170. package/src/core/network/types.ts +4 -1
  171. package/src/core/wasm/bridge.ts +18 -2
  172. package/src/core/wasm/worker/bootstrap.ts +3 -1
  173. package/src/core/wasm/worker/getMarimoWheel.ts +3 -8
  174. package/src/core/wasm/worker/types.ts +3 -0
  175. package/src/core/websocket/useMarimoWebSocket.tsx +7 -1
  176. package/src/css/app/Cell.css +42 -21
  177. package/src/css/app/codemirror.css +5 -1
  178. package/src/css/globals.css +3 -0
  179. package/src/css/md.css +1 -1
  180. package/src/plugins/impl/MicrophonePlugin.tsx +2 -2
  181. package/src/plugins/impl/chat/ChatPlugin.tsx +2 -9
  182. package/src/plugins/impl/chat/chat-ui.tsx +129 -110
  183. package/src/plugins/impl/chat/types.ts +5 -8
  184. package/src/plugins/impl/code/__tests__/language.test.ts +15 -0
  185. package/src/plugins/impl/code/any-language-editor.tsx +11 -8
  186. package/src/plugins/impl/vega/vega.css +121 -0
  187. package/src/plugins/layout/MimeRenderPlugin.tsx +3 -6
  188. package/src/stories/cell.stories.tsx +6 -0
  189. package/src/stories/layout/vertical/one-column.stories.tsx +215 -0
  190. package/src/theme/useTheme.ts +11 -6
  191. package/src/utils/Logger.ts +5 -6
  192. package/src/utils/__tests__/blob.test.ts +37 -0
  193. package/src/utils/arrays.ts +13 -0
  194. package/src/utils/fileToBase64.ts +21 -6
  195. package/src/utils/json/base64.ts +5 -2
  196. package/src/utils/numbers.ts +9 -7
  197. package/dist/any-language-editor-DC5170DQ.js +0 -45
  198. package/dist/asn1-jKiBa2Ya.js +0 -95
  199. package/dist/clojure-CCKyeQKf.js +0 -800
  200. package/dist/css-BkF-NPzE.js +0 -1553
  201. package/dist/index-5ZH_qS8j.js +0 -288
  202. package/dist/index-U4yn89qO.js +0 -341
  203. package/dist/javascript-C2yteZeJ.js +0 -691
  204. package/dist/min-DS5Jz-hg.js +0 -80
  205. package/dist/mindmap-definition-LNHGMQRG-0aOVaMR8.js +0 -3234
  206. package/dist/mllike-BSnXJBGA.js +0 -272
  207. package/dist/pug-CwAQJzGR.js +0 -248
  208. package/dist/python-BkR3uSy8.js +0 -313
  209. package/dist/rpm-IznJm2Xc.js +0 -57
  210. package/dist/sequenceDiagram-WFGC7UMF-DMhHzllb.js +0 -2284
  211. 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: 2, // Boosts.ERROR
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: 2, // Boosts.ERROR
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
+ });