Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

hmpo-form-controller

Package Overview
Dependencies
Maintainers
5
Versions
30
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

hmpo-form-controller - npm Package Compare versions

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

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc