Skip to content

Commit

Permalink
Merge pull request #1312 from alphagov/add-tests-for-gulp-functionality
Browse files Browse the repository at this point in the history
Add tests for gulp functionality
  • Loading branch information
lfdebrux authored May 17, 2022
2 parents dd05b6c + 76720fa commit be89520
Show file tree
Hide file tree
Showing 17 changed files with 2,403 additions and 11 deletions.
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
.gitattributes export-ignore
.github export-ignore
__tests__ export-ignore
cypress export-ignore
cypress.json export-ignore
app.json export-ignore
CHANGELOG.md export-ignore
CONTRIBUTING.md export-ignore
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/run-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ jobs:
run-tests:

strategy:
fail-fast: false # continue other tests if one test in matrix fails
matrix:
node-version: [12.x, 14.x, 16.x]
os: [macos-latest, windows-latest, ubuntu-latest]
Expand All @@ -28,3 +29,5 @@ jobs:
env:
CI: true
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: npm run test:integration
continue-on-error: ${{ matrix.os == 'macos-latest' }} # TODO: fix kit so this isn't needed
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,8 @@ public
node_modules/
.tmuxp.*
.idea
/cypress/videos
/cypress/screenshots
/cypress/downloads
/cypress/support
/cypress/temp
5 changes: 5 additions & 0 deletions cypress.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"testFiles": "**/*.cypress.js",
"video": false,
"chromeWebSecurity": false
}
3 changes: 3 additions & 0 deletions cypress/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
hostName: 'http://localhost:3000'
}
Binary file added cypress/fixtures/images/larry-the-cat.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 18 additions & 0 deletions cypress/fixtures/routes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const express = require('express')
const router = express.Router()

router.get('/cypress-test', (req, res) => {
const heading = 'CYPRESS TEST PAGE'
res.send(`<!DOCTYPE html>
<html lang="en">
<head>
<title>${heading}</title>
</head>
<body>
<h1>${heading}</h1>
</body>
</html>
`)
})

module.exports = router
32 changes: 32 additions & 0 deletions cypress/integration/1-watch-files/watch-images.cypress.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const { waitForApplication } = require('../utils')
const { hostName } = require('../../config')
const imageFile = 'larry-the-cat.jpg'
const cypressImages = 'cypress/fixtures/images'
const appImages = 'app/assets/images'
const publicImages = 'public/images'

describe('watch image files', () => {
before(() => {
waitForApplication()
})

afterEach(() => {
// delete temporary files
cy.task('deleteFile', { filename: `${publicImages}/${imageFile}` })
cy.wait(2000)
cy.task('deleteFile', { filename: `${appImages}/${imageFile}` })
})

it(`image created in ${appImages} should be copied to ${publicImages} and accessible from the browser`, () => {
const source = `${cypressImages}/${imageFile}`
const target = `${appImages}/${imageFile}`
const publicImage = `${publicImages}/${imageFile}`

cy.task('log', `Copying ${source} to ${target}`)
cy.task('copyFile', { source, target })

cy.task('log', `Requesting ${publicImage}`)
cy.request(`${hostName}/${publicImage}`, { timeout: 20000, retryOnStatusCodeFailure: true })
.then(response => expect(response.status).to.eq(200))
})
})
47 changes: 47 additions & 0 deletions cypress/integration/1-watch-files/watch-routes.cypress.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
const { waitForApplication } = require('../utils')
const cypressRoutes = 'cypress/fixtures/routes.js'
const appRoutes = 'app/routes.js'
const backupRoutes = 'cypress/temp/temp-routes.js'
const appUrl = 'http://localhost:3000'
const pagePath = '/cypress-test'
const pageUrl = `${appUrl}${pagePath}`

describe('watch route file', () => {
before(() => {
waitForApplication()
// backup routes
cy.task('copyFile', { source: appRoutes, target: backupRoutes })
})

after(() => {
// delete backup routes
cy.task('deleteFile', { filename: backupRoutes })
})

it(`add and remove ${pageUrl} route`, () => {
cy.task('log', 'The cypress test page should not be found')
cy.visit(pageUrl, { failOnStatusCode: false })
cy.get('body', { timeout: 20000 })
.should('contains.text', `Page not found: ${pagePath}`)

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

waitForApplication()

cy.task('log', 'The cypress test page should be displayed')
cy.visit(pageUrl, { failOnStatusCode: false })
cy.get('h1', { timeout: 20000 })
.should('contains.text', 'CYPRESS TEST PAGE')

cy.task('log', `Restore ${appRoutes}`)
cy.task('copyFile', { source: backupRoutes, target: appRoutes })

waitForApplication()

cy.task('log', 'The cypress test page should not be found')
cy.visit(pageUrl, { failOnStatusCode: false })
cy.get('body', { timeout: 20000 })
.should('contains.text', `Page not found: ${pagePath}`)
})
})
53 changes: 53 additions & 0 deletions cypress/integration/1-watch-files/watch-styles.cypress.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
const { waitForApplication } = require('../utils')
const appStyles = 'app/assets/sass'
const appStylesheet = `${appStyles}/application.scss`
const cypressTestStyles = 'cypress-test'
const cypressTestStylePattern = `${appStyles}/patterns/_${cypressTestStyles}.scss`
const publicStylesheet = 'public/stylesheets/application.css'
const backupAppStylesheet = 'cypress/temp/temp-application.scss'

