Skip to content

Commit

Permalink
Merge pull request #2164 from alphagov/2091-get-live-reloading-workin…
Browse files Browse the repository at this point in the history
…g-within-the-error-screen

Get live reloading working with the error screen
  • Loading branch information
BenSurgisonGDS committed May 15, 2023
2 parents 053195c + f15f973 commit cbd6bf8
Show file tree
Hide file tree
Showing 14 changed files with 133 additions and 30 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test-acceptance.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
matrix:
node-version: [18.x]
os: [macos-latest, windows-latest, ubuntu-latest]
type: [smoke, plugins, dev, prod]
type: [smoke, plugins, dev, prod, errors]

name: Acceptance ${{ matrix.type }} test kit on Node v${{ matrix.node-version }} (${{ matrix.os }})
runs-on: ${{ matrix.os }}
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### New features

- [#2164: Get live reloading working with the error screen](https://github.com/alphagov/govuk-prototype-kit/pull/2164)

## 13.7.0

### New features
Expand Down
18 changes: 10 additions & 8 deletions cypress/e2e/dev/1-watch-files/watch-config.cypress.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,23 @@ const appConfig = path.join(Cypress.env('projectFolder'), appConfigPath)
const originalText = 'Service name goes here'
const newText = 'Cypress test'

const serverNameQuery = 'a.govuk-header__link.govuk-header__service-name, a.govuk-header__link--service-name'
const serverNameQuery = 'h1.govuk-heading-xl'

function restore () {
// Restore config.json from prototype starter
cy.task('copyFromStarterFiles', { filename: appConfigPath })
}

describe('watch config file', () => {
describe(`service name in config file ${appConfig} should be changed and restored`, () => {
before(() => {
// Restore config.json from prototype starter
cy.task('copyFromStarterFiles', { filename: appConfigPath })
})
before(restore)
after(restore)

it('The service name should change to "cypress test"', () => {
waitForApplication()
cy.visit('/')
waitForApplication('/')
cy.get(serverNameQuery).contains(originalText)
cy.task('replaceTextInFile', { filename: appConfig, originalText, newText })
waitForApplication()
waitForApplication('/')
cy.get(serverNameQuery).contains(newText)
})
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,34 @@ const pageName = 'There is an error'
const contactSupportText = 'You can try and fix this yourself or contact the GOV.UK Prototype Kit team if you need help.'
const expectedErrorText = 'ReferenceError: lkewjflkjadsf is not defined'

describe('Fatal Error Test', () => {
before(() => {
cy.task('log', `Replace ${appRoutes} with Cypress routes`)
cy.task('copyFile', { source: completelyBrokenRoutesFixture, target: appRoutes })
})
const homePageName = 'GOV.UK Prototype Kit'

after(() => {
cy.task('log', `Restore ${appRoutesPath}`)
cy.task('copyFromStarterFiles', { filename: appRoutesPath })
})
function restore () {
// Restore config.json from prototype starter
cy.task('log', `Restore ${appRoutesPath}`)
cy.task('copyFromStarterFiles', { filename: appRoutesPath })
}

describe('Fatal Error Test', () => {
before(restore)
after(restore)

it('fatal error should show an error page', () => {
cy.task('waitUntilAppRestarts')
cy.visit('/', { failOnStatusCode: false })

cy.get('.govuk-heading-l').contains(homePageName)

cy.task('log', `Replace ${appRoutes} with Broken routes`)
cy.task('copyFile', { source: completelyBrokenRoutesFixture, target: appRoutes })

cy.get('.govuk-heading-l').contains(pageName)
cy.get('.govuk-body').contains(contactSupportText)
cy.get('code').contains(expectedErrorText)

cy.task('log', `Restore ${appRoutes} with original routes`)
restore()

cy.get('.govuk-heading-l').contains(homePageName)
})
})
9 changes: 9 additions & 0 deletions lib/assets/javascripts/kit.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,12 @@ window.GOVUKPrototypeKit = {
if (window.console && window.console.info) {
window.console.info('GOV.UK Prototype Kit - do not use for production')
}

window.GOVUKPrototypeKit.documentReady(function () {
const sendPageLoadedRequest = function () {
fetch('/manage-prototype/page-loaded').catch(() => {
setTimeout(sendPageLoadedRequest, 500)
})
}
sendPageLoadedRequest()
})
26 changes: 24 additions & 2 deletions lib/errorServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const path = require('path')
const fs = require('fs')

const { getNunjucksAppEnv } = require('./nunjucks/nunjucksConfiguration')
const syncChanges = require('./sync-changes')

function runErrorServer (error) {
let port
Expand All @@ -10,6 +11,7 @@ function runErrorServer (error) {
} catch (e) {
port = process.env.PORT || 3000
}
const proxyPort = port - 50
const http = require('http')
const fileExtensionsToMimeTypes = {
js: 'application/javascript',
Expand All @@ -28,6 +30,14 @@ function runErrorServer (error) {
}

const requestListener = function (req, res) {
if (req.url.startsWith('/browser-sync')) {
return
}
if (req.url.startsWith('/manage-prototype/page-loaded')) {
const result = syncChanges.pageLoaded()
res.end(JSON.stringify(result))
return
}
if (knownPaths[req.url]) {
res.setHeader('Content-Type', knownPaths[req.url].type)
res.writeHead(200)
Expand Down Expand Up @@ -56,7 +66,14 @@ function runErrorServer (error) {
path.join(process.cwd(), 'node_modules', 'govuk-frontend')
])
res.end(nunjucksAppEnv.render('views/error-handling/server-error', {
errorStack: error.stack
errorStack: error.stack,
pluginConfig: {
scripts: [
{
src: '/plugin-assets/govuk-prototype-kit/lib/assets/javascripts/kit.js'
}
]
}
}))
} catch (ignoreThisError) {
fileContentsParts.push('<h1 class="govuk-heading-l">There is an error</h1>')
Expand All @@ -72,7 +89,7 @@ function runErrorServer (error) {

const server = http.createServer(requestListener)

server.listen(port, () => {
server.listen(proxyPort, () => {
console.log('')
console.log('')
console.log(`There's an error, you can see it below or at http://localhost:${port}`)
Expand All @@ -83,6 +100,11 @@ function runErrorServer (error) {
console.log('')
console.log('')
console.log('')
syncChanges.sync({
port,
proxyPort,
files: ['app/**/*.*']
})
})
}

Expand Down
7 changes: 7 additions & 0 deletions lib/manage-prototype-handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const { exec } = require('./exec')
const { requestHttpsJson, prototypeAppScripts, sortByObjectKey } = require('./utils')
const { projectDir, packageDir, appViewsDir } = require('./utils/paths')
const nunjucksConfiguration = require('./nunjucks/nunjucksConfiguration')
const syncChanges = require('./sync-changes')

const contextPath = '/manage-prototype'

Expand Down Expand Up @@ -115,6 +116,11 @@ const csrfErrorHandler = (error, req, res, next) => {
}
}

function getPageLoadedHandler (req, res) {
const result = syncChanges.pageLoaded()
return res.json(result)
}

function getCsrfTokenHandler (req, res) {
const token = generateToken(res, req)
return res.json({ token })
Expand Down Expand Up @@ -704,6 +710,7 @@ module.exports = {
contextPath,
setKitRestarted,
csrfProtection: [doubleCsrfProtection, csrfErrorHandler],
getPageLoadedHandler,
getCsrfTokenHandler,
getClearDataHandler,
postClearDataHandler,
Expand Down
4 changes: 4 additions & 0 deletions lib/manage-prototype-routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const {
contextPath,
setKitRestarted,
csrfProtection,
getPageLoadedHandler,
getCsrfTokenHandler,
getClearDataHandler,
postClearDataHandler,
Expand Down Expand Up @@ -35,6 +36,9 @@ redirectingRouter.use((req, res) => {

router.get('/csrf-token', getCsrfTokenHandler)

// Indicates page has loaded
router.get('/page-loaded', getPageLoadedHandler)

// Clear all data in session
router.get('/clear-data', getClearDataHandler)

Expand Down
1 change: 1 addition & 0 deletions lib/nunjucks/govuk-prototype-kit/includes/scripts.njk
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
<script src="{{scriptConfig.src}}"></script>
{% endif %}
{% endfor %}

44 changes: 44 additions & 0 deletions lib/sync-changes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// dependencies
const EventEmitter = require('events')

// npm dependencies
const browserSync = require('browser-sync')

const eventEmitter = new EventEmitter()

const pageLoadedEvent = 'sync-changes:page-loaded'

function pageLoaded () {
eventEmitter.emit(pageLoadedEvent)
return { status: 'received ok' }
}

function sync ({ port, proxyPort, files }) {
browserSync({
ws: true,
proxy: 'localhost:' + proxyPort,
port,
ui: false,
files,
ghostMode: false,
open: false,
notify: false,
logLevel: 'error',
callbacks: {
ready: (_, bs) => {
// Repeat browser sync reload every 1000 milliseconds until it is successful
const intervalId = setInterval(browserSync.reload, 1000)
eventEmitter.once(pageLoadedEvent, () => {
bs.events.once('browser:reload', () => {
clearInterval(intervalId)
})
})
}
}
})
}

module.exports = {
sync,
pageLoaded
}
16 changes: 6 additions & 10 deletions listen-on-port.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@

// npm dependencies
const browserSync = require('browser-sync')
const { runErrorServer } = require('./lib/errorServer')

try {
// local dependencies
const syncChanges = require('./lib/sync-changes')
const server = require('./server.js')
const { generateAssetsSync } = require('./lib/build')
const config = require('./lib/config.js').getConfig(null, false)

const port = config.port
const proxyPort = port - 50

generateAssetsSync()

Expand All @@ -28,16 +29,11 @@ try {
if (config.isProduction || !config.useBrowserSync) {
server.listen(port)
} else {
server.listen(port - 50, () => {
browserSync({
proxy: 'localhost:' + (port - 50),
server.listen(proxyPort, () => {
syncChanges.sync({
port,
ui: false,
files: ['.tmp/public/**/*.*', 'app/views/**/*.*', 'app/assets/**/*.*', 'app/config.json'],
ghostMode: false,
open: false,
notify: false,
logLevel: 'error'
proxyPort,
files: ['.tmp/public/**/*.*', 'app/views/**/*.*', 'app/assets/**/*.*', 'app/config.json']
})
})
}
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,13 @@
"cypress:e2e:prod": "cypress run --spec \"cypress/e2e/prod/*/*\"",
"cypress:e2e:smoke": "cypress run --spec \"cypress/e2e/smoke/*/*\"",
"cypress:e2e:plugins": "cypress run --spec \"cypress/e2e/plugins/*/*\"",
"cypress:e2e:errors": "cypress run --spec \"cypress/e2e/errors/*/*\"",
"test:heroku": "start-server-and-test start:test:heroku 3000 cypress:e2e:smoke",
"test:acceptance:dev": "cross-env KIT_TEST_DIR=tmp/test-prototype start-server-and-test start:test 3000 cypress:e2e:dev",
"test:acceptance:prod": "cross-env PASSWORD=password KIT_TEST_DIR=tmp/test-prototype start-server-and-test start:test:prod 3000 cypress:e2e:prod",
"test:acceptance:smoke": "cross-env KIT_TEST_DIR=tmp/test-prototype start-server-and-test start:test 3000 cypress:e2e:smoke",
"test:acceptance:plugins": "cross-env KIT_TEST_DIR=tmp/test-prototype start-server-and-test start:test 3000 cypress:e2e:plugins",
"test:acceptance:errors": "cross-env KIT_TEST_DIR=tmp/test-prototype start-server-and-test start:test 3000 cypress:e2e:errors",
"test:acceptance:open": "cross-env KIT_TEST_DIR=tmp/test-prototype start-server-and-test start:test 3000 cypress:open",
"test:unit": "jest --detectOpenHandles lib bin migrator",
"test:integration": "cross-env CREATE_KIT_TIMEOUT=240000 jest --detectOpenHandles --testTimeout=60000 __tests__",
Expand Down

0 comments on commit cbd6bf8

Please sign in to comment.