From b791a0a9181710cc3f4619bffb96b7ea4010619f Mon Sep 17 00:00:00 2001 From: Patrick Cartlidge Date: Wed, 18 Sep 2024 11:20:21 +0100 Subject: [PATCH] Add `canInitialise` to `GOVUKFrontendComponent` If `canInitialise` defined in class that extends GOVUKFrontendComponent then `canInitialise` will be executed in `super()` before initialisation routine and prevent child component from initialising. --- .../govuk-frontend/src/govuk/errors/index.mjs | 7 +++++ .../src/govuk/govuk-frontend-component.mjs | 17 +++++++++-- .../src/govuk/init.jsdom.test.mjs | 28 ++++++++++++++++++- 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/packages/govuk-frontend/src/govuk/errors/index.mjs b/packages/govuk-frontend/src/govuk/errors/index.mjs index aa599ba836..82227e765b 100644 --- a/packages/govuk-frontend/src/govuk/errors/index.mjs +++ b/packages/govuk-frontend/src/govuk/errors/index.mjs @@ -54,6 +54,13 @@ export class ConfigError extends GOVUKFrontendError { name = 'ConfigError' } +/** + * Indicates that a component encountered a false `canInitialise` function call result + */ +export class CanInitError extends GOVUKFrontendError { + name = 'CanInitError' +} + /** * Indicates an issue with an element (possibly `null` or `undefined`) */ diff --git a/packages/govuk-frontend/src/govuk/govuk-frontend-component.mjs b/packages/govuk-frontend/src/govuk/govuk-frontend-component.mjs index 273d00dbf3..22ffba7afb 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 { isInitialised, isSupported } from './common/index.mjs' -import { InitError, SupportError } from './errors/index.mjs' +import { InitError, SupportError, CanInitError } from './errors/index.mjs' /** * Base Component class @@ -20,8 +20,18 @@ export class GOVUKFrontendComponent { this.checkSupport() this.checkInitialised($module) - const moduleName = /** @type {ChildClassConstructor} */ (this.constructor) - .moduleName + const childClassConstructor = /** @type {ChildClassConstructor} */ ( + this.constructor + ) + + const moduleName = childClassConstructor.moduleName + const canInitialise = childClassConstructor.canInitialise + + if (typeof canInitialise === 'function') { + if (!canInitialise()) { + throw new CanInitError('`canInitialise` returned `false`') + } + } if (typeof moduleName === 'string') { moduleName && $module?.setAttribute(`data-${moduleName}-init`, '') @@ -62,6 +72,7 @@ export class GOVUKFrontendComponent { /** * @typedef ChildClass * @property {string} [moduleName] - The module name that'll be looked for in the DOM when initialising the component + * @property {() => boolean} [canInitialise] - The module name that'll be looked for in the DOM when initialising the component */ /** diff --git a/packages/govuk-frontend/src/govuk/init.jsdom.test.mjs b/packages/govuk-frontend/src/govuk/init.jsdom.test.mjs index 58462f9458..020082c9f7 100644 --- a/packages/govuk-frontend/src/govuk/init.jsdom.test.mjs +++ b/packages/govuk-frontend/src/govuk/init.jsdom.test.mjs @@ -4,6 +4,7 @@ import { } from '@govuk-frontend/lib/names' import * as GOVUKFrontend from './all.mjs' +import { GOVUKFrontendComponent } from './govuk-frontend-component.mjs' import { initAll, createAll } from './init.mjs' // Annoyingly these don't get hoisted if done in a loop @@ -226,8 +227,9 @@ describe('createAll', () => { document.body.outerHTML = '' }) - class MockComponent { + class MockComponent extends GOVUKFrontendComponent { constructor(...args) { + super() this.args = args } @@ -321,6 +323,30 @@ describe('createAll', () => { expect(result[1].args).toStrictEqual([document.getElementById('b')]) }) + describe('when a component defines canInitialise', () => { + class MockComponentWithCanInitialise extends MockComponent { + static canInitialise = () => { + return false + } + } + + it('prints error and returns empty array if canInitialise returns false', () => { + document.body.innerHTML = `
` + + jest.spyOn(global.console, 'log').mockImplementation() + + const result = createAll(MockComponentWithCanInitialise) + + expect(global.console.log).toHaveBeenCalledWith( + expect.objectContaining({ + message: '`canInitialise` returned `false`' + }) + ) + + expect(result).toStrictEqual([]) + }) + }) + describe('when a component accepts config', () => { class MockComponentWithConfig extends MockComponent { static defaults = {