Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor ElementError to use component's moduleName instead of componentName #5334

Open
wants to merge 1 commit into
base: public-js-api
Choose a base branch
from

Conversation

patrickpatrickpatrick
Copy link
Contributor

@patrickpatrickpatrick patrickpatrickpatrick commented Sep 18, 2024

What

Refactor ElementError to use component's moduleName instead of componentName. New formatErrorMessage helper function implemented. Tests updated.

Why

To enable further refactoring. This will allow us to move functions that throw ElementError into GOVUKFrontendComponent.

Fixes #5324

@patrickpatrickpatrick patrickpatrickpatrick changed the base branch from main to public-js-api September 18, 2024 13:27
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-5334 September 18, 2024 13:27 Inactive
Copy link

github-actions bot commented Sep 18, 2024

📋 Stats

File sizes

File Size
dist/govuk-frontend-development.min.css 118.52 KiB
dist/govuk-frontend-development.min.js 44.69 KiB
packages/govuk-frontend/dist/govuk/all.bundle.js 94.64 KiB
packages/govuk-frontend/dist/govuk/all.bundle.mjs 88.87 KiB
packages/govuk-frontend/dist/govuk/all.mjs 1.1 KiB
packages/govuk-frontend/dist/govuk/govuk-frontend-component.mjs 1.29 KiB
packages/govuk-frontend/dist/govuk/govuk-frontend.min.css 118.5 KiB
packages/govuk-frontend/dist/govuk/govuk-frontend.min.js 44.68 KiB
packages/govuk-frontend/dist/govuk/i18n.mjs 5.55 KiB
packages/govuk-frontend/dist/govuk/init.mjs 6.9 KiB

Modules

File Size (bundled) Size (minified)
all.mjs 84.9 KiB 42.46 KiB
accordion.mjs 25.34 KiB 13.03 KiB
button.mjs 7.81 KiB 3.33 KiB
character-count.mjs 24.23 KiB 10.56 KiB
checkboxes.mjs 7.67 KiB 3.48 KiB
error-summary.mjs 9.72 KiB 4.1 KiB
exit-this-page.mjs 18.94 KiB 9.9 KiB
header.mjs 6.3 KiB 3.24 KiB
notification-banner.mjs 8.09 KiB 3.26 KiB
password-input.mjs 16.98 KiB 7.89 KiB
radios.mjs 6.66 KiB 3.02 KiB
service-navigation.mjs 6.29 KiB 3.33 KiB
skip-link.mjs 6.22 KiB 2.82 KiB
tabs.mjs 11.89 KiB 6.7 KiB

View stats and visualisations on the review app


Action run for 3cf0e10

Copy link

github-actions bot commented Sep 18, 2024

JavaScript changes to npm package

diff --git a/packages/govuk-frontend/dist/govuk/govuk-frontend.min.js b/packages/govuk-frontend/dist/govuk/govuk-frontend.min.js
index 19e286ee0..44bc03a33 100644
--- a/packages/govuk-frontend/dist/govuk/govuk-frontend.min.js
+++ b/packages/govuk-frontend/dist/govuk/govuk-frontend.min.js
@@ -106,12 +106,15 @@ class ElementError extends GOVUKFrontendError {
         let t = "string" == typeof e ? e : "";
         if ("object" == typeof e) {
             const {
-                componentName: n,
-                identifier: i,
-                element: s,
-                expectedType: o
+                component: n,
+                componentName: i,
+                identifier: s,
+                element: o,
+                expectedType: r
             } = e;
-            t = `${n}: ${i}`, t += s ? ` is not of type ${null!=o?o:"HTMLElement"}` : " not found"
+            void 0 !== i ? t = `${i}: ${s}` : (t = s, void 0 !== n && (t = function(e, t) {
+                return `${e.moduleName}: ${t}`
+            }(n, t))), t += o ? ` is not of type ${null!=r?r:"HTMLElement"}` : " not found"
         }
         super(t), this.name = "ElementError"
     }

Action run for 3cf0e10

Copy link

github-actions bot commented Sep 18, 2024

Other changes to npm package

diff --git a/packages/govuk-frontend/dist/govuk/all.bundle.js b/packages/govuk-frontend/dist/govuk/all.bundle.js
index 1c67edb22..8125b0187 100644
--- a/packages/govuk-frontend/dist/govuk/all.bundle.js
+++ b/packages/govuk-frontend/dist/govuk/all.bundle.js
@@ -159,6 +159,9 @@
   function isObject(option) {
     return !!option && typeof option === 'object' && !isArray(option);
   }
+  function formatErrorMessage(Component, message) {
+    return `${Component.moduleName}: ${message}`;
+  }
 
   /**
    * Schema for component config
@@ -182,6 +185,9 @@
    * @property {string[]} required - List of required config fields
    * @property {string} errorMessage - Error message when required config fields not provided
    */
