@marimo-team/islands 0.24.3-dev21 → 0.24.3-dev23
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/{chat-ui-DliLTEJt.js → chat-ui-Cn1TFk66.js} +2 -2
- package/dist/{common-BxjexIJy.js → common-BWiXcWeK.js} +2 -2
- package/dist/{html-to-image-CvfXy1tG.js → html-to-image-CSU-_5BI.js} +5 -2
- package/dist/main.js +5 -5
- package/dist/{process-output-BTPsjdfd.js → process-output-B6HE2ojm.js} +1 -1
- package/dist/{reveal-component-COGORv1a.js → reveal-component-B9C4Lv2K.js} +2 -2
- package/package.json +1 -1
- package/src/components/chat/acp/__tests__/agent-panel.test.tsx +270 -0
- package/src/components/chat/acp/agent-panel.tsx +45 -44
- package/src/plugins/core/RenderHTML.tsx +24 -12
- package/src/plugins/core/__test__/RenderHTML.test.ts +135 -12
|
@@ -3,12 +3,12 @@ import { c as n, g as r, h as i, t as a } from "./button-DdXAm8iz.js";
|
|
|
3
3
|
import { t as o } from "./react-ofJbxM4x.js";
|
|
4
4
|
import { t as s } from "./compiler-runtime-Cj8ZWiNS.js";
|
|
5
5
|
import { r as c } from "./copy-C0N4m1lm.js";
|
|
6
|
-
import { Cn as l, F as u, Ft as d, Gt as f, Hn as p, In as m, Pn as h, Qn as g, Un as _, Vt as v, Wt as y, Xt as b, Yn as x, Zn as S, _ as C, a as w, c as T, d as E, er as D, f as O, g as ee, gn as k, gt as te, h as ne, hn as A, ht as re, in as ie, l as ae, m as j, mn as oe, n as se, nn as ce, on as le, p as ue, pn as de, pt as fe, q as pe, r as me, rn as he, rr as ge, rt as _e, t as ve, v as ye, vn as be, x as xe, y as Se, yt as Ce, zt as we } from "./html-to-image-
|
|
6
|
+
import { Cn as l, F as u, Ft as d, Gt as f, Hn as p, In as m, Pn as h, Qn as g, Un as _, Vt as v, Wt as y, Xt as b, Yn as x, Zn as S, _ as C, a as w, c as T, d as E, er as D, f as O, g as ee, gn as k, gt as te, h as ne, hn as A, ht as re, in as ie, l as ae, m as j, mn as oe, n as se, nn as ce, on as le, p as ue, pn as de, pt as fe, q as pe, r as me, rn as he, rr as ge, rt as _e, t as ve, v as ye, vn as be, x as xe, y as Se, yt as Ce, zt as we } from "./html-to-image-CSU-_5BI.js";
|
|
7
7
|
import { u as M } from "./dist-6ChWbo8z.js";
|
|
8
8
|
import { C as Te, I as Ee, R as De, b as Oe } from "./select-UMOSCa3W.js";
|
|
9
9
|
import { _ as ke, f as Ae, h as je, i as Me, n as Ne, t as Pe } from "./dropdown-menu-C73aTomr.js";
|
|
10
10
|
import { p as Fe, v as Ie } from "./toDate-C3JOXlJT.js";
|
|
11
|
-
import { d as Le, f as Re, i as ze, n as Be, t as Ve, u as He } from "./process-output-
|
|
11
|
+
import { d as Le, f as Re, i as ze, n as Be, t as Ve, u as He } from "./process-output-B6HE2ojm.js";
|
|
12
12
|
import { t as Ue } from "./jsx-runtime-DgDPJV9S.js";
|
|
13
13
|
import { A as We, B as Ge, C as Ke, D as qe, E as Je, I as Ye, L as Xe, N as Ze, P as Qe, R as $e, S as et, T as tt, _ as nt, b as rt, j as it, k as at, v as ot, w as st, x as ct, y as lt } from "./schemas-BDZr-nP0.js";
|
|
14
14
|
import { n as ut } from "./error-banner-hCaH06e3.js";
|
|
@@ -3,7 +3,7 @@ import { _ as n, c as r, f as i, g as a, h as o, m as s, n as c, p as l, r as u,
|
|
|
3
3
|
import { t as f } from "./react-ofJbxM4x.js";
|
|
4
4
|
import { t as p } from "./compiler-runtime-Cj8ZWiNS.js";
|
|
5
5
|
import { n as m, r as h, t as g } from "./copy-C0N4m1lm.js";
|
|
6
|
-
import { $ as _, $n as v, $t as y, A as b, An as x, At as S, Cn as C, Ct as w, D as T, E, Et as D, F as O, Gn as k, H as A, Hn as j, Ht as M, Jn as N, Kt as P, Ln as F, M as I, Mn as L, N as R, Nt as ee, Pn as z, Q as te, Qn as ne, Qt as re, Rt as ie, S as ae, T as oe, Tt as se, Un as ce, Ut as le, V as ue, Wt as de, Xt as fe, Yn as pe, Z as me, Zn as he, _ as ge, _t as _e, ar as ve, cn as ye, en as be, er as xe, g as Se, gt as Ce, ht as we, it as Te, jn as Ee, jt as De, kt as Oe, lt as ke, mt as Ae, n as je, on as Me, or as Ne, ot as Pe, pt as Fe, q as Ie, qn as Le, r as Re, rr as ze, s as Be, sr as Ve, t as He, tt as Ue, u as We, v as Ge, vt as Ke, w as qe, wn as Je, y as Ye, yn as Xe, yt as Ze } from "./html-to-image-
|
|
6
|
+
import { $ as _, $n as v, $t as y, A as b, An as x, At as S, Cn as C, Ct as w, D as T, E, Et as D, F as O, Gn as k, H as A, Hn as j, Ht as M, Jn as N, Kt as P, Ln as F, M as I, Mn as L, N as R, Nt as ee, Pn as z, Q as te, Qn as ne, Qt as re, Rt as ie, S as ae, T as oe, Tt as se, Un as ce, Ut as le, V as ue, Wt as de, Xt as fe, Yn as pe, Z as me, Zn as he, _ as ge, _t as _e, ar as ve, cn as ye, en as be, er as xe, g as Se, gt as Ce, ht as we, it as Te, jn as Ee, jt as De, kt as Oe, lt as ke, mt as Ae, n as je, on as Me, or as Ne, ot as Pe, pt as Fe, q as Ie, qn as Le, r as Re, rr as ze, s as Be, sr as Ve, t as He, tt as Ue, u as We, v as Ge, vt as Ke, w as qe, wn as Je, y as Ye, yn as Xe, yt as Ze } from "./html-to-image-CSU-_5BI.js";
|
|
7
7
|
import { o as Qe, u as B } from "./dist-6ChWbo8z.js";
|
|
8
8
|
import { c as $e, i as et, r as tt } from "./useLifecycle-BGRBkTFT.js";
|
|
9
9
|
import { a as nt, i as rt, n as it, o as at, r as ot, t as st } from "./useDateFormatter-BKpQHt-W.js";
|
|
@@ -32442,7 +32442,7 @@ function Iq() {
|
|
|
32442
32442
|
return a.warn("Failed to get version from mount config"), null;
|
|
32443
32443
|
}
|
|
32444
32444
|
}
|
|
32445
|
-
var Lq = Iq() || "0.24.3-
|
|
32445
|
+
var Lq = Iq() || "0.24.3-dev23", Rq = V(Lq), zq = V(!0);
|
|
32446
32446
|
V(null);
|
|
32447
32447
|
//#endregion
|
|
32448
32448
|
//#region src/core/wasm/state.ts
|
|
@@ -30169,9 +30169,12 @@ var NT = [], PT = (e) => {
|
|
|
30169
30169
|
}, WT = (e, t) => {
|
|
30170
30170
|
if (t instanceof er.Element && t.attribs?.["data-tooltip"]) {
|
|
30171
30171
|
if ((t.name?.toLowerCase() ?? "").startsWith("marimo-")) return;
|
|
30172
|
-
let n = t.attribs["data-tooltip"]
|
|
30172
|
+
let n = t.attribs["data-tooltip"].replace(/^[\r\n]+|[\r\n]+$/g, "");
|
|
30173
30173
|
return /* @__PURE__ */ (0, z.jsx)(it, {
|
|
30174
|
-
content:
|
|
30174
|
+
content: n.includes("\n") ? /* @__PURE__ */ (0, z.jsx)("span", {
|
|
30175
|
+
className: "whitespace-pre-wrap",
|
|
30176
|
+
children: n.split(/\r?\n/).map((e, t) => /* @__PURE__ */ (0, z.jsxs)(L.Fragment, { children: [t > 0 && /* @__PURE__ */ (0, z.jsx)("br", {}), e] }, t))
|
|
30177
|
+
}) : n,
|
|
30175
30178
|
children: e
|
|
30176
30179
|
});
|
|
30177
30180
|
}
|
package/dist/main.js
CHANGED
|
@@ -3,16 +3,16 @@ import { _ as a, c as o, f as s, g as c, h as l, i as u, l as d, m as f, n as p,
|
|
|
3
3
|
import { t as y } from "./react-ofJbxM4x.js";
|
|
4
4
|
import { t as b } from "./compiler-runtime-Cj8ZWiNS.js";
|
|
5
5
|
import { n as x, r as S, t as C } from "./copy-C0N4m1lm.js";
|
|
6
|
-
import { $n as w, An as T, B as E, Bn as D, Bt as O, C as k, Ct as A, Dn as j, Dt as M, En as N, F as P, Fn as F, G as I, I as L, It as R, J as ee, Jt as z, K as te, Kn as B, L as ne, Lt as re, Mt as ie, Nn as ae, Nt as oe, On as se, Ot as ce, P as le, Pn as ue, Pt as de, Q as fe, Qn as pe, Qt as me, R as he, Rn as ge, Sn as _e, St as ve, U as ye, Un as be, Vn as xe, Vt as Se, W as Ce, Wn as we, Wt as Te, X as Ee, Xn as De, Y as Oe, Yn as ke, Yt as Ae, Z as je, Zn as Me, Zt as Ne, _ as Pe, _n as Fe, a as Ie, an as Le, ar as Re, at as ze, b as Be, bn as Ve, bt as He, cn as Ue, ct as We, dn as Ge, dt as Ke, et as qe, fn as Je, ft as Ye, g as Xe, i as Ze, ir as Qe, j as $e, jt as et, k as tt, kn as nt, kt as rt, ln as it, n as at, nr as ot, o as st, on as ct, ot as lt, q as ut, qt as dt, sn as ft, st as pt, tn as mt, tr as ht, un as gt, ut as _t, v as vt, wn as yt, wt as bt, xn as xt, xt as St, y as Ct, z as wt, zn as Tt } from "./html-to-image-
|
|
6
|
+
import { $n as w, An as T, B as E, Bn as D, Bt as O, C as k, Ct as A, Dn as j, Dt as M, En as N, F as P, Fn as F, G as I, I as L, It as R, J as ee, Jt as z, K as te, Kn as B, L as ne, Lt as re, Mt as ie, Nn as ae, Nt as oe, On as se, Ot as ce, P as le, Pn as ue, Pt as de, Q as fe, Qn as pe, Qt as me, R as he, Rn as ge, Sn as _e, St as ve, U as ye, Un as be, Vn as xe, Vt as Se, W as Ce, Wn as we, Wt as Te, X as Ee, Xn as De, Y as Oe, Yn as ke, Yt as Ae, Z as je, Zn as Me, Zt as Ne, _ as Pe, _n as Fe, a as Ie, an as Le, ar as Re, at as ze, b as Be, bn as Ve, bt as He, cn as Ue, ct as We, dn as Ge, dt as Ke, et as qe, fn as Je, ft as Ye, g as Xe, i as Ze, ir as Qe, j as $e, jt as et, k as tt, kn as nt, kt as rt, ln as it, n as at, nr as ot, o as st, on as ct, ot as lt, q as ut, qt as dt, sn as ft, st as pt, tn as mt, tr as ht, un as gt, ut as _t, v as vt, wn as yt, wt as bt, xn as xt, xt as St, y as Ct, z as wt, zn as Tt } from "./html-to-image-CSU-_5BI.js";
|
|
7
7
|
import { o as Et, s as Dt, u as Ot } from "./dist-6ChWbo8z.js";
|
|
8
8
|
import { D as kt, E as At, O as jt, S as Mt, T as Nt, _ as Pt, a as Ft, c as It, d as Lt, f as Rt, g as zt, h as Bt, i as Vt, l as Ht, m as Ut, n as Wt, o as Gt, p as Kt, r as qt, s as Jt, t as Yt, u as Xt, v as Zt, w as Qt, x as $t, y as en } from "./spec-g740fOWu.js";
|
|
9
|
-
import { $ as tn, $t as nn, A as rn, At as an, B as on, Bt as sn, C as cn, Cn as ln, Ct as un, D as dn, Dn as fn, Dt as pn, E as mn, En as hn, Et as gn, F as _n, Ft as vn, G as yn, Gt as bn, H as xn, Ht as Sn, I as Cn, It as wn, J as Tn, Jt as En, K as Dn, Kt as On, L as kn, Lt as An, M as jn, N as Mn, Nt as Nn, O as Pn, Ot as Fn, P as In, Pt as Ln, Q as Rn, Qt as zn, R as Bn, Rt as Vn, S as Hn, Sn as Un, St as Wn, T as Gn, Tn as Kn, Tt as qn, U as Jn, Ut as Yn, V as Xn, Vt as Zn, W as Qn, Wt as $n, X as er, Xt as tr, Y as nr, Yt as rr, Z as ir, Zt as ar, _ as or, _n as sr, _t as cr, a as lr, an as ur, at as dr, b as fr, bn as pr, bt as mr, cn as hr, dn as gr, dt as _r, en as vr, et as yr, fn as br, ft as xr, g as Sr, gn as Cr, gt as wr, hn as Tr, ht as Er, i as Dr, in as Or, it as kr, j as Ar, k as jr, kt as Mr, l as Nr, ln as Pr, m as Fr, mn as Ir, mt as Lr, n as Rr, nn as zr, nt as Br, on as Vr, ot as Hr, pn as Ur, pt as Wr, q as Gr, qt as Kr, rn as qr, rt as Jr, s as Yr, sn as Xr, st as Zr, t as Qr, tn as $r, tt as ei, un as ti, v as ni, vt as ri, w as ii, wn as ai, wt as oi, x as si, xn as ci, xt as li, y as ui, yn as di, yt as fi, z as pi, zt as mi } from "./common-
|
|
9
|
+
import { $ as tn, $t as nn, A as rn, At as an, B as on, Bt as sn, C as cn, Cn as ln, Ct as un, D as dn, Dn as fn, Dt as pn, E as mn, En as hn, Et as gn, F as _n, Ft as vn, G as yn, Gt as bn, H as xn, Ht as Sn, I as Cn, It as wn, J as Tn, Jt as En, K as Dn, Kt as On, L as kn, Lt as An, M as jn, N as Mn, Nt as Nn, O as Pn, Ot as Fn, P as In, Pt as Ln, Q as Rn, Qt as zn, R as Bn, Rt as Vn, S as Hn, Sn as Un, St as Wn, T as Gn, Tn as Kn, Tt as qn, U as Jn, Ut as Yn, V as Xn, Vt as Zn, W as Qn, Wt as $n, X as er, Xt as tr, Y as nr, Yt as rr, Z as ir, Zt as ar, _ as or, _n as sr, _t as cr, a as lr, an as ur, at as dr, b as fr, bn as pr, bt as mr, cn as hr, dn as gr, dt as _r, en as vr, et as yr, fn as br, ft as xr, g as Sr, gn as Cr, gt as wr, hn as Tr, ht as Er, i as Dr, in as Or, it as kr, j as Ar, k as jr, kt as Mr, l as Nr, ln as Pr, m as Fr, mn as Ir, mt as Lr, n as Rr, nn as zr, nt as Br, on as Vr, ot as Hr, pn as Ur, pt as Wr, q as Gr, qt as Kr, rn as qr, rt as Jr, s as Yr, sn as Xr, st as Zr, t as Qr, tn as $r, tt as ei, un as ti, v as ni, vt as ri, w as ii, wn as ai, wt as oi, x as si, xn as ci, xt as li, y as ui, yn as di, yt as fi, z as pi, zt as mi } from "./common-BWiXcWeK.js";
|
|
10
10
|
import { c as hi, i as gi, n as _i, o as vi, t as yi } from "./useLifecycle-BGRBkTFT.js";
|
|
11
11
|
import { t as bi } from "./check-mcQyZmFG.js";
|
|
12
12
|
import { A as xi, C as Si, D as Ci, E as wi, F as Ti, I as Ei, M as Di, N as Oi, P as ki, R as Ai, S as ji, a as Mi, b as Ni, c as Pi, i as Fi, j as Ii, l as Li, n as Ri, o as zi, r as Bi, s as Vi, t as Hi, w as Ui, x as Wi } from "./select-UMOSCa3W.js";
|
|
13
13
|
import { B as Gi, C as Ki, Ht as qi, J as Ji, Nt as Yi, Pt as Xi, S as Zi, W as Qi, Y as $i, _ as ea, a as ta, at as na, c as ra, f as ia, ft as aa, g as oa, h as sa, i as ca, m as la, mn as ua, n as da, ot as fa, p as pa, pn as ma, r as ha, s as ga, t as _a, v as va, x as ya, yt as ba, z as xa } from "./dropdown-menu-C73aTomr.js";
|
|
14
14
|
import { _ as Sa, c as Ca, d as wa, f as Ta, g as Ea, h as Da, m as Oa, u as ka, v as Aa } from "./toDate-C3JOXlJT.js";
|
|
15
|
-
import { a as ja, c as Ma, d as Na, f as Pa, i as Fa, l as Ia, n as La, o as Ra, r as za, s as Ba, t as Va } from "./process-output-
|
|
15
|
+
import { a as ja, c as Ma, d as Na, f as Pa, i as Fa, l as Ia, n as La, o as Ra, r as za, s as Ba, t as Va } from "./process-output-B6HE2ojm.js";
|
|
16
16
|
import { a as Ha, c as Ua, d as Wa, i as Ga, l as Ka, r as qa, u as Ja } from "./glide-utils-BMnFm0vZ.js";
|
|
17
17
|
import { n as Ya, t as Xa } from "./ErrorBoundary-B7uSdDUm.js";
|
|
18
18
|
import { t as Za } from "./react-dom-BUgJ-ab_.js";
|
|
@@ -10009,7 +10009,7 @@ var FS = (e) => {
|
|
|
10009
10009
|
output: t
|
|
10010
10010
|
};
|
|
10011
10011
|
} };
|
|
10012
|
-
} }, VS = q.lazy(() => import("./chat-ui-
|
|
10012
|
+
} }, VS = q.lazy(() => import("./chat-ui-Cn1TFk66.js").then((e) => ({ default: e.Chatbot }))), HS = G(W({
|
|
10013
10013
|
id: H(),
|
|
10014
10014
|
role: _o([
|
|
10015
10015
|
"system",
|
|
@@ -29942,7 +29942,7 @@ function xR(e) {
|
|
|
29942
29942
|
}
|
|
29943
29943
|
//#endregion
|
|
29944
29944
|
//#region src/components/editor/renderers/slides-layout/slides-layout.tsx
|
|
29945
|
-
var SR = q.lazy(() => import("./reveal-component-
|
|
29945
|
+
var SR = q.lazy(() => import("./reveal-component-B9C4Lv2K.js")), CR = (e) => {
|
|
29946
29946
|
let t = (0, Y.c)(33), { layout: n, setLayout: r, cells: i, mode: a } = e, o = fs(rt), s = a === "read" || o, c = fs(Oe) > 1, [l, u] = (0, q.useState)(null), d;
|
|
29947
29947
|
t[0] !== i || t[1] !== n ? (d = yP(i, n), t[0] = i, t[1] = n, t[2] = d) : d = t[2];
|
|
29948
29948
|
let { slideCells: f, skippedIds: p, noOutputIds: m, slideTypes: h, startCellIndex: g } = d, _;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { O as e, nt as t, rt as n } from "./html-to-image-
|
|
1
|
+
import { O as e, nt as t, rt as n } from "./html-to-image-CSU-_5BI.js";
|
|
2
2
|
import { u as r } from "./dist-6ChWbo8z.js";
|
|
3
3
|
import { b as i } from "./select-UMOSCa3W.js";
|
|
4
4
|
import { n as a } from "./semaphore-89jsoHtS.js";
|
|
@@ -2,9 +2,9 @@ import { a as e } from "./rolldown-runtime-Cb76FRpK.js";
|
|
|
2
2
|
import { c as t, g as n, h as r, m as i, t as a } from "./button-DdXAm8iz.js";
|
|
3
3
|
import { t as o } from "./react-ofJbxM4x.js";
|
|
4
4
|
import { t as s } from "./compiler-runtime-Cj8ZWiNS.js";
|
|
5
|
-
import { kt as c, st as l } from "./html-to-image-
|
|
5
|
+
import { kt as c, st as l } from "./html-to-image-CSU-_5BI.js";
|
|
6
6
|
import { u } from "./dist-6ChWbo8z.js";
|
|
7
|
-
import { _n as d, c as f, ct as p, g as m, h, l as g, lt as _, n as v, o as y, r as b, t as x, u as S, ut as C, vn as w, xn as T } from "./common-
|
|
7
|
+
import { _n as d, c as f, ct as p, g as m, h, l as g, lt as _, n as v, o as y, r as b, t as x, u as S, ut as C, vn as w, xn as T } from "./common-BWiXcWeK.js";
|
|
8
8
|
import { R as E } from "./select-UMOSCa3W.js";
|
|
9
9
|
import { Y as D } from "./dropdown-menu-C73aTomr.js";
|
|
10
10
|
import { t as O } from "./jsx-runtime-DgDPJV9S.js";
|
package/package.json
CHANGED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
act,
|
|
5
|
+
fireEvent,
|
|
6
|
+
render,
|
|
7
|
+
screen,
|
|
8
|
+
waitFor,
|
|
9
|
+
} from "@testing-library/react";
|
|
10
|
+
import { Provider } from "jotai";
|
|
11
|
+
import type { ComponentProps } from "react";
|
|
12
|
+
import { JsonRpcError } from "use-acp";
|
|
13
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
14
|
+
import { MockRequestClient } from "@/__mocks__/requests";
|
|
15
|
+
import type { PromptInput } from "@/components/editor/ai/add-cell-with-ai";
|
|
16
|
+
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
17
|
+
import { toast } from "@/components/ui/use-toast";
|
|
18
|
+
import { pendingAiPromptAtom } from "@/core/ai/state";
|
|
19
|
+
import { requestClientAtom } from "@/core/network/requests";
|
|
20
|
+
import { cwdAtom, filenameAtom } from "@/core/saving/file-state";
|
|
21
|
+
import { store } from "@/core/state/jotai";
|
|
22
|
+
import AgentPanel from "../agent-panel";
|
|
23
|
+
import { parseContextFromPrompt } from "../context-utils";
|
|
24
|
+
import {
|
|
25
|
+
addSession,
|
|
26
|
+
agentSessionStateAtom,
|
|
27
|
+
selectedTabAtom,
|
|
28
|
+
updateSessionExternalAgentSessionId,
|
|
29
|
+
} from "../state";
|
|
30
|
+
import type { ExternalAgentSessionId } from "../types";
|
|
31
|
+
|
|
32
|
+
const { agent, client } = vi.hoisted(() => {
|
|
33
|
+
const agent = {
|
|
34
|
+
initialize: vi.fn().mockResolvedValue({}),
|
|
35
|
+
prompt: vi.fn().mockResolvedValue({ stopReason: "end_turn" }),
|
|
36
|
+
cancel: vi.fn().mockResolvedValue({}),
|
|
37
|
+
};
|
|
38
|
+
return {
|
|
39
|
+
agent,
|
|
40
|
+
client: {
|
|
41
|
+
agent,
|
|
42
|
+
activeSessionId: "session-1",
|
|
43
|
+
connectionState: { status: "connected" },
|
|
44
|
+
notifications: [],
|
|
45
|
+
connect: vi.fn().mockResolvedValue(undefined),
|
|
46
|
+
disconnect: vi.fn(),
|
|
47
|
+
setActiveSessionId: vi.fn(),
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
vi.mock("use-acp", async (importOriginal) => ({
|
|
53
|
+
...(await importOriginal<typeof import("use-acp")>()),
|
|
54
|
+
useAcpClient: () => client,
|
|
55
|
+
}));
|
|
56
|
+
|
|
57
|
+
vi.mock("@/components/editor/ai/add-cell-with-ai", () => ({
|
|
58
|
+
PromptInput: ({
|
|
59
|
+
value,
|
|
60
|
+
onChange,
|
|
61
|
+
onAddFiles,
|
|
62
|
+
onSubmit,
|
|
63
|
+
}: ComponentProps<typeof PromptInput>) => (
|
|
64
|
+
<>
|
|
65
|
+
<textarea
|
|
66
|
+
aria-label="Prompt"
|
|
67
|
+
value={value}
|
|
68
|
+
onChange={(event) => onChange(event.target.value)}
|
|
69
|
+
onKeyDown={(event) => {
|
|
70
|
+
if (event.key === "Enter") {
|
|
71
|
+
onSubmit(undefined, value);
|
|
72
|
+
}
|
|
73
|
+
}}
|
|
74
|
+
/>
|
|
75
|
+
<input
|
|
76
|
+
type="file"
|
|
77
|
+
aria-label="Attachment"
|
|
78
|
+
onChange={(event) => onAddFiles?.([...(event.target.files ?? [])])}
|
|
79
|
+
/>
|
|
80
|
+
</>
|
|
81
|
+
),
|
|
82
|
+
}));
|
|
83
|
+
vi.mock("../session-tabs", () => ({ SessionTabs: () => null }));
|
|
84
|
+
vi.mock("../blocks", () => ({ ReadyToChatBlock: () => null }));
|
|
85
|
+
vi.mock("../context-utils", async (importOriginal) => ({
|
|
86
|
+
...(await importOriginal<typeof import("../context-utils")>()),
|
|
87
|
+
parseContextFromPrompt: vi
|
|
88
|
+
.fn()
|
|
89
|
+
.mockResolvedValue({ contextBlocks: [], attachmentBlocks: [] }),
|
|
90
|
+
}));
|
|
91
|
+
vi.mock("@/components/ui/use-toast", () => ({ toast: vi.fn() }));
|
|
92
|
+
|
|
93
|
+
function renderPanel() {
|
|
94
|
+
return render(
|
|
95
|
+
<Provider store={store}>
|
|
96
|
+
<TooltipProvider>
|
|
97
|
+
<AgentPanel />
|
|
98
|
+
</TooltipProvider>
|
|
99
|
+
</Provider>,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function submitPrompt(prompt = "Explain this notebook") {
|
|
104
|
+
fireEvent.change(screen.getByRole("textbox", { name: "Prompt" }), {
|
|
105
|
+
target: { value: prompt },
|
|
106
|
+
});
|
|
107
|
+
fireEvent.keyDown(screen.getByRole("textbox", { name: "Prompt" }), {
|
|
108
|
+
key: "Enter",
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
describe("AgentPanel prompt submission", () => {
|
|
113
|
+
beforeEach(() => {
|
|
114
|
+
agent.prompt.mockReset().mockResolvedValue({ stopReason: "end_turn" });
|
|
115
|
+
vi.mocked(parseContextFromPrompt).mockReset().mockResolvedValue({
|
|
116
|
+
contextBlocks: [],
|
|
117
|
+
attachmentBlocks: [],
|
|
118
|
+
});
|
|
119
|
+
store.set(requestClientAtom, MockRequestClient.create());
|
|
120
|
+
store.set(filenameAtom, "notebook.py");
|
|
121
|
+
store.set(cwdAtom, "/notebooks");
|
|
122
|
+
store.set(pendingAiPromptAtom, null);
|
|
123
|
+
store.set(
|
|
124
|
+
agentSessionStateAtom,
|
|
125
|
+
updateSessionExternalAgentSessionId(
|
|
126
|
+
addSession({ sessions: [], activeTabId: null }, { agentId: "claude" }),
|
|
127
|
+
"session-1" as ExternalAgentSessionId,
|
|
128
|
+
),
|
|
129
|
+
);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
afterEach(() => {
|
|
133
|
+
store.set(pendingAiPromptAtom, null);
|
|
134
|
+
store.set(agentSessionStateAtom, { sessions: [], activeTabId: null });
|
|
135
|
+
store.set(filenameAtom, null);
|
|
136
|
+
store.set(cwdAtom, null);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("preserves the draft, attachment and title when the notebook is unnamed", async () => {
|
|
140
|
+
store.set(filenameAtom, null);
|
|
141
|
+
renderPanel();
|
|
142
|
+
const title = store.get(selectedTabAtom)?.title;
|
|
143
|
+
fireEvent.change(screen.getByLabelText("Attachment"), {
|
|
144
|
+
target: {
|
|
145
|
+
files: [new File(["notes"], "notes.txt", { type: "text/plain" })],
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
submitPrompt();
|
|
149
|
+
|
|
150
|
+
expect(toast).toHaveBeenCalledWith({
|
|
151
|
+
title: "Notebook must be named",
|
|
152
|
+
description: "Please name the notebook to use the agent",
|
|
153
|
+
variant: "danger",
|
|
154
|
+
});
|
|
155
|
+
expect(agent.prompt).not.toHaveBeenCalled();
|
|
156
|
+
expect(screen.getByRole("textbox", { name: "Prompt" })).toHaveValue(
|
|
157
|
+
"Explain this notebook",
|
|
158
|
+
);
|
|
159
|
+
expect(screen.getByText("notes.txt")).toBeInTheDocument();
|
|
160
|
+
expect(store.get(selectedTabAtom)?.title).toBe(title);
|
|
161
|
+
expect(screen.queryByText("Agent is working...")).not.toBeInTheDocument();
|
|
162
|
+
|
|
163
|
+
act(() => store.set(filenameAtom, "notebook.py"));
|
|
164
|
+
fireEvent.keyDown(screen.getByRole("textbox", { name: "Prompt" }), {
|
|
165
|
+
key: "Enter",
|
|
166
|
+
});
|
|
167
|
+
await waitFor(() => expect(agent.prompt).toHaveBeenCalledOnce());
|
|
168
|
+
expect(agent.prompt).toHaveBeenCalledWith({
|
|
169
|
+
sessionId: "session-1",
|
|
170
|
+
prompt: expect.arrayContaining([
|
|
171
|
+
{ type: "text", text: "Explain this notebook" },
|
|
172
|
+
expect.objectContaining({ type: "resource_link", name: "notes.txt" }),
|
|
173
|
+
]),
|
|
174
|
+
});
|
|
175
|
+
await waitFor(() =>
|
|
176
|
+
expect(screen.queryByText("Agent is working...")).not.toBeInTheDocument(),
|
|
177
|
+
);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it.each([new Error("Agent unavailable"), "Agent unavailable"])(
|
|
181
|
+
"shows a rejected prompt and allows another submission after dismissal (%s)",
|
|
182
|
+
async (error) => {
|
|
183
|
+
agent.prompt.mockRejectedValueOnce(error);
|
|
184
|
+
renderPanel();
|
|
185
|
+
submitPrompt();
|
|
186
|
+
|
|
187
|
+
expect(await screen.findByText(/Agent unavailable/)).toBeInTheDocument();
|
|
188
|
+
fireEvent.click(screen.getByRole("button", { name: "Dismiss" }));
|
|
189
|
+
expect(screen.queryByText(/Agent unavailable/)).not.toBeInTheDocument();
|
|
190
|
+
submitPrompt("Try again");
|
|
191
|
+
await waitFor(() => expect(agent.prompt).toHaveBeenCalledTimes(2));
|
|
192
|
+
await waitFor(() =>
|
|
193
|
+
expect(
|
|
194
|
+
screen.queryByText("Agent is working..."),
|
|
195
|
+
).not.toBeInTheDocument(),
|
|
196
|
+
);
|
|
197
|
+
},
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
it("preserves the existing authentication error action and detail", async () => {
|
|
201
|
+
agent.prompt.mockRejectedValueOnce(
|
|
202
|
+
new JsonRpcError({
|
|
203
|
+
code: -32_000,
|
|
204
|
+
message: "Authentication required",
|
|
205
|
+
data: { message: "Sign in to the agent" },
|
|
206
|
+
}),
|
|
207
|
+
);
|
|
208
|
+
renderPanel();
|
|
209
|
+
submitPrompt();
|
|
210
|
+
|
|
211
|
+
expect(await screen.findByText("Sign in to the agent")).toBeInTheDocument();
|
|
212
|
+
expect(
|
|
213
|
+
screen.getByRole("button", { name: "Restart session" }),
|
|
214
|
+
).toBeInTheDocument();
|
|
215
|
+
expect(
|
|
216
|
+
screen.queryByRole("button", { name: "Dismiss" }),
|
|
217
|
+
).not.toBeInTheDocument();
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it("clears busy state and reports failures while preparing prompt context", async () => {
|
|
221
|
+
vi.mocked(parseContextFromPrompt).mockRejectedValueOnce(
|
|
222
|
+
new Error("Context unavailable"),
|
|
223
|
+
);
|
|
224
|
+
renderPanel();
|
|
225
|
+
submitPrompt();
|
|
226
|
+
|
|
227
|
+
expect(await screen.findByText("Context unavailable")).toBeInTheDocument();
|
|
228
|
+
expect(agent.prompt).not.toHaveBeenCalled();
|
|
229
|
+
fireEvent.click(screen.getByRole("button", { name: "Dismiss" }));
|
|
230
|
+
expect(screen.queryByText("Agent is working...")).not.toBeInTheDocument();
|
|
231
|
+
submitPrompt();
|
|
232
|
+
await waitFor(() => expect(agent.prompt).toHaveBeenCalledOnce());
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it("reports failures from a queued prompt through the same handler", async () => {
|
|
236
|
+
agent.prompt.mockRejectedValueOnce(new Error("Queued prompt failed"));
|
|
237
|
+
renderPanel();
|
|
238
|
+
act(() =>
|
|
239
|
+
store.set(pendingAiPromptAtom, {
|
|
240
|
+
prompt: "Help with this cell",
|
|
241
|
+
submit: true,
|
|
242
|
+
}),
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
expect(await screen.findByText("Queued prompt failed")).toBeInTheDocument();
|
|
246
|
+
expect(store.get(pendingAiPromptAtom)).toBeNull();
|
|
247
|
+
expect(agent.prompt).toHaveBeenCalledWith({
|
|
248
|
+
sessionId: "session-1",
|
|
249
|
+
prompt: expect.arrayContaining([
|
|
250
|
+
{ type: "text", text: "Help with this cell" },
|
|
251
|
+
]),
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it("clears busy state when a cancelled prompt settles", async () => {
|
|
256
|
+
const response = Promise.withResolvers<{ stopReason: string }>();
|
|
257
|
+
agent.prompt.mockReturnValueOnce(response.promise);
|
|
258
|
+
renderPanel();
|
|
259
|
+
submitPrompt();
|
|
260
|
+
await waitFor(() => expect(agent.prompt).toHaveBeenCalledOnce());
|
|
261
|
+
expect(screen.getByText("Agent is working...")).toBeInTheDocument();
|
|
262
|
+
fireEvent.click(screen.getByRole("button", { name: "Stop" }));
|
|
263
|
+
await act(async () => response.resolve({ stopReason: "cancelled" }));
|
|
264
|
+
expect(agent.cancel).toHaveBeenCalledWith({ sessionId: "session-1" });
|
|
265
|
+
expect(screen.queryByText("Agent is working...")).not.toBeInTheDocument();
|
|
266
|
+
expect(
|
|
267
|
+
screen.queryByRole("button", { name: "Dismiss" }),
|
|
268
|
+
).not.toBeInTheDocument();
|
|
269
|
+
});
|
|
270
|
+
});
|
|
@@ -902,6 +902,18 @@ const AgentPanel: React.FC = () => {
|
|
|
902
902
|
return;
|
|
903
903
|
}
|
|
904
904
|
|
|
905
|
+
let absoluteFilename: string;
|
|
906
|
+
try {
|
|
907
|
+
absoluteFilename = getAbsoluteFilename();
|
|
908
|
+
} catch {
|
|
909
|
+
toast({
|
|
910
|
+
title: "Notebook must be named",
|
|
911
|
+
description: "Please name the notebook to use the agent",
|
|
912
|
+
variant: "danger",
|
|
913
|
+
});
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
|
|
905
917
|
logger.debug("Submitting prompt to agent", {
|
|
906
918
|
sessionId: activeSessionId,
|
|
907
919
|
});
|
|
@@ -914,62 +926,51 @@ const AgentPanel: React.FC = () => {
|
|
|
914
926
|
setSessionState((prev) => updateSessionTitle(prev, prompt));
|
|
915
927
|
}
|
|
916
928
|
|
|
917
|
-
let absoluteFilename: string;
|
|
918
929
|
try {
|
|
919
|
-
|
|
920
|
-
} catch {
|
|
921
|
-
toast({
|
|
922
|
-
title: "Notebook must be named",
|
|
923
|
-
description: "Please name the notebook to use the agent",
|
|
924
|
-
variant: "danger",
|
|
925
|
-
});
|
|
926
|
-
return;
|
|
927
|
-
}
|
|
928
|
-
|
|
929
|
-
const promptBlocks: ContentBlock[] = [{ type: "text", text: prompt }];
|
|
930
|
+
const promptBlocks: ContentBlock[] = [{ type: "text", text: prompt }];
|
|
930
931
|
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
932
|
+
// Parse context from the prompt
|
|
933
|
+
const { contextBlocks, attachmentBlocks } =
|
|
934
|
+
await parseContextFromPrompt(prompt);
|
|
935
|
+
promptBlocks.push(...contextBlocks, ...attachmentBlocks);
|
|
935
936
|
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
937
|
+
// Add manually uploaded files as resource links
|
|
938
|
+
if (files && files.length > 0) {
|
|
939
|
+
const fileResourceLinks = await convertFilesToResourceLinks(files);
|
|
940
|
+
promptBlocks.push(...fileResourceLinks);
|
|
941
|
+
}
|
|
941
942
|
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
);
|
|
947
|
-
if (!hasGivenRules) {
|
|
948
|
-
promptBlocks.push(
|
|
949
|
-
{
|
|
950
|
-
type: "resource_link",
|
|
951
|
-
uri: absoluteFilename,
|
|
952
|
-
mimeType: "text/x-python",
|
|
953
|
-
name: absoluteFilename,
|
|
954
|
-
},
|
|
955
|
-
{
|
|
956
|
-
type: "resource",
|
|
957
|
-
resource: {
|
|
958
|
-
uri: "marimo_rules.md",
|
|
959
|
-
mimeType: "text/plain",
|
|
960
|
-
text: getAgentPrompt(absoluteFilename),
|
|
961
|
-
},
|
|
962
|
-
},
|
|
943
|
+
const hasGivenRules = notifications.some(
|
|
944
|
+
(notification) =>
|
|
945
|
+
notification.type === "session_notification" &&
|
|
946
|
+
notification.data.update.sessionUpdate === "user_message_chunk",
|
|
963
947
|
);
|
|
964
|
-
|
|
948
|
+
if (!hasGivenRules) {
|
|
949
|
+
promptBlocks.push(
|
|
950
|
+
{
|
|
951
|
+
type: "resource_link",
|
|
952
|
+
uri: absoluteFilename,
|
|
953
|
+
mimeType: "text/x-python",
|
|
954
|
+
name: absoluteFilename,
|
|
955
|
+
},
|
|
956
|
+
{
|
|
957
|
+
type: "resource",
|
|
958
|
+
resource: {
|
|
959
|
+
uri: "marimo_rules.md",
|
|
960
|
+
mimeType: "text/plain",
|
|
961
|
+
text: getAgentPrompt(absoluteFilename),
|
|
962
|
+
},
|
|
963
|
+
},
|
|
964
|
+
);
|
|
965
|
+
}
|
|
965
966
|
|
|
966
|
-
try {
|
|
967
967
|
await agent.prompt({
|
|
968
968
|
sessionId: activeSessionId,
|
|
969
969
|
prompt: promptBlocks,
|
|
970
970
|
});
|
|
971
971
|
} catch (error) {
|
|
972
972
|
logger.error("Failed to send prompt", { error });
|
|
973
|
+
setError(error instanceof Error ? error : String(error));
|
|
973
974
|
} finally {
|
|
974
975
|
setIsLoading(false);
|
|
975
976
|
}
|
|
@@ -262,18 +262,30 @@ const wrapTooltipTargets: TransformFn = (
|
|
|
262
262
|
if (tagName.startsWith("marimo-")) {
|
|
263
263
|
return undefined;
|
|
264
264
|
}
|
|
265
|
-
const tooltipContent = domNode.attribs["data-tooltip"]
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
265
|
+
const tooltipContent = domNode.attribs["data-tooltip"].replace(
|
|
266
|
+
/^[\r\n]+|[\r\n]+$/g,
|
|
267
|
+
"",
|
|
268
|
+
);
|
|
269
|
+
return (
|
|
270
|
+
<Tooltip
|
|
271
|
+
content={
|
|
272
|
+
tooltipContent.includes("\n") ? (
|
|
273
|
+
<span className="whitespace-pre-wrap">
|
|
274
|
+
{tooltipContent.split(/\r?\n/).map((line, i) => (
|
|
275
|
+
<React.Fragment key={i}>
|
|
276
|
+
{i > 0 && <br />}
|
|
277
|
+
{line}
|
|
278
|
+
</React.Fragment>
|
|
279
|
+
))}
|
|
280
|
+
</span>
|
|
281
|
+
) : (
|
|
282
|
+
tooltipContent
|
|
283
|
+
)
|
|
284
|
+
}
|
|
285
|
+
>
|
|
286
|
+
{reactNode as JSX.Element}
|
|
287
|
+
</Tooltip>
|
|
288
|
+
);
|
|
277
289
|
}
|
|
278
290
|
};
|
|
279
291
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
2
|
import type { ExtractAtomValue } from "jotai";
|
|
3
|
+
import { isValidElement, type ReactNode } from "react";
|
|
4
|
+
import { renderToStaticMarkup } from "react-dom/server";
|
|
3
5
|
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
4
6
|
import { hasRunAnyCellAtom } from "@/components/editor/cell/useRunCells";
|
|
5
7
|
import { userConfigAtom } from "@/core/config/config";
|
|
@@ -458,13 +460,21 @@ describe("wrapTooltipTargets", () => {
|
|
|
458
460
|
expect(result).toMatchInlineSnapshot(`
|
|
459
461
|
<Tooltip
|
|
460
462
|
content={
|
|
461
|
-
|
|
462
|
-
"
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
463
|
+
<span
|
|
464
|
+
className="whitespace-pre-wrap"
|
|
465
|
+
>
|
|
466
|
+
<React.Fragment>
|
|
467
|
+
Line A
|
|
468
|
+
</React.Fragment>
|
|
469
|
+
<React.Fragment>
|
|
470
|
+
<br />
|
|
471
|
+
Line B
|
|
472
|
+
</React.Fragment>
|
|
473
|
+
<React.Fragment>
|
|
474
|
+
<br />
|
|
475
|
+
Line C
|
|
476
|
+
</React.Fragment>
|
|
477
|
+
</span>
|
|
468
478
|
}
|
|
469
479
|
>
|
|
470
480
|
<span
|
|
@@ -483,11 +493,17 @@ describe("wrapTooltipTargets", () => {
|
|
|
483
493
|
expect(parseHtml({ html })).toMatchInlineSnapshot(`
|
|
484
494
|
<Tooltip
|
|
485
495
|
content={
|
|
486
|
-
|
|
487
|
-
"
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
496
|
+
<span
|
|
497
|
+
className="whitespace-pre-wrap"
|
|
498
|
+
>
|
|
499
|
+
<React.Fragment>
|
|
500
|
+
Line A
|
|
501
|
+
</React.Fragment>
|
|
502
|
+
<React.Fragment>
|
|
503
|
+
<br />
|
|
504
|
+
Line B
|
|
505
|
+
</React.Fragment>
|
|
506
|
+
</span>
|
|
491
507
|
}
|
|
492
508
|
>
|
|
493
509
|
<span
|
|
@@ -514,6 +530,113 @@ describe("wrapTooltipTargets", () => {
|
|
|
514
530
|
</Tooltip>
|
|
515
531
|
`);
|
|
516
532
|
});
|
|
533
|
+
|
|
534
|
+
test.each([
|
|
535
|
+
["CRLF", "A\r\nB", '<span class="whitespace-pre-wrap">A<br/>B</span>'],
|
|
536
|
+
[
|
|
537
|
+
"encoded CRLF",
|
|
538
|
+
"A B",
|
|
539
|
+
'<span class="whitespace-pre-wrap">A<br/>B</span>',
|
|
540
|
+
],
|
|
541
|
+
["leading newline", "\nA", "A"],
|
|
542
|
+
["trailing newline", "A\n", "A"],
|
|
543
|
+
[
|
|
544
|
+
"boundary newlines",
|
|
545
|
+
"\n\nA\nB\n\n",
|
|
546
|
+
'<span class="whitespace-pre-wrap">A<br/>B</span>',
|
|
547
|
+
],
|
|
548
|
+
[
|
|
549
|
+
"boundary CRLF",
|
|
550
|
+
" A B ",
|
|
551
|
+
'<span class="whitespace-pre-wrap">A<br/>B</span>',
|
|
552
|
+
],
|
|
553
|
+
["only newlines", "\n\n", ""],
|
|
554
|
+
["only CRLF", " ", ""],
|
|
555
|
+
[
|
|
556
|
+
"internal blank line",
|
|
557
|
+
"A\n\nB",
|
|
558
|
+
'<span class="whitespace-pre-wrap">A<br/><br/>B</span>',
|
|
559
|
+
],
|
|
560
|
+
[
|
|
561
|
+
"spaces and tabs",
|
|
562
|
+
"\n A\t\n\tB \n",
|
|
563
|
+
'<span class="whitespace-pre-wrap"> A\t<br/>\tB </span>',
|
|
564
|
+
],
|
|
565
|
+
[
|
|
566
|
+
"HTML text",
|
|
567
|
+
"<b>A</b>\nB",
|
|
568
|
+
'<span class="whitespace-pre-wrap"><b>A</b><br/>B</span>',
|
|
569
|
+
],
|
|
570
|
+
])("data-tooltip handles %s", (_, content, expected) => {
|
|
571
|
+
const result = parseHtml({
|
|
572
|
+
html: `<span data-tooltip="${content}">Hover me</span>`,
|
|
573
|
+
});
|
|
574
|
+
if (!isValidElement<{ content: ReactNode }>(result)) {
|
|
575
|
+
throw new Error("Expected a Tooltip element");
|
|
576
|
+
}
|
|
577
|
+
expect(renderToStaticMarkup(result.props.content)).toBe(expected);
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
test("data-tooltip with newline renders as line breaks", () => {
|
|
581
|
+
const html = '<span data-tooltip="Line one\nLine two">Hover me</span>';
|
|
582
|
+
expect(parseHtml({ html })).toMatchInlineSnapshot(`
|
|
583
|
+
<Tooltip
|
|
584
|
+
content={
|
|
585
|
+
<span
|
|
586
|
+
className="whitespace-pre-wrap"
|
|
587
|
+
>
|
|
588
|
+
<React.Fragment>
|
|
589
|
+
Line one
|
|
590
|
+
</React.Fragment>
|
|
591
|
+
<React.Fragment>
|
|
592
|
+
<br />
|
|
593
|
+
Line two
|
|
594
|
+
</React.Fragment>
|
|
595
|
+
</span>
|
|
596
|
+
}
|
|
597
|
+
>
|
|
598
|
+
<span
|
|
599
|
+
data-tooltip="Line one
|
|
600
|
+
Line two"
|
|
601
|
+
>
|
|
602
|
+
Hover me
|
|
603
|
+
</span>
|
|
604
|
+
</Tooltip>
|
|
605
|
+
`);
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
test("data-tooltip with multiple newlines renders all as line breaks", () => {
|
|
609
|
+
const html = '<span data-tooltip="A\nB\nC">Hover me</span>';
|
|
610
|
+
expect(parseHtml({ html })).toMatchInlineSnapshot(`
|
|
611
|
+
<Tooltip
|
|
612
|
+
content={
|
|
613
|
+
<span
|
|
614
|
+
className="whitespace-pre-wrap"
|
|
615
|
+
>
|
|
616
|
+
<React.Fragment>
|
|
617
|
+
A
|
|
618
|
+
</React.Fragment>
|
|
619
|
+
<React.Fragment>
|
|
620
|
+
<br />
|
|
621
|
+
B
|
|
622
|
+
</React.Fragment>
|
|
623
|
+
<React.Fragment>
|
|
624
|
+
<br />
|
|
625
|
+
C
|
|
626
|
+
</React.Fragment>
|
|
627
|
+
</span>
|
|
628
|
+
}
|
|
629
|
+
>
|
|
630
|
+
<span
|
|
631
|
+
data-tooltip="A
|
|
632
|
+
B
|
|
633
|
+
C"
|
|
634
|
+
>
|
|
635
|
+
Hover me
|
|
636
|
+
</span>
|
|
637
|
+
</Tooltip>
|
|
638
|
+
`);
|
|
639
|
+
});
|
|
517
640
|
});
|
|
518
641
|
|
|
519
642
|
describe("parseHtml with < nad >", () => {
|