From efa705ef574fb80eee42f5e1f42725f2b32eba5b Mon Sep 17 00:00:00 2001 From: Colin Rotherham Date: Tue, 20 Sep 2022 20:09:12 +0100 Subject: [PATCH] Use async file operations for helpers --- .github/workflows/diff-change-to-dist.yaml | 4 +- app/app.js | 32 +-- app/app.test.js | 7 +- app/full-page-examples.js | 4 +- app/start.js | 8 +- config/jest/.eslintrc.js | 5 + config/jest/matchers.js | 2 - jest.config.js | 3 +- lib/file-helper.js | 131 +++++++-- lib/file-helper.unit.test.js | 23 +- lib/jest-helpers.js | 18 +- .../components/accordion/template.test.js | 8 +- src/govuk/components/all.test.js | 262 +++++++++++------- .../components/back-link/template.test.js | 8 +- .../components/breadcrumbs/template.test.js | 8 +- src/govuk/components/button/template.test.js | 8 +- .../character-count/template.test.js | 8 +- .../components/checkboxes/template.test.js | 8 +- .../components/cookie-banner/template.test.js | 8 +- .../components/date-input/template.test.js | 8 +- src/govuk/components/details/template.test.js | 8 +- .../components/error-message/template.test.js | 8 +- .../error-summary/error-summary.test.js | 8 +- .../components/error-summary/template.test.js | 8 +- .../components/fieldset/template.test.js | 8 +- .../components/file-upload/template.test.js | 8 +- src/govuk/components/footer/template.test.js | 8 +- src/govuk/components/header/template.test.js | 20 +- src/govuk/components/hint/template.test.js | 8 +- src/govuk/components/input/template.test.js | 8 +- .../components/inset-text/template.test.js | 8 +- src/govuk/components/label/template.test.js | 8 +- .../notification-banner.test.js | 8 +- .../notification-banner/template.test.js | 8 +- .../components/pagination/template.test.js | 8 +- src/govuk/components/panel/template.test.js | 8 +- .../components/phase-banner/template.test.js | 8 +- src/govuk/components/radios/template.test.js | 8 +- src/govuk/components/select/template.test.js | 8 +- .../components/skip-link/template.test.js | 8 +- .../components/summary-list/template.test.js | 26 +- src/govuk/components/table/template.test.js | 8 +- src/govuk/components/tabs/template.test.js | 8 +- src/govuk/components/tag/template.test.js | 8 +- .../components/textarea/template.test.js | 8 +- .../components/warning-text/template.test.js | 8 +- tasks/gulp/__tests__/after-build-dist.test.js | 20 +- .../__tests__/after-build-package.test.js | 114 ++++---- 48 files changed, 616 insertions(+), 319 deletions(-) create mode 100644 config/jest/.eslintrc.js diff --git a/.github/workflows/diff-change-to-dist.yaml b/.github/workflows/diff-change-to-dist.yaml index 47d8ba024e..80447311cc 100644 --- a/.github/workflows/diff-change-to-dist.yaml +++ b/.github/workflows/diff-change-to-dist.yaml @@ -41,8 +41,8 @@ jobs: with: github-token: ${{secrets.GITHUB_TOKEN}} script: | - const fs = require('fs').promises - const diff = await fs.readFile( + const { readFile } = require('fs/promises') + const diff = await readFile( process.env.GITHUB_WORKSPACE + '/dist.diff', 'utf8' ) diff --git a/app/app.js b/app/app.js index 0036ed860e..e327843e5e 100644 --- a/app/app.js +++ b/app/app.js @@ -3,12 +3,8 @@ const app = express() const bodyParser = require('body-parser') const nunjucks = require('nunjucks') const { marked } = require('marked') -const util = require('util') -const fs = require('fs') const path = require('path') -const readdir = util.promisify(fs.readdir) - const helperFunctions = require('../lib/helper-functions') const fileHelper = require('../lib/file-helper') const configPaths = require('../config/paths.js') @@ -23,7 +19,7 @@ const appViews = [ `${configPaths.node_modules}/govuk_template_jinja` ] -module.exports = (options) => { +module.exports = async (options) => { const nunjucksOptions = options ? options.nunjucks : {} // Configure nunjucks @@ -37,6 +33,13 @@ module.exports = (options) => { ...nunjucksOptions // merge any additional options and overwrite defaults above. }) + // Cache common component listings + const [examples, components, fullPageExamples] = await Promise.all([ + fileHelper.getDirectories(configPaths.examples), + fileHelper.getAllComponents(), + fileHelper.getFullPageExamples() + ]) + // make the function available as a filter for all templates env.addFilter('componentNameToMacroName', helperFunctions.componentNameToMacroName) env.addGlobal('markdown', marked) @@ -107,10 +110,6 @@ module.exports = (options) => { // Index page - render the component list template app.get('/', async function (req, res) { - const components = fileHelper.allComponents - const examples = await readdir(path.resolve(configPaths.examples)) - const fullPageExamples = fileHelper.fullPageExamples() - res.render('index', { componentsDirectory: components, examplesDirectory: examples, @@ -120,17 +119,15 @@ module.exports = (options) => { // Whenever the route includes a :component parameter, read the component data // from its YAML file - app.param('component', function (req, res, next, componentName) { - res.locals.componentData = fileHelper.getComponentData(componentName) + app.param('component', async function (req, res, next, componentName) { + res.locals.componentData = await fileHelper.getComponentData(componentName) next() }) // All components view - app.get('/components/all', function (req, res, next) { - const components = fileHelper.allComponents - - res.locals.componentData = components.map(componentName => { - const componentData = fileHelper.getComponentData(componentName) + app.get('/components/all', async function (req, res, next) { + res.locals.componentData = await Promise.all(components.map(async componentName => { + const componentData = await fileHelper.getComponentData(componentName) const defaultExample = componentData.examples.find( example => example.name === 'default' ) @@ -138,7 +135,8 @@ module.exports = (options) => { componentName, examples: [defaultExample] } - }) + })) + res.render('all-components', function (error, html) { if (error) { next(error) diff --git a/app/app.test.js b/app/app.test.js index ef375ea054..7cff82322e 100644 --- a/app/app.test.js +++ b/app/app.test.js @@ -1,7 +1,7 @@ const cheerio = require('cheerio') const { Agent, fetch, setGlobalDispatcher } = require('undici') -const lib = require('../lib/file-helper') +const { getAllComponents } = require('../lib/file-helper') const configPaths = require('../config/paths.js') const PORT = configPaths.ports.test @@ -51,10 +51,13 @@ describe(`http://localhost:${PORT}`, () => { it('should display the list of components', async () => { const response = await fetchPath('/') const $ = cheerio.load(await response.text()) + + const components = await getAllComponents() const componentsList = $('li a[href^="/components/"]').get() + // Since we have an 'all' component link that renders the default example of all // components, there will always be one more expected link. - const expectedComponentLinks = lib.allComponents.length + 1 + const expectedComponentLinks = components.length + 1 expect(componentsList.length).toEqual(expectedComponentLinks) }) }) diff --git a/app/full-page-examples.js b/app/full-page-examples.js index f99f78a98a..bcb1f54f47 100644 --- a/app/full-page-examples.js +++ b/app/full-page-examples.js @@ -17,8 +17,8 @@ module.exports = (app) => { require('./views/full-page-examples/what-is-your-postcode')(app) require('./views/full-page-examples/what-was-the-last-country-you-visited')(app) - app.get('/full-page-examples', (req, res, next) => { - res.locals.examples = fileHelper.fullPageExamples() + app.get('/full-page-examples', async (req, res, next) => { + res.locals.examples = await fileHelper.getFullPageExamples() res.render('full-page-examples/index', (error, html) => { if (error) { diff --git a/app/start.js b/app/start.js index 88208bf0db..9c7ca4d501 100644 --- a/app/start.js +++ b/app/start.js @@ -1,8 +1,8 @@ +const app = require('./app.js') const configPaths = require('../config/paths.js') -const PORT = process.env.PORT || configPaths.ports.app -const app = require('./app.js')() +const PORT = process.env.PORT || configPaths.ports.app -app.listen(PORT, () => { +app().then(server => server.listen(PORT, () => { console.log('Server started at http://localhost:' + PORT) -}) +})) diff --git a/config/jest/.eslintrc.js b/config/jest/.eslintrc.js new file mode 100644 index 0000000000..958d51ba27 --- /dev/null +++ b/config/jest/.eslintrc.js @@ -0,0 +1,5 @@ +module.exports = { + env: { + jest: true + } +} diff --git a/config/jest/matchers.js b/config/jest/matchers.js index 08a5da864e..905b0e07f4 100644 --- a/config/jest/matchers.js +++ b/config/jest/matchers.js @@ -1,5 +1,3 @@ -/* eslint-env jest */ - const { toHaveNoViolations } = require('jest-axe') expect.extend(toHaveNoViolations) diff --git a/jest.config.js b/jest.config.js index 6654738602..cefcc887c7 100644 --- a/jest.config.js +++ b/jest.config.js @@ -70,5 +70,6 @@ module.exports = { globalSetup: './config/jest/browser/open.mjs', globalTeardown: './config/jest/browser/close.mjs' } - ] + ], + testTimeout: 30000 } diff --git a/lib/file-helper.js b/lib/file-helper.js index a3efbf50fc..9b57ad47d3 100644 --- a/lib/file-helper.js +++ b/lib/file-helper.js @@ -1,45 +1,120 @@ -const fs = require('fs') +const { readdir, readFile, stat } = require('fs/promises') const path = require('path') const yaml = require('js-yaml') const fm = require('front-matter') const configPaths = require('../config/paths.js') -const childDirectories = dir => { - return fs.readdirSync(dir) - .filter(file => fs.statSync(path.join(dir, file)).isDirectory()) +/** + * Directory listing for path + * + * @param {string} directoryPath + * @returns {Promise<{ basename: string; stats: import('fs').Stats }[]>} entries + */ +const getListing = async (directoryPath) => { + const cache = getListing.cache ??= new Map() + + // Cache listings + if (!cache.has(directoryPath)) { + const listing = await readdir(directoryPath) + + // Loop through listing entries + const entries = listing.map(async basename => { + return { basename, stats: await stat(path.join(directoryPath, basename)) } + }) + + // Resolve on completion + cache.set(directoryPath, Promise.all(entries)) + } + + return cache.get(directoryPath) } -// Generate component list from source directory, excluding anything that's not -// a directory (for example, .DS_Store files) -exports.allComponents = childDirectories(configPaths.components) +/** + * Directory listing (directories only) + * + * @param {string} directoryPath + * @returns {Promise} directories + */ +const getDirectories = async (directoryPath) => { + const cache = getDirectories.cache ??= new Map() + + // Cache directory-only listings + if (!cache.has(directoryPath)) { + const entries = await getListing(directoryPath) -// Read the contents of a file from a given path -const readFileContents = filePath => { - return fs.readFileSync(filePath, 'utf8') + cache.set(directoryPath, entries + .filter(({ stats }) => stats.isDirectory()) + .map(({ basename: directory }) => directory)) + } + + return cache.get(directoryPath) } -exports.readFileContents = readFileContents +/** + * Generate component list from source directory, excluding anything that's not + * a directory (for example, .DS_Store files) + * + * @returns {Promise} directories + */ +const getAllComponents = async () => { + return getDirectories(configPaths.components) +} -const getComponentData = componentName => { - try { - const yamlPath = path.join(configPaths.components, componentName, `${componentName}.yaml`) - return yaml.load( - fs.readFileSync(yamlPath, 'utf8'), { json: true } - ) - } catch (error) { - throw new Error(error) +/** + * Load component data + * + * @param {string} componentName - Component name + * @returns {Promise<{ examples?: unknown[]; params?: unknown[] }>} Component data + */ +const getComponentData = async componentName => { + const cache = getComponentData.cache ??= new Map() + + // Cache component data + if (!cache.has(componentName)) { + let componentData = {} + + try { + const yamlPath = path.join(configPaths.components, componentName, `${componentName}.yaml`) + componentData = yaml.load(await readFile(yamlPath, 'utf8'), { json: true }) + } catch (error) { + throw new Error(error) + } + + cache.set(componentName, componentData) } + + return cache.get(componentName) } -exports.getComponentData = getComponentData +const getFullPageExamples = async () => { + if (!getFullPageExamples.cache) { + const examplesDirectories = await getDirectories(path.resolve(configPaths.fullPageExamples)) + + const examples = await Promise.all( + examplesDirectories.map(async folderName => { + const templatePath = path.join(configPaths.fullPageExamples, folderName, 'index.njk') + const { attributes } = fm(await readFile(templatePath, 'utf8')) + + return { + name: folderName, + path: folderName, + ...attributes + } + }) + ) + + getFullPageExamples.cache = examples + .sort((a, b) => (a.name.toLowerCase() > b.name.toLowerCase()) ? 1 : -1) + } + + return getFullPageExamples.cache +} -exports.fullPageExamples = () => { - return childDirectories(path.resolve(configPaths.fullPageExamples)) - .map(folderName => ({ - name: folderName, - path: folderName, - ...fm(readFileContents(path.join(configPaths.fullPageExamples, folderName, 'index.njk'))).attributes - })) - .sort((a, b) => (a.name.toLowerCase() > b.name.toLowerCase()) ? 1 : -1) +module.exports = { + getAllComponents, + getFullPageExamples, + getComponentData, + getDirectories, + getListing } diff --git a/lib/file-helper.unit.test.js b/lib/file-helper.unit.test.js index 246c3acceb..e92d89e5a2 100644 --- a/lib/file-helper.unit.test.js +++ b/lib/file-helper.unit.test.js @@ -3,19 +3,20 @@ const fileHelper = require('../lib/file-helper') describe('getComponentData', () => { it('returns an error if unable to load component data', () => { - expect(() => { fileHelper.getComponentData('not-a-real-component') }).toThrow(Error) + return expect(fileHelper.getComponentData('not-a-real-component')) + .rejects.toThrowError('Error: ENOENT: no such file or directory') }) - it('looks up the correct component path', () => { + it('looks up the correct component path', async () => { jest.spyOn(path, 'join') - fileHelper.getComponentData('accordion') + await fileHelper.getComponentData('accordion') expect(path.join).toHaveBeenCalledWith('src/govuk/components/', 'accordion', 'accordion.yaml') }) - it('outputs objects with an array of params and examples', () => { - const componentData = fileHelper.getComponentData('accordion') + it('outputs objects with an array of params and examples', async () => { + const componentData = await fileHelper.getComponentData('accordion') expect(componentData).toEqual(expect.objectContaining({ params: expect.any(Array), @@ -23,8 +24,8 @@ describe('getComponentData', () => { })) }) - it('outputs a param for each object with the expected attributes', () => { - const componentData = fileHelper.getComponentData('accordion') + it('outputs a param for each object with the expected attributes', async () => { + const componentData = await fileHelper.getComponentData('accordion') componentData.params.forEach((param) => { expect(param).toEqual( @@ -38,8 +39,8 @@ describe('getComponentData', () => { }) }) - it('contains example objects with the expected attributes', () => { - const componentData = fileHelper.getComponentData('accordion') + it('contains example objects with the expected attributes', async () => { + const componentData = await fileHelper.getComponentData('accordion') componentData.examples.forEach((example) => { expect(example).toEqual( @@ -53,8 +54,8 @@ describe('getComponentData', () => { }) describe('fullPageExamples', () => { - it('contains name and path of each example, at a minimum', () => { - const fullPageExamples = fileHelper.fullPageExamples() + it('contains name and path of each example, at a minimum', async () => { + const fullPageExamples = await fileHelper.getFullPageExamples() fullPageExamples.forEach((example) => { expect(example).toEqual( diff --git a/lib/jest-helpers.js b/lib/jest-helpers.js index ee1e5800d5..d3e75efd1c 100644 --- a/lib/jest-helpers.js +++ b/lib/jest-helpers.js @@ -1,4 +1,4 @@ -const fs = require('fs') +const { readFile } = require('fs/promises') const path = require('path') const util = require('util') @@ -135,8 +135,8 @@ async function renderAndInitialise (componentName, options = {}) { * @param {string} componentName * @returns {object} returns object that includes all examples at once */ -function getExamples (componentName) { - const file = fs.readFileSync( +async function getExamples (componentName) { + const file = await readFile( path.join(configPaths.components, componentName, `${componentName}.yaml`), 'utf8' ) @@ -194,4 +194,14 @@ const axe = configureAxe({ } }) -module.exports = { axe, render, renderHtml, renderAndInitialise, getExamples, htmlWithClassName, renderSass, renderTemplate } +module.exports = { + axe, + getExamples, + htmlWithClassName, + nunjucksEnv, + render, + renderAndInitialise, + renderHtml, + renderSass, + renderTemplate +} diff --git a/src/govuk/components/accordion/template.test.js b/src/govuk/components/accordion/template.test.js index bc83735be8..f19be3d8e7 100644 --- a/src/govuk/components/accordion/template.test.js +++ b/src/govuk/components/accordion/template.test.js @@ -4,9 +4,13 @@ const { axe, render, getExamples } = require('../../../../lib/jest-helpers') -const examples = getExamples('accordion') - describe('Accordion', () => { + let examples + + beforeAll(async () => { + examples = await getExamples('accordion') + }) + describe('default example', () => { it('passes accessibility tests', async () => { const $ = render('accordion', examples.default) diff --git a/src/govuk/components/all.test.js b/src/govuk/components/all.test.js index b5d4bf4207..59ba69e95f 100644 --- a/src/govuk/components/all.test.js +++ b/src/govuk/components/all.test.js @@ -3,11 +3,12 @@ */ const { fetch } = require('undici') +const { HtmlValidate } = require('html-validate') const { WebSocket } = require('ws') require('html-validate/jest') -const { allComponents, getComponentData } = require('../../../lib/file-helper') -const { renderSass, renderHtml } = require('../../../lib/jest-helpers') +const { getAllComponents, getComponentData } = require('../../../lib/file-helper') +const { nunjucksEnv, renderSass, renderHtml } = require('../../../lib/jest-helpers') const configPaths = require('../../../config/paths.js') @@ -18,125 +19,176 @@ const baseUrl = 'http://localhost:' + PORT // over the nunjucks environment. const nunjucks = require('nunjucks') -describe('When nunjucks is configured with a different base path', () => { - let nunjucksEnv +describe('Components', () => { + let components - beforeAll(() => { - // Create a new Nunjucks environment that uses the src directory as its - // base path, rather than the components folder itself - nunjucksEnv = nunjucks.configure(configPaths.src) + beforeAll(async () => { + components = await getAllComponents() }) - it.each(allComponents)('render(\'%s\') does not error', (component) => { - expect(() => { - nunjucksEnv.render(`components/${component}/template.njk`, {}) - }).not.toThrow() - }) -}) - -it('_all.scss renders to CSS without errors', () => { - return renderSass({ - file: `${configPaths.src}/components/_all.scss` - }) -}) - -describe.each(allComponents)('%s', (component) => { - let percySnapshot - - beforeAll(() => { - // Polyfill fetch() detection, upload via WebSocket() - // Fixes Percy running in a non-browser environment - global.window = { fetch, WebSocket } - percySnapshot = require('@percy/puppeteer') - }) + describe('Nunjucks render', () => { + let nunjucksEnvCustom + let nunjucksEnvDefault - it(`${component}.scss renders to CSS without errors`, () => { - return renderSass({ - file: `${configPaths.src}/components/${component}/_${component}.scss` + beforeAll(() => { + // Create a new Nunjucks environment that uses the src directory as its + // base path, rather than the components folder itself + nunjucksEnvCustom = nunjucks.configure(configPaths.src) + nunjucksEnvDefault = nunjucksEnv }) - }) - - it('generate screenshots for Percy visual regression, with JavaScript disabled', async () => { - await page.setJavaScriptEnabled(false) - await page.goto(baseUrl + '/components/' + component + '/preview', { waitUntil: 'load' }) - await percySnapshot(page, 'no-js: ' + component) - }) - it('generate screenshots for Percy visual regression, with JavaScript enabled', async () => { - await page.setJavaScriptEnabled(true) - await page.goto(baseUrl + '/components/' + component + '/preview', { waitUntil: 'load' }) - await percySnapshot(page, 'js: ' + component) - }) + describe('Environments', () => { + it('renders template for each component', () => { + return Promise.all(components.map((component) => + expect(nunjucksEnvDefault.render(`${component}/template.njk`, {})).resolves + )) + }) - describe('examples output valid HTML', () => { - const examples = getComponentData(component).examples.map(function (example) { - return [example.name, example.data] + it('renders template for each component (different base path)', () => { + return Promise.all(components.map((component) => + expect(nunjucksEnvCustom.render(`components/${component}/template.njk`, {})).resolves + )) + }) }) - it.each(examples)('example "%s" outputs valid HTML', async (_, data) => { - expect(renderHtml(component, data)).toHTMLValidate({ - rules: { + describe('HTML validation', () => { + let validator + + beforeAll(() => { + validator = new HtmlValidate({ + rules: { // We don't use boolean attributes consistently – buttons currently // use disabled="disabled" - 'attribute-boolean-style': 'off', - - // Allow pattern attribute on input type="number" - 'input-attributes': 'off', - - // Allow for conditional comments (used in header for fallback png) - 'no-conditional-comment': 'off', - - // Allow inline styles for testing purposes - 'no-inline-style': 'off', - - // Allow for explicit roles on regions that have implict roles - // We do this to better support AT with older versions of IE that - // have partial support for HTML5 semantic elements - 'no-redundant-role': 'off', - - // More hassle than it's worth 👾 - 'no-trailing-whitespace': 'off', - - // We still support creating `input type=button` with the button - // component, but you have to explicitly choose to use them over - // buttons - 'prefer-button': 'off', - - // Allow use of roles where there are native elements that would give - // us that role automatically, e.g.
instead of - //
- // - // This is mainly needed for links styled as buttons, but we do this - // in the cookie banner and notification banner too - 'prefer-native-element': 'off', - - // HTML Validate is opinionated about IDs beginning with a letter and - // only containing letters, numbers, underscores and dashes – which is - // more restrictive than the spec allows. - // - // Relax the rule to allow anything that is valid according to the - // spec. - 'valid-id': ['error', { relaxed: true }] - }, - elements: [ - 'html5', - { + 'attribute-boolean-style': 'off', + + // Allow pattern attribute on input type="number" + 'input-attributes': 'off', + + // Allow for conditional comments (used in header for fallback png) + 'no-conditional-comment': 'off', + + // Allow inline styles for testing purposes + 'no-inline-style': 'off', + + // Allow for explicit roles on regions that have implict roles + // We do this to better support AT with older versions of IE that + // have partial support for HTML5 semantic elements + 'no-redundant-role': 'off', + + // More hassle than it's worth 👾 + 'no-trailing-whitespace': 'off', + + // We still support creating `input type=button` with the button + // component, but you have to explicitly choose to use them over + // buttons + 'prefer-button': 'off', + + // Allow use of roles where there are native elements that would give + // us that role automatically, e.g.
instead of + //
+ // + // This is mainly needed for links styled as buttons, but we do this + // in the cookie banner and notification banner too + 'prefer-native-element': 'off', + + // HTML Validate is opinionated about IDs beginning with a letter and + // only containing letters, numbers, underscores and dashes – which is + // more restrictive than the spec allows. + // + // Relax the rule to allow anything that is valid according to the + // spec. + 'valid-id': ['error', { relaxed: true }] + }, + elements: [ + 'html5', + { // Allow textarea autocomplete attribute to be street-address // (html-validate only allows on/off in default rules) - textarea: { - attributes: { - autocomplete: { enum: ['on', 'off', 'street-address'] } - } - }, - // Allow buttons to omit the type attribute (defaults to 'submit') - button: { - attributes: { - type: { required: false } + textarea: { + attributes: { + autocomplete: { enum: ['on', 'off', 'street-address'] } + } + }, + // Allow buttons to omit the type attribute (defaults to 'submit') + button: { + attributes: { + type: { required: false } + } } } - } - ] + ] + }) }) + + it('renders valid HTML for each component example', () => { + const componentTasks = components.map(async (component) => { + const { examples } = await getComponentData(component) + + // Loop through component examples + examples.forEach(({ name, data }) => { + const html = renderHtml(component, data) + const report = validator.validateString(html) + + expect({ component, name, report }) + .toEqual({ component, name, report: expect.objectContaining({ valid: true }) }) + }) + }) + + return Promise.all(componentTasks) + }, 30000) }) }) + + describe('Sass render', () => { + it('renders CSS for all components', () => { + expect(renderSass({ file: `${configPaths.src}/components/_all.scss` })).resolves.toEqual( + expect.objectContaining({ + css: expect.any(Object), + stats: expect.any(Object) + }) + ) + }) + + it('renders CSS for each component', () => { + components.forEach((component) => { + expect(renderSass({ file: `${configPaths.src}/components/${component}/_${component}.scss` })).resolves.toEqual( + expect.objectContaining({ + css: expect.any(Object), + stats: expect.any(Object) + }) + ) + }) + }) + }) + + describe('Generate screenshots for Percy visual regression', () => { + let percySnapshot + + beforeAll(() => { + // Polyfill fetch() detection, upload via WebSocket() + // Fixes Percy running in a non-browser environment + global.window = { fetch, WebSocket } + percySnapshot = require('@percy/puppeteer') + }) + + it('with JavaScript disabled', async () => { + await page.setJavaScriptEnabled(false) + + // Screenshot each component + for (const component of components) { + await page.goto(baseUrl + '/components/' + component + '/preview', { waitUntil: 'load' }) + await percySnapshot(page, 'no-js: ' + component) + } + }, 120000) + + it('with JavaScript enabled', async () => { + await page.setJavaScriptEnabled(true) + + // Screenshot each component + for (const component of components) { + await page.goto(baseUrl + '/components/' + component + '/preview', { waitUntil: 'load' }) + await percySnapshot(page, 'js: ' + component) + } + }, 120000) + }) }) diff --git a/src/govuk/components/back-link/template.test.js b/src/govuk/components/back-link/template.test.js index 648cf1ec1c..76ebb555b9 100644 --- a/src/govuk/components/back-link/template.test.js +++ b/src/govuk/components/back-link/template.test.js @@ -4,9 +4,13 @@ const { axe, render, getExamples } = require('../../../../lib/jest-helpers') -const examples = getExamples('back-link') - describe('back-link component', () => { + let examples + + beforeAll(async () => { + examples = await getExamples('back-link') + }) + it('default example passes accessibility tests', async () => { const $ = render('back-link', examples.default) diff --git a/src/govuk/components/breadcrumbs/template.test.js b/src/govuk/components/breadcrumbs/template.test.js index 1178a34245..b17f890d83 100644 --- a/src/govuk/components/breadcrumbs/template.test.js +++ b/src/govuk/components/breadcrumbs/template.test.js @@ -4,9 +4,13 @@ const { axe, render, getExamples } = require('../../../../lib/jest-helpers') -const examples = getExamples('breadcrumbs') - describe('Breadcrumbs', () => { + let examples + + beforeAll(async () => { + examples = await getExamples('breadcrumbs') + }) + describe('default example', () => { it('passes accessibility tests', async () => { const $ = render('breadcrumbs', examples.default) diff --git a/src/govuk/components/button/template.test.js b/src/govuk/components/button/template.test.js index 246899d06d..7b6e3f2d53 100644 --- a/src/govuk/components/button/template.test.js +++ b/src/govuk/components/button/template.test.js @@ -4,9 +4,13 @@ const { axe, render, getExamples } = require('../../../../lib/jest-helpers') -const examples = getExamples('button') - describe('Button', () => { + let examples + + beforeAll(async () => { + examples = await getExamples('button') + }) + describe('default example', () => { it('passes accessibility tests', async () => { const $ = render('button', examples.default) diff --git a/src/govuk/components/character-count/template.test.js b/src/govuk/components/character-count/template.test.js index 9ddd7e5873..c2fe3e2bdb 100644 --- a/src/govuk/components/character-count/template.test.js +++ b/src/govuk/components/character-count/template.test.js @@ -4,11 +4,15 @@ const { axe, render, getExamples, htmlWithClassName } = require('../../../../lib/jest-helpers') -const examples = getExamples('character-count') - const WORD_BOUNDARY = '\\b' describe('Character count', () => { + let examples + + beforeAll(async () => { + examples = await getExamples('character-count') + }) + describe('default example', () => { it('passes accessibility tests', async () => { const $ = render('character-count', examples.default) diff --git a/src/govuk/components/checkboxes/template.test.js b/src/govuk/components/checkboxes/template.test.js index 1ff5b7360c..d18403a020 100644 --- a/src/govuk/components/checkboxes/template.test.js +++ b/src/govuk/components/checkboxes/template.test.js @@ -4,12 +4,16 @@ const { axe, render, getExamples, htmlWithClassName } = require('../../../../lib/jest-helpers') -const examples = getExamples('checkboxes') - const WORD_BOUNDARY = '\\b' const WHITESPACE = '\\s' describe('Checkboxes', () => { + let examples + + beforeAll(async () => { + examples = await getExamples('checkboxes') + }) + it('default example passes accessibility tests', async () => { const $ = render('checkboxes', examples.default) diff --git a/src/govuk/components/cookie-banner/template.test.js b/src/govuk/components/cookie-banner/template.test.js index 574ffba9f7..aca6a2d3f0 100644 --- a/src/govuk/components/cookie-banner/template.test.js +++ b/src/govuk/components/cookie-banner/template.test.js @@ -4,9 +4,13 @@ const { axe, render, getExamples } = require('../../../../lib/jest-helpers') -const examples = getExamples('cookie-banner') - describe('Cookie Banner', () => { + let examples + + beforeAll(async () => { + examples = await getExamples('cookie-banner') + }) + describe('question banner', () => { it('passes accessibility tests', async () => { const $ = render('cookie-banner', examples.default) diff --git a/src/govuk/components/date-input/template.test.js b/src/govuk/components/date-input/template.test.js index 8cd672c8e8..cd86abb525 100644 --- a/src/govuk/components/date-input/template.test.js +++ b/src/govuk/components/date-input/template.test.js @@ -4,12 +4,16 @@ const { axe, render, getExamples, htmlWithClassName } = require('../../../../lib/jest-helpers') -const examples = getExamples('date-input') - const WORD_BOUNDARY = '\\b' const WHITESPACE = '\\s' describe('Date input', () => { + let examples + + beforeAll(async () => { + examples = await getExamples('date-input') + }) + describe('default example', () => { it('passes accessibility tests', async () => { const $ = render('date-input', examples.default) diff --git a/src/govuk/components/details/template.test.js b/src/govuk/components/details/template.test.js index a23220622c..8dd9abca27 100644 --- a/src/govuk/components/details/template.test.js +++ b/src/govuk/components/details/template.test.js @@ -4,9 +4,13 @@ const { axe, render, getExamples } = require('../../../../lib/jest-helpers') -const examples = getExamples('details') - describe('Details', () => { + let examples + + beforeAll(async () => { + examples = await getExamples('details') + }) + it('default example passes accessibility tests', async () => { const $ = render('details', examples.default) diff --git a/src/govuk/components/error-message/template.test.js b/src/govuk/components/error-message/template.test.js index 7787b4390e..6fbab2c015 100644 --- a/src/govuk/components/error-message/template.test.js +++ b/src/govuk/components/error-message/template.test.js @@ -4,9 +4,13 @@ const { axe, render, getExamples } = require('../../../../lib/jest-helpers') -const examples = getExamples('error-message') - describe('Error message', () => { + let examples + + beforeAll(async () => { + examples = await getExamples('error-message') + }) + it('default example passes accessibility tests', async () => { const $ = render('error-message', examples.default) diff --git a/src/govuk/components/error-summary/error-summary.test.js b/src/govuk/components/error-summary/error-summary.test.js index 4327f2bb4f..b33fb90420 100644 --- a/src/govuk/components/error-summary/error-summary.test.js +++ b/src/govuk/components/error-summary/error-summary.test.js @@ -3,14 +3,18 @@ */ const { renderAndInitialise, getExamples } = require('../../../../lib/jest-helpers') -const examples = getExamples('error-summary') - const configPaths = require('../../../../config/paths.js') const PORT = configPaths.ports.test const baseUrl = 'http://localhost:' + PORT describe('Error Summary', () => { + let examples + + beforeAll(async () => { + examples = await getExamples('error-summary') + }) + it('adds the tabindex attribute on page load', async () => { await page.goto(baseUrl + '/components/error-summary/preview', { waitUntil: 'load' }) diff --git a/src/govuk/components/error-summary/template.test.js b/src/govuk/components/error-summary/template.test.js index 90f839adf7..3327de6348 100644 --- a/src/govuk/components/error-summary/template.test.js +++ b/src/govuk/components/error-summary/template.test.js @@ -4,9 +4,13 @@ const { axe, render, getExamples } = require('../../../../lib/jest-helpers') -const examples = getExamples('error-summary') - describe('Error-summary', () => { + let examples + + beforeAll(async () => { + examples = await getExamples('error-summary') + }) + describe('default example', () => { it('passes accessibility tests', async () => { const $ = render('error-summary', examples.default) diff --git a/src/govuk/components/fieldset/template.test.js b/src/govuk/components/fieldset/template.test.js index 61939e8bb1..2d4cf0a508 100644 --- a/src/govuk/components/fieldset/template.test.js +++ b/src/govuk/components/fieldset/template.test.js @@ -4,9 +4,13 @@ const { axe, render, getExamples } = require('../../../../lib/jest-helpers') -const examples = getExamples('fieldset') - describe('fieldset', () => { + let examples + + beforeAll(async () => { + examples = await getExamples('fieldset') + }) + it('passes accessibility tests', async () => { const $ = render('fieldset', examples.default) diff --git a/src/govuk/components/file-upload/template.test.js b/src/govuk/components/file-upload/template.test.js index 83423b1541..b08b69d8d9 100644 --- a/src/govuk/components/file-upload/template.test.js +++ b/src/govuk/components/file-upload/template.test.js @@ -4,12 +4,16 @@ const { axe, render, getExamples, htmlWithClassName } = require('../../../../lib/jest-helpers') -const examples = getExamples('file-upload') - const WORD_BOUNDARY = '\\b' const WHITESPACE = '\\s' describe('File upload', () => { + let examples + + beforeAll(async () => { + examples = await getExamples('file-upload') + }) + describe('default example', () => { it('passes accessibility tests', async () => { const $ = render('file-upload', examples.default) diff --git a/src/govuk/components/footer/template.test.js b/src/govuk/components/footer/template.test.js index bc5123b56d..6b3c2fb503 100644 --- a/src/govuk/components/footer/template.test.js +++ b/src/govuk/components/footer/template.test.js @@ -4,9 +4,13 @@ const { axe, render, getExamples } = require('../../../../lib/jest-helpers') -const examples = getExamples('footer') - describe('footer', () => { + let examples + + beforeAll(async () => { + examples = await getExamples('footer') + }) + it('default example passes accessibility tests', async () => { const $ = render('footer', examples.default) diff --git a/src/govuk/components/header/template.test.js b/src/govuk/components/header/template.test.js index 5c5f42745c..66454f3db5 100644 --- a/src/govuk/components/header/template.test.js +++ b/src/govuk/components/header/template.test.js @@ -4,9 +4,13 @@ const { axe, render, getExamples } = require('../../../../lib/jest-helpers') -const examples = getExamples('header') - describe('header', () => { + let examples + + beforeAll(async () => { + examples = await getExamples('header') + }) + describe('default example', () => { it('passes accessibility tests', async () => { const $ = render('header', examples.default) @@ -252,8 +256,13 @@ describe('header', () => { }) describe('SVG logo', () => { - const $ = render('header', examples.default) - const $svg = $('.govuk-header__logotype-crown') + let $ + let $svg + + beforeAll(() => { + $ = render('header', examples.default) + $svg = $('.govuk-header__logotype-crown') + }) it('sets focusable="false" so that IE does not treat it as an interactive element', () => { expect($svg.attr('focusable')).toEqual('false') @@ -264,9 +273,8 @@ describe('header', () => { }) describe('fallback PNG', () => { - const $fallbackImage = $('.govuk-header__logotype-crown-fallback-image') - it('is invisible to modern browsers', () => { + const $fallbackImage = $('.govuk-header__logotype-crown-fallback-image') expect($fallbackImage.length).toEqual(0) }) }) diff --git a/src/govuk/components/hint/template.test.js b/src/govuk/components/hint/template.test.js index 0d71774931..4a02f75602 100644 --- a/src/govuk/components/hint/template.test.js +++ b/src/govuk/components/hint/template.test.js @@ -4,9 +4,13 @@ const { axe, render, getExamples } = require('../../../../lib/jest-helpers') -const examples = getExamples('hint') - describe('Hint', () => { + let examples + + beforeAll(async () => { + examples = await getExamples('hint') + }) + describe('by default', () => { it('passes accessibility tests', async () => { const $ = render('hint', examples.default) diff --git a/src/govuk/components/input/template.test.js b/src/govuk/components/input/template.test.js index e7186e42da..695e1d86ba 100644 --- a/src/govuk/components/input/template.test.js +++ b/src/govuk/components/input/template.test.js @@ -4,12 +4,16 @@ const { axe, render, getExamples, htmlWithClassName } = require('../../../../lib/jest-helpers') -const examples = getExamples('input') - const WORD_BOUNDARY = '\\b' const WHITESPACE = '\\s' describe('Input', () => { + let examples + + beforeAll(async () => { + examples = await getExamples('input') + }) + describe('default example', () => { it('passes accessibility tests', async () => { const $ = render('input', examples.default) diff --git a/src/govuk/components/inset-text/template.test.js b/src/govuk/components/inset-text/template.test.js index 5420f653c5..3fc00ac735 100644 --- a/src/govuk/components/inset-text/template.test.js +++ b/src/govuk/components/inset-text/template.test.js @@ -4,9 +4,13 @@ const { axe, render, getExamples } = require('../../../../lib/jest-helpers') -const examples = getExamples('inset-text') - describe('Inset text', () => { + let examples + + beforeAll(async () => { + examples = await getExamples('inset-text') + }) + describe('by default', () => { it('passes accessibility tests', async () => { const $ = render('inset-text', examples.default) diff --git a/src/govuk/components/label/template.test.js b/src/govuk/components/label/template.test.js index e14f58cbe2..2581e05003 100644 --- a/src/govuk/components/label/template.test.js +++ b/src/govuk/components/label/template.test.js @@ -4,9 +4,13 @@ const { axe, render, getExamples } = require('../../../../lib/jest-helpers') -const examples = getExamples('label') - describe('Label', () => { + let examples + + beforeAll(async () => { + examples = await getExamples('label') + }) + describe('by default', () => { it('passes accessibility tests', async () => { const $ = render('label', examples.default) diff --git a/src/govuk/components/notification-banner/notification-banner.test.js b/src/govuk/components/notification-banner/notification-banner.test.js index 4e11435454..194cdb655f 100644 --- a/src/govuk/components/notification-banner/notification-banner.test.js +++ b/src/govuk/components/notification-banner/notification-banner.test.js @@ -4,14 +4,18 @@ const { renderAndInitialise, getExamples } = require('../../../../lib/jest-helpers') -const examples = getExamples('notification-banner') - const configPaths = require('../../../../config/paths.js') const PORT = configPaths.ports.test const baseUrl = 'http://localhost:' + PORT describe('Notification banner, when type is set to "success"', () => { + let examples + + beforeAll(async () => { + examples = await getExamples('notification-banner') + }) + it('has the correct tabindex attribute to be focused with JavaScript', async () => { await page.goto(baseUrl + '/components/notification-banner/with-type-as-success/preview', { waitUntil: 'load' }) diff --git a/src/govuk/components/notification-banner/template.test.js b/src/govuk/components/notification-banner/template.test.js index 251cfa3bce..75131c9c10 100644 --- a/src/govuk/components/notification-banner/template.test.js +++ b/src/govuk/components/notification-banner/template.test.js @@ -4,9 +4,13 @@ const { axe, render, getExamples } = require('../../../../lib/jest-helpers') -const examples = getExamples('notification-banner') - describe('Notification-banner', () => { + let examples + + beforeAll(async () => { + examples = await getExamples('notification-banner') + }) + describe('default example', () => { it('passes accessibility tests', async () => { const $ = render('notification-banner', examples.default) diff --git a/src/govuk/components/pagination/template.test.js b/src/govuk/components/pagination/template.test.js index be00c78c3e..c7695760c1 100644 --- a/src/govuk/components/pagination/template.test.js +++ b/src/govuk/components/pagination/template.test.js @@ -4,9 +4,13 @@ const { axe, render, getExamples } = require('../../../../lib/jest-helpers') -const examples = getExamples('pagination') - describe('Pagination', () => { + let examples + + beforeAll(async () => { + examples = await getExamples('pagination') + }) + describe('default examples', () => { it('passes accessibility tests', async () => { const $ = render('pagination', examples.default) diff --git a/src/govuk/components/panel/template.test.js b/src/govuk/components/panel/template.test.js index 90acf0dc58..a0a2b6afd4 100644 --- a/src/govuk/components/panel/template.test.js +++ b/src/govuk/components/panel/template.test.js @@ -4,9 +4,13 @@ const { axe, render, getExamples } = require('../../../../lib/jest-helpers') -const examples = getExamples('panel') - describe('Panel', () => { + let examples + + beforeAll(async () => { + examples = await getExamples('panel') + }) + describe('default example', () => { it('passes accessibility tests', async () => { const $ = render('panel', examples.default) diff --git a/src/govuk/components/phase-banner/template.test.js b/src/govuk/components/phase-banner/template.test.js index 80fb3a67b5..306f53fc5d 100644 --- a/src/govuk/components/phase-banner/template.test.js +++ b/src/govuk/components/phase-banner/template.test.js @@ -4,9 +4,13 @@ const { axe, render, getExamples, htmlWithClassName } = require('../../../../lib/jest-helpers') -const examples = getExamples('phase-banner') - describe('Phase banner', () => { + let examples + + beforeAll(async () => { + examples = await getExamples('phase-banner') + }) + describe('by default', () => { it('passes accessibility tests', async () => { const $ = render('phase-banner', examples.default) diff --git a/src/govuk/components/radios/template.test.js b/src/govuk/components/radios/template.test.js index eed6801ebc..6ac238f741 100644 --- a/src/govuk/components/radios/template.test.js +++ b/src/govuk/components/radios/template.test.js @@ -4,12 +4,16 @@ const { axe, render, getExamples, htmlWithClassName } = require('../../../../lib/jest-helpers') -const examples = getExamples('radios') - const WORD_BOUNDARY = '\\b' const WHITESPACE = '\\s' describe('Radios', () => { + let examples + + beforeAll(async () => { + examples = await getExamples('radios') + }) + it('default example passes accessibility tests', async () => { const $ = render('radios', examples.default) diff --git a/src/govuk/components/select/template.test.js b/src/govuk/components/select/template.test.js index 9562ac516b..33ca82e79a 100644 --- a/src/govuk/components/select/template.test.js +++ b/src/govuk/components/select/template.test.js @@ -4,12 +4,16 @@ const { axe, render, getExamples, htmlWithClassName } = require('../../../../lib/jest-helpers') -const examples = getExamples('select') - const WORD_BOUNDARY = '\\b' const WHITESPACE = '\\s' describe('Select', () => { + let examples + + beforeAll(async () => { + examples = await getExamples('select') + }) + describe('by default', () => { it('passes accessibility tests', async () => { const $ = render('select', examples.default) diff --git a/src/govuk/components/skip-link/template.test.js b/src/govuk/components/skip-link/template.test.js index b22523485d..dc86cfc27c 100644 --- a/src/govuk/components/skip-link/template.test.js +++ b/src/govuk/components/skip-link/template.test.js @@ -4,9 +4,13 @@ const { axe, render, getExamples } = require('../../../../lib/jest-helpers') -const examples = getExamples('skip-link') - describe('Skip link', () => { + let examples + + beforeAll(async () => { + examples = await getExamples('skip-link') + }) + describe('default example', () => { it('passes accessibility tests', async () => { const $ = render('skip-link', examples.default) diff --git a/src/govuk/components/summary-list/template.test.js b/src/govuk/components/summary-list/template.test.js index f99464b664..dbf08c6588 100644 --- a/src/govuk/components/summary-list/template.test.js +++ b/src/govuk/components/summary-list/template.test.js @@ -4,9 +4,13 @@ const { axe, render, getExamples } = require('../../../../lib/jest-helpers') -const examples = getExamples('summary-list') - describe('Summary list', () => { + let examples + + beforeAll(async () => { + examples = await getExamples('summary-list') + }) + describe('default example', () => { it('passes accessibility tests', async () => { const $ = render('summary-list', examples.default) @@ -217,8 +221,13 @@ describe('Summary list', () => { }) describe('when only some rows have actions', () => { - const $ = render('summary-list', examples['with some actions']) - const $component = $('.govuk-summary-list') + let $ + let $component + + beforeAll(() => { + $ = render('summary-list', examples['with some actions']) + $component = $('.govuk-summary-list') + }) it('passes accessibility tests', async () => { const results = await axe($.html()) @@ -239,8 +248,13 @@ describe('Summary list', () => { }) describe('when no rows have actions', () => { - const $ = render('summary-list', examples.default) - const $component = $('.govuk-summary-list') + let $ + let $component + + beforeAll(() => { + $ = render('summary-list', examples.default) + $component = $('.govuk-summary-list') + }) it('passes accessibility tests', async () => { const results = await axe($.html()) diff --git a/src/govuk/components/table/template.test.js b/src/govuk/components/table/template.test.js index 056953884f..f3fc3ea21c 100644 --- a/src/govuk/components/table/template.test.js +++ b/src/govuk/components/table/template.test.js @@ -4,9 +4,13 @@ const { axe, render, getExamples } = require('../../../../lib/jest-helpers') -const examples = getExamples('table') - describe('Table', () => { + let examples + + beforeAll(async () => { + examples = await getExamples('table') + }) + it('passes basic accessibility tests', async () => { const $ = render('table', examples.default) diff --git a/src/govuk/components/tabs/template.test.js b/src/govuk/components/tabs/template.test.js index a6729d1bd6..aeecc0cbf1 100644 --- a/src/govuk/components/tabs/template.test.js +++ b/src/govuk/components/tabs/template.test.js @@ -4,9 +4,13 @@ const { axe, render, getExamples } = require('../../../../lib/jest-helpers') -const examples = getExamples('tabs') - describe('Tabs', () => { + let examples + + beforeAll(async () => { + examples = await getExamples('tabs') + }) + describe('default example', () => { it('passes accessibility tests', async () => { const $ = render('tabs', examples.default) diff --git a/src/govuk/components/tag/template.test.js b/src/govuk/components/tag/template.test.js index c20dcf4755..6fe1f83619 100644 --- a/src/govuk/components/tag/template.test.js +++ b/src/govuk/components/tag/template.test.js @@ -4,9 +4,13 @@ const { axe, render, getExamples } = require('../../../../lib/jest-helpers') -const examples = getExamples('tag') - describe('Tag', () => { + let examples + + beforeAll(async () => { + examples = await getExamples('tag') + }) + describe('default example', () => { it('passes accessibility tests', async () => { const $ = render('tag', examples.default) diff --git a/src/govuk/components/textarea/template.test.js b/src/govuk/components/textarea/template.test.js index 4467efcec8..5a3943263d 100644 --- a/src/govuk/components/textarea/template.test.js +++ b/src/govuk/components/textarea/template.test.js @@ -4,12 +4,16 @@ const { axe, render, getExamples, htmlWithClassName } = require('../../../../lib/jest-helpers') -const examples = getExamples('textarea') - const WORD_BOUNDARY = '\\b' const WHITESPACE = '\\s' describe('Textarea', () => { + let examples + + beforeAll(async () => { + examples = await getExamples('textarea') + }) + describe('default example', () => { it('passes accessibility tests', async () => { const $ = render('textarea', examples.default) diff --git a/src/govuk/components/warning-text/template.test.js b/src/govuk/components/warning-text/template.test.js index 3a477a5694..1d8cdb2ae0 100644 --- a/src/govuk/components/warning-text/template.test.js +++ b/src/govuk/components/warning-text/template.test.js @@ -4,9 +4,13 @@ const { axe, render, getExamples } = require('../../../../lib/jest-helpers') -const examples = getExamples('warning-text') - describe('Warning text', () => { + let examples + + beforeAll(async () => { + examples = await getExamples('warning-text') + }) + describe('default example', () => { it('passes accessibility tests', async () => { const $ = render('warning-text', examples.default) diff --git a/tasks/gulp/__tests__/after-build-dist.test.js b/tasks/gulp/__tests__/after-build-dist.test.js index aab60dceea..b37fc291f6 100644 --- a/tasks/gulp/__tests__/after-build-dist.test.js +++ b/tasks/gulp/__tests__/after-build-dist.test.js @@ -1,5 +1,5 @@ const path = require('path') -const lib = require('../../../lib/file-helper') +const { readFile } = require('fs/promises') const configPaths = require('../../../config/paths.js') const recursive = require('recursive-readdir') @@ -54,7 +54,11 @@ describe('dist/', () => { }) describe(`govuk-frontend-${version}.min.css`, () => { - const stylesheet = lib.readFileContents(path.join(configPaths.dist, `govuk-frontend-${version}.min.css`)) + let stylesheet + + beforeAll(async () => { + stylesheet = await readFile(path.join(configPaths.dist, `govuk-frontend-${version}.min.css`), 'utf8') + }) it('should not contain current media query displayed on body element', () => { expect(stylesheet).not.toMatch(/body:before{content:/) @@ -66,7 +70,11 @@ describe('dist/', () => { }) describe(`govuk-frontend-ie8-${version}.min.css`, () => { - const stylesheet = lib.readFileContents(path.join(configPaths.dist, `govuk-frontend-ie8-${version}.min.css`)) + let stylesheet + + beforeAll(async () => { + stylesheet = await readFile(path.join(configPaths.dist, `govuk-frontend-ie8-${version}.min.css`), 'utf8') + }) it('should not contain current media query displayed on body element', () => { expect(stylesheet).not.toMatch(/body:before{content:/) @@ -74,7 +82,11 @@ describe('dist/', () => { }) describe(`govuk-frontend-${version}.min.js`, () => { - const javascript = lib.readFileContents(path.join(configPaths.dist, `govuk-frontend-${version}.min.js`)) + let javascript + + beforeAll(async () => { + javascript = await readFile(path.join(configPaths.dist, `govuk-frontend-${version}.min.js`), 'utf8') + }) it('should have the correct version name', () => { expect(javascript).toBeTruthy() diff --git a/tasks/gulp/__tests__/after-build-package.test.js b/tasks/gulp/__tests__/after-build-package.test.js index aebf845011..521a6508ae 100644 --- a/tasks/gulp/__tests__/after-build-package.test.js +++ b/tasks/gulp/__tests__/after-build-package.test.js @@ -1,21 +1,24 @@ -const fs = require('fs') +const { readFile } = require('fs/promises') const path = require('path') -const util = require('util') const recursive = require('recursive-readdir') const glob = require('glob') const configPaths = require('../../../config/paths.js') -const lib = require('../../../lib/file-helper') +const { getAllComponents } = require('../../../lib/file-helper') const { componentNameToJavaScriptModuleName } = require('../../../lib/helper-functions') const { renderSass } = require('../../../lib/jest-helpers') -const readFile = util.promisify(fs.readFile) -const componentNames = lib.allComponents.slice() -const componentsWithJavaScript = glob.sync(configPaths.package + 'govuk/components/' + '**/!(*.test).js') - describe('package/', () => { + let components + let componentsWithJavaScript + + beforeAll(async () => { + components = await getAllComponents() + componentsWithJavaScript = glob.sync(configPaths.package + 'govuk/components/' + '**/!(*.test).js') + }) + it('should contain the expected files', () => { // Build an array of the files that are present in the package directory. const actualPackageFiles = () => { @@ -129,71 +132,60 @@ describe('package/', () => { }) describe('component', () => { - it.each(componentNames)('\'%s\' should have macro-options.json that contains JSON', (name) => { - const filePath = path.join(configPaths.package, 'govuk', 'components', name, 'macro-options.json') - return readFile(filePath, 'utf8') - .then((data) => { - const parsedData = JSON.parse(data) - - // We expect the component JSON to contain "name", "type", "required", "description" - expect(parsedData).toBeInstanceOf(Array) - parsedData.forEach((option) => { - expect(option).toEqual( - expect.objectContaining({ - name: expect.any(String), - type: expect.any(String), - required: expect.any(Boolean), - description: expect.any(String) - }) - ) - }) - }) - .catch(error => { - throw error + it('should have macro-options.json that contains JSON', () => { + return Promise.all(components.map(async (component) => { + const data = await readFile(path.join(configPaths.package, 'govuk', 'components', component, 'macro-options.json'), 'utf8') + const parsedData = JSON.parse(data) + + // We expect the component JSON to contain "name", "type", "required", "description" + expect(parsedData).toBeInstanceOf(Array) + parsedData.forEach((option) => { + expect(option).toEqual( + expect.objectContaining({ + name: expect.any(String), + type: expect.any(String), + required: expect.any(Boolean), + description: expect.any(String) + }) + ) }) + })) }) }) describe('components with JavaScript', () => { - it.each(componentsWithJavaScript)('\'%s\' should have component JavaScript file with correct module name', (javaScriptFile) => { - const moduleName = componentNameToJavaScriptModuleName(path.parse(javaScriptFile).name) - - return readFile(javaScriptFile, 'utf8') - .then((data) => { - expect(data).toContain("typeof define === 'function' && define.amd ? define('" + moduleName + "', factory)") - }) - .catch(error => { - throw error - }) + it('should have component JavaScript file with correct module name', () => { + return Promise.all(componentsWithJavaScript.map(async (componentWithJavaScript) => { + const data = await readFile(componentWithJavaScript, 'utf8') + const moduleName = componentNameToJavaScriptModuleName(path.parse(componentWithJavaScript).name) + expect(data).toContain("typeof define === 'function' && define.amd ? define('" + moduleName + "', factory)") + })) }) }) describe('fixtures', () => { - it.each(componentNames)('\'%s\' should have fixtures.json that contains JSON', (name) => { - const filePath = path.join(configPaths.package, 'govuk', 'components', name, 'fixtures.json') - return readFile(filePath, 'utf8') - .then((data) => { - const parsedData = JSON.parse(data) - // We expect the component JSON to contain "component" and an array of "fixtures" with "name", "options", and "html" - expect(parsedData).toEqual( - expect.objectContaining({ - component: name, - fixtures: expect.any(Array) - }) - ) - - parsedData.fixtures.forEach((fixture) => { - expect(fixture).toEqual({ - name: expect.any(String), - options: expect.any(Object), - html: expect.any(String), - hidden: expect.any(Boolean) - }) + it('should have fixtures.json that contains JSON', () => { + return Promise.all(components.map(async (component) => { + const data = await readFile(path.join(configPaths.package, 'govuk', 'components', component, 'fixtures.json'), 'utf8') + const parsedData = JSON.parse(data) + + // We expect the component JSON to contain "component" and an array of "fixtures" with "name", "options", and "html" + expect(parsedData).toEqual( + expect.objectContaining({ + component, + fixtures: expect.any(Array) + }) + ) + + parsedData.fixtures.forEach((fixture) => { + expect(fixture).toEqual({ + name: expect.any(String), + options: expect.any(Object), + html: expect.any(String), + hidden: expect.any(Boolean) }) }) - .catch(error => { - throw error - }) + })) }) }) })