describe('watch sass files', () => {
describe(`sass file ${cypressTestStylePattern} should be created and included within the ${appStylesheet} and accessible from the browser as /${publicStylesheet}`, () => {
const cssStatement = `
.govuk-header { background: red; }
`

before(() => {
waitForApplication()
// backup application.scss
cy.task('copyFile', { source: appStylesheet, target: backupAppStylesheet })
})

after(() => {
// restore files
cy.task('copyFile', { source: backupAppStylesheet, target: appStylesheet })

cy.task('deleteFile', { filename: cypressTestStylePattern })

cy.get('.govuk-header', { timeout: 20000 }).should('have.css', 'background-color', 'rgb(11, 12, 12)')
cy.task('deleteFile', { filename: backupAppStylesheet })
})

it('The colour of the header should be change to red then back to black', () => {
cy.task('log', 'The colour of the header should be black')
cy.get('.govuk-header', { timeout: 20000 }).should('have.css', 'background-color', 'rgb(11, 12, 12)')

cy.task('log', `Create ${cypressTestStylePattern}`)
cy.task('createFile', {
filename: cypressTestStylePattern,
data: cssStatement
})

cy.task('log', `Amend ${appStylesheet} to import ${cypressTestStyles}`)
cy.task('appendFile', {
filename: appStylesheet,
data: `
@import "patterns/${cypressTestStyles}";
`
})

cy.task('log', 'The colour of the header should be changed to red')
cy.get('.govuk-header', { timeout: 20000 }).should('have.css', 'background-color', 'rgb(255, 0, 0)')
})
})
})
33 changes: 33 additions & 0 deletions cypress/integration/1-watch-files/watch-views.cypress.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { hostName } from '../../config'
import { waitForApplication } from '../utils'

const templatesView = 'docs/views/templates/start.html'
const appView = 'app/views/start.html'
const pagePath = '/start'
const pageUrl = `${hostName}${pagePath}`

describe('watching start page', () => {
before(() => {
waitForApplication()
cy.task('deleteFile', { filename: appView })
})

after(() => {
cy.task('deleteFile', { filename: appView })
})

it('Add and remove the start page', () => {
cy.task('log', 'The start page should not be found')
cy.visit(pageUrl, { failOnStatusCode: false })
cy.get('body', { timeout: 20000 })
.should('contains.text', `Page not found: ${pagePath}`)

cy.task('log', `Copy ${templatesView} to ${appView}`)
cy.task('copyFile', { source: templatesView, target: appView })

cy.task('log', 'The start page should be displayed')
cy.visit(pageUrl)
cy.get('h1', { timeout: 20000 })
.should('contains.text', 'Service name goes here')
})
})
16 changes: 16 additions & 0 deletions cypress/integration/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const { hostName } = require('../config')

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))

const waitForApplication = async () => {
cy.task('log', 'Waiting for app to restart and load home page')
cy.task('waitUntilAppRestarts')
cy.visit(hostName)
cy.get('h1.govuk-heading-xl', { timeout: 20000 })
.should('contains.text', 'Prototype your service using GOV.UK Prototype Kit')
}

module.exports = {
sleep,
waitForApplication
}
113 changes: 113 additions & 0 deletions cypress/plugins/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/// <reference types="cypress" />
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************

// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)
// const spawn = require('child_process').spawn
const waitOn = require('wait-on')
const fs = require('fs')

const { sleep } = require('../integration/utils')
const { hostName } = require('../config')

const waitUntilAppRestarts = async (timeout) => await waitOn({ delay: 2000, resources: [hostName], timeout })

const createFolderForFile = (filepath) => {
const dir = filepath.substring(0, filepath.lastIndexOf('/'))
if (dir && !fs.existsSync(dir)) {
fs.mkdirSync(dir, {
recursive: true
})
}
}

/**
* @type {Cypress.PluginConfig}
*/
// eslint-disable-next-line no-unused-vars
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config

on('task', {
copyFile: async ({ source, target, timeout = 2000 }) => {
try {
createFolderForFile(target)
fs.copyFileSync(source, target)
// The sleep of 2 seconds allows for the file to be copied completely to prevent
// it from not existing when the file is needed in a subsequent step
await sleep(2000) // pause after the copy
return Promise.resolve(null)
} catch (err) {
return Promise.reject(err)
}
}
})

on('task', {
createFile: async ({ filename, data, replace = false }) => {
try {
createFolderForFile(filename)
fs.writeFileSync(filename, data, {
flag: replace ? 'w' : '' // Flag of w will overwrite
})
return Promise.resolve(null)
} catch (err) {
return Promise.reject(err)
}
}
})

on('task', {
appendFile: async ({ filename, data }) => {
try {
fs.appendFileSync(filename, data)
return Promise.resolve(null)
} catch (err) {
return Promise.reject(err)
}
}
})

on('task', {
deleteFile: async ({ filename, timeout }) => {
try {
fs.unlinkSync(filename)
await sleep(timeout)
return Promise.resolve(null)
} catch (err) {
if (err.code !== 'ENOENT') {
return Promise.reject(err)
}
return Promise.resolve(null)
}
}
})

on('task', {
waitUntilAppRestarts: async (config) => {
const { timeout = 20000 } = config || {}
try {
await waitUntilAppRestarts(timeout)
return Promise.resolve(null)
} catch (err) {
return Promise.reject(err)
}
}
})

on('task', {
log (message) {
console.log(`${new Date().toLocaleTimeString()} => ${message}`)
return null
}
})
}
1 change: 1 addition & 0 deletions gulp/config.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"paths": {
"public": "public/",
"cypress": "cypress/",
"assets" : "app/assets/",
"docsAssets" : "docs/assets/",
"v6Assets": "app/v6/assets/",
Expand Down
1 change: 1 addition & 0 deletions gulp/nodemon.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ gulp.task('server', function () {
watch: ['.env', '**/*.js', '**/*.json'],
script: 'listen-on-port.js',
ignore: [
config.paths.cypress + '*',
config.paths.public + '*',
config.paths.assets + '*',
config.paths.nodeModules + '*'
Expand Down
Loading

0 comments on commit be89520

Please sign in to comment.