@marimo-team/islands 0.23.14-dev3 → 0.23.14-dev30

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 (97) hide show
  1. package/dist/{ConnectedDataExplorerComponent-Du3_nUzI.js → ConnectedDataExplorerComponent-DXBx_nQg.js} +4 -4
  2. package/dist/{chat-ui-CsPewo4h.js → chat-ui-CO8WuXGb.js} +3243 -3039
  3. package/dist/{click-outside-container-BDd67_1U.js → click-outside-container-BLPjMamz.js} +141 -113
  4. package/dist/{code-visibility-BbnxGblD.js → code-visibility-CAkgOEA5.js} +1122 -924
  5. package/dist/data-grid-overlay-editor-C6lxUJp-.js +136 -0
  6. package/dist/{dist-D_bzzWBm.js → dist-Bf9f8MuT.js} +3 -3
  7. package/dist/{formats-d6MhLuQ9.js → formats-WsOgyW_K.js} +1 -1
  8. package/dist/{glide-data-editor-DkzAInWG.js → glide-data-editor-7wxMGXjG.js} +2084 -1889
  9. package/dist/{html-to-image-DXwLcQ6l.js → html-to-image-DLSOS-bE.js} +2351 -2282
  10. package/dist/{input-CbEz_aj_.js → input-BSdZp5Ng.js} +1 -1
  11. package/dist/main.js +1233 -1133
  12. package/dist/{mermaid-CJW9vIyO.js → mermaid-D-HYBMEV.js} +2 -2
  13. package/dist/{number-overlay-editor-D-a0qCT8.js → number-overlay-editor-BLJXvX9c.js} +1 -1
  14. package/dist/{process-output-C6_e1pT_.js → process-output-B9ysqk1y.js} +1 -1
  15. package/dist/{reveal-component-B2onAQFS.js → reveal-component-x6IwgqPf.js} +600 -596
  16. package/dist/{spec-Bv-XlYiv.js → spec-CnTgI25l.js} +1 -1
  17. package/dist/style.css +1 -1
  18. package/dist/{toDate-D-l5s8nn.js → toDate-D1Z7ZXWh.js} +1 -1
  19. package/dist/{useAsyncData-1Dhzjfwf.js → useAsyncData-BMc8itk2.js} +1 -1
  20. package/dist/{useDeepCompareMemoize-CDWT3BDz.js → useDeepCompareMemoize-ZwmDBRDY.js} +1 -1
  21. package/dist/{useLifecycle-AHlswLw-.js → useLifecycle-CxffarYV.js} +1 -1
  22. package/dist/{useTheme-BrYvK-_A.js → useTheme-yGsGEk82.js} +26 -24
  23. package/dist/{vega-component-Pk6lyc_a.js → vega-component-BFJTyykA.js} +5 -5
  24. package/package.json +3 -3
  25. package/src/components/chat/acp/agent-panel.tsx +35 -1
  26. package/src/components/chat/chat-panel.tsx +68 -29
  27. package/src/components/data-table/__tests__/column-explorer.test.tsx +25 -0
  28. package/src/components/data-table/__tests__/column-visibility-dropdown.test.tsx +60 -3
  29. package/src/components/data-table/charts/__tests__/merge-index-fields.test.ts +33 -0
  30. package/src/components/data-table/charts/charts.tsx +24 -1
  31. package/src/components/data-table/column-explorer-panel/column-explorer.tsx +33 -12
  32. package/src/components/data-table/column-visibility-dropdown.tsx +15 -0
  33. package/src/components/editor/actions/useNotebookActions.tsx +2 -2
  34. package/src/components/editor/chrome/panels/snippets-panel.tsx +5 -2
  35. package/src/components/editor/chrome/wrapper/__tests__/useOpenAiAssistant.test.ts +36 -0
  36. package/src/components/editor/chrome/wrapper/footer-items/pyodide-status.tsx +6 -36
  37. package/src/components/editor/chrome/wrapper/useAiPanel.ts +3 -1
  38. package/src/components/editor/chrome/wrapper/useOpenAiAssistant.ts +88 -0
  39. package/src/components/editor/code/readonly-python-code.tsx +16 -2
  40. package/src/components/editor/errors/__tests__/auto-fix.test.ts +23 -0
  41. package/src/components/editor/errors/auto-fix.tsx +88 -34
  42. package/src/components/editor/errors/fix-mode.ts +1 -1
  43. package/src/components/editor/output/MarimoTracebackOutput.tsx +10 -1
  44. package/src/components/editor/output/__tests__/traceback.test.tsx +14 -6
  45. package/src/components/editor/renderers/grid-layout/grid-layout.tsx +7 -2
  46. package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +6 -2
  47. package/src/components/lifecycle/ProgressiveBoundary.tsx +42 -0
  48. package/src/components/lifecycle/RuntimeStatusBadge.tsx +59 -0
  49. package/src/components/lifecycle/__tests__/ProgressiveBoundary.test.tsx +147 -0
  50. package/src/components/lifecycle/__tests__/RuntimeStatusBadge.test.tsx +72 -0
  51. package/src/components/slides/__tests__/slide.test.tsx +33 -0
  52. package/src/components/slides/minimap.tsx +7 -1
  53. package/src/components/slides/reveal-component.tsx +3 -0
  54. package/src/components/slides/slide-cell-view.tsx +10 -6
  55. package/src/components/slides/slide.tsx +16 -13
  56. package/src/core/ai/config.ts +2 -2
  57. package/src/core/ai/context/__tests__/registry.test.ts +133 -3
  58. package/src/core/ai/context/providers/__tests__/datasource.test.ts +3 -1
  59. package/src/core/ai/context/providers/__tests__/error.test.ts +200 -15
  60. package/src/core/ai/context/providers/datasource.ts +27 -2
  61. package/src/core/ai/context/providers/error.ts +226 -36
  62. package/src/core/ai/context/registry.ts +77 -43
  63. package/src/core/ai/state.ts +11 -0
  64. package/src/core/cells/__tests__/readonly-code-display.test.ts +46 -0
  65. package/src/core/cells/readonly-code-display.ts +35 -0
  66. package/src/core/codemirror/__tests__/setup.test.ts +33 -1
  67. package/src/core/codemirror/ai/resources.ts +15 -10
  68. package/src/core/codemirror/cells/extensions.ts +1 -4
  69. package/src/core/codemirror/completion/__tests__/signature-hint.test.ts +94 -0
  70. package/src/core/codemirror/completion/completer.ts +12 -1
  71. package/src/core/codemirror/completion/signature-hint.ts +68 -0
  72. package/src/core/codemirror/language/languages/python.ts +10 -4
  73. package/src/core/codemirror/utils.ts +15 -0
  74. package/src/core/edit-app.tsx +8 -5
  75. package/src/core/errors/errors.ts +2 -1
  76. package/src/core/lifecycle/__tests__/render-policy.test.ts +247 -0
  77. package/src/core/lifecycle/render-policy.ts +125 -0
  78. package/src/core/mime.ts +11 -4
  79. package/src/core/run-app.tsx +27 -23
  80. package/src/core/runtime/__tests__/adapter.test.ts +160 -0
  81. package/src/core/runtime/adapter.ts +182 -0
  82. package/src/core/wasm/PyodideLoader.tsx +20 -62
  83. package/src/core/wasm/bridge.ts +9 -4
  84. package/src/core/wasm/state.ts +8 -19
  85. package/src/css/app/codemirror.css +14 -0
  86. package/src/hooks/__tests__/useDelayElapsed.test.tsx +55 -0
  87. package/src/hooks/useDelayElapsed.ts +27 -0
  88. package/src/plugins/impl/DataTablePlugin.tsx +1 -0
  89. package/src/plugins/impl/anywidget/AnyWidgetPlugin.tsx +53 -2
  90. package/src/plugins/impl/anywidget/__tests__/AnyWidgetPlugin.test.tsx +52 -2
  91. package/src/plugins/impl/data-editor/__tests__/glide-data-editor.test.tsx +187 -0
  92. package/src/plugins/impl/data-editor/glide-data-editor.tsx +6 -0
  93. package/src/plugins/impl/data-editor/glide-portal.tsx +73 -0
  94. package/dist/data-grid-overlay-editor-mfEJ5475.js +0 -128
  95. package/src/core/ai/context/providers/__tests__/__snapshots__/error.test.ts.snap +0 -3
  96. package/src/core/wasm/__tests__/PyodideLoader.test.ts +0 -72
  97. 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,3 +0,0 @@
1
- // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
-
3
- exports[`ErrorContextProvider > formatContext > should format context for basic errors > basic-error-context 1`] = `"<error name="Cell 1" description="Invalid syntax"></error>"`;
@@ -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
- });