Skip to content

Commit 8d7c7f4

Browse files
committed
test(select, ngOptions): add more tests for "required" with "empty" or "unknown" option
1 parent 080357e commit 8d7c7f4

File tree

3 files changed

+231
-54
lines changed

3 files changed

+231
-54
lines changed

src/ng/directive/ngOptions.js

-1
Original file line numberDiff line numberDiff line change
@@ -704,7 +704,6 @@ var ngOptionsDirective = ['$compile', '$document', '$parse', function($compile,
704704
ngModelCtrl.$render();
705705
}
706706
}
707-
708707
}
709708
}
710709

test/ng/directive/ngOptionsSpec.js

+110-17
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22

33
describe('ngOptions', function() {
44

5-
var scope, formElement, element, $compile, linkLog;
5+
var scope, formElement, element, $compile, linkLog, ngModelCtrl;
66

77
function compile(html) {
88
formElement = jqLite('<form name="form">' + html + '</form>');
99
element = formElement.find('select');
1010
$compile(formElement)(scope);
11+
ngModelCtrl = element.controller('ngModel');
1112
scope.$apply();
1213
}
1314

@@ -181,6 +182,7 @@ describe('ngOptions', function() {
181182
afterEach(function() {
182183
scope.$destroy(); //disables unknown option work during destruction
183184
dealoc(formElement);
185+
ngModelCtrl = null;
184186
});
185187

186188
function createSelect(attrs, blank, unknown) {
@@ -2925,42 +2927,68 @@ describe('ngOptions', function() {
29252927
});
29262928

29272929

2928-
describe('ngRequired', function() {
2930+
describe('required state', function() {
29292931

2930-
it('should allow bindings on ngRequired', function() {
2932+
it('should set the error if the empty option is selected', function() {
2933+
createSelect({
2934+
'ng-model': 'selection',
2935+
'ng-options': 'item for item in values',
2936+
'required': ''
2937+
}, true);
2938+
2939+
scope.$apply(function() {
2940+
scope.values = ['a', 'b'];
2941+
scope.selection = scope.values[0];
2942+
});
2943+
expect(element).toBeValid();
2944+
expect(ngModelCtrl.$error.required).toBeFalsy();
2945+
2946+
var options = element.find('option');
2947+
2948+
// view -> model
2949+
browserTrigger(options[0], 'click');
2950+
expect(element).toBeInvalid();
2951+
expect(ngModelCtrl.$error.required).toBeTruthy();
2952+
2953+
browserTrigger(options[1], 'click');
2954+
expect(element).toBeValid();
2955+
expect(ngModelCtrl.$error.required).toBeFalsy();
2956+
2957+
// model -> view
2958+
scope.$apply('selection = "unmatched value"');
2959+
expect(options[0]).toBeMarkedAsSelected();
2960+
expect(element).toBeInvalid();
2961+
expect(ngModelCtrl.$error.required).toBeTruthy();
2962+
});
2963+
2964+
2965+
it('should validate with empty option and bound ngRequired', function() {
29312966
createSelect({
29322967
'ng-model': 'value',
29332968
'ng-options': 'item.name for item in values',
29342969
'ng-required': 'required'
29352970
}, true);
29362971

2937-
29382972
scope.$apply(function() {
29392973
scope.values = [{name: 'A', id: 1}, {name: 'B', id: 2}];
29402974
scope.required = false;
29412975
});
29422976

2943-
element.val('');
2944-
browserTrigger(element, 'change');
2977+
var options = element.find('option');
2978+
2979+
browserTrigger(options[0], 'click');
29452980
expect(element).toBeValid();
29462981

2947-
scope.$apply(function() {
2948-
scope.required = true;
2949-
});
2982+
scope.$apply('required = true');
29502983
expect(element).toBeInvalid();
29512984

2952-
scope.$apply(function() {
2953-
scope.value = scope.values[0];
2954-
});
2985+
scope.$apply('value = values[0]');
29552986
expect(element).toBeValid();
29562987

2957-
element.val('');
2958-
browserTrigger(element, 'change');
2988+
browserTrigger(options[0], 'click');
29592989
expect(element).toBeInvalid();
29602990

2961-
scope.$apply(function() {
2962-
scope.required = false;
2963-
});
2991+
scope.$apply('required = false');
29642992
expect(element).toBeValid();
29652993
});
29662994

@@ -2989,6 +3017,43 @@ describe('ngOptions', function() {
29893017
});
29903018

29913019

3020+
it('should NOT set the error if the empty option is present but required attribute is not',
3021+
function() {
3022+
scope.$apply(function() {
3023+
scope.values = ['a', 'b'];
3024+
});
3025+
3026+
createSingleSelect();
3027+
3028+
expect(element).toBeValid();
3029+
expect(element).toBePristine();
3030+
expect(ngModelCtrl.$error.required).toBeFalsy();
3031+
}
3032+
);
3033+
3034+
3035+
it('should NOT set the error if the unknown option is selected', function() {
3036+
createSelect({
3037+
'ng-model': 'selection',
3038+
'ng-options': 'item for item in values',
3039+
'required': ''
3040+
});
3041+
3042+
scope.$apply(function() {
3043+
scope.values = ['a', 'b'];
3044+
scope.selection = 'a';
3045+
});
3046+
3047+
expect(element).toBeValid();
3048+
expect(ngModelCtrl.$error.required).toBeFalsy();
3049+
3050+
scope.$apply('selection = "c"');
3051+
expect(element).toEqualSelect(['?'], 'string:a', 'string:b');
3052+
expect(element).toBeValid();
3053+
expect(ngModelCtrl.$error.required).toBeFalsy();
3054+
});
3055+
3056+
29923057
it('should allow falsy values as values', function() {
29933058
createSelect({
29943059
'ng-model': 'value',
@@ -3009,6 +3074,34 @@ describe('ngOptions', function() {
30093074
expect(element).toBeValid();
30103075
expect(scope.value).toBe(false);
30113076
});
3077+
3078+
3079+
it('should validate after option list was updated', function() {
3080+
createSelect({
3081+
'ng-model': 'selection',
3082+
'ng-options': 'item for item in values',
3083+
'required': ''
3084+
}, true);
3085+
3086+
scope.$apply(function() {
3087+
scope.values = ['A', 'B'];
3088+
scope.selection = scope.values[0];
3089+
});
3090+
3091+
expect(element).toEqualSelect('', ['string:A'], 'string:B');
3092+
expect(element).toBeValid();
3093+
expect(ngModelCtrl.$error.required).toBeFalsy();
3094+
3095+
scope.$apply(function() {
3096+
scope.values = ['C', 'D'];
3097+
});
3098+
3099+
expect(element).toEqualSelect([''], 'string:C', 'string:D');
3100+
expect(element).toBeInvalid();
3101+
expect(ngModelCtrl.$error.required).toBeTruthy();
3102+
// ngModel sets undefined for invalid values
3103+
expect(scope.selection).toBeUndefined();
3104+
});
30123105
});
30133106

30143107
describe('required and empty option', function() {

test/ng/directive/selectSpec.js

+121-36
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ describe('select', function() {
77
formElement = jqLite('<form name="form">' + html + '</form>');
88
element = formElement.find('select');
99
$compile(formElement)(scope);
10+
ngModelCtrl = element.controller('ngModel');
1011
scope.$digest();
1112
}
1213

@@ -79,6 +80,7 @@ describe('select', function() {
7980
afterEach(function() {
8081
scope.$destroy(); //disables unknown option work during destruction
8182
dealoc(formElement);
83+
ngModelCtrl = null;
8284
});
8385

8486

@@ -190,54 +192,108 @@ describe('select', function() {
190192
});
191193

192194

193-
it('should require', function() {
194-
compile(
195-
'<select name="select" ng-model="selection" required ng-change="change()">' +
196-
'<option value=""></option>' +
197-
'<option value="c">C</option>' +
198-
'</select>');
195+
describe('required state', function() {
199196

200-
scope.change = function() {
201-
scope.log += 'change;';
202-
};
197+
it('should set the error if the empty option is selected', function() {
198+
compile(
199+
'<select name="select" ng-model="selection" required>' +
200+
'<option value=""></option>' +
201+
'<option value="a">A</option>' +
202+
'<option value="b">B</option>' +
203+
'</select>');
203204

204-
scope.$apply(function() {
205-
scope.log = '';
206-
scope.selection = 'c';
205+
scope.$apply(function() {
206+
scope.selection = 'a';
207+
});
208+
209+
expect(element).toBeValid();
210+
expect(ngModelCtrl.$error.required).toBeFalsy();
211+
212+
var options = element.find('option');
213+
214+
// view -> model
215+
browserTrigger(options[0], 'click');
216+
expect(element).toBeInvalid();
217+
expect(ngModelCtrl.$error.required).toBeTruthy();
218+
219+
browserTrigger(options[1], 'click');
220+
expect(element).toBeValid();
221+
expect(ngModelCtrl.$error.required).toBeFalsy();
222+
223+
// model -> view
224+
scope.$apply('selection = null');
225+
options = element.find('option');
226+
expect(options[0]).toBeMarkedAsSelected();
227+
expect(element).toBeInvalid();
228+
expect(ngModelCtrl.$error.required).toBeTruthy();
207229
});
208230

209-
expect(scope.form.select.$error.required).toBeFalsy();
210-
expect(element).toBeValid();
211-
expect(element).toBePristine();
212231

213-
scope.$apply(function() {
214-
scope.selection = '';
232+
it('should validate with empty option and bound ngRequired', function() {
233+
compile(
234+
'<select name="select" ng-model="selection" ng-required="required">' +
235+
'<option value=""></option>' +
236+
'<option value="a">A</option>' +
237+
'<option value="b">B</option>' +
238+
'</select>');
239+
240+
scope.$apply(function() {
241+
scope.required = false;
242+
});
243+
244+
var options = element.find('option');
245+
246+
browserTrigger(options[0], 'click');
247+
expect(element).toBeValid();
248+
249+
scope.$apply('required = true');
250+
expect(element).toBeInvalid();
251+
252+
scope.$apply('selection = "a"');
253+
expect(element).toBeValid();
254+
expect(element).toEqualSelect('', ['a'], 'b');
255+
256+
browserTrigger(options[0], 'click');
257+
expect(element).toBeInvalid();
258+
259+
scope.$apply('required = false');
260+
expect(element).toBeValid();
215261
});
216262

217-
expect(scope.form.select.$error.required).toBeTruthy();
218-
expect(element).toBeInvalid();
219-
expect(element).toBePristine();
220-
expect(scope.log).toEqual('');
221263

222-
element[0].value = 'c';
223-
browserTrigger(element, 'change');
224-
expect(element).toBeValid();
225-
expect(element).toBeDirty();
226-
expect(scope.log).toEqual('change;');
227-
});
264+
it('should not be invalid if no required attribute is present', function() {
265+
compile(
266+
'<select name="select" ng-model="selection">' +
267+
'<option value=""></option>' +
268+
'<option value="c">C</option>' +
269+
'</select>');
228270

271+
expect(element).toBeValid();
272+
expect(element).toBePristine();
273+
});
229274

230-
it('should not be invalid if no require', function() {
231-
compile(
232-
'<select name="select" ng-model="selection">' +
233-
'<option value=""></option>' +
234-
'<option value="c">C</option>' +
235-
'</select>');
236275

237-
expect(element).toBeValid();
238-
expect(element).toBePristine();
239-
});
276+
it('should NOT set the error if the unknown option is selected', function() {
277+
compile(
278+
'<select name="select" ng-model="selection" required>' +
279+
'<option value="a">A</option>' +
280+
'<option value="b">B</option>' +
281+
'</select>');
240282

283+
scope.$apply(function() {
284+
scope.selection = 'a';
285+
});
286+
287+
expect(element).toBeValid();
288+
expect(ngModelCtrl.$error.required).toBeFalsy();
289+
290+
scope.$apply('selection = "c"');
291+
expect(element).toEqualSelect([unknownValue('c')], 'a', 'b');
292+
expect(element).toBeValid();
293+
expect(ngModelCtrl.$error.required).toBeFalsy();
294+
});
295+
296+
});
241297

242298
it('should work with repeated value options', function() {
243299
scope.robots = ['c3p0', 'r2d2'];
@@ -2358,6 +2414,35 @@ describe('select', function() {
23582414
expect(previouslySelectedOptionElement).not.toBe(optionElements[0]);
23592415
});
23602416

2417+
2418+
it('should validate when the options change', function() {
2419+
scope.values = ['A', 'B'];
2420+
scope.selection = 'A';
2421+
2422+
compile(
2423+
'<select ng-model="selection" required>' +
2424+
'<option value="">--select--</option>' +
2425+
'<option ng-repeat="option in values" value="{{option}}">{{option}}</option>' +
2426+
'</select>'
2427+
);
2428+
2429+
expect(element).toEqualSelect('', ['A'], 'B');
2430+
expect(element).toBeValid();
2431+
expect(ngModelCtrl.$error.required).toBeFalsy();
2432+
2433+
scope.$apply(function() {
2434+
// Only when new objects are used, ngRepeat re-creates the element from scratch
2435+
scope.values = ['B', 'C'];
2436+
});
2437+
2438+
expect(element).toEqualSelect([''], 'B', 'C');
2439+
expect(element).toBeInvalid();
2440+
expect(ngModelCtrl.$error.required).toBeTruthy();
2441+
// ngModel sets undefined for invalid values
2442+
expect(scope.selection).toBeUndefined();
2443+
});
2444+
2445+
23612446
});
23622447

23632448

0 commit comments

Comments
 (0)