From 130f1365e0ebed5cafc6baa492bee26709d30483 Mon Sep 17 00:00:00 2001 From: "Dave Methvin (USDS)" <40182224+dmethvin-gov@users.noreply.github.com> Date: Tue, 4 Sep 2018 13:28:12 -0400 Subject: [PATCH] Followup to review comments on #209 (#242) * Followup to review on #209. Improve docs; refactor tests * Move to enzyme for everything in SubmitController test * Add test for just a notice with no checkbox * Clarify docs; make sample match the existing screen shot * Changes based on review feedback (cherry picked from commit f6edfe623c4e7f982e9aa5d528d75a65f9197a39) --- ...able-form-features-and-usage-guidelines.md | 23 +- lib/js/components/PreSubmitSection.js | 19 +- lib/js/components/PreSubmitSection.js.map | 2 +- lib/js/review/SubmitController.js | 89 ++-- lib/js/review/SubmitController.js.map | 2 +- lib/js/state/helpers.js | 6 - lib/js/state/helpers.js.map | 2 +- package-lock.json | 2 +- src/js/components/PreSubmitSection.jsx | 19 +- src/js/review/SubmitController.jsx | 82 +-- src/js/state/helpers.js | 6 - test/js/review/ReviewPage.unit.spec.jsx | 11 +- test/js/review/SubmitController.unit.spec.jsx | 469 +++++++----------- test/js/state/index.unit.spec.js | 8 - 14 files changed, 305 insertions(+), 435 deletions(-) diff --git a/docs/building-a-form/available-form-features-and-usage-guidelines.md b/docs/building-a-form/available-form-features-and-usage-guidelines.md index 45249de..edb0a61 100644 --- a/docs/building-a-form/available-form-features-and-usage-guidelines.md +++ b/docs/building-a-form/available-form-features-and-usage-guidelines.md @@ -419,14 +419,23 @@ For the code implementation, see the [`review` folder](../../src/js/review). ### Required checkbox before form submission -Use this feature to require a user to indicate they have read terms and conditions, a privacy policy, or any other text before submitting your form. It includes a checkbox and short-form text that can include relevant links to more verbose information on separate pages on your site. To configure this feature, place a `preSubmitInfo` object in the `formConfig`: +Use this feature to require a user to agree they have read terms and conditions, a privacy policy, or any other text before submitting your form. It includes a checkbox and short-form text that can include relevant links to additional information on separate pages. + +To configure this feature, place a `preSubmitInfo` object in the `formConfig`. These are the available options: +* `notice`: (Optional) A text string or [React element](https://reactjs.org/docs/rendering-elements.html) placed above the checkbox and submit button. If the form definition file is `.jsx` the definition can be inline, or use `import` to reference an external component. If not specified, no notice appears. +* `required`: (Optional) When `true`, a checkbox and label appear above the submit button. The user must check the box before submitting the form. When `false` or not specified, the `field`, `label`, and `error` options are not used. +* `field`: The name of the form field for the required checkbox. This field has the value `true` in the submitted form data. +* `label`: A text string or React element that labels the checkbox. +* `error`: A text string or React element displayed as an error message if the user attempts to submit the form without checking the checkbox. + + +This is an example of `preSubmitInfo`: ```js preSubmitInfo: { - notice: '

Note: According to federal law, there are criminal penalties, including a fine and/or imprisonment for up to 5 years, for withholding information or for providing incorrect information. (See 18 U.S.C. 1001)

