1
+import TestCase from '../../TestCase';
2
+import Fixture from '../../Fixture';
3
+import PrintRectsFragmentContainer from './PrintRectsFragmentContainer';
4
+
5
+const React = window.React;
6
+const {Fragment, useRef, useState} = React;
7
+
8
+function GetClientRectsTextOnly() {
9
+ return (
10
+ <TestCase title="getClientRects - Text Only">
11
+ <TestCase.Steps>
12
+ <li>Click the "Print Rects" button</li>
13
+ </TestCase.Steps>
14
+ <TestCase.ExpectedResult>
15
+ The fragment contains only text nodes. getClientRects should return
16
+ bounding rectangles for the text content using the Range API.
17
+ </TestCase.ExpectedResult>
18
+ <Fixture>
19
+ <Fixture.Controls>
20
+ <PrintRectsFragmentContainer>
21
+ This is text content inside a fragment with no element children.
22
+ </PrintRectsFragmentContainer>
23
+ </Fixture.Controls>
24
+ </Fixture>
25
+ </TestCase>
26
+ );
27
+}
28
+
29
+function GetClientRectsMixed() {
30
+ return (
31
+ <TestCase title="getClientRects - Mixed Content">
32
+ <TestCase.Steps>
33
+ <li>Click the "Print Rects" button</li>
34
+ </TestCase.Steps>
35
+ <TestCase.ExpectedResult>
36
+ The fragment contains both text nodes and elements. getClientRects
37
+ should return bounding rectangles for both text content (via Range API)
38
+ and elements.
39
+ </TestCase.ExpectedResult>
40
+ <Fixture>
41
+ <Fixture.Controls>
42
+ <PrintRectsFragmentContainer>
43
+ Text before the span.
44
+ <span
45
+ style={{
46
+ display: 'inline-block',
47
+ padding: '5px 10px',
48
+ backgroundColor: 'lightblue',
49
+ border: '1px solid blue',
50
+ margin: '0 5px',
51
+ }}>
52
+ Element
53
+ </span>
54
+ Text after the span.
55
+ <div
56
+ style={{
57
+ width: '500px',
58
+ height: '50px',
59
+ backgroundColor: 'lightpink',
60
+ border: '1px solid black',
61
+ }}></div>
62
+ More text at the end.
63
+ </PrintRectsFragmentContainer>
64
+ </Fixture.Controls>
65
+ </Fixture>
66
+ </TestCase>
67
+ );
68
+}
69
+
70
+function FocusTextOnlyNoop() {
71
+ const fragmentRef = useRef(null);
72
+ const [message, setMessage] = useState('');
73
+
74
+ const tryFocus = () => {
75
+ fragmentRef.current.focus();
76
+ setMessage('Called focus() - no-op for text-only fragments');
77
+ };
78
+
79
+ const tryFocusLast = () => {
80
+ fragmentRef.current.focusLast();
81
+ setMessage('Called focusLast() - no-op for text-only fragments');
82
+ };
83
+
84
+ return (
85
+ <TestCase title="focus/focusLast - Text Only (No-op)">
86
+ <TestCase.Steps>
87
+ <li>Click either focus button</li>
88
+ </TestCase.Steps>
89
+ <TestCase.ExpectedResult>
90
+ Calling focus() or focusLast() on a fragment with only text children is
91
+ a no-op. Nothing happens and no warning is logged. This is because text
92
+ nodes cannot receive focus.
93
+ </TestCase.ExpectedResult>
94
+ <Fixture>
95
+ <Fixture.Controls>
96
+ <button onClick={tryFocus}>focus()</button>
97
+ <button onClick={tryFocusLast}>focusLast()</button>
98
+ {message && (
99
+ <div style={{marginTop: '10px', color: '#666'}}>{message}</div>
100
+ )}
101
+ </Fixture.Controls>
102
+ <div
103
+ style={{
104
+ padding: '20px',
105
+ backgroundColor: '#f5f5f5',
106
+ border: '1px solid #ddd',
107
+ }}>
108
+ <Fragment ref={fragmentRef}>
109
+ This fragment contains only text. Text nodes are not focusable.
110
+ </Fragment>
111
+ </div>
112
+ </Fixture>
113
+ </TestCase>
114
+ );
115
+}
116
+
117
+function ScrollIntoViewTextOnly() {
118
+ const fragmentRef = useRef(null);
119
+ const [message, setMessage] = useState('');
120
+
121
+ const tryScrollIntoView = alignToTop => {
122
+ fragmentRef.current.scrollIntoView(alignToTop);
123
+ setMessage(
124
+ `Called scrollIntoView(${alignToTop}) - page should scroll to text`
125
+ );
126
+ };
127
+
128
+ return (
129
+ <TestCase title="scrollIntoView - Text Only">
130
+ <TestCase.Steps>
131
+ <li>Scroll down the page so the text fragment is not visible</li>
132
+ <li>Click one of the scrollIntoView buttons</li>
133
+ </TestCase.Steps>
134
+ <TestCase.ExpectedResult>
135
+ The page should scroll to bring the text content into view. With
136
+ alignToTop=true, the text should appear at the top of the viewport. With
137
+ alignToTop=false, it should appear at the bottom. This uses the Range
138
+ API to calculate text node positions.
139
+ </TestCase.ExpectedResult>
140
+ <Fixture>
141
+ <Fixture.Controls>
142
+ <button onClick={() => tryScrollIntoView(true)}>
143
+ scrollIntoView(true)
144
+ </button>
145
+ <button onClick={() => tryScrollIntoView(false)}>
146
+ scrollIntoView(false)
147
+ </button>
148
+ {message && (
149
+ <div style={{marginTop: '10px', color: 'green'}}>{message}</div>
150
+ )}
151
+ </Fixture.Controls>
152
+ <div
153
+ style={{
154
+ marginTop: '100vh',
155
+ marginBottom: '100vh',
156
+ padding: '20px',
157
+ backgroundColor: '#f0fff0',
158
+ border: '1px solid #cfc',
159
+ }}>
160
+ <Fragment ref={fragmentRef}>
161
+ This fragment contains only text. The scrollIntoView method uses the
162
+ Range API to calculate the text position and scroll to it.
163
+ </Fragment>
164
+ </div>
165
+ </Fixture>
166
+ </TestCase>
167
+ );
168
+}
169
+
170
+function ScrollIntoViewMixed() {
171
+ const fragmentRef = useRef(null);
172
+ const [message, setMessage] = useState('');
173
+
174
+ const tryScrollIntoView = alignToTop => {
175
+ fragmentRef.current.scrollIntoView(alignToTop);
176
+ setMessage(
177
+ `Called scrollIntoView(${alignToTop}) - page should scroll to fragment`
178
+ );
179
+ };
180
+
181
+ const targetStyle = {
182
+ height: 300,
183
+ marginBottom: 50,
184
+ display: 'flex',
185
+ alignItems: 'center',
186
+ justifyContent: 'center',
187
+ fontSize: '24px',
188
+ fontWeight: 'bold',
189
+ };
190
+
191
+ return (
192
+ <TestCase title="scrollIntoView - Mixed Content">
193
+ <TestCase.Steps>
194
+ <li>Scroll down the page so the fragment is not visible</li>
195
+ <li>Click one of the scrollIntoView buttons</li>
196
+ </TestCase.Steps>
197
+ <TestCase.ExpectedResult>
198
+ The fragment contains raw text nodes (not wrapped in elements) and
199
+ elements in alternating order. With alignToTop=true, scroll starts from
200
+ the last child and works backwards, ending with the first text node at
201
+ the top. With alignToTop=false, scroll starts from the first child and
202
+ works forward, ending with the last text node at the bottom. Text nodes
203
+ use the Range API for scrolling.
204
+ </TestCase.ExpectedResult>
205
+ <Fixture>
206
+ <Fixture.Controls>
207
+ <button onClick={() => tryScrollIntoView(true)}>
208
+ scrollIntoView(true)
209
+ </button>
210
+ <button onClick={() => tryScrollIntoView(false)}>
211
+ scrollIntoView(false)
212
+ </button>
213
+ {message && (
214
+ <div style={{marginTop: '10px', color: 'green'}}>{message}</div>
215
+ )}
216
+ </Fixture.Controls>
217
+ <div
218
+ style={{
219
+ marginTop: '100vh',
220
+ marginBottom: '100vh',
221
+ whiteSpace: 'pre-wrap',
222
+ lineHeight: '2',
223
+ }}>
224
+ <Fragment ref={fragmentRef}>
225
+ TEXT NODE 1 - This is a raw text node at the start of the fragment
226
+ <div style={{...targetStyle, backgroundColor: 'lightyellow'}}>
227
+ ELEMENT 1
228
+ </div>
229
+ TEXT NODE 2 - This is a raw text node between elements
230
+ <div style={{...targetStyle, backgroundColor: 'lightpink'}}>
231
+ ELEMENT 2
232
+ </div>
233
+ TEXT NODE 3 - This is a raw text node between elements
234
+ <div style={{...targetStyle, backgroundColor: 'lightcyan'}}>
235
+ ELEMENT 3
236
+ </div>
237
+ TEXT NODE 4 - This is a raw text node at the end of the fragment
238
+ </Fragment>
239
+ </div>
240
+ </Fixture>
241
+ </TestCase>
242
+ );
243
+}
244
+
245
+function ObserveTextOnlyWarning() {
246
+ const fragmentRef = useRef(null);
247
+ const [message, setMessage] = useState('');
248
+
249
+ const tryObserve = () => {
250
+ setMessage('Called observeUsing() - check console for warning');
251
+ const observer = new IntersectionObserver(() => {});
252
+ fragmentRef.current.observeUsing(observer);
253
+ };
254
+
255
+ return (
256
+ <TestCase title="observeUsing - Text Only Warning">
257
+ <TestCase.Steps>
258
+ <li>Open the browser console</li>
259
+ <li>Click the observeUsing button</li>
260
+ </TestCase.Steps>
261
+ <TestCase.ExpectedResult>
262
+ A warning should appear in the console because IntersectionObserver
263
+ cannot observe text nodes. The warning message should indicate that
264
+ observeUsing() was called on a FragmentInstance with only text children.
265
+ </TestCase.ExpectedResult>
266
+ <Fixture>
267
+ <Fixture.Controls>
268
+ <button onClick={tryObserve}>
269
+ observeUsing(IntersectionObserver)
270
+ </button>
271
+ {message && (
272
+ <div style={{marginTop: '10px', color: 'orange'}}>{message}</div>
273
+ )}
274
+ </Fixture.Controls>
275
+ <div
276
+ style={{
277
+ padding: '20px',
278
+ backgroundColor: '#fff0f0',
279
+ border: '1px solid #fcc',
280
+ }}>
281
+ <Fragment ref={fragmentRef}>
282
+ This fragment contains only text. Text nodes cannot be observed.
283
+ </Fragment>
284
+ </div>
285
+ </Fixture>
286
+ </TestCase>
287
+ );
288
+}
289
+
290
+export default function TextNodesCase() {
291
+ return (
292
+ <TestCase title="Text Node Support">
293
+ <TestCase.ExpectedResult>
294
+ <p>
295
+ This section demonstrates how various FragmentInstance methods work
296
+ with text nodes.
297
+ </p>
298
+ <p>
299
+ <strong>Supported:</strong> getClientRects, compareDocumentPosition,
300
+ scrollIntoView
301
+ </p>
302
+ <p>
303
+ <strong>No-op (silent):</strong> focus, focusLast (text nodes cannot
304
+ receive focus)
305
+ </p>
306
+ <p>
307
+ <strong>Not supported (warns):</strong> observeUsing (observers cannot
308
+ observe text nodes)
309
+ </p>
310
+ </TestCase.ExpectedResult>
311
+ <GetClientRectsTextOnly />
312
+ <GetClientRectsMixed />
313
+ <FocusTextOnlyNoop />
314
+ <ScrollIntoViewTextOnly />
315
+ <ScrollIntoViewMixed />
316
+ <ObserveTextOnlyWarning />
317
+ </TestCase>
318
+ );
319
+}