mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 12:20:20 +01:00
Add fixture components (#8860)
* add fixture components * add a few more options and styles * Test fixture updates - Pull in React from window global - Add field to TestCase for fix PR links - Update some styles * Remove unused Fixture.Result comment * Remove leading # from resolvedBy link * Implement tag loading utility that caches response Don't bust the cache doing feature detection COME ON * Use 'local' without version for option
This commit is contained in:
parent
a190cfce29
commit
2757a53fa5
|
|
@ -6,9 +6,11 @@
|
|||
"react-scripts": "0.8.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"classnames": "^2.2.5",
|
||||
"query-string": "^4.2.3",
|
||||
"react": "^15.4.1",
|
||||
"react-dom": "^15.4.1"
|
||||
"react-dom": "^15.4.1",
|
||||
"semver": "^5.3.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
|
|
|
|||
21
fixtures/dom/src/components/Fixture.js
Normal file
21
fixtures/dom/src/components/Fixture.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
const React = window.React;
|
||||
|
||||
const propTypes = {
|
||||
children: React.PropTypes.node.isRequired,
|
||||
};
|
||||
|
||||
class Fixture extends React.Component {
|
||||
render() {
|
||||
const { children } = this.props;
|
||||
|
||||
return (
|
||||
<div className="test-fixture">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Fixture.propTypes = propTypes;
|
||||
|
||||
export default Fixture
|
||||
28
fixtures/dom/src/components/FixtureSet.js
Normal file
28
fixtures/dom/src/components/FixtureSet.js
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import React from 'react';
|
||||
|
||||
const propTypes = {
|
||||
title: React.PropTypes.node.isRequired,
|
||||
description: React.PropTypes.node.isRequired,
|
||||
};
|
||||
|
||||
class FixtureSet extends React.Component {
|
||||
|
||||
render() {
|
||||
const { title, description, children } = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>{title}</h1>
|
||||
{description && (
|
||||
<p>{description}</p>
|
||||
)}
|
||||
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
FixtureSet.propTypes = propTypes;
|
||||
|
||||
export default FixtureSet
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import { parse, stringify } from 'query-string';
|
||||
import getVersionTags from '../tags';
|
||||
const React = window.React;
|
||||
|
||||
const Header = React.createClass({
|
||||
|
|
@ -9,13 +10,12 @@ const Header = React.createClass({
|
|||
return { version, versions };
|
||||
},
|
||||
componentWillMount() {
|
||||
fetch('https://api.github.com/repos/facebook/react/tags', { mode: 'cors' })
|
||||
.then(res => res.json())
|
||||
getVersionTags()
|
||||
.then(tags => {
|
||||
let versions = tags.map(tag => tag.name.slice(1));
|
||||
versions = ['local', ...versions];
|
||||
versions = [`local`, ...versions];
|
||||
this.setState({ versions });
|
||||
});
|
||||
})
|
||||
},
|
||||
handleVersionChange(event) {
|
||||
const query = parse(window.location.search);
|
||||
|
|
@ -46,6 +46,7 @@ const Header = React.createClass({
|
|||
<option value="/text-inputs">Text Inputs</option>
|
||||
<option value="/selects">Selects</option>
|
||||
<option value="/textareas">Textareas</option>
|
||||
<option value="/input-change-events">Input change events</option>
|
||||
</select>
|
||||
</label>
|
||||
<label htmlFor="react_version">
|
||||
|
|
|
|||
145
fixtures/dom/src/components/TestCase.js
Normal file
145
fixtures/dom/src/components/TestCase.js
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
import cn from 'classnames';
|
||||
import semver from 'semver';
|
||||
import React from 'react';
|
||||
import { parse } from 'query-string';
|
||||
import { semverString } from './propTypes'
|
||||
|
||||
const propTypes = {
|
||||
children: React.PropTypes.node.isRequired,
|
||||
title: React.PropTypes.node.isRequired,
|
||||
resolvedIn: semverString,
|
||||
resolvedBy: React.PropTypes.string
|
||||
};
|
||||
|
||||
class TestCase extends React.Component {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
complete: false,
|
||||
};
|
||||
}
|
||||
|
||||
handleChange = (e) => {
|
||||
this.setState({
|
||||
complete: e.target.checked
|
||||
})
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
title,
|
||||
description,
|
||||
resolvedIn,
|
||||
resolvedBy,
|
||||
affectedBrowsers,
|
||||
children,
|
||||
} = this.props;
|
||||
|
||||
let { complete } = this.state;
|
||||
|
||||
const { version } = parse(window.location.search);
|
||||
const isTestRelevant = (
|
||||
!version ||
|
||||
!resolvedIn ||
|
||||
semver.gte(version, resolvedIn)
|
||||
);
|
||||
|
||||
complete = !isTestRelevant || complete;
|
||||
|
||||
return (
|
||||
<section
|
||||
className={cn(
|
||||
"test-case",
|
||||
complete && 'test-case--complete'
|
||||
)}
|
||||
>
|
||||
<h2 className="test-case__title type-subheading">
|
||||
<label>
|
||||
<input
|
||||
className="test-case__title__check"
|
||||
type="checkbox"
|
||||
checked={complete}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
{' '}{title}
|
||||
</label>
|
||||
</h2>
|
||||
|
||||
<dl className="test-case__details">
|
||||
{resolvedIn && (
|
||||
<dt>First supported in: </dt>)}
|
||||
{resolvedIn && (
|
||||
<dd>
|
||||
<a href={'https://github.com/facebook/react/tag/v' + resolvedIn}>
|
||||
<code>{resolvedIn}</code>
|
||||
</a>
|
||||
</dd>
|
||||
)}
|
||||
|
||||
{resolvedBy && (
|
||||
<dt>Fixed by: </dt>)}
|
||||
{resolvedBy && (
|
||||
<dd>
|
||||
<a href={'https://github.com/facebook/react/pull/' + resolvedBy.slice(1)}>
|
||||
<code>{resolvedBy}</code>
|
||||
</a>
|
||||
</dd>
|
||||
)}
|
||||
|
||||
{affectedBrowsers &&
|
||||
<dt>Affected browsers: </dt>}
|
||||
{affectedBrowsers &&
|
||||
<dd>{affectedBrowsers}</dd>
|
||||
}
|
||||
</dl>
|
||||
|
||||
<p className="test-case__desc">
|
||||
{description}
|
||||
</p>
|
||||
|
||||
<div className="test-case__body">
|
||||
{!isTestRelevant &&(
|
||||
<p className="test-case__invalid-version">
|
||||
<strong>Note:</strong> This test case was fixed in a later version of React.
|
||||
This test is not expected to pass for the selected version, and that's ok!
|
||||
</p>
|
||||
)}
|
||||
|
||||
{children}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TestCase.propTypes = propTypes;
|
||||
|
||||
TestCase.Steps = class extends React.Component {
|
||||
render() {
|
||||
const { children } = this.props;
|
||||
return (
|
||||
<div>
|
||||
<h3>Steps to reproduce:</h3>
|
||||
<ol>
|
||||
{children}
|
||||
</ol>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
TestCase.ExpectedResult = class extends React.Component {
|
||||
render() {
|
||||
const { children } = this.props
|
||||
return (
|
||||
<div>
|
||||
<h3>Expected Result:</h3>
|
||||
<p>
|
||||
{children}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
export default TestCase
|
||||
|
|
@ -2,7 +2,8 @@ const React = window.React;
|
|||
import RangeInputFixtures from './range-inputs';
|
||||
import TextInputFixtures from './text-inputs';
|
||||
import SelectFixtures from './selects';
|
||||
import TextAreaFixtures from './textareas/';
|
||||
import TextAreaFixtures from './textareas';
|
||||
import InputChangeEvents from './input-change-events';
|
||||
|
||||
/**
|
||||
* A simple routing component that renders the appropriate
|
||||
|
|
@ -19,6 +20,8 @@ const FixturesPage = React.createClass({
|
|||
return <SelectFixtures />;
|
||||
case '/textareas':
|
||||
return <TextAreaFixtures />;
|
||||
case '/input-change-events':
|
||||
return <InputChangeEvents />;
|
||||
default:
|
||||
return <p>Please select a test fixture.</p>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
import React from 'react';
|
||||
|
||||
import Fixture from '../../Fixture';
|
||||
|
||||
|
||||
|
||||
class InputPlaceholderFixture extends React.Component {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
placeholder: 'A placeholder',
|
||||
changeCount: 0,
|
||||
};
|
||||
}
|
||||
|
||||
handleChange = () => {
|
||||
this.setState(({ changeCount }) => {
|
||||
return {
|
||||
changeCount: changeCount + 1
|
||||
}
|
||||
})
|
||||
}
|
||||
handleGeneratePlaceholder = () => {
|
||||
this.setState({
|
||||
placeholder: `A placeholder: ${Math.random() * 100}`
|
||||
})
|
||||
}
|
||||
|
||||
handleReset = () => {
|
||||
this.setState({
|
||||
changeCount: 0,
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
const { placeholder, changeCount } = this.state;
|
||||
const color = changeCount === 0 ? 'green' : 'red';
|
||||
|
||||
return (
|
||||
<Fixture>
|
||||
<input
|
||||
type='text'
|
||||
placeholder={placeholder}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
{' '}
|
||||
<button onClick={this.handleGeneratePlaceholder}>
|
||||
Change placeholder
|
||||
</button>
|
||||
|
||||
<p style={{ color }}>
|
||||
<code>onChange</code>{' calls: '}<strong>{changeCount}</strong>
|
||||
</p>
|
||||
<button onClick={this.handleReset}>Reset count</button>
|
||||
</Fixture>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default InputPlaceholderFixture;
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
import React from 'react';
|
||||
|
||||
import Fixture from '../../Fixture';
|
||||
|
||||
class RadioClickFixture extends React.Component {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
changeCount: 0,
|
||||
};
|
||||
}
|
||||
|
||||
handleChange = () => {
|
||||
this.setState(({ changeCount }) => {
|
||||
return {
|
||||
changeCount: changeCount + 1
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
handleReset = () => {
|
||||
this.setState({
|
||||
changeCount: 0,
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
const { changeCount } = this.state;
|
||||
const color = changeCount === 0 ? 'green' : 'red';
|
||||
|
||||
return (
|
||||
<Fixture>
|
||||
<label>
|
||||
<input
|
||||
defaultChecked
|
||||
type='radio'
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
Test case radio input
|
||||
</label>
|
||||
{' '}
|
||||
<p style={{ color }}>
|
||||
<code>onChange</code>{' calls: '}<strong>{changeCount}</strong>
|
||||
</p>
|
||||
<button onClick={this.handleReset}>Reset count</button>
|
||||
</Fixture>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default RadioClickFixture;
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
import React from 'react';
|
||||
|
||||
import Fixture from '../../Fixture';
|
||||
|
||||
|
||||
class RangeKeyboardFixture extends React.Component {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
keydownCount: 0,
|
||||
changeCount: 0,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.input.addEventListener('keydown', this.handleKeydown, false)
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.input.removeEventListener('keydown', this.handleKeydown, false)
|
||||
}
|
||||
|
||||
handleChange = () => {
|
||||
this.setState(({ changeCount }) => {
|
||||
return {
|
||||
changeCount: changeCount + 1
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
handleKeydown = (e) => {
|
||||
// only interesting in arrow key events
|
||||
if (![37, 38, 39, 40].includes(e.keyCode))
|
||||
return;
|
||||
|
||||
this.setState(({ keydownCount }) => {
|
||||
return {
|
||||
keydownCount: keydownCount + 1
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
handleReset = () => {
|
||||
this.setState({
|
||||
keydownCount: 0,
|
||||
changeCount: 0,
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
const { keydownCount, changeCount } = this.state;
|
||||
const color = keydownCount === changeCount ? 'green' : 'red';
|
||||
|
||||
return (
|
||||
<Fixture>
|
||||
<input
|
||||
type='range'
|
||||
ref={r => this.input = r}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
{' '}
|
||||
|
||||
<p style={{ color }}>
|
||||
<code>onKeyDown</code>{' calls: '}<strong>{keydownCount}</strong>
|
||||
{' vs '}
|
||||
<code>onChange</code>{' calls: '}<strong>{changeCount}</strong>
|
||||
</p>
|
||||
<button onClick={this.handleReset}>Reset counts</button>
|
||||
</Fixture>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default RangeKeyboardFixture;
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
import React from 'react';
|
||||
|
||||
import FixtureSet from '../../FixtureSet';
|
||||
import TestCase from '../../TestCase';
|
||||
import RangeKeyboardFixture from './RangeKeyboardFixture';
|
||||
import RadioClickFixture from './RadioClickFixture';
|
||||
import InputPlaceholderFixture from './InputPlaceholderFixture';
|
||||
|
||||
class InputChangeEvents extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<FixtureSet
|
||||
title="Input change events"
|
||||
description="Tests proper behavior of the onChange event for inputs"
|
||||
>
|
||||
<TestCase
|
||||
title="Range keyboard changes"
|
||||
description={`
|
||||
Range inputs should fire onChange events for keyboard events
|
||||
`}
|
||||
>
|
||||
<TestCase.Steps>
|
||||
<li>Focus range input</li>
|
||||
<li>change value via the keyboard arrow keys</li>
|
||||
</TestCase.Steps>
|
||||
|
||||
<TestCase.ExpectedResult>
|
||||
The <code>onKeyDown</code> call count should be equal to
|
||||
the <code>onChange</code> call count.
|
||||
</TestCase.ExpectedResult>
|
||||
|
||||
<RangeKeyboardFixture />
|
||||
</TestCase>
|
||||
|
||||
<TestCase
|
||||
title="Radio input clicks"
|
||||
description={`
|
||||
Radio inputs should only fire change events when the checked
|
||||
state changes.
|
||||
`}
|
||||
resolvedIn="16.0.0"
|
||||
>
|
||||
<TestCase.Steps>
|
||||
<li>Click on the Radio input (or label text)</li>
|
||||
</TestCase.Steps>
|
||||
|
||||
<TestCase.ExpectedResult>
|
||||
The <code>onChange</code> call count should remain at 0
|
||||
</TestCase.ExpectedResult>
|
||||
|
||||
<RadioClickFixture />
|
||||
</TestCase>
|
||||
|
||||
<TestCase
|
||||
title="Inputs with placeholders"
|
||||
description={`
|
||||
Text inputs with placeholders should not trigger changes
|
||||
when the placeholder is altered
|
||||
`}
|
||||
resolvedIn="15.0.0"
|
||||
resolvedBy="#5004"
|
||||
affectedBrowsers="IE9+"
|
||||
>
|
||||
<TestCase.Steps>
|
||||
<li>Click on the Text input</li>
|
||||
<li>Click on the "Change placeholder" button</li>
|
||||
</TestCase.Steps>
|
||||
|
||||
<TestCase.ExpectedResult>
|
||||
The <code>onChange</code> call count should remain at 0
|
||||
</TestCase.ExpectedResult>
|
||||
|
||||
<InputPlaceholderFixture />
|
||||
</TestCase>
|
||||
</FixtureSet>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default InputChangeEvents
|
||||
|
|
@ -24,7 +24,7 @@ const TextInputFixtures = React.createClass({
|
|||
|
||||
return (
|
||||
<div key={type} className="field">
|
||||
<label htmlFor={id}>{type}</label>
|
||||
<label className="control-label" htmlFor={id}>{type}</label>
|
||||
<input id={id} type={type} value={state} onChange={onChange} />
|
||||
→ {JSON.stringify(state)}
|
||||
</div>
|
||||
|
|
@ -35,7 +35,7 @@ const TextInputFixtures = React.createClass({
|
|||
let id = `uncontrolled_${type}`;
|
||||
return (
|
||||
<div key={type} className="field">
|
||||
<label htmlFor={id}>{type}</label>
|
||||
<label className="control-label" htmlFor={id}>{type}</label>
|
||||
<input id={id} type={type} />
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
16
fixtures/dom/src/components/propTypes.js
Normal file
16
fixtures/dom/src/components/propTypes.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import semver from 'semver';
|
||||
|
||||
const React = window.React;
|
||||
|
||||
export function semverString (props, propName, componentName) {
|
||||
let version = props[propName];
|
||||
|
||||
let error = React.PropTypes.string(...arguments);
|
||||
if (!error && version != null && !semver.valid(version))
|
||||
error = new Error(
|
||||
`\`${propName}\` should be a valid "semantic version" matching ` +
|
||||
'an existing React version'
|
||||
);
|
||||
|
||||
return error || null;
|
||||
};
|
||||
|
|
@ -4,22 +4,28 @@
|
|||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 10px;
|
||||
}
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
|
||||
font-size: 1.4rem;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
select {
|
||||
width: 120px;
|
||||
width: 12rem;
|
||||
}
|
||||
|
||||
|
||||
.header {
|
||||
background: #222;
|
||||
box-shadow: inset 0 -1px 3px #000;
|
||||
line-height: 32px;
|
||||
font-size: 1.6rem;
|
||||
line-height: 3.2rem;
|
||||
overflow: hidden;
|
||||
padding: 8px 16px;
|
||||
padding: .8rem 1.6rem;
|
||||
}
|
||||
|
||||
.header__inner {
|
||||
|
|
@ -40,7 +46,7 @@ select {
|
|||
|
||||
.header__logo img {
|
||||
display: inline-block;
|
||||
margin-right: 8px;
|
||||
margin-right: 0.8rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
|
|
@ -63,24 +69,142 @@ select {
|
|||
margin: 0 auto;
|
||||
max-width: 900px;
|
||||
overflow: hidden;
|
||||
padding: 20px;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
label {
|
||||
.control-label {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
font-size: 1.2rem;
|
||||
letter-spacing: 0.01em;
|
||||
margin-bottom: 4px;
|
||||
margin-bottom: 0.4rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.field {
|
||||
padding: 8px;
|
||||
padding: 0.8rem;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
border: 1px solid #aaa;
|
||||
float: left;
|
||||
padding: 16px;
|
||||
padding: 1.6rem;
|
||||
width: 49%;
|
||||
}
|
||||
|
||||
.control-box {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
margin: 0 0 2rem 0;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: 0.4rem;
|
||||
}
|
||||
|
||||
.type-subheading {
|
||||
font-size: 1.8rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.5;
|
||||
margin: 0 0 1.6rem;
|
||||
}
|
||||
|
||||
.hint {
|
||||
font-style: italic;
|
||||
line-height: 1.5;
|
||||
text-size: 1.4rem;
|
||||
}
|
||||
|
||||
.footnote {
|
||||
border-left: 4px solid #aaa;
|
||||
color: #444;
|
||||
font-style: italic;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 2.4rem;
|
||||
margin-left: 0.4rem;
|
||||
padding-left: 1.6rem;
|
||||
text-size: 1.3rem;
|
||||
}
|
||||
|
||||
.test-case {
|
||||
border-radius: 0.2rem;
|
||||
border: 1px solid #d9d9d9;
|
||||
margin: 3.2rem 0 3.2rem;
|
||||
}
|
||||
|
||||
.test-case__title {
|
||||
padding: 10px 15px 8px;
|
||||
line-height: 16px;
|
||||
font-size: 18px;
|
||||
border-bottom: 1px dashed #d9d9d9;
|
||||
margin: 0 0 -1px;
|
||||
}
|
||||
|
||||
.test-case__title__check {
|
||||
display: inline-block;
|
||||
margin-right: 8px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.test-case__body {
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
.test-case__desc {
|
||||
font-style: italic;
|
||||
margin: 15px 0;
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
.test-case--complete {
|
||||
border-color: #5cb85c;
|
||||
}
|
||||
|
||||
.test-case--complete .test-case__title {
|
||||
color: #5cb85c;
|
||||
}
|
||||
|
||||
.test-case__details {
|
||||
border-bottom: 1px dashed #d9d9d9;
|
||||
font-size: 80%;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.02em;
|
||||
margin: 0;
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
.test-case__details > * {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.test-case__details > dt,
|
||||
.test-case__details > dd {
|
||||
padding: 8px 0 6px;
|
||||
}
|
||||
|
||||
.test-case__details > dt {
|
||||
color: #464a4c;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.test-case__details > dd {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
.test-case__details > dd + dt {
|
||||
margin-left: 1.5rem;
|
||||
}
|
||||
|
||||
.test-case__invalid-version {
|
||||
font-style: italic;
|
||||
font-size: 1.6rem;
|
||||
color: #5cb85c;
|
||||
}
|
||||
|
||||
.test-fixture {
|
||||
padding: 20px;
|
||||
margin: 0 -15px; /* opposite of .test-case padding */
|
||||
background-color: #f4f4f4;
|
||||
border-top: 1px solid #d9d9d9;
|
||||
}
|
||||
|
|
|
|||
70
fixtures/dom/src/tags.js
Normal file
70
fixtures/dom/src/tags.js
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
/**
|
||||
* Version tags are loaded from the Github API. Since the Github API is rate-limited
|
||||
* we attempt to save and load the tags in sessionStorage when possible. Since its unlikely
|
||||
* that versions will change during a single session this should be safe.
|
||||
*/
|
||||
|
||||
const TAGS_CACHE_KEY = '@react-dom-fixtures/tags';
|
||||
|
||||
/**
|
||||
* Its possible that users will be testing changes frequently
|
||||
* in a browser that does not support sessionStorage. If the API does
|
||||
* get rate limited this hardcoded fallback will be loaded instead.
|
||||
* This way users can still switch between ~some versions while testing.
|
||||
* If there's a specific version they need to test that is not here, they
|
||||
* can manually load it by editing the URL (`?version={whatever}`)
|
||||
*/
|
||||
const fallbackTags = [
|
||||
'15.4.2',
|
||||
'15.3.2',
|
||||
'15.2.1',
|
||||
'15.1.0',
|
||||
'15.0.2',
|
||||
'0.14.8',
|
||||
'0.13.0'
|
||||
].map(version => ({
|
||||
name: `v${version}`
|
||||
}))
|
||||
|
||||
let canUseSessionStorage = true;
|
||||
|
||||
try {
|
||||
sessionStorage.setItem('foo', '')
|
||||
} catch (err) {
|
||||
canUseSessionStorage = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to load tags from sessionStorage. In cases where
|
||||
* sessionStorage is not available (Safari private browsing) or the
|
||||
* tags are cached a fetch request is made to the Github API.
|
||||
*
|
||||
* Returns a promise so that the consuming module can always assume
|
||||
* the request is async, even if its loaded from sessionStorage.
|
||||
*/
|
||||
export default function getVersionTags() {
|
||||
return new Promise((resolve) => {
|
||||
let cachedTags;
|
||||
if (canUseSessionStorage) {
|
||||
cachedTags = sessionStorage.getItem(TAGS_CACHE_KEY);
|
||||
}
|
||||
if (cachedTags) {
|
||||
cachedTags = JSON.parse(cachedTags);
|
||||
resolve(cachedTags);
|
||||
} else {
|
||||
fetch('https://api.github.com/repos/facebook/react/tags', { mode: 'cors' })
|
||||
.then(res => res.json())
|
||||
.then(tags => {
|
||||
// A message property indicates an error was sent from the API
|
||||
if (tags.message) {
|
||||
return resolve(fallbackTags)
|
||||
}
|
||||
if (canUseSessionStorage) {
|
||||
sessionStorage.setItem(TAGS_CACHE_KEY, JSON.stringify(tags))
|
||||
}
|
||||
resolve(tags)
|
||||
})
|
||||
.catch(() => resolve(fallbackTags))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -1136,6 +1136,10 @@ clap@^1.0.9:
|
|||
dependencies:
|
||||
chalk "^1.1.3"
|
||||
|
||||
classnames@^2.2.5:
|
||||
version "2.2.5"
|
||||
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.5.tgz#fb3801d453467649ef3603c7d61a02bd129bde6d"
|
||||
|
||||
clean-css@3.4.x:
|
||||
version "3.4.23"
|
||||
resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-3.4.23.tgz#604fbbca24c12feb59b02f00b84f1fb7ded6d001"
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user