@marimo-team/frontend 0.22.1-dev26 → 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.
- package/package.json +4 -5
- package/src/__tests__/main.test.tsx +12 -14
- package/src/components/ai/ai-provider-icon.tsx +1 -2
- package/src/components/data-table/cell-selection/types.ts +3 -2
- package/src/components/data-table/column-formatting/types.ts +3 -2
- package/src/components/data-table/column-header.tsx +4 -2
- package/src/components/data-table/column-wrapping/types.ts +3 -2
- package/src/components/data-table/copy-column/types.ts +3 -2
- package/src/components/data-table/focus-row/types.ts +3 -2
- package/src/components/data-table/range-focus/__tests__/atoms.test.ts +11 -11
- package/src/components/data-table/range-focus/__tests__/use-cell-range-selection.test.ts +9 -11
- package/src/components/editor/__tests__/data-attributes.test.tsx +93 -94
- package/src/components/editor/actions/name-cell-input.tsx +4 -2
- package/src/components/editor/actions/useCellActionButton.tsx +4 -2
- package/src/components/editor/cell/CellStatus.tsx +4 -5
- package/src/components/editor/cell/cell-context-menu.tsx +4 -2
- package/src/components/editor/cell/code/cell-editor.tsx +2 -1
- package/src/components/editor/cell/toolbar.tsx +2 -1
- package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +11 -12
- package/src/components/storage/__tests__/storage-snippets.test.ts +4 -6
- package/src/components/tracing/tracing.test.tsx +30 -30
- package/src/components/ui/badge.tsx +2 -1
- package/src/components/ui/button.tsx +2 -1
- package/src/components/ui/calendar.tsx +3 -2
- package/src/components/ui/combobox.tsx +2 -1
- package/src/components/ui/date-input.tsx +7 -6
- package/src/components/ui/date-picker.tsx +6 -4
- package/src/components/ui/field.tsx +1 -2
- package/src/components/ui/progress.tsx +3 -2
- package/src/components/ui/query-param-preserving-link.tsx +4 -2
- package/src/components/ui/sheet.tsx +2 -1
- package/src/components/ui/textarea.tsx +1 -2
- package/src/core/ai/context/providers/cell-output.ts +1 -2
- package/src/core/ai/tools/edit-notebook-tool.ts +4 -3
- package/src/core/ai/tools/run-cells-tool.ts +4 -3
- package/src/core/cells/__tests__/add-missing-import.test.ts +23 -22
- package/src/core/cells/__tests__/cell.test.ts +14 -13
- package/src/core/codemirror/cells/__tests__/extensions.test.ts +15 -17
- package/src/core/codemirror/language/languages/markdown.ts +1 -3
- package/src/core/codemirror/language/languages/python.ts +1 -1
- package/src/core/codemirror/language/languages/sql/completion-sources.tsx +4 -6
- package/src/core/codemirror/language/languages/sql/sql.ts +1 -3
- package/src/core/codemirror/reactive-references/__tests__/analyzer.test.ts +28 -42
- package/src/core/datasets/data-source-connections.ts +4 -2
- package/src/core/dom/__tests__/htmlUtils.test.ts +8 -14
- package/src/core/dom/__tests__/outline.test.ts +2 -3
- package/src/core/islands/__tests__/parse.test.ts +8 -7
- package/src/core/saving/__tests__/filename.test.ts +7 -6
- package/src/core/static/__tests__/download-html.test.ts +16 -15
- package/src/core/static/__tests__/files.test.ts +30 -28
- package/src/css/app/Cell.css +11 -11
- package/src/css/globals.css +40 -14
- package/src/plugins/impl/DataEditorPlugin.tsx +4 -2
- package/src/plugins/impl/FormPlugin.tsx +1 -2
- package/src/plugins/impl/data-frames/forms/__tests__/form.test.tsx +7 -9
- package/src/plugins/impl/vega/__tests__/make-selectable.test.ts +13 -14
- package/src/plugins/layout/ImageComparisonPlugin.tsx +1 -3
- package/src/plugins/stateless-plugin.ts +4 -2
- package/src/utils/__tests__/cell-urls.test.ts +24 -21
- package/src/utils/__tests__/filenames.test.ts +15 -14
- package/src/utils/__tests__/json-parser.test.ts +14 -21
- package/src/utils/__tests__/path.test.ts +34 -31
- package/src/utils/__tests__/urls.test.ts +19 -18
|
@@ -44,23 +44,24 @@ describe("updateAssetUrl", () => {
|
|
|
44
44
|
});
|
|
45
45
|
|
|
46
46
|
describe("filename handling for downloads", () => {
|
|
47
|
-
it.each(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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();
|
package/src/css/app/Cell.css
CHANGED
|
@@ -99,7 +99,9 @@
|
|
|
99
99
|
|
|
100
100
|
/* Special case for particular components */
|
|
101
101
|
|
|
102
|
-
.output-area:has(
|
|
102
|
+
.output-area:has(
|
|
103
|
+
> .output:only-child > marimo-ui-element:only-child > marimo-table
|
|
104
|
+
) {
|
|
103
105
|
padding: 0 0 5px;
|
|
104
106
|
max-height: none;
|
|
105
107
|
overflow: hidden;
|
|
@@ -124,12 +126,12 @@
|
|
|
124
126
|
}
|
|
125
127
|
}
|
|
126
128
|
|
|
127
|
-
|
|
129
|
+
& > :first-child {
|
|
128
130
|
border-top-left-radius: 9px;
|
|
129
131
|
border-top-right-radius: 9px;
|
|
130
132
|
}
|
|
131
133
|
|
|
132
|
-
|
|
134
|
+
& > :last-child {
|
|
133
135
|
border-bottom-left-radius: 9px;
|
|
134
136
|
border-bottom-right-radius: 9px;
|
|
135
137
|
}
|
|
@@ -143,11 +145,11 @@
|
|
|
143
145
|
}
|
|
144
146
|
}
|
|
145
147
|
|
|
146
|
-
>[data-hidden="true"]
|
|
148
|
+
> [data-hidden="true"] ~ * {
|
|
147
149
|
border-top: none;
|
|
148
150
|
}
|
|
149
151
|
|
|
150
|
-
|
|
152
|
+
> * ~ [data-hidden="true"] {
|
|
151
153
|
border-top: none;
|
|
152
154
|
}
|
|
153
155
|
|
|
@@ -155,7 +157,6 @@
|
|
|
155
157
|
|
|
156
158
|
&.stale,
|
|
157
159
|
&.disabled.stale {
|
|
158
|
-
|
|
159
160
|
.output-area,
|
|
160
161
|
.cm-gutters,
|
|
161
162
|
.cm {
|
|
@@ -170,7 +171,6 @@
|
|
|
170
171
|
&.disabled.has-error,
|
|
171
172
|
&.disabled:hover,
|
|
172
173
|
&.disabled.has-error:hover {
|
|
173
|
-
|
|
174
174
|
.output-area,
|
|
175
175
|
.cm-gutters,
|
|
176
176
|
.cm {
|
|
@@ -275,7 +275,7 @@
|
|
|
275
275
|
&.borderless {
|
|
276
276
|
border-color: transparent;
|
|
277
277
|
|
|
278
|
-
|
|
278
|
+
& > * {
|
|
279
279
|
border-bottom: none;
|
|
280
280
|
}
|
|
281
281
|
|
|
@@ -325,7 +325,7 @@
|
|
|
325
325
|
&.borderless {
|
|
326
326
|
border-color: transparent;
|
|
327
327
|
|
|
328
|
-
|
|
328
|
+
& > * {
|
|
329
329
|
border-bottom: none;
|
|
330
330
|
}
|
|
331
331
|
|
|
@@ -420,7 +420,6 @@
|
|
|
420
420
|
|
|
421
421
|
/* Hide tray when dragging a cell, to prevent messing up the measurements. */
|
|
422
422
|
.marimo-cell.is-moving {
|
|
423
|
-
|
|
424
423
|
.tray::before,
|
|
425
424
|
.tray::after {
|
|
426
425
|
display: none;
|
|
@@ -513,7 +512,8 @@
|
|
|
513
512
|
|
|
514
513
|
.marimo-output-stale.marimo-output-loading,
|
|
515
514
|
.marimo-cell.stale .output-area.marimo-output-stale.marimo-output-loading,
|
|
516
|
-
.marimo-cell.stale
|
|
515
|
+
.marimo-cell.stale
|
|
516
|
+
.console-output-area.marimo-output-stale.marimo-output-loading {
|
|
517
517
|
opacity: 0.4;
|
|
518
518
|
filter: grayscale(50%);
|
|
519
519
|
transition: 300ms;
|
package/src/css/globals.css
CHANGED
|
@@ -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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
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
|
-
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
298
|
-
).toMatchInlineSnapshot(`
|
|
290
|
+
expect(jsonToMarkdown([{ link: "[Google](https://google.com)" }]))
|
|
291
|
+
.toMatchInlineSnapshot(`
|
|
299
292
|
"| link |
|
|
300
293
|
|---|
|
|
301
294
|
| [Google](https://google.com) |"
|
|
@@ -170,36 +170,39 @@ describe("PathBuilder", () => {
|
|
|
170
170
|
});
|
|
171
171
|
|
|
172
172
|
describe("edge case filenames", () => {
|
|
173
|
-
it.each(
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
)(
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
173
|
+
it.each(EDGE_CASE_FILENAMES)(
|
|
174
|
+
"should handle unicode and spaces in basename: %s",
|
|
175
|
+
(filename) => {
|
|
176
|
+
const basename = Paths.basename(filename);
|
|
177
|
+
expect(basename).toBe(filename);
|
|
178
|
+
expect(typeof basename).toBe("string");
|
|
179
|
+
expect(basename).not.toBe("");
|
|
180
|
+
},
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
it.each(EDGE_CASE_FILENAMES)(
|
|
184
|
+
"should handle unicode and spaces in dirname: %s",
|
|
185
|
+
(filename) => {
|
|
186
|
+
const fullPath = `/path/to/${filename}`;
|
|
187
|
+
const dirname = Paths.dirname(fullPath);
|
|
188
|
+
expect(dirname).toBe("/path/to");
|
|
189
|
+
},
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
it.each(EDGE_CASE_FILENAMES)(
|
|
193
|
+
"should handle unicode and spaces in path operations: %s",
|
|
194
|
+
(filename) => {
|
|
195
|
+
const baseName = Paths.basename(filename);
|
|
196
|
+
const extension = Paths.extension(filename);
|
|
197
|
+
|
|
198
|
+
// Should preserve unicode characters in basename
|
|
199
|
+
expect(baseName).toContain(filename.split(".")[0]);
|
|
200
|
+
|
|
201
|
+
// Should correctly extract extension
|
|
202
|
+
if (filename.includes(".")) {
|
|
203
|
+
expect(extension).toBe(filename.split(".").pop());
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
);
|
|
204
207
|
});
|
|
205
208
|
});
|