UNPKG

redux-form

Version:

A higher order component decorator for forms using Redux and React

1,541 lines (1,315 loc) 113 kB
import _noop from 'lodash-es/noop'; var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /* eslint react/no-multi-comp:0 */ import React, { Component } from 'react'; import TestUtils from 'react-addons-test-utils'; import { createSpy } from 'expect'; import { combineReducers as plainCombineReducers, createStore } from 'redux'; import { combineReducers as immutableCombineReducers } from 'redux-immutablejs'; import { Provider } from 'react-redux'; import createReducer from '../reducer'; import createReduxForm from '../reduxForm'; import createField from '../Field'; import createFieldArray from '../FieldArray'; import { startSubmit } from '../actions'; import plain from '../structure/plain'; import plainExpectations from '../structure/plain/expectations'; import immutable from '../structure/immutable'; import immutableExpectations from '../structure/immutable/expectations'; import addExpectations from './addExpectations'; import SubmissionError from '../SubmissionError'; import { change } from '../actions'; var describeReduxForm = function describeReduxForm(name, structure, combineReducers, expect) { var fromJS = structure.fromJS; var getIn = structure.getIn; var reduxForm = createReduxForm(structure); var Field = createField(structure); var FieldArray = createFieldArray(structure); var reducer = createReducer(structure); describe(name, function () { var makeStore = function makeStore() { var initial = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; return createStore(combineReducers({ form: reducer }), fromJS({ form: initial })); }; var propChecker = function propChecker(formState) { var renderSpy = arguments.length <= 1 || arguments[1] === undefined ? _noop : arguments[1]; var config = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2]; var store = makeStore({ testForm: formState }); var Form = function (_Component) { _inherits(Form, _Component); function Form() { _classCallCheck(this, Form); return _possibleConstructorReturn(this, (Form.__proto__ || Object.getPrototypeOf(Form)).apply(this, arguments)); } _createClass(Form, [{ key: 'render', value: function render() { renderSpy(this.props); return React.createElement( 'div', null, React.createElement(Field, { name: 'foo', component: 'input' }) ); } }]); return Form; }(Component); var Decorated = reduxForm(_extends({ form: 'testForm' }, config))(Form); var dom = TestUtils.renderIntoDocument(React.createElement( Provider, { store: store }, React.createElement(Decorated, null) )); return TestUtils.findRenderedComponentWithType(dom, Form).props; }; it('should return a decorator function', function () { expect(reduxForm).toBeA('function'); }); it('should render without error', function () { var store = makeStore(); var Form = function (_Component2) { _inherits(Form, _Component2); function Form() { _classCallCheck(this, Form); return _possibleConstructorReturn(this, (Form.__proto__ || Object.getPrototypeOf(Form)).apply(this, arguments)); } _createClass(Form, [{ key: 'render', value: function render() { return React.createElement('div', null); } }]); return Form; }(Component); expect(function () { var Decorated = reduxForm({ form: 'testForm' })(Form); TestUtils.renderIntoDocument(React.createElement( Provider, { store: store }, React.createElement(Decorated, null) )); }).toNotThrow(); }); it('should provide a the correct props', function () { var props = propChecker({}); expect(Object.keys(props).sort()).toEqual(['anyTouched', 'array', 'asyncValidate', 'asyncValidating', 'autofill', 'blur', 'change', 'destroy', 'dirty', 'dispatch', 'error', 'form', 'handleSubmit', 'initialValues', 'initialize', 'initialized', 'invalid', 'pristine', 'pure', 'reset', 'submitFailed', 'submitSucceeded', 'submitting', 'touch', 'untouch', 'valid']); expect(props.anyTouched).toBeA('boolean'); expect(props.array).toExist().toBeA('object'); expect(Object.keys(props.array).sort()).toEqual(['insert', 'move', 'pop', 'push', 'remove', 'removeAll', 'shift', 'splice', 'swap', 'unshift']); expect(props.array.insert).toExist().toBeA('function'); expect(props.array.move).toExist().toBeA('function'); expect(props.array.pop).toExist().toBeA('function'); expect(props.array.push).toExist().toBeA('function'); expect(props.array.remove).toExist().toBeA('function'); expect(props.array.removeAll).toExist().toBeA('function'); expect(props.array.shift).toExist().toBeA('function'); expect(props.array.splice).toExist().toBeA('function'); expect(props.array.swap).toExist().toBeA('function'); expect(props.array.unshift).toExist().toBeA('function'); expect(props.asyncValidate).toExist().toBeA('function'); expect(props.asyncValidating).toBeA('boolean'); expect(props.autofill).toExist().toBeA('function'); expect(props.blur).toExist().toBeA('function'); expect(props.change).toExist().toBeA('function'); expect(props.destroy).toExist().toBeA('function'); expect(props.dirty).toBeA('boolean'); expect(props.form).toExist().toBeA('string'); expect(props.handleSubmit).toExist().toBeA('function'); expect(props.initialize).toExist().toBeA('function'); expect(props.initialized).toBeA('boolean'); expect(props.pristine).toBeA('boolean'); expect(props.reset).toExist().toBeA('function'); expect(props.submitFailed).toBeA('boolean'); expect(props.submitSucceeded).toBeA('boolean'); expect(props.touch).toExist().toBeA('function'); expect(props.untouch).toExist().toBeA('function'); expect(props.valid).toBeA('boolean'); }); it('should provide dirty prop', function () { expect(propChecker({}).dirty).toBe(false); expect(propChecker({ // no initial values values: { foo: 'bar' } }).dirty).toBe(true); expect(propChecker({ initial: { foo: 'bar' }, values: { foo: 'bar' } }).dirty).toBe(false); expect(propChecker({ initial: { foo: 'bar' }, values: { foo: 'baz' } }).dirty).toBe(true); }); it('should provide pristine prop', function () { expect(propChecker({}).pristine).toBe(true); expect(propChecker({ // no initial values values: { foo: 'bar' } }).pristine).toBe(false); expect(propChecker({ initial: { foo: 'bar' }, values: { foo: 'bar' } }).pristine).toBe(true); expect(propChecker({ initial: { foo: 'bar' }, values: { foo: 'baz' } }).pristine).toBe(false); }); it('should provide valid prop', function () { expect(propChecker({}).valid).toBe(true); expect(propChecker({}, undefined, { validate: function validate() { return { foo: 'sync error' }; } }).valid).toBe(false); expect(propChecker({ asyncErrors: { foo: 'bar' } }).valid).toBe(false); expect(propChecker({ asyncErrors: { nested: { myArrayField: [undefined, undefined] } } }).valid).toBe(true); }); it('should provide invalid prop', function () { expect(propChecker({}).invalid).toBe(false); expect(propChecker({}, undefined, { validate: function validate() { return { foo: 'sync error' }; } }).invalid).toBe(true); expect(propChecker({ asyncErrors: { foo: 'bar' } }).invalid).toBe(true); }); it('should provide submitting prop', function () { expect(propChecker({}).submitting).toBe(false); expect(propChecker({ submitting: true }).submitting).toBe(true); expect(propChecker({ submitting: false }).submitting).toBe(false); }); it('should put props under prop namespace if specified', function () { var props = propChecker({}, _noop, { propNamespace: 'fooProps', someOtherProp: 'whatever' }); expect(props.fooProps).toExist().toBeA('object'); expect(props.dispatch).toNotExist(); expect(props.dirty).toNotExist(); expect(props.pristine).toNotExist(); expect(props.submitting).toNotExist(); expect(props.someOtherProp).toExist(); expect(props.fooProps.dispatch).toBeA('function'); expect(props.fooProps.dirty).toBeA('boolean'); expect(props.fooProps.pristine).toBeA('boolean'); expect(props.fooProps.submitting).toBeA('boolean'); expect(props.fooProps.someOtherProp).toNotExist(); }); it('should provide bound array action creators', function () { var arrayProp = propChecker({}).array; expect(arrayProp).toExist(); expect(arrayProp.insert).toExist().toBeA('function'); expect(arrayProp.pop).toExist().toBeA('function'); expect(arrayProp.push).toExist().toBeA('function'); expect(arrayProp.remove).toExist().toBeA('function'); expect(arrayProp.shift).toExist().toBeA('function'); expect(arrayProp.splice).toExist().toBeA('function'); expect(arrayProp.swap).toExist().toBeA('function'); expect(arrayProp.unshift).toExist().toBeA('function'); }); it('should not rerender unless form-wide props (except value!) change', function () { var spy = createSpy(); var _propChecker = propChecker({}, spy, { validate: function validate(values) { var foo = getIn(values, 'foo'); return foo && foo.length > 5 ? { foo: 'Too long' } : {}; } }); var dispatch = _propChecker.dispatch; // render 0 expect(spy.calls.length).toBe(1); // simulate typing the word "giraffe" dispatch(change('testForm', 'foo', 'g')); // render 1 (now dirty) expect(spy.calls.length).toBe(2); dispatch(change('testForm', 'foo', 'gi')); // no render dispatch(change('testForm', 'foo', 'gir')); // no render dispatch(change('testForm', 'foo', 'gira')); // no render dispatch(change('testForm', 'foo', 'giraf')); // no render dispatch(change('testForm', 'foo', 'giraff')); // render 2 (invalid) expect(spy.calls.length).toBe(3); dispatch(change('testForm', 'foo', 'giraffe')); // no render dispatch(change('testForm', 'foo', '')); // render 3 (clean/valid) expect(spy.calls.length).toBe(5); // two renders, one to change value, and other to revalidate expect(spy.calls[0].arguments[0].dirty).toBe(false); expect(spy.calls[0].arguments[0].invalid).toBe(false); expect(spy.calls[0].arguments[0].pristine).toBe(true); expect(spy.calls[0].arguments[0].valid).toBe(true); expect(spy.calls[1].arguments[0].dirty).toBe(true); expect(spy.calls[1].arguments[0].invalid).toBe(false); expect(spy.calls[1].arguments[0].pristine).toBe(false); expect(spy.calls[1].arguments[0].valid).toBe(true); expect(spy.calls[2].arguments[0].dirty).toBe(true); expect(spy.calls[2].arguments[0].invalid).toBe(true); expect(spy.calls[2].arguments[0].pristine).toBe(false); expect(spy.calls[2].arguments[0].valid).toBe(false); expect(spy.calls[4].arguments[0].dirty).toBe(false); expect(spy.calls[4].arguments[0].invalid).toBe(false); expect(spy.calls[4].arguments[0].pristine).toBe(true); expect(spy.calls[4].arguments[0].valid).toBe(true); }); it('should rerender on every change if pure is false', function () { var spy = createSpy(); var _propChecker2 = propChecker({}, spy, { pure: false }); var dispatch = _propChecker2.dispatch; expect(spy.calls.length).toBe(2); // twice, second one is for after field registration // simulate typing the word "giraffe" dispatch(change('testForm', 'foo', 'g')); expect(spy.calls.length).toBe(3); dispatch(change('testForm', 'foo', 'gi')); expect(spy.calls.length).toBe(4); dispatch(change('testForm', 'foo', 'gir')); expect(spy.calls.length).toBe(5); dispatch(change('testForm', 'foo', 'gira')); expect(spy.calls.length).toBe(6); dispatch(change('testForm', 'foo', 'giraf')); expect(spy.calls.length).toBe(7); dispatch(change('testForm', 'foo', 'giraff')); expect(spy.calls.length).toBe(8); dispatch(change('testForm', 'foo', 'giraffe')); expect(spy.calls.length).toBe(9); }); it('should initialize values with initialValues on first render', function () { var store = makeStore({}); var inputRender = createSpy(function (props) { return React.createElement('input', props.input); }).andCallThrough(); var formRender = createSpy(); var initialValues = { deep: { foo: 'bar' } }; var Form = function (_Component3) { _inherits(Form, _Component3); function Form() { _classCallCheck(this, Form); return _possibleConstructorReturn(this, (Form.__proto__ || Object.getPrototypeOf(Form)).apply(this, arguments)); } _createClass(Form, [{ key: 'render', value: function render() { formRender(this.props); return React.createElement( 'form', null, React.createElement(Field, { name: 'deep.foo', component: inputRender, type: 'text' }) ); } }]); return Form; }(Component); var Decorated = reduxForm({ form: 'testForm' })(Form); TestUtils.renderIntoDocument(React.createElement( Provider, { store: store }, React.createElement(Decorated, { initialValues: initialValues }) )); expect(store.getState()).toEqualMap({ form: { testForm: { initial: initialValues, values: initialValues, registeredFields: [{ name: 'deep.foo', type: 'Field' }] } } }); expect(formRender).toHaveBeenCalled(); expect(formRender.calls.length).toBe(1); var checkProps = function checkProps(props) { expect(props.pristine).toBe(true); expect(props.dirty).toBe(false); expect(props.initialized).toBe(false); // will be true on second render expect(props.initialValues).toEqualMap(initialValues); }; checkProps(formRender.calls[0].arguments[0]); expect(inputRender).toHaveBeenCalled(); expect(inputRender.calls.length).toBe(1); expect(inputRender.calls[0].arguments[0].meta.pristine).toBe(true); expect(inputRender.calls[0].arguments[0].meta.dirty).toBe(false); expect(inputRender.calls[0].arguments[0].input.value).toBe('bar'); }); it('should initialize with initialValues on later render if not already initialized', function () { var store = makeStore({}); var inputRender = createSpy(function (props) { return React.createElement('input', props.input); }).andCallThrough(); var formRender = createSpy(); var initialValues = { deep: { foo: 'bar' } }; var Form = function (_Component4) { _inherits(Form, _Component4); function Form() { _classCallCheck(this, Form); return _possibleConstructorReturn(this, (Form.__proto__ || Object.getPrototypeOf(Form)).apply(this, arguments)); } _createClass(Form, [{ key: 'render', value: function render() { formRender(this.props); return React.createElement( 'form', null, React.createElement(Field, { name: 'deep.foo', component: inputRender, type: 'text' }) ); } }]); return Form; }(Component); var Decorated = reduxForm({ form: 'testForm' })(Form); var Container = function (_Component5) { _inherits(Container, _Component5); function Container(props) { _classCallCheck(this, Container); var _this5 = _possibleConstructorReturn(this, (Container.__proto__ || Object.getPrototypeOf(Container)).call(this, props)); _this5.state = {}; return _this5; } _createClass(Container, [{ key: 'render', value: function render() { var _this6 = this; return React.createElement( 'div', null, React.createElement( Provider, { store: store }, React.createElement(Decorated, this.state) ), React.createElement( 'button', { onClick: function onClick() { return _this6.setState({ initialValues: initialValues }); } }, 'Init' ) ); } }]); return Container; }(Component); var dom = TestUtils.renderIntoDocument(React.createElement(Container, null)); expect(store.getState()).toEqualMap({ form: { testForm: { registeredFields: [{ name: 'deep.foo', type: 'Field' }] } } }); expect(formRender).toHaveBeenCalled(); expect(formRender.calls.length).toBe(1); expect(inputRender).toHaveBeenCalled(); expect(inputRender.calls.length).toBe(1); var checkInputProps = function checkInputProps(props, value) { expect(props.meta.pristine).toBe(true); expect(props.meta.dirty).toBe(false); expect(props.input.value).toBe(value); }; checkInputProps(inputRender.calls[0].arguments[0], ''); // initialize var initButton = TestUtils.findRenderedDOMComponentWithTag(dom, 'button'); TestUtils.Simulate.click(initButton); // check initialized state expect(store.getState()).toEqualMap({ form: { testForm: { registeredFields: [{ name: 'deep.foo', type: 'Field' }], initial: initialValues, values: initialValues } } }); // no need to rerender form on initialize expect(formRender.calls.length).toBe(1); // check rerendered input expect(inputRender.calls.length).toBe(2); checkInputProps(inputRender.calls[1].arguments[0], 'bar'); }); it('should NOT reinitialize with initialValues', function () { var store = makeStore({}); var inputRender = createSpy(function (props) { return React.createElement('input', props.input); }).andCallThrough(); var formRender = createSpy(); var initialValues1 = { deep: { foo: 'bar' } }; var initialValues2 = { deep: { foo: 'baz' } }; var Form = function (_Component6) { _inherits(Form, _Component6); function Form() { _classCallCheck(this, Form); return _possibleConstructorReturn(this, (Form.__proto__ || Object.getPrototypeOf(Form)).apply(this, arguments)); } _createClass(Form, [{ key: 'render', value: function render() { formRender(this.props); return React.createElement( 'form', null, React.createElement(Field, { name: 'deep.foo', component: inputRender, type: 'text' }) ); } }]); return Form; }(Component); var Decorated = reduxForm({ form: 'testForm' })(Form); var Container = function (_Component7) { _inherits(Container, _Component7); function Container(props) { _classCallCheck(this, Container); var _this8 = _possibleConstructorReturn(this, (Container.__proto__ || Object.getPrototypeOf(Container)).call(this, props)); _this8.state = { initialValues: initialValues1 }; return _this8; } _createClass(Container, [{ key: 'render', value: function render() { var _this9 = this; return React.createElement( 'div', null, React.createElement( Provider, { store: store }, React.createElement(Decorated, this.state) ), React.createElement( 'button', { onClick: function onClick() { return _this9.setState({ initialValues: initialValues2 }); } }, 'Init' ) ); } }]); return Container; }(Component); var dom = TestUtils.renderIntoDocument(React.createElement(Container, null)); expect(store.getState()).toEqualMap({ form: { testForm: { registeredFields: [{ name: 'deep.foo', type: 'Field' }], initial: initialValues1, values: initialValues1 } } }); expect(formRender).toHaveBeenCalled(); expect(formRender.calls.length).toBe(1); expect(inputRender).toHaveBeenCalled(); expect(inputRender.calls.length).toBe(1); var checkInputProps = function checkInputProps(props, value) { expect(props.meta.pristine).toBe(true); expect(props.meta.dirty).toBe(false); expect(props.input.value).toBe(value); }; checkInputProps(inputRender.calls[0].arguments[0], 'bar'); // initialize var initButton = TestUtils.findRenderedDOMComponentWithTag(dom, 'button'); TestUtils.Simulate.click(initButton); // check initialized state expect(store.getState()).toEqualMap({ form: { testForm: { registeredFields: [{ name: 'deep.foo', type: 'Field' }], initial: initialValues1, values: initialValues1 } } }); // rerender just because prop changed expect(formRender.calls.length).toBe(2); // no need to rerender input since nothing changed expect(inputRender.calls.length).toBe(1); }); it('should reinitialize with initialValues if enableReinitialize', function () { var store = makeStore({}); var inputRender = createSpy(function (props) { return React.createElement('input', props.input); }).andCallThrough(); var formRender = createSpy(); var initialValues1 = { deep: { foo: 'bar' } }; var initialValues2 = { deep: { foo: 'baz' } }; var Form = function (_Component8) { _inherits(Form, _Component8); function Form() { _classCallCheck(this, Form); return _possibleConstructorReturn(this, (Form.__proto__ || Object.getPrototypeOf(Form)).apply(this, arguments)); } _createClass(Form, [{ key: 'render', value: function render() { formRender(this.props); return React.createElement( 'form', null, React.createElement(Field, { name: 'deep.foo', component: inputRender, type: 'text' }) ); } }]); return Form; }(Component); var Decorated = reduxForm({ form: 'testForm', enableReinitialize: true })(Form); var Container = function (_Component9) { _inherits(Container, _Component9); function Container(props) { _classCallCheck(this, Container); var _this11 = _possibleConstructorReturn(this, (Container.__proto__ || Object.getPrototypeOf(Container)).call(this, props)); _this11.state = { initialValues: initialValues1 }; return _this11; } _createClass(Container, [{ key: 'render', value: function render() { var _this12 = this; return React.createElement( 'div', null, React.createElement( Provider, { store: store }, React.createElement(Decorated, this.state) ), React.createElement( 'button', { onClick: function onClick() { return _this12.setState({ initialValues: initialValues2 }); } }, 'Init' ) ); } }]); return Container; }(Component); var dom = TestUtils.renderIntoDocument(React.createElement(Container, null)); expect(store.getState()).toEqualMap({ form: { testForm: { registeredFields: [{ name: 'deep.foo', type: 'Field' }], initial: initialValues1, values: initialValues1 } } }); expect(formRender).toHaveBeenCalled(); expect(formRender.calls.length).toBe(1); expect(inputRender).toHaveBeenCalled(); expect(inputRender.calls.length).toBe(1); var checkInputProps = function checkInputProps(props, value) { expect(props.meta.pristine).toBe(true); expect(props.meta.dirty).toBe(false); expect(props.input.value).toBe(value); }; checkInputProps(inputRender.calls[0].arguments[0], 'bar'); // initialize var initButton = TestUtils.findRenderedDOMComponentWithTag(dom, 'button'); TestUtils.Simulate.click(initButton); // check initialized state expect(store.getState()).toEqualMap({ form: { testForm: { registeredFields: [{ name: 'deep.foo', type: 'Field' }], initial: initialValues2, values: initialValues2 } } }); // rerendered twice because prop changed and values initialized expect(formRender.calls.length).toBe(3); // should rerender input with new value expect(inputRender.calls.length).toBe(2); checkInputProps(inputRender.calls[1].arguments[0], 'baz'); }); it('should retain dirty fields if keepDirtyOnReinitialize is set', function () { var store = makeStore({}); var inputRender = createSpy(function (props) { return React.createElement('input', props.input); }).andCallThrough(); var formRender = createSpy(); var initialValues1 = { deep: { foo: 'bar' } }; var initialValues2 = { deep: { foo: 'baz' } }; var Form = function (_Component10) { _inherits(Form, _Component10); function Form() { _classCallCheck(this, Form); return _possibleConstructorReturn(this, (Form.__proto__ || Object.getPrototypeOf(Form)).apply(this, arguments)); } _createClass(Form, [{ key: 'render', value: function render() { formRender(this.props); return React.createElement( 'form', null, React.createElement(Field, { name: 'deep.foo', component: inputRender, type: 'text' }) ); } }]); return Form; }(Component); var Decorated = reduxForm({ form: 'testForm', enableReinitialize: true, keepDirtyOnReinitialize: true })(Form); var Container = function (_Component11) { _inherits(Container, _Component11); function Container(props) { _classCallCheck(this, Container); var _this14 = _possibleConstructorReturn(this, (Container.__proto__ || Object.getPrototypeOf(Container)).call(this, props)); _this14.state = { initialValues: initialValues1 }; return _this14; } _createClass(Container, [{ key: 'render', value: function render() { var _this15 = this; return React.createElement( 'div', null, React.createElement( Provider, { store: store }, React.createElement(Decorated, this.state) ), React.createElement( 'button', { onClick: function onClick() { return _this15.setState({ initialValues: initialValues2 }); } }, 'Init' ) ); } }]); return Container; }(Component); var dom = TestUtils.renderIntoDocument(React.createElement(Container, null)); expect(store.getState()).toEqualMap({ form: { testForm: { registeredFields: [{ name: 'deep.foo', type: 'Field' }], initial: initialValues1, values: initialValues1 } } }); expect(formRender).toHaveBeenCalled(); expect(formRender.calls.length).toBe(1); expect(inputRender).toHaveBeenCalled(); expect(inputRender.calls.length).toBe(1); var checkInputProps = function checkInputProps(props, value, dirty) { expect(props.meta.pristine).toBe(!dirty); expect(props.meta.dirty).toBe(dirty); expect(props.input.value).toBe(value); }; checkInputProps(inputRender.calls[0].arguments[0], 'bar', false); // Change the input value. var onChange = inputRender.calls[0].arguments[0].input.onChange; onChange('dirtyvalue'); // Expect rerenders due to the change. expect(formRender.calls.length).toBe(2); expect(inputRender.calls.length).toBe(2); // Reinitialize the form var initButton = TestUtils.findRenderedDOMComponentWithTag(dom, 'button'); TestUtils.Simulate.click(initButton); // check initialized state expect(store.getState()).toEqualMap({ form: { testForm: { registeredFields: [{ name: 'deep.foo', type: 'Field' }], initial: initialValues2, values: { deep: { foo: 'dirtyvalue' } } } } }); // Expect the form not to rerender, since the value did not change. expect(formRender.calls.length).toBe(2); // should rerender input with the dirty value. expect(inputRender.calls.length).toBe(2); checkInputProps(inputRender.calls[1].arguments[0], 'dirtyvalue', true); }); it('should not retain dirty fields if keepDirtyOnReinitialize is not set', function () { var store = makeStore({}); var inputRender = createSpy(function (props) { return React.createElement('input', props.input); }).andCallThrough(); var formRender = createSpy(); var initialValues1 = { deep: { foo: 'bar' } }; var initialValues2 = { deep: { foo: 'baz' } }; var Form = function (_Component12) { _inherits(Form, _Component12); function Form() { _classCallCheck(this, Form); return _possibleConstructorReturn(this, (Form.__proto__ || Object.getPrototypeOf(Form)).apply(this, arguments)); } _createClass(Form, [{ key: 'render', value: function render() { formRender(this.props); return React.createElement( 'form', null, React.createElement(Field, { name: 'deep.foo', component: inputRender, type: 'text' }) ); } }]); return Form; }(Component); var Decorated = reduxForm({ form: 'testForm', enableReinitialize: true })(Form); var Container = function (_Component13) { _inherits(Container, _Component13); function Container(props) { _classCallCheck(this, Container); var _this17 = _possibleConstructorReturn(this, (Container.__proto__ || Object.getPrototypeOf(Container)).call(this, props)); _this17.state = { initialValues: initialValues1 }; return _this17; } _createClass(Container, [{ key: 'render', value: function render() { var _this18 = this; return React.createElement( 'div', null, React.createElement( Provider, { store: store }, React.createElement(Decorated, this.state) ), React.createElement( 'button', { onClick: function onClick() { return _this18.setState({ initialValues: initialValues2 }); } }, 'Init' ) ); } }]); return Container; }(Component); var dom = TestUtils.renderIntoDocument(React.createElement(Container, null)); expect(store.getState()).toEqualMap({ form: { testForm: { registeredFields: [{ name: 'deep.foo', type: 'Field' }], initial: initialValues1, values: initialValues1 } } }); expect(formRender).toHaveBeenCalled(); expect(formRender.calls.length).toBe(1); expect(inputRender).toHaveBeenCalled(); expect(inputRender.calls.length).toBe(1); var checkInputProps = function checkInputProps(props, value, dirty) { expect(props.meta.pristine).toBe(!dirty); expect(props.meta.dirty).toBe(dirty); expect(props.input.value).toBe(value); }; checkInputProps(inputRender.calls[0].arguments[0], 'bar', false); // Change the input value. var onChange = inputRender.calls[0].arguments[0].input.onChange; onChange('dirtyvalue'); // Expect rerenders due to the change. expect(formRender.calls.length).toBe(2); expect(inputRender.calls.length).toBe(2); // Reinitialize the form var initButton = TestUtils.findRenderedDOMComponentWithTag(dom, 'button'); TestUtils.Simulate.click(initButton); // check initialized state expect(store.getState()).toEqualMap({ form: { testForm: { registeredFields: [{ name: 'deep.foo', type: 'Field' }], initial: initialValues2, values: initialValues2 } } }); // Expect the form to rerender, since the value was replaced. expect(formRender.calls.length).toBe(3); // should rerender input with the pristine value. expect(inputRender.calls.length).toBe(3); checkInputProps(inputRender.calls[2].arguments[0], 'baz', false); }); it('should make pristine any dirty field that has the new initial value, when keepDirtyOnReinitialize', function () { var store = makeStore({}); var inputRender = createSpy(function (props) { return React.createElement('input', props.input); }).andCallThrough(); var formRender = createSpy(); var initialValues1 = { deep: { foo: 'bar' } }; var initialValues2 = { deep: { foo: 'futurevalue' } }; var Form = function (_Component14) { _inherits(Form, _Component14); function Form() { _classCallCheck(this, Form); return _possibleConstructorReturn(this, (Form.__proto__ || Object.getPrototypeOf(Form)).apply(this, arguments)); } _createClass(Form, [{ key: 'render', value: function render() { formRender(this.props); return React.createElement( 'form', null, React.createElement(Field, { name: 'deep.foo', component: inputRender, type: 'text' }) ); } }]); return Form; }(Component); var Decorated = reduxForm({ form: 'testForm', enableReinitialize: true, keepDirtyOnReinitialize: true })(Form); var Container = function (_Component15) { _inherits(Container, _Component15); function Container(props) { _classCallCheck(this, Container); var _this20 = _possibleConstructorReturn(this, (Container.__proto__ || Object.getPrototypeOf(Container)).call(this, props)); _this20.state = { initialValues: initialValues1 }; return _this20; } _createClass(Container, [{ key: 'render', value: function render() { var _this21 = this; return React.createElement( 'div', null, React.createElement( Provider, { store: store }, React.createElement(Decorated, this.state) ), React.createElement( 'button', { onClick: function onClick() { return _this21.setState({ initialValues: initialValues2 }); } }, 'Init' ) ); } }]); return Container; }(Component); var dom = TestUtils.renderIntoDocument(React.createElement(Container, null)); expect(store.getState()).toEqualMap({ form: { testForm: { registeredFields: [{ name: 'deep.foo', type: 'Field' }], initial: initialValues1, values: initialValues1 } } }); expect(formRender).toHaveBeenCalled(); expect(formRender.calls.length).toBe(1); expect(inputRender).toHaveBeenCalled(); expect(inputRender.calls.length).toBe(1); var checkInputProps = function checkInputProps(props, value, dirty) { expect(props.meta.pristine).toBe(!dirty); expect(props.meta.dirty).toBe(dirty); expect(props.input.value).toBe(value); }; checkInputProps(inputRender.calls[0].arguments[0], 'bar', false); // Change the input value. var onChange = inputRender.calls[0].arguments[0].input.onChange; onChange('futurevalue'); // Expect rerenders due to the change. expect(formRender.calls.length).toBe(2); expect(inputRender.calls.length).toBe(2); // Reinitialize the form var initButton = TestUtils.findRenderedDOMComponentWithTag(dom, 'button'); TestUtils.Simulate.click(initButton); // check initialized state expect(store.getState()).toEqualMap({ form: { testForm: { registeredFields: [{ name: 'deep.foo', type: 'Field' }], initial: initialValues2, values: initialValues2 } } }); // Expect the form to rerender only once more because the value did // not change. expect(formRender.calls.length).toBe(3); // should rerender input with the new value that is now pristine. expect(inputRender.calls.length).toBe(3); checkInputProps(inputRender.calls[2].arguments[0], 'futurevalue', false); }); // Test related to #1436 /* it('should allow initialization via action to set pristine', () => { const store = makeStore({}) const inputRender = createSpy(props => <input {...props.input}/>).andCallThrough() const formRender = createSpy() const initialValues1 = { deep: { foo: 'bar' } } const initialValues2 = { deep: { foo: 'baz' } } class Form extends Component { render() { formRender(this.props) return ( <form> <Field name="deep.foo" component={inputRender} type="text"/> </form> ) } } const Decorated = reduxForm({ form: 'testForm', initialValues: initialValues1 })(Form) TestUtils.renderIntoDocument( <Provider store={store}> <Decorated/> </Provider> ) expect(store.getState()).toEqualMap({ form: { testForm: { registeredFields: [ { name: 'deep.foo', type: 'Field' } ], initial: initialValues1, values: initialValues1 } } }) expect(formRender).toHaveBeenCalled() expect(formRender.calls.length).toBe(1) expect(formRender.calls[ 0 ].arguments[ 0 ].pristine).toBe(true) expect(inputRender).toHaveBeenCalled() expect(inputRender.calls.length).toBe(1) expect(inputRender.calls[ 0 ].arguments[ 0 ].meta.pristine).toBe(true) expect(inputRender.calls[ 0 ].arguments[ 0 ].input.value).toBe('bar') // check initialized state expect(store.getState()).toEqualMap({ form: { testForm: { registeredFields: [ { name: 'deep.foo', type: 'Field' } ], initial: initialValues1, values: initialValues1 } } }) // initialize with action store.dispatch(initialize('testForm', initialValues2)) // check initialized state expect(store.getState()).toEqualMap({ form: { testForm: { registeredFields: [ { name: 'deep.foo', type: 'Field' } ], initial: initialValues2, values: initialValues2 } } }) // rerendered expect(formRender.calls.length).toBe(2) expect(formRender.calls[ 1 ].arguments[ 0 ].pristine).toBe(true) expect(inputRender).toHaveBeenCalled() expect(inputRender.calls.length).toBe(2) expect(inputRender.calls[ 1 ].arguments[ 0 ].meta.pristine).toBe(true) expect(inputRender.calls[ 1 ].arguments[ 0 ].input.value).toBe('baz') }) */ it('should destroy on unmount by default', function () { var store = makeStore({}); var inputRender = createSpy(function (props) { return React.createElement('input', props.input); }).andCallThrough(); var formRender = createSpy(); var Form = function (_Component16) { _inherits(Form, _Component16); function Form() { _classCallCheck(this, Form); return _possibleConstructorReturn(this, (Form.__proto__ || Object.getPrototypeOf(Form)).apply(this, arguments)); } _createClass(Form, [{ key: 'render', value: function render() { formRender(this.props); return React.createElement( 'form', null, React.createElement(Field, { name: 'deep.foo', component: inputRender, type: 'text' }) ); } }]); return Form; }(Component); var Decorated = reduxForm({ form: 'testForm' })(Form); var Container = function (_Component17) { _inherits(Container, _Component17); function Container(props) { _classCallCheck(this, Container); var _this23 = _possibleConstructorReturn(this, (Container.__proto__ || Object.getPrototypeOf(Container)).call(this, props)); _this23.state = { showForm: true }; return _this23; } _createClass(Container, [{ key: 'render', value: function render() { var _this24 = this; var showForm = this.state.showForm; return React.createElement( 'div', null, React.createElement( Provider, { store: store }, React.createElement( 'div', null, showForm && React.createElement(Decorated, this.state) ) ), React.createElement( 'button', { onClick: function onClick() { return _this24.setState({ showForm: !showForm }); } }, 'Toggle' ) ); } }]); return Container; }(Component); var dom = TestUtils.renderIntoDocument(React.createElement(Container, null)); expect(store.getState()).toEqualMap({ form: { testForm: { registeredFields: [{ name: 'deep.foo', type: 'Field' }] } } }, 'Form data in Redux did not get destroyed'); expect(formRender).toHaveBeenCalled(); expect(formRender.calls.length).toBe(1); expect(inputRender).toHaveBeenCalled(); expect(inputRender.calls.length).toBe(1); expect(inputRender.calls[0].arguments[0].input.value).toBe(''); // change field inputRender.calls[0].arguments[0].input.onChange('bob'); // form rerenders because now dirty expect(formRender.calls.length).toBe(2); // input now has value expect(inputRender.calls.length).toBe(2); expect(inputRender.calls[1].arguments[0].input.value).toBe('bob'); // check state expect(store.getState()).toEqualMap({ form: { testForm: { values: { deep: { foo: 'bob' } }, registeredFields: [{ name: 'deep.foo', type: 'Field' }] } } }); // unmount form var toggle = TestUtils.findRenderedDOMComponentWithTag(dom, 'button'); TestUtils.Simulate.click(toggle); // check clean state expect(store.getState()).toEqualMap({ form: {} }); // form still not rendered again expect(formRender.calls.length).toBe(2); // toggle form back into existence TestUtils.Simulate.click(toggle); // form is back expect(formRender.calls.length).toBe(3); // input is back, but without value expect(inputRender.calls.length).toBe(3); expect(inputRender.calls[2].arguments[0].input.value).toBe(''); }); it('should not destroy on unmount if told not to', function () { var store = makeStore({}); var inputRender = createSpy(function (props) { return React.createElement('input', props.input); }).andCallThrough(); var formRender = createSpy(); var Form = function (_Component18) { _inherits(Form, _Component18); function Form() { _classCallCheck(this, Form); return _possibleConstructorReturn(this, (Form.__proto__ || Object.getPrototypeOf(Form)).apply(this, arguments)); } _createClass(Form, [{ key: 'render', value: function render() { formRender(this.props); return React.createElement( 'form', null, React.createElement(Field, { name: 'deep.foo', component: inputRender, type: 'text' }) ); } }]); return Form; }(Component); var Decorated = reduxForm({ form: 'testForm', destroyOnUnmount: false })(Form); var Container = function (_Component19) { _inherits(Container, _Component19); function Container(props) { _classCallCheck(this, Container); var _this26 = _possibleConstructorReturn(this, (Container.__proto__ || Object.getPrototypeOf(Container)).call(this, props)); _this26.state = { showForm: true }; return _this26; } _createClass(Container, [{ key: 'render', value: function render() { var _this27 = this; var showForm = this.state.showForm; return React.createElement( 'div', null, React.createElement( Provider, { store: store }, Re