Skip to content

Commit 7f0a5ec

Browse files
committed
fix initial validation
1 parent 62052a5 commit 7f0a5ec

File tree

2 files changed

+50
-66
lines changed

2 files changed

+50
-66
lines changed

addon/components/bs-form/element.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import BsFormElement from 'ember-bootstrap/components/bs-form/element';
2+
import { action } from '@ember/object';
23
import { dependentKeyCompat } from '@ember/object/compat';
34

45
export default class BsFormElementWithChangesetValidationsSupport extends BsFormElement {
@@ -13,4 +14,43 @@ export default class BsFormElementWithChangesetValidationsSupport extends BsForm
1314
get hasValidator() {
1415
return typeof this.model.validate === 'function';
1516
}
17+
18+
// Ember Changeset does not validate the initial state. Properties are not
19+
// validated until they are set the first time. But Ember Bootstrap may show
20+
// validation results before the property was changed. We need to make sure
21+
// that changeset is validated at that time.
22+
// Ember Bootstrap may show the validation in three cases:
23+
// 1. User triggered one of the events that should cause validation errors to
24+
// be shown (e.g. focus out) by interacting with the form element.
25+
// Ember Bootstrap stores these state in `showOwnValidation` property of
26+
// the form element.
27+
// 2. User submits the form. Ember Bootstrap will show validation errors
28+
// for all form elements in that case. That state is handled by
29+
// `showAllValidations` arguments passed to the form element.
30+
// 3. User passes in a validation error or warning explicilty using
31+
// `customError` or `customWarning` arguments of the form element.
32+
// Ember Bootstrap ensures that the model is valided as part of its submit
33+
// handler. So we can assume that validations are run in second case. Ember
34+
// Bootstrap does not show the validation errors of the model but only the
35+
// custom error and warning if present. So it does not matter if initial
36+
// state is validated or not. That means we only have to handle the first
37+
// case.
38+
// Ember Bootstrap does not provide any API for validation plugins to support
39+
// these needs. We have to override a private method to run the validate
40+
// logic for now.
41+
@action
42+
async showValidationOnHandler(event) {
43+
let validationShowBefore = this.showOwnValidation;
44+
45+
// run original implementation provided by Ember Bootstrap
46+
super.showValidationOnHandler(event);
47+
48+
// run initial validation if
49+
// - visibility of validations changed
50+
let canValidate = this.hasValidator && this.property;
51+
let validationVisibilityChanged = !validationShowBefore && this.showOwnValidation;
52+
if (canValidate && validationVisibilityChanged) {
53+
await this.model.validate(this.property);
54+
}
55+
}
1656
}

tests/integration/components/bs-form-element-test.js

Lines changed: 10 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
11
import { module, test } from 'qunit';
22
import { setupRenderingTest } from 'ember-qunit';
3-
import { render, triggerEvent, fillIn, blur } from '@ember/test-helpers';
4-
3+
import { render, triggerEvent, fillIn, focus, blur } from '@ember/test-helpers';
54
import hbs from 'htmlbars-inline-precompile';
6-
75
import {
86
validatePresence,
97
validateLength,
10-
validateConfirmation,
11-
validateFormat,
128
} from 'ember-changeset-validations/validators';
139

