Add additional fixtures for FragmentInstance text node support (#35631)
Stacked on https://github.com/facebook/react/pull/35630 - Adds test case for compareDocumentPosition, missing before and also extending to text nodes - Adds event handling fixture case for text - Adds getRootNode fixture case for text
Jack Pope committed
Jan 28, 2026 at 14:55 UTC
90b2dd442cc05048b2a6ade5020c463ab0499eca
7 files changed
+669
-48
fixtures/dom/src/components/fixtures/fragment-refs/CompareDocumentPositionCase.js
new
+53
@@ -0,0 +1,53 @@
1
+import TestCase from '../../TestCase';
2
+import Fixture from '../../Fixture';
3
+import CompareDocumentPositionFragmentContainer from './CompareDocumentPositionFragmentContainer';
4
+
5
+const React = window.React;
6
+
7
+export default function CompareDocumentPositionCase() {
8
+ return (
9
+ <TestCase title="compareDocumentPosition">
10
+ <TestCase.Steps>
11
+ <li>Click the "Compare All Positions" button</li>
12
+ </TestCase.Steps>
13
+ <TestCase.ExpectedResult>
14
+ The compareDocumentPosition method compares the position of the fragment
15
+ relative to other elements in the DOM. The "Before Element" should be
16
+ PRECEDING the fragment, and the "After Element" should be FOLLOWING.
17
+ Elements inside the fragment should be CONTAINED_BY.
18
+ </TestCase.ExpectedResult>
19
+ <Fixture>
20
+ <Fixture.Controls>
21
+ <CompareDocumentPositionFragmentContainer>
22
+ <div
23
+ style={{
24
+ padding: '10px',
25
+ backgroundColor: 'lightblue',
26
+ borderRadius: '4px',
27
+ marginBottom: '8px',
28
+ }}>
29
+ First child element
30
+ </div>
31
+ <div
32
+ style={{
33
+ padding: '10px',
34
+ backgroundColor: 'lightgreen',
35
+ borderRadius: '4px',
36
+ marginBottom: '8px',
37
+ }}>
38
+ Second child element
39
+ </div>
40
+ <div
41
+ style={{
42
+ padding: '10px',
43
+ backgroundColor: 'lightpink',
44
+ borderRadius: '4px',
45
+ }}>
46
+ Third child element
47
+ </div>
48
+ </CompareDocumentPositionFragmentContainer>
49
+ </Fixture.Controls>
50
+ </Fixture>
51
+ </TestCase>
52
+ );
53
+}
fixtures/dom/src/components/fixtures/fragment-refs/CompareDocumentPositionFragmentContainer.js
new
+246
@@ -0,0 +1,246 @@
1
+const React = window.React;
2
+const {Fragment, useRef, useState} = React;
3
+
4
+const POSITION_FLAGS = {
5
+ DISCONNECTED: 0x01,
6
+ PRECEDING: 0x02,
7
+ FOLLOWING: 0x04,
8
+ CONTAINS: 0x08,
9
+ CONTAINED_BY: 0x10,
10
+ IMPLEMENTATION_SPECIFIC: 0x20,
11
+};
12
+
13
+function getPositionDescription(bitmask) {
14
+ const flags = [];
15
+ if (bitmask & POSITION_FLAGS.DISCONNECTED) flags.push('DISCONNECTED');
16
+ if (bitmask & POSITION_FLAGS.PRECEDING) flags.push('PRECEDING');
17
+ if (bitmask & POSITION_FLAGS.FOLLOWING) flags.push('FOLLOWING');
18
+ if (bitmask & POSITION_FLAGS.CONTAINS) flags.push('CONTAINS');
19
+ if (bitmask & POSITION_FLAGS.CONTAINED_BY) flags.push('CONTAINED_BY');
20
+ if (bitmask & POSITION_FLAGS.IMPLEMENTATION_SPECIFIC)
21
+ flags.push('IMPLEMENTATION_SPECIFIC');
22
+ return flags.length > 0 ? flags.join(' | ') : 'SAME';
23
+}
24
+
25
+function ResultRow({label, result, color}) {
26
+ if (!result) return null;
27
+
28
+ return (
29
+ <div
30
+ style={{
31
+ padding: '10px 14px',
32
+ marginBottom: '8px',
33
+ backgroundColor: '#f8f9fa',
34
+ borderLeft: `4px solid ${color}`,
35
+ borderRadius: '4px',
36
+ }}>
37
+ <div
38
+ style={{
39
+ fontWeight: 'bold',
40
+ marginBottom: '6px',
41
+ color: '#333',
42
+ }}>
43
+ {label}
44
+ </div>
45
+ <div
46
+ style={{
47
+ display: 'grid',
48
+ gridTemplateColumns: 'auto 1fr',
49
+ gap: '4px 12px',
50
+ fontSize: '13px',
51
+ fontFamily: 'monospace',
52
+ }}>
53
+ <span style={{color: '#666'}}>Raw value:</span>
54
+ <span style={{color: '#333'}}>{result.raw}</span>
55
+ <span style={{color: '#666'}}>Flags:</span>
56
+ <span style={{color: color, fontWeight: 500}}>
57
+ {getPositionDescription(result.raw)}
58
+ </span>
59
+ </div>
60
+ </div>
61
+ );
62
+}
63
+
64
+export default function CompareDocumentPositionFragmentContainer({children}) {
65
+ const fragmentRef = useRef(null);
66
+ const beforeRef = useRef(null);
67
+ const afterRef = useRef(null);
68
+ const insideRef = useRef(null);
69
+ const [results, setResults] = useState(null);
70
+
71
+ const compareAll = () => {
72
+ const fragment = fragmentRef.current;
73
+ const beforePos = fragment.compareDocumentPosition(beforeRef.current);
74
+ const afterPos = fragment.compareDocumentPosition(afterRef.current);
75
+ const insidePos = insideRef.current
76
+ ? fragment.compareDocumentPosition(insideRef.current)
77
+ : null;
78
+
79
+ setResults({
80
+ before: {raw: beforePos},
81
+ after: {raw: afterPos},
82
+ inside: insidePos !== null ? {raw: insidePos} : null,
83
+ });
84
+ };
85
+
86
+ return (
87
+ <Fragment>
88
+ <div style={{marginBottom: '16px'}}>
89
+ <button
90
+ onClick={compareAll}
91
+ style={{
92
+ padding: '8px 16px',
93
+ fontSize: '14px',
94
+ fontWeight: 'bold',
95
+ cursor: 'pointer',
96
+ }}>
97
+ Compare All Positions
98
+ </button>
99
+ {results && (
100
+ <span style={{marginLeft: '12px', color: '#666'}}>
101
+ Comparison complete
102
+ </span>
103
+ )}
104
+ </div>
105
+
106
+ <div style={{display: 'flex', gap: '24px'}}>
107
+ <div style={{flex: '0 0 300px'}}>
108
+ <div
109
+ style={{
110
+ padding: '16px',
111
+ backgroundColor: '#f0f0f0',
112
+ borderRadius: '8px',
113
+ }}>
114
+ <div
115
+ ref={beforeRef}
116
+ style={{
117
+ padding: '12px',
118
+ backgroundColor: '#d4edda',
119
+ border: '2px solid #28a745',
120
+ borderRadius: '4px',
121
+ marginBottom: '12px',
122
+ textAlign: 'center',
123
+ fontWeight: 'bold',
124
+ color: '#155724',
125
+ }}>
126
+ Before Element
127
+ </div>
128
+
129
+ <div
130
+ style={{
131
+ padding: '12px',
132
+ backgroundColor: '#fff3cd',
133
+ border: '2px dashed #ffc107',
134
+ borderRadius: '4px',
135
+ marginBottom: '12px',
136
+ }}>
137
+ <div
138
+ style={{
139
+ fontSize: '11px',
140
+ color: '#856404',
141
+ marginBottom: '8px',
142
+ fontWeight: 'bold',
143
+ }}>
144
+ FRAGMENT
145
+ </div>
146
+ <div ref={insideRef}>
147
+ <Fragment ref={fragmentRef}>{children}</Fragment>
148
+ </div>
149
+ </div>
150
+
151
+ <div
152
+ ref={afterRef}
153
+ style={{
154
+ padding: '12px',
155
+ backgroundColor: '#f8d7da',
156
+ border: '2px solid #dc3545',
157
+ borderRadius: '4px',
158
+ textAlign: 'center',
159
+ fontWeight: 'bold',
160
+ color: '#721c24',
161
+ }}>
162
+ After Element
163
+ </div>
164
+ </div>
165
+ </div>
166
+
167
+ <div style={{flex: 1}}>
168
+ <div
169
+ style={{
170
+ fontSize: '14px',
171
+ fontWeight: 'bold',
172
+ marginBottom: '12px',
173
+ color: '#333',
174
+ }}>
175
+ Comparison Results
176
+ </div>
177
+
178
+ {!results && (
179
+ <div
180
+ style={{
181
+ padding: '20px',
182
+ backgroundColor: '#f8f9fa',
183
+ borderRadius: '4px',
184
+ color: '#666',
185
+ textAlign: 'center',
186
+ }}>
187
+ Click "Compare All Positions" to see results
188
+ </div>
189
+ )}
190
+
191
+ {results && (
192
+ <Fragment>
193
+ <ResultRow
194
+ label='vs "Before Element"'
195
+ result={results.before}
196
+ color="#28a745"
197
+ />
198
+ <ResultRow
199
+ label='vs "After Element"'
200
+ result={results.after}
201
+ color="#dc3545"
202
+ />
203
+ {results.inside && (
204
+ <ResultRow
205
+ label='vs "Inside Element"'
206
+ result={results.inside}
207
+ color="#ffc107"
208
+ />
209
+ )}
210
+
211
+ <div
212
+ style={{
213
+ marginTop: '16px',
214
+ padding: '12px',
215
+ backgroundColor: '#e7f3ff',
216
+ borderRadius: '4px',
217
+ fontSize: '12px',
218
+ color: '#0c5460',
219
+ }}>
220
+ <strong>Flag Reference:</strong>
221
+ <div
222
+ style={{
223
+ marginTop: '8px',
224
+ display: 'grid',
225
+ gridTemplateColumns: 'auto 1fr',
226
+ gap: '2px 12px',
227
+ }}>
228
+ <code>0x01</code>
229
+ <span>DISCONNECTED</span>
230
+ <code>0x02</code>
231
+ <span>PRECEDING (other is before fragment)</span>
232
+ <code>0x04</code>
233
+ <span>FOLLOWING (other is after fragment)</span>
234
+ <code>0x08</code>
235
+ <span>CONTAINS (other contains fragment)</span>
236
+ <code>0x10</code>
237
+ <span>CONTAINED_BY (other is inside fragment)</span>
238
+ </div>
239
+ </div>
240
+ </Fragment>
241
+ )}
242
+ </div>
243
+ </div>
244
+ </Fragment>
245
+ );
246
+}
fixtures/dom/src/components/fixtures/fragment-refs/EventFragmentContainer.js
new
+112
@@ -0,0 +1,112 @@
1
+const React = window.React;
2
+const {Fragment, useRef, useState} = React;
3
+
4
+export default function EventFragmentContainer({children}) {
5
+ const fragmentRef = useRef(null);
6
+ const [eventLog, setEventLog] = useState([]);
7
+ const [listenerAdded, setListenerAdded] = useState(false);
8
+ const [bubblesState, setBubblesState] = useState(true);
9
+
10
+ const logEvent = message => {
11
+ setEventLog(prev => [...prev, message]);
12
+ };
13
+
14
+ const fragmentClickHandler = () => {
15
+ logEvent('Fragment event listener fired');
16
+ };
17
+
18
+ const addListener = () => {
19
+ fragmentRef.current.addEventListener('click', fragmentClickHandler);
20
+ setListenerAdded(true);
21
+ logEvent('Added click listener to fragment');
22
+ };
23
+
24
+ const removeListener = () => {
25
+ fragmentRef.current.removeEventListener('click', fragmentClickHandler);
26
+ setListenerAdded(false);
27
+ logEvent('Removed click listener from fragment');
28
+ };
29
+
30
+ const dispatchClick = () => {
31
+ fragmentRef.current.dispatchEvent(
32
+ new MouseEvent('click', {bubbles: bubblesState})
33
+ );
34
+ logEvent(`Dispatched click event (bubbles: ${bubblesState})`);
35
+ };
36
+
37
+ const clearLog = () => {
38
+ setEventLog([]);
39
+ };
40
+
41
+ return (
42
+ <Fragment>
43
+ <div
44
+ style={{
45
+ marginBottom: '16px',
46
+ display: 'flex',
47
+ gap: '8px',
48
+ flexWrap: 'wrap',
49
+ alignItems: 'center',
50
+ }}>
51
+ <select
52
+ value={bubblesState ? 'true' : 'false'}
53
+ onChange={e => setBubblesState(e.target.value === 'true')}
54
+ style={{padding: '6px 10px'}}>
55
+ <option value="true">Bubbles: true</option>
56
+ <option value="false">Bubbles: false</option>
57
+ </select>
58
+ <button onClick={dispatchClick} style={{padding: '6px 12px'}}>
59
+ Dispatch click event
60
+ </button>
61
+ <button
62
+ onClick={addListener}
63
+ disabled={listenerAdded}
64
+ style={{padding: '6px 12px'}}>
65
+ Add event listener
66
+ </button>
67
+ <button
68
+ onClick={removeListener}
69
+ disabled={!listenerAdded}
70
+ style={{padding: '6px 12px'}}>
71
+ Remove event listener
72
+ </button>
73
+ <button onClick={clearLog} style={{padding: '6px 12px'}}>
74
+ Clear log
75
+ </button>
76
+ </div>
77
+
78
+ <div
79
+ onClick={() => logEvent('Parent div clicked')}
80
+ style={{
81
+ padding: '12px',
82
+ border: '1px dashed #ccc',
83
+ borderRadius: '4px',
84
+ backgroundColor: '#fff',
85
+ }}>
86
+ <Fragment ref={fragmentRef}>{children}</Fragment>
87
+ </div>
88
+
89
+ {eventLog.length > 0 && (
90
+ <div
91
+ style={{
92
+ marginTop: '12px',
93
+ padding: '10px',
94
+ backgroundColor: '#f5f5f5',
95
+ border: '1px solid #ddd',
96
+ borderRadius: '4px',
97
+ maxHeight: '150px',
98
+ overflow: 'auto',
99
+ fontFamily: 'monospace',
100
+ fontSize: '13px',
101
+ }}>
102
+ <strong>Event Log:</strong>
103
+ <ul style={{margin: '5px 0', paddingLeft: '20px'}}>
104
+ {eventLog.map((msg, i) => (
105
+ <li key={i}>{msg}</li>
106
+ ))}
107
+ </ul>
108
+ </div>
109
+ )}
110
+ </Fragment>
111
+ );
112
+}
fixtures/dom/src/components/fixtures/fragment-refs/EventListenerCase.js
+25
-47
@@ -1,46 +1,35 @@
1
import TestCase from '../../TestCase';
2
import Fixture from '../../Fixture';
3
+import EventFragmentContainer from './EventFragmentContainer';
4
5
const React = window.React;
5
-const {Fragment, useEffect, useRef, useState} = React;
6
+const {useState} = React;
7
8
function WrapperComponent(props) {
9
return props.children;
10
}
11
11
-function handler(e) {
12
- const text = e.currentTarget.innerText;
13
- alert('You clicked: ' + text);
14
-}
15
-
12
export default function EventListenerCase() {
17
- const fragmentRef = useRef(null);
13
const [extraChildCount, setExtraChildCount] = useState(0);
14
20
- useEffect(() => {
21
- fragmentRef.current.addEventListener('click', handler);
22
-
23
- const lastFragmentRefValue = fragmentRef.current;
24
- return () => {
25
- lastFragmentRefValue.removeEventListener('click', handler);
26
- };
27
- });
28
-
15
return (
16
<TestCase title="Event Registration">
17
<TestCase.Steps>
32
- <li>Click one of the children, observe the alert</li>
33
- <li>Add a new child, click it, observe the alert</li>
34
- <li>Remove the event listeners, click a child, observe no alert</li>
35
- <li>Add the event listeners back, click a child, observe the alert</li>
18
+ <li>
19
+ Click "Add event listener" to attach a click handler to the fragment
20
+ </li>
21
+ <li>Click "Dispatch click event" to dispatch a click event</li>
22
+ <li>Observe the event log showing the event fired</li>
23
+ <li>Add a new child, dispatch again to see it still works</li>
24
+ <li>
25
+ Click "Remove event listener" and dispatch again to see no event fires
26
+ </li>
27
</TestCase.Steps>
28
29
<TestCase.ExpectedResult>
30
<p>
31
Fragment refs can manage event listeners on the first level of host
41
- children. This page loads with an effect that sets up click event
42
- hanndlers on each child card. Clicking on a card will show an alert
43
- with the card's text.
32
+ children. The event log shows when events are dispatched and handled.
33
</p>
34
<p>
35
New child nodes will also have event listeners applied. Removed nodes
@@ -50,28 +39,17 @@ export default function EventListenerCase() {
39
40
<Fixture>
41
<Fixture.Controls>
53
- <div>Target count: {extraChildCount + 3}</div>
54
- <button
55
- onClick={() => {
56
- setExtraChildCount(prev => prev + 1);
57
- }}>
58
- Add Child
59
- </button>
60
- <button
61
- onClick={() => {
62
- fragmentRef.current.addEventListener('click', handler);
63
- }}>
64
- Add click event listeners
65
- </button>
66
- <button
67
- onClick={() => {
68
- fragmentRef.current.removeEventListener('click', handler);
69
- }}>
70
- Remove click event listeners
71
- </button>
72
- </Fixture.Controls>
73
- <div className="card-container">
74
- <Fragment ref={fragmentRef}>
42
+ <div style={{marginBottom: '10px'}}>
43
+ Target count: {extraChildCount + 3}
44
+ <button
45
+ onClick={() => {
46
+ setExtraChildCount(prev => prev + 1);
47
+ }}
48
+ style={{marginLeft: '10px'}}>
49
+ Add Child
50
+ </button>
51
+ </div>
52
+ <EventFragmentContainer>
53
<div className="card" id="child-a">
54
Child A
55
</div>
@@ -88,8 +66,8 @@ export default function EventListenerCase() {
66
</div>
67
))}
68
</WrapperComponent>
91
- </Fragment>
92
- </div>
69
+ </EventFragmentContainer>
70
+ </Fixture.Controls>
71
</Fixture>
72
</TestCase>
73
);
fixtures/dom/src/components/fixtures/fragment-refs/GetRootNodeFragmentContainer.js
new
+79
@@ -0,0 +1,79 @@
1
+const React = window.React;
2
+const {Fragment, useRef, useState} = React;
3
+
4
+export default function GetRootNodeFragmentContainer({children}) {
5
+ const fragmentRef = useRef(null);
6
+ const [rootNodeInfo, setRootNodeInfo] = useState(null);
7
+
8
+ const getRootNodeInfo = () => {
9
+ const rootNode = fragmentRef.current.getRootNode();
10
+ setRootNodeInfo({
11
+ nodeName: rootNode.nodeName,
12
+ nodeType: rootNode.nodeType,
13
+ nodeTypeLabel: getNodeTypeLabel(rootNode.nodeType),
14
+ isDocument: rootNode === document,
15
+ });
16
+ };
17
+
18
+ const getNodeTypeLabel = nodeType => {
19
+ const types = {
20
+ 1: 'ELEMENT_NODE',
21
+ 3: 'TEXT_NODE',
22
+ 9: 'DOCUMENT_NODE',
23
+ 11: 'DOCUMENT_FRAGMENT_NODE',
24
+ };
25
+ return types[nodeType] || `UNKNOWN (${nodeType})`;
26
+ };
27
+
28
+ return (
29
+ <Fragment>
30
+ <div style={{marginBottom: '16px'}}>
31
+ <button
32
+ onClick={getRootNodeInfo}
33
+ style={{
34
+ padding: '8px 16px',
35
+ fontSize: '14px',
36
+ fontWeight: 'bold',
37
+ cursor: 'pointer',
38
+ }}>
39
+ Get Root Node
40
+ </button>
41
+ </div>
42
+
43
+ {rootNodeInfo && (
44
+ <div
45
+ style={{
46
+ marginBottom: '16px',
47
+ padding: '12px',
48
+ backgroundColor: '#e8f4e8',
49
+ border: '1px solid #9c9',
50
+ borderRadius: '4px',
51
+ fontFamily: 'monospace',
52
+ fontSize: '13px',
53
+ }}>
54
+ <div style={{marginBottom: '4px'}}>
55
+ <strong>Node Name:</strong> {rootNodeInfo.nodeName}
56
+ </div>
57
+ <div style={{marginBottom: '4px'}}>
58
+ <strong>Node Type:</strong> {rootNodeInfo.nodeType} (
59
+ {rootNodeInfo.nodeTypeLabel})
60
+ </div>
61
+ <div>
62
+ <strong>Is Document:</strong>{' '}
63
+ {rootNodeInfo.isDocument ? 'Yes' : 'No'}
64
+ </div>
65
+ </div>
66
+ )}
67
+
68
+ <div
69
+ style={{
70
+ padding: '12px',
71
+ border: '1px dashed #ccc',
72
+ borderRadius: '4px',
73
+ backgroundColor: '#fff',
74
+ }}>
75
+ <Fragment ref={fragmentRef}>{children}</Fragment>
76
+ </div>
77
+ </Fragment>
78
+ );
79
+}
fixtures/dom/src/components/fixtures/fragment-refs/TextNodesCase.js
+152
-1
@@ -1,6 +1,9 @@
1
import TestCase from '../../TestCase';
2
import Fixture from '../../Fixture';
3
import PrintRectsFragmentContainer from './PrintRectsFragmentContainer';
4
+import CompareDocumentPositionFragmentContainer from './CompareDocumentPositionFragmentContainer';
5
+import EventFragmentContainer from './EventFragmentContainer';
6
+import GetRootNodeFragmentContainer from './GetRootNodeFragmentContainer';
7
8
const React = window.React;
9
const {Fragment, useRef, useState} = React;
@@ -242,6 +245,28 @@ function ScrollIntoViewMixed() {
245
);
246
}
247
248
+function CompareDocumentPositionTextNodes() {
249
+ return (
250
+ <TestCase title="compareDocumentPosition - Text Only">
251
+ <TestCase.Steps>
252
+ <li>Click the "Compare All Positions" button</li>
253
+ </TestCase.Steps>
254
+ <TestCase.ExpectedResult>
255
+ compareDocumentPosition should work correctly even when the fragment
256
+ contains only text nodes. The "Before" element should be PRECEDING the
257
+ fragment, and the "After" element should be FOLLOWING.
258
+ </TestCase.ExpectedResult>
259
+ <Fixture>
260
+ <Fixture.Controls>
261
+ <CompareDocumentPositionFragmentContainer>
262
+ This is text-only content inside the fragment.
263
+ </CompareDocumentPositionFragmentContainer>
264
+ </Fixture.Controls>
265
+ </Fixture>
266
+ </TestCase>
267
+ );
268
+}
269
+
270
function ObserveTextOnlyWarning() {
271
const fragmentRef = useRef(null);
272
const [message, setMessage] = useState('');
@@ -287,6 +312,126 @@ function ObserveTextOnlyWarning() {
312
);
313
}
314
315
+function EventTextOnly() {
316
+ return (
317
+ <TestCase title="Event Operations - Text Only">
318
+ <TestCase.Steps>
319
+ <li>
320
+ Click "Add event listener" to attach a click handler to the fragment
321
+ </li>
322
+ <li>Click "Dispatch click event" to dispatch a click event</li>
323
+ <li>Observe that the fragment's event listener fires</li>
324
+ <li>Click "Remove event listener" and dispatch again</li>
325
+ </TestCase.Steps>
326
+ <TestCase.ExpectedResult>
327
+ Event operations (addEventListener, removeEventListener, dispatchEvent)
328
+ work on fragments with text-only content. The event is dispatched on the
329
+ fragment's parent element since text nodes cannot be event targets.
330
+ </TestCase.ExpectedResult>
331
+ <Fixture>
332
+ <Fixture.Controls>
333
+ <EventFragmentContainer>
334
+ This fragment contains only text. Events are handled via the parent.
335
+ </EventFragmentContainer>
336
+ </Fixture.Controls>
337
+ </Fixture>
338
+ </TestCase>
339
+ );
340
+}
341
+
342
+function EventMixed() {
343
+ return (
344
+ <TestCase title="Event Operations - Mixed Content">
345
+ <TestCase.Steps>
346
+ <li>
347
+ Click "Add event listener" to attach a click handler to the fragment
348
+ </li>
349
+ <li>Click "Dispatch click event" to dispatch a click event</li>
350
+ <li>Observe that the fragment's event listener fires</li>
351
+ <li>Click directly on the element or text content to see bubbling</li>
352
+ </TestCase.Steps>
353
+ <TestCase.ExpectedResult>
354
+ Event operations work on fragments with mixed text and element content.
355
+ dispatchEvent forwards to the parent element. Clicks on child elements
356
+ or text bubble up through the DOM as normal.
357
+ </TestCase.ExpectedResult>
358
+ <Fixture>
359
+ <Fixture.Controls>
360
+ <EventFragmentContainer>
361
+ Text node before element.
362
+ <span
363
+ style={{
364
+ display: 'inline-block',
365
+ padding: '5px 10px',
366
+ margin: '0 5px',
367
+ backgroundColor: 'lightblue',
368
+ border: '1px solid blue',
369
+ }}>
370
+ Element
371
+ </span>
372
+ Text node after element.
373
+ </EventFragmentContainer>
374
+ </Fixture.Controls>
375
+ </Fixture>
376
+ </TestCase>
377
+ );
378
+}
379
+
380
+function GetRootNodeTextOnly() {
381
+ return (
382
+ <TestCase title="getRootNode - Text Only">
383
+ <TestCase.Steps>
384
+ <li>Click the "Get Root Node" button</li>
385
+ </TestCase.Steps>
386
+ <TestCase.ExpectedResult>
387
+ getRootNode should return the root of the DOM tree containing the
388
+ fragment's text content. For a fragment in the main document, this
389
+ should return the Document node.
390
+ </TestCase.ExpectedResult>
391
+ <Fixture>
392
+ <Fixture.Controls>
393
+ <GetRootNodeFragmentContainer>
394
+ This fragment contains only text. getRootNode returns the document.
395
+ </GetRootNodeFragmentContainer>
396
+ </Fixture.Controls>
397
+ </Fixture>
398
+ </TestCase>
399
+ );
400
+}
401
+
402
+function GetRootNodeMixed() {
403
+ return (
404
+ <TestCase title="getRootNode - Mixed Content">
405
+ <TestCase.Steps>
406
+ <li>Click the "Get Root Node" button</li>
407
+ </TestCase.Steps>
408
+ <TestCase.ExpectedResult>
409
+ getRootNode should return the root of the DOM tree for fragments with
410
+ mixed text and element content. The result is the same whether checking
411
+ from text nodes or element nodes within the fragment.
412
+ </TestCase.ExpectedResult>
413
+ <Fixture>
414
+ <Fixture.Controls>
415
+ <GetRootNodeFragmentContainer>
416
+ Text before element.
417
+ <span
418
+ style={{
419
+ display: 'inline-block',
420
+ padding: '5px 10px',
421
+ margin: '0 5px',
422
+ backgroundColor: 'lightyellow',
423
+ border: '1px solid #cc0',
424
+ }}>
425
+ Element
426
+ </span>
427
+ Text after element.
428
+ </GetRootNodeFragmentContainer>
429
+ </Fixture.Controls>
430
+ </Fixture>
431
+ </TestCase>
432
+ );
433
+}
434
+
435
export default function TextNodesCase() {
436
return (
437
<TestCase title="Text Node Support">
@@ -297,7 +442,8 @@ export default function TextNodesCase() {
442
</p>
443
<p>
444
<strong>Supported:</strong> getClientRects, compareDocumentPosition,
300
- scrollIntoView
445
+ scrollIntoView, getRootNode, addEventListener, removeEventListener,
446
+ dispatchEvent
447
</p>
448
<p>
449
<strong>No-op (silent):</strong> focus, focusLast (text nodes cannot
@@ -310,10 +456,15 @@ export default function TextNodesCase() {
456
</TestCase.ExpectedResult>
457
<GetClientRectsTextOnly />
458
<GetClientRectsMixed />
459
+ <CompareDocumentPositionTextNodes />
460
<FocusTextOnlyNoop />
461
<ScrollIntoViewTextOnly />
462
<ScrollIntoViewMixed />
463
<ObserveTextOnlyWarning />
464
+ <EventTextOnly />
465
+ <EventMixed />
466
+ <GetRootNodeTextOnly />
467
+ <GetRootNodeMixed />
468
</TestCase>
469
);
470
}
fixtures/dom/src/components/fixtures/fragment-refs/index.js
+2
@@ -5,6 +5,7 @@ import IntersectionObserverCase from './IntersectionObserverCase';
5
import ResizeObserverCase from './ResizeObserverCase';
6
import FocusCase from './FocusCase';
7
import GetClientRectsCase from './GetClientRectsCase';
8
+import CompareDocumentPositionCase from './CompareDocumentPositionCase';
9
import ScrollIntoViewCase from './ScrollIntoViewCase';
10
import TextNodesCase from './TextNodesCase';
11
@@ -19,6 +20,7 @@ export default function FragmentRefsPage() {
20
<ResizeObserverCase />
21
<FocusCase />
22
<GetClientRectsCase />
23
+ <CompareDocumentPositionCase />
24
<ScrollIntoViewCase />
25
<TextNodesCase />
26
</FixtureSet>