From d1e91a5956f4685bb303473a1acc66854786e154 Mon Sep 17 00:00:00 2001 From: Aapeli Date: Tue, 1 Aug 2017 11:46:53 +0300 Subject: [PATCH] onStepChange added with tests and docs --- README.md | 3 ++ dist/main.js | 115 +++++++++++++++++++--------------------- src/examples/Example.js | 2 + src/examples/Step1.js | 8 ++- src/main.js | 6 ++- tests/main.spec.js | 36 +++++++++++++ 6 files changed, 107 insertions(+), 63 deletions(-) diff --git a/README.md b/README.md index b55ffbd..18e7f57 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,9 @@ nextTextOnFinalActionStep: "Save" // its recommended that you use basic javascript validation (i.e simple validation implemented inside your step component. But stepzilla steps can also use 'react-validation-mixin' which wraps your steps as higher order components. If you use this then you need to specify those steps indexes that use 'react-validation-mixin' below in this array) hocValidationAppliedTo: [1, 2] +// function, which is called every time the index of the current step changes (it uses a zero based index) +onStepChange: (step) => console.log(step) + ``` example options usage: diff --git a/dist/main.js b/dist/main.js index c24eb21..4832148 100644 --- a/dist/main.js +++ b/dist/main.js @@ -4,8 +4,6 @@ Object.defineProperty(exports, "__esModule", { value: true }); -var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; - 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; }; }(); @@ -172,72 +170,66 @@ var StepZilla = function (_Component) { // a child step wants to invoke a jump between steps. in this case 'evt' is the numeric step number and not the JS event this.setNavState(evt); } else { - var _ret = function () { - // the main navigation step ui is invoking a jump between steps - if (!_this3.props.stepsNavigation || evt.target.value == _this3.state.compState) { - // if stepsNavigation is turned off or user clicked on existing step again (on step 2 and clicked on 2 again) then ignore - evt.preventDefault(); - evt.stopPropagation(); - - return { - v: void 0 - }; - } + // the main navigation step ui is invoking a jump between steps + if (!this.props.stepsNavigation || evt.target.value == this.state.compState) { + // if stepsNavigation is turned off or user clicked on existing step again (on step 2 and clicked on 2 again) then ignore + evt.preventDefault(); + evt.stopPropagation(); - evt.persist(); // evt is a react event so we need to persist it as we deal with aync promises which nullifies these events (https://facebook.github.io/react/docs/events.html#event-pooling) + return; + } - var movingBack = evt.target.value < _this3.state.compState; // are we trying to move back or front? - var passThroughStepsNotValid = false; // if we are jumping forward, only allow that if inbetween steps are all validated. This flag informs the logic... - var proceed = false; // flag on if we should move on + evt.persist(); // evt is a react event so we need to persist it as we deal with aync promises which nullifies these events (https://facebook.github.io/react/docs/events.html#event-pooling) - _this3.abstractStepMoveAllowedToPromise(movingBack).then(function () { - var valid = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; - // validation was a success (promise or sync validation). In it was a Promise's resolve() then proceed will be undefined, so make it true. Or else 'proceed' will carry the true/false value from sync v - proceed = valid; + var movingBack = evt.target.value < this.state.compState; // are we trying to move back or front? + var passThroughStepsNotValid = false; // if we are jumping forward, only allow that if inbetween steps are all validated. This flag informs the logic... + var proceed = false; // flag on if we should move on - if (!movingBack) { - _this3.updateStepValidationFlag(proceed); - } + this.abstractStepMoveAllowedToPromise(movingBack).then(function () { + var valid = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; + // validation was a success (promise or sync validation). In it was a Promise's resolve() then proceed will be undefined, so make it true. Or else 'proceed' will carry the true/false value from sync v + proceed = valid; - if (proceed) { - if (!movingBack) { - // looks like we are moving forward, 'reduce' a new array of step>validated values we need to check and 'some' that to get a decision on if we should allow moving forward - passThroughStepsNotValid = _this3.props.steps.reduce(function (a, c, i) { - if (i >= _this3.state.compState && i < evt.target.value) { - a.push(c.validated); - } - return a; - }, []).some(function (c) { - return c === false; - }); - } - } - }).catch(function (e) { - // Promise based validation was a fail (i.e reject()) + if (!movingBack) { + _this3.updateStepValidationFlag(proceed); + } + + if (proceed) { if (!movingBack) { - _this3.updateStepValidationFlag(false); - } - }).then(function () { - // this is like finally(), executes if error no no error - if (proceed && !passThroughStepsNotValid) { - if (evt.target.value === _this3.props.steps.length - 1 && _this3.state.compState === _this3.props.steps.length - 1) { - _this3.setNavState(_this3.props.steps.length); - } else { - _this3.setNavState(evt.target.value); - } - } - }).catch(function (e) { - if (e) { - // see note below called "CatchRethrowing" - // ... plus the finally then() above is what throws the JS Error so we need to catch that here specifically - setTimeout(function () { - throw e; + // looks like we are moving forward, 'reduce' a new array of step>validated values we need to check and 'some' that to get a decision on if we should allow moving forward + passThroughStepsNotValid = _this3.props.steps.reduce(function (a, c, i) { + if (i >= _this3.state.compState && i < evt.target.value) { + a.push(c.validated); + } + return a; + }, []).some(function (c) { + return c === false; }); } - }); - }(); - - if ((typeof _ret === 'undefined' ? 'undefined' : _typeof(_ret)) === "object") return _ret.v; + } + }).catch(function (e) { + // Promise based validation was a fail (i.e reject()) + if (!movingBack) { + _this3.updateStepValidationFlag(false); + } + }).then(function () { + // this is like finally(), executes if error no no error + if (proceed && !passThroughStepsNotValid) { + if (evt.target.value === _this3.props.steps.length - 1 && _this3.state.compState === _this3.props.steps.length - 1) { + _this3.setNavState(_this3.props.steps.length); + } else { + _this3.setNavState(evt.target.value); + } + } + }).catch(function (e) { + if (e) { + // see note below called "CatchRethrowing" + // ... plus the finally then() above is what throws the JS Error so we need to catch that here specifically + setTimeout(function () { + throw e; + }); + } + }); } } @@ -485,5 +477,6 @@ StepZilla.propTypes = { nextButtonCls: _propTypes2.default.string, backButtonCls: _propTypes2.default.string, backButtonText: _propTypes2.default.string, - hocValidationAppliedTo: _propTypes2.default.array + hocValidationAppliedTo: _propTypes2.default.array, + onStepChange: _propTypes2.default.func }; \ No newline at end of file diff --git a/src/examples/Example.js b/src/examples/Example.js index 62d679a..2d3902f 100644 --- a/src/examples/Example.js +++ b/src/examples/Example.js @@ -55,6 +55,8 @@ export default class Example extends Component { preventEnterSubmission={true} nextTextOnFinalActionStep={"Save"} hocValidationAppliedTo={[3]} + startAtStep={window.sessionStorage.getItem('step') ? parseFloat(window.sessionStorage.getItem('step')) : 0} + onStepChange={(step) => window.sessionStorage.setItem('step', step)} /> diff --git a/src/examples/Step1.js b/src/examples/Step1.js index 48c238e..ff10228 100644 --- a/src/examples/Step1.js +++ b/src/examples/Step1.js @@ -30,7 +30,13 @@ export default class Step1 extends Component {

