mirror of
https://github.com/zoriya/react-native-web.git
synced 2026-06-04 02:56:42 +00:00
TextInput: props and tests
This commit is contained in:
@@ -4,7 +4,7 @@
|
|||||||
[![npm version][npm-image]][npm-url]
|
[![npm version][npm-image]][npm-url]
|
||||||
|
|
||||||
The core [React Native][react-native-url] components adapted and expanded upon
|
The core [React Native][react-native-url] components adapted and expanded upon
|
||||||
for the web, backed by a precomputed CSS library. ~19KB minified and gzipped.
|
for the web, backed by a precomputed CSS library. ~21KB minified and gzipped.
|
||||||
|
|
||||||
* [Slack: reactiflux channel #react-native-web][slack-url]
|
* [Slack: reactiflux channel #react-native-web][slack-url]
|
||||||
* [Gitter: react-native-web][gitter-url]
|
* [Gitter: react-native-web][gitter-url]
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ var webpackConfig = require('./webpack.config.base')
|
|||||||
module.exports = function (config) {
|
module.exports = function (config) {
|
||||||
config.set({
|
config.set({
|
||||||
basePath: constants.ROOT_DIRECTORY,
|
basePath: constants.ROOT_DIRECTORY,
|
||||||
browsers: [ process.env.TRAVIS ? 'Firefox' : 'Chrome' ],
|
browsers: process.env.TRAVIS ? [ 'Firefox' ] : [ 'Chrome' ],
|
||||||
browserNoActivityTimeout: 60000,
|
browserNoActivityTimeout: 60000,
|
||||||
client: {
|
client: {
|
||||||
captureConsole: true,
|
captureConsole: true,
|
||||||
|
|||||||
+120
-28
@@ -5,67 +5,130 @@ such as auto-complete, auto-focus, placeholder text, and event callbacks.
|
|||||||
|
|
||||||
Note: some props are exclusive to or excluded from `multiline`.
|
Note: some props are exclusive to or excluded from `multiline`.
|
||||||
|
|
||||||
|
Unsupported React Native props:
|
||||||
|
`autoCapitalize`,
|
||||||
|
`autoCorrect`,
|
||||||
|
`onEndEditing`,
|
||||||
|
`onSubmitEditing`,
|
||||||
|
`clearButtonMode` (ios),
|
||||||
|
`enablesReturnKeyAutomatically` (ios),
|
||||||
|
`returnKeyType` (ios),
|
||||||
|
`selectionState` (ios),
|
||||||
|
`textAlign` (android),
|
||||||
|
`textAlignVertical` (android),
|
||||||
|
`underlineColorAndroid` (android)
|
||||||
|
|
||||||
## Props
|
## Props
|
||||||
|
|
||||||
**autoComplete** bool
|
(web) **accessibilityLabel**: string
|
||||||
|
|
||||||
|
Defines the text label available to assistive technologies upon interaction
|
||||||
|
with the element. (This is implemented using `aria-label`.)
|
||||||
|
|
||||||
|
(web) **autoComplete**: bool = false
|
||||||
|
|
||||||
Indicates whether the value of the control can be automatically completed by the browser.
|
Indicates whether the value of the control can be automatically completed by the browser.
|
||||||
|
|
||||||
**autoFocus** bool
|
**autoFocus**: bool = false
|
||||||
|
|
||||||
If true, focuses the input on `componentDidMount`. Only the first form element
|
If true, focuses the input on `componentDidMount`. Only the first form element
|
||||||
in a document with `autofocus` is focused. Default: `false`.
|
in a document with `autofocus` is focused.
|
||||||
|
|
||||||
**defaultValue** string
|
**clearTextOnFocus**: bool = false
|
||||||
|
|
||||||
|
If `true`, clears the text field automatically when focused.
|
||||||
|
|
||||||
|
**defaultValue**: string
|
||||||
|
|
||||||
Provides an initial value that will change when the user starts typing. Useful
|
Provides an initial value that will change when the user starts typing. Useful
|
||||||
for simple use-cases where you don't want to deal with listening to events and
|
for simple use-cases where you don't want to deal with listening to events and
|
||||||
updating the `value` prop to keep the controlled state in sync.
|
updating the `value` prop to keep the controlled state in sync.
|
||||||
|
|
||||||
**editable** bool
|
**editable**: bool = true
|
||||||
|
|
||||||
If false, text is not editable. Default: `true`.
|
If `false`, text is not editable (i.e., read-only).
|
||||||
|
|
||||||
**keyboardType** oneOf('default', 'email', 'numeric', 'search', 'tel', 'url')
|
**keyboardType**: oneOf('default', 'email-address', 'numeric', 'phone-pad', 'url') = 'default'
|
||||||
|
|
||||||
Determines which keyboard to open, e.g. `email`. Default: `default`. (Not
|
Determines which keyboard to open.
|
||||||
available when `multiline` is `true`.)
|
|
||||||
|
|
||||||
**multiline** bool
|
(Not available when `multiline` is `true`.)
|
||||||
|
|
||||||
If true, the text input can be multiple lines. Default: `false`.
|
**maxLength**: number
|
||||||
|
|
||||||
**onBlur** function
|
Limits the maximum number of characters that can be entered.
|
||||||
|
|
||||||
|
(web) **maxNumberOfLines**: number = numberOfLines
|
||||||
|
|
||||||
|
Limits the maximum number of lines for a multiline `TextInput`.
|
||||||
|
|
||||||
|
(Requires `multiline` to be `true`.)
|
||||||
|
|
||||||
|
**multiline**: bool = false
|
||||||
|
|
||||||
|
If true, the text input can be multiple lines.
|
||||||
|
|
||||||
|
**numberOfLines**: number = 2
|
||||||
|
|
||||||
|
Sets the initial number of lines for a multiline `TextInput`.
|
||||||
|
|
||||||
|
(Requires `multiline` to be `true`.)
|
||||||
|
|
||||||
|
**onBlur**: function
|
||||||
|
|
||||||
Callback that is called when the text input is blurred.
|
Callback that is called when the text input is blurred.
|
||||||
|
|
||||||
**onChange** function
|
**onChange**: function
|
||||||
|
|
||||||
Callback that is called when the text input's text changes.
|
Callback that is called when the text input's text changes.
|
||||||
|
|
||||||
**onChangeText** function
|
**onChangeText**: function
|
||||||
|
|
||||||
Callback that is called when the text input's text changes. Changed text is
|
Callback that is called when the text input's text changes. The text is passed
|
||||||
passed as an argument to the callback handler.
|
as an argument to the callback handler.
|
||||||
|
|
||||||
**onFocus** function
|
**onFocus**: function
|
||||||
|
|
||||||
Callback that is called when the text input is focused.
|
Callback that is called when the text input is focused.
|
||||||
|
|
||||||
**placeholder** string
|
**onLayout**: function
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
(web) **onSelectionChange**: function
|
||||||
|
|
||||||
|
Callback that is called when the text input's selection changes. The following
|
||||||
|
object is passed as an argument to the callback handler.
|
||||||
|
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
selectionDirection,
|
||||||
|
selectionEnd,
|
||||||
|
selectionStart,
|
||||||
|
nativeEvent
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**placeholder**: string
|
||||||
|
|
||||||
The string that will be rendered before text input has been entered.
|
The string that will be rendered before text input has been entered.
|
||||||
|
|
||||||
**placeholderTextColor** string
|
**placeholderTextColor**: string
|
||||||
|
|
||||||
The text color of the placeholder string.
|
TODO. The text color of the placeholder string.
|
||||||
|
|
||||||
**secureTextEntry** bool
|
**secureTextEntry**: bool = false
|
||||||
|
|
||||||
If true, the text input obscures the text entered so that sensitive text like
|
If true, the text input obscures the text entered so that sensitive text like
|
||||||
passwords stay secure. Default: `false`. (Not available when `multiline` is `true`.)
|
passwords stay secure.
|
||||||
|
|
||||||
**style** style
|
(Not available when `multiline` is `true`.)
|
||||||
|
|
||||||
|
**selectTextOnFocus**: bool = false
|
||||||
|
|
||||||
|
If `true`, all text will automatically be selected on focus.
|
||||||
|
|
||||||
|
**style**: style
|
||||||
|
|
||||||
[View](View.md) style
|
[View](View.md) style
|
||||||
|
|
||||||
@@ -81,31 +144,60 @@ passwords stay secure. Default: `false`. (Not available when `multiline` is `tru
|
|||||||
+ `textDecoration`
|
+ `textDecoration`
|
||||||
+ `textTransform`
|
+ `textTransform`
|
||||||
|
|
||||||
**testID** string
|
**testID**: string
|
||||||
|
|
||||||
Used to locate this view in end-to-end tests.
|
Used to locate this view in end-to-end tests.
|
||||||
|
|
||||||
|
**value**: string
|
||||||
|
|
||||||
|
The value to show for the text input. `TextInput` is a controlled component,
|
||||||
|
which means the native `value` will be forced to match this prop if provided.
|
||||||
|
Read about how [React form
|
||||||
|
components](https://facebook.github.io/react/docs/forms.html) work. To prevent
|
||||||
|
user edits to the value set `editable={false}`.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import React, { TextInput } from 'react-native-web'
|
import React, { TextInput } from 'react-native-web'
|
||||||
|
|
||||||
const { Component, PropTypes } = React
|
const { Component } = React
|
||||||
|
|
||||||
class AppTextInput extends Component {
|
class AppTextInput extends Component {
|
||||||
static propTypes = {
|
constructor(props, context) {
|
||||||
|
super(props, context)
|
||||||
|
this.state = { isFocused: false }
|
||||||
}
|
}
|
||||||
|
|
||||||
static defaultProps = {
|
_onFocus(e) {
|
||||||
|
this.setState({ isFocused: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<TextInput />
|
<TextInput
|
||||||
|
accessibilityLabel='Write a status update'
|
||||||
|
maxNumberOfLines={5}
|
||||||
|
multiline
|
||||||
|
numberOfLines={2}
|
||||||
|
onFocus={this._onFocus.bind(this)}
|
||||||
|
placeholder={`What's happening?`}
|
||||||
|
style={
|
||||||
|
...styles.default
|
||||||
|
...(this.state.isFocused && styles.focused)
|
||||||
|
}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = {
|
const styles = {
|
||||||
|
default: {
|
||||||
|
borderColor: 'gray',
|
||||||
|
borderWidth: '0 0 2px 0'
|
||||||
|
},
|
||||||
|
focused: {
|
||||||
|
borderColor: 'blue'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
+2
-1
@@ -18,7 +18,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react": ">=0.13.3",
|
"react": ">=0.13.3",
|
||||||
"react-swipeable": "^3.0.2",
|
"react-swipeable": "^3.0.2",
|
||||||
"react-tappable": "^0.6.0"
|
"react-tappable": "^0.6.0",
|
||||||
|
"react-textarea-autosize": "^2.5.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"autoprefixer-loader": "^3.1.0",
|
"autoprefixer-loader": "^3.1.0",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { pickProps } from '../../modules/filterObjectProps'
|
import { pickProps } from '../../modules/filterObjectProps'
|
||||||
import CoreComponent from '../CoreComponent'
|
import CoreComponent from '../CoreComponent'
|
||||||
import React, { PropTypes } from 'react'
|
import React, { PropTypes } from 'react'
|
||||||
|
import TextareaAutosize from 'react-textarea-autosize'
|
||||||
import TextInputStylePropTypes from './TextInputStylePropTypes'
|
import TextInputStylePropTypes from './TextInputStylePropTypes'
|
||||||
|
|
||||||
const textInputStyleKeys = Object.keys(TextInputStylePropTypes)
|
const textInputStyleKeys = Object.keys(TextInputStylePropTypes)
|
||||||
@@ -9,90 +10,166 @@ const styles = {
|
|||||||
initial: {
|
initial: {
|
||||||
appearance: 'none',
|
appearance: 'none',
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
|
borderColor: 'black',
|
||||||
borderWidth: '1px',
|
borderWidth: '1px',
|
||||||
|
boxSizing: 'border-box',
|
||||||
color: 'inherit',
|
color: 'inherit',
|
||||||
font: 'inherit'
|
font: 'inherit',
|
||||||
|
padding: 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TextInput extends React.Component {
|
class TextInput extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
accessibilityLabel: PropTypes.string,
|
||||||
autoComplete: PropTypes.bool,
|
autoComplete: PropTypes.bool,
|
||||||
autoFocus: PropTypes.bool,
|
autoFocus: PropTypes.bool,
|
||||||
|
clearTextOnFocus: PropTypes.bool,
|
||||||
defaultValue: PropTypes.string,
|
defaultValue: PropTypes.string,
|
||||||
editable: PropTypes.bool,
|
editable: PropTypes.bool,
|
||||||
keyboardType: PropTypes.oneOf(['default', 'email', 'numeric', 'search', 'tel', 'url']),
|
keyboardType: PropTypes.oneOf(['default', 'email-address', 'numeric', 'phone-pad', 'url']),
|
||||||
|
maxLength: PropTypes.number,
|
||||||
|
maxNumberOfLines: PropTypes.number,
|
||||||
multiline: PropTypes.bool,
|
multiline: PropTypes.bool,
|
||||||
|
numberOfLines: PropTypes.number,
|
||||||
onBlur: PropTypes.func,
|
onBlur: PropTypes.func,
|
||||||
onChange: PropTypes.func,
|
onChange: PropTypes.func,
|
||||||
onChangeText: PropTypes.func,
|
onChangeText: PropTypes.func,
|
||||||
onFocus: PropTypes.func,
|
onFocus: PropTypes.func,
|
||||||
|
onSelectionChange: PropTypes.func,
|
||||||
placeholder: PropTypes.string,
|
placeholder: PropTypes.string,
|
||||||
|
placeholderTextColor: PropTypes.string,
|
||||||
secureTextEntry: PropTypes.bool,
|
secureTextEntry: PropTypes.bool,
|
||||||
|
selectTextOnFocus: PropTypes.bool,
|
||||||
style: PropTypes.shape(TextInputStylePropTypes),
|
style: PropTypes.shape(TextInputStylePropTypes),
|
||||||
testID: CoreComponent.propTypes.testID
|
testID: CoreComponent.propTypes.testID,
|
||||||
|
value: PropTypes.string
|
||||||
}
|
}
|
||||||
|
|
||||||
static stylePropTypes = TextInputStylePropTypes
|
static stylePropTypes = TextInputStylePropTypes
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
autoComplete: false,
|
|
||||||
autoFocus: false,
|
|
||||||
editable: true,
|
editable: true,
|
||||||
keyboardType: 'default',
|
keyboardType: 'default',
|
||||||
multiline: false,
|
multiline: false,
|
||||||
|
numberOfLines: 2,
|
||||||
secureTextEntry: false,
|
secureTextEntry: false,
|
||||||
style: styles.initial
|
style: styles.initial
|
||||||
}
|
}
|
||||||
|
|
||||||
_onBlur(e) {
|
_onBlur(e) {
|
||||||
if (this.props.onBlur) this.props.onBlur(e)
|
const { onBlur } = this.props
|
||||||
|
if (onBlur) onBlur(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
_onChange(e) {
|
_onChange(e) {
|
||||||
if (this.props.onChangeText) this.props.onChangeText(e.target.value)
|
const { onChange, onChangeText } = this.props
|
||||||
if (this.props.onChange) this.props.onChange(e)
|
if (onChangeText) onChangeText(e.target.value)
|
||||||
|
if (onChange) onChange(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
_onFocus(e) {
|
_onFocus(e) {
|
||||||
if (this.props.onFocus) this.props.onFocus(e)
|
const { clearTextOnFocus, onFocus, selectTextOnFocus } = this.props
|
||||||
|
const node = React.findDOMNode(this)
|
||||||
|
if (clearTextOnFocus) node.value = ''
|
||||||
|
if (selectTextOnFocus) node.select()
|
||||||
|
if (onFocus) onFocus(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
_onSelectionChange(e) {
|
||||||
|
const { onSelectionChange } = this.props
|
||||||
|
const { selectionDirection, selectionEnd, selectionStart } = e.target
|
||||||
|
const event = {
|
||||||
|
selectionDirection,
|
||||||
|
selectionEnd,
|
||||||
|
selectionStart,
|
||||||
|
nativeEvent: e.nativeEvent
|
||||||
|
}
|
||||||
|
if (onSelectionChange) onSelectionChange(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
|
accessibilityLabel,
|
||||||
autoComplete,
|
autoComplete,
|
||||||
autoFocus,
|
autoFocus,
|
||||||
defaultValue,
|
defaultValue,
|
||||||
editable,
|
editable,
|
||||||
keyboardType,
|
keyboardType,
|
||||||
|
maxLength,
|
||||||
|
maxNumberOfLines,
|
||||||
multiline,
|
multiline,
|
||||||
|
numberOfLines,
|
||||||
|
onBlur,
|
||||||
|
onChange,
|
||||||
|
onChangeText,
|
||||||
|
onSelectionChange,
|
||||||
placeholder,
|
placeholder,
|
||||||
secureTextEntry,
|
secureTextEntry,
|
||||||
style,
|
style,
|
||||||
testID
|
testID,
|
||||||
|
value
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
const resolvedStyle = pickProps(style, textInputStyleKeys)
|
const resolvedStyle = pickProps(style, textInputStyleKeys)
|
||||||
const type = secureTextEntry && 'password' || (keyboardType === 'default' ? '' : keyboardType)
|
let type
|
||||||
|
|
||||||
return (
|
switch (keyboardType) {
|
||||||
<CoreComponent
|
case 'email-address':
|
||||||
autoComplete={autoComplete}
|
type = 'email'
|
||||||
autoFocus={autoFocus}
|
break
|
||||||
className={'TextInput'}
|
case 'numeric':
|
||||||
component={multiline ? 'textarea' : 'input'}
|
type = 'number'
|
||||||
defaultValue={defaultValue || placeholder}
|
break
|
||||||
onBlur={this._onBlur.bind(this)}
|
case 'phone-pad':
|
||||||
onChange={this._onChange.bind(this)}
|
type = 'tel'
|
||||||
onFocus={this._onFocus.bind(this)}
|
break
|
||||||
readOnly={!editable}
|
case 'url':
|
||||||
style={{
|
type = 'url'
|
||||||
...(styles.initial),
|
break
|
||||||
...resolvedStyle
|
}
|
||||||
}}
|
|
||||||
testID={testID}
|
if (secureTextEntry) {
|
||||||
type={multiline ? type : undefined}
|
type = 'password'
|
||||||
/>
|
}
|
||||||
|
|
||||||
|
const propsCommon = {
|
||||||
|
'aria-label': accessibilityLabel,
|
||||||
|
autoComplete: autoComplete && 'on',
|
||||||
|
autoFocus,
|
||||||
|
className: 'TextInput',
|
||||||
|
defaultValue,
|
||||||
|
maxLength,
|
||||||
|
onBlur: onBlur && this._onBlur.bind(this),
|
||||||
|
onChange: (onChange || onChangeText) && this._onChange.bind(this),
|
||||||
|
onFocus: this._onFocus.bind(this),
|
||||||
|
onSelect: onSelectionChange && this._onSelectionChange.bind(this),
|
||||||
|
placeholder,
|
||||||
|
readOnly: !editable,
|
||||||
|
style: {
|
||||||
|
...styles.initial,
|
||||||
|
...resolvedStyle
|
||||||
|
},
|
||||||
|
testID,
|
||||||
|
value
|
||||||
|
}
|
||||||
|
|
||||||
|
const propsMultiline = {
|
||||||
|
...propsCommon,
|
||||||
|
component: TextareaAutosize,
|
||||||
|
maxRows: maxNumberOfLines || numberOfLines,
|
||||||
|
minRows: numberOfLines
|
||||||
|
}
|
||||||
|
|
||||||
|
const propsSingleline = {
|
||||||
|
...propsCommon,
|
||||||
|
component: 'input',
|
||||||
|
type
|
||||||
|
}
|
||||||
|
|
||||||
|
return (multiline
|
||||||
|
? <CoreComponent {...propsMultiline} />
|
||||||
|
: <CoreComponent {...propsSingleline} />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
/*
|
import * as utils from '../../modules/specHelpers'
|
||||||
import { assertProps, renderToDOM, shallowRender } from '../../modules/specHelpers'
|
|
||||||
import assert from 'assert'
|
import assert from 'assert'
|
||||||
import React from 'react/addons'
|
import React from 'react/addons'
|
||||||
|
|
||||||
@@ -7,7 +6,217 @@ import TextInput from '.'
|
|||||||
|
|
||||||
const ReactTestUtils = React.addons.TestUtils
|
const ReactTestUtils = React.addons.TestUtils
|
||||||
|
|
||||||
suite.skip('TextInput', () => {
|
suite('TextInput', () => {
|
||||||
test('prop "children"', () => {})
|
test('prop "accessibilityLabel"', () => {
|
||||||
|
utils.assertProps.accessibilityLabel(TextInput)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('prop "autoComplete"', () => {
|
||||||
|
// off
|
||||||
|
let dom = utils.renderToDOM(<TextInput />)
|
||||||
|
assert.equal(dom.getAttribute('autocomplete'), undefined)
|
||||||
|
// on
|
||||||
|
dom = utils.renderToDOM(<TextInput autoComplete />)
|
||||||
|
assert.equal(dom.getAttribute('autocomplete'), 'on')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('prop "autoFocus"', () => {
|
||||||
|
// false
|
||||||
|
let dom = utils.renderToDOM(<TextInput />)
|
||||||
|
assert.deepEqual(document.activeElement, document.body)
|
||||||
|
// true
|
||||||
|
dom = utils.renderToDOM(<TextInput autoFocus />)
|
||||||
|
assert.deepEqual(document.activeElement, dom)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('prop "clearTextOnFocus"', () => {
|
||||||
|
const defaultValue = 'defaultValue'
|
||||||
|
utils.requiresFocus(() => {
|
||||||
|
// false
|
||||||
|
let dom = utils.renderAndInject(<TextInput defaultValue={defaultValue} />)
|
||||||
|
dom.focus()
|
||||||
|
assert.equal(dom.value, defaultValue)
|
||||||
|
// true
|
||||||
|
dom = utils.renderAndInject(<TextInput clearTextOnFocus defaultValue={defaultValue} />)
|
||||||
|
dom.focus()
|
||||||
|
assert.equal(dom.value, '')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('prop "defaultValue"', () => {
|
||||||
|
const defaultValue = 'defaultValue'
|
||||||
|
const result = utils.shallowRender(<TextInput defaultValue={defaultValue} />)
|
||||||
|
assert.equal(result.props.defaultValue, defaultValue)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('prop "editable"', () => {
|
||||||
|
// true
|
||||||
|
let dom = utils.renderToDOM(<TextInput />)
|
||||||
|
assert.equal(dom.getAttribute('readonly'), undefined)
|
||||||
|
// false
|
||||||
|
dom = utils.renderToDOM(<TextInput editable={false} />)
|
||||||
|
assert.equal(dom.getAttribute('readonly'), '')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('prop "keyboardType"', () => {
|
||||||
|
// default
|
||||||
|
let dom = utils.renderToDOM(<TextInput />)
|
||||||
|
assert.equal(dom.getAttribute('type'), undefined)
|
||||||
|
dom = utils.renderToDOM(<TextInput keyboardType='default' />)
|
||||||
|
assert.equal(dom.getAttribute('type'), undefined)
|
||||||
|
// email-address
|
||||||
|
dom = utils.renderToDOM(<TextInput keyboardType='email-address' />)
|
||||||
|
assert.equal(dom.getAttribute('type'), 'email')
|
||||||
|
// numeric
|
||||||
|
dom = utils.renderToDOM(<TextInput keyboardType='numeric' />)
|
||||||
|
assert.equal(dom.getAttribute('type'), 'number')
|
||||||
|
// phone-pad
|
||||||
|
dom = utils.renderToDOM(<TextInput keyboardType='phone-pad' />)
|
||||||
|
assert.equal(dom.getAttribute('type'), 'tel')
|
||||||
|
// url
|
||||||
|
dom = utils.renderToDOM(<TextInput keyboardType='url' />)
|
||||||
|
assert.equal(dom.getAttribute('type'), 'url')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('prop "maxLength"', () => {
|
||||||
|
let dom = utils.renderToDOM(<TextInput />)
|
||||||
|
assert.equal(dom.getAttribute('maxlength'), undefined)
|
||||||
|
dom = utils.renderToDOM(<TextInput maxLength={10} />)
|
||||||
|
assert.equal(dom.getAttribute('maxlength'), '10')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('prop "maxNumberOfLines"', () => {
|
||||||
|
const style = { borderWidth: 0, fontSize: 20, lineHeight: 1 }
|
||||||
|
const value = (() => {
|
||||||
|
let str = ''
|
||||||
|
while (str.length < 100) str += 'x'
|
||||||
|
return str
|
||||||
|
}())
|
||||||
|
let dom = utils.renderAndInject(
|
||||||
|
<TextInput
|
||||||
|
maxNumberOfLines={3}
|
||||||
|
multiline
|
||||||
|
style={style}
|
||||||
|
value={value}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
const height = dom.getBoundingClientRect().height
|
||||||
|
// need a range because of cross-browser differences
|
||||||
|
assert.ok(height >= 60)
|
||||||
|
assert.ok(height <= 65)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('prop "multiline"', () => {
|
||||||
|
// false
|
||||||
|
let dom = utils.renderToDOM(<TextInput />)
|
||||||
|
assert.equal(dom.tagName, 'INPUT')
|
||||||
|
// true
|
||||||
|
dom = utils.renderToDOM(<TextInput multiline />)
|
||||||
|
assert.equal(dom.tagName, 'TEXTAREA')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('prop "numberOfLines"', () => {
|
||||||
|
const style = { borderWidth: 0, fontSize: 20, lineHeight: 1 }
|
||||||
|
// missing multiline
|
||||||
|
let dom = utils.renderToDOM(<TextInput numberOfLines={2} />)
|
||||||
|
assert.equal(dom.tagName, 'INPUT')
|
||||||
|
// with multiline
|
||||||
|
dom = utils.renderAndInject(<TextInput multiline numberOfLines={2} style={style} />)
|
||||||
|
assert.equal(dom.tagName, 'TEXTAREA')
|
||||||
|
const height = dom.getBoundingClientRect().height
|
||||||
|
// need a range because of cross-browser differences
|
||||||
|
assert.ok(height >= 40)
|
||||||
|
assert.ok(height <= 45)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('prop "onBlur"', (done) => {
|
||||||
|
const input = utils.renderToDOM(<TextInput onBlur={onBlur} />)
|
||||||
|
ReactTestUtils.Simulate.blur(input)
|
||||||
|
function onBlur(e) {
|
||||||
|
assert.ok(e)
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test('prop "onChange"', (done) => {
|
||||||
|
const input = utils.renderToDOM(<TextInput onChange={onChange} />)
|
||||||
|
ReactTestUtils.Simulate.change(input)
|
||||||
|
function onChange(e) {
|
||||||
|
assert.ok(e)
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test('prop "onChangeText"', (done) => {
|
||||||
|
const newText = 'newText'
|
||||||
|
const input = utils.renderToDOM(<TextInput onChangeText={onChangeText} />)
|
||||||
|
ReactTestUtils.Simulate.change(input, { target: { value: newText } })
|
||||||
|
function onChangeText(text) {
|
||||||
|
assert.equal(text, newText)
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test('prop "onFocus"', (done) => {
|
||||||
|
const input = utils.renderToDOM(<TextInput onFocus={onFocus} />)
|
||||||
|
ReactTestUtils.Simulate.focus(input)
|
||||||
|
function onFocus(e) {
|
||||||
|
assert.ok(e)
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test.skip('prop "onLayout"', () => {})
|
||||||
|
|
||||||
|
test('prop "onSelectionChange"', (done) => {
|
||||||
|
const input = utils.renderAndInject(<TextInput defaultValue='12345' onSelectionChange={onSelectionChange} />)
|
||||||
|
ReactTestUtils.Simulate.select(input, { target: { selectionStart: 0, selectionEnd: 3 } })
|
||||||
|
function onSelectionChange(e) {
|
||||||
|
assert.equal(e.selectionEnd, 3)
|
||||||
|
assert.equal(e.selectionStart, 0)
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test.skip('prop "placeholder"', () => {})
|
||||||
|
|
||||||
|
test.skip('prop "placeholderTextColor"', () => {})
|
||||||
|
|
||||||
|
test('prop "secureTextEntry"', () => {
|
||||||
|
let dom = utils.renderToDOM(<TextInput secureTextEntry />)
|
||||||
|
assert.equal(dom.getAttribute('type'), 'password')
|
||||||
|
// ignored for multiline
|
||||||
|
dom = utils.renderToDOM(<TextInput multiline secureTextEntry />)
|
||||||
|
assert.equal(dom.getAttribute('type'), undefined)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('prop "selectTextOnFocus"', () => {
|
||||||
|
const text = 'Text'
|
||||||
|
utils.requiresFocus(() => {
|
||||||
|
// false
|
||||||
|
let dom = utils.renderAndInject(<TextInput defaultValue={text} />)
|
||||||
|
dom.focus()
|
||||||
|
assert.equal(dom.selectionEnd, 0)
|
||||||
|
assert.equal(dom.selectionStart, 0)
|
||||||
|
// true
|
||||||
|
dom = utils.renderAndInject(<TextInput defaultValue={text} selectTextOnFocus />)
|
||||||
|
dom.focus()
|
||||||
|
assert.equal(dom.selectionEnd, 4)
|
||||||
|
assert.equal(dom.selectionStart, 0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('prop "style"', () => {
|
||||||
|
utils.assertProps.style(TextInput)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('prop "testID"', () => {
|
||||||
|
utils.assertProps.testID(TextInput)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('prop "value"', () => {
|
||||||
|
const value = 'value'
|
||||||
|
const result = utils.shallowRender(<TextInput value={value} />)
|
||||||
|
assert.equal(result.props.value, value)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
*/
|
|
||||||
|
|||||||
+10
-3
@@ -132,13 +132,20 @@ class Example extends Component {
|
|||||||
onChange={(e) => { console.log('TextInput.onChange', e) }}
|
onChange={(e) => { console.log('TextInput.onChange', e) }}
|
||||||
onChangeText={(e) => { console.log('TextInput.onChangeText', e) }}
|
onChangeText={(e) => { console.log('TextInput.onChangeText', e) }}
|
||||||
onFocus={(e) => { console.log('TextInput.onFocus', e) }}
|
onFocus={(e) => { console.log('TextInput.onFocus', e) }}
|
||||||
|
onSelectionChange={(e) => { console.log('TextInput.onSelectionChange', e) }}
|
||||||
/>
|
/>
|
||||||
<TextInput secureTextEntry />
|
<TextInput secureTextEntry />
|
||||||
|
<TextInput defaultValue='read only' editable={false} />
|
||||||
|
<TextInput keyboardType='email-address' />
|
||||||
<TextInput keyboardType='numeric' />
|
<TextInput keyboardType='numeric' />
|
||||||
<TextInput keyboardType='tel' />
|
<TextInput keyboardType='phone-pad' />
|
||||||
<TextInput keyboardType='url' />
|
<TextInput keyboardType='url' />
|
||||||
<TextInput keyboardType='search' />
|
<TextInput
|
||||||
<TextInput defaultValue='default value' multiline />
|
defaultValue='default value'
|
||||||
|
maxNumberOfLines={10}
|
||||||
|
multiline
|
||||||
|
numberOfLines={5}
|
||||||
|
/>
|
||||||
|
|
||||||
<Heading level='2' size='large'>Touchable</Heading>
|
<Heading level='2' size='large'>Touchable</Heading>
|
||||||
<Touchable
|
<Touchable
|
||||||
|
|||||||
@@ -59,8 +59,8 @@ export const assertProps = {
|
|||||||
const styleToMerge = { margin: '10' }
|
const styleToMerge = { margin: '10' }
|
||||||
shallow = shallowRender(<Component {...props} style={styleToMerge} />)
|
shallow = shallowRender(<Component {...props} style={styleToMerge} />)
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
shallow.props.style.margin,
|
shallow.props.style,
|
||||||
styleToMerge.margin,
|
{ ...Component.defaultProps.style, ...styleToMerge }
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -86,6 +86,33 @@ export function renderToDOM(element, container) {
|
|||||||
return React.findDOMNode(result)
|
return React.findDOMNode(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 React.findDOMNode(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function requiresFocus(test, fallback) {
|
||||||
|
if (document.hasFocus && document.hasFocus()) {
|
||||||
|
test()
|
||||||
|
} else {
|
||||||
|
console.warn('Test was skipped as the document is not focused')
|
||||||
|
if (fallback) {
|
||||||
|
fallback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function shallowRender(component, context = {}) {
|
export function shallowRender(component, context = {}) {
|
||||||
const shallowRenderer = React.addons.TestUtils.createRenderer()
|
const shallowRenderer = React.addons.TestUtils.createRenderer()
|
||||||
shallowRenderer.render(component, context)
|
shallowRenderer.render(component, context)
|
||||||
|
|||||||
Reference in New Issue
Block a user