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 4c15ca2669..60e429b427 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,3 +1,4 @@ +import { MissingElementError } from '../../errors/index.mjs' import { GOVUKFrontendComponent } from '../../govuk-frontend-component.mjs' /** @@ -20,6 +21,7 @@ export class SkipLink extends GOVUKFrontendComponent { /** * @param {Element} $module - HTML element to use for skip link + * @throws {MissingElementError} If the element with the specified ID is not found */ constructor($module) { super() @@ -31,12 +33,17 @@ export class SkipLink extends GOVUKFrontendComponent { this.$module = $module // Check for linked element - const $linkedElement = this.getLinkedElement() - if (!$linkedElement) { - return + try { + const $linkedElement = this.getLinkedElement() + this.$linkedElement = $linkedElement + } catch (error) { + throw new MissingElementError( + `Skip link: ${ + error instanceof Error ? error.message : 'Linked element not found' + }` + ) } - this.$linkedElement = $linkedElement this.$module.addEventListener('click', () => this.focusLinkedElement()) } @@ -44,15 +51,25 @@ export class SkipLink extends GOVUKFrontendComponent { * Get linked element * * @private - * @returns {HTMLElement | null} $linkedElement - DOM element linked to from the skip link + * @throws {Error} If the "href" attribute does not contain a hash + * @throws {TypeError} If the element with the specified ID is not found + * @returns {HTMLElement} $linkedElement - DOM element linked to from the skip link */ getLinkedElement() { const linkedElementId = this.getFragmentFromUrl() if (!linkedElementId) { - return null + throw new Error(`$module "href" attribute does not contain a hash`) + } + + const linkedElement = document.getElementById(linkedElementId) + + if (!linkedElement) { + throw new TypeError( + `Linked element selector "#${linkedElementId}" not found` + ) } - return document.getElementById(linkedElementId) + return linkedElement } /** diff --git a/packages/govuk-frontend/src/govuk/components/skip-link/skip-link.test.js b/packages/govuk-frontend/src/govuk/components/skip-link/skip-link.test.js index ad065b1a65..d808ccaaf9 100644 --- a/packages/govuk-frontend/src/govuk/components/skip-link/skip-link.test.js +++ b/packages/govuk-frontend/src/govuk/components/skip-link/skip-link.test.js @@ -81,5 +81,34 @@ describe('Skip Link', () => { message: 'GOV.UK Frontend is not supported in this browser' }) }) + + it('throws when the linked element is missing', async () => { + await expect( + renderAndInitialise(page, 'skip-link', { + params: { + text: 'Skip to main content', + href: '#this-element-does-not-exist' + } + }) + ).rejects.toEqual({ + name: 'MissingElementError', + message: + 'Skip link: Linked element selector "#this-element-does-not-exist" not found' + }) + }) + + it('throws when the href does not contain a hash', async () => { + await expect( + renderAndInitialise(page, 'skip-link', { + params: { + text: 'Skip to main content', + href: 'this-element-does-not-exist' + } + }) + ).rejects.toEqual({ + name: 'MissingElementError', + message: 'Skip link: $module "href" attribute does not contain a hash' + }) + }) }) }) diff --git a/packages/govuk-frontend/src/govuk/errors/index.mjs b/packages/govuk-frontend/src/govuk/errors/index.mjs index 52636188a4..583d073d59 100644 --- a/packages/govuk-frontend/src/govuk/errors/index.mjs +++ b/packages/govuk-frontend/src/govuk/errors/index.mjs @@ -22,6 +22,13 @@ export class GOVUKFrontendError extends Error { name = 'GOVUKFrontendError' } +/** + * Indicates that a Component's required HTML element is missing + */ +export class MissingElementError extends GOVUKFrontendError { + name = 'MissingElementError' +} + /** * Indicates that GOV.UK Frontend is not supported */