This example uses this custom config (which overwrites the default config):

- preventEnterSubmission=true
nextTextOnFinalActionStep="Save"
hocValidationAppliedTo=[3]
+ + preventEnterSubmission=true
+ nextTextOnFinalActionStep="Save"
+ hocValidationAppliedTo=[3]
+ startAtStep=window.sessionStorage.getItem('step') ? parseFloat(window.sessionStorage.getItem('step')) : 0
+ onStepChange=(step) => window.sessionStorage.setItem('step', step) +

The default config settings are...

diff --git a/src/main.js b/src/main.js index e9dd4ea..9101404 100644 --- a/src/main.js +++ b/src/main.js @@ -90,6 +90,9 @@ export default class StepZilla extends Component { // which step are we in? checkNavState(currentStep) { + if (this.props.onStepChange) { + this.props.onStepChange(currentStep); + } this.setState(this.getPrevNextBtnState(currentStep)); } @@ -372,5 +375,6 @@ StepZilla.propTypes = { nextButtonCls: PropTypes.string, backButtonCls: PropTypes.string, backButtonText: PropTypes.string, - hocValidationAppliedTo: PropTypes.array + hocValidationAppliedTo: PropTypes.array, + onStepChange: PropTypes.func } diff --git a/tests/main.spec.js b/tests/main.spec.js index ad00d71..10ae20d 100644 --- a/tests/main.spec.js +++ b/tests/main.spec.js @@ -1,5 +1,6 @@ import React from 'react'; import StepZilla from '../src/main'; +import sinon from 'sinon' const shallow = enzyme.shallow; const makeFakeSteps = (num, makePure) => { @@ -397,5 +398,40 @@ describe('StepZilla', () => { expect(enzymeWrapper.find('.progtrckr').childAt(2).hasClass('progtrckr-doing')).to.be.true; }); }); + + describe('onStepChange: not null use case', () => { + let onStepChange; + let enzymeWrapper; + + beforeEach(() => { + onStepChange = sinon.spy(); + enzymeWrapper = setup(3, { + startAtStep: 1, + onStepChange + }).enzymeWrapper; + }) + + it('should call onStepChange when clicked to next step', (done) => { + + enzymeWrapper.find('.footer-buttons #next-button').simulate('click'); + + // click above is promise driven so it's async, setTimeout is probably not the best way to do this but it will do for now + setTimeout(() => { + expect(onStepChange.calledWith(2)).to.be.true; + done(); + }, 10); + }); + + it('should call onStepChange when clicked to previous step', (done) => { + + enzymeWrapper.find('.footer-buttons #prev-button').simulate('click'); + + // click above is promise driven so it's async, setTimeout is probably not the best way to do this but it will do for now + setTimeout(() => { + expect(onStepChange.calledWith(0)).to.be.true; + done(); + }, 10); + }); + }) }); // end - custom props based render group });