diff --git a/packages/govuk-frontend/src/govuk/common/index.mjs b/packages/govuk-frontend/src/govuk/common/index.mjs index 22d26b8945..b9e868a91b 100644 --- a/packages/govuk-frontend/src/govuk/common/index.mjs +++ b/packages/govuk-frontend/src/govuk/common/index.mjs @@ -133,3 +133,17 @@ export function extractConfigByNamespace(configObject, namespace) { } return newObject } + +/** + * Checks if GOV.UK Frontend is supported on this page + * + * Some browsers will load and run our JavaScript but GOV.UK Frontend + * won't be supported. + * + * @internal + * @param {HTMLElement} [$scope] - The `` element of the document to check for support + * @returns {boolean} Whether GOV.UK Frontend is supported on this page + */ +export function isSupported($scope = document.body) { + return $scope.classList.contains('govuk-frontend-supported') +} diff --git a/packages/govuk-frontend/src/govuk/common/index.unit.test.mjs b/packages/govuk-frontend/src/govuk/common/index.unit.test.mjs index 70c9c4f838..dd67015a83 100644 --- a/packages/govuk-frontend/src/govuk/common/index.unit.test.mjs +++ b/packages/govuk-frontend/src/govuk/common/index.unit.test.mjs @@ -1,4 +1,8 @@ -import { mergeConfigs, extractConfigByNamespace } from './index.mjs' +import { + mergeConfigs, + extractConfigByNamespace, + isSupported +} from './index.mjs' describe('Common JS utilities', () => { describe('mergeConfigs', () => { @@ -112,4 +116,22 @@ describe('Common JS utilities', () => { expect(() => extractConfigByNamespace(flattenedConfig)).toThrow() }) }) + + describe.only('isSupported', () => { + beforeEach(() => { + // Jest does not tidy the JSDOM document between tests + // so we need to take care of that ourselves + document.documentElement.innerHTML = '' + }) + + it('returns true if the govuk-frontend-supported class is set', () => { + document.body.classList.add('govuk-frontend-supported') + + expect(isSupported(document.body)).toBe(true) + }) + + it('returns false if the govuk-frontend-supported class is not set', () => { + expect(isSupported(document.body)).toBe(false) + }) + }) }) diff --git a/packages/govuk-frontend/src/govuk/components/accordion/accordion.mjs b/packages/govuk-frontend/src/govuk/components/accordion/accordion.mjs index 338c680dc3..43e618d0a4 100644 --- a/packages/govuk-frontend/src/govuk/components/accordion/accordion.mjs +++ b/packages/govuk-frontend/src/govuk/components/accordion/accordion.mjs @@ -1,6 +1,6 @@ import { mergeConfigs, extractConfigByNamespace } from '../../common/index.mjs' import { normaliseDataset } from '../../common/normalise-dataset.mjs' -import { GOVUKFrontendSupportError } from '../../errors/index.mjs' +import { GOVUKFrontendComponent } from '../../govuk-frontend-component.mjs' import { I18n } from '../../i18n.mjs' /** @@ -15,7 +15,7 @@ import { I18n } from '../../i18n.mjs' * The state of each section is saved to the DOM via the `aria-expanded` * attribute, which also provides accessibility. */ -export class Accordion { +export class Accordion extends GOVUKFrontendComponent { /** @private */ $module @@ -114,9 +114,7 @@ export class Accordion { * @param {AccordionConfig} [config] - Accordion config */ constructor($module, config) { - if (!document.body.classList.contains('govuk-frontend-supported')) { - throw new GOVUKFrontendSupportError() - } + super() if (!($module instanceof HTMLElement)) { return this diff --git a/packages/govuk-frontend/src/govuk/components/button/button.mjs b/packages/govuk-frontend/src/govuk/components/button/button.mjs index cd478b940d..58e2f91b29 100644 --- a/packages/govuk-frontend/src/govuk/components/button/button.mjs +++ b/packages/govuk-frontend/src/govuk/components/button/button.mjs @@ -1,6 +1,6 @@ import { mergeConfigs } from '../../common/index.mjs' import { normaliseDataset } from '../../common/normalise-dataset.mjs' -import { GOVUKFrontendSupportError } from '../../errors/index.mjs' +import { GOVUKFrontendComponent } from '../../govuk-frontend-component.mjs' const KEY_SPACE = 32 const DEBOUNCE_TIMEOUT_IN_SECONDS = 1 @@ -8,7 +8,7 @@ const DEBOUNCE_TIMEOUT_IN_SECONDS = 1 /** * JavaScript enhancements for the Button component */ -export class Button { +export class Button extends GOVUKFrontendComponent { /** @private */ $module @@ -30,9 +30,7 @@ export class Button { * @param {ButtonConfig} [config] - Button config */ constructor($module, config) { - if (!document.body.classList.contains('govuk-frontend-supported')) { - throw new GOVUKFrontendSupportError() - } + super() if (!($module instanceof HTMLElement)) { return this 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 b2814fe38a..c8c45061da 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 @@ -1,7 +1,7 @@ import { closestAttributeValue } from '../../common/closest-attribute-value.mjs' import { extractConfigByNamespace, mergeConfigs } from '../../common/index.mjs' import { normaliseDataset } from '../../common/normalise-dataset.mjs' -import { GOVUKFrontendSupportError } from '../../errors/index.mjs' +import { GOVUKFrontendComponent } from '../../govuk-frontend-component.mjs' import { I18n } from '../../i18n.mjs' /** @@ -14,7 +14,7 @@ import { I18n } from '../../i18n.mjs' * You can configure the message to only appear after a certain percentage * of the available characters/words has been entered. */ -export class CharacterCount { +export class CharacterCount extends GOVUKFrontendComponent { /** @private */ $module @@ -65,9 +65,7 @@ export class CharacterCount { * @param {CharacterCountConfig} [config] - Character count config */ constructor($module, config) { - if (!document.body.classList.contains('govuk-frontend-supported')) { - throw new GOVUKFrontendSupportError() - } + super() if (!($module instanceof HTMLElement)) { return this diff --git a/packages/govuk-frontend/src/govuk/components/checkboxes/checkboxes.mjs b/packages/govuk-frontend/src/govuk/components/checkboxes/checkboxes.mjs index b6238d1a71..7349d98ec7 100644 --- a/packages/govuk-frontend/src/govuk/components/checkboxes/checkboxes.mjs +++ b/packages/govuk-frontend/src/govuk/components/checkboxes/checkboxes.mjs @@ -1,9 +1,9 @@ -import { GOVUKFrontendSupportError } from '../../errors/index.mjs' +import { GOVUKFrontendComponent } from '../../govuk-frontend-component.mjs' /** * Checkboxes component */ -export class Checkboxes { +export class Checkboxes extends GOVUKFrontendComponent { /** @private */ $module @@ -25,9 +25,7 @@ export class Checkboxes { * @param {Element} $module - HTML element to use for checkboxes */ constructor($module) { - if (!document.body.classList.contains('govuk-frontend-supported')) { - throw new GOVUKFrontendSupportError() - } + super() if (!($module instanceof HTMLElement)) { return this 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 d296e946fe..0b811b62e8 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 @@ -1,13 +1,13 @@ import { mergeConfigs } from '../../common/index.mjs' import { normaliseDataset } from '../../common/normalise-dataset.mjs' -import { GOVUKFrontendSupportError } from '../../errors/index.mjs' +import { GOVUKFrontendComponent } from '../../govuk-frontend-component.mjs' /** * Error summary component * * Takes focus on initialisation for accessible announcement, unless disabled in configuration. */ -export class ErrorSummary { +export class ErrorSummary extends GOVUKFrontendComponent { /** @private */ $module @@ -23,9 +23,7 @@ export class ErrorSummary { * @param {ErrorSummaryConfig} [config] - Error summary config */ constructor($module, config) { - if (!document.body.classList.contains('govuk-frontend-supported')) { - throw new GOVUKFrontendSupportError() - } + super() if (!($module instanceof HTMLElement)) { return this 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 4b115afe0f..4a7ee8ddda 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 @@ -1,12 +1,12 @@ import { mergeConfigs, extractConfigByNamespace } from '../../common/index.mjs' import { normaliseDataset } from '../../common/normalise-dataset.mjs' -import { GOVUKFrontendSupportError } from '../../errors/index.mjs' +import { GOVUKFrontendComponent } from '../../govuk-frontend-component.mjs' import { I18n } from '../../i18n.mjs' /** * Exit This Page component */ -export class ExitThisPage { +export class ExitThisPage extends GOVUKFrontendComponent { /** @private */ $module @@ -76,9 +76,7 @@ export class ExitThisPage { * @param {ExitThisPageConfig} [config] - Exit This Page config */ constructor($module, config) { - if (!document.body.classList.contains('govuk-frontend-supported')) { - throw new GOVUKFrontendSupportError() - } + super() if (!($module instanceof HTMLElement)) { return this diff --git a/packages/govuk-frontend/src/govuk/components/header/header.mjs b/packages/govuk-frontend/src/govuk/components/header/header.mjs index 1ea0f260f1..9c40057b6f 100644 --- a/packages/govuk-frontend/src/govuk/components/header/header.mjs +++ b/packages/govuk-frontend/src/govuk/components/header/header.mjs @@ -1,9 +1,9 @@ -import { GOVUKFrontendSupportError } from '../../errors/index.mjs' +import { GOVUKFrontendComponent } from '../../govuk-frontend-component.mjs' /** * Header component */ -export class Header { +export class Header extends GOVUKFrontendComponent { /** @private */ $module @@ -39,9 +39,7 @@ export class Header { * @param {Element} $module - HTML element to use for header */ constructor($module) { - if (!document.body.classList.contains('govuk-frontend-supported')) { - throw new GOVUKFrontendSupportError() - } + super() if (!($module instanceof HTMLElement)) { return this 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 526e163a4d..a0ebed786e 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 @@ -1,11 +1,11 @@ import { mergeConfigs } from '../../common/index.mjs' import { normaliseDataset } from '../../common/normalise-dataset.mjs' -import { GOVUKFrontendSupportError } from '../../errors/index.mjs' +import { GOVUKFrontendComponent } from '../../govuk-frontend-component.mjs' /** * Notification Banner component */ -export class NotificationBanner { +export class NotificationBanner extends GOVUKFrontendComponent { /** @private */ $module @@ -20,9 +20,7 @@ export class NotificationBanner { * @param {NotificationBannerConfig} [config] - Notification banner config */ constructor($module, config) { - if (!document.body.classList.contains('govuk-frontend-supported')) { - throw new GOVUKFrontendSupportError() - } + super() if (!($module instanceof HTMLElement)) { return this diff --git a/packages/govuk-frontend/src/govuk/components/radios/radios.mjs b/packages/govuk-frontend/src/govuk/components/radios/radios.mjs index 8aef228268..7f27650696 100644 --- a/packages/govuk-frontend/src/govuk/components/radios/radios.mjs +++ b/packages/govuk-frontend/src/govuk/components/radios/radios.mjs @@ -1,9 +1,9 @@ -import { GOVUKFrontendSupportError } from '../../errors/index.mjs' +import { GOVUKFrontendComponent } from '../../govuk-frontend-component.mjs' /** * Radios component */ -export class Radios { +export class Radios extends GOVUKFrontendComponent { /** @private */ $module @@ -25,9 +25,7 @@ export class Radios { * @param {Element} $module - HTML element to use for radios */ constructor($module) { - if (!document.body.classList.contains('govuk-frontend-supported')) { - throw new GOVUKFrontendSupportError() - } + super() if (!($module instanceof HTMLElement)) { return this 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 8205f5d597..7fc5a65c2b 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 @@ -1,9 +1,9 @@ -import { GOVUKFrontendSupportError } from '../../errors/index.mjs' +import { GOVUKFrontendComponent } from '../../govuk-frontend-component.mjs' /** * Skip link component */ -export class SkipLink { +export class SkipLink extends GOVUKFrontendComponent { /** @private */ $module @@ -21,9 +21,7 @@ export class SkipLink { * @param {Element} $module - HTML element to use for skip link */ constructor($module) { - if (!document.body.classList.contains('govuk-frontend-supported')) { - throw new GOVUKFrontendSupportError() - } + super() if (!($module instanceof HTMLAnchorElement)) { return this diff --git a/packages/govuk-frontend/src/govuk/components/tabs/tabs.mjs b/packages/govuk-frontend/src/govuk/components/tabs/tabs.mjs index 3c7462574f..42f0722bd7 100644 --- a/packages/govuk-frontend/src/govuk/components/tabs/tabs.mjs +++ b/packages/govuk-frontend/src/govuk/components/tabs/tabs.mjs @@ -1,9 +1,9 @@ -import { GOVUKFrontendSupportError } from '../../errors/index.mjs' +import { GOVUKFrontendComponent } from '../../govuk-frontend-component.mjs' /** * Tabs component */ -export class Tabs { +export class Tabs extends GOVUKFrontendComponent { /** @private */ $module @@ -38,9 +38,7 @@ export class Tabs { * @param {Element} $module - HTML element to use for tabs */ constructor($module) { - if (!document.body.classList.contains('govuk-frontend-supported')) { - throw new GOVUKFrontendSupportError() - } + super() if (!($module instanceof HTMLElement)) { return this diff --git a/packages/govuk-frontend/src/govuk/govuk-frontend-component.mjs b/packages/govuk-frontend/src/govuk/govuk-frontend-component.mjs new file mode 100644 index 0000000000..7c3e10ab52 --- /dev/null +++ b/packages/govuk-frontend/src/govuk/govuk-frontend-component.mjs @@ -0,0 +1,32 @@ +import { isSupported } from './common/index.mjs' +import { GOVUKFrontendSupportError } from './errors/index.mjs' + +/** + * Base Component class + * + * Centralises the behaviours shared by our components + * + * @internal + * @abstract + */ +export class GOVUKFrontendComponent { + /** + * Constructs a new component, validating that GOV.UK Frontend is supported + * + * @internal + */ + constructor() { + this.checkSupport() + } + + /** + * Validates whether GOV.UK Frontend is supported + * + * @internal + */ + checkSupport() { + if (!isSupported()) { + throw new GOVUKFrontendSupportError() + } + } +} diff --git a/packages/govuk-frontend/tasks/build/package.test.mjs b/packages/govuk-frontend/tasks/build/package.test.mjs index 088976f396..163502ef7d 100644 --- a/packages/govuk-frontend/tasks/build/package.test.mjs +++ b/packages/govuk-frontend/tasks/build/package.test.mjs @@ -216,7 +216,10 @@ describe('packages/govuk-frontend/dist/', () => { for (const componentName of componentNamesWithJavaScript) { const componentClassName = componentNameToClassName(componentName) - expect(contents).toContain(`class ${componentClassName} {`) + expect(contents).toContain( + // Trailing space is important to not match `class ${componentClassName}Something` + `class ${componentClassName} ` + ) expect(contents).toContain( `exports.${componentClassName} = ${componentClassName};` )