Skip to content
Permalink
Tree: 734be8db5a
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
2973 lines (2698 sloc) 84.9 KB
/* eslint react/no-multi-comp:0 */
import React, { Component } from 'react'
import { Provider } from 'react-redux'
import { combineReducers as plainCombineReducers, createStore } from 'redux'
import { combineReducers as immutableCombineReducers } from 'redux-immutablejs'
import TestUtils from 'react-dom/test-utils'
import createReduxForm from '../createReduxForm'
import createReducer from '../createReducer'
import createField from '../createField'
import FormSection from '../FormSection'
import plain from '../structure/plain'
import plainExpectations from '../structure/plain/__tests__/expectations'
import immutable from '../structure/immutable'
import immutableExpectations from '../structure/immutable/__tests__/expectations'
import { dragStartMock, dropMock } from '../util/eventMocks'
import { dataKey } from '../util/eventConsts'
const testFormName = 'testForm'
const describeField = (name, structure, combineReducers, setup) => {
const reduxForm = createReduxForm(structure)
const Field = createField(structure)
const reducer = createReducer(structure)
const { fromJS, getIn } = structure
const makeStore = initial =>
createStore(combineReducers({ form: reducer }), fromJS({ form: initial }))
class TestInput extends Component {
render() {
return <div>TEST INPUT</div>
}
}
const testProps = (state, config = {}) => {
const store = makeStore({ [testFormName]: state })
class Form extends Component {
render() {
return (
<div>
<Field name="foo" component={TestInput} />
</div>
)
}
}
const TestForm = reduxForm({ form: testFormName, ...config })(Form)
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
return TestUtils.findRenderedComponentWithType(dom, TestInput).props
}
describe(name, () => {
beforeAll(() => {
setup()
})
it('should throw an error if not in ReduxForm', () => {
expect(() => {
TestUtils.renderIntoDocument(
<div>
<Field name="foo" component={TestInput} />
</div>
)
}).toThrow(/must be inside a component decorated with reduxForm/)
})
it('should throw an error if invalid component prop is provided', () => {
const store = makeStore()
const notAComponent = {}
class Form extends Component {
render() {
return <Field component={notAComponent} />
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
expect(() => {
TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
}).toThrow(/Element type is invalid/)
})
it('should get value from Redux state', () => {
const props = testProps({
values: {
foo: 'bar'
}
})
expect(props.input.value).toBe('bar')
})
it('should get initial value from Redux state', () => {
const props = testProps({
initial: {
foo: 'bar'
},
values: {
foo: 'baz'
}
})
expect(props.meta.initial).toBe('bar')
})
it('should get dirty/pristine from Redux state', () => {
const props1 = testProps({
initial: {
foo: 'bar'
},
values: {
foo: 'bar'
}
})
expect(props1.meta.pristine).toBe(true)
expect(props1.meta.dirty).toBe(false)
const props2 = testProps({
initial: {
foo: 'bar'
},
values: {
foo: 'baz'
}
})
expect(props2.meta.pristine).toBe(false)
expect(props2.meta.dirty).toBe(true)
const props3 = testProps({
initial: {
foo: [4, 'abc', { def: null, key: [-45, '...', [0, 99]] }]
},
values: {
foo: [4, 'abc', { def: null, key: [-45, '...', [0, 99]] }]
}
})
expect(props3.meta.pristine).toBe(true)
expect(props3.meta.dirty).toBe(false)
})
it('should allow an empty value from Redux state to be pristine', () => {
const props1 = testProps({
initial: {
foo: 'bar'
},
values: {
foo: ''
}
})
expect(props1.meta.pristine).toBe(false)
expect(props1.meta.dirty).toBe(true)
const props2 = testProps({
initial: {
foo: ''
},
values: {
foo: ''
}
})
expect(props2.meta.pristine).toBe(true)
expect(props2.meta.dirty).toBe(false)
})
it('should get asyncValidating from Redux state', () => {
const props1 = testProps({
initial: {
foo: 'bar'
},
values: {
foo: 'bar'
},
asyncValidating: 'dog'
})
expect(props1.meta.asyncValidating).toBe(false)
const props2 = testProps({
initial: {
foo: 'bar'
},
values: {
foo: 'baz'
},
asyncValidating: 'foo'
})
expect(props2.meta.asyncValidating).toBe(true)
})
it('should get active from Redux state', () => {
const props1 = testProps({
values: {
foo: 'bar'
}
})
expect(props1.meta.active).toBe(false)
const props2 = testProps({
values: {
foo: 'bar'
},
fields: {
foo: {
active: true
}
}
})
expect(props2.meta.active).toBe(true)
})
it('should get autofilled from Redux state', () => {
const props1 = testProps({
values: {
foo: 'bar'
}
})
expect(props1.meta.autofilled).toBe(false)
const props2 = testProps({
values: {
foo: 'bar'
},
fields: {
foo: {
autofilled: true
}
}
})
expect(props2.meta.autofilled).toBe(true)
})
it('should get touched from Redux state', () => {
const props1 = testProps({
values: {
foo: 'bar'
}
})
expect(props1.meta.touched).toBe(false)
const props2 = testProps({
values: {
foo: 'bar'
},
fields: {
foo: {
touched: true
}
}
})
expect(props2.meta.touched).toBe(true)
})
it('should get visited from Redux state', () => {
const props1 = testProps({
values: {
foo: 'bar'
}
})
expect(props1.meta.visited).toBe(false)
const props2 = testProps({
values: {
foo: 'bar'
},
fields: {
foo: {
visited: true
}
}
})
expect(props2.meta.visited).toBe(true)
})
it('should pass in the form name as meta.form', () => {
const props = testProps()
expect(props.meta.form).toBe(testFormName)
})
it('should get sync errors from outer reduxForm component', () => {
const props = testProps(
{
initial: {
foo: 'bar'
},
values: {
foo: 'bar'
},
registeredFields: {
foo: { name: 'foo', type: 'Field' }
}
},
{
validate: () => ({ foo: 'foo error' })
}
)
expect(props.meta.error).toBe('foo error')
})
it('should get sync warnings from outer reduxForm component', () => {
const props = testProps(
{
initial: {
foo: 'bar'
},
values: {
foo: 'bar'
},
registeredFields: {
foo: { name: 'foo', type: 'Field' }
}
},
{
warn: () => ({ foo: 'foo warning' })
}
)
expect(props.meta.warning).toBe('foo warning')
})
it('should get async errors from Redux state', () => {
const props = testProps({
initial: {
foo: 'bar'
},
values: {
foo: 'bar'
},
asyncErrors: {
foo: 'foo error'
}
})
expect(props.meta.error).toBe('foo error')
})
it('should get submit errors from Redux state', () => {
const props = testProps({
initial: {
foo: 'bar'
},
values: {
foo: 'bar'
},
submitErrors: {
foo: 'foo error'
}
})
expect(props.meta.error).toBe('foo error')
})
it('should get submitFailed prop from Redux state', () => {
const props = testProps({
submitFailed: true
})
expect(props.meta.submitFailed).toBe(true)
})
it('should provide meta.dispatch', () => {
const props = testProps({})
expect(typeof props.meta.dispatch).toBe('function')
})
it('should provide name getter', () => {
const store = makeStore({
testForm: {
values: {
foo: 'bar'
}
}
})
class Form extends Component {
render() {
return (
<div>
<Field name="foo" component={TestInput} />
</div>
)
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
const stub = TestUtils.findRenderedComponentWithType(dom, Field)
expect(stub.name).toBe('foo')
})
it('should provide value getter', () => {
const store = makeStore({
testForm: {
values: {
foo: 'bar'
}
}
})
class Form extends Component {
render() {
return (
<div>
<Field name="foo" component={TestInput} />
</div>
)
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
const stub = TestUtils.findRenderedComponentWithType(dom, Field)
expect(stub.value).toBe('bar')
})
it('should provide dirty getter that is true when dirty', () => {
const store = makeStore({
testForm: {
values: {
foo: 'bar'
}
}
})
class Form extends Component {
render() {
return (
<div>
<Field name="foo" component={TestInput} />
</div>
)
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
const stub = TestUtils.findRenderedComponentWithType(dom, Field)
expect(stub.dirty).toBe(true)
})
it('should provide dirty getter that is false when pristine', () => {
const store = makeStore({
testForm: {
initial: {
foo: 'bar'
},
values: {
foo: 'bar'
}
}
})
class Form extends Component {
render() {
return (
<div>
<Field name="foo" component={TestInput} />
</div>
)
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
const stub = TestUtils.findRenderedComponentWithType(dom, Field)
expect(stub.dirty).toBe(false)
})
it('should provide pristine getter that is false when dirty', () => {
const store = makeStore({
testForm: {
values: {
foo: 'bar'
}
}
})
class Form extends Component {
render() {
return (
<div>
<Field name="foo" component={TestInput} />
</div>
)
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
const stub = TestUtils.findRenderedComponentWithType(dom, Field)
expect(stub.pristine).toBe(false)
})
it('should provide pristine getter that is true when pristine', () => {
const store = makeStore({
testForm: {
initial: {
foo: 'bar'
},
values: {
foo: 'bar'
}
}
})
class Form extends Component {
render() {
return (
<div>
<Field name="foo" component={TestInput} />
</div>
)
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
const stub = TestUtils.findRenderedComponentWithType(dom, Field)
expect(stub.pristine).toBe(true)
})
it('should have value set to initial value on first render', () => {
const store = makeStore({})
const input = jest.fn(props => <input {...props.input} />)
class Form extends Component {
render() {
return (
<div>
<Field name="foo" component={input} />
</div>
)
}
}
const TestForm = reduxForm({
form: 'testForm'
})(Form)
TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm initialValues={{ foo: 'bar' }} />
</Provider>
)
expect(input).toHaveBeenCalled()
expect(input.mock.calls[0][0].input.value).toBe('bar')
})
it('should provide sync error for array field', () => {
const store = makeStore({
testForm: {
values: {
foo: ['bar']
}
}
})
const input = jest.fn(props => <input {...props.input} />)
const validate = () => ({ foo: ['bar error'] })
class Form extends Component {
render() {
return (
<div>
<Field name="foo[0]" component={input} />
</div>
)
}
}
const TestForm = reduxForm({
form: 'testForm',
validate
})(Form)
TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
expect(input).toHaveBeenCalled()
expect(input).toHaveBeenCalledTimes(1)
expect(input.mock.calls[0][0].meta.valid).toBe(false)
expect(input.mock.calls[0][0].meta.error).toBe('bar error')
})
it('should provide sync warning for array field', () => {
const store = makeStore({
testForm: {
values: {
foo: ['bar']
}
}
})
const input = jest.fn(props => <input {...props.input} />)
const warn = () => ({ foo: ['bar warning'] })
class Form extends Component {
render() {
return (
<div>
<Field name="foo[0]" component={input} />
</div>
)
}
}
const TestForm = reduxForm({
form: 'testForm',
warn
})(Form)
TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
expect(input).toHaveBeenCalled()
expect(input).toHaveBeenCalledTimes(1)
expect(input.mock.calls[0][0].meta.warning).toBe('bar warning')
})
it('should provide access to rendered component', () => {
const store = makeStore({
testForm: {
values: {
foo: 'bar'
}
}
})
class Form extends Component {
render() {
return (
<div>
<Field name="foo" component={TestInput} withRef />
</div>
)
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
const field = TestUtils.findRenderedComponentWithType(dom, Field)
const input = TestUtils.findRenderedComponentWithType(dom, TestInput)
expect(field.getRenderedComponent()).toBe(input)
})
it('should reconnect when name changes', () => {
const store = makeStore({
testForm: {
values: {
foo: 'fooValue',
bar: 'barValue'
},
fields: {
bar: {
touched: true
}
}
}
})
const input = jest.fn(props => <input {...props.input} />)
class Form extends Component {
constructor() {
super()
this.state = { field: 'foo' }
}
render() {
return (
<div>
<Field name={this.state.field} component={input} />
<button onClick={() => this.setState({ field: 'bar' })}>
Change
</button>
</div>
)
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
expect(input).toHaveBeenCalled()
expect(input).toHaveBeenCalledTimes(1)
expect(input.mock.calls[0][0].input.value).toBe('fooValue')
expect(input.mock.calls[0][0].meta.touched).toBe(false)
const button = TestUtils.findRenderedDOMComponentWithTag(dom, 'button')
TestUtils.Simulate.click(button)
expect(input).toHaveBeenCalledTimes(2)
expect(input.mock.calls[1][0].input.value).toBe('barValue')
expect(input.mock.calls[1][0].meta.touched).toBe(true)
})
it('should prefix name getter when inside FormSection', () => {
const store = makeStore()
class Form extends Component {
render() {
return (
<FormSection name="foo" component="span">
<Field name="bar" component="input" />
</FormSection>
)
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
const stub = TestUtils.findRenderedComponentWithType(dom, Field)
expect(stub.name).toBe('foo.bar')
})
it('should prefix name getter when inside multiple FormSection', () => {
const store = makeStore()
class Form extends Component {
render() {
return (
<FormSection name="foo">
<FormSection name="fighter">
<Field name="bar" component="input" />
</FormSection>
</FormSection>
)
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
const stub = TestUtils.findRenderedComponentWithType(dom, Field)
expect(stub.name).toBe('foo.fighter.bar')
})
it('should prefix name when inside FormSection', () => {
const store = makeStore()
class Form extends Component {
render() {
return (
<FormSection name="foo" component="span">
<Field name="bar" component="input" />
</FormSection>
)
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
expect(store.getState()).toEqualMap({
form: {
testForm: {
registeredFields: {
'foo.bar': { name: 'foo.bar', type: 'Field', count: 1 }
}
}
}
})
})
it('should prefix name when inside multiple FormSections', () => {
const store = makeStore()
class Form extends Component {
render() {
return (
<FormSection name="foo">
<FormSection name="fighter">
<Field name="bar" component="input" />
</FormSection>
</FormSection>
)
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
expect(store.getState()).toEqualMap({
form: {
testForm: {
registeredFields: {
'foo.fighter.bar': {
name: 'foo.fighter.bar',
type: 'Field',
count: 1
}
}
}
}
})
})
it('should re-register when name changes', () => {
const store = makeStore()
class Form extends Component {
constructor() {
super()
this.state = { field: 'foo' }
}
render() {
return (
<div>
<Field name={this.state.field} component="input" />
<button onClick={() => this.setState({ field: 'bar' })}>
Change
</button>
</div>
)
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
expect(store.getState()).toEqualMap({
form: {
testForm: {
registeredFields: { foo: { name: 'foo', type: 'Field', count: 1 } }
}
}
})
const button = TestUtils.findRenderedDOMComponentWithTag(dom, 'button')
TestUtils.Simulate.click(button)
expect(store.getState()).toEqualMap({
form: {
testForm: {
registeredFields: { bar: { name: 'bar', type: 'Field', count: 1 } }
}
}
})
})
it('should rerender when props change', () => {
const store = makeStore()
const input = jest.fn(props => (
<div>
{props.highlighted}
<input {...props.input} />
</div>
))
class Form extends Component {
constructor() {
super()
this.state = { highlighted: 0 }
}
render() {
const { highlighted } = this.state
return (
<div>
<Field name="foo" highlighted={highlighted} component={input} />
<button
onClick={() => this.setState({ highlighted: highlighted + 1 })}
>
Change
</button>
</div>
)
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
expect(input).toHaveBeenCalled()
expect(input).toHaveBeenCalledTimes(1)
expect(input.mock.calls[0][0].highlighted).toBe(0)
const button = TestUtils.findRenderedDOMComponentWithTag(dom, 'button')
TestUtils.Simulate.click(button)
expect(input).toHaveBeenCalledTimes(2)
expect(input.mock.calls[1][0].highlighted).toBe(1)
})
it('should NOT rerender when props.props is shallow-equal, but !==', () => {
const store = makeStore()
const input = jest.fn(props => <input {...props.input} />)
const renderSpy = jest.fn()
class Form extends Component {
constructor() {
super()
this.state = { foo: 'bar' }
}
render() {
renderSpy()
return (
<div>
<Field name="myField" component={input} props={{ rel: 'test' }} />
<button onClick={() => this.setState({ foo: 'qux' })}>
Change
</button>
</div>
)
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
expect(renderSpy).toHaveBeenCalled()
expect(renderSpy).toHaveBeenCalledTimes(1)
expect(input).toHaveBeenCalled()
expect(input).toHaveBeenCalledTimes(1)
expect(input.mock.calls[0][0].rel).toBe('test')
const button = TestUtils.findRenderedDOMComponentWithTag(dom, 'button')
TestUtils.Simulate.click(button)
expect(renderSpy).toHaveBeenCalledTimes(2)
expect(input).toHaveBeenCalledTimes(1)
})
it('The render() function should not be called if neither state nor props of the field has changed', () => {
const store = makeStore()
const input = jest.fn(props => <input {...props.input} />)
const renderSpy = jest.fn()
class TestField extends Field {
render() {
renderSpy()
return super.render()
}
}
class Form extends Component {
constructor() {
super()
this.state = { foo: 'bar' }
}
render() {
return (
<div>
<TestField name="myField" component={input} />
<button onClick={() => this.setState({ foo: 'qux' })}>
Change
</button>
</div>
)
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
expect(renderSpy).toHaveBeenCalledTimes(1)
expect(input).toHaveBeenCalledTimes(1)
const button = TestUtils.findRenderedDOMComponentWithTag(dom, 'button')
TestUtils.Simulate.click(button)
expect(renderSpy).toHaveBeenCalledTimes(1)
expect(input).toHaveBeenCalledTimes(1)
})
it('should call normalize function on change', () => {
const store = makeStore({
testForm: {
values: {
title: 'Redux Form',
author: 'Erik Rasmussen',
username: 'oldusername'
}
}
})
const renderUsername = jest.fn(props => <input {...props.input} />)
const normalize = jest.fn(value => value.toLowerCase())
class Form extends Component {
render() {
return (
<div>
<Field name="title" component="input" />
<Field name="author" component="input" />
<Field
name="username"
component={renderUsername}
normalize={normalize}
/>
</div>
)
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
expect(normalize).not.toHaveBeenCalled()
expect(renderUsername.mock.calls[0][0].input.value).toBe('oldusername')
renderUsername.mock.calls[0][0].input.onChange('ERIKRAS')
expect(normalize).toHaveBeenCalledWith(
'ERIKRAS',
'oldusername',
fromJS({
title: 'Redux Form',
author: 'Erik Rasmussen',
username: 'ERIKRAS'
}),
fromJS({
title: 'Redux Form',
author: 'Erik Rasmussen',
username: 'oldusername'
})
)
expect(normalize).toHaveBeenCalledTimes(1)
expect(renderUsername.mock.calls[1][0].input.value).toBe('erikras')
})
it('should call normalize function on blur', () => {
const store = makeStore({
testForm: {
values: {
title: 'Redux Form',
author: 'Erik Rasmussen',
username: 'oldusername'
}
}
})
const renderUsername = jest.fn(props => <input {...props.input} />)
const normalize = jest.fn(value => value.toLowerCase())
class Form extends Component {
render() {
return (
<div>
<Field name="title" component="input" />
<Field name="author" component="input" />
<Field
name="username"
component={renderUsername}
normalize={normalize}
/>
</div>
)
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
expect(normalize).not.toHaveBeenCalled()
expect(renderUsername.mock.calls[0][0].input.value).toBe('oldusername')
renderUsername.mock.calls[0][0].input.onBlur('ERIKRAS')
expect(normalize).toHaveBeenCalledWith(
'ERIKRAS',
'oldusername',
fromJS({
title: 'Redux Form',
author: 'Erik Rasmussen',
username: 'ERIKRAS'
}),
fromJS({
title: 'Redux Form',
author: 'Erik Rasmussen',
username: 'oldusername'
})
)
expect(normalize).toHaveBeenCalledTimes(1)
expect(renderUsername.mock.calls[1][0].input.value).toBe('erikras')
})
it('should call asyncValidate function on blur', () => {
const store = makeStore({
testForm: {
values: {
title: 'Redux Form',
author: 'Erik Rasmussen',
username: 'oldusername'
}
}
})
const renderUsername = jest.fn(props => <input {...props.input} />)
class Form extends Component {
render() {
return (
<div>
<Field name="title" component="input" />
<Field name="author" component="input" />
<Field name="username" component={renderUsername} />
</div>
)
}
}
const asyncValidate = jest.fn(() => new Promise(resolve => resolve()))
const TestForm = reduxForm({ form: 'testForm', asyncValidate })(Form)
TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
renderUsername.mock.calls[0][0].input.onBlur('ERIKRAS')
expect(asyncValidate).toHaveBeenCalled()
})
it('should call asyncValidate function on change', () => {
const store = makeStore({
testForm: {
values: {
title: 'Redux Form',
author: 'Erik Rasmussen',
username: 'oldusername'
}
}
})
const renderUsername = jest.fn(props => <input {...props.input} />)
class Form extends Component {
render() {
return (
<div>
<Field name="title" component="input" />
<Field name="author" component="input" />
<Field name="username" component={renderUsername} />
</div>
)
}
}
const asyncValidate = jest.fn(() => new Promise(resolve => resolve()))
const TestForm = reduxForm({ form: 'testForm', asyncValidate })(Form)
TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
renderUsername.mock.calls[0][0].input.onChange('ERIKRAS')
expect(asyncValidate).toHaveBeenCalled()
})
it('should call asyncValidate function on blur if field is specified', () => {
const store = makeStore({
testForm: {
values: {
title: 'Redux Form',
author: 'Erik Rasmussen',
username: 'oldusername'
}
}
})
const renderUsername = jest.fn(props => <input {...props.input} />)
class Form extends Component {
render() {
return (
<div>
<Field name="title" component="input" />
<Field name="author" component="input" />
<Field name="username" component={renderUsername} />
</div>
)
}
}
const asyncValidate = jest.fn(() => new Promise(resolve => resolve()))
const TestForm = reduxForm({
form: 'testForm',
asyncValidate,
asyncBlurFields: ['username']
})(Form)
TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
renderUsername.mock.calls[0][0].input.onBlur('ERIKRAS')
expect(asyncValidate).toHaveBeenCalled()
})
it('should call asyncValidate function on change if field is specified', () => {
const store = makeStore({
testForm: {
values: {
title: 'Redux Form',
author: 'Erik Rasmussen',
username: 'oldusername'
}
}
})
const renderUsername = jest.fn(props => <input {...props.input} />)
class Form extends Component {
render() {
return (
<div>
<Field name="title" component="input" />
<Field name="author" component="input" />
<Field name="username" component={renderUsername} />
</div>
)
}
}
const asyncValidate = jest.fn(() => new Promise(resolve => resolve()))
const TestForm = reduxForm({
form: 'testForm',
asyncValidate,
asyncChangeFields: ['username']
})(Form)
TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
renderUsername.mock.calls[0][0].input.onChange('ERIKRAS')
expect(asyncValidate).toHaveBeenCalled()
})
it('should not call asyncValidate function on blur if field is specified and different', () => {
const store = makeStore({
testForm: {
values: {
title: 'Redux Form',
author: 'Erik Rasmussen',
username: 'oldusername'
}
}
})
const renderUsername = jest.fn(props => <input {...props.input} />)
class Form extends Component {
render() {
return (
<div>
<Field name="title" component="input" />
<Field name="author" component="input" />
<Field name="username" component={renderUsername} />
</div>
)
}
}
const asyncValidate = jest.fn(() => new Promise(resolve => resolve()))
const TestForm = reduxForm({
form: 'testForm',
asyncValidate,
asyncBlurFields: ['author']
})(Form)
TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
renderUsername.mock.calls[0][0].input.onBlur('ERIKRAS')
expect(asyncValidate).not.toHaveBeenCalled()
})
it('should not call asyncValidate function on change if field is specified and different', () => {
const store = makeStore({
testForm: {
values: {
title: 'Redux Form',
author: 'Erik Rasmussen',
username: 'oldusername'
}
}
})
const renderUsername = jest.fn(props => <input {...props.input} />)
class Form extends Component {
render() {
return (
<div>
<Field name="title" component="input" />
<Field name="author" component="input" />
<Field name="username" component={renderUsername} />
</div>
)
}
}
const asyncValidate = jest.fn(() => new Promise(resolve => resolve()))
const TestForm = reduxForm({
form: 'testForm',
asyncValidate,
asyncChangeFields: ['author']
})(Form)
TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
renderUsername.mock.calls[0][0].input.onChange('ERIKRAS')
expect(asyncValidate).not.toHaveBeenCalled()
})
it('should not call asyncValidate function on change if field is specified as onBlur', () => {
const store = makeStore({
testForm: {
values: {
title: 'Redux Form',
author: 'Erik Rasmussen',
username: 'oldusername'
}
}
})
const renderUsername = jest.fn(props => <input {...props.input} />)
class Form extends Component {
render() {
return (
<div>
<Field name="title" component="input" />
<Field name="author" component="input" />
<Field name="username" component={renderUsername} />
</div>
)
}
}
const asyncValidate = jest.fn(() => new Promise(resolve => resolve()))
const TestForm = reduxForm({
form: 'testForm',
asyncValidate,
asyncBlurFields: ['username']
})(Form)
TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
renderUsername.mock.calls[0][0].input.onChange('ERIKRAS')
expect(asyncValidate).not.toHaveBeenCalled()
})
it('should not call asyncValidate function on blur if field is specified as onChange', () => {
const store = makeStore({
testForm: {
values: {
title: 'Redux Form',
author: 'Erik Rasmussen',
username: 'oldusername'
}
}
})
const renderUsername = jest.fn(props => <input {...props.input} />)
class Form extends Component {
render() {
return (
<div>
<Field name="title" component="input" />
<Field name="author" component="input" />
<Field name="username" component={renderUsername} />
</div>
)
}
}
const asyncValidate = jest.fn(() => new Promise(resolve => resolve()))
const TestForm = reduxForm({
form: 'testForm',
asyncValidate,
asyncChangeFields: ['username']
})(Form)
TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
renderUsername.mock.calls[0][0].input.onBlur('ERIKRAS')
expect(asyncValidate).not.toHaveBeenCalled()
})
it('should call handle on focus', () => {
const store = makeStore({
testForm: {
values: {
title: 'Redux Form'
}
}
})
const renderTitle = jest.fn(props => <input {...props.input} />)
class Form extends Component {
render() {
return <Field name="title" component={renderTitle} />
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
expect(renderTitle.mock.calls[0][0].meta.visited).toBe(false)
renderTitle.mock.calls[0][0].input.onFocus()
expect(renderTitle.mock.calls[1][0].meta.visited).toBe(true)
})
it('should not change the value of a radio when blur', () => {
const store = makeStore({
testForm: {
values: {
title: 'Redux Form',
author: 'Erik Rasmussen',
sex: 'male'
}
}
})
const renderSex = jest.fn(props => <input {...props.input} />)
class Form extends Component {
render() {
return (
<div>
<Field name="title" component="input" />
<Field name="author" component="input" />
<Field
name="sex"
value="female"
type="radio"
component={renderSex}
/>
<Field
name="sex"
value="male"
type="radio"
component={renderSex}
/>
</div>
)
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
expect(renderSex.mock.calls[0][0].input.checked).toBe(false)
expect(renderSex.mock.calls[1][0].input.checked).toBe(true)
renderSex.mock.calls[0][0].input.onBlur('female')
expect(renderSex.mock.calls[2][0].input.checked).toBe(false)
expect(renderSex.mock.calls[3][0].input.checked).toBe(true)
})
it('should call handle on drag start with value', () => {
const store = makeStore({
testForm: {
values: {
title: 'Redux Form'
}
}
})
const renderTitle = jest.fn(props => <input {...props.input} />)
const dragSpy = jest.fn((key, val) => val)
const event = dragStartMock(dragSpy)
class Form extends Component {
render() {
return <Field name="title" component={renderTitle} />
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
expect(dragSpy).not.toHaveBeenCalled()
renderTitle.mock.calls[0][0].input.onDragStart(event)
expect(dragSpy).toHaveBeenCalledWith(dataKey, 'Redux Form')
})
it('should call handle on drag start without value', () => {
const store = makeStore({
testForm: {
values: {
title: null
}
}
})
const renderTitle = jest.fn(props => <input {...props.input} />)
const dragSpy = jest.fn((key, val) => val)
const event = dragStartMock(dragSpy)
class Form extends Component {
render() {
return <Field name="title" component={renderTitle} />
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
expect(dragSpy).not.toHaveBeenCalled()
renderTitle.mock.calls[0][0].input.onDragStart(event)
expect(dragSpy).toHaveBeenCalledWith(dataKey, '')
})
it('should call handle on drop', () => {
const store = makeStore({
testForm: {
values: {
title: 'Redux Form'
}
}
})
const renderTitle = jest.fn(props => <input {...props.input} />)
const dropSpy = jest.fn(key => key)
const event = dropMock(dropSpy)
event.preventDefault = jest.fn(event.preventDefault)
class Form extends Component {
render() {
return <Field name="title" component={renderTitle} />
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
expect(dropSpy).not.toHaveBeenCalled()
renderTitle.mock.calls[0][0].input.onDrop(event)
expect(event.preventDefault).toHaveBeenCalled()
expect(dropSpy).toHaveBeenCalledWith(dataKey)
})
it('should call format function on first render', () => {
const store = makeStore({
testForm: {
values: {
name: 'Redux Form'
}
}
})
const input = jest.fn(props => <input {...props.input} />)
const format = jest.fn(value => value.toLowerCase())
class Form extends Component {
render() {
return (
<div>
<Field name="name" component={input} format={format} />
</div>
)
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
expect(format).toHaveBeenCalled()
expect(format).toHaveBeenCalledTimes(1)
expect(format.mock.calls[0]).toEqual(['Redux Form', 'name'])
expect(input.mock.calls[0][0].input.value).toBe('redux form')
})
it('should call parse function on change', () => {
const store = makeStore({
testForm: {
values: {
name: 'redux form'
}
}
})
const input = jest.fn(props => <input {...props.input} />)
const parse = jest.fn(value => value.toLowerCase())
class Form extends Component {
render() {
return (
<div>
<Field name="name" component={input} parse={parse} />
</div>
)
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
expect(parse).not.toHaveBeenCalled()
expect(input).toHaveBeenCalledTimes(1)
expect(input.mock.calls[0][0].input.value).toBe('redux form')
input.mock.calls[0][0].input.onChange('REDUX FORM ROCKS')
expect(parse).toHaveBeenCalled()
expect(parse).toHaveBeenCalledTimes(1)
expect(parse.mock.calls[0]).toEqual(['REDUX FORM ROCKS', 'name'])
expect(input).toHaveBeenCalledTimes(2)
expect(input.mock.calls[1][0].input.value).toBe('redux form rocks')
})
it('should call parse function on blur', () => {
const store = makeStore({
testForm: {
values: {
name: 'redux form'
}
}
})
const input = jest.fn(props => <input {...props.input} />)
const parse = jest.fn(value => value.toLowerCase())
class Form extends Component {
render() {
return (
<div>
<Field name="name" component={input} parse={parse} />
</div>
)
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
expect(parse).not.toHaveBeenCalled()
expect(input).toHaveBeenCalledTimes(1)
expect(input.mock.calls[0][0].input.value).toBe('redux form')
input.mock.calls[0][0].input.onBlur('REDUX FORM ROCKS')
expect(parse).toHaveBeenCalled()
expect(parse).toHaveBeenCalledTimes(1)
expect(parse.mock.calls[0]).toEqual(['REDUX FORM ROCKS', 'name'])
expect(input).toHaveBeenCalledTimes(2)
expect(input.mock.calls[1][0].input.value).toBe('redux form rocks')
})
it('should not update a value if onBlur is passed undefined', () => {
const store = makeStore({
testForm: {
values: {
name: 'redux form'
}
}
})
const input = jest.fn(props => <input {...props.input} />)
class Form extends Component {
render() {
return (
<div>
<Field name="name" component={input} />
</div>
)
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
// verify state
expect(store.getState()).toEqualMap({
form: {
testForm: {
values: {
name: 'redux form'
},
registeredFields: {
name: {
name: 'name',
type: 'Field',
count: 1
}
}
}
}
})
// verify props
expect(input).toHaveBeenCalled()
expect(input).toHaveBeenCalledTimes(1)
expect(input.mock.calls[0][0].meta.active).toBe(false)
expect(input.mock.calls[0][0].input.value).toBe('redux form')
// call onFocus
input.mock.calls[0][0].input.onFocus()
// verify state
expect(store.getState()).toEqualMap({
form: {
testForm: {
active: 'name',
values: {
name: 'redux form'
},
registeredFields: {
name: {
name: 'name',
type: 'Field',
count: 1
}
},
fields: {
name: {
visited: true,
active: true
}
}
}
}
})
// verify props
expect(input).toHaveBeenCalledTimes(2) // active now
expect(input.mock.calls[1][0].meta.active).toBe(true)
expect(input.mock.calls[1][0].input.value).toBe('redux form')
// call onBlur
input.mock.calls[0][0].input.onBlur()
// verify state
expect(store.getState()).toEqualMap({
form: {
testForm: {
anyTouched: true,
values: {
name: 'redux form' // UNCHANGED!
},
registeredFields: {
name: {
name: 'name',
type: 'Field',
count: 1
}
},
fields: {
name: {
visited: true,
touched: true
}
}
}
}
})
// verify props
expect(input).toHaveBeenCalledTimes(3) // not active now
expect(input.mock.calls[2][0].meta.active).toBe(false)
expect(input.mock.calls[2][0].input.value).toBe('redux form') // UNCHANGED!
})
it('should parse and format to maintain different type in store', () => {
const store = makeStore({
testForm: {
values: {
age: 42
}
}
})
const input = jest.fn(props => <input {...props.input} />)
const parse = jest.fn(value => value && parseInt(value, 10))
const format = jest.fn(value => value && value.toString())
class Form extends Component {
render() {
return (
<div>
<Field
name="age"
component={input}
format={format}
parse={parse}
/>
</div>
)
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
// format called once
expect(format).toHaveBeenCalled()
expect(format).toHaveBeenCalledTimes(1)
// parse not called yet
expect(parse).not.toHaveBeenCalled()
// input displaying string value
expect(input).toHaveBeenCalledTimes(1)
expect(input.mock.calls[0][0].input.value).toBe('42')
// update value
input.mock.calls[0][0].input.onChange('15')
// parse was called
expect(parse).toHaveBeenCalled()
expect(parse).toHaveBeenCalledTimes(1)
expect(parse.mock.calls[0]).toEqual(['15', 'age'])
// value in store is number
expect(store.getState()).toEqualMap({
form: {
testForm: {
values: {
age: 15 // number
},
registeredFields: { age: { name: 'age', type: 'Field', count: 1 } }
}
}
})
// format called again
expect(format).toHaveBeenCalled()
expect(format).toHaveBeenCalledTimes(2)
expect(format.mock.calls[1]).toEqual([15, 'age'])
// input rerendered with string value
expect(input).toHaveBeenCalledTimes(2)
expect(input.mock.calls[1][0].input.value).toBe('15')
})
it('should rerender when sync error changes', () => {
const store = makeStore({
testForm: {
values: {
password: 'redux-form sucks',
confirm: 'redux-form rocks'
}
}
})
const passwordInput = jest.fn(props => <input {...props.input} />)
const confirmInput = jest.fn(props => <input {...props.input} />)
const validate = values => {
const password = getIn(values, 'password')
const confirm = getIn(values, 'confirm')
return password === confirm ? {} : { confirm: 'Must match!' }
}
class Form extends Component {
render() {
return (
<div>
<Field name="password" component={passwordInput} />
<Field name="confirm" component={confirmInput} />
</div>
)
}
}
const TestForm = reduxForm({
form: 'testForm',
validate
})(Form)
TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
// password input rendered
expect(passwordInput).toHaveBeenCalled()
expect(passwordInput).toHaveBeenCalledTimes(1)
// confirm input rendered with error
expect(confirmInput).toHaveBeenCalled()
expect(confirmInput).toHaveBeenCalledTimes(1)
expect(confirmInput.mock.calls[0][0].meta.valid).toBe(false)
expect(confirmInput.mock.calls[0][0].meta.error).toBe('Must match!')
// update password field so that they match
passwordInput.mock.calls[0][0].input.onChange('redux-form rocks')
// password input rerendered
expect(passwordInput).toHaveBeenCalledTimes(2)
// confirm input should also rerender, but with no error
expect(confirmInput).toHaveBeenCalledTimes(2)
expect(confirmInput.mock.calls[1][0].meta.valid).toBe(true)
expect(confirmInput.mock.calls[1][0].meta.error).toBe(undefined)
})
it('should rerender when sync error is cleared', () => {
const store = makeStore()
const usernameInput = jest.fn(props => <input {...props.input} />)
const validate = values => {
const username = getIn(values, 'username')
return username ? {} : { username: 'Required' }
}
class Form extends Component {
render() {
return (
<div>
<Field name="username" component={usernameInput} />
</div>
)
}
}
const TestForm = reduxForm({
form: 'testForm',
validate
})(Form)
TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
// username input rendered
expect(usernameInput).toHaveBeenCalled()
expect(usernameInput).toHaveBeenCalledTimes(1)
// username field has error
expect(usernameInput.mock.calls[0][0].meta.valid).toBe(false)
expect(usernameInput.mock.calls[0][0].meta.error).toBe('Required')
// update username field so it passes
usernameInput.mock.calls[0][0].input.onChange('erikras')
// username input rerendered
expect(usernameInput).toHaveBeenCalledTimes(2)
// should be valid now
expect(usernameInput.mock.calls[1][0].meta.valid).toBe(true)
expect(usernameInput.mock.calls[1][0].meta.error).toBe(undefined)
})
it('should rerender when sync warning changes', () => {
const store = makeStore({
testForm: {
values: {
password: 'redux-form sucks',
confirm: 'redux-form rocks'
}
}
})
const passwordInput = jest.fn(props => <input {...props.input} />)
const confirmInput = jest.fn(props => <input {...props.input} />)
const warn = values => {
const password = getIn(values, 'password')
const confirm = getIn(values, 'confirm')
return password === confirm
? {}
: { confirm: 'Should match. Or not. Whatever.' }
}
class Form extends Component {
render() {
return (
<div>
<Field name="password" component={passwordInput} />
<Field name="confirm" component={confirmInput} />
</div>
)
}
}
const TestForm = reduxForm({
form: 'testForm',
warn
})(Form)
TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
// password input rendered
expect(passwordInput).toHaveBeenCalled()
expect(passwordInput).toHaveBeenCalledTimes(1)
// confirm input rendered with warning
expect(confirmInput).toHaveBeenCalled()
expect(confirmInput).toHaveBeenCalledTimes(1)
expect(confirmInput.mock.calls[0][0].meta.warning).toBe(
'Should match. Or not. Whatever.'
)
// update password field so that they match
passwordInput.mock.calls[0][0].input.onChange('redux-form rocks')
// password input rerendered
expect(passwordInput).toHaveBeenCalledTimes(2)
// confirm input should also rerender, but with no warning
expect(confirmInput).toHaveBeenCalledTimes(2)
expect(confirmInput.mock.calls[1][0].meta.warning).toBe(undefined)
})
it('should rerender when sync warning is cleared', () => {
const store = makeStore()
const usernameInput = jest.fn(props => <input {...props.input} />)
const warn = values => {
const username = getIn(values, 'username')
return username ? {} : { username: 'Recommended' }
}
class Form extends Component {
render() {
return (
<div>
<Field name="username" component={usernameInput} />
</div>
)
}
}
const TestForm = reduxForm({
form: 'testForm',
warn
})(Form)
TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
// username input rendered
expect(usernameInput).toHaveBeenCalled()
expect(usernameInput).toHaveBeenCalledTimes(1)
// username field has warning
expect(usernameInput.mock.calls[0][0].meta.warning).toBe('Recommended')
// update username field so it passes
usernameInput.mock.calls[0][0].input.onChange('erikras')
// username input rerendered
expect(usernameInput).toHaveBeenCalledTimes(2)
// should be valid now
expect(usernameInput.mock.calls[1][0].meta.warning).toBe(undefined)
})
it('should sync validate with field level validator', () => {
const store = makeStore()
const usernameInput = jest.fn(props => <input {...props.input} />)
const required = jest.fn(
value => (value == null ? 'Required' : undefined)
)
class Form extends Component {
render() {
return (
<div>
<Field
name="username"
component={usernameInput}
validate={required}
/>
</div>
)
}
}
const TestForm = reduxForm({
form: 'testForm'
})(Form)
TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
// username input rendered
expect(usernameInput).toHaveBeenCalled()
expect(usernameInput).toHaveBeenCalledTimes(2)
expect(required).toHaveBeenCalled()
expect(required).toHaveBeenCalledTimes(1)
// username field has error
expect(usernameInput.mock.calls[1][0].meta.valid).toBe(false)
expect(usernameInput.mock.calls[1][0].meta.error).toBe('Required')
// update username field so it passes
usernameInput.mock.calls[0][0].input.onChange('erikras')
// username input rerendered
expect(usernameInput).toHaveBeenCalledTimes(3)
// should be valid now
expect(usernameInput.mock.calls[2][0].meta.valid).toBe(true)
expect(usernameInput.mock.calls[2][0].meta.error).toBe(undefined)
})
//it('should sync validate with multiple field level validators', () => {
// const store = makeStore()
// const usernameInput = createSpy(props =>
// <input {...props.input} />
// ).andCallThrough()
// const required = createSpy(
// value => (value == null ? 'Required' : undefined)
// ).andCallThrough()
// const minLength5 = createSpy(
// value => (value && value.length < 5 ? 'Min length 5' : undefined)
// ).andCallThrough()
// class Form extends Component {
// render() {
// return (
// <div>
// <Field
// name="username"
// component={usernameInput}
// validate={[required, minLength5]}
// />
// </div>
// )
// }
// }
// const TestForm = reduxForm({
// form: 'testForm'
// })(Form)
// TestUtils.renderIntoDocument(
// <Provider store={store}>
// <TestForm />
// </Provider>
// )
//
// // username input rendered
// expect(usernameInput).toHaveBeenCalled()
// expect(usernameInput.calls.length).toBe(2)
// expect(required).toHaveBeenCalled()
// expect(required.calls.length).toBe(1)
//
// // username field has error
// expect(usernameInput.calls[1].arguments[0].meta.valid).toBe(false)
// expect(usernameInput.calls[1].arguments[0].meta.error).toBe('Required')
//
// // update username field so it passes
// usernameInput.calls[0].arguments[0].input.onChange('erikras')
//
// // username input rerendered
// expect(usernameInput.calls.length).toBe(3)
//
// // should be valid now
// expect(usernameInput.calls[2].arguments[0].meta.valid).toBe(true)
// expect(usernameInput.calls[2].arguments[0].meta.error).toBe(undefined)
//})
it('should update field level validation when validate prop changes', () => {
const store = makeStore()
const usernameInput = jest.fn(props => <input {...props.input} />)
const required = jest.fn(
value => (value == null ? 'Required' : undefined)
)
class Form extends Component {
constructor() {
super()
this.state = { validate: undefined }
}
render() {
return (
<div>
<Field
name="username"
component={usernameInput}
validate={this.state.validate}
/>
<button onClick={() => this.setState({ validate: required })}>
Change
</button>
</div>
)
}
}
const TestForm = reduxForm({
form: 'testForm'
})(Form)
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
// username field is ok
expect(
usernameInput.mock.calls[usernameInput.mock.calls.length - 1][0].meta
.valid
).toBe(true)
// update validate prop
const button = TestUtils.findRenderedDOMComponentWithTag(dom, 'button')
TestUtils.Simulate.click(button)
// should be invalid now
expect(
usernameInput.mock.calls[usernameInput.mock.calls.length - 1][0].meta
.valid
).toBe(false)
expect(
usernameInput.mock.calls[usernameInput.mock.calls.length - 1][0].meta
.error
).toBe('Required')
})
it('should revalidate when Field unregisters and registers with new validate prop', () => {
const weightFieldName = 'weight'
const weightValue = 100
const initialWeightLimit = 20
const nextWeightLimit = 50
const store = makeStore({
testForm: {
values: {
weight: weightValue
}
}
})
const renderWeight = jest.fn(props => <input {...props.input} />)
const weightValidationText = 'Max allowed weight is '
const getWeightLimitValidator = weightLimit =>
jest.fn(
value =>
value > weightLimit ? weightValidationText + weightLimit : undefined
)
class Form extends Component {
constructor() {
super()
this.state = {
weightLimit: initialWeightLimit,
validate: [getWeightLimitValidator(initialWeightLimit)]
}
}
render() {
// This will trigger unregister and register Field
// and these must happen in correct order for Field validation to work
const someCrazyBusinessLogic = this.state.weightLimit > 30
return (
<div>
{someCrazyBusinessLogic && (
<Field
name={weightFieldName}
component={renderWeight}
validate={this.state.validate}
/>
)}
{!someCrazyBusinessLogic && (
<Field
name={weightFieldName}
component={renderWeight}
validate={this.state.validate}
/>
)}
<button
onClick={() =>
this.setState({
weightLimit: nextWeightLimit,
validate: [getWeightLimitValidator(nextWeightLimit)]
})
}
>
Change weight limit
</button>
</div>
)
}
}
const TestForm = reduxForm({ form: testFormName })(Form)
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
testWeightValidator(initialWeightLimit)
const changeWeightLimit = TestUtils.findRenderedDOMComponentWithTag(
dom,
'button'
)
TestUtils.Simulate.click(changeWeightLimit)
testWeightValidator(nextWeightLimit)
function testWeightValidator(weightLimit) {
expect(
renderWeight.mock.calls[renderWeight.mock.calls.length - 1][0].meta
.valid
).toBe(false)
expect(
renderWeight.mock.calls[renderWeight.mock.calls.length - 1][0].meta
.error
).toBe(weightValidationText + weightLimit)
}
})
it('should sync warn with field level warning function', () => {
const store = makeStore()
const usernameInput = jest.fn(props => <input {...props.input} />)
const required = jest.fn(
value => (value == null ? 'Recommended' : undefined)
)
class Form extends Component {
render() {
return (
<div>
<Field
name="username"
component={usernameInput}
warn={required}
/>
</div>
)
}
}
const TestForm = reduxForm({
form: 'testForm'
})(Form)
TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
// username input rendered
expect(usernameInput).toHaveBeenCalled()
expect(usernameInput).toHaveBeenCalledTimes(2)
expect(required).toHaveBeenCalled()
expect(required).toHaveBeenCalledTimes(1)
// username field has warning
expect(usernameInput.mock.calls[1][0].meta.valid).toBe(true)
expect(usernameInput.mock.calls[1][0].meta.warning).toBe('Recommended')
// update username field so it passes
usernameInput.mock.calls[0][0].input.onChange('erikras')
// username input rerendered
expect(usernameInput).toHaveBeenCalledTimes(3)
// should be valid now
expect(usernameInput.mock.calls[2][0].meta.valid).toBe(true)
expect(usernameInput.mock.calls[2][0].meta.warning).toBe(undefined)
})
it('should update field level warning when warn prop changes', () => {
const store = makeStore()
const usernameInput = jest.fn(props => <input {...props.input} />)
const required = jest.fn(
value => (value == null ? 'Required' : undefined)
)
class Form extends Component {
constructor() {
super()
this.state = { warn: undefined }
}
render() {
return (
<div>
<Field
name="username"
component={usernameInput}
warn={this.state.warn}
/>
<button onClick={() => this.setState({ warn: required })}>
Change
</button>
</div>
)
}
}
const TestForm = reduxForm({
form: 'testForm'
})(Form)
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
// username field is ok
expect(
usernameInput.mock.calls[usernameInput.mock.calls.length - 1][0].meta
.warning
).toBe(undefined)
// update warn prop
const button = TestUtils.findRenderedDOMComponentWithTag(dom, 'button')
TestUtils.Simulate.click(button)
// should have warning now
expect(
usernameInput.mock.calls[usernameInput.mock.calls.length - 1][0].meta
.warning
).toBe('Required')
})
it('should not generate any warnings by passing api props into custom', () => {
const store = makeStore()
const renderSpy = jest.fn()
class InputComponent extends Component {
render() {
renderSpy(this.props)
return <input {...this.props.input} />
}
}
const apiProps = {
// all the official API props you can pass to Field
component: InputComponent,
name: 'foo',
normalize: x => x,
parse: x => x,
props: {},
format: x => x,
validate: () => undefined,
warn: () => undefined,
withRef: true,
immutableProps: []
}
class Form extends Component {
render() {
return (
<div>
<Field {...apiProps} />
</div>
)
}
}
const TestForm = reduxForm({
form: 'testForm'
})(Form)
TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
expect(renderSpy).toHaveBeenCalled()
const props = renderSpy.mock.calls[0][0]
Object.keys(apiProps).forEach(key => expect(props[key]).toBeFalsy())
})
it('should only rerender field that has changed', () => {
const store = makeStore()
const input1 = jest.fn(props => <input {...props.input} />)
const input2 = jest.fn(props => <input {...props.input} />)
class Form extends Component {
render() {
return (
<div>
<Field name="input1" component={input1} />
<Field name="input2" component={input2} />
</div>
)
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
expect(input1).toHaveBeenCalled()
expect(input1).toHaveBeenCalledTimes(1)
expect(input1.mock.calls[0][0].input.value).toBe('')
expect(input2).toHaveBeenCalled()
expect(input2).toHaveBeenCalledTimes(1)
expect(input2.mock.calls[0][0].input.value).toBe('')
// change input #1
input1.mock.calls[0][0].input.onChange('foo')
// expect input #1 to have been rerendered
expect(input1).toHaveBeenCalledTimes(2)
expect(input1.mock.calls[1][0].input.value).toBe('foo')
// expect input #2 to NOT have been rerendered
expect(input2).toHaveBeenCalledTimes(1)
})
it('should allow onChange callback', () => {
const store = makeStore()
const renderInput = jest.fn(props => <input {...props.input} />)
const callback = jest.fn()
class Form extends Component {
render() {
return (
<div>
<Field name="foo" component={renderInput} onChange={callback} />
</div>
)
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
const input = TestUtils.findRenderedDOMComponentWithTag(dom, 'input')
input.value = 'bar'
expect(callback).not.toHaveBeenCalled()
// rendered once with no onChange prop passed down in custom props
expect(renderInput).toHaveBeenCalledTimes(1)
expect(renderInput.mock.calls[0][0].onChange).toBeFalsy()
TestUtils.Simulate.change(input)
// call back was called
expect(callback).toHaveBeenCalled()
expect(callback).toHaveBeenCalledTimes(1)
expect(callback.mock.calls[0][0]).toBeTruthy() // event
expect(callback.mock.calls[0][1]).toBe('bar')
expect(callback.mock.calls[0][2]).toBe(undefined)
expect(callback.mock.calls[0][3]).toBe('foo')
// value changed
expect(renderInput).toHaveBeenCalledTimes(2)
expect(renderInput.mock.calls[1][0].input.value).toBe('bar')
})
it('should allow onChange callback to prevent change', () => {
const store = makeStore()
const renderInput = jest.fn(props => <input {...props.input} />)
const callback = jest.fn(event => event.preventDefault())
class Form extends Component {
render() {
return (
<div>
<Field name="foo" component={renderInput} onChange={callback} />
</div>
)
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
const input = TestUtils.findRenderedDOMComponentWithTag(dom, 'input')
input.value = 'bar'
expect(callback).not.toHaveBeenCalled()
// rendered once with no onChange prop passed down in custom props
expect(renderInput).toHaveBeenCalledTimes(1)
expect(renderInput.mock.calls[0][0].onChange).toBeFalsy()
TestUtils.Simulate.change(input)
// call back was called
expect(callback).toHaveBeenCalled()
expect(callback).toHaveBeenCalledTimes(1)
expect(callback.mock.calls[0][0]).toBeTruthy()
expect(callback.mock.calls[0][1]).toBe('bar')
expect(callback.mock.calls[0][2]).toBe(undefined)
expect(callback.mock.calls[0][3]).toBe('foo')
// value NOT changed
expect(renderInput).toHaveBeenCalledTimes(1)
expect(renderInput.mock.calls[0][0].input.value).toBe('')
})
it('should allow onBlur callback', () => {
const store = makeStore()
const renderInput = jest.fn(props => <input {...props.input} />)
const callback = jest.fn()
class Form extends Component {
render() {
return (
<div>
<Field name="foo" component={renderInput} onBlur={callback} />
</div>
)
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
const input = TestUtils.findRenderedDOMComponentWithTag(dom, 'input')
input.value = 'bar'
expect(callback).not.toHaveBeenCalled()
// rendered once with no onBlur prop passed down in custom props
expect(renderInput).toHaveBeenCalledTimes(1)
expect(renderInput.mock.calls[0][0].onBlur).toBeFalsy()
TestUtils.Simulate.blur(input)
// call back was called
expect(callback).toHaveBeenCalled()
expect(callback).toHaveBeenCalledTimes(1)
expect(callback.mock.calls[0][0]).toBeTruthy() // event
expect(callback.mock.calls[0][1]).toBe('bar')
expect(callback.mock.calls[0][2]).toBe(undefined)
expect(callback.mock.calls[0][3]).toBe('foo')
// value changed
expect(renderInput).toHaveBeenCalledTimes(2)
expect(renderInput.mock.calls[1][0].input.value).toBe('bar')
})
it('should allow onBlur callback to prevent blur', () => {
const store = makeStore()
const renderInput = jest.fn(props => <input {...props.input} />)
const callback = jest.fn(event => event.preventDefault())
class Form extends Component {
render() {
return (
<div>
<Field name="foo" component={renderInput} onBlur={callback} />
</div>
)
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
const input = TestUtils.findRenderedDOMComponentWithTag(dom, 'input')
input.value = 'bar'
expect(callback).not.toHaveBeenCalled()
// rendered once with no onBlur prop passed down in custom props
expect(renderInput).toHaveBeenCalledTimes(1)
expect(renderInput.mock.calls[0][0].onBlur).toBeFalsy()
TestUtils.Simulate.blur(input)
// call back was called
expect(callback).toHaveBeenCalled()
expect(callback).toHaveBeenCalledTimes(1)
expect(callback.mock.calls[0][0]).toBeTruthy()
expect(callback.mock.calls[0][1]).toBe('bar')
expect(callback.mock.calls[0][2]).toBe(undefined)
expect(callback.mock.calls[0][3]).toBe('foo')
// value NOT changed
expect(renderInput).toHaveBeenCalledTimes(1)
expect(renderInput.mock.calls[0][0].input.value).toBe('')
})
it('should allow onFocus callback', () => {
const store = makeStore()
const renderInput = jest.fn(props => <input {...props.input} />)
const callback = jest.fn()
class Form extends Component {
render() {
return (
<div>
<Field name="foo" component={renderInput} onFocus={callback} />
</div>
)
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
const input = TestUtils.findRenderedDOMComponentWithTag(dom, 'input')
expect(callback).not.toHaveBeenCalled()
// rendered once with no onFocus prop passed down in custom props
expect(renderInput).toHaveBeenCalledTimes(1)
expect(renderInput.mock.calls[0][0].onFocus).toBeFalsy()
// not marked as active
expect(renderInput.mock.calls[0][0].meta.active).toBe(false)
TestUtils.Simulate.focus(input)
// call back was called
expect(callback).toHaveBeenCalled()
expect(callback).toHaveBeenCalledTimes(1)
expect(callback.mock.calls[0][0]).toBeTruthy() // event
expect(callback.mock.calls[0][1]).toBe('foo')
// field marked active
expect(renderInput).toHaveBeenCalledTimes(2)
expect(renderInput.mock.calls[1][0].meta.active).toBe(true)
})
it('should allow onFocus callback to prevent focus', () => {
const store = makeStore()
const renderInput = jest.fn(props => <input {...props.input} />)
const callback = jest.fn(event => event.preventDefault())
class Form extends Component {
render() {
return (
<div>
<Field name="foo" component={renderInput} onFocus={callback} />
</div>
)
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
const input = TestUtils.findRenderedDOMComponentWithTag(dom, 'input')
expect(callback).not.toHaveBeenCalled()
// rendered once with no onFocus prop passed down in custom props
expect(renderInput).toHaveBeenCalledTimes(1)
expect(renderInput.mock.calls[0][0].onFocus).toBeFalsy()
// not marked as active
expect(renderInput.mock.calls[0][0].meta.active).toBe(false)
TestUtils.Simulate.focus(input)
// call back was called
expect(callback).toHaveBeenCalled()
expect(callback).toHaveBeenCalledTimes(1)
expect(callback.mock.calls[0][0]).toBeTruthy()
expect(callback.mock.calls[0][1]).toBe('foo')
// field NOT marked active
expect(renderInput).toHaveBeenCalledTimes(1)
expect(renderInput.mock.calls[0][0].meta.active).toBe(false)
})
it('should allow onDrop callback', () => {
const store = makeStore()
const renderInput = jest.fn(props => <input {...props.input} />)
const callback = jest.fn()
class Form extends Component {
render() {
return (
<div>
<Field name="foo" component={renderInput} onDrop={callback} />
</div>
)
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
const input = TestUtils.findRenderedDOMComponentWithTag(dom, 'input')
expect(callback).not.toHaveBeenCalled()
// rendered once with no onDrop prop passed down in custom props
expect(renderInput).toHaveBeenCalledTimes(1)
expect(renderInput.mock.calls[0][0].onDrop).toBeFalsy()
TestUtils.Simulate.drop(input, {
dataTransfer: { getData: () => 'bar' }
})
// call back was called
expect(callback).toHaveBeenCalled()
expect(callback).toHaveBeenCalledTimes(1)
expect(callback.mock.calls[0][0]).toBeTruthy() // event
expect(callback.mock.calls[0][1]).toBe('bar')
expect(callback.mock.calls[0][2]).toBe(undefined)
expect(callback.mock.calls[0][3]).toBe('foo')
// value changed
expect(renderInput).toHaveBeenCalledTimes(2)
expect(renderInput.mock.calls[1][0].input.value).toBe('bar')
})
it('should allow onDrop callback to prevent drop', () => {
const store = makeStore()
const renderInput = jest.fn(props => <input {...props.input} />)
const callback = jest.fn(event => event.preventDefault())
class Form extends Component {
render() {
return (
<div>
<Field name="foo" component={renderInput} onDrop={callback} />
</div>
)
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
const input = TestUtils.findRenderedDOMComponentWithTag(dom, 'input')
input.value = 'bar'
expect(callback).not.toHaveBeenCalled()
// rendered once with no onDrop prop passed down in custom props
expect(renderInput).toHaveBeenCalledTimes(1)
expect(renderInput.mock.calls[0][0].onDrop).toBeFalsy()
TestUtils.Simulate.drop(input, {
dataTransfer: { getData: () => 'bar' }
})
// call back was called
expect(callback).toHaveBeenCalled()
expect(callback).toHaveBeenCalledTimes(1)
expect(callback.mock.calls[0][0]).toBeTruthy()
expect(callback.mock.calls[0][1]).toBe('bar')
expect(callback.mock.calls[0][2]).toBe(undefined)
expect(callback.mock.calls[0][3]).toBe('foo')
// value NOT changed
expect(renderInput).toHaveBeenCalledTimes(1)
expect(renderInput.mock.calls[0][0].input.value).toBe('')
})
it('should allow onDragStart callback', () => {
const store = makeStore()
const renderInput = jest.fn(props => <input {...props.input} />)
const callback = jest.fn()
class Form extends Component {
render() {
return (
<div>
<Field
name="foo"
component={renderInput}
onDragStart={callback}
/>
</div>
)
}
}
const TestForm = reduxForm({ form: 'testForm' })(Form)
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
const input = TestUtils.findRenderedDOMComponentWithTag(dom, 'input')
expect(callback).not.toHaveBeenCalled()
// rendered once with no onDragStart prop passed down in custom props
expect(renderInput).toHaveBeenCalledTimes(1)
expect(renderInput.mock.calls[0][0].onDragStart).toBeFalsy()
TestUtils.Simulate.dragStart(input, {
dataTransfer: { setData: () => {} }
})
// call back was called
expect(callback).toHaveBeenCalled()
expect(callback).toHaveBeenCalledTimes(1)
expect(callback.mock.calls[0][0]).toBeTruthy() // event
expect(callback.mock.calls[0][1]).toBe('foo')
// value NOT changed
expect(renderInput).toHaveBeenCalledTimes(1)
})
it('should strict equals props in immutableProps', () => {
const store = makeStore()
const inputRender = jest.fn(props => <input {...props.input} />)
const formRender = jest.fn()
class Form extends Component {
constructor() {
super()
this.state = {
foo: {
get no() {
throw new Error(
'props inside an immutableProps object should not be looked at'
)
}
}
}
}
render() {
formRender(this.props)
return (
<div>
<Field
name="input"
component={inputRender}
immutableProps={['foo']}
foo={this.state.foo}
/>
<button onClick={() => this.setState({ foo: { no: undefined } })}>
Change
</button>
</div>
)
}
}
const TestForm = reduxForm({
form: 'testForm'
})(Form)
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<TestForm />
</Provider>
)
expect(formRender).toHaveBeenCalled()
expect(formRender).toHaveBeenCalledTimes(1)
expect(inputRender).toHaveBeenCalled()
expect(inputRender).toHaveBeenCalledTimes(1)
// update foo prop
const button = TestUtils.findRenderedDOMComponentWithTag(dom, 'button')
TestUtils.Simulate.click(button)
expect(formRender).toHaveBeenCalledTimes(2)
expect(inputRender).toHaveBeenCalledTimes(2)
})
})
}
describeField('Field.plain', plain, plainCombineReducers, () =>
expect.extend(plainExpectations)
)
describeField('Field.immutable', immutable, immutableCombineReducers, () =>
expect.extend(immutableExpectations)
)
You can’t perform that action at this time.