@lion/field
Advanced tools
Comparing version 0.4.1 to 0.5.0
@@ -6,2 +6,13 @@ # Change Log | ||
# [0.5.0](https://github.com/ing-bank/lion/compare/@lion/field@0.4.1...@lion/field@0.5.0) (2019-11-18) | ||
### Features | ||
* finalize validation and adopt it everywhere ([396deb2](https://github.com/ing-bank/lion/commit/396deb2e3b4243f102a5c98e9b0518fa0f31a6b1)) | ||
## [0.4.1](https://github.com/ing-bank/lion/compare/@lion/field@0.4.0...@lion/field@0.4.1) (2019-11-15) | ||
@@ -8,0 +19,0 @@ |
@@ -64,4 +64,4 @@ # ModelValue | ||
```js | ||
function handleChange({ target: { modelValue, errorState } }) { | ||
if (!(modelValue instanceof Unparseable) && !errorState) { | ||
function handleChange({ target: { modelValue, hasFeedbackFor } }) { | ||
if (!(modelValue instanceof Unparseable) && !(hasFeedbackFor.include('error))) { | ||
// do my thing | ||
@@ -68,0 +68,0 @@ } |
{ | ||
"name": "@lion/field", | ||
"version": "0.4.1", | ||
"version": "0.5.0", | ||
"description": "Fields are the most fundamental building block of the Form System", | ||
@@ -37,3 +37,3 @@ "author": "ing-bank", | ||
"@lion/core": "^0.3.0", | ||
"@lion/validate": "^0.3.1" | ||
"@lion/validate": "^0.4.0" | ||
}, | ||
@@ -46,3 +46,3 @@ "devDependencies": { | ||
}, | ||
"gitHead": "e1485188a03c063819623288f3231cfecca023ec" | ||
"gitHead": "da30ee3bb13d26f873d633ed960163bec9cba164" | ||
} |
@@ -13,3 +13,3 @@ /* eslint-disable class-methods-use-this */ | ||
// - consider `formatOn` as an overridable function, by default something like: | ||
// `(!__isHandlingUserInput || !errorState) && !focused` | ||
// `(!__isHandlingUserInput || !hasError) && !focused` | ||
// This would allow for more advanced scenarios, like formatting an input whenever it becomes valid. | ||
@@ -249,3 +249,3 @@ // This would make formattedValue as a concept obsolete, since for maximum flexibility, the | ||
__callFormatter() { | ||
// - Why check for this.errorState? | ||
// - Why check for this.hasError? | ||
// We only want to format values that are considered valid. For best UX, | ||
@@ -257,7 +257,13 @@ // we only 'reward' valid inputs. | ||
// the value, no matter what. | ||
// This means, whenever we are in errorState and modelValue is set | ||
// This means, whenever we are in hasError and modelValue is set | ||
// imperatively, we DO want to format a value (it is the only way to get meaningful | ||
// input into `._inputNode` with modelValue as input) | ||
if (this.__isHandlingUserInput && this.errorState && this._inputNode) { | ||
if ( | ||
this.__isHandlingUserInput && | ||
this.hasFeedbackFor && | ||
this.hasFeedbackFor.length && | ||
this.hasFeedbackFor.includes('error') && | ||
this._inputNode | ||
) { | ||
return this._inputNode ? this.value : undefined; | ||
@@ -264,0 +270,0 @@ } |
@@ -467,44 +467,2 @@ import { html, css, nothing, dedupeMixin, SlotMixin } from '@lion/core'; | ||
// Extend validity showing conditions of ValidateMixin | ||
showErrorCondition(newStates) { | ||
return super.showErrorCondition(newStates) && this._interactionStateFeedbackCondition(); | ||
} | ||
showWarningCondition(newStates) { | ||
return super.showWarningCondition(newStates) && this._interactionStateFeedbackCondition(); | ||
} | ||
showInfoCondition(newStates) { | ||
return super.showInfoCondition(newStates) && this._interactionStateFeedbackCondition(); | ||
} | ||
showSuccessCondition(newStates, oldStates) { | ||
return ( | ||
super.showSuccessCondition(newStates, oldStates) && | ||
this._interactionStateFeedbackCondition() | ||
); | ||
} | ||
_interactionStateFeedbackCondition() { | ||
/** | ||
* Show the validity feedback when one of the following conditions is met: | ||
* | ||
* - submitted | ||
* If the form is submitted, always show the error message. | ||
* | ||
* - prefilled | ||
* the user already filled in something, or the value is prefilled | ||
* when the form is initially rendered. | ||
* | ||
* - touched && dirty && !prefilled | ||
* When a user starts typing for the first time in a field with for instance `required` | ||
* validation, error message should not be shown until a field becomes `touched` | ||
* (a user leaves(blurs) a field). | ||
* When a user enters a field without altering the value(making it `dirty`), | ||
* an error message shouldn't be shown either. | ||
* | ||
*/ | ||
return (this.touched && this.dirty) || this.prefilled || this.submitted; | ||
} | ||
// aria-labelledby and aria-describedby helpers | ||
@@ -511,0 +469,0 @@ // TODO: consider extracting to generic ariaLabel helper mixin |
@@ -228,10 +228,12 @@ import { SlotMixin, LitElement } from '@lion/core'; | ||
// eslint-disable-next-line class-methods-use-this | ||
__isRequired(modelValue) { | ||
return { | ||
required: | ||
(typeof modelValue === 'string' && modelValue !== '') || | ||
(typeof modelValue !== 'string' && typeof modelValue !== 'undefined'), // TODO: && modelValue !== null ? | ||
}; | ||
set fieldName(value) { | ||
this.__fieldName = value; | ||
} | ||
get fieldName() { | ||
const label = | ||
this.label || | ||
(this.querySelector('[slot=label]') && this.querySelector('[slot=label]').textContent); | ||
return this.__fieldName || label || this.name; | ||
} | ||
} |
@@ -5,3 +5,3 @@ import { expect, fixture, html, aTimeout, defineCE, unsafeStatic } from '@open-wc/testing'; | ||
import { LitElement } from '@lion/core'; | ||
import { Unparseable } from '@lion/validate'; | ||
import { Unparseable, Validator } from '@lion/validate'; | ||
import { FormatMixin } from '../src/FormatMixin.js'; | ||
@@ -325,3 +325,3 @@ | ||
it('will only call the formatter for valid values on `user-input-changed` ', async () => { | ||
it.skip('will only call the formatter for valid values on `user-input-changed` ', async () => { | ||
const formatterSpy = sinon.spy(value => `foo: ${value}`); | ||
@@ -343,16 +343,26 @@ | ||
el.errorState = true; | ||
// Ensure errorState is always true by putting a validator on it that always returns false. | ||
// Setting errorState = true is not enough if the element has errorValidators (uses ValidateMixin) | ||
// that set errorState back to false when the user input is mimicked. | ||
const alwaysInvalidator = () => ({ 'always-invalid': false }); | ||
el.errorValidators = [alwaysInvalidator]; | ||
el.hasError = true; | ||
// Ensure hasError is always true by putting a validator on it that always returns false. | ||
// Setting hasError = true is not enough if the element has errorValidators (uses ValidateMixin) | ||
// that set hasError back to false when the user input is mimicked. | ||
const AlwaysInvalid = class extends Validator { | ||
constructor(...args) { | ||
super(...args); | ||
this.name = 'AlwaysInvalid'; | ||
} | ||
execute() { | ||
return true; | ||
} | ||
}; | ||
el.validators = [new AlwaysInvalid()]; | ||
mimicUserInput(el, generatedViewValueAlt); | ||
expect(formatterSpy.callCount).to.equal(1); | ||
// Due to errorState, the formatter should not have ran. | ||
// Due to hasError, the formatter should not have ran. | ||
expect(el.formattedValue).to.equal(generatedViewValueAlt); | ||
el.errorState = false; | ||
el.errorValidators = []; | ||
el.hasError = false; | ||
el.validators = []; | ||
mimicUserInput(el, generatedViewValue); | ||
@@ -359,0 +369,0 @@ expect(formatterSpy.callCount).to.equal(2); |
@@ -12,2 +12,3 @@ import { | ||
import sinon from 'sinon'; | ||
import { Validator, Required } from '@lion/validate'; | ||
import { localize } from '@lion/localize'; | ||
@@ -39,2 +40,20 @@ import { localizeTearDown } from '@lion/localize/test-helpers.js'; | ||
it(`has a fieldName based on the label`, async () => { | ||
const el1 = await fixture(html`<${tag} label="foo">${inputSlot}</${tag}>`); | ||
expect(el1.fieldName).to.equal(el1._labelNode.textContent); | ||
const el2 = await fixture(html`<${tag}><label slot="label">bar</label>${inputSlot}</${tag}>`); | ||
expect(el2.fieldName).to.equal(el2._labelNode.textContent); | ||
}); | ||
it(`has a fieldName based on the name if no label exists`, async () => { | ||
const el = await fixture(html`<${tag} name="foo">${inputSlot}</${tag}>`); | ||
expect(el.fieldName).to.equal(el.name); | ||
}); | ||
it(`can override fieldName`, async () => { | ||
const el = await fixture(html`<${tag} label="foo" .fieldName="${'bar'}">${inputSlot}</${tag}>`); | ||
expect(el.__fieldName).to.equal(el.fieldName); | ||
}); | ||
it('fires focus/blur event on host and native input if focused/blurred', async () => { | ||
@@ -289,39 +308,64 @@ const el = await fixture(html`<${tag}>${inputSlot}</${tag}>`); | ||
it('shows validity states(error|warning|info|success) when interaction criteria met ', async () => { | ||
// TODO: in order to make this test work as an integration test, we chose a modelValue | ||
// that is compatible with lion-input-email. | ||
// However, when we can put priorities to validators (making sure error message of hasX is | ||
// shown instead of a predefined validator like isEmail), we should fix this. | ||
function hasX(str) { | ||
return { hasX: str.indexOf('x') > -1 }; | ||
} | ||
const el = await fixture(html`<${tag}>${inputSlot}</${tag}>`); | ||
const feedbackEl = el._feedbackElement; | ||
it('should conditionally show error', async () => { | ||
const HasX = class extends Validator { | ||
constructor() { | ||
super(); | ||
this.name = 'HasX'; | ||
} | ||
el.modelValue = 'a@b.nl'; | ||
el.errorValidators = [[hasX]]; | ||
execute(value) { | ||
const result = value.indexOf('x') === -1; | ||
return result; | ||
} | ||
}; | ||
const el = await fixture(html` | ||
<${tag} | ||
.validators=${[new HasX()]} | ||
.modelValue=${'a@b.nl'} | ||
> | ||
${inputSlot} | ||
</${tag}> | ||
`); | ||
expect(el.error.hasX).to.equal(true); | ||
expect(feedbackEl.innerText.trim()).to.equal( | ||
'', | ||
'shows no feedback, although the element has an error', | ||
); | ||
el.dirty = true; | ||
el.touched = true; | ||
el.modelValue = 'ab@c.nl'; // retrigger validation | ||
await el.updateComplete; | ||
const executeScenario = async (_sceneEl, scenario) => { | ||
const sceneEl = _sceneEl; | ||
sceneEl.resetInteractionState(); | ||
sceneEl.touched = scenario.el.touched; | ||
sceneEl.dirty = scenario.el.dirty; | ||
sceneEl.prefilled = scenario.el.prefilled; | ||
sceneEl.submitted = scenario.el.submitted; | ||
expect(feedbackEl.innerText.trim()).to.equal( | ||
'This is error message for hasX', | ||
'shows feedback, because touched=true and dirty=true', | ||
); | ||
await sceneEl.updateComplete; | ||
await sceneEl.feedbackComplete; | ||
expect(sceneEl.showsFeedbackFor).to.deep.equal(scenario.wantedShowsFeedbackFor); | ||
}; | ||
el.touched = false; | ||
el.dirty = false; | ||
el.prefilled = true; | ||
await el.updateComplete; | ||
expect(feedbackEl.innerText.trim()).to.equal( | ||
'This is error message for hasX', | ||
'shows feedback, because prefilled=true', | ||
); | ||
await executeScenario(el, { | ||
index: 0, | ||
el: { touched: true, dirty: true, prefilled: false, submitted: false }, | ||
wantedShowsFeedbackFor: ['error'], | ||
}); | ||
await executeScenario(el, { | ||
index: 1, | ||
el: { touched: false, dirty: false, prefilled: true, submitted: false }, | ||
wantedShowsFeedbackFor: ['error'], | ||
}); | ||
await executeScenario(el, { | ||
index: 2, | ||
el: { touched: false, dirty: false, prefilled: false, submitted: true }, | ||
wantedShowsFeedbackFor: ['error'], | ||
}); | ||
await executeScenario(el, { | ||
index: 3, | ||
el: { touched: false, dirty: true, prefilled: false, submitted: false }, | ||
wantedShowsFeedbackFor: [], | ||
}); | ||
await executeScenario(el, { | ||
index: 4, | ||
el: { touched: true, dirty: false, prefilled: false, submitted: false }, | ||
wantedShowsFeedbackFor: [], | ||
}); | ||
}); | ||
@@ -332,8 +376,10 @@ | ||
<${tag} | ||
.errorValidators=${[['required']]} | ||
.validators=${[new Required()]} | ||
>${inputSlot}</${tag}> | ||
`); | ||
expect(el.error.required).to.be.true; | ||
expect(el.hasFeedbackFor).to.deep.equal(['error']); | ||
expect(el.validationStates.error).to.have.a.property('Required'); | ||
el.modelValue = 'cat'; | ||
expect(el.error.required).to.be.undefined; | ||
expect(el.hasFeedbackFor).to.deep.equal([]); | ||
expect(el.validationStates.error).not.to.have.a.property('Required'); | ||
}); | ||
@@ -343,5 +389,13 @@ | ||
const formatterSpy = sinon.spy(value => `foo: ${value}`); | ||
function isBarValidator(value) { | ||
return { isBar: value === 'bar' }; | ||
} | ||
const Bar = class extends Validator { | ||
constructor(...args) { | ||
super(...args); | ||
this.name = 'Bar'; | ||
} | ||
execute(value) { | ||
const hasError = value !== 'bar'; | ||
return hasError; | ||
} | ||
}; | ||
const el = await fixture(html` | ||
@@ -351,3 +405,3 @@ <${tag} | ||
.formatter=${formatterSpy} | ||
.errorValidators=${[[isBarValidator]]} | ||
.validators=${[new Bar()]} | ||
>${inputSlot}</${tag}> | ||
@@ -354,0 +408,0 @@ `); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
211397
2976
+ Added@lion/localize@0.6.0(transitive)
+ Added@lion/validate@0.4.2(transitive)
- Removed@lion/localize@0.5.0(transitive)
- Removed@lion/validate@0.3.1(transitive)
Updated@lion/validate@^0.4.0