@live-react-islands/core 0.1.0
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/LICENSE +21 -0
- package/dist/IndividualIslandRenderer.d.ts +4 -0
- package/dist/IndividualIslandRenderer.d.ts.map +1 -0
- package/dist/IslandRenderer.d.ts +11 -0
- package/dist/IslandRenderer.d.ts.map +1 -0
- package/dist/PortalIslandsRenderer.d.ts +4 -0
- package/dist/PortalIslandsRenderer.d.ts.map +1 -0
- package/dist/PortalIslandsRenderer.test.d.ts +2 -0
- package/dist/PortalIslandsRenderer.test.d.ts.map +1 -0
- package/dist/client/IndividualIslandRenderer.d.ts +14 -0
- package/dist/client/IndividualIslandRenderer.d.ts.map +1 -0
- package/dist/client/PortalIslandsRenderer.d.ts +4 -0
- package/dist/client/PortalIslandsRenderer.d.ts.map +1 -0
- package/dist/client/islandsManager.d.ts +23 -0
- package/dist/client/islandsManager.d.ts.map +1 -0
- package/dist/client.d.ts +23 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/context.d.ts +7 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/createHooks.d.ts +25 -0
- package/dist/createHooks.d.ts.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.esm.js +680 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +684 -0
- package/dist/index.js.map +1 -0
- package/dist/island-manager.d.ts +59 -0
- package/dist/island-manager.d.ts.map +1 -0
- package/dist/islandsManager.d.ts +32 -0
- package/dist/islandsManager.d.ts.map +1 -0
- package/dist/islandsManager.test.d.ts +2 -0
- package/dist/islandsManager.test.d.ts.map +1 -0
- package/dist/server.d.ts +18 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/setupTests.d.ts +2 -0
- package/dist/setupTests.d.ts.map +1 -0
- package/dist/ssr.d.ts +15 -0
- package/dist/ssr.d.ts.map +1 -0
- package/dist/ssr.esm.js +92 -0
- package/dist/ssr.esm.js.map +1 -0
- package/dist/ssr.js +94 -0
- package/dist/ssr.js.map +1 -0
- package/dist/types.d.ts +65 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/useForm.d.ts +60 -0
- package/dist/useForm.d.ts.map +1 -0
- package/dist/useIslandStream.d.ts +15 -0
- package/dist/useIslandStream.d.ts.map +1 -0
- package/dist/useStream.d.ts +9 -0
- package/dist/useStream.d.ts.map +1 -0
- package/package.json +66 -0
- package/src/IndividualIslandRenderer.tsx +80 -0
- package/src/PortalIslandsRenderer.test.tsx +304 -0
- package/src/PortalIslandsRenderer.tsx +118 -0
- package/src/context.ts +9 -0
- package/src/createHooks.ts +341 -0
- package/src/index.ts +4 -0
- package/src/islandsManager.test.ts +454 -0
- package/src/islandsManager.ts +356 -0
- package/src/setupTests.ts +1 -0
- package/src/ssr.ts +118 -0
- package/src/types.ts +91 -0
- package/src/useForm.ts +253 -0
- package/src/useStream.ts +36 -0
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import ReactDOM from "react-dom/client";
|
|
3
|
+
import IslandsManager, { SHARED_ROOT_ID } from "./islandsManager";
|
|
4
|
+
import type {
|
|
5
|
+
SharedIslandsRendererProps,
|
|
6
|
+
IndividualIslandsRendererProps,
|
|
7
|
+
IslandData,
|
|
8
|
+
} from "./types";
|
|
9
|
+
|
|
10
|
+
jest.mock("react-dom/client");
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
jest.spyOn(console, "log").mockImplementation(() => {});
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
document.body.innerHTML = "";
|
|
18
|
+
jest.restoreAllMocks();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe("IslandsManager.initialize", () => {
|
|
22
|
+
let mockRoot: any;
|
|
23
|
+
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
jest.clearAllMocks();
|
|
26
|
+
mockRoot = { render: jest.fn() };
|
|
27
|
+
(ReactDOM.createRoot as jest.Mock).mockReturnValue(mockRoot);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("creates shared root element in DOM", () => {
|
|
31
|
+
IslandsManager.initialize({});
|
|
32
|
+
|
|
33
|
+
const rootElement = document.getElementById(SHARED_ROOT_ID);
|
|
34
|
+
expect(rootElement).toBeTruthy();
|
|
35
|
+
expect(rootElement?.style.display).toBe("none");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("reuses existing shared root element", () => {
|
|
39
|
+
const existingRoot = document.createElement("div");
|
|
40
|
+
existingRoot.id = SHARED_ROOT_ID;
|
|
41
|
+
document.body.appendChild(existingRoot);
|
|
42
|
+
|
|
43
|
+
IslandsManager.initialize({});
|
|
44
|
+
|
|
45
|
+
const allRoots = document.querySelectorAll(`#${SHARED_ROOT_ID}`);
|
|
46
|
+
expect(allRoots.length).toBe(1);
|
|
47
|
+
expect(allRoots[0]).toBe(existingRoot);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("creates React root with shared root element", () => {
|
|
51
|
+
IslandsManager.initialize({});
|
|
52
|
+
|
|
53
|
+
expect(ReactDOM.createRoot).toHaveBeenCalledTimes(1);
|
|
54
|
+
const rootElement = document.getElementById(SHARED_ROOT_ID);
|
|
55
|
+
expect(ReactDOM.createRoot).toHaveBeenCalledWith(rootElement);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("storeAccess returns null for non-existent props and globals", () => {
|
|
59
|
+
IslandsManager.initialize({});
|
|
60
|
+
|
|
61
|
+
const renderCall = mockRoot.render.mock.calls[0][0];
|
|
62
|
+
const storeAccess = renderCall.props.children.props.children.props.storeAccess;
|
|
63
|
+
|
|
64
|
+
expect(storeAccess.getProps("non-existent")).toBeNull();
|
|
65
|
+
expect(storeAccess.getGlobals()).toBeNull();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("renders SharedIslandsRenderer with storeAccess", () => {
|
|
69
|
+
IslandsManager.initialize({});
|
|
70
|
+
|
|
71
|
+
expect(mockRoot.render).toHaveBeenCalledTimes(1);
|
|
72
|
+
const renderCall = mockRoot.render.mock.calls[0][0];
|
|
73
|
+
|
|
74
|
+
expect(renderCall.type).toBeDefined();
|
|
75
|
+
|
|
76
|
+
const rendererElement = renderCall.props.children.props.children;
|
|
77
|
+
expect(rendererElement.props.storeAccess).toBeDefined();
|
|
78
|
+
expect(rendererElement.props.storeAccess.subscribeToProps).toBeInstanceOf(Function);
|
|
79
|
+
expect(rendererElement.props.storeAccess.subscribeToGlobals).toBeInstanceOf(Function);
|
|
80
|
+
expect(rendererElement.props.storeAccess.subscribeToSharedIslands).toBeInstanceOf(Function);
|
|
81
|
+
expect(rendererElement.props.storeAccess.getProps).toBeInstanceOf(Function);
|
|
82
|
+
expect(rendererElement.props.storeAccess.getGlobals).toBeInstanceOf(Function);
|
|
83
|
+
expect(rendererElement.props.storeAccess.getSharedIslands).toBeInstanceOf(Function);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("uses custom SharedContextProvider when provided", () => {
|
|
87
|
+
const CustomProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => children;
|
|
88
|
+
CustomProvider.displayName = "CustomProvider";
|
|
89
|
+
|
|
90
|
+
IslandsManager.initialize({ SharedContextProvider: CustomProvider });
|
|
91
|
+
|
|
92
|
+
const renderCall = mockRoot.render.mock.calls[0][0];
|
|
93
|
+
const contextProviderWrapper = renderCall.props.children.type;
|
|
94
|
+
expect(contextProviderWrapper.displayName).toContain("CustomProvider");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("uses custom SharedIslandsRenderer when provided", () => {
|
|
98
|
+
const CustomRenderer: React.FC<SharedIslandsRendererProps> = () => null;
|
|
99
|
+
|
|
100
|
+
IslandsManager.initialize({ SharedIslandsRenderer: CustomRenderer });
|
|
101
|
+
|
|
102
|
+
const renderCall = mockRoot.render.mock.calls[0][0];
|
|
103
|
+
const rendererElement = renderCall.props.children.props.children;
|
|
104
|
+
expect(rendererElement.type).toBe(CustomRenderer);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe("IslandsManager.mountIsland", () => {
|
|
109
|
+
const createMockIslandData = (overrides?: Partial<IslandData>): IslandData => {
|
|
110
|
+
const el = document.createElement("div");
|
|
111
|
+
el.id = "test-island";
|
|
112
|
+
document.body.appendChild(el);
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
id: "test-island",
|
|
116
|
+
el,
|
|
117
|
+
Component: () => null,
|
|
118
|
+
ssrStrategy: "none",
|
|
119
|
+
pushEvent: jest.fn(),
|
|
120
|
+
hydrationData: null,
|
|
121
|
+
globalKeys: [],
|
|
122
|
+
...overrides,
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
beforeEach(() => {
|
|
127
|
+
jest.clearAllMocks();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
describe("shared islands", () => {
|
|
131
|
+
test("adds island to sharedIslandsStore", () => {
|
|
132
|
+
const mockRoot = { render: jest.fn() };
|
|
133
|
+
(ReactDOM.createRoot as jest.Mock).mockReturnValue(mockRoot);
|
|
134
|
+
|
|
135
|
+
const state = IslandsManager.initialize({});
|
|
136
|
+
const renderCall = mockRoot.render.mock.calls[0][0];
|
|
137
|
+
const storeAccess = renderCall.props.children.props.children.props.storeAccess;
|
|
138
|
+
|
|
139
|
+
const islandData = createMockIslandData({ ssrStrategy: "none" });
|
|
140
|
+
IslandsManager.mountIsland(state, islandData);
|
|
141
|
+
|
|
142
|
+
const sharedIslands = storeAccess.getSharedIslands();
|
|
143
|
+
expect(sharedIslands[islandData.id]).toBe(islandData);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("notifies sharedIslands listeners when island is added", () => {
|
|
147
|
+
const mockRoot = { render: jest.fn() };
|
|
148
|
+
(ReactDOM.createRoot as jest.Mock).mockReturnValue(mockRoot);
|
|
149
|
+
|
|
150
|
+
const state = IslandsManager.initialize({});
|
|
151
|
+
const renderCall = mockRoot.render.mock.calls[0][0];
|
|
152
|
+
const storeAccess = renderCall.props.children.props.children.props.storeAccess;
|
|
153
|
+
|
|
154
|
+
const listener = jest.fn();
|
|
155
|
+
storeAccess.subscribeToSharedIslands(listener);
|
|
156
|
+
|
|
157
|
+
const islandData = createMockIslandData({ ssrStrategy: "none" });
|
|
158
|
+
IslandsManager.mountIsland(state, islandData);
|
|
159
|
+
|
|
160
|
+
expect(listener).toHaveBeenCalledTimes(1);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("handles both 'none' and 'overwrite' strategies via shared store", () => {
|
|
164
|
+
const mockRoot = { render: jest.fn() };
|
|
165
|
+
(ReactDOM.createRoot as jest.Mock).mockReturnValue(mockRoot);
|
|
166
|
+
|
|
167
|
+
const state = IslandsManager.initialize({});
|
|
168
|
+
const renderCall = mockRoot.render.mock.calls[0][0];
|
|
169
|
+
const storeAccess = renderCall.props.children.props.children.props.storeAccess;
|
|
170
|
+
|
|
171
|
+
const noneIsland = createMockIslandData({ id: "none-island", ssrStrategy: "none" });
|
|
172
|
+
const overwriteIsland = createMockIslandData({ id: "overwrite-island", ssrStrategy: "overwrite" });
|
|
173
|
+
|
|
174
|
+
IslandsManager.mountIsland(state, noneIsland);
|
|
175
|
+
IslandsManager.mountIsland(state, overwriteIsland);
|
|
176
|
+
|
|
177
|
+
const sharedIslands = storeAccess.getSharedIslands();
|
|
178
|
+
expect(sharedIslands["none-island"]).toBe(noneIsland);
|
|
179
|
+
expect(sharedIslands["overwrite-island"]).toBe(overwriteIsland);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe("individual islands (hydrate_root)", () => {
|
|
184
|
+
test("calls ReactDOM.hydrateRoot for 'hydrate_root' strategy", () => {
|
|
185
|
+
const mockRoot = { render: jest.fn() };
|
|
186
|
+
const mockHydrateRoot = { unmount: jest.fn() };
|
|
187
|
+
(ReactDOM.createRoot as jest.Mock).mockReturnValue(mockRoot);
|
|
188
|
+
(ReactDOM.hydrateRoot as jest.Mock).mockReturnValue(mockHydrateRoot);
|
|
189
|
+
|
|
190
|
+
const state = IslandsManager.initialize({});
|
|
191
|
+
const islandData = createMockIslandData({
|
|
192
|
+
ssrStrategy: "hydrate_root",
|
|
193
|
+
hydrationData: { props: {}, globals: {} },
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
IslandsManager.mountIsland(state, islandData);
|
|
197
|
+
|
|
198
|
+
expect(ReactDOM.hydrateRoot).toHaveBeenCalledWith(
|
|
199
|
+
islandData.el,
|
|
200
|
+
expect.anything()
|
|
201
|
+
);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test("passes island data and storeAccess to renderer", () => {
|
|
205
|
+
const mockRoot = { render: jest.fn() };
|
|
206
|
+
const mockHydrateRoot = { unmount: jest.fn() };
|
|
207
|
+
(ReactDOM.createRoot as jest.Mock).mockReturnValue(mockRoot);
|
|
208
|
+
(ReactDOM.hydrateRoot as jest.Mock).mockReturnValue(mockHydrateRoot);
|
|
209
|
+
|
|
210
|
+
const state = IslandsManager.initialize({});
|
|
211
|
+
const islandData = createMockIslandData({
|
|
212
|
+
ssrStrategy: "hydrate_root",
|
|
213
|
+
hydrationData: { props: { foo: "bar" }, globals: { user: "test" } },
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
IslandsManager.mountIsland(state, islandData);
|
|
217
|
+
|
|
218
|
+
const hydrateCall = (ReactDOM.hydrateRoot as jest.Mock).mock.calls[0];
|
|
219
|
+
expect(hydrateCall[0]).toBe(islandData.el);
|
|
220
|
+
|
|
221
|
+
const rendererElement = hydrateCall[1].props.children;
|
|
222
|
+
expect(rendererElement.props.data).toBe(islandData);
|
|
223
|
+
expect(rendererElement.props.storeAccess).toBeDefined();
|
|
224
|
+
expect(rendererElement.props.storeAccess.getProps).toBeInstanceOf(Function);
|
|
225
|
+
expect(rendererElement.props.storeAccess.getGlobals).toBeInstanceOf(Function);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test("uses custom IndividualIslandRenderer when provided", () => {
|
|
229
|
+
const mockRoot = { render: jest.fn() };
|
|
230
|
+
const mockHydrateRoot = { unmount: jest.fn() };
|
|
231
|
+
(ReactDOM.createRoot as jest.Mock).mockReturnValue(mockRoot);
|
|
232
|
+
(ReactDOM.hydrateRoot as jest.Mock).mockReturnValue(mockHydrateRoot);
|
|
233
|
+
|
|
234
|
+
const CustomIndividualRenderer: React.FC<IndividualIslandsRendererProps> = () => null;
|
|
235
|
+
|
|
236
|
+
const state = IslandsManager.initialize({});
|
|
237
|
+
const islandData = createMockIslandData({
|
|
238
|
+
ssrStrategy: "hydrate_root",
|
|
239
|
+
hydrationData: { props: {}, globals: {} },
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
IslandsManager.mountIsland(state, islandData, CustomIndividualRenderer);
|
|
243
|
+
|
|
244
|
+
const hydrateCall = (ReactDOM.hydrateRoot as jest.Mock).mock.calls[0];
|
|
245
|
+
const rendererElement = hydrateCall[1].props.children;
|
|
246
|
+
const wrappedRenderer = rendererElement.type;
|
|
247
|
+
expect(wrappedRenderer.displayName).toContain("HydratedRoot");
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
describe("IslandsManager.updateIslandProps", () => {
|
|
253
|
+
let state: any;
|
|
254
|
+
let storeAccess: any;
|
|
255
|
+
|
|
256
|
+
beforeEach(() => {
|
|
257
|
+
jest.clearAllMocks();
|
|
258
|
+
const mockRoot = { render: jest.fn() };
|
|
259
|
+
(ReactDOM.createRoot as jest.Mock).mockReturnValue(mockRoot);
|
|
260
|
+
|
|
261
|
+
state = IslandsManager.initialize({});
|
|
262
|
+
const renderCall = mockRoot.render.mock.calls[0][0];
|
|
263
|
+
storeAccess = renderCall.props.children.props.children.props.storeAccess;
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test("makes props available via storeAccess.getProps", () => {
|
|
267
|
+
const props = { foo: "bar", count: 42 };
|
|
268
|
+
IslandsManager.updateIslandProps(state, "island-1", props);
|
|
269
|
+
|
|
270
|
+
expect(storeAccess.getProps("island-1")).toEqual(props);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
test("notifies subscribers with new props", () => {
|
|
274
|
+
const listener = jest.fn();
|
|
275
|
+
let capturedProps;
|
|
276
|
+
|
|
277
|
+
storeAccess.subscribeToProps(() => {
|
|
278
|
+
listener();
|
|
279
|
+
capturedProps = storeAccess.getProps("island-1");
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
const newProps = { foo: "bar" };
|
|
283
|
+
IslandsManager.updateIslandProps(state, "island-1", newProps);
|
|
284
|
+
|
|
285
|
+
expect(listener).toHaveBeenCalledTimes(1);
|
|
286
|
+
expect(capturedProps).toEqual(newProps);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
test("overwrites previous props for same island", () => {
|
|
290
|
+
IslandsManager.updateIslandProps(state, "island-1", { foo: "bar" });
|
|
291
|
+
IslandsManager.updateIslandProps(state, "island-1", { foo: "baz" });
|
|
292
|
+
|
|
293
|
+
expect(storeAccess.getProps("island-1")).toEqual({ foo: "baz" });
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
describe("IslandsManager.updateGlobals", () => {
|
|
298
|
+
let state: any;
|
|
299
|
+
let storeAccess: any;
|
|
300
|
+
|
|
301
|
+
beforeEach(() => {
|
|
302
|
+
jest.clearAllMocks();
|
|
303
|
+
const mockRoot = { render: jest.fn() };
|
|
304
|
+
(ReactDOM.createRoot as jest.Mock).mockReturnValue(mockRoot);
|
|
305
|
+
|
|
306
|
+
state = IslandsManager.initialize({});
|
|
307
|
+
const renderCall = mockRoot.render.mock.calls[0][0];
|
|
308
|
+
storeAccess = renderCall.props.children.props.children.props.storeAccess;
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
test("makes globals available via storeAccess.getGlobals", () => {
|
|
312
|
+
const globals = { user: "test", theme: "dark", __version: 0 };
|
|
313
|
+
IslandsManager.updateGlobals(state, globals);
|
|
314
|
+
|
|
315
|
+
expect(storeAccess.getGlobals()).toEqual(globals);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
test("notifies subscribers with new globals", () => {
|
|
319
|
+
const listener = jest.fn();
|
|
320
|
+
let capturedGlobals;
|
|
321
|
+
|
|
322
|
+
storeAccess.subscribeToGlobals(() => {
|
|
323
|
+
listener();
|
|
324
|
+
capturedGlobals = storeAccess.getGlobals();
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
const newGlobals = { user: "test", __version: 0 };
|
|
328
|
+
IslandsManager.updateGlobals(state, newGlobals);
|
|
329
|
+
|
|
330
|
+
expect(listener).toHaveBeenCalledTimes(1);
|
|
331
|
+
expect(capturedGlobals).toEqual(newGlobals);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
test("storeAccess continues to see updates after multiple changes", () => {
|
|
335
|
+
const globals1 = { user: "test1", __version: 0 };
|
|
336
|
+
const globals2 = { user: "test2", __version: 1 };
|
|
337
|
+
|
|
338
|
+
IslandsManager.updateGlobals(state, globals1);
|
|
339
|
+
expect(storeAccess.getGlobals()).toEqual(globals1);
|
|
340
|
+
|
|
341
|
+
IslandsManager.updateGlobals(state, globals2);
|
|
342
|
+
expect(storeAccess.getGlobals()).toEqual({ ...globals1, ...globals2 });
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
describe("IslandsManager.resetGlobals", () => {
|
|
347
|
+
let state: any;
|
|
348
|
+
let storeAccess: any;
|
|
349
|
+
|
|
350
|
+
beforeEach(() => {
|
|
351
|
+
jest.clearAllMocks();
|
|
352
|
+
const mockRoot = { render: jest.fn() };
|
|
353
|
+
(ReactDOM.createRoot as jest.Mock).mockReturnValue(mockRoot);
|
|
354
|
+
|
|
355
|
+
state = IslandsManager.initialize({});
|
|
356
|
+
const renderCall = mockRoot.render.mock.calls[0][0];
|
|
357
|
+
storeAccess = renderCall.props.children.props.children.props.storeAccess;
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
test("clears globals via storeAccess.getGlobals", () => {
|
|
361
|
+
const globals = { user: "test", theme: "dark", __version: 5 };
|
|
362
|
+
IslandsManager.updateGlobals(state, globals);
|
|
363
|
+
expect(storeAccess.getGlobals()).toEqual(globals);
|
|
364
|
+
|
|
365
|
+
IslandsManager.resetGlobals(state);
|
|
366
|
+
expect(storeAccess.getGlobals()).toBeNull();
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
test("allows new globals with lower version after reset", () => {
|
|
370
|
+
const globals1 = { user: "test1", __version: 5 };
|
|
371
|
+
IslandsManager.updateGlobals(state, globals1);
|
|
372
|
+
|
|
373
|
+
IslandsManager.resetGlobals(state);
|
|
374
|
+
|
|
375
|
+
const globals2 = { user: "test2", __version: 0 };
|
|
376
|
+
IslandsManager.updateGlobals(state, globals2);
|
|
377
|
+
expect(storeAccess.getGlobals()).toEqual(globals2);
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
describe("IslandsManager.unmountIsland", () => {
|
|
382
|
+
const createMockIslandData = (overrides?: Partial<IslandData>): IslandData => {
|
|
383
|
+
const el = document.createElement("div");
|
|
384
|
+
el.id = overrides?.id || "test-island";
|
|
385
|
+
document.body.appendChild(el);
|
|
386
|
+
|
|
387
|
+
return {
|
|
388
|
+
id: overrides?.id || "test-island",
|
|
389
|
+
el,
|
|
390
|
+
Component: () => null,
|
|
391
|
+
ssrStrategy: "none",
|
|
392
|
+
pushEvent: jest.fn(),
|
|
393
|
+
hydrationData: null,
|
|
394
|
+
globalKeys: [],
|
|
395
|
+
...overrides,
|
|
396
|
+
};
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
beforeEach(() => {
|
|
400
|
+
jest.clearAllMocks();
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
test("props no longer accessible via storeAccess.getProps", () => {
|
|
404
|
+
const mockRoot = { render: jest.fn() };
|
|
405
|
+
(ReactDOM.createRoot as jest.Mock).mockReturnValue(mockRoot);
|
|
406
|
+
|
|
407
|
+
const state = IslandsManager.initialize({});
|
|
408
|
+
const renderCall = mockRoot.render.mock.calls[0][0];
|
|
409
|
+
const storeAccess = renderCall.props.children.props.children.props.storeAccess;
|
|
410
|
+
|
|
411
|
+
IslandsManager.updateIslandProps(state, "island-1", { foo: "bar" });
|
|
412
|
+
expect(storeAccess.getProps("island-1")).toEqual({ foo: "bar" });
|
|
413
|
+
|
|
414
|
+
IslandsManager.unmountIsland(state, "island-1");
|
|
415
|
+
expect(storeAccess.getProps("island-1")).toBeNull();
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
test("removes island from sharedIslandsStore for shared islands", () => {
|
|
419
|
+
const mockRoot = { render: jest.fn() };
|
|
420
|
+
(ReactDOM.createRoot as jest.Mock).mockReturnValue(mockRoot);
|
|
421
|
+
|
|
422
|
+
const state = IslandsManager.initialize({});
|
|
423
|
+
const renderCall = mockRoot.render.mock.calls[0][0];
|
|
424
|
+
const storeAccess = renderCall.props.children.props.children.props.storeAccess;
|
|
425
|
+
|
|
426
|
+
const islandData = createMockIslandData({ id: "island-1", ssrStrategy: "none" });
|
|
427
|
+
IslandsManager.mountIsland(state, islandData);
|
|
428
|
+
|
|
429
|
+
expect(storeAccess.getSharedIslands()["island-1"]).toBe(islandData);
|
|
430
|
+
|
|
431
|
+
IslandsManager.unmountIsland(state, "island-1");
|
|
432
|
+
|
|
433
|
+
expect(storeAccess.getSharedIslands()["island-1"]).toBeUndefined();
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
test("calls unmount on React root for individual islands", () => {
|
|
437
|
+
const mockRoot = { render: jest.fn() };
|
|
438
|
+
const mockHydrateRoot = { unmount: jest.fn() };
|
|
439
|
+
(ReactDOM.createRoot as jest.Mock).mockReturnValue(mockRoot);
|
|
440
|
+
(ReactDOM.hydrateRoot as jest.Mock).mockReturnValue(mockHydrateRoot);
|
|
441
|
+
|
|
442
|
+
const state = IslandsManager.initialize({});
|
|
443
|
+
const islandData = createMockIslandData({
|
|
444
|
+
id: "hydrate-island",
|
|
445
|
+
ssrStrategy: "hydrate_root",
|
|
446
|
+
hydrationData: { props: {}, globals: {} },
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
const mountedState = IslandsManager.mountIsland(state, islandData);
|
|
450
|
+
IslandsManager.unmountIsland(mountedState, "hydrate-island");
|
|
451
|
+
|
|
452
|
+
expect(mockHydrateRoot.unmount).toHaveBeenCalledTimes(1);
|
|
453
|
+
});
|
|
454
|
+
});
|