@marimo-team/islands 0.23.16-dev9 → 0.23.17-dev0

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 (164) hide show
  1. package/dist/{chat-ui-JeekqoMH.js → chat-ui-DsZJj75A.js} +3104 -3107
  2. package/dist/{code-visibility-DveLKNtj.js → common-CotPLtrU.js} +1068 -1036
  3. package/dist/{formats-CO8cn5_K.js → formats-Dwq1THhr.js} +1 -1
  4. package/dist/{html-to-image-Bif4utQZ.js → html-to-image-CZ1kLKkq.js} +7336 -6342
  5. package/dist/main.js +211 -147
  6. package/dist/{node-sql-parser-DLl_zNw0.js → node-sql-parser-v1Vi-Fft.js} +16867 -16665
  7. package/dist/{process-output-TAzy-GUk.js → process-output-bVfbcx5_.js} +30 -26
  8. package/dist/{reveal-component-DxV4Ppa2.js → reveal-component-IoHipy82.js} +634 -626
  9. package/dist/style.css +1 -1
  10. package/dist/toDate-CqhBWjvO.js +713 -0
  11. package/dist/{vega-component-DphdxaJ_.js → vega-component-C7nO5zcf.js} +2 -2
  12. package/package.json +3 -4
  13. package/src/__mocks__/requests.ts +56 -4
  14. package/src/components/data-table/__tests__/__snapshots__/chart-spec-model.test.ts.snap +174 -21
  15. package/src/components/data-table/__tests__/chart-spec-model.test.ts +102 -1
  16. package/src/components/data-table/__tests__/columns.test.tsx +9 -1
  17. package/src/components/data-table/column-summary/chart-spec-model.tsx +163 -103
  18. package/src/components/data-table/column-summary/legacy-chart-spec.ts +15 -7
  19. package/src/components/data-table/column-summary/utils.ts +26 -7
  20. package/src/components/data-table/columns.tsx +11 -1
  21. package/src/components/data-table/types.ts +1 -1
  22. package/src/components/editor/__tests__/viewer-banner.test.tsx +14 -0
  23. package/src/components/editor/actions/__tests__/pair-with-agent-commands.test.ts +99 -18
  24. package/src/components/editor/actions/__tests__/pdf-export.test.ts +32 -13
  25. package/src/components/editor/actions/pair-with-agent-commands.ts +21 -10
  26. package/src/components/editor/actions/pair-with-agent-modal.tsx +2 -2
  27. package/src/components/editor/actions/pdf-export.ts +11 -14
  28. package/src/components/editor/actions/useNotebookActions.tsx +28 -19
  29. package/src/components/editor/cell/code/__tests__/cell-editor-activity-lifecycle.test.ts +250 -0
  30. package/src/components/editor/cell/code/cell-editor.tsx +13 -7
  31. package/src/components/editor/chrome/__tests__/state.test.ts +7 -1
  32. package/src/components/editor/chrome/panels/__tests__/logs-panel.test.tsx +25 -0
  33. package/src/components/editor/chrome/panels/__tests__/panel-accordion-state.test.ts +42 -0
  34. package/src/components/editor/chrome/panels/file-explorer-panel.tsx +6 -17
  35. package/src/components/editor/chrome/panels/logs-panel.tsx +12 -2
  36. package/src/components/editor/chrome/panels/panel-accordion-state.ts +51 -0
  37. package/src/components/editor/chrome/panels/session-panel.tsx +5 -16
  38. package/src/components/editor/chrome/panels/write-secret-modal.tsx +40 -12
  39. package/src/components/editor/chrome/state.ts +10 -0
  40. package/src/components/editor/connections/__tests__/json-credentials.test.ts +114 -0
  41. package/src/components/editor/connections/__tests__/paths.test.ts +39 -0
  42. package/src/components/editor/connections/__tests__/quick-add-data-sources.test.tsx +113 -0
  43. package/src/components/editor/connections/add-connection-dialog.tsx +2 -0
  44. package/src/components/editor/connections/components.tsx +6 -2
  45. package/src/components/editor/connections/database/__tests__/__snapshots__/as-code.test.ts.snap +27 -5
  46. package/src/components/editor/connections/database/__tests__/as-code.test.ts +33 -0
  47. package/src/components/editor/connections/database/as-code.ts +37 -13
  48. package/src/components/editor/connections/database/schemas.ts +6 -5
  49. package/src/components/editor/connections/form-renderers.tsx +99 -1
  50. package/src/components/editor/connections/json-credentials.ts +57 -0
  51. package/src/components/editor/connections/paths.ts +21 -0
  52. package/src/components/editor/connections/quick-add-data-sources.tsx +106 -0
  53. package/src/components/editor/connections/secret-combobox.tsx +41 -20
  54. package/src/components/editor/connections/storage/__tests__/__snapshots__/as-code.test.ts.snap +88 -2
  55. package/src/components/editor/connections/storage/__tests__/as-code.test.ts +128 -8
  56. package/src/components/editor/connections/storage/as-code.ts +79 -36
  57. package/src/components/editor/connections/storage/schemas.ts +85 -44
  58. package/src/components/editor/file-tree/__tests__/renderers.test.ts +31 -0
  59. package/src/components/editor/file-tree/file-viewer.tsx +11 -7
  60. package/src/components/editor/file-tree/renderers.tsx +21 -0
  61. package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +2 -2
  62. package/src/components/editor/viewer-banner.tsx +6 -1
  63. package/src/components/forms/form.tsx +59 -24
  64. package/src/components/forms/options.ts +2 -1
  65. package/src/components/slides/__tests__/cell-output-block.test.tsx +40 -0
  66. package/src/components/slides/__tests__/slide-cell-view.test.tsx +33 -0
  67. package/src/components/slides/reveal-component.tsx +22 -17
  68. package/src/components/slides/slide-cell-view.tsx +1 -1
  69. package/src/components/slides/slides.css +4 -2
  70. package/src/components/static-html/share-modal.tsx +1 -1
  71. package/src/core/ai/context/providers/file.ts +8 -29
  72. package/src/core/cells/__tests__/cells.test.ts +12 -2
  73. package/src/core/cells/__tests__/logs.test.ts +5 -5
  74. package/src/core/cells/cells.ts +17 -4
  75. package/src/core/cells/document-changes.ts +1 -0
  76. package/src/core/cells/logs.ts +14 -4
  77. package/src/core/codemirror/cm.ts +3 -0
  78. package/src/core/codemirror/code-lens/__tests__/actions.test.ts +68 -0
  79. package/src/core/codemirror/code-lens/__tests__/analyzer.test.ts +224 -0
  80. package/src/core/codemirror/code-lens/__tests__/entities.test.ts +175 -0
  81. package/src/core/codemirror/code-lens/__tests__/extension.test.ts +325 -0
  82. package/src/core/codemirror/code-lens/__tests__/perf.test.ts +121 -0
  83. package/src/core/codemirror/code-lens/__tests__/popover.test.ts +93 -0
  84. package/src/core/codemirror/code-lens/actions.ts +33 -0
  85. package/src/core/codemirror/code-lens/analyzer.ts +284 -0
  86. package/src/core/codemirror/code-lens/entities.ts +123 -0
  87. package/src/core/codemirror/code-lens/extension.ts +298 -0
  88. package/src/core/codemirror/code-lens/icons.ts +36 -0
  89. package/src/core/codemirror/code-lens/popover.tsx +240 -0
  90. package/src/core/codemirror/copilot/__tests__/language-server.test.ts +396 -100
  91. package/src/core/codemirror/copilot/__tests__/transport.test.ts +128 -540
  92. package/src/core/codemirror/copilot/language-server.ts +182 -70
  93. package/src/core/codemirror/copilot/transport.ts +15 -207
  94. package/src/core/codemirror/copilot/types.ts +9 -2
  95. package/src/core/codemirror/find-replace/__tests__/navigate.test.ts +42 -30
  96. package/src/core/codemirror/find-replace/navigate.ts +2 -0
  97. package/src/core/codemirror/go-to-definition/__tests__/commands.test.ts +24 -3
  98. package/src/core/codemirror/go-to-definition/commands.ts +93 -90
  99. package/src/core/codemirror/language/__tests__/sql.test.ts +627 -61
  100. package/src/core/codemirror/language/languages/python.ts +23 -11
  101. package/src/core/codemirror/language/languages/sql/completion-store.ts +5 -1
  102. package/src/core/codemirror/language/languages/sql/renderers.tsx +3 -3
  103. package/src/core/codemirror/language/languages/sql/sql.ts +251 -16
  104. package/src/core/codemirror/lsp/__tests__/federated-lsp.test.ts +161 -0
  105. package/src/core/codemirror/lsp/__tests__/log-messages.test.ts +53 -0
  106. package/src/core/codemirror/lsp/__tests__/normalize-markdown-math.test.ts +25 -0
  107. package/src/core/codemirror/lsp/__tests__/notebook-lsp.test.ts +598 -156
  108. package/src/core/codemirror/lsp/__tests__/transports.test.ts +112 -0
  109. package/src/core/codemirror/lsp/federated-lsp.ts +76 -23
  110. package/src/core/codemirror/lsp/log-messages.ts +37 -0
  111. package/src/core/codemirror/lsp/normalize-markdown-math.ts +8 -4
  112. package/src/core/codemirror/lsp/notebook-lsp.ts +220 -56
  113. package/src/core/codemirror/lsp/transports.ts +8 -1
  114. package/src/core/codemirror/lsp/types.ts +68 -8
  115. package/src/core/codemirror/lsp/utils.ts +14 -0
  116. package/src/core/codemirror/python-node-names.ts +90 -0
  117. package/src/core/codemirror/reactive-references/analyzer.ts +52 -57
  118. package/src/core/codemirror/utils.ts +28 -0
  119. package/src/core/config/feature-flag.tsx +3 -1
  120. package/src/core/datasets/data-source-discovery.ts +5 -0
  121. package/src/core/datasets/request-registry.ts +11 -0
  122. package/src/core/edit-app.tsx +1 -2
  123. package/src/core/export/__tests__/hooks.test.ts +2 -4
  124. package/src/core/export/hooks.ts +4 -3
  125. package/src/core/islands/__tests__/bridge.test.ts +107 -0
  126. package/src/core/islands/bootstrap.ts +1 -0
  127. package/src/core/islands/bridge.ts +18 -0
  128. package/src/core/kernel/messages.ts +2 -0
  129. package/src/core/lsp/__tests__/transport.test.ts +509 -337
  130. package/src/core/lsp/transport.ts +304 -132
  131. package/src/core/mode.ts +10 -0
  132. package/src/core/network/__tests__/api.test.ts +42 -0
  133. package/src/core/network/__tests__/connection.test.ts +45 -0
  134. package/src/core/network/__tests__/requests-lazy.test.ts +47 -1
  135. package/src/core/network/__tests__/requests-network.test.ts +48 -0
  136. package/src/core/network/api.ts +27 -0
  137. package/src/core/network/connection.ts +15 -0
  138. package/src/core/network/requests-lazy.ts +3 -0
  139. package/src/core/network/requests-network.ts +31 -8
  140. package/src/core/network/requests-static.ts +3 -0
  141. package/src/core/network/requests-toasting.tsx +3 -0
  142. package/src/core/network/types.ts +27 -6
  143. package/src/core/rtc/state.ts +9 -0
  144. package/src/core/static/download-html.ts +5 -14
  145. package/src/core/wasm/__tests__/bridge.test.ts +99 -5
  146. package/src/core/wasm/bridge.ts +42 -9
  147. package/src/core/wasm/worker/types.ts +10 -3
  148. package/src/core/wasm/worker/worker.ts +7 -8
  149. package/src/core/websocket/useMarimoKernelConnection.tsx +4 -0
  150. package/src/css/app/codemirror.css +16 -2
  151. package/src/hooks/__tests__/useDataSourceDiscovery.test.ts +80 -0
  152. package/src/hooks/useDataSourceDiscovery.ts +18 -0
  153. package/src/plugins/impl/DataTablePlugin.tsx +4 -1
  154. package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +158 -0
  155. package/src/plugins/impl/anywidget/__tests__/model.test.ts +55 -0
  156. package/src/plugins/impl/anywidget/model.ts +20 -5
  157. package/src/plugins/impl/data-frames/forms/__tests__/__snapshots__/form.test.tsx.snap +33 -21
  158. package/src/plugins/impl/data-frames/forms/renderers.tsx +11 -8
  159. package/src/stories/log-viewer.stories.tsx +12 -6
  160. package/src/utils/__tests__/blob.test.ts +27 -1
  161. package/src/utils/__tests__/download.test.tsx +44 -4
  162. package/src/utils/blob.ts +24 -1
  163. package/src/utils/download.ts +14 -15
  164. package/dist/toDate-CmR_xR3P.js +0 -692
