@marimo-team/frontend 0.22.1-dev25 → 0.22.1-dev27

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 (67) hide show
  1. package/dist/assets/{index-CCazW2UV.css → index-DawClVBD.css} +1 -1
  2. package/dist/assets/{index-DPuSyce0.js → index-Dfcne_w3.js} +1 -1
  3. package/dist/index.html +2 -2
  4. package/package.json +4 -5
  5. package/src/__tests__/main.test.tsx +12 -14
  6. package/src/components/ai/ai-provider-icon.tsx +1 -2
  7. package/src/components/data-table/cell-selection/types.ts +3 -2
  8. package/src/components/data-table/charts/charts.tsx +2 -2
  9. package/src/components/data-table/column-formatting/types.ts +3 -2
  10. package/src/components/data-table/column-header.tsx +4 -2
  11. package/src/components/data-table/column-wrapping/types.ts +3 -2
  12. package/src/components/data-table/copy-column/types.ts +3 -2
  13. package/src/components/data-table/focus-row/types.ts +3 -2
  14. package/src/components/data-table/range-focus/__tests__/atoms.test.ts +11 -11
  15. package/src/components/data-table/range-focus/__tests__/use-cell-range-selection.test.ts +9 -11
  16. package/src/components/editor/__tests__/data-attributes.test.tsx +93 -94
  17. package/src/components/editor/actions/name-cell-input.tsx +4 -2
  18. package/src/components/editor/actions/useCellActionButton.tsx +4 -2
  19. package/src/components/editor/cell/CellStatus.tsx +4 -5
  20. package/src/components/editor/cell/cell-context-menu.tsx +4 -2
  21. package/src/components/editor/cell/code/cell-editor.tsx +2 -1
  22. package/src/components/editor/cell/toolbar.tsx +2 -1
  23. package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +11 -12
  24. package/src/components/storage/__tests__/storage-snippets.test.ts +4 -6
  25. package/src/components/tracing/tracing.test.tsx +30 -30
  26. package/src/components/ui/badge.tsx +2 -1
  27. package/src/components/ui/button.tsx +2 -1
  28. package/src/components/ui/calendar.tsx +3 -2
  29. package/src/components/ui/combobox.tsx +2 -1
  30. package/src/components/ui/date-input.tsx +7 -6
  31. package/src/components/ui/date-picker.tsx +6 -4
  32. package/src/components/ui/field.tsx +1 -2
  33. package/src/components/ui/progress.tsx +3 -2
  34. package/src/components/ui/query-param-preserving-link.tsx +4 -2
  35. package/src/components/ui/sheet.tsx +2 -1
  36. package/src/components/ui/textarea.tsx +1 -2
  37. package/src/core/ai/context/providers/cell-output.ts +1 -2
  38. package/src/core/ai/tools/edit-notebook-tool.ts +4 -3
  39. package/src/core/ai/tools/run-cells-tool.ts +4 -3
  40. package/src/core/cells/__tests__/add-missing-import.test.ts +23 -22
  41. package/src/core/cells/__tests__/cell.test.ts +14 -13
  42. package/src/core/codemirror/cells/__tests__/extensions.test.ts +15 -17
  43. package/src/core/codemirror/language/languages/markdown.ts +1 -3
  44. package/src/core/codemirror/language/languages/python.ts +1 -1
  45. package/src/core/codemirror/language/languages/sql/completion-sources.tsx +4 -6
  46. package/src/core/codemirror/language/languages/sql/sql.ts +1 -3
  47. package/src/core/codemirror/reactive-references/__tests__/analyzer.test.ts +28 -42
  48. package/src/core/datasets/data-source-connections.ts +4 -2
  49. package/src/core/dom/__tests__/htmlUtils.test.ts +8 -14
  50. package/src/core/dom/__tests__/outline.test.ts +2 -3
  51. package/src/core/islands/__tests__/parse.test.ts +8 -7
  52. package/src/core/saving/__tests__/filename.test.ts +7 -6
  53. package/src/core/static/__tests__/download-html.test.ts +16 -15
  54. package/src/core/static/__tests__/files.test.ts +30 -28
  55. package/src/css/app/Cell.css +5 -2
  56. package/src/css/globals.css +40 -14
  57. package/src/plugins/impl/DataEditorPlugin.tsx +4 -2
  58. package/src/plugins/impl/FormPlugin.tsx +1 -2
  59. package/src/plugins/impl/data-frames/forms/__tests__/form.test.tsx +7 -9
  60. package/src/plugins/impl/vega/__tests__/make-selectable.test.ts +13 -14
  61. package/src/plugins/layout/ImageComparisonPlugin.tsx +1 -3
  62. package/src/plugins/stateless-plugin.ts +4 -2
  63. package/src/utils/__tests__/cell-urls.test.ts +24 -21
  64. package/src/utils/__tests__/filenames.test.ts +15 -14
  65. package/src/utils/__tests__/json-parser.test.ts +14 -21
  66. package/src/utils/__tests__/path.test.ts +34 -31
  67. package/src/utils/__tests__/urls.test.ts +19 -18
