diff --git a/packages/govuk-frontend/src/govuk/components/accordion/accordion.mjs b/packages/govuk-frontend/src/govuk/components/accordion/accordion.mjs index 8657e63dcb..25f815d688 100644 --- a/packages/govuk-frontend/src/govuk/components/accordion/accordion.mjs +++ b/packages/govuk-frontend/src/govuk/components/accordion/accordion.mjs @@ -120,9 +120,10 @@ export class Accordion extends GOVUKFrontendComponent { super() if (!($module instanceof HTMLElement)) { - throw new ElementError('Root element (`$module`)', { + throw new ElementError({ componentName: 'Accordion', - element: $module + element: $module, + identifier: 'Root element (`$module`)' }) } diff --git a/packages/govuk-frontend/src/govuk/components/button/button.mjs b/packages/govuk-frontend/src/govuk/components/button/button.mjs index 35770bc1ee..86cc2ccdbe 100644 --- a/packages/govuk-frontend/src/govuk/components/button/button.mjs +++ b/packages/govuk-frontend/src/govuk/components/button/button.mjs @@ -35,9 +35,10 @@ export class Button extends GOVUKFrontendComponent { super() if (!($module instanceof HTMLElement)) { - throw new ElementError('Root element (`$module`)', { + throw new ElementError({ componentName: 'Button', - element: $module + element: $module, + identifier: 'Root element (`$module`)' }) } 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 d6be0d40a1..b3197bc4a3 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 @@ -75,9 +75,10 @@ export class CharacterCount extends GOVUKFrontendComponent { super() if (!($module instanceof HTMLElement)) { - throw new ElementError('Root element (`$module`)', { + throw new ElementError({ componentName: 'Character count', - element: $module + element: $module, + identifier: 'Root element (`$module`)' }) } @@ -88,10 +89,11 @@ export class CharacterCount extends GOVUKFrontendComponent { $textarea instanceof HTMLInputElement ) ) { - throw new ElementError('.govuk-js-character-count', { + throw new ElementError({ componentName: 'Character count', element: $textarea, - expectedType: 'HTMLTextareaElement or HTMLInputElement' + expectedType: 'HTMLTextareaElement or HTMLInputElement', + identifier: 'Form field (`.govuk-js-character-count`)' }) } @@ -140,9 +142,10 @@ export class CharacterCount extends GOVUKFrontendComponent { const textareaDescriptionId = `${this.$textarea.id}-info` const $textareaDescription = document.getElementById(textareaDescriptionId) if (!$textareaDescription) { - throw new ElementError(`#${textareaDescriptionId}`, { + throw new ElementError({ componentName: 'Character count', - element: $textareaDescription + element: $textareaDescription, + identifier: `#${textareaDescriptionId}` }) } 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 33be8036d0..2558fc3766 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 @@ -865,7 +865,8 @@ describe('Character count', () => { }) ).rejects.toEqual({ name: 'ElementError', - message: 'Character count: .govuk-js-character-count not found' + message: + 'Character count: Form field (`.govuk-js-character-count`) not found' }) }) @@ -884,7 +885,7 @@ describe('Character count', () => { ).rejects.toEqual({ name: 'ElementError', message: - 'Character count: .govuk-js-character-count is not of type HTMLTextareaElement or HTMLInputElement' + 'Character count: Form field (`.govuk-js-character-count`) is not of type HTMLTextareaElement or HTMLInputElement' }) }) diff --git a/packages/govuk-frontend/src/govuk/components/checkboxes/checkboxes.mjs b/packages/govuk-frontend/src/govuk/components/checkboxes/checkboxes.mjs index a76bfde818..452ef45d6a 100644 --- a/packages/govuk-frontend/src/govuk/components/checkboxes/checkboxes.mjs +++ b/packages/govuk-frontend/src/govuk/components/checkboxes/checkboxes.mjs @@ -31,16 +31,18 @@ export class Checkboxes extends GOVUKFrontendComponent { super() if (!($module instanceof HTMLElement)) { - throw new ElementError(`[data-module="${Checkboxes.moduleName}"]`, { + throw new ElementError({ componentName: 'Checkboxes', - element: $module + element: $module, + identifier: `[data-module="${Checkboxes.moduleName}"]` }) } const $inputs = $module.querySelectorAll('input[type="checkbox"]') if (!$inputs.length) { - throw new ElementError('input[type="checkbox"]', { - componentName: 'Checkboxes' + throw new ElementError({ + componentName: 'Checkboxes', + identifier: 'input[type="checkbox"]' }) } @@ -57,8 +59,9 @@ export class Checkboxes extends GOVUKFrontendComponent { // Throw if target conditional element does not exist. if (!document.getElementById(targetId)) { - throw new ElementError(`#${targetId}`, { - componentName: 'Checkboxes' + throw new ElementError({ + componentName: 'Checkboxes', + identifier: `#${targetId}` }) } 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 9f0863d524..769bea78a7 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 @@ -29,9 +29,10 @@ export class ErrorSummary extends GOVUKFrontendComponent { super() if (!($module instanceof HTMLElement)) { - throw new ElementError('Root element (`$module`)', { + throw new ElementError({ componentName: 'Error summary', - element: $module + element: $module, + identifier: 'Root element (`$module`)' }) } 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 f3d06b728e..d57f717669 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 @@ -82,18 +82,20 @@ export class ExitThisPage extends GOVUKFrontendComponent { super() if (!($module instanceof HTMLElement)) { - throw new ElementError('Root element (`$module`)', { + throw new ElementError({ componentName: 'Exit this page', - element: $module + element: $module, + identifier: 'Root element (`$module`)' }) } const $button = $module.querySelector('.govuk-exit-this-page__button') if (!($button instanceof HTMLAnchorElement)) { - throw new ElementError('Button', { + throw new ElementError({ componentName: 'Exit this page', element: $button, - expectedType: 'HTMLAnchorElement' + expectedType: 'HTMLAnchorElement', + identifier: 'Button' }) } diff --git a/packages/govuk-frontend/src/govuk/components/header/header.mjs b/packages/govuk-frontend/src/govuk/components/header/header.mjs index 2fbd23765e..cff72f3ea1 100644 --- a/packages/govuk-frontend/src/govuk/components/header/header.mjs +++ b/packages/govuk-frontend/src/govuk/components/header/header.mjs @@ -45,9 +45,10 @@ export class Header extends GOVUKFrontendComponent { super() if (!$module) { - throw new ElementError('Root element (`$module`)', { + throw new ElementError({ componentName: 'Header', - element: $module + element: $module, + identifier: 'Root element (`$module`)' }) } @@ -63,16 +64,18 @@ export class Header extends GOVUKFrontendComponent { const menuId = $menuButton.getAttribute('aria-controls') if (!menuId) { - throw new ElementError('.govuk-js-header-toggle[aria-controls]', { - componentName: 'Header' + throw new ElementError({ + componentName: 'Header', + identifier: '.govuk-js-header-toggle[aria-controls]' }) } const $menu = document.getElementById(menuId) if (!$menu) { - throw new ElementError(`#${menuId}`, { + throw new ElementError({ componentName: 'Header', - element: $menu + element: $menu, + identifier: `#${menuId}` }) } 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 055571cec0..1da998cf7c 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 @@ -26,9 +26,10 @@ export class NotificationBanner extends GOVUKFrontendComponent { super() if (!($module instanceof HTMLElement)) { - throw new ElementError('Root element (`$module`)', { + throw new ElementError({ componentName: 'Notification banner', - element: $module + element: $module, + identifier: 'Root element (`$module`)' }) } diff --git a/packages/govuk-frontend/src/govuk/components/radios/radios.mjs b/packages/govuk-frontend/src/govuk/components/radios/radios.mjs index 68873595d3..a6072d0a9e 100644 --- a/packages/govuk-frontend/src/govuk/components/radios/radios.mjs +++ b/packages/govuk-frontend/src/govuk/components/radios/radios.mjs @@ -31,16 +31,18 @@ export class Radios extends GOVUKFrontendComponent { super() if (!($module instanceof HTMLElement)) { - throw new ElementError(`[data-module="${Radios.moduleName}"]`, { + throw new ElementError({ componentName: 'Radios', - element: $module + element: $module, + identifier: `[data-module="${Radios.moduleName}"]` }) } const $inputs = $module.querySelectorAll('input[type="radio"]') if (!$inputs.length) { - throw new ElementError('input[type="radio"]', { - componentName: 'Radios' + throw new ElementError({ + componentName: 'Radios', + identifier: 'input[type="radio"]' }) } @@ -57,8 +59,9 @@ export class Radios extends GOVUKFrontendComponent { // Throw if target conditional element does not exist. if (!document.getElementById(targetId)) { - throw new ElementError(`#${targetId}`, { - componentName: 'Radios' + throw new ElementError({ + componentName: 'Radios', + identifier: `#${targetId}` }) } 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 b9c14b03ca..cf9877483a 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 @@ -27,10 +27,11 @@ export class SkipLink extends GOVUKFrontendComponent { super() if (!($module instanceof HTMLAnchorElement)) { - throw new ElementError('Root element (`$module`)', { + throw new ElementError({ componentName: 'Skip link', element: $module, - expectedType: 'HTMLAnchorElement' + expectedType: 'HTMLAnchorElement', + identifier: 'Root element (`$module`)' }) } @@ -52,11 +53,7 @@ export class SkipLink extends GOVUKFrontendComponent { // Check for link hash fragment if (!linkedElementId) { throw new ElementError( - 'Root element (`$module`) attribute (`href`) has no URL fragment', - { - componentName: 'Skip link', - identifierIsMessage: true - } + 'Skip link: Root element (`$module`) attribute (`href`) has no URL fragment' ) } @@ -64,9 +61,10 @@ export class SkipLink extends GOVUKFrontendComponent { // Check for link target element if (!$linkedElement) { - throw new ElementError(`Linked element #${linkedElementId}`, { + throw new ElementError({ componentName: 'Skip link', - element: $linkedElement + element: $linkedElement, + identifier: `Linked element #${linkedElementId}` }) } diff --git a/packages/govuk-frontend/src/govuk/components/tabs/tabs.mjs b/packages/govuk-frontend/src/govuk/components/tabs/tabs.mjs index be1c6769c7..b2fd0076e4 100644 --- a/packages/govuk-frontend/src/govuk/components/tabs/tabs.mjs +++ b/packages/govuk-frontend/src/govuk/components/tabs/tabs.mjs @@ -51,16 +51,18 @@ export class Tabs extends GOVUKFrontendComponent { super() if (!$module) { - throw new ElementError('Root element (`$module`)', { + throw new ElementError({ componentName: 'Tabs', - element: $module + element: $module, + identifier: 'Root element (`$module`)' }) } const $tabs = $module.querySelectorAll('a.govuk-tabs__tab') if (!$tabs.length) { - throw new ElementError(`a.govuk-tabs__tab`, { - componentName: 'Tabs' + throw new ElementError({ + componentName: 'Tabs', + identifier: `a.govuk-tabs__tab` }) } @@ -78,14 +80,16 @@ export class Tabs extends GOVUKFrontendComponent { ) if (!$tabList) { - throw new ElementError(`.govuk-tabs__list`, { - componentName: 'Tabs' + throw new ElementError({ + componentName: 'Tabs', + identifier: `.govuk-tabs__list` }) } if (!$tabListItems.length) { - throw new ElementError(`.govuk-tabs__list-item`, { - componentName: 'Tabs' + throw new ElementError({ + componentName: 'Tabs', + identifier: `.govuk-tabs__list-item` }) } 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 efd0e3f18f..6eb73e0a2a 100644 --- a/packages/govuk-frontend/src/govuk/errors/index.jsdom.test.mjs +++ b/packages/govuk-frontend/src/govuk/errors/index.jsdom.test.mjs @@ -28,22 +28,30 @@ describe('errors', () => { describe('ElementError', () => { it('is an instance of GOVUKFrontendError', () => { expect( - new ElementError('variableName', { - componentName: 'Component name' + new ElementError({ + componentName: 'Component name', + identifier: 'variableName' }) ).toBeInstanceOf(GOVUKFrontendError) }) it('has its own name set', () => { expect( - new ElementError('variableName', { - componentName: 'Component name' + new ElementError({ + componentName: 'Component name', + identifier: 'variableName' }).name ).toBe('ElementError') }) + it('accepts a string and does not process it in any way', () => { + expect(new ElementError('Complex custom error message').message).toBe( + 'Complex custom error message' + ) + }) it('formats the message when the element is not found', () => { expect( - new ElementError('variableName', { - componentName: 'Component name' + new ElementError({ + componentName: 'Component name', + identifier: 'variableName' }).message ).toBe('Component name: variableName not found') }) @@ -51,10 +59,11 @@ describe('errors', () => { const element = document.createElement('div') expect( - new ElementError('variableName', { + new ElementError({ componentName: 'Component name', element, - expectedType: 'HTMLAnchorElement' + expectedType: 'HTMLAnchorElement', + identifier: 'variableName' }).message ).toBe('Component name: variableName is not of type HTMLAnchorElement') }) diff --git a/packages/govuk-frontend/src/govuk/errors/index.mjs b/packages/govuk-frontend/src/govuk/errors/index.mjs index 5b55ccad31..4afbed6a1e 100644 --- a/packages/govuk-frontend/src/govuk/errors/index.mjs +++ b/packages/govuk-frontend/src/govuk/errors/index.mjs @@ -48,31 +48,39 @@ export class ElementError extends GOVUKFrontendError { name = 'ElementError' /** - * @param {string} identifier - An identifier that'll let the user understand which element has an error. This is whatever makes the most sense - * @param {object} options - Element error options - * @param {string} options.componentName - The name of the component throwing the error - * @param {Element | null} [options.element] - The element in error - * @param {string} [options.expectedType] - The type that was expected for the identifier - * @param {boolean} [options.identifierIsMessage] - Whether or not to process the identifier text. Use this to pass in custom messages + * + * @overload + * @param {string} messageOrOptions - The error message */ - constructor( - identifier, - { componentName, element, expectedType, identifierIsMessage } - ) { + + /** + * @overload + * @param {object} messageOrOptions - Element error options + * @param {string} messageOrOptions.componentName - The name of the component throwing the error + * @param {string} messageOrOptions.identifier - An identifier that'll let the user understand which element has an error. This is whatever makes the most sense + * @param {Element | null} [messageOrOptions.element] - The element in error + * @param {string} [messageOrOptions.expectedType] - The type that was expected for the identifier + * @param {boolean} [messageOrOptions.identifierIsMessage] - Whether or not to process the identifier text. Use this to pass in custom messages + */ + constructor(messageOrOptions) { + /* eslint-disable @typescript-eslint/no-unsafe-member-access */ + let message = '' - if (identifierIsMessage) { - message = identifier - } else { - message = `${identifier} not found` + if (typeof messageOrOptions === 'string') { + message = messageOrOptions + } else if (typeof messageOrOptions === 'object') { + message = `${messageOrOptions.componentName}: ${messageOrOptions.identifier} not found` // Otherwise check for type mismatch - if (element) { - message = `${identifier} is not of type ${ - expectedType || 'HTMLElement' - }` + if (messageOrOptions.element) { + message = `${messageOrOptions.componentName}: ${ + messageOrOptions.identifier + } is not of type ${messageOrOptions.expectedType || 'HTMLElement'}` } } - super(`${componentName}: ${message}`) + /* eslint-enable @typescript-eslint/no-unsafe-member-access */ + + super(`${message}`) } }