@marimo-team/islands 0.23.14-dev3 → 0.23.14-dev31
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-Du3_nUzI.js → ConnectedDataExplorerComponent-DXBx_nQg.js} +4 -4
- package/dist/{chat-ui-CsPewo4h.js → chat-ui-CO8WuXGb.js} +3243 -3039
- package/dist/{click-outside-container-BDd67_1U.js → click-outside-container-BLPjMamz.js} +141 -113
- package/dist/{code-visibility-BbnxGblD.js → code-visibility-0uqOIXQg.js} +1122 -924
- package/dist/data-grid-overlay-editor-C6lxUJp-.js +136 -0
- package/dist/{dist-D_bzzWBm.js → dist-Bf9f8MuT.js} +3 -3
- package/dist/{formats-d6MhLuQ9.js → formats-WsOgyW_K.js} +1 -1
- package/dist/{glide-data-editor-DkzAInWG.js → glide-data-editor-7wxMGXjG.js} +2084 -1889
- package/dist/{html-to-image-DXwLcQ6l.js → html-to-image-DLSOS-bE.js} +2351 -2282
- package/dist/{input-CbEz_aj_.js → input-BSdZp5Ng.js} +1 -1
- package/dist/main.js +1231 -1133
- package/dist/{mermaid-CJW9vIyO.js → mermaid-D-HYBMEV.js} +2 -2
- package/dist/{number-overlay-editor-D-a0qCT8.js → number-overlay-editor-BLJXvX9c.js} +1 -1
- package/dist/{process-output-C6_e1pT_.js → process-output-B9ysqk1y.js} +1 -1
- package/dist/{reveal-component-B2onAQFS.js → reveal-component-DxTNjwZw.js} +600 -596
- package/dist/{spec-Bv-XlYiv.js → spec-CnTgI25l.js} +1 -1
- package/dist/style.css +1 -1
- package/dist/{toDate-D-l5s8nn.js → toDate-D1Z7ZXWh.js} +1 -1
- package/dist/{useAsyncData-1Dhzjfwf.js → useAsyncData-BMc8itk2.js} +1 -1
- package/dist/{useDeepCompareMemoize-CDWT3BDz.js → useDeepCompareMemoize-ZwmDBRDY.js} +1 -1
- package/dist/{useLifecycle-AHlswLw-.js → useLifecycle-CxffarYV.js} +1 -1
- package/dist/{useTheme-BrYvK-_A.js → useTheme-yGsGEk82.js} +26 -24
- package/dist/{vega-component-Pk6lyc_a.js → vega-component-BFJTyykA.js} +5 -5
- package/package.json +3 -3
- package/src/components/chat/acp/agent-panel.tsx +35 -1
- package/src/components/chat/chat-panel.tsx +68 -29
- package/src/components/data-table/__tests__/column-explorer.test.tsx +25 -0
- package/src/components/data-table/__tests__/column-visibility-dropdown.test.tsx +60 -3
- package/src/components/data-table/charts/__tests__/merge-index-fields.test.ts +33 -0
- package/src/components/data-table/charts/charts.tsx +23 -1
- package/src/components/data-table/column-explorer-panel/column-explorer.tsx +33 -12
- package/src/components/data-table/column-visibility-dropdown.tsx +15 -0
- package/src/components/editor/actions/useNotebookActions.tsx +2 -2
- package/src/components/editor/chrome/panels/snippets-panel.tsx +5 -2
- package/src/components/editor/chrome/wrapper/__tests__/useOpenAiAssistant.test.ts +36 -0
- package/src/components/editor/chrome/wrapper/footer-items/pyodide-status.tsx +6 -36
- package/src/components/editor/chrome/wrapper/useAiPanel.ts +3 -1
- package/src/components/editor/chrome/wrapper/useOpenAiAssistant.ts +88 -0
- package/src/components/editor/code/readonly-python-code.tsx +16 -2
- package/src/components/editor/errors/__tests__/auto-fix.test.ts +23 -0
- package/src/components/editor/errors/auto-fix.tsx +88 -34
- package/src/components/editor/errors/fix-mode.ts +1 -1
- package/src/components/editor/output/MarimoTracebackOutput.tsx +10 -1
- package/src/components/editor/output/__tests__/traceback.test.tsx +14 -6
- package/src/components/editor/renderers/grid-layout/grid-layout.tsx +7 -2
- package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +6 -2
- package/src/components/lifecycle/ProgressiveBoundary.tsx +42 -0
- package/src/components/lifecycle/RuntimeStatusBadge.tsx +59 -0
- package/src/components/lifecycle/__tests__/ProgressiveBoundary.test.tsx +147 -0
- package/src/components/lifecycle/__tests__/RuntimeStatusBadge.test.tsx +72 -0
- package/src/components/slides/__tests__/slide.test.tsx +33 -0
- package/src/components/slides/minimap.tsx +7 -1
- package/src/components/slides/reveal-component.tsx +3 -0
- package/src/components/slides/slide-cell-view.tsx +10 -6
- package/src/components/slides/slide.tsx +16 -13
- package/src/core/ai/config.ts +2 -2
- package/src/core/ai/context/__tests__/registry.test.ts +133 -3
- package/src/core/ai/context/providers/__tests__/datasource.test.ts +3 -1
- package/src/core/ai/context/providers/__tests__/error.test.ts +200 -15
- package/src/core/ai/context/providers/datasource.ts +27 -2
- package/src/core/ai/context/providers/error.ts +226 -36
- package/src/core/ai/context/registry.ts +77 -43
- package/src/core/ai/state.ts +11 -0
- package/src/core/cells/__tests__/readonly-code-display.test.ts +46 -0
- package/src/core/cells/readonly-code-display.ts +35 -0
- package/src/core/codemirror/__tests__/setup.test.ts +33 -1
- package/src/core/codemirror/ai/resources.ts +15 -10
- package/src/core/codemirror/cells/extensions.ts +1 -4
- package/src/core/codemirror/completion/__tests__/signature-hint.test.ts +94 -0
- package/src/core/codemirror/completion/completer.ts +12 -1
- package/src/core/codemirror/completion/signature-hint.ts +68 -0
- package/src/core/codemirror/language/languages/python.ts +10 -4
- package/src/core/codemirror/utils.ts +15 -0
- package/src/core/edit-app.tsx +8 -5
- package/src/core/errors/errors.ts +2 -1
- package/src/core/lifecycle/__tests__/render-policy.test.ts +247 -0
- package/src/core/lifecycle/render-policy.ts +125 -0
- package/src/core/mime.ts +11 -4
- package/src/core/run-app.tsx +27 -23
- package/src/core/runtime/__tests__/adapter.test.ts +160 -0
- package/src/core/runtime/adapter.ts +182 -0
- package/src/core/wasm/PyodideLoader.tsx +20 -62
- package/src/core/wasm/bridge.ts +9 -4
- package/src/core/wasm/state.ts +8 -19
- package/src/css/app/codemirror.css +14 -0
- package/src/hooks/__tests__/useDelayElapsed.test.tsx +55 -0
- package/src/hooks/useDelayElapsed.ts +27 -0
- package/src/plugins/impl/DataTablePlugin.tsx +1 -0
- package/src/plugins/impl/anywidget/AnyWidgetPlugin.tsx +53 -2
- package/src/plugins/impl/anywidget/__tests__/AnyWidgetPlugin.test.tsx +52 -2
- package/src/plugins/impl/data-editor/__tests__/glide-data-editor.test.tsx +187 -0
- package/src/plugins/impl/data-editor/glide-data-editor.tsx +6 -0
- package/src/plugins/impl/data-editor/glide-portal.tsx +73 -0
- package/dist/data-grid-overlay-editor-mfEJ5475.js +0 -128
- package/src/core/ai/context/providers/__tests__/__snapshots__/error.test.ts.snap +0 -3
- package/src/core/wasm/__tests__/PyodideLoader.test.ts +0 -72
- package/src/core/wasm/__tests__/state.test.ts +0 -124
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import React, { useEffect, useState } from "react";
|
|
4
|
+
import { createPortal } from "react-dom";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Dismiss any open Glide Data Grid overlay by firing Escape on it.
|
|
8
|
+
*/
|
|
9
|
+
export function dismissGlideOverlay() {
|
|
10
|
+
const overlay = document.querySelector(
|
|
11
|
+
"[data-testid='glide-data-editor-portal'] .gdg-clip-region",
|
|
12
|
+
);
|
|
13
|
+
if (overlay) {
|
|
14
|
+
overlay.dispatchEvent(
|
|
15
|
+
new KeyboardEvent("keydown", { key: "Escape", bubbles: true }),
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function getGlidePortalContainer(): Element {
|
|
21
|
+
return document.fullscreenElement ?? document.body;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function useGlidePortalContainer(): Element {
|
|
25
|
+
const [container, setContainer] = useState<Element>(getGlidePortalContainer);
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
const handleFullscreenChange = () => {
|
|
29
|
+
if (document.fullscreenElement) {
|
|
30
|
+
setContainer(document.fullscreenElement);
|
|
31
|
+
} else {
|
|
32
|
+
dismissGlideOverlay();
|
|
33
|
+
setContainer(document.body);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
document.addEventListener("fullscreenchange", handleFullscreenChange);
|
|
38
|
+
// Sync on mount in case fullscreen became active before this editor mounted.
|
|
39
|
+
handleFullscreenChange();
|
|
40
|
+
|
|
41
|
+
return () => {
|
|
42
|
+
document.removeEventListener("fullscreenchange", handleFullscreenChange);
|
|
43
|
+
};
|
|
44
|
+
}, []);
|
|
45
|
+
|
|
46
|
+
return container;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Per-instance Glide Data Grid overlay portal on document.body.
|
|
51
|
+
* Moves into the active fullscreen element via createPortal so edit overlays stay visible
|
|
52
|
+
*/
|
|
53
|
+
export function GlideDataEditorPortal({
|
|
54
|
+
portalRef,
|
|
55
|
+
}: {
|
|
56
|
+
portalRef: React.RefObject<HTMLDivElement | null>;
|
|
57
|
+
}) {
|
|
58
|
+
const container = useGlidePortalContainer();
|
|
59
|
+
|
|
60
|
+
return createPortal(
|
|
61
|
+
<div
|
|
62
|
+
ref={portalRef}
|
|
63
|
+
data-testid="glide-data-editor-portal"
|
|
64
|
+
style={{
|
|
65
|
+
position: "fixed",
|
|
66
|
+
left: 0,
|
|
67
|
+
top: 0,
|
|
68
|
+
zIndex: 9999,
|
|
69
|
+
}}
|
|
70
|
+
/>,
|
|
71
|
+
container,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import { s as __toESM } from "./chunk-BNovOVIE.js";
|
|
2
|
-
import { t as require_react } from "./react-DA-nE2FX.js";
|
|
3
|
-
import { t as require_react_dom } from "./react-dom-BTJzcVJ9.js";
|
|
4
|
-
import { _ as isEditableGridCell, i as makeCSSStyle, n as ThemeContext, t as ClickOutsideContainer, v as isInnerOnlyCell, y as isObjectEditorCallbackResult } from "./click-outside-container-BDd67_1U.js";
|
|
5
|
-
import { t as styled_default } from "./dist-D_bzzWBm.js";
|
|
6
|
-
var import_react_dom = require_react_dom(), import_react = /* @__PURE__ */ __toESM(require_react(), 1), _exp2 = () => (n) => n.targetX, _exp3 = () => (n) => n.targetY, _exp4 = () => (n) => n.targetWidth, _exp5 = () => (n) => n.targetHeight, _exp6 = () => (n) => n.targetY + 10, _exp7 = () => (n) => Math.max(0, (n.targetHeight - 28) / 2);
|
|
7
|
-
const DataGridOverlayEditorStyle = /* @__PURE__ */ styled_default("div")({
|
|
8
|
-
name: "DataGridOverlayEditorStyle",
|
|
9
|
-
class: "gdg-d19meir1",
|
|
10
|
-
propsAsIs: false,
|
|
11
|
-
vars: {
|
|
12
|
-
"d19meir1-0": [_exp3(), "px"],
|
|
13
|
-
"d19meir1-1": [_exp2(), "px"],
|
|
14
|
-
"d19meir1-2": [_exp4(), "px"],
|
|
15
|
-
"d19meir1-3": [_exp5(), "px"],
|
|
16
|
-
"d19meir1-4": [_exp6(), "px"],
|
|
17
|
-
"d19meir1-5": [_exp7(), "px"]
|
|
18
|
-
}
|
|
19
|
-
});
|
|
20
|
-
function useRefState() {
|
|
21
|
-
let [n, c] = import_react.useState();
|
|
22
|
-
return [n ?? void 0, c];
|
|
23
|
-
}
|
|
24
|
-
function useStayOnScreen() {
|
|
25
|
-
let [n, c] = useRefState(), [l, u] = import_react.useState(0), [d, f] = import_react.useState(true);
|
|
26
|
-
return import_react.useLayoutEffect(() => {
|
|
27
|
-
if (n === void 0 || !("IntersectionObserver" in window)) return;
|
|
28
|
-
let c2 = new IntersectionObserver((n2) => {
|
|
29
|
-
n2.length !== 0 && f(n2[0].isIntersecting);
|
|
30
|
-
}, { threshold: 1 });
|
|
31
|
-
return c2.observe(n), () => c2.disconnect();
|
|
32
|
-
}, [n]), import_react.useEffect(() => {
|
|
33
|
-
if (d || n === void 0) return;
|
|
34
|
-
let c2, l2 = () => {
|
|
35
|
-
let { right: d2 } = n.getBoundingClientRect();
|
|
36
|
-
u((n2) => Math.min(n2 + window.innerWidth - d2 - 10, 0)), c2 = requestAnimationFrame(l2);
|
|
37
|
-
};
|
|
38
|
-
return c2 = requestAnimationFrame(l2), () => {
|
|
39
|
-
c2 !== void 0 && cancelAnimationFrame(c2);
|
|
40
|
-
};
|
|
41
|
-
}, [n, d]), {
|
|
42
|
-
ref: c,
|
|
43
|
-
style: import_react.useMemo(() => ({ transform: `translateX(${l}px)` }), [l])
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
var data_grid_overlay_editor_default = (n) => {
|
|
47
|
-
let { target: c, content: l, onFinishEditing: p, forceEditMode: m, initialValue: h, imageEditorOverride: g, markdownDivCreateNode: _, highlight: v, className: y, theme: b, id: x, cell: S, bloom: C, validateCell: w, getCellRenderer: T, provideEditor: E, isOutsideClick: D, customEventTarget: O } = n, [k, A] = import_react.useState(m ? l : void 0), j = import_react.useRef(k ?? l);
|
|
48
|
-
j.current = k ?? l;
|
|
49
|
-
let [M, N] = import_react.useState(() => w === void 0 ? true : !(isEditableGridCell(l) && (w == null ? void 0 : w(S, l, j.current)) === false)), P = import_react.useCallback((n2, c2) => {
|
|
50
|
-
p(M ? n2 : void 0, c2);
|
|
51
|
-
}, [M, p]), F = import_react.useCallback((n2) => {
|
|
52
|
-
if (w !== void 0 && n2 !== void 0 && isEditableGridCell(n2)) {
|
|
53
|
-
let c2 = w(S, n2, j.current);
|
|
54
|
-
c2 === false ? N(false) : (typeof c2 == "object" && (n2 = c2), N(true));
|
|
55
|
-
}
|
|
56
|
-
A(n2);
|
|
57
|
-
}, [S, w]), I = import_react.useRef(false), L = import_react.useRef(void 0), R = import_react.useCallback(() => {
|
|
58
|
-
P(k, [0, 0]), I.current = true;
|
|
59
|
-
}, [k, P]), z = import_react.useCallback((n2, c2) => {
|
|
60
|
-
P(n2, c2 ?? L.current ?? [0, 0]), I.current = true;
|
|
61
|
-
}, [P]), B = import_react.useCallback(async (n2) => {
|
|
62
|
-
let c2 = false;
|
|
63
|
-
n2.key === "Escape" ? (n2.stopPropagation(), n2.preventDefault(), L.current = [0, 0]) : n2.key === "Enter" && !n2.shiftKey ? (n2.stopPropagation(), n2.preventDefault(), L.current = [0, 1], c2 = true) : n2.key === "Tab" && (n2.stopPropagation(), n2.preventDefault(), L.current = [n2.shiftKey ? -1 : 1, 0], c2 = true), window.setTimeout(() => {
|
|
64
|
-
!I.current && L.current !== void 0 && (P(c2 ? k : void 0, L.current), I.current = true);
|
|
65
|
-
}, 0);
|
|
66
|
-
}, [P, k]), V = k ?? l, [H, U] = import_react.useMemo(() => {
|
|
67
|
-
var _a, _b;
|
|
68
|
-
if (isInnerOnlyCell(l)) return [];
|
|
69
|
-
let n2 = E == null ? void 0 : E(l);
|
|
70
|
-
return n2 === void 0 ? [(_b = (_a = T(l)) == null ? void 0 : _a.provideEditor) == null ? void 0 : _b.call(_a, l), false] : [n2, false];
|
|
71
|
-
}, [
|
|
72
|
-
l,
|
|
73
|
-
T,
|
|
74
|
-
E
|
|
75
|
-
]), { ref: W, style: G } = useStayOnScreen(), K = true, q, J = true, Y;
|
|
76
|
-
if (H !== void 0) {
|
|
77
|
-
K = H.disablePadding !== true, J = H.disableStyling !== true;
|
|
78
|
-
let n2 = isObjectEditorCallbackResult(H);
|
|
79
|
-
n2 && (Y = H.styleOverride);
|
|
80
|
-
let l2 = n2 ? H.editor : H;
|
|
81
|
-
q = import_react.createElement(l2, {
|
|
82
|
-
isHighlighted: v,
|
|
83
|
-
onChange: F,
|
|
84
|
-
value: V,
|
|
85
|
-
initialValue: h,
|
|
86
|
-
onFinishedEditing: z,
|
|
87
|
-
validatedSelection: isEditableGridCell(V) ? V.selectionRange : void 0,
|
|
88
|
-
forceEditMode: m,
|
|
89
|
-
target: c,
|
|
90
|
-
imageEditorOverride: g,
|
|
91
|
-
markdownDivCreateNode: _,
|
|
92
|
-
isValid: M,
|
|
93
|
-
theme: b
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
Y = {
|
|
97
|
-
...Y,
|
|
98
|
-
...G
|
|
99
|
-
};
|
|
100
|
-
let X = document.getElementById("portal");
|
|
101
|
-
if (X === null) return console.error('Cannot open Data Grid overlay editor, because portal not found. Please add `<div id="portal" />` as the last child of your `<body>`.'), null;
|
|
102
|
-
let Z = J ? "gdg-style" : "gdg-unstyle";
|
|
103
|
-
M || (Z += " gdg-invalid"), K && (Z += " gdg-pad");
|
|
104
|
-
let Q = (C == null ? void 0 : C[0]) ?? 1, $ = (C == null ? void 0 : C[1]) ?? 1;
|
|
105
|
-
return (0, import_react_dom.createPortal)(import_react.createElement(ThemeContext.Provider, { value: b }, import_react.createElement(ClickOutsideContainer, {
|
|
106
|
-
style: makeCSSStyle(b),
|
|
107
|
-
className: y,
|
|
108
|
-
onClickOutside: R,
|
|
109
|
-
isOutsideClick: D,
|
|
110
|
-
customEventTarget: O
|
|
111
|
-
}, import_react.createElement(DataGridOverlayEditorStyle, {
|
|
112
|
-
ref: W,
|
|
113
|
-
id: x,
|
|
114
|
-
className: Z,
|
|
115
|
-
style: Y,
|
|
116
|
-
as: U === true ? "label" : void 0,
|
|
117
|
-
targetX: c.x - Q,
|
|
118
|
-
targetY: c.y - $,
|
|
119
|
-
targetWidth: c.width + Q * 2,
|
|
120
|
-
targetHeight: c.height + $ * 2
|
|
121
|
-
}, import_react.createElement("div", {
|
|
122
|
-
className: "gdg-clip-region",
|
|
123
|
-
onKeyDown: B
|
|
124
|
-
}, q)))), X);
|
|
125
|
-
};
|
|
126
|
-
export {
|
|
127
|
-
data_grid_overlay_editor_default as default
|
|
128
|
-
};
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
-
|
|
3
|
-
import { describe, expect, it } from "vitest";
|
|
4
|
-
import { shouldShowSpinner } from "../PyodideLoader";
|
|
5
|
-
|
|
6
|
-
describe("shouldShowSpinner", () => {
|
|
7
|
-
it("shows the spinner when there are no cells yet (Pyodide hasn't parsed)", () => {
|
|
8
|
-
expect(
|
|
9
|
-
shouldShowSpinner({
|
|
10
|
-
hasCells: false,
|
|
11
|
-
hasOutput: false,
|
|
12
|
-
mode: "read",
|
|
13
|
-
codeHidden: false,
|
|
14
|
-
}),
|
|
15
|
-
).toBe(true);
|
|
16
|
-
|
|
17
|
-
expect(
|
|
18
|
-
shouldShowSpinner({
|
|
19
|
-
hasCells: false,
|
|
20
|
-
hasOutput: true,
|
|
21
|
-
mode: "edit",
|
|
22
|
-
codeHidden: false,
|
|
23
|
-
}),
|
|
24
|
-
).toBe(true);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it("renders children once cells exist with code visible", () => {
|
|
28
|
-
// run mode, code visible, no outputs yet — the user can read the code
|
|
29
|
-
expect(
|
|
30
|
-
shouldShowSpinner({
|
|
31
|
-
hasCells: true,
|
|
32
|
-
hasOutput: false,
|
|
33
|
-
mode: "read",
|
|
34
|
-
codeHidden: false,
|
|
35
|
-
}),
|
|
36
|
-
).toBe(false);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it("renders children once cells exist with cached outputs (snapshot case)", () => {
|
|
40
|
-
expect(
|
|
41
|
-
shouldShowSpinner({
|
|
42
|
-
hasCells: true,
|
|
43
|
-
hasOutput: true,
|
|
44
|
-
mode: "read",
|
|
45
|
-
codeHidden: true,
|
|
46
|
-
}),
|
|
47
|
-
).toBe(false);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it("keeps the spinner up in headless run mode with no outputs", () => {
|
|
51
|
-
// read mode + code hidden + no outputs = nothing visible to render
|
|
52
|
-
expect(
|
|
53
|
-
shouldShowSpinner({
|
|
54
|
-
hasCells: true,
|
|
55
|
-
hasOutput: false,
|
|
56
|
-
mode: "read",
|
|
57
|
-
codeHidden: true,
|
|
58
|
-
}),
|
|
59
|
-
).toBe(true);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it("never blocks edit mode once cells exist", () => {
|
|
63
|
-
expect(
|
|
64
|
-
shouldShowSpinner({
|
|
65
|
-
hasCells: true,
|
|
66
|
-
hasOutput: false,
|
|
67
|
-
mode: "edit",
|
|
68
|
-
codeHidden: true,
|
|
69
|
-
}),
|
|
70
|
-
).toBe(false);
|
|
71
|
-
});
|
|
72
|
-
});
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
-
|
|
3
|
-
import { createRef } from "react";
|
|
4
|
-
import { describe, expect, it } from "vitest";
|
|
5
|
-
import { cellId } from "@/__tests__/branded";
|
|
6
|
-
import type { NotebookState } from "@/core/cells/cells";
|
|
7
|
-
import { initialNotebookState, notebookAtom } from "@/core/cells/cells";
|
|
8
|
-
import { createCell, createCellRuntimeState } from "@/core/cells/types";
|
|
9
|
-
import type { OutputMessage } from "@/core/kernel/messages";
|
|
10
|
-
import { store } from "@/core/state/jotai";
|
|
11
|
-
import { CollapsibleTree, MultiColumn } from "@/utils/id-tree";
|
|
12
|
-
import { hasAnyOutputAtom } from "../state";
|
|
13
|
-
|
|
14
|
-
describe("hasAnyOutputAtom", () => {
|
|
15
|
-
const createNotebookState = (
|
|
16
|
-
outputs: (OutputMessage | null)[],
|
|
17
|
-
): NotebookState => ({
|
|
18
|
-
...initialNotebookState(),
|
|
19
|
-
cellIds: new MultiColumn([
|
|
20
|
-
CollapsibleTree.from(outputs.map((_, i) => cellId(`${i}`))),
|
|
21
|
-
]),
|
|
22
|
-
cellData: Object.fromEntries(
|
|
23
|
-
outputs.map((_, i) => [
|
|
24
|
-
cellId(`${i}`),
|
|
25
|
-
createCell({ id: cellId(`${i}`) }),
|
|
26
|
-
]),
|
|
27
|
-
),
|
|
28
|
-
cellRuntime: Object.fromEntries(
|
|
29
|
-
outputs.map((output, i) => [
|
|
30
|
-
`${i}`,
|
|
31
|
-
createCellRuntimeState({
|
|
32
|
-
output,
|
|
33
|
-
status: "queued",
|
|
34
|
-
outline: { items: [] },
|
|
35
|
-
}),
|
|
36
|
-
]),
|
|
37
|
-
),
|
|
38
|
-
cellHandles: Object.fromEntries(
|
|
39
|
-
outputs.map((_, i) => [cellId(`${i}`), createRef()]),
|
|
40
|
-
),
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it("should return false when there are no outputs", () => {
|
|
44
|
-
store.set(notebookAtom, createNotebookState([null, null]));
|
|
45
|
-
expect(store.get(hasAnyOutputAtom)).toBe(false);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it("should return false when all outputs are empty", () => {
|
|
49
|
-
store.set(
|
|
50
|
-
notebookAtom,
|
|
51
|
-
createNotebookState([
|
|
52
|
-
{ channel: "output", mimetype: "text/plain", data: "", timestamp: 0 },
|
|
53
|
-
{ channel: "output", mimetype: "text/plain", data: "", timestamp: 0 },
|
|
54
|
-
]),
|
|
55
|
-
);
|
|
56
|
-
expect(store.get(hasAnyOutputAtom)).toBe(false);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it("should return true when there is at least one non-empty output", () => {
|
|
60
|
-
store.set(
|
|
61
|
-
notebookAtom,
|
|
62
|
-
createNotebookState([
|
|
63
|
-
{ channel: "output", mimetype: "text/plain", data: "", timestamp: 0 },
|
|
64
|
-
{
|
|
65
|
-
channel: "output",
|
|
66
|
-
mimetype: "text/plain",
|
|
67
|
-
data: "hello",
|
|
68
|
-
timestamp: 0,
|
|
69
|
-
},
|
|
70
|
-
]),
|
|
71
|
-
);
|
|
72
|
-
expect(store.get(hasAnyOutputAtom)).toBe(true);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it("should handle various output types", () => {
|
|
76
|
-
store.set(
|
|
77
|
-
notebookAtom,
|
|
78
|
-
createNotebookState([
|
|
79
|
-
{
|
|
80
|
-
channel: "output",
|
|
81
|
-
mimetype: "application/json",
|
|
82
|
-
data: { foo: "bar" },
|
|
83
|
-
timestamp: 0,
|
|
84
|
-
},
|
|
85
|
-
{ channel: "output", mimetype: "text/plain", data: "", timestamp: 0 },
|
|
86
|
-
]),
|
|
87
|
-
);
|
|
88
|
-
expect(store.get(hasAnyOutputAtom)).toBe(true);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it("should return true when all outputs are idle", () => {
|
|
92
|
-
const notebookState = createNotebookState([null, null]);
|
|
93
|
-
const cellId0 = cellId("0");
|
|
94
|
-
const cellId1 = cellId("1");
|
|
95
|
-
// Some idle cell, so returns false
|
|
96
|
-
store.set(notebookAtom, {
|
|
97
|
-
...notebookState,
|
|
98
|
-
cellRuntime: {
|
|
99
|
-
...notebookState.cellRuntime,
|
|
100
|
-
[cellId0]: {
|
|
101
|
-
...notebookState.cellRuntime[cellId0],
|
|
102
|
-
status: "idle",
|
|
103
|
-
},
|
|
104
|
-
},
|
|
105
|
-
});
|
|
106
|
-
expect(store.get(hasAnyOutputAtom)).toBe(false);
|
|
107
|
-
|
|
108
|
-
// All cells are idle, so returns true
|
|
109
|
-
store.set(notebookAtom, {
|
|
110
|
-
...notebookState,
|
|
111
|
-
cellRuntime: {
|
|
112
|
-
[cellId0]: {
|
|
113
|
-
...notebookState.cellRuntime[cellId0],
|
|
114
|
-
status: "idle",
|
|
115
|
-
},
|
|
116
|
-
[cellId1]: {
|
|
117
|
-
...notebookState.cellRuntime[cellId1],
|
|
118
|
-
status: "idle",
|
|
119
|
-
},
|
|
120
|
-
},
|
|
121
|
-
});
|
|
122
|
-
expect(store.get(hasAnyOutputAtom)).toBe(true);
|
|
123
|
-
});
|
|
124
|
-
});
|