@@ -6,20 +6,14 @@ import { UIElementRegistry } from "../uiregistry";
6
6
  const registry = UIElementRegistry.INSTANCE;
7
7
 
8
8
  describe("htmlUtils", () => {
9
- it.each([
10
- false,
11
- { a: 1 },
12
- true,
13
- 0,
14
- 1,
15
- [{ a: 1 }, { b: 2 }],
16
- "hello",
17
- "",
18
- ])("can parse element.dataset.initialValue", (initialValue) => {
19
- const div = document.createElement("div");
20
- div.dataset.initialValue = JSON.stringify(initialValue);
21
- expect(parseInitialValue(div, registry)).toEqual(initialValue);
22
- });
9
+ it.each([false, { a: 1 }, true, 0, 1, [{ a: 1 }, { b: 2 }], "hello", ""])(
10
+ "can parse element.dataset.initialValue",
11
+ (initialValue) => {
12
+ const div = document.createElement("div");
13
+ div.dataset.initialValue = JSON.stringify(initialValue);
14
+ expect(parseInitialValue(div, registry)).toEqual(initialValue);
15
+ },
16
+ );
23
17
 
24
18
  it("can parse an element with no initialValue", () => {
25
19
  const div = document.createElement("div");
@@ -439,9 +439,8 @@ const OUTLINE_2: Outline = {
439
439
  };
440
440
 
441
441
  it("mergeOutlines", () => {
442
- expect(
443
- mergeOutlines([OUTLINE_1, null, OUTLINE_2, null]),
444
- ).toMatchInlineSnapshot(`
442
+ expect(mergeOutlines([OUTLINE_1, null, OUTLINE_2, null]))
443
+ .toMatchInlineSnapshot(`
445
444
  {
446
445
  "items": [
447
446
  {
@@ -78,11 +78,12 @@ describe("parseIslandCode", () => {
78
78
 
79
79
  codes = [...codes, ...codes.map(encodeURIComponent)];
80
80
 
81
- it.each(
82
- codes,
83
- )("should return the code without leading or trailing whitespace", (code) => {
84
- const result = parseIslandCode(code);
85
- const expected = 'def __():\n print("Hello, World!")\n return';
86
- expect(result).toBe(expected);
87
- });
81
+ it.each(codes)(
82
+ "should return the code without leading or trailing whitespace",
83
+ (code) => {
84
+ const result = parseIslandCode(code);
85
+ const expected = 'def __():\n print("Hello, World!")\n return';
86
+ expect(result).toBe(expected);
87
+ },
88
+ );
88
89
  });
@@ -5,12 +5,13 @@ import { EDGE_CASE_FILENAMES } from "../../../__tests__/mocks";
5
5
  import { Paths } from "../../../utils/paths";
6
6
 
7
7
  describe("filename handling logic", () => {
8
- it.each(
9
- EDGE_CASE_FILENAMES,
10
- )("should extract basename correctly for document title: %s", (filename) => {
11
- const basename = Paths.basename(filename);
12
- expect(basename).toBe(filename); // Since no path separator
13
- });
8
+ it.each(EDGE_CASE_FILENAMES)(
9
+ "should extract basename correctly for document title: %s",
10
+ (filename) => {
11
+ const basename = Paths.basename(filename);
12
+ expect(basename).toBe(filename); // Since no path separator
13
+ },
14
+ );
14
15
 
15
16
  it("should handle full paths with unicode filenames", () => {
16
17
  EDGE_CASE_FILENAMES.forEach((filename) => {
@@ -44,23 +44,24 @@ describe("updateAssetUrl", () => {
44
44
  });
45
45
 
46
46
  describe("filename handling for downloads", () => {
47
- it.each(
48
- EDGE_CASE_FILENAMES,
49
- )("should handle edge case filenames in download operations: %s", (filename) => {
50
- // Test that basename extraction works correctly for downloads
51
- const basename = Paths.basename(filename);
52
- expect(basename).toBe(filename);
47
+ it.each(EDGE_CASE_FILENAMES)(
48
+ "should handle edge case filenames in download operations: %s",
49
+ (filename) => {
50
+ // Test that basename extraction works correctly for downloads
51
+ const basename = Paths.basename(filename);
52
+ expect(basename).toBe(filename);
53
53
 
54
- // Test filename conversion for HTML downloads
55
- const htmlFilename = Filenames.toHTML(filename);
56
- expect(htmlFilename).toMatch(/\.html$/);
57
- expect(htmlFilename).toContain(Filenames.withoutExtension(filename));
54
+ // Test filename conversion for HTML downloads
55
+ const htmlFilename = Filenames.toHTML(filename);
56
+ expect(htmlFilename).toMatch(/\.html$/);
57
+ expect(htmlFilename).toContain(Filenames.withoutExtension(filename));
58
58
 
59
- // Ensure unicode and spaces are preserved in basename
60
- const withoutExt = Filenames.withoutExtension(filename);
61
- expect(withoutExt).not.toBe("");
62
- expect(typeof withoutExt).toBe("string");
63
- });
59
+ // Ensure unicode and spaces are preserved in basename
60
+ const withoutExt = Filenames.withoutExtension(filename);
61
+ expect(withoutExt).not.toBe("");
62
+ expect(typeof withoutExt).toBe("string");
63
+ },
64
+ );
64
65
 
65
66
  it("should handle blob download filename generation", () => {
66
67
  // Mock URL.createObjectURL for blob testing
@@ -192,20 +192,21 @@ describe("patchVegaLoader - loader.http", () => {
192
192
  "http://foo.com/virtual-file.json",
193
193
  ];
194
194
 
195
- it.each(
196
- pathsToTest,
197
- )("should return file content for virtual files for %s", async (s) => {
198
- const virtualFiles = {
199
- "/virtual-file.json":
200
- "data:application/json;base64,eyJrZXkiOiAidmFsdWUifQ==" as DataURLString,
201
- };
202
-
203
- const loader = createLoader();
204
- const unpatch = patchVegaLoader(loader, virtualFiles);
205
- const content = await loader.http(s);
206
- unpatch();
207
- expect(content).toBe('{"key": "value"}');
208
- });
195
+ it.each(pathsToTest)(
196
+ "should return file content for virtual files for %s",
197
+ async (s) => {
198
+ const virtualFiles = {
199
+ "/virtual-file.json":
200
+ "data:application/json;base64,eyJrZXkiOiAidmFsdWUifQ==" as DataURLString,
201
+ };
202
+
203
+ const loader = createLoader();
204
+ const unpatch = patchVegaLoader(loader, virtualFiles);
205
+ const content = await loader.http(s);
206
+ unpatch();
207
+ expect(content).toBe('{"key": "value"}');
208
+ },
209
+ );
209
210
 
210
211
  it("should fallback to original http method for non-virtual files", async () => {
211
212
  const loader = createLoader();
@@ -236,20 +237,21 @@ describe("patchVegaLoader - loader.load", () => {
236
237
  "http://foo.com/virtual-file.json",
237
238
  ];
238
239
 
239
- it.each(
240
- pathsToTest,
241
- )("should return file content for virtual files for %s", async (s) => {
242
- const virtualFiles = {
243
- "/virtual-file.json":
244
- "data:application/json;base64,eyJrZXkiOiAidmFsdWUifQ==" as DataURLString,
245
- };
246
-
247
- const loader = createLoader();
248
- const unpatch = patchVegaLoader(loader, virtualFiles);
249
- const content = await loader.load(s);
250
- unpatch();
251
- expect(content).toBe('{"key": "value"}');
252
- });
240
+ it.each(pathsToTest)(
241
+ "should return file content for virtual files for %s",
242
+ async (s) => {
243
+ const virtualFiles = {
244
+ "/virtual-file.json":
245
+ "data:application/json;base64,eyJrZXkiOiAidmFsdWUifQ==" as DataURLString,
246
+ };
247
+
248
+ const loader = createLoader();
249
+ const unpatch = patchVegaLoader(loader, virtualFiles);
250
+ const content = await loader.load(s);
251
+ unpatch();
252
+ expect(content).toBe('{"key": "value"}');
253
+ },
254
+ );
253
255
 
254
256
  it("should fallback to original load method for non-virtual files", async () => {
255
257
  const loader = createLoader();
@@ -15,6 +15,7 @@
15
15
  border: 1px solid var(--gray-4);
16
16
 
17
17
  @apply divide-y divide-(--gray-5);
18
+
18
19
  &:hover {
19
20
  border-color: var(--gray-6);
20
21
  }
@@ -99,8 +100,8 @@
99
100
  /* Special case for particular components */
100
101
 
101
102
  .output-area:has(
102
- > .output:only-child > marimo-ui-element:only-child > marimo-table
103
- ) {
103
+ > .output:only-child > marimo-ui-element:only-child > marimo-table
104
+ ) {
104
105
  padding: 0 0 5px;
105
106
  max-height: none;
106
107
  overflow: hidden;
@@ -112,6 +113,7 @@
112
113
  margin-top: 0.25rem;
113
114
  border: none;
114
115
  border-radius: 0;
116
+ border-top-left-radius: 9px;
115
117
  }
116
118
 
117
119
  marimo-table::part(table-wrapper) {
@@ -385,6 +387,7 @@
385
387
  border-top-left-radius: 9px;
386
388
  border-top-right-radius: 9px;
387
389
  }
390
+
388
391
  div[data-ai-input-open="true"] .cm-editor {
389
392
  border-top-left-radius: 0;
390
393
  border-top-right-radius: 0;
@@ -148,46 +148,72 @@
148
148
  --shadow-xxs: 0px 0px 2px 0px var(--base-shadow-darker);
149
149
 
150
150
  /* biome-ignore format: definition needs to be oneline or breaks variants */
151
- --shadow-xs: 1px 1px 2px 0px var(--base-shadow), 0px 0px 2px 0px hsl(0deg 0% 25% / var(--base-shadow-opacity));
151
+ --shadow-xs:
152
+ 1px 1px 2px 0px var(--base-shadow),
153
+ 0px 0px 2px 0px hsl(0deg 0% 25% / var(--base-shadow-opacity));
152
154
 
153
155
  /* biome-ignore format: definition needs to be oneline or breaks variants */
154
- --shadow-sm: 2px 2px 2px 0px var(--base-shadow), 0px 0px 2px 0px hsl(0deg 0% 25% / var(--base-shadow-opacity));
156
+ --shadow-sm:
157
+ 2px 2px 2px 0px var(--base-shadow),
158
+ 0px 0px 2px 0px hsl(0deg 0% 25% / var(--base-shadow-opacity));
155
159
 
156
160
  /* biome-ignore format: definition needs to be oneline or breaks variants */
157
- --shadow-md: 4px 4px 4px 0px var(--base-shadow), 0 0px 4px 0px hsl(0deg 0% 60% / var(--base-shadow-opacity));
161
+ --shadow-md:
162
+ 4px 4px 4px 0px var(--base-shadow),
163
+ 0 0px 4px 0px hsl(0deg 0% 60% / var(--base-shadow-opacity));
158
164
 
159
165
  /* biome-ignore format: definition needs to be oneline or breaks variants */
160
- --shadow-lg: 5px 6px 4px 0px var(--base-shadow), 0 0px 4px 0px hsl(0deg 0% 75% / var(--base-shadow-opacity));
166
+ --shadow-lg:
167
+ 5px 6px 4px 0px var(--base-shadow),
168
+ 0 0px 4px 0px hsl(0deg 0% 75% / var(--base-shadow-opacity));
161
169
 
162
170
  /* biome-ignore format: definition needs to be oneline or breaks variants */
163
- --shadow-xl: 8px 9px 4px 0px var(--base-shadow), 0 0px 6px 0px hsl(0deg 0% 85% / var(--base-shadow-opacity));
171
+ --shadow-xl:
172
+ 8px 9px 4px 0px var(--base-shadow),
173
+ 0 0px 6px 0px hsl(0deg 0% 85% / var(--base-shadow-opacity));
164
174
 
165
175
  /* biome-ignore format: definition needs to be oneline or breaks variants */
166
- --shadow-2xl: 10px 12px 10px 0px var(--base-shadow), 0 0px 8px 0px hsl(0deg 0% 90% / var(--base-shadow-opacity));
176
+ --shadow-2xl:
177
+ 10px 12px 10px 0px var(--base-shadow),
178
+ 0 0px 8px 0px hsl(0deg 0% 90% / var(--base-shadow-opacity));
167
179
 
168
180
  /* biome-ignore format: definition needs to be oneline or breaks variants */
169
- --shadow-xs-solid: 1px 1px 0px 0px var(--base-shadow-darker), 0px 0px 2px 0px hsl(0deg 0% 50% / 20%);
181
+ --shadow-xs-solid:
182
+ 1px 1px 0px 0px var(--base-shadow-darker),
183
+ 0px 0px 2px 0px hsl(0deg 0% 50% / 20%);
170
184
 
171
185
  /* biome-ignore format: definition needs to be oneline or breaks variants */
172
- --shadow-sm-solid: 2px 2px 0px 0px var(--base-shadow-darker), 0px 0px 2px 0px hsl(0deg 0% 50% / 20%);
186
+ --shadow-sm-solid:
187
+ 2px 2px 0px 0px var(--base-shadow-darker),
188
+ 0px 0px 2px 0px hsl(0deg 0% 50% / 20%);
173
189
 
174
190
  /* biome-ignore format: definition needs to be oneline or breaks variants */
175
- --shadow-md-solid: 4px 4px 0px 0px var(--base-shadow-darker), 0 0px 2px 0px hsl(0deg 0% 60% / 50%);
191
+ --shadow-md-solid:
192
+ 4px 4px 0px 0px var(--base-shadow-darker),
193
+ 0 0px 2px 0px hsl(0deg 0% 60% / 50%);
176
194
 
177
195
  /* biome-ignore format: definition needs to be oneline or breaks variants */
178
- --shadow-lg-solid: 5px 6px 0px 0px var(--base-shadow-darker), 0 0px 4px 0px hsl(0deg 0% 75% / 50%);
196
+ --shadow-lg-solid:
197
+ 5px 6px 0px 0px var(--base-shadow-darker),
198
+ 0 0px 4px 0px hsl(0deg 0% 75% / 50%);
179
199
 
180
200
  /* biome-ignore format: definition needs to be oneline or breaks variants */
181
- --shadow-xl-solid: 7px 8px 0px 0px var(--base-shadow-darker), 0 0px 4px 0px hsl(0deg 0% 85% / 50%);
201
+ --shadow-xl-solid:
202
+ 7px 8px 0px 0px var(--base-shadow-darker),
203
+ 0 0px 4px 0px hsl(0deg 0% 85% / 50%);
182
204
 
183
205
  /* biome-ignore format: definition needs to be oneline or breaks variants */
184
- --shadow-2xl-solid: 10px 12px 0px 0px var(--base-shadow-darker), 0 0px 8px 0px hsl(0deg 0% 90% / 50%);
206
+ --shadow-2xl-solid:
207
+ 10px 12px 0px 0px var(--base-shadow-darker),
208
+ 0 0px 8px 0px hsl(0deg 0% 90% / 50%);
185
209
 
186
210
  /* Solid shadows with lighter shade color */
187
211
 
188
212
  /* biome-ignore format: definition needs to be oneline or breaks variants */
189
- --shadow-sm-solid-shade: 2px 2px 0px 0px var(--base-shadow), 0px 0px 2px 0px hsl(0deg 0% 50% / 20%);
213
+ --shadow-sm-solid-shade:
214
+ 2px 2px 0px 0px var(--base-shadow), 0px 0px 2px 0px hsl(0deg 0% 50% / 20%);
190
215
 
191
216
  /* biome-ignore format: definition needs to be oneline or breaks variants */
192
- --shadow-md-solid-shade: 4px 4px 0px 0px var(--base-shadow), 0 0px 2px 0px hsl(0deg 0% 60% / 50%);
217
+ --shadow-md-solid-shade:
218
+ 4px 4px 0px 0px var(--base-shadow), 0 0px 2px 0px hsl(0deg 0% 60% / 50%);
193
219
  }
@@ -76,8 +76,10 @@ export const DataEditorPlugin = createPlugin<Edits>("marimo-data-editor", {
76
76
  );
77
77
  });
78
78
 
79
- interface Props
80
- extends Omit<DataEditorProps<object>, "data" | "onAddEdits" | "onAddRows"> {
79
+ interface Props extends Omit<
80
+ DataEditorProps<object>,
81
+ "data" | "onAddEdits" | "onAddRows"
82
+ > {
81
83
  data: TableData<object>;
82
84
  edits: Edits;
83
85
  onEdits: Setter<Edits>;
@@ -80,8 +80,7 @@ export const FormPlugin = createPlugin("marimo-form")
80
80
  });
81
81
 
82
82
  export interface FormWrapperProps<T>
83
- extends Omit<Data, "elementId">,
84
- Functions {
83
+ extends Omit<Data, "elementId">, Functions {
85
84
  children: React.ReactNode;
86
85
  currentValue: T;
87
86
  newValue: T;
@@ -44,16 +44,14 @@ describe("renderZodSchema", () => {
44
44
  [...TransformTypeSchema.options],
45
45
  (z) => getUnionLiteral(z).value,
46
46
  );
47
- it.each(
48
- Object.entries(options),
49
- )("should render a form %s", (name, schema: z.ZodType<
50
- unknown,
51
- FieldValues
52
- >) => {
53
- const expected = render(<Subject schema={schema} />);
47
+ it.each(Object.entries(options))(
48
+ "should render a form %s",
49
+ (name, schema: z.ZodType<unknown, FieldValues>) => {
50
+ const expected = render(<Subject schema={schema} />);
54
51
 
55
- expect(expected.asFragment()).toMatchSnapshot();
56
- });
52
+ expect(expected.asFragment()).toMatchSnapshot();
53
+ },
54
+ );
57
55
  });
58
56
 
59
57
  const options = [
@@ -578,20 +578,19 @@ describe("makeSelectable", () => {
578
578
  `);
579
579
  });
580
580
 
581
- it.each([
582
- "errorbar",
583
- "errorband",
584
- "boxplot",
585
- ])("should return the same spec if mark is %s", (mark) => {
586
- const spec = {
587
- mark,
588
- } as unknown as VegaLiteSpec;
589
- const newSpec = makeSelectable(spec, {});
590
- expect(newSpec).toEqual(spec);
591
- expect(getSelectionParamNames(newSpec)).toEqual([]);
592
- expect(newSpec).toMatchSnapshot();
593
- expect(parse(newSpec)).toBeDefined();
594
- });
581
+ it.each(["errorbar", "errorband", "boxplot"])(
582
+ "should return the same spec if mark is %s",
583
+ (mark) => {
584
+ const spec = {
585
+ mark,
586
+ } as unknown as VegaLiteSpec;
587
+ const newSpec = makeSelectable(spec, {});
588
+ expect(newSpec).toEqual(spec);
589
+ expect(getSelectionParamNames(newSpec)).toEqual([]);
590
+ expect(newSpec).toMatchSnapshot();
591
+ expect(parse(newSpec)).toBeDefined();
592
+ },
593
+ );
595
594
 
596
595
  it("should add legend selection to composite charts (issue #6676)", () => {
597
596
  // Test case from https://github.com/marimo-team/marimo/issues/6676
@@ -11,9 +11,7 @@ const LazyImageComparisonComponent = React.lazy(
11
11
  () => import("../impl/image-comparison/ImageComparisonComponent"),
12
12
  );
13
13
 
14
- export class ImageComparisonPlugin
15
- implements IStatelessPlugin<ImageComparisonData>
16
- {
14
+ export class ImageComparisonPlugin implements IStatelessPlugin<ImageComparisonData> {
17
15
  tagName = "marimo-image-comparison";
18
16
 
19
17
  validator = z.object({
@@ -20,8 +20,10 @@ export interface IStatelessPluginProps<D> {
20
20
  children?: React.ReactNode | undefined;
21
21
  }
22
22
 
23
- export interface IStatelessPlugin<D>
24
- extends Omit<IPlugin<never, D>, "render" | "functions"> {
23
+ export interface IStatelessPlugin<D> extends Omit<
24
+ IPlugin<never, D>,
25
+ "render" | "functions"
26
+ > {
25
27
  /**
26
28
  * Render the plugin.
27
29
  */
@@ -92,27 +92,30 @@ describe("cell-urls utilities", () => {
92
92
  });
93
93
 
94
94
  describe("edge case cell names with unicode and special characters", () => {
95
- it.each(
96
- EDGE_CASE_CELL_NAMES,
97
- )("should handle unicode cell names in createCellLink: %s", (cellName) => {
98
- const url = createCellLink(cellName);
99
- expect(url).toContain("scrollTo=");
100
- expect(url).toContain(encodeURIComponent(cellName));
101
- });
102
-
103
- it.each(
104
- EDGE_CASE_CELL_NAMES,
105
- )("should round-trip unicode cell names correctly: %s", (cellName) => {
106
- const url = createCellLink(cellName);
107
- const hash = url.split("#")[1];
108
- const extracted = extractCellNameFromHash(`#${hash}`);
109
- expect(extracted).toBe(cellName);
110
- });
95
+ it.each(EDGE_CASE_CELL_NAMES)(
96
+ "should handle unicode cell names in createCellLink: %s",
97
+ (cellName) => {
98
+ const url = createCellLink(cellName);
99
+ expect(url).toContain("scrollTo=");
100
+ expect(url).toContain(encodeURIComponent(cellName));
101
+ },
102
+ );
103
+
104
+ it.each(EDGE_CASE_CELL_NAMES)(
105
+ "should round-trip unicode cell names correctly: %s",
106
+ (cellName) => {
107
+ const url = createCellLink(cellName);
108
+ const hash = url.split("#")[1];
109
+ const extracted = extractCellNameFromHash(`#${hash}`);
110
+ expect(extracted).toBe(cellName);
111
+ },
112
+ );
111
113
 
112
- it.each(
113
- EDGE_CASE_CELL_NAMES,
114
- )("should allow linking to unicode cell names: %s", (cellName) => {
115
- expect(canLinkToCell(cellName)).toBe(true);
116
- });
114
+ it.each(EDGE_CASE_CELL_NAMES)(
115
+ "should allow linking to unicode cell names: %s",
116
+ (cellName) => {
117
+ expect(canLinkToCell(cellName)).toBe(true);
118
+ },
119
+ );
117
120
  });
118
121
  });
@@ -35,22 +35,23 @@ describe("Filenames", () => {
35
35
  expect(Filenames.withoutExtension("test.foo.txt")).toEqual("test.foo");
36
36
  });
37
37
 
38
- it.each(
39
- EDGE_CASE_FILENAMES,
40
- )("should handle edge case filenames: %s", (filename) => {
41
- // Test all filename operations with edge cases
42
- const withoutExt = Filenames.withoutExtension(filename);
38
+ it.each(EDGE_CASE_FILENAMES)(
39
+ "should handle edge case filenames: %s",
40
+ (filename) => {
41
+ // Test all filename operations with edge cases
42
+ const withoutExt = Filenames.withoutExtension(filename);
43
43
 
44
- expect(Filenames.toMarkdown(filename)).toEqual(`${withoutExt}.md`);
45
- expect(Filenames.toHTML(filename)).toEqual(`${withoutExt}.html`);
46
- expect(Filenames.toPNG(filename)).toEqual(`${withoutExt}.png`);
47
- expect(Filenames.toPY(filename)).toEqual(`${withoutExt}.py`);
48
- expect(Filenames.toIPYNB(filename)).toEqual(`${withoutExt}.ipynb`);
44
+ expect(Filenames.toMarkdown(filename)).toEqual(`${withoutExt}.md`);
45
+ expect(Filenames.toHTML(filename)).toEqual(`${withoutExt}.html`);
46
+ expect(Filenames.toPNG(filename)).toEqual(`${withoutExt}.png`);
47
+ expect(Filenames.toPY(filename)).toEqual(`${withoutExt}.py`);
48
+ expect(Filenames.toIPYNB(filename)).toEqual(`${withoutExt}.ipynb`);
49
49
 
50
- // Ensure operations preserve unicode and special characters in base name
51
- expect(withoutExt).not.toEqual("");
52
- expect(typeof withoutExt).toBe("string");
53
- });
50
+ // Ensure operations preserve unicode and special characters in base name
51
+ expect(withoutExt).not.toEqual("");
52
+ expect(typeof withoutExt).toBe("string");
53
+ },
54
+ );
54
55
  });
55
56
 
56
57
  describe("getImageExtension", () => {
@@ -184,9 +184,8 @@ it("can convert json to markdown - with URLs", () => {
184
184
  `);
185
185
 
186
186
  // Mixed content - URL and text in same cell
187
- expect(
188
- jsonToMarkdown([{ info: "Visit https://example.com for more" }]),
189
- ).toMatchInlineSnapshot(`
187
+ expect(jsonToMarkdown([{ info: "Visit https://example.com for more" }]))
188
+ .toMatchInlineSnapshot(`
190
189
  "| info |
191
190
  |---|
192
191
  | Visit [https://example.com](https://example.com) for more |"
@@ -203,9 +202,8 @@ it("can convert json to markdown - with URLs", () => {
203
202
  });
204
203
 
205
204
  it("can convert json to markdown - handles nulls and undefined", () => {
206
- expect(
207
- jsonToMarkdown([{ a: null, b: undefined, c: 1 }]),
208
- ).toMatchInlineSnapshot(`
205
+ expect(jsonToMarkdown([{ a: null, b: undefined, c: 1 }]))
206
+ .toMatchInlineSnapshot(`
209
207
  "| a | b | c |
210
208
  |---|---|---|
211
209
  | | | 1 |"
@@ -226,18 +224,16 @@ it("can convert json to markdown - handles nulls and undefined", () => {
226
224
 
227
225
  it("can convert json to markdown - handles special characters", () => {
228
226
  // Pipes need to be escaped since they're markdown table delimiters
229
- expect(
230
- jsonToMarkdown([{ a: "value|with|pipes", b: "normal" }]),
231
- ).toMatchInlineSnapshot(`
227
+ expect(jsonToMarkdown([{ a: "value|with|pipes", b: "normal" }]))
228
+ .toMatchInlineSnapshot(`
232
229
  "| a | b |
233
230
  |---|---|
234
231
  | value\\|with\\|pipes | normal |"
235
232
  `);
236
233
 
237
234
  // Newlines should be replaced with spaces
238
- expect(
239
- jsonToMarkdown([{ a: "line1\nline2", b: "normal" }]),
240
- ).toMatchInlineSnapshot(`
235
+ expect(jsonToMarkdown([{ a: "line1\nline2", b: "normal" }]))
236
+ .toMatchInlineSnapshot(`
241
237
  "| a | b |
242
238
  |---|---|
243
239
  | line1 line2 | normal |"
@@ -252,9 +248,8 @@ it("can convert json to markdown - handles special characters", () => {
252
248
  });
253
249
 
254
250
  it("can convert json to markdown - handles different data types", () => {
255
- expect(
256
- jsonToMarkdown([{ str: "text", num: 42, bool: true, nil: null }]),
257
- ).toMatchInlineSnapshot(`
251
+ expect(jsonToMarkdown([{ str: "text", num: 42, bool: true, nil: null }]))
252
+ .toMatchInlineSnapshot(`
258
253
  "| str | num | bool | nil |
259
254
  |---|---|---|---|
260
255
  | text | 42 | true | |"
@@ -282,9 +277,8 @@ it("can convert json to markdown - handles different data types", () => {
282
277
  | [1,2,3] |"
283
278
  `);
284
279
 
285
- expect(
286
- jsonToMarkdown([{ data: { nested: "value" } }]),
287
- ).toMatchInlineSnapshot(`
280
+ expect(jsonToMarkdown([{ data: { nested: "value" } }]))
281
+ .toMatchInlineSnapshot(`
288
282
  "| data |
289
283
  |---|
290
284
  | {"nested":"value"} |"
@@ -293,9 +287,8 @@ it("can convert json to markdown - handles different data types", () => {
293
287
 
294
288
  it("can convert json to markdown - handles existing markdown links", () => {
295
289
  // When input already contains a markdown link, it should be preserved as-is
296
- expect(
297
- jsonToMarkdown([{ link: "[Google](https://google.com)" }]),
298
- ).toMatchInlineSnapshot(`
290
+ expect(jsonToMarkdown([{ link: "[Google](https://google.com)" }]))
291
+ .toMatchInlineSnapshot(`
299
292
  "| link |
300
293
  |---|
301
294
  | [Google](https://google.com) |"