1import TestCase from '../../TestCase';2import Fixture from '../../Fixture';3import PrintRectsFragmentContainer from './PrintRectsFragmentContainer';4import CompareDocumentPositionFragmentContainer from './CompareDocumentPositionFragmentContainer';5import EventFragmentContainer from './EventFragmentContainer';6import GetRootNodeFragmentContainer from './GetRootNodeFragmentContainer';78const React = window.React;9const {Fragment, useRef, useState} = React;1011function GetClientRectsTextOnly() {12 return (13 <TestCase title="getClientRects - Text Only">14 <TestCase.Steps>15 <li>Click the "Print Rects" button</li>16 </TestCase.Steps>17 <TestCase.ExpectedResult>18 The fragment contains only text nodes. getClientRects should return19 bounding rectangles for the text content using the Range API.20 </TestCase.ExpectedResult>21 <Fixture>22 <Fixture.Controls>23 <PrintRectsFragmentContainer>24 This is text content inside a fragment with no element children.25 </PrintRectsFragmentContainer>26 </Fixture.Controls>27 </Fixture>28 </TestCase>29 );30}3132function GetClientRectsMixed() {33 return (34 <TestCase title="getClientRects - Mixed Content">35 <TestCase.Steps>36 <li>Click the "Print Rects" button</li>37 </TestCase.Steps>38 <TestCase.ExpectedResult>39 The fragment contains both text nodes and elements. getClientRects40 should return bounding rectangles for both text content (via Range API)41 and elements.42 </TestCase.ExpectedResult>43 <Fixture>44 <Fixture.Controls>45 <PrintRectsFragmentContainer>46 Text before the span.47 <span48 style={{49 display: 'inline-block',50 padding: '5px 10px',51 backgroundColor: 'lightblue',52 border: '1px solid blue',53 margin: '0 5px',54 }}>55 Element56 </span>57 Text after the span.58 <div59 style={{60 width: '500px',61 height: '50px',62 backgroundColor: 'lightpink',63 border: '1px solid black',64 }}></div>65 More text at the end.66 </PrintRectsFragmentContainer>67 </Fixture.Controls>68 </Fixture>69 </TestCase>70 );71}7273function FocusTextOnlyNoop() {74 const fragmentRef = useRef(null);75 const [message, setMessage] = useState('');7677 const tryFocus = () => {78 fragmentRef.current.focus();79 setMessage('Called focus() - no-op for text-only fragments');80 };8182 const tryFocusLast = () => {83 fragmentRef.current.focusLast();84 setMessage('Called focusLast() - no-op for text-only fragments');85 };8687 return (88 <TestCase title="focus/focusLast - Text Only (No-op)">89 <TestCase.Steps>90 <li>Click either focus button</li>91 </TestCase.Steps>92 <TestCase.ExpectedResult>93 Calling focus() or focusLast() on a fragment with only text children is94 a no-op. Nothing happens and no warning is logged. This is because text95 nodes cannot receive focus.96 </TestCase.ExpectedResult>97 <Fixture>98 <Fixture.Controls>99 <button onClick={tryFocus}>focus()</button>100 <button onClick={tryFocusLast}>focusLast()</button>101 {message && (102 <div style={{marginTop: '10px', color: '#666'}}>{message}</div>103 )}104 </Fixture.Controls>105 <div106 style={{107 padding: '20px',108 backgroundColor: '#f5f5f5',109 border: '1px solid #ddd',110 }}>111 <Fragment ref={fragmentRef}>112 This fragment contains only text. Text nodes are not focusable.113 </Fragment>114 </div>115 </Fixture>116 </TestCase>117 );118}119120function ScrollIntoViewTextOnly() {121 const fragmentRef = useRef(null);122 const [message, setMessage] = useState('');123124 const tryScrollIntoView = alignToTop => {125 fragmentRef.current.scrollIntoView(alignToTop);126 setMessage(127 `Called scrollIntoView(${alignToTop}) - page should scroll to text`128 );129 };130131 return (132 <TestCase title="scrollIntoView - Text Only">133 <TestCase.Steps>134 <li>Scroll down the page so the text fragment is not visible</li>135 <li>Click one of the scrollIntoView buttons</li>136 </TestCase.Steps>137 <TestCase.ExpectedResult>138 The page should scroll to bring the text content into view. With139 alignToTop=true, the text should appear at the top of the viewport. With140 alignToTop=false, it should appear at the bottom. This uses the Range141 API to calculate text node positions.142 </TestCase.ExpectedResult>143 <Fixture>144 <Fixture.Controls>145 <button onClick={() => tryScrollIntoView(true)}>146 scrollIntoView(true)147 </button>148 <button onClick={() => tryScrollIntoView(false)}>149 scrollIntoView(false)150 </button>151 {message && (152 <div style={{marginTop: '10px', color: 'green'}}>{message}</div>153 )}154 </Fixture.Controls>155 <div156 style={{157 marginTop: '100vh',158 marginBottom: '100vh',159 padding: '20px',160 backgroundColor: '#f0fff0',161 border: '1px solid #cfc',162 }}>163 <Fragment ref={fragmentRef}>164 This fragment contains only text. The scrollIntoView method uses the165 Range API to calculate the text position and scroll to it.166 </Fragment>167 </div>168 </Fixture>169 </TestCase>170 );171}172173function ScrollIntoViewMixed() {174 const fragmentRef = useRef(null);175 const [message, setMessage] = useState('');176177 const tryScrollIntoView = alignToTop => {178 fragmentRef.current.scrollIntoView(alignToTop);179 setMessage(180 `Called scrollIntoView(${alignToTop}) - page should scroll to fragment`181 );182 };183184 const targetStyle = {185 height: 300,186 marginBottom: 50,187 display: 'flex',188 alignItems: 'center',189 justifyContent: 'center',190 fontSize: '24px',191 fontWeight: 'bold',192 };193194 return (195 <TestCase title="scrollIntoView - Mixed Content">196 <TestCase.Steps>197 <li>Scroll down the page so the fragment is not visible</li>198 <li>Click one of the scrollIntoView buttons</li>199 </TestCase.Steps>200 <TestCase.ExpectedResult>201 The fragment contains raw text nodes (not wrapped in elements) and202 elements in alternating order. With alignToTop=true, scroll starts from203 the last child and works backwards, ending with the first text node at204 the top. With alignToTop=false, scroll starts from the first child and205 works forward, ending with the last text node at the bottom. Text nodes206 use the Range API for scrolling.207 </TestCase.ExpectedResult>208 <Fixture>209 <Fixture.Controls>210 <button onClick={() => tryScrollIntoView(true)}>211 scrollIntoView(true)212 </button>213 <button onClick={() => tryScrollIntoView(false)}>214 scrollIntoView(false)215 </button>216 {message && (217 <div style={{marginTop: '10px', color: 'green'}}>{message}</div>218 )}219 </Fixture.Controls>220 <div221 style={{222 marginTop: '100vh',223 marginBottom: '100vh',224 whiteSpace: 'pre-wrap',225 lineHeight: '2',226 }}>227 <Fragment ref={fragmentRef}>228 TEXT NODE 1 - This is a raw text node at the start of the fragment229 <div style={{...targetStyle, backgroundColor: 'lightyellow'}}>230 ELEMENT 1231 </div>232 TEXT NODE 2 - This is a raw text node between elements233 <div style={{...targetStyle, backgroundColor: 'lightpink'}}>234 ELEMENT 2235 </div>236 TEXT NODE 3 - This is a raw text node between elements237 <div style={{...targetStyle, backgroundColor: 'lightcyan'}}>238 ELEMENT 3239 </div>240 TEXT NODE 4 - This is a raw text node at the end of the fragment241 </Fragment>242 </div>243 </Fixture>244 </TestCase>245 );246}247248function 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 fragment256 contains only text nodes. The "Before" element should be PRECEDING the257 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}269270function ObserveTextOnlyWarning() {271 const fragmentRef = useRef(null);272 const [message, setMessage] = useState('');273274 const tryObserve = () => {275 setMessage('Called observeUsing() - check console for warning');276 const observer = new IntersectionObserver(() => {});277 fragmentRef.current.observeUsing(observer);278 };279280 return (281 <TestCase title="observeUsing - Text Only Warning">282 <TestCase.Steps>283 <li>Open the browser console</li>284 <li>Click the observeUsing button</li>285 </TestCase.Steps>286 <TestCase.ExpectedResult>287 A warning should appear in the console because IntersectionObserver288 cannot observe text nodes. The warning message should indicate that289 observeUsing() was called on a FragmentInstance with only text children.290 </TestCase.ExpectedResult>291 <Fixture>292 <Fixture.Controls>293 <button onClick={tryObserve}>294 observeUsing(IntersectionObserver)295 </button>296 {message && (297 <div style={{marginTop: '10px', color: 'orange'}}>{message}</div>298 )}299 </Fixture.Controls>300 <div301 style={{302 padding: '20px',303 backgroundColor: '#fff0f0',304 border: '1px solid #fcc',305 }}>306 <Fragment ref={fragmentRef}>307 This fragment contains only text. Text nodes cannot be observed.308 </Fragment>309 </div>310 </Fixture>311 </TestCase>312 );313}314315function 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 fragment321 </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 the329 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}341342function 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 fragment348 </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 elements356 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 <span363 style={{364 display: 'inline-block',365 padding: '5px 10px',366 margin: '0 5px',367 backgroundColor: 'lightblue',368 border: '1px solid blue',369 }}>370 Element371 </span>372 Text node after element.373 </EventFragmentContainer>374 </Fixture.Controls>375 </Fixture>376 </TestCase>377 );378}379380function 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 the388 fragment's text content. For a fragment in the main document, this389 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}401402function 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 with410 mixed text and element content. The result is the same whether checking411 from text nodes or element nodes within the fragment.412 </TestCase.ExpectedResult>413 <Fixture>414 <Fixture.Controls>415 <GetRootNodeFragmentContainer>416 Text before element.417 <span418 style={{419 display: 'inline-block',420 padding: '5px 10px',421 margin: '0 5px',422 backgroundColor: 'lightyellow',423 border: '1px solid #cc0',424 }}>425 Element426 </span>427 Text after element.428 </GetRootNodeFragmentContainer>429 </Fixture.Controls>430 </Fixture>431 </TestCase>432 );433}434435export default function TextNodesCase() {436 return (437 <TestCase title="Text Node Support">438 <TestCase.ExpectedResult>439 <p>440 This section demonstrates how various FragmentInstance methods work441 with text nodes.442 </p>443 <p>444 <strong>Supported:</strong> getClientRects, compareDocumentPosition,445 scrollIntoView, getRootNode, addEventListener, removeEventListener,446 dispatchEvent447 </p>448 <p>449 <strong>No-op (silent):</strong> focus, focusLast (text nodes cannot450 receive focus)451 </p>452 <p>453 <strong>Not supported (warns):</strong> observeUsing (observers cannot454 observe text nodes)455 </p>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}
Findings
✓ No findings reported for this file.