@ripple-ts/compat-react 0.2.167 → 0.2.169

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ripple-ts/compat-react",
3
- "version": "0.2.167",
3
+ "version": "0.2.169",
4
4
  "description": "Ripple compatibility layer for React",
5
5
  "main": "src/index.js",
6
6
  "author": "Dominic Gannaway",
@@ -17,7 +17,7 @@
17
17
  "dependencies": {
18
18
  "react": "^19.2.0",
19
19
  "react-dom": "^19.2.0",
20
- "ripple": "0.2.167"
20
+ "ripple": "0.2.169"
21
21
  },
22
22
  "devDependencies": {
23
23
  "@types/react": "^19.2.2",
package/src/index.js CHANGED
@@ -5,6 +5,7 @@ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
5
  import {
6
6
  useSyncExternalStore,
7
7
  useLayoutEffect,
8
+ useEffect,
8
9
  useRef,
9
10
  useState,
10
11
  Component,
@@ -24,6 +25,8 @@ import {
24
25
  suspend,
25
26
  TRY_BLOCK,
26
27
  destroy_block,
28
+ root,
29
+ init_operations,
27
30
  } from 'ripple/internal/client';
28
31
  import { Context } from 'ripple';
29
32
 
@@ -202,7 +205,7 @@ export function Ripple({ component, props }) {
202
205
  }
203
206
  const portals = portals_ref.current;
204
207
 
205
- useLayoutEffect(() => {
208
+ useEffect(() => {
206
209
  const span = /** @type {HTMLSpanElement | null} */ (ref.current);
207
210
  if (span === null) {
208
211
  return;
@@ -210,6 +213,12 @@ export function Ripple({ component, props }) {
210
213
  const frag = document.createDocumentFragment();
211
214
  const anchor = document.createTextNode('');
212
215
  const block = get_block_from_dom(span);
216
+
217
+ if (block === null) {
218
+ throw new Error(
219
+ 'Ripple component must be rendered inside a Ripple root. If you are using Ripple inside a React app, ensure your React root contains <RippleRoot>.',
220
+ );
221
+ }
213
222
  const tracked_props = (tracked_props_ref.current = tracked(props || {}, block));
214
223
  const proxied_props = proxy_props(() => get_tracked(tracked_props));
215
224
  frag.append(anchor);
@@ -230,7 +239,7 @@ export function Ripple({ component, props }) {
230
239
  };
231
240
  }, [component]);
232
241
 
233
- useLayoutEffect(() => {
242
+ useEffect(() => {
234
243
  set(/** @type {any} */ (tracked_props_ref.current), props || {});
235
244
  }, [props]);
236
245
 
@@ -241,3 +250,23 @@ export function Ripple({ component, props }) {
241
250
  ],
242
251
  });
243
252
  }
253
+
254
+ /**
255
+ * @param {{ children: React.ReactNode }} props
256
+ */
257
+ export function RippleRoot({ children }) {
258
+ const ref = useRef(null);
259
+
260
+ useLayoutEffect(() => {
261
+ const target_element = /** @type {HTMLSpanElement | null} */ (ref.current);
262
+ if (target_element === null) {
263
+ return;
264
+ }
265
+ init_operations();
266
+ const e = root(() => {});
267
+ // @ts-ignore
268
+ target_element.__ripple_block = e;
269
+ }, []);
270
+
271
+ return jsx('span', { ref, style: { display: 'contents' }, children });
272
+ }
@@ -1,363 +1,421 @@
1
1
  import { track, flushSync } from 'ripple';
2
2
  import { act, createContext, useContext } from 'react';
3
3
  import { Ripple } from '@ripple-ts/compat-react';
4
+ import { createRoot } from 'react-dom/client';
5
+ import { jsx, jsxs } from 'react/jsx-runtime';
6
+ import { RippleRoot } from '@ripple-ts/compat-react';
4
7
 
5
8
  describe('compat-react', () => {
6
- it('should render basic React JSX inside tsx:react tags', async () => {
7
- component App() {
8
- <div>
9
- <h1>{'Hello from Ripple'}</h1>
10
- <tsx:react>
11
- <div className="react-content">Hello from React</div>
12
- </tsx:react>
13
- </div>
14
- }
9
+ describe('tsx:react integration', () => {
10
+ it('should render basic React JSX inside tsx:react tags', async () => {
11
+ component App() {
12
+ <div>
13
+ <h1>{'Hello from Ripple'}</h1>
14
+ <tsx:react>
15
+ <div className="react-content">Hello from React</div>
16
+ </tsx:react>
17
+ </div>
18
+ }
19
+
20
+ await act(async () => {
21
+ render(App);
22
+ });
15
23
 
16
- await act(async () => {
17
- render(App);
24
+ const rippleHeading = container.querySelector('h1');
25
+ const reactDiv = container.querySelector('.react-content');
26
+ expect(rippleHeading).toBeTruthy();
27
+ expect(rippleHeading.textContent).toBe('Hello from Ripple');
28
+ expect(reactDiv).toBeTruthy();
29
+ expect(reactDiv.textContent).toBe('Hello from React');
18
30
  });
19
31
 
20
- const rippleHeading = container.querySelector('h1');
21
- const reactDiv = container.querySelector('.react-content');
22
- expect(rippleHeading).toBeTruthy();
23
- expect(rippleHeading.textContent).toBe('Hello from Ripple');
24
- expect(reactDiv).toBeTruthy();
25
- expect(reactDiv.textContent).toBe('Hello from React');
26
- });
32
+ it('should render React fragments inside tsx:react tags', async () => {
33
+ component App() {
34
+ <div>
35
+ <tsx:react>
36
+ <>
37
+ <span className="first">First</span>
38
+ <span className="second">Second</span>
39
+ </>
40
+ </tsx:react>
41
+ </div>
42
+ }
27
43
 
28
- it('should render React fragments inside tsx:react tags', async () => {
29
- component App() {
30
- <div>
31
- <tsx:react>
32
- <>
33
- <span className="first">First</span>
34
- <span className="second">Second</span>
35
- </>
36
- </tsx:react>
37
- </div>
38
- }
44
+ await act(async () => {
45
+ render(App);
46
+ });
39
47
 
40
- await act(async () => {
41
- render(App);
48
+ const first = container.querySelector('.first');
49
+ const second = container.querySelector('.second');
50
+ expect(first).toBeTruthy();
51
+ expect(first.textContent).toBe('First');
52
+ expect(second).toBeTruthy();
53
+ expect(second.textContent).toBe('Second');
42
54
  });
43
55
 
44
- const first = container.querySelector('.first');
45
- const second = container.querySelector('.second');
46
- expect(first).toBeTruthy();
47
- expect(first.textContent).toBe('First');
48
- expect(second).toBeTruthy();
49
- expect(second.textContent).toBe('Second');
50
- });
51
-
52
- it('should render nested React components', async () => {
53
- component App() {
54
- <div>
55
- <tsx:react>
56
- <div className="wrapper">
57
- <div className="inner">
58
- <span className="content">Nested content</span>
56
+ it('should render nested React components', async () => {
57
+ component App() {
58
+ <div>
59
+ <tsx:react>
60
+ <div className="wrapper">
61
+ <div className="inner">
62
+ <span className="content">Nested content</span>
63
+ </div>
59
64
  </div>
60
- </div>
61
- </tsx:react>
62
- </div>
63
- }
65
+ </tsx:react>
66
+ </div>
67
+ }
64
68
 
65
- await act(async () => {
66
- render(App);
69
+ await act(async () => {
70
+ render(App);
71
+ });
72
+
73
+ const wrapper = container.querySelector('.wrapper');
74
+ const inner = container.querySelector('.inner');
75
+ const content = container.querySelector('.content');
76
+ expect(wrapper).toBeTruthy();
77
+ expect(inner).toBeTruthy();
78
+ expect(content).toBeTruthy();
79
+ expect(content.textContent).toBe('Nested content');
67
80
  });
68
81
 
69
- const wrapper = container.querySelector('.wrapper');
70
- const inner = container.querySelector('.inner');
71
- const content = container.querySelector('.content');
72
- expect(wrapper).toBeTruthy();
73
- expect(inner).toBeTruthy();
74
- expect(content).toBeTruthy();
75
- expect(content.textContent).toBe('Nested content');
76
- });
77
-
78
- it('should mix Ripple and React content', async () => {
79
- component App() {
80
- <div class="container">
81
- <div class="ripple">{'This is Ripple'}</div>
82
- <tsx:react>
83
- <div className="react">This is React</div>
84
- </tsx:react>
85
- <div class="ripple-2">{'Back to Ripple'}</div>
86
- </div>
87
- }
82
+ it('should mix Ripple and React content', async () => {
83
+ component App() {
84
+ <div class="container">
85
+ <div class="ripple">{'This is Ripple'}</div>
86
+ <tsx:react>
87
+ <div className="react">This is React</div>
88
+ </tsx:react>
89
+ <div class="ripple-2">{'Back to Ripple'}</div>
90
+ </div>
91
+ }
88
92
 
89
- await act(async () => {
90
- render(App);
93
+ await act(async () => {
94
+ render(App);
95
+ });
96
+
97
+ const rippleDiv = container.querySelector('.ripple');
98
+ const reactDiv = container.querySelector('.react');
99
+ const rippleDiv2 = container.querySelector('.ripple-2');
100
+ expect(rippleDiv).toBeTruthy();
101
+ expect(rippleDiv.textContent).toBe('This is Ripple');
102
+ expect(reactDiv).toBeTruthy();
103
+ expect(reactDiv.textContent).toBe('This is React');
104
+ expect(rippleDiv2).toBeTruthy();
105
+ expect(rippleDiv2.textContent).toBe('Back to Ripple');
91
106
  });
92
107
 
93
- const rippleDiv = container.querySelector('.ripple');
94
- const reactDiv = container.querySelector('.react');
95
- const rippleDiv2 = container.querySelector('.ripple-2');
96
- expect(rippleDiv).toBeTruthy();
97
- expect(rippleDiv.textContent).toBe('This is Ripple');
98
- expect(reactDiv).toBeTruthy();
99
- expect(reactDiv.textContent).toBe('This is React');
100
- expect(rippleDiv2).toBeTruthy();
101
- expect(rippleDiv2.textContent).toBe('Back to Ripple');
102
- });
103
-
104
- it('should handle multiple tsx:react blocks', async () => {
105
- component App() {
106
- <div>
107
- <tsx:react>
108
- <div className="react-1">React Block 1</div>
109
- </tsx:react>
110
- <div class="ripple-middle">{'Ripple in between'}</div>
111
- <tsx:react>
112
- <div className="react-2">React Block 2</div>
113
- </tsx:react>
114
- </div>
115
- }
108
+ it('should handle multiple tsx:react blocks', async () => {
109
+ component App() {
110
+ <div>
111
+ <tsx:react>
112
+ <div className="react-1">React Block 1</div>
113
+ </tsx:react>
114
+ <div class="ripple-middle">{'Ripple in between'}</div>
115
+ <tsx:react>
116
+ <div className="react-2">React Block 2</div>
117
+ </tsx:react>
118
+ </div>
119
+ }
116
120
 
117
- await act(async () => {
118
- render(App);
121
+ await act(async () => {
122
+ render(App);
123
+ });
124
+
125
+ const react1 = container.querySelector('.react-1');
126
+ const middle = container.querySelector('.ripple-middle');
127
+ const react2 = container.querySelector('.react-2');
128
+ expect(react1).toBeTruthy();
129
+ expect(react1.textContent).toBe('React Block 1');
130
+ expect(middle).toBeTruthy();
131
+ expect(middle.textContent).toBe('Ripple in between');
132
+ expect(react2).toBeTruthy();
133
+ expect(react2.textContent).toBe('React Block 2');
119
134
  });
120
135
 
121
- const react1 = container.querySelector('.react-1');
122
- const middle = container.querySelector('.ripple-middle');
123
- const react2 = container.querySelector('.react-2');
124
- expect(react1).toBeTruthy();
125
- expect(react1.textContent).toBe('React Block 1');
126
- expect(middle).toBeTruthy();
127
- expect(middle.textContent).toBe('Ripple in between');
128
- expect(react2).toBeTruthy();
129
- expect(react2.textContent).toBe('React Block 2');
130
- });
136
+ it('should handle React components with attributes', async () => {
137
+ component App() {
138
+ <div>
139
+ <tsx:react>
140
+ <div className="react" id="test-id">
141
+ <span>Content</span>
142
+ </div>
143
+ </tsx:react>
144
+ </div>
145
+ }
131
146
 
132
- it('should handle React components with attributes', async () => {
133
- component App() {
134
- <div>
135
- <tsx:react>
136
- <div className="react" id="test-id">
137
- <span>Content</span>
138
- </div>
139
- </tsx:react>
140
- </div>
141
- }
147
+ await act(async () => {
148
+ render(App);
149
+ });
142
150
 
143
- await act(async () => {
144
- render(App);
151
+ const reactDiv = container.querySelector('.react');
152
+ expect(reactDiv).toBeTruthy();
153
+ expect(reactDiv.id).toBe('test-id');
154
+ expect(reactDiv.querySelector('span').textContent).toBe('Content');
145
155
  });
146
156
 
147
- const reactDiv = container.querySelector('.react');
148
- expect(reactDiv).toBeTruthy();
149
- expect(reactDiv.id).toBe('test-id');
150
- expect(reactDiv.querySelector('span').textContent).toBe('Content');
151
- });
152
-
153
- it('should handle nested fragments', async () => {
154
- component App() {
155
- <div>
156
- <tsx:react>
157
- <>
158
- <div className="outer">Outer</div>
157
+ it('should handle nested fragments', async () => {
158
+ component App() {
159
+ <div>
160
+ <tsx:react>
159
161
  <>
160
- <div className="inner">Inner</div>
162
+ <div className="outer">Outer</div>
163
+ <>
164
+ <div className="inner">Inner</div>
165
+ </>
161
166
  </>
162
- </>
163
- </tsx:react>
164
- </div>
165
- }
167
+ </tsx:react>
168
+ </div>
169
+ }
166
170
 
167
- await act(async () => {
168
- render(App);
169
- });
171
+ await act(async () => {
172
+ render(App);
173
+ });
170
174
 
171
- const outer = container.querySelector('.outer');
172
- const inner = container.querySelector('.inner');
173
- expect(outer).toBeTruthy();
174
- expect(outer.textContent).toBe('Outer');
175
- expect(inner).toBeTruthy();
176
- expect(inner.textContent).toBe('Inner');
177
- });
175
+ const outer = container.querySelector('.outer');
176
+ const inner = container.querySelector('.inner');
177
+ expect(outer).toBeTruthy();
178
+ expect(outer.textContent).toBe('Outer');
179
+ expect(inner).toBeTruthy();
180
+ expect(inner.textContent).toBe('Inner');
181
+ });
178
182
 
179
- it('should handle complex nested structures', async () => {
180
- component App() {
181
- <div>
182
- <tsx:react>
183
- <div className="list">
184
- <ul>
185
- <li>Item 1</li>
186
- <li>Item 2</li>
187
- <li>Item 3</li>
188
- </ul>
189
- </div>
190
- </tsx:react>
191
- </div>
192
- }
183
+ it('should handle complex nested structures', async () => {
184
+ component App() {
185
+ <div>
186
+ <tsx:react>
187
+ <div className="list">
188
+ <ul>
189
+ <li>Item 1</li>
190
+ <li>Item 2</li>
191
+ <li>Item 3</li>
192
+ </ul>
193
+ </div>
194
+ </tsx:react>
195
+ </div>
196
+ }
193
197
 
194
- await act(async () => {
195
- render(App);
198
+ await act(async () => {
199
+ render(App);
200
+ });
201
+
202
+ const list = container.querySelector('.list');
203
+ const items = container.querySelectorAll('li');
204
+ expect(list).toBeTruthy();
205
+ expect(items.length).toBe(3);
206
+ expect(items[0].textContent).toBe('Item 1');
207
+ expect(items[1].textContent).toBe('Item 2');
208
+ expect(items[2].textContent).toBe('Item 3');
196
209
  });
197
210
 
198
- const list = container.querySelector('.list');
199
- const items = container.querySelectorAll('li');
200
- expect(list).toBeTruthy();
201
- expect(items.length).toBe(3);
202
- expect(items[0].textContent).toBe('Item 1');
203
- expect(items[1].textContent).toBe('Item 2');
204
- expect(items[2].textContent).toBe('Item 3');
205
- });
211
+ it('should handle empty fragments', async () => {
212
+ component App() {
213
+ <div>
214
+ <tsx:react>
215
+ <></>
216
+ </tsx:react>
217
+ <div class="after">{'After empty fragment'}</div>
218
+ </div>
219
+ }
206
220
 
207
- it('should handle empty fragments', async () => {
208
- component App() {
209
- <div>
210
- <tsx:react>
211
- <></>
212
- </tsx:react>
213
- <div class="after">{'After empty fragment'}</div>
214
- </div>
215
- }
221
+ await act(async () => {
222
+ render(App);
223
+ });
216
224
 
217
- await act(async () => {
218
- render(App);
225
+ const after = container.querySelector('.after');
226
+ expect(after).toBeTruthy();
227
+ expect(after.textContent).toBe('After empty fragment');
219
228
  });
220
229
 
221
- const after = container.querySelector('.after');
222
- expect(after).toBeTruthy();
223
- expect(after.textContent).toBe('After empty fragment');
224
- });
230
+ it('should work with Ripple reactivity', async () => {
231
+ component App() {
232
+ let count = track(0);
233
+ <div>
234
+ <div class="ripple-count">{@count}</div>
235
+ <button onClick={() => @count++}>{'Increment'}</button>
236
+ <tsx:react>
237
+ <div className="react-message">
238
+ {'React content is static'}
239
+ </div>
240
+ </tsx:react>
241
+ </div>
242
+ }
225
243
 
226
- it('should work with Ripple reactivity', async () => {
227
- component App() {
228
- let count = track(0);
229
- <div>
230
- <div class="ripple-count">{@count}</div>
231
- <button onClick={() => @count++}>{'Increment'}</button>
232
- <tsx:react>
233
- <div className="react-message">
234
- {'React content is static'}
235
- </div>
236
- </tsx:react>
237
- </div>
238
- }
244
+ await act(async () => {
245
+ render(App);
246
+ });
247
+
248
+ const rippleCount = container.querySelector('.ripple-count');
249
+ const button = container.querySelector('button');
250
+ const reactMessage = container.querySelector('.react-message');
251
+ expect(rippleCount.textContent).toBe('0');
252
+ expect(reactMessage.textContent).toBe('React content is static');
253
+ button.click();
254
+ flushSync();
255
+ expect(rippleCount.textContent).toBe('1');
256
+ expect(reactMessage.textContent).toBe('React content is static');
257
+ button.click();
258
+ flushSync();
259
+ expect(rippleCount.textContent).toBe('2');
260
+ });
261
+
262
+ it('should handle a call expression at the top-level of a tsx:react block', async () => {
263
+ function renderReactText() {
264
+ return 'This is rendered from a React component!';
265
+ }
239
266
 
240
- await act(async () => {
241
- render(App);
267
+ component App() {
268
+ <div>
269
+ <tsx:react>
270
+ {renderReactText()}
271
+ </tsx:react>
272
+ </div>
273
+ }
274
+
275
+ await act(async () => {
276
+ render(App);
277
+ });
278
+
279
+ const reactContent = container.querySelector('div > div');
280
+ expect(reactContent).toBeTruthy();
281
+ expect(reactContent.textContent).toBe('This is rendered from a React component!');
242
282
  });
243
283
 
244
- const rippleCount = container.querySelector('.ripple-count');
245
- const button = container.querySelector('button');
246
- const reactMessage = container.querySelector('.react-message');
247
- expect(rippleCount.textContent).toBe('0');
248
- expect(reactMessage.textContent).toBe('React content is static');
249
- button.click();
250
- flushSync();
251
- expect(rippleCount.textContent).toBe('1');
252
- expect(reactMessage.textContent).toBe('React content is static');
253
- button.click();
254
- flushSync();
255
- expect(rippleCount.textContent).toBe('2');
256
- });
284
+ it('should handle a React context propagation', async () => {
285
+ const SomeContext = createContext('Default Value');
257
286
 
258
- it('should handle a call expression at the top-level of a tsx:react block', async () => {
259
- function renderReactText() {
260
- return 'This is rendered from a React component!';
261
- }
287
+ function ReactChild() {
288
+ return useContext(SomeContext);
289
+ }
262
290
 
263
- component App() {
264
- <div>
265
- <tsx:react>
266
- {renderReactText()}
267
- </tsx:react>
268
- </div>
269
- }
291
+ component App() {
292
+ <div>
293
+ <tsx:react>
294
+ <SomeContext.Provider value="Provided Value">
295
+ <ReactChild />
296
+ </SomeContext.Provider>
297
+ </tsx:react>
298
+ </div>
299
+ }
270
300
 
271
- await act(async () => {
272
- render(App);
301
+ await act(async () => {
302
+ render(App);
303
+ });
304
+
305
+ const reactContent = container.querySelector('div > div');
306
+ expect(reactContent).toBeTruthy();
307
+ expect(reactContent.textContent).toBe('Provided Value');
273
308
  });
274
309
 
275
- const reactContent = container.querySelector('div > div');
276
- expect(reactContent).toBeTruthy();
277
- expect(reactContent.textContent).toBe('This is rendered from a React component!');
278
- });
310
+ it('should handle a React errors', async () => {
311
+ function ReactChild() {
312
+ throw new Error('Test Error');
313
+ }
279
314
 
280
- it('should handle a React context propagation', async () => {
281
- const SomeContext = createContext('Default Value');
315
+ component App() {
316
+ try {
317
+ <div>
318
+ <tsx:react>
319
+ <ReactChild />
320
+ </tsx:react>
321
+ </div>
322
+ } catch (e) {
323
+ <div class="error">{'ReactChiild had an error'}</div>
324
+ }
325
+ }
282
326
 
283
- function ReactChild() {
284
- return useContext(SomeContext);
285
- }
327
+ await act(async () => {
328
+ render(App);
329
+ });
330
+
331
+ const reactContent = container.querySelector('div > div');
332
+ expect(reactContent).toBeTruthy();
333
+ expect(reactContent.textContent).toBe('ReactChiild had an error');
334
+ });
286
335
 
287
- component App() {
288
- <div>
336
+ it('should handle a React errors #2', async () => {
337
+ function ReactChild() {
338
+ throw new Error('Test Error');
339
+ }
340
+
341
+ component RippleChild() {
289
342
  <tsx:react>
290
- <SomeContext.Provider value="Provided Value">
291
- <ReactChild />
292
- </SomeContext.Provider>
343
+ <ReactChild />
293
344
  </tsx:react>
294
- </div>
295
- }
345
+ }
296
346
 
297
- await act(async () => {
298
- render(App);
299
- });
347
+ component App() {
348
+ try {
349
+ <div>
350
+ <tsx:react>
351
+ <Ripple component={RippleChild} />
352
+ </tsx:react>
353
+ </div>
354
+ } catch (e) {
355
+ <div class="error">{'ReactChiild had an error'}</div>
356
+ }
357
+ }
358
+
359
+ await act(async () => {
360
+ render(App);
361
+ });
300
362
 
301
- const reactContent = container.querySelector('div > div');
302
- expect(reactContent).toBeTruthy();
303
- expect(reactContent.textContent).toBe('Provided Value');
363
+ const reactContent = container.querySelector('div > div');
364
+ expect(reactContent).toBeTruthy();
365
+ expect(reactContent.textContent).toBe('ReactChiild had an error');
366
+ });
304
367
  });
305
368
 
306
- it('should handle a React errors', async () => {
307
- function ReactChild() {
308
- throw new Error('Test Error');
309
- }
369
+ describe('Ripple in React app', () => {
370
+ let container: null | Node;
310
371
 
311
- component App() {
312
- try {
313
- <div>
314
- <tsx:react>
315
- <ReactChild />
316
- </tsx:react>
317
- </div>
318
- } catch (e) {
319
- <div class="error">{'ReactChiild had an error'}</div>
320
- }
321
- }
372
+ beforeEach(() => {
373
+ container = document.createElement('div');
374
+ document.body.appendChild(container);
375
+ });
322
376
 
323
- await act(async () => {
324
- render(App);
377
+ afterEach(() => {
378
+ document.body.removeChild(container!);
379
+ container = null;
325
380
  });
326
381
 
327
- const reactContent = container.querySelector('div > div');
328
- expect(reactContent).toBeTruthy();
329
- expect(reactContent.textContent).toBe('ReactChiild had an error');
330
- });
382
+ it('should render a basic React app', async () => {
383
+ function App() {
384
+ return jsx('div', { children: 'Hello from React App with RippleRoot' });
385
+ }
331
386
 
332
- it('should handle a React errors #2', async () => {
333
- function ReactChild() {
334
- throw new Error('Test Error');
335
- }
387
+ const root = createRoot(container!);
336
388
 
337
- component RippleChild() {
338
- <tsx:react>
339
- <ReactChild />
340
- </tsx:react>
341
- }
389
+ await act(async () => {
390
+ root.render(jsx(RippleRoot, { children: jsx(App, {}) }));
391
+ });
342
392
 
343
- component App() {
344
- try {
345
- <div>
346
- <tsx:react>
347
- <Ripple component={RippleChild} />
348
- </tsx:react>
349
- </div>
350
- } catch (e) {
351
- <div class="error">{'ReactChiild had an error'}</div>
393
+ expect(container!.textContent).toBe('Hello from React App with RippleRoot');
394
+ });
395
+
396
+ it('should render a basic React app with RippleRoot', async () => {
397
+ component RippleComponent() {
398
+ <div>{'Hello from Ripple Component'}</div>
352
399
  }
353
- }
354
400
 
355
- await act(async () => {
356
- render(App);
357
- });
401
+ function App() {
402
+ return jsxs('div', {
403
+ children: [
404
+ 'Hello from React App with RippleRoot\n',
405
+ jsx(Ripple, { component: RippleComponent }),
406
+ ],
407
+ });
408
+ }
409
+
410
+ const root = createRoot(container!);
358
411
 
359
- const reactContent = container.querySelector('div > div');
360
- expect(reactContent).toBeTruthy();
361
- expect(reactContent.textContent).toBe('ReactChiild had an error');
412
+ await act(async () => {
413
+ root.render(jsx(RippleRoot, { children: jsx(App, {}) }));
414
+ });
415
+
416
+ expect(container!.textContent).toBe(
417
+ 'Hello from React App with RippleRoot\nHello from Ripple Component',
418
+ );
419
+ });
362
420
  });
363
421
  });
package/types/index.d.ts CHANGED
@@ -12,3 +12,5 @@ export declare function createReactCompat(): {
12
12
  };
13
13
 
14
14
  export declare function Ripple<P>(component: Component<P>, props?: P): React.JSX.Element;
15
+
16
+ export declare function RippleRoot(props: { children: React.ReactNode }): React.JSX.Element;