@marimo-team/islands 0.23.12-dev10 → 0.23.12-dev11
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/{code-visibility-D1W_9sHW.js → code-visibility-Bi2d-Fh5.js} +1 -1
- package/dist/main.js +1084 -988
- package/dist/{reveal-component-BXP8Y-bq.js → reveal-component-OEq7xNow.js} +1 -1
- package/package.json +1 -1
- package/src/core/islands/__tests__/bridge.test.ts +25 -0
- package/src/core/islands/__tests__/parse.test.ts +585 -1
- package/src/core/islands/__tests__/test-utils.tsx +10 -1
- package/src/core/islands/bridge.ts +6 -1
- package/src/core/islands/constants.ts +2 -0
- package/src/core/islands/parse.ts +293 -13
|
@@ -9,7 +9,7 @@ import { t as require_compiler_runtime } from "./compiler-runtime-CEbnTgxf.js";
|
|
|
9
9
|
import { lt as kioskModeAtom } from "./html-to-image-Di0mtt6O.js";
|
|
10
10
|
import "./chunk-5FQGJX7Z-BbqSm5gU.js";
|
|
11
11
|
import { u as createLucideIcon } from "./dist--2Bqjvs0.js";
|
|
12
|
-
import { a as DEFAULT_SLIDE_TYPE, c as Slide, ct as PanelResizeHandle, i as DEFAULT_DECK_TRANSITION, in as Expand, ot as Panel, rn as EyeOff, s as SlideSidebar, sn as Code, st as PanelGroup, t as useNotebookCodeAvailable } from "./code-visibility-
|
|
12
|
+
import { a as DEFAULT_SLIDE_TYPE, c as Slide, ct as PanelResizeHandle, i as DEFAULT_DECK_TRANSITION, in as Expand, ot as Panel, rn as EyeOff, s as SlideSidebar, sn as Code, st as PanelGroup, t as useNotebookCodeAvailable } from "./code-visibility-Bi2d-Fh5.js";
|
|
13
13
|
import { X as useDebouncedCallback } from "./input-CbEz_aj_.js";
|
|
14
14
|
import "./toDate-D-l5s8nn.js";
|
|
15
15
|
import "./react-dom-BTJzcVJ9.js";
|
package/package.json
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
type Base64String = components["schemas"]["Base64String"];
|
|
13
13
|
interface TestIslandApp {
|
|
14
14
|
id: string;
|
|
15
|
+
payloadBacked?: boolean;
|
|
15
16
|
cells: { code: string; idx: number; output: string }[];
|
|
16
17
|
}
|
|
17
18
|
interface TestExportContext {
|
|
@@ -142,6 +143,30 @@ describe("IslandsPyodideBridge", () => {
|
|
|
142
143
|
});
|
|
143
144
|
});
|
|
144
145
|
|
|
146
|
+
it("should ignore trusted export notebook code for a payload-backed app", async () => {
|
|
147
|
+
const payloadApp = {
|
|
148
|
+
id: "app-1",
|
|
149
|
+
payloadBacked: true,
|
|
150
|
+
cells: [{ code: "x = 1", idx: 0, output: "<div>1</div>" }],
|
|
151
|
+
};
|
|
152
|
+
mockParseMarimoIslandApps.mockReturnValue([payloadApp]);
|
|
153
|
+
mockGetMarimoExportContext.mockReturnValue({
|
|
154
|
+
trusted: true,
|
|
155
|
+
notebookCode: "full notebook should be ignored",
|
|
156
|
+
});
|
|
157
|
+
mockCreateMarimoFile.mockReturnValue("generated payload app");
|
|
158
|
+
|
|
159
|
+
await (
|
|
160
|
+
bridge as unknown as { startSessionsForAllApps(): Promise<void> }
|
|
161
|
+
).startSessionsForAllApps();
|
|
162
|
+
|
|
163
|
+
expect(mockCreateMarimoFile).toHaveBeenCalledWith(payloadApp);
|
|
164
|
+
expect(mockStartSessionRequest).toHaveBeenCalledWith({
|
|
165
|
+
appId: "app-1",
|
|
166
|
+
code: "generated payload app",
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
145
170
|
it("should keep synthesized per-app files for multiple reactive apps even when export context exists", async () => {
|
|
146
171
|
mockParseMarimoIslandApps.mockReturnValue([
|
|
147
172
|
{
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
-
import {
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
4
|
import {
|
|
4
5
|
ISLAND_DATA_ATTRIBUTES,
|
|
5
6
|
ISLAND_TAG_NAMES,
|
|
7
|
+
ISLANDS_JSON_SCRIPT_TYPE,
|
|
6
8
|
} from "@/core/islands/constants";
|
|
9
|
+
import { Logger } from "@/utils/Logger";
|
|
7
10
|
import {
|
|
8
11
|
createMarimoFile,
|
|
9
12
|
extractIslandCodeFromEmbed,
|
|
@@ -15,6 +18,43 @@ import {
|
|
|
15
18
|
} from "../parse";
|
|
16
19
|
import { createMockIslandElement, createMockIslands } from "./test-utils.tsx";
|
|
17
20
|
|
|
21
|
+
function createPayloadCell(
|
|
22
|
+
overrides: Partial<{
|
|
23
|
+
cellId: string;
|
|
24
|
+
code: string;
|
|
25
|
+
outputHtml: string;
|
|
26
|
+
outputMimetype: string;
|
|
27
|
+
reactive: boolean;
|
|
28
|
+
displayCode: boolean;
|
|
29
|
+
displayOutput: boolean;
|
|
30
|
+
}> = {},
|
|
31
|
+
) {
|
|
32
|
+
return {
|
|
33
|
+
cellId: "cell-1",
|
|
34
|
+
code: 'print("payload")',
|
|
35
|
+
outputHtml: "<div>payload</div>",
|
|
36
|
+
outputMimetype: "text/html",
|
|
37
|
+
reactive: true,
|
|
38
|
+
displayCode: false,
|
|
39
|
+
displayOutput: true,
|
|
40
|
+
...overrides,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function appendPayload(
|
|
45
|
+
root: HTMLElement,
|
|
46
|
+
payload: {
|
|
47
|
+
schemaVersion: number;
|
|
48
|
+
appId: string;
|
|
49
|
+
cells: ReturnType<typeof createPayloadCell>[];
|
|
50
|
+
},
|
|
51
|
+
) {
|
|
52
|
+
const script = document.createElement("script");
|
|
53
|
+
script.type = ISLANDS_JSON_SCRIPT_TYPE;
|
|
54
|
+
script.textContent = JSON.stringify(payload);
|
|
55
|
+
root.appendChild(script);
|
|
56
|
+
}
|
|
57
|
+
|
|
18
58
|
describe("createMarimoFile", () => {
|
|
19
59
|
it("should return a string", () => {
|
|
20
60
|
const app = {
|
|
@@ -90,6 +130,27 @@ describe("createMarimoFile", () => {
|
|
|
90
130
|
`);
|
|
91
131
|
});
|
|
92
132
|
|
|
133
|
+
it("should create disabled marimo cells", () => {
|
|
134
|
+
const app = {
|
|
135
|
+
cells: [{ code: "x = 0", disabled: true }, { code: "x = 1" }],
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const result = createMarimoFile(app);
|
|
139
|
+
|
|
140
|
+
expect(result).toMatchInlineSnapshot(`
|
|
141
|
+
"import marimo
|
|
142
|
+
app = marimo.App()
|
|
143
|
+
@app.cell(disabled=True)
|
|
144
|
+
def __():
|
|
145
|
+
pass
|
|
146
|
+
return
|
|
147
|
+
@app.cell
|
|
148
|
+
def __():
|
|
149
|
+
x = 1
|
|
150
|
+
return"
|
|
151
|
+
`);
|
|
152
|
+
});
|
|
153
|
+
|
|
93
154
|
it("should properly indent multi-line code", () => {
|
|
94
155
|
const app = {
|
|
95
156
|
cells: [{ code: "if True:\n print('hello')\n print('world')" }],
|
|
@@ -459,6 +520,7 @@ describe("parseMarimoIslandApps", () => {
|
|
|
459
520
|
|
|
460
521
|
afterEach(() => {
|
|
461
522
|
document.body.removeChild(container);
|
|
523
|
+
vi.restoreAllMocks();
|
|
462
524
|
});
|
|
463
525
|
|
|
464
526
|
it("should parse islands from document", () => {
|
|
@@ -528,4 +590,526 @@ describe("parseMarimoIslandApps", () => {
|
|
|
528
590
|
]
|
|
529
591
|
`);
|
|
530
592
|
});
|
|
593
|
+
|
|
594
|
+
it("should use supported JSON payload data for matched islands", () => {
|
|
595
|
+
const island = createMockIslandElement({
|
|
596
|
+
appId: "app1",
|
|
597
|
+
cellId: "cell-1",
|
|
598
|
+
code: 'print("dom")',
|
|
599
|
+
innerHTML: "<div>dom</div>",
|
|
600
|
+
});
|
|
601
|
+
island.setAttribute(ISLAND_DATA_ATTRIBUTES.REACTIVE, "true");
|
|
602
|
+
container.appendChild(island);
|
|
603
|
+
appendPayload(container, {
|
|
604
|
+
schemaVersion: 1,
|
|
605
|
+
appId: "app1",
|
|
606
|
+
cells: [createPayloadCell()],
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
const result = parseMarimoIslandApps(container);
|
|
610
|
+
|
|
611
|
+
expect(result).toEqual([
|
|
612
|
+
{
|
|
613
|
+
id: "app1",
|
|
614
|
+
payloadBacked: true,
|
|
615
|
+
cells: [
|
|
616
|
+
{
|
|
617
|
+
cellId: "cell-1",
|
|
618
|
+
code: 'print("payload")',
|
|
619
|
+
idx: 0,
|
|
620
|
+
output: "<div>payload</div>",
|
|
621
|
+
},
|
|
622
|
+
],
|
|
623
|
+
},
|
|
624
|
+
]);
|
|
625
|
+
expect(island.getAttribute(ISLAND_DATA_ATTRIBUTES.CELL_IDX)).toBe("0");
|
|
626
|
+
expect(island.getAttribute(ISLAND_DATA_ATTRIBUTES.CELL_ID)).toBeNull();
|
|
627
|
+
expect(extractIslandCodeFromEmbed(island)).toBe('print("payload")');
|
|
628
|
+
expect(island.querySelector(ISLAND_TAG_NAMES.CELL_OUTPUT)?.innerHTML).toBe(
|
|
629
|
+
"<div>payload</div>",
|
|
630
|
+
);
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
it("should parse Python-generated island payload snapshots", () => {
|
|
634
|
+
const html = readFileSync(
|
|
635
|
+
new URL(
|
|
636
|
+
"../../../../../tests/_islands/snapshots/html-payload.txt",
|
|
637
|
+
import.meta.url,
|
|
638
|
+
).pathname.replace(/^\/@fs/, ""),
|
|
639
|
+
"utf8",
|
|
640
|
+
);
|
|
641
|
+
container.innerHTML = html;
|
|
642
|
+
|
|
643
|
+
const result = parseMarimoIslandApps(container);
|
|
644
|
+
const islands = container.querySelectorAll<HTMLElement>(
|
|
645
|
+
ISLAND_TAG_NAMES.ISLAND,
|
|
646
|
+
);
|
|
647
|
+
|
|
648
|
+
expect(result).toEqual([
|
|
649
|
+
{
|
|
650
|
+
id: "main",
|
|
651
|
+
payloadBacked: true,
|
|
652
|
+
cells: [
|
|
653
|
+
{
|
|
654
|
+
cellId: "Hbol",
|
|
655
|
+
code: "import marimo as mo",
|
|
656
|
+
idx: 0,
|
|
657
|
+
output: "",
|
|
658
|
+
},
|
|
659
|
+
{
|
|
660
|
+
cellId: "MJUe",
|
|
661
|
+
code: "mo.md('Hello, HTML!')",
|
|
662
|
+
idx: 1,
|
|
663
|
+
output: "",
|
|
664
|
+
},
|
|
665
|
+
],
|
|
666
|
+
},
|
|
667
|
+
]);
|
|
668
|
+
expect(islands[0].getAttribute(ISLAND_DATA_ATTRIBUTES.CELL_IDX)).toBeNull();
|
|
669
|
+
expect(islands[1].getAttribute(ISLAND_DATA_ATTRIBUTES.CELL_IDX)).toBe("0");
|
|
670
|
+
expect(islands[2].getAttribute(ISLAND_DATA_ATTRIBUTES.CELL_IDX)).toBe("1");
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
it("should fall back to DOM islands for unsupported payload versions", () => {
|
|
674
|
+
const island = createMockIslandElement({
|
|
675
|
+
appId: "app1",
|
|
676
|
+
cellId: "cell-1",
|
|
677
|
+
code: 'print("dom")',
|
|
678
|
+
innerHTML: "<div>dom</div>",
|
|
679
|
+
});
|
|
680
|
+
island.setAttribute(ISLAND_DATA_ATTRIBUTES.REACTIVE, "true");
|
|
681
|
+
container.appendChild(island);
|
|
682
|
+
appendPayload(container, {
|
|
683
|
+
schemaVersion: 2,
|
|
684
|
+
appId: "app1",
|
|
685
|
+
cells: [createPayloadCell()],
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
const result = parseMarimoIslandApps(container);
|
|
689
|
+
|
|
690
|
+
expect(result).toEqual([
|
|
691
|
+
{
|
|
692
|
+
id: "app1",
|
|
693
|
+
cells: [
|
|
694
|
+
{
|
|
695
|
+
code: 'print("dom")',
|
|
696
|
+
idx: 0,
|
|
697
|
+
output: "<div>dom</div>",
|
|
698
|
+
},
|
|
699
|
+
],
|
|
700
|
+
},
|
|
701
|
+
]);
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
it("should ignore payload scripts rendered inside island output", () => {
|
|
705
|
+
const nestedPayload = {
|
|
706
|
+
schemaVersion: 1,
|
|
707
|
+
appId: "app1",
|
|
708
|
+
cells: [
|
|
709
|
+
createPayloadCell({
|
|
710
|
+
code: "payload_code_should_not_run = True",
|
|
711
|
+
outputHtml: "<div>payload</div>",
|
|
712
|
+
}),
|
|
713
|
+
],
|
|
714
|
+
};
|
|
715
|
+
const island = createMockIslandElement({
|
|
716
|
+
appId: "app1",
|
|
717
|
+
cellId: "cell-1",
|
|
718
|
+
code: 'print("dom")',
|
|
719
|
+
innerHTML: `<div>dom</div><script type="${ISLANDS_JSON_SCRIPT_TYPE}">${JSON.stringify(nestedPayload)}</script>`,
|
|
720
|
+
});
|
|
721
|
+
island.setAttribute(ISLAND_DATA_ATTRIBUTES.REACTIVE, "true");
|
|
722
|
+
container.appendChild(island);
|
|
723
|
+
|
|
724
|
+
const result = parseMarimoIslandApps(container);
|
|
725
|
+
|
|
726
|
+
expect(result).toEqual([
|
|
727
|
+
{
|
|
728
|
+
id: "app1",
|
|
729
|
+
cells: [
|
|
730
|
+
{
|
|
731
|
+
code: 'print("dom")',
|
|
732
|
+
idx: 0,
|
|
733
|
+
output: island.querySelector(ISLAND_TAG_NAMES.CELL_OUTPUT)
|
|
734
|
+
?.innerHTML,
|
|
735
|
+
},
|
|
736
|
+
],
|
|
737
|
+
},
|
|
738
|
+
]);
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
it("should use payload order for runtime cell indices", () => {
|
|
742
|
+
const second = createMockIslandElement({
|
|
743
|
+
appId: "app1",
|
|
744
|
+
cellId: "cell-2",
|
|
745
|
+
code: "second_dom = True",
|
|
746
|
+
innerHTML: "<div>second dom</div>",
|
|
747
|
+
});
|
|
748
|
+
const first = createMockIslandElement({
|
|
749
|
+
appId: "app1",
|
|
750
|
+
cellId: "cell-1",
|
|
751
|
+
code: "first_dom = True",
|
|
752
|
+
innerHTML: "<div>first dom</div>",
|
|
753
|
+
});
|
|
754
|
+
for (const island of [second, first]) {
|
|
755
|
+
island.setAttribute(ISLAND_DATA_ATTRIBUTES.REACTIVE, "true");
|
|
756
|
+
container.appendChild(island);
|
|
757
|
+
}
|
|
758
|
+
appendPayload(container, {
|
|
759
|
+
schemaVersion: 1,
|
|
760
|
+
appId: "app1",
|
|
761
|
+
cells: [
|
|
762
|
+
createPayloadCell({
|
|
763
|
+
cellId: "cell-1",
|
|
764
|
+
code: "first_payload = True",
|
|
765
|
+
outputHtml: "<div>first payload</div>",
|
|
766
|
+
}),
|
|
767
|
+
createPayloadCell({
|
|
768
|
+
cellId: "cell-2",
|
|
769
|
+
code: "second_payload = True",
|
|
770
|
+
outputHtml: "<div>second payload</div>",
|
|
771
|
+
}),
|
|
772
|
+
],
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
const result = parseMarimoIslandApps(container);
|
|
776
|
+
|
|
777
|
+
expect(result[0].cells.map((cell) => cell.code)).toEqual([
|
|
778
|
+
"first_payload = True",
|
|
779
|
+
"second_payload = True",
|
|
780
|
+
]);
|
|
781
|
+
expect(first.getAttribute(ISLAND_DATA_ATTRIBUTES.CELL_IDX)).toBe("0");
|
|
782
|
+
expect(second.getAttribute(ISLAND_DATA_ATTRIBUTES.CELL_IDX)).toBe("1");
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
it("should include payload-only runtime cells", () => {
|
|
786
|
+
const island = createMockIslandElement({
|
|
787
|
+
appId: "app1",
|
|
788
|
+
cellId: "cell-2",
|
|
789
|
+
code: "value",
|
|
790
|
+
innerHTML: "<div>value</div>",
|
|
791
|
+
});
|
|
792
|
+
island.setAttribute(ISLAND_DATA_ATTRIBUTES.REACTIVE, "true");
|
|
793
|
+
container.appendChild(island);
|
|
794
|
+
appendPayload(container, {
|
|
795
|
+
schemaVersion: 1,
|
|
796
|
+
appId: "app1",
|
|
797
|
+
cells: [
|
|
798
|
+
createPayloadCell({
|
|
799
|
+
cellId: "cell-1",
|
|
800
|
+
code: "import marimo as mo",
|
|
801
|
+
outputHtml: "",
|
|
802
|
+
}),
|
|
803
|
+
createPayloadCell({
|
|
804
|
+
cellId: "cell-2",
|
|
805
|
+
code: "mo.md('visible')",
|
|
806
|
+
outputHtml: "<div>visible</div>",
|
|
807
|
+
}),
|
|
808
|
+
],
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
const result = parseMarimoIslandApps(container);
|
|
812
|
+
|
|
813
|
+
expect(result[0].cells.map((cell) => cell.code)).toEqual([
|
|
814
|
+
"import marimo as mo",
|
|
815
|
+
"mo.md('visible')",
|
|
816
|
+
]);
|
|
817
|
+
expect(island.getAttribute(ISLAND_DATA_ATTRIBUTES.CELL_IDX)).toBe("1");
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
it("should bind payload-backed islands by runtime index after static cells", () => {
|
|
821
|
+
const island = createMockIslandElement({
|
|
822
|
+
appId: "app1",
|
|
823
|
+
cellId: "cell-2",
|
|
824
|
+
code: "visible_dom = True",
|
|
825
|
+
innerHTML: "<div>visible dom</div>",
|
|
826
|
+
});
|
|
827
|
+
island.setAttribute(ISLAND_DATA_ATTRIBUTES.REACTIVE, "true");
|
|
828
|
+
container.appendChild(island);
|
|
829
|
+
appendPayload(container, {
|
|
830
|
+
schemaVersion: 1,
|
|
831
|
+
appId: "app1",
|
|
832
|
+
cells: [
|
|
833
|
+
createPayloadCell({
|
|
834
|
+
cellId: "cell-1",
|
|
835
|
+
code: "",
|
|
836
|
+
outputHtml: "<div>static</div>",
|
|
837
|
+
reactive: false,
|
|
838
|
+
}),
|
|
839
|
+
createPayloadCell({
|
|
840
|
+
cellId: "cell-2",
|
|
841
|
+
code: "visible_payload = True",
|
|
842
|
+
outputHtml: "<div>visible payload</div>",
|
|
843
|
+
}),
|
|
844
|
+
],
|
|
845
|
+
});
|
|
846
|
+
|
|
847
|
+
const result = parseMarimoIslandApps(container);
|
|
848
|
+
|
|
849
|
+
expect(result[0].cells).toEqual([
|
|
850
|
+
{
|
|
851
|
+
cellId: "cell-1",
|
|
852
|
+
code: "",
|
|
853
|
+
disabled: true,
|
|
854
|
+
idx: 0,
|
|
855
|
+
output: "<div>static</div>",
|
|
856
|
+
},
|
|
857
|
+
{
|
|
858
|
+
cellId: "cell-2",
|
|
859
|
+
code: "visible_payload = True",
|
|
860
|
+
idx: 1,
|
|
861
|
+
output: "<div>visible payload</div>",
|
|
862
|
+
},
|
|
863
|
+
]);
|
|
864
|
+
expect(island.getAttribute(ISLAND_DATA_ATTRIBUTES.CELL_IDX)).toBe("1");
|
|
865
|
+
expect(island.getAttribute(ISLAND_DATA_ATTRIBUTES.CELL_ID)).toBeNull();
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
it("should fall back to DOM when no payload cell matches an island", () => {
|
|
869
|
+
const island = createMockIslandElement({
|
|
870
|
+
appId: "app1",
|
|
871
|
+
cellId: "dom-only",
|
|
872
|
+
code: "dom_only = True",
|
|
873
|
+
innerHTML: "<div>dom only</div>",
|
|
874
|
+
});
|
|
875
|
+
island.setAttribute(ISLAND_DATA_ATTRIBUTES.REACTIVE, "true");
|
|
876
|
+
island.removeAttribute(ISLAND_DATA_ATTRIBUTES.CELL_IDX);
|
|
877
|
+
container.appendChild(island);
|
|
878
|
+
appendPayload(container, {
|
|
879
|
+
schemaVersion: 1,
|
|
880
|
+
appId: "app1",
|
|
881
|
+
cells: [
|
|
882
|
+
createPayloadCell({
|
|
883
|
+
cellId: "payload-only",
|
|
884
|
+
code: "payload_only = True",
|
|
885
|
+
outputHtml: "<div>payload only</div>",
|
|
886
|
+
}),
|
|
887
|
+
],
|
|
888
|
+
});
|
|
889
|
+
|
|
890
|
+
const result = parseMarimoIslandApps(container);
|
|
891
|
+
|
|
892
|
+
expect(result).toEqual([
|
|
893
|
+
{
|
|
894
|
+
id: "app1",
|
|
895
|
+
cells: [
|
|
896
|
+
{
|
|
897
|
+
code: "dom_only = True",
|
|
898
|
+
idx: 0,
|
|
899
|
+
output: "<div>dom only</div>",
|
|
900
|
+
},
|
|
901
|
+
],
|
|
902
|
+
},
|
|
903
|
+
]);
|
|
904
|
+
expect(island.getAttribute(ISLAND_DATA_ATTRIBUTES.CELL_IDX)).toBe("0");
|
|
905
|
+
expect(island.getAttribute(ISLAND_DATA_ATTRIBUTES.CELL_ID)).toBe(
|
|
906
|
+
"dom-only",
|
|
907
|
+
);
|
|
908
|
+
expect(island.getAttribute(ISLAND_DATA_ATTRIBUTES.REACTIVE)).toBe("true");
|
|
909
|
+
});
|
|
910
|
+
|
|
911
|
+
it("should ignore payloads without matching islands", () => {
|
|
912
|
+
const warn = vi.spyOn(Logger, "warn").mockImplementation(() => undefined);
|
|
913
|
+
appendPayload(container, {
|
|
914
|
+
schemaVersion: 1,
|
|
915
|
+
appId: "app1",
|
|
916
|
+
cells: [createPayloadCell()],
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
const result = parseMarimoIslandApps(container);
|
|
920
|
+
|
|
921
|
+
expect(result).toEqual([]);
|
|
922
|
+
expect(warn).toHaveBeenCalledWith("No embedded marimo apps found.");
|
|
923
|
+
});
|
|
924
|
+
|
|
925
|
+
it("should still parse DOM-only apps when another app has payload", () => {
|
|
926
|
+
const payloadIsland = createMockIslandElement({
|
|
927
|
+
appId: "app1",
|
|
928
|
+
cellId: "cell-1",
|
|
929
|
+
code: "payload_dom = True",
|
|
930
|
+
innerHTML: "<div>payload dom</div>",
|
|
931
|
+
});
|
|
932
|
+
const domIsland = createMockIslandElement({
|
|
933
|
+
appId: "app2",
|
|
934
|
+
code: "dom_app = True",
|
|
935
|
+
innerHTML: "<div>dom app</div>",
|
|
936
|
+
});
|
|
937
|
+
for (const island of [payloadIsland, domIsland]) {
|
|
938
|
+
island.setAttribute(ISLAND_DATA_ATTRIBUTES.REACTIVE, "true");
|
|
939
|
+
container.appendChild(island);
|
|
940
|
+
}
|
|
941
|
+
appendPayload(container, {
|
|
942
|
+
schemaVersion: 1,
|
|
943
|
+
appId: "app1",
|
|
944
|
+
cells: [createPayloadCell()],
|
|
945
|
+
});
|
|
946
|
+
|
|
947
|
+
const result = parseMarimoIslandApps(container);
|
|
948
|
+
|
|
949
|
+
expect(result.map((app) => app.id)).toEqual(["app1", "app2"]);
|
|
950
|
+
expect(result[0].cells[0].code).toBe('print("payload")');
|
|
951
|
+
expect(result[1].cells[0].code).toBe("dom_app = True");
|
|
952
|
+
});
|
|
953
|
+
|
|
954
|
+
it("should materialize non-reactive payload islands without runtime cells", () => {
|
|
955
|
+
const island = createMockIslandElement({
|
|
956
|
+
appId: "app1",
|
|
957
|
+
cellId: "cell-1",
|
|
958
|
+
code: "static_dom = True",
|
|
959
|
+
innerHTML: "<div>static dom</div>",
|
|
960
|
+
});
|
|
961
|
+
island.setAttribute(ISLAND_DATA_ATTRIBUTES.REACTIVE, "false");
|
|
962
|
+
island.removeAttribute(ISLAND_DATA_ATTRIBUTES.CELL_IDX);
|
|
963
|
+
container.appendChild(island);
|
|
964
|
+
appendPayload(container, {
|
|
965
|
+
schemaVersion: 1,
|
|
966
|
+
appId: "app1",
|
|
967
|
+
cells: [
|
|
968
|
+
createPayloadCell({
|
|
969
|
+
code: "",
|
|
970
|
+
outputHtml: "<div>static payload</div>",
|
|
971
|
+
reactive: false,
|
|
972
|
+
}),
|
|
973
|
+
],
|
|
974
|
+
});
|
|
975
|
+
|
|
976
|
+
const result = parseMarimoIslandApps(container);
|
|
977
|
+
|
|
978
|
+
expect(result).toEqual([]);
|
|
979
|
+
expect(island.getAttribute(ISLAND_DATA_ATTRIBUTES.CELL_IDX)).toBeNull();
|
|
980
|
+
expect(island.getAttribute(ISLAND_DATA_ATTRIBUTES.CELL_ID)).toBeNull();
|
|
981
|
+
expect(island.querySelector(ISLAND_TAG_NAMES.CELL_OUTPUT)?.innerHTML).toBe(
|
|
982
|
+
"<div>static payload</div>",
|
|
983
|
+
);
|
|
984
|
+
});
|
|
985
|
+
|
|
986
|
+
it("should keep non-reactive display code out of runtime cells", () => {
|
|
987
|
+
const staticIsland = createMockIslandElement({
|
|
988
|
+
appId: "app1",
|
|
989
|
+
cellId: "cell-1",
|
|
990
|
+
code: "x = 0",
|
|
991
|
+
innerHTML: "<div>static</div>",
|
|
992
|
+
});
|
|
993
|
+
const reactiveIsland = createMockIslandElement({
|
|
994
|
+
appId: "app1",
|
|
995
|
+
cellId: "cell-2",
|
|
996
|
+
code: "y = 1",
|
|
997
|
+
innerHTML: "<div>reactive</div>",
|
|
998
|
+
});
|
|
999
|
+
staticIsland.setAttribute(ISLAND_DATA_ATTRIBUTES.REACTIVE, "false");
|
|
1000
|
+
reactiveIsland.setAttribute(ISLAND_DATA_ATTRIBUTES.REACTIVE, "true");
|
|
1001
|
+
container.append(staticIsland, reactiveIsland);
|
|
1002
|
+
appendPayload(container, {
|
|
1003
|
+
schemaVersion: 1,
|
|
1004
|
+
appId: "app1",
|
|
1005
|
+
cells: [
|
|
1006
|
+
createPayloadCell({
|
|
1007
|
+
cellId: "cell-1",
|
|
1008
|
+
code: "x = 0",
|
|
1009
|
+
outputHtml: "<div>static payload</div>",
|
|
1010
|
+
reactive: false,
|
|
1011
|
+
displayCode: true,
|
|
1012
|
+
}),
|
|
1013
|
+
createPayloadCell({
|
|
1014
|
+
cellId: "cell-2",
|
|
1015
|
+
code: "y = 1",
|
|
1016
|
+
outputHtml: "<div>reactive payload</div>",
|
|
1017
|
+
}),
|
|
1018
|
+
],
|
|
1019
|
+
});
|
|
1020
|
+
|
|
1021
|
+
const result = parseMarimoIslandApps(container);
|
|
1022
|
+
const file = createMarimoFile(result[0]);
|
|
1023
|
+
|
|
1024
|
+
expect(result[0].cells).toEqual([
|
|
1025
|
+
{
|
|
1026
|
+
cellId: "cell-1",
|
|
1027
|
+
code: "",
|
|
1028
|
+
disabled: true,
|
|
1029
|
+
idx: 0,
|
|
1030
|
+
output: "<div>static payload</div>",
|
|
1031
|
+
},
|
|
1032
|
+
{
|
|
1033
|
+
cellId: "cell-2",
|
|
1034
|
+
code: "y = 1",
|
|
1035
|
+
idx: 1,
|
|
1036
|
+
output: "<div>reactive payload</div>",
|
|
1037
|
+
},
|
|
1038
|
+
]);
|
|
1039
|
+
expect(file).not.toContain("x = 0");
|
|
1040
|
+
expect(file).toContain("@app.cell(disabled=True)\ndef __():\n pass");
|
|
1041
|
+
expect(reactiveIsland.getAttribute(ISLAND_DATA_ATTRIBUTES.CELL_IDX)).toBe(
|
|
1042
|
+
"1",
|
|
1043
|
+
);
|
|
1044
|
+
});
|
|
1045
|
+
|
|
1046
|
+
it("should update editor initial values from payload code", () => {
|
|
1047
|
+
const island = createMockIslandElement({
|
|
1048
|
+
appId: "app1",
|
|
1049
|
+
cellId: "cell-1",
|
|
1050
|
+
code: 'print("dom")',
|
|
1051
|
+
innerHTML: "<div>dom</div>",
|
|
1052
|
+
});
|
|
1053
|
+
island.insertAdjacentHTML(
|
|
1054
|
+
"beforeend",
|
|
1055
|
+
'<div data-marimo-element><marimo-code-editor data-initial-value="\\"print(\\\\\\"dom\\\\\\")\\""></marimo-code-editor></div>',
|
|
1056
|
+
);
|
|
1057
|
+
island.setAttribute(ISLAND_DATA_ATTRIBUTES.REACTIVE, "true");
|
|
1058
|
+
container.appendChild(island);
|
|
1059
|
+
appendPayload(container, {
|
|
1060
|
+
schemaVersion: 1,
|
|
1061
|
+
appId: "app1",
|
|
1062
|
+
cells: [createPayloadCell()],
|
|
1063
|
+
});
|
|
1064
|
+
|
|
1065
|
+
parseMarimoIslandApps(container);
|
|
1066
|
+
|
|
1067
|
+
expect(
|
|
1068
|
+
island
|
|
1069
|
+
.querySelector(ISLAND_TAG_NAMES.CODE_EDITOR)
|
|
1070
|
+
?.getAttribute("data-initial-value"),
|
|
1071
|
+
).toBe(JSON.stringify('print("payload")'));
|
|
1072
|
+
});
|
|
1073
|
+
|
|
1074
|
+
it("should match duplicate cell ids by occurrence", () => {
|
|
1075
|
+
const first = createMockIslandElement({
|
|
1076
|
+
appId: "app1",
|
|
1077
|
+
cellId: "cell-1",
|
|
1078
|
+
code: "dom_first = True",
|
|
1079
|
+
innerHTML: "<div>dom first</div>",
|
|
1080
|
+
});
|
|
1081
|
+
const second = createMockIslandElement({
|
|
1082
|
+
appId: "app1",
|
|
1083
|
+
cellId: "cell-1",
|
|
1084
|
+
code: "dom_second = True",
|
|
1085
|
+
innerHTML: "<div>dom second</div>",
|
|
1086
|
+
});
|
|
1087
|
+
for (const island of [first, second]) {
|
|
1088
|
+
island.setAttribute(ISLAND_DATA_ATTRIBUTES.REACTIVE, "true");
|
|
1089
|
+
container.appendChild(island);
|
|
1090
|
+
}
|
|
1091
|
+
appendPayload(container, {
|
|
1092
|
+
schemaVersion: 1,
|
|
1093
|
+
appId: "app1",
|
|
1094
|
+
cells: [
|
|
1095
|
+
createPayloadCell({
|
|
1096
|
+
code: "payload_first = True",
|
|
1097
|
+
outputHtml: "<div>payload first</div>",
|
|
1098
|
+
}),
|
|
1099
|
+
createPayloadCell({
|
|
1100
|
+
code: "payload_second = True",
|
|
1101
|
+
outputHtml: "<div>payload second</div>",
|
|
1102
|
+
}),
|
|
1103
|
+
],
|
|
1104
|
+
});
|
|
1105
|
+
|
|
1106
|
+
const result = parseMarimoIslandApps(container);
|
|
1107
|
+
|
|
1108
|
+
expect(result[0].cells.map((cell) => cell.code)).toEqual([
|
|
1109
|
+
"payload_first = True",
|
|
1110
|
+
"payload_second = True",
|
|
1111
|
+
]);
|
|
1112
|
+
expect(first.getAttribute(ISLAND_DATA_ATTRIBUTES.CELL_IDX)).toBe("0");
|
|
1113
|
+
expect(second.getAttribute(ISLAND_DATA_ATTRIBUTES.CELL_IDX)).toBe("1");
|
|
1114
|
+
});
|
|
531
1115
|
});
|