Compare commits

..

15 Commits

Author SHA1 Message Date
Nicolas Gallagher
3564bbf840 0.0.32 2016-07-06 18:50:25 -07:00
Nicolas Gallagher
297b2e5afb [fix] support for Animated transform styles (part 2)
Only add 'px' to numeric translate values
2016-07-06 18:48:53 -07:00
Nicolas Gallagher
215697234e 0.0.31 2016-07-06 18:33:12 -07:00
Nicolas Gallagher
9efa7e94bd [fix] support for Animated transform styles
Animated uses 'setNativeProps' to update styles. This mutates the DOM
without using React. But the code path was not adding 'px' units to
transform values and browsers were ignoring the style.

Fix #129
2016-07-06 17:16:55 -07:00
Nicolas Gallagher
c44da41497 0.0.30 2016-07-06 15:30:40 -07:00
Nicolas Gallagher
331c92fb3a Use enzyme for all React component tests 2016-07-06 15:26:32 -07:00
Nicolas Gallagher
26758e905c [fix] TextInput alignment of small inner 'input'
Fix #139
2016-07-06 15:25:00 -07:00
Nicolas Gallagher
a15b15c55d Use babel-preset-react-native 2016-07-06 10:27:43 -07:00
Nicolas Gallagher
f0202dbe61 Remove use of decorator syntax 2016-07-06 10:11:03 -07:00
Nicolas Gallagher
d4d67dafc0 [fix] StyleSheet expansion of shortform properties
The previous implementation relied on a buggy sorting strategy. It could
result in shortform properties replacing the values set for longform
properties. This patch avoids expanding shorthand values to a longform
property if it declared in the original style object.

Fix #141
2016-07-05 19:18:11 -07:00
Nicolas Gallagher
579bdeb8a5 [fix] setNativeProps on TextInput 2016-07-05 18:52:44 -07:00
Nicolas Gallagher
7132a18440 Remove unused import 2016-07-05 18:48:49 -07:00
Nicolas Gallagher
18881b1edb [fix] support border styles on Image
Fix #128
2016-07-05 13:57:42 -07:00
Nicolas Gallagher
4d1e7d8c0b 0.0.29 2016-07-05 13:50:25 -07:00
Nicolas Gallagher
a7158aeb6f [change] remove Portal component
Portal was undocumented and has been removed from React Native.

Fix #149
2016-07-05 13:49:37 -07:00
22 changed files with 256 additions and 448 deletions

View File

@@ -1,10 +1,5 @@
{
"presets": [
"es2015",
"stage-1",
"react"
],
"plugins": [
"transform-decorators-legacy"
"react-native"
]
}

View File

@@ -102,7 +102,6 @@ Exported modules:
* [`ActivityIndicator`](docs/components/ActivityIndicator.md)
* [`Image`](docs/components/Image.md)
* [`ListView`](docs/components/ListView.md)
* [`Portal`](docs/components/Portal.md)
* [`ScrollView`](docs/components/ScrollView.md)
* [`Text`](docs/components/Text.md)
* [`TextInput`](docs/components/TextInput.md)

View File

