@marimo-team/islands 0.24.1-dev50 → 0.24.1-dev51
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/dist/{common-DuhsfU8I.js → common-DTAGJQCY.js} +1 -1
- package/dist/main.js +2 -2
- package/dist/{reveal-component-AVl0irhz.js → reveal-component-DJp2KMep.js} +1 -1
- package/package.json +1 -1
- package/src/components/editor/file-tree/__tests__/file-render-mode.test.ts +3 -0
- package/src/components/editor/file-tree/__tests__/file-viewer.test.tsx +21 -0
- package/src/components/editor/file-tree/__tests__/renderers.test.tsx +129 -0
- package/src/components/editor/file-tree/file-viewer.tsx +1 -0
- package/src/components/editor/file-tree/renderers.tsx +27 -1
- package/src/components/storage/__tests__/storage-file-viewer.test.tsx +84 -0
- package/src/components/storage/storage-file-viewer.tsx +1 -0
- package/src/components/editor/file-tree/__tests__/renderers.test.ts +0 -31
|
@@ -31787,7 +31787,7 @@ function ZG() {
|
|
|
31787
31787
|
return i.warn("Failed to get version from mount config"), null;
|
|
31788
31788
|
}
|
|
31789
31789
|
}
|
|
31790
|
-
var QG = ZG() || "0.24.1-
|
|
31790
|
+
var QG = ZG() || "0.24.1-dev51", $G = Ti(QG), eK = Ti(!0);
|
|
31791
31791
|
Ti(null);
|
|
31792
31792
|
//#endregion
|
|
31793
31793
|
//#region src/core/meta/code-visibility.ts
|
package/dist/main.js
CHANGED
|
@@ -6,7 +6,7 @@ import { n as x, r as S, t as C } from "./copy-DgAphl4k.js";
|
|
|
6
6
|
import { $t as w, At as T, B as E, C as D, Cn as O, Ct as k, Dn as A, Dt as j, En as M, Et as N, F as P, Ft as F, G as I, Gn as L, Gt as ee, H as te, Hn as R, Ht as ne, I as z, In as re, J as ie, Jn as ae, K as oe, Kn as se, Kt as ce, L as le, Ln as ue, Mn as de, Nn as fe, On as pe, Ot as me, P as he, Pn as ge, Pt as _e, Q as ve, Qn as ye, R as be, Rt as xe, Sn as Se, St as Ce, Tt as we, U as Te, Un as Ee, Ut as De, Vt as Oe, W as ke, Wn as Ae, X as je, Y as Me, Yn as Ne, Yt as Pe, Zn as Fe, _ as Ie, _n as Le, _t as Re, a as ze, an as Be, b as Ve, bn as He, bt as Ue, ct as We, en as Ge, g as Ke, gt as qe, hn as Je, i as Ye, in as Xe, it as Ze, j as Qe, jn as $e, jt as et, k as tt, mn as nt, n as rt, nn as it, nt as at, o as ot, ot as st, pn as ct, q as lt, rn as ut, rt as dt, st as ft, tn as pt, un as mt, v as ht, vt as gt, xn as _t, xt as vt, y as yt, yn as bt, yt as xt, z as St, zn as Ct } from "./html-to-image-hKcfSWj_.js";
|
|
7
7
|
import { o as wt, s as Tt, u as Et } from "./dist-DTSuOdDc.js";
|
|
8
8
|
import { D as Dt, E as Ot, O as kt, S as At, T as jt, _ as Mt, a as Nt, c as Pt, d as Ft, f as It, g as Lt, h as Rt, i as zt, l as Bt, m as Vt, n as Ht, o as Ut, p as Wt, r as Gt, s as Kt, t as qt, u as Jt, v as Yt, w as Xt, x as Zt, y as Qt } from "./spec-DqUrBQpS.js";
|
|
9
|
-
import { $ as $t, $t as en, A as tn, At as nn, B as rn, Bt as an, C as on, Ct as sn, D as cn, Dt as ln, E as un, Et as dn, F as fn, Ft as pn, G as mn, Gt as hn, H as gn, Ht as _n, I as vn, It as yn, Jt as bn, K as xn, Kt as Sn, L as Cn, Lt as wn, M as Tn, Mt as En, N as Dn, Nt as On, O as kn, Ot as An, P as jn, Pt as Mn, Q as Nn, Qt as Pn, R as Fn, Rt as In, S as Ln, St as Rn, T as zn, Tt as Bn, U as Vn, Ut as Hn, V as Un, Vt as Wn, W as Gn, Wt as Kn, Xt as qn, Yt as Jn, Z as Yn, Zt as Xn, _ as Zn, an as Qn, at as $n, b as er, bt as tr, c as nr, cn as rr, ct as ir, d as ar, dn as or, dt as sr, en as cr, et as lr, f as ur, fn as dr, ft as fr, g as pr, gt as mr, h as hr, ht as gr, i as _r, it as vr, j as yr, jt as br, k as xr, kt as Sr, ln as Cr, lt as wr, m as Tr, mt as Er, n as Dr, nn as Or, nt as kr, on as Ar, ot as jr, p as Mr, pn as Nr, pt as Pr, q as Fr, qt as Ir, r as Lr, rn as Rr, rt as zr, sn as Br, st as Vr, t as Hr, tn as Ur, tt as Wr, u as Gr, un as Kr, ut as qr, v as Jr, w as Yr, wt as Xr, x as Zr, xt as Qr, y as $r, yt as ei, z as ti, zt as ni } from "./common-
|
|
9
|
+
import { $ as $t, $t as en, A as tn, At as nn, B as rn, Bt as an, C as on, Ct as sn, D as cn, Dt as ln, E as un, Et as dn, F as fn, Ft as pn, G as mn, Gt as hn, H as gn, Ht as _n, I as vn, It as yn, Jt as bn, K as xn, Kt as Sn, L as Cn, Lt as wn, M as Tn, Mt as En, N as Dn, Nt as On, O as kn, Ot as An, P as jn, Pt as Mn, Q as Nn, Qt as Pn, R as Fn, Rt as In, S as Ln, St as Rn, T as zn, Tt as Bn, U as Vn, Ut as Hn, V as Un, Vt as Wn, W as Gn, Wt as Kn, Xt as qn, Yt as Jn, Z as Yn, Zt as Xn, _ as Zn, an as Qn, at as $n, b as er, bt as tr, c as nr, cn as rr, ct as ir, d as ar, dn as or, dt as sr, en as cr, et as lr, f as ur, fn as dr, ft as fr, g as pr, gt as mr, h as hr, ht as gr, i as _r, it as vr, j as yr, jt as br, k as xr, kt as Sr, ln as Cr, lt as wr, m as Tr, mt as Er, n as Dr, nn as Or, nt as kr, on as Ar, ot as jr, p as Mr, pn as Nr, pt as Pr, q as Fr, qt as Ir, r as Lr, rn as Rr, rt as zr, sn as Br, st as Vr, t as Hr, tn as Ur, tt as Wr, u as Gr, un as Kr, ut as qr, v as Jr, w as Yr, wt as Xr, x as Zr, xt as Qr, y as $r, yt as ei, z as ti, zt as ni } from "./common-DTAGJQCY.js";
|
|
10
10
|
import { c as ri, i as ii, n as ai, o as oi, t as si } from "./useLifecycle-D-zb2Ha5.js";
|
|
11
11
|
import { t as ci } from "./check-DIJJHY-E.js";
|
|
12
12
|
import { A as li, C as ui, D as di, E as fi, F as pi, I as mi, M as hi, N as gi, P as _i, R as vi, S as yi, a as bi, b as xi, c as Si, i as Ci, j as wi, l as Ti, n as Ei, o as Di, r as Oi, s as ki, t as Ai, w as ji, x as Mi } from "./select-Bakm9EUs.js";
|
|
@@ -35256,7 +35256,7 @@ function Gq(e) {
|
|
|
35256
35256
|
}
|
|
35257
35257
|
//#endregion
|
|
35258
35258
|
//#region src/components/editor/renderers/slides-layout/slides-layout.tsx
|
|
35259
|
-
var Kq = q.lazy(() => import("./reveal-component-
|
|
35259
|
+
var Kq = q.lazy(() => import("./reveal-component-DJp2KMep.js")), qq = (e) => {
|
|
35260
35260
|
let t = (0, Y.c)(33), { layout: n, setLayout: r, cells: i, mode: a } = e, o = Nm(k), s = a === "read" || o, c = Nm(lt) > 1, [l, u] = (0, q.useState)(null), d;
|
|
35261
35261
|
t[0] !== i || t[1] !== n ? (d = UU(i, n), t[0] = i, t[1] = n, t[2] = d) : d = t[2];
|
|
35262
35262
|
let { slideCells: f, skippedIds: p, noOutputIds: m, slideTypes: h, startCellIndex: g } = d, _;
|
|
@@ -4,7 +4,7 @@ import { t as o } from "./react-DMWcHin-.js";
|
|
|
4
4
|
import { t as s } from "./compiler-runtime-CAH9RLw2.js";
|
|
5
5
|
import { Ct as c, rt as l } from "./html-to-image-hKcfSWj_.js";
|
|
6
6
|
import { u } from "./dist-DTSuOdDc.js";
|
|
7
|
-
import { J as d, X as f, Y as p, in as m, l as h, n as g, rn as _, sn as v, t as y, u as b } from "./common-
|
|
7
|
+
import { J as d, X as f, Y as p, in as m, l as h, n as g, rn as _, sn as v, t as y, u as b } from "./common-DTAGJQCY.js";
|
|
8
8
|
import { R as x } from "./select-Bakm9EUs.js";
|
|
9
9
|
import { X as S } from "./dropdown-menu-DEu759Oe.js";
|
|
10
10
|
import { t as C } from "./jsx-runtime-CpoKNRgu.js";
|
package/package.json
CHANGED
|
@@ -8,7 +8,10 @@ describe("getFileRenderMode", () => {
|
|
|
8
8
|
["text/plain", false, "text"],
|
|
9
9
|
["text/x-rust", false, "text"],
|
|
10
10
|
["application/json", false, "text"],
|
|
11
|
+
["application/toml", false, "text"],
|
|
12
|
+
["application/x-toml", false, "text"],
|
|
11
13
|
["application/xml", false, "text"],
|
|
14
|
+
["text/x-toml", false, "text"],
|
|
12
15
|
["text/csv", false, "csv"],
|
|
13
16
|
["image/png", true, "media"],
|
|
14
17
|
["video/mp4", true, "media"],
|
|
@@ -15,13 +15,19 @@ vi.mock("@/plugins/impl/code/LazyAnyLanguageCodeMirror", () => ({
|
|
|
15
15
|
LazyAnyLanguageCodeMirror: ({
|
|
16
16
|
value,
|
|
17
17
|
onChange,
|
|
18
|
+
language,
|
|
19
|
+
readOnly,
|
|
18
20
|
}: {
|
|
19
21
|
value?: string;
|
|
20
22
|
onChange?: (value: string) => void;
|
|
23
|
+
language?: string;
|
|
24
|
+
readOnly?: boolean;
|
|
21
25
|
}) => (
|
|
22
26
|
<textarea
|
|
23
27
|
data-testid="code-editor"
|
|
28
|
+
data-language={language}
|
|
24
29
|
value={value ?? ""}
|
|
30
|
+
readOnly={readOnly}
|
|
25
31
|
onChange={(evt) => onChange?.(evt.target.value)}
|
|
26
32
|
/>
|
|
27
33
|
),
|
|
@@ -106,6 +112,21 @@ describe("FileViewer bounded previews", () => {
|
|
|
106
112
|
expect(screen.queryByText(/too large to preview/i)).not.toBeInTheDocument();
|
|
107
113
|
});
|
|
108
114
|
|
|
115
|
+
it("renders TOML files as editable TOML", async () => {
|
|
116
|
+
renderViewer({
|
|
117
|
+
file: { ...file, name: "pyproject.toml", size: 20 },
|
|
118
|
+
contents: '[project]\nname = "demo"',
|
|
119
|
+
mimeType: "application/toml",
|
|
120
|
+
isBase64: false,
|
|
121
|
+
isTooLarge: false,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const editor =
|
|
125
|
+
await screen.findByTestId<HTMLTextAreaElement>("code-editor");
|
|
126
|
+
expect(editor).toHaveAttribute("data-language", "toml");
|
|
127
|
+
expect(editor).not.toHaveAttribute("readonly");
|
|
128
|
+
});
|
|
129
|
+
|
|
109
130
|
it("preserves edits to an initially empty text file across remount", async () => {
|
|
110
131
|
const emptyFile: FileInfo = {
|
|
111
132
|
id: "/workspace/empty.txt",
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
import { fireEvent, render, screen } from "@testing-library/react";
|
|
3
|
+
import { describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { buildMediaSource, FileContentRenderer } from "../renderers";
|
|
5
|
+
|
|
6
|
+
vi.mock("@/plugins/impl/code/LazyAnyLanguageCodeMirror", () => ({
|
|
7
|
+
LazyAnyLanguageCodeMirror: ({
|
|
8
|
+
value,
|
|
9
|
+
language,
|
|
10
|
+
readOnly,
|
|
11
|
+
onChange,
|
|
12
|
+
}: {
|
|
13
|
+
value?: string;
|
|
14
|
+
language?: string;
|
|
15
|
+
readOnly?: boolean;
|
|
16
|
+
onChange?: (value: string) => void;
|
|
17
|
+
}) => (
|
|
18
|
+
<textarea
|
|
19
|
+
data-testid="code-editor"
|
|
20
|
+
data-language={language}
|
|
21
|
+
value={value}
|
|
22
|
+
readOnly={readOnly}
|
|
23
|
+
onChange={(event) => onChange?.(event.target.value)}
|
|
24
|
+
/>
|
|
25
|
+
),
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
vi.mock("@/theme/useTheme", () => ({
|
|
29
|
+
useTheme: () => ({ theme: "light" }),
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
describe("buildMediaSource", () => {
|
|
33
|
+
it("returns base64 source for binary media", () => {
|
|
34
|
+
expect(
|
|
35
|
+
buildMediaSource({
|
|
36
|
+
contents: "aGVsbG8=",
|
|
37
|
+
mimeType: "image/png",
|
|
38
|
+
isBase64: true,
|
|
39
|
+
}),
|
|
40
|
+
).toEqual({
|
|
41
|
+
base64: "aGVsbG8=",
|
|
42
|
+
mime: "image/png",
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("returns UTF-8 data URL for text-based media", () => {
|
|
47
|
+
const svg = '<svg xmlns="http://www.w3.org/2000/svg"></svg>';
|
|
48
|
+
expect(
|
|
49
|
+
buildMediaSource({
|
|
50
|
+
contents: svg,
|
|
51
|
+
mimeType: "image/svg+xml",
|
|
52
|
+
isBase64: false,
|
|
53
|
+
}),
|
|
54
|
+
).toEqual({
|
|
55
|
+
url: `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`,
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe("FileContentRenderer", () => {
|
|
61
|
+
it("renders editable TOML with syntax highlighting", () => {
|
|
62
|
+
const onChange = vi.fn();
|
|
63
|
+
render(
|
|
64
|
+
<FileContentRenderer
|
|
65
|
+
mimeType="application/toml"
|
|
66
|
+
contents={'name = "demo"'}
|
|
67
|
+
readOnly={false}
|
|
68
|
+
onChange={onChange}
|
|
69
|
+
/>,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const editor = screen.getByTestId<HTMLTextAreaElement>("code-editor");
|
|
73
|
+
expect(editor).toHaveAttribute("data-language", "toml");
|
|
74
|
+
expect(editor).not.toHaveAttribute("readonly");
|
|
75
|
+
|
|
76
|
+
fireEvent.change(editor, { target: { value: 'name = "updated"' } });
|
|
77
|
+
expect(onChange).toHaveBeenCalledWith('name = "updated"');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it.each(["application/toml", "application/x-toml", "text/x-toml"])(
|
|
81
|
+
"renders %s as read-only TOML by default",
|
|
82
|
+
(mimeType) => {
|
|
83
|
+
render(
|
|
84
|
+
<FileContentRenderer mimeType={mimeType} contents={'name = "demo"'} />,
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const editor = screen.getByTestId<HTMLTextAreaElement>("code-editor");
|
|
88
|
+
expect(editor).toHaveAttribute("data-language", "toml");
|
|
89
|
+
expect(editor).toHaveAttribute("readonly");
|
|
90
|
+
},
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
it.each([
|
|
94
|
+
"application/octet-stream",
|
|
95
|
+
"text/plain",
|
|
96
|
+
"text/plain; charset=utf-8",
|
|
97
|
+
])("uses the filename when a provider returns %s", (mimeType) => {
|
|
98
|
+
render(
|
|
99
|
+
<FileContentRenderer
|
|
100
|
+
mimeType={mimeType}
|
|
101
|
+
filename="pyproject.TOML"
|
|
102
|
+
contents={'name = "demo"'}
|
|
103
|
+
/>,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
expect(screen.getByTestId("code-editor")).toHaveAttribute(
|
|
107
|
+
"data-language",
|
|
108
|
+
"toml",
|
|
109
|
+
);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it.each(["file.constructor", "file.__proto__"])(
|
|
113
|
+
"ignores inherited extension-map property in %s",
|
|
114
|
+
(filename) => {
|
|
115
|
+
render(
|
|
116
|
+
<FileContentRenderer
|
|
117
|
+
mimeType="text/plain"
|
|
118
|
+
filename={filename}
|
|
119
|
+
contents="text"
|
|
120
|
+
/>,
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
expect(screen.getByTestId("code-editor")).toHaveAttribute(
|
|
124
|
+
"data-language",
|
|
125
|
+
"markdown",
|
|
126
|
+
);
|
|
127
|
+
},
|
|
128
|
+
);
|
|
129
|
+
});
|
|
@@ -16,6 +16,7 @@ import { useTheme } from "@/theme/useTheme";
|
|
|
16
16
|
import { Arrays } from "@/utils/arrays";
|
|
17
17
|
import { type Base64String, base64ToDataURL } from "@/utils/json/base64";
|
|
18
18
|
import { Objects } from "@/utils/objects";
|
|
19
|
+
import { Paths } from "@/utils/paths";
|
|
19
20
|
|
|
20
21
|
const PAGE_SIZE = 25;
|
|
21
22
|
|
|
@@ -38,13 +39,36 @@ export const MIME_TO_LANGUAGE: Record<string, string> = {
|
|
|
38
39
|
"text/css": "css",
|
|
39
40
|
"text/x-python": "python",
|
|
40
41
|
"application/json": "json",
|
|
42
|
+
"application/toml": "toml",
|
|
43
|
+
"application/x-toml": "toml",
|
|
41
44
|
"application/xml": "xml",
|
|
45
|
+
"text/x-toml": "toml",
|
|
42
46
|
"text/x-yaml": "yaml",
|
|
43
47
|
"text/csv": "markdown",
|
|
44
48
|
"text/plain": "markdown",
|
|
45
49
|
default: "markdown",
|
|
46
50
|
};
|
|
47
51
|
|
|
52
|
+
const EXTENSION_TO_LANGUAGE: Record<string, string> = {
|
|
53
|
+
toml: "toml",
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const GENERIC_MIME_TYPES = new Set(["application/octet-stream", "text/plain"]);
|
|
57
|
+
|
|
58
|
+
function resolveLanguage(mimeType: string, filename?: string): string {
|
|
59
|
+
const normalizedMimeType = mimeType.split(";", 1)[0].trim().toLowerCase();
|
|
60
|
+
if (GENERIC_MIME_TYPES.has(normalizedMimeType) && filename) {
|
|
61
|
+
const extension = Paths.extension(filename).toLowerCase();
|
|
62
|
+
const language = Object.hasOwn(EXTENSION_TO_LANGUAGE, extension)
|
|
63
|
+
? EXTENSION_TO_LANGUAGE[extension]
|
|
64
|
+
: undefined;
|
|
65
|
+
if (language) {
|
|
66
|
+
return language;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return MIME_TO_LANGUAGE[normalizedMimeType] || MIME_TO_LANGUAGE.default;
|
|
70
|
+
}
|
|
71
|
+
|
|
48
72
|
/**
|
|
49
73
|
* Media viewer props: provide either a direct `url` or `base64` + `mime`.
|
|
50
74
|
* When `url` is set it takes precedence.
|
|
@@ -167,6 +191,7 @@ export const MediaRenderer: React.FC<MediaSource & { mimeType: string }> = ({
|
|
|
167
191
|
*/
|
|
168
192
|
export const FileContentRenderer: React.FC<{
|
|
169
193
|
mimeType: string;
|
|
194
|
+
filename?: string;
|
|
170
195
|
/** Text content for text/CSV files. */
|
|
171
196
|
contents?: string;
|
|
172
197
|
/** Media source for image/audio/video/PDF files. */
|
|
@@ -177,6 +202,7 @@ export const FileContentRenderer: React.FC<{
|
|
|
177
202
|
extensions?: Extension[];
|
|
178
203
|
}> = ({
|
|
179
204
|
mimeType,
|
|
205
|
+
filename,
|
|
180
206
|
contents,
|
|
181
207
|
mediaSource,
|
|
182
208
|
readOnly = true,
|
|
@@ -202,7 +228,7 @@ export const FileContentRenderer: React.FC<{
|
|
|
202
228
|
}
|
|
203
229
|
|
|
204
230
|
if (contents != null) {
|
|
205
|
-
const language =
|
|
231
|
+
const language = resolveLanguage(mimeType, filename);
|
|
206
232
|
return (
|
|
207
233
|
<div className="flex-1 overflow-auto">
|
|
208
234
|
<Suspense>
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import { render, screen } from "@testing-library/react";
|
|
4
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
6
|
+
import { DownloadStorage } from "@/core/storage/request-registry";
|
|
7
|
+
import { StorageFileViewer } from "../storage-file-viewer";
|
|
8
|
+
|
|
9
|
+
vi.mock("@/components/editor/cell/useAddCell", () => ({
|
|
10
|
+
useAddCodeToNewCell: () => vi.fn(),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
vi.mock("@/plugins/impl/code/LazyAnyLanguageCodeMirror", () => ({
|
|
14
|
+
LazyAnyLanguageCodeMirror: ({
|
|
15
|
+
value,
|
|
16
|
+
language,
|
|
17
|
+
readOnly,
|
|
18
|
+
}: {
|
|
19
|
+
value?: string;
|
|
20
|
+
language?: string;
|
|
21
|
+
readOnly?: boolean;
|
|
22
|
+
}) => (
|
|
23
|
+
<textarea
|
|
24
|
+
data-testid="code-editor"
|
|
25
|
+
data-language={language}
|
|
26
|
+
value={value}
|
|
27
|
+
readOnly={readOnly}
|
|
28
|
+
/>
|
|
29
|
+
),
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
vi.mock("@/theme/useTheme", () => ({
|
|
33
|
+
useTheme: () => ({ theme: "light" }),
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
describe("StorageFileViewer", () => {
|
|
37
|
+
afterEach(() => {
|
|
38
|
+
vi.restoreAllMocks();
|
|
39
|
+
vi.unstubAllGlobals();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("renders TOML from generic storage metadata as read-only TOML", async () => {
|
|
43
|
+
const request = vi.spyOn(DownloadStorage, "request").mockResolvedValue({
|
|
44
|
+
request_id: "request-id",
|
|
45
|
+
url: "https://example.com/pyproject.toml?signature=secret",
|
|
46
|
+
});
|
|
47
|
+
vi.stubGlobal(
|
|
48
|
+
"fetch",
|
|
49
|
+
vi.fn().mockResolvedValue({
|
|
50
|
+
ok: true,
|
|
51
|
+
text: async () => '[project]\nname = "demo"',
|
|
52
|
+
}),
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
render(
|
|
56
|
+
<TooltipProvider>
|
|
57
|
+
<StorageFileViewer
|
|
58
|
+
entry={{
|
|
59
|
+
path: "config/pyproject.toml",
|
|
60
|
+
kind: "file",
|
|
61
|
+
size: 24,
|
|
62
|
+
lastModified: null,
|
|
63
|
+
mimeType: null,
|
|
64
|
+
}}
|
|
65
|
+
namespace="files"
|
|
66
|
+
protocol="s3"
|
|
67
|
+
backendType="fsspec"
|
|
68
|
+
onBack={vi.fn()}
|
|
69
|
+
/>
|
|
70
|
+
</TooltipProvider>,
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const editor =
|
|
74
|
+
await screen.findByTestId<HTMLTextAreaElement>("code-editor");
|
|
75
|
+
expect(editor).toHaveAttribute("data-language", "toml");
|
|
76
|
+
expect(editor).toHaveAttribute("readonly");
|
|
77
|
+
expect(editor).toHaveValue('[project]\nname = "demo"');
|
|
78
|
+
expect(request).toHaveBeenCalledWith({
|
|
79
|
+
namespace: "files",
|
|
80
|
+
path: "config/pyproject.toml",
|
|
81
|
+
preview: true,
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -237,6 +237,7 @@ export const StorageFileViewer: React.FC<Props> = ({
|
|
|
237
237
|
{renderMetadata({})}
|
|
238
238
|
<FileContentRenderer
|
|
239
239
|
mimeType={mime}
|
|
240
|
+
filename={name}
|
|
240
241
|
contents={preview.type === "text" ? preview.content : undefined}
|
|
241
242
|
mediaSource={
|
|
242
243
|
preview.type === "media" ? { url: preview.url } : undefined
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
-
import { describe, expect, it } from "vitest";
|
|
3
|
-
import { buildMediaSource } from "../renderers";
|
|
4
|
-
|
|
5
|
-
describe("buildMediaSource", () => {
|
|
6
|
-
it("returns base64 source for binary media", () => {
|
|
7
|
-
expect(
|
|
8
|
-
buildMediaSource({
|
|
9
|
-
contents: "aGVsbG8=",
|
|
10
|
-
mimeType: "image/png",
|
|
11
|
-
isBase64: true,
|
|
12
|
-
}),
|
|
13
|
-
).toEqual({
|
|
14
|
-
base64: "aGVsbG8=",
|
|
15
|
-
mime: "image/png",
|
|
16
|
-
});
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it("returns UTF-8 data URL for text-based media", () => {
|
|
20
|
-
const svg = '<svg xmlns="http://www.w3.org/2000/svg"></svg>';
|
|
21
|
-
expect(
|
|
22
|
-
buildMediaSource({
|
|
23
|
-
contents: svg,
|
|
24
|
-
mimeType: "image/svg+xml",
|
|
25
|
-
isBase64: false,
|
|
26
|
-
}),
|
|
27
|
-
).toEqual({
|
|
28
|
-
url: `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`,
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
});
|