1410
module('Integration | Component | bs form element', function(hooks) {
@@ -21,19 +17,7 @@ module('Integration | Component | bs form element', function(hooks) {
2117
]
2218
};
2319

24-
const extendedValidation = {
25-
name: [
26-
validatePresence(true),
27-
validateLength({ min: 4 })
28-
],
29-
email: validateFormat({ type: 'email', allowBlank: true }),
30-
password: [
31-
validateLength({ min: 6 })
32-
],
33-
passwordConfirmation: validateConfirmation({ on: 'password' })
34-
}
35-
36-
test('valid validation is supported as expected', async function(assert) {
20+
test('form is submitted if valid and validation success shown', async function(assert) {
3721
let model = {
3822
name: '1234',
3923
};
@@ -93,6 +77,7 @@ module('Integration | Component | bs form element', function(hooks) {
9377
`);
9478
assert.dom('input').doesNotHaveClass('is-invalid');
9579

80+
await focus('input');
9681
await blur('input');
9782
assert.dom('input').hasClass('is-invalid');
9883
});
@@ -108,6 +93,7 @@ module('Integration | Component | bs form element', function(hooks) {
10893
`);
10994
assert.dom('input').doesNotHaveClass('is-valid');
11095

96+
await focus('input');
11197
await blur('input');
11298
assert.dom('input').hasClass('is-valid');
11399
});
@@ -124,8 +110,10 @@ module('Integration | Component | bs form element', function(hooks) {
124110
assert.dom('input').doesNotHaveClass('is-invalid');
125111

126112
await fillIn('input', 'R');
113+
assert.dom('input').doesNotHaveClass('is-invalid', 'validation is not shown while user is typing');
114+
127115
await blur('input');
128-
assert.dom('input').hasClass('is-invalid');
116+
assert.dom('input').hasClass('is-invalid', 'validation error is shown after focus out');
129117
});
130118

131119
test('validation success is shown after user input', async function(assert) {
@@ -140,53 +128,9 @@ module('Integration | Component | bs form element', function(hooks) {
140128
assert.dom('input').doesNotHaveClass('is-valid');
141129

142130
await fillIn('input', 'Rosa');
143-
await blur('input');
144-
assert.dom('input').hasClass('is-valid');
145-
});
146-
147-
test('more complicated validations', async function(assert) {
148-
let model = {
149-
name: '',
150-
password: null,
151-
passwordConfirmation: null,
152-
email: '',
153-
};
131+
assert.dom('input').doesNotHaveClass('is-valid', 'validation is not shown while user is typing');
154132

155-
this.set('model', model);
156-
this.set('validation', extendedValidation);
157-
this.submitAction = function() {
158-
assert.ok(false, 'submit action must not been called.');
159-
};
160-
this.invalidAction = function() {
161-
assert.step('Invalid action has been called.');
162-
};
163-
164-
await render(hbs`
165-
<BsForm @model={{changeset this.model this.validation}} @onSubmit={{this.submitAction}} @onInvalid={{this.invalidAction}} as |form|>
166-
<form.element id="name" @label="Name" @property="name" />
167-
<form.element id="email" @label="Email" @property="email" />
168-
<form.element id="password" @label="Password" @property="password" />
169-
<form.element id="password-confirmation" @label="Password confirmation" @property="passwordConfirmation" />
170-
</BsForm>
171-
`);
172-
173-
await fillIn('#password input', 'bad');
174-
assert.dom('#password input').doesNotHaveClass('is-invalid', 'password does not have error while typing.');
175-
assert.dom('#password input').doesNotHaveClass('is-valid', 'password does not have success while typing.');
176-
177-
await blur('#password input');
178-
assert.dom('#password input').hasClass('is-invalid', 'password does have error when focus out.');
179-
180-
await fillIn('#password-confirmation input', 'betterpass');
181-
assert.dom('#password-confirmation input').doesNotHaveClass('is-invalid', 'password confirmation does not have error while typing.');
182-
183-
await blur('#password-confirmation input');
184-
assert.dom('#password-confirmation input').hasClass('is-invalid', 'password confirmation does have error when focus out.');
185-
186-
await triggerEvent('form', 'submit');
187-
assert.dom('#password input').hasClass('is-invalid', 'password still has error after submit.');
188-
assert.dom('#password-confirmation input').hasClass('is-invalid', 'password confirmation still has error after submit.');
189-
assert.verifySteps(['Invalid action has been called.']);
133+
await blur('input');
134+
assert.dom('input').hasClass('is-valid', 'validation error is shown after focus out');
190135
});
191-
192136
});

0 commit comments

Comments
 (0)