Skip to content

Commit

Permalink
Create DOM fixture that tests state preservation of timed out content
Browse files Browse the repository at this point in the history
  • Loading branch information
acdlite committed Oct 15, 2018
1 parent 46386e2 commit 68f9ccc
Show file tree
Hide file tree
Showing 2 changed files with 298 additions and 0 deletions.
1 change: 1 addition & 0 deletions fixtures/dom/src/components/Header.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ class Header extends React.Component {
<option value="/pointer-events">Pointer Events</option>
<option value="/mouse-events">Mouse Events</option>
<option value="/selection-events">Selection Events</option>
<option value="/suspense">Suspense</option>
</select>
</label>
<label htmlFor="react_version">
Expand Down
297 changes: 297 additions & 0 deletions fixtures/dom/src/components/fixtures/suspense/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,297 @@
import Fixture from '../../Fixture';
import FixtureSet from '../../FixtureSet';
import TestCase from '../../TestCase';

const React = window.React;
const ReactDOM = window.ReactDOM;

const Suspense = React.unstable_Suspense;

let cache = new Set();

function AsyncStep({text, ms}) {
if (!cache.has(text)) {
throw new Promise(resolve =>
setTimeout(() => {
cache.add(text);
resolve();
}, ms)
);
}
return null;
}

let suspendyTreeIdCounter = 0;
class SuspendyTreeChild extends React.Component {
id = suspendyTreeIdCounter++;
state = {
step: 1,
isHidden: false,
};
increment = () => this.setState(s => ({step: s.step + 1}));

componentDidMount() {
document.addEventListener('keydown', this.onKeydown);
}

componentWillUnmount() {
document.removeEventListener('keydown', this.onKeydown);
}

onKeydown = event => {
if (event.metaKey && event.key === 'Enter') {
this.increment();
}
};

render() {
return (
<React.Fragment>
<Suspense fallback={<div>(display: none)</div>}>
<div>
<AsyncStep text={`${this.state.step} + ${this.id}`} ms={500} />
{this.props.children}
</div>
</Suspense>
<button onClick={this.increment}>Hide</button>
</React.Fragment>
);
}
}

class SuspendyTree extends React.Component {
parentContainer = React.createRef(null);
container = React.createRef(null);
componentDidMount() {
this.setState({});
document.addEventListener('keydown', this.onKeydown);
}
componentWillUnmount() {
document.removeEventListener('keydown', this.onKeydown);
}
onKeydown = event => {
if (event.metaKey && event.key === '/') {
this.removeAndRestore();
}
};
removeAndRestore = () => {
const parentContainer = this.parentContainer.current;
const container = this.container.current;
parentContainer.removeChild(container);
parentContainer.textContent = '(removed from DOM)';
setTimeout(() => {
parentContainer.textContent = '';
parentContainer.appendChild(container);
}, 500);
};
render() {
return (
<React.Fragment>
<div ref={this.parentContainer}>
<div ref={this.container} />
</div>
<div>
{this.container.current !== null
? ReactDOM.createPortal(
<React.Fragment>
<SuspendyTreeChild>{this.props.children}</SuspendyTreeChild>
<button onClick={this.removeAndRestore}>Remove</button>
</React.Fragment>,
this.container.current
)
: null}
</div>
</React.Fragment>
);
}
}

class TextInputFixtures extends React.Component {
render() {
return (
<FixtureSet
title="Suspense"
description="Preserving the state of timed-out children">
<div className="footnote">
Clicking "Hide" will cause the fixtures to time-out for 1 second, then
restore. You can also use Command + Enter (or Control + Enter on
Windows, Linux), which is useful for preserving focus.
</div>
<TestCase title="Text selection where entire range times out">
<TestCase.Steps>
<li>Use your cursor to select the text below.</li>
<li>Click "Hide" or "Remove".</li>
</TestCase.Steps>

<TestCase.ExpectedResult>
Text selection is preserved when hiding, but not when removing.
</TestCase.ExpectedResult>

<Fixture>
<SuspendyTree>
Select this entire sentence (and only this sentence).
</SuspendyTree>
</Fixture>
</TestCase>
<TestCase title="Text selection that extends outside timed-out subtree">
<TestCase.Steps>
<li>
Use your cursor to select a range that includes both the text and
the "Go" button.
</li>
<li>Click "Hide" or "Remove".</li>
</TestCase.Steps>

<TestCase.ExpectedResult>
Text selection is preserved when hiding, but not when removing.
</TestCase.ExpectedResult>

<Fixture>
<SuspendyTree>
Select a range that includes both this sentence and the "Go"
button.
</SuspendyTree>
</Fixture>
</TestCase>
<TestCase title="Focus">
<TestCase.Steps>
<li>
Use your cursor to select a range that includes both the text and
the "Go" button.
</li>
<li>
Intead of clicking "Go", which switches focus, press Command +
Enter (or Control + Enter on Windows, Linux).
</li>
</TestCase.Steps>

<TestCase.ExpectedResult>
The ideal behavior is that the focus would not be lost, but
currently it is (both when hiding and removing).
</TestCase.ExpectedResult>

<Fixture>
<SuspendyTree>
<button>Focus me</button>
</SuspendyTree>
</Fixture>
</TestCase>

<TestCase title="Uncontrolled form input">
<TestCase.Steps>
<li>Type something ("Hello") into the text input.</li>
<li>Click "Hide" or "Remove".</li>
</TestCase.Steps>

<TestCase.ExpectedResult>
Input is preserved when hiding, but not when removing.
</TestCase.ExpectedResult>

<Fixture>
<SuspendyTree>
<input type="text" />
</SuspendyTree>
</Fixture>
</TestCase>

<TestCase title="Image flicker">
<TestCase.Steps>
<li>Click "Hide" or "Remove".</li>
</TestCase.Steps>

<TestCase.ExpectedResult>
The image should reappear without flickering. The text should not
reflow.
</TestCase.ExpectedResult>

<Fixture>
<SuspendyTree>
<img src="https://upload.wikimedia.org/wikipedia/commons/e/ee/Atom_%282%29.png" />React
is cool
</SuspendyTree>
</Fixture>
</TestCase>

<TestCase title="Iframe">
<TestCase.Steps>
<li>
The iframe shows a nested version of this fixtures app. Navigate
to the "Text inputs" page.
</li>
<li>Select one of the checkboxes.</li>
<li>Click "Hide" or "Remove".</li>
</TestCase.Steps>

<TestCase.ExpectedResult>
When removing, the iframe is reloaded. When hiding, the iframe
should still be on the "Text inputs" page. The checkbox should still
be checked. (Unfortunately, scroll position is lost.)
</TestCase.ExpectedResult>

<Fixture>
<SuspendyTree>
<iframe width="500" height="300" src="/" />
</SuspendyTree>
</Fixture>
</TestCase>

<TestCase title="Video playback">
<TestCase.Steps>
<li>Start playing the video, or seek to a specific position.</li>
<li>Click "Hide" or "Remove".</li>
</TestCase.Steps>

<TestCase.ExpectedResult>
The playback position should stay the same. When hiding, the video
plays in the background for the entire duration. When removing, the
video stops playing, but the position is not lost.
</TestCase.ExpectedResult>

<Fixture>
<SuspendyTree>
<video controls>
<source
src="http://techslides.com/demos/sample-videos/small.webm"
type="video/webm"
/>
<source
src="http://techslides.com/demos/sample-videos/small.ogv"
type="video/ogg"
/>
<source
src="http://techslides.com/demos/sample-videos/small.mp4"
type="video/mp4"
/>
<source
src="http://techslides.com/demos/sample-videos/small.3gp"
type="video/3gp"
/>
</video>
</SuspendyTree>
</Fixture>
</TestCase>
<TestCase title="Audio playback">
<TestCase.Steps>
<li>Start playing the audio, or seek to a specific position.</li>
<li>Click "Hide" or "Remove".</li>
</TestCase.Steps>

<TestCase.ExpectedResult>
The playback position should stay the same. When hiding, the audio
plays in the background for the entire duration. When removing, the
audio stops playing, but the position is not lost.
</TestCase.ExpectedResult>
<Fixture>
<SuspendyTree>
<audio controls={true}>
<source src="https://upload.wikimedia.org/wikipedia/commons/e/ec/Mozart_K448.ogg" />
</audio>
</SuspendyTree>
</Fixture>
</TestCase>
</FixtureSet>
);
}
}

export default TextInputFixtures;

0 comments on commit 68f9ccc

Please sign in to comment.