hmpo-form-controller
Advanced tools
Comparing version 1.0.0 to 1.1.0
149
lib/form.js
/*eslint no-unused-vars: [2, {"vars": "all", "args": "none"}]*/ | ||
var util = require('util'), | ||
express = require('express'), | ||
EventEmitter = require('events').EventEmitter; | ||
EventEmitter = require('events').EventEmitter, | ||
clone = require('lodash.clonedeep'); | ||
@@ -24,5 +25,2 @@ var _ = require('underscore'), | ||
this.formatter = dataFormatter(this.options.fields, this.options.defaultFormatters); | ||
this.validator = dataValidator(this.options.fields); | ||
this.router = express.Router({ mergeParams: true }); | ||
@@ -35,2 +33,5 @@ }; | ||
requestHandler: function () { | ||
this._configure(); | ||
this.middlewareMixins(); | ||
var methods = ['get', 'post', 'put', 'delete']; | ||
@@ -55,3 +56,2 @@ _.each(methods, function (method) { | ||
get: function (req, res, callback) { | ||
req.form = req.form || {}; | ||
var router = express.Router({ mergeParams: true }); | ||
@@ -62,2 +62,3 @@ router.use([ | ||
this._locals.bind(this), | ||
this._checkStatus.bind(this), | ||
this.render.bind(this) | ||
@@ -68,5 +69,2 @@ ]); | ||
}); | ||
if (_.isEmpty(this.options.fields) && this.options.next) { | ||
this.emit('complete', req, res); | ||
} | ||
router.handle(req, res, callback); | ||
@@ -77,3 +75,2 @@ }, | ||
req.form = req.form || {}; | ||
var router = express.Router({ mergeParams: true }); | ||
@@ -91,2 +88,30 @@ router.use([ | ||
}, | ||
_configure: function () { | ||
this.use(function (req, res, callback) { | ||
req.form = req.form || {}; | ||
req.form.options = clone(this.options); | ||
this.configure(req, res, callback); | ||
}.bind(this)); | ||
}, | ||
configure: function (req, res, callback) { | ||
callback(); | ||
}, | ||
middlewareMixins: function () {}, | ||
_getErrors: function (req, res, callback) { | ||
req.form.errors = this.getErrors(req, res); | ||
callback(); | ||
}, | ||
// placeholder methods for persisting error messages between POST and GET | ||
getErrors: function (/*req, res*/) { | ||
return {}; | ||
}, | ||
_getValues: function (req, res, callback) { | ||
this.getValues(req, res, function (err, values) { | ||
req.form.values = values || {}; | ||
callback(err); | ||
}); | ||
}, | ||
getValues: function (req, res, callback) { | ||
callback(); | ||
}, | ||
_locals: function (req, res, callback) { | ||
@@ -97,3 +122,3 @@ _.extend(res.locals, { | ||
values: req.form.values, | ||
options: this.options, | ||
options: req.form.options, | ||
action: req.baseUrl !== '/' ? req.baseUrl + req.path : req.path | ||
@@ -107,18 +132,27 @@ }); | ||
}, | ||
_checkStatus: function (req, res, callback) { | ||
if (_.isEmpty(req.form.options.fields) && req.form.options.next) { | ||
this.emit('complete', req, res); | ||
} | ||
callback(); | ||
}, | ||
render: function (req, res, callback) { | ||
if (!this.options.template) { | ||
if (!req.form.options.template) { | ||
callback(new Error('A template must be provided')); | ||
} else { | ||
res.render(this.options.template); | ||
res.render(req.form.options.template); | ||
} | ||
}, | ||
_getErrors: function (req, res, callback) { | ||
req.form.errors = this.getErrors(req, res); | ||
setErrors: function (/*err, req, res*/) {}, | ||
_process: function (req, res, callback) { | ||
req.form.values = req.form.values || {}; | ||
var formatter = dataFormatter(req.form.options.fields, req.form.options.defaultFormatters); | ||
_.each(req.form.options.fields, function (value, key) { | ||
req.form.values[key] = formatter(key, req.body[key] || ''); | ||
}); | ||
this.process(req, res, callback); | ||
}, | ||
process: function (req, res, callback) { | ||
callback(); | ||
}, | ||
// placeholder methods for persisting error messages between POST and GET | ||
getErrors: function (/*req, res*/) { | ||
return {}; | ||
}, | ||
setErrors: function (/*err, req, res*/) {}, | ||
_validate: function (req, res, callback) { | ||
@@ -129,4 +163,7 @@ debug('Validating...'); | ||
var formatter = dataFormatter(req.form.options.fields, req.form.options.defaultFormatters); | ||
var validator = dataValidator(req.form.options.fields); | ||
_.each(req.form.values, function (value, key) { | ||
var error = this.validateField(key, req); | ||
var error = this.validateField(key, req, validator, formatter); | ||
if (error) { | ||
@@ -150,29 +187,25 @@ if (error.group) { | ||
}, | ||
validateField: function (key, req) { | ||
var emptyValue = this.formatter(key, ''); | ||
return this.validator(key, req.form.values[key], req.form.values, emptyValue); | ||
validateField: function (key, req, validator, formatter) { | ||
formatter = formatter || dataFormatter(req.form.options.fields, req.form.options.defaultFormatters); | ||
validator = validator || dataValidator(req.form.options.fields); | ||
var emptyValue = formatter(key, ''); | ||
return validator(key, req.form.values[key], req.form.values, emptyValue); | ||
}, | ||
_process: function (req, res, callback) { | ||
req.form = { values: {} }; | ||
var formatter = dataFormatter(this.options.fields, this.options.defaultFormatters); | ||
_.each(this.options.fields, function (value, key) { | ||
req.form.values[key] = formatter(key, req.body[key] || ''); | ||
}); | ||
this.process(req, res, callback); | ||
}, | ||
process: function (req, res, callback) { | ||
saveValues: function (req, res, callback) { | ||
callback(); | ||
}, | ||
_getValues: function (req, res, callback) { | ||
this.getValues(req, res, function (err, values) { | ||
req.form.values = values || {}; | ||
callback(err); | ||
}); | ||
successHandler: function (req, res) { | ||
this.emit('complete', req, res); | ||
res.redirect(this.getNextStep(req, res)); | ||
}, | ||
getValues: function (req, res, callback) { | ||
callback(); | ||
getNextStep: function (req, res) { | ||
var next = req.form.options.next || req.path; | ||
if (req.form.options.forks && Array.isArray(req.form.options.forks)) { | ||
next = this._getForkTarget(req, res); | ||
} | ||
if (req.baseUrl !== '/') { | ||
next = req.baseUrl + next; | ||
} | ||
return next; | ||
}, | ||
saveValues: function (req, res, callback) { | ||
callback(); | ||
}, | ||
_getForkTarget: function (req, res) { | ||
@@ -186,7 +219,7 @@ function evalCondition(condition) { | ||
// If a fork condition is met, its target supercedes the next property | ||
return this.options.forks.reduce(function (result, value) { | ||
return req.form.options.forks.reduce(function (result, value) { | ||
return evalCondition(value.condition) ? | ||
value.target : | ||
result; | ||
}, this.options.next); | ||
}, req.form.options.next); | ||
}, | ||
@@ -196,11 +229,10 @@ getForkTarget: function (req, res) { | ||
}, | ||
getNextStep: function (req, res) { | ||
var next = this.options.next || req.path; | ||
if (this.options.forks && Array.isArray(this.options.forks)) { | ||
next = this._getForkTarget(req, res); | ||
errorHandler: function (err, req, res, callback) { | ||
if (this.isValidationError(err)) { | ||
this.setErrors(err, req, res); | ||
res.redirect(this.getErrorStep(err, req)); | ||
} else { | ||
// if the error is not a validation error then throw and let the error handler pick it up | ||
return callback(err); | ||
} | ||
if (req.baseUrl !== '/') { | ||
next = req.baseUrl + next; | ||
} | ||
return next; | ||
}, | ||
@@ -225,15 +257,2 @@ getErrorStep: function (err, req) { | ||
return !_.isEmpty(err) && _.all(err, function (e) { return e instanceof this.Error; }, this); | ||
}, | ||
errorHandler: function (err, req, res, callback) { | ||
if (this.isValidationError(err)) { | ||
this.setErrors(err, req, res); | ||
res.redirect(this.getErrorStep(err, req)); | ||
} else { | ||
// if the error is not a validation error then throw and let the error handler pick it up | ||
return callback(err); | ||
} | ||
}, | ||
successHandler: function (req, res) { | ||
this.emit('complete', req, res); | ||
res.redirect(this.getNextStep(req, res)); | ||
} | ||
@@ -240,0 +259,0 @@ }); |
@@ -64,2 +64,3 @@ var _ = require('underscore'), | ||
return function (key, value, values, emptyValue) { | ||
debug('Validating field: "' + key + '" with value: "' + value + '"'); | ||
emptyValue = emptyValue === undefined ? '' : emptyValue; | ||
@@ -66,0 +67,0 @@ |
{ | ||
"name": "hmpo-form-controller", | ||
"version": "1.0.0", | ||
"version": "1.1.0", | ||
"description": "", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "npm run lint && npm run unit && npm run cover && npm run check-coverage && npm run snyk", | ||
"test": "npm run lint && npm run unit && npm run cover && npm run check-coverage", | ||
"unit": "mocha --recursive test/spec/ --require test/helper --timeout 300000", | ||
@@ -31,2 +31,3 @@ "cover": "istanbul cover _mocha -- -R dot --recursive test/spec/ --require test/helper --timeout 300000", | ||
"express": "^4.12.3", | ||
"lodash.clonedeep": "^4.5.0", | ||
"moment": "^2.9.0", | ||
@@ -46,4 +47,4 @@ "underscore": "^1.7.0" | ||
"sinon-chai": "^2.6.0", | ||
"snyk": "1.23.4" | ||
"snyk": "^1.24.5" | ||
} | ||
} |
@@ -43,2 +43,4 @@ # passports-form-controller | ||
* `configure` Allows for dynamic overwriting of particular points of form configuration based on user session | ||
* `middlewareMixins` Allows additional middleware to be added after the configure stage | ||
* `process` Allows for custom formatting and processing of input prior to validation | ||
@@ -141,1 +143,31 @@ * `validate` Allows for custom input validation | ||
``` | ||
### Dynamic field options | ||
If the options for a particular field are dependent on aspects of the user session, then these can be extended on a per-session basis using the `configure` method. | ||
For example, for a dynamic address selection component: | ||
```js | ||
MyForm.prototype.configure = function configure(req, res, next) { | ||
req.form.options.fields['address-select'].options = req.sessionModel.get('addresses'); | ||
next(); | ||
} | ||
``` | ||
### Middleware mixins | ||
If you want to add middleware that uses dynamic field options then you can use the `middlewareMixins` method. This is called after `configure` so after the dynamic field options are set. | ||
For example, for setting the base url to res locals: | ||
```js | ||
MyForm.prototype.middlewareMixins = function middlewareMixins(req, res, next) { | ||
this.use(this.setBaseUrlLocal).bind(this); | ||
} | ||
MyForm.prototype.setBaseUrlLocal = function setBaseUrlLocal(req, res, next) { | ||
res.locals.baseUrl = req.baseUrl; | ||
next(); | ||
} | ||
``` |
@@ -54,5 +54,9 @@ var Form = require('../../'); | ||
beforeEach(function () { | ||
form = new Form({ template: 'index' }); | ||
form = new Form({ | ||
template: 'index' | ||
}); | ||
sinon.stub(form, 'get').yields(); | ||
sinon.stub(form, 'post').yields(); | ||
sinon.stub(form, 'configure').yields(); | ||
sinon.stub(form, 'middlewareMixins').returns(); | ||
// use a spy instead of a stub so that the length is unaffected | ||
@@ -76,2 +80,16 @@ sinon.spy(form, 'errorHandler'); | ||
it('calls form.configure', function () { | ||
handler = form.requestHandler(); | ||
handler(req, res, cb); | ||
form.configure.should.have.been.calledWith(req, res, sinon.match.func); | ||
form.configure.should.have.been.calledOn(form); | ||
}); | ||
it('calls form.middlewareMixins', function () { | ||
handler = form.requestHandler(); | ||
handler(req, res, cb); | ||
form.middlewareMixins.should.have.been.called; | ||
form.middlewareMixins.should.have.been.calledOn(form); | ||
}); | ||
it('calls form.get in response to get requests', function () { | ||
@@ -81,3 +99,3 @@ req.method = 'GET'; | ||
handler(req, res, cb); | ||
form.get.should.have.been.calledWith(req, res); | ||
form.get.should.have.been.calledWith(req, res, sinon.match.func); | ||
form.get.should.have.been.calledOn(form); | ||
@@ -90,3 +108,3 @@ }); | ||
handler(req, res, cb); | ||
form.post.should.have.been.calledWith(req, res); | ||
form.post.should.have.been.calledWith(req, res, sinon.match.func); | ||
form.post.should.have.been.calledOn(form); | ||
@@ -107,2 +125,17 @@ }); | ||
it('calls middleware mixins after configure and before invoking request handlers', function (done) { | ||
var middleware = sinon.stub().yields(); | ||
form.middlewareMixins = function middlewareMixins() { | ||
form.use(middleware); | ||
}; | ||
req.method = 'GET'; | ||
handler = form.requestHandler(); | ||
handler(req, res, function () { | ||
middleware.should.have.been.calledWith(req, res, sinon.match.func); | ||
middleware.should.have.been.calledAfter(form.configure); | ||
middleware.should.have.been.calledBefore(form.get); | ||
done(); | ||
}); | ||
}); | ||
it('calls any additional middlewares before invoking request handlers', function (done) { | ||
@@ -150,7 +183,62 @@ var middleware = sinon.stub().yields(); | ||
describe('get', function () { | ||
var form, req, res, cb; | ||
beforeEach(function () { | ||
cb = sinon.stub(); | ||
form = new Form({ | ||
template: 'index' | ||
}); | ||
req = request({ | ||
flash: sinon.stub(), | ||
method: 'GET' | ||
}); | ||
res = {}; | ||
sinon.stub(Form.prototype, '_getErrors').yields(null); | ||
sinon.stub(Form.prototype, '_getValues').yields(null); | ||
sinon.stub(Form.prototype, '_locals').yields(null); | ||
sinon.stub(Form.prototype, '_checkStatus').yields(null); | ||
sinon.stub(Form.prototype, 'render').yields(null); | ||
}); | ||
afterEach(function () { | ||
Form.prototype._getErrors.restore(); | ||
Form.prototype._getValues.restore(); | ||
Form.prototype._locals.restore(); | ||
Form.prototype._checkStatus.restore(); | ||
Form.prototype.render.restore(); | ||
}); | ||
it('calls _getErrors', function () { | ||
form.get(req, res, cb); | ||
form._getErrors.should.have.been.called.and.calledWith(req, res, sinon.match.func); | ||
}); | ||
it('calls _getValues', function () { | ||
form.get(req, res, cb); | ||
form._getValues.should.have.been.called.and.calledWith(req, res, sinon.match.func); | ||
}); | ||
it('calls _locals', function () { | ||
form.get(req, res, cb); | ||
form._locals.should.have.been.called.and.calledWith(req, res, sinon.match.func); | ||
}); | ||
it('calls _checkStatus', function () { | ||
form.get(req, res, cb); | ||
form._checkStatus.should.have.been.called.and.calledWith(req, res, sinon.match.func); | ||
}); | ||
it('calls render', function () { | ||
form.get(req, res, cb); | ||
form.render.should.have.been.called.and.calledWith(req, res, sinon.match.func); | ||
}); | ||
}); | ||
describe('GET request', function () { | ||
var form, req, res, cb, getRequest; | ||
beforeEach(function () { | ||
form = new Form({ | ||
template: 'index', | ||
@@ -164,3 +252,4 @@ next: '/next', | ||
path: '/index', | ||
baseUrl: '/base' | ||
baseUrl: '/base', | ||
method: 'GET' | ||
}); | ||
@@ -175,2 +264,4 @@ res = { | ||
sinon.stub(Form.prototype, 'render'); | ||
sinon.stub(Form.prototype, 'errorHandler').yields(null); | ||
getRequest = form.requestHandler(); | ||
}); | ||
@@ -182,7 +273,8 @@ | ||
Form.prototype.render.restore(); | ||
Form.prototype.errorHandler.restore(); | ||
}); | ||
it('calls form.getValues', function () { | ||
form.get(req, res, cb); | ||
form.getValues.should.have.been.calledWith(req, res); | ||
getRequest(req, res, cb); | ||
form.getValues.should.have.been.calledWith(req, res, sinon.match.func); | ||
form.getValues.should.have.been.calledOn(form); | ||
@@ -193,3 +285,3 @@ }); | ||
Form.prototype.getValues.yields(null, { foo: 'bar' }); | ||
form.get(req, res, cb); | ||
getRequest(req, res, cb); | ||
req.form.values.should.eql({ foo: 'bar' }); | ||
@@ -200,3 +292,3 @@ }); | ||
Form.prototype.getValues.yields(null); | ||
form.get(req, res, cb); | ||
getRequest(req, res, cb); | ||
req.form.values.should.eql({ }); | ||
@@ -206,3 +298,3 @@ }); | ||
it('calls form.render', function () { | ||
form.get(req, res, cb); | ||
getRequest(req, res, cb); | ||
form.render.should.have.been.calledOnce; | ||
@@ -214,3 +306,3 @@ form.render.should.have.been.calledWith(req, res); | ||
form.getErrors.returns({ field: { message: 'error' } }); | ||
form.get(req, res, cb); | ||
getRequest(req, res, cb); | ||
res.locals.errors.should.eql({ field: { message: 'error' } }); | ||
@@ -221,21 +313,33 @@ }); | ||
form.getValues.yields(null, { values: [1] }); | ||
form.get(req, res, cb); | ||
getRequest(req, res, cb); | ||
res.locals.values.should.eql({ values: [1] }); | ||
}); | ||
it('calls callback with error if getValues fails', function () { | ||
it('calls errorHandler if getValues fails', function () { | ||
form.getValues.yields({ error: 'message' }); | ||
form.get(req, res, cb); | ||
cb.should.have.been.calledOnce; | ||
cb.should.have.been.calledWith({ error: 'message' }); | ||
getRequest(req, res, cb); | ||
form.errorHandler.should.have.been.calledOnce; | ||
form.errorHandler.should.have.been.calledWith({ error: 'message' }, req, res); | ||
}); | ||
it('includes form options in rendered response', function () { | ||
form.get(req, res, cb); | ||
getRequest(req, res, cb); | ||
res.locals.options.should.eql(form.options); | ||
}); | ||
it('includes dynamic form options in rendered response', function () { | ||
form.configure = function configure(req, res, next) { | ||
req.form.options.fields['field'] = 'updated'; | ||
req.form.options.fields['another-field'] = 'new'; | ||
next(); | ||
}; | ||
getRequest = form.requestHandler(); | ||
getRequest(req, res, cb); | ||
res.locals.options.fields['field'].should.eql('updated'); | ||
res.locals.options.fields['another-field'].should.eql('new'); | ||
}); | ||
it('emits "complete" event if form has no fields', function () { | ||
form.options.fields = {}; | ||
form.get(req, res, cb); | ||
getRequest(req, res, cb); | ||
form.emit.withArgs('complete').should.have.been.calledOnce; | ||
@@ -247,10 +351,28 @@ form.emit.withArgs('complete').should.have.been.calledOn(form); | ||
it('does not emit "complete" event if form has fields', function () { | ||
form = new Form({ template: 'index', fields: { key: {} } }); | ||
form.get(req, res, cb); | ||
form = new Form({ | ||
template: 'index', | ||
fields: { key: {} } | ||
}); | ||
getRequest = form.requestHandler(); | ||
getRequest(req, res, cb); | ||
form.emit.withArgs('complete').should.not.have.been.called; | ||
}); | ||
it('does not emit "complete" event if form has dynamic fields on the request object', function () { | ||
form.configure = function configure(req, res, next) { | ||
req.form.options.fields.name = { | ||
mixin: 'input-text', | ||
validate: 'required' | ||
}; | ||
next(); | ||
}; | ||
form.options.fields = {}; | ||
getRequest = form.requestHandler(); | ||
getRequest(req, res, cb); | ||
form.emit.withArgs('complete').should.not.have.been.called; | ||
}); | ||
it('does not emit "complete" event if form has no defined next step', function () { | ||
delete form.options.next; | ||
form.get(req, res, cb); | ||
getRequest(req, res, cb); | ||
form.emit.withArgs('complete').should.not.have.been.called; | ||
@@ -260,7 +382,7 @@ }); | ||
it('sets the action property on res.locals', function () { | ||
form.get(req, res, cb); | ||
getRequest(req, res, cb); | ||
res.locals.action.should.equal('/base/index'); | ||
req.baseUrl = '/'; | ||
form.get(req, res, cb); | ||
getRequest(req, res, cb); | ||
res.locals.action.should.equal('/index'); | ||
@@ -272,4 +394,59 @@ }); | ||
describe('post', function () { | ||
var form, req, res, cb; | ||
var form, req, res, cb; | ||
beforeEach(function () { | ||
cb = sinon.stub(); | ||
form = new Form({ | ||
template: 'index' | ||
}); | ||
req = request({ | ||
flash: sinon.stub(), | ||
method: 'POST' | ||
}); | ||
res = {}; | ||
sinon.stub(Form.prototype, 'setErrors'); | ||
sinon.stub(Form.prototype, '_process').yields(null); | ||
sinon.stub(Form.prototype, '_validate').yields(null); | ||
sinon.stub(Form.prototype, 'saveValues').yields(null); | ||
sinon.stub(Form.prototype, 'successHandler'); | ||
}); | ||
afterEach(function () { | ||
Form.prototype.setErrors.restore(); | ||
Form.prototype._process.restore(); | ||
Form.prototype._validate.restore(); | ||
Form.prototype.saveValues.restore(); | ||
Form.prototype.successHandler.restore(); | ||
}); | ||
it('calls setErrors', function () { | ||
form.post(req, res, cb); | ||
form.setErrors.should.have.been.called.and.calledWith(null, req, res); | ||
}); | ||
it('calls _process', function () { | ||
form.post(req, res, cb); | ||
form._process.should.have.been.called.and.calledWith(req, res, sinon.match.func); | ||
}); | ||
it('calls _validate', function () { | ||
form.post(req, res, cb); | ||
form._validate.should.have.been.called.and.calledWith(req, res, sinon.match.func); | ||
}); | ||
it('calls saveValues', function () { | ||
form.post(req, res, cb); | ||
form.saveValues.should.have.been.called.and.calledWith(req, res, sinon.match.func); | ||
}); | ||
it('calls successHandler', function () { | ||
form.post(req, res, cb); | ||
form.successHandler.should.have.been.called.and.calledWith(req, res); | ||
}); | ||
}); | ||
describe('POST request', function () { | ||
var form, req, res, cb, postRequest; | ||
var validators = Form.validators; | ||
@@ -298,3 +475,4 @@ | ||
bool: 'true' | ||
} | ||
}, | ||
method: 'POST' | ||
}); | ||
@@ -306,5 +484,7 @@ res = {}; | ||
sinon.stub(Form.prototype, 'successHandler'); | ||
sinon.stub(Form.prototype, 'errorHandler').yields(null); | ||
_.each(validators, function (fn, key) { | ||
sinon.stub(validators, key).returns(true); | ||
}); | ||
postRequest = form.requestHandler(); | ||
}); | ||
@@ -317,2 +497,3 @@ | ||
Form.prototype.successHandler.restore(); | ||
Form.prototype.errorHandler.restore(); | ||
_.each(validators, function (fn, key) { | ||
@@ -323,3 +504,3 @@ validators[key].restore(); | ||
it('returns an error if an unknown validator is specified', function () { | ||
it('calls errorHandler if an unknown validator is specified', function () { | ||
var form = new Form({ | ||
@@ -331,4 +512,5 @@ template: 'index', | ||
}); | ||
form.post(req, res, cb); | ||
cb.should.have.been.calledWithExactly(new Error('Undefined validator:unknown')); | ||
postRequest = form.requestHandler(); | ||
postRequest(req, res, cb); | ||
form.errorHandler.should.have.been.calledWith(new Error('Undefined validator:unknown'), req, res); | ||
}); | ||
@@ -343,4 +525,5 @@ | ||
}); | ||
postRequest = form.requestHandler(); | ||
var fn = function () { | ||
form.post(req, res, cb); | ||
postRequest(req, res, cb); | ||
}; | ||
@@ -357,4 +540,5 @@ fn.should.not.throw(); | ||
}); | ||
postRequest = form.requestHandler(); | ||
req.body.field = ['value', 'another value']; | ||
form.post(req, res, cb); | ||
postRequest(req, res, cb); | ||
req.form.values.field.should.be.eql(['VALUE', 'ANOTHER VALUE']); | ||
@@ -364,3 +548,3 @@ }); | ||
it('writes field values to req.form.values', function () { | ||
form.post(req, res, cb); | ||
postRequest(req, res, cb); | ||
req.form.values.should.have.keys([ | ||
@@ -377,16 +561,16 @@ 'field', | ||
it('sets errors to null', function () { | ||
form.post(req, res, cb); | ||
postRequest(req, res, cb); | ||
form.setErrors.should.have.been.calledWithExactly(null, req, res); | ||
}); | ||
it('calls callback with error if _process fails', function () { | ||
var cb = sinon.stub(); | ||
sinon.stub(form, '_process').yields('error'); | ||
form.post(req, res, cb); | ||
cb.should.have.been.calledOnce; | ||
cb.should.have.been.calledWith('error'); | ||
it('calls errorHandler with error if _process fails', function () { | ||
sinon.stub(form, '_process').yields({ error: true }); | ||
postRequest(req, res, cb); | ||
form.errorHandler.should.have.been.calledOnce; | ||
form.errorHandler.should.have.been.calledWith({ error: true }, req, res); | ||
form.errorHandler.should.have.been.calledOn(form); | ||
}); | ||
it('formats posted values according to `fields` option', function () { | ||
form.post(req, res, cb); | ||
postRequest(req, res, cb); | ||
req.form.values.field.should.equal('VALUE'); | ||
@@ -397,10 +581,10 @@ req.form.values.bool.should.equal(true); | ||
it('creates a validate array when validate is a string or field options exist', function () { | ||
form.post(req, res, cb); | ||
expect(form.options.fields.bool.validate).to.be.undefined; | ||
form.options.fields.place.validate.should.eql(['required']); | ||
form.options.fields.options.validate.length.should.equal(1); | ||
postRequest(req, res, cb); | ||
expect(req.form.options.fields.bool.validate).to.be.undefined; | ||
req.form.options.fields.place.validate.should.eql(['required']); | ||
req.form.options.fields.options.validate.length.should.equal(1); | ||
}); | ||
it('validates the fields', function () { | ||
form.post(req, res, cb); | ||
postRequest(req, res, cb); | ||
validators.required.should.have.been.calledWith('VALUE'); | ||
@@ -410,3 +594,3 @@ }); | ||
it('validates fields with multiple validators defined', function () { | ||
form.post(req, res, cb); | ||
postRequest(req, res, cb); | ||
validators.required.should.have.been.calledWith('test@example.com'); | ||
@@ -420,3 +604,3 @@ validators.email.should.have.been.calledWith('test@example.com'); | ||
}; | ||
form.post(req, res, cb); | ||
postRequest(req, res, cb); | ||
validators.required.should.have.been.calledWith('John Smith'); | ||
@@ -430,3 +614,3 @@ validators.minlength.should.have.been.calledWith('John Smith', 10); | ||
}; | ||
form.post(req, res, cb); | ||
postRequest(req, res, cb); | ||
validators.maxlength.should.have.been.calledWith('A name longer than twenty characters', 20); | ||
@@ -439,20 +623,8 @@ }); | ||
}; | ||
form.post(req, res, cb); | ||
validators.equal.should.have.been.calledOnce; | ||
postRequest(req, res, cb); | ||
validators.equal.should.have.been.calledWith('number', 'one', 'two', 'three'); | ||
}); | ||
it('does not keep adding equality validators if one already exists', function () { | ||
req.body = { | ||
options: 'number' | ||
}; | ||
form.post(req, res, cb); | ||
validators.equal.should.have.been.calledOnce; | ||
form.post(req, res, cb); | ||
validators.equal.should.have.been.calledTwice; | ||
form.options.fields['options'].validate.length.should.equal(1); | ||
}); | ||
it('calls out to form.validate', function () { | ||
form.post(req, res, cb); | ||
postRequest(req, res, cb); | ||
form.validate.should.have.been.calledWith(req, res); | ||
@@ -465,3 +637,3 @@ form.validate.should.have.been.calledOn(form); | ||
it('calls form.saveValues', function () { | ||
form.post(req, res, cb); | ||
postRequest(req, res, cb); | ||
form.saveValues.should.have.been.calledWith(req, res); | ||
@@ -472,3 +644,3 @@ form.saveValues.should.have.been.calledOn(form); | ||
it('calls form.successHandler if saved successfully', function () { | ||
form.post(req, res, cb); | ||
postRequest(req, res, cb); | ||
form.successHandler.should.have.been.calledWith(req, res); | ||
@@ -478,6 +650,12 @@ form.successHandler.should.have.been.calledOn(form); | ||
it('calls callback if not saved successfully', function () { | ||
it('calls errorHandler if not saved successfully', function () { | ||
var form = new Form({ | ||
template: 'index' | ||
}); | ||
form.saveValues.yields({ error: true }); | ||
form.post(req, res, cb); | ||
cb.should.have.been.calledWith({ error: true }); | ||
postRequest = form.requestHandler(); | ||
postRequest(req, res, cb); | ||
form.errorHandler.should.have.been.calledOnce; | ||
form.errorHandler.should.have.been.calledWith({ error: true }, req, res); | ||
form.errorHandler.should.have.been.calledOn(form); | ||
}); | ||
@@ -489,9 +667,9 @@ | ||
it('calls callback with validation errors matching failed validation type', function () { | ||
it('calls errorHandler with validation errors matching failed validation type', function () { | ||
req.body.field = ''; | ||
validators.email.returns(false); | ||
req.body.email = 'foo'; | ||
form.post(req, res, cb); | ||
cb.should.have.been.calledOnce; | ||
Object.keys(cb.args[0][0]).should.eql(['email']); | ||
_.each(cb.args[0][0], function (err, key) { | ||
postRequest(req, res, cb); | ||
form.errorHandler.should.have.been.calledOnce; | ||
Object.keys(form.errorHandler.args[0][0]).should.eql(['email']); | ||
_.each(form.errorHandler.args[0][0], function (err, key) { | ||
err.type.should.equal('email'); | ||
@@ -504,4 +682,4 @@ err.key.should.equal(key); | ||
validators.required.returns(false); | ||
form.post(req, res, cb); | ||
cb.should.have.been.called; | ||
postRequest(req, res, cb); | ||
form.errorHandler.should.have.been.called; | ||
form.validate.should.not.have.been.called; | ||
@@ -512,6 +690,6 @@ }); | ||
validators.required.withArgs('test@example.com').returns(false); | ||
form.post(req, res, cb); | ||
cb.should.have.been.calledOnce; | ||
Object.keys(cb.args[0][0]).should.eql(['email']); | ||
_.each(cb.args[0][0], function (err, key) { | ||
postRequest(req, res, cb); | ||
form.errorHandler.should.have.been.calledOnce; | ||
Object.keys(form.errorHandler.args[0][0]).should.eql(['email']); | ||
_.each(form.errorHandler.args[0][0], function (err, key) { | ||
err.type.should.equal('required'); | ||
@@ -526,6 +704,6 @@ err.key.should.equal(key); | ||
req.body = { field: 'value', email: 'foo', name: 'John' }; | ||
form.post(req, res, cb); | ||
cb.should.have.been.calledOnce; | ||
Object.keys(cb.args[0][0]).should.eql(['field', 'email', 'name', 'place']); | ||
_.each(cb.args[0][0], function (err, key) { | ||
postRequest(req, res, cb); | ||
form.errorHandler.should.have.been.calledOnce; | ||
Object.keys(form.errorHandler.args[0][0]).should.eql(['field', 'email', 'name', 'place']); | ||
_.each(form.errorHandler.args[0][0], function (err, key) { | ||
err.type.should.equal('required'); | ||
@@ -540,3 +718,3 @@ err.key.should.equal(key); | ||
req.body = { field: 'value', email: 'foo', name: 'John' }; | ||
form.post(req, res, function (err) { | ||
postRequest(req, res, function (err) { | ||
_.each(err, function (e) { | ||
@@ -553,3 +731,3 @@ e.should.be.an.instanceOf(form.Error); | ||
req.body = { field: 'value', email: 'foo', name: 'John' }; | ||
form.post(req, res, function () { | ||
postRequest(req, res, function () { | ||
form.Error.should.have.been.calledWithExactly('field', sinon.match({ type: 'required' }), req, res); | ||
@@ -570,5 +748,5 @@ form.Error.should.have.been.calledWithExactly('email', sinon.match({ type: 'required' }), req, res); | ||
it('calls callback with validation errors', function () { | ||
form.post(req, res, cb); | ||
cb.should.have.been.calledWith({ field: 'invalid' }); | ||
it('calls errorHandler with validation errors', function () { | ||
postRequest(req, res, cb); | ||
form.errorHandler.should.have.been.calledWith({ field: 'invalid' }); | ||
}); | ||
@@ -580,2 +758,122 @@ | ||
describe('_configure', function () { | ||
var form, req, res, cb, handler; | ||
beforeEach(function () { | ||
form = new Form({ | ||
template: 'index', | ||
next: '/next', | ||
fields: { | ||
field: 'name' | ||
} | ||
}); | ||
req = request({ | ||
path: '/index', | ||
baseUrl: '/base' | ||
}); | ||
res = { | ||
render: sinon.stub(), | ||
locals: {} | ||
}; | ||
cb = function callback() {}; | ||
sinon.spy(form, '_configure'); | ||
sinon.stub(form, 'configure').yields(); | ||
sinon.spy(form, 'use'); | ||
handler = form.router.get('*', function (req, res, callback) { | ||
callback(); | ||
}); | ||
}); | ||
it('is called as part of the form.requestHandler', function () { | ||
handler = form.requestHandler(); | ||
handler(req, res, cb); | ||
form._configure.should.have.been.calledOnce; | ||
}); | ||
it('calls form.use to add router middleware', function () { | ||
form._configure(); | ||
form.use.should.have.been.calledOnce; | ||
}); | ||
it('adds middleware that calls through to form.configure', function () { | ||
form._configure(); | ||
handler(req, res, cb); | ||
form.configure.should.have.been.calledOnce.and.calledWith(req, res, sinon.match.func); | ||
}); | ||
it('adds middleware that writes form options to `req.form.options`', function () { | ||
form._configure(); | ||
handler(req, res, cb); | ||
req.form.options.should.deep.equal(form.options); | ||
}); | ||
it('adds middleware that clones form options to `req.form.options` to avoid config mutation', function () { | ||
form._configure(); | ||
handler(req, res, cb); | ||
req.form.options.should.not.equal(form.options); | ||
}); | ||
it('adds middleware that performs a deep clone of form options', function () { | ||
form.configure = sinon.spy(function (req, res, next) { | ||
req.form.options.fields.field = 'mutated'; | ||
next(); | ||
}); | ||
form._configure(); | ||
handler(req, res, cb); | ||
req.form.options.fields.field.should.equal('mutated'); | ||
form.options.fields.field.should.equal('name'); | ||
}); | ||
}); | ||
describe('_checkStatus', function () { | ||
var form, req, res, cb; | ||
beforeEach(function () { | ||
form = new Form({ | ||
template: 'index' | ||
}); | ||
req = request({ | ||
form: { | ||
options: { | ||
next: '/next', | ||
fields: {} | ||
} | ||
} | ||
}); | ||
res = {}; | ||
cb = function callback() {}; | ||
sinon.spy(form, '_checkStatus'); | ||
}); | ||
it('is called as part of `get` pipeline', function () { | ||
form.get(req, res, cb); | ||
form._checkStatus.should.have.been.calledOnce.and.calledWith(req, res); | ||
}); | ||
it('emits "complete" event if fields are empty and next is set', function () { | ||
form._checkStatus(req, res, cb); | ||
form.emit.withArgs('complete').should.have.been.calledOnce; | ||
form.emit.withArgs('complete').should.have.been.calledOn(form); | ||
form.emit.should.have.been.calledWithExactly('complete', req, res); | ||
}); | ||
it('does not emit "complete" event if fields exist', function () { | ||
req.form.options.fields = { | ||
field: 'name' | ||
}; | ||
form._checkStatus(req, res, cb); | ||
form.emit.withArgs('complete').should.not.have.been.called; | ||
}); | ||
it('does not emit "complete" event if next is not set', function () { | ||
req.form.options.next = null; | ||
form._checkStatus(req, res, cb); | ||
form.emit.withArgs('complete').should.not.have.been.called; | ||
}); | ||
}); | ||
describe('render', function () { | ||
@@ -593,2 +891,7 @@ | ||
}); | ||
req = { | ||
form: { | ||
options: form.options | ||
} | ||
}; | ||
res = { | ||
@@ -607,3 +910,3 @@ render: sinon.stub() | ||
var err = new Error('A template must be provided'); | ||
form.options.template = undefined; | ||
req.form.options.template = undefined; | ||
form.render(req, res, cb); | ||
@@ -623,3 +926,4 @@ cb.should.have.been.calledOnce.and.calledWithExactly(err); | ||
body: { field: 'value' }, | ||
flash: sinon.stub() | ||
flash: sinon.stub(), | ||
form: { options: form.options } | ||
}); | ||
@@ -643,3 +947,3 @@ res = { | ||
sinon.stub(Form.prototype, '_getForkTarget').returns('/fork'); | ||
form.options.forks = []; | ||
req.form.options.forks = []; | ||
}); | ||
@@ -694,3 +998,4 @@ | ||
body: { field: 'value' }, | ||
flash: sinon.stub() | ||
flash: sinon.stub(), | ||
form: { options: form.options } | ||
}); | ||
@@ -701,3 +1006,3 @@ }); | ||
req.form.values['example-radio'] = 'conditionMet'; | ||
form.options.forks = [{ | ||
req.form.options.forks = [{ | ||
target: '/target-page', | ||
@@ -714,3 +1019,3 @@ condition: { | ||
req.form.values['example-radio'] = 'conditionNotMet'; | ||
form.options.forks = [{ | ||
req.form.options.forks = [{ | ||
target: '/target-page', | ||
@@ -726,3 +1031,3 @@ condition: { | ||
it('returns the fork target if the condition function is met', function () { | ||
form.options.forks = [{ | ||
req.form.options.forks = [{ | ||
target: '/target-page', | ||
@@ -737,3 +1042,3 @@ condition: function () { | ||
it('returns the original next target if the condition function is not met', function () { | ||
form.options.forks = [{ | ||
req.form.options.forks = [{ | ||
target: '/target-page', | ||
@@ -752,6 +1057,6 @@ condition: function () { | ||
beforeEach(function () { | ||
req.form = { values: { | ||
req.form.values = { | ||
'example-radio': 'condition-met' | ||
}}; | ||
form.options.forks = [{ | ||
}; | ||
req.form.options.forks = [{ | ||
target: '/target-page', | ||
@@ -780,3 +1085,3 @@ condition: { | ||
beforeEach(function () { | ||
form.options.forks = [{ | ||
req.form.options.forks = [{ | ||
target: '/target-page', | ||
@@ -797,6 +1102,6 @@ condition: { | ||
it('returns the last forks\' target if each condition is met', function () { | ||
req.form = { values: { | ||
req.form.values = { | ||
'example-radio': 'conditionMet', | ||
'example-email': 'conditionMet' | ||
}}; | ||
}; | ||
form._getForkTarget(req, {}).should.contain('/target-page-2'); | ||
@@ -912,3 +1217,3 @@ }); | ||
var form; | ||
var form, req, res, cb; | ||
beforeEach(function () { | ||
@@ -936,6 +1241,3 @@ form = new Form({ | ||
}); | ||
}); | ||
it('should *only* place errors against a single error key if the validator that created them belongs to a group', function () { | ||
var req = request({ | ||
req = request({ | ||
flash: sinon.stub(), | ||
@@ -947,8 +1249,11 @@ form: { | ||
'is-thing-c': '' | ||
} | ||
}, | ||
options: form.options | ||
} | ||
}); | ||
var res = {}; | ||
var cb = sinon.stub(); | ||
res = {}; | ||
cb = sinon.stub(); | ||
}); | ||
it('should *only* place errors against a single error key if the validator that created them belongs to a group', function () { | ||
form._validate(req, res, cb); | ||
@@ -1028,6 +1333,6 @@ cb.should.be.calledWith({ | ||
'is-thing-notes': 'some notes' | ||
} | ||
}, | ||
options: form.options | ||
} | ||
}); | ||
form._validate(req, res, cb); | ||
@@ -1072,6 +1377,6 @@ cb.should.not.be.calledWithMatch({}); | ||
'is-thing-b': '' | ||
} | ||
}, | ||
options: form.options | ||
} | ||
}); | ||
form._validate(req, res, cb); | ||
@@ -1109,6 +1414,6 @@ cb.should.have.been.calledWith({ | ||
'is-thing-b': '' | ||
} | ||
}, | ||
options: form.options | ||
} | ||
}); | ||
form._validate(req, res, cb); | ||
@@ -1146,6 +1451,6 @@ cb.should.have.been.calledWith({ | ||
'is-thing-b': '' | ||
} | ||
}, | ||
options: form.options | ||
} | ||
}); | ||
form._validate(req, res, cb); | ||
@@ -1180,3 +1485,4 @@ cb.should.have.been.calledWith(); | ||
'field-2': '' | ||
} | ||
}, | ||
options: form.options | ||
} | ||
@@ -1196,3 +1502,4 @@ }); | ||
'field-2': '' | ||
} | ||
}, | ||
options: form.options | ||
} | ||
@@ -1199,0 +1506,0 @@ }); |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
108574
2554
172
0
5
+ Addedlodash.clonedeep@^4.5.0
+ Addedlodash.clonedeep@4.5.0(transitive)