@marimo-team/frontend 0.15.1-dev33 → 0.15.1-dev35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{ConnectedDataExplorerComponent-B0S6KZz0.js → ConnectedDataExplorerComponent-BYxPTiA6.js} +1 -1
- package/dist/assets/{ImageComparisonComponent-C-VZ4Tnc.js → ImageComparisonComponent-DBpvpxlw.js} +1 -1
- package/dist/assets/{VegaLite-BnVARnZA.js → VegaLite-DzkGo9LJ.js} +1 -1
- package/dist/assets/{_baseEach-CnkgAoU6.js → _baseEach-CnK_W3-W.js} +1 -1
- package/dist/assets/_baseMap-CIc0IDdl.js +1 -0
- package/dist/assets/{_baseUniq-CwJ2iszo.js → _baseUniq-DsEUejTO.js} +1 -1
- package/dist/assets/{_createAggregator-DzNuEiXE.js → _createAggregator-xSD3e6yX.js} +1 -1
- package/dist/assets/{any-language-editor-CkEw-cSX.js → any-language-editor-CSLfx3Fo.js} +1 -1
- package/dist/assets/{architectureDiagram-KFL7JDKH-D5bxGLSO.js → architectureDiagram-KFL7JDKH-DDJcEe4E.js} +1 -1
- package/dist/assets/{blockDiagram-ZYB65J3Q-lBSpJVzg.js → blockDiagram-ZYB65J3Q-DrajNxU6.js} +1 -1
- package/dist/assets/{c4Diagram-AAMF2YG6-Cp2ZcvGq.js → c4Diagram-AAMF2YG6-DkbBAlkB.js} +1 -1
- package/dist/assets/channel-D2aPrBH4.js +1 -0
- package/dist/assets/{chunk-ANTBXLJU-BI9zlp2d.js → chunk-ANTBXLJU-CyFpCIAD.js} +1 -1
- package/dist/assets/{chunk-FHKO5MBM-DmqxuwHW.js → chunk-FHKO5MBM-DaRG9EJ9.js} +1 -1
- package/dist/assets/{chunk-GLLZNHP4-DDRTaZzv.js → chunk-GLLZNHP4-C852Uilf.js} +1 -1
- package/dist/assets/{chunk-JBRWN2VN-CT2Hh2iP.js → chunk-JBRWN2VN-DF6c2w7p.js} +1 -1
- package/dist/assets/{chunk-LXBSTHXV-DAMSTx1Q.js → chunk-LXBSTHXV-DJc1jClD.js} +1 -1
- package/dist/assets/{chunk-NRVI72HA-BZuYvBej.js → chunk-NRVI72HA-BYzJjL14.js} +1 -1
- package/dist/assets/{chunk-OMD6QJNC-BU_2rBlE.js → chunk-OMD6QJNC-DpzW6M54.js} +1 -1
- package/dist/assets/{chunk-WVR4S24B-CovEV1lg.js → chunk-WVR4S24B-Ds_Fnulu.js} +1 -1
- package/dist/assets/{circle-play-D59wTwHu.js → circle-play-ILK7D0lo.js} +1 -1
- package/dist/assets/classDiagram-3BZAVTQC-DAUNRz1h.js +1 -0
- package/dist/assets/classDiagram-v2-QTMF73CY-DAUNRz1h.js +1 -0
- package/dist/assets/clone-CYXehyk_.js +1 -0
- package/dist/assets/{compile-BKBdwA1q.js → compile-Q6b1NW0Y.js} +1 -1
- package/dist/assets/{dagre-2BBEFEWP-BmXAp5fB.js → dagre-2BBEFEWP-DUplj_Iy.js} +1 -1
- package/dist/assets/{data-grid-overlay-editor-BycmLqkg.js → data-grid-overlay-editor-BHVNUwJL.js} +1 -1
- package/dist/assets/{diagram-4IRLE6MV-CI8uZuDu.js → diagram-4IRLE6MV-CqSPK4-w.js} +1 -1
- package/dist/assets/{diagram-GUPCWM2R-DM01dWOn.js → diagram-GUPCWM2R-Yz9pe62o.js} +1 -1
- package/dist/assets/{diagram-RP2FKANI-3A7xcD_S.js → diagram-RP2FKANI-DAvv4Av_.js} +1 -1
- package/dist/assets/{edit-page-r0MON4eX.js → edit-page-hlKtRc6p.js} +4 -4
- package/dist/assets/{erDiagram-HZWUO2LU-DVhn3kEV.js → erDiagram-HZWUO2LU-BIlDOHrw.js} +1 -1
- package/dist/assets/{flowDiagram-THRYKUMA-ByiKsMTR.js → flowDiagram-THRYKUMA-w6HYIjcR.js} +1 -1
- package/dist/assets/{ganttDiagram-WV7ZQ7D5-C3yWfK7N.js → ganttDiagram-WV7ZQ7D5-CS1t__7-.js} +1 -1
- package/dist/assets/{gitGraphDiagram-OJR772UL-DT2oVbFG.js → gitGraphDiagram-OJR772UL-DCcukZnI.js} +1 -1
- package/dist/assets/{glide-data-editor-B-hKUbt8.js → glide-data-editor-BoQ4MEIu.js} +4 -4
- package/dist/assets/{graph-DKF8gkY7.js → graph-DzywueYf.js} +1 -1
- package/dist/assets/{home-page-DG5IpRSH.js → home-page-CTLf7o0o.js} +1 -1
- package/dist/assets/{index-COS_Xbgw.js → index-B8egTd_Y.js} +1 -1
- package/dist/assets/{index-CxQa_cAw.js → index-BNpIaqHq.js} +1 -1
- package/dist/assets/{index-B4KTvqjC.js → index-BNuSEPHf.js} +1 -1
- package/dist/assets/{index-Cxy_1y9a.js → index-BOaSI40H.js} +1 -1
- package/dist/assets/{index-B_03AI_O.js → index-BVdia1-T.js} +1 -1
- package/dist/assets/{index-BDye4bMD.js → index-BbrvgVGd.js} +1 -1
- package/dist/assets/{index-kCp9Vp_G.js → index-BobSdMcV.js} +1 -1
- package/dist/assets/{index-BSq77Vg9.js → index-BsZupkBE.js} +1 -1
- package/dist/assets/{index-BciA4s2J.js → index-CTTXrsVd.js} +1 -1
- package/dist/assets/{index-CDzLk5Q3.js → index-C_ZOWmXX.js} +1 -1
- package/dist/assets/{index-tI3mZqES.js → index-CjvmeMRd.js} +1 -1
- package/dist/assets/{index-C89mOURj.js → index-DMgZFfTJ.js} +1 -1
- package/dist/assets/{index-DTE_Lol9.js → index-DMvScaq6.js} +133 -133
- package/dist/assets/{index-BJMIm3h5.js → index-DNjb9MyH.js} +1 -1
- package/dist/assets/{index-BQCqmfXS.js → index-DUumP-lt.js} +1 -1
- package/dist/assets/{index-RfoHYoLS.js → index-Dl6_NSX4.js} +1 -1
- package/dist/assets/{index-BKnuS3t4.js → index-DsMyERcC.js} +1 -1
- package/dist/assets/{index-BAre-3Jh.js → index-EaCZDGUn.js} +1 -1
- package/dist/assets/{index-CKZtF7JM.js → index-gZP0fYKG.js} +1 -1
- package/dist/assets/{index-CDxt4mvu.js → index-txir7kB2.js} +1 -1
- package/dist/assets/{infoDiagram-6WOFNB3A-CVxc3Oq6.js → infoDiagram-6WOFNB3A-B1zwcYam.js} +1 -1
- package/dist/assets/{journeyDiagram-FFXJYRFH-yx-qCooh.js → journeyDiagram-FFXJYRFH-IwybgEnB.js} +1 -1
- package/dist/assets/{kanban-definition-KOZQBZVT-DpJgEsNc.js → kanban-definition-KOZQBZVT-BnLxznwF.js} +1 -1
- package/dist/assets/{layout-HC2KG283.js → layout-lIKJEEeS.js} +1 -1
- package/dist/assets/{linear-DwZzRezN.js → linear-DzfVWjvh.js} +1 -1
- package/dist/assets/{links-_NwdJ11g.js → links-yIZ6advT.js} +1 -1
- package/dist/assets/{mermaid-BsNRTdZg.js → mermaid-CEpdDmik.js} +4 -4
- package/dist/assets/{min-DdOdESXu.js → min-CH9RTxJs.js} +1 -1
- package/dist/assets/{mindmap-definition-LNHGMQRG-Ds3h-hI5.js → mindmap-definition-LNHGMQRG-BGOBqdZQ.js} +1 -1
- package/dist/assets/{number-overlay-editor-CYrbVyKN.js → number-overlay-editor-Bp8EEFG4.js} +1 -1
- package/dist/assets/{pieDiagram-DBDJKBY4-Ce-oEPaV.js → pieDiagram-DBDJKBY4-BH18DMec.js} +1 -1
- package/dist/assets/{quadrantDiagram-YPSRARAO-Urm8U0LY.js → quadrantDiagram-YPSRARAO-DyEaA3Tj.js} +1 -1
- package/dist/assets/{react-plotly-DGqhBdP1.js → react-plotly-BWJyJ31U.js} +1 -1
- package/dist/assets/{requirementDiagram-EGVEC5DT-CCTq9hQZ.js → requirementDiagram-EGVEC5DT-BIDYwWds.js} +1 -1
- package/dist/assets/{run-page-CsCj50OD.js → run-page-CFGHZjjp.js} +1 -1
- package/dist/assets/{sankeyDiagram-HRAUVNP4-C2I9sfem.js → sankeyDiagram-HRAUVNP4-CI7WE82z.js} +1 -1
- package/dist/assets/{sequenceDiagram-WFGC7UMF-CLQmyPy2.js → sequenceDiagram-WFGC7UMF-CAt5_R3a.js} +1 -1
- package/dist/assets/{slides-component-DJCkXPus.js → slides-component-BE3m7wod.js} +1 -1
- package/dist/assets/{sortBy-Duwc1vyP.js → sortBy-D8wYLA9x.js} +1 -1
- package/dist/assets/{stateDiagram-UUKSUZ4H-VvU373AQ.js → stateDiagram-UUKSUZ4H-B9ghWYn7.js} +1 -1
- package/dist/assets/stateDiagram-v2-EYPG3UTE-DtL7XYtB.js +1 -0
- package/dist/assets/{storage-1hHZMyeU.js → storage-BseyfFaj.js} +3 -3
- package/dist/assets/{terminal-DV-tPxSZ.js → terminal-R2SnqHO_.js} +1 -1
- package/dist/assets/{time-4bh0fJAY.js → time-CftJc27E.js} +1 -1
- package/dist/assets/{timeline-definition-3HZDQTIS-D5z1lzVG.js → timeline-definition-3HZDQTIS-B8ej5Uev.js} +1 -1
- package/dist/assets/{tracing-C32etwYw.js → tracing-OU6KdAjg.js} +2 -2
- package/dist/assets/{trash-W9nmeKd9.js → trash-BX0buH-6.js} +1 -1
- package/dist/assets/{treemap-75Q7IDZK-CHza0lWj.js → treemap-75Q7IDZK-C6t8vi8s.js} +1 -1
- package/dist/assets/{vega-component-BDWLTrqk.js → vega-component-a-Wve7v1.js} +1 -1
- package/dist/assets/{xychartDiagram-FDP5SA34-DVdoRkWV.js → xychartDiagram-FDP5SA34-DmTMju1m.js} +1 -1
- package/dist/index.html +1 -1
- package/package.json +1 -1
- package/src/__mocks__/notebook.ts +108 -0
- package/src/components/editor/ai/__tests__/completion-utils.test.ts +119 -139
- package/src/components/editor/ai/completion-utils.ts +7 -33
- package/src/core/ai/context/__tests__/registry.test.ts +154 -0
- package/src/core/ai/context/__tests__/utils.test.ts +174 -0
- package/src/core/ai/context/context.ts +3 -1
- package/src/core/ai/context/providers/__tests__/__snapshots__/error.test.ts.snap +3 -0
- package/src/core/ai/context/providers/__tests__/__snapshots__/tables.test.ts.snap +8 -12
- package/src/core/ai/context/providers/__tests__/__snapshots__/variable.test.ts.snap +4 -20
- package/src/core/ai/context/providers/__tests__/error.test.ts +328 -0
- package/src/core/ai/context/providers/common.ts +1 -0
- package/src/core/ai/context/providers/error.ts +165 -0
- package/src/core/ai/context/providers/tables.ts +14 -6
- package/src/core/ai/context/providers/variable.ts +11 -2
- package/src/core/ai/context/utils.ts +49 -0
- package/src/core/cells/cells.ts +1 -1
- package/src/core/dom/outline.ts +16 -2
- package/dist/assets/_baseMap-BQjPNy9z.js +0 -1
- package/dist/assets/channel-8Z-5YHHd.js +0 -1
- package/dist/assets/classDiagram-3BZAVTQC-BOvgzS6u.js +0 -1
- package/dist/assets/classDiagram-v2-QTMF73CY-BOvgzS6u.js +0 -1
- package/dist/assets/clone-DVVP9Odz.js +0 -1
- package/dist/assets/stateDiagram-v2-EYPG3UTE-D41Oi10c.js +0 -1
@@ -0,0 +1,174 @@
|
|
1
|
+
/* Copyright 2024 Marimo. All rights reserved. */
|
2
|
+
|
3
|
+
import { describe, expect, it } from "vitest";
|
4
|
+
import { contextToXml } from "../utils";
|
5
|
+
|
6
|
+
describe("contextToXml", () => {
|
7
|
+
it("should convert basic context to XML", () => {
|
8
|
+
const context = {
|
9
|
+
type: "data",
|
10
|
+
data: {
|
11
|
+
name: "dataset1",
|
12
|
+
source: "memory",
|
13
|
+
},
|
14
|
+
};
|
15
|
+
|
16
|
+
const result = contextToXml(context);
|
17
|
+
expect(result).toBe('<data name="dataset1" source="memory"></data>');
|
18
|
+
});
|
19
|
+
|
20
|
+
it("should handle context with details", () => {
|
21
|
+
const context = {
|
22
|
+
type: "variable",
|
23
|
+
data: {
|
24
|
+
name: "my_var",
|
25
|
+
dataType: "string",
|
26
|
+
},
|
27
|
+
details: "This is a string variable",
|
28
|
+
};
|
29
|
+
|
30
|
+
const result = contextToXml(context);
|
31
|
+
expect(result).toBe(
|
32
|
+
'<variable name="my_var" dataType="string">This is a string variable</variable>',
|
33
|
+
);
|
34
|
+
});
|
35
|
+
|
36
|
+
it("should escape XML characters in attributes", () => {
|
37
|
+
const context = {
|
38
|
+
type: "data",
|
39
|
+
data: {
|
40
|
+
name: "dataset<>&\"'",
|
41
|
+
description: "Contains special chars",
|
42
|
+
},
|
43
|
+
};
|
44
|
+
|
45
|
+
const result = contextToXml(context);
|
46
|
+
expect(result).toMatchInlineSnapshot(
|
47
|
+
`"<data name="dataset<>&"'" description="Contains special chars"></data>"`,
|
48
|
+
);
|
49
|
+
});
|
50
|
+
|
51
|
+
it("should escape XML characters in details", () => {
|
52
|
+
const context = {
|
53
|
+
type: "error",
|
54
|
+
data: {
|
55
|
+
name: "error1",
|
56
|
+
},
|
57
|
+
details: "Error message with <tags> & \"quotes\" and 'apostrophes'",
|
58
|
+
};
|
59
|
+
|
60
|
+
const result = contextToXml(context);
|
61
|
+
expect(result).toMatchInlineSnapshot(
|
62
|
+
`"<error name="error1">Error message with <tags> & "quotes" and 'apostrophes'</error>"`,
|
63
|
+
);
|
64
|
+
});
|
65
|
+
|
66
|
+
it("should handle undefined values in data", () => {
|
67
|
+
const context = {
|
68
|
+
type: "variable",
|
69
|
+
data: {
|
70
|
+
name: "my_var",
|
71
|
+
dataType: undefined,
|
72
|
+
value: "test",
|
73
|
+
},
|
74
|
+
};
|
75
|
+
|
76
|
+
const result = contextToXml(context);
|
77
|
+
expect(result).toBe('<variable name="my_var" value="test"></variable>');
|
78
|
+
});
|
79
|
+
|
80
|
+
it("should handle empty data object", () => {
|
81
|
+
const context = {
|
82
|
+
type: "empty",
|
83
|
+
data: {},
|
84
|
+
};
|
85
|
+
|
86
|
+
const result = contextToXml(context);
|
87
|
+
expect(result).toBe("<empty></empty>");
|
88
|
+
});
|
89
|
+
|
90
|
+
it("should handle numeric values", () => {
|
91
|
+
const context = {
|
92
|
+
type: "metric",
|
93
|
+
data: {
|
94
|
+
count: 42,
|
95
|
+
percentage: 85.5,
|
96
|
+
isActive: true,
|
97
|
+
},
|
98
|
+
};
|
99
|
+
|
100
|
+
const result = contextToXml(context);
|
101
|
+
expect(result).toBe(
|
102
|
+
'<metric count="42" percentage="85.5" isActive="true"></metric>',
|
103
|
+
);
|
104
|
+
});
|
105
|
+
|
106
|
+
it("should handle complex nested data", () => {
|
107
|
+
const context = {
|
108
|
+
type: "complex",
|
109
|
+
data: {
|
110
|
+
name: "test",
|
111
|
+
config: JSON.stringify({ key: "value", nested: { prop: "test" } }),
|
112
|
+
},
|
113
|
+
details: "Complex configuration data",
|
114
|
+
};
|
115
|
+
|
116
|
+
const result = contextToXml(context);
|
117
|
+
expect(result).toMatchInlineSnapshot(
|
118
|
+
`"<complex name="test" config="{"key":"value","nested":{"prop":"test"}}">Complex configuration data</complex>"`,
|
119
|
+
);
|
120
|
+
});
|
121
|
+
|
122
|
+
it("should handle boolean values", () => {
|
123
|
+
const context = {
|
124
|
+
type: "flags",
|
125
|
+
data: {
|
126
|
+
enabled: true,
|
127
|
+
visible: false,
|
128
|
+
},
|
129
|
+
};
|
130
|
+
|
131
|
+
const result = contextToXml(context);
|
132
|
+
expect(result).toBe('<flags enabled="true" visible="false"></flags>');
|
133
|
+
});
|
134
|
+
|
135
|
+
it("should handle null values", () => {
|
136
|
+
const context = {
|
137
|
+
type: "nullable",
|
138
|
+
data: {
|
139
|
+
name: "test",
|
140
|
+
value: null,
|
141
|
+
},
|
142
|
+
};
|
143
|
+
|
144
|
+
const result = contextToXml(context);
|
145
|
+
expect(result).toBe('<nullable name="test" value="null"></nullable>');
|
146
|
+
});
|
147
|
+
|
148
|
+
it("should handle multiline details", () => {
|
149
|
+
const context = {
|
150
|
+
type: "multiline",
|
151
|
+
data: {
|
152
|
+
name: "test",
|
153
|
+
},
|
154
|
+
details: "Line 1\nLine 2\nLine 3",
|
155
|
+
};
|
156
|
+
|
157
|
+
const result = contextToXml(context);
|
158
|
+
expect(result).toBe(
|
159
|
+
'<multiline name="test">Line 1\nLine 2\nLine 3</multiline>',
|
160
|
+
);
|
161
|
+
});
|
162
|
+
|
163
|
+
it("should handle special characters in type name", () => {
|
164
|
+
const context = {
|
165
|
+
type: "data-source",
|
166
|
+
data: {
|
167
|
+
name: "test",
|
168
|
+
},
|
169
|
+
};
|
170
|
+
|
171
|
+
const result = contextToXml(context);
|
172
|
+
expect(result).toBe('<data-source name="test"></data-source>');
|
173
|
+
});
|
174
|
+
});
|
@@ -3,6 +3,7 @@
|
|
3
3
|
import { allTablesAtom } from "@/core/datasets/data-source-connections";
|
4
4
|
import type { JotaiStore } from "@/core/state/jotai";
|
5
5
|
import { variablesAtom } from "@/core/variables/state";
|
6
|
+
import { ErrorContextProvider } from "./providers/error";
|
6
7
|
import { TableContextProvider } from "./providers/tables";
|
7
8
|
import { VariableContextProvider } from "./providers/variable";
|
8
9
|
import { AIContextRegistry } from "./registry";
|
@@ -12,5 +13,6 @@ export function getAIContextRegistry(store: JotaiStore) {
|
|
12
13
|
const variables = store.get(variablesAtom);
|
13
14
|
return new AIContextRegistry()
|
14
15
|
.register(new TableContextProvider(tablesMap))
|
15
|
-
.register(new VariableContextProvider(variables, tablesMap))
|
16
|
+
.register(new VariableContextProvider(variables, tablesMap))
|
17
|
+
.register(new ErrorContextProvider(store));
|
16
18
|
}
|
@@ -1,38 +1,34 @@
|
|
1
1
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
2
2
|
|
3
3
|
exports[`TableContextProvider > formatContext > should format context for basic table > basic-table-context 1`] = `
|
4
|
-
"
|
5
|
-
Source: memory
|
4
|
+
"<data name="products" source="memory">
|
6
5
|
Shape: 100 rows, 3 columns
|
7
6
|
Columns:
|
8
7
|
- id: integer
|
9
8
|
- name: string
|
10
|
-
- active: boolean"
|
9
|
+
- active: boolean</data>"
|
11
10
|
`;
|
12
11
|
|
13
12
|
exports[`TableContextProvider > formatContext > should format context for remote database table > remote-table-context 1`] = `
|
14
|
-
"
|
15
|
-
Source: postgresql://localhost:5432/mydb
|
13
|
+
"<data name="remote_table" source="postgresql://localhost:5432/mydb">
|
16
14
|
Shape: 100 rows, 3 columns
|
17
15
|
Columns:
|
18
16
|
- uuid: string
|
19
17
|
- created_at: string
|
20
|
-
- metadata: string"
|
18
|
+
- metadata: string</data>"
|
21
19
|
`;
|
22
20
|
|
23
21
|
exports[`TableContextProvider > formatContext > should format context for table without columns > no-columns-table-context 1`] = `
|
24
|
-
"
|
25
|
-
|
26
|
-
Shape: 100 rows, 3 columns"
|
22
|
+
"<data name="no_columns" source="memory">
|
23
|
+
Shape: 100 rows, 3 columns</data>"
|
27
24
|
`;
|
28
25
|
|
29
26
|
exports[`TableContextProvider > formatContext > should format context for table without shape info > no-shape-table-context 1`] = `
|
30
|
-
"
|
31
|
-
Source: memory
|
27
|
+
"<data name="no_shape" source="memory">
|
32
28
|
Columns:
|
33
29
|
- id: integer
|
34
30
|
- name: string
|
35
|
-
- active: boolean"
|
31
|
+
- active: boolean</data>"
|
36
32
|
`;
|
37
33
|
|
38
34
|
exports[`TableContextProvider > getItems > should handle dataframe tables with variable names > dataframe-table 1`] = `
|
@@ -1,28 +1,12 @@
|
|
1
1
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
2
2
|
|
3
|
-
exports[`VariableContextProvider > formatContext > should format context for basic variable > basic-variable-context 1`] = `
|
4
|
-
"Variable: username
|
5
|
-
Type: str
|
6
|
-
Preview: "\\"alice\\"""
|
7
|
-
`;
|
3
|
+
exports[`VariableContextProvider > formatContext > should format context for basic variable > basic-variable-context 1`] = `"<variable name="username" dataType="str">"\\"alice\\""</variable>"`;
|
8
4
|
|
9
|
-
exports[`VariableContextProvider > formatContext > should format context for dataframe variable > dataframe-variable-context 1`] = `
|
10
|
-
"Variable: sales_df
|
11
|
-
Type: pandas.DataFrame
|
12
|
-
Preview: "<DataFrame shape: (1000, 8)>\\n date product quantity price\\n0 2023-01-01 Widget A 10 29.99\\n1 2023-01-02 Widget B 5 49.99\\n...""
|
13
|
-
`;
|
5
|
+
exports[`VariableContextProvider > formatContext > should format context for dataframe variable > dataframe-variable-context 1`] = `"<variable name="sales_df" dataType="pandas.DataFrame">"<DataFrame shape: (1000, 8)>\\n date product quantity price\\n0 2023-01-01 Widget A 10 29.99\\n1 2023-01-02 Widget B 5 49.99\\n..."</variable>"`;
|
14
6
|
|
15
|
-
exports[`VariableContextProvider > formatContext > should format context for variable with complex value > complex-value-variable-context 1`] = `
|
16
|
-
"Variable: complex_data
|
17
|
-
Type: dict
|
18
|
-
Preview: "{\\"users\\": [{\\"id\\": 1, \\"name\\": \\"Alice\\"}, {\\"id\\": 2, \\"name\\": \\"Bob\\"}], \\"total\\": 2}""
|
19
|
-
`;
|
7
|
+
exports[`VariableContextProvider > formatContext > should format context for variable with complex value > complex-value-variable-context 1`] = `"<variable name="complex_data" dataType="dict">"{\\"users\\": [{\\"id\\": 1, \\"name\\": \\"Alice\\"}, {\\"id\\": 2, \\"name\\": \\"Bob\\"}], \\"total\\": 2}"</variable>"`;
|
20
8
|
|
21
|
-
exports[`VariableContextProvider > formatContext > should format context for variable without dataType > no-datatype-variable-context 1`] = `
|
22
|
-
"Variable: mystery_var
|
23
|
-
Type: unknown
|
24
|
-
Preview: "some_value""
|
25
|
-
`;
|
9
|
+
exports[`VariableContextProvider > formatContext > should format context for variable without dataType > no-datatype-variable-context 1`] = `"<variable name="mystery_var" dataType="null">"some_value"</variable>"`;
|
26
10
|
|
27
11
|
exports[`VariableContextProvider > getItems > should handle complex data types > complex-data-types 1`] = `
|
28
12
|
[
|
@@ -0,0 +1,328 @@
|
|
1
|
+
/* Copyright 2024 Marimo. All rights reserved. */
|
2
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
3
|
+
|
4
|
+
import { createStore } from "jotai";
|
5
|
+
import { beforeEach, describe, expect, it } from "vitest";
|
6
|
+
import { MockNotebook } from "@/__mocks__/notebook";
|
7
|
+
import { notebookAtom } from "@/core/cells/cells";
|
8
|
+
import { type CellId, CellId as CellIdClass } from "@/core/cells/ids";
|
9
|
+
import { ErrorContextProvider } from "../error";
|
10
|
+
|
11
|
+
describe("ErrorContextProvider", () => {
|
12
|
+
let provider: ErrorContextProvider;
|
13
|
+
let store: ReturnType<typeof createStore>;
|
14
|
+
|
15
|
+
beforeEach(() => {
|
16
|
+
store = createStore();
|
17
|
+
provider = new ErrorContextProvider(store);
|
18
|
+
});
|
19
|
+
|
20
|
+
const createMockNotebookWithErrors = (
|
21
|
+
errors: Parameters<typeof MockNotebook.notebookStateWithErrors>[0],
|
22
|
+
) => {
|
23
|
+
const notebookState = MockNotebook.notebookStateWithErrors(errors);
|
24
|
+
store.set(notebookAtom, notebookState);
|
25
|
+
};
|
26
|
+
|
27
|
+
describe("provider properties", () => {
|
28
|
+
it("should have correct provider properties", () => {
|
29
|
+
expect(provider.title).toBe("Errors");
|
30
|
+
expect(provider.mentionPrefix).toBe("@");
|
31
|
+
expect(provider.contextType).toBe("error");
|
32
|
+
});
|
33
|
+
});
|
34
|
+
|
35
|
+
describe("getItems", () => {
|
36
|
+
it("should return empty array when no errors", () => {
|
37
|
+
const items = provider.getItems();
|
38
|
+
expect(items).toEqual([]);
|
39
|
+
});
|
40
|
+
|
41
|
+
it("should return error items when errors exist", () => {
|
42
|
+
const cellId1 = CellIdClass.create();
|
43
|
+
const cellId2 = CellIdClass.create();
|
44
|
+
|
45
|
+
createMockNotebookWithErrors([
|
46
|
+
{
|
47
|
+
cellId: cellId1,
|
48
|
+
cellName: "Cell 1",
|
49
|
+
errorData: [MockNotebook.errors.syntax("Invalid syntax")],
|
50
|
+
},
|
51
|
+
{
|
52
|
+
cellId: cellId2,
|
53
|
+
cellName: "Cell 2",
|
54
|
+
errorData: [MockNotebook.errors.exception("Runtime error")],
|
55
|
+
},
|
56
|
+
]);
|
57
|
+
|
58
|
+
const items = provider.getItems();
|
59
|
+
|
60
|
+
expect(items).toHaveLength(1);
|
61
|
+
expect(items[0]).toMatchObject({
|
62
|
+
name: "Errors",
|
63
|
+
type: "error",
|
64
|
+
description: "All errors in the notebook",
|
65
|
+
data: {
|
66
|
+
type: "all-errors",
|
67
|
+
errors: [
|
68
|
+
{
|
69
|
+
cellId: cellId1,
|
70
|
+
cellName: "Cell 1",
|
71
|
+
errorData: [{ type: "syntax", msg: "Invalid syntax" }],
|
72
|
+
},
|
73
|
+
{
|
74
|
+
cellId: cellId2,
|
75
|
+
cellName: "Cell 2",
|
76
|
+
errorData: [{ type: "exception", msg: "Runtime error" }],
|
77
|
+
},
|
78
|
+
],
|
79
|
+
},
|
80
|
+
});
|
81
|
+
});
|
82
|
+
|
83
|
+
it("should handle cells without names", () => {
|
84
|
+
const cellId = CellIdClass.create();
|
85
|
+
|
86
|
+
createMockNotebookWithErrors([
|
87
|
+
{
|
88
|
+
cellId,
|
89
|
+
cellName: "",
|
90
|
+
errorData: [MockNotebook.errors.syntax("Invalid syntax")],
|
91
|
+
},
|
92
|
+
]);
|
93
|
+
|
94
|
+
const items = provider.getItems();
|
95
|
+
expect(items[0].data.errors[0].cellName).toBe("");
|
96
|
+
});
|
97
|
+
});
|
98
|
+
|
99
|
+
describe("formatCompletion", () => {
|
100
|
+
it("should format completion for all-errors type", () => {
|
101
|
+
const cellId1 = CellIdClass.create();
|
102
|
+
const cellId2 = CellIdClass.create();
|
103
|
+
|
104
|
+
createMockNotebookWithErrors([
|
105
|
+
{
|
106
|
+
cellId: cellId1,
|
107
|
+
cellName: "Cell 1",
|
108
|
+
errorData: [MockNotebook.errors.syntax("Invalid syntax")],
|
109
|
+
},
|
110
|
+
{
|
111
|
+
cellId: cellId2,
|
112
|
+
cellName: "Cell 2",
|
113
|
+
errorData: [MockNotebook.errors.exception("Runtime error")],
|
114
|
+
},
|
115
|
+
]);
|
116
|
+
|
117
|
+
const items = provider.getItems();
|
118
|
+
const completion = provider.formatCompletion(items[0]);
|
119
|
+
|
120
|
+
expect(completion).toMatchObject({
|
121
|
+
label: "@Errors",
|
122
|
+
displayLabel: "Errors",
|
123
|
+
detail: "2 errors",
|
124
|
+
boost: 2, // Boosts.ERROR
|
125
|
+
type: "error",
|
126
|
+
apply: "@Errors",
|
127
|
+
section: "Errors",
|
128
|
+
});
|
129
|
+
|
130
|
+
// Test the info function
|
131
|
+
expect(completion.info).toBeDefined();
|
132
|
+
if (typeof completion.info === "function") {
|
133
|
+
const infoResult = completion.info(completion);
|
134
|
+
if (
|
135
|
+
infoResult &&
|
136
|
+
typeof infoResult === "object" &&
|
137
|
+
"dom" in infoResult
|
138
|
+
) {
|
139
|
+
const infoElement = infoResult.dom as HTMLElement;
|
140
|
+
expect(infoElement.tagName).toBe("DIV");
|
141
|
+
expect(infoElement.textContent).toContain("Errors");
|
142
|
+
expect(infoElement.textContent).toContain("2 errors");
|
143
|
+
} else if (infoResult && "tagName" in (infoResult as any)) {
|
144
|
+
const infoElement = infoResult as HTMLElement;
|
145
|
+
expect(infoElement.tagName).toBe("DIV");
|
146
|
+
expect(infoElement.textContent).toContain("Errors");
|
147
|
+
expect(infoElement.textContent).toContain("2 errors");
|
148
|
+
}
|
149
|
+
}
|
150
|
+
});
|
151
|
+
|
152
|
+
it("should handle single error correctly", () => {
|
153
|
+
const cellId = CellIdClass.create();
|
154
|
+
|
155
|
+
createMockNotebookWithErrors([
|
156
|
+
{
|
157
|
+
cellId,
|
158
|
+
cellName: "Cell 1",
|
159
|
+
errorData: [MockNotebook.errors.syntax("Invalid syntax")],
|
160
|
+
},
|
161
|
+
]);
|
162
|
+
|
163
|
+
const items = provider.getItems();
|
164
|
+
const completion = provider.formatCompletion(items[0]);
|
165
|
+
expect(completion.detail).toBe("1 error");
|
166
|
+
});
|
167
|
+
|
168
|
+
it("should handle fallback for unknown error types", () => {
|
169
|
+
const item = {
|
170
|
+
uri: "error://unknown",
|
171
|
+
name: "Unknown Error",
|
172
|
+
type: "error" as const,
|
173
|
+
data: {
|
174
|
+
type: "unknown" as any,
|
175
|
+
errors: [],
|
176
|
+
},
|
177
|
+
description: "Unknown error type",
|
178
|
+
};
|
179
|
+
|
180
|
+
const completion = provider.formatCompletion(item);
|
181
|
+
expect(completion).toMatchObject({
|
182
|
+
label: "Error",
|
183
|
+
displayLabel: "Error",
|
184
|
+
boost: 2, // Boosts.ERROR
|
185
|
+
});
|
186
|
+
});
|
187
|
+
});
|
188
|
+
|
189
|
+
describe("formatContext", () => {
|
190
|
+
it("should format context for basic errors", () => {
|
191
|
+
const cellId = CellIdClass.create();
|
192
|
+
|
193
|
+
createMockNotebookWithErrors([
|
194
|
+
{
|
195
|
+
cellId,
|
196
|
+
cellName: "Cell 1",
|
197
|
+
errorData: [MockNotebook.errors.syntax("Invalid syntax")],
|
198
|
+
},
|
199
|
+
]);
|
200
|
+
|
201
|
+
const items = provider.getItems();
|
202
|
+
const context = provider.formatContext(items[0]);
|
203
|
+
expect(context).toMatchSnapshot("basic-error-context");
|
204
|
+
});
|
205
|
+
|
206
|
+
it("should format context for multiple error types", () => {
|
207
|
+
const cellId1 = "cell-1" as CellId;
|
208
|
+
const cellId2 = "cell-2" as CellId;
|
209
|
+
|
210
|
+
createMockNotebookWithErrors([
|
211
|
+
{
|
212
|
+
cellId: cellId1,
|
213
|
+
cellName: "Cell 1",
|
214
|
+
errorData: [
|
215
|
+
MockNotebook.errors.syntax("Invalid syntax"),
|
216
|
+
MockNotebook.errors.exception("Runtime error"),
|
217
|
+
],
|
218
|
+
},
|
219
|
+
{
|
220
|
+
cellId: cellId2,
|
221
|
+
cellName: "",
|
222
|
+
errorData: [
|
223
|
+
MockNotebook.errors.cycle(),
|
224
|
+
MockNotebook.errors.multipleDefs("variable_x"),
|
225
|
+
],
|
226
|
+
},
|
227
|
+
]);
|
228
|
+
|
229
|
+
const items = provider.getItems();
|
230
|
+
const context = provider.formatContext(items[0]);
|
231
|
+
|
232
|
+
// Check for expected content instead of exact snapshot match due to random cell IDs
|
233
|
+
expect(context).toMatchInlineSnapshot(`
|
234
|
+
"<error name="Cell 1" description="Invalid syntax
|
235
|
+
Runtime error"></error>
|
236
|
+
|
237
|
+
<error name="Cell cell-2" description="This cell is in a cycle
|
238
|
+
The variable 'variable_x' was defined by another cell"></error>"
|
239
|
+
`);
|
240
|
+
});
|
241
|
+
|
242
|
+
it("should handle cells without names", () => {
|
243
|
+
const cellId = CellIdClass.create();
|
244
|
+
|
245
|
+
createMockNotebookWithErrors([
|
246
|
+
{
|
247
|
+
cellId,
|
248
|
+
cellName: "",
|
249
|
+
errorData: [MockNotebook.errors.syntax("Invalid syntax")],
|
250
|
+
},
|
251
|
+
]);
|
252
|
+
|
253
|
+
const items = provider.getItems();
|
254
|
+
const context = provider.formatContext(items[0]);
|
255
|
+
expect(context).toContain(`Cell ${cellId}`);
|
256
|
+
});
|
257
|
+
});
|
258
|
+
|
259
|
+
describe("error description handling", () => {
|
260
|
+
it("should handle all error types correctly", () => {
|
261
|
+
// Note: ancestor-* error types are intentionally filtered out by cellErrorsAtom
|
262
|
+
const errorTypes = [
|
263
|
+
{
|
264
|
+
error: MockNotebook.errors.setupRefs(),
|
265
|
+
expected: "The setup cell cannot have references",
|
266
|
+
},
|
267
|
+
{
|
268
|
+
error: MockNotebook.errors.cycle(),
|
269
|
+
expected: "This cell is in a cycle",
|
270
|
+
},
|
271
|
+
{
|
272
|
+
error: MockNotebook.errors.multipleDefs("var_x"),
|
273
|
+
expected: "The variable 'var_x' was defined by another cell",
|
274
|
+
},
|
275
|
+
{
|
276
|
+
error: MockNotebook.errors.importStar("Import star error"),
|
277
|
+
expected: "Import star error",
|
278
|
+
},
|
279
|
+
{
|
280
|
+
error: MockNotebook.errors.exception("Runtime exception"),
|
281
|
+
expected: "Runtime exception",
|
282
|
+
},
|
283
|
+
{
|
284
|
+
error: MockNotebook.errors.strictException("Strict exception", "ref"),
|
285
|
+
expected: "Strict exception",
|
286
|
+
},
|
287
|
+
{
|
288
|
+
error: MockNotebook.errors.interruption(),
|
289
|
+
expected: "This cell was interrupted and needs to be re-run",
|
290
|
+
},
|
291
|
+
{
|
292
|
+
error: MockNotebook.errors.syntax("Syntax error"),
|
293
|
+
expected: "Syntax error",
|
294
|
+
},
|
295
|
+
{
|
296
|
+
error: MockNotebook.errors.unknown("Unknown error"),
|
297
|
+
expected: "Unknown error",
|
298
|
+
},
|
299
|
+
];
|
300
|
+
|
301
|
+
for (const { error, expected } of errorTypes) {
|
302
|
+
// Create a fresh store and provider for each error type
|
303
|
+
const testStore = createStore();
|
304
|
+
const testProvider = new ErrorContextProvider(testStore);
|
305
|
+
|
306
|
+
const { notebookState } = MockNotebook.cellWithErrors("Cell 1", [
|
307
|
+
error,
|
308
|
+
]);
|
309
|
+
testStore.set(notebookAtom, notebookState);
|
310
|
+
|
311
|
+
const items = testProvider.getItems();
|
312
|
+
expect(items).toHaveLength(1);
|
313
|
+
const context = testProvider.formatContext(items[0]);
|
314
|
+
expect(context).toContain(expected);
|
315
|
+
}
|
316
|
+
});
|
317
|
+
|
318
|
+
it("should handle unknown error type gracefully", () => {
|
319
|
+
const error = { type: "unknown-type" as any };
|
320
|
+
const { notebookState } = MockNotebook.cellWithErrors("Cell 1", [error]);
|
321
|
+
store.set(notebookAtom, notebookState);
|
322
|
+
|
323
|
+
const items = provider.getItems();
|
324
|
+
const context = provider.formatContext(items[0]);
|
325
|
+
expect(context).toContain("Unknown error");
|
326
|
+
});
|
327
|
+
});
|
328
|
+
});
|