From 5a05c2387db2e87027a400a3553b56e8b24be3c8 Mon Sep 17 00:00:00 2001 From: Patrick Cartlidge Date: Mon, 19 Aug 2024 11:27:24 +0100 Subject: [PATCH 01/45] Add `isSupported` to `all.mjs` - adds `isSupported` as an export for import from `govuk-frontend` - removes `internal` from JSDoc to mark function as public - adds `exports isSupported function` test - updates `exports Components` test --- packages/govuk-frontend/src/govuk/all.mjs | 1 + .../src/govuk/all.puppeteer.test.js | 16 +++++++++++++++- .../govuk-frontend/src/govuk/common/index.mjs | 3 +-- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/packages/govuk-frontend/src/govuk/all.mjs b/packages/govuk-frontend/src/govuk/all.mjs index 78fe080e39..f5f5b42992 100644 --- a/packages/govuk-frontend/src/govuk/all.mjs +++ b/packages/govuk-frontend/src/govuk/all.mjs @@ -13,6 +13,7 @@ export { ServiceNavigation } from './components/service-navigation/service-navig export { SkipLink } from './components/skip-link/skip-link.mjs' export { Tabs } from './components/tabs/tabs.mjs' export { initAll, createAll } from './init.mjs' +export { isSupported } from './common/index.mjs' /** * @typedef {import('./init.mjs').Config} Config diff --git a/packages/govuk-frontend/src/govuk/all.puppeteer.test.js b/packages/govuk-frontend/src/govuk/all.puppeteer.test.js index 9343b57932..f4c16de093 100644 --- a/packages/govuk-frontend/src/govuk/all.puppeteer.test.js +++ b/packages/govuk-frontend/src/govuk/all.puppeteer.test.js @@ -41,10 +41,24 @@ describe('GOV.UK Frontend', () => { expect(typeofCreateAll).toBe('function') }) + it('exports `isSupported` function', async () => { + const typeofIsSupported = await page.evaluate( + async (importPath, exportName) => { + const namespace = await import(importPath) + return typeof namespace[exportName] + }, + scriptsPath.href, + 'isSupported' + ) + + expect(typeofIsSupported).toBe('function') + }) + it('exports Components', async () => { const components = exported .filter( - (method) => !['initAll', 'createAll', 'version'].includes(method) + (method) => + !['initAll', 'createAll', 'version', 'isSupported'].includes(method) ) .sort() diff --git a/packages/govuk-frontend/src/govuk/common/index.mjs b/packages/govuk-frontend/src/govuk/common/index.mjs index d2e18910b1..60699f4088 100644 --- a/packages/govuk-frontend/src/govuk/common/index.mjs +++ b/packages/govuk-frontend/src/govuk/common/index.mjs @@ -194,8 +194,7 @@ export function setFocus($element, options = {}) { * Some browsers will load and run our JavaScript but GOV.UK Frontend * won't be supported. * - * @internal - * @param {HTMLElement | null} [$scope] - HTML element `` checked for browser support + * @param {HTMLElement | null} [$scope] - (internal) `` HTML element checked for browser support * @returns {boolean} Whether GOV.UK Frontend is supported on this page */ export function isSupported($scope = document.body) { From d47f41618567ad92cde3e95ea2b807879e9a8884 Mon Sep 17 00:00:00 2001 From: Patrick Cartlidge Date: Mon, 19 Aug 2024 15:03:10 +0100 Subject: [PATCH 02/45] Add `isSupported` to `createAll` - createAll returns empty array and throws error is `isSupported` false - update existing createAll tests and add test for `isSupported` false --- .../src/govuk/init.jsdom.test.mjs | 20 ++++++++++++++++++- packages/govuk-frontend/src/govuk/init.mjs | 6 ++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/packages/govuk-frontend/src/govuk/init.jsdom.test.mjs b/packages/govuk-frontend/src/govuk/init.jsdom.test.mjs index 0ccb899b44..eff41e8402 100644 --- a/packages/govuk-frontend/src/govuk/init.jsdom.test.mjs +++ b/packages/govuk-frontend/src/govuk/init.jsdom.test.mjs @@ -154,8 +154,12 @@ describe('initAll', () => { }) describe('createAll', () => { + beforeEach(() => { + document.body.classList.add('govuk-frontend-supported') + }) + afterEach(() => { - document.body.innerHTML = '' + document.body.outerHTML = '' }) class MockComponent { @@ -184,6 +188,20 @@ describe('createAll', () => { expect(result).toStrictEqual([]) }) + it('throws error and returns empty array if not supported', () => { + document.body.classList.remove('govuk-frontend-supported') + + const result = createAll(MockComponent) + + expect(global.console.log).toHaveBeenCalledWith( + expect.objectContaining({ + message: 'GOV.UK Frontend is not supported in this browser' + }) + ) + + expect(result).toStrictEqual([]) + }) + it('returns an empty array if no matching components exist on the page', () => { const componentRoot = document.createElement('div') componentRoot.setAttribute( diff --git a/packages/govuk-frontend/src/govuk/init.mjs b/packages/govuk-frontend/src/govuk/init.mjs index 2aaaf86bc0..6a24ce67ed 100644 --- a/packages/govuk-frontend/src/govuk/init.mjs +++ b/packages/govuk-frontend/src/govuk/init.mjs @@ -76,6 +76,12 @@ function createAll(Component, config, $scope = document) { `[data-module="${Component.moduleName}"]` ) + // Skip initialisation when GOV.UK Frontend is not supported + if (!isSupported()) { + console.log(new SupportError()) + return [] + } + /* eslint-disable-next-line @typescript-eslint/no-unsafe-return -- * We can't define CompatibleClass as `{new(): CompatibleClass, moduleName: string}`, * as when doing `typeof Accordion` (or any component), TypeScript doesn't seem From 3cbc709d489e04a468d9032a942f26870af656d2 Mon Sep 17 00:00:00 2001 From: Patrick Cartlidge Date: Wed, 21 Aug 2024 15:15:36 +0100 Subject: [PATCH 03/45] Add `errorCallback` to `createAll` - Add support for `onError` callback in `createAll` which is called if error occurs on component initialisation. - New parameter for `createAll`, `createAllOptions` which allows user to specify a `scope`, `onError` or an object that contains both. - `isSupported` will execute `onError` callback if specified - New tests added for `onError` callback and `createAllOptions`. --- .../src/govuk/init.jsdom.test.mjs | 120 +++++++++++++++++- packages/govuk-frontend/src/govuk/init.mjs | 69 +++++++++- 2 files changed, 183 insertions(+), 6 deletions(-) diff --git a/packages/govuk-frontend/src/govuk/init.jsdom.test.mjs b/packages/govuk-frontend/src/govuk/init.jsdom.test.mjs index eff41e8402..ebef29c340 100644 --- a/packages/govuk-frontend/src/govuk/init.jsdom.test.mjs +++ b/packages/govuk-frontend/src/govuk/init.jsdom.test.mjs @@ -202,6 +202,40 @@ describe('createAll', () => { expect(result).toStrictEqual([]) }) + it('executes specified onError callback and returns empty array if not supported', () => { + document.body.classList.remove('govuk-frontend-supported') + + const errorCallback = jest.fn((error, context) => { + console.log(error) + console.log(context) + }) + + // Silence warnings in test output, and allow us to 'expect' them + jest.spyOn(global.console, 'log').mockImplementation() + + expect(() => { + createAll( + MockComponent, + { attribute: 'random' }, + { onError: errorCallback } + ) + }).not.toThrow() + + expect(errorCallback).toHaveBeenCalled() + + expect(global.console.log).toHaveBeenCalledWith( + expect.objectContaining({ + message: 'GOV.UK Frontend is not supported in this browser' + }) + ) + expect(global.console.log).toHaveBeenCalledWith( + expect.objectContaining({ + component: MockComponent, + config: { attribute: 'random' } + }) + ) + }) + it('returns an empty array if no matching components exist on the page', () => { const componentRoot = document.createElement('div') componentRoot.setAttribute( @@ -298,7 +332,7 @@ describe('createAll', () => { }) }) - describe('when a $scope is passed', () => { + describe('when a $scope is passed as third parameter', () => { it('only initialises components within that scope', () => { document.body.innerHTML = `
@@ -321,6 +355,28 @@ describe('createAll', () => { document.querySelector('.my-scope [data-module="mock-component"]') ]) }) + + it('only initialises components within that scope if scope passed as options attribute', () => { + document.body.innerHTML = ` +
+
+
+
' +
+
+
` + + const result = createAll(MockComponent, undefined, { + onError: (e, x) => {}, + scope: document.querySelector('.my-scope') + }) + + expect(result).toStrictEqual([expect.any(MockComponent)]) + + expect(result[0].args).toStrictEqual([ + document.querySelector('.my-scope [data-module="mock-component"]') + ]) + }) }) describe('when components throw errors', () => { @@ -333,6 +389,68 @@ describe('createAll', () => { } } + it('executes callback if specified as part of options object', () => { + document.body.innerHTML = `
` + + const errorCallback = jest.fn((error, context) => { + console.log(error) + console.log(context) + }) + + // Silence warnings in test output, and allow us to 'expect' them + jest.spyOn(global.console, 'log').mockImplementation() + + expect(() => { + createAll( + MockComponentThatErrors, + { attribute: 'random' }, + { onError: errorCallback } + ) + }).not.toThrow() + + expect(errorCallback).toHaveBeenCalled() + + expect(global.console.log).toHaveBeenCalledWith(expect.any(Error)) + expect(global.console.log).toHaveBeenCalledWith( + expect.objectContaining({ + component: MockComponentThatErrors, + config: { attribute: 'random' }, + element: document.querySelector('[data-module="mock-component"]') + }) + ) + }) + + it('executes callback if specified as function', () => { + document.body.innerHTML = `
` + + const errorCallback = jest.fn((error, context) => { + console.log(error) + console.log(context) + }) + + // Silence warnings in test output, and allow us to 'expect' them + jest.spyOn(global.console, 'log').mockImplementation() + + expect(() => { + createAll( + MockComponentThatErrors, + { attribute: 'random' }, + errorCallback + ) + }).not.toThrow() + + expect(errorCallback).toHaveBeenCalled() + + expect(global.console.log).toHaveBeenCalledWith(expect.any(Error)) + expect(global.console.log).toHaveBeenCalledWith( + expect.objectContaining({ + component: MockComponentThatErrors, + config: { attribute: 'random' }, + element: document.querySelector('[data-module="mock-component"]') + }) + ) + }) + it('catches errors thrown by components and logs them to the console', () => { document.body.innerHTML = `
` diff --git a/packages/govuk-frontend/src/govuk/init.mjs b/packages/govuk-frontend/src/govuk/init.mjs index 6a24ce67ed..171891e5e2 100644 --- a/packages/govuk-frontend/src/govuk/init.mjs +++ b/packages/govuk-frontend/src/govuk/init.mjs @@ -67,18 +67,46 @@ function initAll(config) { * * @template {CompatibleClass} T * @param {T} Component - class of the component to create - * @param {T["defaults"]} [config] - config for the component - * @param {Element|Document} [$scope] - scope of the document to search within + * @param {T["defaults"]} [config] - Config supplied to component + * @param {OnErrorCallback | Element | Document | CreateAllOptions } [createAllOptions] - options for createAll including scope of the document to search within and callback function if error throw by component on init * @returns {Array>} - array of instantiated components */ -function createAll(Component, config, $scope = document) { +function createAll(Component, config, createAllOptions) { + let /** @type {Element | Document} */ $scope = document + let /** @type {OnErrorCallback | undefined} */ onError + + if (typeof createAllOptions === 'object') { + createAllOptions = /** @type {CreateAllOptions} */ ( + // eslint-disable-next-line no-self-assign + createAllOptions + ) + + $scope = createAllOptions.scope ?? $scope + onError = createAllOptions.onError + } + + if (typeof createAllOptions === 'function') { + onError = createAllOptions + } + + if (createAllOptions instanceof HTMLElement) { + $scope = createAllOptions + } + const $elements = $scope.querySelectorAll( `[data-module="${Component.moduleName}"]` ) // Skip initialisation when GOV.UK Frontend is not supported if (!isSupported()) { - console.log(new SupportError()) + if (onError) { + onError(new SupportError(), { + component: Component, + config + }) + } else { + console.log(new SupportError()) + } return [] } @@ -98,7 +126,16 @@ function createAll(Component, config, $scope = document) { ? new Component($element, config) : new Component($element) } catch (error) { - console.log(error) + if (onError && error instanceof Error) { + onError(error, { + element: $element, + component: Component, + config + }) + } else { + console.log(error) + } + return null } }) @@ -152,3 +189,25 @@ export { initAll, createAll } * * @typedef {keyof Config} ConfigKey */ + +/** + * @template {CompatibleClass} T + * @typedef {object} ErrorContext + * @property {Element} [element] - Element used for component module initialisation + * @property {T} component - Class of component + * @property {T["defaults"]} config - Config supplied to component + */ + +/** + * @template {CompatibleClass} T + * @callback OnErrorCallback + * @param {Error} error - Thrown error + * @param {ErrorContext} context - Object containing the element, component class and configuration + */ + +/** + * @template {CompatibleClass} T + * @typedef {object} CreateAllOptions + * @property {Element | Document} [scope] - scope of the document to search within + * @property {OnErrorCallback} [onError] - callback function if error throw by component on init + */ From b4483fabe0492342bbf1040c2985406f6a94e389 Mon Sep 17 00:00:00 2001 From: Patrick Cartlidge Date: Thu, 29 Aug 2024 16:21:25 +0100 Subject: [PATCH 04/45] Add `onError` to `initAll` config - `config` parameter of `initAll` now accepts `onError` callback - `initAll` tests updated for `onError` callback --- .../src/govuk/init.jsdom.test.mjs | 68 +++++++++++++++++++ packages/govuk-frontend/src/govuk/init.mjs | 21 ++++-- 2 files changed, 84 insertions(+), 5 deletions(-) diff --git a/packages/govuk-frontend/src/govuk/init.jsdom.test.mjs b/packages/govuk-frontend/src/govuk/init.jsdom.test.mjs index ebef29c340..03b5cff437 100644 --- a/packages/govuk-frontend/src/govuk/init.jsdom.test.mjs +++ b/packages/govuk-frontend/src/govuk/init.jsdom.test.mjs @@ -105,6 +105,38 @@ describe('initAll', () => { }) ) }) + + it('executes onError if specified', () => { + const errorCallback = jest.fn((error, context) => { + console.log(error) + console.log(context) + }) + + initAll({ + accordion: { + rememberExpanded: true + }, + onError: errorCallback + }) + + expect(errorCallback).toHaveBeenCalled() + + expect(global.console.log).toHaveBeenCalledWith( + expect.objectContaining({ + message: 'GOV.UK Frontend is not supported in this browser' + }) + ) + expect(global.console.log).toHaveBeenCalledWith( + expect.objectContaining({ + config: { + accordion: { + rememberExpanded: true + }, + onError: errorCallback + } + }) + ) + }) }) it('only initialises components within a given scope', () => { @@ -151,6 +183,42 @@ describe('initAll', () => { }) ) }) + + it('executes onError callback on component create if specified', () => { + document.body.classList.add('govuk-frontend-supported') + document.body.innerHTML = '
' + + jest.mocked(GOVUKFrontend.Accordion).mockImplementation(() => { + throw new Error('Error thrown from accordion') + }) + + const errorCallback = jest.fn((error, context) => { + console.log(error) + console.log(context) + }) + + // Silence warnings in test output, and allow us to 'expect' them + jest.spyOn(global.console, 'log').mockImplementation() + + initAll({ + onError: errorCallback, + accordion: { + rememberExpanded: true + } + }) + + expect(global.console.log).toHaveBeenCalledWith( + expect.objectContaining({ + message: 'Error thrown from accordion' + }) + ) + expect(global.console.log).toHaveBeenCalledWith( + expect.objectContaining({ + component: GOVUKFrontend.Accordion, + config: { rememberExpanded: true } + }) + ) + }) }) describe('createAll', () => { diff --git a/packages/govuk-frontend/src/govuk/init.mjs b/packages/govuk-frontend/src/govuk/init.mjs index 171891e5e2..28a20127a5 100644 --- a/packages/govuk-frontend/src/govuk/init.mjs +++ b/packages/govuk-frontend/src/govuk/init.mjs @@ -20,14 +20,20 @@ import { SupportError } from './errors/index.mjs' * Use the `data-module` attributes to find, instantiate and init all of the * components provided as part of GOV.UK Frontend. * - * @param {Config & { scope?: Element }} [config] - Config for all components (with optional scope) + * @param {Config & { scope?: Element, onError?: OnErrorCallback }} [config] - Config for all components (with optional scope) */ function initAll(config) { config = typeof config !== 'undefined' ? config : {} // Skip initialisation when GOV.UK Frontend is not supported if (!isSupported()) { - console.log(new SupportError()) + if (config.onError) { + config.onError(new SupportError(), { + config + }) + } else { + console.log(new SupportError()) + } return } @@ -49,10 +55,15 @@ function initAll(config) { // Allow the user to initialise GOV.UK Frontend in only certain sections of the page // Defaults to the entire document if nothing is set. - const $scope = config.scope ?? document + // const $scope = config.scope ?? document + + const options = { + scope: config.scope ?? document, + onError: config.onError + } components.forEach(([Component, config]) => { - createAll(Component, config, $scope) + createAll(Component, config, options) }) } @@ -194,7 +205,7 @@ export { initAll, createAll } * @template {CompatibleClass} T * @typedef {object} ErrorContext * @property {Element} [element] - Element used for component module initialisation - * @property {T} component - Class of component + * @property {T} [component] - Class of component * @property {T["defaults"]} config - Config supplied to component */ From e82acf076e4105275959168e37764ace63744bdb Mon Sep 17 00:00:00 2001 From: Patrick Cartlidge Date: Wed, 11 Sep 2024 11:48:49 +0100 Subject: [PATCH 05/45] Update tests for `onError` Tests for `onError` now don't test for not calling `console.log` (the default behaviour for error occuring in `createAll` and `initAll`) and now test for the mocked function being called with the correct arguments. --- .../src/govuk/init.jsdom.test.mjs | 54 ++++++++----------- 1 file changed, 21 insertions(+), 33 deletions(-) diff --git a/packages/govuk-frontend/src/govuk/init.jsdom.test.mjs b/packages/govuk-frontend/src/govuk/init.jsdom.test.mjs index 03b5cff437..58462f9458 100644 --- a/packages/govuk-frontend/src/govuk/init.jsdom.test.mjs +++ b/packages/govuk-frontend/src/govuk/init.jsdom.test.mjs @@ -107,10 +107,7 @@ describe('initAll', () => { }) it('executes onError if specified', () => { - const errorCallback = jest.fn((error, context) => { - console.log(error) - console.log(context) - }) + const errorCallback = jest.fn((_error, _context) => {}) initAll({ accordion: { @@ -119,14 +116,12 @@ describe('initAll', () => { onError: errorCallback }) - expect(errorCallback).toHaveBeenCalled() + expect(global.console.log).not.toHaveBeenCalled() - expect(global.console.log).toHaveBeenCalledWith( + expect(errorCallback).toHaveBeenCalledWith( expect.objectContaining({ message: 'GOV.UK Frontend is not supported in this browser' - }) - ) - expect(global.console.log).toHaveBeenCalledWith( + }), expect.objectContaining({ config: { accordion: { @@ -188,17 +183,15 @@ describe('initAll', () => { document.body.classList.add('govuk-frontend-supported') document.body.innerHTML = '
' + const accordionEl = document.querySelector( + "[data-module='govuk-accordion']" + ) + jest.mocked(GOVUKFrontend.Accordion).mockImplementation(() => { throw new Error('Error thrown from accordion') }) - const errorCallback = jest.fn((error, context) => { - console.log(error) - console.log(context) - }) - - // Silence warnings in test output, and allow us to 'expect' them - jest.spyOn(global.console, 'log').mockImplementation() + const errorCallback = jest.fn((_error, _context) => {}) initAll({ onError: errorCallback, @@ -207,15 +200,18 @@ describe('initAll', () => { } }) - expect(global.console.log).toHaveBeenCalledWith( + expect(global.console.log).not.toHaveBeenCalled() + + expect(errorCallback).toHaveBeenCalledWith( expect.objectContaining({ message: 'Error thrown from accordion' - }) - ) - expect(global.console.log).toHaveBeenCalledWith( + }), expect.objectContaining({ component: GOVUKFrontend.Accordion, - config: { rememberExpanded: true } + config: { + rememberExpanded: true + }, + element: accordionEl }) ) }) @@ -273,13 +269,7 @@ describe('createAll', () => { it('executes specified onError callback and returns empty array if not supported', () => { document.body.classList.remove('govuk-frontend-supported') - const errorCallback = jest.fn((error, context) => { - console.log(error) - console.log(context) - }) - - // Silence warnings in test output, and allow us to 'expect' them - jest.spyOn(global.console, 'log').mockImplementation() + const errorCallback = jest.fn((_error, _context) => {}) expect(() => { createAll( @@ -289,14 +279,12 @@ describe('createAll', () => { ) }).not.toThrow() - expect(errorCallback).toHaveBeenCalled() + expect(global.console.log).not.toHaveBeenCalled() - expect(global.console.log).toHaveBeenCalledWith( + expect(errorCallback).toHaveBeenCalledWith( expect.objectContaining({ message: 'GOV.UK Frontend is not supported in this browser' - }) - ) - expect(global.console.log).toHaveBeenCalledWith( + }), expect.objectContaining({ component: MockComponent, config: { attribute: 'random' } From 985c0d7cb776572f2e21d0f5449a4ec3d35cb235 Mon Sep 17 00:00:00 2001 From: Colin Rotherham Date: Tue, 12 Dec 2023 15:43:33 +0000 Subject: [PATCH 06/45] Add `InitError` to throw for components already initialised --- .../src/govuk/errors/index.jsdom.test.mjs | 40 ++++++++++++++++++- .../govuk-frontend/src/govuk/errors/index.mjs | 22 ++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/packages/govuk-frontend/src/govuk/errors/index.jsdom.test.mjs b/packages/govuk-frontend/src/govuk/errors/index.jsdom.test.mjs index 516bf1af91..401d8bde66 100644 --- a/packages/govuk-frontend/src/govuk/errors/index.jsdom.test.mjs +++ b/packages/govuk-frontend/src/govuk/errors/index.jsdom.test.mjs @@ -1,4 +1,9 @@ -import { ElementError, GOVUKFrontendError, SupportError } from './index.mjs' +import { + ElementError, + GOVUKFrontendError, + InitError, + SupportError +} from './index.mjs' describe('errors', () => { describe('GOVUKFrontendError', () => { @@ -47,6 +52,39 @@ describe('errors', () => { }) }) + describe('InitError', () => { + let $element + let $moduleName + + beforeAll(() => { + $element = document.createElement('div') + $element.setAttribute('data-module', 'govuk-accordion') + $moduleName = 'govuk-accordion' + }) + + it('is an instance of GOVUKFrontendError', () => { + expect(new InitError($moduleName)).toBeInstanceOf(GOVUKFrontendError) + }) + + it('has its own name set', () => { + expect(new InitError($moduleName).name).toBe('InitError') + }) + + it('provides feedback for modules already initialised', () => { + expect(new InitError($moduleName).message).toBe( + 'Root element (`$module`) already initialised (`govuk-accordion`)' + ) + }) + + it('provides feedback for modules already initialised', () => { + $moduleName = undefined + + expect(new InitError($moduleName, 'Accordion').message).toBe( + 'moduleName not defined in component (`Accordion`)' + ) + }) + }) + describe('ElementError', () => { it('is an instance of GOVUKFrontendError', () => { expect( diff --git a/packages/govuk-frontend/src/govuk/errors/index.mjs b/packages/govuk-frontend/src/govuk/errors/index.mjs index 6e079d23b4..aa599ba836 100644 --- a/packages/govuk-frontend/src/govuk/errors/index.mjs +++ b/packages/govuk-frontend/src/govuk/errors/index.mjs @@ -97,6 +97,28 @@ export class ElementError extends GOVUKFrontendError { } } +/** + * Indicates that a component is already initialised + */ +export class InitError extends GOVUKFrontendError { + name = 'InitError' + + /** + * @internal + * @param {string|undefined} moduleName - name of the component module + * @param {string} [className] - name of the component module + */ + constructor(moduleName, className) { + let errorText = `moduleName not defined in component (\`${className}\`)` + + if (typeof moduleName === 'string') { + errorText = `Root element (\`$module\`) already initialised (\`${moduleName}\`)` + } + + super(errorText) + } +} + /** * Element error options * From 7ad92cf186884f489d958bc8f2c286ebad0e390a Mon Sep 17 00:00:00 2001 From: Colin Rotherham Date: Mon, 8 Jan 2024 17:46:06 +0000 Subject: [PATCH 07/45] Throw `InitError` when components are already initialised --- .../govuk-frontend/src/govuk/common/index.mjs | 15 +++++++ .../govuk/components/accordion/accordion.mjs | 2 +- .../src/govuk/components/button/button.mjs | 2 +- .../character-count/character-count.mjs | 2 +- .../components/checkboxes/checkboxes.mjs | 2 +- .../error-summary/error-summary.mjs | 2 +- .../exit-this-page/exit-this-page.mjs | 2 +- .../src/govuk/components/header/header.mjs | 2 +- .../notification-banner.mjs | 2 +- .../src/govuk/components/radios/radios.mjs | 2 +- .../govuk/components/skip-link/skip-link.mjs | 2 +- .../src/govuk/components/tabs/tabs.mjs | 2 +- .../src/govuk/govuk-frontend-component.mjs | 42 +++++++++++++++++-- 13 files changed, 65 insertions(+), 14 deletions(-) diff --git a/packages/govuk-frontend/src/govuk/common/index.mjs b/packages/govuk-frontend/src/govuk/common/index.mjs index 60699f4088..68b2f9b2c7 100644 --- a/packages/govuk-frontend/src/govuk/common/index.mjs +++ b/packages/govuk-frontend/src/govuk/common/index.mjs @@ -188,6 +188,21 @@ export function setFocus($element, options = {}) { $element.focus() } +/** + * Checks if component is already initialised + * + * @internal + * @param {Element} $module - HTML element to be checked + * @param {string} moduleName - name of component module + * @returns {boolean} Whether component is already initialised + */ +export function isInitialised($module, moduleName) { + return ( + $module instanceof HTMLElement && + $module.hasAttribute(`data-${moduleName}-init`) + ) +} + /** * Checks if GOV.UK Frontend is supported on this page * diff --git a/packages/govuk-frontend/src/govuk/components/accordion/accordion.mjs b/packages/govuk-frontend/src/govuk/components/accordion/accordion.mjs index d18d179f24..01bef59de7 100644 --- a/packages/govuk-frontend/src/govuk/components/accordion/accordion.mjs +++ b/packages/govuk-frontend/src/govuk/components/accordion/accordion.mjs @@ -114,7 +114,7 @@ export class Accordion extends GOVUKFrontendComponent { * @param {AccordionConfig} [config] - Accordion config */ constructor($module, config = {}) { - super() + super($module) if (!($module instanceof HTMLElement)) { throw new ElementError({ diff --git a/packages/govuk-frontend/src/govuk/components/button/button.mjs b/packages/govuk-frontend/src/govuk/components/button/button.mjs index 7dde40b11e..a0b5fee192 100644 --- a/packages/govuk-frontend/src/govuk/components/button/button.mjs +++ b/packages/govuk-frontend/src/govuk/components/button/button.mjs @@ -31,7 +31,7 @@ export class Button extends GOVUKFrontendComponent { * @param {ButtonConfig} [config] - Button config */ constructor($module, config = {}) { - super() + super($module) if (!($module instanceof HTMLElement)) { throw new ElementError({ diff --git a/packages/govuk-frontend/src/govuk/components/character-count/character-count.mjs b/packages/govuk-frontend/src/govuk/components/character-count/character-count.mjs index 6c434b4dcf..cbd8e9351b 100644 --- a/packages/govuk-frontend/src/govuk/components/character-count/character-count.mjs +++ b/packages/govuk-frontend/src/govuk/components/character-count/character-count.mjs @@ -62,7 +62,7 @@ export class CharacterCount extends GOVUKFrontendComponent { * @param {CharacterCountConfig} [config] - Character count config */ constructor($module, config = {}) { - super() + super($module) if (!($module instanceof HTMLElement)) { throw new ElementError({ diff --git a/packages/govuk-frontend/src/govuk/components/checkboxes/checkboxes.mjs b/packages/govuk-frontend/src/govuk/components/checkboxes/checkboxes.mjs index 1cb074c58e..8314ae217d 100644 --- a/packages/govuk-frontend/src/govuk/components/checkboxes/checkboxes.mjs +++ b/packages/govuk-frontend/src/govuk/components/checkboxes/checkboxes.mjs @@ -28,7 +28,7 @@ export class Checkboxes extends GOVUKFrontendComponent { * @param {Element | null} $module - HTML element to use for checkboxes */ constructor($module) { - super() + super($module) if (!($module instanceof HTMLElement)) { throw new ElementError({ diff --git a/packages/govuk-frontend/src/govuk/components/error-summary/error-summary.mjs b/packages/govuk-frontend/src/govuk/components/error-summary/error-summary.mjs index 51a8b4144f..9707a53af4 100644 --- a/packages/govuk-frontend/src/govuk/components/error-summary/error-summary.mjs +++ b/packages/govuk-frontend/src/govuk/components/error-summary/error-summary.mjs @@ -30,7 +30,7 @@ export class ErrorSummary extends GOVUKFrontendComponent { * @param {ErrorSummaryConfig} [config] - Error summary config */ constructor($module, config = {}) { - super() + super($module) if (!($module instanceof HTMLElement)) { throw new ElementError({ diff --git a/packages/govuk-frontend/src/govuk/components/exit-this-page/exit-this-page.mjs b/packages/govuk-frontend/src/govuk/components/exit-this-page/exit-this-page.mjs index a9983bf60a..d7a9c1cc2c 100644 --- a/packages/govuk-frontend/src/govuk/components/exit-this-page/exit-this-page.mjs +++ b/packages/govuk-frontend/src/govuk/components/exit-this-page/exit-this-page.mjs @@ -79,7 +79,7 @@ export class ExitThisPage extends GOVUKFrontendComponent { * @param {ExitThisPageConfig} [config] - Exit This Page config */ constructor($module, config = {}) { - super() + super($module) if (!($module instanceof HTMLElement)) { throw new ElementError({ diff --git a/packages/govuk-frontend/src/govuk/components/header/header.mjs b/packages/govuk-frontend/src/govuk/components/header/header.mjs index 0e03a309b4..1fee86c8a1 100644 --- a/packages/govuk-frontend/src/govuk/components/header/header.mjs +++ b/packages/govuk-frontend/src/govuk/components/header/header.mjs @@ -43,7 +43,7 @@ export class Header extends GOVUKFrontendComponent { * @param {Element | null} $module - HTML element to use for header */ constructor($module) { - super() + super($module) if (!$module) { throw new ElementError({ diff --git a/packages/govuk-frontend/src/govuk/components/notification-banner/notification-banner.mjs b/packages/govuk-frontend/src/govuk/components/notification-banner/notification-banner.mjs index 089fd0438b..133031332f 100644 --- a/packages/govuk-frontend/src/govuk/components/notification-banner/notification-banner.mjs +++ b/packages/govuk-frontend/src/govuk/components/notification-banner/notification-banner.mjs @@ -23,7 +23,7 @@ export class NotificationBanner extends GOVUKFrontendComponent { * @param {NotificationBannerConfig} [config] - Notification banner config */ constructor($module, config = {}) { - super() + super($module) if (!($module instanceof HTMLElement)) { throw new ElementError({ diff --git a/packages/govuk-frontend/src/govuk/components/radios/radios.mjs b/packages/govuk-frontend/src/govuk/components/radios/radios.mjs index 675d555754..336430f168 100644 --- a/packages/govuk-frontend/src/govuk/components/radios/radios.mjs +++ b/packages/govuk-frontend/src/govuk/components/radios/radios.mjs @@ -28,7 +28,7 @@ export class Radios extends GOVUKFrontendComponent { * @param {Element | null} $module - HTML element to use for radios */ constructor($module) { - super() + super($module) if (!($module instanceof HTMLElement)) { throw new ElementError({ diff --git a/packages/govuk-frontend/src/govuk/components/skip-link/skip-link.mjs b/packages/govuk-frontend/src/govuk/components/skip-link/skip-link.mjs index ea34d387be..2b6d276f57 100644 --- a/packages/govuk-frontend/src/govuk/components/skip-link/skip-link.mjs +++ b/packages/govuk-frontend/src/govuk/components/skip-link/skip-link.mjs @@ -18,7 +18,7 @@ export class SkipLink extends GOVUKFrontendComponent { * @throws {ElementError} when the linked element is missing or the wrong type */ constructor($module) { - super() + super($module) if (!($module instanceof HTMLAnchorElement)) { throw new ElementError({ diff --git a/packages/govuk-frontend/src/govuk/components/tabs/tabs.mjs b/packages/govuk-frontend/src/govuk/components/tabs/tabs.mjs index d5af038949..4effe5b769 100644 --- a/packages/govuk-frontend/src/govuk/components/tabs/tabs.mjs +++ b/packages/govuk-frontend/src/govuk/components/tabs/tabs.mjs @@ -45,7 +45,7 @@ export class Tabs extends GOVUKFrontendComponent { * @param {Element | null} $module - HTML element to use for tabs */ constructor($module) { - super() + super($module) if (!$module) { throw new ElementError({ diff --git a/packages/govuk-frontend/src/govuk/govuk-frontend-component.mjs b/packages/govuk-frontend/src/govuk/govuk-frontend-component.mjs index df99479f31..273d00dbf3 100644 --- a/packages/govuk-frontend/src/govuk/govuk-frontend-component.mjs +++ b/packages/govuk-frontend/src/govuk/govuk-frontend-component.mjs @@ -1,5 +1,5 @@ -import { isSupported } from './common/index.mjs' -import { SupportError } from './errors/index.mjs' +import { isInitialised, isSupported } from './common/index.mjs' +import { InitError, SupportError } from './errors/index.mjs' /** * Base Component class @@ -14,9 +14,36 @@ export class GOVUKFrontendComponent { * Constructs a new component, validating that GOV.UK Frontend is supported * * @internal + * @param {Element | null} [$module] - HTML element to use for component */ - constructor() { + constructor($module) { this.checkSupport() + this.checkInitialised($module) + + const moduleName = /** @type {ChildClassConstructor} */ (this.constructor) + .moduleName + + if (typeof moduleName === 'string') { + moduleName && $module?.setAttribute(`data-${moduleName}-init`, '') + } else { + throw new InitError(moduleName) + } + } + + /** + * Validates whether component is already initialised + * + * @private + * @param {Element | null} [$module] - HTML element to be checked + * @throws {InitError} when component is already initialised + */ + checkInitialised($module) { + const moduleName = /** @type {ChildClassConstructor} */ (this.constructor) + .moduleName + + if ($module && moduleName && isInitialised($module, moduleName)) { + throw new InitError(moduleName) + } } /** @@ -31,3 +58,12 @@ export class GOVUKFrontendComponent { } } } + +/** + * @typedef ChildClass + * @property {string} [moduleName] - The module name that'll be looked for in the DOM when initialising the component + */ + +/** + * @typedef {typeof GOVUKFrontendComponent & ChildClass} ChildClassConstructor + */ From 3da21dd248e4e3dbb246eedfea262ec9e03c0f22 Mon Sep 17 00:00:00 2001 From: Colin Rotherham Date: Tue, 12 Dec 2023 15:47:35 +0000 Subject: [PATCH 08/45] Add tests for initialising components twice --- .../accordion/accordion.puppeteer.test.js | 17 +++++++++++++++++ .../components/button/button.puppeteer.test.js | 17 +++++++++++++++++ .../character-count.puppeteer.test.js | 17 +++++++++++++++++ .../checkboxes/checkboxes.puppeteer.test.js | 17 +++++++++++++++++ .../error-summary.puppeteer.test.js | 17 +++++++++++++++++ .../exit-this-page.puppeteer.test.js | 17 +++++++++++++++++ .../components/header/header.puppeteer.test.js | 17 +++++++++++++++++ .../notification-banner.puppeteer.test.js | 17 +++++++++++++++++ .../components/radios/radios.puppeteer.test.js | 16 ++++++++++++++++ .../skip-link/skip-link.puppeteer.test.js | 17 +++++++++++++++++ .../components/tabs/tabs.puppeteer.test.js | 16 ++++++++++++++++ 11 files changed, 185 insertions(+) diff --git a/packages/govuk-frontend/src/govuk/components/accordion/accordion.puppeteer.test.js b/packages/govuk-frontend/src/govuk/components/accordion/accordion.puppeteer.test.js index d823c5dc09..0a866e2016 100644 --- a/packages/govuk-frontend/src/govuk/components/accordion/accordion.puppeteer.test.js +++ b/packages/govuk-frontend/src/govuk/components/accordion/accordion.puppeteer.test.js @@ -1,3 +1,5 @@ +/* eslint-disable no-new */ + const { goToExample, render, @@ -712,6 +714,21 @@ describe('/components/accordion', () => { }) }) + it('throws when initialised twice', async () => { + await expect( + render(page, 'accordion', examples.default, { + async afterInitialisation($module) { + const { Accordion } = await import('govuk-frontend') + new Accordion($module) + } + }) + ).rejects.toMatchObject({ + name: 'InitError', + message: + 'Root element (`$module`) already initialised (`govuk-accordion`)' + }) + }) + it('throws when $module is not set', async () => { await expect( render(page, 'accordion', examples.default, { diff --git a/packages/govuk-frontend/src/govuk/components/button/button.puppeteer.test.js b/packages/govuk-frontend/src/govuk/components/button/button.puppeteer.test.js index e6b3bf6590..dda57595a9 100644 --- a/packages/govuk-frontend/src/govuk/components/button/button.puppeteer.test.js +++ b/packages/govuk-frontend/src/govuk/components/button/button.puppeteer.test.js @@ -1,3 +1,5 @@ +/* eslint-disable no-new */ + const { render } = require('@govuk-frontend/helpers/puppeteer') const { getExamples } = require('@govuk-frontend/lib/components') @@ -329,6 +331,21 @@ describe('/components/button', () => { }) }) + it('throws when initialised twice', async () => { + await expect( + render(page, 'button', examples.default, { + async afterInitialisation($module) { + const { Button } = await import('govuk-frontend') + new Button($module) + } + }) + ).rejects.toMatchObject({ + name: 'InitError', + message: + 'Root element (`$module`) already initialised (`govuk-button`)' + }) + }) + it('throws when $module is not set', async () => { await expect( render(page, 'button', examples.default, { diff --git a/packages/govuk-frontend/src/govuk/components/character-count/character-count.puppeteer.test.js b/packages/govuk-frontend/src/govuk/components/character-count/character-count.puppeteer.test.js index f6d2c95102..4f6d7fe76a 100644 --- a/packages/govuk-frontend/src/govuk/components/character-count/character-count.puppeteer.test.js +++ b/packages/govuk-frontend/src/govuk/components/character-count/character-count.puppeteer.test.js @@ -1,3 +1,5 @@ +/* eslint-disable no-new */ + const { setTimeout } = require('timers/promises') const { render } = require('@govuk-frontend/helpers/puppeteer') @@ -812,6 +814,21 @@ describe('Character count', () => { }) }) + it('throws when initialised twice', async () => { + await expect( + render(page, 'character-count', examples.default, { + async afterInitialisation($module) { + const { CharacterCount } = await import('govuk-frontend') + new CharacterCount($module) + } + }) + ).rejects.toMatchObject({ + name: 'InitError', + message: + 'Root element (`$module`) already initialised (`govuk-character-count`)' + }) + }) + it('throws when $module is not set', async () => { await expect( render(page, 'character-count', examples.default, { diff --git a/packages/govuk-frontend/src/govuk/components/checkboxes/checkboxes.puppeteer.test.js b/packages/govuk-frontend/src/govuk/components/checkboxes/checkboxes.puppeteer.test.js index fa913c9acf..fedadb067c 100644 --- a/packages/govuk-frontend/src/govuk/components/checkboxes/checkboxes.puppeteer.test.js +++ b/packages/govuk-frontend/src/govuk/components/checkboxes/checkboxes.puppeteer.test.js @@ -1,3 +1,5 @@ +/* eslint-disable no-new */ + const { goToExample, getAttribute, @@ -367,6 +369,21 @@ describe('Checkboxes', () => { }) }) + it('throws when initialised twice', async () => { + await expect( + render(page, 'checkboxes', examples.default, { + async afterInitialisation($module) { + const { Checkboxes } = await import('govuk-frontend') + new Checkboxes($module) + } + }) + ).rejects.toMatchObject({ + name: 'InitError', + message: + 'Root element (`$module`) already initialised (`govuk-checkboxes`)' + }) + }) + it('throws when $module is not set', async () => { await expect( render(page, 'checkboxes', examples.default, { diff --git a/packages/govuk-frontend/src/govuk/components/error-summary/error-summary.puppeteer.test.js b/packages/govuk-frontend/src/govuk/components/error-summary/error-summary.puppeteer.test.js index e77a1c1b27..169d992756 100644 --- a/packages/govuk-frontend/src/govuk/components/error-summary/error-summary.puppeteer.test.js +++ b/packages/govuk-frontend/src/govuk/components/error-summary/error-summary.puppeteer.test.js @@ -1,3 +1,5 @@ +/* eslint-disable no-new */ + const { goToExample, render } = require('@govuk-frontend/helpers/puppeteer') const { getExamples } = require('@govuk-frontend/lib/components') @@ -236,6 +238,21 @@ describe('Error Summary', () => { }) }) + it('throws when initialised twice', async () => { + await expect( + render(page, 'error-summary', examples.default, { + async afterInitialisation($module) { + const { ErrorSummary } = await import('govuk-frontend') + new ErrorSummary($module) + } + }) + ).rejects.toMatchObject({ + name: 'InitError', + message: + 'Root element (`$module`) already initialised (`govuk-error-summary`)' + }) + }) + it('throws when $module is not set', async () => { await expect( render(page, 'error-summary', examples.default, { diff --git a/packages/govuk-frontend/src/govuk/components/exit-this-page/exit-this-page.puppeteer.test.js b/packages/govuk-frontend/src/govuk/components/exit-this-page/exit-this-page.puppeteer.test.js index cdd37cf7c8..452b206428 100644 --- a/packages/govuk-frontend/src/govuk/components/exit-this-page/exit-this-page.puppeteer.test.js +++ b/packages/govuk-frontend/src/govuk/components/exit-this-page/exit-this-page.puppeteer.test.js @@ -1,3 +1,5 @@ +/* eslint-disable no-new */ + const { setTimeout } = require('timers/promises') const { goToExample, render } = require('@govuk-frontend/helpers/puppeteer') @@ -231,6 +233,21 @@ describe('/components/exit-this-page', () => { }) }) + it('throws when initialised twice', async () => { + await expect( + render(page, 'exit-this-page', examples.default, { + async afterInitialisation($module) { + const { ExitThisPage } = await import('govuk-frontend') + new ExitThisPage($module) + } + }) + ).rejects.toMatchObject({ + name: 'InitError', + message: + 'Root element (`$module`) already initialised (`govuk-exit-this-page`)' + }) + }) + it('throws when $module is not set', async () => { await expect( render(page, 'exit-this-page', examples.default, { diff --git a/packages/govuk-frontend/src/govuk/components/header/header.puppeteer.test.js b/packages/govuk-frontend/src/govuk/components/header/header.puppeteer.test.js index fe7c0e3d55..cab35c7285 100644 --- a/packages/govuk-frontend/src/govuk/components/header/header.puppeteer.test.js +++ b/packages/govuk-frontend/src/govuk/components/header/header.puppeteer.test.js @@ -1,3 +1,5 @@ +/* eslint-disable no-new */ + const { render } = require('@govuk-frontend/helpers/puppeteer') const { getExamples } = require('@govuk-frontend/lib/components') const { KnownDevices } = require('puppeteer') @@ -181,6 +183,21 @@ describe('Header navigation', () => { }) }) + it('throws when initialised twice', async () => { + await expect( + render(page, 'header', examples.default, { + async afterInitialisation($module) { + const { Header } = await import('govuk-frontend') + new Header($module) + } + }) + ).rejects.toMatchObject({ + name: 'InitError', + message: + 'Root element (`$module`) already initialised (`govuk-header`)' + }) + }) + it('throws when $module is not set', async () => { await expect( render(page, 'header', examples.default, { diff --git a/packages/govuk-frontend/src/govuk/components/notification-banner/notification-banner.puppeteer.test.js b/packages/govuk-frontend/src/govuk/components/notification-banner/notification-banner.puppeteer.test.js index 344d70ecc1..fd14b1d6bc 100644 --- a/packages/govuk-frontend/src/govuk/components/notification-banner/notification-banner.puppeteer.test.js +++ b/packages/govuk-frontend/src/govuk/components/notification-banner/notification-banner.puppeteer.test.js @@ -1,3 +1,5 @@ +/* eslint-disable no-new */ + const { render } = require('@govuk-frontend/helpers/puppeteer') const { getExamples } = require('@govuk-frontend/lib/components') @@ -236,6 +238,21 @@ describe('Notification banner', () => { }) }) + it('throws when initialised twice', async () => { + await expect( + render(page, 'notification-banner', examples.default, { + async afterInitialisation($module) { + const { NotificationBanner } = await import('govuk-frontend') + new NotificationBanner($module) + } + }) + ).rejects.toMatchObject({ + name: 'InitError', + message: + 'Root element (`$module`) already initialised (`govuk-notification-banner`)' + }) + }) + it('throws when $module is not set', async () => { await expect( render(page, 'notification-banner', examples.default, { diff --git a/packages/govuk-frontend/src/govuk/components/radios/radios.puppeteer.test.js b/packages/govuk-frontend/src/govuk/components/radios/radios.puppeteer.test.js index 6a21385981..19894f62c2 100644 --- a/packages/govuk-frontend/src/govuk/components/radios/radios.puppeteer.test.js +++ b/packages/govuk-frontend/src/govuk/components/radios/radios.puppeteer.test.js @@ -1,3 +1,5 @@ +/* eslint-disable no-new */ + const { goToExample, getProperty, @@ -320,6 +322,20 @@ describe('Radios', () => { }) }) + it('throws when initialised twice', async () => { + await expect( + render(page, 'radios', examples.default, { + async afterInitialisation($module) { + const { Radios } = await import('govuk-frontend') + new Radios($module) + } + }) + ).rejects.toMatchObject({ + name: 'InitError', + message: 'Root element (`$module`) already initialised (`govuk-radios`)' + }) + }) + it('throws when $module is not set', async () => { await expect( render(page, 'radios', examples.default, { diff --git a/packages/govuk-frontend/src/govuk/components/skip-link/skip-link.puppeteer.test.js b/packages/govuk-frontend/src/govuk/components/skip-link/skip-link.puppeteer.test.js index 26c30818b7..2f7764025a 100644 --- a/packages/govuk-frontend/src/govuk/components/skip-link/skip-link.puppeteer.test.js +++ b/packages/govuk-frontend/src/govuk/components/skip-link/skip-link.puppeteer.test.js @@ -1,3 +1,5 @@ +/* eslint-disable no-new */ + const { render } = require('@govuk-frontend/helpers/puppeteer') const { getExamples } = require('@govuk-frontend/lib/components') @@ -127,6 +129,21 @@ describe('Skip Link', () => { }) }) + it('throws when initialised twice', async () => { + await expect( + render(page, 'skip-link', examples.default, { + async afterInitialisation($module) { + const { SkipLink } = await import('govuk-frontend') + new SkipLink($module) + } + }) + ).rejects.toMatchObject({ + name: 'InitError', + message: + 'Root element (`$module`) already initialised (`govuk-skip-link`)' + }) + }) + it('throws when $module is not set', async () => { return expect( render(page, 'skip-link', examples.default, { diff --git a/packages/govuk-frontend/src/govuk/components/tabs/tabs.puppeteer.test.js b/packages/govuk-frontend/src/govuk/components/tabs/tabs.puppeteer.test.js index a24982d4a6..6bef0229b5 100644 --- a/packages/govuk-frontend/src/govuk/components/tabs/tabs.puppeteer.test.js +++ b/packages/govuk-frontend/src/govuk/components/tabs/tabs.puppeteer.test.js @@ -1,3 +1,5 @@ +/* eslint-disable no-new */ + const { render } = require('@govuk-frontend/helpers/puppeteer') const { getExamples } = require('@govuk-frontend/lib/components') const { KnownDevices } = require('puppeteer') @@ -269,6 +271,20 @@ describe('/components/tabs', () => { }) }) + it('throws when initialised twice', async () => { + await expect( + render(page, 'tabs', examples.default, { + async afterInitialisation($module) { + const { Tabs } = await import('govuk-frontend') + new Tabs($module) + } + }) + ).rejects.toMatchObject({ + name: 'InitError', + message: 'Root element (`$module`) already initialised (`govuk-tabs`)' + }) + }) + it('throws when $module is not set', async () => { await expect( render(page, 'tabs', examples.default, { From ee7513df43a43d177179cceb53034eff65d438a4 Mon Sep 17 00:00:00 2001 From: Romaric Pascal Date: Mon, 16 Sep 2024 16:46:03 +0100 Subject: [PATCH 09/45] Encapsulate check for components support in method of `GOVUKFrontendComponent` This will allow subclasses to override when components are supported, possibly combining it with when GOV.UK Frontend itself is supported --- .../govuk-frontend-component.jsdom.test.mjs | 44 +++++++++++++++++++ .../src/govuk/govuk-frontend-component.mjs | 16 +++++-- 2 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 packages/govuk-frontend/src/govuk/govuk-frontend-component.jsdom.test.mjs diff --git a/packages/govuk-frontend/src/govuk/govuk-frontend-component.jsdom.test.mjs b/packages/govuk-frontend/src/govuk/govuk-frontend-component.jsdom.test.mjs new file mode 100644 index 0000000000..7a63bc133f --- /dev/null +++ b/packages/govuk-frontend/src/govuk/govuk-frontend-component.jsdom.test.mjs @@ -0,0 +1,44 @@ +import { SupportError } from './errors/index.mjs' +import { GOVUKFrontendComponent } from './govuk-frontend-component.mjs' + +describe('GOVUKFrontendComponent', () => { + describe('isSupported()', () => { + beforeEach(() => { + // Jest does not tidy the JSDOM document between tests + // so we need to take care of that ourselves + document.documentElement.innerHTML = '' + }) + + describe('default implementation', () => { + class ServiceComponent extends GOVUKFrontendComponent { + static moduleName = 'app-service-component' + } + + it('Makes initialisation throw if GOV.UK Frontend is not supported', () => { + expect(() => new ServiceComponent(document.body)).toThrow(SupportError) + }) + + it('Allows initialisation if GOV.UK Frontend is supported', () => { + document.body.classList.add('govuk-frontend-supported') + + expect(() => new ServiceComponent(document.body)).not.toThrow() + }) + }) + + describe('when overriden', () => { + it('Allows child classes to define their own condition for support', () => { + class ServiceComponent extends GOVUKFrontendComponent { + static moduleName = 'app-service-component' + + isSupported() { + return true + } + } + + expect(() => new ServiceComponent(document.body)).not.toThrow( + SupportError + ) + }) + }) + }) +}) diff --git a/packages/govuk-frontend/src/govuk/govuk-frontend-component.mjs b/packages/govuk-frontend/src/govuk/govuk-frontend-component.mjs index 273d00dbf3..7f0cd7ffdd 100644 --- a/packages/govuk-frontend/src/govuk/govuk-frontend-component.mjs +++ b/packages/govuk-frontend/src/govuk/govuk-frontend-component.mjs @@ -47,16 +47,26 @@ export class GOVUKFrontendComponent { } /** - * Validates whether GOV.UK Frontend is supported + * Validates whether components are supported * * @private - * @throws {SupportError} when GOV.UK Frontend is not supported + * @throws {SupportError} when the components are not supported */ checkSupport() { - if (!isSupported()) { + if (!this.isSupported()) { throw new SupportError() } } + + /** + * Defines whether the components are supported + * + * @protected + * @returns {boolean} whether the components are supported + */ + isSupported() { + return isSupported() + } } /** From fc50ec84d3dbf3e4d4ab204f35f6eac6c7b0bf00 Mon Sep 17 00:00:00 2001 From: Romaric Pascal Date: Fri, 20 Sep 2024 10:47:45 +0100 Subject: [PATCH 10/45] Rename `$module` to `$root` in common helpers and errors --- packages/govuk-frontend/src/govuk/common/index.mjs | 8 ++++---- .../components/accordion/accordion.puppeteer.test.js | 2 +- .../src/govuk/components/button/button.puppeteer.test.js | 3 +-- .../character-count/character-count.puppeteer.test.js | 2 +- .../components/checkboxes/checkboxes.puppeteer.test.js | 2 +- .../error-summary/error-summary.puppeteer.test.js | 2 +- .../exit-this-page/exit-this-page.puppeteer.test.js | 2 +- .../src/govuk/components/header/header.puppeteer.test.js | 3 +-- .../notification-banner.puppeteer.test.js | 2 +- .../src/govuk/components/radios/radios.puppeteer.test.js | 2 +- .../components/skip-link/skip-link.puppeteer.test.js | 2 +- .../src/govuk/components/tabs/tabs.puppeteer.test.js | 2 +- .../govuk-frontend/src/govuk/errors/index.jsdom.test.mjs | 2 +- packages/govuk-frontend/src/govuk/errors/index.mjs | 2 +- 14 files changed, 17 insertions(+), 19 deletions(-) diff --git a/packages/govuk-frontend/src/govuk/common/index.mjs b/packages/govuk-frontend/src/govuk/common/index.mjs index 68b2f9b2c7..3b5440283d 100644 --- a/packages/govuk-frontend/src/govuk/common/index.mjs +++ b/packages/govuk-frontend/src/govuk/common/index.mjs @@ -192,14 +192,14 @@ export function setFocus($element, options = {}) { * Checks if component is already initialised * * @internal - * @param {Element} $module - HTML element to be checked + * @param {Element} $root - HTML element to be checked * @param {string} moduleName - name of component module * @returns {boolean} Whether component is already initialised */ -export function isInitialised($module, moduleName) { +export function isInitialised($root, moduleName) { return ( - $module instanceof HTMLElement && - $module.hasAttribute(`data-${moduleName}-init`) + $root instanceof HTMLElement && + $root.hasAttribute(`data-${moduleName}-init`) ) } diff --git a/packages/govuk-frontend/src/govuk/components/accordion/accordion.puppeteer.test.js b/packages/govuk-frontend/src/govuk/components/accordion/accordion.puppeteer.test.js index 0a866e2016..3d74e9dbe6 100644 --- a/packages/govuk-frontend/src/govuk/components/accordion/accordion.puppeteer.test.js +++ b/packages/govuk-frontend/src/govuk/components/accordion/accordion.puppeteer.test.js @@ -725,7 +725,7 @@ describe('/components/accordion', () => { ).rejects.toMatchObject({ name: 'InitError', message: - 'Root element (`$module`) already initialised (`govuk-accordion`)' + 'Root element (`$root`) already initialised (`govuk-accordion`)' }) }) diff --git a/packages/govuk-frontend/src/govuk/components/button/button.puppeteer.test.js b/packages/govuk-frontend/src/govuk/components/button/button.puppeteer.test.js index dda57595a9..05efdae8fe 100644 --- a/packages/govuk-frontend/src/govuk/components/button/button.puppeteer.test.js +++ b/packages/govuk-frontend/src/govuk/components/button/button.puppeteer.test.js @@ -341,8 +341,7 @@ describe('/components/button', () => { }) ).rejects.toMatchObject({ name: 'InitError', - message: - 'Root element (`$module`) already initialised (`govuk-button`)' + message: 'Root element (`$root`) already initialised (`govuk-button`)' }) }) diff --git a/packages/govuk-frontend/src/govuk/components/character-count/character-count.puppeteer.test.js b/packages/govuk-frontend/src/govuk/components/character-count/character-count.puppeteer.test.js index 4f6d7fe76a..a708297234 100644 --- a/packages/govuk-frontend/src/govuk/components/character-count/character-count.puppeteer.test.js +++ b/packages/govuk-frontend/src/govuk/components/character-count/character-count.puppeteer.test.js @@ -825,7 +825,7 @@ describe('Character count', () => { ).rejects.toMatchObject({ name: 'InitError', message: - 'Root element (`$module`) already initialised (`govuk-character-count`)' + 'Root element (`$root`) already initialised (`govuk-character-count`)' }) }) diff --git a/packages/govuk-frontend/src/govuk/components/checkboxes/checkboxes.puppeteer.test.js b/packages/govuk-frontend/src/govuk/components/checkboxes/checkboxes.puppeteer.test.js index fedadb067c..b4fd79e43a 100644 --- a/packages/govuk-frontend/src/govuk/components/checkboxes/checkboxes.puppeteer.test.js +++ b/packages/govuk-frontend/src/govuk/components/checkboxes/checkboxes.puppeteer.test.js @@ -380,7 +380,7 @@ describe('Checkboxes', () => { ).rejects.toMatchObject({ name: 'InitError', message: - 'Root element (`$module`) already initialised (`govuk-checkboxes`)' + 'Root element (`$root`) already initialised (`govuk-checkboxes`)' }) }) diff --git a/packages/govuk-frontend/src/govuk/components/error-summary/error-summary.puppeteer.test.js b/packages/govuk-frontend/src/govuk/components/error-summary/error-summary.puppeteer.test.js index 169d992756..337df3e8ae 100644 --- a/packages/govuk-frontend/src/govuk/components/error-summary/error-summary.puppeteer.test.js +++ b/packages/govuk-frontend/src/govuk/components/error-summary/error-summary.puppeteer.test.js @@ -249,7 +249,7 @@ describe('Error Summary', () => { ).rejects.toMatchObject({ name: 'InitError', message: - 'Root element (`$module`) already initialised (`govuk-error-summary`)' + 'Root element (`$root`) already initialised (`govuk-error-summary`)' }) }) diff --git a/packages/govuk-frontend/src/govuk/components/exit-this-page/exit-this-page.puppeteer.test.js b/packages/govuk-frontend/src/govuk/components/exit-this-page/exit-this-page.puppeteer.test.js index 452b206428..adc6c7be3c 100644 --- a/packages/govuk-frontend/src/govuk/components/exit-this-page/exit-this-page.puppeteer.test.js +++ b/packages/govuk-frontend/src/govuk/components/exit-this-page/exit-this-page.puppeteer.test.js @@ -244,7 +244,7 @@ describe('/components/exit-this-page', () => { ).rejects.toMatchObject({ name: 'InitError', message: - 'Root element (`$module`) already initialised (`govuk-exit-this-page`)' + 'Root element (`$root`) already initialised (`govuk-exit-this-page`)' }) }) diff --git a/packages/govuk-frontend/src/govuk/components/header/header.puppeteer.test.js b/packages/govuk-frontend/src/govuk/components/header/header.puppeteer.test.js index cab35c7285..70a5e3ac96 100644 --- a/packages/govuk-frontend/src/govuk/components/header/header.puppeteer.test.js +++ b/packages/govuk-frontend/src/govuk/components/header/header.puppeteer.test.js @@ -193,8 +193,7 @@ describe('Header navigation', () => { }) ).rejects.toMatchObject({ name: 'InitError', - message: - 'Root element (`$module`) already initialised (`govuk-header`)' + message: 'Root element (`$root`) already initialised (`govuk-header`)' }) }) diff --git a/packages/govuk-frontend/src/govuk/components/notification-banner/notification-banner.puppeteer.test.js b/packages/govuk-frontend/src/govuk/components/notification-banner/notification-banner.puppeteer.test.js index fd14b1d6bc..f923002635 100644 --- a/packages/govuk-frontend/src/govuk/components/notification-banner/notification-banner.puppeteer.test.js +++ b/packages/govuk-frontend/src/govuk/components/notification-banner/notification-banner.puppeteer.test.js @@ -249,7 +249,7 @@ describe('Notification banner', () => { ).rejects.toMatchObject({ name: 'InitError', message: - 'Root element (`$module`) already initialised (`govuk-notification-banner`)' + 'Root element (`$root`) already initialised (`govuk-notification-banner`)' }) }) diff --git a/packages/govuk-frontend/src/govuk/components/radios/radios.puppeteer.test.js b/packages/govuk-frontend/src/govuk/components/radios/radios.puppeteer.test.js index 19894f62c2..ddaf7b716a 100644 --- a/packages/govuk-frontend/src/govuk/components/radios/radios.puppeteer.test.js +++ b/packages/govuk-frontend/src/govuk/components/radios/radios.puppeteer.test.js @@ -332,7 +332,7 @@ describe('Radios', () => { }) ).rejects.toMatchObject({ name: 'InitError', - message: 'Root element (`$module`) already initialised (`govuk-radios`)' + message: 'Root element (`$root`) already initialised (`govuk-radios`)' }) }) diff --git a/packages/govuk-frontend/src/govuk/components/skip-link/skip-link.puppeteer.test.js b/packages/govuk-frontend/src/govuk/components/skip-link/skip-link.puppeteer.test.js index 2f7764025a..b0243e1f4a 100644 --- a/packages/govuk-frontend/src/govuk/components/skip-link/skip-link.puppeteer.test.js +++ b/packages/govuk-frontend/src/govuk/components/skip-link/skip-link.puppeteer.test.js @@ -140,7 +140,7 @@ describe('Skip Link', () => { ).rejects.toMatchObject({ name: 'InitError', message: - 'Root element (`$module`) already initialised (`govuk-skip-link`)' + 'Root element (`$root`) already initialised (`govuk-skip-link`)' }) }) diff --git a/packages/govuk-frontend/src/govuk/components/tabs/tabs.puppeteer.test.js b/packages/govuk-frontend/src/govuk/components/tabs/tabs.puppeteer.test.js index 6bef0229b5..6ef8ee7d4e 100644 --- a/packages/govuk-frontend/src/govuk/components/tabs/tabs.puppeteer.test.js +++ b/packages/govuk-frontend/src/govuk/components/tabs/tabs.puppeteer.test.js @@ -281,7 +281,7 @@ describe('/components/tabs', () => { }) ).rejects.toMatchObject({ name: 'InitError', - message: 'Root element (`$module`) already initialised (`govuk-tabs`)' + message: 'Root element (`$root`) already initialised (`govuk-tabs`)' }) }) diff --git a/packages/govuk-frontend/src/govuk/errors/index.jsdom.test.mjs b/packages/govuk-frontend/src/govuk/errors/index.jsdom.test.mjs index 401d8bde66..2810c704b7 100644 --- a/packages/govuk-frontend/src/govuk/errors/index.jsdom.test.mjs +++ b/packages/govuk-frontend/src/govuk/errors/index.jsdom.test.mjs @@ -72,7 +72,7 @@ describe('errors', () => { it('provides feedback for modules already initialised', () => { expect(new InitError($moduleName).message).toBe( - 'Root element (`$module`) already initialised (`govuk-accordion`)' + 'Root element (`$root`) already initialised (`govuk-accordion`)' ) }) diff --git a/packages/govuk-frontend/src/govuk/errors/index.mjs b/packages/govuk-frontend/src/govuk/errors/index.mjs index aa599ba836..f4fda78efd 100644 --- a/packages/govuk-frontend/src/govuk/errors/index.mjs +++ b/packages/govuk-frontend/src/govuk/errors/index.mjs @@ -112,7 +112,7 @@ export class InitError extends GOVUKFrontendError { let errorText = `moduleName not defined in component (\`${className}\`)` if (typeof moduleName === 'string') { - errorText = `Root element (\`$module\`) already initialised (\`${moduleName}\`)` + errorText = `Root element (\`$root\`) already initialised (\`${moduleName}\`)` } super(errorText) From b88cbfc42b74f34e21ccb4e278bec82656edc111 Mon Sep 17 00:00:00 2001 From: Romaric Pascal Date: Fri, 20 Sep 2024 11:04:19 +0100 Subject: [PATCH 11/45] Rename `$module` to `$root` in Accordion --- .../govuk/components/accordion/accordion.mjs | 24 +++++------ .../accordion/accordion.puppeteer.test.js | 40 +++++++++---------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/packages/govuk-frontend/src/govuk/components/accordion/accordion.mjs b/packages/govuk-frontend/src/govuk/components/accordion/accordion.mjs index 01bef59de7..a98441f0b3 100644 --- a/packages/govuk-frontend/src/govuk/components/accordion/accordion.mjs +++ b/packages/govuk-frontend/src/govuk/components/accordion/accordion.mjs @@ -20,7 +20,7 @@ import { I18n } from '../../i18n.mjs' */ export class Accordion extends GOVUKFrontendComponent { /** @private */ - $module + $root /** * @private @@ -110,31 +110,31 @@ export class Accordion extends GOVUKFrontendComponent { $showAllText = null /** - * @param {Element | null} $module - HTML element to use for accordion + * @param {Element | null} $root - HTML element to use for accordion * @param {AccordionConfig} [config] - Accordion config */ - constructor($module, config = {}) { - super($module) + constructor($root, config = {}) { + super($root) - if (!($module instanceof HTMLElement)) { + if (!($root instanceof HTMLElement)) { throw new ElementError({ componentName: 'Accordion', - element: $module, - identifier: 'Root element (`$module`)' + element: $root, + identifier: 'Root element (`$root`)' }) } - this.$module = $module + this.$root = $root this.config = mergeConfigs( Accordion.defaults, config, - normaliseDataset(Accordion, $module.dataset) + normaliseDataset(Accordion, $root.dataset) ) this.i18n = new I18n(this.config.i18n) - const $sections = this.$module.querySelectorAll(`.${this.sectionClass}`) + const $sections = this.$root.querySelectorAll(`.${this.sectionClass}`) if (!$sections.length) { throw new ElementError({ componentName: 'Accordion', @@ -171,7 +171,7 @@ export class Accordion extends GOVUKFrontendComponent { const $accordionControls = document.createElement('div') $accordionControls.setAttribute('class', this.controlsClass) $accordionControls.appendChild(this.$showAllButton) - this.$module.insertBefore($accordionControls, this.$module.firstChild) + this.$root.insertBefore($accordionControls, this.$root.firstChild) // Build additional wrapper for Show all toggle text and place after icon this.$showAllText = document.createElement('span') @@ -251,7 +251,7 @@ export class Accordion extends GOVUKFrontendComponent { $button.setAttribute('type', 'button') $button.setAttribute( 'aria-controls', - `${this.$module.id}-content-${index + 1}` + `${this.$root.id}-content-${index + 1}` ) // Copy all attributes from $span to $button (except `id`, which gets added diff --git a/packages/govuk-frontend/src/govuk/components/accordion/accordion.puppeteer.test.js b/packages/govuk-frontend/src/govuk/components/accordion/accordion.puppeteer.test.js index 3d74e9dbe6..8d51ed2c41 100644 --- a/packages/govuk-frontend/src/govuk/components/accordion/accordion.puppeteer.test.js +++ b/packages/govuk-frontend/src/govuk/components/accordion/accordion.puppeteer.test.js @@ -717,9 +717,9 @@ describe('/components/accordion', () => { it('throws when initialised twice', async () => { await expect( render(page, 'accordion', examples.default, { - async afterInitialisation($module) { + async afterInitialisation($root) { const { Accordion } = await import('govuk-frontend') - new Accordion($module) + new Accordion($root) } }) ).rejects.toMatchObject({ @@ -729,34 +729,34 @@ describe('/components/accordion', () => { }) }) - it('throws when $module is not set', async () => { + it('throws when $root is not set', async () => { await expect( render(page, 'accordion', examples.default, { - beforeInitialisation($module) { - $module.remove() + beforeInitialisation($root) { + $root.remove() } }) ).rejects.toMatchObject({ cause: { name: 'ElementError', - message: 'Accordion: Root element (`$module`) not found' + message: 'Accordion: Root element (`$root`) not found' } }) }) - it('throws when receiving the wrong type for $module', async () => { + it('throws when receiving the wrong type for $root', async () => { await expect( render(page, 'accordion', examples.default, { - beforeInitialisation($module) { + beforeInitialisation($root) { // Replace with an `` element which is not an `HTMLElement` in the DOM (but an `SVGElement`) - $module.outerHTML = `` + $root.outerHTML = `` } }) ).rejects.toMatchObject({ cause: { name: 'ElementError', message: - 'Accordion: Root element (`$module`) is not of type HTMLElement' + 'Accordion: Root element (`$root`) is not of type HTMLElement' } }) }) @@ -764,8 +764,8 @@ describe('/components/accordion', () => { it('throws when the accordion sections are missing', async () => { await expect( render(page, 'accordion', examples.default, { - beforeInitialisation($module, { selector }) { - $module + beforeInitialisation($root, { selector }) { + $root .querySelectorAll(selector) .forEach((item) => item.remove()) }, @@ -785,8 +785,8 @@ describe('/components/accordion', () => { it('throws when section header is missing', async () => { await expect( render(page, 'accordion', examples.default, { - beforeInitialisation($module, { selector }) { - $module + beforeInitialisation($root, { selector }) { + $root .querySelectorAll(selector) .forEach((item) => item.remove()) }, @@ -806,8 +806,8 @@ describe('/components/accordion', () => { it('throws when any section heading is missing', async () => { await expect( render(page, 'accordion', examples.default, { - beforeInitialisation($module, { selector }) { - $module.querySelector(selector).remove() + beforeInitialisation($root, { selector }) { + $root.querySelector(selector).remove() }, context: { selector: '.govuk-accordion__section-heading' @@ -825,8 +825,8 @@ describe('/components/accordion', () => { it('throws when any section button placeholder span is missing', async () => { await expect( render(page, 'accordion', examples.default, { - beforeInitialisation($module, { selector }) { - $module.querySelector(selector).remove() + beforeInitialisation($root, { selector }) { + $root.querySelector(selector).remove() }, context: { selector: '.govuk-accordion__section-button' @@ -844,8 +844,8 @@ describe('/components/accordion', () => { it('throws when any section content is missing', async () => { await expect( render(page, 'accordion', examples.default, { - beforeInitialisation($module, { selector }) { - $module.querySelector(selector).remove() + beforeInitialisation($root, { selector }) { + $root.querySelector(selector).remove() }, context: { selector: '.govuk-accordion__section-content' From 423514fe75525a142b1d263fe2c67941e1b2ae1d Mon Sep 17 00:00:00 2001 From: Romaric Pascal Date: Fri, 20 Sep 2024 11:06:05 +0100 Subject: [PATCH 12/45] Rename `$module` to `$root` in Button --- .../src/govuk/components/button/button.mjs | 24 +++++++++---------- .../button/button.puppeteer.test.js | 21 ++++++++-------- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/packages/govuk-frontend/src/govuk/components/button/button.mjs b/packages/govuk-frontend/src/govuk/components/button/button.mjs index a0b5fee192..a43d60d17b 100644 --- a/packages/govuk-frontend/src/govuk/components/button/button.mjs +++ b/packages/govuk-frontend/src/govuk/components/button/button.mjs @@ -12,7 +12,7 @@ const DEBOUNCE_TIMEOUT_IN_SECONDS = 1 */ export class Button extends GOVUKFrontendComponent { /** @private */ - $module + $root /** * @private @@ -27,32 +27,30 @@ export class Button extends GOVUKFrontendComponent { debounceFormSubmitTimer = null /** - * @param {Element | null} $module - HTML element to use for button + * @param {Element | null} $root - HTML element to use for button * @param {ButtonConfig} [config] - Button config */ - constructor($module, config = {}) { - super($module) + constructor($root, config = {}) { + super($root) - if (!($module instanceof HTMLElement)) { + if (!($root instanceof HTMLElement)) { throw new ElementError({ componentName: 'Button', - element: $module, - identifier: 'Root element (`$module`)' + element: $root, + identifier: 'Root element (`$root`)' }) } - this.$module = $module + this.$root = $root this.config = mergeConfigs( Button.defaults, config, - normaliseDataset(Button, $module.dataset) + normaliseDataset(Button, $root.dataset) ) - this.$module.addEventListener('keydown', (event) => - this.handleKeyDown(event) - ) - this.$module.addEventListener('click', (event) => this.debounce(event)) + this.$root.addEventListener('keydown', (event) => this.handleKeyDown(event)) + this.$root.addEventListener('click', (event) => this.debounce(event)) } /** diff --git a/packages/govuk-frontend/src/govuk/components/button/button.puppeteer.test.js b/packages/govuk-frontend/src/govuk/components/button/button.puppeteer.test.js index 05efdae8fe..80f29328dc 100644 --- a/packages/govuk-frontend/src/govuk/components/button/button.puppeteer.test.js +++ b/packages/govuk-frontend/src/govuk/components/button/button.puppeteer.test.js @@ -334,9 +334,9 @@ describe('/components/button', () => { it('throws when initialised twice', async () => { await expect( render(page, 'button', examples.default, { - async afterInitialisation($module) { + async afterInitialisation($root) { const { Button } = await import('govuk-frontend') - new Button($module) + new Button($root) } }) ).rejects.toMatchObject({ @@ -345,34 +345,33 @@ describe('/components/button', () => { }) }) - it('throws when $module is not set', async () => { + it('throws when $root is not set', async () => { await expect( render(page, 'button', examples.default, { - beforeInitialisation($module) { - $module.remove() + beforeInitialisation($root) { + $root.remove() } }) ).rejects.toMatchObject({ cause: { name: 'ElementError', - message: 'Button: Root element (`$module`) not found' + message: 'Button: Root element (`$root`) not found' } }) }) - it('throws when receiving the wrong type for $module', async () => { + it('throws when receiving the wrong type for $root', async () => { await expect( render(page, 'button', examples.default, { - beforeInitialisation($module) { + beforeInitialisation($root) { // Replace with an `` element which is not an `HTMLElement` in the DOM (but an `SVGElement`) - $module.outerHTML = `` + $root.outerHTML = `` } }) ).rejects.toMatchObject({ cause: { name: 'ElementError', - message: - 'Button: Root element (`$module`) is not of type HTMLElement' + message: 'Button: Root element (`$root`) is not of type HTMLElement' } }) }) From 82df8d473e78fc4943cc8d872538aa15ce2ef54d Mon Sep 17 00:00:00 2001 From: Romaric Pascal Date: Fri, 20 Sep 2024 11:08:19 +0100 Subject: [PATCH 13/45] Rename `$module` to `$root` in Character Count --- .../character-count/character-count.mjs | 22 ++++++------ .../character-count.puppeteer.test.js | 36 +++++++++---------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/packages/govuk-frontend/src/govuk/components/character-count/character-count.mjs b/packages/govuk-frontend/src/govuk/components/character-count/character-count.mjs index cbd8e9351b..a3a0795847 100644 --- a/packages/govuk-frontend/src/govuk/components/character-count/character-count.mjs +++ b/packages/govuk-frontend/src/govuk/components/character-count/character-count.mjs @@ -19,7 +19,7 @@ import { I18n } from '../../i18n.mjs' */ export class CharacterCount extends GOVUKFrontendComponent { /** @private */ - $module + $root /** @private */ $textarea @@ -58,21 +58,21 @@ export class CharacterCount extends GOVUKFrontendComponent { maxLength /** - * @param {Element | null} $module - HTML element to use for character count + * @param {Element | null} $root - HTML element to use for character count * @param {CharacterCountConfig} [config] - Character count config */ - constructor($module, config = {}) { - super($module) + constructor($root, config = {}) { + super($root) - if (!($module instanceof HTMLElement)) { + if (!($root instanceof HTMLElement)) { throw new ElementError({ componentName: 'Character count', - element: $module, - identifier: 'Root element (`$module`)' + element: $root, + identifier: 'Root element (`$root`)' }) } - const $textarea = $module.querySelector('.govuk-js-character-count') + const $textarea = $root.querySelector('.govuk-js-character-count') if ( !( $textarea instanceof HTMLTextAreaElement || @@ -88,7 +88,7 @@ export class CharacterCount extends GOVUKFrontendComponent { } // Read config set using dataset ('data-' values) - const datasetConfig = normaliseDataset(CharacterCount, $module.dataset) + const datasetConfig = normaliseDataset(CharacterCount, $root.dataset) // To ensure data-attributes take complete precedence, even if they change // the type of count, we need to reset the `maxlength` and `maxwords` from @@ -120,13 +120,13 @@ export class CharacterCount extends GOVUKFrontendComponent { this.i18n = new I18n(this.config.i18n, { // Read the fallback if necessary rather than have it set in the defaults - locale: closestAttributeValue($module, 'lang') + locale: closestAttributeValue($root, 'lang') }) // Determine the limit attribute (characters or words) this.maxLength = this.config.maxwords ?? this.config.maxlength ?? Infinity - this.$module = $module + this.$root = $root this.$textarea = $textarea const textareaDescriptionId = `${this.$textarea.id}-info` diff --git a/packages/govuk-frontend/src/govuk/components/character-count/character-count.puppeteer.test.js b/packages/govuk-frontend/src/govuk/components/character-count/character-count.puppeteer.test.js index a708297234..d864a591b2 100644 --- a/packages/govuk-frontend/src/govuk/components/character-count/character-count.puppeteer.test.js +++ b/packages/govuk-frontend/src/govuk/components/character-count/character-count.puppeteer.test.js @@ -817,9 +817,9 @@ describe('Character count', () => { it('throws when initialised twice', async () => { await expect( render(page, 'character-count', examples.default, { - async afterInitialisation($module) { + async afterInitialisation($root) { const { CharacterCount } = await import('govuk-frontend') - new CharacterCount($module) + new CharacterCount($root) } }) ).rejects.toMatchObject({ @@ -829,34 +829,34 @@ describe('Character count', () => { }) }) - it('throws when $module is not set', async () => { + it('throws when $root is not set', async () => { await expect( render(page, 'character-count', examples.default, { - beforeInitialisation($module) { - $module.remove() + beforeInitialisation($root) { + $root.remove() } }) ).rejects.toMatchObject({ cause: { name: 'ElementError', - message: 'Character count: Root element (`$module`) not found' + message: 'Character count: Root element (`$root`) not found' } }) }) - it('throws when receiving the wrong type for $module', async () => { + it('throws when receiving the wrong type for $root', async () => { await expect( render(page, 'character-count', examples.default, { - beforeInitialisation($module) { + beforeInitialisation($root) { // Replace with an `` element which is not an `HTMLElement` in the DOM (but an `SVGElement`) - $module.outerHTML = `` + $root.outerHTML = `` } }) ).rejects.toMatchObject({ cause: { name: 'ElementError', message: - 'Character count: Root element (`$module`) is not of type HTMLElement' + 'Character count: Root element (`$root`) is not of type HTMLElement' } }) }) @@ -864,8 +864,8 @@ describe('Character count', () => { it('throws when the textarea is missing', async () => { await expect( render(page, 'character-count', examples.default, { - beforeInitialisation($module, { selector }) { - $module.querySelector(selector).remove() + beforeInitialisation($root, { selector }) { + $root.querySelector(selector).remove() }, context: { selector: '.govuk-js-character-count' @@ -883,9 +883,9 @@ describe('Character count', () => { it('throws when the textarea is not the right type', async () => { await expect( render(page, 'character-count', examples.default, { - beforeInitialisation($module, { selector }) { + beforeInitialisation($root, { selector }) { // Replace with a tag that's neither an `
' }, context: { @@ -904,8 +904,8 @@ describe('Character count', () => { it('throws when the textarea description is missing', async () => { await expect( render(page, 'character-count', examples.default, { - beforeInitialisation($module, { selector }) { - $module.querySelector(selector).remove() + beforeInitialisation($root, { selector }) { + $root.querySelector(selector).remove() }, context: { selector: '#more-detail-info' @@ -949,14 +949,14 @@ describe('Character count', () => { // Override maxlength to 10 maxlength: 10 }, - beforeInitialisation($module) { + beforeInitialisation($root) { // Set locale to Welsh, which expects translations for 'one', 'two', // 'few' 'many' and 'other' forms – with the default English strings // provided we only have translations for 'one' and 'other'. // // We want to make sure we handle this gracefully in case users have // an existing character count inside an incorrect locale. - $module.setAttribute('lang', 'cy') + $root.setAttribute('lang', 'cy') } }) From ac453bd9e595d72650907ea0823379034d0322c3 Mon Sep 17 00:00:00 2001 From: Romaric Pascal Date: Fri, 20 Sep 2024 11:12:53 +0100 Subject: [PATCH 14/45] Rename `$module` to `$root` in Checkboxes --- .../components/checkboxes/checkboxes.mjs | 24 ++++++++-------- .../checkboxes/checkboxes.puppeteer.test.js | 28 +++++++++---------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/packages/govuk-frontend/src/govuk/components/checkboxes/checkboxes.mjs b/packages/govuk-frontend/src/govuk/components/checkboxes/checkboxes.mjs index 8314ae217d..3eea3aee72 100644 --- a/packages/govuk-frontend/src/govuk/components/checkboxes/checkboxes.mjs +++ b/packages/govuk-frontend/src/govuk/components/checkboxes/checkboxes.mjs @@ -8,7 +8,7 @@ import { GOVUKFrontendComponent } from '../../govuk-frontend-component.mjs' */ export class Checkboxes extends GOVUKFrontendComponent { /** @private */ - $module + $root /** @private */ $inputs @@ -25,20 +25,20 @@ export class Checkboxes extends GOVUKFrontendComponent { * (for example if the user has navigated back), and set up event handlers to * keep the reveal in sync with the checkbox state. * - * @param {Element | null} $module - HTML element to use for checkboxes + * @param {Element | null} $root - HTML element to use for checkboxes */ - constructor($module) { - super($module) + constructor($root) { + super($root) - if (!($module instanceof HTMLElement)) { + if (!($root instanceof HTMLElement)) { throw new ElementError({ componentName: 'Checkboxes', - element: $module, - identifier: 'Root element (`$module`)' + element: $root, + identifier: 'Root element (`$root`)' }) } - const $inputs = $module.querySelectorAll('input[type="checkbox"]') + const $inputs = $root.querySelectorAll('input[type="checkbox"]') if (!$inputs.length) { throw new ElementError({ componentName: 'Checkboxes', @@ -46,7 +46,7 @@ export class Checkboxes extends GOVUKFrontendComponent { }) } - this.$module = $module + this.$root = $root this.$inputs = $inputs this.$inputs.forEach(($input) => { @@ -82,11 +82,11 @@ export class Checkboxes extends GOVUKFrontendComponent { this.syncAllConditionalReveals() // Handle events - this.$module.addEventListener('click', (event) => this.handleClick(event)) + this.$root.addEventListener('click', (event) => this.handleClick(event)) } /** - * Sync the conditional reveal states for all checkboxes in this $module. + * Sync the conditional reveal states for all checkboxes in this component. * * @private */ @@ -174,7 +174,7 @@ export class Checkboxes extends GOVUKFrontendComponent { /** * Click event handler * - * Handle a click within the $module – if the click occurred on a checkbox, + * Handle a click within the component root – if the click occurred on a checkbox, * sync the state of any associated conditional reveal with the checkbox * state. * diff --git a/packages/govuk-frontend/src/govuk/components/checkboxes/checkboxes.puppeteer.test.js b/packages/govuk-frontend/src/govuk/components/checkboxes/checkboxes.puppeteer.test.js index b4fd79e43a..aa25822cef 100644 --- a/packages/govuk-frontend/src/govuk/components/checkboxes/checkboxes.puppeteer.test.js +++ b/packages/govuk-frontend/src/govuk/components/checkboxes/checkboxes.puppeteer.test.js @@ -372,9 +372,9 @@ describe('Checkboxes', () => { it('throws when initialised twice', async () => { await expect( render(page, 'checkboxes', examples.default, { - async afterInitialisation($module) { + async afterInitialisation($root) { const { Checkboxes } = await import('govuk-frontend') - new Checkboxes($module) + new Checkboxes($root) } }) ).rejects.toMatchObject({ @@ -384,34 +384,34 @@ describe('Checkboxes', () => { }) }) - it('throws when $module is not set', async () => { + it('throws when $root is not set', async () => { await expect( render(page, 'checkboxes', examples.default, { - beforeInitialisation($module) { - $module.remove() + beforeInitialisation($root) { + $root.remove() } }) ).rejects.toMatchObject({ cause: { name: 'ElementError', - message: 'Checkboxes: Root element (`$module`) not found' + message: 'Checkboxes: Root element (`$root`) not found' } }) }) - it('throws when receiving the wrong type for $module', async () => { + it('throws when receiving the wrong type for $root', async () => { await expect( render(page, 'checkboxes', examples.default, { - beforeInitialisation($module) { + beforeInitialisation($root) { // Replace with an `` element which is not an `HTMLElement` in the DOM (but an `SVGElement`) - $module.outerHTML = `` + $root.outerHTML = `` } }) ).rejects.toMatchObject({ cause: { name: 'ElementError', message: - 'Checkboxes: Root element (`$module`) is not of type HTMLElement' + 'Checkboxes: Root element (`$root`) is not of type HTMLElement' } }) }) @@ -419,8 +419,8 @@ describe('Checkboxes', () => { it('throws when the input list is empty', async () => { await expect( render(page, 'checkboxes', examples.default, { - beforeInitialisation($module, { selector }) { - $module + beforeInitialisation($root, { selector }) { + $root .querySelectorAll(selector) .forEach((item) => item.remove()) }, @@ -440,8 +440,8 @@ describe('Checkboxes', () => { it('throws when a conditional target element is not found', async () => { await expect( render(page, 'checkboxes', examples['with conditional items'], { - beforeInitialisation($module, { selector }) { - $module.querySelector(selector).remove() + beforeInitialisation($root, { selector }) { + $root.querySelector(selector).remove() }, context: { selector: '.govuk-checkboxes__conditional' From 90849a09cffd1d3609d6070dc1c177ab065be0a3 Mon Sep 17 00:00:00 2001 From: Romaric Pascal Date: Fri, 20 Sep 2024 11:14:51 +0100 Subject: [PATCH 15/45] Rename `$module` to `$root` in Error Summary --- .../error-summary/error-summary.mjs | 22 +++++++++---------- .../error-summary.puppeteer.test.js | 20 ++++++++--------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/packages/govuk-frontend/src/govuk/components/error-summary/error-summary.mjs b/packages/govuk-frontend/src/govuk/components/error-summary/error-summary.mjs index 9707a53af4..4d08851f2a 100644 --- a/packages/govuk-frontend/src/govuk/components/error-summary/error-summary.mjs +++ b/packages/govuk-frontend/src/govuk/components/error-summary/error-summary.mjs @@ -17,7 +17,7 @@ import { GOVUKFrontendComponent } from '../../govuk-frontend-component.mjs' */ export class ErrorSummary extends GOVUKFrontendComponent { /** @private */ - $module + $root /** * @private @@ -26,36 +26,36 @@ export class ErrorSummary extends GOVUKFrontendComponent { config /** - * @param {Element | null} $module - HTML element to use for error summary + * @param {Element | null} $root - HTML element to use for error summary * @param {ErrorSummaryConfig} [config] - Error summary config */ - constructor($module, config = {}) { - super($module) + constructor($root, config = {}) { + super($root) - if (!($module instanceof HTMLElement)) { + if (!($root instanceof HTMLElement)) { throw new ElementError({ componentName: 'Error summary', - element: $module, - identifier: 'Root element (`$module`)' + element: $root, + identifier: 'Root element (`$root`)' }) } - this.$module = $module + this.$root = $root this.config = mergeConfigs( ErrorSummary.defaults, config, - normaliseDataset(ErrorSummary, $module.dataset) + normaliseDataset(ErrorSummary, $root.dataset) ) /** * Focus the error summary */ if (!this.config.disableAutoFocus) { - setFocus(this.$module) + setFocus(this.$root) } - this.$module.addEventListener('click', (event) => this.handleClick(event)) + this.$root.addEventListener('click', (event) => this.handleClick(event)) } /** diff --git a/packages/govuk-frontend/src/govuk/components/error-summary/error-summary.puppeteer.test.js b/packages/govuk-frontend/src/govuk/components/error-summary/error-summary.puppeteer.test.js index 337df3e8ae..b6de86dc7b 100644 --- a/packages/govuk-frontend/src/govuk/components/error-summary/error-summary.puppeteer.test.js +++ b/packages/govuk-frontend/src/govuk/components/error-summary/error-summary.puppeteer.test.js @@ -241,9 +241,9 @@ describe('Error Summary', () => { it('throws when initialised twice', async () => { await expect( render(page, 'error-summary', examples.default, { - async afterInitialisation($module) { + async afterInitialisation($root) { const { ErrorSummary } = await import('govuk-frontend') - new ErrorSummary($module) + new ErrorSummary($root) } }) ).rejects.toMatchObject({ @@ -253,34 +253,34 @@ describe('Error Summary', () => { }) }) - it('throws when $module is not set', async () => { + it('throws when $root is not set', async () => { await expect( render(page, 'error-summary', examples.default, { - beforeInitialisation($module) { - $module.remove() + beforeInitialisation($root) { + $root.remove() } }) ).rejects.toMatchObject({ cause: { name: 'ElementError', - message: 'Error summary: Root element (`$module`) not found' + message: 'Error summary: Root element (`$root`) not found' } }) }) - it('throws when receiving the wrong type for $module', async () => { + it('throws when receiving the wrong type for $root', async () => { await expect( render(page, 'error-summary', examples.default, { - beforeInitialisation($module) { + beforeInitialisation($root) { // Replace with an `` element which is not an `HTMLElement` in the DOM (but an `SVGElement`) - $module.outerHTML = `` + $root.outerHTML = `` } }) ).rejects.toMatchObject({ cause: { name: 'ElementError', message: - 'Error summary: Root element (`$module`) is not of type HTMLElement' + 'Error summary: Root element (`$root`) is not of type HTMLElement' } }) }) From 6330247a6c65d18447beb3af80eee666ea7edf93 Mon Sep 17 00:00:00 2001 From: Romaric Pascal Date: Fri, 20 Sep 2024 11:15:25 +0100 Subject: [PATCH 16/45] Rename `$module` to `$root` in Exit This Page --- .../exit-this-page/exit-this-page.mjs | 22 ++++++++--------- .../exit-this-page.puppeteer.test.js | 24 +++++++++---------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/packages/govuk-frontend/src/govuk/components/exit-this-page/exit-this-page.mjs b/packages/govuk-frontend/src/govuk/components/exit-this-page/exit-this-page.mjs index d7a9c1cc2c..ff7a933c95 100644 --- a/packages/govuk-frontend/src/govuk/components/exit-this-page/exit-this-page.mjs +++ b/packages/govuk-frontend/src/govuk/components/exit-this-page/exit-this-page.mjs @@ -11,7 +11,7 @@ import { I18n } from '../../i18n.mjs' */ export class ExitThisPage extends GOVUKFrontendComponent { /** @private */ - $module + $root /** * @private @@ -75,21 +75,21 @@ export class ExitThisPage extends GOVUKFrontendComponent { timeoutMessageId = null /** - * @param {Element | null} $module - HTML element that wraps the Exit This Page button + * @param {Element | null} $root - HTML element that wraps the Exit This Page button * @param {ExitThisPageConfig} [config] - Exit This Page config */ - constructor($module, config = {}) { - super($module) + constructor($root, config = {}) { + super($root) - if (!($module instanceof HTMLElement)) { + if (!($root instanceof HTMLElement)) { throw new ElementError({ componentName: 'Exit this page', - element: $module, - identifier: 'Root element (`$module`)' + element: $root, + identifier: 'Root element (`$root`)' }) } - const $button = $module.querySelector('.govuk-exit-this-page__button') + const $button = $root.querySelector('.govuk-exit-this-page__button') if (!($button instanceof HTMLAnchorElement)) { throw new ElementError({ componentName: 'Exit this page', @@ -102,11 +102,11 @@ export class ExitThisPage extends GOVUKFrontendComponent { this.config = mergeConfigs( ExitThisPage.defaults, config, - normaliseDataset(ExitThisPage, $module.dataset) + normaliseDataset(ExitThisPage, $root.dataset) ) this.i18n = new I18n(this.config.i18n) - this.$module = $module + this.$root = $root this.$button = $button const $skiplinkButton = document.querySelector( @@ -142,7 +142,7 @@ export class ExitThisPage extends GOVUKFrontendComponent { this.$updateSpan.setAttribute('role', 'status') this.$updateSpan.className = 'govuk-visually-hidden' - this.$module.appendChild(this.$updateSpan) + this.$root.appendChild(this.$updateSpan) } /** diff --git a/packages/govuk-frontend/src/govuk/components/exit-this-page/exit-this-page.puppeteer.test.js b/packages/govuk-frontend/src/govuk/components/exit-this-page/exit-this-page.puppeteer.test.js index adc6c7be3c..edbee46fea 100644 --- a/packages/govuk-frontend/src/govuk/components/exit-this-page/exit-this-page.puppeteer.test.js +++ b/packages/govuk-frontend/src/govuk/components/exit-this-page/exit-this-page.puppeteer.test.js @@ -236,9 +236,9 @@ describe('/components/exit-this-page', () => { it('throws when initialised twice', async () => { await expect( render(page, 'exit-this-page', examples.default, { - async afterInitialisation($module) { + async afterInitialisation($root) { const { ExitThisPage } = await import('govuk-frontend') - new ExitThisPage($module) + new ExitThisPage($root) } }) ).rejects.toMatchObject({ @@ -248,34 +248,34 @@ describe('/components/exit-this-page', () => { }) }) - it('throws when $module is not set', async () => { + it('throws when $root is not set', async () => { await expect( render(page, 'exit-this-page', examples.default, { - beforeInitialisation($module) { - $module.remove() + beforeInitialisation($root) { + $root.remove() } }) ).rejects.toMatchObject({ cause: { name: 'ElementError', - message: 'Exit this page: Root element (`$module`) not found' + message: 'Exit this page: Root element (`$root`) not found' } }) }) - it('throws when receiving the wrong type for $module', async () => { + it('throws when receiving the wrong type for $root', async () => { await expect( render(page, 'exit-this-page', examples.default, { - beforeInitialisation($module) { + beforeInitialisation($root) { // Replace with an `` element which is not an `HTMLElement` in the DOM (but an `SVGElement`) - $module.outerHTML = `` + $root.outerHTML = `` } }) ).rejects.toMatchObject({ cause: { name: 'ElementError', message: - 'Exit this page: Root element (`$module`) is not of type HTMLElement' + 'Exit this page: Root element (`$root`) is not of type HTMLElement' } }) }) @@ -283,8 +283,8 @@ describe('/components/exit-this-page', () => { it('throws when the button is missing', async () => { await expect( render(page, 'exit-this-page', examples.default, { - beforeInitialisation($module, { selector }) { - $module.querySelector(selector).remove() + beforeInitialisation($root, { selector }) { + $root.querySelector(selector).remove() }, context: { selector: '.govuk-exit-this-page__button' From 21b8cb3294d469343ece7873106cd9c45dda804b Mon Sep 17 00:00:00 2001 From: Romaric Pascal Date: Fri, 20 Sep 2024 11:19:27 +0100 Subject: [PATCH 17/45] Rename `$module` to `$root` in Header --- .../src/govuk/components/header/header.mjs | 18 +++++++-------- .../header/header.puppeteer.test.js | 22 +++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/packages/govuk-frontend/src/govuk/components/header/header.mjs b/packages/govuk-frontend/src/govuk/components/header/header.mjs index 1fee86c8a1..7a9554817f 100644 --- a/packages/govuk-frontend/src/govuk/components/header/header.mjs +++ b/packages/govuk-frontend/src/govuk/components/header/header.mjs @@ -9,7 +9,7 @@ import { GOVUKFrontendComponent } from '../../govuk-frontend-component.mjs' */ export class Header extends GOVUKFrontendComponent { /** @private */ - $module + $root /** @private */ $menuButton @@ -40,21 +40,21 @@ export class Header extends GOVUKFrontendComponent { * Apply a matchMedia for desktop which will trigger a state sync if the * browser viewport moves between states. * - * @param {Element | null} $module - HTML element to use for header + * @param {Element | null} $root - HTML element to use for header */ - constructor($module) { - super($module) + constructor($root) { + super($root) - if (!$module) { + if (!$root) { throw new ElementError({ componentName: 'Header', - element: $module, - identifier: 'Root element (`$module`)' + element: $root, + identifier: 'Root element (`$root`)' }) } - this.$module = $module - const $menuButton = $module.querySelector('.govuk-js-header-toggle') + this.$root = $root + const $menuButton = $root.querySelector('.govuk-js-header-toggle') // Headers don't necessarily have a navigation. When they don't, the menu // toggle won't be rendered by our macro (or may be omitted when writing diff --git a/packages/govuk-frontend/src/govuk/components/header/header.puppeteer.test.js b/packages/govuk-frontend/src/govuk/components/header/header.puppeteer.test.js index 70a5e3ac96..52b6879e8b 100644 --- a/packages/govuk-frontend/src/govuk/components/header/header.puppeteer.test.js +++ b/packages/govuk-frontend/src/govuk/components/header/header.puppeteer.test.js @@ -186,9 +186,9 @@ describe('Header navigation', () => { it('throws when initialised twice', async () => { await expect( render(page, 'header', examples.default, { - async afterInitialisation($module) { + async afterInitialisation($root) { const { Header } = await import('govuk-frontend') - new Header($module) + new Header($root) } }) ).rejects.toMatchObject({ @@ -197,19 +197,19 @@ describe('Header navigation', () => { }) }) - it('throws when $module is not set', async () => { + it('throws when $root is not set', async () => { await expect( render(page, 'header', examples.default, { - beforeInitialisation($module) { + beforeInitialisation($root) { // Remove the root of the components as a way - // for the constructor to receive the wrong type for `$module` - $module.remove() + // for the constructor to receive the wrong type for `$root` + $root.remove() } }) ).rejects.toMatchObject({ cause: { name: 'ElementError', - message: 'Header: Root element (`$module`) not found' + message: 'Header: Root element (`$root`) not found' } }) }) @@ -217,8 +217,8 @@ describe('Header navigation', () => { it("throws when the toggle's aria-control attribute is missing", async () => { await expect( render(page, 'header', examples['with navigation'], { - beforeInitialisation($module, { selector }) { - $module.querySelector(selector).removeAttribute('aria-controls') + beforeInitialisation($root, { selector }) { + $root.querySelector(selector).removeAttribute('aria-controls') }, context: { selector: '.govuk-js-header-toggle' @@ -236,9 +236,9 @@ describe('Header navigation', () => { it('throws when the menu is missing, but a toggle is present', async () => { await expect( render(page, 'header', examples['with navigation'], { - beforeInitialisation($module, { selector }) { + beforeInitialisation($root, { selector }) { // Remove the menu `