Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
MekalaNagarajan-Centrica committed Nov 7, 2024
2 parents fd9194a + f03e8ce commit 62f948e
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 50 deletions.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

53 changes: 46 additions & 7 deletions packages/muon/components/inputter/src/inputter-component.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,29 @@ export class Inputter extends ScopedElementsMixin(ValidationMixin(MaskMixin(Muon
this._helperId = `${this._randomId}-helper`;
}

willUpdate(changedProperties) {
super.willUpdate(changedProperties);

let validationEle = this.querySelector(`#${this._id}-validation`);
if (!validationEle) {
validationEle = document.createElement('div');
validationEle.setAttribute('class', 'visually-hidden');
validationEle.setAttribute('id', `${this._id}-validation`);
this.appendChild(validationEle);
}
const slottedInput = this._slottedInputs[0];
if (this._shouldShowValidation) {
validationEle.setAttribute('aria-live', 'polite');
slottedInput?.setAttribute('aria-errormessage', `${this._id}-validation`);
slottedInput?.setAttribute('aria-invalid', 'true');
validationEle.textContent = `${this._isMultiple ? this.heading : this._slottedLabel?.textContent} ${this.validationMessage}`;
} else {
slottedInput?.removeAttribute('aria-errormessage');
slottedInput?.removeAttribute('aria-invalid');
validationEle.textContent = '';
}
}

_onChange(changeEvent) {
this._pristine = false;
changeEvent.stopPropagation();
Expand Down Expand Up @@ -211,6 +234,24 @@ export class Inputter extends ScopedElementsMixin(ValidationMixin(MaskMixin(Muon
return false;
}

get _multiInputHeading() {
return html`
<legend>${this._addHeading}</legend>
`;
}

get __wrapperContent() {
return html`
${this._isMultiple ? this._multiInputHeading : this._addLabel}
${this._addHelper}
<div class="wrapper">
${super.standardTemplate}
${this._addMask}
${this._addInputTypeIcon}
</div>
`;
}

/**
* Getter method to construct template for type `standard`.
* @protected
Expand All @@ -220,13 +261,11 @@ export class Inputter extends ScopedElementsMixin(ValidationMixin(MaskMixin(Muon
return html`
<div class="${classMap(this.classes)}" style="${styleMap(this.inlineStyles)}" aria-describedby=${ifDefined(this.helper && !this.__isTipDetailAvailable ? this._helperId : undefined)}
aria-details=${ifDefined(this.helper && this.__isTipDetailAvailable ? this._helperId : undefined)}>
${this._isMultiple ? this._addHeading : this._addLabel}
${this._addHelper}
<div class="wrapper">
${super.standardTemplate}
${this._addMask}
${this._addInputTypeIcon}
</div>
${this._isMultiple ? html`
<fieldset>
${this.__wrapperContent}
</fieldset>
` : this.__wrapperContent}
</div>
${this._addValidationMessage}
`;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
@import "./inputter-extends.css";
@import "../../../css/accessibility.css";

light-dom {
@extend %global-accessibility;

/* NOTE: targeting Safari only */
@media not all and (min-resolution: 0.001dpcm) { /* stylelint-disable-line media-feature-range-notation */
/*
Expand Down
9 changes: 6 additions & 3 deletions packages/muon/mixins/validation-mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,10 @@ export const ValidationMixin = dedupeMixin((superClass) =>
}).join(' ');
}

get _shouldShowValidation() {
return this.showMessage && this.isDirty && !!this.validationMessage;
}

/**
* A method to get validation message template.
*
Expand All @@ -229,12 +233,11 @@ export const ValidationMixin = dedupeMixin((superClass) =>
* @override
*/
get _addValidationMessage() {
if (this.showMessage && this.isDirty && this.validationMessage) {
if (this._shouldShowValidation) {
return html`
<div class="validation">
${this._addValidationIcon}
<div class="message" role="alert" aria-live="assertive">
<div class="visually-hidden">${this._isMultiple ? this.heading : this._slottedLabel?.textContent}</div>
<div class="message">
${this.validationMessage}
</div>
</div>`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,39 +140,51 @@ snapshots["Inputter text mask & validation"] =

snapshots["Inputter radio standard radio"] =
`<div class="inputter radio">
<span class="input-heading">
What is your heating source?
</span>
<div class="wrapper">
<slot>
</slot>
</div>
<fieldset>
<legend>
<span class="input-heading">
What is your heating source?
</span>
</legend>
<div class="wrapper">
<slot>
</slot>
</div>
</fieldset>
</div>
`;
/* end snapshot Inputter radio standard radio */

snapshots["Inputter radio radio mask"] =
`<div class="has-mask inputter radio">
<span class="input-heading">
What is your heating source?
</span>
<div class="wrapper">
<slot>
</slot>
</div>
<fieldset>
<legend>
<span class="input-heading">
What is your heating source?
</span>
</legend>
<div class="wrapper">
<slot>
</slot>
</div>
</fieldset>
</div>
`;
/* end snapshot Inputter radio radio mask */

snapshots["Inputter radio radio mask validation"] =
`<div class="has-mask inputter radio">
<span class="input-heading">
What is your heating source?
</span>
<div class="wrapper">
<slot>
</slot>
</div>
<fieldset>
<legend>
<span class="input-heading">
What is your heating source?
</span>
</legend>
<div class="wrapper">
<slot>
</slot>
</div>
</fieldset>
</div>
`;
/* end snapshot Inputter radio radio mask validation */
Expand Down
17 changes: 11 additions & 6 deletions packages/muon/tests/components/inputter/inputter.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,13 @@ describe('Inputter', () => {

const validationMessage = shadowRoot.querySelector('.validation .message');
expect(validationMessage).to.not.be.null; // eslint-disable-line no-unused-expressions
expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('input label Length must be between 8 and 20 characters.', 'validation message has correct value');
expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('Length must be between 8 and 20 characters.', 'validation message has correct value');

const validationId = `${inputter._id}-validation`;
const validationLightDOM = inputter.querySelector(`#${validationId}`);
// eslint-disable-next-line no-unused-expressions
expect(validationLightDOM).to.be.ok;
expect(inputElement.getAttribute('aria-errormessage')).to.be.equal(validationId);
const validationIcon = shadowRoot.querySelector('.validation .icon');
expect(validationIcon).to.not.be.null; // eslint-disable-line no-unused-expressions
expect(validationIcon.name).to.equal('exclamation-circle', 'validation icon has correct value');
Expand Down Expand Up @@ -210,7 +215,7 @@ describe('Inputter', () => {

const validationMessage = shadowRoot.querySelector('.validation .message');
expect(validationMessage).to.not.be.null; // eslint-disable-line no-unused-expressions
expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('input label Length must be between 8 and 20 characters.', 'validation message has correct value');
expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('Length must be between 8 and 20 characters.', 'validation message has correct value');

const validationIcon = shadowRoot.querySelector('.validation .icon');
expect(validationIcon).to.not.be.null; // eslint-disable-line no-unused-expressions
Expand Down Expand Up @@ -251,7 +256,7 @@ describe('Inputter', () => {

const validationMessage = shadowRoot.querySelector('.validation .message');
expect(validationMessage).to.not.be.null; // eslint-disable-line no-unused-expressions
expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('input label This field is required.', 'validation message has correct value');
expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('This field is required.', 'validation message has correct value');

const validationIcon = shadowRoot.querySelector('.validation .icon');
expect(validationIcon).to.not.be.null; // eslint-disable-line no-unused-expressions
Expand Down Expand Up @@ -309,7 +314,7 @@ describe('Inputter', () => {

const validationMessage = shadowRoot.querySelector('.validation .message');
expect(validationMessage).to.not.be.null; // eslint-disable-line no-unused-expressions
expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('input label This field is required.', 'validation message has correct value');
expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('This field is required.', 'validation message has correct value');

const validationIcon = shadowRoot.querySelector('.validation .icon');
expect(validationIcon).to.not.be.null; // eslint-disable-line no-unused-expressions
Expand All @@ -319,7 +324,7 @@ describe('Inputter', () => {
await inputter.updateComplete;
expect(changeEventSpy.callCount).to.equal(3, '`change` event fired');
expect(changeEventSpy.lastCall.args[0].detail.value).to.equal('12-3', '`change` event has value `12-3`');
expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('input label Length must be at least 4 characters.', 'validation message has correct value');
expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('Length must be at least 4 characters.', 'validation message has correct value');

inputMask = shadowRoot.querySelector('.input-mask');
expect(inputMask.textContent).to.be.equal(' 0', '`input-mask` has correct value');
Expand Down Expand Up @@ -415,7 +420,7 @@ describe('Inputter', () => {
expect(validationMessage).to.not.be.null; // eslint-disable-line no-unused-expressions


expect(validationMessage.textContent?.trim().replace(/\s\s+/g, ' ')).to.equal('What is your heating source? This field is required.', 'validation message has correct value');
expect(validationMessage.textContent?.trim().replace(/\s\s+/g, ' ')).to.equal('This field is required.', 'validation message has correct value');

const validationIcon = shadowRoot.querySelector('.validation .icon');
expect(validationIcon).to.not.be.null; // eslint-disable-line no-unused-expressions
Expand Down
24 changes: 12 additions & 12 deletions packages/muon/tests/mixins/validation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ describe('form-element-validation', () => {
await formElement.updateComplete;
let validationMessage = shadowRoot.querySelector('.validation');
expect(validationMessage).to.not.be.null; // eslint-disable-line no-unused-expressions
expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('input label This field is required.', 'validation message has correct value');
expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('This field is required.', 'validation message has correct value');

await fillIn(inputElement, 'hello world');
expect(formElement.value).to.equal('hello world', '`value` property has value `hello world`');
Expand All @@ -113,7 +113,7 @@ describe('form-element-validation', () => {
await formElement.updateComplete;
validationMessage = shadowRoot.querySelector('.validation');
expect(validationMessage).to.not.be.null; // eslint-disable-line no-unused-expressions
expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('input label Length must be between 5 and 10 characters.', 'validation message has correct value');
expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('Length must be between 5 and 10 characters.', 'validation message has correct value');
});

it('text validation on input', async () => {
Expand Down Expand Up @@ -147,7 +147,7 @@ describe('form-element-validation', () => {
await formElement.updateComplete;
let validationMessage = shadowRoot.querySelector('.validation');
expect(validationMessage).to.not.be.null; // eslint-disable-line no-unused-expressions
expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('input label This field is required.', 'validation message has correct value');
expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('This field is required.', 'validation message has correct value');

await fillIn(inputElement, 'hello world', 'input');
expect(formElement.value).to.equal('hello world', '`value` property has value `hello world`');
Expand All @@ -157,7 +157,7 @@ describe('form-element-validation', () => {
await formElement.updateComplete;
validationMessage = shadowRoot.querySelector('.validation');
expect(validationMessage).to.not.be.null; // eslint-disable-line no-unused-expressions
expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('input label Length must be between 5 and 10 characters.', 'validation message has correct value');
expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('Length must be between 5 and 10 characters.', 'validation message has correct value');
});

it('text native validation', async () => {
Expand Down Expand Up @@ -191,7 +191,7 @@ describe('form-element-validation', () => {
await formElement.updateComplete;
let validationMessage = shadowRoot.querySelector('.validation');
expect(validationMessage).to.not.be.null; // eslint-disable-line no-unused-expressions
expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ').toLowerCase()).contains('input label this field is required', 'validation message has correct value');
expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ').toLowerCase()).contains('this field is required', 'validation message has correct value');

await fillIn(inputElement, 'test validation');
expect(formElement.value).to.equal('test validation', '`value` property has value `test validation`');
Expand Down Expand Up @@ -234,7 +234,7 @@ describe('form-element-validation', () => {
await formElement.updateComplete;
let validationMessage = shadowRoot.querySelector('.validation');
expect(validationMessage).to.not.be.null; // eslint-disable-line no-unused-expressions
expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('input label This field is required.', 'validation message has correct value');
expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('This field is required.', 'validation message has correct value');

await fillIn(inputElement, '56');
expect(formElement.value).to.equal('56', '`value` property has value `56`');
Expand All @@ -244,7 +244,7 @@ describe('form-element-validation', () => {
await formElement.updateComplete;
validationMessage = shadowRoot.querySelector('.validation');
expect(validationMessage).to.not.be.null; // eslint-disable-line no-unused-expressions
expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('input label match the pattern.', 'validation message has correct value');
expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('match the pattern.', 'validation message has correct value');

});

Expand Down Expand Up @@ -309,7 +309,7 @@ describe('form-element-validation', () => {
await formElement.updateComplete;
const validationMessage = shadowRoot.querySelector('.validation');
expect(validationMessage).to.not.be.null; // eslint-disable-line no-unused-expressions
expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('What is your heating source? This field is required.', 'validation message has correct value');
expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('This field is required.', 'validation message has correct value');
});

it('checkbox validation', async () => {
Expand Down Expand Up @@ -341,7 +341,7 @@ describe('form-element-validation', () => {
expect(changeEventSpy.callCount).to.equal(1, '`change` event fired');
const validationMessage = shadowRoot.querySelector('.validation');
expect(validationMessage).to.not.be.null; // eslint-disable-line no-unused-expressions
expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('What is your heating source? This field is required.', 'validation message has correct value');
expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('This field is required.', 'validation message has correct value');
});

it('select validation', async () => {
Expand Down Expand Up @@ -373,7 +373,7 @@ describe('form-element-validation', () => {
expect(changeEventSpy.callCount).to.equal(1, '`change` event fired');
const validationMessage = shadowRoot.querySelector('.validation');
expect(validationMessage).to.not.be.null; // eslint-disable-line no-unused-expressions
expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('What is your heating source? This field is required.', 'validation message has correct value');
expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('This field is required.', 'validation message has correct value');
});

it('date validation', async () => {
Expand Down Expand Up @@ -408,7 +408,7 @@ describe('form-element-validation', () => {

let validationMessage = shadowRoot.querySelector('.validation');
expect(validationMessage).to.not.be.null; // eslint-disable-line no-unused-expressions
expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('input label This field is required.', 'validation message has correct value');
expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('This field is required.', 'validation message has correct value');

await fillIn(inputElement, '10/11/2021');
await formElement.updateComplete;
Expand All @@ -418,6 +418,6 @@ describe('form-element-validation', () => {

validationMessage = shadowRoot.querySelector('.validation');
expect(validationMessage).to.not.be.null; // eslint-disable-line no-unused-expressions
expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('input label Date must be on or after 11/11/2021.', 'validation message has correct value');
expect(validationMessage.textContent.trim().replace(/\s\s+/g, ' ')).to.equal('Date must be on or after 11/11/2021.', 'validation message has correct value');
});
});

0 comments on commit 62f948e

Please sign in to comment.