Skip to content

Commit a6da614

Browse files
haydenmcpadmwx7
authored andcommitted
Breaking: adding in async based step validation so it can be bound to an API (#15)
1 parent b39eab3 commit a6da614

File tree

5 files changed

+71
-45
lines changed

5 files changed

+71
-45
lines changed

mtz-wizard-step-behavior.html

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
type: Boolean,
2222
value: false,
2323
reflectToAttribute: true,
24-
notify: true
24+
notify: true,
2525
},
2626
/* Used to track the dirty state of the form, if there is one */
2727
dirty: {
@@ -45,7 +45,6 @@
4545
/* The validity state of the element */
4646
invalid: {
4747
type: Boolean,
48-
readOnly: true,
4948
reflectToAttribute: true,
5049
notify: true,
5150
value: false
@@ -129,19 +128,21 @@
129128
*/
130129
reset() {
131130
if (this.form) this.form.reset();
132-
this._setInvalid(false);
131+
this.invalid = false;
133132
this._setDirty(false);
134133
},
135134
/**
136135
* Validates all the required elements (custom and native) in the form, if one exists, and checks step validity.
136+
* NOTE: this method is a promise so validation for the step in custom implementations can rely on async validation
137+
* via things like API services.
137138
*
138-
* @return {Boolean}
139+
* @return {Promise} - resolves with a Boolean that says if the wizard is valid or not
139140
*/
140141
validate() {
141142
const validForm = !this.form || this.form.validate();
142143
const valid = !this.required || validForm;
143-
this._setInvalid(!valid);
144-
return valid;
144+
this.invalid = !valid;
145+
return Promise.resolve(valid);
145146
},
146147
/**
147148
* Sets the dirty state to true.

mtz-wizard-step-errors-behavior.html

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,6 @@
2424
* @type {Object.<{step: String, field: String, code: String, message: String}>}
2525
*/
2626
errors: Array,
27-
/* The attribute to set to true when an element is in error state */
28-
invalidAttribute: {
29-
type: String,
30-
value: 'invalid'
31-
},
3227
/* The attribute to key off of when doing the lookup for fields within the form */
3328
keyAttribute: {
3429
type: String,
@@ -64,16 +59,14 @@
6459
},
6560
/**
6661
* Looks up each element based on the keyAttr for the associated error then sets the error message based on
67-
* messageAttr and toggles on the invalidAttr flag.
62+
* messageAttr and toggles on the invalid flag.
6863
* @private
6964
*
7065
* @param {Object[]} errors
7166
* @param {String} [messageAttr = this.errorMessageAttribute] - the attribute to set the message on
72-
* @param {String} [invalidAttr = this.invalidAttribute] - the attribute to toggle for invalid state
7367
* @param {String} [keyAttr = this.keyAttribute] - the attribute to lookup on
7468
*/
75-
__setErrorState(errors, messageAttr = this.errorMessageAttribute, invalidAttr = this.invalidAttribute,
76-
keyAttr = this.keyAttribute) {
69+
__setErrorState(errors, messageAttr = this.errorMessageAttribute, keyAttr = this.keyAttribute) {
7770
if (!errors) return;
7871

7972
errors.forEach((error) => {
@@ -82,16 +75,19 @@
8275
const field = this.form.querySelector(`[${keyAttr}="${error.field}"]`);
8376
if (!field) return;
8477

78+
// Store the original state for later
8579
field.originalErrorMessage = field[messageAttr];
80+
// Update our invalid state and associated messaging
8681
field[messageAttr] = error.message;
87-
// Required to get the UI to update properly
8882
field.setAttribute(messageAttr, error.message);
89-
field[invalidAttr] = true;
83+
field.invalid = true;
84+
field.setAttribute('invalid', '');
9085

9186
field.addEventListener('invalid-changed', this.__revertErrorMessage);
9287
}
9388
});
9489
this.set('errors', null);
90+
this.invalid = true;
9591
},
9692
}];
9793
</script>

