Merge pull request #141 from ing-bank/fix/handleEmptyModelValues

Fix/handle empty model values
This commit is contained in:
Thijs Louisse 2019-07-02 17:28:16 +02:00 committed by GitHub
commit adae87398c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 60 additions and 10 deletions

View file

@ -212,6 +212,13 @@ export const ChoiceInputMixin = superclass =>
}; };
} }
/**
* @override
* Overridden from FormatMixin, since a different modelValue is used for choice inputs.
* Synchronization from user input is already arranged in this Mixin.
*/
_syncValueUpwards() {}
/** /**
* @deprecated use .checked * @deprecated use .checked
*/ */

View file

@ -68,10 +68,12 @@ describe('ChoiceInputMixin', () => {
let counter = 0; let counter = 0;
const el = await fixture(html` const el = await fixture(html`
<choice-input <choice-input
@user-input-changed=${() => { @user-input-changed="${() => {
counter += 1; counter += 1;
}} }}"
></choice-input> >
<input slot="input" />
</choice-input>
`); `);
expect(counter).to.equal(0); expect(counter).to.equal(0);
// Here we try to mimic user interaction by firing browser events // Here we try to mimic user interaction by firing browser events
@ -100,8 +102,6 @@ describe('ChoiceInputMixin', () => {
const precheckedElementAttr = await fixture(html` const precheckedElementAttr = await fixture(html`
<choice-input .checked=${true}></choice-input> <choice-input .checked=${true}></choice-input>
`); `);
el._syncValueUpwards = () => {}; // We need to disable the method for the test to pass
expect(precheckedElementAttr.checked).to.equal(true, 'initially checked via attribute'); expect(precheckedElementAttr.checked).to.equal(true, 'initially checked via attribute');
}); });

View file

@ -182,11 +182,37 @@ export const FormatMixin = dedupeMixin(
} }
__callParser(value = this.formattedValue) { __callParser(value = this.formattedValue) {
let result; // A) check if we need to parse at all
if (typeof value === 'string') {
result = this.parser(value, this.formatOptions); // A.1) The end user had no intention to parse
if (value === '') {
// Ideally, modelValue should be undefined for empty strings.
// For backwards compatibility we return an empty string:
// - it triggers validation for required validators (see ValidateMixin.validate())
// - it can be expected by 3rd parties (for instance unit tests)
// TODO: In a breaking refactor of the Validation System, this behaviot can be corrected.
return '';
} }
return typeof result !== 'undefined' ? result : new Unparseable(value);
// A.2) Handle edge cases We might have no view value yet, for instance because
// inputElement.value was not available yet
if (typeof value !== 'string') {
// This means there is nothing to find inside the view that can be of
// interest to the Application Developer or needed to store for future form state
// retrieval.
return undefined;
}
// B) parse the view value
// - if result:
// return the successfully parsed viewValue
// - if no result:
// Apparently, the parser was not able to produce a satisfactory output for the desired
// modelValue type, based on the current viewValue. Unparseable allows to restore all
// states (for instance from a lost user session), since it saves the current viewValue.
const result = this.parser(value, this.formatOptions);
return result !== undefined ? result : new Unparseable(value);
} }
__callFormatter() { __callFormatter() {

View file

@ -212,7 +212,7 @@ describe('FormatMixin', () => {
expect(formatterSpy.args[0][1].decimalSeparator).to.equal('-'); expect(formatterSpy.args[0][1].decimalSeparator).to.equal('-');
}); });
it('will only call the parser for string values', async () => { it('will only call the parser for defined values', async () => {
const parserSpy = sinon.spy(); const parserSpy = sinon.spy();
const el = await fixture(html` const el = await fixture(html`
<${elem} .parser="${parserSpy}"> <${elem} .parser="${parserSpy}">
@ -221,8 +221,25 @@ describe('FormatMixin', () => {
`); `);
el.modelValue = 'foo'; el.modelValue = 'foo';
expect(parserSpy.callCount).to.equal(1); expect(parserSpy.callCount).to.equal(1);
// This could happen for instance in a reset
el.modelValue = undefined; el.modelValue = undefined;
expect(parserSpy.callCount).to.equal(1); 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 () => { it('will only call the formatter for valid values on `user-input-changed` ', async () => {