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

Use features of base component for FileUpload #5590

Merged

Conversation

romaricpascal
Copy link
Member

@romaricpascal romaricpascal commented Jan 9, 2025

This PR makes FileUpload extend ConfigurableComponent rather than GOVUKFrontendComponent so it's consistent with the other components receiving configuration.

It also adds tests for the errors thrown during initialisation and makes their error message consistent.

Fixes #5615

Ahead of using `ConfigurableComponent` add tests for errors that won't be handled
by the base class, so we know the functionality is still there.

Tests for the errors thrown by `ConfiguableComponent` will be added when the class
gets used.
Use the recently added class for consistency with the other components.
Also adds the tests related to the behaviour provided by the `ConfigurableComponent` class
Make the error message when the `<input>` does not have the expected type
consistent with the messages from the other errors thrown by the component.
@govuk-design-system-ci govuk-design-system-ci temporarily deployed to govuk-frontend-pr-5590 January 9, 2025 16:21 Inactive
Copy link

github-actions bot commented Jan 9, 2025

📋 Stats

File sizes

File Size
dist/govuk-frontend-development.min.css 119.03 KiB
dist/govuk-frontend-development.min.js 45.42 KiB
packages/govuk-frontend/dist/govuk/all.bundle.js 98.3 KiB
packages/govuk-frontend/dist/govuk/all.bundle.mjs 92.29 KiB
packages/govuk-frontend/dist/govuk/all.mjs 1.32 KiB
packages/govuk-frontend/dist/govuk/govuk-frontend-component.mjs 1.74 KiB
packages/govuk-frontend/dist/govuk/govuk-frontend.min.css 119.02 KiB
packages/govuk-frontend/dist/govuk/govuk-frontend.min.js 45.41 KiB
packages/govuk-frontend/dist/govuk/i18n.mjs 5.55 KiB
packages/govuk-frontend/dist/govuk/init.mjs 7.5 KiB

Modules

File Size (bundled) Size (minified)
all.mjs 87.45 KiB 42.99 KiB
accordion.mjs 26.58 KiB 13.41 KiB
button.mjs 9.09 KiB 3.78 KiB
character-count.mjs 25.39 KiB 10.9 KiB
checkboxes.mjs 7.81 KiB 3.42 KiB
error-summary.mjs 10.99 KiB 4.54 KiB
exit-this-page.mjs 20.2 KiB 10.34 KiB
file-upload.mjs 18.03 KiB 8.57 KiB
header.mjs 6.46 KiB 3.22 KiB
notification-banner.mjs 9.35 KiB 3.7 KiB
password-input.mjs 18.24 KiB 8.33 KiB
radios.mjs 6.81 KiB 2.98 KiB
service-navigation.mjs 6.44 KiB 3.26 KiB
skip-link.mjs 6.4 KiB 2.76 KiB
tabs.mjs 12.04 KiB 6.67 KiB

View stats and visualisations on the review app


Action run for 6b4d4fa

Copy link

github-actions bot commented Jan 9, 2025

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 721a7e7fd..a58c936ee 100644
--- a/packages/govuk-frontend/dist/govuk/govuk-frontend.min.js
+++ b/packages/govuk-frontend/dist/govuk/govuk-frontend.min.js
@@ -120,7 +120,12 @@ class ConfigurableComponent extends GOVUKFrontendComponent {
         super(e), this._config = void 0;
         const i = this.constructor;
         if (void 0 === i.defaults) throw new ConfigError(formatErrorMessage(i, "Config passed as parameter into constructor but no defaults defined"));
-        const s = normaliseDataset(i, this._$root.dataset);
+        const s = function(Component, t) {
+            if (void 0 === Component.schema) throw new ConfigError(formatErrorMessage(Component, "Config passed as parameter into constructor but no schema defined"));
+            const e = {};
+            for (const [n, i] of Object.entries(Component.schema.properties)) n in t && (e[n] = normaliseString(t[n], i)), "object" === (null == i ? void 0 : i.type) && (e[n] = extractConfigByNamespace(Component.schema, t, n));
+            return e
+        }(i, this._$root.dataset);
         this._config = mergeConfigs(i.defaults, null != n ? n : {}, this[t](s), s)
     }
 }
@@ -141,13 +146,6 @@ function normaliseString(t, e) {
     return i
 }
 
-function normaliseDataset(Component, t) {
-    if (void 0 === Component.schema) throw new ConfigError(formatErrorMessage(Component, "Config passed as parameter into constructor but no schema defined"));
-    const e = {};
-    for (const [n, i] of Object.entries(Component.schema.properties)) n in t && (e[n] = normaliseString(t[n], i)), "object" === (null == i ? void 0 : i.type) && (e[n] = extractConfigByNamespace(Component.schema, t, n));
-    return e
-}
-
 function mergeConfigs(...t) {
     const e = {};
     for (const n of t)
@@ -750,11 +748,10 @@ ExitThisPage.moduleName = "govuk-exit-this-page", ExitThisPage.defaults = Object
         }
     }
 });