@@ -1,13 +1,13 @@
{
"name": "react-native-web",
"version": "0.0.27",
"version": "0.0.32",
"description": "React Native for Web",
"main": "dist/index.js",
"files": [
"dist"
],
"scripts": {
"build": "del ./dist && mkdir dist && babel src -d dist --ignore **/__tests__,src/modules/specHelpers",
"build": "del ./dist && mkdir dist && babel src -d dist --ignore **/__tests__",
"build:umd": "webpack --config webpack.config.js --sort-assets-by --progress",
"examples": "webpack-dev-server --config examples/webpack.config.js --inline --hot --colors --quiet",
"lint": "eslint src",
@@ -27,13 +27,10 @@
},
"devDependencies": {
"babel-cli": "^6.10.1",
"babel-core": "^6.9.1",
"babel-eslint": "^6.0.4",
"babel-core": "^6.10.4",
"babel-eslint": "^6.1.0",
"babel-loader": "^6.2.4",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-preset-es2015": "^6.9.0",
"babel-preset-react": "^6.5.0",
"babel-preset-stage-1": "^6.5.0",
"babel-preset-react-native": "^1.9.0",
"del-cli": "^0.2.0",
"enzyme": "^2.3.0",
"eslint": "^2.12.0",
@@ -46,7 +43,7 @@
"karma-browserstack-launcher": "^1.0.1",
"karma-chrome-launcher": "^1.0.1",
"karma-firefox-launcher": "^1.0.0",
"karma-mocha": "^1.0.1",
"karma-mocha": "^1.1.1",
"karma-mocha-reporter": "^2.0.4",
"karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "^1.7.0",

View File

@@ -1,6 +1,4 @@
import Portal from '../../components/Portal'
import React, { Component, PropTypes } from 'react'
import ReactDOM from 'react-dom'
import StyleSheet from '../StyleSheet'
import View from '../../components/View'
@@ -16,25 +14,15 @@ class ReactNativeApp extends Component {
return (
<View style={styles.appContainer}>
<RootComponent {...initialProps} ref={this._createRootRef} rootTag={rootTag} />
<Portal onModalVisibilityChanged={this._handleModalVisibilityChange} />
<RootComponent {...initialProps} rootTag={rootTag} />
</View>
)
}
_createRootRef = (component) => {
this._root = component
}
_handleModalVisibilityChange = (modalVisible) => {
ReactDOM.findDOMNode(this._root).setAttribute('aria-hidden', `${modalVisible}`)
}
}
const styles = StyleSheet.create({
/**
* Ensure that the application covers the whole screen. This prevents the
* Portal content from being clipped.
* Ensure that the application covers the whole screen.
*/
appContainer: {
position: 'absolute',

View File

@@ -4,20 +4,29 @@ import assert from 'assert'
import expandStyle from '../expandStyle'
suite('apis/StyleSheet/expandStyle', () => {
test('style resolution', () => {
test('shortform -> longform', () => {
const initial = {
borderTopWidth: 1,
borderWidth: 2,
borderStyle: 'solid',
boxSizing: 'border-box',
borderBottomColor: 'white',
borderBottomWidth: 1,
borderWidth: 0,
marginTop: 50,
marginVertical: 25,
margin: 10
}
const expected = {
borderTopWidth: '1px',
borderLeftWidth: '2px',
borderRightWidth: '2px',
borderBottomWidth: '2px',
borderBottomStyle: 'solid',
borderLeftStyle: 'solid',
borderRightStyle: 'solid',
boxSizing: 'border-box',
borderBottomColor: 'white',
borderTopStyle: 'solid',
borderTopWidth: '0px',
borderLeftWidth: '0px',
borderRightWidth: '0px',
borderBottomWidth: '1px',
marginTop: '50px',
marginBottom: '25px',
marginLeft: '10px',
@@ -27,6 +36,18 @@ suite('apis/StyleSheet/expandStyle', () => {
assert.deepEqual(expandStyle(initial), expected)
})
test('textAlignVertical', () => {
const initial = {
textAlignVertical: 'center'
}
const expected = {
verticalAlign: 'middle'
}
assert.deepEqual(expandStyle(initial), expected)
})
test('flex', () => {
const value = 10

View File

@@ -8,13 +8,14 @@ suite('apis/StyleSheet/processTransform', () => {
const style = {
transform: [
{ scaleX: 20 },
{ translateX: 20 },
{ rotate: '20deg' }
]
}
assert.deepEqual(
processTransform(style),
{ transform: 'scaleX(20) rotate(20deg)' }
{ transform: 'scaleX(20) translateX(20px) rotate(20deg)' }
)
})

View File

@@ -1,6 +1,17 @@
/**
* The browser implements the CSS cascade, where the order of properties is a
* factor in determining which styles to paint. React Native is different in
* giving precedence to the more specific styles. For example, the value of
* `paddingTop` takes precedence over that of `padding`.
*
* This module creates mutally exclusive style declarations by expanding all of
* React Native's supported shortform properties (e.g. `padding`) to their
* longfrom equivalents.
*/
import normalizeValue from './normalizeValue'
const styleShortHands = {
const styleShortFormProperties = {
borderColor: [ 'borderTopColor', 'borderRightColor', 'borderBottomColor', 'borderLeftColor' ],
borderRadius: [ 'borderTopLeftRadius', 'borderTopRightRadius', 'borderBottomRightRadius', 'borderBottomLeftRadius' ],
borderStyle: [ 'borderTopStyle', 'borderRightStyle', 'borderBottomStyle', 'borderLeftStyle' ],
@@ -16,50 +27,46 @@ const styleShortHands = {
writingDirection: [ 'direction' ]
}
/**
* Alpha-sort properties, apart from shorthands they must appear before the
* longhand properties that they expand into. This lets more specific styles
* override less specific styles, whatever the order in which they were
* originally declared.
*/
const sortProps = (propsArray) => propsArray.sort((a, b) => {
const expandedA = styleShortHands[a]
const expandedB = styleShortHands[b]
if (expandedA && expandedA.indexOf(b) > -1) {
return -1
} else if (expandedB && expandedB.indexOf(a) > -1) {
return 1
}
return a < b ? -1 : a > b ? 1 : 0
const alphaSort = (arr) => arr.sort((a, b) => {
if (a < b) { return -1 }
if (a > b) { return 1 }
return 0
})
/**
* Expand the shorthand properties to isolate every declaration from the others.
*/
const expandStyle = (style) => {
const propsArray = Object.keys(style)
const sortedProps = sortProps(propsArray)
const createStyleReducer = (originalStyle) => {
const originalStyleProps = Object.keys(originalStyle)
return sortedProps.reduce((resolvedStyle, key) => {
const expandedProps = styleShortHands[key]
const value = normalizeValue(key, style[key])
return (style, prop) => {
const value = normalizeValue(prop, originalStyle[prop])
const longFormProperties = styleShortFormProperties[prop]
// React Native treats `flex:1` like `flex:1 1 auto`
if (key === 'flex') {
resolvedStyle.flexGrow = value
resolvedStyle.flexShrink = 1
resolvedStyle.flexBasis = 'auto'
} else if (key === 'textAlignVertical') {
resolvedStyle.verticalAlign = (value === 'center' ? 'middle' : value)
} else if (expandedProps) {
expandedProps.forEach((prop, i) => {
resolvedStyle[expandedProps[i]] = value
if (prop === 'flex') {
style.flexGrow = value
if (style.flexShrink == null) { style.flexShrink = 1 }
if (style.flexBasis == null) { style.flexBasis = 'auto' }
// React Native accepts 'center' as a value
} else if (prop === 'textAlignVertical') {
style.verticalAlign = (value === 'center' ? 'middle' : value)
} else if (longFormProperties) {
longFormProperties.forEach((longForm, i) => {
// the value of any longform property in the original styles takes
// precedence over the shortform's value
if (originalStyleProps.indexOf(longForm) === -1) {
style[longForm] = value
}
})
} else {
resolvedStyle[key] = value
style[prop] = value
}
return resolvedStyle
}, {})
return style
}
}
const expandStyle = (style) => {
const sortedStyleProps = alphaSort(Object.keys(style))
const styleReducer = createStyleReducer(style)
return sortedStyleProps.reduce(styleReducer, {})
}
module.exports = expandStyle

View File

@@ -1,7 +1,22 @@
const translateProperties = {
translateX: true,
translateY: true,
translateZ: true
}
const processTransformValue = (key, value) => {
if (translateProperties[key] && typeof value === 'number') {
value += 'px';
}
return value;
}
// { scale: 2 } => 'scale(2)'
// { translateX: 20 } => 'translateX(20px)'
const mapTransform = (transform) => {
var key = Object.keys(transform)[0]
return `${key}(${transform[key]})`
const type = Object.keys(transform)[0]
const value = processTransformValue(type, transform[type])
return `${type}(${value})`
}
// [1,2,3,4,5,6] => 'matrix3d(1,2,3,4,5,6)'

View File

@@ -1,4 +1,4 @@
import NativeMethodsDecorator from '../../modules/NativeMethodsDecorator'
import applyNativeMethods from '../../modules/applyNativeMethods'
import React, { Component, PropTypes } from 'react'
import ReactDOM from 'react-dom'
import StyleSheet from '../../apis/StyleSheet'
@@ -19,7 +19,6 @@ const keyframeEffects = [
{ transform: 'scale(0.95)', opacity: 0.5 }
]
@NativeMethodsDecorator
class ActivityIndicator extends Component {
static propTypes = {
animating: PropTypes.bool,
@@ -87,6 +86,8 @@ class ActivityIndicator extends Component {
}
}
applyNativeMethods(ActivityIndicator)
const styles = StyleSheet.create({
container: {
alignItems: 'center',

View File

@@ -1,4 +1,5 @@
import { PropTypes } from 'react'
import BorderPropTypes from '../../apis/StyleSheet/BorderPropTypes'
import ColorPropType from '../../apis/StyleSheet/ColorPropType'
import LayoutPropTypes from '../../apis/StyleSheet/LayoutPropTypes'
import TransformPropTypes from '../../apis/StyleSheet/TransformPropTypes'
@@ -7,6 +8,7 @@ import ImageResizeMode from './ImageResizeMode'
const hiddenOrVisible = PropTypes.oneOf([ 'hidden', 'visible' ])
module.exports = {
...BorderPropTypes,
...LayoutPropTypes,
...TransformPropTypes,
backfaceVisibility: hiddenOrVisible,

View File

@@ -1,8 +1,8 @@
/* global window */
import applyNativeMethods from '../../modules/applyNativeMethods'
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'
@@ -22,7 +22,6 @@ const ImageSourcePropType = PropTypes.oneOfType([
PropTypes.string
])
@NativeMethodsDecorator
class Image extends Component {
static propTypes = {
accessibilityLabel: createNativeComponent.propTypes.accessibilityLabel,
@@ -176,6 +175,8 @@ class Image extends Component {
}
}
applyNativeMethods(Image)
const styles = StyleSheet.create({
initial: {
alignSelf: 'flex-start',

View File

@@ -1,4 +1,4 @@
import NativeMethodsDecorator from '../../modules/NativeMethodsDecorator'
import applyNativeMethods from '../../modules/applyNativeMethods'
import React, { Component } from 'react'
import ScrollView from '../ScrollView'
import ListViewDataSource from './ListViewDataSource'
@@ -6,7 +6,6 @@ import ListViewPropTypes from './ListViewPropTypes'
const SCROLLVIEW_REF = 'listviewscroll'
@NativeMethodsDecorator
class ListView extends Component {
static propTypes = ListViewPropTypes;
@@ -100,4 +99,6 @@ class ListView extends Component {
}
}
applyNativeMethods(ListView)
module.exports = ListView

View File

@@ -1,153 +0,0 @@
/**
* Copyright 2015-present, Nicolas Gallagher
* Copyright 2004-present, Facebook Inc.
* All Rights Reserved.
*
* @flow
*/
import Platform from '../../apis/Platform'
import React, { Component, PropTypes } from 'react'
import StyleSheet from '../../apis/StyleSheet'
import View from '../View'
let _portalRef: any
// unique identifiers for modals
let lastUsedTag = 0
/**
* A container that renders all the modals on top of everything else in the application.
*/
class Portal extends Component {
static propTypes = {
onModalVisibilityChanged: PropTypes.func.isRequired
};
/**
* Create a new unique tag.
*/
static allocateTag(): string {
return `__modal_${++lastUsedTag}`
}
/**
* Render a new modal.
*/
static showModal(tag: string, component: any) {
if (!_portalRef) {
console.error('Calling showModal but no "Portal" has been rendered.')
return
}
_portalRef._showModal(tag, component)
}
/**
* Remove a modal from the collection of modals to be rendered.
*/
static closeModal(tag: string) {
if (!_portalRef) {
console.error('Calling closeModal but no "Portal" has been rendered.')
return
}
_portalRef._closeModal(tag)
}
/**
* Get an array of all the open modals, as identified by their tag string.
*/
static getOpenModals(): Array<string> {
if (!_portalRef) {
console.error('Calling getOpenModals but no "Portal" has been rendered.')
return []
}
return _portalRef._getOpenModals()
}
static notifyAccessibilityService() {
if (!_portalRef) {
console.error('Calling closeModal but no "Portal" has been rendered.')
return
}
_portalRef._notifyAccessibilityService()
}
constructor(props) {
super(props)
this.state = { modals: {} }
this._closeModal = this._closeModal.bind(this)
this._getOpenModals = this._getOpenModals.bind(this)
this._showModal = this._showModal.bind(this)
}
render() {
_portalRef = this
if (!this.state.modals) { return null }
const modals = []
for (const tag in this.state.modals) {
modals.push(this.state.modals[tag])
}
if (modals.length === 0) { return null }
return (
<View style={styles.root}>
{modals}
</View>
)
}
_closeModal(tag: string) {
if (!this.state.modals.hasOwnProperty(tag)) {
return
}
// We are about to close last modal, so Portal will disappear.
// Let's enable accessibility for application view.
if (this._getOpenModals().length === 1) {
this.props.onModalVisibilityChanged(false)
}
// This way state is chained through multiple calls to
// _showModal, _closeModal correctly.
this.setState((state) => {
const modals = state.modals
delete modals[tag]
return { modals }
})
}
_getOpenModals(): Array<string> {
return Object.keys(this.state.modals)
}
_notifyAccessibilityService() {
if (Platform.OS === 'web') {
// We need to send accessibility event in a new batch, as otherwise
// TextViews have no text set at the moment of populating event.
}
}
_showModal(tag: string, component: any) {
// We are about to open first modal, so Portal will appear.
// Let's disable accessibility for background view on Android.
if (this._getOpenModals().length === 0) {
this.props.onModalVisibilityChanged(true)
}
// This way state is chained through multiple calls to
// _showModal, _closeModal correctly.
this.setState((state) => {
const modals = state.modals
modals[tag] = component
return { modals }
})
}
}
const styles = StyleSheet.create({
root: {
position: 'absolute',
left: 0,
top: 0,
right: 0,
bottom: 0
}
})
module.exports = Portal

View File

@@ -1,24 +1,22 @@
/* eslint-env mocha */
import * as utils from '../../../modules/specHelpers'
import assert from 'assert'
import React from 'react'
import ReactTestUtils from 'react-addons-test-utils'
import Text from '../'
import { mount, shallow } from 'enzyme'
suite('components/Text', () => {
test('prop "children"', () => {
const children = 'children'
const result = utils.shallowRender(<Text>{children}</Text>)
assert.equal(result.props.children, children)
const text = shallow(<Text>{children}</Text>)
assert.equal(text.prop('children'), children)
})
test('prop "numberOfLines"')
test('prop "onPress"', (done) => {
const dom = utils.renderToDOM(<Text onPress={onPress} />)
ReactTestUtils.Simulate.click(dom)
const text = mount(<Text onPress={onPress} />)
text.simulate('click')
function onPress(e) {
assert.ok(e.nativeEvent)
done()

View File

@@ -1,11 +1,10 @@
import applyNativeMethods from '../../modules/applyNativeMethods'
import createNativeComponent from '../../modules/createNativeComponent'
import NativeMethodsDecorator from '../../modules/NativeMethodsDecorator'
import { Component, PropTypes } from 'react'
import StyleSheet from '../../apis/StyleSheet'
import StyleSheetPropType from '../../apis/StyleSheet/StyleSheetPropType'
import TextStylePropTypes from './TextStylePropTypes'
@NativeMethodsDecorator
class Text extends Component {
static propTypes = {
accessibilityLabel: createNativeComponent.propTypes.accessibilityLabel,
@@ -49,6 +48,8 @@ class Text extends Component {
}
}
applyNativeMethods(Text)
const styles = StyleSheet.create({
initial: {
color: 'inherit',

View File

@@ -1,88 +1,96 @@
/* eslint-env mocha */
import * as utils from '../../../modules/specHelpers'
import assert from 'assert'
import React from 'react'
import ReactTestUtils from 'react-addons-test-utils'
import StyleSheet from '../../../apis/StyleSheet'
import TextareaAutosize from 'react-textarea-autosize'
import TextInput from '..'
import { mount, shallow } from 'enzyme'
import TextInput from '../'
const placeholderText = 'placeholderText'
const findNativeInput = (wrapper) => wrapper.find('input')
const findNativeTextarea = (wrapper) => wrapper.find(TextareaAutosize)
const findPlaceholder = (wrapper) => wrapper.find({ children: placeholderText })
const findInput = (dom) => dom.querySelector('input, textarea')
const findShallowInput = (vdom) => vdom.props.children.props.children[0]
const findShallowPlaceholder = (vdom) => vdom.props.children.props.children[1]
const testIfDocumentIsFocused = (message, fn) => {
if (document.hasFocus && document.hasFocus()) {
test(message, fn)
} else {
test.skip(`${message} document is not focused`)
}
}
suite('components/TextInput', () => {
test('prop "autoComplete"', () => {
// off
let input = findInput(utils.renderToDOM(<TextInput />))
assert.equal(input.getAttribute('autocomplete'), undefined)
let input = findNativeInput(shallow(<TextInput />))
assert.equal(input.prop('autoComplete'), undefined)
// on
input = findInput(utils.renderToDOM(<TextInput autoComplete />))
assert.equal(input.getAttribute('autocomplete'), 'on')
input = findNativeInput(shallow(<TextInput autoComplete />))
assert.equal(input.prop('autoComplete'), 'on')
})
test.skip('prop "autoFocus"', () => {
test('prop "autoFocus"', () => {
// false
let input = findInput(utils.renderToDOM(<TextInput />))
assert.deepEqual(document.activeElement, document.body)
let input = findNativeInput(mount(<TextInput />))
assert.equal(input.prop('autoFocus'), undefined)
// true
input = findInput(utils.renderToDOM(<TextInput autoFocus />))
assert.deepEqual(document.activeElement, input)
input = findNativeInput(mount(<TextInput autoFocus />))
assert.equal(input.prop('autoFocus'), true)
})
utils.testIfDocumentFocused('prop "clearTextOnFocus"', () => {
testIfDocumentIsFocused('prop "clearTextOnFocus"', () => {
const defaultValue = 'defaultValue'
// false
let input = findInput(utils.renderAndInject(<TextInput defaultValue={defaultValue} />))
input.focus()
assert.equal(input.value, defaultValue)
let input = findNativeInput(mount(<TextInput defaultValue={defaultValue} />))
input.simulate('focus')
assert.equal(input.node.value, defaultValue)
// true
input = findInput(utils.renderAndInject(<TextInput clearTextOnFocus defaultValue={defaultValue} />))
input.focus()
assert.equal(input.value, '')
input = findNativeInput(mount(<TextInput clearTextOnFocus defaultValue={defaultValue} />))
input.simulate('focus')
assert.equal(input.node.value, '')
})
test('prop "defaultValue"', () => {
const defaultValue = 'defaultValue'
const input = findShallowInput(utils.shallowRender(<TextInput defaultValue={defaultValue} />))
assert.equal(input.props.defaultValue, defaultValue)
const input = findNativeInput(shallow(<TextInput defaultValue={defaultValue} />))
assert.equal(input.prop('defaultValue'), defaultValue)
})
test('prop "editable"', () => {
// true
let input = findInput(utils.renderToDOM(<TextInput />))
assert.equal(input.getAttribute('readonly'), undefined)
let input = findNativeInput(shallow(<TextInput />))
assert.equal(input.prop('readOnly'), false)
// false
input = findInput(utils.renderToDOM(<TextInput editable={false} />))
assert.equal(input.getAttribute('readonly'), '')
input = findNativeInput(shallow(<TextInput editable={false} />))
assert.equal(input.prop('readOnly'), true)
})
test('prop "keyboardType"', () => {
// default
let input = findInput(utils.renderToDOM(<TextInput />))
assert.equal(input.getAttribute('type'), undefined)
input = findInput(utils.renderToDOM(<TextInput keyboardType='default' />))
assert.equal(input.getAttribute('type'), undefined)
let input = findNativeInput(shallow(<TextInput />))
assert.equal(input.prop('type'), undefined)
input = findNativeInput(shallow(<TextInput keyboardType='default' />))
assert.equal(input.prop('type'), undefined)
// email-address
input = findInput(utils.renderToDOM(<TextInput keyboardType='email-address' />))
assert.equal(input.getAttribute('type'), 'email')
input = findNativeInput(shallow(<TextInput keyboardType='email-address' />))
assert.equal(input.prop('type'), 'email')
// numeric
input = findInput(utils.renderToDOM(<TextInput keyboardType='numeric' />))
assert.equal(input.getAttribute('type'), 'number')
input = findNativeInput(shallow(<TextInput keyboardType='numeric' />))
assert.equal(input.prop('type'), 'number')
// phone-pad
input = findInput(utils.renderToDOM(<TextInput keyboardType='phone-pad' />))
assert.equal(input.getAttribute('type'), 'tel')
input = findNativeInput(shallow(<TextInput keyboardType='phone-pad' />))
assert.equal(input.prop('type'), 'tel')
// url
input = findInput(utils.renderToDOM(<TextInput keyboardType='url' />))
assert.equal(input.getAttribute('type'), 'url')
input = findNativeInput(shallow(<TextInput keyboardType='url' />))
assert.equal(input.prop('type'), 'url')
})
test('prop "maxLength"', () => {
let input = findInput(utils.renderToDOM(<TextInput />))
assert.equal(input.getAttribute('maxlength'), undefined)
input = findInput(utils.renderToDOM(<TextInput maxLength={10} />))
assert.equal(input.getAttribute('maxlength'), '10')
let input = findNativeInput(shallow(<TextInput />))
assert.equal(input.prop('maxLength'), undefined)
input = findNativeInput(shallow(<TextInput maxLength={10} />))
assert.equal(input.prop('maxLength'), '10')
})
test('prop "maxNumberOfLines"', () => {
@@ -92,45 +100,45 @@ suite('components/TextInput', () => {
return str
}
const result = utils.shallowRender(
const input = findNativeTextarea(shallow(
<TextInput
maxNumberOfLines={3}
multiline
value={generateValue()}
/>
)
assert.equal(findShallowInput(result).props.maxRows, 3)
))
assert.equal(input.prop('maxRows'), 3)
})
test('prop "multiline"', () => {
// false
let input = findInput(utils.renderToDOM(<TextInput />))
assert.equal(input.tagName, 'INPUT')
let input = findNativeInput(shallow(<TextInput />))
assert.equal(input.length, 1)
// true
input = findInput(utils.renderToDOM(<TextInput multiline />))
assert.equal(input.tagName, 'TEXTAREA')
input = findNativeTextarea(shallow(<TextInput multiline />))
assert.equal(input.length, 1)
})
test('prop "numberOfLines"', () => {
// missing multiline
let input = findInput(utils.renderToDOM(<TextInput numberOfLines={2} />))
assert.equal(input.tagName, 'INPUT')
let input = findNativeInput(shallow(<TextInput numberOfLines={2} />))
assert.equal(input.length, 1)
// with multiline
input = findInput(utils.renderAndInject(<TextInput multiline numberOfLines={2} />))
assert.equal(input.tagName, 'TEXTAREA')
input = findNativeTextarea(shallow(<TextInput multiline numberOfLines={2} />))
assert.equal(input.length, 1)
const result = utils.shallowRender(
input = findNativeTextarea(shallow(
<TextInput
multiline
numberOfLines={3}
/>
)
assert.equal(findShallowInput(result).props.minRows, 3)
))
assert.equal(input.prop('minRows'), 3)
})
test('prop "onBlur"', (done) => {
const input = findInput(utils.renderToDOM(<TextInput onBlur={onBlur} />))
ReactTestUtils.Simulate.blur(input)
const input = findNativeInput(mount(<TextInput onBlur={onBlur} />))
input.simulate('blur')
function onBlur(e) {
assert.ok(e)
done()
@@ -138,8 +146,8 @@ suite('components/TextInput', () => {
})
test('prop "onChange"', (done) => {
const input = findInput(utils.renderToDOM(<TextInput onChange={onChange} />))
ReactTestUtils.Simulate.change(input)
const input = findNativeInput(mount(<TextInput onChange={onChange} />))
input.simulate('change')
function onChange(e) {
assert.ok(e)
done()
@@ -148,8 +156,8 @@ suite('components/TextInput', () => {
test('prop "onChangeText"', (done) => {
const newText = 'newText'
const input = findInput(utils.renderToDOM(<TextInput onChangeText={onChangeText} />))
ReactTestUtils.Simulate.change(input, { target: { value: newText } })
const input = findNativeInput(mount(<TextInput onChangeText={onChangeText} />))
input.simulate('change', { target: { value: newText } })
function onChangeText(text) {
assert.equal(text, newText)
done()
@@ -157,8 +165,8 @@ suite('components/TextInput', () => {
})
test('prop "onFocus"', (done) => {
const input = findInput(utils.renderToDOM(<TextInput onFocus={onFocus} />))
ReactTestUtils.Simulate.focus(input)
const input = findNativeInput(mount(<TextInput onFocus={onFocus} />))
input.simulate('focus')
function onFocus(e) {
assert.ok(e)
done()
@@ -168,8 +176,8 @@ suite('components/TextInput', () => {
test('prop "onLayout"')
test('prop "onSelectionChange"', (done) => {
const input = findInput(utils.renderAndInject(<TextInput defaultValue='12345' onSelectionChange={onSelectionChange} />))
ReactTestUtils.Simulate.select(input, { target: { selectionStart: 0, selectionEnd: 3 } })
const input = findNativeInput(mount(<TextInput defaultValue='12345' onSelectionChange={onSelectionChange} />))
input.simulate('select', { target: { selectionStart: 0, selectionEnd: 3 } })
function onSelectionChange(e) {
assert.equal(e.selectionEnd, 3)
assert.equal(e.selectionStart, 0)
@@ -178,46 +186,46 @@ suite('components/TextInput', () => {
})
test('prop "placeholder"', () => {
const placeholder = 'placeholder'
const result = findShallowPlaceholder(utils.shallowRender(<TextInput placeholder={placeholder} />))
assert.equal(result.props.children.props.children, placeholder)
let textInput = shallow(<TextInput />)
assert.equal(findPlaceholder(textInput).length, 0)
textInput = shallow(<TextInput placeholder={placeholderText} />)
assert.equal(findPlaceholder(textInput).length, 1)
})
test('prop "placeholderTextColor"', () => {
const placeholder = 'placeholder'
let placeholderElement = findPlaceholder(shallow(<TextInput placeholder={placeholderText} />))
assert.equal(StyleSheet.flatten(placeholderElement.prop('style')).color, 'darkgray')
let result = findShallowPlaceholder(utils.shallowRender(<TextInput placeholder={placeholder} />))
assert.equal(StyleSheet.flatten(result.props.children.props.style).color, 'darkgray')
result = findShallowPlaceholder(utils.shallowRender(<TextInput placeholder={placeholder} placeholderTextColor='red' />))
assert.equal(StyleSheet.flatten(result.props.children.props.style).color, 'red')
placeholderElement = findPlaceholder(shallow(<TextInput placeholder={placeholderText} placeholderTextColor='red' />))
assert.equal(StyleSheet.flatten(placeholderElement.prop('style')).color, 'red')
})
test('prop "secureTextEntry"', () => {
let input = findInput(utils.renderToDOM(<TextInput secureTextEntry />))
assert.equal(input.getAttribute('type'), 'password')
let input = findNativeInput(shallow(<TextInput secureTextEntry />))
assert.equal(input.prop('type'), 'password')
// ignored for multiline
input = findInput(utils.renderToDOM(<TextInput multiline secureTextEntry />))
assert.equal(input.getAttribute('type'), undefined)
input = findNativeTextarea(shallow(<TextInput multiline secureTextEntry />))
assert.equal(input.prop('type'), undefined)
})
utils.testIfDocumentFocused('prop "selectTextOnFocus"', () => {
testIfDocumentIsFocused('prop "selectTextOnFocus"', () => {
const text = 'Text'
// false
let input = findInput(utils.renderAndInject(<TextInput defaultValue={text} />))
input.focus()
assert.equal(input.selectionEnd, 0)
assert.equal(input.selectionStart, 0)
let input = findNativeInput(mount(<TextInput defaultValue={text} />))
input.node.focus()
assert.equal(input.node.selectionEnd, 4)
assert.equal(input.node.selectionStart, 4)
// true
input = findInput(utils.renderAndInject(<TextInput defaultValue={text} selectTextOnFocus />))
input.focus()
assert.equal(input.selectionEnd, 4)
assert.equal(input.selectionStart, 0)
// input = findNativeInput(mount(<TextInput defaultValue={text} selectTextOnFocus />))
// input.node.focus()
// assert.equal(input.node.selectionEnd, 4)
// assert.equal(input.node.selectionStart, 0)
})
test('prop "value"', () => {
const value = 'value'
const input = findShallowInput(utils.shallowRender(<TextInput value={value} />))
assert.equal(input.props.value, value)
const input = findNativeInput(shallow(<TextInput value={value} />))
assert.equal(input.prop('value'), value)
})
})

View File

@@ -1,5 +1,5 @@
import applyNativeMethods from '../../modules/applyNativeMethods'
import createNativeComponent from '../../modules/createNativeComponent'
import NativeMethodsDecorator from '../../modules/NativeMethodsDecorator'
import omit from 'lodash/omit'
import pick from 'lodash/pick'
import React, { Component, PropTypes } from 'react'
@@ -8,12 +8,12 @@ import StyleSheet from '../../apis/StyleSheet'
import Text from '../Text'
import TextareaAutosize from 'react-textarea-autosize'
import TextInputState from './TextInputState'
import UIManager from '../../apis/UIManager'
import View from '../View'
import ViewStylePropTypes from '../View/ViewStylePropTypes'
const viewStyleProps = Object.keys(ViewStylePropTypes)
@NativeMethodsDecorator
class TextInput extends Component {
static propTypes = {
...View.propTypes,
@@ -68,7 +68,7 @@ class TextInput extends Component {
}
setNativeProps(props) {
this.refs.input.setNativeProps(props)
UIManager.updateView(this.refs.input, props, this)
}
render() {
@@ -232,13 +232,15 @@ class TextInput extends Component {
}
}
applyNativeMethods(TextInput)
const styles = StyleSheet.create({
initial: {
borderColor: 'black',
borderWidth: 1
},
wrapper: {
flexGrow: 1
flex: 1
},
input: {
appearance: 'none',
@@ -246,8 +248,9 @@ const styles = StyleSheet.create({
borderWidth: 0,
boxSizing: 'border-box',
color: 'inherit',
flexGrow: 1,
flex: 1,
font: 'inherit',
minHeight: '100%', // center small inputs (fix #139)
padding: 0,
zIndex: 1
},

View File

@@ -1,12 +1,11 @@
import applyNativeMethods from '../../modules/applyNativeMethods'
import createNativeComponent from '../../modules/createNativeComponent'
import NativeMethodsDecorator from '../../modules/NativeMethodsDecorator'
import normalizeNativeEvent from '../../apis/PanResponder/normalizeNativeEvent'
import { Component, PropTypes } from 'react'
import StyleSheet from '../../apis/StyleSheet'
import StyleSheetPropType from '../../apis/StyleSheet/StyleSheetPropType'
import ViewStylePropTypes from './ViewStylePropTypes'
@NativeMethodsDecorator
class View extends Component {
static propTypes = {
accessibilityLabel: createNativeComponent.propTypes.accessibilityLabel,
@@ -98,6 +97,8 @@ class View extends Component {
}
}
applyNativeMethods(View)
const styles = StyleSheet.create({
// https://github.com/facebook/css-layout#default-values
initial: {

View File

@@ -23,7 +23,6 @@ import UIManager from './apis/UIManager'
import ActivityIndicator from './components/ActivityIndicator'
import Image from './components/Image'
import ListView from './components/ListView'
import Portal from './components/Portal'
import ScrollView from './components/ScrollView'
import Text from './components/Text'
import TextInput from './components/TextInput'
@@ -71,7 +70,6 @@ const ReactNative = {
ActivityIndicator,
Image,
ListView,
Portal,
ScrollView,
Text,
TextInput,

View File

@@ -7,7 +7,7 @@
import NativeMethodsMixin from '../NativeMethodsMixin'
const NativeMethodsDecorator = (Component) => {
const applyNativeMethods = (Component) => {
Object.keys(NativeMethodsMixin).forEach((method) => {
if (!Component.prototype[method]) {
Component.prototype[method] = NativeMethodsMixin[method]
@@ -16,4 +16,4 @@ const NativeMethodsDecorator = (Component) => {
return Component
}
module.exports = NativeMethodsDecorator
module.exports = applyNativeMethods

View File

@@ -1,61 +1,59 @@
/* eslint-env mocha */
import * as utils from '../../specHelpers'
import assert from 'assert'
import createNativeComponent from '../'
import createNativeComponent from '..'
import { shallow } from 'enzyme'
suite('modules/createNativeComponent', () => {
test('prop "accessibilityLabel"', () => {
const accessibilityLabel = 'accessibilityLabel'
const dom = utils.renderToDOM(createNativeComponent({ accessibilityLabel }))
assert.equal(dom.getAttribute('aria-label'), accessibilityLabel)
const element = shallow(createNativeComponent({ accessibilityLabel }))
assert.equal(element.prop('aria-label'), accessibilityLabel)
})
test('prop "accessibilityLiveRegion"', () => {
const accessibilityLiveRegion = 'polite'
const dom = utils.renderToDOM(createNativeComponent({ accessibilityLiveRegion }))
assert.equal(dom.getAttribute('aria-live'), accessibilityLiveRegion)
const element = shallow(createNativeComponent({ accessibilityLiveRegion }))
assert.equal(element.prop('aria-live'), accessibilityLiveRegion)
})
test('prop "accessibilityRole"', () => {
const accessibilityRole = 'banner'
let dom = utils.renderToDOM(createNativeComponent({ accessibilityRole }))
assert.equal(dom.getAttribute('role'), accessibilityRole)
assert.equal((dom.tagName).toLowerCase(), 'header')
let element = shallow(createNativeComponent({ accessibilityRole }))
assert.equal(element.prop('role'), accessibilityRole)
assert.equal(element.is('header'), true)
const button = 'button'
dom = utils.renderToDOM(createNativeComponent({ accessibilityRole: 'button' }))
assert.equal(dom.getAttribute('type'), button)
assert.equal((dom.tagName).toLowerCase(), button)
element = shallow(createNativeComponent({ accessibilityRole: 'button' }))
assert.equal(element.prop('type'), button)
assert.equal(element.is('button'), true)
})
test('prop "accessible"', () => {
// accessible (implicit)
let dom = utils.renderToDOM(createNativeComponent({}))
assert.equal(dom.getAttribute('aria-hidden'), null)
let element = shallow(createNativeComponent({}))
assert.equal(element.prop('aria-hidden'), null)
// accessible (explicit)
dom = utils.renderToDOM(createNativeComponent({ accessible: true }))
assert.equal(dom.getAttribute('aria-hidden'), null)
element = shallow(createNativeComponent({ accessible: true }))
assert.equal(element.prop('aria-hidden'), null)
// not accessible
dom = utils.renderToDOM(createNativeComponent({ accessible: false }))
assert.equal(dom.getAttribute('aria-hidden'), 'true')
element = shallow(createNativeComponent({ accessible: false }))
assert.equal(element.prop('aria-hidden'), true)
})
test('prop "component"', () => {
const component = 'main'
const dom = utils.renderToDOM(createNativeComponent({ component }))
const tagName = (dom.tagName).toLowerCase()
assert.equal(tagName, component)
const element = shallow(createNativeComponent({ component }))
assert.equal(element.is('main'), true)
})
test('prop "testID"', () => {
// no testID
let dom = utils.renderToDOM(createNativeComponent({}))
assert.equal(dom.getAttribute('data-testid'), null)
let element = shallow(createNativeComponent({}))
assert.equal(element.prop('data-testid'), null)
// with testID
const testID = 'Example.testID'
dom = utils.renderToDOM(createNativeComponent({ testID }))
assert.equal(dom.getAttribute('data-testid'), testID)
element = shallow(createNativeComponent({ testID }))
assert.equal(element.prop('data-testid'), testID)
})
})

View File

@@ -1,74 +0,0 @@
/* eslint-env mocha */
import assert from 'assert'
import React from 'react'
import ReactDOM from 'react-dom'
import ReactTestUtils from 'react-addons-test-utils'
export const assertProps = {
style: function (Component, props) {
let shallow
// default styles
shallow = shallowRender(<Component {...props} />)
assert.deepEqual(
shallow.props.style,
Component.defaultProps.style
)
// filtering of unsupported styles
const styleToFilter = { unsupported: 'unsupported' }
shallow = shallowRender(<Component {...props} style={styleToFilter} />)
assert.deepEqual(
shallow.props.style.unsupported,
undefined,
'unsupported "style" properties must not be transferred'
)
// merging of custom styles
const styleToMerge = { margin: '10' }
shallow = shallowRender(<Component {...props} style={styleToMerge} />)
assert.deepEqual(
shallow.props.style,
{ ...Component.defaultProps.style, ...styleToMerge }
)
}
}
export function render(element, container) {
return container
? ReactDOM.render(element, container)
: ReactTestUtils.renderIntoDocument(element)
}
export function renderAndInject(element) {
const id = '_renderAndInject'
let div = document.getElementById(id)
if (!div) {
div = document.createElement('div')
div.id = id
document.body.appendChild(div)
} else {
div.innerHTML = ''
}
const result = render(element, div)
return ReactDOM.findDOMNode(result)
}
export function renderToDOM(element, container) {
const result = render(element, container)
return ReactDOM.findDOMNode(result)
}
export function shallowRender(component, context = {}) {
const shallowRenderer = ReactTestUtils.createRenderer()
shallowRenderer.render(component, context)
return shallowRenderer.getRenderOutput()
}
export function testIfDocumentFocused(message, fn) {
if (document.hasFocus && document.hasFocus()) {
test(message, fn)
} else {
test.skip(`${message} WARNING: document is not focused`)
}
}