Merge pull request #163 from ing-bank/develop
fix: improvements to field and validate in preparation for select-rich
This commit is contained in:
commit
118e963210
8 changed files with 366 additions and 166 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
import { html, css, nothing, dedupeMixin } from '@lion/core';
|
import { html, css, nothing, dedupeMixin, SlotMixin } from '@lion/core';
|
||||||
import { ObserverMixin } from '@lion/core/src/ObserverMixin.js';
|
import { ObserverMixin } from '@lion/core/src/ObserverMixin.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -14,7 +14,7 @@ import { ObserverMixin } from '@lion/core/src/ObserverMixin.js';
|
||||||
export const FormControlMixin = dedupeMixin(
|
export const FormControlMixin = dedupeMixin(
|
||||||
superclass =>
|
superclass =>
|
||||||
// eslint-disable-next-line no-shadow, no-unused-vars
|
// eslint-disable-next-line no-shadow, no-unused-vars
|
||||||
class FormControlMixin extends ObserverMixin(superclass) {
|
class FormControlMixin extends ObserverMixin(SlotMixin(superclass)) {
|
||||||
static get properties() {
|
static get properties() {
|
||||||
return {
|
return {
|
||||||
...super.properties,
|
...super.properties,
|
||||||
|
|
@ -75,8 +75,21 @@ export const FormControlMixin = dedupeMixin(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @deprecated will be this._inputNode in next breaking release */
|
||||||
get inputElement() {
|
get inputElement() {
|
||||||
return (this.$$slot && this.$$slot('input')) || this.querySelector('[slot=input]'); // eslint-disable-line
|
return this.__getDirectSlotChild('input');
|
||||||
|
}
|
||||||
|
|
||||||
|
get _labelNode() {
|
||||||
|
return this.__getDirectSlotChild('label');
|
||||||
|
}
|
||||||
|
|
||||||
|
get _helpTextNode() {
|
||||||
|
return this.__getDirectSlotChild('help-text');
|
||||||
|
}
|
||||||
|
|
||||||
|
get _feedbackNode() {
|
||||||
|
return this.__getDirectSlotChild('feedback');
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
@ -107,29 +120,31 @@ export const FormControlMixin = dedupeMixin(
|
||||||
}
|
}
|
||||||
|
|
||||||
_enhanceLightDomA11y() {
|
_enhanceLightDomA11y() {
|
||||||
if (this.inputElement) {
|
const { inputElement, _labelNode, _helpTextNode, _feedbackNode } = this;
|
||||||
this.inputElement.id = this.inputElement.id || this._inputId;
|
|
||||||
|
if (inputElement) {
|
||||||
|
inputElement.id = inputElement.id || this._inputId;
|
||||||
}
|
}
|
||||||
if (this.$$slot('label')) {
|
if (_labelNode) {
|
||||||
this.$$slot('label').setAttribute('for', this._inputId);
|
_labelNode.setAttribute('for', this._inputId);
|
||||||
this.$$slot('label').id = this.$$slot('label').id || `label-${this._inputId}`;
|
_labelNode.id = _labelNode.id || `label-${this._inputId}`;
|
||||||
const labelledById = ` ${this.$$slot('label').id}`;
|
const labelledById = ` ${_labelNode.id}`;
|
||||||
if (this._ariaLabelledby.indexOf(labelledById) === -1) {
|
if (this._ariaLabelledby.indexOf(labelledById) === -1) {
|
||||||
this._ariaLabelledby += ` ${this.$$slot('label').id}`;
|
this._ariaLabelledby += ` ${_labelNode.id}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.$$slot('help-text')) {
|
if (_helpTextNode) {
|
||||||
this.$$slot('help-text').id = this.$$slot('help-text').id || `help-text-${this._inputId}`;
|
_helpTextNode.id = _helpTextNode.id || `help-text-${this._inputId}`;
|
||||||
const describeIdHelpText = ` ${this.$$slot('help-text').id}`;
|
const describeIdHelpText = ` ${_helpTextNode.id}`;
|
||||||
if (this._ariaDescribedby.indexOf(describeIdHelpText) === -1) {
|
if (this._ariaDescribedby.indexOf(describeIdHelpText) === -1) {
|
||||||
this._ariaDescribedby += ` ${this.$$slot('help-text').id}`;
|
this._ariaDescribedby += ` ${_helpTextNode.id}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.$$slot('feedback')) {
|
if (_feedbackNode) {
|
||||||
this.$$slot('feedback').id = this.$$slot('feedback').id || `feedback-${this._inputId}`;
|
_feedbackNode.id = _feedbackNode.id || `feedback-${this._inputId}`;
|
||||||
const describeIdFeedback = ` ${this.$$slot('feedback').id}`;
|
const describeIdFeedback = ` ${_feedbackNode.id}`;
|
||||||
if (this._ariaDescribedby.indexOf(describeIdFeedback) === -1) {
|
if (this._ariaDescribedby.indexOf(describeIdFeedback) === -1) {
|
||||||
this._ariaDescribedby += ` ${this.$$slot('feedback').id}`;
|
this._ariaDescribedby += ` ${_feedbackNode.id}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this._enhanceLightDomA11yForAdditionalSlots();
|
this._enhanceLightDomA11yForAdditionalSlots();
|
||||||
|
|
@ -181,7 +196,7 @@ export const FormControlMixin = dedupeMixin(
|
||||||
additionalSlots = ['prefix', 'suffix', 'before', 'after'],
|
additionalSlots = ['prefix', 'suffix', 'before', 'after'],
|
||||||
) {
|
) {
|
||||||
additionalSlots.forEach(additionalSlot => {
|
additionalSlots.forEach(additionalSlot => {
|
||||||
const element = this.$$slot(additionalSlot);
|
const element = this.__getDirectSlotChild(additionalSlot);
|
||||||
if (element) {
|
if (element) {
|
||||||
element.id = element.id || `${additionalSlot}-${this._inputId}`;
|
element.id = element.id || `${additionalSlot}-${this._inputId}`;
|
||||||
if (element.hasAttribute('data-label') === true) {
|
if (element.hasAttribute('data-label') === true) {
|
||||||
|
|
@ -218,14 +233,14 @@ export const FormControlMixin = dedupeMixin(
|
||||||
}
|
}
|
||||||
|
|
||||||
_onLabelChanged({ label }) {
|
_onLabelChanged({ label }) {
|
||||||
if (this.$$slot && this.$$slot('label')) {
|
if (this._labelNode) {
|
||||||
this.$$slot('label').textContent = label;
|
this._labelNode.textContent = label;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_onHelpTextChanged({ helpText }) {
|
_onHelpTextChanged({ helpText }) {
|
||||||
if (this.$$slot && this.$$slot('help-text')) {
|
if (this._helpTextNode) {
|
||||||
this.$$slot('help-text').textContent = helpText;
|
this._helpTextNode.textContent = helpText;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -552,7 +567,7 @@ export const FormControlMixin = dedupeMixin(
|
||||||
|
|
||||||
// Returns dom references to all elements that should be referred to by field(s)
|
// Returns dom references to all elements that should be referred to by field(s)
|
||||||
_getAriaDescriptionElements() {
|
_getAriaDescriptionElements() {
|
||||||
return [this.$$slot('help-text'), this.$$slot('feedback')];
|
return [this._helpTextNode, this._feedbackNode];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -574,5 +589,9 @@ export const FormControlMixin = dedupeMixin(
|
||||||
addToAriaDescription(id) {
|
addToAriaDescription(id) {
|
||||||
this._ariaDescribedby += ` ${id}`;
|
this._ariaDescribedby += ` ${id}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
__getDirectSlotChild(slotName) {
|
||||||
|
return [...this.children].find(el => el.slot === slotName);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
/* eslint-disable class-methods-use-this */
|
/* eslint-disable class-methods-use-this */
|
||||||
|
|
||||||
import { dedupeMixin } from '@lion/core';
|
import { dedupeMixin } from '@lion/core';
|
||||||
import { EventMixin } from '@lion/core/src/EventMixin.js';
|
|
||||||
import { ObserverMixin } from '@lion/core/src/ObserverMixin.js';
|
import { ObserverMixin } from '@lion/core/src/ObserverMixin.js';
|
||||||
import { Unparseable } from '@lion/validate';
|
import { Unparseable } from '@lion/validate';
|
||||||
|
|
||||||
|
|
@ -21,7 +20,7 @@ import { Unparseable } from '@lion/validate';
|
||||||
export const FormatMixin = dedupeMixin(
|
export const FormatMixin = dedupeMixin(
|
||||||
superclass =>
|
superclass =>
|
||||||
// eslint-disable-next-line no-unused-vars, no-shadow
|
// eslint-disable-next-line no-unused-vars, no-shadow
|
||||||
class FormatMixin extends EventMixin(ObserverMixin(superclass)) {
|
class FormatMixin extends ObserverMixin(superclass) {
|
||||||
static get properties() {
|
static get properties() {
|
||||||
return {
|
return {
|
||||||
...super.properties,
|
...super.properties,
|
||||||
|
|
@ -231,7 +230,7 @@ export const FormatMixin = dedupeMixin(
|
||||||
// imperatively, we DO want to format a value (it is the only way to get meaningful
|
// imperatively, we DO want to format a value (it is the only way to get meaningful
|
||||||
// input into `.inputElement` with modelValue as input)
|
// input into `.inputElement` with modelValue as input)
|
||||||
|
|
||||||
if (this.__isHandlingUserInput && this.errorState) {
|
if (this.__isHandlingUserInput && this.errorState && this.inputElement) {
|
||||||
return this.inputElement ? this.value : undefined;
|
return this.inputElement ? this.value : undefined;
|
||||||
}
|
}
|
||||||
return this.formatter(this.modelValue, this.formatOptions);
|
return this.formatter(this.modelValue, this.formatOptions);
|
||||||
|
|
@ -336,8 +335,6 @@ export const FormatMixin = dedupeMixin(
|
||||||
// is guaranteed to be calculated
|
// is guaranteed to be calculated
|
||||||
setTimeout(this._reflectBackFormattedValueToUser);
|
setTimeout(this._reflectBackFormattedValueToUser);
|
||||||
};
|
};
|
||||||
this.inputElement.addEventListener(this.formatOn, this._reflectBackFormattedValueDebounced);
|
|
||||||
this.inputElement.addEventListener('input', this._proxyInputEvent);
|
|
||||||
this.addEventListener('user-input-changed', this._onUserInputChanged);
|
this.addEventListener('user-input-changed', this._onUserInputChanged);
|
||||||
// Connect the value found in <input> to the formatting/parsing/serializing loop as a
|
// Connect the value found in <input> to the formatting/parsing/serializing loop as a
|
||||||
// fallback mechanism. Assume the user uses the value property of the
|
// fallback mechanism. Assume the user uses the value property of the
|
||||||
|
|
@ -348,16 +345,26 @@ export const FormatMixin = dedupeMixin(
|
||||||
this._syncValueUpwards();
|
this._syncValueUpwards();
|
||||||
}
|
}
|
||||||
this._reflectBackFormattedValueToUser();
|
this._reflectBackFormattedValueToUser();
|
||||||
|
|
||||||
|
if (this.inputElement) {
|
||||||
|
this.inputElement.addEventListener(
|
||||||
|
this.formatOn,
|
||||||
|
this._reflectBackFormattedValueDebounced,
|
||||||
|
);
|
||||||
|
this.inputElement.addEventListener('input', this._proxyInputEvent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
this.inputElement.removeEventListener('input', this._proxyInputEvent);
|
|
||||||
this.removeEventListener('user-input-changed', this._onUserInputChanged);
|
this.removeEventListener('user-input-changed', this._onUserInputChanged);
|
||||||
this.inputElement.removeEventListener(
|
if (this.inputElement) {
|
||||||
this.formatOn,
|
this.inputElement.removeEventListener('input', this._proxyInputEvent);
|
||||||
this._reflectBackFormattedValueDebounced,
|
this.inputElement.removeEventListener(
|
||||||
);
|
this.formatOn,
|
||||||
|
this._reflectBackFormattedValueDebounced,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
import { dedupeMixin } from '@lion/core';
|
import { dedupeMixin } from '@lion/core';
|
||||||
import { CssClassMixin } from '@lion/core/src/CssClassMixin.js';
|
|
||||||
import { ObserverMixin } from '@lion/core/src/ObserverMixin.js';
|
import { ObserverMixin } from '@lion/core/src/ObserverMixin.js';
|
||||||
import { Unparseable } from '@lion/validate';
|
import { Unparseable } from '@lion/validate';
|
||||||
import { FocusMixin } from './FocusMixin.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `InteractionStateMixin` adds meta information about touched and dirty states, that can
|
* `InteractionStateMixin` adds meta information about touched and dirty states, that can
|
||||||
|
|
@ -16,7 +14,7 @@ import { FocusMixin } from './FocusMixin.js';
|
||||||
export const InteractionStateMixin = dedupeMixin(
|
export const InteractionStateMixin = dedupeMixin(
|
||||||
superclass =>
|
superclass =>
|
||||||
// eslint-disable-next-line no-unused-vars, no-shadow
|
// eslint-disable-next-line no-unused-vars, no-shadow
|
||||||
class InteractionStateMixin extends CssClassMixin(FocusMixin(ObserverMixin(superclass))) {
|
class InteractionStateMixin extends ObserverMixin(superclass) {
|
||||||
static get properties() {
|
static get properties() {
|
||||||
return {
|
return {
|
||||||
...super.properties,
|
...super.properties,
|
||||||
|
|
@ -25,7 +23,7 @@ export const InteractionStateMixin = dedupeMixin(
|
||||||
*/
|
*/
|
||||||
touched: {
|
touched: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
nonEmptyToClass: 'state-touched',
|
reflect: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -33,7 +31,7 @@ export const InteractionStateMixin = dedupeMixin(
|
||||||
*/
|
*/
|
||||||
dirty: {
|
dirty: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
nonEmptyToClass: 'state-dirty',
|
reflect: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -75,7 +73,7 @@ export const InteractionStateMixin = dedupeMixin(
|
||||||
this.touched = false;
|
this.touched = false;
|
||||||
this.dirty = false;
|
this.dirty = false;
|
||||||
this.prefilled = false;
|
this.prefilled = false;
|
||||||
this.leaveEvent = 'blur';
|
this._leaveEvent = 'blur';
|
||||||
this._valueChangedEvent = 'model-value-changed';
|
this._valueChangedEvent = 'model-value-changed';
|
||||||
|
|
||||||
this._iStateOnLeave = this._iStateOnLeave.bind(this);
|
this._iStateOnLeave = this._iStateOnLeave.bind(this);
|
||||||
|
|
@ -89,7 +87,7 @@ export const InteractionStateMixin = dedupeMixin(
|
||||||
if (super.connectedCallback) {
|
if (super.connectedCallback) {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
}
|
}
|
||||||
this.addEventListener(this.leaveEvent, this._iStateOnLeave);
|
this.addEventListener(this._leaveEvent, this._iStateOnLeave);
|
||||||
this.addEventListener(this._valueChangedEvent, this._iStateOnValueChange);
|
this.addEventListener(this._valueChangedEvent, this._iStateOnValueChange);
|
||||||
this.initInteractionState();
|
this.initInteractionState();
|
||||||
}
|
}
|
||||||
|
|
@ -98,10 +96,21 @@ export const InteractionStateMixin = dedupeMixin(
|
||||||
if (super.disconnectedCallback) {
|
if (super.disconnectedCallback) {
|
||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
}
|
}
|
||||||
this.removeEventListener(this.leaveEvent, this._iStateOnLeave);
|
this.removeEventListener(this._leaveEvent, this._iStateOnLeave);
|
||||||
this.removeEventListener(this._valueChangedEvent, this._iStateOnValueChange);
|
this.removeEventListener(this._valueChangedEvent, this._iStateOnValueChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updated(changedProperties) {
|
||||||
|
super.updated(changedProperties);
|
||||||
|
// classes are added only for backward compatibility - they are deprecated
|
||||||
|
if (changedProperties.has('touched')) {
|
||||||
|
this.classList[this.touched ? 'add' : 'remove']('state-touched');
|
||||||
|
}
|
||||||
|
if (changedProperties.has('dirty')) {
|
||||||
|
this.classList[this.dirty ? 'add' : 'remove']('state-dirty');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Evaluations performed on connectedCallback. Since some components can be out of sync
|
* Evaluations performed on connectedCallback. Since some components can be out of sync
|
||||||
* (due to interdependence on light children that can only be processed
|
* (due to interdependence on light children that can only be processed
|
||||||
|
|
@ -150,5 +159,19 @@ export const InteractionStateMixin = dedupeMixin(
|
||||||
_onDirtyChanged() {
|
_onDirtyChanged() {
|
||||||
this.dispatchEvent(new CustomEvent('dirty-changed', { bubbles: true, composed: true }));
|
this.dispatchEvent(new CustomEvent('dirty-changed', { bubbles: true, composed: true }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
get leaveEvent() {
|
||||||
|
return this._leaveEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
set leaveEvent(eventName) {
|
||||||
|
this._leaveEvent = eventName;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import { ValidateMixin } from '@lion/validate';
|
||||||
import { FormControlMixin } from './FormControlMixin.js';
|
import { FormControlMixin } from './FormControlMixin.js';
|
||||||
import { InteractionStateMixin } from './InteractionStateMixin.js'; // applies FocusMixin
|
import { InteractionStateMixin } from './InteractionStateMixin.js'; // applies FocusMixin
|
||||||
import { FormatMixin } from './FormatMixin.js';
|
import { FormatMixin } from './FormatMixin.js';
|
||||||
|
import { FocusMixin } from './FocusMixin.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* LionField: wraps components input, textarea and select and potentially others
|
* LionField: wraps components input, textarea and select and potentially others
|
||||||
|
|
@ -29,9 +30,11 @@ import { FormatMixin } from './FormatMixin.js';
|
||||||
// eslint-disable-next-line max-len, no-unused-vars
|
// eslint-disable-next-line max-len, no-unused-vars
|
||||||
export class LionField extends FormControlMixin(
|
export class LionField extends FormControlMixin(
|
||||||
InteractionStateMixin(
|
InteractionStateMixin(
|
||||||
FormatMixin(
|
FocusMixin(
|
||||||
ValidateMixin(
|
FormatMixin(
|
||||||
CssClassMixin(ElementMixin(DelegateMixin(SlotMixin(ObserverMixin(LionLitElement))))),
|
ValidateMixin(
|
||||||
|
CssClassMixin(ElementMixin(DelegateMixin(SlotMixin(ObserverMixin(LionLitElement))))),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { expect, fixture, html, aTimeout, defineCE, unsafeStatic } from '@open-wc/testing';
|
import { expect, fixture, html, aTimeout, defineCE, unsafeStatic } from '@open-wc/testing';
|
||||||
import sinon from 'sinon';
|
import sinon from 'sinon';
|
||||||
|
|
||||||
import { LionLitElement } from '@lion/core/src/LionLitElement.js';
|
import { LitElement } from '@lion/core';
|
||||||
import { Unparseable } from '@lion/validate';
|
import { Unparseable } from '@lion/validate';
|
||||||
import { FormatMixin } from '../src/FormatMixin.js';
|
import { FormatMixin } from '../src/FormatMixin.js';
|
||||||
|
|
||||||
|
|
@ -17,7 +17,7 @@ describe('FormatMixin', () => {
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
const tagString = defineCE(
|
const tagString = defineCE(
|
||||||
class extends FormatMixin(LionLitElement) {
|
class extends FormatMixin(LitElement) {
|
||||||
render() {
|
render() {
|
||||||
return html`
|
return html`
|
||||||
<slot name="input"></slot>
|
<slot name="input"></slot>
|
||||||
|
|
@ -176,6 +176,14 @@ describe('FormatMixin', () => {
|
||||||
expect(el.inputElement.value).to.equal('foo: test2');
|
expect(el.inputElement.value).to.equal('foo: test2');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('works if there is no underlying inputElement', async () => {
|
||||||
|
const tagNoInputString = defineCE(class extends FormatMixin(LitElement) {});
|
||||||
|
const tagNoInput = unsafeStatic(tagNoInputString);
|
||||||
|
expect(async () => {
|
||||||
|
await fixture(html`<${tagNoInput}></${tagNoInput}>`);
|
||||||
|
}).to.not.throw();
|
||||||
|
});
|
||||||
|
|
||||||
describe('parsers/formatters/serializers', () => {
|
describe('parsers/formatters/serializers', () => {
|
||||||
it('should call the parser|formatter|serializer provided by user', async () => {
|
it('should call the parser|formatter|serializer provided by user', async () => {
|
||||||
const formatterSpy = sinon.spy(value => `foo: ${value}`);
|
const formatterSpy = sinon.spy(value => `foo: ${value}`);
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,28 @@
|
||||||
import { expect, fixture, unsafeStatic, html, defineCE } from '@open-wc/testing';
|
import {
|
||||||
|
expect,
|
||||||
|
fixture,
|
||||||
|
unsafeStatic,
|
||||||
|
html,
|
||||||
|
defineCE,
|
||||||
|
triggerFocusFor,
|
||||||
|
triggerBlurFor,
|
||||||
|
} from '@open-wc/testing';
|
||||||
import sinon from 'sinon';
|
import sinon from 'sinon';
|
||||||
import { LionLitElement } from '@lion/core/src/LionLitElement.js';
|
import { LitElement } from '@lion/core';
|
||||||
|
|
||||||
import { InteractionStateMixin } from '../src/InteractionStateMixin.js';
|
import { InteractionStateMixin } from '../src/InteractionStateMixin.js';
|
||||||
|
|
||||||
describe('InteractionStateMixin', async () => {
|
describe('InteractionStateMixin', async () => {
|
||||||
let elem;
|
let tagString;
|
||||||
|
let tag;
|
||||||
before(() => {
|
before(() => {
|
||||||
elem = defineCE(
|
tagString = defineCE(
|
||||||
class IState extends InteractionStateMixin(LionLitElement) {
|
class IState extends InteractionStateMixin(LitElement) {
|
||||||
|
connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
this.tabIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
set modelValue(v) {
|
set modelValue(v) {
|
||||||
this._modelValue = v;
|
this._modelValue = v;
|
||||||
this.dispatchEvent(
|
this.dispatchEvent(
|
||||||
|
|
@ -19,149 +33,157 @@ describe('InteractionStateMixin', async () => {
|
||||||
get modelValue() {
|
get modelValue() {
|
||||||
return this._modelValue;
|
return this._modelValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
get inputElement() {
|
|
||||||
return this.querySelector('input');
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
tag = unsafeStatic(tagString);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sets states to false on init', async () => {
|
it('sets states to false on init', async () => {
|
||||||
const input = await fixture(`<${elem}><input slot="input"></${elem}>`);
|
const el = await fixture(html`<${tag}></${tag}>`);
|
||||||
expect(input.dirty).to.equal(false);
|
expect(el.dirty).to.be.false;
|
||||||
expect(input.touched).to.equal(false);
|
expect(el.touched).to.be.false;
|
||||||
expect(input.prefilled).to.equal(false);
|
expect(el.prefilled).to.be.false;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sets dirty when value changed', async () => {
|
it('sets dirty when value changed', async () => {
|
||||||
const input = await fixture(`<${elem}><input slot="input"></${elem}>`);
|
const el = await fixture(html`<${tag}></${tag}>`);
|
||||||
input.modelValue = 'foobar';
|
expect(el.dirty).to.be.false;
|
||||||
expect(input.dirty).to.equal(true);
|
el.modelValue = 'foobar';
|
||||||
|
expect(el.dirty).to.be.true;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Skipping, since this issue (not being able to set focus on element extending from LitElement)
|
|
||||||
// only occurs in WCT context (not in Storybook/Stackblitz).
|
|
||||||
// See: https://stackblitz.com/edit/lit-element-request-update-bug-g59tjq?file=blurry.js
|
|
||||||
// it.skip
|
|
||||||
it('sets touched to true when field left after focus', async () => {
|
it('sets touched to true when field left after focus', async () => {
|
||||||
// const formElement = await LionTest.htmlFixture(`<${elem}><input type="text" slot="input"></${elem}>`);
|
const el = await fixture(html`<${tag}></${tag}>`);
|
||||||
// await triggerFocusFor(formElement.inputElement); // focus/blur can't be delegated
|
await triggerFocusFor(el);
|
||||||
// await triggerBlurFor(formElement.inputElement);
|
await triggerBlurFor(el);
|
||||||
// expect(formElement.touched).to.equal(true);
|
expect(el.touched).to.be.true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// classes are added only for backward compatibility - they are deprecated
|
||||||
it('sets a class "state-(touched|dirty)"', async () => {
|
it('sets a class "state-(touched|dirty)"', async () => {
|
||||||
const state = await fixture(`<${elem}><input slot="input"></${elem}>`);
|
const el = await fixture(html`<${tag}></${tag}>`);
|
||||||
state.touched = true;
|
el.touched = true;
|
||||||
await state.updateComplete;
|
await el.updateComplete;
|
||||||
expect(state.classList.contains('state-touched')).to.equal(true, 'has class "state-touched"');
|
expect(el.classList.contains('state-touched')).to.equal(true, 'has class "state-touched"');
|
||||||
|
|
||||||
state.dirty = true;
|
el.dirty = true;
|
||||||
await state.updateComplete;
|
await el.updateComplete;
|
||||||
expect(state.classList.contains('state-dirty')).to.equal(true, 'has class "state-dirty"');
|
expect(el.classList.contains('state-dirty')).to.equal(true, 'has class "state-dirty"');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets an attribute "touched', async () => {
|
||||||
|
const el = await fixture(html`<${tag}></${tag}>`);
|
||||||
|
el.touched = true;
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(el.hasAttribute('touched')).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets an attribute "dirty', async () => {
|
||||||
|
const el = await fixture(html`<${tag}></${tag}>`);
|
||||||
|
el.dirty = true;
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(el.hasAttribute('dirty')).to.be.true;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('fires "(touched|dirty)-state-changed" event when state changes', async () => {
|
it('fires "(touched|dirty)-state-changed" event when state changes', async () => {
|
||||||
const iState = await fixture(`<${elem}><input slot="input"></${elem}>`);
|
const touchedSpy = sinon.spy();
|
||||||
const cbTouched = sinon.spy();
|
const dirtySpy = sinon.spy();
|
||||||
const cbDirty = sinon.spy();
|
const el = await fixture(
|
||||||
|
html`<${tag} @touched-changed=${touchedSpy} @dirty-changed=${dirtySpy}></${tag}>`,
|
||||||
|
);
|
||||||
|
|
||||||
iState.addEventListener('touched-changed', cbTouched);
|
el.touched = true;
|
||||||
iState.addEventListener('dirty-changed', cbDirty);
|
expect(touchedSpy.callCount).to.equal(1);
|
||||||
|
|
||||||
iState.touched = true;
|
el.dirty = true;
|
||||||
expect(cbTouched.callCount).to.equal(1);
|
expect(dirtySpy.callCount).to.equal(1);
|
||||||
|
|
||||||
iState.dirty = true;
|
|
||||||
expect(cbDirty.callCount).to.equal(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Skipping, since this issue (not being able to set focus on element extending from LitElement)
|
|
||||||
// only occurs in WCT context (not in Storybook/Stackblitz).
|
|
||||||
// See: https://stackblitz.com/edit/lit-element-request-update-bug-g59tjq?file=blurry.js
|
|
||||||
// it.skip
|
|
||||||
it('sets prefilled to true when field left and value non-empty', async () => {
|
|
||||||
// const iState = await LionTest.htmlFixture(`<${elem}><input slot="input"></${elem}>`);
|
|
||||||
// await triggerFocusFor(iState.inputElement);
|
|
||||||
// iState.modelValue = externalVariables.prefilledModelValue || '000';
|
|
||||||
// await triggerBlurFor(iState.inputElement);
|
|
||||||
// expect(iState.prefilled).to.equal(true);
|
|
||||||
// await triggerFocusFor(iState.inputElement);
|
|
||||||
// iState.modelValue = externalVariables.nonPrefilledModelValue || '';
|
|
||||||
// await triggerBlurFor(iState.inputElement);
|
|
||||||
// expect(iState.prefilled).to.equal(false);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sets prefilled once instantiated', async () => {
|
it('sets prefilled once instantiated', async () => {
|
||||||
const tag = unsafeStatic(elem);
|
const el = await fixture(html`
|
||||||
const element = await fixture(html`
|
<${tag} .modelValue=${'prefilled'}></${tag}>
|
||||||
<${tag}
|
`);
|
||||||
.modelValue=${'prefilled'}
|
expect(el.prefilled).to.be.true;
|
||||||
><input slot="input"></${tag}>`);
|
|
||||||
expect(element.prefilled).to.equal(true);
|
|
||||||
|
|
||||||
const nonPrefilled = await fixture(html`
|
const nonPrefilled = await fixture(html`
|
||||||
<${tag}
|
<${tag} .modelValue=${''}></${tag}>
|
||||||
.modelValue=''}
|
`);
|
||||||
><input slot="input"></${tag}>`);
|
expect(nonPrefilled.prefilled).to.be.false;
|
||||||
expect(nonPrefilled.prefilled).to.equal(false);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// This method actually tests the implementation of the _isPrefilled method.
|
// This method actually tests the implementation of the _isPrefilled method.
|
||||||
it(`can determine "prefilled" based on different modelValue types (Arrays, Objects, Numbers,
|
it(`can determine "prefilled" based on different modelValue types (Arrays, Objects, Numbers,
|
||||||
Booleans, Strings)`, async () => {
|
Booleans, Strings)`, async () => {
|
||||||
const input = await fixture(`<${elem}><input slot="input"></${elem}>`);
|
const el = await fixture(html`<${tag}></${tag}>`);
|
||||||
|
|
||||||
const changeModelValueAndLeave = modelValue => {
|
const changeModelValueAndLeave = modelValue => {
|
||||||
input.dispatchEvent(new Event('focus', { bubbles: true }));
|
el.dispatchEvent(new Event('focus', { bubbles: true }));
|
||||||
input.modelValue = modelValue;
|
el.modelValue = modelValue;
|
||||||
input.dispatchEvent(new Event('blur', { bubbles: true }));
|
el.dispatchEvent(new Event('blur', { bubbles: true }));
|
||||||
};
|
};
|
||||||
|
|
||||||
// Prefilled
|
// Prefilled
|
||||||
changeModelValueAndLeave(input, ['bla']);
|
changeModelValueAndLeave(['not-empty']);
|
||||||
expect(input.prefilled).to.equal(false, 'empty array should be considered "prefilled"');
|
expect(el.prefilled, 'not empty array should be "prefilled"').to.be.true;
|
||||||
changeModelValueAndLeave(input, { bla: 'bla' });
|
changeModelValueAndLeave({ not: 'empty' });
|
||||||
expect(input.prefilled).to.equal(false, 'empty object should be considered "prefilled"');
|
expect(el.prefilled, 'not empty object should be "prefilled"').to.be.true;
|
||||||
changeModelValueAndLeave(input, 0);
|
changeModelValueAndLeave(0);
|
||||||
expect(input.prefilled).to.equal(false, 'numbers should be considered "prefilled"');
|
expect(el.prefilled, 'numbers should be "prefilled"').to.be.true;
|
||||||
changeModelValueAndLeave(input, false);
|
changeModelValueAndLeave(false);
|
||||||
expect(input.prefilled).to.equal(false, 'Booleans should be considered "prefilled"');
|
expect(el.prefilled, 'booleans should be "prefilled"').to.be.true;
|
||||||
changeModelValueAndLeave(input, '');
|
|
||||||
expect(input.prefilled).to.equal(false, 'empty string should be considered "prefilled"');
|
|
||||||
|
|
||||||
// Not prefilled
|
// Not prefilled
|
||||||
changeModelValueAndLeave(input, []);
|
changeModelValueAndLeave([]);
|
||||||
expect(input.prefilled).to.equal(false, 'empty array should not be considered "prefilled"');
|
expect(el.prefilled, 'empty array should not be "prefilled"').to.be.false;
|
||||||
changeModelValueAndLeave(input, {});
|
changeModelValueAndLeave({});
|
||||||
expect(input.prefilled).to.equal(false, 'empty object should not be considered "prefilled"');
|
expect(el.prefilled, 'empty object should not be "prefilled"').to.be.false;
|
||||||
changeModelValueAndLeave(input, '');
|
changeModelValueAndLeave('');
|
||||||
expect(input.prefilled).to.equal(false, 'empty string should not be considered "prefilled"');
|
expect(el.prefilled, 'empty string should not be "prefilled"').to.be.false;
|
||||||
|
changeModelValueAndLeave(null);
|
||||||
changeModelValueAndLeave(input, null);
|
expect(el.prefilled, 'null should not be "prefilled"').to.be.false;
|
||||||
expect(input.prefilled).to.equal(false, 'null should not be considered "prefilled"');
|
changeModelValueAndLeave(undefined);
|
||||||
changeModelValueAndLeave(input, undefined);
|
expect(el.prefilled, 'undefined should not be "prefilled"').to.be.false;
|
||||||
expect(input.prefilled).to.equal(false, 'undefined should not be considered "prefilled"');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('has a method resetInteractionState()', async () => {
|
it('has a method resetInteractionState()', async () => {
|
||||||
const input = await fixture(`<${elem}><input slot="input"></${elem}>`);
|
const el = await fixture(html`<${tag}></${tag}>`);
|
||||||
input.dirty = true;
|
el.dirty = true;
|
||||||
input.touched = true;
|
el.touched = true;
|
||||||
input.prefilled = true;
|
el.prefilled = true;
|
||||||
input.resetInteractionState();
|
el.resetInteractionState();
|
||||||
expect(input.dirty).to.equal(false);
|
expect(el.dirty).to.be.false;
|
||||||
expect(input.touched).to.equal(false);
|
expect(el.touched).to.be.false;
|
||||||
expect(input.prefilled).to.equal(false);
|
expect(el.prefilled).to.be.false;
|
||||||
|
|
||||||
input.dirty = true;
|
el.dirty = true;
|
||||||
input.touched = true;
|
el.touched = true;
|
||||||
input.prefilled = false;
|
el.prefilled = false;
|
||||||
input.modelValue = 'Some value';
|
el.modelValue = 'Some value';
|
||||||
input.resetInteractionState();
|
el.resetInteractionState();
|
||||||
expect(input.dirty).to.equal(false);
|
expect(el.dirty).to.be.false;
|
||||||
expect(input.touched).to.equal(false);
|
expect(el.touched).to.be.false;
|
||||||
expect(input.prefilled).to.equal(true);
|
expect(el.prefilled).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('SubClassers', () => {
|
||||||
|
it('can override the `_leaveEvent`', async () => {
|
||||||
|
const tagLeaveString = defineCE(
|
||||||
|
class IState extends InteractionStateMixin(LitElement) {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this._leaveEvent = 'custom-blur';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const tagLeave = unsafeStatic(tagLeaveString);
|
||||||
|
const el = await fixture(html`<${tagLeave}></${tagLeave}>`);
|
||||||
|
el.dispatchEvent(new Event('custom-blur'));
|
||||||
|
expect(el.touched).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can override the deprecated `leaveEvent`', async () => {
|
||||||
|
const el = await fixture(html`<${tag} .leaveEvent=${'custom-blur'}></${tag}>`);
|
||||||
|
expect(el._leaveEvent).to.equal('custom-blur');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
import { dedupeMixin, SlotMixin } from '@lion/core';
|
import { dedupeMixin, SlotMixin } from '@lion/core';
|
||||||
import { ObserverMixin } from '@lion/core/src/ObserverMixin.js';
|
import { ObserverMixin } from '@lion/core/src/ObserverMixin.js';
|
||||||
import { CssClassMixin } from '@lion/core/src/CssClassMixin.js';
|
|
||||||
import { localize, LocalizeMixin } from '@lion/localize';
|
import { localize, LocalizeMixin } from '@lion/localize';
|
||||||
import { Unparseable } from './Unparseable.js';
|
import { Unparseable } from './Unparseable.js';
|
||||||
import { randomOk } from './validators.js';
|
import { randomOk } from './validators.js';
|
||||||
|
|
@ -15,7 +14,7 @@ const pascalCase = str => str.charAt(0).toUpperCase() + str.slice(1);
|
||||||
export const ValidateMixin = dedupeMixin(
|
export const ValidateMixin = dedupeMixin(
|
||||||
superclass =>
|
superclass =>
|
||||||
// eslint-disable-next-line no-unused-vars, no-shadow, max-len
|
// eslint-disable-next-line no-unused-vars, no-shadow, max-len
|
||||||
class ValidateMixin extends CssClassMixin(ObserverMixin(LocalizeMixin(SlotMixin(superclass)))) {
|
class ValidateMixin extends ObserverMixin(LocalizeMixin(SlotMixin(superclass))) {
|
||||||
/* * * * * * * * * *
|
/* * * * * * * * * *
|
||||||
Configuration */
|
Configuration */
|
||||||
|
|
||||||
|
|
@ -120,11 +119,13 @@ export const ValidateMixin = dedupeMixin(
|
||||||
},
|
},
|
||||||
errorState: {
|
errorState: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
nonEmptyToClass: 'state-error',
|
attribute: 'error-state',
|
||||||
|
reflect: true,
|
||||||
},
|
},
|
||||||
errorShow: {
|
errorShow: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
nonEmptyToClass: 'state-error-show',
|
attribute: 'error-show',
|
||||||
|
reflect: true,
|
||||||
},
|
},
|
||||||
warningValidators: {
|
warningValidators: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|
@ -134,11 +135,13 @@ export const ValidateMixin = dedupeMixin(
|
||||||
},
|
},
|
||||||
warningState: {
|
warningState: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
nonEmptyToClass: 'state-warning',
|
attribute: 'warning-state',
|
||||||
|
reflect: true,
|
||||||
},
|
},
|
||||||
warningShow: {
|
warningShow: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
nonEmptyToClass: 'state-warning-show',
|
attribute: 'warning-show',
|
||||||
|
reflect: true,
|
||||||
},
|
},
|
||||||
infoValidators: {
|
infoValidators: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|
@ -148,11 +151,13 @@ export const ValidateMixin = dedupeMixin(
|
||||||
},
|
},
|
||||||
infoState: {
|
infoState: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
nonEmptyToClass: 'state-info',
|
attribute: 'info-state',
|
||||||
|
reflect: true,
|
||||||
},
|
},
|
||||||
infoShow: {
|
infoShow: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
nonEmptyToClass: 'state-info-show',
|
attribute: 'info-show',
|
||||||
|
reflect: true,
|
||||||
},
|
},
|
||||||
successValidators: {
|
successValidators: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|
@ -162,15 +167,17 @@ export const ValidateMixin = dedupeMixin(
|
||||||
},
|
},
|
||||||
successState: {
|
successState: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
nonEmptyToClass: 'state-success',
|
attribute: 'success-state',
|
||||||
|
reflect: true,
|
||||||
},
|
},
|
||||||
successShow: {
|
successShow: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
nonEmptyToClass: 'state-success-show',
|
attribute: 'success-show',
|
||||||
|
reflect: true,
|
||||||
},
|
},
|
||||||
invalid: {
|
invalid: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
nonEmptyToClass: 'state-invalid',
|
reflect: true,
|
||||||
},
|
},
|
||||||
message: {
|
message: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
|
@ -236,6 +243,23 @@ export const ValidateMixin = dedupeMixin(
|
||||||
return (this.$$slot && this.$$slot('feedback')) || this.querySelector('[slot="feedback"]');
|
return (this.$$slot && this.$$slot('feedback')) || this.querySelector('[slot="feedback"]');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updated(changedProperties) {
|
||||||
|
super.updated(changedProperties);
|
||||||
|
|
||||||
|
// @deprecated adding css classes for backwards compatibility
|
||||||
|
this.constructor.validationTypes.forEach(name => {
|
||||||
|
if (changedProperties.has(`${name}State`)) {
|
||||||
|
this.classList[this[`${name}State`] ? 'add' : 'remove'](`state-${name}`);
|
||||||
|
}
|
||||||
|
if (changedProperties.has(`${name}Show`)) {
|
||||||
|
this.classList[this[`${name}Show`] ? 'add' : 'remove'](`state-${name}-show`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (changedProperties.has('invalid')) {
|
||||||
|
this.classList[this.invalid ? 'add' : 'remove'](`state-invalid`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getFieldName(validatorParams) {
|
getFieldName(validatorParams) {
|
||||||
const label =
|
const label =
|
||||||
this.label || (this.$$slot && this.$$slot('label') && this.$$slot('label').textContent);
|
this.label || (this.$$slot && this.$$slot('label') && this.$$slot('label').textContent);
|
||||||
|
|
|
||||||
|
|
@ -143,6 +143,100 @@ describe('ValidateMixin', () => {
|
||||||
expect(otherValidatorSpy.calledWith('foo')).to.equal(true);
|
expect(otherValidatorSpy.calledWith('foo')).to.equal(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// classes are added only for backward compatibility - they are deprecated
|
||||||
|
it('sets a class "state-(error|warning|info|success|invalid)"', async () => {
|
||||||
|
const el = await fixture(html`<${tag}></${tag}>`);
|
||||||
|
el.errorState = true;
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(el.classList.contains('state-error')).to.equal(true, 'has class "state-error"');
|
||||||
|
|
||||||
|
el.warningState = true;
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(el.classList.contains('state-warning')).to.equal(true, 'has class "state-warning"');
|
||||||
|
|
||||||
|
el.infoState = true;
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(el.classList.contains('state-info')).to.equal(true, 'has class "state-info"');
|
||||||
|
|
||||||
|
el.successState = true;
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(el.classList.contains('state-success')).to.equal(true, 'has class "state-success"');
|
||||||
|
|
||||||
|
el.invalid = true;
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(el.classList.contains('state-invalid')).to.equal(true, 'has class "state-invalid"');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets a class "state-(error|warning|info|success)-show"', async () => {
|
||||||
|
const el = await fixture(html`<${tag}></${tag}>`);
|
||||||
|
el.errorShow = true;
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(el.classList.contains('state-error-show')).to.equal(
|
||||||
|
true,
|
||||||
|
'has class "state-error-show"',
|
||||||
|
);
|
||||||
|
|
||||||
|
el.warningShow = true;
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(el.classList.contains('state-warning-show')).to.equal(
|
||||||
|
true,
|
||||||
|
'has class "state-warning-show"',
|
||||||
|
);
|
||||||
|
|
||||||
|
el.infoShow = true;
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(el.classList.contains('state-info-show')).to.equal(true, 'has class "state-info-show"');
|
||||||
|
|
||||||
|
el.successShow = true;
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(el.classList.contains('state-success-show')).to.equal(
|
||||||
|
true,
|
||||||
|
'has class "state-success-show"',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets attribute "(error|warning|info|success|invalid)-state"', async () => {
|
||||||
|
const el = await fixture(html`<${tag}></${tag}>`);
|
||||||
|
el.errorState = true;
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(el.hasAttribute('error-state'), 'has error-state attribute').to.be.true;
|
||||||
|
|
||||||
|
el.warningState = true;
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(el.hasAttribute('warning-state'), 'has warning-state attribute').to.be.true;
|
||||||
|
|
||||||
|
el.infoState = true;
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(el.hasAttribute('info-state'), 'has info-state attribute').to.be.true;
|
||||||
|
|
||||||
|
el.successState = true;
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(el.hasAttribute('success-state'), 'has error-state attribute').to.be.true;
|
||||||
|
|
||||||
|
el.invalid = true;
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(el.hasAttribute('invalid'), 'has invalid attribute').to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sets attribute "(error|warning|info|success)-show"', async () => {
|
||||||
|
const el = await fixture(html`<${tag}></${tag}>`);
|
||||||
|
el.errorShow = true;
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(el.hasAttribute('error-show'), 'has error-show attribute').to.be.true;
|
||||||
|
|
||||||
|
el.warningShow = true;
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(el.hasAttribute('warning-show'), 'has warning-show attribute').to.be.true;
|
||||||
|
|
||||||
|
el.infoShow = true;
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(el.hasAttribute('info-show'), 'has info-show attribute').to.be.true;
|
||||||
|
|
||||||
|
el.successShow = true;
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(el.hasAttribute('success-show'), 'has success-show attribute').to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
describe(`Validators ${suffixName}`, () => {
|
describe(`Validators ${suffixName}`, () => {
|
||||||
function isCat(modelValue, opts) {
|
function isCat(modelValue, opts) {
|
||||||
const validateString = opts && opts.number ? `cat${opts.number}` : 'cat';
|
const validateString = opts && opts.number ? `cat${opts.number}` : 'cat';
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue