[fix] CoreComponent -> createNativeComponent

'CoreComponent' creates new component instances and clutters the React
component tree during debugging. This patch converts 'CoreComponent' to
a simple function that creates a native web element.

This patch also includes a fix for use of the 'flexShrink' style on
'View'.

Fix #140
This commit is contained in:
Nicolas Gallagher
2016-06-21 14:53:08 -07:00
parent 393a6ef835
commit d03d89ac71
10 changed files with 138 additions and 205 deletions
-68
View File
@@ -1,68 +0,0 @@
import NativeMethodsDecorator from '../../modules/NativeMethodsDecorator'
import React, { Component, PropTypes } from 'react'
import StyleSheet from '../../apis/StyleSheet'
const roleComponents = {
article: 'article',
banner: 'header',
button: 'button',
complementary: 'aside',
contentinfo: 'footer',
form: 'form',
heading: 'h1',
link: 'a',
list: 'ul',
listitem: 'li',
main: 'main',
navigation: 'nav',
region: 'section'
}
@NativeMethodsDecorator
class CoreComponent extends Component {
static propTypes = {
accessibilityLabel: PropTypes.string,
accessibilityLiveRegion: PropTypes.oneOf([ 'assertive', 'off', 'polite' ]),
accessibilityRole: PropTypes.string,
accessible: PropTypes.bool,
component: PropTypes.oneOfType([ PropTypes.func, PropTypes.string ]),
style: PropTypes.oneOfType([ PropTypes.array, PropTypes.object ]),
testID: PropTypes.string,
type: PropTypes.string
};
static defaultProps = {
accessible: true,
component: 'div'
};
render() {
const {
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}
/>
)
}
}
module.exports = CoreComponent
+7 -7
View File
@@ -1,9 +1,9 @@
/* global window */
import NativeMethodsDecorator from '../../modules/NativeMethodsDecorator'
import resolveAssetSource from './resolveAssetSource'
import CoreComponent from '../CoreComponent'
import createNativeComponent from '../../modules/createNativeComponent'
import ImageResizeMode from './ImageResizeMode'
import ImageStylePropTypes from './ImageStylePropTypes'
import NativeMethodsDecorator from '../../modules/NativeMethodsDecorator'
import resolveAssetSource from './resolveAssetSource'
import React, { Component, PropTypes } from 'react'
import StyleSheet from '../../apis/StyleSheet'
import StyleSheetPropType from '../../apis/StyleSheet/StyleSheetPropType'
@@ -25,8 +25,8 @@ const ImageSourcePropType = PropTypes.oneOfType([
@NativeMethodsDecorator
class Image extends Component {
static propTypes = {
accessibilityLabel: CoreComponent.propTypes.accessibilityLabel,
accessible: CoreComponent.propTypes.accessible,
accessibilityLabel: createNativeComponent.propTypes.accessibilityLabel,
accessible: createNativeComponent.propTypes.accessible,
children: PropTypes.any,
defaultSource: ImageSourcePropType,
onError: PropTypes.func,
@@ -36,7 +36,7 @@ class Image extends Component {
resizeMode: PropTypes.oneOf(['contain', 'cover', 'none', 'stretch']),
source: ImageSourcePropType,
style: StyleSheetPropType(ImageStylePropTypes),
testID: CoreComponent.propTypes.testID
testID: createNativeComponent.propTypes.testID
};
static defaultProps = {
@@ -170,7 +170,7 @@ class Image extends Component {
]}
testID={testID}
>
<img src={displayImage} style={styles.img} />
{createNativeComponent({ component: 'img', src: displayImage, style: styles.img })}
{children ? (
<View children={children} pointerEvents='box-none' style={styles.children} />
) : null}
@@ -8,24 +8,6 @@ import ReactTestUtils from 'react-addons-test-utils'
import Text from '../'
suite('components/Text', () => {
test('prop "accessibilityLabel"', () => {
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"', () => {
const accessible = false
const result = utils.shallowRender(<Text accessible={accessible} />)
assert.equal(result.props.accessible, accessible)
})
test('prop "children"', () => {
const children = 'children'
const result = utils.shallowRender(<Text>{children}</Text>)
@@ -42,10 +24,4 @@ suite('components/Text', () => {
done()
}
})
test('prop "testID"', () => {
const testID = 'testID'
const result = utils.shallowRender(<Text testID={testID} />)
assert.equal(result.props.testID, testID)
})
})
+16 -18
View File
@@ -1,6 +1,6 @@
import createNativeComponent from '../../modules/createNativeComponent'
import NativeMethodsDecorator from '../../modules/NativeMethodsDecorator'
import CoreComponent from '../CoreComponent'
import React, { Component, PropTypes } from 'react'
import { Component, PropTypes } from 'react'
import StyleSheet from '../../apis/StyleSheet'
import StyleSheetPropType from '../../apis/StyleSheet/StyleSheetPropType'
import TextStylePropTypes from './TextStylePropTypes'
@@ -8,14 +8,14 @@ import TextStylePropTypes from './TextStylePropTypes'
@NativeMethodsDecorator
class Text extends Component {
static propTypes = {
accessibilityLabel: CoreComponent.propTypes.accessibilityLabel,
accessibilityRole: CoreComponent.propTypes.accessibilityRole,
accessible: CoreComponent.propTypes.accessible,
accessibilityLabel: createNativeComponent.propTypes.accessibilityLabel,
accessibilityRole: createNativeComponent.propTypes.accessibilityRole,
accessible: createNativeComponent.propTypes.accessible,
children: PropTypes.any,
numberOfLines: PropTypes.number,
onPress: PropTypes.func,
style: StyleSheetPropType(TextStylePropTypes),
testID: CoreComponent.propTypes.testID
testID: createNativeComponent.propTypes.testID
};
static defaultProps = {
@@ -36,18 +36,16 @@ class Text extends Component {
...other
} = this.props
return (
<CoreComponent
{...other}
component='span'
onClick={this._onPress}
style={[
styles.initial,
style,
numberOfLines === 1 && styles.singleLineStyle
]}
/>
)
return createNativeComponent({
...other,
component: 'span',
onClick: this._onPress,
style: [
styles.initial,
style,
numberOfLines === 1 && styles.singleLineStyle
]
})
}
}
@@ -13,12 +13,6 @@ const findShallowInput = (vdom) => vdom.props.children.props.children[0]
const findShallowPlaceholder = (vdom) => vdom.props.children.props.children[1]
suite('components/TextInput', () => {
test('prop "accessibilityLabel"', () => {
const accessibilityLabel = 'accessibilityLabel'
const result = utils.shallowRender(<TextInput accessibilityLabel={accessibilityLabel} />)
assert.equal(result.props.accessibilityLabel, accessibilityLabel)
})
test('prop "autoComplete"', () => {
// off
let input = findInput(utils.renderToDOM(<TextInput />))
@@ -221,12 +215,6 @@ suite('components/TextInput', () => {
assert.equal(input.selectionStart, 0)
})
test('prop "testID"', () => {
const testID = 'testID'
const result = utils.shallowRender(<TextInput testID={testID} />)
assert.equal(result.props.testID, testID)
})
test('prop "value"', () => {
const value = 'value'
const input = findShallowInput(utils.shallowRender(<TextInput value={value} />))
+3 -3
View File
@@ -1,5 +1,5 @@
import createNativeComponent from '../../modules/createNativeComponent'
import NativeMethodsDecorator from '../../modules/NativeMethodsDecorator'
import CoreComponent from '../CoreComponent'
import React, { Component, PropTypes } from 'react'
import ReactDOM from 'react-dom'
import StyleSheet from '../../apis/StyleSheet'
@@ -32,7 +32,7 @@ class TextInput extends Component {
secureTextEntry: PropTypes.bool,
selectTextOnFocus: PropTypes.bool,
style: Text.propTypes.style,
testID: CoreComponent.propTypes.testID,
testID: Text.propTypes.testID,
value: PropTypes.string
};
@@ -197,7 +197,7 @@ class TextInput extends Component {
testID={testID}
>
<View style={styles.wrapper}>
<CoreComponent {...props} ref='input' />
{createNativeComponent({ ...props, ref: 'input' })}
{placeholder && this.state.showPlaceholder && <Text
pointerEvents='none'
style={[
+13 -30
View File
@@ -3,35 +3,10 @@
import * as utils from '../../../modules/specHelpers'
import assert from 'assert'
import React from 'react'
import StyleSheet from '../../../apis/StyleSheet'
import View from '../'
suite('components/View', () => {
test('prop "accessibilityLabel"', () => {
const accessibilityLabel = 'accessibilityLabel'
const result = utils.shallowRender(<View accessibilityLabel={accessibilityLabel} />)
assert.equal(result.props.accessibilityLabel, accessibilityLabel)
})
test('prop "accessibilityLiveRegion"', () => {
const accessibilityLiveRegion = 'polite'
const result = utils.shallowRender(<View accessibilityLiveRegion={accessibilityLiveRegion} />)
assert.equal(result.props.accessibilityLiveRegion, accessibilityLiveRegion)
})
test('prop "accessibilityRole"', () => {
const accessibilityRole = 'accessibilityRole'
const result = utils.shallowRender(<View accessibilityRole={accessibilityRole} />)
assert.equal(result.props.accessibilityRole, accessibilityRole)
})
test('prop "accessible"', () => {
const accessible = false
const result = utils.shallowRender(<View accessible={accessible} />)
assert.equal(result.props.accessible, accessible)
})
test('prop "children"', () => {
const children = 'children'
const result = utils.shallowRender(<View>{children}</View>)
@@ -40,12 +15,20 @@ suite('components/View', () => {
test('prop "pointerEvents"', () => {
const result = utils.shallowRender(<View pointerEvents='box-only' />)
assert.equal(StyleSheet.flatten(result.props.style).pointerEvents, 'box-only')
assert.equal(result.props.className, '__style_pebo')
})
test('prop "testID"', () => {
const testID = 'testID'
const result = utils.shallowRender(<View testID={testID} />)
assert.equal(result.props.testID, testID)
test('prop "style"', () => {
const noFlex = utils.shallowRender(<View />)
assert.equal(noFlex.props.style.flexShrink, 0)
const flex = utils.shallowRender(<View style={{ flex: 1 }} />)
assert.equal(flex.props.style.flexShrink, 1)
const flexShrink = utils.shallowRender(<View style={{ flexShrink: 1 }} />)
assert.equal(flexShrink.props.style.flexShrink, 1)
const flexAndShrink = utils.shallowRender(<View style={{ flex: 1, flexShrink: 2 }} />)
assert.equal(flexAndShrink.props.style.flexShrink, 2)
})
})
+29 -29
View File
@@ -1,7 +1,7 @@
import createNativeComponent from '../../modules/createNativeComponent'
import NativeMethodsDecorator from '../../modules/NativeMethodsDecorator'
import normalizeNativeEvent from '../../apis/PanResponder/normalizeNativeEvent'
import CoreComponent from '../CoreComponent'
import React, { Component, PropTypes } from 'react'
import { Component, PropTypes } from 'react'
import StyleSheet from '../../apis/StyleSheet'
import StyleSheetPropType from '../../apis/StyleSheet/StyleSheetPropType'
import ViewStylePropTypes from './ViewStylePropTypes'
@@ -9,10 +9,10 @@ import ViewStylePropTypes from './ViewStylePropTypes'
@NativeMethodsDecorator
class View extends Component {
static propTypes = {
accessibilityLabel: CoreComponent.propTypes.accessibilityLabel,
accessibilityLiveRegion: CoreComponent.propTypes.accessibilityLiveRegion,
accessibilityRole: CoreComponent.propTypes.accessibilityRole,
accessible: CoreComponent.propTypes.accessible,
accessibilityLabel: createNativeComponent.propTypes.accessibilityLabel,
accessibilityLiveRegion: createNativeComponent.propTypes.accessibilityLiveRegion,
accessibilityRole: createNativeComponent.propTypes.accessibilityRole,
accessible: createNativeComponent.propTypes.accessible,
children: PropTypes.any,
onClick: PropTypes.func,
onClickCapture: PropTypes.func,
@@ -36,7 +36,7 @@ class View extends Component {
onTouchStartCapture: PropTypes.func,
pointerEvents: PropTypes.oneOf(['auto', 'box-none', 'box-only', 'none']),
style: StyleSheetPropType(ViewStylePropTypes),
testID: CoreComponent.propTypes.testID
testID: createNativeComponent.propTypes.testID
};
static defaultProps = {
@@ -59,28 +59,28 @@ class View extends Component {
const flattenedStyle = StyleSheet.flatten(style)
const pointerEventsStyle = pointerEvents && { pointerEvents }
return (
<CoreComponent
{...other}
onClick={this._handleClick}
onClickCapture={this._normalizeEventForHandler(this.props.onClickCapture)}
onTouchCancel={this._normalizeEventForHandler(this.props.onTouchCancel)}
onTouchCancelCapture={this._normalizeEventForHandler(this.props.onTouchCancelCapture)}
onTouchEnd={this._normalizeEventForHandler(this.props.onTouchEnd)}
onTouchEndCapture={this._normalizeEventForHandler(this.props.onTouchEndCapture)}
onTouchMove={this._normalizeEventForHandler(this.props.onTouchMove)}
onTouchMoveCapture={this._normalizeEventForHandler(this.props.onTouchMoveCapture)}
onTouchStart={this._normalizeEventForHandler(this.props.onTouchStart)}
onTouchStartCapture={this._normalizeEventForHandler(this.props.onTouchStartCapture)}
style={[
styles.initial,
style,
// 'View' needs to use 'flexShrink' in its reset when there is no 'flex' style provided
flattenedStyle.flex == null && styles.flexReset,
pointerEventsStyle
]}
/>
)
const props = {
...other,
onClick: this._handleClick,
onClickCapture: this._normalizeEventForHandler(this.props.onClickCapture),
onTouchCancel: this._normalizeEventForHandler(this.props.onTouchCancel),
onTouchCancelCapture: this._normalizeEventForHandler(this.props.onTouchCancelCapture),
onTouchEnd: this._normalizeEventForHandler(this.props.onTouchEnd),
onTouchEndCapture: this._normalizeEventForHandler(this.props.onTouchEndCapture),
onTouchMove: this._normalizeEventForHandler(this.props.onTouchMove),
onTouchMoveCapture: this._normalizeEventForHandler(this.props.onTouchMoveCapture),
onTouchStart: this._normalizeEventForHandler(this.props.onTouchStart),
onTouchStartCapture: this._normalizeEventForHandler(this.props.onTouchStartCapture),
style: [
styles.initial,
style,
// 'View' needs to use 'flexShrink' in its reset when there is no 'flex' style provided
(flattenedStyle.flex == null && flattenedStyle.flexShrink == null) && styles.flexReset,
pointerEventsStyle
]
}
return createNativeComponent(props)
}
/**
@@ -1,62 +1,61 @@
/* eslint-env mocha */
import * as utils from '../../../modules/specHelpers'
import * as utils from '../../specHelpers'
import assert from 'assert'
import React from 'react'
import CoreComponent from '../'
import createNativeComponent from '../'
suite('components/CoreComponent', () => {
suite('modules/createNativeComponent', () => {
test('prop "accessibilityLabel"', () => {
const accessibilityLabel = 'accessibilityLabel'
const dom = utils.renderToDOM(<CoreComponent accessibilityLabel={accessibilityLabel} />)
const dom = utils.renderToDOM(createNativeComponent({ accessibilityLabel }))
assert.equal(dom.getAttribute('aria-label'), accessibilityLabel)
})
test('prop "accessibilityLiveRegion"', () => {
const accessibilityLiveRegion = 'polite'
const dom = utils.renderToDOM(<CoreComponent accessibilityLiveRegion={accessibilityLiveRegion} />)
const dom = utils.renderToDOM(createNativeComponent({ accessibilityLiveRegion }))
assert.equal(dom.getAttribute('aria-live'), accessibilityLiveRegion)
})
test('prop "accessibilityRole"', () => {
const accessibilityRole = 'banner'
let dom = utils.renderToDOM(<CoreComponent accessibilityRole={accessibilityRole} />)
let dom = utils.renderToDOM(createNativeComponent({ accessibilityRole }))
assert.equal(dom.getAttribute('role'), accessibilityRole)
assert.equal((dom.tagName).toLowerCase(), 'header')
const button = 'button'
dom = utils.renderToDOM(<CoreComponent accessibilityRole={button} />)
dom = utils.renderToDOM(createNativeComponent({ accessibilityRole: 'button' }))
assert.equal(dom.getAttribute('type'), button)
assert.equal((dom.tagName).toLowerCase(), button)
})
test('prop "accessible"', () => {
// accessible (implicit)
let dom = utils.renderToDOM(<CoreComponent />)
let dom = utils.renderToDOM(createNativeComponent({}))
assert.equal(dom.getAttribute('aria-hidden'), null)
// accessible (explicit)
dom = utils.renderToDOM(<CoreComponent accessible />)
dom = utils.renderToDOM(createNativeComponent({ accessible: true }))
assert.equal(dom.getAttribute('aria-hidden'), null)
// not accessible
dom = utils.renderToDOM(<CoreComponent accessible={false} />)
dom = utils.renderToDOM(createNativeComponent({ accessible: false }))
assert.equal(dom.getAttribute('aria-hidden'), 'true')
})
test('prop "component"', () => {
const component = 'main'
const dom = utils.renderToDOM(<CoreComponent component={component} />)
const dom = utils.renderToDOM(createNativeComponent({ component }))
const tagName = (dom.tagName).toLowerCase()
assert.equal(tagName, component)
})
test('prop "testID"', () => {
// no testID
let dom = utils.renderToDOM(<CoreComponent />)
let dom = utils.renderToDOM(createNativeComponent({}))
assert.equal(dom.getAttribute('data-testid'), null)
// with testID
const testID = 'Example.testID'
dom = utils.renderToDOM(<CoreComponent testID={testID} />)
dom = utils.renderToDOM(createNativeComponent({ testID }))
assert.equal(dom.getAttribute('data-testid'), testID)
})
})
@@ -0,0 +1,57 @@
import React, { PropTypes } from 'react'
import StyleSheet from '../../apis/StyleSheet'
const roleComponents = {
article: 'article',
banner: 'header',
button: 'button',
complementary: 'aside',
contentinfo: 'footer',
form: 'form',
heading: 'h1',
link: 'a',
list: 'ul',
listitem: 'li',
main: 'main',
navigation: 'nav',
region: 'section'
}
const createNativeComponent = ({
accessibilityLabel,
accessibilityLiveRegion,
accessibilityRole,
accessible = true,
component = 'div',
testID,
type,
...other
}) => {
const Component = accessibilityRole && roleComponents[accessibilityRole] ? 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}
/>
)
}
createNativeComponent.propTypes = {
accessibilityLabel: PropTypes.string,
accessibilityLiveRegion: PropTypes.oneOf([ 'assertive', 'off', 'polite' ]),
accessibilityRole: PropTypes.string,
accessible: PropTypes.bool,
component: PropTypes.oneOfType([ PropTypes.func, PropTypes.string ]),
style: PropTypes.oneOfType([ PropTypes.array, PropTypes.object ]),
testID: PropTypes.string,
type: PropTypes.string
}
module.exports = createNativeComponent