@marimo-team/islands 0.16.1 → 0.16.2
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-DyqLQGPc.js → ConnectedDataExplorerComponent-KvtsEmcw.js} +2 -2
- package/dist/{ImageComparisonComponent-CQDGJfUA.js → ImageComparisonComponent-R2tjqLSx.js} +1 -1
- package/dist/{_baseUniq-B2Nna6Kt.js → _baseUniq-BykZEXIq.js} +1 -1
- package/dist/{any-language-editor-D-wq0tOG.js → any-language-editor-CMt4Y6oz.js} +1 -1
- package/dist/{architectureDiagram-W76B3OCA-C6tdnMBf.js → architectureDiagram-W76B3OCA-mQ3sJdEW.js} +4 -4
- package/dist/{blockDiagram-QIGZ2CNN-IagL8LCN.js → blockDiagram-QIGZ2CNN-BxLRv5EM.js} +5 -5
- package/dist/{c4Diagram-FPNF74CW-D3_lIWUP.js → c4Diagram-FPNF74CW-Cfz16aWq.js} +2 -2
- package/dist/{channel-DCJI_DKk.js → channel-zr1uJJ5g.js} +1 -1
- package/dist/{chunk-4BX2VUAB-B2DrODwN.js → chunk-4BX2VUAB-B8iHvpDe.js} +1 -1
- package/dist/{chunk-55IACEB6-BUWDsQ-t.js → chunk-55IACEB6-CJs4dL1H.js} +1 -1
- package/dist/{chunk-FMBD7UC4-BExPNFv1.js → chunk-FMBD7UC4-C5irHg20.js} +1 -1
- package/dist/{chunk-K7UQS3LO-Cixi-Yko.js → chunk-K7UQS3LO-B2XW75WS.js} +4 -4
- package/dist/{chunk-QN33PNHL-B83MtvER.js → chunk-QN33PNHL-BtwctqGa.js} +1 -1
- package/dist/{chunk-QZHKN3VN-CXvbu85X.js → chunk-QZHKN3VN-Sb8ZD0FY.js} +1 -1
- package/dist/{chunk-TVAH2DTR-CpiumCHg.js → chunk-TVAH2DTR-CEJ5zkLX.js} +3 -3
- package/dist/{chunk-TZMSLE5B-DIzaZjcI.js → chunk-TZMSLE5B-Ccm4T92V.js} +1 -1
- package/dist/{classDiagram-v2-RKCZMP56-DyN5HPdk.js → classDiagram-KNZD7YFC-DWTbT0ww.js} +2 -2
- package/dist/{classDiagram-KNZD7YFC-DyN5HPdk.js → classDiagram-v2-RKCZMP56-DWTbT0ww.js} +2 -2
- package/dist/{clone-DrJYap2i.js → clone-CDDiMerE.js} +1 -1
- package/dist/{cose-bilkent-S5V4N54A-D39b4WrQ.js → cose-bilkent-S5V4N54A-B74aLjZ_.js} +2 -2
- package/dist/{dagre-5GWH7T2D-BLjRxDpS.js → dagre-5GWH7T2D-Cie_wUzI.js} +6 -6
- package/dist/{data-grid-overlay-editor-DTALqerV.js → data-grid-overlay-editor-OsCMRzfP.js} +2 -2
- package/dist/{diagram-N5W7TBWH-MM8AIKGR.js → diagram-N5W7TBWH-CCeFeV2B.js} +5 -5
- package/dist/{diagram-QEK2KX5R-BZGarWuJ.js → diagram-QEK2KX5R-BPROiVV7.js} +3 -3
- package/dist/{diagram-S2PKOQOG-CnPinN9Q.js → diagram-S2PKOQOG-BkLFbUa_.js} +3 -3
- package/dist/{dockerfile-U8DnCJ4X.js → dockerfile-CuJXUZQ_.js} +1 -1
- package/dist/{erDiagram-AWTI2OKA-CvDVbxOO.js → erDiagram-AWTI2OKA-CpBWOTMK.js} +4 -4
- package/dist/{flowDiagram-PVAE7QVJ-C2uuBTZS.js → flowDiagram-PVAE7QVJ-C_X4bmq3.js} +5 -5
- package/dist/{ganttDiagram-OWAHRB6G-BEff10RF.js → ganttDiagram-OWAHRB6G-CruldwEp.js} +4 -4
- package/dist/{gitGraphDiagram-NY62KEGX-wggu0kb2.js → gitGraphDiagram-NY62KEGX-ZJnVxaQP.js} +4 -4
- package/dist/{glide-data-editor-Bqh5_dzJ.js → glide-data-editor-CnOBht4I.js} +3 -3
- package/dist/{graph-DKpp_wzf.js → graph-DjTtWtcG.js} +3 -3
- package/dist/{index-DzJ_YPCG.js → index-CZ9vIBEc.js} +3 -3
- package/dist/{index-DdfF_cLK.js → index-DSpjUDnr.js} +1 -1
- package/dist/{index-DW0BCGJE.js → index-DeOkA9fC.js} +1 -1
- package/dist/{index-4XruEJkp.js → index-saLjL5eo.js} +1 -1
- package/dist/{infoDiagram-STP46IZ2-DF7KW-Op.js → infoDiagram-STP46IZ2-RZgl96nR.js} +2 -2
- package/dist/{journeyDiagram-BIP6EPQ6-B_jmhmqd.js → journeyDiagram-BIP6EPQ6-9ytZy-zY.js} +3 -3
- package/dist/{kanban-definition-6OIFK2YF-B-M9FTyw.js → kanban-definition-6OIFK2YF-CpjPWkgX.js} +2 -2
- package/dist/{layout-C4oVYZZD.js → layout-BAvhX25D.js} +4 -4
- package/dist/{linear-C-HCGr0T.js → linear-BKZuvJrK.js} +1 -1
- package/dist/{main-B9x2-9f2.js → main-pE28kbH0.js} +37316 -36749
- package/dist/main.js +1 -1
- package/dist/{mermaid-BE4cM3Qs.js → mermaid-BGd7kEsf.js} +30 -30
- package/dist/{min-DTpHJ698.js → min-DmsBJf5S.js} +2 -2
- package/dist/{mindmap-definition-Q6HEUPPD-Cpd-hO1E.js → mindmap-definition-Q6HEUPPD-BerypnVD.js} +3 -3
- package/dist/{number-overlay-editor-CvURA2Ud.js → number-overlay-editor-A7ZQ6sSs.js} +2 -2
- package/dist/{pieDiagram-ADFJNKIX-D9f_f6fn.js → pieDiagram-ADFJNKIX-ChcQLZM2.js} +3 -3
- package/dist/{quadrantDiagram-LMRXKWRM-DgllE7xw.js → quadrantDiagram-LMRXKWRM-BOMxo-5G.js} +2 -2
- package/dist/{react-plotly-BU-JRJSi.js → react-plotly-8f0vAPdB.js} +1 -1
- package/dist/{requirementDiagram-4UW4RH46-Dk_G8eUb.js → requirementDiagram-4UW4RH46-rql0vkIr.js} +3 -3
- package/dist/{sankeyDiagram-GR3RE2ED-BhLIhDc1.js → sankeyDiagram-GR3RE2ED-BMz3hqw4.js} +1 -1
- package/dist/{sequenceDiagram-C3RYC4MD-DHoZdMFJ.js → sequenceDiagram-C3RYC4MD-DFK39LUK.js} +3 -3
- package/dist/{slides-component-DXAgdf7K.js → slides-component-BNsZuUgg.js} +1 -1
- package/dist/{stateDiagram-KXAO66HF-C1Ie-7Xf.js → stateDiagram-KXAO66HF-B_3SWOCl.js} +4 -4
- package/dist/{stateDiagram-v2-UMBNRL4Z--CRuIHtM.js → stateDiagram-v2-UMBNRL4Z-BWAYN6aU.js} +2 -2
- package/dist/style.css +1 -1
- package/dist/{time-yQjlGPwa.js → time-DCvYzQ5t.js} +2 -2
- package/dist/{timeline-definition-XQNQX7LJ-D_PjxB1B.js → timeline-definition-XQNQX7LJ-CSbxJ5mV.js} +1 -1
- package/dist/{treemap-75Q7IDZK--NYqQjUZ.js → treemap-75Q7IDZK-LMXGKln_.js} +5 -5
- package/dist/{vega-component-CCUOMM5K.js → vega-component-CRmon7pH.js} +2 -2
- package/dist/{xychartDiagram-6GGTOJPD-WLKsEnzs.js → xychartDiagram-6GGTOJPD-DaUD4dq3.js} +2 -2
- package/package.json +3 -3
- package/src/__mocks__/requests.ts +1 -0
- package/src/components/data-table/__tests__/columns.test.tsx +38 -0
- package/src/components/data-table/cell-hover-template/feature.ts +1 -1
- package/src/components/data-table/cell-hover-template/types.ts +1 -1
- package/src/components/data-table/columns.tsx +21 -2
- package/src/components/data-table/renderers.tsx +16 -8
- package/src/components/data-table/schemas.ts +16 -0
- package/src/components/editor/Cell.tsx +2 -0
- package/src/components/editor/errors/sql-validation-errors.tsx +34 -0
- package/src/components/editor/output/ConsoleOutput.tsx +13 -1
- package/src/components/editor/output/MarimoErrorOutput.tsx +60 -1
- package/src/core/ai/context/providers/cell-output.ts +1 -18
- package/src/core/codemirror/language/__tests__/extension.test.ts +24 -0
- package/src/core/codemirror/language/__tests__/sql-validation.test.ts +133 -0
- package/src/core/codemirror/language/languages/sql/sql-mode.ts +20 -0
- package/src/core/codemirror/language/languages/sql/sql.ts +90 -3
- package/src/core/codemirror/language/languages/sql/validation-errors.ts +79 -0
- package/src/core/codemirror/language/panel/panel.tsx +8 -2
- package/src/core/codemirror/language/panel/sql.tsx +81 -4
- package/src/core/config/feature-flag.tsx +3 -1
- package/src/core/datasets/request-registry.ts +17 -1
- package/src/core/islands/bridge.ts +1 -0
- package/src/core/islands/main.ts +1 -0
- package/src/core/kernel/messages.ts +1 -0
- package/src/core/network/requests-network.ts +7 -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 +2 -0
- package/src/core/wasm/bridge.ts +5 -0
- package/src/core/websocket/useMarimoWebSocket.tsx +4 -0
- package/src/plugins/core/registerReactComponent.tsx +23 -19
- package/src/plugins/impl/DataTablePlugin.tsx +11 -4
- package/src/plugins/impl/data-frames/DataFramePlugin.tsx +17 -5
- package/src/stories/dataframe.stories.tsx +2 -0
- package/src/utils/__tests__/dom.test.ts +167 -0
- package/src/utils/dom.ts +55 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { R as q, r as ln, o as P, q as X, C as Z, u as D, v as fn } from "./timer-B0-z63CM.js";
|
|
2
|
-
import { c as hn, a as on } from "./linear-
|
|
2
|
+
import { c as hn, a as on } from "./linear-BKZuvJrK.js";
|
|
3
3
|
import { i as bn } from "./init-DxzjmxYy.js";
|
|
4
|
-
import { W as gn, X as d, Y as v, Z as k, _ as x, $ as L, a0 as yn, a1 as R, a2 as H, a3 as W, a4 as pn, a5 as mn, a6 as wn, a7 as Mn, a8 as dn, a9 as vn, aa as kn, ab as $, ac as A, ad as B, ae as K, af as _, ag as j, ah as xn } from "./main-
|
|
4
|
+
import { W as gn, X as d, Y as v, Z as k, _ as x, $ as L, a0 as yn, a1 as R, a2 as H, a3 as W, a4 as pn, a5 as mn, a6 as wn, a7 as Mn, a8 as dn, a9 as vn, aa as kn, ab as $, ac as A, ad as B, ae as K, af as _, ag as j, ah as xn } from "./main-pE28kbH0.js";
|
|
5
5
|
function Pn(n, t) {
|
|
6
6
|
let i;
|
|
7
7
|
if (t === void 0)
|
package/dist/{timeline-definition-XQNQX7LJ-D_PjxB1B.js → timeline-definition-XQNQX7LJ-CSbxJ5mV.js}
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { _ as s, c as xt, l as E, d as j, V as kt, W as vt, X as _t, Y as bt, D as wt, $ as St, z as Et } from "./mermaid-
|
|
1
|
+
import { _ as s, c as xt, l as E, d as j, V as kt, W as vt, X as _t, Y as bt, D as wt, $ as St, z as Et } from "./mermaid-BGd7kEsf.js";
|
|
2
2
|
import { d as nt } from "./arc-BOhn-m2C.js";
|
|
3
3
|
var Q = (function() {
|
|
4
4
|
var n = /* @__PURE__ */ s(function(x, r, a, c) {
|
|
@@ -2,11 +2,11 @@ var __defProp = Object.defineProperty;
|
|
|
2
2
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
3
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
4
|
var _a2, _b, _c2, _d2, _e, _f2, _g, _h2, _i2, _j, _k, _l2, _m2, _n2, _o2, _p2, _q;
|
|
5
|
-
import { d_ as Kl, bQ as Vl, al as Ma, aT as Hl, u as _t, aO as Wl, ao as Z, bk as jl, bl as zl, k as ql, n as Ni, m as Da, am as Zs, d$ as Fa, aK as Yl, b0 as Ga, e0 as Ua, aM as Xl, b2 as wi, an as Jl, e1 as Ql, bg as Zl, e2 as eu, aP as Ie, q as vt, ar as _i, b3 as tu, e3 as H, e4 as Re } from "./main-
|
|
6
|
-
import { g as ei, d as bt, k as nu, v as W, l as Ba, m as ru, n as su, a as Ka, c as x, i as qe, r as ae, f as ke, o as z } from "./_baseUniq-
|
|
7
|
-
import { m as k, f as Lt, h as I, e as ti, l as Ot, d as iu } from "./min-
|
|
8
|
-
import { ad as P } from "./mermaid-
|
|
9
|
-
import { c as te } from "./clone-
|
|
5
|
+
import { d_ as Kl, bQ as Vl, al as Ma, aT as Hl, u as _t, aO as Wl, ao as Z, bk as jl, bl as zl, k as ql, n as Ni, m as Da, am as Zs, d$ as Fa, aK as Yl, b0 as Ga, e0 as Ua, aM as Xl, b2 as wi, an as Jl, e1 as Ql, bg as Zl, e2 as eu, aP as Ie, q as vt, ar as _i, b3 as tu, e3 as H, e4 as Re } from "./main-pE28kbH0.js";
|
|
6
|
+
import { g as ei, d as bt, k as nu, v as W, l as Ba, m as ru, n as su, a as Ka, c as x, i as qe, r as ae, f as ke, o as z } from "./_baseUniq-BykZEXIq.js";
|
|
7
|
+
import { m as k, f as Lt, h as I, e as ti, l as Ot, d as iu } from "./min-DmsBJf5S.js";
|
|
8
|
+
import { ad as P } from "./mermaid-BGd7kEsf.js";
|
|
9
|
+
import { c as te } from "./clone-CDDiMerE.js";
|
|
10
10
|
var au = Object.prototype, ou = au.hasOwnProperty, Ee = Kl(function(n, e) {
|
|
11
11
|
if (Vl(e) || Ma(e)) {
|
|
12
12
|
Hl(e, _t(e), n);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { D as V, F as K, A as Z, G as z, H as R, I as G, j as d, E as W, J as U, r as k, K as j, M, N as $, L as x, O as _, P as B, Q, S as q, T as J, U as Y, V as X } from "./main-
|
|
2
|
-
import { M as m, V as ee } from "./index-
|
|
1
|
+
import { D as V, F as K, A as Z, G as z, H as R, I as G, j as d, E as W, J as U, r as k, K as j, M, N as $, L as x, O as _, P as B, Q, S as q, T as J, U as Y, V as X } from "./main-pE28kbH0.js";
|
|
2
|
+
import { M as m, V as ee } from "./index-CZ9vIBEc.js";
|
|
3
3
|
function te(e) {
|
|
4
4
|
return e.data && "url" in e.data && (e.data.url = V(e.data.url).href), e;
|
|
5
5
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i2, _j;
|
|
2
|
-
import { _ as a, s as ei, g as si, t as Lt, q as ni, a as ai, b as oi, l as Et, K as ri, e as hi, z as li, G as xt, F as It, H as ci, M as ui, i as gi, ac as xi } from "./mermaid-
|
|
2
|
+
import { _ as a, s as ei, g as si, t as Lt, q as ni, a as ai, b as oi, l as Et, K as ri, e as hi, z as li, G as xt, F as It, H as ci, M as ui, i as gi, ac as xi } from "./mermaid-BGd7kEsf.js";
|
|
3
3
|
import { i as di } from "./init-DxzjmxYy.js";
|
|
4
4
|
import { o as fi } from "./ordinal-CYN5qNoq.js";
|
|
5
5
|
import { r as pi } from "./range-DdOGybNB.js";
|
|
6
|
-
import { l as Tt } from "./linear-
|
|
6
|
+
import { l as Tt } from "./linear-BKZuvJrK.js";
|
|
7
7
|
import { l as Dt } from "./timer-B0-z63CM.js";
|
|
8
8
|
function ht() {
|
|
9
9
|
var t = fi().unknown(void 0), i = t.domain, e = t.range, s = 0, n = 1, d, g, m = false, S = 0, D = 0, v = 0.5;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@marimo-team/islands",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.2",
|
|
4
4
|
"main": "dist/main.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -245,8 +245,8 @@
|
|
|
245
245
|
"storybook": "^9.1.3",
|
|
246
246
|
"stylelint": "^16.23.1",
|
|
247
247
|
"stylelint-config-standard": "^36.0.1",
|
|
248
|
-
"tailwindcss": "^4.1.
|
|
249
|
-
"vite": "^6.3.
|
|
248
|
+
"tailwindcss": "^4.1.13",
|
|
249
|
+
"vite": "^6.3.6",
|
|
250
250
|
"vite-plugin-top-level-await": "^1.6.0",
|
|
251
251
|
"vite-plugin-wasm": "^3.5.0",
|
|
252
252
|
"vite-tsconfig-paths": "^5.1.4",
|
|
@@ -40,6 +40,7 @@ export const MockRequestClient = {
|
|
|
40
40
|
previewSQLTable: vi.fn().mockResolvedValue({}),
|
|
41
41
|
previewSQLTableList: vi.fn().mockResolvedValue({ tables: [] }),
|
|
42
42
|
previewDataSourceConnection: vi.fn().mockResolvedValue({}),
|
|
43
|
+
validateSQL: vi.fn().mockResolvedValue({}),
|
|
43
44
|
openFile: vi.fn().mockResolvedValue({}),
|
|
44
45
|
getUsageStats: vi.fn().mockResolvedValue({}),
|
|
45
46
|
sendPdb: vi.fn().mockResolvedValue({}),
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { render } from "@testing-library/react";
|
|
4
4
|
import { describe, expect, it, test } from "vitest";
|
|
5
|
+
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
5
6
|
import { generateColumns, inferFieldTypes } from "../columns";
|
|
6
7
|
import { getMimeValues, isMimeValue, MimeCell } from "../mime-cell";
|
|
7
8
|
import type { FieldTypesWithExternalType } from "../types";
|
|
@@ -245,6 +246,43 @@ describe("generateColumns", () => {
|
|
|
245
246
|
expect(columns[0].id).toBe("name");
|
|
246
247
|
expect(columns[1].id).toBe("age");
|
|
247
248
|
});
|
|
249
|
+
|
|
250
|
+
it("should render header with tooltip when headerTooltip is provided", () => {
|
|
251
|
+
const columns = generateColumns({
|
|
252
|
+
rowHeaders: [],
|
|
253
|
+
selection: null,
|
|
254
|
+
fieldTypes,
|
|
255
|
+
headerTooltip: { name: "Custom Name Tooltip" },
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
// Get the header function for the first column
|
|
259
|
+
const headerFunction = columns[0].header;
|
|
260
|
+
expect(headerFunction).toBeTypeOf("function");
|
|
261
|
+
|
|
262
|
+
const mockColumn = {
|
|
263
|
+
id: "name",
|
|
264
|
+
getCanSort: () => false,
|
|
265
|
+
getCanFilter: () => false,
|
|
266
|
+
columnDef: {
|
|
267
|
+
meta: {
|
|
268
|
+
dtype: "string",
|
|
269
|
+
dataType: "string",
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const { container } = render(
|
|
275
|
+
<TooltipProvider>
|
|
276
|
+
{/* @ts-expect-error: mock column and header function */}
|
|
277
|
+
{headerFunction({ column: mockColumn })}
|
|
278
|
+
</TooltipProvider>,
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
expect(container.textContent).toContain("name");
|
|
282
|
+
// The tooltip functionality is tested by verifying that the header renders correctly
|
|
283
|
+
// when headerTooltip is provided.
|
|
284
|
+
expect(container.firstChild).toBeTruthy();
|
|
285
|
+
});
|
|
248
286
|
});
|
|
249
287
|
|
|
250
288
|
describe("MimeCell", () => {
|
|
@@ -17,6 +17,7 @@ import { JsonOutput } from "../editor/output/JsonOutput";
|
|
|
17
17
|
import { Button } from "../ui/button";
|
|
18
18
|
import { Checkbox } from "../ui/checkbox";
|
|
19
19
|
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
|
|
20
|
+
import { Tooltip } from "../ui/tooltip";
|
|
20
21
|
import { DataTableColumnHeader } from "./column-header";
|
|
21
22
|
import type { ColumnChartSpecModel } from "./column-summary/chart-spec-model";
|
|
22
23
|
import { TableColumnSummary } from "./column-summary/column-summary";
|
|
@@ -103,6 +104,7 @@ export function generateColumns<T>({
|
|
|
103
104
|
chartSpecModel,
|
|
104
105
|
textJustifyColumns,
|
|
105
106
|
wrappedColumns,
|
|
107
|
+
headerTooltip,
|
|
106
108
|
showDataTypes,
|
|
107
109
|
calculateTopKRows,
|
|
108
110
|
}: {
|
|
@@ -112,6 +114,7 @@ export function generateColumns<T>({
|
|
|
112
114
|
chartSpecModel?: ColumnChartSpecModel<unknown>;
|
|
113
115
|
textJustifyColumns?: Record<string, "left" | "center" | "right">;
|
|
114
116
|
wrappedColumns?: string[];
|
|
117
|
+
headerTooltip?: Record<string, string>;
|
|
115
118
|
showDataTypes?: boolean;
|
|
116
119
|
calculateTopKRows?: CalculateTopKRows;
|
|
117
120
|
}): Array<ColumnDef<T>> {
|
|
@@ -165,6 +168,7 @@ export function generateColumns<T>({
|
|
|
165
168
|
header: ({ column }) => {
|
|
166
169
|
const stats = chartSpecModel?.getColumnStats(key);
|
|
167
170
|
const dtype = column.columnDef.meta?.dtype;
|
|
171
|
+
const headerTitle = headerTooltip?.[key];
|
|
168
172
|
const dtypeHeader =
|
|
169
173
|
showDataTypes && dtype ? (
|
|
170
174
|
<div className="flex flex-row gap-1">
|
|
@@ -179,14 +183,29 @@ export function generateColumns<T>({
|
|
|
179
183
|
|
|
180
184
|
const headerWithType = (
|
|
181
185
|
<div className="flex flex-col">
|
|
182
|
-
<span
|
|
186
|
+
<span
|
|
187
|
+
className={cn(
|
|
188
|
+
"font-bold",
|
|
189
|
+
headerTitle && "underline decoration-dotted",
|
|
190
|
+
)}
|
|
191
|
+
>
|
|
192
|
+
{key === "" ? " " : key}
|
|
193
|
+
</span>
|
|
183
194
|
{dtypeHeader}
|
|
184
195
|
</div>
|
|
185
196
|
);
|
|
186
197
|
|
|
198
|
+
const headerWithTooltip = headerTitle ? (
|
|
199
|
+
<Tooltip content={headerTitle} delayDuration={300}>
|
|
200
|
+
{headerWithType}
|
|
201
|
+
</Tooltip>
|
|
202
|
+
) : (
|
|
203
|
+
headerWithType
|
|
204
|
+
);
|
|
205
|
+
|
|
187
206
|
const dataTableColumnHeader = (
|
|
188
207
|
<DataTableColumnHeader
|
|
189
|
-
header={
|
|
208
|
+
header={headerWithTooltip}
|
|
190
209
|
column={column}
|
|
191
210
|
calculateTopKRows={calculateTopKRows}
|
|
192
211
|
/>
|
|
@@ -106,9 +106,9 @@ export const DataTableBody = <TData,>({
|
|
|
106
106
|
const s = renderUnknownValue({ value: v, nullAsEmptyString: true });
|
|
107
107
|
idToValue.set(c.column.id, s);
|
|
108
108
|
}
|
|
109
|
-
return template.
|
|
109
|
+
return template.replaceAll(variableRegex, (_substr, varName: string) => {
|
|
110
110
|
const val = idToValue.get(varName);
|
|
111
|
-
return val
|
|
111
|
+
return val === undefined ? `{{${varName}}}` : val;
|
|
112
112
|
});
|
|
113
113
|
}
|
|
114
114
|
|
|
@@ -154,6 +154,8 @@ export const DataTableBody = <TData,>({
|
|
|
154
154
|
}
|
|
155
155
|
};
|
|
156
156
|
|
|
157
|
+
const hoverTemplate = table.getState().cellHoverTemplate || null;
|
|
158
|
+
|
|
157
159
|
return (
|
|
158
160
|
<TableBody onKeyDown={handleCellsKeyDown} ref={tableRef}>
|
|
159
161
|
{table.getRowModel().rows?.length ? (
|
|
@@ -165,12 +167,18 @@ export const DataTableBody = <TData,>({
|
|
|
165
167
|
const isRowViewedInPanel =
|
|
166
168
|
rowViewerPanelOpen && viewedRowIdx === rowIndex;
|
|
167
169
|
|
|
168
|
-
// Compute hover title once per row using
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
170
|
+
// Compute hover title once per row using all visible cells
|
|
171
|
+
let rowTitle: string | undefined;
|
|
172
|
+
if (hoverTemplate) {
|
|
173
|
+
const visibleCells = row.getVisibleCells?.() ?? [
|
|
174
|
+
...row.getLeftVisibleCells(),
|
|
175
|
+
...row.getCenterVisibleCells(),
|
|
176
|
+
...row.getRightVisibleCells(),
|
|
177
|
+
];
|
|
178
|
+
rowTitle = hoverTemplate
|
|
179
|
+
? applyHoverTemplate(hoverTemplate, visibleCells)
|
|
180
|
+
: undefined;
|
|
181
|
+
}
|
|
174
182
|
|
|
175
183
|
return (
|
|
176
184
|
<TableRow
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import z from "zod";
|
|
4
|
+
import { rpc } from "@/plugins/core/rpc";
|
|
5
|
+
|
|
6
|
+
export type DownloadAsArgs = (req: {
|
|
7
|
+
format: "csv" | "json" | "parquet";
|
|
8
|
+
}) => Promise<string>;
|
|
9
|
+
|
|
10
|
+
export const DownloadAsSchema = rpc
|
|
11
|
+
.input(
|
|
12
|
+
z.object({
|
|
13
|
+
format: z.enum(["csv", "json", "parquet"]),
|
|
14
|
+
}),
|
|
15
|
+
)
|
|
16
|
+
.output(z.string());
|
|
@@ -80,6 +80,7 @@ import { useDeleteCellCallback } from "./cell/useDeleteCell";
|
|
|
80
80
|
import { useRunCell } from "./cell/useRunCells";
|
|
81
81
|
import { HideCodeButton } from "./code/readonly-python-code";
|
|
82
82
|
import { cellDomProps } from "./common";
|
|
83
|
+
import { SqlValidationErrorBanner } from "./errors/sql-validation-errors";
|
|
83
84
|
import { useCellNavigationProps } from "./navigation/navigation";
|
|
84
85
|
import {
|
|
85
86
|
useTemporarilyShownCode,
|
|
@@ -653,6 +654,7 @@ const EditableCellComponent = ({
|
|
|
653
654
|
)}
|
|
654
655
|
</div>
|
|
655
656
|
</div>
|
|
657
|
+
<SqlValidationErrorBanner cellId={cellId} />
|
|
656
658
|
{cellOutput === "below" && outputArea}
|
|
657
659
|
{cellRuntime.serialization && (
|
|
658
660
|
<div className="py-1 px-2 flex items-center justify-end gap-2 last:rounded-b">
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { AlertCircleIcon } from "lucide-react";
|
|
4
|
+
import type { CellId } from "@/core/cells/ids";
|
|
5
|
+
import { useSqlValidationErrorsForCell } from "@/core/codemirror/language/languages/sql/validation-errors";
|
|
6
|
+
|
|
7
|
+
export const SqlValidationErrorBanner = ({ cellId }: { cellId: CellId }) => {
|
|
8
|
+
const error = useSqlValidationErrorsForCell(cellId);
|
|
9
|
+
|
|
10
|
+
if (!error) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<div className="p-3 text-sm flex flex-col text-muted-foreground gap-1.5 bg-destructive/5">
|
|
16
|
+
<div className="flex items-start gap-1.5">
|
|
17
|
+
<AlertCircleIcon size={13} className="mt-[3px] text-destructive" />
|
|
18
|
+
<p>
|
|
19
|
+
<span className="font-bold text-destructive">{error.errorType}:</span>{" "}
|
|
20
|
+
{error.errorMessage}
|
|
21
|
+
</p>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
{error.codeblock && (
|
|
25
|
+
<pre
|
|
26
|
+
lang="sql"
|
|
27
|
+
className="text-xs bg-muted rounded p-2 pb-0 mx-3 overflow-x-auto font-mono whitespace-pre-wrap"
|
|
28
|
+
>
|
|
29
|
+
{error.codeblock}
|
|
30
|
+
</pre>
|
|
31
|
+
)}
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
};
|
|
@@ -14,6 +14,7 @@ import type { OutputMessage } from "@/core/kernel/messages";
|
|
|
14
14
|
import { useSelectAllContent } from "@/hooks/useSelectAllContent";
|
|
15
15
|
import { cn } from "@/utils/cn";
|
|
16
16
|
import { copyToClipboard } from "@/utils/copy";
|
|
17
|
+
import { ansiToPlainText, parseHtmlContent } from "@/utils/dom";
|
|
17
18
|
import { invariant } from "@/utils/invariant";
|
|
18
19
|
import { Strings } from "@/utils/strings";
|
|
19
20
|
import { NameCellContentEditable } from "../actions/name-cell-input";
|
|
@@ -124,7 +125,18 @@ const ConsoleOutputInternal = (props: Props): React.ReactNode => {
|
|
|
124
125
|
onClick={() => {
|
|
125
126
|
const text = reversedOutputs
|
|
126
127
|
.filter((output) => output.channel !== "pdb")
|
|
127
|
-
.map((output) =>
|
|
128
|
+
.map((output) => {
|
|
129
|
+
// If starts with `<`, then assume it's HTML
|
|
130
|
+
if (
|
|
131
|
+
typeof output.data === "string" &&
|
|
132
|
+
output.data.startsWith("<")
|
|
133
|
+
) {
|
|
134
|
+
return parseHtmlContent(output.data);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Otherwise, convert the ANSI to HTML, then parse as HTML
|
|
138
|
+
return ansiToPlainText(Strings.asString(output.data));
|
|
139
|
+
})
|
|
128
140
|
.join("\n");
|
|
129
141
|
void copyToClipboard(text);
|
|
130
142
|
}}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
InfoIcon,
|
|
5
|
+
NotebookPenIcon,
|
|
6
|
+
SquareArrowOutUpRightIcon,
|
|
7
|
+
} from "lucide-react";
|
|
4
8
|
import { Fragment, type JSX } from "react";
|
|
5
9
|
import {
|
|
6
10
|
Accordion,
|
|
@@ -72,6 +76,8 @@ export const MarimoErrorOutput = ({
|
|
|
72
76
|
titleContents = "Ancestor stopped";
|
|
73
77
|
alertVariant = "default";
|
|
74
78
|
titleColor = "text-secondary-foreground";
|
|
79
|
+
} else if (errors.some((e) => e.type === "sql-error")) {
|
|
80
|
+
titleContents = "SQL Error in statement";
|
|
75
81
|
} else {
|
|
76
82
|
// Check for exception type
|
|
77
83
|
const exceptionError = errors.find((e) => e.type === "exception");
|
|
@@ -126,6 +132,10 @@ export const MarimoErrorOutput = ({
|
|
|
126
132
|
const unknownErrors = errors.filter(
|
|
127
133
|
(e): e is Extract<MarimoError, { type: "unknown" }> => e.type === "unknown",
|
|
128
134
|
);
|
|
135
|
+
const sqlErrors = errors.filter(
|
|
136
|
+
(e): e is Extract<MarimoError, { type: "sql-error" }> =>
|
|
137
|
+
e.type === "sql-error",
|
|
138
|
+
);
|
|
129
139
|
|
|
130
140
|
const openScratchpad = () => {
|
|
131
141
|
chromeActions.openApplication("scratchpad");
|
|
@@ -485,6 +495,55 @@ export const MarimoErrorOutput = ({
|
|
|
485
495
|
);
|
|
486
496
|
}
|
|
487
497
|
|
|
498
|
+
if (sqlErrors.length > 0) {
|
|
499
|
+
messages.push(
|
|
500
|
+
<div key="sql-errors">
|
|
501
|
+
{sqlErrors.map((error, idx) => {
|
|
502
|
+
const line =
|
|
503
|
+
error.sql_line != null ? (error?.sql_line | 0) + 1 : null;
|
|
504
|
+
const col = error.sql_col != null ? (error?.sql_col | 0) + 1 : null;
|
|
505
|
+
return (
|
|
506
|
+
<div key={`sql-error-${idx}`} className="space-y-2">
|
|
507
|
+
<p className="text-muted-foreground">{error.msg}</p>
|
|
508
|
+
{error.hint && (
|
|
509
|
+
<div className="flex items-start gap-2">
|
|
510
|
+
<InfoIcon className="h-4 w-4 text-muted-foreground mt-0.5 flex-shrink-0" />
|
|
511
|
+
<pre className="whitespace-pre-wrap text-sm text-muted-foreground">
|
|
512
|
+
{error.hint}
|
|
513
|
+
</pre>
|
|
514
|
+
</div>
|
|
515
|
+
)}
|
|
516
|
+
{error.sql_statement && (
|
|
517
|
+
<div className="bg-muted/50 p-2 rounded text-xs font-mono">
|
|
518
|
+
<pre className="whitespace-pre-wrap">
|
|
519
|
+
{error.sql_statement}
|
|
520
|
+
</pre>
|
|
521
|
+
</div>
|
|
522
|
+
)}
|
|
523
|
+
{line !== null && col !== null && (
|
|
524
|
+
<p className="text-xs text-muted-foreground">
|
|
525
|
+
Error at line {line}, column {col}
|
|
526
|
+
</p>
|
|
527
|
+
)}
|
|
528
|
+
</div>
|
|
529
|
+
);
|
|
530
|
+
})}
|
|
531
|
+
{cellId && <AutoFixButton errors={sqlErrors} cellId={cellId} />}
|
|
532
|
+
<Tip title="How to fix SQL errors">
|
|
533
|
+
<p className="pb-2">
|
|
534
|
+
SQL parsing errors often occur due to invalid syntax, missing
|
|
535
|
+
keywords, or unsupported SQL features.
|
|
536
|
+
</p>
|
|
537
|
+
<p className="py-2">
|
|
538
|
+
Check your SQL syntax and ensure you're using supported SQL
|
|
539
|
+
dialect features. The error location can help you identify the
|
|
540
|
+
problematic part of your query.
|
|
541
|
+
</p>
|
|
542
|
+
</Tip>
|
|
543
|
+
</div>,
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
|
|
488
547
|
return messages;
|
|
489
548
|
};
|
|
490
549
|
|
|
@@ -9,6 +9,7 @@ import { displayCellName } from "@/core/cells/names";
|
|
|
9
9
|
import { isOutputEmpty } from "@/core/cells/outputs";
|
|
10
10
|
import type { OutputMessage } from "@/core/kernel/messages";
|
|
11
11
|
import type { JotaiStore } from "@/core/state/jotai";
|
|
12
|
+
import { parseHtmlContent } from "@/utils/dom";
|
|
12
13
|
import { Logger } from "@/utils/Logger";
|
|
13
14
|
import { type AIContextItem, AIContextProvider } from "../registry";
|
|
14
15
|
import { contextToXml } from "../utils";
|
|
@@ -64,24 +65,6 @@ function isMediaMimetype(
|
|
|
64
65
|
return false;
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
function parseHtmlContent(htmlString: string): string {
|
|
68
|
-
try {
|
|
69
|
-
// Create a temporary DOM element to parse HTML
|
|
70
|
-
const tempDiv = document.createElement("div");
|
|
71
|
-
tempDiv.innerHTML = htmlString;
|
|
72
|
-
|
|
73
|
-
// Extract text content, removing HTML tags
|
|
74
|
-
const textContent = tempDiv.textContent || tempDiv.innerText || "";
|
|
75
|
-
|
|
76
|
-
// Clean up extra whitespace
|
|
77
|
-
return textContent.replaceAll(/\s+/g, " ").trim();
|
|
78
|
-
} catch (error) {
|
|
79
|
-
Logger.error("Error parsing HTML content:", error);
|
|
80
|
-
// If parsing fails, return the original string
|
|
81
|
-
return htmlString;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
68
|
export class CellOutputContextProvider extends AIContextProvider<CellOutputContextItem> {
|
|
86
69
|
readonly title = "Cell Outputs";
|
|
87
70
|
readonly mentionPrefix = "@";
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
languageAdapterState,
|
|
15
15
|
switchLanguage,
|
|
16
16
|
} from "../extension";
|
|
17
|
+
import { exportedForTesting as sqlValidationErrorsForTesting } from "../languages/sql/validation-errors";
|
|
17
18
|
import { languageMetadataField } from "../metadata";
|
|
18
19
|
|
|
19
20
|
let view: EditorView | null = null;
|
|
@@ -258,3 +259,26 @@ describe("switchLanguage", () => {
|
|
|
258
259
|
});
|
|
259
260
|
});
|
|
260
261
|
});
|
|
262
|
+
|
|
263
|
+
describe("sqlValidationErrors", () => {
|
|
264
|
+
const { splitErrorMessage } = sqlValidationErrorsForTesting;
|
|
265
|
+
|
|
266
|
+
describe("split error message", () => {
|
|
267
|
+
it("should split the error message into error type and error message", () => {
|
|
268
|
+
const error = "SyntaxError: SELECT * FROM df";
|
|
269
|
+
const { errorType, errorMessage } = splitErrorMessage(error);
|
|
270
|
+
expect(errorType).toBe("SyntaxError");
|
|
271
|
+
expect(errorMessage).toBe("SELECT * FROM df");
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it("should handle multiple colons", () => {
|
|
275
|
+
const error =
|
|
276
|
+
"SyntaxError: SELECT * FROM df:SyntaxError: SELECT * FROM df";
|
|
277
|
+
const { errorType, errorMessage } = splitErrorMessage(error);
|
|
278
|
+
expect(errorType).toBe("SyntaxError");
|
|
279
|
+
expect(errorMessage).toBe(
|
|
280
|
+
"SELECT * FROM df:SyntaxError: SELECT * FROM df",
|
|
281
|
+
);
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
});
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { exportedForTesting } from "../languages/sql/validation-errors";
|
|
5
|
+
|
|
6
|
+
describe("Error Message Splitting", () => {
|
|
7
|
+
it("should handle error message splitting correctly", () => {
|
|
8
|
+
const { splitErrorMessage } = exportedForTesting;
|
|
9
|
+
|
|
10
|
+
const result1 = splitErrorMessage("Syntax error: unexpected token");
|
|
11
|
+
expect(result1.errorType).toBe("Syntax error");
|
|
12
|
+
expect(result1.errorMessage).toBe("unexpected token");
|
|
13
|
+
|
|
14
|
+
const result2 = splitErrorMessage("Multiple: colons: in error");
|
|
15
|
+
expect(result2.errorType).toBe("Multiple");
|
|
16
|
+
expect(result2.errorMessage).toBe("colons: in error");
|
|
17
|
+
|
|
18
|
+
const result3 = splitErrorMessage("No colon error");
|
|
19
|
+
expect(result3.errorType).toBe("No colon error");
|
|
20
|
+
expect(result3.errorMessage).toBe("");
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe("DuckDB Error Handling", () => {
|
|
25
|
+
it("should extract codeblock from error with LINE information", () => {
|
|
26
|
+
const { handleDuckdbError } = exportedForTesting;
|
|
27
|
+
|
|
28
|
+
const error =
|
|
29
|
+
'Binder Error: Referenced column "attacks" not found in FROM clause! Candidate bindings: "Attack", "Total" LINE 1:... from pokemon WHERE \'type_2\' = 32 and attack = 32 and not attacks = \'hi\' ^';
|
|
30
|
+
|
|
31
|
+
const result = handleDuckdbError(error);
|
|
32
|
+
|
|
33
|
+
expect(result.errorType).toBe("Binder Error");
|
|
34
|
+
expect(result.errorMessage).toBe(
|
|
35
|
+
'Referenced column "attacks" not found in FROM clause! Candidate bindings: "Attack", "Total"',
|
|
36
|
+
);
|
|
37
|
+
expect(result.codeblock).toBe(
|
|
38
|
+
"LINE 1:... from pokemon WHERE 'type_2' = 32 and attack = 32 and not attacks = 'hi' ^",
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("should handle error without LINE information", () => {
|
|
43
|
+
const { handleDuckdbError } = exportedForTesting;
|
|
44
|
+
|
|
45
|
+
const error = "Syntax Error: Invalid syntax near WHERE";
|
|
46
|
+
|
|
47
|
+
const result = handleDuckdbError(error);
|
|
48
|
+
|
|
49
|
+
expect(result.errorType).toBe("Syntax Error");
|
|
50
|
+
expect(result.errorMessage).toBe("Invalid syntax near WHERE");
|
|
51
|
+
expect(result.codeblock).toBeUndefined();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("should handle error with LINE at the beginning", () => {
|
|
55
|
+
const { handleDuckdbError } = exportedForTesting;
|
|
56
|
+
|
|
57
|
+
const error = "LINE 1: SELECT * FROM table WHERE invalid_column = 1 ^";
|
|
58
|
+
|
|
59
|
+
const result = handleDuckdbError(error);
|
|
60
|
+
|
|
61
|
+
expect(result.errorType).toBe("LINE 1");
|
|
62
|
+
expect(result.errorMessage).toBe(
|
|
63
|
+
"SELECT * FROM table WHERE invalid_column = 1 ^",
|
|
64
|
+
);
|
|
65
|
+
expect(result.codeblock).toBeUndefined();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("should handle error with multiple LINE occurrences", () => {
|
|
69
|
+
const { handleDuckdbError } = exportedForTesting;
|
|
70
|
+
|
|
71
|
+
const error =
|
|
72
|
+
"Error: Something went wrong LINE 1: SELECT * FROM table WHERE invalid_column = 1 ^";
|
|
73
|
+
|
|
74
|
+
const result = handleDuckdbError(error);
|
|
75
|
+
|
|
76
|
+
expect(result.errorType).toBe("Error");
|
|
77
|
+
expect(result.errorMessage).toBe("Something went wrong");
|
|
78
|
+
expect(result.codeblock).toBe(
|
|
79
|
+
"LINE 1: SELECT * FROM table WHERE invalid_column = 1 ^",
|
|
80
|
+
);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("should handle complex error with nested quotes", () => {
|
|
84
|
+
const { handleDuckdbError } = exportedForTesting;
|
|
85
|
+
|
|
86
|
+
const error =
|
|
87
|
+
"Binder Error: Column \"name\" not found! LINE 1: SELECT * FROM users WHERE name = 'John' AND age > 25 ^";
|
|
88
|
+
|
|
89
|
+
const result = handleDuckdbError(error);
|
|
90
|
+
|
|
91
|
+
expect(result.errorType).toBe("Binder Error");
|
|
92
|
+
expect(result.errorMessage).toBe('Column "name" not found!');
|
|
93
|
+
expect(result.codeblock).toBe(
|
|
94
|
+
"LINE 1: SELECT * FROM users WHERE name = 'John' AND age > 25 ^",
|
|
95
|
+
);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("should handle error with LINE but no caret", () => {
|
|
99
|
+
const { handleDuckdbError } = exportedForTesting;
|
|
100
|
+
|
|
101
|
+
const error = "Error: Invalid query LINE 1: SELECT * FROM table";
|
|
102
|
+
|
|
103
|
+
const result = handleDuckdbError(error);
|
|
104
|
+
|
|
105
|
+
expect(result.errorType).toBe("Error");
|
|
106
|
+
expect(result.errorMessage).toBe("Invalid query");
|
|
107
|
+
expect(result.codeblock).toBe("LINE 1: SELECT * FROM table");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("should trim whitespace from codeblock", () => {
|
|
111
|
+
const { handleDuckdbError } = exportedForTesting;
|
|
112
|
+
|
|
113
|
+
const error = "Error: Something wrong LINE 1: SELECT * FROM table ^ ";
|
|
114
|
+
|
|
115
|
+
const result = handleDuckdbError(error);
|
|
116
|
+
|
|
117
|
+
expect(result.errorType).toBe("Error");
|
|
118
|
+
expect(result.errorMessage).toBe("Something wrong");
|
|
119
|
+
expect(result.codeblock).toBe("LINE 1: SELECT * FROM table ^");
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("should handle empty error message", () => {
|
|
123
|
+
const { handleDuckdbError } = exportedForTesting;
|
|
124
|
+
|
|
125
|
+
const error = "";
|
|
126
|
+
|
|
127
|
+
const result = handleDuckdbError(error);
|
|
128
|
+
|
|
129
|
+
expect(result.errorType).toBe("");
|
|
130
|
+
expect(result.errorMessage).toBe("");
|
|
131
|
+
expect(result.codeblock).toBeUndefined();
|
|
132
|
+
});
|
|
133
|
+
});
|