Skip to content

Commit

Permalink
V13 Pre-refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
BenSurgisonGDS committed Sep 28, 2022
1 parent 8de4e2a commit 46a1a50
Show file tree
Hide file tree
Showing 9 changed files with 213 additions and 148 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
### New Features

- [#1476: Update to GOV.UK Frontend 4.2.0](https://github.com/alphagov/govuk-prototype-kit/pull/1476)
- [#1624: V13 pre refactor](https://github.com/alphagov/govuk-prototype-kit/pull/1624)
- Add support for globals

### Fixes

Expand Down
24 changes: 12 additions & 12 deletions internal_docs/releasing.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,32 +33,32 @@ v8.0.0 // After implementing backwards incompatible changes

5. If the major version has changed make sure it's updated for the plugins in `GOVUKPrototypeKit.majorVersion` (JS) and `$govuk-prototype-kit-major-version` (SASS).

5. Update the [CHANGELOG.md](/CHANGELOG.md) by:
6. Update the [CHANGELOG.md](/CHANGELOG.md) by:

- changing the 'Unreleased' heading to the new version-number and release-type - for example, '12.0.1 (Fix release)'
- adding a new 'Unreleased' heading above the new version-number and release-type, so users will know where to add PRs to the changelog

6. Update the version number in [VERSION.txt](/VERSION.txt) and update "version" in [package.json](/package.json#L4).
7. Update the version number in [VERSION.txt](/VERSION.txt) and update "version" in [package.json](/package.json#L4).

7. Run `npm install` to update `package-lock.json`.
8. Run `npm install` to update `package-lock.json`.

8. Commit your changes and open a new pull request on GitHub - copy the relevant Changelog section into the description.
9. Commit your changes and open a new pull request on GitHub - copy the relevant Changelog section into the description.

9. Once someone has merged the pull request, [draft a new release on GitHub](https://github.com/alphagov/govuk-prototype-kit/releases)
10. Once someone has merged the pull request, [draft a new release on GitHub](https://github.com/alphagov/govuk-prototype-kit/releases)

10. In Tag version and Release title, put v[version number], for example `v7.0.0`.
11. In Tag version and Release title, put v[version number], for example `v7.0.0`.

11. In the description, paste the relevant section from the release notes in the Google Doc.
12. In the description, paste the relevant section from the release notes in the Google Doc.

12. Checkout the *main* branch and pull the latest changes.
13. Checkout the *main* branch and pull the latest changes.

13. Run `node scripts/create-release-archive`, which will generate a ZIP in the root of this project.
14. Run `node scripts/create-release-archive`, which will generate a ZIP in the root of this project.

14. Attach the generated ZIP to the release.
15. Attach the generated ZIP to the release.

15. Click 'Publish release'.
16. Click 'Publish release'.

16. Let the community know about the release
17. Let the community know about the release

Write a brief summary with highlights from the release then send it to the following slack channels:

Expand Down
6 changes: 5 additions & 1 deletion lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ const appConfigPath = path.join(appDir, 'config.json')
function getConfigFromFile () {
const configFileExists = fse.existsSync(appConfigPath)
if (configFileExists) {
return fse.readJsonSync(appConfigPath)
try {
return fse.readJsonSync(appConfigPath)
} catch (e) {
console.error(`Could not load config from ${appConfigPath}, please check your JSON is well formed.`)
}
}
return {}
}
Expand Down
38 changes: 29 additions & 9 deletions lib/filters/api.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
let environment
const filtersToAddToEnv = []
const whenEnvIsAvailable = []

const runWhenEnvIsAvailable = (fn) => {
if (environment) {
fn()
} else {
whenEnvIsAvailable.push(fn)
}
}

const addFilterToEnvironment = (name, fn, config) => {
let fnToAdd = fn
Expand All @@ -12,13 +20,22 @@ const addFilterToEnvironment = (name, fn, config) => {
environment.addFilter(name, fnToAdd)
}

const addGlobalToEnvironment = (name, value) => {
environment.addGlobal(name, value)
}

const addFilter = (name, fn, config) => {
if (environment) {
runWhenEnvIsAvailable(() => {
addFilterToEnvironment(name, fn, config)
} else {
filtersToAddToEnv.push({ name, fn, config })
}
})
}

const addGlobal = (name, value) => {
runWhenEnvIsAvailable(() => {
addGlobalToEnvironment(name, value)
})
}

const getFilter = (name) => {
if (!environment) {
console.warn(`Trying to get filter before the environment is set, couldn't retrieve filter [${name}]`)
Expand All @@ -34,17 +51,20 @@ const getFilter = (name) => {
}
}
}

const setEnvironment = (env) => {
environment = env
while (filtersToAddToEnv.length > 0) {
const { name, fn, config } = filtersToAddToEnv.shift()
addFilterToEnvironment(name, fn, config)
while (whenEnvIsAvailable.length > 0) {
const fn = whenEnvIsAvailable.shift()
fn()
}
}

module.exports = {
external: {
addFilter,
getFilter
getFilter,
addGlobal
},
setEnvironment
}
18 changes: 6 additions & 12 deletions lib/routes/api.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const express = require('express')

const routers = require('./api')
const fakeValues = {
router: {},
router: { use: () => {} },
static: {}
}

Expand All @@ -19,47 +19,41 @@ describe('routes API', () => {
routers.resetState()
})
it('should return a router', () => {
const fakeRouter = {}

const router = routers.external.setupRouter()

expect(router).toEqual(fakeRouter)
expect(router).toBe(fakeValues.router)
})
it('should set the router up with the app', () => {
const fakeRouter = {}
const fakeApp = { use: jest.fn() }

routers.setApp(fakeApp)
routers.external.setupRouter()

expect(fakeApp.use).toHaveBeenCalledWith('/', fakeRouter)
expect(fakeApp.use).toHaveBeenCalledWith('/', fakeValues.router)
})
it('should set the router up with the app even if the app is included after the router is created', () => {
const fakeRouter = {}
const fakeApp = { use: jest.fn() }

routers.external.setupRouter()
routers.setApp(fakeApp)

expect(fakeApp.use).toHaveBeenCalledWith('/', fakeRouter)
expect(fakeApp.use).toHaveBeenCalledWith('/', fakeValues.router)
})
it('should allow the user to set the path of the router', () => {
const fakeRouter = {}
const fakeApp = { use: jest.fn() }

routers.setApp(fakeApp)
routers.external.setupRouter('/my-path')

expect(fakeApp.use).toHaveBeenCalledWith('/my-path', fakeRouter)
expect(fakeApp.use).toHaveBeenCalledWith('/my-path', fakeValues.router)
})
it('should allow the user to set the path of the router when the app is included after the router', () => {
const fakeRouter = {}
const fakeApp = { use: jest.fn() }

routers.external.setupRouter('/my-path')
routers.setApp(fakeApp)

expect(fakeApp.use).toHaveBeenCalledWith('/my-path', fakeRouter)
expect(fakeApp.use).toHaveBeenCalledWith('/my-path', fakeValues.router)
})
it('should error if anything other than a string is provided for the path', () => {
const expectedError = new Error('setupRouter cannot be provided with a router, it sets up a router and returns it to you.')
Expand Down
149 changes: 149 additions & 0 deletions lib/sessionUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
const { getConfig } = require('./config')
const sessionInCookie = require('client-sessions')
const sessionInMemory = require('express-session')
const path = require('path')
const { projectDir } = require('./path-utils')
const { get: getKeypath } = require('lodash')

// Add Nunjucks function called 'checked' to populate radios and checkboxes
const addCheckedFunction = function (env) {
env.addGlobal('checked', function (name, value) {
// Check data exists
if (this.ctx.data === undefined) {
return ''
}

// Use string keys or object notation to support:
// checked("field-name")
// checked("['field-name']")
// checked("['parent']['field-name']")
name = !name.match(/[.[]/g) ? `['${name}']` : name
var storedValue = getKeypath(this.ctx.data, name)

// Check the requested data exists
if (storedValue === undefined) {
return ''
}

var checked = ''

// If data is an array, check it exists in the array
if (Array.isArray(storedValue)) {
if (storedValue.indexOf(value) !== -1) {
checked = 'checked'
}
} else {
// The data is just a simple value, check it matches
if (storedValue === value) {
checked = 'checked'
}
}
return checked
})
}

// Store data from POST body or GET query in session
const storeData = function (input, data) {
for (const i in input) {
// any input where the name starts with _ is ignored
if (i.indexOf('_') === 0) {
continue
}

let val = input[i]

// Delete values when users unselect checkboxes
if (val === '_unchecked' || val === ['_unchecked']) {
delete data[i]
continue
}

// Remove _unchecked from arrays of checkboxes
if (Array.isArray(val)) {
val = val.filter((item) => item !== '_unchecked')
} else if (typeof val === 'object') {
// Store nested objects that aren't arrays
if (typeof data[i] !== 'object') {
data[i] = {}
}

// Add nested values
storeData(val, data[i])
continue
}

data[i] = val
}
}

// Get session default data from file

let sessionDataDefaults

const sessionDataDefaultsFile = path.join(projectDir, 'app', 'data', 'session-data-defaults.js')

try {
sessionDataDefaults = require(sessionDataDefaultsFile)
} catch (e) {
sessionDataDefaults = {}
}
// Middleware - store any data sent in session, and pass it to all views

const autoStoreData = function (req, res, next) {
if (!req.session.data) {
req.session.data = {}
}

req.session.data = Object.assign({}, sessionDataDefaults, req.session.data)

storeData(req.body, req.session.data)
storeData(req.query, req.session.data)

// Send session data to all views

res.locals.data = {}

for (var j in req.session.data) {
res.locals.data[j] = req.session.data[j]
}

next()
}

const getSessionNameFromServiceName = (serviceName) => {
return 'govuk-prototype-kit-' + (Buffer.from(serviceName, 'utf8')).toString('hex')
}

const getSessionMiddleware = () => {
// Session uses service name to avoid clashes with other prototypes
const sessionName = getSessionNameFromServiceName(getConfig().serviceName)
const sessionHours = 4
const sessionOptions = {
secret: sessionName,
cookie: {
maxAge: 1000 * 60 * 60 * sessionHours,
secure: getConfig().isSecure
}
}

// Support session data in cookie or memory
if (getConfig().useCookieSessionStore) {
return sessionInCookie(Object.assign(sessionOptions, {
cookieName: sessionName,
proxy: true,
requestKey: 'session'
}))
} else {
return sessionInMemory(Object.assign(sessionOptions, {
name: sessionName,
resave: false,
saveUninitialized: false
}))
}
}

module.exports = {
addCheckedFunction,
getSessionMiddleware,
autoStoreData
}
10 changes: 5 additions & 5 deletions lib/utils.test.js → lib/sessionUtils.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,21 @@

const nunjucks = require('nunjucks')

const utils = require('./utils.js')
const sessionUtils = require('./sessionUtils.js')

describe('checked', () => {
var ctx, checked

beforeAll(() => {
var env = new nunjucks.Environment()
utils.addCheckedFunction(env)
sessionUtils.addCheckedFunction(env)
ctx = { data: {} }
checked = env.getGlobal('checked').bind({ ctx })
})

it('can be added as global function to a nunjucks env', () => {
var env = new nunjucks.Environment()
utils.addCheckedFunction(env)
sessionUtils.addCheckedFunction(env)
expect(env.getGlobal('checked')).toBeDefined()
})

Expand Down Expand Up @@ -71,7 +71,7 @@ describe('autoStoreData', () => {
doIncludeMe: 'include me',
existingData: 'existing data'
}
utils.autoStoreData(req, res, () => {
sessionUtils.autoStoreData(req, res, () => {
expect(res.locals.data).toEqual(expectedData)
expect(req.session.data).toEqual(expectedData)
})
Expand All @@ -90,7 +90,7 @@ describe('autoStoreData', () => {
checkBoxes3: ['cb3-1', 'cb3-2'],
existingData: 'existing data'
}
utils.autoStoreData(req, res, () => {
sessionUtils.autoStoreData(req, res, () => {
expect(res.locals.data).toEqual(expectedData)
expect(req.session.data).toEqual(expectedData)
})
Expand Down
Loading

0 comments on commit 46a1a50

Please sign in to comment.