-class FileUpload extends GOVUKFrontendComponent {
+class FileUpload extends ConfigurableComponent {
     constructor(t, e = {}) {
-        if (super(t), this.$wrapper = void 0, this.$button = void 0, this.$status = void 0, this.config = void 0, this.i18n = void 0, !(this.$root instanceof HTMLInputElement)) return;
-        if ("file" !== this.$root.type) throw new ElementError("File upload: Form field must be an input of type `file`.");
-        if (this.config = mergeConfigs(FileUpload.defaults, e, normaliseDataset(FileUpload, this.$root.dataset)), this.i18n = new I18n(this.config.i18n, {
+        if (super(t, e), this.$wrapper = void 0, this.$button = void 0, this.$status = void 0, this.i18n = void 0, "file" !== this.$root.type) throw new ElementError(formatErrorMessage(FileUpload, "Form field must be an input of type `file`."));
+        if (this.i18n = new I18n(this.config.i18n, {
                 locale: closestAttributeValue(this.$root, "lang")
             }), this.$label = document.querySelector(`[for="${this.$root.id}"]`), !this.$label) throw new ElementError({
             component: FileUpload,
@@ -771,9 +768,9 @@ class FileUpload extends GOVUKFrontendComponent {
         if (!("files" in this.$root)) return;
         if (!this.$root.files) return;
         const t = this.$root.files.length;
-        this.$status && this.i18n && (this.$status.innerText = 0 === t ? this.i18n.t("filesSelectedDefault") : 1 === t ? this.$root.files[0].name : this.i18n.t("filesSelected", {
+        this.$status.innerText = 0 === t ? this.i18n.t("filesSelectedDefault") : 1 === t ? this.$root.files[0].name : this.i18n.t("filesSelected", {
             count: t
-        }))
+        })
     }
     onClick() {
         this.$label instanceof HTMLElement && this.$label.click()

Action run for 6b4d4fa

Copy link

github-actions bot commented Jan 9, 2025

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 a2e6c5c58..c781bbe48 100644
--- a/packages/govuk-frontend/dist/govuk/all.bundle.js
+++ b/packages/govuk-frontend/dist/govuk/all.bundle.js
@@ -1658,26 +1658,22 @@
    * File upload component
    *
    * @preserve
+   * @augments ConfigurableComponent<FileUploadConfig,HTMLInputElement>
    */
-  class FileUpload extends GOVUKFrontendComponent {
+  class FileUpload extends ConfigurableComponent {
     /**
      * @param {Element | null} $root - File input element
      * @param {FileUploadConfig} [config] - File Upload config
      */
     constructor($root, config = {}) {
-      super($root);
+      super($root, config);
       this.$wrapper = void 0;
       this.$button = void 0;
       this.$status = void 0;
-      this.config = void 0;
       this.i18n = void 0;
-      if (!(this.$root instanceof HTMLInputElement)) {
-        return;
-      }
       if (this.$root.type !== 'file') {
-        throw new ElementError('File upload: Form field must be an input of type `file`.');
+        throw new ElementError(formatErrorMessage(FileUpload, 'Form field must be an input of type `file`.'));
       }
-      this.config = mergeConfigs(FileUpload.defaults, config, normaliseDataset(FileUpload, this.$root.dataset));
       this.i18n = new I18n(this.config.i18n, {
         locale: closestAttributeValue(this.$root, 'lang')
       });
@@ -1722,9 +1718,6 @@
         return;
       }
       const fileCount = this.$root.files.length;
-      if (!this.$status || !this.i18n) {
-        return;
-      }
       if (fileCount === 0) {
         this.$status.innerText = this.i18n.t('filesSelectedDefault');
       } else if (fileCount === 1) {
diff --git a/packages/govuk-frontend/dist/govuk/all.bundle.mjs b/packages/govuk-frontend/dist/govuk/all.bundle.mjs
index f261b255d..213c3ce51 100644
--- a/packages/govuk-frontend/dist/govuk/all.bundle.mjs
+++ b/packages/govuk-frontend/dist/govuk/all.bundle.mjs
@@ -1652,26 +1652,22 @@ ExitThisPage.schema = Object.freeze({
  * File upload component
  *
  * @preserve
+ * @augments ConfigurableComponent<FileUploadConfig,HTMLInputElement>
  */
-class FileUpload extends GOVUKFrontendComponent {
+class FileUpload extends ConfigurableComponent {
   /**
    * @param {Element | null} $root - File input element
    * @param {FileUploadConfig} [config] - File Upload config
    */
   constructor($root, config = {}) {
-    super($root);
+    super($root, config);
     this.$wrapper = void 0;
     this.$button = void 0;
     this.$status = void 0;
-    this.config = void 0;
     this.i18n = void 0;
-    if (!(this.$root instanceof HTMLInputElement)) {
-      return;
-    }
     if (this.$root.type !== 'file') {
-      throw new ElementError('File upload: Form field must be an input of type `file`.');
+      throw new ElementError(formatErrorMessage(FileUpload, 'Form field must be an input of type `file`.'));
     }
-    this.config = mergeConfigs(FileUpload.defaults, config, normaliseDataset(FileUpload, this.$root.dataset));
     this.i18n = new I18n(this.config.i18n, {
       locale: closestAttributeValue(this.$root, 'lang')
     });
@@ -1716,9 +1712,6 @@ class FileUpload extends GOVUKFrontendComponent {
       return;
     }
     const fileCount = this.$root.files.length;
-    if (!this.$status || !this.i18n) {
-      return;
-    }
     if (fileCount === 0) {
       this.$status.innerText = this.i18n.t('filesSelectedDefault');
     } else if (fileCount === 1) {
diff --git a/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.bundle.js b/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.bundle.js
index 69b24f51d..095ec7ec6 100644
--- a/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.bundle.js
+++ b/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.bundle.js
@@ -150,6 +150,32 @@
    */
   GOVUKFrontendComponent.elementType = HTMLElement;
 
+  const configOverride = Symbol.for('configOverride');
+  class ConfigurableComponent extends GOVUKFrontendComponent {
+    [configOverride](param) {
+      return {};
+    }
+
+    /**
+     * Returns the root element of the component
+     *
+     * @protected
+     * @returns {ConfigurationType} - the root element of component
+     */
+    get config() {
+      return this._config;
+    }
+    constructor($root, config) {
+      super($root);
+      this._config = void 0;
+      const childConstructor = this.constructor;
+      if (typeof childConstructor.defaults === 'undefined') {
+        throw new ConfigError(formatErrorMessage(childConstructor, 'Config passed as parameter into constructor but no defaults defined'));
+      }
+      const datasetConfig = normaliseDataset(childConstructor, this._$root.dataset);
+      this._config = mergeConfigs(childConstructor.defaults, config != null ? config : {}, this[configOverride](datasetConfig), datasetConfig);
+    }
+  }
   function normaliseString(value, property) {
     const trimmedValue = value ? value.trim() : '';
     let output;
@@ -459,26 +485,22 @@
    * File upload component
    *
    * @preserve
+   * @augments ConfigurableComponent<FileUploadConfig,HTMLInputElement>
    */
-  class FileUpload extends GOVUKFrontendComponent {
+  class FileUpload extends ConfigurableComponent {
     /**
      * @param {Element | null} $root - File input element
      * @param {FileUploadConfig} [config] - File Upload config
      */
     constructor($root, config = {}) {
-      super($root);
+      super($root, config);
       this.$wrapper = void 0;
       this.$button = void 0;
       this.$status = void 0;
-      this.config = void 0;
       this.i18n = void 0;
-      if (!(this.$root instanceof HTMLInputElement)) {
-        return;
-      }
       if (this.$root.type !== 'file') {
-        throw new ElementError('File upload: Form field must be an input of type `file`.');
+        throw new ElementError(formatErrorMessage(FileUpload, 'Form field must be an input of type `file`.'));
       }
-      this.config = mergeConfigs(FileUpload.defaults, config, normaliseDataset(FileUpload, this.$root.dataset));
       this.i18n = new I18n(this.config.i18n, {
         locale: closestAttributeValue(this.$root, 'lang')
       });
@@ -523,9 +545,6 @@
         return;
       }
       const fileCount = this.$root.files.length;
-      if (!this.$status || !this.i18n) {
-        return;
-      }
       if (fileCount === 0) {
         this.$status.innerText = this.i18n.t('filesSelectedDefault');
       } else if (fileCount === 1) {
diff --git a/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.bundle.mjs b/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.bundle.mjs
index 65b4aae36..72de1cc29 100644
--- a/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.bundle.mjs
+++ b/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.bundle.mjs
@@ -144,6 +144,32 @@ class GOVUKFrontendComponent {
  */
 GOVUKFrontendComponent.elementType = HTMLElement;
 
+const configOverride = Symbol.for('configOverride');
+class ConfigurableComponent extends GOVUKFrontendComponent {
+  [configOverride](param) {
+    return {};
+  }
+
+  /**
+   * Returns the root element of the component
+   *
+   * @protected
+   * @returns {ConfigurationType} - the root element of component
+   */
+  get config() {
+    return this._config;
+  }
+  constructor($root, config) {
+    super($root);
+    this._config = void 0;
+    const childConstructor = this.constructor;
+    if (typeof childConstructor.defaults === 'undefined') {
+      throw new ConfigError(formatErrorMessage(childConstructor, 'Config passed as parameter into constructor but no defaults defined'));
+    }
+    const datasetConfig = normaliseDataset(childConstructor, this._$root.dataset);
+    this._config = mergeConfigs(childConstructor.defaults, config != null ? config : {}, this[configOverride](datasetConfig), datasetConfig);
+  }
+}
 function normaliseString(value, property) {
   const trimmedValue = value ? value.trim() : '';
   let output;
@@ -453,26 +479,22 @@ I18n.pluralRules = {
  * File upload component
  *
  * @preserve
+ * @augments ConfigurableComponent<FileUploadConfig,HTMLInputElement>
  */
-class FileUpload extends GOVUKFrontendComponent {
+class FileUpload extends ConfigurableComponent {
   /**
    * @param {Element | null} $root - File input element
    * @param {FileUploadConfig} [config] - File Upload config
    */
   constructor($root, config = {}) {
-    super($root);
+    super($root, config);
     this.$wrapper = void 0;
     this.$button = void 0;
     this.$status = void 0;
-    this.config = void 0;
     this.i18n = void 0;
-    if (!(this.$root instanceof HTMLInputElement)) {
-      return;
-    }
     if (this.$root.type !== 'file') {
-      throw new ElementError('File upload: Form field must be an input of type `file`.');
+      throw new ElementError(formatErrorMessage(FileUpload, 'Form field must be an input of type `file`.'));
     }
-    this.config = mergeConfigs(FileUpload.defaults, config, normaliseDataset(FileUpload, this.$root.dataset));
     this.i18n = new I18n(this.config.i18n, {
       locale: closestAttributeValue(this.$root, 'lang')
     });
@@ -517,9 +539,6 @@ class FileUpload extends GOVUKFrontendComponent {
       return;
     }
     const fileCount = this.$root.files.length;
-    if (!this.$status || !this.i18n) {
-      return;
-    }
     if (fileCount === 0) {
       this.$status.innerText = this.i18n.t('filesSelectedDefault');
     } else if (fileCount === 1) {
diff --git a/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.mjs b/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.mjs
index a1ff0baf8..596c4a95a 100644
--- a/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.mjs
+++ b/packages/govuk-frontend/dist/govuk/components/file-upload/file-upload.mjs
@@ -1,33 +1,29 @@
 import { closestAttributeValue } from '../../common/closest-attribute-value.mjs';
-import { mergeConfigs, normaliseDataset } from '../../common/configuration.mjs';
+import { ConfigurableComponent } from '../../common/configuration.mjs';
+import { formatErrorMessage } from '../../common/index.mjs';
 import { ElementError } from '../../errors/index.mjs';
-import { GOVUKFrontendComponent } from '../../govuk-frontend-component.mjs';
 import { I18n } from '../../i18n.mjs';
 
 /**
  * File upload component
  *
  * @preserve
+ * @augments ConfigurableComponent<FileUploadConfig,HTMLInputElement>
  */
-class FileUpload extends GOVUKFrontendComponent {
+class FileUpload extends ConfigurableComponent {
   /**
    * @param {Element | null} $root - File input element
    * @param {FileUploadConfig} [config] - File Upload config
    */
   constructor($root, config = {}) {
-    super($root);
+    super($root, config);
     this.$wrapper = void 0;
     this.$button = void 0;
     this.$status = void 0;
-    this.config = void 0;
     this.i18n = void 0;
-    if (!(this.$root instanceof HTMLInputElement)) {
-      return;
-    }
     if (this.$root.type !== 'file') {
-      throw new ElementError('File upload: Form field must be an input of type `file`.');
+      throw new ElementError(formatErrorMessage(FileUpload, 'Form field must be an input of type `file`.'));
     }
-    this.config = mergeConfigs(FileUpload.defaults, config, normaliseDataset(FileUpload, this.$root.dataset));
     this.i18n = new I18n(this.config.i18n, {
       locale: closestAttributeValue(this.$root, 'lang')
     });
@@ -72,9 +68,6 @@ class FileUpload extends GOVUKFrontendComponent {
       return;
     }
     const fileCount = this.$root.files.length;
-    if (!this.$status || !this.i18n) {
-      return;
-    }
     if (fileCount === 0) {
       this.$status.innerText = this.i18n.t('filesSelectedDefault');
     } else if (fileCount === 1) {

Action run for 6b4d4fa

@romaricpascal romaricpascal merged commit 9abadc5 into spike-enhanced-file-upload Jan 10, 2025
49 checks passed
@romaricpascal romaricpascal deleted the enhanced-file-upload-base-class branch January 10, 2025 14:11
@romaricpascal romaricpascal linked an issue Jan 16, 2025 that may be closed by this pull request
1 task
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.

Make the FileUpload component use ConfigurableComponent
3 participants