', - required: true, // when false, the notice is shown without a checkbox - field: 'privacyAgreementAccepted', // name of the field in submitted data - label: 'I have read and accept the privacy policy.', + required: true, + field: 'privacyAgreementAccepted', + label: I have read and accept the privacy policy., error: 'You must accept the privacy policy before continuing', } ``` @@ -435,8 +444,8 @@ preSubmitInfo: { #### Usage guidelines -Right now, the required checkbox is automatically included in all forms. The US Forms System team will refactor this component to make it more customizable. To follow that discussion, subscribe to https://github.com/usds/us-forms-system/issues/53. +If you don't specify a `preSubmitInfo` section, no notice or checkbox appears above the submit button. Most applications will want to give some sort of notice to the user before they submit the form. Although this section is optional, we recommend you specify it. -For the code implementation, see [`ErrorableCheckbox`](../../src/js/components/ErrorableCheckbox.jsx). +For the code implementation, see [`PreSubmitSection`](../../src/js/components/PreSubmitSection.jsx) and [`SubmitController`](../../src/js/review/SubmitController.jsx). [Back to *Building a Form*](./README.md) diff --git a/lib/js/components/PreSubmitSection.js b/lib/js/components/PreSubmitSection.js index 28abf51..c83528b 100644 --- a/lib/js/components/PreSubmitSection.js +++ b/lib/js/components/PreSubmitSection.js @@ -23,28 +23,25 @@ function PreSubmitSection(_ref) { var onChange = _ref.onChange, showError = _ref.showError, preSubmitInfo = _ref.preSubmitInfo, - form = _ref.form; + checked = _ref.checked; - var info = preSubmitInfo || {}; - var field = info.field || 'AGREED'; return _react2.default.createElement( 'div', null, - info.notice, - info.required && _react2.default.createElement(_ErrorableCheckbox2.default, { required: true, - checked: form.data[field], + preSubmitInfo.notice, + preSubmitInfo.required && _react2.default.createElement(_ErrorableCheckbox2.default, { required: true, + checked: checked, onValueChange: onChange, - name: field, - errorMessage: showError && !form.data[field] ? info.error || 'Please agree before continuing' : undefined, - label: info.label }) + name: preSubmitInfo.field, + errorMessage: showError && !checked ? preSubmitInfo.error || 'Please accept' : undefined, + label: preSubmitInfo.label }) ); } PreSubmitSection.propTypes = { - form: _propTypes2.default.object.isRequired, onChange: _propTypes2.default.func.isRequired, - preSubmitInfo: _propTypes2.default.object, + preSubmitInfo: _propTypes2.default.object.isRequired, showError: _propTypes2.default.bool }; //# sourceMappingURL=PreSubmitSection.js.map \ No newline at end of file diff --git a/lib/js/components/PreSubmitSection.js.map b/lib/js/components/PreSubmitSection.js.map index fe521ea..766fde1 100644 --- a/lib/js/components/PreSubmitSection.js.map +++ b/lib/js/components/PreSubmitSection.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/js/components/PreSubmitSection.jsx"],"names":["PreSubmitSection","onChange","showError","preSubmitInfo","form","info","field","notice","required","data","error","undefined","label","propTypes","PropTypes","object","isRequired","func","bool"],"mappings":";;;;;QAIgBA,gB,GAAAA,gB;;AAJhB;;;;AACA;;;;AACA;;;;;;AAEO,SAASA,gBAAT,OAAwE;AAAA,MAA5CC,QAA4C,QAA5CA,QAA4C;AAAA,MAAlCC,SAAkC,QAAlCA,SAAkC;AAAA,MAAvBC,aAAuB,QAAvBA,aAAuB;AAAA,MAARC,IAAQ,QAARA,IAAQ;;AAC7E,MAAMC,OAAOF,iBAAiB,EAA9B;AACA,MAAMG,QAAQD,KAAKC,KAAL,IAAc,QAA5B;;AAEA,SACE;AAAA;AAAA;AACGD,SAAKE,MADR;AAEGF,SAAKG,QAAL,IACC,8BAAC,2BAAD,IAAmB,cAAnB;AACE,eAASJ,KAAKK,IAAL,CAAUH,KAAV,CADX;AAEE,qBAAeL,QAFjB;AAGE,YAAMK,KAHR;AAIE,oBAAcJ,aAAa,CAACE,KAAKK,IAAL,CAAUH,KAAV,CAAd,GAAkCD,KAAKK,KAAL,IAAc,gCAAhD,GAAoFC,SAJpG;AAKE,aAAON,KAAKO,KALd;AAHJ,GADF;AAaD;;AAEDZ,iBAAiBa,SAAjB,GAA6B;AAC3BT,QAAMU,oBAAUC,MAAV,CAAiBC,UADI;AAE3Bf,YAAUa,oBAAUG,IAAV,CAAeD,UAFE;AAG3Bb,iBAAeW,oBAAUC,MAHE;AAI3Bb,aAAWY,oBAAUI;AAJM,CAA7B","file":"PreSubmitSection.js","sourcesContent":["import PropTypes from 'prop-types';\nimport React from 'react';\nimport ErrorableCheckbox from './ErrorableCheckbox';\n\nexport function PreSubmitSection({ onChange, showError, preSubmitInfo, form }) {\n const info = preSubmitInfo || {};\n const field = info.field || 'AGREED';\n\n return (\n
\n {info.notice}\n {info.required &&\n \n }\n
\n );\n}\n\nPreSubmitSection.propTypes = {\n form: PropTypes.object.isRequired,\n onChange: PropTypes.func.isRequired,\n preSubmitInfo: PropTypes.object,\n showError: PropTypes.bool\n};\n"]} \ No newline at end of file +{"version":3,"sources":["../../../src/js/components/PreSubmitSection.jsx"],"names":["PreSubmitSection","onChange","showError","preSubmitInfo","checked","notice","required","field","error","undefined","label","propTypes","PropTypes","func","isRequired","object","bool"],"mappings":";;;;;QAIgBA,gB,GAAAA,gB;;AAJhB;;;;AACA;;;;AACA;;;;;;AAEO,SAASA,gBAAT,OAA2E;AAAA,MAA/CC,QAA+C,QAA/CA,QAA+C;AAAA,MAArCC,SAAqC,QAArCA,SAAqC;AAAA,MAA1BC,aAA0B,QAA1BA,aAA0B;AAAA,MAAXC,OAAW,QAAXA,OAAW;;;AAEhF,SACE;AAAA;AAAA;AACGD,kBAAcE,MADjB;AAEGF,kBAAcG,QAAd,IACC,8BAAC,2BAAD,IAAmB,cAAnB;AACE,eAASF,OADX;AAEE,qBAAeH,QAFjB;AAGE,YAAME,cAAcI,KAHtB;AAIE,oBAAcL,aAAa,CAACE,OAAd,GAAyBD,cAAcK,KAAd,IAAuB,eAAhD,GAAmEC,SAJnF;AAKE,aAAON,cAAcO,KALvB;AAHJ,GADF;AAaD;;AAEDV,iBAAiBW,SAAjB,GAA6B;AAC3BV,YAAUW,oBAAUC,IAAV,CAAeC,UADE;AAE3BX,iBAAeS,oBAAUG,MAAV,CAAiBD,UAFL;AAG3BZ,aAAWU,oBAAUI;AAHM,CAA7B","file":"PreSubmitSection.js","sourcesContent":["import PropTypes from 'prop-types';\nimport React from 'react';\nimport ErrorableCheckbox from './ErrorableCheckbox';\n\nexport function PreSubmitSection({ onChange, showError, preSubmitInfo, checked }) {\n\n return (\n
\n {preSubmitInfo.notice}\n {preSubmitInfo.required &&\n \n }\n
\n );\n}\n\nPreSubmitSection.propTypes = {\n onChange: PropTypes.func.isRequired,\n preSubmitInfo: PropTypes.object.isRequired,\n showError: PropTypes.bool\n};\n"]} \ No newline at end of file diff --git a/lib/js/review/SubmitController.js b/lib/js/review/SubmitController.js index 4c53b84..306376c 100644 --- a/lib/js/review/SubmitController.js +++ b/lib/js/review/SubmitController.js @@ -5,6 +5,8 @@ Object.defineProperty(exports, "__esModule", { }); exports.SubmitController = undefined; +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; }; }(); var _react = require('react'); @@ -57,7 +59,14 @@ var SubmitController = function (_React$Component) { args[_key] = arguments[_key]; } - return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_ref = SubmitController.__proto__ || Object.getPrototypeOf(SubmitController)).call.apply(_ref, [this].concat(args))), _this), _this.goBack = function () { + return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_ref = SubmitController.__proto__ || Object.getPrototypeOf(SubmitController)).call.apply(_ref, [this].concat(args))), _this), _this.getPreSubmit = function (formConfig) { + return _extends({ + required: false, + field: 'AGREED', + label: 'I agree to the terms and conditions.', + error: 'You must accept the agreement before submitting.' + }, formConfig.preSubmitInfo); + }, _this.goBack = function () { var _this$props = _this.props, form = _this$props.form, pageList = _this$props.pageList, @@ -66,6 +75,9 @@ var SubmitController = function (_React$Component) { var expandedPageList = (0, _helpers.getActiveExpandedPages)(pageList, form.data); + // TODO: Fix this bug that assumes there is a confirmation page. + // Actually, it assumes the app also doesn't add routes at the end! + // A component at this level should not need to know these things! router.push(expandedPageList[expandedPageList.length - 2].path); }, _this.handleSubmit = function () { var _this$props2 = _this.props, @@ -74,40 +86,39 @@ var SubmitController = function (_React$Component) { pagesByChapter = _this$props2.pagesByChapter, trackingPrefix = _this$props2.trackingPrefix; + // If a pre-submit agreement is required, make sure it was accepted - var isValid = void 0; - var errors = void 0; - - // If a pre-submit agreement was specified, it has to be accepted first - var preSubmitField = formConfig.preSubmitInfo && formConfig.preSubmitInfo.required && (formConfig.preSubmitInfo.field || 'AGREED'); - if (preSubmitField && !form.data[preSubmitField]) { - isValid = false; - } else { - var _isValidForm = (0, _validation.isValidForm)(form, pagesByChapter); - - isValid = _isValidForm.isValid; - errors = _isValidForm.errors; + var preSubmit = _this.getPreSubmit(formConfig); + if (preSubmit.required && !form.data[preSubmit.field]) { + _this.props.setSubmission('hasAttemptedSubmit', true); + // is displaying an error for this case + return; } - if (isValid) { - _this.props.submitForm(formConfig, form); - } else { - // validation errors in this situation are not visible, so we’d - // like to know if they’re common - if (preSubmitField && form.data[preSubmitField]) { - (0, _helpers.recordEvent)({ - event: trackingPrefix + '-validation-failed' - }); - _ravenJs2.default.captureMessage('Validation issue not displayed', { - extra: { - errors: errors, - prefix: trackingPrefix - } - }); - _this.props.setSubmission('status', 'validationError'); - } + // Validation errors in this situation are not visible, so we’d + // like to know if they’re common + + var _isValidForm = (0, _validation.isValidForm)(form, pagesByChapter), + isValid = _isValidForm.isValid, + errors = _isValidForm.errors; + + if (!isValid) { + (0, _helpers.recordEvent)({ + event: trackingPrefix + '-validation-failed' + }); + _ravenJs2.default.captureMessage('Validation issue not displayed', { + extra: { + errors: errors, + prefix: trackingPrefix + } + }); + _this.props.setSubmission('status', 'validationError'); _this.props.setSubmission('hasAttemptedSubmit', true); + return; } + + // User accepted if required, and no errors, so submit + _this.props.submitForm(formConfig, form); }, _temp), _possibleConstructorReturn(_this, _ret); } @@ -130,24 +141,24 @@ var SubmitController = function (_React$Component) { form = _props.form, formConfig = _props.formConfig, showPreSubmitError = _props.showPreSubmitError, - renderErrorMessage = _props.renderErrorMessage, - submission = _props.submission; + renderErrorMessage = _props.renderErrorMessage; + + var preSubmit = this.getPreSubmit(formConfig); return _react2.default.createElement( 'div', null, - this.preSubmitInfo && _react2.default.createElement(_PreSubmitSection.PreSubmitSection, { - required: true, - preSubmitInfo: formConfig.preSubmitInfo, - onChange: function onChange() { - return _this2.props.setPreSubmit(formConfig.preSubmitInfo.field, _this2.value); + _react2.default.createElement(_PreSubmitSection.PreSubmitSection, { + preSubmitInfo: preSubmit, + onChange: function onChange(event) { + return _this2.props.setPreSubmit(preSubmit.field, event.target.value); }, - form: form, + checked: form.data[preSubmit.field], showError: showPreSubmitError }), _react2.default.createElement(_SubmitButtons2.default, { onBack: this.goBack, onSubmit: this.handleSubmit, - submission: submission, + submission: form.submission, renderErrorMessage: renderErrorMessage }) ); } diff --git a/lib/js/review/SubmitController.js.map b/lib/js/review/SubmitController.js.map index 181f927..b369fda 100644 --- a/lib/js/review/SubmitController.js.map +++ b/lib/js/review/SubmitController.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/js/review/SubmitController.jsx"],"names":["SubmitController","goBack","props","form","pageList","router","expandedPageList","data","push","length","path","handleSubmit","formConfig","pagesByChapter","trackingPrefix","isValid","errors","preSubmitField","preSubmitInfo","required","field","submitForm","event","Raven","captureMessage","extra","prefix","setSubmission","nextProps","nextStatus","submission","status","previousStatus","newRoute","urlPrefix","showPreSubmitError","renderErrorMessage","setPreSubmit","value","React","Component","mapStateToProps","state","ownProps","hasAttemptedSubmit","mapDispatchToProps","propTypes","PropTypes","object","isRequired","array","func","string"],"mappings":";;;;;;;;;AAAA;;;;AACA;;;;AACA;;;;AACA;;AACA;;AAEA;;;;AACA;;AACA;;AACA;;AAKA;;;;;;;;;;IAMMA,gB;;;;;;;;;;;;;;0MAWJC,M,GAAS,YAAM;AAAA,wBAKT,MAAKC,KALI;AAAA,UAEXC,IAFW,eAEXA,IAFW;AAAA,UAGXC,QAHW,eAGXA,QAHW;AAAA,UAIXC,MAJW,eAIXA,MAJW;;;AAOb,UAAMC,mBAAmB,qCAAuBF,QAAvB,EAAiCD,KAAKI,IAAtC,CAAzB;;AAEAF,aAAOG,IAAP,CAAYF,iBAAiBA,iBAAiBG,MAAjB,GAA0B,CAA3C,EAA8CC,IAA1D;AACD,K,QAEDC,Y,GAAe,YAAM;AAAA,yBAMf,MAAKT,KANU;AAAA,UAEjBC,IAFiB,gBAEjBA,IAFiB;AAAA,UAGjBS,UAHiB,gBAGjBA,UAHiB;AAAA,UAIjBC,cAJiB,gBAIjBA,cAJiB;AAAA,UAKjBC,cALiB,gBAKjBA,cALiB;;;AAQnB,UAAIC,gBAAJ;AACA,UAAIC,eAAJ;;AAEA;AACA,UAAMC,iBAAiBL,WAAWM,aAAX,IACnBN,WAAWM,aAAX,CAAyBC,QADN,KACmBP,WAAWM,aAAX,CAAyBE,KAAzB,IAAkC,QADrD,CAAvB;AAEA,UAAIH,kBAAkB,CAACd,KAAKI,IAAL,CAAUU,cAAV,CAAvB,EAAkD;AAChDF,kBAAU,KAAV;AACD,OAFD,MAEO;AAAA,2BACkB,6BAAYZ,IAAZ,EAAkBU,cAAlB,CADlB;;AACFE,eADE,gBACFA,OADE;AACOC,cADP,gBACOA,MADP;AAEN;;AAED,UAAID,OAAJ,EAAa;AACX,cAAKb,KAAL,CAAWmB,UAAX,CAAsBT,UAAtB,EAAkCT,IAAlC;AACD,OAFD,MAEO;AACL;AACA;AACA,YAAIc,kBAAkBd,KAAKI,IAAL,CAAUU,cAAV,CAAtB,EAAiD;AAC/C,oCAAY;AACVK,mBAAUR,cAAV;AADU,WAAZ;AAGAS,4BAAMC,cAAN,CAAqB,gCAArB,EAAuD;AACrDC,mBAAO;AACLT,4BADK;AAELU,sBAAQZ;AAFH;AAD8C,WAAvD;AAMA,gBAAKZ,KAAL,CAAWyB,aAAX,CAAyB,QAAzB,EAAmC,iBAAnC;AACD;AACD,cAAKzB,KAAL,CAAWyB,aAAX,CAAyB,oBAAzB,EAA+C,IAA/C;AACD;AACF,K;;;;;8CA5DyBC,S,EAAW;AACnC,UAAMC,aAAaD,UAAUzB,IAAV,CAAe2B,UAAf,CAA0BC,MAA7C;AACA,UAAMC,iBAAiB,KAAK9B,KAAL,CAAWC,IAAX,CAAgB2B,UAAhB,CAA2BC,MAAlD;AACA,UAAIF,eAAeG,cAAf,IAAiCH,eAAe,sBAApD,EAA4E;AAC1E,YAAMI,WAAcL,UAAUhB,UAAV,CAAqBsB,SAAnC,iBAAN;AACA,aAAKhC,KAAL,CAAWG,MAAX,CAAkBG,IAAlB,CAAuByB,QAAvB;AACD;AACF;;;6BAuDQ;AAAA;;AAAA,mBAOH,KAAK/B,KAPF;AAAA,UAELC,IAFK,UAELA,IAFK;AAAA,UAGLS,UAHK,UAGLA,UAHK;AAAA,UAILuB,kBAJK,UAILA,kBAJK;AAAA,UAKLC,kBALK,UAKLA,kBALK;AAAA,UAMLN,UANK,UAMLA,UANK;;AAQP,aACE;AAAA;AAAA;AACI,aAAKZ,aAAL,IAAsB,8BAAC,kCAAD;AACtB,wBADsB;AAEtB,yBAAeN,WAAWM,aAFJ;AAGtB,oBAAU;AAAA,mBAAM,OAAKhB,KAAL,CAAWmC,YAAX,CAAwBzB,WAAWM,aAAX,CAAyBE,KAAjD,EAAwD,OAAKkB,KAA7D,CAAN;AAAA,WAHY;AAItB,gBAAMnC,IAJgB;AAKtB,qBAAWgC,kBALW,GAD1B;AAOE,sCAAC,uBAAD;AACE,kBAAQ,KAAKlC,MADf;AAEE,oBAAU,KAAKU,YAFjB;AAGE,sBAAYmB,UAHd;AAIE,8BAAoBM,kBAJtB;AAPF,OADF;AAeD;;;;EAvF4BG,gBAAMC,S;;AA0FrC,SAASC,eAAT,CAAyBC,KAAzB,EAAgCC,QAAhC,EAA0C;AAAA,MAEtC/B,UAFsC,GAKpC+B,QALoC,CAEtC/B,UAFsC;AAAA,MAGtCR,QAHsC,GAKpCuC,QALoC,CAGtCvC,QAHsC;AAAA,MAItCgC,kBAJsC,GAKpCO,QALoC,CAItCP,kBAJsC;;AAMxC,MAAM/B,SAASsC,SAAStC,MAAxB;;AAEA,MAAMF,OAAOuC,MAAMvC,IAAnB;AACA,MAAMU,iBAAiB,sCAAwBD,UAAxB,CAAvB;AACA,MAAME,iBAAiBF,WAAWE,cAAlC;AACA,MAAMgB,aAAa3B,KAAK2B,UAAxB;AACA,MAAMK,qBAAqBL,WAAWc,kBAAtC;;AAEA,SAAO;AACLzC,cADK;AAELS,0BAFK;AAGLC,kCAHK;AAILT,sBAJK;AAKLgC,0CALK;AAML/B,kBANK;AAOLyB,0BAPK;AAQLK,0CARK;AASLrB;AATK,GAAP;AAWD;;AAED,IAAM+B,qBAAqB;AACzBR,qCADyB;AAEzBV,uCAFyB;AAGzBN;AAHyB,CAA3B;;AAMArB,iBAAiB8C,SAAjB,GAA6B;AAC3B3C,QAAM4C,oBAAUC,MAAV,CAAiBC,UADI;AAE3BrC,cAAYmC,oBAAUC,MAAV,CAAiBC,UAFF;AAG3BpC,kBAAgBkC,oBAAUC,MAAV,CAAiBC,UAHN;AAI3B7C,YAAU2C,oBAAUG,KAAV,CAAgBD,UAJC;AAK3Bb,sBAAoBW,oBAAUI,IALH;AAM3B9C,UAAQ0C,oBAAUC,MAAV,CAAiBC,UANE;AAO3BZ,gBAAcU,oBAAUI,IAAV,CAAeF,UAPF;AAQ3BtB,iBAAeoB,oBAAUI,IAAV,CAAeF,UARH;AAS3B5B,cAAY0B,oBAAUI,IAAV,CAAeF,UATA;AAU3BnB,cAAYiB,oBAAUC,MAAV,CAAiBC,UAVF;AAW3BnC,kBAAgBiC,oBAAUK,MAAV,CAAiBH;AAXN,CAA7B;;kBAce,6BAAW,yBAAQR,eAAR,EAAyBI,kBAAzB,EAA6C7C,gBAA7C,CAAX,C;;AAEf;;QACSA,gB,GAAAA,gB","file":"SubmitController.js","sourcesContent":["import React from 'react';\nimport Raven from 'raven-js';\nimport PropTypes from 'prop-types';\nimport { connect } from 'react-redux';\nimport { withRouter } from 'react-router';\n\nimport SubmitButtons from './SubmitButtons';\nimport { PreSubmitSection } from '../components/PreSubmitSection';\nimport { isValidForm } from '../validation';\nimport {\n createPageListByChapter,\n getActiveExpandedPages,\n recordEvent\n} from '../helpers';\nimport {\n setPreSubmit,\n setSubmission,\n submitForm\n} from '../actions';\n\nclass SubmitController extends React.Component {\n\n componentWillReceiveProps(nextProps) {\n const nextStatus = nextProps.form.submission.status;\n const previousStatus = this.props.form.submission.status;\n if (nextStatus !== previousStatus && nextStatus === 'applicationSubmitted') {\n const newRoute = `${nextProps.formConfig.urlPrefix}confirmation`;\n this.props.router.push(newRoute);\n }\n }\n\n goBack = () => {\n const {\n form,\n pageList,\n router\n } = this.props;\n\n const expandedPageList = getActiveExpandedPages(pageList, form.data);\n\n router.push(expandedPageList[expandedPageList.length - 2].path);\n }\n\n handleSubmit = () => {\n const {\n form,\n formConfig,\n pagesByChapter,\n trackingPrefix\n } = this.props;\n\n let isValid;\n let errors;\n\n // If a pre-submit agreement was specified, it has to be accepted first\n const preSubmitField = formConfig.preSubmitInfo &&\n formConfig.preSubmitInfo.required && (formConfig.preSubmitInfo.field || 'AGREED');\n if (preSubmitField && !form.data[preSubmitField]) {\n isValid = false;\n } else {\n ({ isValid, errors } = isValidForm(form, pagesByChapter));\n }\n\n if (isValid) {\n this.props.submitForm(formConfig, form);\n } else {\n // validation errors in this situation are not visible, so we’d\n // like to know if they’re common\n if (preSubmitField && form.data[preSubmitField]) {\n recordEvent({\n event: `${trackingPrefix}-validation-failed`,\n });\n Raven.captureMessage('Validation issue not displayed', {\n extra: {\n errors,\n prefix: trackingPrefix\n }\n });\n this.props.setSubmission('status', 'validationError');\n }\n this.props.setSubmission('hasAttemptedSubmit', true);\n }\n }\n\n render() {\n const {\n form,\n formConfig,\n showPreSubmitError,\n renderErrorMessage,\n submission\n } = this.props;\n return (\n
\n { this.preSubmitInfo && this.props.setPreSubmit(formConfig.preSubmitInfo.field, this.value)}\n form={form}\n showError={showPreSubmitError}/> }\n \n
\n );\n }\n}\n\nfunction mapStateToProps(state, ownProps) {\n const {\n formConfig,\n pageList,\n renderErrorMessage\n } = ownProps;\n const router = ownProps.router;\n\n const form = state.form;\n const pagesByChapter = createPageListByChapter(formConfig);\n const trackingPrefix = formConfig.trackingPrefix;\n const submission = form.submission;\n const showPreSubmitError = submission.hasAttemptedSubmit;\n\n return {\n form,\n formConfig,\n pagesByChapter,\n pageList,\n renderErrorMessage,\n router,\n submission,\n showPreSubmitError,\n trackingPrefix\n };\n}\n\nconst mapDispatchToProps = {\n setPreSubmit,\n setSubmission,\n submitForm\n};\n\nSubmitController.propTypes = {\n form: PropTypes.object.isRequired,\n formConfig: PropTypes.object.isRequired,\n pagesByChapter: PropTypes.object.isRequired,\n pageList: PropTypes.array.isRequired,\n renderErrorMessage: PropTypes.func,\n router: PropTypes.object.isRequired,\n setPreSubmit: PropTypes.func.isRequired,\n setSubmission: PropTypes.func.isRequired,\n submitForm: PropTypes.func.isRequired,\n submission: PropTypes.object.isRequired,\n trackingPrefix: PropTypes.string.isRequired\n};\n\nexport default withRouter(connect(mapStateToProps, mapDispatchToProps)(SubmitController));\n\n// for tests\nexport { SubmitController };\n"]} \ No newline at end of file +{"version":3,"sources":["../../../src/js/review/SubmitController.jsx"],"names":["SubmitController","getPreSubmit","required","field","label","error","formConfig","preSubmitInfo","goBack","props","form","pageList","router","expandedPageList","data","push","length","path","handleSubmit","pagesByChapter","trackingPrefix","preSubmit","setSubmission","isValid","errors","event","Raven","captureMessage","extra","prefix","submitForm","nextProps","nextStatus","submission","status","previousStatus","newRoute","urlPrefix","showPreSubmitError","renderErrorMessage","setPreSubmit","target","value","React","Component","mapStateToProps","state","ownProps","hasAttemptedSubmit","mapDispatchToProps","propTypes","PropTypes","object","isRequired","array","func","string"],"mappings":"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","file":"SubmitController.js","sourcesContent":["import React from 'react';\nimport Raven from 'raven-js';\nimport PropTypes from 'prop-types';\nimport { connect } from 'react-redux';\nimport { withRouter } from 'react-router';\n\nimport SubmitButtons from './SubmitButtons';\nimport { PreSubmitSection } from '../components/PreSubmitSection';\nimport { isValidForm } from '../validation';\nimport {\n createPageListByChapter,\n getActiveExpandedPages,\n recordEvent\n} from '../helpers';\nimport {\n setPreSubmit,\n setSubmission,\n submitForm\n} from '../actions';\n\nclass SubmitController extends React.Component {\n\n componentWillReceiveProps(nextProps) {\n const nextStatus = nextProps.form.submission.status;\n const previousStatus = this.props.form.submission.status;\n if (nextStatus !== previousStatus && nextStatus === 'applicationSubmitted') {\n const newRoute = `${nextProps.formConfig.urlPrefix}confirmation`;\n this.props.router.push(newRoute);\n }\n }\n\n getPreSubmit = formConfig => {\n return {\n required: false,\n field: 'AGREED',\n label: 'I agree to the terms and conditions.',\n error: 'You must accept the agreement before submitting.',\n ...formConfig.preSubmitInfo\n };\n }\n\n goBack = () => {\n const {\n form,\n pageList,\n router\n } = this.props;\n\n const expandedPageList = getActiveExpandedPages(pageList, form.data);\n\n // TODO: Fix this bug that assumes there is a confirmation page.\n // Actually, it assumes the app also doesn't add routes at the end!\n // A component at this level should not need to know these things!\n router.push(expandedPageList[expandedPageList.length - 2].path);\n }\n\n handleSubmit = () => {\n const {\n form,\n formConfig,\n pagesByChapter,\n trackingPrefix\n } = this.props;\n\n // If a pre-submit agreement is required, make sure it was accepted\n const preSubmit = this.getPreSubmit(formConfig);\n if (preSubmit.required && !form.data[preSubmit.field]) {\n this.props.setSubmission('hasAttemptedSubmit', true);\n // is displaying an error for this case\n return;\n }\n\n // Validation errors in this situation are not visible, so we’d\n // like to know if they’re common\n const { isValid, errors } = isValidForm(form, pagesByChapter);\n if (!isValid) {\n recordEvent({\n event: `${trackingPrefix}-validation-failed`,\n });\n Raven.captureMessage('Validation issue not displayed', {\n extra: {\n errors,\n prefix: trackingPrefix\n }\n });\n this.props.setSubmission('status', 'validationError');\n this.props.setSubmission('hasAttemptedSubmit', true);\n return;\n }\n\n // User accepted if required, and no errors, so submit\n this.props.submitForm(formConfig, form);\n }\n\n render() {\n const {\n form,\n formConfig,\n showPreSubmitError,\n renderErrorMessage\n } = this.props;\n const preSubmit = this.getPreSubmit(formConfig);\n\n return (\n
\n this.props.setPreSubmit(preSubmit.field, event.target.value)}\n checked={form.data[preSubmit.field]}\n showError={showPreSubmitError}/>\n \n
\n );\n }\n}\n\nfunction mapStateToProps(state, ownProps) {\n const {\n formConfig,\n pageList,\n renderErrorMessage\n } = ownProps;\n const router = ownProps.router;\n\n const form = state.form;\n const pagesByChapter = createPageListByChapter(formConfig);\n const trackingPrefix = formConfig.trackingPrefix;\n const submission = form.submission;\n const showPreSubmitError = submission.hasAttemptedSubmit;\n\n return {\n form,\n formConfig,\n pagesByChapter,\n pageList,\n renderErrorMessage,\n router,\n submission,\n showPreSubmitError,\n trackingPrefix\n };\n}\n\nconst mapDispatchToProps = {\n setPreSubmit,\n setSubmission,\n submitForm\n};\n\nSubmitController.propTypes = {\n form: PropTypes.object.isRequired,\n formConfig: PropTypes.object.isRequired,\n pagesByChapter: PropTypes.object.isRequired,\n pageList: PropTypes.array.isRequired,\n renderErrorMessage: PropTypes.func,\n router: PropTypes.object.isRequired,\n setPreSubmit: PropTypes.func.isRequired,\n setSubmission: PropTypes.func.isRequired,\n submitForm: PropTypes.func.isRequired,\n submission: PropTypes.object.isRequired,\n trackingPrefix: PropTypes.string.isRequired\n};\n\nexport default withRouter(connect(mapStateToProps, mapDispatchToProps)(SubmitController));\n\n// for tests\nexport { SubmitController };\n"]} \ No newline at end of file diff --git a/lib/js/state/helpers.js b/lib/js/state/helpers.js index ed45c40..c6319dc 100644 --- a/lib/js/state/helpers.js +++ b/lib/js/state/helpers.js @@ -563,12 +563,6 @@ function createInitialState(formConfig) { pages: {} }); - // Initialize the preSubmit flag if one was specified - var preSubmitInfo = formConfig.preSubmitInfo; - if (preSubmitInfo && preSubmitInfo.field) { - pageAndDataState.data[preSubmitInfo.field] = false; - } - initialState = (0, _assign3.default)(initialState, pageAndDataState); // Take another pass and recalculate the schema and data based on the default data // We do this to avoid passing undefined for the whole form state when the form first renders diff --git a/lib/js/state/helpers.js.map b/lib/js/state/helpers.js.map index 65e66eb..b45bf4a 100644 --- a/lib/js/state/helpers.js.map +++ b/lib/js/state/helpers.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/js/state/helpers.js"],"names":["updateRequiredFields","isContentExpanded","setHiddenFields","removeHiddenData","updateSchemaFromUiSchema","replaceRefSchemas","updateItemsSchema","updateSchemaAndData","recalculateSchemaAndData","createInitialState","isHiddenField","schema","get","path","data","reduce","current","next","uiSchema","formData","index","type","newRequired","Object","keys","properties","requiredArray","nextProp","field","isRequired","arrayHasField","some","prop","filter","concat","required","newSchema","currentSchema","nextSchema","length","newItemSchemas","items","map","item","idx","newItem","matcher","containingObject","slice","updatedSchema","hideIf","expandUnder","expandUnderCondition","newProperties","undefined","nextData","newItems","updateSchema","newSchemaProps","definitions","Error","$ref","refPath","replace","split","definition","fieldData","Array","isArray","additionalItems","fillIn","fill","updatedItems","newData","initialState","pages","state","pageKey","page","newState","showPagePerItem","arrayData","arrayPath","editMode","formConfig","submission","status","errorMessage","id","timestamp","hasAttemptedSubmit","formId","loadedData","metadata","reviewPageView","openChapters","viewedPages","Set","trackingPrefix","pageAndDataState","defaultDefinitions","isArrayPage","initialData","itemFilter","preSubmitInfo"],"mappings":"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","file":"helpers.js","sourcesContent":["import _ from 'lodash/fp';\nimport { getDefaultFormState } from '@department-of-veterans-affairs/react-jsonschema-form/lib/utils';\n\nimport {\n checkValidSchema,\n createFormPageList\n} from '../helpers';\n\nfunction isHiddenField(schema) {\n return !!schema['ui:collapsed'] || !!schema['ui:hidden'];\n}\n\nfunction get(path, data) {\n return path.reduce((current, next) => {\n return typeof current === 'undefined' ? current : current[next];\n }, data);\n}\n\n/*\n * This function goes through a schema/uiSchema and updates the required array\n * based on any ui:required field properties in the uiSchema.\n *\n * If no required fields are changing, it makes sure to not mutate the existing schema,\n * so we can still take advantage of any shouldComponentUpdate optimizations\n */\nexport function updateRequiredFields(schema, uiSchema, formData, index = null) {\n if (!uiSchema) {\n return schema;\n }\n\n if (schema.type === 'object') {\n const newRequired = Object.keys(schema.properties).reduce((requiredArray, nextProp) => {\n const field = uiSchema[nextProp];\n if (field && field['ui:required']) {\n const isRequired = field['ui:required'](formData, index);\n const arrayHasField = requiredArray.some(prop => prop === nextProp);\n\n if (arrayHasField && !isRequired) {\n return requiredArray.filter(prop => prop !== nextProp);\n } else if (!arrayHasField && isRequired) {\n return requiredArray.concat(nextProp);\n }\n\n return requiredArray;\n }\n\n return requiredArray;\n }, schema.required || []);\n\n const newSchema = Object.keys(schema.properties).reduce((currentSchema, nextProp) => {\n if (uiSchema) {\n const nextSchema = updateRequiredFields(currentSchema.properties[nextProp], uiSchema[nextProp], formData, index);\n if (nextSchema !== currentSchema.properties[nextProp]) {\n return _.set(['properties', nextProp], nextSchema, currentSchema);\n }\n }\n\n return currentSchema;\n }, schema);\n\n if (newSchema.required !== newRequired && (newSchema.required || newRequired.length > 0)) {\n return _.set('required', newRequired, newSchema);\n }\n\n return newSchema;\n }\n\n if (schema.type === 'array') {\n // each item has its own schema, so we need to update the required fields on those schemas\n // and then check for differences\n const newItemSchemas = schema.items.map((item, idx) => updateRequiredFields(item, uiSchema.items, formData, idx));\n if (newItemSchemas.some((newItem, idx) => newItem !== schema.items[idx])) {\n return _.set('items', newItemSchemas, schema);\n }\n }\n\n return schema;\n}\n\nexport function isContentExpanded(data, matcher) {\n if (typeof matcher === 'undefined') {\n return !!data;\n } else if (typeof matcher === 'function') {\n return matcher(data);\n }\n\n return data === matcher;\n}\n\n/*\n * This steps through a schema and sets any fields to hidden, based on a\n * hideIf function from uiSchema and the current page data. Sets 'ui:hidden'\n * which is a non-standard JSON Schema property\n *\n * The path parameter will contain the path, relative to formData, to the\n * form data corresponding to the current schema object\n */\nexport function setHiddenFields(schema, uiSchema, formData, path = []) {\n if (!uiSchema) {\n return schema;\n }\n\n // expandUnder fields are relative to the parent object of the current\n // field, so get that object using path here\n const containingObject = get(path.slice(0, -1), formData) || formData;\n\n let updatedSchema = schema;\n const hideIf = get(['ui:options', 'hideIf'], uiSchema);\n const index = path.reduce((current, next) => {\n return typeof next === 'number' ? next : current;\n }, null);\n\n if (hideIf && hideIf(formData, index)) {\n if (!updatedSchema['ui:hidden']) {\n updatedSchema = _.set('ui:hidden', true, updatedSchema);\n }\n } else if (updatedSchema['ui:hidden']) {\n updatedSchema = _.unset('ui:hidden', updatedSchema);\n }\n\n const expandUnder = get(['ui:options', 'expandUnder'], uiSchema);\n const expandUnderCondition = get(['ui:options', 'expandUnderCondition'], uiSchema);\n if (expandUnder && !isContentExpanded(containingObject[expandUnder], expandUnderCondition)) {\n if (!updatedSchema['ui:collapsed']) {\n updatedSchema = _.set('ui:collapsed', true, updatedSchema);\n }\n } else if (updatedSchema['ui:collapsed']) {\n updatedSchema = _.unset('ui:collapsed', updatedSchema);\n }\n\n if (updatedSchema.type === 'object') {\n const newProperties = Object.keys(updatedSchema.properties).reduce((current, next) => {\n const newSchema = setHiddenFields(updatedSchema.properties[next], uiSchema[next], formData, path.concat(next));\n\n if (newSchema !== updatedSchema.properties[next]) {\n return _.set(next, newSchema, current);\n }\n\n return current;\n }, updatedSchema.properties);\n\n if (newProperties !== updatedSchema.properties) {\n return _.set('properties', newProperties, updatedSchema);\n }\n }\n\n if (updatedSchema.type === 'array') {\n // each item has its own schema, so we need to update the required fields on those schemas\n // and then check for differences\n const newItemSchemas = updatedSchema.items.map((item, idx) =>\n setHiddenFields(item, uiSchema.items, formData, path.concat(idx))\n );\n\n if (newItemSchemas.some((newItem, idx) => newItem !== updatedSchema.items[idx])) {\n return _.set('items', newItemSchemas, updatedSchema);\n }\n }\n\n return updatedSchema;\n}\n\n/*\n * Steps through data and removes any fields that are marked as hidden\n * This is done so that hidden fields don’t cause validation errors that\n * a user can’t see.\n */\nexport function removeHiddenData(schema, data) {\n // null is necessary here because Rails 4 will convert empty arrays to null\n // In the forms, there's no difference between an empty array and null or undefined\n if (isHiddenField(schema) || typeof data === 'undefined' || data === null) {\n return undefined;\n }\n\n if (schema.type === 'object') {\n return Object.keys(data).reduce((current, next) => {\n if (typeof data[next] !== 'undefined' && schema.properties[next]) {\n const nextData = removeHiddenData(schema.properties[next], data[next]);\n\n // if the data was removed, then just unset it\n if (typeof nextData === 'undefined') {\n return _.unset(next, current);\n }\n\n // if data was updated (like a nested prop was removed), update it\n if (nextData !== data[next]) {\n return _.set(next, nextData, current);\n }\n }\n\n return current;\n }, data);\n }\n\n if (schema.type === 'array') {\n const newItems = data.map((item, index) => {\n return removeHiddenData(schema.items[index], item);\n });\n\n if (newItems.some((newItem, idx) => newItem !== data[idx])) {\n return newItems;\n }\n\n return data;\n }\n\n return data;\n}\n\n/*\n * This is similar to the hidden fields schema function above, except more general.\n * It will step through a schema and replace parts of it based on an updateSchema\n * function in uiSchema. This means the schema can be re-calculated based on data\n * a user has entered.\n */\nexport function updateSchemaFromUiSchema(schema, uiSchema, formData, index = null, path = []) {\n if (!uiSchema) {\n return schema;\n }\n\n let currentSchema = schema;\n\n if (currentSchema.type === 'object') {\n const newSchema = Object.keys(currentSchema.properties).reduce((current, next) => {\n const nextProp = updateSchemaFromUiSchema(current.properties[next], uiSchema[next], formData, index, path.concat(next));\n\n if (current.properties[next] !== nextProp) {\n return _.set(['properties', next], nextProp, current);\n }\n\n return current;\n }, currentSchema);\n\n if (newSchema !== schema) {\n currentSchema = newSchema;\n }\n }\n\n if (currentSchema.type === 'array') {\n // each item has its own schema, so we need to update the required fields on those schemas\n // and then check for differences\n const newItemSchemas = currentSchema.items.map((item, idx) =>\n updateSchemaFromUiSchema(item, uiSchema.items, formData, idx, path.concat(idx))\n );\n\n if (newItemSchemas.some((newItem, idx) => newItem !== currentSchema.items[idx])) {\n currentSchema = _.set('items', newItemSchemas, currentSchema);\n }\n }\n\n const updateSchema = get(['ui:options', 'updateSchema'], uiSchema);\n\n if (updateSchema) {\n const newSchemaProps = updateSchema(formData, currentSchema, uiSchema, index, path);\n\n const newSchema = Object.keys(newSchemaProps).reduce((current, next) => {\n if (newSchemaProps[next] !== schema[next]) {\n return _.set(next, newSchemaProps[next], current);\n }\n\n return current;\n }, currentSchema);\n\n if (newSchema !== currentSchema) {\n return newSchema;\n }\n }\n\n return currentSchema;\n}\n\nexport function replaceRefSchemas(schema, definitions, path = '') {\n // this can happen if you import a field that doesn’t exist from a schema\n if (!schema) {\n throw new Error(`Schema is undefined at ${path}`);\n }\n if (schema.$ref) {\n // There’s a whole spec for JSON pointers, but we don’t use anything more complicated\n // than this so far\n const refPath = schema.$ref.replace('#/definitions/', '').split('/');\n const definition = get(refPath, definitions);\n if (!definition) {\n throw new Error(`Missing definition for ${schema.$ref} at ${path}. You probably need to add it to defaultDefinitions`);\n }\n\n return replaceRefSchemas(definition, definitions, path);\n }\n\n if (schema.type === 'object') {\n const newSchema = Object.keys(schema.properties).reduce((current, next) => {\n const nextProp = replaceRefSchemas(schema.properties[next], definitions, `${path}.${next}`);\n\n if (current.properties[next] !== nextProp) {\n return _.set(['properties', next], nextProp, current);\n }\n\n return current;\n }, schema);\n\n return newSchema;\n }\n\n if (schema.type === 'array') {\n const newItems = replaceRefSchemas(schema.items, definitions, `${path}.items`);\n\n if (newItems !== schema.items) {\n return _.set('items', newItems, schema);\n }\n }\n\n return schema;\n}\n\n/**\n * This function updates an array schema to use the array of\n * item schema format and keeps that array in sync with the\n * data in that array in the form data.\n *\n * This allows us to have conditional fields for each array item,\n * because our conditional field implementation depends on modifying\n * schemas\n *\n * @param {Object} schema The current JSON Schema object\n * @param {any} fieldData The data associated with the current schema\n */\nexport function updateItemsSchema(schema, fieldData = null) {\n if (schema.type === 'array') {\n let newSchema = schema;\n\n // This happens the first time this function is called when\n // generating the form\n if (!Array.isArray(schema.items)) {\n newSchema = _.assign(schema, {\n items: [],\n additionalItems: schema.items\n });\n }\n\n if (!fieldData) {\n // If there’s no data, the list of schemas should be empty\n newSchema = _.set('items', [], newSchema);\n } else if (fieldData.length > newSchema.items.length) {\n // Here we’re filling in the items array to make it the same\n // length as the array of form data. This happens when you add\n // another record on the form, mainly.\n const fillIn = Array(fieldData.length - newSchema.items.length)\n .fill(newSchema.additionalItems);\n newSchema = _.set('items', newSchema.items.concat(fillIn), newSchema);\n } else if (fieldData.length < newSchema.items.length) {\n // If someone removed a record we’re removing the last schema item\n // This may not be the actual removed schema, but the schemas will\n // always be updated in the next step\n newSchema = _.set('items', _.dropRight(1, newSchema.items), newSchema);\n }\n\n const updatedItems = newSchema.items.map(\n (item, index) => updateItemsSchema(item, fieldData[index])\n );\n if (newSchema.items.some((item, index) => item !== updatedItems[index])) {\n return _.set('items', updatedItems, newSchema);\n }\n\n return newSchema;\n }\n\n if (schema.type === 'object') {\n const newSchema = Object.keys(schema.properties).reduce((current, next) => {\n const nextProp = updateItemsSchema(schema.properties[next], fieldData ? fieldData[next] : null);\n\n if (current.properties[next] !== nextProp) {\n return _.set(['properties', next], nextProp, current);\n }\n\n return current;\n }, schema);\n\n return newSchema;\n }\n\n return schema;\n}\n\n/**\n * This is the main sequence of updates that happens when data is changed\n * on a form. Most updates are applied to the schema, except that the data\n * is updated to remove newly hidden data\n *\n * @param {Object} schema The current JSON Schema\n * @param {Object} uiSchema The current UI Schema (does not change)\n * @param {Object} formData Flattened data for the entire form\n */\nexport function updateSchemaAndData(schema, uiSchema, formData) {\n let newSchema = updateItemsSchema(schema, formData);\n newSchema = updateRequiredFields(newSchema, uiSchema, formData);\n\n // Update the schema with any fields that are now hidden because of the data change\n newSchema = setHiddenFields(newSchema, uiSchema, formData);\n\n // Update the schema with any general updates based on the new data\n newSchema = updateSchemaFromUiSchema(newSchema, uiSchema, formData);\n\n // Remove any data that’s now hidden in the schema\n const newData = removeHiddenData(newSchema, formData);\n\n // We need to do this again because array data might have been removed\n newSchema = updateItemsSchema(newSchema, newData);\n\n checkValidSchema(newSchema);\n\n return {\n data: newData,\n schema: newSchema\n };\n}\n\nexport function recalculateSchemaAndData(initialState) {\n return Object.keys(initialState.pages)\n .reduce((state, pageKey) => {\n // on each data change, we need to do the following steps\n // Recalculate any required fields, based on the new data\n const page = state.pages[pageKey];\n const formData = initialState.data;\n\n const { data, schema } = updateSchemaAndData(page.schema, page.uiSchema, formData);\n\n let newState = state;\n\n if (formData !== data) {\n newState = _.set('data', data, state);\n }\n\n if (page.schema !== schema) {\n newState = _.set(['pages', pageKey, 'schema'], schema, newState);\n }\n\n if (page.showPagePerItem) {\n const arrayData = _.get(page.arrayPath, newState.data) || [];\n // If an item was added or removed for the data used by a showPagePerItem page,\n // we have to reset everything because we can’t match the edit states to rows directly\n // This will rarely ever be noticeable\n if (page.editMode.length !== arrayData.length) {\n newState = _.set(['pages', pageKey, 'editMode'], arrayData.map(() => false), newState);\n }\n }\n\n return newState;\n }, initialState);\n}\n\nexport function createInitialState(formConfig) {\n let initialState = {\n submission: {\n status: false,\n errorMessage: false,\n id: false,\n timestamp: false,\n hasAttemptedSubmit: false\n },\n formId: formConfig.formId,\n loadedData: {\n formData: {},\n metadata: {}\n },\n reviewPageView: {\n openChapters: [],\n viewedPages: new Set()\n },\n trackingPrefix: formConfig.trackingPrefix\n };\n\n const pageAndDataState = createFormPageList(formConfig)\n .reduce((state, page) => {\n const definitions = _.assign(formConfig.defaultDefinitions || {}, page.schema.definitions);\n let schema = replaceRefSchemas(page.schema, definitions, page.pageKey);\n // Throw an error if the new schema is invalid\n checkValidSchema(schema);\n schema = updateItemsSchema(schema);\n const isArrayPage = page.showPagePerItem;\n const data = getDefaultFormState(schema, page.initialData, schema.definitions);\n\n /* eslint-disable no-param-reassign */\n state.pages[page.pageKey] = {\n uiSchema: page.uiSchema,\n schema,\n editMode: isArrayPage ? [] : false,\n showPagePerItem: page.showPagePerItem,\n arrayPath: page.arrayPath,\n itemFilter: page.itemFilter\n };\n\n state.data = _.merge(state.data, data);\n /* eslint-enable no-param-reassign */\n\n return state;\n }, {\n data: {},\n pages: {},\n });\n\n // Initialize the preSubmit flag if one was specified\n const preSubmitInfo = formConfig.preSubmitInfo;\n if (preSubmitInfo && preSubmitInfo.field) {\n pageAndDataState.data[preSubmitInfo.field] = false;\n }\n\n initialState = _.assign(initialState, pageAndDataState);\n // Take another pass and recalculate the schema and data based on the default data\n // We do this to avoid passing undefined for the whole form state when the form first renders\n initialState = recalculateSchemaAndData(initialState);\n\n return initialState;\n}\n"]} \ No newline at end of file +{"version":3,"sources":["../../../src/js/state/helpers.js"],"names":["updateRequiredFields","isContentExpanded","setHiddenFields","removeHiddenData","updateSchemaFromUiSchema","replaceRefSchemas","updateItemsSchema","updateSchemaAndData","recalculateSchemaAndData","createInitialState","isHiddenField","schema","get","path","data","reduce","current","next","uiSchema","formData","index","type","newRequired","Object","keys","properties","requiredArray","nextProp","field","isRequired","arrayHasField","some","prop","filter","concat","required","newSchema","currentSchema","nextSchema","length","newItemSchemas","items","map","item","idx","newItem","matcher","containingObject","slice","updatedSchema","hideIf","expandUnder","expandUnderCondition","newProperties","undefined","nextData","newItems","updateSchema","newSchemaProps","definitions","Error","$ref","refPath","replace","split","definition","fieldData","Array","isArray","additionalItems","fillIn","fill","updatedItems","newData","initialState","pages","state","pageKey","page","newState","showPagePerItem","arrayData","arrayPath","editMode","formConfig","submission","status","errorMessage","id","timestamp","hasAttemptedSubmit","formId","loadedData","metadata","reviewPageView","openChapters","viewedPages","Set","trackingPrefix","pageAndDataState","defaultDefinitions","isArrayPage","initialData","itemFilter"],"mappings":"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","file":"helpers.js","sourcesContent":["import _ from 'lodash/fp';\nimport { getDefaultFormState } from '@department-of-veterans-affairs/react-jsonschema-form/lib/utils';\n\nimport {\n checkValidSchema,\n createFormPageList\n} from '../helpers';\n\nfunction isHiddenField(schema) {\n return !!schema['ui:collapsed'] || !!schema['ui:hidden'];\n}\n\nfunction get(path, data) {\n return path.reduce((current, next) => {\n return typeof current === 'undefined' ? current : current[next];\n }, data);\n}\n\n/*\n * This function goes through a schema/uiSchema and updates the required array\n * based on any ui:required field properties in the uiSchema.\n *\n * If no required fields are changing, it makes sure to not mutate the existing schema,\n * so we can still take advantage of any shouldComponentUpdate optimizations\n */\nexport function updateRequiredFields(schema, uiSchema, formData, index = null) {\n if (!uiSchema) {\n return schema;\n }\n\n if (schema.type === 'object') {\n const newRequired = Object.keys(schema.properties).reduce((requiredArray, nextProp) => {\n const field = uiSchema[nextProp];\n if (field && field['ui:required']) {\n const isRequired = field['ui:required'](formData, index);\n const arrayHasField = requiredArray.some(prop => prop === nextProp);\n\n if (arrayHasField && !isRequired) {\n return requiredArray.filter(prop => prop !== nextProp);\n } else if (!arrayHasField && isRequired) {\n return requiredArray.concat(nextProp);\n }\n\n return requiredArray;\n }\n\n return requiredArray;\n }, schema.required || []);\n\n const newSchema = Object.keys(schema.properties).reduce((currentSchema, nextProp) => {\n if (uiSchema) {\n const nextSchema = updateRequiredFields(currentSchema.properties[nextProp], uiSchema[nextProp], formData, index);\n if (nextSchema !== currentSchema.properties[nextProp]) {\n return _.set(['properties', nextProp], nextSchema, currentSchema);\n }\n }\n\n return currentSchema;\n }, schema);\n\n if (newSchema.required !== newRequired && (newSchema.required || newRequired.length > 0)) {\n return _.set('required', newRequired, newSchema);\n }\n\n return newSchema;\n }\n\n if (schema.type === 'array') {\n // each item has its own schema, so we need to update the required fields on those schemas\n // and then check for differences\n const newItemSchemas = schema.items.map((item, idx) => updateRequiredFields(item, uiSchema.items, formData, idx));\n if (newItemSchemas.some((newItem, idx) => newItem !== schema.items[idx])) {\n return _.set('items', newItemSchemas, schema);\n }\n }\n\n return schema;\n}\n\nexport function isContentExpanded(data, matcher) {\n if (typeof matcher === 'undefined') {\n return !!data;\n } else if (typeof matcher === 'function') {\n return matcher(data);\n }\n\n return data === matcher;\n}\n\n/*\n * This steps through a schema and sets any fields to hidden, based on a\n * hideIf function from uiSchema and the current page data. Sets 'ui:hidden'\n * which is a non-standard JSON Schema property\n *\n * The path parameter will contain the path, relative to formData, to the\n * form data corresponding to the current schema object\n */\nexport function setHiddenFields(schema, uiSchema, formData, path = []) {\n if (!uiSchema) {\n return schema;\n }\n\n // expandUnder fields are relative to the parent object of the current\n // field, so get that object using path here\n const containingObject = get(path.slice(0, -1), formData) || formData;\n\n let updatedSchema = schema;\n const hideIf = get(['ui:options', 'hideIf'], uiSchema);\n const index = path.reduce((current, next) => {\n return typeof next === 'number' ? next : current;\n }, null);\n\n if (hideIf && hideIf(formData, index)) {\n if (!updatedSchema['ui:hidden']) {\n updatedSchema = _.set('ui:hidden', true, updatedSchema);\n }\n } else if (updatedSchema['ui:hidden']) {\n updatedSchema = _.unset('ui:hidden', updatedSchema);\n }\n\n const expandUnder = get(['ui:options', 'expandUnder'], uiSchema);\n const expandUnderCondition = get(['ui:options', 'expandUnderCondition'], uiSchema);\n if (expandUnder && !isContentExpanded(containingObject[expandUnder], expandUnderCondition)) {\n if (!updatedSchema['ui:collapsed']) {\n updatedSchema = _.set('ui:collapsed', true, updatedSchema);\n }\n } else if (updatedSchema['ui:collapsed']) {\n updatedSchema = _.unset('ui:collapsed', updatedSchema);\n }\n\n if (updatedSchema.type === 'object') {\n const newProperties = Object.keys(updatedSchema.properties).reduce((current, next) => {\n const newSchema = setHiddenFields(updatedSchema.properties[next], uiSchema[next], formData, path.concat(next));\n\n if (newSchema !== updatedSchema.properties[next]) {\n return _.set(next, newSchema, current);\n }\n\n return current;\n }, updatedSchema.properties);\n\n if (newProperties !== updatedSchema.properties) {\n return _.set('properties', newProperties, updatedSchema);\n }\n }\n\n if (updatedSchema.type === 'array') {\n // each item has its own schema, so we need to update the required fields on those schemas\n // and then check for differences\n const newItemSchemas = updatedSchema.items.map((item, idx) =>\n setHiddenFields(item, uiSchema.items, formData, path.concat(idx))\n );\n\n if (newItemSchemas.some((newItem, idx) => newItem !== updatedSchema.items[idx])) {\n return _.set('items', newItemSchemas, updatedSchema);\n }\n }\n\n return updatedSchema;\n}\n\n/*\n * Steps through data and removes any fields that are marked as hidden\n * This is done so that hidden fields don’t cause validation errors that\n * a user can’t see.\n */\nexport function removeHiddenData(schema, data) {\n // null is necessary here because Rails 4 will convert empty arrays to null\n // In the forms, there's no difference between an empty array and null or undefined\n if (isHiddenField(schema) || typeof data === 'undefined' || data === null) {\n return undefined;\n }\n\n if (schema.type === 'object') {\n return Object.keys(data).reduce((current, next) => {\n if (typeof data[next] !== 'undefined' && schema.properties[next]) {\n const nextData = removeHiddenData(schema.properties[next], data[next]);\n\n // if the data was removed, then just unset it\n if (typeof nextData === 'undefined') {\n return _.unset(next, current);\n }\n\n // if data was updated (like a nested prop was removed), update it\n if (nextData !== data[next]) {\n return _.set(next, nextData, current);\n }\n }\n\n return current;\n }, data);\n }\n\n if (schema.type === 'array') {\n const newItems = data.map((item, index) => {\n return removeHiddenData(schema.items[index], item);\n });\n\n if (newItems.some((newItem, idx) => newItem !== data[idx])) {\n return newItems;\n }\n\n return data;\n }\n\n return data;\n}\n\n/*\n * This is similar to the hidden fields schema function above, except more general.\n * It will step through a schema and replace parts of it based on an updateSchema\n * function in uiSchema. This means the schema can be re-calculated based on data\n * a user has entered.\n */\nexport function updateSchemaFromUiSchema(schema, uiSchema, formData, index = null, path = []) {\n if (!uiSchema) {\n return schema;\n }\n\n let currentSchema = schema;\n\n if (currentSchema.type === 'object') {\n const newSchema = Object.keys(currentSchema.properties).reduce((current, next) => {\n const nextProp = updateSchemaFromUiSchema(current.properties[next], uiSchema[next], formData, index, path.concat(next));\n\n if (current.properties[next] !== nextProp) {\n return _.set(['properties', next], nextProp, current);\n }\n\n return current;\n }, currentSchema);\n\n if (newSchema !== schema) {\n currentSchema = newSchema;\n }\n }\n\n if (currentSchema.type === 'array') {\n // each item has its own schema, so we need to update the required fields on those schemas\n // and then check for differences\n const newItemSchemas = currentSchema.items.map((item, idx) =>\n updateSchemaFromUiSchema(item, uiSchema.items, formData, idx, path.concat(idx))\n );\n\n if (newItemSchemas.some((newItem, idx) => newItem !== currentSchema.items[idx])) {\n currentSchema = _.set('items', newItemSchemas, currentSchema);\n }\n }\n\n const updateSchema = get(['ui:options', 'updateSchema'], uiSchema);\n\n if (updateSchema) {\n const newSchemaProps = updateSchema(formData, currentSchema, uiSchema, index, path);\n\n const newSchema = Object.keys(newSchemaProps).reduce((current, next) => {\n if (newSchemaProps[next] !== schema[next]) {\n return _.set(next, newSchemaProps[next], current);\n }\n\n return current;\n }, currentSchema);\n\n if (newSchema !== currentSchema) {\n return newSchema;\n }\n }\n\n return currentSchema;\n}\n\nexport function replaceRefSchemas(schema, definitions, path = '') {\n // this can happen if you import a field that doesn’t exist from a schema\n if (!schema) {\n throw new Error(`Schema is undefined at ${path}`);\n }\n if (schema.$ref) {\n // There’s a whole spec for JSON pointers, but we don’t use anything more complicated\n // than this so far\n const refPath = schema.$ref.replace('#/definitions/', '').split('/');\n const definition = get(refPath, definitions);\n if (!definition) {\n throw new Error(`Missing definition for ${schema.$ref} at ${path}. You probably need to add it to defaultDefinitions`);\n }\n\n return replaceRefSchemas(definition, definitions, path);\n }\n\n if (schema.type === 'object') {\n const newSchema = Object.keys(schema.properties).reduce((current, next) => {\n const nextProp = replaceRefSchemas(schema.properties[next], definitions, `${path}.${next}`);\n\n if (current.properties[next] !== nextProp) {\n return _.set(['properties', next], nextProp, current);\n }\n\n return current;\n }, schema);\n\n return newSchema;\n }\n\n if (schema.type === 'array') {\n const newItems = replaceRefSchemas(schema.items, definitions, `${path}.items`);\n\n if (newItems !== schema.items) {\n return _.set('items', newItems, schema);\n }\n }\n\n return schema;\n}\n\n/**\n * This function updates an array schema to use the array of\n * item schema format and keeps that array in sync with the\n * data in that array in the form data.\n *\n * This allows us to have conditional fields for each array item,\n * because our conditional field implementation depends on modifying\n * schemas\n *\n * @param {Object} schema The current JSON Schema object\n * @param {any} fieldData The data associated with the current schema\n */\nexport function updateItemsSchema(schema, fieldData = null) {\n if (schema.type === 'array') {\n let newSchema = schema;\n\n // This happens the first time this function is called when\n // generating the form\n if (!Array.isArray(schema.items)) {\n newSchema = _.assign(schema, {\n items: [],\n additionalItems: schema.items\n });\n }\n\n if (!fieldData) {\n // If there’s no data, the list of schemas should be empty\n newSchema = _.set('items', [], newSchema);\n } else if (fieldData.length > newSchema.items.length) {\n // Here we’re filling in the items array to make it the same\n // length as the array of form data. This happens when you add\n // another record on the form, mainly.\n const fillIn = Array(fieldData.length - newSchema.items.length)\n .fill(newSchema.additionalItems);\n newSchema = _.set('items', newSchema.items.concat(fillIn), newSchema);\n } else if (fieldData.length < newSchema.items.length) {\n // If someone removed a record we’re removing the last schema item\n // This may not be the actual removed schema, but the schemas will\n // always be updated in the next step\n newSchema = _.set('items', _.dropRight(1, newSchema.items), newSchema);\n }\n\n const updatedItems = newSchema.items.map(\n (item, index) => updateItemsSchema(item, fieldData[index])\n );\n if (newSchema.items.some((item, index) => item !== updatedItems[index])) {\n return _.set('items', updatedItems, newSchema);\n }\n\n return newSchema;\n }\n\n if (schema.type === 'object') {\n const newSchema = Object.keys(schema.properties).reduce((current, next) => {\n const nextProp = updateItemsSchema(schema.properties[next], fieldData ? fieldData[next] : null);\n\n if (current.properties[next] !== nextProp) {\n return _.set(['properties', next], nextProp, current);\n }\n\n return current;\n }, schema);\n\n return newSchema;\n }\n\n return schema;\n}\n\n/**\n * This is the main sequence of updates that happens when data is changed\n * on a form. Most updates are applied to the schema, except that the data\n * is updated to remove newly hidden data\n *\n * @param {Object} schema The current JSON Schema\n * @param {Object} uiSchema The current UI Schema (does not change)\n * @param {Object} formData Flattened data for the entire form\n */\nexport function updateSchemaAndData(schema, uiSchema, formData) {\n let newSchema = updateItemsSchema(schema, formData);\n newSchema = updateRequiredFields(newSchema, uiSchema, formData);\n\n // Update the schema with any fields that are now hidden because of the data change\n newSchema = setHiddenFields(newSchema, uiSchema, formData);\n\n // Update the schema with any general updates based on the new data\n newSchema = updateSchemaFromUiSchema(newSchema, uiSchema, formData);\n\n // Remove any data that’s now hidden in the schema\n const newData = removeHiddenData(newSchema, formData);\n\n // We need to do this again because array data might have been removed\n newSchema = updateItemsSchema(newSchema, newData);\n\n checkValidSchema(newSchema);\n\n return {\n data: newData,\n schema: newSchema\n };\n}\n\nexport function recalculateSchemaAndData(initialState) {\n return Object.keys(initialState.pages)\n .reduce((state, pageKey) => {\n // on each data change, we need to do the following steps\n // Recalculate any required fields, based on the new data\n const page = state.pages[pageKey];\n const formData = initialState.data;\n\n const { data, schema } = updateSchemaAndData(page.schema, page.uiSchema, formData);\n\n let newState = state;\n\n if (formData !== data) {\n newState = _.set('data', data, state);\n }\n\n if (page.schema !== schema) {\n newState = _.set(['pages', pageKey, 'schema'], schema, newState);\n }\n\n if (page.showPagePerItem) {\n const arrayData = _.get(page.arrayPath, newState.data) || [];\n // If an item was added or removed for the data used by a showPagePerItem page,\n // we have to reset everything because we can’t match the edit states to rows directly\n // This will rarely ever be noticeable\n if (page.editMode.length !== arrayData.length) {\n newState = _.set(['pages', pageKey, 'editMode'], arrayData.map(() => false), newState);\n }\n }\n\n return newState;\n }, initialState);\n}\n\nexport function createInitialState(formConfig) {\n let initialState = {\n submission: {\n status: false,\n errorMessage: false,\n id: false,\n timestamp: false,\n hasAttemptedSubmit: false\n },\n formId: formConfig.formId,\n loadedData: {\n formData: {},\n metadata: {}\n },\n reviewPageView: {\n openChapters: [],\n viewedPages: new Set()\n },\n trackingPrefix: formConfig.trackingPrefix\n };\n\n const pageAndDataState = createFormPageList(formConfig)\n .reduce((state, page) => {\n const definitions = _.assign(formConfig.defaultDefinitions || {}, page.schema.definitions);\n let schema = replaceRefSchemas(page.schema, definitions, page.pageKey);\n // Throw an error if the new schema is invalid\n checkValidSchema(schema);\n schema = updateItemsSchema(schema);\n const isArrayPage = page.showPagePerItem;\n const data = getDefaultFormState(schema, page.initialData, schema.definitions);\n\n /* eslint-disable no-param-reassign */\n state.pages[page.pageKey] = {\n uiSchema: page.uiSchema,\n schema,\n editMode: isArrayPage ? [] : false,\n showPagePerItem: page.showPagePerItem,\n arrayPath: page.arrayPath,\n itemFilter: page.itemFilter\n };\n\n state.data = _.merge(state.data, data);\n /* eslint-enable no-param-reassign */\n\n return state;\n }, {\n data: {},\n pages: {},\n });\n\n initialState = _.assign(initialState, pageAndDataState);\n // Take another pass and recalculate the schema and data based on the default data\n // We do this to avoid passing undefined for the whole form state when the form first renders\n initialState = recalculateSchemaAndData(initialState);\n\n return initialState;\n}\n"]} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index dfd4bcf..b2f2020 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3209,7 +3209,7 @@ }, "eslint": { "version": "4.19.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz", + "resolved": "http://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz", "integrity": "sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ==", "dev": true, "requires": { diff --git a/src/js/components/PreSubmitSection.jsx b/src/js/components/PreSubmitSection.jsx index 639c1e5..2bf6356 100644 --- a/src/js/components/PreSubmitSection.jsx +++ b/src/js/components/PreSubmitSection.jsx @@ -2,28 +2,25 @@ import PropTypes from 'prop-types'; import React from 'react'; import ErrorableCheckbox from './ErrorableCheckbox'; -export function PreSubmitSection({ onChange, showError, preSubmitInfo, form }) { - const info = preSubmitInfo || {}; - const field = info.field || 'AGREED'; +export function PreSubmitSection({ onChange, showError, preSubmitInfo, checked }) { return (
- {info.notice} - {info.required && + {preSubmitInfo.notice} + {preSubmitInfo.required && + name={preSubmitInfo.field} + errorMessage={showError && !checked ? (preSubmitInfo.error || 'Please accept') : undefined} + label={preSubmitInfo.label}/> }
); } PreSubmitSection.propTypes = { - form: PropTypes.object.isRequired, onChange: PropTypes.func.isRequired, - preSubmitInfo: PropTypes.object, + preSubmitInfo: PropTypes.object.isRequired, showError: PropTypes.bool }; diff --git a/src/js/review/SubmitController.jsx b/src/js/review/SubmitController.jsx index 16bcc65..66fd260 100644 --- a/src/js/review/SubmitController.jsx +++ b/src/js/review/SubmitController.jsx @@ -29,6 +29,16 @@ class SubmitController extends React.Component { } } + getPreSubmit = formConfig => { + return { + required: false, + field: 'AGREED', + label: 'I agree to the terms and conditions.', + error: 'You must accept the agreement before submitting.', + ...formConfig.preSubmitInfo + }; + } + goBack = () => { const { form, @@ -38,6 +48,9 @@ class SubmitController extends React.Component { const expandedPageList = getActiveExpandedPages(pageList, form.data); + // TODO: Fix this bug that assumes there is a confirmation page. + // Actually, it assumes the app also doesn't add routes at the end! + // A component at this level should not need to know these things! router.push(expandedPageList[expandedPageList.length - 2].path); } @@ -49,37 +62,34 @@ class SubmitController extends React.Component { trackingPrefix } = this.props; - let isValid; - let errors; - - // If a pre-submit agreement was specified, it has to be accepted first - const preSubmitField = formConfig.preSubmitInfo && - formConfig.preSubmitInfo.required && (formConfig.preSubmitInfo.field || 'AGREED'); - if (preSubmitField && !form.data[preSubmitField]) { - isValid = false; - } else { - ({ isValid, errors } = isValidForm(form, pagesByChapter)); + // If a pre-submit agreement is required, make sure it was accepted + const preSubmit = this.getPreSubmit(formConfig); + if (preSubmit.required && !form.data[preSubmit.field]) { + this.props.setSubmission('hasAttemptedSubmit', true); + // is displaying an error for this case + return; } - if (isValid) { - this.props.submitForm(formConfig, form); - } else { - // validation errors in this situation are not visible, so we’d - // like to know if they’re common - if (preSubmitField && form.data[preSubmitField]) { - recordEvent({ - event: `${trackingPrefix}-validation-failed`, - }); - Raven.captureMessage('Validation issue not displayed', { - extra: { - errors, - prefix: trackingPrefix - } - }); - this.props.setSubmission('status', 'validationError'); - } + // Validation errors in this situation are not visible, so we’d + // like to know if they’re common + const { isValid, errors } = isValidForm(form, pagesByChapter); + if (!isValid) { + recordEvent({ + event: `${trackingPrefix}-validation-failed`, + }); + Raven.captureMessage('Validation issue not displayed', { + extra: { + errors, + prefix: trackingPrefix + } + }); + this.props.setSubmission('status', 'validationError'); this.props.setSubmission('hasAttemptedSubmit', true); + return; } + + // User accepted if required, and no errors, so submit + this.props.submitForm(formConfig, form); } render() { @@ -87,21 +97,21 @@ class SubmitController extends React.Component { form, formConfig, showPreSubmitError, - renderErrorMessage, - submission + renderErrorMessage } = this.props; + const preSubmit = this.getPreSubmit(formConfig); + return (
- { this.preSubmitInfo && this.props.setPreSubmit(formConfig.preSubmitInfo.field, this.value)} - form={form} - showError={showPreSubmitError}/> } + this.props.setPreSubmit(preSubmit.field, event.target.value)} + checked={form.data[preSubmit.field]} + showError={showPreSubmitError}/>
); diff --git a/src/js/state/helpers.js b/src/js/state/helpers.js index 718423a..3ee2747 100644 --- a/src/js/state/helpers.js +++ b/src/js/state/helpers.js @@ -496,12 +496,6 @@ export function createInitialState(formConfig) { pages: {}, }); - // Initialize the preSubmit flag if one was specified - const preSubmitInfo = formConfig.preSubmitInfo; - if (preSubmitInfo && preSubmitInfo.field) { - pageAndDataState.data[preSubmitInfo.field] = false; - } - initialState = _.assign(initialState, pageAndDataState); // Take another pass and recalculate the schema and data based on the default data // We do this to avoid passing undefined for the whole form state when the form first renders diff --git a/test/js/review/ReviewPage.unit.spec.jsx b/test/js/review/ReviewPage.unit.spec.jsx index 6eaf169..b4c3564 100644 --- a/test/js/review/ReviewPage.unit.spec.jsx +++ b/test/js/review/ReviewPage.unit.spec.jsx @@ -11,13 +11,6 @@ describe('Schemaform review: ReviewPage', () => { it('should render chapters', () => { const formConfig = { - preSubmitInfo: { - required: true, - field: 'privacyAgreementAccepted', - notice: 'Notice', - label: 'I accept the privacy agreement', - error: 'You must accept the privacy agreement' - }, chapters: { chapter1: { pages: { @@ -49,9 +42,7 @@ describe('Schemaform review: ReviewPage', () => { submission: { hasAttemptedSubmit: false }, - data: { - privacyAgreementAccepted: false - } + data: {} }; const tree = shallow( diff --git a/test/js/review/SubmitController.unit.spec.jsx b/test/js/review/SubmitController.unit.spec.jsx index 8a30f96..c5febb2 100644 --- a/test/js/review/SubmitController.unit.spec.jsx +++ b/test/js/review/SubmitController.unit.spec.jsx @@ -1,64 +1,105 @@ import React from 'react'; import { expect } from 'chai'; -import SkinDeep from 'skin-deep'; -import { mount } from 'enzyme'; +import { shallow, mount } from 'enzyme'; import sinon from 'sinon'; import { SubmitController } from '../../../src/js/review/SubmitController'; -const preSubmitInfo = { - required: true, - field: 'privacyAgreementAccepted', - notice: '
Notice
', - label: 'I accept the privacy agreement', - error: 'You must accept the privacy agreement' -}; - -describe('Schemaform review: SubmitController', () => { - it('should route to confirmation page after submit', () => { - const formConfig = { - preSubmitInfo, - chapters: { - chapter1: { - pages: { - page1: {} - } +// Return fresh objects from templates for use with individual tests +// Default setup: Valid (but empty) form, privacy agreement not set +const createFormConfig = options => ({ + urlPrefix: '/', + preSubmitInfo: { + required: true, + field: 'privacyAgreementAccepted', + notice: '
Notice
', + label: 'I accept the privacy agreement', + error: 'You must accept the privacy agreement' + }, + chapters: { + chapter1: { + pages: { + page1: { + schema: {} }, - chapter2: { - pages: { - page2: {} - } + page2: { + schema: {} } } - }; - - const form = { - submission: { - hasAttemptedSubmit: false, - }, - data: { - privacyAgreementAccepted: false + }, + chapter2: { + pages: { + page3: { + schema: {} + } } - }; + } + }, + ...options +}); - const router = { - push: sinon.spy() - }; +const createForm = options => ({ + submission: { + hasAttemptedSubmit: false, + status: false + }, + pages: { + page1: { + schema: {}, + }, + page2: { + schema: {}, + }, + page3: { + schema: {}, + } + }, + data: {}, + ...options +}); - const pageList = [ - { - path: 'previous-page' - }, - { - path: 'testing', - pageKey: 'testPage' - }, - { - path: 'next-page' - } - ]; +const createPageList = () => ([ + { + path: 'page-1', + pageKey: 'page1' + }, + { + path: 'page-2', + pageKey: 'page2' + }, + { + path: 'page-3', + pageKey: 'page3' + } +]); + +const createPagesByChapter = () => ({ + chapter1: [ + { + chapterKey: 'chapter1', + pageKey: 'page1' + }, + { + chapterKey: 'chapter1', + pageKey: 'page2' + } + ], + chapter2: [ + { + chapterKey: 'chapter2', + pageKey: 'page3' + } + ] +}); - const tree = SkinDeep.shallowRender( +describe('Schemaform review: SubmitController', () => { + it('should route to confirmation page after submit', () => { + const formConfig = createFormConfig(); + const form = createForm(); + const pageList = createPageList(); + const router = { push: sinon.spy() }; + + const tree = shallow( { router={router}/> ); - tree.getMountedInstance().componentWillReceiveProps({ - route: { - }, - formConfig: { - urlPrefix: '/', - preSubmitInfo - }, - form: { - submission: { - status: 'applicationSubmitted' - }, - data: { - privacyAgreementAccepted: false - } - } + tree.setProps({ + route: {}, + formConfig, + form: createForm({ + submission: { status: 'applicationSubmitted' }, + data: { privacyAgreementAccepted: true } + }) }); + // BUG: this assumes there is always a confirmation page with this route expect(router.push.calledWith('/confirmation')).to.be.true; }); it('should not submit when privacy agreement not accepted', () => { - const form = { - submission: { - hasAttemptedSubmit: false - }, - pages: { - page1: {}, - page2: {}, - }, - data: { - privacyAgreementAccepted: false - } - }; - - const pagesByChapter = { - chapter1: [{ - chapterKey: 'chapter1', - pageKey: 'page1' - }], - chapter2: [{ - chapterKey: 'chapter2', - pageKey: 'page2' - }] - }; - const formConfig = { - preSubmitInfo - }; + const form = createForm(); + const pagesByChapter = createPagesByChapter(); + const formConfig = createFormConfig(); const submitForm = sinon.spy(); const setSubmission = sinon.spy(); - const tree = SkinDeep.shallowRender( + const tree = mount( + pagesByChapter={pagesByChapter}/> ); - tree.getMountedInstance().handleSubmit(); + // SubmitButtons .usa-button-primary is the submit button + tree.find('.usa-button-primary').simulate('click'); expect(submitForm.called).to.be.false; - expect(setSubmission.called).to.be.true; + expect(setSubmission.calledWith('hasAttemptedSubmit')).to.be.true; }); it('should not submit when invalid', () => { - const formConfig = { - preSubmitInfo, + // Form with missing rquired field + const formConfig = createFormConfig({ chapters: { chapter1: { pages: { page1: { - schema: {} - } - } - }, - chapter2: { - pages: { - page2: { - schema: {} + title: 'Missing stuff', + schema: { + type: 'object', + required: ['stuff'], + properties: { + stuff: { type: 'string' } + } + } } } } - } - }; - - const pagesByChapter = { - chapter1: [{ - chapterKey: 'chapter1', - pageKey: 'page1' - }], - chapter2: [{ - chapterKey: 'chapter2', - pageKey: 'page2' - }] - }; - - const form = { - submission: { - hasAttemptedSubmit: false - }, - pages: { - page1: {}, - page2: {}, - }, - data: { - privacyAgreementAccepted: false - } - }; - - const pageList = [ - { - path: 'previous-page' - }, - { - path: 'testing', - pageKey: 'testPage' }, - { - path: 'next-page' - } - ]; - + data: { privacyAgreementAccepted: true } + }); + const pagesByChapter = createPagesByChapter(); + const form = createForm(); + const pageList = createPageList(); const submitForm = sinon.spy(); const setSubmission = sinon.spy(); - const tree = SkinDeep.shallowRender( + const tree = mount( { route={{ formConfig, pageList }}/> ); - tree.getMountedInstance().handleSubmit(); + tree.find('.usa-button-primary').simulate('click'); expect(submitForm.called).to.be.false; - expect(setSubmission.called).to.be.true; + expect(setSubmission.calledWith('hasAttemptedSubmit')).to.be.true; }); it('should submit when valid', () => { - const formConfig = { - preSubmitInfo, - chapters: { - chapter1: { - pages: { - page1: { - schema: {} - } - } - }, - chapter2: { - pages: { - page2: { - } - } - } - } - }; - - const pagesByChapter = { - chapter1: [{ - chapterKey: 'chapter1', - pageKey: 'page1' - }], - chapter2: [{ - chapterKey: 'chapter2', - pageKey: 'page2' - }] - }; - - const form = { - submission: { - hasAttemptedSubmit: false - }, - pages: { - page1: { - schema: {}, - }, - page2: { - schema: {}, - }, - }, - data: { - privacyAgreementAccepted: true - } - }; - - const pageList = [ - { - path: 'previous-page' - }, - { - path: 'testing', - pageKey: 'testPage' - }, - { - path: 'next-page' - } - ]; - + const formConfig = createFormConfig(); + const pagesByChapter = createPagesByChapter(); + const form = createForm({ + data: { privacyAgreementAccepted: true } + }); + const pageList = createPageList(); const submitForm = sinon.spy(); - const tree = SkinDeep.shallowRender( + const tree = mount( { route={{ formConfig, pageList }}/> ); - tree.getMountedInstance().handleSubmit(); + tree.find('.usa-button-primary').simulate('click'); expect(submitForm.called).to.be.true; }); it('should submit when valid and no preSubmit specified', () => { - const formConfig = { - chapters: { - chapter1: { - pages: { - page1: { - schema: {} - } - } - }, - chapter2: { - pages: { - page2: { - } - } - } - } - }; + const formConfig = createFormConfig({ + preSubmitInfo: undefined + }); + const pagesByChapter = createPagesByChapter(); + const form = createForm(); + const pageList = createPageList(); + const submitForm = sinon.spy(); - const pagesByChapter = { - chapter1: [{ - chapterKey: 'chapter1', - pageKey: 'page1' - }], - chapter2: [{ - chapterKey: 'chapter2', - pageKey: 'page2' - }] - }; + const tree = mount( + + ); - const form = { - submission: { - hasAttemptedSubmit: false - }, - pages: { - page1: { - schema: {}, - }, - page2: { - schema: {}, - }, - }, - data: {} - }; + tree.find('.usa-button-primary').simulate('click'); - const pageList = [ - { - path: 'previous-page' - }, - { - path: 'testing', - pageKey: 'testPage' - }, - { - path: 'next-page' + expect(submitForm.called).to.be.true; + }); + it('should submit when valid and only preSubmit.notice specified', () => { + const formConfig = createFormConfig({ + preSubmitInfo: { + notice:

