Skip to content

Commit c88e2eb

Browse files
authored
support multiple validation errors (#29)
1 parent 3cc29fd commit c88e2eb

File tree

2 files changed

+131
-5
lines changed

2 files changed

+131
-5
lines changed

addon/components/bs-form/element.js

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,38 @@
11
import BsFormElement from 'ember-bootstrap/components/bs-form/element';
22
import { action, get } from '@ember/object';
33
import { dependentKeyCompat } from '@ember/object/compat';
4+
import { isNone, typeOf } from '@ember/utils';
45

56
export default class BsFormElementWithChangesetValidationsSupport extends BsFormElement {
67
'__ember-bootstrap_subclass' = true;
78

9+
// We convert
10+
//
11+
// `model.error.${this.property}.validation` which could be either a string or an array
12+
// see https://github.com/validated-changeset/validated-changeset/#error
13+
//
14+
// into
15+
//
16+
// Ember Bootstrap expects errors property of FormElement to be an array of validation messages:
17+
// see https://www.ember-bootstrap.com/api/classes/Components.FormElement.html#property_errors
18+
//
19+
// If the if the property is valid but no validation is present `model.error.[this.property] could also be undefined.
820
@dependentKeyCompat
921
get errors() {
10-
let error = get(this, `model.error.${this.property}.validation`);
11-
return error ? [error] : [];
22+
let errors = get(this, `model.error.${this.property}.validation`);
23+
24+
// no messages
25+
if (isNone(errors)) {
26+
return [];
27+
}
28+
29+
// a single messages
30+
if (typeOf(errors) === 'string') {
31+
return [errors];
32+
}
33+
34+
// assume it's an array of messages
35+
return errors;
1236
}
1337

1438
get hasValidator() {

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

Lines changed: 105 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { module, test } from 'qunit';
22
import { setupRenderingTest } from 'ember-qunit';
3-
import { render, triggerEvent, fillIn, focus, blur } from '@ember/test-helpers';
3+
import { render, triggerEvent, fillIn, focus, blur, findAll } from '@ember/test-helpers';
44
import hbs from 'htmlbars-inline-precompile';
55
import {
66
validatePresence,
@@ -213,8 +213,110 @@ module('Integration | Component | bs form element', function(hooks) {
213213
assert.dom('input').doesNotHaveClass('is-invalid');
214214

215215
await triggerEvent('form', 'submit');
216-
assert.dom('input').doesNotHaveClass('is-valid');
217-
assert.dom('input').doesNotHaveClass('is-invalid');
216+
assert.dom('input')
217+
.doesNotHaveClass('is-valid');
218+
assert.dom('input')
219+
.doesNotHaveClass('is-invalid');
218220
assert.verifySteps(['submit action has been called']);
219221
});
222+
223+
test('invalid-feedback is shown from single validation', async function (assert) {
224+
let model = {
225+
name: '',
226+
};
227+
228+
this.set('model', model);
229+
this.set('validation', {
230+
name: validatePresence(true)
231+
});
232+
233+
await render(hbs`
234+
<BsForm @model={{changeset this.model this.validation}} as |form|>
235+
<form.element @label="Name" @property="name" />
236+
</BsForm>
237+
`);
238+
239+
await triggerEvent('form', 'submit');
240+
assert.dom('.invalid-feedback')
241+
.hasText('Name can\'t be blank');
242+
});
243+
244+
test('invalid-feedback is shown in order from multiple validations', async function (assert) {
245+
let model = {
246+
name: '',
247+
};
248+
249+
this.set('model', model);
250+
this.set('validation', {
251+
name: [
252+
validatePresence(true),
253+
validateLength({ min: 4 })
254+
]
255+
});
256+
257+
await render(hbs`
258+
<BsForm @model={{changeset this.model this.validation}} as |form|>
259+
<form.element @label="Name" @property="name" />
260+
</BsForm>
261+
`);
262+
263+
await triggerEvent('form', 'submit');
264+
assert.dom('.invalid-feedback')
265+
.hasText('Name can\'t be blank');
266+
267+
await fillIn('input', 'R');
268+
await triggerEvent('form', 'submit');
269+
assert.dom('.invalid-feedback')
270+
.hasText('Name is too short (minimum is 4 characters)');
271+
});
272+
273+
test('invalid-feedback is shown (multiple messages) in order from multiple validations', async function (assert) {
274+
let model = {
275+
name: '',
276+
};
277+
278+
this.set('model', model);
279+
this.set('validation', {
280+
name: [
281+
validatePresence(true),
282+
validateLength({ min: 4 })
283+
]
284+
});
285+
286+
await render(hbs`
287+
<BsForm @model={{changeset this.model this.validation}} as |form|>
288+
<form.element @label="Name" @property="name" @showMultipleErrors={{true}}/>
289+
</BsForm>
290+
`);
291+
292+
await triggerEvent('form', 'submit');
293+
294+
let feedbackElements = findAll('.invalid-feedback');
295+
let results = Array.from(feedbackElements, element => element.textContent.trim())
296+
let expected = ["Name can't be blank", "Name is too short (minimum is 4 characters)"];
297+
298+
expected.forEach((message) => {
299+
assert.ok(results.includes(message))
300+
})
301+
});
302+
303+
test('no feedback is shown for nonexistant validations', async function (assert) {
304+
let model = {
305+
name: '',
306+
};
307+
308+
this.set('model', model);
309+
this.set('validation', {
310+
nombre: validatePresence(true)
311+
});
312+
313+
await render(hbs`
314+
<BsForm @model={{changeset this.model this.validation}} as |form|>
315+
<form.element @label="Name" @property="name" />
316+
</BsForm>
317+
`);
318+
319+
await triggerEvent('form', 'submit');
320+
assert.dom('.invalid-feedback').doesNotExist();
321+
});
220322
});

0 commit comments

Comments
 (0)