@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 +2 -2
- package/src/index.js +31 -2
- package/tests/index.test.ripple +353 -295
- package/types/index.d.ts +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ripple-ts/compat-react",
|
|
3
|
-
"version": "0.2.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|
package/tests/index.test.ripple
CHANGED
|
@@ -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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
<
|
|
10
|
-
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
17
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
41
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
</
|
|
61
|
-
</
|
|
62
|
-
|
|
63
|
-
}
|
|
65
|
+
</tsx:react>
|
|
66
|
+
</div>
|
|
67
|
+
}
|
|
64
68
|
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
90
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
118
|
-
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
144
|
-
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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="
|
|
162
|
+
<div className="outer">Outer</div>
|
|
163
|
+
<>
|
|
164
|
+
<div className="inner">Inner</div>
|
|
165
|
+
</>
|
|
161
166
|
</>
|
|
162
|
-
|
|
163
|
-
</
|
|
164
|
-
|
|
165
|
-
}
|
|
167
|
+
</tsx:react>
|
|
168
|
+
</div>
|
|
169
|
+
}
|
|
166
170
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
171
|
+
await act(async () => {
|
|
172
|
+
render(App);
|
|
173
|
+
});
|
|
170
174
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
|
|
195
|
-
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
218
|
-
|
|
225
|
+
const after = container.querySelector('.after');
|
|
226
|
+
expect(after).toBeTruthy();
|
|
227
|
+
expect(after.textContent).toBe('After empty fragment');
|
|
219
228
|
});
|
|
220
229
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
-
|
|
241
|
-
|
|
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
|
-
|
|
245
|
-
|
|
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
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
}
|
|
287
|
+
function ReactChild() {
|
|
288
|
+
return useContext(SomeContext);
|
|
289
|
+
}
|
|
262
290
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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
|
-
|
|
272
|
-
|
|
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
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
310
|
+
it('should handle a React errors', async () => {
|
|
311
|
+
function ReactChild() {
|
|
312
|
+
throw new Error('Test Error');
|
|
313
|
+
}
|
|
279
314
|
|
|
280
|
-
|
|
281
|
-
|
|
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
|
-
|
|
284
|
-
|
|
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
|
-
|
|
288
|
-
|
|
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
|
-
<
|
|
291
|
-
<ReactChild />
|
|
292
|
-
</SomeContext.Provider>
|
|
343
|
+
<ReactChild />
|
|
293
344
|
</tsx:react>
|
|
294
|
-
|
|
295
|
-
}
|
|
345
|
+
}
|
|
296
346
|
|
|
297
|
-
|
|
298
|
-
|
|
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
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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
|
-
|
|
307
|
-
|
|
308
|
-
throw new Error('Test Error');
|
|
309
|
-
}
|
|
369
|
+
describe('Ripple in React app', () => {
|
|
370
|
+
let container: null | Node;
|
|
310
371
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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
|
-
|
|
324
|
-
|
|
377
|
+
afterEach(() => {
|
|
378
|
+
document.body.removeChild(container!);
|
|
379
|
+
container = null;
|
|
325
380
|
});
|
|
326
381
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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
|
-
|
|
333
|
-
function ReactChild() {
|
|
334
|
-
throw new Error('Test Error');
|
|
335
|
-
}
|
|
387
|
+
const root = createRoot(container!);
|
|
336
388
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
</tsx:react>
|
|
341
|
-
}
|
|
389
|
+
await act(async () => {
|
|
390
|
+
root.render(jsx(RippleRoot, { children: jsx(App, {}) }));
|
|
391
|
+
});
|
|
342
392
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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
|
-
|
|
356
|
-
|
|
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
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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