diff --git a/lib/directive/ng_control.dart b/lib/directive/ng_control.dart index 70d065b62..155b79582 100644 --- a/lib/directive/ng_control.dart +++ b/lib/directive/ng_control.dart @@ -170,18 +170,23 @@ abstract class NgControl implements NgAttachAware, NgDetachAware { * error is real). */ updateControlValidity(NgControl ngModel, String errorType, bool isValid) { + String validClassName = errorType + '-valid'; + String invalidClassName = errorType + '-invalid'; + if (isValid) { if (errors.containsKey(errorType)) { Set errorsByName = errors[errorType]; errorsByName.remove(ngModel); if (errorsByName.isEmpty) { errors.remove(errorType); + element..removeClass(invalidClassName)..addClass(validClassName); } } if (errors.isEmpty) { valid = true; } } else { + element..removeClass(validClassName)..addClass(invalidClassName); errors.putIfAbsent(errorType, () => new Set()).add(ngModel); invalid = true; } diff --git a/test/directive/ng_form_spec.dart b/test/directive/ng_form_spec.dart index 6b4d8acff..6780ecf3b 100644 --- a/test/directive/ng_form_spec.dart +++ b/test/directive/ng_form_spec.dart @@ -529,6 +529,53 @@ void main() { })); }); + describe('validators', () { + it('should display the valid and invalid CSS classes on the element for each validation', + inject((TestBed _, Scope scope) { + + var form = _.compile( + '
' + + ' ' + + '
' + ); + + scope.apply(); + + expect(form.classes.contains('ng-required-invalid')).toBe(true); + expect(form.classes.contains('ng-required-valid')).toBe(false); + + scope.apply(() { + scope.context['myModel'] = 'value'; + }); + + expect(form.classes.contains('ng-required-valid')).toBe(true); + expect(form.classes.contains('ng-required-invalid')).toBe(false); + })); + + it('should display the valid and invalid CSS classes on the element for custom validations', () { + module((Module module) { + module.type(MyCustomFormValidator); + }); + inject((TestBed _, Scope scope) { + var form = _.compile('
' + + ' ' + + '
'); + + scope.apply(); + + expect(form.classes.contains('custom-invalid')).toBe(true); + expect(form.classes.contains('custom-valid')).toBe(false); + + scope.apply(() { + scope.context['myModel'] = 'yes'; + }); + + expect(form.classes.contains('custom-valid')).toBe(true); + expect(form.classes.contains('custom-invalid')).toBe(false); + }); + }); + }); + describe('reset()', () { it('should reset the model value to its original state', inject((TestBed _) { _.compile('
' + @@ -661,3 +708,17 @@ void main() { }); }); } + +@NgDirective( + selector: '[custom-form-validation]') +class MyCustomFormValidator extends NgValidator { + MyCustomFormValidator(NgModel ngModel) { + ngModel.addValidator(this); + } + + final String name = 'custom'; + + bool isValid(name) { + return name != null && name == 'yes'; + } +} diff --git a/test/directive/ng_model_spec.dart b/test/directive/ng_model_spec.dart index 45a93fec2..750d4ee9e 100644 --- a/test/directive/ng_model_spec.dart +++ b/test/directive/ng_model_spec.dart @@ -8,7 +8,8 @@ void main() { TestBed _; beforeEach(module((Module module) { - module..type(ControllerWithNoLove); + module.type(ControllerWithNoLove); + module.type(MyCustomInputValidator); })); beforeEach(inject((TestBed tb) => _ = tb)); @@ -1307,6 +1308,46 @@ void main() { expect(model.untouched).toBe(true); }); + describe('validators', () { + it('should display the valid and invalid CSS classes on the element for each validation', + inject((TestBed _, Scope scope) { + + var input = _.compile(''); + + scope.apply(() { + scope.context['myModel'] = 'value'; + }); + + expect(input.classes.contains('ng-email-invalid')).toBe(true); + expect(input.classes.contains('ng-email-valid')).toBe(false); + + scope.apply(() { + scope.context['myModel'] = 'value@email.com'; + }); + + expect(input.classes.contains('ng-email-valid')).toBe(true); + expect(input.classes.contains('ng-email-invalid')).toBe(false); + })); + + it('should display the valid and invalid CSS classes on the element for custom validations', + inject((TestBed _, Scope scope) { + + var input = _.compile(''); + + scope.apply(); + + expect(input.classes.contains('custom-invalid')).toBe(true); + expect(input.classes.contains('custom-valid')).toBe(false); + + scope.apply(() { + scope.context['myModel'] = 'yes'; + }); + + expect(input.classes.contains('custom-valid')).toBe(true); + expect(input.classes.contains('custom-invalid')).toBe(false); + })); + }); + describe('converters', () { it('should parse the model value according to the given parser', inject((Scope scope) { _.compile(''); @@ -1439,3 +1480,17 @@ class VowelValueParser implements NgModelConverter { return value; } } + +@NgDirective( + selector: '[custom-input-validation]') +class MyCustomInputValidator extends NgValidator { + MyCustomInputValidator(NgModel ngModel) { + ngModel.addValidator(this); + } + + final String name = 'custom'; + + bool isValid(name) { + return name != null && name == 'yes'; + } +}