Socket
Socket
Sign inDemoInstall

@lion/field

Package Overview
Dependencies
Maintainers
1
Versions
103
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@lion/field - npm Package Compare versions

Comparing version 0.1.42 to 0.1.43

test-suites/FormatMixin.suite.js

16

CHANGELOG.md

@@ -6,2 +6,18 @@ # Change Log

## [0.1.43](https://github.com/ing-bank/lion/compare/@lion/field@0.1.42...@lion/field@0.1.43) (2019-08-14)
### Bug Fixes
* **field:** getter/setter for selectionStart/End instead of delegation ([07eddb3](https://github.com/ing-bank/lion/commit/07eddb3))
* **field:** move type property to input & add step property to input ([5d893f3](https://github.com/ing-bank/lion/commit/5d893f3))
* **field:** no delegate in FocusMixin; sync focused, redispatch events ([88f5264](https://github.com/ing-bank/lion/commit/88f5264))
* **field:** sync name down instead of delegating ([d2f4e3c](https://github.com/ing-bank/lion/commit/d2f4e3c))
* **field:** sync type down instead of delegating ([13b2740](https://github.com/ing-bank/lion/commit/13b2740))
* **field:** value delegation compatible with FormatMixin ([3217c1a](https://github.com/ing-bank/lion/commit/3217c1a))
## [0.1.42](https://github.com/ing-bank/lion/compare/@lion/field@0.1.41...@lion/field@0.1.42) (2019-08-07)

@@ -8,0 +24,0 @@

6

package.json
{
"name": "@lion/field",
"version": "0.1.42",
"version": "0.1.43",
"description": "Fields are the most fundamental building block of the Form System",

@@ -37,3 +37,3 @@ "author": "ing-bank",

"@lion/core": "^0.1.13",
"@lion/validate": "^0.2.25"
"@lion/validate": "^0.2.26"
},

@@ -46,3 +46,3 @@ "devDependencies": {

},
"gitHead": "f32aab653391872e23a25ecacb17111ad0b941a0"
"gitHead": "6cd0e34abc5f4c175e9a788bb860f9fa72fa808a"
}

@@ -16,3 +16,2 @@ import { dedupeMixin, nothing } from '@lion/core';

return {
...super.properties,
/**

@@ -19,0 +18,0 @@ * When no light dom defined and prop set

@@ -1,2 +0,2 @@

import { dedupeMixin, DelegateMixin } from '@lion/core';
import { dedupeMixin } from '@lion/core';

@@ -6,48 +6,116 @@ export const FocusMixin = dedupeMixin(

// eslint-disable-next-line no-unused-vars, max-len, no-shadow
class FocusMixin extends DelegateMixin(superclass) {
get delegations() {
class FocusMixin extends superclass {
static get properties() {
return {
...super.delegations,
target: () => this.inputElement,
events: [...super.delegations.events, 'focus', 'blur'], // since these events don't bubble
methods: [...super.delegations.methods, 'focus', 'blur'],
properties: [...super.delegations.properties, 'onfocus', 'onblur', 'autofocus'],
attributes: [...super.delegations.attributes, 'onfocus', 'onblur', 'autofocus'],
focused: {
type: Boolean,
reflect: true,
},
};
}
constructor() {
super();
this.focused = false;
}
connectedCallback() {
super.connectedCallback();
this._onFocus = this._onFocus.bind(this);
this._onBlur = this._onBlur.bind(this);
this.inputElement.addEventListener('focusin', this._onFocus);
this.inputElement.addEventListener('focusout', this._onBlur);
if (super.connectedCallback) {
super.connectedCallback();
}
this.__registerEventsForFocusMixin();
}
disconnectedCallback() {
super.disconnectedCallback();
this.inputElement.removeEventListener('focusin', this._onFocus);
this.inputElement.removeEventListener('focusout', this._onBlur);
if (super.disconnectedCallback) {
super.disconnectedCallback();
}
this.__teardownEventsForFocusMixin();
}
focus() {
const native = this.inputElement;
if (native) {
native.focus();
}
}
blur() {
const native = this.inputElement;
if (native) {
native.blur();
}
}
updated(changedProperties) {
super.updated(changedProperties);
// 'state-focused' css classes are deprecated
if (changedProperties.has('focused')) {
this.classList[this.focused ? 'add' : 'remove']('state-focused');
}
}
/**
* Helper Function to easily check if the element is being focused
* Functions should be private
*
* TODO: performance comparision vs
* return this.inputElement === document.activeElement;
* @deprecated
*/
get focused() {
return this.classList.contains('state-focused');
}
_onFocus() {
if (super._onFocus) super._onFocus();
this.classList.add('state-focused');
if (super._onFocus) {
super._onFocus();
}
this.focused = true;
}
/**
* Functions should be private
*
* @deprecated
*/
_onBlur() {
if (super._onBlur) super._onBlur();
this.classList.remove('state-focused');
if (super._onBlur) {
super._onBlur();
}
this.focused = false;
}
__registerEventsForFocusMixin() {
// focus
this.__redispatchFocus = ev => {
ev.stopPropagation();
this.dispatchEvent(new FocusEvent('focus'));
};
this.inputElement.addEventListener('focus', this.__redispatchFocus);
// blur
this.__redispatchBlur = ev => {
ev.stopPropagation();
this.dispatchEvent(new FocusEvent('blur'));
};
this.inputElement.addEventListener('blur', this.__redispatchBlur);
// focusin
this.__redispatchFocusin = ev => {
ev.stopPropagation();
this._onFocus(ev);
this.dispatchEvent(new FocusEvent('focusin', { bubbles: true, composed: true }));
};
this.inputElement.addEventListener('focusin', this.__redispatchFocusin);
// focusout
this.__redispatchFocusout = ev => {
ev.stopPropagation();
this._onBlur();
this.dispatchEvent(new FocusEvent('focusout', { bubbles: true, composed: true }));
};
this.inputElement.addEventListener('focusout', this.__redispatchFocusout);
}
__teardownEventsForFocusMixin() {
this.inputElement.removeEventListener('focus', this.__redispatchFocus);
this.inputElement.removeEventListener('blur', this.__redispatchBlur);
this.inputElement.removeEventListener('focusin', this.__redispatchFocusin);
this.inputElement.removeEventListener('focusout', this.__redispatchFocusout);
}
},
);

@@ -56,4 +56,2 @@ /* eslint-disable class-methods-use-this */

return {
...super.properties,
/**

@@ -60,0 +58,0 @@ * The model value is the result of the parser function(when available).

@@ -21,3 +21,2 @@ import { html, css, nothing, dedupeMixin, SlotMixin } from '@lion/core';

return {
...super.properties,
/**

@@ -24,0 +23,0 @@ * A list of ids that will be put on the inputElement as a serialized string

@@ -21,3 +21,2 @@ import { dedupeMixin } from '@lion/core';

return {
...super.properties,
/**

@@ -24,0 +23,0 @@ * True when user has focused and left(blurred) the field.

@@ -1,4 +0,4 @@

import { DelegateMixin, SlotMixin, LitElement } from '@lion/core';
import { SlotMixin, LitElement } from '@lion/core';
import { ElementMixin } from '@lion/core/src/ElementMixin.js';
import { CssClassMixin } from '@lion/core/src/CssClassMixin.js';
import { DisabledMixin } from '@lion/core/src/DisabledMixin.js';
import { ObserverMixin } from '@lion/core/src/ObserverMixin.js';

@@ -38,29 +38,8 @@ import { ValidateMixin } from '@lion/validate';

FocusMixin(
FormatMixin(
ValidateMixin(
CssClassMixin(ElementMixin(DelegateMixin(SlotMixin(ObserverMixin(LitElement))))),
),
),
FormatMixin(ValidateMixin(DisabledMixin(ElementMixin(SlotMixin(ObserverMixin(LitElement)))))),
),
),
) {
get delegations() {
return {
...super.delegations,
target: () => this.inputElement,
properties: [
...super.delegations.properties,
'name',
'type',
'disabled',
'selectionStart',
'selectionEnd',
],
attributes: [...super.delegations.attributes, 'name', 'type', 'disabled'],
};
}
static get properties() {
return {
...super.properties,
submitted: {

@@ -70,5 +49,39 @@ // make sure validation can be triggered based on observer

},
name: {
type: String,
reflect: true,
},
};
}
get selectionStart() {
const native = this.inputElement;
if (native && native.selectionStart) {
return native.selectionStart;
}
return 0;
}
set selectionStart(value) {
const native = this.inputElement;
if (native && native.selectionStart) {
native.selectionStart = value;
}
}
get selectionEnd() {
const native = this.inputElement;
if (native && native.selectionEnd) {
return native.selectionEnd;
}
return 0;
}
set selectionEnd(value) {
const native = this.inputElement;
if (native && native.selectionEnd) {
native.selectionEnd = value;
}
}
// We don't delegate, because we want to preserve caret position via _setValueAndPreserveCaret

@@ -87,10 +100,15 @@ set value(value) {

static get asyncObservers() {
return {
...super.asyncObservers,
_setDisabledClass: ['disabled'],
};
constructor() {
super();
this.name = '';
this.submitted = false;
}
connectedCallback() {
// TODO: Normally we put super calls on top for predictability,
// here we temporarily need to do attribute delegation before,
// so the FormatMixin uses the right value. Should be solved
// when value delegation is part of the calculation loop of
// FormatMixin
this._delegateInitialValueAttr();
super.connectedCallback();

@@ -100,4 +118,2 @@

this.inputElement.addEventListener('change', this._onChange);
this._delegateInitialValueAttr();
this._setDisabledClass();
this.classList.add('form-field'); // eslint-disable-line

@@ -119,4 +135,18 @@ }

_setDisabledClass() {
this.classList[this.disabled ? 'add' : 'remove']('state-disabled');
updated(changedProps) {
super.updated(changedProps);
if (changedProps.has('disabled')) {
if (this.disabled) {
this.inputElement.disabled = true;
this.classList.add('state-disabled'); // eslint-disable-line wc/no-self-class
} else {
this.inputElement.disabled = false;
this.classList.remove('state-disabled'); // eslint-disable-line wc/no-self-class
}
}
if (changedProps.has('name')) {
this.inputElement.name = this.name;
}
}

@@ -123,0 +153,0 @@

import { defineCE } from '@open-wc/testing';
import { runInteractionStateMixinSuite } from '../test-suites/InteractionStateMixin.suite.js';
import '../lion-field.js';
import { runFormatMixinSuite } from '../test-suites/FormatMixin.suite.js';

@@ -22,2 +23,6 @@ const fieldTagString = defineCE(

});
runFormatMixinSuite({
tagString: fieldTagString,
});
});

@@ -14,3 +14,2 @@ import { expect, fixture, defineCE } from '@open-wc/testing';

return {
...super.properties,
modelValue: {

@@ -17,0 +16,0 @@ type: String,

@@ -1,313 +0,3 @@

import { expect, fixture, html, aTimeout, defineCE, unsafeStatic } from '@open-wc/testing';
import sinon from 'sinon';
import { runFormatMixinSuite } from '../test-suites/FormatMixin.suite.js';
import { LitElement } from '@lion/core';
import { Unparseable } from '@lion/validate';
import { FormatMixin } from '../src/FormatMixin.js';
function mimicUserInput(formControl, newViewValue) {
formControl.value = newViewValue; // eslint-disable-line no-param-reassign
formControl.inputElement.dispatchEvent(new CustomEvent('input', { bubbles: true }));
}
describe('FormatMixin', () => {
let elem;
let nonFormat;
let fooFormat;
before(async () => {
const tagString = defineCE(
class extends FormatMixin(LitElement) {
render() {
return html`
<slot name="input"></slot>
`;
}
set value(newValue) {
this.inputElement.value = newValue;
}
get value() {
return this.inputElement.value;
}
get inputElement() {
return this.querySelector('input');
}
},
);
elem = unsafeStatic(tagString);
nonFormat = await fixture(html`<${elem}><input slot="input"></${elem}>`);
fooFormat = await fixture(html`
<${elem}
.formatter="${value => `foo: ${value}`}"
.parser="${value => value.replace('foo: ', '')}"
.serializer="${value => `[foo] ${value}`}"
.deserializer="${value => value.replace('[foo] ', '')}"
><input slot="input">
</${elem}>`);
});
it('fires `model-value-changed` for every change on the input', async () => {
const formatEl = await fixture(html`<${elem}><input slot="input"></${elem}>`);
let counter = 0;
formatEl.addEventListener('model-value-changed', () => {
counter += 1;
});
mimicUserInput(formatEl, 'one');
expect(counter).to.equal(1);
// no change means no event
mimicUserInput(formatEl, 'one');
expect(counter).to.equal(1);
mimicUserInput(formatEl, 'two');
expect(counter).to.equal(2);
});
it('fires `model-value-changed` for every modelValue change', async () => {
const el = await fixture(html`<${elem}><input slot="input"></${elem}>`);
let counter = 0;
el.addEventListener('model-value-changed', () => {
counter += 1;
});
el.modelValue = 'one';
expect(counter).to.equal(1);
// no change means no event
el.modelValue = 'one';
expect(counter).to.equal(1);
el.modelValue = 'two';
expect(counter).to.equal(2);
});
it('has modelValue, formattedValue and serializedValue which are computed synchronously', async () => {
expect(nonFormat.modelValue).to.equal('', 'modelValue initially');
expect(nonFormat.formattedValue).to.equal('', 'formattedValue initially');
expect(nonFormat.serializedValue).to.equal('', 'serializedValue initially');
nonFormat.modelValue = 'string';
expect(nonFormat.modelValue).to.equal('string', 'modelValue as provided');
expect(nonFormat.formattedValue).to.equal('string', 'formattedValue synchronized');
expect(nonFormat.serializedValue).to.equal('string', 'serializedValue synchronized');
});
it('has an input node (like <input>/<textarea>) which holds the formatted (view) value', async () => {
fooFormat.modelValue = 'string';
expect(fooFormat.formattedValue).to.equal('foo: string');
expect(fooFormat.value).to.equal('foo: string');
expect(fooFormat.inputElement.value).to.equal('foo: string');
});
it('converts modelValue => formattedValue (via this.formatter)', async () => {
fooFormat.modelValue = 'string';
expect(fooFormat.formattedValue).to.equal('foo: string');
expect(fooFormat.serializedValue).to.equal('[foo] string');
});
it('converts modelValue => serializedValue (via this.serializer)', async () => {
fooFormat.modelValue = 'string';
expect(fooFormat.serializedValue).to.equal('[foo] string');
});
it('converts formattedValue => modelValue (via this.parser)', async () => {
fooFormat.formattedValue = 'foo: string';
expect(fooFormat.modelValue).to.equal('string');
});
it('converts serializedValue => modelValue (via this.deserializer)', async () => {
fooFormat.serializedValue = '[foo] string';
expect(fooFormat.modelValue).to.equal('string');
});
it('synchronizes inputElement.value as a fallback mechanism', async () => {
// Note that in lion-field, the attribute would be put on <lion-field>, not on <input>
const formatElem = await fixture(html`
<${elem}
value="string",
.formatter=${value => `foo: ${value}`}
.parser=${value => value.replace('foo: ', '')}
.serializer=${value => `[foo] ${value}`}
.deserializer=${value => value.replace('[foo] ', '')}
><input slot="input" value="string"/></${elem}>`);
// Now check if the format/parse/serialize loop has been triggered
await aTimeout();
expect(formatElem.formattedValue).to.equal('foo: string');
expect(formatElem.inputElement.value).to.equal('foo: string');
expect(formatElem.serializedValue).to.equal('[foo] string');
expect(formatElem.modelValue).to.equal('string');
});
it('reflects back formatted value to user on leave', async () => {
const formatEl = await fixture(html`
<${elem} .formatter="${value => `foo: ${value}`}">
<input slot="input" />
</${elem}>
`);
// users types value 'test'
mimicUserInput(formatEl, 'test');
expect(formatEl.inputElement.value).to.not.equal('foo: test');
// user leaves field
formatEl.inputElement.dispatchEvent(new CustomEvent(formatEl.formatOn, { bubbles: true }));
await aTimeout();
expect(formatEl.inputElement.value).to.equal('foo: test');
});
it('reflects back .formattedValue immediately when .modelValue changed imperatively', async () => {
const el = await fixture(html`
<${elem} .formatter="${value => `foo: ${value}`}">
<input slot="input" />
</${elem}>
`);
// The FormatMixin can be used in conjunction with the ValidateMixin, in which case
// it can hold errorState (affecting the formatting)
el.errorState = true;
// users types value 'test'
mimicUserInput(el, 'test');
expect(el.inputElement.value).to.not.equal('foo: test');
// Now see the difference for an imperative change
el.modelValue = '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', () => {
it('should call the parser|formatter|serializer provided by user', async () => {
const formatterSpy = sinon.spy(value => `foo: ${value}`);
const parserSpy = sinon.spy(value => value.replace('foo: ', ''));
const serializerSpy = sinon.spy(value => `[foo] ${value}`);
const el = await fixture(html`
<${elem}
.formatter=${formatterSpy}
.parser=${parserSpy}
.serializer=${serializerSpy}
.modelValue=${'test'}
>
<input slot="input">
</${elem}>
`);
expect(formatterSpy.called).to.equal(true);
expect(serializerSpy.called).to.equal(true);
el.formattedValue = 'raw';
expect(parserSpy.called).to.equal(true);
});
it('should have formatOptions available in formatter', async () => {
const formatterSpy = sinon.spy(value => `foo: ${value}`);
await fixture(html`
<${elem}
value="string",
.formatter="${formatterSpy}"
.formatOptions="${{ locale: 'en-GB', decimalSeparator: '-' }}">
<input slot="input" value="string">
</${elem}>`);
await aTimeout();
expect(formatterSpy.args[0][1].locale).to.equal('en-GB');
expect(formatterSpy.args[0][1].decimalSeparator).to.equal('-');
});
it('will only call the parser for defined values', async () => {
const parserSpy = sinon.spy();
const el = await fixture(html`
<${elem} .parser="${parserSpy}">
<input slot="input" value="string">
</${elem}>
`);
el.modelValue = 'foo';
expect(parserSpy.callCount).to.equal(1);
// This could happen for instance in a reset
el.modelValue = undefined;
expect(parserSpy.callCount).to.equal(1);
// This could happen when the user erases the input value
mimicUserInput(el, '');
expect(parserSpy.callCount).to.equal(1);
});
it('will not return Unparseable when empty strings are inputted', async () => {
const el = await fixture(html`
<${elem}>
<input slot="input" value="string">
</${elem}>
`);
// This could happen when the user erases the input value
mimicUserInput(el, '');
// For backwards compatibility, we keep the modelValue an empty string here.
// Undefined would be more appropriate 'conceptually', however
expect(el.modelValue).to.equal('');
});
it('will only call the formatter for valid values on `user-input-changed` ', async () => {
const formatterSpy = sinon.spy(value => `foo: ${value}`);
const el = await fixture(html`
<${elem} .formatter=${formatterSpy}>
<input slot="input" value="init-string">
</${elem}>
`);
expect(formatterSpy.callCount).to.equal(1);
el.errorState = true;
mimicUserInput(el, 'bar');
expect(formatterSpy.callCount).to.equal(1);
expect(el.formattedValue).to.equal('bar');
el.errorState = false;
mimicUserInput(el, 'bar2');
expect(formatterSpy.callCount).to.equal(2);
expect(el.formattedValue).to.equal('foo: bar2');
});
});
describe('Unparseable values', () => {
it('should convert to Unparseable when wrong value inputted by user', async () => {
const el = await fixture(html`
<${elem}
.parser=${viewValue => Number(viewValue) || undefined}
>
<input slot="input">
</${elem}>
`);
mimicUserInput(el, 'test');
expect(el.modelValue).to.be.an.instanceof(Unparseable);
});
it('should preserve the viewValue when not parseable', async () => {
const el = await fixture(html`
<${elem}
.parser=${viewValue => Number(viewValue) || undefined}
>
<input slot="input">
</${elem}>
`);
mimicUserInput(el, 'test');
expect(el.formattedValue).to.equal('test');
expect(el.value).to.equal('test');
});
it('should display the viewValue when modelValue is of type Unparseable', async () => {
const el = await fixture(html`
<${elem}
.parser=${viewValue => Number(viewValue) || undefined}
>
<input slot="input">
</${elem}>
`);
el.modelValue = new Unparseable('foo');
expect(el.value).to.equal('foo');
});
});
});
runFormatMixinSuite();

@@ -16,3 +16,2 @@ import { expect, fixture, html, defineCE, unsafeStatic } from '@open-wc/testing';

return {
...super.properties,
modelValue: {

@@ -19,0 +18,0 @@ type: String,

@@ -34,20 +34,20 @@ import {

it(`puts a unique id "${tagString}-[hash]" on the native input`, async () => {
const lionField = await fixture(`<${tagString}>${inputSlotString}</${tagString}>`);
expect(lionField.$$slot('input').id).to.equal(lionField._inputId);
const el = await fixture(`<${tagString}>${inputSlotString}</${tagString}>`);
expect(el.$$slot('input').id).to.equal(el._inputId);
});
it('fires focus/blur event on host and native input if focused/blurred', async () => {
const lionField = await fixture(`<${tagString}>${inputSlotString}</${tagString}>`);
const el = await fixture(`<${tagString}>${inputSlotString}</${tagString}>`);
const cbFocusHost = sinon.spy();
lionField.addEventListener('focus', cbFocusHost);
el.addEventListener('focus', cbFocusHost);
const cbFocusNativeInput = sinon.spy();
lionField.inputElement.addEventListener('focus', cbFocusNativeInput);
el.inputElement.addEventListener('focus', cbFocusNativeInput);
const cbBlurHost = sinon.spy();
lionField.addEventListener('blur', cbBlurHost);
el.addEventListener('blur', cbBlurHost);
const cbBlurNativeInput = sinon.spy();
lionField.inputElement.addEventListener('blur', cbBlurNativeInput);
el.inputElement.addEventListener('blur', cbBlurNativeInput);
await triggerFocusFor(lionField);
await triggerFocusFor(el);
expect(document.activeElement).to.equal(lionField.inputElement);
expect(document.activeElement).to.equal(el.inputElement);
expect(cbFocusHost.callCount).to.equal(1);

@@ -58,12 +58,12 @@ expect(cbFocusNativeInput.callCount).to.equal(1);

await triggerBlurFor(lionField);
await triggerBlurFor(el);
expect(cbBlurHost.callCount).to.equal(1);
expect(cbBlurNativeInput.callCount).to.equal(1);
await triggerFocusFor(lionField);
expect(document.activeElement).to.equal(lionField.inputElement);
await triggerFocusFor(el);
expect(document.activeElement).to.equal(el.inputElement);
expect(cbFocusHost.callCount).to.equal(2);
expect(cbFocusNativeInput.callCount).to.equal(2);
await triggerBlurFor(lionField);
await triggerBlurFor(el);
expect(cbBlurHost.callCount).to.equal(2);

@@ -73,85 +73,85 @@ expect(cbBlurNativeInput.callCount).to.equal(2);

it('has class "state-focused" if focused', async () => {
const el = await fixture(`<${tagString}>${inputSlotString}</${tagString}>`);
expect(el.classList.contains('state-focused')).to.equal(false, 'no state-focused initially');
await triggerFocusFor(el.inputElement);
expect(el.classList.contains('state-focused')).to.equal(true, 'state-focused after focus()');
await triggerBlurFor(el.inputElement);
expect(el.classList.contains('state-focused')).to.equal(false, 'no state-focused after blur()');
it('can be disabled via attribute', async () => {
const elDisabled = await fixture(`<${tagString} disabled>${inputSlotString}</${tagString}>`);
expect(elDisabled.disabled).to.equal(true);
expect(elDisabled.inputElement.disabled).to.equal(true);
});
it('offers simple getter "this.focused" returning true/false for the current focus state', async () => {
const lionField = await fixture(`<${tagString}>${inputSlotString}</${tagString}>`);
expect(lionField.focused).to.equal(false);
await triggerFocusFor(lionField);
expect(lionField.focused).to.equal(true);
await triggerBlurFor(lionField);
expect(lionField.focused).to.equal(false);
it('can be disabled via property', async () => {
const el = await fixture(`<${tagString}>${inputSlotString}</${tagString}>`);
el.disabled = true;
await el.updateComplete;
expect(el.inputElement.disabled).to.equal(true);
});
it('can be disabled via attribute', async () => {
const lionFieldDisabled = await fixture(
`<${tagString} disabled>${inputSlotString}</${tagString}>`,
);
expect(lionFieldDisabled.disabled).to.equal(true);
expect(lionFieldDisabled.inputElement.disabled).to.equal(true);
// classes are added only for backward compatibility - they are deprecated
it('sets a state-disabled class when disabled', async () => {
const el = await fixture(`<${tagString} disabled>${inputSlotString}</${tagString}>`);
await el.updateComplete;
expect(el.classList.contains('state-disabled')).to.equal(true);
el.disabled = false;
await el.updateComplete;
expect(el.classList.contains('state-disabled')).to.equal(false);
});
it('can be disabled via property', async () => {
const lionField = await fixture(`<${tagString}>${inputSlotString}</${tagString}>`);
lionField.disabled = true;
await lionField.updateComplete;
expect(lionField.inputElement.disabled).to.equal(true);
});
it('can be cleared which erases value, validation and interaction states', async () => {
const lionField = await fixture(
const el = await fixture(
`<${tagString} value="Some value from attribute">${inputSlotString}</${tagString}>`,
);
lionField.clear();
expect(lionField.value).to.equal('');
lionField.value = 'Some value from property';
expect(lionField.value).to.equal('Some value from property');
lionField.clear();
expect(lionField.value).to.equal('');
el.clear();
expect(el.value).to.equal('');
el.value = 'Some value from property';
expect(el.value).to.equal('Some value from property');
el.clear();
expect(el.value).to.equal('');
});
it('reads initial value from attribute value', async () => {
const lionField = await fixture(`<${tagString} value="one">${inputSlotString}</${tagString}>`);
expect(lionField.$$slot('input').value).to.equal('one');
const el = await fixture(`<${tagString} value="one">${inputSlotString}</${tagString}>`);
expect(el.$$slot('input').value).to.equal('one');
});
it('delegates value property', async () => {
const lionField = await fixture(`<${tagString}>${inputSlotString}</${tagString}>`);
expect(lionField.$$slot('input').value).to.equal('');
lionField.value = 'one';
expect(lionField.value).to.equal('one');
expect(lionField.$$slot('input').value).to.equal('one');
const el = await fixture(`<${tagString}>${inputSlotString}</${tagString}>`);
expect(el.$$slot('input').value).to.equal('');
el.value = 'one';
expect(el.value).to.equal('one');
expect(el.$$slot('input').value).to.equal('one');
});
it('has a name which is reflected to an attribute and is synced down to the native input', async () => {
const el = await fixture(`<${tagString}>${inputSlotString}</${tagString}>`);
expect(el.name).to.equal('');
expect(el.getAttribute('name')).to.equal('');
expect(el.inputElement.getAttribute('name')).to.equal('');
el.name = 'foo';
await el.updateComplete;
expect(el.getAttribute('name')).to.equal('foo');
expect(el.inputElement.getAttribute('name')).to.equal('foo');
});
// TODO: find out if we could put all listeners on this.value (instead of this.inputElement.value)
// and make it act on this.value again
it('has a class "state-filled" if this.value is filled', async () => {
const lionField = await fixture(
`<${tagString} value="filled">${inputSlotString}</${tagString}>`,
);
expect(lionField.classList.contains('state-filled')).to.equal(true);
lionField.value = '';
await lionField.updateComplete;
expect(lionField.classList.contains('state-filled')).to.equal(false);
lionField.value = 'bla';
await lionField.updateComplete;
expect(lionField.classList.contains('state-filled')).to.equal(true);
const el = await fixture(`<${tagString} value="filled">${inputSlotString}</${tagString}>`);
expect(el.classList.contains('state-filled')).to.equal(true);
el.value = '';
await el.updateComplete;
expect(el.classList.contains('state-filled')).to.equal(false);
el.value = 'bla';
await el.updateComplete;
expect(el.classList.contains('state-filled')).to.equal(true);
});
it('preserves the caret position on value change for native text fields (input|textarea)', async () => {
const lionField = await fixture(`<${tagString}>${inputSlotString}</${tagString}>`);
await triggerFocusFor(lionField);
await lionField.updateComplete;
lionField.inputElement.value = 'hello world';
lionField.inputElement.selectionStart = 2;
lionField.inputElement.selectionEnd = 2;
lionField.value = 'hey there universe';
expect(lionField.inputElement.selectionStart).to.equal(2);
expect(lionField.inputElement.selectionEnd).to.equal(2);
const el = await fixture(`<${tagString}>${inputSlotString}</${tagString}>`);
await triggerFocusFor(el);
await el.updateComplete;
el.inputElement.value = 'hello world';
el.inputElement.selectionStart = 2;
el.inputElement.selectionEnd = 2;
el.value = 'hey there universe';
expect(el.inputElement.selectionStart).to.equal(2);
expect(el.inputElement.selectionEnd).to.equal(2);
});

@@ -161,18 +161,16 @@

it('has a class "state-disabled"', async () => {
const lionField = await fixture(`<${tagString}>${inputSlotString}</${tagString}>`);
expect(lionField.classList.contains('state-disabled')).to.equal(false);
expect(lionField.inputElement.hasAttribute('disabled')).to.equal(false);
const el = await fixture(`<${tagString}>${inputSlotString}</${tagString}>`);
expect(el.classList.contains('state-disabled')).to.equal(false);
expect(el.inputElement.hasAttribute('disabled')).to.equal(false);
lionField.disabled = true;
await lionField.updateComplete;
el.disabled = true;
await el.updateComplete;
await aTimeout();
expect(lionField.classList.contains('state-disabled')).to.equal(true);
expect(lionField.inputElement.hasAttribute('disabled')).to.equal(true);
expect(el.classList.contains('state-disabled')).to.equal(true);
expect(el.inputElement.hasAttribute('disabled')).to.equal(true);
const disabledlionField = await fixture(
`<${tagString} disabled>${inputSlotString}</${tagString}>`,
);
expect(disabledlionField.classList.contains('state-disabled')).to.equal(true);
expect(disabledlionField.inputElement.hasAttribute('disabled')).to.equal(true);
const disabledel = await fixture(`<${tagString} disabled>${inputSlotString}</${tagString}>`);
expect(disabledel.classList.contains('state-disabled')).to.equal(true);
expect(disabledel.inputElement.hasAttribute('disabled')).to.equal(true);
});

@@ -195,3 +193,3 @@

~~~`, async () => {
const lionField = await fixture(`<${tagString}>
const el = await fixture(`<${tagString}>
<label slot="label">My Name</label>

@@ -203,11 +201,7 @@ ${inputSlotString}

`);
const nativeInput = lionField.$$slot('input');
const nativeInput = el.$$slot('input');
expect(nativeInput.getAttribute('aria-labelledby')).to.equal(` label-${lionField._inputId}`);
expect(nativeInput.getAttribute('aria-describedby')).to.contain(
` help-text-${lionField._inputId}`,
);
expect(nativeInput.getAttribute('aria-describedby')).to.contain(
` feedback-${lionField._inputId}`,
);
expect(nativeInput.getAttribute('aria-labelledby')).to.equal(` label-${el._inputId}`);
expect(nativeInput.getAttribute('aria-describedby')).to.contain(` help-text-${el._inputId}`);
expect(nativeInput.getAttribute('aria-describedby')).to.contain(` feedback-${el._inputId}`);
});

@@ -217,3 +211,3 @@

(via attribute data-label) and in describedby (via attribute data-description)`, async () => {
const lionField = await fixture(`<${tagString}>
const el = await fixture(`<${tagString}>
${inputSlotString}

@@ -227,8 +221,8 @@ <span slot="before" data-label>[before]</span>

const nativeInput = lionField.$$slot('input');
const nativeInput = el.$$slot('input');
expect(nativeInput.getAttribute('aria-labelledby')).to.contain(
` before-${lionField._inputId} after-${lionField._inputId}`,
` before-${el._inputId} after-${el._inputId}`,
);
expect(nativeInput.getAttribute('aria-describedby')).to.contain(
` prefix-${lionField._inputId} suffix-${lionField._inputId}`,
` prefix-${el._inputId} suffix-${el._inputId}`,
);

@@ -302,9 +296,9 @@ });

}
const lionField = await fixture(`<${tagString}>${inputSlotString}</${tagString}>`);
const feedbackEl = lionField._feedbackElement;
const el = await fixture(`<${tagString}>${inputSlotString}</${tagString}>`);
const feedbackEl = el._feedbackElement;
lionField.modelValue = 'a@b.nl';
lionField.errorValidators = [[hasX]];
el.modelValue = 'a@b.nl';
el.errorValidators = [[hasX]];
expect(lionField.error.hasX).to.equal(true);
expect(el.error.hasX).to.equal(true);
expect(feedbackEl.innerText.trim()).to.equal(

@@ -314,6 +308,6 @@ '',

);
lionField.dirty = true;
lionField.touched = true;
lionField.modelValue = 'ab@c.nl'; // retrigger validation
await lionField.updateComplete;
el.dirty = true;
el.touched = true;
el.modelValue = 'ab@c.nl'; // retrigger validation
await el.updateComplete;

@@ -325,6 +319,6 @@ expect(feedbackEl.innerText.trim()).to.equal(

lionField.touched = false;
lionField.dirty = false;
lionField.prefilled = true;
await lionField.updateComplete;
el.touched = false;
el.dirty = false;
el.prefilled = true;
await el.updateComplete;
expect(feedbackEl.innerText.trim()).to.equal(

@@ -337,3 +331,3 @@ 'This is error message for hasX',

it('can be required', async () => {
const lionField = await fixture(html`
const el = await fixture(html`
<${tag}

@@ -343,5 +337,5 @@ .errorValidators=${[['required']]}

`);
expect(lionField.error.required).to.be.true;
lionField.modelValue = 'cat';
expect(lionField.error.required).to.be.undefined;
expect(el.error.required).to.be.true;
el.modelValue = 'cat';
expect(el.error.required).to.be.undefined;
});

@@ -354,3 +348,3 @@

}
const lionField = await fixture(html`
const el = await fixture(html`
<${tag}

@@ -364,11 +358,11 @@ .modelValue=${'init-string'}

expect(formatterSpy.callCount).to.equal(0);
expect(lionField.formattedValue).to.equal('init-string');
expect(el.formattedValue).to.equal('init-string');
lionField.modelValue = 'bar';
el.modelValue = 'bar';
expect(formatterSpy.callCount).to.equal(1);
expect(lionField.formattedValue).to.equal('foo: bar');
expect(el.formattedValue).to.equal('foo: bar');
mimicUserInput(lionField, 'foo');
mimicUserInput(el, 'foo');
expect(formatterSpy.callCount).to.equal(1);
expect(lionField.value).to.equal('foo');
expect(el.value).to.equal('foo');
});

@@ -379,3 +373,3 @@ });

it('renders correctly all slot elements in light DOM', async () => {
const lionField = await fixture(`
const el = await fixture(`
<${tagString}>

@@ -404,4 +398,4 @@ <label slot="label">[label]</label>

names.forEach(slotName => {
lionField.querySelector(`[slot="${slotName}"]`).setAttribute('test-me', 'ok');
const slot = lionField.shadowRoot.querySelector(`slot[name="${slotName}"]`);
el.querySelector(`[slot="${slotName}"]`).setAttribute('test-me', 'ok');
const slot = el.shadowRoot.querySelector(`slot[name="${slotName}"]`);
const assignedNodes = slot.assignedNodes();

@@ -415,8 +409,2 @@ expect(assignedNodes.length).to.equal(1);

describe(`Delegation${nameSuffix}`, () => {
it('delegates attribute autofocus', async () => {
const el = await fixture(`<${tagString} autofocus>${inputSlotString}</${tagString}>`);
expect(el.hasAttribute('autofocus')).to.be.false;
expect(el.inputElement.hasAttribute('autofocus')).to.be.true;
});
it('delegates property value', async () => {

@@ -430,38 +418,4 @@ const el = await fixture(`<${tagString}>${inputSlotString}</${tagString}>`);

it('delegates property type', async () => {
const el = await fixture(`<${tagString} type="text">${inputSlotString}</${tagString}>`);
const inputElemTag = el.inputElement.tagName.toLowerCase();
if (inputElemTag === 'select') {
// TODO: later on we might want to support multi select ?
expect(el.inputElement.type).to.contain('select-one');
} else if (inputElemTag === 'textarea') {
expect(el.inputElement.type).to.contain('textarea');
} else {
// input or custom inputElement
expect(el.inputElement.type).to.contain('text');
el.type = 'password';
expect(el.type).to.equal('password');
expect(el.inputElement.type).to.equal('password');
}
});
it('delegates property onfocus', async () => {
const el = await fixture(`<${tagString}>${inputSlotString}</${tagString}>`);
const cbFocusHost = sinon.spy();
el.onfocus = cbFocusHost;
await triggerFocusFor(el.inputElement);
expect(cbFocusHost.callCount).to.equal(1);
});
it('delegates property onblur', async () => {
const el = await fixture(`<${tagString}>${inputSlotString}</${tagString}>`);
const cbBlurHost = sinon.spy();
el.onblur = cbBlurHost;
await triggerFocusFor(el.inputElement);
await triggerBlurFor(el.inputElement);
expect(cbBlurHost.callCount).to.equal(1);
});
it('delegates property selectionStart and selectionEnd', async () => {
const lionField = await fixture(html`
const el = await fixture(html`
<${tag}

@@ -472,8 +426,8 @@ .modelValue=${'Some text to select'}

lionField.selectionStart = 5;
lionField.selectionEnd = 12;
expect(lionField.inputElement.selectionStart).to.equal(5);
expect(lionField.inputElement.selectionEnd).to.equal(12);
el.selectionStart = 5;
el.selectionEnd = 12;
expect(el.inputElement.selectionStart).to.equal(5);
expect(el.inputElement.selectionEnd).to.equal(12);
});
});
});
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc