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

@hmcts/one-per-page

Package Overview
Dependencies
Maintainers
13
Versions
67
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@hmcts/one-per-page - npm Package Compare versions

Comparing version 0.3.0 to 1.0.0-0

examples/test-app/docker-compose.yaml

41

CHANGELOG.md

@@ -0,1 +1,42 @@

<a name="1.0.0-0"></a>
# 1.0.0-0 (2017-09-29)
* Add basic field level joi validation ([c4752b1](https://github.com/hmcts/one-per-page/commit/c4752b1))
* Add validate and errors functions to form ([e6cd404](https://github.com/hmcts/one-per-page/commit/e6cd404))
* Create new flow module to provide consistent interface ([9ad9fe6](https://github.com/hmcts/one-per-page/commit/9ad9fe6))
* Enable multiple validations in fields ([878f1c7](https://github.com/hmcts/one-per-page/commit/878f1c7))
* Expose form and provide #validated for deciding whether to display errors ([d726346](https://github.com/hmcts/one-per-page/commit/d726346))
* Fix broken requireSession import ([cac6851](https://github.com/hmcts/one-per-page/commit/cac6851))
* Increase code coverage in forms package ([d9e0aef](https://github.com/hmcts/one-per-page/commit/d9e0aef))
* Move field in to the new forms module ([8ec2556](https://github.com/hmcts/one-per-page/commit/8ec2556))
* Move form in to it's own file ([b2ee033](https://github.com/hmcts/one-per-page/commit/b2ee033))
* Move session middleware in to session package ([0fe4cf7](https://github.com/hmcts/one-per-page/commit/0fe4cf7))
* Move session shims to it's own file ([faff544](https://github.com/hmcts/one-per-page/commit/faff544))
* Move sessionStore shims in to it's own file ([60656d0](https://github.com/hmcts/one-per-page/commit/60656d0))
* Proxy over the form to make accessing fields cleaner ([1d0c11e](https://github.com/hmcts/one-per-page/commit/1d0c11e))
* Remove unnecessary #invalidFields function ([7bb3b99](https://github.com/hmcts/one-per-page/commit/7bb3b99))
* Remove unnecessary setting of res.locals.fields ([575cfe5](https://github.com/hmcts/one-per-page/commit/575cfe5))
* Remove unused functions in field ([e86350c](https://github.com/hmcts/one-per-page/commit/e86350c))
* Reorgansise session in to session module ([23f2c2c](https://github.com/hmcts/one-per-page/commit/23f2c2c))
* Replace repository name in links ([04b5862](https://github.com/hmcts/one-per-page/commit/04b5862))
* Shorter coverage summary during local test runs ([9174987](https://github.com/hmcts/one-per-page/commit/9174987))
* Simplify the setting of options for session ([664934b](https://github.com/hmcts/one-per-page/commit/664934b))
* Split flow classes into their own files ([7cf5016](https://github.com/hmcts/one-per-page/commit/7cf5016))
* Switch the example app to use the new joi validation ([f31f94a](https://github.com/hmcts/one-per-page/commit/f31f94a))
* Fix: field.errors and .valid shouldn't run validations ([98584ab](https://github.com/hmcts/one-per-page/commit/98584ab))
* Fix: update example app to use fields proxy ([416890c](https://github.com/hmcts/one-per-page/commit/416890c))
* chore(package): update sinon to version 4.0.0 ([e507c33](https://github.com/hmcts/one-per-page/commit/e507c33))
<a name="0.5.0"></a>
# 0.5.0 (2017-09-26)
* Content (#26) ([1bb9911](https://github.com/hmcts/one-per-page/commit/1bb9911))
* Exit Step and Session Destroy (#23) ([7722475](https://github.com/hmcts/one-per-page/commit/7722475))
* Fix linting issues introduced in recent PRs ([f2d143c](https://github.com/hmcts/one-per-page/commit/f2d143c))
* Question validation (#25) ([4eb7e5d](https://github.com/hmcts/one-per-page/commit/4eb7e5d))
<a name="0.3.0"></a>

@@ -2,0 +43,0 @@ # 0.3.0 (2017-08-31)

8

examples/test-app/app.js

@@ -8,3 +8,3 @@ const config = require('config');

const CreateSession = require('./steps/CreateSession');
const Sessions = require('./steps/Sessions');
const Sessions = require('./steps/Session');
const Start = require('./steps/Start');

@@ -15,2 +15,3 @@ const Name = require('./steps/Name');

const ExitNorthernIreland = require('./steps/ExitNorthernIreland');
const Exit = require('./steps/Exit');

@@ -23,3 +24,3 @@ const app = express();

baseUrl,
express: { views: [path.resolve(__dirname, 'views')] },
express: { views: [path.resolve(__dirname, 'steps')] },
webpack: { entry: [path.resolve(__dirname, 'assets/scss/main.scss')] }

@@ -38,3 +39,4 @@ });

new Country(),
new ExitNorthernIreland()
new ExitNorthernIreland(),
new Exit()
],

@@ -41,0 +43,0 @@ session: {

@@ -17,3 +17,5 @@ {

"copy-webpack-plugin": "^4.0.1",
"express": "^4.15.3"
"express": "^4.15.3",
"i18next": "^9.0.0",
"joi": "^10.6.0"
},

@@ -20,0 +22,0 @@ "devDependencies": {

{
"name": "@hmcts/one-per-page",
"description": "One question per page apps made easy",
"version": "0.3.0",
"version": "1.0.0-0",
"main": "./src/main.js",

@@ -14,2 +14,4 @@ "dependencies": {

"http-status-codes": "^1.2.0",
"i18next": "^9.0.0",
"joi": "^11.1.1",
"js-yaml": "^3.9.0",

@@ -33,3 +35,3 @@ "nunjucks": "^3.0.1",

"proxyquire": "^1.8.0",
"sinon": "^3.2.1",
"sinon": "^4.0.0",
"sinon-chai": "^2.13.0",

@@ -45,3 +47,3 @@ "supertest": "^3.0.0",

"lint": "eslint .",
"test": "NODE_PATH=. NODE_ENV=testing nyc mocha 'test/**/*.test.js'"
"test": "NODE_PATH=. NODE_ENV=testing nyc --reporter=text-summary mocha 'test/**/*.test.js'"
},

@@ -48,0 +50,0 @@ "license": "MIT",

@@ -26,3 +26,3 @@ # One per page

[Dockerfile]:https://github.com/hmcts/nodejs-one-per-page/blob/master/Dockerfile
[docker-compose.yml]:https://github.com/hmcts/nodejs-one-per-page/blob/master/docker-compose.yml
[Dockerfile]:https://github.com/hmcts/one-per-page/blob/master/Dockerfile
[docker-compose.yml]:https://github.com/hmcts/one-per-page/blob/master/docker-compose.yml

@@ -1,3 +0,4 @@

const sessions = require('./services/sessions');
const session = require('./session');
const urlParse = require('url-parse');
const defaultIfUndefined = require('./util/defaultIfUndefined');

@@ -11,14 +12,33 @@ const parseUrl = baseUrl => {

const journey = (app, {
baseUrl,
steps = [],
session = {},
noSessionHandler
} = {}) => {
const options = userOpts => {
let sessionProvider = null;
if (typeof userOpts.session === 'function') {
sessionProvider = userOpts.session;
} else {
const cookie = Object.assign(
{ domain: parseUrl(userOpts.baseUrl).hostname },
userOpts.session.cookie || {}
);
const sessionOpts = Object.assign({}, userOpts.session, { cookie });
sessionProvider = session(sessionOpts);
}
return {
baseUrl: userOpts.baseUrl,
steps: defaultIfUndefined(userOpts.steps, []),
session: sessionProvider,
noSessionHandler: userOpts.noSessionHandler
};
};
const journey = (app, userOpts) => {
const opts = options(userOpts);
const setupMiddleware = (req, res, next) => {
req.journey = req.journey || {};
if (typeof noSessionHandler !== 'undefined') {
req.journey.noSessionHandler = noSessionHandler;
if (typeof opts.noSessionHandler !== 'undefined') {
req.journey.noSessionHandler = opts.noSessionHandler;
}
steps.forEach(step => {
opts.steps.forEach(step => {
req.journey[step.name] = step;

@@ -28,17 +48,10 @@ });

};
app.use(setupMiddleware);
app.use(opts.session);
if (typeof session === 'function') {
app.use(session);
} else {
const cookie = Object.assign(
{ domain: parseUrl(baseUrl).hostname },
session.cookie || {}
);
const sessionOptions = Object.assign({}, session, { cookie });
app.use(sessions(sessionOptions));
}
steps.forEach(step => {
opts.steps.forEach(step => {
app.use(step.router);
});
return app;

@@ -45,0 +58,0 @@ };

@@ -5,2 +5,3 @@ const BaseStep = require('./steps/BaseStep');

const EntryPoint = require('./steps/EntryPoint');
const ExitPoint = require('./steps/ExitPoint');
const Redirect = require('./steps/Redirect');

@@ -10,11 +11,12 @@

const requireSession = require('./middleware/requireSession');
const requireSession = require('./session/requireSession');
const parseRequest = require('./middleware/parseRequest');
const { field, form } = require('./services/fields');
const { field, form } = require('./forms');
const { goTo, branch } = require('./services/flow');
const { goTo, branch } = require('./flow');
module.exports = {
EntryPoint,
ExitPoint,
Redirect,

@@ -21,0 +23,0 @@ BaseStep,

@@ -1,8 +0,2 @@

const defineNotEnumberable = (obj, prop, value) => {
Object.defineProperty(obj, prop, {
enumerable: false,
value,
writable: true
});
};
const formProxyHandler = require('../forms/formProxyHandler');

@@ -12,29 +6,22 @@ const parseRequest = (req, res, next) => {

res.locals = res.locals || {};
res.locals.fields = req.fields;
req.currentStep.fields = req.fields;
if (typeof req.currentStep.form === 'undefined') {
const form = req.currentStep.form;
if (typeof form === 'undefined') {
next();
return;
}
const form = req.currentStep.form;
if (req.method === 'POST') {
const parsedFields = form.parse(req);
parsedFields.forEach(field => {
req.fields[field.name] = field;
});
form.parse(req);
form.validate();
} else if (req.method === 'GET') {
const retrievedFields = form.retrieve(req);
retrievedFields.forEach(field => {
req.fields[field.name] = field;
});
form.retrieve(req);
}
defineNotEnumberable(req.fields, 'validate', () => form.validate(req.fields));
defineNotEnumberable(req.fields, 'store', () => form.store(req));
defineNotEnumberable(req.fields, 'errors', () => form.errors(req.fields));
defineNotEnumberable(req.fields, 'valid', () => form.valid(req.fields));
// set accessors for fields
req.form = form;
req.fields = new Proxy(form, formProxyHandler);
res.locals.form = req.form;
res.locals.fields = req.fields;
req.currentStep.fields = req.fields;

@@ -41,0 +28,0 @@ next();

@@ -6,2 +6,3 @@ const { Router: expressRouter } = require('express');

req.currentStep = step;
step.locals = res.locals;
step.journey = req.journey;

@@ -30,2 +31,7 @@ next();

});
if (this.afterMiddleware) {
this.afterMiddleware.forEach(middleware => {
this._router.all(this.url, middleware.bind(this));
});
}
this._router.all(this.url, this.handler.bind(this));

@@ -32,0 +38,0 @@ return this._router;

const Redirect = require('./Redirect');
const createSession = require('../middleware/createSession');
const createSession = require('../session/createSession');

@@ -4,0 +4,0 @@ class EntryPoint extends Redirect {

const BaseStep = require('./BaseStep');
const addLocals = require('../middleware/addLocals');
const { METHOD_NOT_ALLOWED } = require('http-status-codes');
const i18next = require('i18next');
const walkMap = require('../util/walkMap');
const applyContent = require('../middleware/applyContent');
class Page extends BaseStep {
constructor() {
super();
if (this.i18NextContent) {
this.i18next = i18next.createInstance();
this.i18next.init({
lng: 'en',
resources: this.i18NextContent
});
}
}
get content() {
if (!this.i18next) {
return {};
}
return walkMap(this.i18NextContent.en.translation, path =>
this.i18next.t(path, this.locals));
}
get middleware() {

@@ -10,4 +34,8 @@ return [addLocals];

get afterMiddleware() {
return [applyContent];
}
get template() {
return this.name;
return `${this.name}/template`;
}

@@ -14,0 +42,0 @@

const Page = require('./Page');
const requireSession = require('./../middleware/requireSession');
const requireSession = require('./../session/requireSession');
const parseRequest = require('../middleware/parseRequest');
const bodyParser = require('body-parser');
const { expectImplemented } = require('../errors/expectImplemented');
const { METHOD_NOT_ALLOWED } = require('http-status-codes');
const { expectImplemented } = require('../errors/expectImplemented');

@@ -23,6 +23,2 @@ class Question extends Page {

get template() {
return this.name;
}
handler(req, res) {

@@ -32,5 +28,7 @@ if (req.method === 'GET') {

} else if (req.method === 'POST') {
if (this.fields.valid()) {
this.fields.store();
if (req.form.valid) {
req.form.store(req);
this.next().redirect(req, res);
} else {
res.render(this.template);
}

@@ -37,0 +35,0 @@ } else {

@@ -7,3 +7,3 @@ const proxyquire = require('proxyquire');

const Page = require('./../src/steps/Page');
const sessions = require('./../src/services/sessions');
const session = require('./../src/session');

@@ -23,7 +23,10 @@ const testUrl = '/test/page';

};
const options = (...overrides) => Object.assign(
{},
defaultOptions,
...overrides
);
const options = (...overrides) => {
const foo = Object.assign(
{},
defaultOptions,
...overrides
);
return foo;
};
const handlerTest = ({ test, options: extraOptions }) => {

@@ -100,6 +103,6 @@ const testPage = new class extends Page {

beforeEach(() => {
spy = sinon.spy(sessions);
spy = sinon.spy(session);
stubbedJourney = proxyquire(
'../src/Journey',
{ './services/sessions': spy }
{ './session': spy }
);

@@ -136,3 +139,3 @@ });

};
const app = journey(testApp(), { session: sessionOverride });
const app = journey(testApp(), options({ session: sessionOverride }));
return supertest(app)

@@ -150,6 +153,6 @@ .get('/')

it('configures the session middleware', () => {
const spy = sinon.spy(sessions);
const spy = sinon.spy(session);
const stubbedJourney = proxyquire(
'../src/Journey',
{ './services/sessions': spy }
{ './session': spy }
);

@@ -156,0 +159,0 @@ const domain = '127.0.0.1';

@@ -5,3 +5,8 @@ const { expect, sinon } = require('../util/chai');

const parseRequest = require('../../src/middleware/parseRequest');
const { field, form } = require('../../src/services/fields.js');
const {
field,
form,
Form,
FieldDesriptor
} = require('../../src/forms');

@@ -38,6 +43,8 @@ const handlerTest = (_form, { method = 'get', assertions }) => {

it('attaches a FieldDesriptor for each field to req.fields.[name]', () => {
return handlerTest(form(field('foo'), field('bar')), {
const foo = field('foo');
const bar = field('bar');
return handlerTest(form(foo, bar), {
assertions(req) {
expect(req.fields).to.have.property('foo');
expect(req.fields).to.have.property('bar');
expect(req.fields.foo).to.be.an.instanceof(FieldDesriptor);
expect(req.fields.bar).to.be.an.instanceof(FieldDesriptor);
}

@@ -47,14 +54,6 @@ });

it('attaches #valid to req.fields', () => {
return handlerTest(form(), {
assertions(req) {
expect(req.fields.validate).to.be.a('function');
}
});
});
it('attaches req.fields to the currentStep (this in handler)', () => {
return handlerTest(form(field('foo'), field('bar')), {
assertions(req) {
expect(req.currentStep.fields).to.eql(req.fields);
expect(req.currentStep.fields).to.be.an.instanceof(Form);
}

@@ -85,5 +84,8 @@ });

it('has a field for each declared field', () => {
return handlerTest(form(field('foo'), field('bar')), {
const foo = field('foo');
const bar = field('bar');
return handlerTest(form(foo, bar), {
assertions(req) {
expect(req.fields).to.have.keys(['foo', 'bar']);
expect(req.fields.foo).to.equal(foo);
expect(req.fields.bar).to.equal(bar);
}

@@ -99,3 +101,3 @@ });

assertions(req) {
expect(req.fields).to.have.key('fake');
expect(req.fields.fake).to.equal(fakeField);
expect(req.fields.fake).to.have.property('name', 'fake');

@@ -114,3 +116,3 @@ }

assertions(req) {
expect(req.fields).to.have.key('fake');
expect(req.fields.fake).to.equal(fakeField);
expect(req.fields.fake).to.have.property('name', 'fake');

@@ -117,0 +119,0 @@ }

const { testStep } = require('../util/supertest');
const { expect } = require('../util/chai');
const EntryPoint = require('../../src/steps/EntryPoint');
const { goTo } = require('../../src/services/flow');
const { goTo } = require('../../src/flow');

@@ -6,0 +6,0 @@ describe('steps/EntryPoint', () => {

@@ -48,2 +48,71 @@ const Page = require('./../../src/steps/Page');

describe('content', () => {
const content = { title: 'some title' };
it('creates i18next if i18NextContent exsists', () => {
const page = new class extends Page {
get url() {
return '/my/page';
}
get template() {
return 'page_views/simplePage';
}
get i18NextContent() {
return content;
}
}();
expect(page.i18next).to.not.undefined;
});
it('does not create i18next if i18NextContent does not exsists', () => {
const page = new class extends Page {
get url() {
return '/my/page';
}
get template() {
return 'page_views/simplePage';
}
}();
expect(page.i18next).to.be.undefined;
});
it('transforms session items in content', () => {
const contentWithSessionData = {
en: {
translation: {
title: 'some title {{ session.foo }}',
subTitle: 'some subtitle {{ session.bar }}'
}
}
};
const session = { foo: 'some value', bar: 'some other value' };
const page = new class extends Page {
get url() {
return '/my/page';
}
get template() {
return 'page_views/simplePage';
}
get i18NextContent() {
return contentWithSessionData;
}
}();
page.locals = { session };
expect(page.content.title).to.eql('some title some value');
expect(page.content.subTitle).to.eql('some subtitle some other value');
});
it('returns empty content if no i18NextContent exsists', () => {
const page = new class extends Page {
get url() {
return '/my/page';
}
get template() {
return 'page_views/simplePage';
}
}();
expect(page.content).to.eql({});
});
});
it('has access to the session', () => {

@@ -50,0 +119,0 @@ const page = new class extends Page {

@@ -1,7 +0,7 @@

const { expect } = require('../util/chai');
const { expect, sinon } = require('../util/chai');
const { testStep } = require('../util/supertest');
const Question = require('../../src/steps/Question');
const { NotImplemented } = require('../../src/errors/expectImplemented');
const { field, form } = require('../../src/services/fields');
const { goTo } = require('../../src/services/flow');
const { field, form } = require('../../src/forms');
const { goTo } = require('../../src/flow');
const { METHOD_NOT_ALLOWED } = require('http-status-codes');

@@ -56,3 +56,3 @@

}();
expect(foo.template).to.eql(foo.name);
expect(foo.template).to.eql(`${foo.name}/template`);
});

@@ -62,3 +62,3 @@ });

{
const question = new class extends Question {
const SimpleQuestion = class extends Question {
get form() {

@@ -77,4 +77,6 @@ return form(field('name'));

}
}();
};
const question = new SimpleQuestion();
describe('GET', () => {

@@ -94,7 +96,7 @@ it('renders the page on GET', () => {

req.session.generate();
req.session.Question_name = 'Michael Allen';
req.session.SimpleQuestion_name = 'Michael Allen';
})
.get()
.html($ => {
expect($('#Question_name')).to.contain.$val('Michael Allen');
expect($('#SimpleQuestion_name')).to.contain.$val('Michael Allen');
});

@@ -112,9 +114,58 @@ });

return postRequest.session(session => {
expect(session).to.contain.key('Question_name');
expect(session).to.contain.key('SimpleQuestion_name');
});
});
it('redirects to the next step if valid', () => {
it('redirects to the next step', () => {
return postRequest.expect(302).expect('Location', '/next-step');
});
describe('is invalid', () => {
const errorMessage = 'Error message';
const returnIsInvalid = sinon.stub().returns(errorMessage);
const InvalidQuestion = class extends SimpleQuestion {
get url() {
return '/question-1';
}
get form() {
return form(
field('name').validate(returnIsInvalid)
);
}
};
const invalidQuestion = new InvalidQuestion();
it('renders step with error message', () => {
return testStep(invalidQuestion)
.withSetup(req => req.session.generate())
.withField('name', 'Invalid Answer')
.post()
.html($ => {
return expect($('.error-message')).to.contain.$text(errorMessage);
});
});
});
describe('is valid', () => {
const returnIsValid = sinon.stub().returns();
const ValidQuestion = class extends SimpleQuestion {
get url() {
return '/next-step';
}
get form() {
return form(
field('name').validate(returnIsValid)
);
}
};
const validQuestion = new ValidQuestion();
it('redirects to the next step if valid', () => {
const response = testStep(validQuestion)
.withSetup(req => req.session.generate())
.withField('name', 'valid answer')
.post();
return response.expect(302).expect('Location', '/next-step');
});
});
});

@@ -121,0 +172,0 @@

const { testStep } = require('../util/supertest');
const { expect } = require('../util/chai');
const Redirect = require('../../src/steps/Redirect');
const { goTo } = require('../../src/services/flow');
const { goTo } = require('../../src/flow');

@@ -6,0 +6,0 @@ describe('steps/Redirect', () => {

const express = require('express');
const supertest = require('supertest');
const sessions = require('./../../src/services/sessions');
const session = require('./../../src/session');
const nunjucks = require('express-nunjucks');
const zepto = require('zepto-node');
const domino = require('domino');
const { expect } = require('../util/chai');

@@ -42,6 +43,6 @@ function testApp() {

app.use(sessions({ baseUrl: '127.0.0.1', secret: 'keyboard cat' }));
app.use(session({ baseUrl: '127.0.0.1', secret: 'keyboard cat' }));
app.get('/supertest-check-session', (req, res) => {
const session = Object.assign(
const currentSession = Object.assign(
{},

@@ -51,3 +52,3 @@ req.session,

);
res.end(JSON.stringify(session));
res.end(JSON.stringify(currentSession));
});

@@ -78,4 +79,4 @@

}).then(res => {
const session = JSON.parse(res.text);
return Promise.all([assertions(session)]);
const currentSession = JSON.parse(res.text);
return Promise.all([assertions(currentSession)]);
});

@@ -86,2 +87,16 @@ };

const shouldNotSetCookie = name => {
return res => Promise.all([
expect(Object.keys(res.headers)).to.not.include('set-cookie'),
expect(res.headers['set-cookie']).to.not.include.match(name)
]);
};
const shouldSetCookie = name => {
return res => Promise.all([
expect(Object.keys(res.headers)).to.include('set-cookie'),
expect(res.headers['set-cookie']).to.include.match(name)
]).then(() => res);
};
class TestStepDSL {

@@ -98,5 +113,5 @@ constructor(step, body = {}, middleware = []) {

withSession(session) {
withSession(sessionData) {
return this.withMiddleware((req, res, next) => {
req.session = Object.assign(req.session, session);
req.session = Object.assign(req.session, sessionData);
next();

@@ -135,3 +150,5 @@ });

if (Object.keys(this.body).length !== 0) {
return postRequest.send(this.body);
return postRequest
.type('form')
.send(this.body);
}

@@ -154,3 +171,5 @@ return postRequest;

testApp,
testStep: TestStepDSL.create
testStep: TestStepDSL.create,
shouldNotSetCookie,
shouldSetCookie
};

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

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