+  /**
+   * @typedef {{new (...args: any[]): any, defaults?: object, moduleName: string}} CompatibleClass
+   */
 
   function normaliseDataset(Component, dataset) {
     const out = {};
@@ -225,12 +231,20 @@
       let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
       if (typeof messageOrOptions === 'object') {
         const {
+          component,
           componentName,
           identifier,
           element,
           expectedType
         } = messageOrOptions;
-        message = `${componentName}: ${identifier}`;
+        if (typeof componentName !== 'undefined') {
+          message = `${componentName}: ${identifier}`;
+        } else {
+          message = identifier;
+          if (typeof component !== 'undefined') {
+            message = formatErrorMessage(component, message);
+          }
+        }
         message += element ? ` is not of type ${expectedType != null ? expectedType : 'HTMLElement'}` : ' not found';
       }
       super(message);
diff --git a/packages/govuk-frontend/dist/govuk/all.bundle.mjs b/packages/govuk-frontend/dist/govuk/all.bundle.mjs
index f8c1c3ed1..0322b8575 100644
--- a/packages/govuk-frontend/dist/govuk/all.bundle.mjs
+++ b/packages/govuk-frontend/dist/govuk/all.bundle.mjs
@@ -153,6 +153,9 @@ function isArray(option) {
 function isObject(option) {
   return !!option && typeof option === 'object' && !isArray(option);
 }
+function formatErrorMessage(Component, message) {
+  return `${Component.moduleName}: ${message}`;
+}
 
 /**
  * Schema for component config
@@ -176,6 +179,9 @@ function isObject(option) {
  * @property {string[]} required - List of required config fields
  * @property {string} errorMessage - Error message when required config fields not provided
  */
+/**
+ * @typedef {{new (...args: any[]): any, defaults?: object, moduleName: string}} CompatibleClass
+ */
 
 function normaliseDataset(Component, dataset) {
   const out = {};
@@ -219,12 +225,20 @@ class ElementError extends GOVUKFrontendError {
     let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
     if (typeof messageOrOptions === 'object') {
       const {
+        component,
         componentName,
         identifier,
         element,
         expectedType
       } = messageOrOptions;
-      message = `${componentName}: ${identifier}`;
+      if (typeof componentName !== 'undefined') {
+        message = `${componentName}: ${identifier}`;
+      } else {
+        message = identifier;
+        if (typeof component !== 'undefined') {
+          message = formatErrorMessage(component, message);
+        }
+      }
       message += element ? ` is not of type ${expectedType != null ? expectedType : 'HTMLElement'}` : ' not found';
     }
     super(message);
diff --git a/packages/govuk-frontend/dist/govuk/common/index.mjs b/packages/govuk-frontend/dist/govuk/common/index.mjs
index e3aafbbb9..d4fc8a136 100644
--- a/packages/govuk-frontend/dist/govuk/common/index.mjs
+++ b/packages/govuk-frontend/dist/govuk/common/index.mjs
@@ -124,6 +124,9 @@ function isArray(option) {
 function isObject(option) {
   return !!option && typeof option === 'object' && !isArray(option);
 }
+function formatErrorMessage(Component, message) {
+  return `${Component.moduleName}: ${message}`;
+}
 
 /**
  * Schema for component config
@@ -147,6 +150,9 @@ function isObject(option) {
  * @property {string[]} required - List of required config fields
  * @property {string} errorMessage - Error message when required config fields not provided
  */
+/**
+ * @typedef {{new (...args: any[]): any, defaults?: object, moduleName: string}} CompatibleClass
+ */
 
-export { extractConfigByNamespace, getBreakpoint, getFragmentFromUrl, isInitialised, isSupported, mergeConfigs, setFocus, validateConfig };
+export { extractConfigByNamespace, formatErrorMessage, getBreakpoint, getFragmentFromUrl, isInitialised, isSupported, mergeConfigs, setFocus, validateConfig };
 //# sourceMappingURL=index.mjs.map
diff --git a/packages/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.js b/packages/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.js
index 0b96e7125..52cc3a9ec 100644
--- a/packages/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.js
+++ b/packages/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.js
@@ -99,6 +99,9 @@
   function isObject(option) {
     return !!option && typeof option === 'object' && !isArray(option);
   }
+  function formatErrorMessage(Component, message) {
+    return `${Component.moduleName}: ${message}`;
+  }
 
   /**
    * Schema for component config
@@ -122,6 +125,9 @@
    * @property {string[]} required - List of required config fields
    * @property {string} errorMessage - Error message when required config fields not provided
    */
+  /**
+   * @typedef {{new (...args: any[]): any, defaults?: object, moduleName: string}} CompatibleClass
+   */
 
   function normaliseDataset(Component, dataset) {
     const out = {};
@@ -159,12 +165,20 @@
       let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
       if (typeof messageOrOptions === 'object') {
         const {
+          component,
           componentName,
           identifier,
           element,
           expectedType
         } = messageOrOptions;
-        message = `${componentName}: ${identifier}`;
+        if (typeof componentName !== 'undefined') {
+          message = `${componentName}: ${identifier}`;
+        } else {
+          message = identifier;
+          if (typeof component !== 'undefined') {
+            message = formatErrorMessage(component, message);
+          }
+        }
         message += element ? ` is not of type ${expectedType != null ? expectedType : 'HTMLElement'}` : ' not found';
       }
       super(message);
diff --git a/packages/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.mjs b/packages/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.mjs
index 4c4530f84..3093c54bd 100644
--- a/packages/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.mjs
+++ b/packages/govuk-frontend/dist/govuk/components/accordion/accordion.bundle.mjs
@@ -93,6 +93,9 @@ function isArray(option) {
 function isObject(option) {
   return !!option && typeof option === 'object' && !isArray(option);
 }
+function formatErrorMessage(Component, message) {
+  return `${Component.moduleName}: ${message}`;
+}
 
 /**
  * Schema for component config
@@ -116,6 +119,9 @@ function isObject(option) {
  * @property {string[]} required - List of required config fields
  * @property {string} errorMessage - Error message when required config fields not provided
  */
+/**
+ * @typedef {{new (...args: any[]): any, defaults?: object, moduleName: string}} CompatibleClass
+ */
 
 function normaliseDataset(Component, dataset) {
   const out = {};
@@ -153,12 +159,20 @@ class ElementError extends GOVUKFrontendError {
     let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
     if (typeof messageOrOptions === 'object') {
       const {
+        component,
         componentName,
         identifier,
         element,
         expectedType
       } = messageOrOptions;
-      message = `${componentName}: ${identifier}`;
+      if (typeof componentName !== 'undefined') {
+        message = `${componentName}: ${identifier}`;
+      } else {
+        message = identifier;
+        if (typeof component !== 'undefined') {
+          message = formatErrorMessage(component, message);
+        }
+      }
       message += element ? ` is not of type ${expectedType != null ? expectedType : 'HTMLElement'}` : ' not found';
     }
     super(message);
diff --git a/packages/govuk-frontend/dist/govuk/components/button/button.bundle.js b/packages/govuk-frontend/dist/govuk/components/button/button.bundle.js
index 07424eb65..8fb32c10b 100644
--- a/packages/govuk-frontend/dist/govuk/components/button/button.bundle.js
+++ b/packages/govuk-frontend/dist/govuk/components/button/button.bundle.js
@@ -99,6 +99,9 @@
   function isObject(option) {
     return !!option && typeof option === 'object' && !isArray(option);
   }
+  function formatErrorMessage(Component, message) {
+    return `${Component.moduleName}: ${message}`;
+  }
 
   /**
    * Schema for component config
@@ -122,6 +125,9 @@
    * @property {string[]} required - List of required config fields
    * @property {string} errorMessage - Error message when required config fields not provided
    */
+  /**
+   * @typedef {{new (...args: any[]): any, defaults?: object, moduleName: string}} CompatibleClass
+   */
 
   function normaliseDataset(Component, dataset) {
     const out = {};
@@ -159,12 +165,20 @@
       let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
       if (typeof messageOrOptions === 'object') {
         const {
+          component,
           componentName,
           identifier,
           element,
           expectedType
         } = messageOrOptions;
-        message = `${componentName}: ${identifier}`;
+        if (typeof componentName !== 'undefined') {
+          message = `${componentName}: ${identifier}`;
+        } else {
+          message = identifier;
+          if (typeof component !== 'undefined') {
+            message = formatErrorMessage(component, message);
+          }
+        }
         message += element ? ` is not of type ${expectedType != null ? expectedType : 'HTMLElement'}` : ' not found';
       }
       super(message);
diff --git a/packages/govuk-frontend/dist/govuk/components/button/button.bundle.mjs b/packages/govuk-frontend/dist/govuk/components/button/button.bundle.mjs
index d9412f41d..a76c4c15c 100644
--- a/packages/govuk-frontend/dist/govuk/components/button/button.bundle.mjs
+++ b/packages/govuk-frontend/dist/govuk/components/button/button.bundle.mjs
@@ -93,6 +93,9 @@ function isArray(option) {
 function isObject(option) {
   return !!option && typeof option === 'object' && !isArray(option);
 }
+function formatErrorMessage(Component, message) {
+  return `${Component.moduleName}: ${message}`;
+}
 
 /**
  * Schema for component config
@@ -116,6 +119,9 @@ function isObject(option) {
  * @property {string[]} required - List of required config fields
  * @property {string} errorMessage - Error message when required config fields not provided
  */
+/**
+ * @typedef {{new (...args: any[]): any, defaults?: object, moduleName: string}} CompatibleClass
+ */
 
 function normaliseDataset(Component, dataset) {
   const out = {};
@@ -153,12 +159,20 @@ class ElementError extends GOVUKFrontendError {
     let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
     if (typeof messageOrOptions === 'object') {
       const {
+        component,
         componentName,
         identifier,
         element,
         expectedType
       } = messageOrOptions;
-      message = `${componentName}: ${identifier}`;
+      if (typeof componentName !== 'undefined') {
+        message = `${componentName}: ${identifier}`;
+      } else {
+        message = identifier;
+        if (typeof component !== 'undefined') {
+          message = formatErrorMessage(component, message);
+        }
+      }
       message += element ? ` is not of type ${expectedType != null ? expectedType : 'HTMLElement'}` : ' not found';
     }
     super(message);
diff --git a/packages/govuk-frontend/dist/govuk/components/character-count/character-count.bundle.js b/packages/govuk-frontend/dist/govuk/components/character-count/character-count.bundle.js
index 76ce4eea5..9449a6140 100644
--- a/packages/govuk-frontend/dist/govuk/components/character-count/character-count.bundle.js
+++ b/packages/govuk-frontend/dist/govuk/components/character-count/character-count.bundle.js
@@ -124,6 +124,9 @@
   function isObject(option) {
     return !!option && typeof option === 'object' && !isArray(option);
   }
+  function formatErrorMessage(Component, message) {
+    return `${Component.moduleName}: ${message}`;
+  }
 
   /**
    * Schema for component config
@@ -147,6 +150,9 @@
    * @property {string[]} required - List of required config fields
    * @property {string} errorMessage - Error message when required config fields not provided
    */
+  /**
+   * @typedef {{new (...args: any[]): any, defaults?: object, moduleName: string}} CompatibleClass
+   */
 
   function normaliseDataset(Component, dataset) {
     const out = {};
@@ -190,12 +196,20 @@
       let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
       if (typeof messageOrOptions === 'object') {
         const {
+          component,
           componentName,
           identifier,
           element,
           expectedType
         } = messageOrOptions;
-        message = `${componentName}: ${identifier}`;
+        if (typeof componentName !== 'undefined') {
+          message = `${componentName}: ${identifier}`;
+        } else {
+          message = identifier;
+          if (typeof component !== 'undefined') {
+            message = formatErrorMessage(component, message);
+          }
+        }
         message += element ? ` is not of type ${expectedType != null ? expectedType : 'HTMLElement'}` : ' not found';
       }
       super(message);
diff --git a/packages/govuk-frontend/dist/govuk/components/character-count/character-count.bundle.mjs b/packages/govuk-frontend/dist/govuk/components/character-count/character-count.bundle.mjs
index 34ee63460..89675e4ff 100644
--- a/packages/govuk-frontend/dist/govuk/components/character-count/character-count.bundle.mjs
+++ b/packages/govuk-frontend/dist/govuk/components/character-count/character-count.bundle.mjs
@@ -118,6 +118,9 @@ function isArray(option) {
 function isObject(option) {
   return !!option && typeof option === 'object' && !isArray(option);
 }
+function formatErrorMessage(Component, message) {
+  return `${Component.moduleName}: ${message}`;
+}
 
 /**
  * Schema for component config
@@ -141,6 +144,9 @@ function isObject(option) {
  * @property {string[]} required - List of required config fields
  * @property {string} errorMessage - Error message when required config fields not provided
  */
+/**
+ * @typedef {{new (...args: any[]): any, defaults?: object, moduleName: string}} CompatibleClass
+ */
 
 function normaliseDataset(Component, dataset) {
   const out = {};
@@ -184,12 +190,20 @@ class ElementError extends GOVUKFrontendError {
     let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
     if (typeof messageOrOptions === 'object') {
       const {
+        component,
         componentName,
         identifier,
         element,
         expectedType
       } = messageOrOptions;
-      message = `${componentName}: ${identifier}`;
+      if (typeof componentName !== 'undefined') {
+        message = `${componentName}: ${identifier}`;
+      } else {
+        message = identifier;
+        if (typeof component !== 'undefined') {
+          message = formatErrorMessage(component, message);
+        }
+      }
       message += element ? ` is not of type ${expectedType != null ? expectedType : 'HTMLElement'}` : ' not found';
     }
     super(message);
diff --git a/packages/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.bundle.js b/packages/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.bundle.js
index b77f9b743..fcc42ee94 100644
--- a/packages/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.bundle.js
+++ b/packages/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.bundle.js
@@ -4,6 +4,55 @@
   (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.GOVUKFrontend = {}));
 })(this, (function (exports) { 'use strict';
 
+  function isInitialised($module, moduleName) {
+    return $module instanceof HTMLElement && $module.hasAttribute(`data-${moduleName}-init`);
+  }
+
+  /**
+   * 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.
+   *
+   * @param {HTMLElement | null} [$scope] - (internal) `<body>` HTML element checked for browser support
+   * @returns {boolean} Whether GOV.UK Frontend is supported on this page
+   */
+  function isSupported($scope = document.body) {
+    if (!$scope) {
+      return false;
+    }
+    return $scope.classList.contains('govuk-frontend-supported');
+  }
+  function formatErrorMessage(Component, message) {
+    return `${Component.moduleName}: ${message}`;
+  }
+
+  /**
+   * Schema for component config
+   *
+   * @typedef {object} Schema
+   * @property {{ [field: string]: SchemaProperty | undefined }} properties - Schema properties
+   * @property {SchemaCondition[]} [anyOf] - List of schema conditions
+   */
+
+  /**
+   * Schema property for component config
+   *
+   * @typedef {object} SchemaProperty
+   * @property {'string' | 'boolean' | 'number' | 'object'} type - Property type
+   */
+
+  /**
+   * Schema condition for component config
+   *
+   * @typedef {object} SchemaCondition
+   * @property {string[]} required - List of required config fields
+   * @property {string} errorMessage - Error message when required config fields not provided
+   */
+  /**
+   * @typedef {{new (...args: any[]): any, defaults?: object, moduleName: string}} CompatibleClass
+   */
+
   class GOVUKFrontendError extends Error {
     constructor(...args) {
       super(...args);
@@ -27,12 +76,20 @@
       let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
       if (typeof messageOrOptions === 'object') {
         const {
+          component,
           componentName,
           identifier,
           element,
           expectedType
         } = messageOrOptions;
-        message = `${componentName}: ${identifier}`;
+        if (typeof componentName !== 'undefined') {
+          message = `${componentName}: ${identifier}`;
+        } else {
+          message = identifier;
+          if (typeof component !== 'undefined') {
+            message = formatErrorMessage(component, message);
+          }
+        }
         message += element ? ` is not of type ${expectedType != null ? expectedType : 'HTMLElement'}` : ' not found';
       }
       super(message);
@@ -50,49 +107,6 @@
     }
   }
 
-  function isInitialised($module, moduleName) {
-    return $module instanceof HTMLElement && $module.hasAttribute(`data-${moduleName}-init`);
-  }
-
-  /**
-   * 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.
-   *
-   * @param {HTMLElement | null} [$scope] - (internal) `<body>` HTML element checked for browser support
-   * @returns {boolean} Whether GOV.UK Frontend is supported on this page
-   */
-  function isSupported($scope = document.body) {
-    if (!$scope) {
-      return false;
-    }
-    return $scope.classList.contains('govuk-frontend-supported');
-  }
-
-  /**
-   * Schema for component config
-   *
-   * @typedef {object} Schema
-   * @property {{ [field: string]: SchemaProperty | undefined }} properties - Schema properties
-   * @property {SchemaCondition[]} [anyOf] - List of schema conditions
-   */
-
-  /**
-   * Schema property for component config
-   *
-   * @typedef {object} SchemaProperty
-   * @property {'string' | 'boolean' | 'number' | 'object'} type - Property type
-   */
-
-  /**
-   * Schema condition for component config
-   *
-   * @typedef {object} SchemaCondition
-   * @property {string[]} required - List of required config fields
-   * @property {string} errorMessage - Error message when required config fields not provided
-   */
-
   class GOVUKFrontendComponent {
     constructor($module) {
       this.checkSupport();
diff --git a/packages/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.bundle.mjs b/packages/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.bundle.mjs
index e3c1fd925..39e6af0e5 100644
--- a/packages/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.bundle.mjs
+++ b/packages/govuk-frontend/dist/govuk/components/checkboxes/checkboxes.bundle.mjs
@@ -1,3 +1,52 @@
+function isInitialised($module, moduleName) {
+  return $module instanceof HTMLElement && $module.hasAttribute(`data-${moduleName}-init`);
+}
+
+/**
+ * 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.
+ *
+ * @param {HTMLElement | null} [$scope] - (internal) `<body>` HTML element checked for browser support
+ * @returns {boolean} Whether GOV.UK Frontend is supported on this page
+ */
+function isSupported($scope = document.body) {
+  if (!$scope) {
+    return false;
+  }
+  return $scope.classList.contains('govuk-frontend-supported');
+}
+function formatErrorMessage(Component, message) {
+  return `${Component.moduleName}: ${message}`;
+}
+
+/**
+ * Schema for component config
+ *
+ * @typedef {object} Schema
+ * @property {{ [field: string]: SchemaProperty | undefined }} properties - Schema properties
+ * @property {SchemaCondition[]} [anyOf] - List of schema conditions
+ */
+
+/**
+ * Schema property for component config
+ *
+ * @typedef {object} SchemaProperty
+ * @property {'string' | 'boolean' | 'number' | 'object'} type - Property type
+ */
+
+/**
+ * Schema condition for component config
+ *
+ * @typedef {object} SchemaCondition
+ * @property {string[]} required - List of required config fields
+ * @property {string} errorMessage - Error message when required config fields not provided
+ */
+/**
+ * @typedef {{new (...args: any[]): any, defaults?: object, moduleName: string}} CompatibleClass
+ */
+
 class GOVUKFrontendError extends Error {
   constructor(...args) {
     super(...args);
@@ -21,12 +70,20 @@ class ElementError extends GOVUKFrontendError {
     let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
     if (typeof messageOrOptions === 'object') {
       const {
+        component,
         componentName,
         identifier,
         element,
         expectedType
       } = messageOrOptions;
-      message = `${componentName}: ${identifier}`;
+      if (typeof componentName !== 'undefined') {
+        message = `${componentName}: ${identifier}`;
+      } else {
+        message = identifier;
+        if (typeof component !== 'undefined') {
+          message = formatErrorMessage(component, message);
+        }
+      }
       message += element ? ` is not of type ${expectedType != null ? expectedType : 'HTMLElement'}` : ' not found';
     }
     super(message);
@@ -44,49 +101,6 @@ class InitError extends GOVUKFrontendError {
   }
 }
 
-function isInitialised($module, moduleName) {
-  return $module instanceof HTMLElement && $module.hasAttribute(`data-${moduleName}-init`);
-}
-
-/**
- * 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.
- *
- * @param {HTMLElement | null} [$scope] - (internal) `<body>` HTML element checked for browser support
- * @returns {boolean} Whether GOV.UK Frontend is supported on this page
- */
-function isSupported($scope = document.body) {
-  if (!$scope) {
-    return false;
-  }
-  return $scope.classList.contains('govuk-frontend-supported');
-}
-
-/**
- * Schema for component config
- *
- * @typedef {object} Schema
- * @property {{ [field: string]: SchemaProperty | undefined }} properties - Schema properties
- * @property {SchemaCondition[]} [anyOf] - List of schema conditions
- */
-
-/**
- * Schema property for component config
- *
- * @typedef {object} SchemaProperty
- * @property {'string' | 'boolean' | 'number' | 'object'} type - Property type
- */
-
-/**
- * Schema condition for component config
- *
- * @typedef {object} SchemaCondition
- * @property {string[]} required - List of required config fields
- * @property {string} errorMessage - Error message when required config fields not provided
- */
-
 class GOVUKFrontendComponent {
   constructor($module) {
     this.checkSupport();
diff --git a/packages/govuk-frontend/dist/govuk/components/error-summary/error-summary.bundle.js b/packages/govuk-frontend/dist/govuk/components/error-summary/error-summary.bundle.js
index 99c71b5ff..825b67812 100644
--- a/packages/govuk-frontend/dist/govuk/components/error-summary/error-summary.bundle.js
+++ b/packages/govuk-frontend/dist/govuk/components/error-summary/error-summary.bundle.js
@@ -129,6 +129,9 @@
   function isObject(option) {
     return !!option && typeof option === 'object' && !isArray(option);
   }
+  function formatErrorMessage(Component, message) {
+    return `${Component.moduleName}: ${message}`;
+  }
 
   /**
    * Schema for component config
@@ -152,6 +155,9 @@
    * @property {string[]} required - List of required config fields
    * @property {string} errorMessage - Error message when required config fields not provided
    */
+  /**
+   * @typedef {{new (...args: any[]): any, defaults?: object, moduleName: string}} CompatibleClass
+   */
 
   function normaliseDataset(Component, dataset) {
     const out = {};
@@ -189,12 +195,20 @@
       let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
       if (typeof messageOrOptions === 'object') {
         const {
+          component,
           componentName,
           identifier,
           element,
           expectedType
         } = messageOrOptions;
-        message = `${componentName}: ${identifier}`;
+        if (typeof componentName !== 'undefined') {
+          message = `${componentName}: ${identifier}`;
+        } else {
+          message = identifier;
+          if (typeof component !== 'undefined') {
+            message = formatErrorMessage(component, message);
+          }
+        }
         message += element ? ` is not of type ${expectedType != null ? expectedType : 'HTMLElement'}` : ' not found';
       }
       super(message);
diff --git a/packages/govuk-frontend/dist/govuk/components/error-summary/error-summary.bundle.mjs b/packages/govuk-frontend/dist/govuk/components/error-summary/error-summary.bundle.mjs
index 8600154f1..6b09a2a80 100644
--- a/packages/govuk-frontend/dist/govuk/components/error-summary/error-summary.bundle.mjs
+++ b/packages/govuk-frontend/dist/govuk/components/error-summary/error-summary.bundle.mjs
@@ -123,6 +123,9 @@ function isArray(option) {
 function isObject(option) {
   return !!option && typeof option === 'object' && !isArray(option);
 }
+function formatErrorMessage(Component, message) {
+  return `${Component.moduleName}: ${message}`;
+}
 
 /**
  * Schema for component config
@@ -146,6 +149,9 @@ function isObject(option) {
  * @property {string[]} required - List of required config fields
  * @property {string} errorMessage - Error message when required config fields not provided
  */
+/**
+ * @typedef {{new (...args: any[]): any, defaults?: object, moduleName: string}} CompatibleClass
+ */
 
 function normaliseDataset(Component, dataset) {
   const out = {};
@@ -183,12 +189,20 @@ class ElementError extends GOVUKFrontendError {
     let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
     if (typeof messageOrOptions === 'object') {
       const {
+        component,
         componentName,
         identifier,
         element,
         expectedType
       } = messageOrOptions;
-      message = `${componentName}: ${identifier}`;
+      if (typeof componentName !== 'undefined') {
+        message = `${componentName}: ${identifier}`;
+      } else {
+        message = identifier;
+        if (typeof component !== 'undefined') {
+          message = formatErrorMessage(component, message);
+        }
+      }
       message += element ? ` is not of type ${expectedType != null ? expectedType : 'HTMLElement'}` : ' not found';
     }
     super(message);
diff --git a/packages/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.js b/packages/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.js
index fb4533397..132b8a29a 100644
--- a/packages/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.js
+++ b/packages/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.js
@@ -99,6 +99,9 @@
   function isObject(option) {
     return !!option && typeof option === 'object' && !isArray(option);
   }
+  function formatErrorMessage(Component, message) {
+    return `${Component.moduleName}: ${message}`;
+  }
 
   /**
    * Schema for component config
@@ -122,6 +125,9 @@
    * @property {string[]} required - List of required config fields
    * @property {string} errorMessage - Error message when required config fields not provided
    */
+  /**
+   * @typedef {{new (...args: any[]): any, defaults?: object, moduleName: string}} CompatibleClass
+   */
 
   function normaliseDataset(Component, dataset) {
     const out = {};
@@ -159,12 +165,20 @@
       let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
       if (typeof messageOrOptions === 'object') {
         const {
+          component,
           componentName,
           identifier,
           element,
           expectedType
         } = messageOrOptions;
-        message = `${componentName}: ${identifier}`;
+        if (typeof componentName !== 'undefined') {
+          message = `${componentName}: ${identifier}`;
+        } else {
+          message = identifier;
+          if (typeof component !== 'undefined') {
+            message = formatErrorMessage(component, message);
+          }
+        }
         message += element ? ` is not of type ${expectedType != null ? expectedType : 'HTMLElement'}` : ' not found';
       }
       super(message);
diff --git a/packages/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.mjs b/packages/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.mjs
index 714daefad..ecb8ffc5f 100644
--- a/packages/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.mjs
+++ b/packages/govuk-frontend/dist/govuk/components/exit-this-page/exit-this-page.bundle.mjs
@@ -93,6 +93,9 @@ function isArray(option) {
 function isObject(option) {
   return !!option && typeof option === 'object' && !isArray(option);
 }
+function formatErrorMessage(Component, message) {
+  return `${Component.moduleName}: ${message}`;
+}
 
 /**
  * Schema for component config
@@ -116,6 +119,9 @@ function isObject(option) {
  * @property {string[]} required - List of required config fields
  * @property {string} errorMessage - Error message when required config fields not provided
  */
+/**
+ * @typedef {{new (...args: any[]): any, defaults?: object, moduleName: string}} CompatibleClass
+ */
 
 function normaliseDataset(Component, dataset) {
   const out = {};
@@ -153,12 +159,20 @@ class ElementError extends GOVUKFrontendError {
     let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
     if (typeof messageOrOptions === 'object') {
       const {
+        component,
         componentName,
         identifier,
         element,
         expectedType
       } = messageOrOptions;
-      message = `${componentName}: ${identifier}`;
+      if (typeof componentName !== 'undefined') {
+        message = `${componentName}: ${identifier}`;
+      } else {
+        message = identifier;
+        if (typeof component !== 'undefined') {
+          message = formatErrorMessage(component, message);
+        }
+      }
       message += element ? ` is not of type ${expectedType != null ? expectedType : 'HTMLElement'}` : ' not found';
     }
     super(message);
diff --git a/packages/govuk-frontend/dist/govuk/components/header/header.bundle.js b/packages/govuk-frontend/dist/govuk/components/header/header.bundle.js
index 5bbcbe1be..27135acff 100644
--- a/packages/govuk-frontend/dist/govuk/components/header/header.bundle.js
+++ b/packages/govuk-frontend/dist/govuk/components/header/header.bundle.js
@@ -31,6 +31,9 @@
     }
     return $scope.classList.contains('govuk-frontend-supported');
   }
+  function formatErrorMessage(Component, message) {
+    return `${Component.moduleName}: ${message}`;
+  }
 
   /**
    * Schema for component config
@@ -54,6 +57,9 @@
    * @property {string[]} required - List of required config fields
    * @property {string} errorMessage - Error message when required config fields not provided
    */
+  /**
+   * @typedef {{new (...args: any[]): any, defaults?: object, moduleName: string}} CompatibleClass
+   */
 
   class GOVUKFrontendError extends Error {
     constructor(...args) {
@@ -78,12 +84,20 @@
       let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
       if (typeof messageOrOptions === 'object') {
         const {
+          component,
           componentName,
           identifier,
           element,
           expectedType
         } = messageOrOptions;
-        message = `${componentName}: ${identifier}`;
+        if (typeof componentName !== 'undefined') {
+          message = `${componentName}: ${identifier}`;
+        } else {
+          message = identifier;
+          if (typeof component !== 'undefined') {
+            message = formatErrorMessage(component, message);
+          }
+        }
         message += element ? ` is not of type ${expectedType != null ? expectedType : 'HTMLElement'}` : ' not found';
       }
       super(message);
diff --git a/packages/govuk-frontend/dist/govuk/components/header/header.bundle.mjs b/packages/govuk-frontend/dist/govuk/components/header/header.bundle.mjs
index 224c11925..300607e67 100644
--- a/packages/govuk-frontend/dist/govuk/components/header/header.bundle.mjs
+++ b/packages/govuk-frontend/dist/govuk/components/header/header.bundle.mjs
@@ -25,6 +25,9 @@ function isSupported($scope = document.body) {
   }
   return $scope.classList.contains('govuk-frontend-supported');
 }
+function formatErrorMessage(Component, message) {
+  return `${Component.moduleName}: ${message}`;
+}
 
 /**
  * Schema for component config
@@ -48,6 +51,9 @@ function isSupported($scope = document.body) {
  * @property {string[]} required - List of required config fields
  * @property {string} errorMessage - Error message when required config fields not provided
  */
+/**
+ * @typedef {{new (...args: any[]): any, defaults?: object, moduleName: string}} CompatibleClass
+ */
 
 class GOVUKFrontendError extends Error {
   constructor(...args) {
@@ -72,12 +78,20 @@ class ElementError extends GOVUKFrontendError {
     let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
     if (typeof messageOrOptions === 'object') {
       const {
+        component,
         componentName,
         identifier,
         element,
         expectedType
       } = messageOrOptions;
-      message = `${componentName}: ${identifier}`;
+      if (typeof componentName !== 'undefined') {
+        message = `${componentName}: ${identifier}`;
+      } else {
+        message = identifier;
+        if (typeof component !== 'undefined') {
+          message = formatErrorMessage(component, message);
+        }
+      }
       message += element ? ` is not of type ${expectedType != null ? expectedType : 'HTMLElement'}` : ' not found';
     }
     super(message);
diff --git a/packages/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.bundle.js b/packages/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.bundle.js
index 6a44dd11c..559a4e2f8 100644
--- a/packages/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.bundle.js
+++ b/packages/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.bundle.js
@@ -123,6 +123,9 @@
   function isObject(option) {
     return !!option && typeof option === 'object' && !isArray(option);
   }
+  function formatErrorMessage(Component, message) {
+    return `${Component.moduleName}: ${message}`;
+  }
 
   /**
    * Schema for component config
@@ -146,6 +149,9 @@
    * @property {string[]} required - List of required config fields
    * @property {string} errorMessage - Error message when required config fields not provided
    */
+  /**
+   * @typedef {{new (...args: any[]): any, defaults?: object, moduleName: string}} CompatibleClass
+   */
 
   function normaliseDataset(Component, dataset) {
     const out = {};
@@ -183,12 +189,20 @@
       let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
       if (typeof messageOrOptions === 'object') {
         const {
+          component,
           componentName,
           identifier,
           element,
           expectedType
         } = messageOrOptions;
-        message = `${componentName}: ${identifier}`;
+        if (typeof componentName !== 'undefined') {
+          message = `${componentName}: ${identifier}`;
+        } else {
+          message = identifier;
+          if (typeof component !== 'undefined') {
+            message = formatErrorMessage(component, message);
+          }
+        }
         message += element ? ` is not of type ${expectedType != null ? expectedType : 'HTMLElement'}` : ' not found';
       }
       super(message);
diff --git a/packages/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.bundle.mjs b/packages/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.bundle.mjs
index 7b0c12d39..15c771bb2 100644
--- a/packages/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.bundle.mjs
+++ b/packages/govuk-frontend/dist/govuk/components/notification-banner/notification-banner.bundle.mjs
@@ -117,6 +117,9 @@ function isArray(option) {
 function isObject(option) {
   return !!option && typeof option === 'object' && !isArray(option);
 }
+function formatErrorMessage(Component, message) {
+  return `${Component.moduleName}: ${message}`;
+}
 
 /**
  * Schema for component config
@@ -140,6 +143,9 @@ function isObject(option) {
  * @property {string[]} required - List of required config fields
  * @property {string} errorMessage - Error message when required config fields not provided
  */
+/**
+ * @typedef {{new (...args: any[]): any, defaults?: object, moduleName: string}} CompatibleClass
+ */
 
 function normaliseDataset(Component, dataset) {
   const out = {};
@@ -177,12 +183,20 @@ class ElementError extends GOVUKFrontendError {
     let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
     if (typeof messageOrOptions === 'object') {
       const {
+        component,
         componentName,
         identifier,
         element,
         expectedType
       } = messageOrOptions;
-      message = `${componentName}: ${identifier}`;
+      if (typeof componentName !== 'undefined') {
+        message = `${componentName}: ${identifier}`;
+      } else {
+        message = identifier;
+        if (typeof component !== 'undefined') {
+          message = formatErrorMessage(component, message);
+        }
+      }
       message += element ? ` is not of type ${expectedType != null ? expectedType : 'HTMLElement'}` : ' not found';
     }
     super(message);
diff --git a/packages/govuk-frontend/dist/govuk/components/password-input/password-input.bundle.js b/packages/govuk-frontend/dist/govuk/components/password-input/password-input.bundle.js
index 38adb9a52..31458e2fa 100644
--- a/packages/govuk-frontend/dist/govuk/components/password-input/password-input.bundle.js
+++ b/packages/govuk-frontend/dist/govuk/components/password-input/password-input.bundle.js
@@ -104,6 +104,9 @@
   function isObject(option) {
     return !!option && typeof option === 'object' && !isArray(option);
   }
+  function formatErrorMessage(Component, message) {
+    return `${Component.moduleName}: ${message}`;
+  }
 
   /**
    * Schema for component config
@@ -127,6 +130,9 @@
    * @property {string[]} required - List of required config fields
    * @property {string} errorMessage - Error message when required config fields not provided
    */
+  /**
+   * @typedef {{new (...args: any[]): any, defaults?: object, moduleName: string}} CompatibleClass
+   */
 
   function normaliseDataset(Component, dataset) {
     const out = {};
@@ -164,12 +170,20 @@
       let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
       if (typeof messageOrOptions === 'object') {
         const {
+          component,
           componentName,
           identifier,
           element,
           expectedType
         } = messageOrOptions;
-        message = `${componentName}: ${identifier}`;
+        if (typeof componentName !== 'undefined') {
+          message = `${componentName}: ${identifier}`;
+        } else {
+          message = identifier;
+          if (typeof component !== 'undefined') {
+            message = formatErrorMessage(component, message);
+          }
+        }
         message += element ? ` is not of type ${expectedType != null ? expectedType : 'HTMLElement'}` : ' not found';
       }
       super(message);
diff --git a/packages/govuk-frontend/dist/govuk/components/password-input/password-input.bundle.mjs b/packages/govuk-frontend/dist/govuk/components/password-input/password-input.bundle.mjs
index a0465d235..e962bc1a7 100644
--- a/packages/govuk-frontend/dist/govuk/components/password-input/password-input.bundle.mjs
+++ b/packages/govuk-frontend/dist/govuk/components/password-input/password-input.bundle.mjs
@@ -98,6 +98,9 @@ function isArray(option) {
 function isObject(option) {
   return !!option && typeof option === 'object' && !isArray(option);
 }
+function formatErrorMessage(Component, message) {
+  return `${Component.moduleName}: ${message}`;
+}
 
 /**
  * Schema for component config
@@ -121,6 +124,9 @@ function isObject(option) {
  * @property {string[]} required - List of required config fields
  * @property {string} errorMessage - Error message when required config fields not provided
  */
+/**
+ * @typedef {{new (...args: any[]): any, defaults?: object, moduleName: string}} CompatibleClass
+ */
 
 function normaliseDataset(Component, dataset) {
   const out = {};
@@ -158,12 +164,20 @@ class ElementError extends GOVUKFrontendError {
     let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
     if (typeof messageOrOptions === 'object') {
       const {
+        component,
         componentName,
         identifier,
         element,
         expectedType
       } = messageOrOptions;
-      message = `${componentName}: ${identifier}`;
+      if (typeof componentName !== 'undefined') {
+        message = `${componentName}: ${identifier}`;
+      } else {
+        message = identifier;
+        if (typeof component !== 'undefined') {
+          message = formatErrorMessage(component, message);
+        }
+      }
       message += element ? ` is not of type ${expectedType != null ? expectedType : 'HTMLElement'}` : ' not found';
     }
     super(message);
diff --git a/packages/govuk-frontend/dist/govuk/components/radios/radios.bundle.js b/packages/govuk-frontend/dist/govuk/components/radios/radios.bundle.js
index 7444eea07..3191f9d96 100644
--- a/packages/govuk-frontend/dist/govuk/components/radios/radios.bundle.js
+++ b/packages/govuk-frontend/dist/govuk/components/radios/radios.bundle.js
@@ -4,6 +4,55 @@
   (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.GOVUKFrontend = {}));
 })(this, (function (exports) { 'use strict';
 
+  function isInitialised($module, moduleName) {
+    return $module instanceof HTMLElement && $module.hasAttribute(`data-${moduleName}-init`);
+  }
+
+  /**
+   * 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.
+   *
+   * @param {HTMLElement | null} [$scope] - (internal) `<body>` HTML element checked for browser support
+   * @returns {boolean} Whether GOV.UK Frontend is supported on this page
+   */
+  function isSupported($scope = document.body) {
+    if (!$scope) {
+      return false;
+    }
+    return $scope.classList.contains('govuk-frontend-supported');
+  }
+  function formatErrorMessage(Component, message) {
+    return `${Component.moduleName}: ${message}`;
+  }
+
+  /**
+   * Schema for component config
+   *
+   * @typedef {object} Schema
+   * @property {{ [field: string]: SchemaProperty | undefined }} properties - Schema properties
+   * @property {SchemaCondition[]} [anyOf] - List of schema conditions
+   */
+
+  /**
+   * Schema property for component config
+   *
+   * @typedef {object} SchemaProperty
+   * @property {'string' | 'boolean' | 'number' | 'object'} type - Property type
+   */
+
+  /**
+   * Schema condition for component config
+   *
+   * @typedef {object} SchemaCondition
+   * @property {string[]} required - List of required config fields
+   * @property {string} errorMessage - Error message when required config fields not provided
+   */
+  /**
+   * @typedef {{new (...args: any[]): any, defaults?: object, moduleName: string}} CompatibleClass
+   */
+
   class GOVUKFrontendError extends Error {
     constructor(...args) {
       super(...args);
@@ -27,12 +76,20 @@
       let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
       if (typeof messageOrOptions === 'object') {
         const {
+          component,
           componentName,
           identifier,
           element,
           expectedType
         } = messageOrOptions;
-        message = `${componentName}: ${identifier}`;
+        if (typeof componentName !== 'undefined') {
+          message = `${componentName}: ${identifier}`;
+        } else {
+          message = identifier;
+          if (typeof component !== 'undefined') {
+            message = formatErrorMessage(component, message);
+          }
+        }
         message += element ? ` is not of type ${expectedType != null ? expectedType : 'HTMLElement'}` : ' not found';
       }
       super(message);
@@ -50,49 +107,6 @@
     }
   }
 
-  function isInitialised($module, moduleName) {
-    return $module instanceof HTMLElement && $module.hasAttribute(`data-${moduleName}-init`);
-  }
-
-  /**
-   * 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.
-   *
-   * @param {HTMLElement | null} [$scope] - (internal) `<body>` HTML element checked for browser support
-   * @returns {boolean} Whether GOV.UK Frontend is supported on this page
-   */
-  function isSupported($scope = document.body) {
-    if (!$scope) {
-      return false;
-    }
-    return $scope.classList.contains('govuk-frontend-supported');
-  }
-
-  /**
-   * Schema for component config
-   *
-   * @typedef {object} Schema
-   * @property {{ [field: string]: SchemaProperty | undefined }} properties - Schema properties
-   * @property {SchemaCondition[]} [anyOf] - List of schema conditions
-   */
-
-  /**
-   * Schema property for component config
-   *
-   * @typedef {object} SchemaProperty
-   * @property {'string' | 'boolean' | 'number' | 'object'} type - Property type
-   */
-
-  /**
-   * Schema condition for component config
-   *
-   * @typedef {object} SchemaCondition
-   * @property {string[]} required - List of required config fields
-   * @property {string} errorMessage - Error message when required config fields not provided
-   */
-
   class GOVUKFrontendComponent {
     constructor($module) {
       this.checkSupport();
diff --git a/packages/govuk-frontend/dist/govuk/components/radios/radios.bundle.mjs b/packages/govuk-frontend/dist/govuk/components/radios/radios.bundle.mjs
index 1fd810d35..ee2564abd 100644
--- a/packages/govuk-frontend/dist/govuk/components/radios/radios.bundle.mjs
+++ b/packages/govuk-frontend/dist/govuk/components/radios/radios.bundle.mjs
@@ -1,3 +1,52 @@
+function isInitialised($module, moduleName) {
+  return $module instanceof HTMLElement && $module.hasAttribute(`data-${moduleName}-init`);
+}
+
+/**
+ * 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.
+ *
+ * @param {HTMLElement | null} [$scope] - (internal) `<body>` HTML element checked for browser support
+ * @returns {boolean} Whether GOV.UK Frontend is supported on this page
+ */
+function isSupported($scope = document.body) {
+  if (!$scope) {
+    return false;
+  }
+  return $scope.classList.contains('govuk-frontend-supported');
+}
+function formatErrorMessage(Component, message) {
+  return `${Component.moduleName}: ${message}`;
+}
+
+/**
+ * Schema for component config
+ *
+ * @typedef {object} Schema
+ * @property {{ [field: string]: SchemaProperty | undefined }} properties - Schema properties
+ * @property {SchemaCondition[]} [anyOf] - List of schema conditions
+ */
+
+/**
+ * Schema property for component config
+ *
+ * @typedef {object} SchemaProperty
+ * @property {'string' | 'boolean' | 'number' | 'object'} type - Property type
+ */
+
+/**
+ * Schema condition for component config
+ *
+ * @typedef {object} SchemaCondition
+ * @property {string[]} required - List of required config fields
+ * @property {string} errorMessage - Error message when required config fields not provided
+ */
+/**
+ * @typedef {{new (...args: any[]): any, defaults?: object, moduleName: string}} CompatibleClass
+ */
+
 class GOVUKFrontendError extends Error {
   constructor(...args) {
     super(...args);
@@ -21,12 +70,20 @@ class ElementError extends GOVUKFrontendError {
     let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
     if (typeof messageOrOptions === 'object') {
       const {
+        component,
         componentName,
         identifier,
         element,
         expectedType
       } = messageOrOptions;
-      message = `${componentName}: ${identifier}`;
+      if (typeof componentName !== 'undefined') {
+        message = `${componentName}: ${identifier}`;
+      } else {
+        message = identifier;
+        if (typeof component !== 'undefined') {
+          message = formatErrorMessage(component, message);
+        }
+      }
       message += element ? ` is not of type ${expectedType != null ? expectedType : 'HTMLElement'}` : ' not found';
     }
     super(message);
@@ -44,49 +101,6 @@ class InitError extends GOVUKFrontendError {
   }
 }
 
-function isInitialised($module, moduleName) {
-  return $module instanceof HTMLElement && $module.hasAttribute(`data-${moduleName}-init`);
-}
-
-/**
- * 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.
- *
- * @param {HTMLElement | null} [$scope] - (internal) `<body>` HTML element checked for browser support
- * @returns {boolean} Whether GOV.UK Frontend is supported on this page
- */
-function isSupported($scope = document.body) {
-  if (!$scope) {
-    return false;
-  }
-  return $scope.classList.contains('govuk-frontend-supported');
-}
-
-/**
- * Schema for component config
- *
- * @typedef {object} Schema
- * @property {{ [field: string]: SchemaProperty | undefined }} properties - Schema properties
- * @property {SchemaCondition[]} [anyOf] - List of schema conditions
- */
-
-/**
- * Schema property for component config
- *
- * @typedef {object} SchemaProperty
- * @property {'string' | 'boolean' | 'number' | 'object'} type - Property type
- */
-
-/**
- * Schema condition for component config
- *
- * @typedef {object} SchemaCondition
- * @property {string[]} required - List of required config fields
- * @property {string} errorMessage - Error message when required config fields not provided
- */
-
 class GOVUKFrontendComponent {
   constructor($module) {
     this.checkSupport();
diff --git a/packages/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.bundle.js b/packages/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.bundle.js
index 09499031c..6a54e359b 100644
--- a/packages/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.bundle.js
+++ b/packages/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.bundle.js
@@ -31,6 +31,9 @@
     }
     return $scope.classList.contains('govuk-frontend-supported');
   }
+  function formatErrorMessage(Component, message) {
+    return `${Component.moduleName}: ${message}`;
+  }
 
   /**
    * Schema for component config
@@ -54,6 +57,9 @@
    * @property {string[]} required - List of required config fields
    * @property {string} errorMessage - Error message when required config fields not provided
    */
+  /**
+   * @typedef {{new (...args: any[]): any, defaults?: object, moduleName: string}} CompatibleClass
+   */
 
   class GOVUKFrontendError extends Error {
     constructor(...args) {
@@ -78,12 +84,20 @@
       let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
       if (typeof messageOrOptions === 'object') {
         const {
+          component,
           componentName,
           identifier,
           element,
           expectedType
         } = messageOrOptions;
-        message = `${componentName}: ${identifier}`;
+        if (typeof componentName !== 'undefined') {
+          message = `${componentName}: ${identifier}`;
+        } else {
+          message = identifier;
+          if (typeof component !== 'undefined') {
+            message = formatErrorMessage(component, message);
+          }
+        }
         message += element ? ` is not of type ${expectedType != null ? expectedType : 'HTMLElement'}` : ' not found';
       }
       super(message);
diff --git a/packages/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.bundle.mjs b/packages/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.bundle.mjs
index c944b0639..8a80e6db4 100644
--- a/packages/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.bundle.mjs
+++ b/packages/govuk-frontend/dist/govuk/components/service-navigation/service-navigation.bundle.mjs
@@ -25,6 +25,9 @@ function isSupported($scope = document.body) {
   }
   return $scope.classList.contains('govuk-frontend-supported');
 }
+function formatErrorMessage(Component, message) {
+  return `${Component.moduleName}: ${message}`;
+}
 
 /**
  * Schema for component config
@@ -48,6 +51,9 @@ function isSupported($scope = document.body) {
  * @property {string[]} required - List of required config fields
  * @property {string} errorMessage - Error message when required config fields not provided
  */
+/**
+ * @typedef {{new (...args: any[]): any, defaults?: object, moduleName: string}} CompatibleClass
+ */
 
 class GOVUKFrontendError extends Error {
   constructor(...args) {
@@ -72,12 +78,20 @@ class ElementError extends GOVUKFrontendError {
     let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
     if (typeof messageOrOptions === 'object') {
       const {
+        component,
         componentName,
         identifier,
         element,
         expectedType
       } = messageOrOptions;
-      message = `${componentName}: ${identifier}`;
+      if (typeof componentName !== 'undefined') {
+        message = `${componentName}: ${identifier}`;
+      } else {
+        message = identifier;
+        if (typeof component !== 'undefined') {
+          message = formatErrorMessage(component, message);
+        }
+      }
       message += element ? ` is not of type ${expectedType != null ? expectedType : 'HTMLElement'}` : ' not found';
     }
     super(message);
diff --git a/packages/govuk-frontend/dist/govuk/components/skip-link/skip-link.bundle.js b/packages/govuk-frontend/dist/govuk/components/skip-link/skip-link.bundle.js
index 29e6e9727..1ca0c73a3 100644
--- a/packages/govuk-frontend/dist/govuk/components/skip-link/skip-link.bundle.js
+++ b/packages/govuk-frontend/dist/govuk/components/skip-link/skip-link.bundle.js
@@ -53,6 +53,9 @@
     }
     return $scope.classList.contains('govuk-frontend-supported');
   }
+  function formatErrorMessage(Component, message) {
+    return `${Component.moduleName}: ${message}`;
+  }
 
   /**
    * Schema for component config
@@ -76,6 +79,9 @@
    * @property {string[]} required - List of required config fields
    * @property {string} errorMessage - Error message when required config fields not provided
    */
+  /**
+   * @typedef {{new (...args: any[]): any, defaults?: object, moduleName: string}} CompatibleClass
+   */
 
   class GOVUKFrontendError extends Error {
     constructor(...args) {
@@ -100,12 +106,20 @@
       let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
       if (typeof messageOrOptions === 'object') {
         const {
+          component,
           componentName,
           identifier,
           element,
           expectedType
         } = messageOrOptions;
-        message = `${componentName}: ${identifier}`;
+        if (typeof componentName !== 'undefined') {
+          message = `${componentName}: ${identifier}`;
+        } else {
+          message = identifier;
+          if (typeof component !== 'undefined') {
+            message = formatErrorMessage(component, message);
+          }
+        }
         message += element ? ` is not of type ${expectedType != null ? expectedType : 'HTMLElement'}` : ' not found';
       }
       super(message);
diff --git a/packages/govuk-frontend/dist/govuk/components/skip-link/skip-link.bundle.mjs b/packages/govuk-frontend/dist/govuk/components/skip-link/skip-link.bundle.mjs
index b5b902021..96f8e35bf 100644
--- a/packages/govuk-frontend/dist/govuk/components/skip-link/skip-link.bundle.mjs
+++ b/packages/govuk-frontend/dist/govuk/components/skip-link/skip-link.bundle.mjs
@@ -47,6 +47,9 @@ function isSupported($scope = document.body) {
   }
   return $scope.classList.contains('govuk-frontend-supported');
 }
+function formatErrorMessage(Component, message) {
+  return `${Component.moduleName}: ${message}`;
+}
 
 /**
  * Schema for component config
@@ -70,6 +73,9 @@ function isSupported($scope = document.body) {
  * @property {string[]} required - List of required config fields
  * @property {string} errorMessage - Error message when required config fields not provided
  */
+/**
+ * @typedef {{new (...args: any[]): any, defaults?: object, moduleName: string}} CompatibleClass
+ */
 
 class GOVUKFrontendError extends Error {
   constructor(...args) {
@@ -94,12 +100,20 @@ class ElementError extends GOVUKFrontendError {
     let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
     if (typeof messageOrOptions === 'object') {
       const {
+        component,
         componentName,
         identifier,
         element,
         expectedType
       } = messageOrOptions;
-      message = `${componentName}: ${identifier}`;
+      if (typeof componentName !== 'undefined') {
+        message = `${componentName}: ${identifier}`;
+      } else {
+        message = identifier;
+        if (typeof component !== 'undefined') {
+          message = formatErrorMessage(component, message);
+        }
+      }
       message += element ? ` is not of type ${expectedType != null ? expectedType : 'HTMLElement'}` : ' not found';
     }
     super(message);
diff --git a/packages/govuk-frontend/dist/govuk/components/tabs/tabs.bundle.js b/packages/govuk-frontend/dist/govuk/components/tabs/tabs.bundle.js
index 3400748c8..a98d7f1cf 100644
--- a/packages/govuk-frontend/dist/govuk/components/tabs/tabs.bundle.js
+++ b/packages/govuk-frontend/dist/govuk/components/tabs/tabs.bundle.js
@@ -37,6 +37,9 @@
     }
     return $scope.classList.contains('govuk-frontend-supported');
   }
+  function formatErrorMessage(Component, message) {
+    return `${Component.moduleName}: ${message}`;
+  }
 
   /**
    * Schema for component config
@@ -60,6 +63,9 @@
    * @property {string[]} required - List of required config fields
    * @property {string} errorMessage - Error message when required config fields not provided
    */
+  /**
+   * @typedef {{new (...args: any[]): any, defaults?: object, moduleName: string}} CompatibleClass
+   */
 
   class GOVUKFrontendError extends Error {
     constructor(...args) {
@@ -84,12 +90,20 @@
       let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
       if (typeof messageOrOptions === 'object') {
         const {
+          component,
           componentName,
           identifier,
           element,
           expectedType
         } = messageOrOptions;
-        message = `${componentName}: ${identifier}`;
+        if (typeof componentName !== 'undefined') {
+          message = `${componentName}: ${identifier}`;
+        } else {
+          message = identifier;
+          if (typeof component !== 'undefined') {
+            message = formatErrorMessage(component, message);
+          }
+        }
         message += element ? ` is not of type ${expectedType != null ? expectedType : 'HTMLElement'}` : ' not found';
       }
       super(message);
diff --git a/packages/govuk-frontend/dist/govuk/components/tabs/tabs.bundle.mjs b/packages/govuk-frontend/dist/govuk/components/tabs/tabs.bundle.mjs
index fb5ce2526..9d2dbb147 100644
--- a/packages/govuk-frontend/dist/govuk/components/tabs/tabs.bundle.mjs
+++ b/packages/govuk-frontend/dist/govuk/components/tabs/tabs.bundle.mjs
@@ -31,6 +31,9 @@ function isSupported($scope = document.body) {
   }
   return $scope.classList.contains('govuk-frontend-supported');
 }
+function formatErrorMessage(Component, message) {
+  return `${Component.moduleName}: ${message}`;
+}
 
 /**
  * Schema for component config
@@ -54,6 +57,9 @@ function isSupported($scope = document.body) {
  * @property {string[]} required - List of required config fields
  * @property {string} errorMessage - Error message when required config fields not provided
  */
+/**
+ * @typedef {{new (...args: any[]): any, defaults?: object, moduleName: string}} CompatibleClass
+ */
 
 class GOVUKFrontendError extends Error {
   constructor(...args) {
@@ -78,12 +84,20 @@ class ElementError extends GOVUKFrontendError {
     let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
     if (typeof messageOrOptions === 'object') {
       const {
+        component,
         componentName,
         identifier,
         element,
         expectedType
       } = messageOrOptions;
-      message = `${componentName}: ${identifier}`;
+      if (typeof componentName !== 'undefined') {
+        message = `${componentName}: ${identifier}`;
+      } else {
+        message = identifier;
+        if (typeof component !== 'undefined') {
+          message = formatErrorMessage(component, message);
+        }
+      }
       message += element ? ` is not of type ${expectedType != null ? expectedType : 'HTMLElement'}` : ' not found';
     }
     super(message);
diff --git a/packages/govuk-frontend/dist/govuk/errors/index.mjs b/packages/govuk-frontend/dist/govuk/errors/index.mjs
index 5fdb0858c..be7437ee0 100644
--- a/packages/govuk-frontend/dist/govuk/errors/index.mjs
+++ b/packages/govuk-frontend/dist/govuk/errors/index.mjs
@@ -1,3 +1,5 @@
+import { formatErrorMessage } from '../common/index.mjs';
+
 class GOVUKFrontendError extends Error {
   constructor(...args) {
     super(...args);
@@ -27,12 +29,20 @@ class ElementError extends GOVUKFrontendError {
     let message = typeof messageOrOptions === 'string' ? messageOrOptions : '';
     if (typeof messageOrOptions === 'object') {
       const {
+        component,
         componentName,
         identifier,
         element,
         expectedType
       } = messageOrOptions;
-      message = `${componentName}: ${identifier}`;
+      if (typeof componentName !== 'undefined') {
+        message = `${componentName}: ${identifier}`;
+      } else {
+        message = identifier;
+        if (typeof component !== 'undefined') {
+          message = formatErrorMessage(component, message);
+        }
+      }
       message += element ? ` is not of type ${expectedType != null ? expectedType : 'HTMLElement'}` : ' not found';
     }
     super(message);

Action run for 3cf0e10

@patrickpatrickpatrick patrickpatrickpatrick changed the title element error refactor Refactor ElementError to use component's moduleName instead of componentName Sep 18, 2024
@patrickpatrickpatrick
Copy link
Contributor Author

@romaricpascal I'm not sure how we're structuring the work here, I don't know if this refactor can take place without making the change the to the base component first.

@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-5334 September 19, 2024 06:52 Inactive
`ElementError` now accepts a `component` in config. This means the
component name can be accessed from the supplied `component`.

`formatErrorMessage` helper function added as it is a common string
format for error messages that can be used by other errors going
forward.
@patrickpatrickpatrick
Copy link
Contributor Author

Implemented in a way that means we can change the functions that throw ElementError in a separate PR.

Copy link
Member

@romaricpascal romaricpascal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I've completely missed the mark on how to break down the PRs around that refactoring, my bad. I've put more details in the comment of the ElementError class, hopefully it makes more sense (we can discuss at stand up on Monday, in any case).

* Format error message
*
* @internal
* @param {CompatibleClass} Component - Component that threw the error
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion I'm not sure we need a whole CompatibleClass here, it's asking more than what we actually need as we don't need the parameter to be instantiable or have a default property.

@@ -81,11 +83,18 @@ export class ElementError extends GOVUKFrontendError {

// Build message from options
if (typeof messageOrOptions === 'object') {
const { componentName, identifier, element, expectedType } =
const { component, componentName, identifier, element, expectedType } =
Copy link
Member

@romaricpascal romaricpascal Sep 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue Sorry, I didn't explain very well how I was hoping the PRs to be isolated from each other. I was expecting this refactor to move to using only component already:

Suggested change
const { component, componentName, identifier, element, expectedType } =
const { component, identifier, element, expectedType } =

With each component moving to doing (for example, with the Accordion):

if (!($module instanceof HTMLElement)) {
      throw new ElementError({
        component: Accordion,
        element: $module,
        identifier: 'Root element (`$module`)'
      })
    }

Then in a further PR, moving these if() in each components to GOVUKFrontendComponent and juggling with types...

My bad for the lack of explanation, sorry.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah no worries! That's not a big change to make :)

* @property {string} identifier - An identifier that'll let the user understand which element has an error. This is whatever makes the most sense
* @property {Element | null} [element] - The element in error
* @property {string} [expectedType] - The type that was expected for the identifier
* @property {import("../init.mjs").CompatibleClass} [component] - Component throwing the error
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion Similarly to the formatErrorMessage helper, I think we only really need {moduleName: string} as a type here to avoid requesting more than what we'll use.

Maybe we need some kind of HasModuleName type as {moduleName: string} is a type we use both:

  • in the CompatibleClass
  • to cast the ChildClassConstructor
  • to format the error message

How does that sound to you?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants