@marimo-team/islands 0.23.5-dev1 → 0.23.5-dev11

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.
@@ -0,0 +1,141 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { renderHook } from "@testing-library/react";
4
+ import { createStore, Provider } from "jotai";
5
+ import type { ReactNode } from "react";
6
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
7
+ import { showCodeInRunModeAtom } from "@/core/meta/state";
8
+ import { type AppMode, kioskModeAtom, viewStateAtom } from "@/core/mode";
9
+ import { useNotebookCodeAvailable } from "../code-visibility";
10
+
11
+ interface StoreOpts {
12
+ mode?: AppMode;
13
+ kiosk?: boolean;
14
+ showInRunMode?: boolean;
15
+ }
16
+
17
+ function makeStore({
18
+ mode = "read",
19
+ kiosk = false,
20
+ showInRunMode = true,
21
+ }: StoreOpts = {}) {
22
+ const store = createStore();
23
+ store.set(viewStateAtom, { mode, cellAnchor: null });
24
+ store.set(kioskModeAtom, kiosk);
25
+ store.set(showCodeInRunModeAtom, showInRunMode);
26
+ return store;
27
+ }
28
+
29
+ function wrap(store: ReturnType<typeof createStore>) {
30
+ return ({ children }: { children: ReactNode }) => (
31
+ <Provider store={store}>{children}</Provider>
32
+ );
33
+ }
34
+
35
+ const cellsWithCode = [{ code: "x = 1" }, { code: "" }];
36
+ const cellsWithoutCode = [{ code: "" }, { code: "" }];
37
+
38
+ const originalHref = window.location.href;
39
+
40
+ function setSearch(search: string) {
41
+ window.history.replaceState(null, "", `/${search}`);
42
+ }
43
+
44
+ describe("useNotebookCodeAvailable", () => {
45
+ beforeEach(() => {
46
+ setSearch("");
47
+ });
48
+
49
+ afterEach(() => {
50
+ window.history.replaceState(null, "", originalHref);
51
+ });
52
+
53
+ it("returns true in edit mode regardless of cells", () => {
54
+ const store = makeStore({ mode: "edit" });
55
+ const { result } = renderHook(
56
+ () => useNotebookCodeAvailable(cellsWithoutCode),
57
+ { wrapper: wrap(store) },
58
+ );
59
+ expect(result.current).toBe(true);
60
+ });
61
+
62
+ it("returns true in present mode (only reachable from edit)", () => {
63
+ const store = makeStore({ mode: "present" });
64
+ const { result } = renderHook(
65
+ () => useNotebookCodeAvailable(cellsWithoutCode),
66
+ { wrapper: wrap(store) },
67
+ );
68
+ expect(result.current).toBe(true);
69
+ });
70
+
71
+ it("returns true in kiosk mode even when read mode and no code", () => {
72
+ const store = makeStore({
73
+ mode: "read",
74
+ kiosk: true,
75
+ showInRunMode: false,
76
+ });
77
+ const { result } = renderHook(
78
+ () => useNotebookCodeAvailable(cellsWithoutCode),
79
+ { wrapper: wrap(store) },
80
+ );
81
+ expect(result.current).toBe(true);
82
+ });
83
+
84
+ it("returns true in read mode when at least one cell has code", () => {
85
+ const store = makeStore({ mode: "read" });
86
+ const { result } = renderHook(
87
+ () => useNotebookCodeAvailable(cellsWithCode),
88
+ { wrapper: wrap(store) },
89
+ );
90
+ expect(result.current).toBe(true);
91
+ });
92
+
93
+ it("returns false in read mode when every cell.code is empty (server stripped)", () => {
94
+ const store = makeStore({ mode: "read" });
95
+ const { result } = renderHook(
96
+ () => useNotebookCodeAvailable(cellsWithoutCode),
97
+ { wrapper: wrap(store) },
98
+ );
99
+ expect(result.current).toBe(false);
100
+ });
101
+
102
+ it("returns false in read mode when host opts out via showAppCode", () => {
103
+ const store = makeStore({ mode: "read", showInRunMode: false });
104
+ const { result } = renderHook(
105
+ () => useNotebookCodeAvailable(cellsWithCode),
106
+ { wrapper: wrap(store) },
107
+ );
108
+ expect(result.current).toBe(false);
109
+ });
110
+
111
+ it("returns false in read mode when ?include-code=false", () => {
112
+ setSearch("?include-code=false");
113
+ const store = makeStore({ mode: "read" });
114
+ const { result } = renderHook(
115
+ () => useNotebookCodeAvailable(cellsWithCode),
116
+ { wrapper: wrap(store) },
117
+ );
118
+ expect(result.current).toBe(false);
119
+ });
120
+
121
+ it("ignores ?include-code=false in kiosk mode", () => {
122
+ setSearch("?include-code=false");
123
+ const store = makeStore({ mode: "read", kiosk: true });
124
+ const { result } = renderHook(
125
+ () => useNotebookCodeAvailable(cellsWithoutCode),
126
+ { wrapper: wrap(store) },
127
+ );
128
+ expect(result.current).toBe(true);
129
+ });
130
+
131
+ it("returns false for non-edit/non-read modes (home, gallery)", () => {
132
+ for (const mode of ["home", "gallery"] as const) {
133
+ const store = makeStore({ mode });
134
+ const { result } = renderHook(
135
+ () => useNotebookCodeAvailable(cellsWithCode),
136
+ { wrapper: wrap(store) },
137
+ );
138
+ expect(result.current).toBe(false);
139
+ }
140
+ });
141
+ });
@@ -0,0 +1,48 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { useAtomValue } from "jotai";
4
+ import { KnownQueryParams } from "@/core/constants";
5
+ import { showCodeInRunModeAtom } from "@/core/meta/state";
6
+ import { kioskModeAtom, viewStateAtom } from "@/core/mode";
7
+ import { logNever } from "@/utils/assertNever";
8
+
9
+ /**
10
+ * Whether the notebook source code reached the frontend and can be rendered.
11
+ *
12
+ * In `marimo run` the server omits cell sources unless `--include-code` is
13
+ * set (every `cell.code` arrives as `""`). Use this to gate any "show code"
14
+ * / "copy code" / "download .py" affordance.
15
+ */
16
+ export function useNotebookCodeAvailable(
17
+ cells: ReadonlyArray<{ code: string }>,
18
+ ): boolean {
19
+ const kioskMode = useAtomValue(kioskModeAtom);
20
+ const { mode } = useAtomValue(viewStateAtom);
21
+ const showInRunMode = useAtomValue(showCodeInRunModeAtom);
22
+
23
+ if (kioskMode) {
24
+ return true;
25
+ }
26
+
27
+ switch (mode) {
28
+ case "edit":
29
+ case "present":
30
+ return true;
31
+ case "home":
32
+ case "gallery":
33
+ return false;
34
+ case "read": {
35
+ if (!showInRunMode) {
36
+ return false;
37
+ }
38
+ const params = new URLSearchParams(window.location.search);
39
+ if (params.get(KnownQueryParams.includeCode) === "false") {
40
+ return false;
41
+ }
42
+ return cells.some((cell) => Boolean(cell.code));
43
+ }
44
+ default:
45
+ logNever(mode);
46
+ return false;
47
+ }
48
+ }