@@ -0,0 +1,224 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { python } from "@codemirror/lang-python";
4
+ import { EditorState } from "@codemirror/state";
5
+ import { describe, expect, it } from "vitest";
6
+ import { findCacheSites, findDeclarationSites } from "../analyzer";
7
+
8
+ function pythonState(code: string) {
9
+ return EditorState.create({ doc: code, extensions: [python()] });
10
+ }
11
+
12
+ function run(names: string[], code: string) {
13
+ return findDeclarationSites({
14
+ state: pythonState(code),
15
+ names: new Set(names),
16
+ });
17
+ }
18
+
19
+ describe("findDeclarationSites", () => {
20
+ it("finds a simple assignment", () => {
21
+ const code = 'df = pd.read_csv("a.csv")';
22
+ const targets = run(["df"], code);
23
+ expect(targets).toEqual([{ from: 0, to: 2, name: "df" }]);
24
+ expect(code.slice(targets[0].from, targets[0].to)).toBe("df");
25
+ });
26
+
27
+ it("only matches the given names", () => {
28
+ const targets = run(["df"], "df = 1\nother = 2");
29
+ expect(targets.map((t) => t.name)).toEqual(["df"]);
30
+ });
31
+
32
+ it("handles annotated assignments", () => {
33
+ const targets = run(["df", "DataFrame"], "df: DataFrame = load()");
34
+ expect(targets.map((t) => t.name)).toEqual(["df"]);
35
+ });
36
+
37
+ it("handles chained assignments", () => {
38
+ const targets = run(["a", "b"], "a = b = load()");
39
+ expect(targets.map((t) => t.name).toSorted()).toEqual(["a", "b"]);
40
+ });
41
+
42
+ it("handles tuple and list unpacking", () => {
43
+ expect(run(["x", "y"], "x, y = f()").map((t) => t.name)).toEqual([
44
+ "x",
45
+ "y",
46
+ ]);
47
+ expect(run(["a", "b"], "(a, b) = f()").map((t) => t.name)).toEqual([
48
+ "a",
49
+ "b",
50
+ ]);
51
+ expect(run(["a", "b"], "[a, b] = f()").map((t) => t.name)).toEqual([
52
+ "a",
53
+ "b",
54
+ ]);
55
+ });
56
+
57
+ it("handles a parenthesized single target", () => {
58
+ expect(run(["df"], "(df) = load()").map((t) => t.name)).toEqual(["df"]);
59
+ });
60
+
61
+ it("returns only the first assignment site per name", () => {
62
+ const targets = run(["df"], "df = 1\ndf = 2");
63
+ expect(targets).toEqual([{ from: 0, to: 2, name: "df" }]);
64
+ });
65
+
66
+ it("ignores attribute and subscript targets", () => {
67
+ expect(run(["df"], "obj.df = 1")).toEqual([]);
68
+ expect(run(["df"], "data[df] = 1")).toEqual([]);
69
+ });
70
+
71
+ it("ignores augmented assignments", () => {
72
+ expect(run(["df"], "df += 1")).toEqual([]);
73
+ });
74
+
75
+ it("ignores assignments in function, lambda, and class scopes", () => {
76
+ expect(run(["df"], "def f():\n df = 1")).toEqual([]);
77
+ expect(run(["df"], "class A:\n df = 1")).toEqual([]);
78
+ });
79
+
80
+ it("finds assignments in top-level control flow", () => {
81
+ const targets = run(["df"], "if cond:\n df = load()");
82
+ expect(targets.map((t) => t.name)).toEqual(["df"]);
83
+ });
84
+
85
+ it("ignores usages that are not assignments", () => {
86
+ expect(run(["df"], "print(df)")).toEqual([]);
87
+ });
88
+
89
+ it("returns nothing for syntax errors", () => {
90
+ expect(run(["df"], "df = (")).toEqual([]);
91
+ });
92
+
93
+ it("returns nothing for an empty name set", () => {
94
+ expect(run([], "df = 1")).toEqual([]);
95
+ });
96
+ });
97
+
98
+ describe("findCacheSites", () => {
99
+ function runCache(code: string) {
100
+ return findCacheSites(pythonState(code)).map((site) =>
101
+ code.slice(site.from, site.to),
102
+ );
103
+ }
104
+
105
+ it("matches decorator usage", () => {
106
+ expect(runCache("@mo.cache\ndef f():\n return 1")).toEqual(["mo.cache"]);
107
+ });
108
+
109
+ it("extends past decorator arguments", () => {
110
+ expect(
111
+ runCache("@mo.cache(pin_modules=True)\ndef f():\n return 1"),
112
+ ).toEqual(["mo.cache(pin_modules=True)"]);
113
+ });
114
+
115
+ it("extends past call arguments", () => {
116
+ expect(runCache("g = mo.cache(f)")).toEqual(["mo.cache(f)"]);
117
+ expect(runCache("g = mo.cache(f, pin_modules=True)")).toEqual([
118
+ "mo.cache(f, pin_modules=True)",
119
+ ]);
120
+ });
121
+
122
+ it("does not extend a bare reference or an enclosing call", () => {
123
+ expect(runCache("g = mo.cache")).toEqual(["mo.cache"]);
124
+ expect(runCache("print(mo.cache, 1)")).toEqual(["mo.cache"]);
125
+ });
126
+
127
+ it("matches persistent_cache context manager usage", () => {
128
+ expect(runCache('with mo.persistent_cache("k"):\n pass')).toEqual([
129
+ 'mo.persistent_cache("k")',
130
+ ]);
131
+ });
132
+
133
+ it("matches multiple occurrences", () => {
134
+ expect(runCache("mo.cache(f)\nmo.persistent_cache(g)")).toHaveLength(2);
135
+ });
136
+
137
+ it("does not match similar names", () => {
138
+ expect(runCache("memo.cache(f)")).toEqual([]);
139
+ expect(runCache("mo.cached(f)")).toEqual([]);
140
+ expect(runCache("mo.cache_info()")).toEqual([]);
141
+ });
142
+
143
+ it("does not match attribute chains like obj.mo.cache", () => {
144
+ expect(runCache("obj.mo.cache(f)")).toEqual([]);
145
+ expect(runCache("self.mo.persistent_cache(g)")).toEqual([]);
146
+ });
147
+
148
+ it("does not match mentions in comments or strings", () => {
149
+ expect(runCache("# uses mo.cache internally")).toEqual([]);
150
+ expect(runCache('x = "mo.cache"')).toEqual([]);
151
+ expect(runCache("mo.cache(f) # not mo.persistent_cache")).toEqual([
152
+ "mo.cache(f)",
153
+ ]);
154
+ });
155
+
156
+ it("returns nothing for syntax errors", () => {
157
+ expect(runCache("mo.cache(f)\ndf = (")).toEqual([]);
158
+ });
159
+
160
+ describe("bound name and cache name extraction", () => {
161
+ function sites(code: string) {
162
+ return findCacheSites(pythonState(code)).map(
163
+ ({ boundName, cacheName }) => ({ boundName, cacheName }),
164
+ );
165
+ }
166
+
167
+ it("extracts the decorated function name", () => {
168
+ expect(sites("@mo.cache\ndef add(a, b):\n return a + b")).toEqual([
169
+ { boundName: "add", cacheName: null },
170
+ ]);
171
+ expect(
172
+ sites("@mo.cache(pin_modules=True)\ndef add(a, b):\n return a + b"),
173
+ ).toEqual([{ boundName: "add", cacheName: null }]);
174
+ });
175
+
176
+ it("extracts the assignment target", () => {
177
+ expect(sites("g = mo.cache(f)")).toEqual([
178
+ { boundName: "g", cacheName: null },
179
+ ]);
180
+ });
181
+
182
+ it("extracts the with-statement binding and cache name", () => {
183
+ expect(sites('with mo.persistent_cache("k") as c:\n pass')).toEqual([
184
+ { boundName: "c", cacheName: "k" },
185
+ ]);
186
+ expect(sites('with mo.persistent_cache("k"):\n pass')).toEqual([
187
+ { boundName: null, cacheName: "k" },
188
+ ]);
189
+ expect(sites('with mo.persistent_cache(name="k"):\n pass')).toEqual([
190
+ { boundName: null, cacheName: "k" },
191
+ ]);
192
+ });
193
+
194
+ it("ignores string-valued options that are not cache names", () => {
195
+ expect(
196
+ sites(
197
+ '@mo.cache(hash_type="content")\ndef add(a, b):\n return a + b',
198
+ ),
199
+ ).toEqual([{ boundName: "add", cacheName: null }]);
200
+ expect(
201
+ sites(
202
+ '@mo.persistent_cache(save_path="cache")\ndef add(a, b):\n return a + b',
203
+ ),
204
+ ).toEqual([{ boundName: "add", cacheName: null }]);
205
+ expect(
206
+ sites('with mo.persistent_cache(prefix, save_path="cache"):\n pass'),
207
+ ).toEqual([{ boundName: null, cacheName: null }]);
208
+ });
209
+
210
+ it("extracts an explicit name after other keyword arguments", () => {
211
+ expect(
212
+ sites(
213
+ 'with mo.persistent_cache(save_path="cache", name="k"):\n pass',
214
+ ),
215
+ ).toEqual([{ boundName: null, cacheName: "k" }]);
216
+ });
217
+
218
+ it("extracts nothing for bare expressions", () => {
219
+ expect(sites("mo.cache(f)")).toEqual([
220
+ { boundName: null, cacheName: null },
221
+ ]);
222
+ });
223
+ });
224
+ });
@@ -0,0 +1,175 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import { beforeEach, describe, expect, it, vi } from "vitest";
4
+ import { cellId, variableName } from "@/__tests__/branded";
5
+ import {
6
+ type DataSourceConnection,
7
+ dataSourceConnectionsAtom,
8
+ } from "@/core/datasets/data-source-connections";
9
+ import { type ConnectionName, DUCKDB_ENGINE } from "@/core/datasets/engines";
10
+ import { datasetsAtom } from "@/core/datasets/state";
11
+ import type { QualifiedColumn } from "@/core/datasets/types";
12
+ import type { DataTable } from "@/core/kernel/messages";
13
+ import { store } from "@/core/state/jotai";
14
+ import { storageAtom } from "@/core/storage/state";
15
+ import type { StorageNamespace } from "@/core/storage/types";
16
+ import { variablesAtom } from "@/core/variables/state";
17
+ import type { Variables } from "@/core/variables/types";
18
+ import { Logger } from "@/utils/Logger";
19
+ import { getLensEntities } from "../entities";
20
+
21
+ const CELL = cellId("cell1");
22
+ const OTHER_CELL = cellId("other-cell");
23
+
24
+ const table = (name: string): DataTable => ({
25
+ name,
26
+ source: "memory",
27
+ source_type: "local",
28
+ num_rows: 1,
29
+ num_columns: 1,
30
+ columns: [],
31
+ variable_name: variableName(name),
32
+ });
33
+
34
+ const namespace = (name: string): StorageNamespace => ({
35
+ name: variableName(name),
36
+ displayName: name,
37
+ protocol: "s3",
38
+ rootPath: `s3://${name}`,
39
+ backendType: "obstore",
40
+ storageEntries: [],
41
+ });
42
+
43
+ const connection = (name: string): DataSourceConnection => ({
44
+ name: name as ConnectionName,
45
+ source: "postgres",
46
+ dialect: "postgresql",
47
+ display_name: name,
48
+ databases: [],
49
+ });
50
+
51
+ function seedStore(opts: {
52
+ tables?: DataTable[];
53
+ namespaces?: StorageNamespace[];
54
+ connections?: DataSourceConnection[];
55
+ variables?: Variables;
56
+ }) {
57
+ store.set(datasetsAtom, {
58
+ tables: opts.tables ?? [],
59
+ expandedTables: new Set<string>(),
60
+ expandedColumns: new Set<QualifiedColumn>(),
61
+ columnsPreviews: new Map(),
62
+ });
63
+ store.set(storageAtom, {
64
+ namespaces: opts.namespaces ?? [],
65
+ entriesByPath: new Map(),
66
+ pageMetadataByPath: new Map(),
67
+ });
68
+ store.set(dataSourceConnectionsAtom, {
69
+ latestEngineSelected: DUCKDB_ENGINE,
70
+ connectionsMap: new Map((opts.connections ?? []).map((c) => [c.name, c])),
71
+ });
72
+ store.set(variablesAtom, opts.variables ?? {});
73
+ }
74
+
75
+ describe("getLensEntities", () => {
76
+ beforeEach(() => {
77
+ seedStore({});
78
+ vi.restoreAllMocks();
79
+ });
80
+
81
+ it("returns an empty map when there are no entities", () => {
82
+ expect(getLensEntities(CELL)).toEqual(new Map());
83
+ });
84
+
85
+ it("includes tables, connections, and buckets declared in the cell", () => {
86
+ seedStore({
87
+ tables: [table("df")],
88
+ connections: [connection("engine")],
89
+ namespaces: [namespace("bucket")],
90
+ });
91
+
92
+ const entities = getLensEntities(CELL);
93
+ expect(entities).toEqual(
94
+ new Map([
95
+ ["df", "table"],
96
+ ["engine", "connection"],
97
+ ["bucket", "bucket"],
98
+ ]),
99
+ );
100
+ });
101
+
102
+ it("excludes the internal DuckDB engine from connections", () => {
103
+ seedStore({ connections: [connection(DUCKDB_ENGINE)] });
104
+ expect(getLensEntities(CELL)).toEqual(new Map());
105
+ });
106
+
107
+ it("excludes entities declared by a different cell", () => {
108
+ seedStore({
109
+ tables: [table("df")],
110
+ variables: {
111
+ [variableName("df")]: {
112
+ name: variableName("df"),
113
+ declaredBy: [OTHER_CELL],
114
+ usedBy: [],
115
+ },
116
+ },
117
+ });
118
+
119
+ expect(getLensEntities(CELL)).toEqual(new Map());
120
+ });
121
+
122
+ it("is permissive when the kernel hasn't reported the variable yet", () => {
123
+ // No entry in `variablesAtom` for "df" at all.
124
+ seedStore({ tables: [table("df")] });
125
+ expect(getLensEntities(CELL)).toEqual(new Map([["df", "table"]]));
126
+ });
127
+
128
+ it("last-write-wins and logs an error when a name is claimed by more than one kind", () => {
129
+ const errorSpy = vi
130
+ .spyOn(Logger, "error")
131
+ .mockImplementation(() => undefined);
132
+ seedStore({
133
+ tables: [table("shared")],
134
+ connections: [connection("shared")],
135
+ });
136
+
137
+ const entities = getLensEntities(CELL);
138
+
139
+ expect(entities.get("shared")).toBe("connection");
140
+ expect(errorSpy).toHaveBeenCalledExactlyOnceWith(
141
+ expect.stringContaining("shared"),
142
+ );
143
+ });
144
+
145
+ it("does not log when the same name repeats with the same kind", () => {
146
+ const errorSpy = vi
147
+ .spyOn(Logger, "error")
148
+ .mockImplementation(() => undefined);
149
+ seedStore({ tables: [table("df"), table("df")] });
150
+
151
+ const entities = getLensEntities(CELL);
152
+
153
+ expect(entities.get("df")).toBe("table");
154
+ expect(errorSpy).not.toHaveBeenCalled();
155
+ });
156
+
157
+ it("shares the underlying entity map across cells instead of rebuilding it per call", () => {
158
+ // Regression test: the table/connection/namespace scan (and its
159
+ // collision logging) must run once and be reused by every cell, not
160
+ // once per `getLensEntities` call.
161
+ const errorSpy = vi
162
+ .spyOn(Logger, "error")
163
+ .mockImplementation(() => undefined);
164
+ seedStore({
165
+ tables: [table("shared")],
166
+ connections: [connection("shared")],
167
+ });
168
+
169
+ getLensEntities(CELL);
170
+ getLensEntities(OTHER_CELL);
171
+ getLensEntities(CELL);
172
+
173
+ expect(errorSpy).toHaveBeenCalledTimes(1);
174
+ });
175
+ });