mtz-wizard.html

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
active: {
3232
type: Boolean,
3333
value: false,
34+
reflectToAttribute: true,
3435
},
3536
/* The attribute to use to match the selection key against */
3637
attrForSelected: {
@@ -147,12 +148,19 @@
147148
* Runs the validation logic on all steps with a form bound to them.
148149
*
149150
* @param {HTMLElement[]} [steps = this.steps]
150-
* @return {Boolean}
151+
* @return {Promise} - resolves with a Boolean that says if the wizard is valid or not
151152
*/
152153
validate(steps = this.steps) {
153-
return (steps || []).reduce((prev, step) => {
154-
return prev && (step.validate ? step.validate() : true);
155-
}, true);
154+
return Promise.all((steps || []).
155+
map((step) => {
156+
return Promise.resolve(!step.validate || step.validate());
157+
})).
158+
then((promises) => {
159+
return promises.every(Boolean);
160+
}).
161+
catch(() => {
162+
return false;
163+
});
156164
},
157165
/**
158166
* Sets the fallback selection as the first item.

test/mtz-wizard-step_test.html

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
<link rel="import" href="../demo/demo-shadow-form.html">
1717
</head>
1818
<body>
19-
2019
<test-fixture id="basic">
2120
<template>
2221
<mtz-wizard-step></mtz-wizard-step>
@@ -114,7 +113,7 @@
114113
/* Public Methods */
115114
describe('reset()', () => {
116115
it('should set invalid to false', () => {
117-
element._setInvalid(true);
116+
element.invalid = true;
118117
expect(element.invalid).to.equal(true);
119118
element.reset();
120119
expect(element.invalid).to.equal(false);
@@ -139,13 +138,17 @@
139138
});
140139
describe('validate()', () => {
141140
it('should return true when no form and !required', () => {
142-
expect(element.validate()).to.equal(true);
143-
expect(element.invalid).to.equal(false);
141+
return element.validate().then((valid) => {
142+
expect(valid).to.be.true;
143+
expect(element.invalid).to.be.false;
144+
});
144145
});
145146
it('should return true when no form and required', () => {
146147
element.required = true;
147-
expect(element.validate()).to.equal(true);
148-
expect(element.invalid).to.equal(false);
148+
return element.validate().then((valid) => {
149+
expect(valid).to.be.true;
150+
expect(element.invalid).to.be.false;
151+
});
149152
});
150153

151154
describe('when there is a form', () => {
@@ -162,12 +165,16 @@
162165

163166
it('should return true when required', () => {
164167
element.required = true;
165-
expect(element.validate()).to.equal(true);
166-
expect(element.invalid).to.equal(false);
168+
return element.validate().then((valid) => {
169+
expect(valid).to.be.true;
170+
expect(element.invalid).to.be.false;
171+
});
167172
});
168173
it('should return true when !required', () => {
169-
expect(element.validate()).to.equal(true);
170-
expect(element.invalid).to.equal(false);
174+
return element.validate().then((valid) => {
175+
expect(valid).to.be.true;
176+
expect(element.invalid).to.be.false;
177+
});
171178
});
172179
});
173180
describe('and form.validate() fails', () => {
@@ -177,12 +184,16 @@
177184

178185
it('should return false when required', () => {
179186
element.required = true;
180-
expect(element.validate()).to.equal(false);
181-
expect(element.invalid).to.equal(true);
187+
return element.validate().then((valid) => {
188+
expect(valid).to.be.false;
189+
expect(element.invalid).to.be.true;
190+
});
182191
});
183192
it('should return true when !required', () => {
184-
expect(element.validate()).to.equal(true);
185-
expect(element.invalid).to.equal(false);
193+
return element.validate().then((valid) => {
194+
expect(valid).to.be.true;
195+
expect(element.invalid).to.be.false;
196+
});
186197
});
187198
});
188199
});
@@ -229,7 +240,7 @@
229240
});
230241
});
231242
});
232-
describe('__setErrorState(errors, messageAttr, invalidAttr, keyAttr)', () => {
243+
describe('__setErrorState(errors, messageAttr, keyAttr)', () => {
233244
const badFieldError = {
234245
step: 'step',
235246
field: 'bad-field',
@@ -278,14 +289,18 @@
278289
element.__setErrorState([goodFieldError]);
279290
expect(input[element.errorMessageAttribute]).to.equal(goodFieldError.message);
280291
});
281-
it('should set field[invalidAttr] to true', () => {
292+
it('should set field.invalid to true', () => {
282293
element.__setErrorState([goodFieldError]);
283-
expect(input[element.invalidAttribute]).to.be.true;
294+
expect(input.invalid).to.be.true;
284295
});
285296
it('should bind an event listener for invalid-changed', () => {
286297
element.__setErrorState([goodFieldError]);
287298
expect(spy).calledWith('invalid-changed', element.__revertErrorMessage);
288299
});
300+
it('should flag this step as invalid', () => {
301+
element.__setErrorState([goodFieldError]);
302+
expect(element.invalid).to.be.true;
303+
});
289304
});
290305
describe('__updateDirty()', () => {
291306
it('should set dirty to true', () => {

test/mtz-wizard_test.html

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -223,12 +223,16 @@
223223
];
224224
});
225225

226-
it('should return false if any step fails validation', () => {
227-
expect(element.validate(steps)).to.equal(false);
226+
it('should resolve false if any step fails validation', () => {
227+
return element.validate(steps).then((valid) => {
228+
expect(valid).to.be.false;
229+
});
228230
});
229-
it('should return true if all steps pass validation', () => {
230-
steps[1].validate = () => true;
231-
expect(element.validate(steps)).to.equal(true);
231+
it('should resolve true if all steps pass validation', () => {
232+
steps[1].validate = () => Promise.resolve(true);
233+
return element.validate(steps).then((valid) => {
234+
expect(valid).to.be.true;
235+
});
232236
});
233237
});
234238
describe('without validatable steps', () => {
@@ -239,8 +243,10 @@
239243
];
240244
});
241245

242-
it('should return true', () => {
243-
expect(element.validate(steps)).to.equal(true);
246+
it('should resolve true', () => {
247+
return element.validate(steps).then((valid) => {
248+
expect(valid).to.be.true;
249+
});
244250
});
245251
});
246252
});

0 commit comments

Comments
 (0)