diff --git a/src/WebformBuilder.js b/src/WebformBuilder.js index 6581e6b4a1..eabf9b1ba5 100644 --- a/src/WebformBuilder.js +++ b/src/WebformBuilder.js @@ -1300,6 +1300,9 @@ export default class WebformBuilder extends Component { highlightInvalidComponents() { const repeatablePaths = this.findRepeatablePaths(); let hasInvalidComponents = false; + // Matches anything expect letters and '_' at the beginning of the key and anything except of letters, numbers, + // '-', '.' and '_' in the rest of the key + const badCharacters = /^[^A-Za-z_]+|[^A-Za-z0-9\-._]+/g; this.webform.everyComponent((comp) => { const path = comp.path; @@ -1307,6 +1310,11 @@ export default class WebformBuilder extends Component { comp.setCustomValidity(this.t('apiKey', { key: comp.key })); hasInvalidComponents = true; } + + if (comp.key.replace(badCharacters, '') === '') { + comp.setCustomValidity(this.t('apiKeyNotValid', { key: comp.key })); + hasInvalidComponents = true; + } }); this.emit('builderFormValidityChange', hasInvalidComponents); diff --git a/src/Wizard.js b/src/Wizard.js index 7c814ec71f..5bcc34cfea 100644 --- a/src/Wizard.js +++ b/src/Wizard.js @@ -1080,7 +1080,7 @@ export default class Wizard extends Webform { errors = [...errors, ...subWizard.errors] } }) - }; + } return super.showErrors(errors, triggerEvent) } diff --git a/src/components/textarea/TextArea.js b/src/components/textarea/TextArea.js index ef018983bc..82204f9236 100644 --- a/src/components/textarea/TextArea.js +++ b/src/components/textarea/TextArea.js @@ -356,10 +356,10 @@ export default class TextAreaComponent extends TextFieldComponent { } /** - * Normalize values coming into updateValue. For example, depending on the configuration, string value `"true"` will be normalized to boolean `true`. - * @param {*} value - The value to normalize - * @returns {*} - Returns the normalized value - */ + * Normalize values coming into updateValue. For example, depending on the configuration, string value `"true"` will be normalized to boolean `true`. + * @param {*} value - The value to normalize + * @returns {*} - Returns the normalized value + */ normalizeValue(value) { if (this.component.multiple && Array.isArray(value)) { return value.map((singleValue) => this.normalizeSingleValue(singleValue)); diff --git a/src/translations/en.js b/src/translations/en.js index a3b72f3033..17dbf10768 100644 --- a/src/translations/en.js +++ b/src/translations/en.js @@ -72,6 +72,7 @@ export default { reCaptchaTokenValidationError: 'ReCAPTCHA: Token validation error', reCaptchaTokenNotSpecifiedError: 'ReCAPTCHA: Token is not specified in submission', apiKey: 'API Key is not unique: {{key}}', + apiKeyNotValid: 'API Key is not valid: {{key}}', typeRemaining: '{{ remaining }} {{ type }} remaining.', typeCount: '{{ count }} {{ type }}', requiredDayField: '{{ field }} is required', diff --git a/test/forms/formWithNumericKeys.js b/test/forms/formWithNumericKeys.js new file mode 100644 index 0000000000..3e76bea557 --- /dev/null +++ b/test/forms/formWithNumericKeys.js @@ -0,0 +1,30 @@ +export default { + type: 'form', + display: 'form', + components: [ + { + label: 'Text Field', + applyMaskOn: 'change', + tableView: true, + key: 'test', + type: 'textfield', + input: true, + }, + { + label: 'Text Field', + applyMaskOn: 'change', + tableView: true, + key: '1234', + type: 'textfield', + input: true, + }, + { + type: 'button', + label: 'Submit', + key: 'submit', + disableOnInvalid: true, + input: true, + tableView: false, + }, + ], +}; diff --git a/test/unit/WebformBuilder.unit.js b/test/unit/WebformBuilder.unit.js index 3352982454..ab8bca5573 100644 --- a/test/unit/WebformBuilder.unit.js +++ b/test/unit/WebformBuilder.unit.js @@ -10,6 +10,7 @@ import testApiKeysUniquifying from '../forms/testApiKeysUniquifying'; import formBasedOnWizard from '../forms/formBasedOnWizard'; import formWithFormController from '../forms/formWithFormController'; import simpleWebform from '../forms/simpleWebform'; +import formWithNumericKeys from '../forms/formWithNumericKeys'; global.requestAnimationFrame = (cb) => cb(); global.cancelAnimationFrame = () => {}; @@ -359,7 +360,17 @@ describe('WebformBuilder tests', function() { }, 200); }, 200) }).catch(done); - }) + }); + + it('Should show API error when components have invalid API keys', (done) => { + const builder = Harness.getBuilder(); + builder.webform.setForm(formWithNumericKeys).then(() => { + builder.highlightInvalidComponents(); + const component = builder.webform.components[1]; + assert.equal(component.errors.length, 1); + done(); + }).catch(done); + }); }); describe('Select Component selectData property', () => {