mirror of
https://github.com/facebook/react.git
synced 2026-02-21 19:31:52 +00:00
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
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
import TestCase from '../../TestCase';
|
||||
import Fixture from '../../Fixture';
|
||||
import CompareDocumentPositionFragmentContainer from './CompareDocumentPositionFragmentContainer';
|
||||
|
||||
const React = window.React;
|
||||
|
||||
export default function CompareDocumentPositionCase() {
|
||||
return (
|
||||
<TestCase title="compareDocumentPosition">
|
||||
<TestCase.Steps>
|
||||
<li>Click the "Compare All Positions" button</li>
|
||||
</TestCase.Steps>
|
||||
<TestCase.ExpectedResult>
|
||||
The compareDocumentPosition method compares the position of the fragment
|
||||
relative to other elements in the DOM. The "Before Element" should be
|
||||
PRECEDING the fragment, and the "After Element" should be FOLLOWING.
|
||||
Elements inside the fragment should be CONTAINED_BY.
|
||||
</TestCase.ExpectedResult>
|
||||
<Fixture>
|
||||
<Fixture.Controls>
|
||||
<CompareDocumentPositionFragmentContainer>
|
||||
<div
|
||||
style={{
|
||||
padding: '10px',
|
||||
backgroundColor: 'lightblue',
|
||||
borderRadius: '4px',
|
||||
marginBottom: '8px',
|
||||
}}>
|
||||
First child element
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
padding: '10px',
|
||||
backgroundColor: 'lightgreen',
|
||||
borderRadius: '4px',
|
||||
marginBottom: '8px',
|
||||
}}>
|
||||
Second child element
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
padding: '10px',
|
||||
backgroundColor: 'lightpink',
|
||||
borderRadius: '4px',
|
||||
}}>
|
||||
Third child element
|
||||
</div>
|
||||
</CompareDocumentPositionFragmentContainer>
|
||||
</Fixture.Controls>
|
||||
</Fixture>
|
||||
</TestCase>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
const React = window.React;
|
||||
const {Fragment, useRef, useState} = React;
|
||||
|
||||
const POSITION_FLAGS = {
|
||||
DISCONNECTED: 0x01,
|
||||
PRECEDING: 0x02,
|
||||
FOLLOWING: 0x04,
|
||||
CONTAINS: 0x08,
|
||||
CONTAINED_BY: 0x10,
|
||||
IMPLEMENTATION_SPECIFIC: 0x20,
|
||||
};
|
||||
|
||||
function getPositionDescription(bitmask) {
|
||||
const flags = [];
|
||||
if (bitmask & POSITION_FLAGS.DISCONNECTED) flags.push('DISCONNECTED');
|
||||
if (bitmask & POSITION_FLAGS.PRECEDING) flags.push('PRECEDING');
|
||||
if (bitmask & POSITION_FLAGS.FOLLOWING) flags.push('FOLLOWING');
|
||||
if (bitmask & POSITION_FLAGS.CONTAINS) flags.push('CONTAINS');
|
||||
if (bitmask & POSITION_FLAGS.CONTAINED_BY) flags.push('CONTAINED_BY');
|
||||
if (bitmask & POSITION_FLAGS.IMPLEMENTATION_SPECIFIC)
|
||||
flags.push('IMPLEMENTATION_SPECIFIC');
|
||||
return flags.length > 0 ? flags.join(' | ') : 'SAME';
|
||||
}
|
||||
|
||||
function ResultRow({label, result, color}) {
|
||||
if (!result) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
padding: '10px 14px',
|
||||
marginBottom: '8px',
|
||||
backgroundColor: '#f8f9fa',
|
||||
borderLeft: `4px solid ${color}`,
|
||||
borderRadius: '4px',
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
fontWeight: 'bold',
|
||||
marginBottom: '6px',
|
||||
color: '#333',
|
||||
}}>
|
||||
{label}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'auto 1fr',
|
||||
gap: '4px 12px',
|
||||
fontSize: '13px',
|
||||
fontFamily: 'monospace',
|
||||
}}>
|
||||
<span style={{color: '#666'}}>Raw value:</span>
|
||||
<span style={{color: '#333'}}>{result.raw}</span>
|
||||
<span style={{color: '#666'}}>Flags:</span>
|
||||
<span style={{color: color, fontWeight: 500}}>
|
||||
{getPositionDescription(result.raw)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function CompareDocumentPositionFragmentContainer({children}) {
|
||||
const fragmentRef = useRef(null);
|
||||
const beforeRef = useRef(null);
|
||||
const afterRef = useRef(null);
|
||||
const insideRef = useRef(null);
|
||||
const [results, setResults] = useState(null);
|
||||
|
||||
const compareAll = () => {
|
||||
const fragment = fragmentRef.current;
|
||||
const beforePos = fragment.compareDocumentPosition(beforeRef.current);
|
||||
const afterPos = fragment.compareDocumentPosition(afterRef.current);
|
||||
const insidePos = insideRef.current
|
||||
? fragment.compareDocumentPosition(insideRef.current)
|
||||
: null;
|
||||
|
||||
setResults({
|
||||
before: {raw: beforePos},
|
||||
after: {raw: afterPos},
|
||||
inside: insidePos !== null ? {raw: insidePos} : null,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div style={{marginBottom: '16px'}}>
|
||||
<button
|
||||
onClick={compareAll}
|
||||
style={{
|
||||
padding: '8px 16px',
|
||||
fontSize: '14px',
|
||||
fontWeight: 'bold',
|
||||
cursor: 'pointer',
|
||||
}}>
|
||||
Compare All Positions
|
||||
</button>
|
||||
{results && (
|
||||
<span style={{marginLeft: '12px', color: '#666'}}>
|
||||
Comparison complete
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div style={{display: 'flex', gap: '24px'}}>
|
||||
<div style={{flex: '0 0 300px'}}>
|
||||
<div
|
||||
style={{
|
||||
padding: '16px',
|
||||
backgroundColor: '#f0f0f0',
|
||||
borderRadius: '8px',
|
||||
}}>
|
||||
<div
|
||||
ref={beforeRef}
|
||||
style={{
|
||||
padding: '12px',
|
||||
backgroundColor: '#d4edda',
|
||||
border: '2px solid #28a745',
|
||||
borderRadius: '4px',
|
||||
marginBottom: '12px',
|
||||
textAlign: 'center',
|
||||
fontWeight: 'bold',
|
||||
color: '#155724',
|
||||
}}>
|
||||
Before Element
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
padding: '12px',
|
||||
backgroundColor: '#fff3cd',
|
||||
border: '2px dashed #ffc107',
|
||||
borderRadius: '4px',
|
||||
marginBottom: '12px',
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
fontSize: '11px',
|
||||
color: '#856404',
|
||||
marginBottom: '8px',
|
||||
fontWeight: 'bold',
|
||||
}}>
|
||||
FRAGMENT
|
||||
</div>
|
||||
<div ref={insideRef}>
|
||||
<Fragment ref={fragmentRef}>{children}</Fragment>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
ref={afterRef}
|
||||
style={{
|
||||
padding: '12px',
|
||||
backgroundColor: '#f8d7da',
|
||||
border: '2px solid #dc3545',
|
||||
borderRadius: '4px',
|
||||
textAlign: 'center',
|
||||
fontWeight: 'bold',
|
||||
color: '#721c24',
|
||||
}}>
|
||||
After Element
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{flex: 1}}>
|
||||
<div
|
||||
style={{
|
||||
fontSize: '14px',
|
||||
fontWeight: 'bold',
|
||||
marginBottom: '12px',
|
||||
color: '#333',
|
||||
}}>
|
||||
Comparison Results
|
||||
</div>
|
||||
|
||||
{!results && (
|
||||
<div
|
||||
style={{
|
||||
padding: '20px',
|
||||
backgroundColor: '#f8f9fa',
|
||||
borderRadius: '4px',
|
||||
color: '#666',
|
||||
textAlign: 'center',
|
||||
}}>
|
||||
Click "Compare All Positions" to see results
|
||||
</div>
|
||||
)}
|
||||
|
||||
{results && (
|
||||
<Fragment>
|
||||
<ResultRow
|
||||
label='vs "Before Element"'
|
||||
result={results.before}
|
||||
color="#28a745"
|
||||
/>
|
||||
<ResultRow
|
||||
label='vs "After Element"'
|
||||
result={results.after}
|
||||
color="#dc3545"
|
||||
/>
|
||||
{results.inside && (
|
||||
<ResultRow
|
||||
label='vs "Inside Element"'
|
||||
result={results.inside}
|
||||
color="#ffc107"
|
||||
/>
|
||||
)}
|
||||
|
||||
<div
|
||||
style={{
|
||||
marginTop: '16px',
|
||||
padding: '12px',
|
||||
backgroundColor: '#e7f3ff',
|
||||
borderRadius: '4px',
|
||||
fontSize: '12px',
|
||||
color: '#0c5460',
|
||||
}}>
|
||||
<strong>Flag Reference:</strong>
|
||||
<div
|
||||
style={{
|
||||
marginTop: '8px',
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'auto 1fr',
|
||||
gap: '2px 12px',
|
||||
}}>
|
||||
<code>0x01</code>
|
||||
<span>DISCONNECTED</span>
|
||||
<code>0x02</code>
|
||||
<span>PRECEDING (other is before fragment)</span>
|
||||
<code>0x04</code>
|
||||
<span>FOLLOWING (other is after fragment)</span>
|
||||
<code>0x08</code>
|
||||
<span>CONTAINS (other contains fragment)</span>
|
||||
<code>0x10</code>
|
||||
<span>CONTAINED_BY (other is inside fragment)</span>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
const React = window.React;
|
||||
const {Fragment, useRef, useState} = React;
|
||||
|
||||
export default function EventFragmentContainer({children}) {
|
||||
const fragmentRef = useRef(null);
|
||||
const [eventLog, setEventLog] = useState([]);
|
||||
const [listenerAdded, setListenerAdded] = useState(false);
|
||||
const [bubblesState, setBubblesState] = useState(true);
|
||||
|
||||
const logEvent = message => {
|
||||
setEventLog(prev => [...prev, message]);
|
||||
};
|
||||
|
||||
const fragmentClickHandler = () => {
|
||||
logEvent('Fragment event listener fired');
|
||||
};
|
||||
|
||||
const addListener = () => {
|
||||
fragmentRef.current.addEventListener('click', fragmentClickHandler);
|
||||
setListenerAdded(true);
|
||||
logEvent('Added click listener to fragment');
|
||||
};
|
||||
|
||||
const removeListener = () => {
|
||||
fragmentRef.current.removeEventListener('click', fragmentClickHandler);
|
||||
setListenerAdded(false);
|
||||
logEvent('Removed click listener from fragment');
|
||||
};
|
||||
|
||||
const dispatchClick = () => {
|
||||
fragmentRef.current.dispatchEvent(
|
||||
new MouseEvent('click', {bubbles: bubblesState})
|
||||
);
|
||||
logEvent(`Dispatched click event (bubbles: ${bubblesState})`);
|
||||
};
|
||||
|
||||
const clearLog = () => {
|
||||
setEventLog([]);
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div
|
||||
style={{
|
||||
marginBottom: '16px',
|
||||
display: 'flex',
|
||||
gap: '8px',
|
||||
flexWrap: 'wrap',
|
||||
alignItems: 'center',
|
||||
}}>
|
||||
<select
|
||||
value={bubblesState ? 'true' : 'false'}
|
||||
onChange={e => setBubblesState(e.target.value === 'true')}
|
||||
style={{padding: '6px 10px'}}>
|
||||
<option value="true">Bubbles: true</option>
|
||||
<option value="false">Bubbles: false</option>
|
||||
</select>
|
||||
<button onClick={dispatchClick} style={{padding: '6px 12px'}}>
|
||||
Dispatch click event
|
||||
</button>
|
||||
<button
|
||||
onClick={addListener}
|
||||
disabled={listenerAdded}
|
||||
style={{padding: '6px 12px'}}>
|
||||
Add event listener
|
||||
</button>
|
||||
<button
|
||||
onClick={removeListener}
|
||||
disabled={!listenerAdded}
|
||||
style={{padding: '6px 12px'}}>
|
||||
Remove event listener
|
||||
</button>
|
||||
<button onClick={clearLog} style={{padding: '6px 12px'}}>
|
||||
Clear log
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
onClick={() => logEvent('Parent div clicked')}
|
||||
style={{
|
||||
padding: '12px',
|
||||
border: '1px dashed #ccc',
|
||||
borderRadius: '4px',
|
||||
backgroundColor: '#fff',
|
||||
}}>
|
||||
<Fragment ref={fragmentRef}>{children}</Fragment>
|
||||
</div>
|
||||
|
||||
{eventLog.length > 0 && (
|
||||
<div
|
||||
style={{
|
||||
marginTop: '12px',
|
||||
padding: '10px',
|
||||
backgroundColor: '#f5f5f5',
|
||||
border: '1px solid #ddd',
|
||||
borderRadius: '4px',
|
||||
maxHeight: '150px',
|
||||
overflow: 'auto',
|
||||
fontFamily: 'monospace',
|
||||
fontSize: '13px',
|
||||
}}>
|
||||
<strong>Event Log:</strong>
|
||||
<ul style={{margin: '5px 0', paddingLeft: '20px'}}>
|
||||
{eventLog.map((msg, i) => (
|
||||
<li key={i}>{msg}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
@@ -1,46 +1,35 @@
|
||||
import TestCase from '../../TestCase';
|
||||
import Fixture from '../../Fixture';
|
||||
import EventFragmentContainer from './EventFragmentContainer';
|
||||
|
||||
const React = window.React;
|
||||
const {Fragment, useEffect, useRef, useState} = React;
|
||||
const {useState} = React;
|
||||
|
||||
function WrapperComponent(props) {
|
||||
return props.children;
|
||||
}
|
||||
|
||||
function handler(e) {
|
||||
const text = e.currentTarget.innerText;
|
||||
alert('You clicked: ' + text);
|
||||
}
|
||||
|
||||
export default function EventListenerCase() {
|
||||
const fragmentRef = useRef(null);
|
||||
const [extraChildCount, setExtraChildCount] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
fragmentRef.current.addEventListener('click', handler);
|
||||
|
||||
const lastFragmentRefValue = fragmentRef.current;
|
||||
return () => {
|
||||
lastFragmentRefValue.removeEventListener('click', handler);
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<TestCase title="Event Registration">
|
||||
<TestCase.Steps>
|
||||
<li>Click one of the children, observe the alert</li>
|
||||
<li>Add a new child, click it, observe the alert</li>
|
||||
<li>Remove the event listeners, click a child, observe no alert</li>
|
||||
<li>Add the event listeners back, click a child, observe the alert</li>
|
||||
<li>
|
||||
Click "Add event listener" to attach a click handler to the fragment
|
||||
</li>
|
||||
<li>Click "Dispatch click event" to dispatch a click event</li>
|
||||
<li>Observe the event log showing the event fired</li>
|
||||
<li>Add a new child, dispatch again to see it still works</li>
|
||||
<li>
|
||||
Click "Remove event listener" and dispatch again to see no event fires
|
||||
</li>
|
||||
</TestCase.Steps>
|
||||
|
||||
<TestCase.ExpectedResult>
|
||||
<p>
|
||||
Fragment refs can manage event listeners on the first level of host
|
||||
children. This page loads with an effect that sets up click event
|
||||
hanndlers on each child card. Clicking on a card will show an alert
|
||||
with the card's text.
|
||||
children. The event log shows when events are dispatched and handled.
|
||||
</p>
|
||||
<p>
|
||||
New child nodes will also have event listeners applied. Removed nodes
|
||||
@@ -50,28 +39,17 @@ export default function EventListenerCase() {
|
||||
|
||||
<Fixture>
|
||||
<Fixture.Controls>
|
||||
<div>Target count: {extraChildCount + 3}</div>
|
||||
<button
|
||||
onClick={() => {
|
||||
setExtraChildCount(prev => prev + 1);
|
||||
}}>
|
||||
Add Child
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
fragmentRef.current.addEventListener('click', handler);
|
||||
}}>
|
||||
Add click event listeners
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
fragmentRef.current.removeEventListener('click', handler);
|
||||
}}>
|
||||
Remove click event listeners
|
||||
</button>
|
||||
</Fixture.Controls>
|
||||
<div className="card-container">
|
||||
<Fragment ref={fragmentRef}>
|
||||
<div style={{marginBottom: '10px'}}>
|
||||
Target count: {extraChildCount + 3}
|
||||
<button
|
||||
onClick={() => {
|
||||
setExtraChildCount(prev => prev + 1);
|
||||
}}
|
||||
style={{marginLeft: '10px'}}>
|
||||
Add Child
|
||||
</button>
|
||||
</div>
|
||||
<EventFragmentContainer>
|
||||
<div className="card" id="child-a">
|
||||
Child A
|
||||
</div>
|
||||
@@ -88,8 +66,8 @@ export default function EventListenerCase() {
|
||||
</div>
|
||||
))}
|
||||
</WrapperComponent>
|
||||
</Fragment>
|
||||
</div>
|
||||
</EventFragmentContainer>
|
||||
</Fixture.Controls>
|
||||
</Fixture>
|
||||
</TestCase>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
const React = window.React;
|
||||
const {Fragment, useRef, useState} = React;
|
||||
|
||||
export default function GetRootNodeFragmentContainer({children}) {
|
||||
const fragmentRef = useRef(null);
|
||||
const [rootNodeInfo, setRootNodeInfo] = useState(null);
|
||||
|
||||
const getRootNodeInfo = () => {
|
||||
const rootNode = fragmentRef.current.getRootNode();
|
||||
setRootNodeInfo({
|
||||
nodeName: rootNode.nodeName,
|
||||
nodeType: rootNode.nodeType,
|
||||
nodeTypeLabel: getNodeTypeLabel(rootNode.nodeType),
|
||||
isDocument: rootNode === document,
|
||||
});
|
||||
};
|
||||
|
||||
const getNodeTypeLabel = nodeType => {
|
||||
const types = {
|
||||
1: 'ELEMENT_NODE',
|
||||
3: 'TEXT_NODE',
|
||||
9: 'DOCUMENT_NODE',
|
||||
11: 'DOCUMENT_FRAGMENT_NODE',
|
||||
};
|
||||
return types[nodeType] || `UNKNOWN (${nodeType})`;
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div style={{marginBottom: '16px'}}>
|
||||
<button
|
||||
onClick={getRootNodeInfo}
|
||||
style={{
|
||||
padding: '8px 16px',
|
||||
fontSize: '14px',
|
||||
fontWeight: 'bold',
|
||||
cursor: 'pointer',
|
||||
}}>
|
||||
Get Root Node
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{rootNodeInfo && (
|
||||
<div
|
||||
style={{
|
||||
marginBottom: '16px',
|
||||
padding: '12px',
|
||||
backgroundColor: '#e8f4e8',
|
||||
border: '1px solid #9c9',
|
||||
borderRadius: '4px',
|
||||
fontFamily: 'monospace',
|
||||
fontSize: '13px',
|
||||
}}>
|
||||
<div style={{marginBottom: '4px'}}>
|
||||
<strong>Node Name:</strong> {rootNodeInfo.nodeName}
|
||||
</div>
|
||||
<div style={{marginBottom: '4px'}}>
|
||||
<strong>Node Type:</strong> {rootNodeInfo.nodeType} (
|
||||
{rootNodeInfo.nodeTypeLabel})
|
||||
</div>
|
||||
<div>
|
||||
<strong>Is Document:</strong>{' '}
|
||||
{rootNodeInfo.isDocument ? 'Yes' : 'No'}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
style={{
|
||||
padding: '12px',
|
||||
border: '1px dashed #ccc',
|
||||
borderRadius: '4px',
|
||||
backgroundColor: '#fff',
|
||||
}}>
|
||||
<Fragment ref={fragmentRef}>{children}</Fragment>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
import TestCase from '../../TestCase';
|
||||
import Fixture from '../../Fixture';
|
||||
import PrintRectsFragmentContainer from './PrintRectsFragmentContainer';
|
||||
import CompareDocumentPositionFragmentContainer from './CompareDocumentPositionFragmentContainer';
|
||||
import EventFragmentContainer from './EventFragmentContainer';
|
||||
import GetRootNodeFragmentContainer from './GetRootNodeFragmentContainer';
|
||||
|
||||
const React = window.React;
|
||||
const {Fragment, useRef, useState} = React;
|
||||
@@ -242,6 +245,28 @@ function ScrollIntoViewMixed() {
|
||||
);
|
||||
}
|
||||
|
||||
function CompareDocumentPositionTextNodes() {
|
||||
return (
|
||||
<TestCase title="compareDocumentPosition - Text Only">
|
||||
<TestCase.Steps>
|
||||
<li>Click the "Compare All Positions" button</li>
|
||||
</TestCase.Steps>
|
||||
<TestCase.ExpectedResult>
|
||||
compareDocumentPosition should work correctly even when the fragment
|
||||
contains only text nodes. The "Before" element should be PRECEDING the
|
||||
fragment, and the "After" element should be FOLLOWING.
|
||||
</TestCase.ExpectedResult>
|
||||
<Fixture>
|
||||
<Fixture.Controls>
|
||||
<CompareDocumentPositionFragmentContainer>
|
||||
This is text-only content inside the fragment.
|
||||
</CompareDocumentPositionFragmentContainer>
|
||||
</Fixture.Controls>
|
||||
</Fixture>
|
||||
</TestCase>
|
||||
);
|
||||
}
|
||||
|
||||
function ObserveTextOnlyWarning() {
|
||||
const fragmentRef = useRef(null);
|
||||
const [message, setMessage] = useState('');
|
||||
@@ -287,6 +312,126 @@ function ObserveTextOnlyWarning() {
|
||||
);
|
||||
}
|
||||
|
||||
function EventTextOnly() {
|
||||
return (
|
||||
<TestCase title="Event Operations - Text Only">
|
||||
<TestCase.Steps>
|
||||
<li>
|
||||
Click "Add event listener" to attach a click handler to the fragment
|
||||
</li>
|
||||
<li>Click "Dispatch click event" to dispatch a click event</li>
|
||||
<li>Observe that the fragment's event listener fires</li>
|
||||
<li>Click "Remove event listener" and dispatch again</li>
|
||||
</TestCase.Steps>
|
||||
<TestCase.ExpectedResult>
|
||||
Event operations (addEventListener, removeEventListener, dispatchEvent)
|
||||
work on fragments with text-only content. The event is dispatched on the
|
||||
fragment's parent element since text nodes cannot be event targets.
|
||||
</TestCase.ExpectedResult>
|
||||
<Fixture>
|
||||
<Fixture.Controls>
|
||||
<EventFragmentContainer>
|
||||
This fragment contains only text. Events are handled via the parent.
|
||||
</EventFragmentContainer>
|
||||
</Fixture.Controls>
|
||||
</Fixture>
|
||||
</TestCase>
|
||||
);
|
||||
}
|
||||
|
||||
function EventMixed() {
|
||||
return (
|
||||
<TestCase title="Event Operations - Mixed Content">
|
||||
<TestCase.Steps>
|
||||
<li>
|
||||
Click "Add event listener" to attach a click handler to the fragment
|
||||
</li>
|
||||
<li>Click "Dispatch click event" to dispatch a click event</li>
|
||||
<li>Observe that the fragment's event listener fires</li>
|
||||
<li>Click directly on the element or text content to see bubbling</li>
|
||||
</TestCase.Steps>
|
||||
<TestCase.ExpectedResult>
|
||||
Event operations work on fragments with mixed text and element content.
|
||||
dispatchEvent forwards to the parent element. Clicks on child elements
|
||||
or text bubble up through the DOM as normal.
|
||||
</TestCase.ExpectedResult>
|
||||
<Fixture>
|
||||
<Fixture.Controls>
|
||||
<EventFragmentContainer>
|
||||
Text node before element.
|
||||
<span
|
||||
style={{
|
||||
display: 'inline-block',
|
||||
padding: '5px 10px',
|
||||
margin: '0 5px',
|
||||
backgroundColor: 'lightblue',
|
||||
border: '1px solid blue',
|
||||
}}>
|
||||
Element
|
||||
</span>
|
||||
Text node after element.
|
||||
</EventFragmentContainer>
|
||||
</Fixture.Controls>
|
||||
</Fixture>
|
||||
</TestCase>
|
||||
);
|
||||
}
|
||||
|
||||
function GetRootNodeTextOnly() {
|
||||
return (
|
||||
<TestCase title="getRootNode - Text Only">
|
||||
<TestCase.Steps>
|
||||
<li>Click the "Get Root Node" button</li>
|
||||
</TestCase.Steps>
|
||||
<TestCase.ExpectedResult>
|
||||
getRootNode should return the root of the DOM tree containing the
|
||||
fragment's text content. For a fragment in the main document, this
|
||||
should return the Document node.
|
||||
</TestCase.ExpectedResult>
|
||||
<Fixture>
|
||||
<Fixture.Controls>
|
||||
<GetRootNodeFragmentContainer>
|
||||
This fragment contains only text. getRootNode returns the document.
|
||||
</GetRootNodeFragmentContainer>
|
||||
</Fixture.Controls>
|
||||
</Fixture>
|
||||
</TestCase>
|
||||
);
|
||||
}
|
||||
|
||||
function GetRootNodeMixed() {
|
||||
return (
|
||||
<TestCase title="getRootNode - Mixed Content">
|
||||
<TestCase.Steps>
|
||||
<li>Click the "Get Root Node" button</li>
|
||||
</TestCase.Steps>
|
||||
<TestCase.ExpectedResult>
|
||||
getRootNode should return the root of the DOM tree for fragments with
|
||||
mixed text and element content. The result is the same whether checking
|
||||
from text nodes or element nodes within the fragment.
|
||||
</TestCase.ExpectedResult>
|
||||
<Fixture>
|
||||
<Fixture.Controls>
|
||||
<GetRootNodeFragmentContainer>
|
||||
Text before element.
|
||||
<span
|
||||
style={{
|
||||
display: 'inline-block',
|
||||
padding: '5px 10px',
|
||||
margin: '0 5px',
|
||||
backgroundColor: 'lightyellow',
|
||||
border: '1px solid #cc0',
|
||||
}}>
|
||||
Element
|
||||
</span>
|
||||
Text after element.
|
||||
</GetRootNodeFragmentContainer>
|
||||
</Fixture.Controls>
|
||||
</Fixture>
|
||||
</TestCase>
|
||||
);
|
||||
}
|
||||
|
||||
export default function TextNodesCase() {
|
||||
return (
|
||||
<TestCase title="Text Node Support">
|
||||
@@ -297,7 +442,8 @@ export default function TextNodesCase() {
|
||||
</p>
|
||||
<p>
|
||||
<strong>Supported:</strong> getClientRects, compareDocumentPosition,
|
||||
scrollIntoView
|
||||
scrollIntoView, getRootNode, addEventListener, removeEventListener,
|
||||
dispatchEvent
|
||||
</p>
|
||||
<p>
|
||||
<strong>No-op (silent):</strong> focus, focusLast (text nodes cannot
|
||||
@@ -310,10 +456,15 @@ export default function TextNodesCase() {
|
||||
</TestCase.ExpectedResult>
|
||||
<GetClientRectsTextOnly />
|
||||
<GetClientRectsMixed />
|
||||
<CompareDocumentPositionTextNodes />
|
||||
<FocusTextOnlyNoop />
|
||||
<ScrollIntoViewTextOnly />
|
||||
<ScrollIntoViewMixed />
|
||||
<ObserveTextOnlyWarning />
|
||||
<EventTextOnly />
|
||||
<EventMixed />
|
||||
<GetRootNodeTextOnly />
|
||||
<GetRootNodeMixed />
|
||||
</TestCase>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import IntersectionObserverCase from './IntersectionObserverCase';
|
||||
import ResizeObserverCase from './ResizeObserverCase';
|
||||
import FocusCase from './FocusCase';
|
||||
import GetClientRectsCase from './GetClientRectsCase';
|
||||
import CompareDocumentPositionCase from './CompareDocumentPositionCase';
|
||||
import ScrollIntoViewCase from './ScrollIntoViewCase';
|
||||
import TextNodesCase from './TextNodesCase';
|
||||
|
||||
@@ -19,6 +20,7 @@ export default function FragmentRefsPage() {
|
||||
<ResizeObserverCase />
|
||||
<FocusCase />
|
||||
<GetClientRectsCase />
|
||||
<CompareDocumentPositionCase />
|
||||
<ScrollIntoViewCase />
|
||||
<TextNodesCase />
|
||||
</FixtureSet>
|
||||
|
||||
Reference in New Issue
Block a user