diff --git a/packages/govuk-frontend/src/govuk/init.jsdom.test.mjs b/packages/govuk-frontend/src/govuk/init.jsdom.test.mjs index c837b24b84..12363440fd 100644 --- a/packages/govuk-frontend/src/govuk/init.jsdom.test.mjs +++ b/packages/govuk-frontend/src/govuk/init.jsdom.test.mjs @@ -307,6 +307,38 @@ describe('createAll', () => { } } + it('executes callback if specified', () => { + 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' }, + document, + 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 c909684568..6b51ea3137 100644 --- a/packages/govuk-frontend/src/govuk/init.mjs +++ b/packages/govuk-frontend/src/govuk/init.mjs @@ -54,6 +54,13 @@ function initAll(config) { }) } +/** + * @template {CompatibleClass} T + * @callback errorCallback + * @param {Error} error - Thrown error + * @param {ErrorContext} context - Object containing the element, component class and configuration + */ + /** * Create all instances of a specific component on the page * @@ -67,9 +74,10 @@ function initAll(config) { * @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 {errorCallback} [errorCallback] - scope of the document to search within * @returns {Array>} - array of instantiated components */ -function createAll(Component, config, $scope = document) { +function createAll(Component, config, $scope = document, errorCallback) { const $elements = $scope.querySelectorAll( `[data-module="${Component.moduleName}"]` ) @@ -90,7 +98,16 @@ function createAll(Component, config, $scope = document) { ? new Component($element, config) : new Component($element) } catch (error) { - console.log(error) + if (errorCallback) { + errorCallback(/** @type {Error} */ (error), { + element: $element, + component: Component, + config + }) + } else { + console.log(error) + } + return null } }) @@ -144,3 +161,11 @@ 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 + */