mirror of
https://github.com/zhigang1992/react-native-web.git
synced 2026-03-31 10:11:38 +08:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a26033be2d | ||
|
|
fdb4ee4aae | ||
|
|
08300f624f | ||
|
|
7f5a2807e2 | ||
|
|
292f045c52 |
32
README.md
32
README.md
@@ -6,7 +6,7 @@
|
||||
[React Native][react-native-url] components and APIs for the Web.
|
||||
~17.7 KB minified and gzipped.
|
||||
|
||||
* [Slack: reactiflux channel #react-native-web][slack-url]
|
||||
* [Slack: #react-native-web on reactiflux][slack-url]
|
||||
* [Gitter: react-native-web][gitter-url]
|
||||
|
||||
## Table of contents
|
||||
@@ -16,6 +16,7 @@
|
||||
* [APIs](#apis)
|
||||
* [Components](#components)
|
||||
* [Styling](#styling)
|
||||
* [Accessibility](#accessibility)
|
||||
* [Contributing](#contributing)
|
||||
* [Thanks](#thanks)
|
||||
* [License](#license)
|
||||
@@ -96,7 +97,9 @@ const css = StyleSheet.renderToString();
|
||||
const Html = () => (
|
||||
<html>
|
||||
<head>
|
||||
<style id="react-stylesheet">{css}</style>
|
||||
<meta charSet="utf-8" />
|
||||
<meta content="initial-scale=1,width=device-width" name="viewport" />
|
||||
<style id="react-stylesheet" dangerouslySetInnerHTML={{ __html: css } />
|
||||
</head>
|
||||
<body>
|
||||
<div id="react-root" dangerouslySetInnerHTML={{ __html: html }} />
|
||||
@@ -111,13 +114,13 @@ Render styles on the client:
|
||||
// client.js
|
||||
import App from './components/App'
|
||||
import React, { StyleSheet } from 'react-native-web'
|
||||
import ReactDOM from 'react-dom'
|
||||
|
||||
React.render(
|
||||
<App />,
|
||||
document.getElementById('react-root')
|
||||
)
|
||||
const reactRoot = document.getElementById('react-root')
|
||||
const reactStyleSheet = document.getElementById('react-stylesheet')
|
||||
|
||||
document.getElementById('stylesheet').textContent = StyleSheet.renderToString()
|
||||
ReactDOM.render(<App />, reactRoot)
|
||||
reactStyleSheet.textContent = StyleSheet.renderToString()
|
||||
```
|
||||
|
||||
## APIs
|
||||
@@ -169,6 +172,19 @@ flexbox][flexbox-guide-url].
|
||||
Styling components can be achieved with inline styles or the use of
|
||||
[StyleSheet](docs/apis/StyleSheet.md).
|
||||
|
||||
## Accessibility
|
||||
|
||||
Major accessibility features are available through the following props:
|
||||
`accessible`, `accessibilityLabel`, `accessibilityLiveRegion`, and
|
||||
`accessibilityRole`. The `accessibilityRole` prop is used to determine the
|
||||
rendered DOM element. For example:
|
||||
|
||||
* `<View accessibilityRole='banner' />` => `<header role='banner' />`.
|
||||
* `<View accessibilityRole='button' />` => `<button type='button' role='button' />`.
|
||||
* `<Text accessibilityRole='link' href='/' />` => `<a role='link' href='/' />`.
|
||||
|
||||
See the component documentation for more details.
|
||||
|
||||
## Contributing
|
||||
|
||||
Please read the [contribution guidelines][contributing-url]. Contributions are
|
||||
@@ -177,7 +193,7 @@ welcome!
|
||||
## Thanks
|
||||
|
||||
Thanks to current and past members of the React and React Native teams (in
|
||||
particular Vjeux and Pete Hunt), and Tobias Koppers for Webpack and CSS loader.
|
||||
particular Vjeux and Pete Hunt).
|
||||
|
||||
Thanks to [react-tappable](https://github.com/JedWatson/react-tappable) for
|
||||
backing the current implementation of `Touchable`.
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
var webpack = require('webpack')
|
||||
|
||||
var DedupePlugin = webpack.optimize.DedupePlugin
|
||||
var EnvironmentPlugin = webpack.EnvironmentPlugin
|
||||
var OccurenceOrderPlugin = webpack.optimize.OccurenceOrderPlugin
|
||||
var UglifyJsPlugin = webpack.optimize.UglifyJsPlugin
|
||||
|
||||
var plugins = [
|
||||
new DedupePlugin(),
|
||||
new EnvironmentPlugin('NODE_ENV'),
|
||||
new OccurenceOrderPlugin()
|
||||
]
|
||||
|
||||
|
||||
@@ -1,21 +1,13 @@
|
||||
# StyleSheet
|
||||
|
||||
React Native for Web will automatically vendor-prefix styles applied to the
|
||||
libraries components. The `StyleSheet` abstraction converts predefined styles
|
||||
library's components. The `StyleSheet` abstraction converts predefined styles
|
||||
to CSS without a compile-time step. Some styles cannot be resolved outside of
|
||||
the render loop and are applied as inline styles.
|
||||
|
||||
The `style`-to-`className` conversion strategy is optimized to minimize the
|
||||
amount of CSS required. Unique declarations are defined using "atomic" CSS – a
|
||||
unique class name for a unique declaration.
|
||||
|
||||
React Native for Web includes a CSS reset to remove unwanted user agent styles
|
||||
from elements and pseudo-elements beyond the reach of React (e.g., `html` and
|
||||
`body`).
|
||||
|
||||
Create a new StyleSheet:
|
||||
|
||||
```
|
||||
```js
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
borderRadius: 4,
|
||||
@@ -55,17 +47,53 @@ StyleSheet.renderToString()
|
||||
|
||||
**create**(obj: {[key: string]: any})
|
||||
|
||||
**destroy**()
|
||||
|
||||
Clears all style information.
|
||||
|
||||
**renderToString**()
|
||||
|
||||
## Strategy
|
||||
Renders a CSS Style Sheet.
|
||||
|
||||
Mapping entire `style` objects to CSS rules can lead to increasingly large CSS
|
||||
files. Each new component adds new rules to the stylesheet.
|
||||
## About
|
||||
|
||||

|
||||
### Strategy
|
||||
|
||||
React Native for Web uses an alternative strategy: mapping declarations to
|
||||
declarations.
|
||||
React Native for Web uses a `style`-to-`className` conversion strategy that is
|
||||
designed to avoid issues arising from the [7 deadly sins of
|
||||
CSS](https://speakerdeck.com/vjeux/react-css-in-js):
|
||||
|
||||
1. Global namespace
|
||||
2. Dependency hell
|
||||
3. Dead code elimination
|
||||
4. Code minification
|
||||
5. Sharing constants
|
||||
6. Non-deterministic resolution
|
||||
7. Breaking isolation
|
||||
|
||||
The strategy also minimizes the amount of generated CSS, making it more viable
|
||||
to inline the style sheet when pre-rendering pages on the server. There is one
|
||||
unique selector per unique style _declaration_.
|
||||
|
||||
```js
|
||||
// definition
|
||||
{
|
||||
heading: {
|
||||
color: 'gray',
|
||||
fontSize: '2rem'
|
||||
},
|
||||
text: {
|
||||
color: 'gray',
|
||||
fontSize: '1.25rem'
|
||||
}
|
||||
}
|
||||
|
||||
// css
|
||||
//
|
||||
// .a { color: gray; }
|
||||
// .b { font-size: 2rem; }
|
||||
// .c { font-size: 1.25rem; }
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
@@ -102,11 +130,11 @@ In production the class names are obfuscated.
|
||||
(CSS libraries like [Atomic CSS](http://acss.io/),
|
||||
[Basscss](http://www.basscss.com/), [SUIT CSS](https://suitcss.github.io/), and
|
||||
[tachyons](http://tachyons.io/) are attempts to limit style scope and limit
|
||||
stylesheet growth in a similar way. But they're CSS utility libraries, each with a
|
||||
style sheet growth in a similar way. But they're CSS utility libraries, each with a
|
||||
particular set of classes and features to learn. All of them require developers
|
||||
to manually connect CSS classes for given styles.)
|
||||
|
||||
## Media Queries, pseudo-classes, and pseudo-elements
|
||||
### Media Queries, pseudo-classes, and pseudo-elements
|
||||
|
||||
Media Queries in JavaScript can be used to modify the render tree and styles.
|
||||
This has the benefit of co-locating breakpoint-specific DOM and style changes.
|
||||
@@ -115,3 +143,33 @@ Pseudo-classes like `:hover` and `:focus` can be replaced with JavaScript
|
||||
events.
|
||||
|
||||
Pseudo-elements are not supported.
|
||||
|
||||
### Reset
|
||||
|
||||
React Native for Web includes a very small CSS reset taken from
|
||||
[normalize.css](https://necolas.github.io/normalize.css/). It removes unwanted
|
||||
User Agent styles from (pseudo-)elements beyond the reach of React (e.g.,
|
||||
`html`, `body`) or inline styles (e.g., `::-moz-focus-inner`).
|
||||
|
||||
```css
|
||||
html {
|
||||
font-family: sans-serif;
|
||||
-ms-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
button::-moz-focus-inner,
|
||||
input::-moz-focus-inner {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
input[type="search"]::-webkit-search-cancel-button,
|
||||
input[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
```
|
||||
|
||||
@@ -23,6 +23,17 @@ NOTE: `Text` will transfer all other props to the rendered HTML element.
|
||||
Defines the text available to assistive technologies upon interaction with the
|
||||
element. (This is implemented using `aria-label`.)
|
||||
|
||||
(web) **accessibilityRole**: oneOf(roles)
|
||||
|
||||
Allows assistive technologies to present and support interaction with the view
|
||||
in a manner that is consistent with user expectations for similar views of that
|
||||
type. For example, marking a touchable view with an `accessibilityRole` of
|
||||
`button`. (This is implemented using [ARIA roles](http://www.w3.org/TR/wai-aria/roles#role_definitions)).
|
||||
|
||||
Note: Avoid changing `accessibilityRole` values over time or after user
|
||||
actions. Generally, accessibility APIs do not provide a means of notifying
|
||||
assistive technologies of a `role` value change.
|
||||
|
||||
(web) **accessible**: bool = true
|
||||
|
||||
When `false`, the text is hidden from assistive technologies. (This is
|
||||
@@ -32,10 +43,6 @@ implemented using `aria-hidden`.)
|
||||
|
||||
Child content.
|
||||
|
||||
(web) **component**: function | string = 'span'
|
||||
|
||||
Backing component.
|
||||
|
||||
**numberOfLines**: number
|
||||
|
||||
Truncates the text with an ellipsis after this many lines. Currently only supports `1`.
|
||||
@@ -70,7 +77,7 @@ Used to locate this view in end-to-end tests.
|
||||
## Examples
|
||||
|
||||
```js
|
||||
import React, { Text } from 'react-native-web'
|
||||
import React, { StyleSheet, Text } from 'react-native-web'
|
||||
|
||||
const { Component, PropTypes } = React
|
||||
|
||||
@@ -104,7 +111,7 @@ class PrettyText extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
const localStyle = {
|
||||
const localStyle = StyleSheet.create({
|
||||
color: {
|
||||
white: { color: 'white' },
|
||||
gray: { color: 'gray' },
|
||||
@@ -120,5 +127,5 @@ const localStyle = {
|
||||
normal: { fontWeight: '400' },
|
||||
bold: { fontWeight: '700' }
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
@@ -54,10 +54,6 @@ assistive technologies of a `role` value change.
|
||||
When `false`, the view is hidden from assistive technologies. (This is
|
||||
implemented using `aria-hidden`.)
|
||||
|
||||
(web) **component**: function | string = 'div'
|
||||
|
||||
The React Component for this view.
|
||||
|
||||
**onLayout**: function
|
||||
|
||||
(TODO)
|
||||
@@ -159,7 +155,7 @@ Used to locate this view in end-to-end tests.
|
||||
## Examples
|
||||
|
||||
```js
|
||||
import React, { View } from 'react-native-web'
|
||||
import React, { StyleSheet, View } from 'react-native-web'
|
||||
|
||||
const { Component, PropTypes } = React
|
||||
|
||||
@@ -177,14 +173,14 @@ class Example extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
const styles = {
|
||||
const styles = StyleSheet.create({
|
||||
row: {
|
||||
flexDirection: 'row'
|
||||
},
|
||||
cell: {
|
||||
flexGrow: 1
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export default Example
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-native-web",
|
||||
"version": "0.0.7",
|
||||
"version": "0.0.8",
|
||||
"description": "React Native for Web",
|
||||
"main": "dist/react-native-web.js",
|
||||
"files": [
|
||||
@@ -25,10 +25,10 @@
|
||||
"babel-eslint": "^4.1.1",
|
||||
"babel-loader": "^5.3.2",
|
||||
"babel-runtime": "^5.8.20",
|
||||
"eslint": "^1.3.1",
|
||||
"eslint": "^1.7.1",
|
||||
"eslint-config-standard": "^4.3.1",
|
||||
"eslint-config-standard-react": "^1.0.4",
|
||||
"eslint-plugin-react": "^3.3.1",
|
||||
"eslint-plugin-react": "^3.6.0",
|
||||
"eslint-plugin-standard": "^1.3.0",
|
||||
"karma": "^0.13.9",
|
||||
"karma-browserstack-launcher": "^0.1.5",
|
||||
|
||||
62
src/components/CoreComponent/__tests__/index-test.js
Normal file
62
src/components/CoreComponent/__tests__/index-test.js
Normal file
@@ -0,0 +1,62 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
import * as utils from '../../../modules/specHelpers'
|
||||
import assert from 'assert'
|
||||
import React from 'react'
|
||||
|
||||
import CoreComponent from '../'
|
||||
|
||||
suite('components/CoreComponent', () => {
|
||||
test('prop "accessibilityLabel"', () => {
|
||||
const accessibilityLabel = 'accessibilityLabel'
|
||||
const dom = utils.renderToDOM(<CoreComponent accessibilityLabel={accessibilityLabel} />)
|
||||
assert.equal(dom.getAttribute('aria-label'), accessibilityLabel)
|
||||
})
|
||||
|
||||
test('prop "accessibilityLiveRegion"', () => {
|
||||
const accessibilityLiveRegion = 'polite'
|
||||
const dom = utils.renderToDOM(<CoreComponent accessibilityLiveRegion={accessibilityLiveRegion} />)
|
||||
assert.equal(dom.getAttribute('aria-live'), accessibilityLiveRegion)
|
||||
})
|
||||
|
||||
test('prop "accessibilityRole"', () => {
|
||||
const accessibilityRole = 'banner'
|
||||
let dom = utils.renderToDOM(<CoreComponent accessibilityRole={accessibilityRole} />)
|
||||
assert.equal(dom.getAttribute('role'), accessibilityRole)
|
||||
assert.equal((dom.tagName).toLowerCase(), 'header')
|
||||
|
||||
const button = 'button'
|
||||
dom = utils.renderToDOM(<CoreComponent accessibilityRole={button} />)
|
||||
assert.equal(dom.getAttribute('type'), button)
|
||||
assert.equal((dom.tagName).toLowerCase(), button)
|
||||
})
|
||||
|
||||
test('prop "accessible"', () => {
|
||||
// accessible (implicit)
|
||||
let dom = utils.renderToDOM(<CoreComponent />)
|
||||
assert.equal(dom.getAttribute('aria-hidden'), null)
|
||||
// accessible (explicit)
|
||||
dom = utils.renderToDOM(<CoreComponent accessible />)
|
||||
assert.equal(dom.getAttribute('aria-hidden'), null)
|
||||
// not accessible
|
||||
dom = utils.renderToDOM(<CoreComponent accessible={false} />)
|
||||
assert.equal(dom.getAttribute('aria-hidden'), 'true')
|
||||
})
|
||||
|
||||
test('prop "component"', () => {
|
||||
const component = 'main'
|
||||
const dom = utils.renderToDOM(<CoreComponent component={component} />)
|
||||
const tagName = (dom.tagName).toLowerCase()
|
||||
assert.equal(tagName, component)
|
||||
})
|
||||
|
||||
test('prop "testID"', () => {
|
||||
// no testID
|
||||
let dom = utils.renderToDOM(<CoreComponent />)
|
||||
assert.equal(dom.getAttribute('data-testid'), null)
|
||||
// with testID
|
||||
const testID = 'Example.testID'
|
||||
dom = utils.renderToDOM(<CoreComponent testID={testID} />)
|
||||
assert.equal(dom.getAttribute('data-testid'), testID)
|
||||
})
|
||||
})
|
||||
@@ -2,18 +2,38 @@ import React, { PropTypes } from 'react'
|
||||
import StylePropTypes from '../../modules/StylePropTypes'
|
||||
import StyleSheet from '../../modules/StyleSheet'
|
||||
|
||||
const roleComponents = {
|
||||
article: 'article',
|
||||
banner: 'header',
|
||||
button: 'button',
|
||||
complementary: 'aside',
|
||||
contentinfo: 'footer',
|
||||
form: 'form',
|
||||
heading: 'h1',
|
||||
link: 'a',
|
||||
main: 'main',
|
||||
navigation: 'nav',
|
||||
region: 'section'
|
||||
}
|
||||
|
||||
class CoreComponent extends React.Component {
|
||||
static propTypes = {
|
||||
accessibilityLabel: PropTypes.string,
|
||||
accessibilityLiveRegion: PropTypes.oneOf(['assertive', 'off', 'polite']),
|
||||
accessibilityRole: PropTypes.string,
|
||||
accessible: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
component: PropTypes.oneOfType([
|
||||
PropTypes.func,
|
||||
PropTypes.string
|
||||
]),
|
||||
style: PropTypes.object,
|
||||
testID: PropTypes.string
|
||||
testID: PropTypes.string,
|
||||
type: PropTypes.string
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
accessible: true,
|
||||
component: 'div'
|
||||
}
|
||||
|
||||
@@ -21,16 +41,28 @@ class CoreComponent extends React.Component {
|
||||
|
||||
render() {
|
||||
const {
|
||||
component: Component,
|
||||
accessibilityLabel,
|
||||
accessibilityLiveRegion,
|
||||
accessibilityRole,
|
||||
accessible,
|
||||
component,
|
||||
testID,
|
||||
type,
|
||||
...other
|
||||
} = this.props
|
||||
|
||||
const Component = roleComponents[accessibilityRole] || component
|
||||
|
||||
return (
|
||||
<Component
|
||||
{...other}
|
||||
{...StyleSheet.resolve(other)}
|
||||
aria-hidden={accessible ? null : true}
|
||||
aria-label={accessibilityLabel}
|
||||
aria-live={accessibilityLiveRegion}
|
||||
data-testid={testID}
|
||||
role={accessibilityRole}
|
||||
type={accessibilityRole === 'button' ? 'button' : type}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
import { assertProps, render, renderToDOM } from '../../../modules/specHelpers'
|
||||
import * as utils from '../../../modules/specHelpers'
|
||||
import assert from 'assert'
|
||||
import React from 'react'
|
||||
|
||||
@@ -8,30 +8,34 @@ import Image from '../'
|
||||
|
||||
suite('components/Image', () => {
|
||||
test('default accessibility', () => {
|
||||
const dom = renderToDOM(<Image />)
|
||||
const dom = utils.renderToDOM(<Image />)
|
||||
assert.equal(dom.getAttribute('role'), 'img')
|
||||
})
|
||||
|
||||
test('prop "accessibilityLabel"', () => {
|
||||
assertProps.accessibilityLabel(Image)
|
||||
const accessibilityLabel = 'accessibilityLabel'
|
||||
const result = utils.shallowRender(<Image accessibilityLabel={accessibilityLabel} />)
|
||||
assert.equal(result.props.accessibilityLabel, accessibilityLabel)
|
||||
})
|
||||
|
||||
test('prop "accessible"', () => {
|
||||
assertProps.accessible(Image)
|
||||
const accessible = false
|
||||
const result = utils.shallowRender(<Image accessible={accessible} />)
|
||||
assert.equal(result.props.accessible, accessible)
|
||||
})
|
||||
|
||||
test('prop "children"')
|
||||
|
||||
test('prop "defaultSource"', () => {
|
||||
const defaultSource = { uri: 'https://google.com/favicon.ico' }
|
||||
const dom = renderToDOM(<Image defaultSource={defaultSource} />)
|
||||
const dom = utils.renderToDOM(<Image defaultSource={defaultSource} />)
|
||||
const backgroundImage = dom.style.backgroundImage
|
||||
assert(backgroundImage.indexOf(defaultSource.uri) > -1)
|
||||
})
|
||||
|
||||
test('prop "onError"', function (done) {
|
||||
this.timeout(5000)
|
||||
render(<Image
|
||||
utils.render(<Image
|
||||
onError={onError}
|
||||
source={{ uri: 'https://google.com/favicon.icox' }}
|
||||
/>)
|
||||
@@ -43,7 +47,7 @@ suite('components/Image', () => {
|
||||
|
||||
test('prop "onLoad"', function (done) {
|
||||
this.timeout(5000)
|
||||
render(<Image
|
||||
utils.render(<Image
|
||||
onLoad={onLoad}
|
||||
source={{ uri: 'https://google.com/favicon.ico' }}
|
||||
/>)
|
||||
@@ -62,10 +66,12 @@ suite('components/Image', () => {
|
||||
test('prop "source"')
|
||||
|
||||
test('prop "style"', () => {
|
||||
assertProps.style(Image)
|
||||
utils.assertProps.style(Image)
|
||||
})
|
||||
|
||||
test('prop "testID"', () => {
|
||||
assertProps.testID(Image)
|
||||
const testID = 'testID'
|
||||
const result = utils.shallowRender(<Image testID={testID} />)
|
||||
assert.equal(result.props.testID, testID)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -55,18 +55,17 @@ const styles = StyleSheet.create({
|
||||
class Image extends React.Component {
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
|
||||
const { uri } = props.source
|
||||
// state
|
||||
this.state = { status: props.source.uri ? STATUS_PENDING : STATUS_IDLE }
|
||||
|
||||
this.state = { status: uri ? STATUS_PENDING : STATUS_IDLE }
|
||||
// autobinding
|
||||
this._onError = this._onError.bind(this)
|
||||
this._onLoad = this._onLoad.bind(this)
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
accessibilityLabel: PropTypes.string,
|
||||
accessible: PropTypes.bool,
|
||||
accessibilityLabel: CoreComponent.propTypes.accessibilityLabel,
|
||||
accessible: CoreComponent.propTypes.accessible,
|
||||
children: PropTypes.any,
|
||||
defaultSource: PropTypes.object,
|
||||
onError: PropTypes.func,
|
||||
@@ -102,8 +101,8 @@ class Image extends React.Component {
|
||||
|
||||
_destroyImageLoader() {
|
||||
if (this.image) {
|
||||
this.image.onload = null
|
||||
this.image.onerror = null
|
||||
this.image.onload = null
|
||||
this.image = null
|
||||
}
|
||||
}
|
||||
@@ -124,8 +123,8 @@ class Image extends React.Component {
|
||||
|
||||
this._destroyImageLoader()
|
||||
this.setState({ status: STATUS_LOADED })
|
||||
this._onLoadEnd()
|
||||
if (onLoad) onLoad(event)
|
||||
this._onLoadEnd()
|
||||
}
|
||||
|
||||
_onLoadEnd() {
|
||||
@@ -194,7 +193,6 @@ class Image extends React.Component {
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
accessibilityRole='img'
|
||||
accessible={accessible}
|
||||
component='div'
|
||||
style={{
|
||||
...styles.initial,
|
||||
...resolvedStyle,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
import { assertProps, renderToDOM, shallowRender } from '../../../modules/specHelpers'
|
||||
import * as utils from '../../../modules/specHelpers'
|
||||
import assert from 'assert'
|
||||
import React from 'react'
|
||||
import ReactTestUtils from 'react-addons-test-utils'
|
||||
@@ -9,27 +9,33 @@ import Text from '../'
|
||||
|
||||
suite('components/Text', () => {
|
||||
test('prop "accessibilityLabel"', () => {
|
||||
assertProps.accessibilityLabel(Text)
|
||||
const accessibilityLabel = 'accessibilityLabel'
|
||||
const result = utils.shallowRender(<Text accessibilityLabel={accessibilityLabel} />)
|
||||
assert.equal(result.props.accessibilityLabel, accessibilityLabel)
|
||||
})
|
||||
|
||||
test('prop "accessibilityRole"', () => {
|
||||
const accessibilityRole = 'accessibilityRole'
|
||||
const result = utils.shallowRender(<Text accessibilityRole={accessibilityRole} />)
|
||||
assert.equal(result.props.accessibilityRole, accessibilityRole)
|
||||
})
|
||||
|
||||
test('prop "accessible"', () => {
|
||||
assertProps.accessible(Text)
|
||||
const accessible = false
|
||||
const result = utils.shallowRender(<Text accessible={accessible} />)
|
||||
assert.equal(result.props.accessible, accessible)
|
||||
})
|
||||
|
||||
test('prop "children"', () => {
|
||||
const children = 'children'
|
||||
const result = shallowRender(<Text>{children}</Text>)
|
||||
const result = utils.shallowRender(<Text>{children}</Text>)
|
||||
assert.equal(result.props.children, children)
|
||||
})
|
||||
|
||||
test('prop "component"', () => {
|
||||
assertProps.component(Text, 'span')
|
||||
})
|
||||
|
||||
test('prop "numberOfLines"')
|
||||
|
||||
test('prop "onPress"', (done) => {
|
||||
const dom = renderToDOM(<Text onPress={onPress} />)
|
||||
const dom = utils.renderToDOM(<Text onPress={onPress} />)
|
||||
ReactTestUtils.Simulate.click(dom)
|
||||
function onPress(e) {
|
||||
assert.ok(e.nativeEvent)
|
||||
@@ -38,10 +44,12 @@ suite('components/Text', () => {
|
||||
})
|
||||
|
||||
test('prop "style"', () => {
|
||||
assertProps.style(Text)
|
||||
utils.assertProps.style(Text)
|
||||
})
|
||||
|
||||
test('prop "testID"', () => {
|
||||
assertProps.testID(Text)
|
||||
const testID = 'testID'
|
||||
const result = utils.shallowRender(<Text testID={testID} />)
|
||||
assert.equal(result.props.testID, testID)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -26,10 +26,10 @@ const styles = StyleSheet.create({
|
||||
class Text extends React.Component {
|
||||
static propTypes = {
|
||||
_className: PropTypes.string, // escape-hatch for code migrations
|
||||
accessibilityLabel: PropTypes.string,
|
||||
accessible: PropTypes.bool,
|
||||
accessibilityLabel: CoreComponent.propTypes.accessibilityLabel,
|
||||
accessibilityRole: CoreComponent.propTypes.accessibilityRole,
|
||||
accessible: CoreComponent.propTypes.accessible,
|
||||
children: PropTypes.any,
|
||||
component: CoreComponent.propTypes.component,
|
||||
numberOfLines: PropTypes.number,
|
||||
onPress: PropTypes.func,
|
||||
style: PropTypes.shape(TextStylePropTypes),
|
||||
@@ -41,7 +41,6 @@ class Text extends React.Component {
|
||||
static defaultProps = {
|
||||
_className: '',
|
||||
accessible: true,
|
||||
component: 'span',
|
||||
style: styles.initial
|
||||
}
|
||||
|
||||
@@ -52,14 +51,9 @@ class Text extends React.Component {
|
||||
render() {
|
||||
const {
|
||||
_className,
|
||||
accessibilityLabel,
|
||||
accessible,
|
||||
children,
|
||||
component,
|
||||
numberOfLines,
|
||||
onPress,
|
||||
style,
|
||||
testID,
|
||||
...other
|
||||
} = this.props
|
||||
|
||||
@@ -69,18 +63,14 @@ class Text extends React.Component {
|
||||
return (
|
||||
<CoreComponent
|
||||
{...other}
|
||||
aria-hidden={accessible ? null : true}
|
||||
aria-label={accessibilityLabel}
|
||||
children={children}
|
||||
className={className}
|
||||
component={component}
|
||||
component='span'
|
||||
onClick={this._onPress.bind(this)}
|
||||
style={{
|
||||
...styles.initial,
|
||||
...resolvedStyle,
|
||||
...(numberOfLines === 1 && styles.singleLineStyle)
|
||||
}}
|
||||
testID={testID}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -9,7 +9,9 @@ import TextInput from '../'
|
||||
|
||||
suite('components/TextInput', () => {
|
||||
test('prop "accessibilityLabel"', () => {
|
||||
utils.assertProps.accessibilityLabel(TextInput)
|
||||
const accessibilityLabel = 'accessibilityLabel'
|
||||
const result = utils.shallowRender(<TextInput accessibilityLabel={accessibilityLabel} />)
|
||||
assert.equal(result.props.accessibilityLabel, accessibilityLabel)
|
||||
})
|
||||
|
||||
test('prop "autoComplete"', () => {
|
||||
@@ -208,7 +210,9 @@ suite('components/TextInput', () => {
|
||||
})
|
||||
|
||||
test('prop "testID"', () => {
|
||||
utils.assertProps.testID(TextInput)
|
||||
const testID = 'testID'
|
||||
const result = utils.shallowRender(<TextInput testID={testID} />)
|
||||
assert.equal(result.props.testID, testID)
|
||||
})
|
||||
|
||||
test('prop "value"', () => {
|
||||
|
||||
@@ -23,7 +23,7 @@ const styles = StyleSheet.create({
|
||||
|
||||
class TextInput extends React.Component {
|
||||
static propTypes = {
|
||||
accessibilityLabel: PropTypes.string,
|
||||
accessibilityLabel: CoreComponent.propTypes.accessibilityLabel,
|
||||
autoComplete: PropTypes.bool,
|
||||
autoFocus: PropTypes.bool,
|
||||
clearTextOnFocus: PropTypes.bool,
|
||||
@@ -136,7 +136,7 @@ class TextInput extends React.Component {
|
||||
}
|
||||
|
||||
const propsCommon = {
|
||||
'aria-label': accessibilityLabel,
|
||||
accessibilityLabel,
|
||||
autoComplete: autoComplete && 'on',
|
||||
autoFocus,
|
||||
className: 'TextInput',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
import { assertProps, shallowRender } from '../../../modules/specHelpers'
|
||||
import * as utils from '../../../modules/specHelpers'
|
||||
import assert from 'assert'
|
||||
import React from 'react'
|
||||
|
||||
@@ -11,19 +11,25 @@ const requiredProps = { children }
|
||||
|
||||
suite('components/Touchable', () => {
|
||||
test('prop "accessibilityLabel"', () => {
|
||||
assertProps.accessibilityLabel(Touchable, requiredProps)
|
||||
const accessibilityLabel = 'accessibilityLabel'
|
||||
const result = utils.shallowRender(<Touchable {...requiredProps} accessibilityLabel={accessibilityLabel} />)
|
||||
assert.equal(result.props.accessibilityLabel, accessibilityLabel)
|
||||
})
|
||||
|
||||
test('prop "accessibilityRole"', () => {
|
||||
assertProps.accessibilityRole(Touchable, requiredProps)
|
||||
const accessibilityRole = 'accessibilityRole'
|
||||
const result = utils.shallowRender(<Touchable {...requiredProps} accessibilityRole={accessibilityRole} />)
|
||||
assert.equal(result.props.accessibilityRole, accessibilityRole)
|
||||
})
|
||||
|
||||
test('prop "accessible"', () => {
|
||||
assertProps.accessible(Touchable, requiredProps)
|
||||
const accessible = false
|
||||
const result = utils.shallowRender(<Touchable {...requiredProps} accessible={accessible} />)
|
||||
assert.equal(result.props.accessible, accessible)
|
||||
})
|
||||
|
||||
test('prop "children"', () => {
|
||||
const result = shallowRender(<Touchable {...requiredProps} />)
|
||||
const result = utils.shallowRender(<Touchable {...requiredProps} />)
|
||||
assert.deepEqual(result.props.children, children)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -25,9 +25,9 @@ class Touchable extends React.Component {
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
accessibilityLabel: PropTypes.string,
|
||||
accessibilityRole: PropTypes.string,
|
||||
accessible: PropTypes.bool,
|
||||
accessibilityLabel: View.propTypes.accessibilityLabel,
|
||||
accessibilityRole: View.propTypes.accessibilityRole,
|
||||
accessible: View.propTypes.accessible,
|
||||
activeOpacity: PropTypes.number,
|
||||
activeUnderlayColor: PropTypes.string,
|
||||
children: PropTypes.element,
|
||||
@@ -45,7 +45,6 @@ class Touchable extends React.Component {
|
||||
accessibilityRole: 'button',
|
||||
activeOpacity: 1,
|
||||
activeUnderlayColor: 'transparent',
|
||||
component: 'div',
|
||||
delayLongPress: 1000,
|
||||
delayPressIn: 0,
|
||||
delayPressOut: 0,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
import { assertProps, shallowRender } from '../../../modules/specHelpers'
|
||||
import * as utils from '../../../modules/specHelpers'
|
||||
import assert from 'assert'
|
||||
import React from 'react'
|
||||
|
||||
@@ -8,41 +8,47 @@ import View from '../'
|
||||
|
||||
suite('components/View', () => {
|
||||
test('prop "accessibilityLabel"', () => {
|
||||
assertProps.accessibilityLabel(View)
|
||||
const accessibilityLabel = 'accessibilityLabel'
|
||||
const result = utils.shallowRender(<View accessibilityLabel={accessibilityLabel} />)
|
||||
assert.equal(result.props.accessibilityLabel, accessibilityLabel)
|
||||
})
|
||||
|
||||
test('prop "accessibilityLiveRegion"', () => {
|
||||
assertProps.accessibilityLiveRegion(View)
|
||||
const accessibilityLiveRegion = 'polite'
|
||||
const result = utils.shallowRender(<View accessibilityLiveRegion={accessibilityLiveRegion} />)
|
||||
assert.equal(result.props.accessibilityLiveRegion, accessibilityLiveRegion)
|
||||
})
|
||||
|
||||
test('prop "accessibilityRole"', () => {
|
||||
assertProps.accessibilityRole(View)
|
||||
const accessibilityRole = 'accessibilityRole'
|
||||
const result = utils.shallowRender(<View accessibilityRole={accessibilityRole} />)
|
||||
assert.equal(result.props.accessibilityRole, accessibilityRole)
|
||||
})
|
||||
|
||||
test('prop "accessible"', () => {
|
||||
assertProps.accessible(View)
|
||||
const accessible = false
|
||||
const result = utils.shallowRender(<View accessible={accessible} />)
|
||||
assert.equal(result.props.accessible, accessible)
|
||||
})
|
||||
|
||||
test('prop "children"', () => {
|
||||
const children = 'children'
|
||||
const result = shallowRender(<View>{children}</View>)
|
||||
const result = utils.shallowRender(<View>{children}</View>)
|
||||
assert.equal(result.props.children, children)
|
||||
})
|
||||
|
||||
test('prop "component"', () => {
|
||||
assertProps.component(View)
|
||||
})
|
||||
|
||||
test('prop "pointerEvents"', () => {
|
||||
const result = shallowRender(<View pointerEvents='box-only' />)
|
||||
const result = utils.shallowRender(<View pointerEvents='box-only' />)
|
||||
assert.equal(result.props.style.pointerEvents, 'box-only')
|
||||
})
|
||||
|
||||
test('prop "style"', () => {
|
||||
assertProps.style(View)
|
||||
utils.assertProps.style(View)
|
||||
})
|
||||
|
||||
test('prop "testID"', () => {
|
||||
assertProps.testID(View)
|
||||
const testID = 'testID'
|
||||
const result = utils.shallowRender(<View testID={testID} />)
|
||||
assert.equal(result.props.testID, testID)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -32,12 +32,11 @@ const styles = StyleSheet.create({
|
||||
class View extends React.Component {
|
||||
static propTypes = {
|
||||
_className: PropTypes.string, // escape-hatch for code migrations
|
||||
accessibilityLabel: PropTypes.string,
|
||||
accessibilityLiveRegion: PropTypes.oneOf(['assertive', 'off', 'polite']),
|
||||
accessibilityRole: PropTypes.string,
|
||||
accessible: PropTypes.bool,
|
||||
accessibilityLabel: CoreComponent.propTypes.accessibilityLabel,
|
||||
accessibilityLiveRegion: CoreComponent.propTypes.accessibilityLiveRegion,
|
||||
accessibilityRole: CoreComponent.propTypes.accessibilityRole,
|
||||
accessible: CoreComponent.propTypes.accessible,
|
||||
children: PropTypes.any,
|
||||
component: CoreComponent.propTypes.component,
|
||||
pointerEvents: PropTypes.oneOf(['auto', 'box-none', 'box-only', 'none']),
|
||||
style: PropTypes.shape(ViewStylePropTypes),
|
||||
testID: CoreComponent.propTypes.testID
|
||||
@@ -48,20 +47,14 @@ class View extends React.Component {
|
||||
static defaultProps = {
|
||||
_className: '',
|
||||
accessible: true,
|
||||
component: 'div',
|
||||
style: styles.initial
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
_className,
|
||||
accessibilityLabel,
|
||||
accessibilityLiveRegion,
|
||||
accessibilityRole,
|
||||
accessible,
|
||||
pointerEvents,
|
||||
style,
|
||||
testID,
|
||||
...other
|
||||
} = this.props
|
||||
|
||||
@@ -72,17 +65,12 @@ class View extends React.Component {
|
||||
return (
|
||||
<CoreComponent
|
||||
{...other}
|
||||
aria-hidden={accessible ? null : true}
|
||||
aria-label={accessibilityLabel}
|
||||
aria-live={accessibilityLiveRegion}
|
||||
className={className}
|
||||
role={accessibilityRole}
|
||||
style={{
|
||||
...styles.initial,
|
||||
...resolvedStyle,
|
||||
...pointerEventsStyle
|
||||
}}
|
||||
testID={testID}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React, { Image, StyleSheet, Text, TextInput, Touchable, View } from '.'
|
||||
import ReactDOM from 'react-dom'
|
||||
|
||||
const Heading = ({ children, level = '1', size = 'normal' }) => (
|
||||
const Heading = ({ children, size = 'normal' }) => (
|
||||
<Text
|
||||
accessibilityRole='heading'
|
||||
children={children}
|
||||
component={`h${level}`}
|
||||
style={headingStyles.size[size]}
|
||||
/>
|
||||
)
|
||||
@@ -36,15 +36,15 @@ class Example extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<View accessibilityRole='main' style={styles.root}>
|
||||
<Heading level='1' size='xlarge'>React Native Web</Heading>
|
||||
<Heading size='xlarge'>React Native Web</Heading>
|
||||
<Text>React Native Web takes the core components from <Text
|
||||
component='a' href='https://facebook.github.io/react-native/'>React
|
||||
accessibilityRole='link' href='https://facebook.github.io/react-native/'>React
|
||||
Native</Text> and brings them to the web. These components provide
|
||||
simple building blocks – touch handling, flexbox layout,
|
||||
scroll views – from which more complex components and apps can be
|
||||
constructed.</Text>
|
||||
|
||||
<Heading level='2' size='large'>Image</Heading>
|
||||
<Heading size='large'>Image</Heading>
|
||||
<Image
|
||||
accessibilityLabel='accessible image'
|
||||
children={<Text>Inner content</Text>}
|
||||
@@ -67,7 +67,7 @@ class Example extends React.Component {
|
||||
testID='Example.image'
|
||||
/>
|
||||
|
||||
<Heading level='2' size='large'>Text</Heading>
|
||||
<Heading size='large'>Text</Heading>
|
||||
<Text
|
||||
onPress={(e) => { console.log('Text.onPress', e) }}
|
||||
testID={'Example.text'}
|
||||
@@ -92,7 +92,7 @@ class Example extends React.Component {
|
||||
hendrerit consequat.
|
||||
</Text>
|
||||
|
||||
<Heading level='2' size='large'>TextInput</Heading>
|
||||
<Heading size='large'>TextInput</Heading>
|
||||
<TextInput
|
||||
keyboardType='default'
|
||||
onBlur={(e) => { console.log('TextInput.onBlur', e) }}
|
||||
@@ -114,7 +114,7 @@ class Example extends React.Component {
|
||||
numberOfLines={5}
|
||||
/>
|
||||
|
||||
<Heading level='2' size='large'>Touchable</Heading>
|
||||
<Heading size='large'>Touchable</Heading>
|
||||
<Touchable
|
||||
accessibilityLabel={'Touchable element'}
|
||||
activeHighlight='lightblue'
|
||||
@@ -129,8 +129,8 @@ class Example extends React.Component {
|
||||
</View>
|
||||
</Touchable>
|
||||
|
||||
<Heading level='2' size='large'>View</Heading>
|
||||
<Heading level='3'>Default layout</Heading>
|
||||
<Heading size='large'>View</Heading>
|
||||
<Heading>Default layout</Heading>
|
||||
<View>
|
||||
{[ 1, 2, 3, 4, 5, 6 ].map((item, i) => {
|
||||
return (
|
||||
@@ -141,7 +141,7 @@ class Example extends React.Component {
|
||||
})}
|
||||
</View>
|
||||
|
||||
<Heading level='3'>Row layout</Heading>
|
||||
<Heading>Row layout</Heading>
|
||||
<View style={styles.row}>
|
||||
{[ 1, 2, 3, 4, 5, 6 ].map((item, i) => {
|
||||
return (
|
||||
@@ -152,13 +152,13 @@ class Example extends React.Component {
|
||||
})}
|
||||
</View>
|
||||
|
||||
<Heading level='3'>pointerEvents</Heading>
|
||||
<Heading>pointerEvents</Heading>
|
||||
<View style={styles.row}>
|
||||
{['box-none', 'box-only', 'none'].map((value, i) => {
|
||||
return (
|
||||
<View
|
||||
accessibilityRole='link'
|
||||
children={value}
|
||||
component='a'
|
||||
href='https://google.com'
|
||||
key={i}
|
||||
pointerEvents={value}
|
||||
|
||||
@@ -6,44 +6,6 @@ import ReactDOM from 'react-dom'
|
||||
import ReactTestUtils from 'react-addons-test-utils'
|
||||
|
||||
export const assertProps = {
|
||||
accessibilityLabel: function (Component, props) {
|
||||
// with label
|
||||
const accessibilityLabel = 'accessibilityLabel'
|
||||
const dom = renderToDOM(<Component {...props} accessibilityLabel={accessibilityLabel} />)
|
||||
assert.equal(dom.getAttribute('aria-label'), accessibilityLabel)
|
||||
},
|
||||
|
||||
accessibilityLiveRegion: function (Component, props) {
|
||||
const accessibilityLiveRegion = 'polite'
|
||||
const dom = renderToDOM(<Component {...props} accessibilityLiveRegion={accessibilityLiveRegion} />)
|
||||
assert.equal(dom.getAttribute('aria-live'), accessibilityLiveRegion)
|
||||
},
|
||||
|
||||
accessibilityRole: function (Component, props) {
|
||||
const accessibilityRole = 'main'
|
||||
const dom = renderToDOM(<Component {...props} accessibilityRole={accessibilityRole} />)
|
||||
assert.equal(dom.getAttribute('role'), accessibilityRole)
|
||||
},
|
||||
|
||||
accessible: function (Component, props) {
|
||||
// accessible (implicit)
|
||||
let dom = renderToDOM(<Component {...props} />)
|
||||
assert.equal(dom.getAttribute('aria-hidden'), null)
|
||||
// accessible (explicit)
|
||||
dom = renderToDOM(<Component {...props} accessible />)
|
||||
assert.equal(dom.getAttribute('aria-hidden'), null)
|
||||
// not accessible
|
||||
dom = renderToDOM(<Component {...props} accessible={false} />)
|
||||
assert.equal(dom.getAttribute('aria-hidden'), 'true')
|
||||
},
|
||||
|
||||
component: function (Component, props) {
|
||||
const component = 'main'
|
||||
const dom = renderToDOM(<Component {...props} component={component} />)
|
||||
const tagName = (dom.tagName).toLowerCase()
|
||||
assert.equal(tagName, component)
|
||||
},
|
||||
|
||||
style: function (Component, props) {
|
||||
let shallow
|
||||
// default styles
|
||||
@@ -67,16 +29,6 @@ export const assertProps = {
|
||||
shallow.props.style,
|
||||
{ ...Component.defaultProps.style, ...styleToMerge }
|
||||
)
|
||||
},
|
||||
|
||||
testID: function (Component, props) {
|
||||
// no testID
|
||||
let dom = renderToDOM(<Component {...props} />)
|
||||
assert.equal(dom.getAttribute('data-testid'), null)
|
||||
// with testID
|
||||
const testID = 'Example.testID'
|
||||
dom = renderToDOM(<Component {...props} testID={testID} />)
|
||||
assert.equal(dom.getAttribute('data-testid'), testID)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user