From a7b8cfecb40a824a03080b7d5020be8c01ae3434 Mon Sep 17 00:00:00 2001 From: Chris LoCasto Date: Sat, 11 Feb 2017 17:14:00 -0600 Subject: [PATCH 01/11] improved Form component's ability to correctly find nested Field components and pass them the correct props --- src/components/Form.jsx | 10 +++------- src/helpers/utilities.jsx | 8 ++++++++ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/components/Form.jsx b/src/components/Form.jsx index 7333d68..0da3e9f 100644 --- a/src/components/Form.jsx +++ b/src/components/Form.jsx @@ -1,5 +1,5 @@ import React from 'react'; -import { addFieldToState, mapPropsToChild } from '../helpers/utilities'; +import { addFieldToState, mapPropsToChild, makeFieldProps } from '../helpers/utilities'; const Form = class extends React.Component { constructor(props) { @@ -23,12 +23,8 @@ const Form = class extends React.Component { return (
{React.Children - .map(this.props.children, (child) => { - const { name } = child.props; - const value = this.state[name].value; - const fieldProps = { key: child.props.name, value, name }; - return mapPropsToChild(child, 'Field', fieldProps); - })} + .map(this.props.children, child => + mapPropsToChild(child, 'Field', makeFieldProps(child, this.state)))}
); } diff --git a/src/helpers/utilities.jsx b/src/helpers/utilities.jsx index d3cfcd9..86d406f 100644 --- a/src/helpers/utilities.jsx +++ b/src/helpers/utilities.jsx @@ -57,6 +57,14 @@ export function getValuesOf(obj = {}) { return Object.keys(obj).map(key => obj[key]); } +export function makeFieldProps(child, state) { + if (typeof child.type === 'function' && child.type.name === 'Field') { + const name = child.props.name; + return { key: name, name, value: state[name].value }; + } + return null; +} + export function mapPropsToChild(child, type, props) { if (child.type === type || (typeof child.type === 'function' && child.type.name === type)) { return React.cloneElement(child, props); From f34c7976ad77bcff85e91f54118e37c0798567ec Mon Sep 17 00:00:00 2001 From: Chris LoCasto Date: Sat, 11 Feb 2017 17:19:05 -0600 Subject: [PATCH 02/11] transpile current build --- dist/components/Form.js | 6 +----- dist/helpers/utilities.js | 9 +++++++++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/dist/components/Form.js b/dist/components/Form.js index e883b0a..59c7a31 100644 --- a/dist/components/Form.js +++ b/dist/components/Form.js @@ -56,11 +56,7 @@ var Form = function (_React$Component) { 'form', { onSubmit: this.onSubmit }, _react2.default.Children.map(this.props.children, function (child) { - var name = child.props.name; - - var value = _this2.state[name].value; - var fieldProps = { key: child.props.name, value: value, name: name }; - return (0, _utilities.mapPropsToChild)(child, 'Field', fieldProps); + return (0, _utilities.mapPropsToChild)(child, 'Field', (0, _utilities.makeFieldProps)(child, _this2.state)); }) ); } diff --git a/dist/helpers/utilities.js b/dist/helpers/utilities.js index 371a3c4..37eae08 100644 --- a/dist/helpers/utilities.js +++ b/dist/helpers/utilities.js @@ -11,6 +11,7 @@ exports.updateValidators = updateValidators; exports.isValid = isValid; exports.addFieldToState = addFieldToState; exports.getValuesOf = getValuesOf; +exports.makeFieldProps = makeFieldProps; exports.mapPropsToChild = mapPropsToChild; var _react = require('react'); @@ -110,6 +111,14 @@ function getValuesOf() { }); } +function makeFieldProps(child, state) { + if (typeof child.type === 'function' && child.type.name === 'Field') { + var name = child.props.name; + return { key: name, name: name, value: state[name].value }; + } + return null; +} + function mapPropsToChild(child, type, props) { if (child.type === type || typeof child.type === 'function' && child.type.name === type) { return _react2.default.cloneElement(child, props); From 162821f00de1f823cd1736520e96bea4a1626123 Mon Sep 17 00:00:00 2001 From: Chris LoCasto Date: Sat, 11 Feb 2017 17:28:25 -0600 Subject: [PATCH 03/11] fixed bug where Form was not passing down an onChange callback to Field --- src/components/Field.jsx | 10 ++++++---- src/components/Form.jsx | 3 ++- src/helpers/utilities.jsx | 4 ++-- tests/components/Form.spec.js | 4 ++++ 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/components/Field.jsx b/src/components/Field.jsx index 38a087b..9b1af67 100644 --- a/src/components/Field.jsx +++ b/src/components/Field.jsx @@ -94,13 +94,15 @@ const Field = class extends React.Component { if (!childCount) { return ( - +
+ +
); } return ( -
+
{React.Children .map(this.props.children, child => mapPropsToChild(child, 'input', inputProps))}
diff --git a/src/components/Form.jsx b/src/components/Form.jsx index 0da3e9f..34d8271 100644 --- a/src/components/Form.jsx +++ b/src/components/Form.jsx @@ -7,6 +7,7 @@ const Form = class extends React.Component { this.addFieldToState = addFieldToState.bind(this); this.onSubmit = this.onSubmit.bind(this); + this.onFieldChange = this.onFieldChange.bind(this); this.state = {}; const fieldsToAdd = React.Children.toArray(props.children) @@ -24,7 +25,7 @@ const Form = class extends React.Component {
{React.Children .map(this.props.children, child => - mapPropsToChild(child, 'Field', makeFieldProps(child, this.state)))} + mapPropsToChild(child, 'Field', makeFieldProps(child, this.onFieldChange, this.state)))}
); } diff --git a/src/helpers/utilities.jsx b/src/helpers/utilities.jsx index 86d406f..b1b75eb 100644 --- a/src/helpers/utilities.jsx +++ b/src/helpers/utilities.jsx @@ -57,10 +57,10 @@ export function getValuesOf(obj = {}) { return Object.keys(obj).map(key => obj[key]); } -export function makeFieldProps(child, state) { +export function makeFieldProps(child, onChange, state) { if (typeof child.type === 'function' && child.type.name === 'Field') { const name = child.props.name; - return { key: name, name, value: state[name].value }; + return { name, onChange, key: name, value: state[name].value }; } return null; } diff --git a/tests/components/Form.spec.js b/tests/components/Form.spec.js index 77107d0..39f0db1 100644 --- a/tests/components/Form.spec.js +++ b/tests/components/Form.spec.js @@ -58,5 +58,9 @@ describe('
Higher-Order-Component', () => { expect(nameProps).to.have.property('value', 'Enter your name'); expect(emailProps).to.have.property('value', 'test@example.com'); }); + + xit('updates its state upon a Field\'s input changing', () => { + + }); }); }); From e8db2a3f352fca56376715e4f1fd76df994051a4 Mon Sep 17 00:00:00 2001 From: Chris LoCasto Date: Sat, 11 Feb 2017 17:32:18 -0600 Subject: [PATCH 04/11] removed duplicate spec helper file --- dist/components/Field.js | 12 ++++++++---- dist/components/Form.js | 3 ++- dist/helpers/utilities.js | 4 ++-- tests/components/Field.spec.js | 16 ++++++++-------- tests/components/Form.spec.js | 3 ++- tests/spec_helpers/index.js | 4 ---- 6 files changed, 22 insertions(+), 20 deletions(-) diff --git a/dist/components/Field.js b/dist/components/Field.js index b987617..db79ac1 100644 --- a/dist/components/Field.js +++ b/dist/components/Field.js @@ -123,14 +123,18 @@ var Field = function (_React$Component) { if (!childCount) { return _react2.default.createElement( - 'label', - { htmlFor: this.props.name }, - _react2.default.createElement('input', inputProps) + 'div', + null, + _react2.default.createElement( + 'label', + { htmlFor: this.props.name }, + _react2.default.createElement('input', inputProps) + ) ); } return _react2.default.createElement( 'div', - { htmlFor: this.props.name }, + null, _react2.default.Children.map(this.props.children, function (child) { return (0, _utilities.mapPropsToChild)(child, 'input', inputProps); }) diff --git a/dist/components/Form.js b/dist/components/Form.js index 59c7a31..8bace3a 100644 --- a/dist/components/Form.js +++ b/dist/components/Form.js @@ -32,6 +32,7 @@ var Form = function (_React$Component) { _this.addFieldToState = _utilities.addFieldToState.bind(_this); _this.onSubmit = _this.onSubmit.bind(_this); + _this.onFieldChange = _this.onFieldChange.bind(_this); _this.state = {}; var fieldsToAdd = _react2.default.Children.toArray(props.children).filter(function (child) { @@ -56,7 +57,7 @@ var Form = function (_React$Component) { 'form', { onSubmit: this.onSubmit }, _react2.default.Children.map(this.props.children, function (child) { - return (0, _utilities.mapPropsToChild)(child, 'Field', (0, _utilities.makeFieldProps)(child, _this2.state)); + return (0, _utilities.mapPropsToChild)(child, 'Field', (0, _utilities.makeFieldProps)(child, _this2.onFieldChange, _this2.state)); }) ); } diff --git a/dist/helpers/utilities.js b/dist/helpers/utilities.js index 37eae08..c237a59 100644 --- a/dist/helpers/utilities.js +++ b/dist/helpers/utilities.js @@ -111,10 +111,10 @@ function getValuesOf() { }); } -function makeFieldProps(child, state) { +function makeFieldProps(child, onChange, state) { if (typeof child.type === 'function' && child.type.name === 'Field') { var name = child.props.name; - return { key: name, name: name, value: state[name].value }; + return { name: name, onChange: onChange, key: name, value: state[name].value }; } return null; } diff --git a/tests/components/Field.spec.js b/tests/components/Field.spec.js index f098402..e70d005 100644 --- a/tests/components/Field.spec.js +++ b/tests/components/Field.spec.js @@ -5,7 +5,7 @@ import { expect } from 'chai'; // eslint-disable-line import { shallow, mount } from 'enzyme'; // eslint-disable-line import { Field } from '../../dist/index'; -import { updateInput, simulateChange } from '../spec_helpers'; +import { updateInput } from '../spec_helpers'; describe(' Higher-Order-Component', () => { const nameField = { name: 'name', value: 'Test Name' }; @@ -166,7 +166,7 @@ describe(' Higher-Order-Component', () => { expect(renderSpy.callCount).to.equal(1); expect(wrapper.state()).to.have.property('value', 'firstValue'); - simulateChange(wrapper, 'secondValue'); + updateInput(wrapper, 'secondValue'); expect(shouldUpdateSpy.callCount).to.equal(1); expect(shouldUpdateSpy.calledBefore(willUpdateSpy)).to.equal(true); @@ -182,7 +182,7 @@ describe(' Higher-Order-Component', () => { expect(wrapper.state()).to.have.property('value', 'firstValue'); expect(wrapper.instance()).to.have.property('finalValue', null); - simulateChange(wrapper, 'firstValue'); + updateInput(wrapper, 'firstValue'); expect(shouldUpdateSpy.callCount).to.equal(1); expect(shouldUpdateSpy.calledBefore(willUpdateSpy)).to.equal(true); @@ -198,7 +198,7 @@ describe(' Higher-Order-Component', () => { expect(wrapper.state()).to.have.property('value', 'firstValue'); expect(wrapper.instance()).to.have.property('finalValue', null); - simulateChange(wrapper, 'secondValue'); + updateInput(wrapper, 'secondValue'); expect(shouldUpdateSpy.callCount).to.equal(1); expect(shouldUpdateSpy.calledBefore(willUpdateSpy)).to.equal(true); @@ -217,7 +217,7 @@ describe(' Higher-Order-Component', () => { expect(wrapper.state()).to.have.property('value', 'firstValue'); expect(wrapper.instance()).to.have.property('finalValue', null); - simulateChange(wrapper, 'secondValue'); + updateInput(wrapper, 'secondValue'); expect(renderSpy.callCount).to.equal(2); expect(willUpdateSpy.callCount).to.equal(1); @@ -241,7 +241,7 @@ describe(' Higher-Order-Component', () => { expect(wrapper.state()).to.have.property('value', 'firstValue'); expect(wrapper.instance()).to.have.property('finalValue', null); - simulateChange(wrapper, 'secondValue'); + updateInput(wrapper, 'secondValue'); expect(willUnmountSpy.callCount).to.equal(0); expect(cancelBroadcastSpy.callCount).to.equal(0); @@ -282,7 +282,7 @@ describe(' Higher-Order-Component', () => { expect(wrapper.state()).to.have.property('value', 'firstValue'); expect(wrapper.state()).to.have.property('debounce', 300); - simulateChange(wrapper, 'secondValue'); + updateInput(wrapper, 'secondValue'); expect(wrapper.state()).to.have.property('value', 'secondValue'); expect(willUpdateSpy.callCount).to.equal(1); @@ -298,7 +298,7 @@ describe(' Higher-Order-Component', () => { expect(wrapper.state()).to.have.property('value', 'firstValue'); expect(wrapper.state()).to.have.property('debounce', debounce); - simulateChange(wrapper, 'secondValue'); + updateInput(wrapper, 'secondValue'); expect(wrapper.state()).to.have.property('value', 'secondValue'); expect(onChangeSpy.callCount).to.equal(0); diff --git a/tests/components/Form.spec.js b/tests/components/Form.spec.js index 39f0db1..0b21f37 100644 --- a/tests/components/Form.spec.js +++ b/tests/components/Form.spec.js @@ -4,6 +4,7 @@ import { expect } from 'chai'; // eslint-disable-line import { shallow, mount } from 'enzyme'; // eslint-disable-line import { Form, Field } from '../../dist/index'; +import { updateInput } from '../spec_helpers'; describe(' Higher-Order-Component', () => { describe('Default Form', () => { @@ -59,7 +60,7 @@ describe(' Higher-Order-Component', () => { expect(emailProps).to.have.property('value', 'test@example.com'); }); - xit('updates its state upon a Field\'s input changing', () => { + it('updates its state upon a Field\'s input changing', () => { }); }); diff --git a/tests/spec_helpers/index.js b/tests/spec_helpers/index.js index bb1d5c7..97fbc6a 100644 --- a/tests/spec_helpers/index.js +++ b/tests/spec_helpers/index.js @@ -13,7 +13,3 @@ export function buildField(mountingFunction, validator, value, type) { // eslint-disable-next-line return mountingFunction(); } - -export function simulateChange(wrapper, value) { - return wrapper.find('input').simulate('change', { target: { value } }); -} From e2c274f74945783ebf906c1302243cf82883e00b Mon Sep 17 00:00:00 2001 From: Chris LoCasto Date: Sat, 11 Feb 2017 17:34:27 -0600 Subject: [PATCH 05/11] wrote form's onChange callback --- src/components/Form.jsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/Form.jsx b/src/components/Form.jsx index 34d8271..9d2c6a4 100644 --- a/src/components/Form.jsx +++ b/src/components/Form.jsx @@ -15,6 +15,12 @@ const Form = class extends React.Component { this.addFieldToState(fieldsToAdd); } + onFieldChange({ name, value, status, pristine }) { + this.setState({ + [name]: { name, value, status, pristine }, + }); + } + onSubmit(e) { e.preventDefault(); if (this.props.onSubmit) this.props.onSubmit({ ...this.state }); From b5c9c31bda170438c8b1f61faf043a2a2896c443 Mon Sep 17 00:00:00 2001 From: Chris LoCasto Date: Sat, 11 Feb 2017 18:04:10 -0600 Subject: [PATCH 06/11] refactored Field to store its data outside of state so as to enable synchronous updating broadcasting of state --- dist/components/Field.js | 20 +++++++++++--------- dist/components/Form.js | 12 ++++++++++++ dist/helpers/utilities.js | 2 +- package.json | 2 +- src/components/Field.jsx | 18 ++++++++++-------- src/components/Form.jsx | 4 ++-- src/helpers/utilities.jsx | 2 +- tests/components/Form.spec.js | 28 +++++++++++++++++++++++++++- 8 files changed, 65 insertions(+), 23 deletions(-) diff --git a/dist/components/Field.js b/dist/components/Field.js index db79ac1..d78df61 100644 --- a/dist/components/Field.js +++ b/dist/components/Field.js @@ -22,7 +22,8 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons 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; } +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; } /* globals window */ + var Field = function (_React$Component) { _inherits(Field, _React$Component); @@ -34,13 +35,12 @@ var Field = function (_React$Component) { _this.state = { value: props.value || '', - valid: false, - pristine: true, debounce: Math.floor(Math.pow(Math.pow(+props.debounce, 2), 0.5)) || 0, //eslint-disable-line validators: (0, _utilities.assembleValidators)(props) }; - _this.finalValue = null; + _this.valid = false; + _this.pristine = true; _this.onChange = _this.onChange.bind(_this); _this.broadcastChange = _this.broadcastChange.bind(_this); @@ -60,7 +60,7 @@ var Field = function (_React$Component) { if (this.props.match !== nextProps.match) { var validators = (0, _utilities.updateValidators)({ match: nextProps.match }, this.state.validators); - this.setState({ valid: (0, _utilities.isValid)(this.state.value, (0, _utilities.getValuesOf)(validators)), validators: validators }); + this.valid = (0, _utilities.isValid)(this.state.value, (0, _utilities.getValuesOf)(validators)); } } }, { @@ -82,10 +82,12 @@ var Field = function (_React$Component) { value: function onChange(e) { var value = e.target.value; - var validators = (0, _utilities.getValuesOf)(this.state.validators); + this.setState({ value: value }); - this.setState({ value: value, valid: (0, _utilities.isValid)(value, validators), pristine: false }); + var validators = (0, _utilities.getValuesOf)(this.state.validators); + this.valid = (0, _utilities.isValid)(value, validators); this.finalValue = value; + this.pristine = false; this.debouncedBroadcastChange(); } }, { @@ -95,8 +97,8 @@ var Field = function (_React$Component) { this.props.onChange({ name: this.props.name, value: this.finalValue, - status: this.state.valid, - pristine: this.state.pristine + valid: this.valid, + pristine: this.pristine }); } } diff --git a/dist/components/Form.js b/dist/components/Form.js index 8bace3a..268fe28 100644 --- a/dist/components/Form.js +++ b/dist/components/Form.js @@ -16,6 +16,8 @@ var _utilities = require('../helpers/utilities'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + 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; } @@ -43,6 +45,16 @@ var Form = function (_React$Component) { } _createClass(Form, [{ + key: 'onFieldChange', + value: function onFieldChange(_ref) { + var name = _ref.name, + value = _ref.value, + valid = _ref.valid, + pristine = _ref.pristine; + + this.setState(_defineProperty({}, name, { value: value, valid: valid, pristine: pristine })); + } + }, { key: 'onSubmit', value: function onSubmit(e) { e.preventDefault(); diff --git a/dist/helpers/utilities.js b/dist/helpers/utilities.js index c237a59..28d59dc 100644 --- a/dist/helpers/utilities.js +++ b/dist/helpers/utilities.js @@ -93,7 +93,7 @@ function addFieldToState(field) { valid = _field$props.valid, pristine = _field$props.pristine; - var newState = { value: '', valid: false, pristine: false }; + var newState = { value: '', valid: false, pristine: true }; if (value !== undefined) Object.assign(newState, { value: value }); if (valid !== undefined) Object.assign(newState, { valid: valid }); diff --git a/package.json b/package.json index 6cf4f76..e008784 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "formulize-react", - "version": "0.0.1", + "version": "0.1.0", "description": "A simple form validation library for React.js which wires up custom, controlled inputs through a declarative API.", "main": "dist/index", "keywords": [ diff --git a/src/components/Field.jsx b/src/components/Field.jsx index 9b1af67..2b01be6 100644 --- a/src/components/Field.jsx +++ b/src/components/Field.jsx @@ -1,3 +1,4 @@ +/* globals window */ import React from 'react'; import debounce from 'lodash.debounce'; import { @@ -14,13 +15,12 @@ const Field = class extends React.Component { this.state = { value: props.value || '', - valid: false, - pristine: true, debounce: Math.floor(Math.pow(Math.pow(+props.debounce, 2), 0.5)) || 0, //eslint-disable-line validators: assembleValidators(props), }; - this.finalValue = null; + this.valid = false; + this.pristine = true; this.onChange = this.onChange.bind(this); this.broadcastChange = this.broadcastChange.bind(this); @@ -38,7 +38,7 @@ const Field = class extends React.Component { if (this.props.match !== nextProps.match) { const validators = updateValidators({ match: nextProps.match }, this.state.validators); - this.setState({ valid: isValid(this.state.value, getValuesOf(validators)), validators }); + this.valid = isValid(this.state.value, getValuesOf(validators)); } } @@ -56,10 +56,12 @@ const Field = class extends React.Component { onChange(e) { const { value } = e.target; - const validators = getValuesOf(this.state.validators); + this.setState({ value }); - this.setState({ value, valid: isValid(value, validators), pristine: false }); + const validators = getValuesOf(this.state.validators); + this.valid = isValid(value, validators); this.finalValue = value; + this.pristine = false; this.debouncedBroadcastChange(); } @@ -68,8 +70,8 @@ const Field = class extends React.Component { this.props.onChange({ name: this.props.name, value: this.finalValue, - status: this.state.valid, - pristine: this.state.pristine, + valid: this.valid, + pristine: this.pristine, }); } } diff --git a/src/components/Form.jsx b/src/components/Form.jsx index 9d2c6a4..b35caab 100644 --- a/src/components/Form.jsx +++ b/src/components/Form.jsx @@ -15,9 +15,9 @@ const Form = class extends React.Component { this.addFieldToState(fieldsToAdd); } - onFieldChange({ name, value, status, pristine }) { + onFieldChange({ name, value, valid, pristine }) { this.setState({ - [name]: { name, value, status, pristine }, + [name]: { value, valid, pristine }, }); } diff --git a/src/helpers/utilities.jsx b/src/helpers/utilities.jsx index b1b75eb..002803b 100644 --- a/src/helpers/utilities.jsx +++ b/src/helpers/utilities.jsx @@ -43,7 +43,7 @@ export function addFieldToState(field) { field.forEach(name => this.addFieldToState(name)); } else if (typeof field === 'object') { const { name, value, valid, pristine } = field.props; - const newState = { value: '', valid: false, pristine: false }; + const newState = { value: '', valid: false, pristine: true }; if (value !== undefined) Object.assign(newState, { value }); if (valid !== undefined) Object.assign(newState, { valid }); diff --git a/tests/components/Form.spec.js b/tests/components/Form.spec.js index 0b21f37..f6203cf 100644 --- a/tests/components/Form.spec.js +++ b/tests/components/Form.spec.js @@ -60,8 +60,34 @@ describe(' Higher-Order-Component', () => { expect(emailProps).to.have.property('value', 'test@example.com'); }); - it('updates its state upon a Field\'s input changing', () => { + it.only('updates its state upon a Field\'s input changing', () => { + wrapper = mount(( + + + + )); + + expect(wrapper.state().name).to.eql({ + value: 'Enter your name', + valid: false, + pristine: true, + }); + + updateInput(wrapper, 'Good Name'); + + expect(wrapper.state().name).to.eql({ + value: 'Good Name', + valid: true, + pristine: false, + }); + + // updateInput(wrapper, 'Good Name'); + // expect(wrapper.state().name).to.eql({ + // value: 'Good Name', + // valid: true, + // pristine: false, + // }); }); }); }); From 090d0d64d729e1af832586fb4545b41f54144fff Mon Sep 17 00:00:00 2001 From: Chris LoCasto Date: Sat, 11 Feb 2017 18:14:16 -0600 Subject: [PATCH 07/11] fixed tests and utility functions to address synchronous Field state management --- tests/components/Form.spec.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/components/Form.spec.js b/tests/components/Form.spec.js index f6203cf..892ea1f 100644 --- a/tests/components/Form.spec.js +++ b/tests/components/Form.spec.js @@ -73,6 +73,14 @@ describe('
Higher-Order-Component', () => { pristine: true, }); + updateInput(wrapper, 'Way too long of a name'); + + expect(wrapper.state().name).to.eql({ + value: 'Way too long of a name', + valid: false, + pristine: false, + }); + updateInput(wrapper, 'Good Name'); expect(wrapper.state().name).to.eql({ @@ -80,14 +88,6 @@ describe(' Higher-Order-Component', () => { valid: true, pristine: false, }); - - // updateInput(wrapper, 'Good Name'); - - // expect(wrapper.state().name).to.eql({ - // value: 'Good Name', - // valid: true, - // pristine: false, - // }); }); }); }); From c37a6d19cb7ec8525809f30894910d8516430de8 Mon Sep 17 00:00:00 2001 From: Chris LoCasto Date: Sat, 11 Feb 2017 18:46:59 -0600 Subject: [PATCH 08/11] corrected test spec --- tests/components/Form.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/components/Form.spec.js b/tests/components/Form.spec.js index 892ea1f..d17713c 100644 --- a/tests/components/Form.spec.js +++ b/tests/components/Form.spec.js @@ -60,7 +60,7 @@ describe(' Higher-Order-Component', () => { expect(emailProps).to.have.property('value', 'test@example.com'); }); - it.only('updates its state upon a Field\'s input changing', () => { + it('updates its state upon a Field\'s input changing', () => { wrapper = mount(( From 7c7bf63191284c1f78b819b0e05b4ea7c989352a Mon Sep 17 00:00:00 2001 From: Chris LoCasto Date: Sat, 11 Feb 2017 19:20:52 -0600 Subject: [PATCH 09/11] refactored broadcast asynchronicity issue; returned to keeping field status entirely within state --- dist/components/Field.js | 25 +++++++++++++------------ src/components/Field.jsx | 26 ++++++++++++++------------ 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/dist/components/Field.js b/dist/components/Field.js index d78df61..7d5cb15 100644 --- a/dist/components/Field.js +++ b/dist/components/Field.js @@ -35,12 +35,12 @@ var Field = function (_React$Component) { _this.state = { value: props.value || '', + valid: false, + pristine: true, debounce: Math.floor(Math.pow(Math.pow(+props.debounce, 2), 0.5)) || 0, //eslint-disable-line validators: (0, _utilities.assembleValidators)(props) }; _this.finalValue = null; - _this.valid = false; - _this.pristine = true; _this.onChange = _this.onChange.bind(_this); _this.broadcastChange = _this.broadcastChange.bind(_this); @@ -60,7 +60,7 @@ var Field = function (_React$Component) { if (this.props.match !== nextProps.match) { var validators = (0, _utilities.updateValidators)({ match: nextProps.match }, this.state.validators); - this.valid = (0, _utilities.isValid)(this.state.value, (0, _utilities.getValuesOf)(validators)); + this.setState({ valid: (0, _utilities.isValid)(this.state.value, (0, _utilities.getValuesOf)(validators)), validators: validators }); } } }, { @@ -81,14 +81,15 @@ var Field = function (_React$Component) { key: 'onChange', value: function onChange(e) { var value = e.target.value; - - this.setState({ value: value }); + this.finalValue = value; var validators = (0, _utilities.getValuesOf)(this.state.validators); - this.valid = (0, _utilities.isValid)(value, validators); - this.finalValue = value; - this.pristine = false; - this.debouncedBroadcastChange(); + + this.setState({ + value: value, + valid: (0, _utilities.isValid)(value, validators), + pristine: false + }, this.debouncedBroadcastChange); } }, { key: 'broadcastChange', @@ -96,9 +97,9 @@ var Field = function (_React$Component) { if (this.props.onChange) { this.props.onChange({ name: this.props.name, - value: this.finalValue, - valid: this.valid, - pristine: this.pristine + value: this.state.value, + valid: this.state.valid, + pristine: this.state.pristine }); } } diff --git a/src/components/Field.jsx b/src/components/Field.jsx index 2b01be6..e1cc2ec 100644 --- a/src/components/Field.jsx +++ b/src/components/Field.jsx @@ -15,12 +15,12 @@ const Field = class extends React.Component { this.state = { value: props.value || '', + valid: false, + pristine: true, debounce: Math.floor(Math.pow(Math.pow(+props.debounce, 2), 0.5)) || 0, //eslint-disable-line validators: assembleValidators(props), }; this.finalValue = null; - this.valid = false; - this.pristine = true; this.onChange = this.onChange.bind(this); this.broadcastChange = this.broadcastChange.bind(this); @@ -38,7 +38,7 @@ const Field = class extends React.Component { if (this.props.match !== nextProps.match) { const validators = updateValidators({ match: nextProps.match }, this.state.validators); - this.valid = isValid(this.state.value, getValuesOf(validators)); + this.setState({ valid: isValid(this.state.value, getValuesOf(validators)), validators }); } } @@ -55,23 +55,25 @@ const Field = class extends React.Component { } onChange(e) { - const { value } = e.target; - this.setState({ value }); + const value = e.target.value; + this.finalValue = value; const validators = getValuesOf(this.state.validators); - this.valid = isValid(value, validators); - this.finalValue = value; - this.pristine = false; - this.debouncedBroadcastChange(); + + this.setState({ + value, + valid: isValid(value, validators), + pristine: false, + }, this.debouncedBroadcastChange); } broadcastChange() { if (this.props.onChange) { this.props.onChange({ name: this.props.name, - value: this.finalValue, - valid: this.valid, - pristine: this.pristine, + value: this.state.value, + valid: this.state.valid, + pristine: this.state.pristine, }); } } From ee96483acb63a6fcc06e54e80a1088b2b9205014 Mon Sep 17 00:00:00 2001 From: Chris LoCasto Date: Sat, 11 Feb 2017 19:21:38 -0600 Subject: [PATCH 10/11] removed eslint global definition from Field component --- src/components/Field.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/Field.jsx b/src/components/Field.jsx index e1cc2ec..a325b83 100644 --- a/src/components/Field.jsx +++ b/src/components/Field.jsx @@ -1,4 +1,3 @@ -/* globals window */ import React from 'react'; import debounce from 'lodash.debounce'; import { From 24e67c6b155dec6bfec85afbf910f3077ca16890 Mon Sep 17 00:00:00 2001 From: Chris LoCasto Date: Sat, 11 Feb 2017 19:49:56 -0600 Subject: [PATCH 11/11] added a test for form submission --- dist/components/Field.js | 3 +-- tests/components/Form.spec.js | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/dist/components/Field.js b/dist/components/Field.js index 7d5cb15..9b744af 100644 --- a/dist/components/Field.js +++ b/dist/components/Field.js @@ -22,8 +22,7 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons 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; } /* globals window */ - +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; } var Field = function (_React$Component) { _inherits(Field, _React$Component); diff --git a/tests/components/Form.spec.js b/tests/components/Form.spec.js index d17713c..8d80e81 100644 --- a/tests/components/Form.spec.js +++ b/tests/components/Form.spec.js @@ -2,6 +2,7 @@ import React from 'react'; import { expect } from 'chai'; // eslint-disable-line import { shallow, mount } from 'enzyme'; // eslint-disable-line +import sinon from 'sinon'; // eslint-disable-line import { Form, Field } from '../../dist/index'; import { updateInput } from '../spec_helpers'; @@ -89,5 +90,22 @@ describe(' Higher-Order-Component', () => { pristine: false, }); }); + + it('invoked an onSubmit callback upon form submission', () => { + const onSubmitSpy = sinon.spy(); + wrapper = mount( + + +