NOTICE

, + required: false } - ]; - + }); + const pagesByChapter = createPagesByChapter(); + const form = createForm(); + const pageList = createPageList(); const submitForm = sinon.spy(); - const tree = SkinDeep.shallowRender( + const tree = mount( { route={{ formConfig, pageList }}/> ); - tree.getMountedInstance().handleSubmit(); + tree.find('.usa-button-primary').simulate('click'); + expect(tree.find('.presubmit-notice').text()).to.equal('NOTICE'); expect(submitForm.called).to.be.true; }); it('should go back', () => { - const router = { - push: sinon.spy() - }; - const formConfig = { - preSubmitInfo, - chapters: { - chapter1: { - pages: { - page1: { - schema: {} - } - } - }, - chapter2: { - pages: { - page2: { - } - } - } - } - }; + const formConfig = createFormConfig(); + const pageList = createPageList(); + const form = createForm({ + data: { privacyAgreementAccepted: true } + }); + const router = { push: sinon.spy() }; const submission = { hasAttemptedSubmit: false }; - const form = { - page1: { - schema: {}, - }, - page2: { - schema: {}, - }, - data: { - privacyAgreementAccepted: true - } - }; const tree = mount( ).instance(); tree.goBack(); - expect(router.push.called).to.be.true; + // BUG: The code is making a bunch of bogus assumptions about routes + // and pages since it always adds review and confirmation routes. + expect(router.push.calledWith('page-2')).to.be.true; }); }); diff --git a/test/js/state/index.unit.spec.js b/test/js/state/index.unit.spec.js index f1d8654..e08bbba 100644 --- a/test/js/state/index.unit.spec.js +++ b/test/js/state/index.unit.spec.js @@ -15,13 +15,6 @@ import createSchemaFormReducer from '../../../src/js/state'; describe('schemaform createSchemaFormReducer', () => { it('creates a reducer with initial state for each page', () => { const formConfig = { - preSubmitInfo: { - required: true, - field: 'privacyAgreementAccepted', - notice: 'Notice', - label: 'I accept the privacy agreement', - error: 'You must accept the privacy agreement' - }, disableSave: true, chapters: { test: { @@ -50,7 +43,6 @@ describe('schemaform createSchemaFormReducer', () => { const state = reducer(undefined, {}); expect(state.submission).not.to.be.undefined; - expect(state.data.privacyAgreementAccepted).to.be.false; expect(state.data.field).to.eql(formConfig.chapters.test.pages.page1.initialData.field); }); describe('reducer', () => {