redux-form
Version:
A higher order component decorator for forms using Redux and React
1,941 lines (1,804 loc) • 63.1 kB
JavaScript
/* eslint react/no-multi-comp:0 */
import expect, {createSpy} from 'expect';
import React, {Component, PropTypes} from 'react';
import {connect} from 'react-redux';
import ReactDOM from 'react-dom';
import TestUtils from 'react-addons-test-utils';
import {combineReducers, createStore} from 'redux';
import {Provider} from 'react-redux';
import reducer from '../reducer';
import createReduxForm from '../createReduxForm';
const createRestorableSpy = (fn) => {
return createSpy(fn, function restore() {
this.calls = [];
});
};
describe('createReduxForm', () => {
const reduxForm = createReduxForm(false, React, connect);
const makeStore = () => createStore(combineReducers({
form: reducer
}));
it('should return a decorator function', () => {
expect(reduxForm).toBeA('function');
});
class Form extends Component {
render() {
return <div />;
}
}
const expectField = ({field, name, value, initial, valid, dirty, error, touched, visited, readonly}) => {
expect(field).toBeA('object');
expect(field.name).toBe(name);
expect(field.value).toEqual(value);
if (readonly) {
expect(field.onBlur).toNotExist();
expect(field.onChange).toNotExist();
expect(field.onDragStart).toNotExist();
expect(field.onDrop).toNotExist();
expect(field.onFocus).toNotExist();
expect(field.onUpdate).toNotExist();
} else {
expect(field.onBlur).toBeA('function');
expect(field.onChange).toBeA('function');
expect(field.onDragStart).toBeA('function');
expect(field.onDrop).toBeA('function');
expect(field.onFocus).toBeA('function');
expect(field.onUpdate).toBeA('function');
}
expect(field.initialValue).toEqual(initial);
expect(field.defaultValue).toEqual(initial);
expect(field.defaultChecked).toBe(initial === true);
expect(field.valid).toBe(valid);
expect(field.invalid).toBe(!valid);
expect(field.dirty).toBe(dirty);
expect(field.pristine).toBe(!dirty);
expect(field.error).toEqual(error);
expect(field.touched).toBe(touched);
expect(field.visited).toBe(visited);
};
it('should render without error', () => {
const store = makeStore();
expect(() => {
const Decorated = reduxForm({
form: 'testForm',
fields: ['foo', 'bar']
})(Form);
TestUtils.renderIntoDocument(
<Provider store={store}>
<Decorated/>
</Provider>
);
}).toNotThrow();
});
it('should pass fields as props', () => {
const store = makeStore();
const Decorated = reduxForm({
form: 'testForm',
fields: ['foo', 'bar']
})(Form);
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<Decorated/>
</Provider>
);
const stub = TestUtils.findRenderedComponentWithType(dom, Form);
expect(stub.props.fields).toBeA('object');
expectField({
field: stub.props.fields.foo,
name: 'foo',
value: undefined,
initial: undefined,
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false,
readonly: false
});
expectField({
field: stub.props.fields.bar,
name: 'bar',
value: undefined,
initial: undefined,
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false,
readonly: false
});
});
it('should initialize field values', () => {
const store = makeStore();
const Decorated = reduxForm({
form: 'testForm',
fields: ['foo', 'bar']
})(Form);
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<Decorated initialValues={{foo: 'fooValue', bar: 'barValue'}}/>
</Provider>
);
const stub = TestUtils.findRenderedComponentWithType(dom, Form);
expect(stub.props.fields).toBeA('object');
expectField({
field: stub.props.fields.foo,
name: 'foo',
value: 'fooValue',
initial: 'fooValue',
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false,
readonly: false
});
expectField({
field: stub.props.fields.bar,
name: 'bar',
value: 'barValue',
initial: 'barValue',
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false,
readonly: false
});
});
it('should set value and touch field on blur', () => {
const store = makeStore();
const form = 'testForm';
const Decorated = reduxForm({
form,
fields: ['foo', 'bar']
})(Form);
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<Decorated/>
</Provider>
);
const stub = TestUtils.findRenderedComponentWithType(dom, Form);
stub.props.fields.foo.onBlur('fooValue');
expect(stub.props.fields).toBeA('object');
expectField({
field: stub.props.fields.foo,
name: 'foo',
value: 'fooValue',
initial: undefined,
valid: true,
dirty: true,
error: undefined,
touched: true,
visited: false,
readonly: false
});
expectField({
field: stub.props.fields.bar,
name: 'bar',
value: undefined,
initial: undefined,
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false,
readonly: false
});
});
it('should set value and NOT touch field on blur if touchOnBlur is disabled', () => {
const store = makeStore();
const form = 'testForm';
const Decorated = reduxForm({
form,
fields: ['foo', 'bar'],
touchOnBlur: false
})(Form);
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<Decorated/>
</Provider>
);
const stub = TestUtils.findRenderedComponentWithType(dom, Form);
stub.props.fields.foo.onBlur('fooValue');
expect(stub.props.fields).toBeA('object');
expectField({
field: stub.props.fields.foo,
name: 'foo',
value: 'fooValue',
initial: undefined,
valid: true,
dirty: true,
error: undefined,
touched: false,
visited: false,
readonly: false
});
expectField({
field: stub.props.fields.bar,
name: 'bar',
value: undefined,
initial: undefined,
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false,
readonly: false
});
});
it('should set value and NOT touch field on change', () => {
const store = makeStore();
const form = 'testForm';
const Decorated = reduxForm({
form,
fields: ['foo', 'bar']
})(Form);
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<Decorated/>
</Provider>
);
const stub = TestUtils.findRenderedComponentWithType(dom, Form);
stub.props.fields.foo.onChange('fooValue');
expect(stub.props.fields).toBeA('object');
expectField({
field: stub.props.fields.foo,
name: 'foo',
value: 'fooValue',
initial: undefined,
valid: true,
dirty: true,
error: undefined,
touched: false,
visited: false,
readonly: false
});
expectField({
field: stub.props.fields.bar,
name: 'bar',
value: undefined,
initial: undefined,
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false,
readonly: false
});
});
it('should set value and touch field on change if touchOnChange is enabled', () => {
const store = makeStore();
const form = 'testForm';
const Decorated = reduxForm({
form,
fields: ['foo', 'bar'],
touchOnChange: true
})(Form);
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<Decorated/>
</Provider>
);
const stub = TestUtils.findRenderedComponentWithType(dom, Form);
stub.props.fields.foo.onChange('fooValue');
expect(stub.props.fields).toBeA('object');
expectField({
field: stub.props.fields.foo,
name: 'foo',
value: 'fooValue',
initial: undefined,
valid: true,
dirty: true,
error: undefined,
touched: true,
visited: false,
readonly: false
});
expectField({
field: stub.props.fields.bar,
name: 'bar',
value: undefined,
initial: undefined,
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false,
readonly: false
});
});
it('should set visited field on focus', () => {
const store = makeStore();
const form = 'testForm';
const Decorated = reduxForm({
form,
fields: ['foo', 'bar']
})(Form);
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<Decorated/>
</Provider>
);
const stub = TestUtils.findRenderedComponentWithType(dom, Form);
expect(stub.props.active).toBe(undefined);
stub.props.fields.foo.onFocus();
expect(stub.props.active).toBe('foo');
expect(stub.props.fields).toBeA('object');
expectField({
field: stub.props.fields.foo,
name: 'foo',
value: undefined,
initial: undefined,
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: true,
readonly: false
});
expectField({
field: stub.props.fields.bar,
name: 'bar',
value: undefined,
initial: undefined,
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false,
readonly: false
});
});
it('should set dirty when field changes', () => {
const store = makeStore();
const form = 'testForm';
const Decorated = reduxForm({
form,
fields: ['foo', 'bar']
})(Form);
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<Decorated initialValues={{foo: 'fooValue', bar: 'barValue'}}/>
</Provider>
);
const stub = TestUtils.findRenderedComponentWithType(dom, Form);
expectField({
field: stub.props.fields.foo,
name: 'foo',
value: 'fooValue',
initial: 'fooValue',
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false,
readonly: false
});
stub.props.fields.foo.onChange('fooValue!');
expectField({
field: stub.props.fields.foo,
name: 'foo',
value: 'fooValue!',
initial: 'fooValue',
valid: true,
dirty: true,
error: undefined,
touched: false,
visited: false,
readonly: false
});
});
it('should set dirty when and array field changes', () => {
const store = makeStore();
const form = 'testForm';
const Decorated = reduxForm({
form,
fields: ['children[].name']
})(Form);
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<Decorated initialValues={{children: [{name: 'Tom'}, {name: 'Jerry'}]}}/>
</Provider>
);
const stub = TestUtils.findRenderedComponentWithType(dom, Form);
expect(stub.props.fields.children).toBeA('array');
expect(stub.props.fields.children.length).toBe(2);
expectField({
field: stub.props.fields.children[0].name,
name: 'children[0].name',
value: 'Tom',
initial: 'Tom',
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false,
readonly: false
});
expectField({
field: stub.props.fields.children[1].name,
name: 'children[1].name',
value: 'Jerry',
initial: 'Jerry',
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false,
readonly: false
});
stub.props.fields.children[0].name.onChange('Tim');
expectField({
field: stub.props.fields.children[0].name,
name: 'children[0].name',
value: 'Tim',
initial: 'Tom',
valid: true,
dirty: true,
error: undefined,
touched: false,
visited: false,
readonly: false
});
expectField({
field: stub.props.fields.children[1].name,
name: 'children[1].name',
value: 'Jerry',
initial: 'Jerry',
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false,
readonly: false
});
});
it('should trigger sync error on change that invalidates value', () => {
const store = makeStore();
const form = 'testForm';
const Decorated = reduxForm({
form,
fields: ['foo', 'bar'],
validate: values => {
const errors = {};
if (values.foo && values.foo.length > 8) {
errors.foo = 'Too long';
}
if (!values.bar) {
errors.bar = 'Required';
}
return errors;
}
})(Form);
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<Decorated initialValues={{foo: 'fooValue', bar: 'barValue'}}/>
</Provider>
);
const stub = TestUtils.findRenderedComponentWithType(dom, Form);
expectField({
field: stub.props.fields.foo,
name: 'foo',
value: 'fooValue',
initial: 'fooValue',
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false,
readonly: false
});
expectField({
field: stub.props.fields.bar,
name: 'bar',
value: 'barValue',
initial: 'barValue',
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false,
readonly: false
});
expect(stub.props.valid).toBe(true);
expect(stub.props.invalid).toBe(false);
expect(stub.props.errors).toEqual({});
stub.props.fields.foo.onChange('fooValue!');
expectField({
field: stub.props.fields.foo,
name: 'foo',
value: 'fooValue!',
initial: 'fooValue',
valid: false,
dirty: true,
error: 'Too long',
touched: false,
visited: false,
readonly: false
});
stub.props.fields.bar.onChange('');
expectField({
field: stub.props.fields.bar,
name: 'bar',
value: '',
initial: 'barValue',
valid: false,
dirty: true,
error: 'Required',
touched: false,
visited: false,
readonly: false
});
expect(stub.props.valid).toBe(false);
expect(stub.props.invalid).toBe(true);
expect(stub.props.errors).toEqual({
foo: 'Too long',
bar: 'Required'
});
});
it('should trigger sync error on change that invalidates nested value', () => {
const store = makeStore();
const form = 'testForm';
const Decorated = reduxForm({
form,
fields: ['foo.bar'],
validate: values => {
const errors = {};
if (values.foo.bar && values.foo.bar.length > 8) {
errors.foo = { bar: 'Too long' };
}
return errors;
}
})(Form);
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<Decorated initialValues={{foo: {bar: 'fooBar'}}}/>
</Provider>
);
const stub = TestUtils.findRenderedComponentWithType(dom, Form);
expectField({
field: stub.props.fields.foo.bar,
name: 'foo.bar',
value: 'fooBar',
initial: 'fooBar',
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false,
readonly: false
});
expect(stub.props.valid).toBe(true);
expect(stub.props.invalid).toBe(false);
expect(stub.props.errors).toEqual({});
stub.props.fields.foo.bar.onChange('fooBarBaz');
expectField({
field: stub.props.fields.foo.bar,
name: 'foo.bar',
value: 'fooBarBaz',
initial: 'fooBar',
valid: false,
dirty: true,
error: 'Too long',
touched: false,
visited: false,
readonly: false
});
expect(stub.props.valid).toBe(false);
expect(stub.props.invalid).toBe(true);
expect(stub.props.errors).toEqual({
foo: {
bar: 'Too long'
}
});
});
it('should trigger sync error on change that invalidates array value', () => {
const store = makeStore();
const form = 'testForm';
const Decorated = reduxForm({
form,
fields: ['foo[]', 'bar[].name'],
validate: values => {
const errors = {};
if (values.foo && values.foo.length && values.foo[0] && values.foo[0].length > 8) {
errors.foo = ['Too long'];
}
if (values.bar && values.bar.length && values.bar[0] && values.bar[0].name === 'Ralphie') {
errors.bar = [{ name: `You'll shoot your eye out, kid!` }];
}
return errors;
}
})(Form);
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<Decorated initialValues={{foo: ['fooBar'], bar: [{name: ''}]}}/>
</Provider>
);
const stub = TestUtils.findRenderedComponentWithType(dom, Form);
expectField({
field: stub.props.fields.foo[0],
name: 'foo[0]',
value: 'fooBar',
initial: 'fooBar',
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false,
readonly: false
});
expectField({
field: stub.props.fields.bar[0].name,
name: 'bar[0].name',
value: '',
initial: '',
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false,
readonly: false
});
expect(stub.props.valid).toBe(true);
expect(stub.props.invalid).toBe(false);
expect(stub.props.errors).toEqual({});
stub.props.fields.foo[0].onChange('fooBarBaz');
expectField({
field: stub.props.fields.foo[0],
name: 'foo[0]',
value: 'fooBarBaz',
initial: 'fooBar',
valid: false,
dirty: true,
error: 'Too long',
touched: false,
visited: false,
readonly: false
});
stub.props.fields.bar[0].name.onChange('Ralphie');
expectField({
field: stub.props.fields.bar[0].name,
name: 'bar[0].name',
value: 'Ralphie',
initial: '',
valid: false,
dirty: true,
error: `You'll shoot your eye out, kid!`,
touched: false,
visited: false,
readonly: false
});
expect(stub.props.valid).toBe(false);
expect(stub.props.invalid).toBe(true);
expect(stub.props.errors).toEqual({
foo: ['Too long'],
bar: [{ name: `You'll shoot your eye out, kid!` }]
});
});
it('should call destroy on unmount', () => {
const store = makeStore();
const form = 'testForm';
const Decorated = reduxForm({
form,
fields: ['foo', 'bar']
})(Form);
const div = document.createElement('div');
ReactDOM.render(
<Provider store={store}>
<Decorated initialValues={{foo: 'fooValue', bar: 'barValue'}}/>
</Provider>,
div
);
const before = store.getState();
expect(before.form).toBeA('object');
expect(before.form[form]).toBeA('object');
expect(before.form[form].foo).toBeA('object');
expect(before.form[form].bar).toBeA('object');
ReactDOM.unmountComponentAtNode(div);
const after = store.getState();
expect(after.form).toBeA('object');
expect(after.form[form]).toNotExist();
});
it('should NOT call destroy on unmount if destroyOnUnmount is disabled', () => {
const store = makeStore();
const form = 'testForm';
const Decorated = reduxForm({
form,
fields: ['foo', 'bar'],
destroyOnUnmount: false
})(Form);
const div = document.createElement('div');
ReactDOM.render(
<Provider store={store}>
<Decorated initialValues={{foo: 'fooValue', bar: 'barValue'}}/>
</Provider>,
div
);
const before = store.getState();
expect(before.form).toBeA('object');
expect(before.form[form]).toBeA('object');
expect(before.form[form].foo).toBeA('object');
expect(before.form[form].bar).toBeA('object');
ReactDOM.unmountComponentAtNode(div);
const after = store.getState();
expect(after.form).toBeA('object');
expect(after.form[form]).toBeA('object');
expect(after.form[form].foo).toBeA('object');
expect(after.form[form].bar).toBeA('object');
});
it('should hoist statics', () => {
class FormWithStatics extends Component {
render() {
return <div/>;
}
}
FormWithStatics.someStatic1 = 'cat';
FormWithStatics.someStatic2 = 42;
const Decorated = reduxForm({
form: 'testForm',
fields: ['foo', 'bar']
})(FormWithStatics);
expect(Decorated.someStatic1).toBe('cat');
expect(Decorated.someStatic2).toBe(42);
});
it('should not provide mutators when readonly', () => {
const store = makeStore();
const form = 'testForm';
const Decorated = reduxForm({
form,
fields: ['foo', 'bar'],
readonly: true
})(Form);
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<Decorated/>
</Provider>
);
const stub = TestUtils.findRenderedComponentWithType(dom, Form);
expectField({
field: stub.props.fields.foo,
name: 'foo',
value: undefined,
initial: undefined,
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false,
readonly: true
});
expectField({
field: stub.props.fields.bar,
name: 'bar',
value: undefined,
initial: undefined,
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false,
readonly: true
});
});
it('should initialize an array field', () => {
const store = makeStore();
const form = 'testForm';
const Decorated = reduxForm({
form,
fields: ['children[].name'],
initialValues: {
children: [{ name: 'Tom' }, { name: 'Jerry' }]
}
})(Form);
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<Decorated/>
</Provider>
);
const stub = TestUtils.findRenderedComponentWithType(dom, Form);
expectField({
field: stub.props.fields.children[0].name,
name: 'children[0].name',
value: 'Tom',
initial: 'Tom',
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false
});
expectField({
field: stub.props.fields.children[1].name,
name: 'children[1].name',
value: 'Jerry',
initial: 'Jerry',
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false
});
});
it('should call onSubmit prop', (done) => {
const submit = (values) => {
expect(values).toEqual({
foo: undefined,
bar: undefined
});
done();
};
class FormComponent extends Component {
render() {
return (
<form onSubmit={this.props.handleSubmit}/>
);
}
}
FormComponent.propTypes = {
handleSubmit: PropTypes.func.isRequired
};
const store = makeStore();
const form = 'testForm';
const Decorated = reduxForm({
form,
fields: ['foo', 'bar'],
readonly: true
})(FormComponent);
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<Decorated onSubmit={submit}/>
</Provider>
);
const formElement = TestUtils.findRenderedDOMComponentWithTag(dom, 'form');
TestUtils.Simulate.submit(formElement);
});
it('should call async onSubmit prop', (done) => {
const submit = (values) => {
expect(values).toEqual({
foo: undefined,
bar: undefined
});
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, 100);
}).then(done);
};
class FormComponent extends Component {
render() {
return (
<form onSubmit={this.props.handleSubmit}/>
);
}
}
FormComponent.propTypes = {
handleSubmit: PropTypes.func.isRequired
};
const store = makeStore();
const form = 'testForm';
const Decorated = reduxForm({
form,
fields: ['foo', 'bar'],
readonly: true
})(FormComponent);
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<Decorated onSubmit={submit}/>
</Provider>
);
const formElement = TestUtils.findRenderedDOMComponentWithTag(dom, 'form');
TestUtils.Simulate.submit(formElement);
});
it('should NOT call async validation if form is pristine and initialized', () => {
const store = makeStore();
const form = 'testForm';
const errorValue = { foo: 'no bears allowed' };
const asyncValidate = createSpy().andReturn(Promise.reject(errorValue));
const Decorated = reduxForm({
form,
fields: ['foo', 'bar'],
asyncValidate,
asyncBlurFields: ['foo'],
initialValues: {
foo: 'dog',
bar: 'cat'
}
})(Form);
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<Decorated/>
</Provider>
);
const stub = TestUtils.findRenderedComponentWithType(dom, Form);
stub.props.fields.foo.onBlur('dog');
expect(asyncValidate).toNotHaveBeenCalled();
});
it('should call async validation if form is dirty and initialized', () => {
const store = makeStore();
const form = 'testForm';
const errorValue = { foo: 'no bears allowed' };
const asyncValidate = createSpy().andReturn(Promise.reject(errorValue));
const Decorated = reduxForm({
form,
fields: ['foo', 'bar'],
asyncValidate,
asyncBlurFields: ['foo'],
initialValues: {
foo: 'dog',
bar: 'cat'
}
})(Form);
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<Decorated/>
</Provider>
);
const stub = TestUtils.findRenderedComponentWithType(dom, Form);
stub.props.fields.foo.onBlur('bear');
expect(asyncValidate).toHaveBeenCalled();
});
it('should call async validation if form is pristine and NOT initialized', () => {
const store = makeStore();
const form = 'testForm';
const errorValue = { foo: 'no bears allowed' };
const asyncValidate = createSpy().andReturn(Promise.reject(errorValue));
const Decorated = reduxForm({
form,
fields: ['foo', 'bar'],
asyncValidate,
asyncBlurFields: ['foo']
})(Form);
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<Decorated/>
</Provider>
);
const stub = TestUtils.findRenderedComponentWithType(dom, Form);
stub.props.fields.foo.onBlur();
expect(asyncValidate).toHaveBeenCalled();
});
it('should call async validation on submit even if pristine and initialized', () => {
const submit = createSpy();
class FormComponent extends Component {
render() {
return (
<form onSubmit={this.props.handleSubmit(submit)}/>
);
}
}
FormComponent.propTypes = {
handleSubmit: PropTypes.func.isRequired
};
const store = makeStore();
const form = 'testForm';
const errorValue = { foo: 'no dogs allowed' };
const asyncValidate = createSpy().andReturn(Promise.reject(errorValue));
const Decorated = reduxForm({
form,
fields: ['foo', 'bar'],
asyncValidate,
asyncBlurFields: ['foo'],
initialValues: {
foo: 'dog',
bar: 'cat'
}
})(FormComponent);
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<Decorated/>
</Provider>
);
const formElement = TestUtils.findRenderedDOMComponentWithTag(dom, 'form');
TestUtils.Simulate.submit(formElement);
expect(asyncValidate).toHaveBeenCalled();
expect(submit).toNotHaveBeenCalled();
});
it('should call submit function passed to handleSubmit', (done) => {
const submit = (values) => {
expect(values).toEqual({
foo: undefined,
bar: undefined
});
done();
};
class FormComponent extends Component {
render() {
return (
<form onSubmit={this.props.handleSubmit(submit)}/>
);
}
}
FormComponent.propTypes = {
handleSubmit: PropTypes.func.isRequired
};
const store = makeStore();
const form = 'testForm';
const Decorated = reduxForm({
form,
fields: ['foo', 'bar'],
readonly: true
})(FormComponent);
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<Decorated />
</Provider>
);
const formElement = TestUtils.findRenderedDOMComponentWithTag(dom, 'form');
TestUtils.Simulate.submit(formElement);
});
it('should call submit function passed to async handleSubmit', (done) => {
const submit = (values) => {
expect(values).toEqual({
foo: undefined,
bar: undefined
});
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, 100);
}).then(done);
};
class FormComponent extends Component {
render() {
return (
<form onSubmit={this.props.handleSubmit(submit)}/>
);
}
}
FormComponent.propTypes = {
handleSubmit: PropTypes.func.isRequired
};
const store = makeStore();
const form = 'testForm';
const Decorated = reduxForm({
form,
fields: ['foo', 'bar'],
readonly: true
})(FormComponent);
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<Decorated />
</Provider>
);
const formElement = TestUtils.findRenderedDOMComponentWithTag(dom, 'form');
TestUtils.Simulate.submit(formElement);
});
it('should initialize a non-array field with an array value and let it read it back', () => {
const store = makeStore();
const form = 'testForm';
const Decorated = reduxForm({
form,
fields: ['children'],
initialValues: {
children: [1, 2]
}
})(Form);
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<Decorated/>
</Provider>
);
const stub = TestUtils.findRenderedComponentWithType(dom, Form);
expectField({
field: stub.props.fields.children,
name: 'children',
value: [1, 2],
initial: [1, 2],
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false
});
});
it('should initialize an array field with an array value', () => {
const store = makeStore();
const form = 'testForm';
const Decorated = reduxForm({
form,
fields: ['colors[]'],
initialValues: {
colors: ['red', 'blue']
}
})(Form);
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<Decorated/>
</Provider>
);
const stub = TestUtils.findRenderedComponentWithType(dom, Form);
expect(stub.props.fields.colors).toBeA('array');
expect(stub.props.fields.colors.length).toBe(2);
expectField({
field: stub.props.fields.colors[0],
name: 'colors[0]',
value: 'red',
initial: 'red',
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false
});
expectField({
field: stub.props.fields.colors[1],
name: 'colors[1]',
value: 'blue',
initial: 'blue',
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false
});
});
it('should initialize a deep array field with values', () => {
const store = makeStore();
const form = 'testForm';
const Decorated = reduxForm({
form,
fields: ['users[].name', 'users[].age'],
initialValues: {
users: [
{
name: 'Bob',
age: 27
}
]
}
})(Form);
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<Decorated/>
</Provider>
);
const stub = TestUtils.findRenderedComponentWithType(dom, Form);
expect(stub.props.fields.users).toBeA('array');
expect(stub.props.fields.users.length).toBe(1);
expect(stub.props.fields.users[0]).toBeA('object');
expectField({
field: stub.props.fields.users[0].name,
name: 'users[0].name',
value: 'Bob',
initial: 'Bob',
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false
});
expectField({
field: stub.props.fields.users[0].age,
name: 'users[0].age',
value: 27,
initial: 27,
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false
});
});
it('should add array values with defaults', () => {
const store = makeStore();
const form = 'testForm';
const Decorated = reduxForm({
form,
fields: ['users[].name', 'users[].age']
})(Form);
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<Decorated/>
</Provider>
);
const stub = TestUtils.findRenderedComponentWithType(dom, Form);
expect(stub.props.fields.users).toBeA('array');
expect(stub.props.fields.users.length).toBe(0);
expect(stub.props.fields.users.addField).toBeA('function');
const before = stub.props.fields.users;
// add field
stub.props.fields.users.addField({ name: 'Bob', age: 27 });
// check field
expect(stub.props.fields.users.length).toBe(1);
expect(stub.props.fields.users[0]).toBeA('object');
expectField({
field: stub.props.fields.users[0].name,
name: 'users[0].name',
value: 'Bob',
initial: 'Bob',
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false
});
expectField({
field: stub.props.fields.users[0].age,
name: 'users[0].age',
value: 27,
initial: 27,
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false
});
const after = stub.props.fields.users;
expect(after).toNotBe(before); // should be a new instance
// check state
expect(store.getState().form.testForm.users).toBeA('array');
expect(store.getState().form.testForm.users.length).toBe(1);
expect(store.getState().form.testForm.users[0].name)
.toEqual({
initial: 'Bob',
value: 'Bob'
});
expect(store.getState().form.testForm.users[0].age)
.toEqual({
initial: 27,
value: 27
});
});
// Test to demonstrate bug: https://github.com/erikras/redux-form/issues/630
it('should add array values when root is not an array', () => {
const store = makeStore();
const form = 'testForm';
const Decorated = reduxForm({
form,
fields: [
'acknowledgements.items[].number',
'acknowledgements.items[].name',
'acknowledgements.show'
]
})(Form);
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<Decorated/>
</Provider>
);
const stub = TestUtils.findRenderedComponentWithType(dom, Form);
expect(stub.props.fields.acknowledgements).toBeA('object');
expect(stub.props.fields.acknowledgements.items).toBeA('array');
expect(stub.props.fields.acknowledgements.items.length).toBe(0);
expect(stub.props.fields.acknowledgements.items.addField).toBeA('function');
// add field
stub.props.fields.acknowledgements.items.addField({
number: 1,
name: 'foo'
});
// check field
expect(stub.props.fields.acknowledgements.items.length).toBe(1);
expect(stub.props.fields.acknowledgements.items[0]).toBeA('object');
expectField({
field: stub.props.fields.acknowledgements.items[0].number,
name: 'acknowledgements.items[0].number',
value: 1,
initial: 1,
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false
});
expectField({
field: stub.props.fields.acknowledgements.items[0].name,
name: 'acknowledgements.items[0].name',
value: 'foo',
initial: 'foo',
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false
});
});
// Test to demonstrate bug: https://github.com/erikras/redux-form/issues/468
it('should add array values with DEEP defaults', () => {
const store = makeStore();
const form = 'testForm';
const Decorated = reduxForm({
form,
fields: [
'proposals[].arrival',
'proposals[].departure',
'proposals[].note',
'proposals[].rooms[].name',
'proposals[].rooms[].adults',
'proposals[].rooms[].children'
]
})(Form);
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<Decorated/>
</Provider>
);
const stub = TestUtils.findRenderedComponentWithType(dom, Form);
expect(stub.props.fields.proposals).toBeA('array');
expect(stub.props.fields.proposals.length).toBe(0);
expect(stub.props.fields.proposals.addField).toBeA('function');
// add field
const today = new Date();
stub.props.fields.proposals.addField({
arrival: today,
departure: today,
note: '',
rooms: [{
name: 'Room 1',
adults: 2,
children: 0
}]
});
stub.props.fields.proposals[0].rooms.addField({
name: 'Room 2',
adults: 0,
children: 2
});
// check field
expect(stub.props.fields.proposals.length).toBe(1);
expect(stub.props.fields.proposals[0]).toBeA('object');
expectField({
field: stub.props.fields.proposals[0].arrival,
name: 'proposals[0].arrival',
value: today,
initial: today,
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false
});
expectField({
field: stub.props.fields.proposals[0].departure,
name: 'proposals[0].departure',
value: today,
initial: today,
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false
});
expectField({
field: stub.props.fields.proposals[0].note,
name: 'proposals[0].note',
value: '',
initial: '',
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false
});
expectField({
field: stub.props.fields.proposals[0].rooms[0].name,
name: 'proposals[0].rooms[0].name',
value: 'Room 1',
initial: 'Room 1',
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false
});
expectField({
field: stub.props.fields.proposals[0].rooms[0].adults,
name: 'proposals[0].rooms[0].adults',
value: 2,
initial: 2,
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false
});
expectField({
field: stub.props.fields.proposals[0].rooms[0].children,
name: 'proposals[0].rooms[0].children',
value: 0,
initial: 0,
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false
});
expectField({
field: stub.props.fields.proposals[0].rooms[1].name,
name: 'proposals[0].rooms[1].name',
value: 'Room 2',
initial: 'Room 2',
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false
});
expectField({
field: stub.props.fields.proposals[0].rooms[1].adults,
name: 'proposals[0].rooms[1].adults',
value: 0,
initial: 0,
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false
});
expectField({
field: stub.props.fields.proposals[0].rooms[1].children,
name: 'proposals[0].rooms[1].children',
value: 2,
initial: 2,
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false
});
});
// Test to demonstrate https://github.com/erikras/redux-form/issues/612
//it('should work with a root-level array field', () => {
// const store = makeStore();
// const form = 'testForm';
// const Decorated = reduxForm({
// form,
// fields: ['tags[]']
// })(Form);
// const dom = TestUtils.renderIntoDocument(
// <Provider store={store}>
// <Decorated/>
// </Provider>
// );
// const stub = TestUtils.findRenderedComponentWithType(dom, Form);
//
// expect(stub.props.fields.tags).toBeA('array');
// expect(stub.props.fields.tags.length).toBe(0);
// expect(stub.props.fields.tags.addField).toBeA('function');
//
// // add field
// stub.props.fields.proposals.addField('foo');
//
// // check field
// expect(stub.props.fields.tags.length).toBe(1);
// expect(stub.props.fields.tags[0]).toBeA('object');
// expectField({
// field: stub.props.fields.tags[0],
// name: 'tags[0]',
// value: 'foo',
// initial: 'foo',
// valid: true,
// dirty: false,
// error: undefined,
// touched: false,
// visited: false
// });
//});
it('should initialize an array field, blowing away existing value', () => {
const store = makeStore();
const form = 'testForm';
const Decorated = reduxForm({
form,
fields: ['children']
})(Form);
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<Decorated/>
</Provider>
);
const stub = TestUtils.findRenderedComponentWithType(dom, Form);
// set value
stub.props.fields.children.onChange([1, 2]);
// check value
expectField({
field: stub.props.fields.children,
name: 'children',
value: [1, 2],
initial: undefined,
valid: true,
dirty: true,
error: undefined,
touched: false,
visited: false
});
// initialize new values
stub.props.initializeForm({ children: [3, 4] });
// check value
expectField({
field: stub.props.fields.children,
name: 'children',
value: [3, 4],
initial: [3, 4],
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false
});
// check state
expect(store.getState().form.testForm.children)
.toEqual({
initial: [3, 4],
value: [3, 4]
});
// reset form to newly initialized values
stub.props.resetForm();
// check value
expectField({
field: stub.props.fields.children,
name: 'children',
value: [3, 4],
initial: [3, 4],
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false
});
});
it('should only initialize on mount once', () => {
const store = makeStore();
const form = 'testForm';
const Decorated = reduxForm({
form,
fields: ['name']
})(Form);
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<Decorated initialValues={{name: 'Bob'}}/>
</Provider>
);
const stub = TestUtils.findRenderedComponentWithType(dom, Form);
// check value
expectField({
field: stub.props.fields.name,
name: 'name',
value: 'Bob',
initial: 'Bob',
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false
});
// check state
expect(store.getState().form.testForm.name)
.toEqual({
initial: 'Bob',
value: 'Bob'
});
// set value
stub.props.fields.name.onChange('Dan');
// check value
expectField({
field: stub.props.fields.name,
name: 'name',
value: 'Dan',
initial: 'Bob',
valid: true,
dirty: true,
error: undefined,
touched: false,
visited: false
});
// check state
expect(store.getState().form.testForm.name)
.toEqual({
initial: 'Bob',
value: 'Dan'
});
// should NOT dispatch INITIALIZE this time
const dom2 = TestUtils.renderIntoDocument(
<Provider store={store}>
<Decorated initialValues={{name: 'Bob'}}/>
</Provider>
);
const stub2 = TestUtils.findRenderedComponentWithType(dom2, Form);
// check that value is unchanged
expectField({
field: stub2.props.fields.name,
name: 'name',
value: 'Dan',
initial: 'Bob',
valid: true,
dirty: true,
error: undefined,
touched: false,
visited: false
});
// check state
expect(store.getState().form.testForm.name)
.toEqual({
initial: 'Bob',
value: 'Dan'
});
// manually initialize new values
stub2.props.initializeForm({ name: 'Tom' });
// check value
expectField({
field: stub2.props.fields.name,
name: 'name',
value: 'Tom',
initial: 'Tom',
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false
});
// check state
expect(store.getState().form.testForm.name)
.toEqual({
initial: 'Tom',
value: 'Tom'
});
});
it('should allow initialization from action', () => {
const store = makeStore();
const form = 'testForm';
const Decorated = reduxForm({
form,
fields: ['name']
})(Form);
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<Decorated/>
</Provider>
);
const stub = TestUtils.findRenderedComponentWithType(dom, Form);
// check value
expectField({
field: stub.props.fields.name,
name: 'name',
value: undefined,
initial: undefined,
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false
});
// manually initialize new values
stub.props.initializeForm({ name: 'Tom' });
// check state
expect(store.getState().form.testForm.name)
.toEqual({
initial: 'Tom',
value: 'Tom'
});
// check value
expectField({
field: stub.props.fields.name,
name: 'name',
value: 'Tom',
initial: 'Tom',
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false
});
});
it('should allow deep sync validation error values', () => {
const store = makeStore();
const form = 'testForm';
const deepError = {
some: 'object with',
deep: 'values'
};
const Decorated = reduxForm({
form,
fields: ['name'],
validate: () => ({ name: deepError })
})(Form);
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<Decorated/>
</Provider>
);
const stub = TestUtils.findRenderedComponentWithType(dom, Form);
expectField({
field: stub.props.fields.name,
name: 'name',
value: undefined,
initial: undefined,
valid: false,
dirty: false,
error: deepError,
touched: false,
visited: false
});
});
it('should allow deep async validation error values', () => {
const store = makeStore();
const form = 'testForm';
const deepError = {
some: 'object with',
deep: 'values'
};
const Decorated = reduxForm({
form,
fields: ['name'],
initialValues: { name: 'Tom' },
asyncValidate: () => Promise.reject({ name: deepError })
})(Form);
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<Decorated/>
</Provider>
);
const stub = TestUtils.findRenderedComponentWithType(dom, Form);
// check field before validation
expectField({
field: stub.props.fields.name,
name: 'name',
value: 'Tom',
initial: 'Tom',
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false
});
// form must be dirty for asyncValidate()
stub.props.fields.name.onChange('Moe');
return stub.props.asyncValidate()
.then(() => {
expect(true).toBe(false); // should not be in success block
}, () => {
// check state
expect(store.getState().form.testForm.name)
.toEqual({
initial: 'Tom',
value: 'Moe',
asyncError: deepError
});
// check field
expectField({
field: stub.props.fields.name,
name: 'name',
value: 'Moe',
initial: 'Tom',
valid: false,
dirty: true,
error: deepError,
touched: false,
visited: false
});
});
});
it('should allow deep submit validation error values', () => {
const store = makeStore();
const form = 'testForm';
const deepError = {
some: 'object with',
deep: 'values'
};
const Decorated = reduxForm({
form,
fields: ['name'],
initialValues: { name: 'Tom' },
onSubmit: () => Promise.reject({ name: deepError })
})(Form);
const dom = TestUtils.renderIntoDocument(
<Provider store={store}>
<Decorated/>
</Provider>
);
const stub = TestUtils.findRenderedComponentWithType(dom, Form);
// check before validation
expectField({
field: stub.props.fields.name,
name: 'name',
value: 'Tom',
initial: 'Tom',
valid: true,
dirty: false,
error: undefined,
touched: false,
visited: false
});
return stub.props.handleSubmit()
.then(() => {
// check state
expect(store.getS