Index: third_party/pkg/angular/test/directive/ng_model_spec.dart |
diff --git a/third_party/pkg/angular/test/directive/ng_model_spec.dart b/third_party/pkg/angular/test/directive/ng_model_spec.dart |
index 645e9c1a4c00823fca80d0d4f72d63e12d812471..0fd41441e4ad7691495ca0f0963f76f294755c1c 100644 |
--- a/third_party/pkg/angular/test/directive/ng_model_spec.dart |
+++ b/third_party/pkg/angular/test/directive/ng_model_spec.dart |
@@ -3,18 +3,51 @@ library ng_model_spec; |
import '../_specs.dart'; |
import 'dart:html' as dom; |
+//----------------------------------------------------------------------------- |
+// Utility functions |
+ |
+/* This function simulates typing the given text into the input field. The |
+ * text will be added wherever the insertion point happens to be. This method |
+ * has as side-effect to set the focus on the input (without setting the |
+ * focus, the text dispatch may not work). |
+ */ |
+void simulateTypingText(InputElement input, String text) { |
+ input..focus()..dispatchEvent(new TextEvent('textInput', data: text)); |
+} |
+ |
+bool simulateTypingTextWithConfirmation(InputElement input, String text, |
+ { bool shouldWorkForChrome : true }) { |
+ bool result; |
+ String val = input.value; |
+ try { |
+ simulateTypingText(input, text); |
+ result = input.value == val + text; |
+ } catch (e) { |
+ result = false; |
+ } |
+ if (!result && shouldWorkForChrome) expect(isBrowser('Chrome')).toBeFalsy(); |
+ return result; |
+} |
+ |
+bool isBrowser(String pattern) => dom.window.navigator.userAgent.indexOf(pattern) > 0; |
+ |
+//----------------------------------------------------------------------------- |
+ |
void main() { |
describe('ng-model', () { |
TestBed _; |
- beforeEach(module((Module module) { |
- module..type(ControllerWithNoLove); |
- })); |
+ beforeEachModule((Module module) { |
+ module |
+ ..type(ControllerWithNoLove) |
+ ..type(MyCustomInputValidator) |
+ ..type(CountingValidator); |
+ }); |
- beforeEach(inject((TestBed tb) => _ = tb)); |
+ beforeEach((TestBed tb) => _ = tb); |
describe('type="text" like', () { |
- it('should update input value from model', inject(() { |
+ it('should update input value from model', () { |
_.compile('<input type="text" ng-model="model">'); |
_.rootScope.apply(); |
@@ -22,9 +55,9 @@ void main() { |
_.rootScope.apply('model = "misko"'); |
expect((_.rootElement as dom.InputElement).value).toEqual('misko'); |
- })); |
+ }); |
- it('should render null as the empty string', inject(() { |
+ it('should render null as the empty string', () { |
_.compile('<input type="text" ng-model="model">'); |
_.rootScope.apply(); |
@@ -32,9 +65,9 @@ void main() { |
_.rootScope.apply('model = null'); |
expect((_.rootElement as dom.InputElement).value).toEqual(''); |
- })); |
+ }); |
- it('should update model from the input value', inject(() { |
+ it('should update model from the input value', () { |
_.compile('<input type="text" ng-model="model" probe="p">'); |
Probe probe = _.rootScope.context['p']; |
var ngModel = probe.directive(NgModel); |
@@ -45,12 +78,69 @@ void main() { |
expect(_.rootScope.context['model']).toEqual('abc'); |
inputElement.value = 'def'; |
- var input = probe.directive(InputTextLikeDirective); |
+ var input = probe.directive(InputTextLike); |
input.processValue(); |
expect(_.rootScope.context['model']).toEqual('def'); |
- })); |
+ }); |
+ |
+ it('should write to input only if the value is different', |
+ (Injector i, Animate animate) { |
+ |
+ var scope = _.rootScope; |
+ var element = new dom.InputElement(); |
+ var ngElement = new NgElement(element, scope, animate); |
+ |
+ NodeAttrs nodeAttrs = new NodeAttrs(new DivElement()); |
+ nodeAttrs['ng-model'] = 'model'; |
+ var model = new NgModel(scope, ngElement, i.createChild([new Module()]), |
+ nodeAttrs, new Animate()); |
+ dom.querySelector('body').append(element); |
+ var input = new InputTextLike(element, model, scope); |
+ |
+ element |
+ ..value = 'abc' |
+ ..selectionStart = 1 |
+ ..selectionEnd = 2; |
+ |
+ scope.apply(() { |
+ scope.context['model'] = 'abc'; |
+ }); |
+ |
+ expect(element.value).toEqual('abc'); |
+ // No update. selectionStart/End is unchanged. |
+ expect(element.selectionStart).toEqual(1); |
+ expect(element.selectionEnd).toEqual(2); |
+ |
+ scope.apply(() { |
+ scope.context['model'] = 'xyz'; |
+ }); |
+ |
+ // Value updated. selectionStart/End changed. |
+ expect(element.value).toEqual('xyz'); |
+ expect(element.selectionStart).toEqual(3); |
+ expect(element.selectionEnd).toEqual(3); |
+ }); |
+ |
+ it('should only render the input value upon the next digest', (Scope scope) { |
+ _.compile('<input type="text" ng-model="model" probe="p">'); |
+ Probe probe = _.rootScope.context['p']; |
+ var ngModel = probe.directive(NgModel); |
+ InputElement inputElement = probe.element; |
+ |
+ ngModel.render('xyz'); |
+ scope.context['model'] = 'xyz'; |
+ |
+ expect(inputElement.value).not.toEqual('xyz'); |
- it('should update model from the input value for type=number', inject(() { |
+ scope.apply(); |
+ |
+ expect(inputElement.value).toEqual('xyz'); |
+ }); |
+ }); |
+ |
+ describe('type="number" or type="range"', () { |
+ |
+ it('should update model from the input value for type=number', () { |
_.compile('<input type="number" ng-model="model" probe="p">'); |
Probe probe = _.rootScope.context['p']; |
var ngModel = probe.directive(NgModel); |
@@ -61,12 +151,12 @@ void main() { |
expect(_.rootScope.context['model']).toEqual(12); |
inputElement.value = '14'; |
- var input = probe.directive(InputNumberLikeDirective); |
+ var input = probe.directive(InputNumberLike); |
input.processValue(); |
expect(_.rootScope.context['model']).toEqual(14); |
- })); |
+ }); |
- it('should update input type=number to blank when model is null', inject(() { |
+ it('should update input type=number to blank when model is null', () { |
_.compile('<input type="number" ng-model="model" probe="p">'); |
Probe probe = _.rootScope.context['p']; |
var ngModel = probe.directive(NgModel); |
@@ -79,55 +169,28 @@ void main() { |
_.rootScope.context['model'] = null; |
_.rootScope.apply(); |
expect(inputElement.value).toEqual(''); |
- })); |
- |
- it('should write to input only if value is different', inject((Injector i, AstParser parser) { |
- var scope = _.rootScope; |
- var element = new dom.InputElement(); |
- NodeAttrs nodeAttrs = new NodeAttrs(new DivElement()); |
- nodeAttrs['ng-model'] = 'model'; |
- var model = new NgModel(scope, element, i.createChild([new Module()]), new NgNullForm(), parser, nodeAttrs); |
- dom.querySelector('body').append(element); |
- var input = new InputTextLikeDirective(element, model, scope); |
- |
- element |
- ..value = 'abc' |
- ..selectionStart = 1 |
- ..selectionEnd = 2; |
- |
- model.render('abc'); |
- |
- expect(element.value).toEqual('abc'); |
- // No update. selectionStart/End is unchanged. |
- expect(element.selectionStart).toEqual(1); |
- expect(element.selectionEnd).toEqual(2); |
- |
- model.render('xyz'); |
- |
- // Value updated. selectionStart/End changed. |
- expect(element.value).toEqual('xyz'); |
- expect(element.selectionStart).toEqual(3); |
- expect(element.selectionEnd).toEqual(3); |
- })); |
- }); |
+ }); |
- /* This function simulates typing the given text into the input |
- * field. The text will be added wherever the insertion point |
- * happens to be. This method has as side-effect to set the |
- * focus on the input (without setting the focus the text |
- * dispatch may not work). |
- */ |
- void simulateTypingText(InputElement input, String text) { |
- input..focus()..dispatchEvent(new TextEvent('textInput', data: text)); |
- } |
+ it('should be invalid when the input value results in a NaN value', () { |
+ _.compile('<input type="number" ng-model="model" probe="p">'); |
+ Probe probe = _.rootScope.context['p']; |
+ var ngModel = probe.directive(NgModel); |
+ InputElement inputElement = probe.element; |
- describe('type="number" like', () { |
+ inputElement.value = 'aa'; |
+ _.triggerEvent(inputElement, 'change'); |
+ expect(_.rootScope.context['model'].isNaN).toBe(true); |
+ expect(ngModel.valid).toBe(false); |
+ }); |
- it('should leave input unchanged when text does not represent a valid number', inject((Injector i) { |
+ it('should leave input unchanged when text does not represent a valid number', (Injector i) { |
var modelFieldName = 'modelForNumFromInvalid1'; |
var element = _.compile('<input type="number" ng-model="$modelFieldName">'); |
dom.querySelector('body').append(element); |
+ if (!simulateTypingTextWithConfirmation(element, '1')) return; // skip test. |
+ element.value = ''; // reset input |
+ |
// This test will progressively enter the text '1e1' |
// '1' is a valid number. |
// '1e' is not a valid number. |
@@ -149,35 +212,38 @@ void main() { |
_.triggerEvent(element, 'change'); |
expect(element.value).toEqual('1e1'); |
expect(_.rootScope.context[modelFieldName]).toEqual(10); |
- })); |
+ }); |
- it('should not reformat user input to equivalent numeric representation', inject((Injector i) { |
+ it('should not reformat user input to equivalent numeric representation', (Injector i) { |
var modelFieldName = 'modelForNumFromInvalid2'; |
var element = _.compile('<input type="number" ng-model="$modelFieldName">'); |
dom.querySelector('body').append(element); |
+ if (!simulateTypingTextWithConfirmation(element, '1')) return; // skip test. |
+ element.value = ''; // reset input |
+ |
simulateTypingText(element, '1e-1'); |
expect(element.value).toEqual('1e-1'); |
expect(_.rootScope.context[modelFieldName]).toEqual(0.1); |
- })); |
+ }); |
- it('should update input value from model', inject(() { |
+ it('should update input value from model', () { |
_.compile('<input type="number" ng-model="model">'); |
_.rootScope.apply(); |
_.rootScope.apply('model = 42'); |
expect((_.rootElement as dom.InputElement).value).toEqual('42'); |
- })); |
+ }); |
- it('should update input value from model for range inputs', inject(() { |
+ it('should update input value from model for range inputs', () { |
_.compile('<input type="range" ng-model="model">'); |
_.rootScope.apply(); |
_.rootScope.apply('model = 42'); |
expect((_.rootElement as dom.InputElement).value).toEqual('42'); |
- })); |
+ }); |
- it('should update model from the input value', inject(() { |
+ it('should update model from the input value', () { |
_.compile('<input type="number" ng-model="model" probe="p">'); |
Probe probe = _.rootScope.context['p']; |
var ngModel = probe.directive(NgModel); |
@@ -188,12 +254,12 @@ void main() { |
expect(_.rootScope.context['model']).toEqual(42); |
inputElement.value = '43'; |
- var input = probe.directive(InputNumberLikeDirective); |
+ var input = probe.directive(InputNumberLike); |
input.processValue(); |
expect(_.rootScope.context['model']).toEqual(43); |
- })); |
+ }); |
- it('should update model to NaN from a blank input value', inject(() { |
+ it('should update model to NaN from a blank input value', () { |
_.compile('<input type="number" ng-model="model" probe="p">'); |
Probe probe = _.rootScope.context['p']; |
var ngModel = probe.directive(NgModel); |
@@ -202,9 +268,9 @@ void main() { |
inputElement.value = ''; |
_.triggerEvent(inputElement, 'change'); |
expect(_.rootScope.context['model'].isNaN).toBeTruthy(); |
- })); |
+ }); |
- it('should update model from the input value for range inputs', inject(() { |
+ it('should update model from the input value for range inputs', () { |
_.compile('<input type="range" ng-model="model" probe="p">'); |
Probe probe = _.rootScope.context['p']; |
var ngModel = probe.directive(NgModel); |
@@ -215,12 +281,12 @@ void main() { |
expect(_.rootScope.context['model']).toEqual(42); |
inputElement.value = '43'; |
- var input = probe.directive(InputNumberLikeDirective); |
+ var input = probe.directive(InputNumberLike); |
input.processValue(); |
expect(_.rootScope.context['model']).toEqual(43); |
- })); |
+ }); |
- it('should update model to a native default value from a blank range input value', inject(() { |
+ it('should update model to a native default value from a blank range input value', () { |
_.compile('<input type="range" ng-model="model" probe="p">'); |
Probe probe = _.rootScope.context['p']; |
var ngModel = probe.directive(NgModel); |
@@ -229,20 +295,36 @@ void main() { |
inputElement.value = ''; |
_.triggerEvent(inputElement, 'change'); |
expect(_.rootScope.context['model']).toBeDefined(); |
- })); |
+ }); |
- it('should render null as blank', inject(() { |
+ it('should render null as blank', () { |
_.compile('<input type="number" ng-model="model">'); |
_.rootScope.apply(); |
_.rootScope.apply('model = null'); |
expect((_.rootElement as dom.InputElement).value).toEqual(''); |
- })); |
+ }); |
+ |
+ it('should only render the input value upon the next digest', (Scope scope) { |
+ _.compile('<input type="number" ng-model="model" probe="p">'); |
+ Probe probe = _.rootScope.context['p']; |
+ var ngModel = probe.directive(NgModel); |
+ InputElement inputElement = probe.element; |
+ |
+ ngModel.render(123); |
+ scope.context['model'] = 123; |
+ |
+ expect(inputElement.value).not.toEqual('123'); |
+ |
+ scope.apply(); |
+ |
+ expect(inputElement.value).toEqual('123'); |
+ }); |
}); |
describe('type="password"', () { |
- it('should update input value from model', inject(() { |
+ it('should update input value from model', () { |
_.compile('<input type="password" ng-model="model">'); |
_.rootScope.apply(); |
@@ -250,9 +332,9 @@ void main() { |
_.rootScope.apply('model = "misko"'); |
expect((_.rootElement as dom.InputElement).value).toEqual('misko'); |
- })); |
+ }); |
- it('should render null as the empty string', inject(() { |
+ it('should render null as the empty string', () { |
_.compile('<input type="password" ng-model="model">'); |
_.rootScope.apply(); |
@@ -260,9 +342,9 @@ void main() { |
_.rootScope.apply('model = null'); |
expect((_.rootElement as dom.InputElement).value).toEqual(''); |
- })); |
+ }); |
- it('should update model from the input value', inject(() { |
+ it('should update model from the input value', () { |
_.compile('<input type="password" ng-model="model" probe="p">'); |
Probe probe = _.rootScope.context['p']; |
var ngModel = probe.directive(NgModel); |
@@ -273,42 +355,67 @@ void main() { |
expect(_.rootScope.context['model']).toEqual('abc'); |
inputElement.value = 'def'; |
- var input = probe.directive(InputTextLikeDirective); |
+ var input = probe.directive(InputTextLike); |
input.processValue(); |
expect(_.rootScope.context['model']).toEqual('def'); |
- })); |
+ }); |
+ |
+ it('should write to input only if value is different', |
+ (Injector i, Animate animate) { |
- it('should write to input only if value is different', inject((Injector i, AstParser parser) { |
var scope = _.rootScope; |
var element = new dom.InputElement(); |
+ var ngElement = new NgElement(element, scope, animate); |
+ |
NodeAttrs nodeAttrs = new NodeAttrs(new DivElement()); |
nodeAttrs['ng-model'] = 'model'; |
- var model = new NgModel(scope, element, i.createChild([new Module()]), new NgNullForm(), parser, nodeAttrs); |
+ var model = new NgModel(scope, ngElement, i.createChild([new Module()]), |
+ nodeAttrs, new Animate()); |
dom.querySelector('body').append(element); |
- var input = new InputTextLikeDirective(element, model, scope); |
+ var input = new InputTextLike(element, model, scope); |
element |
..value = 'abc' |
..selectionStart = 1 |
..selectionEnd = 2; |
- model.render('abc'); |
+ scope.apply(() { |
+ scope.context['model'] = 'abc'; |
+ }); |
expect(element.value).toEqual('abc'); |
expect(element.selectionStart).toEqual(1); |
expect(element.selectionEnd).toEqual(2); |
- model.render('xyz'); |
+ scope.apply(() { |
+ scope.context['model'] = 'xyz'; |
+ }); |
expect(element.value).toEqual('xyz'); |
expect(element.selectionStart).toEqual(3); |
expect(element.selectionEnd).toEqual(3); |
- })); |
+ }); |
+ |
+ it('should only render the input value upon the next digest', (Scope scope) { |
+ _.compile('<input type="password" ng-model="model" probe="p">'); |
+ Probe probe = _.rootScope.context['p']; |
+ var ngModel = probe.directive(NgModel); |
+ InputElement inputElement = probe.element; |
+ |
+ ngModel.render('xyz'); |
+ scope.context['model'] = 'xyz'; |
+ |
+ expect(inputElement.value).not.toEqual('xyz'); |
+ |
+ scope.apply(); |
+ |
+ expect(inputElement.value).toEqual('xyz'); |
+ }); |
}); |
describe('type="search"', () { |
- it('should update input value from model', inject(() { |
+ it('should update input value from model', () { |
_.compile('<input type="search" ng-model="model">'); |
_.rootScope.apply(); |
@@ -316,9 +423,9 @@ void main() { |
_.rootScope.apply('model = "misko"'); |
expect((_.rootElement as dom.InputElement).value).toEqual('misko'); |
- })); |
+ }); |
- it('should render null as the empty string', inject(() { |
+ it('should render null as the empty string', () { |
_.compile('<input type="search" ng-model="model">'); |
_.rootScope.apply(); |
@@ -326,9 +433,9 @@ void main() { |
_.rootScope.apply('model = null'); |
expect((_.rootElement as dom.InputElement).value).toEqual(''); |
- })); |
+ }); |
- it('should update model from the input value', inject(() { |
+ it('should update model from the input value', () { |
_.compile('<input type="search" ng-model="model" probe="p">'); |
Probe probe = _.rootScope.context['p']; |
var ngModel = probe.directive(NgModel); |
@@ -339,49 +446,74 @@ void main() { |
expect(_.rootScope.context['model']).toEqual('abc'); |
inputElement.value = 'def'; |
- var input = probe.directive(InputTextLikeDirective); |
+ var input = probe.directive(InputTextLike); |
input.processValue(); |
expect(_.rootScope.context['model']).toEqual('def'); |
- })); |
+ }); |
+ |
+ it('should write to input only if value is different', |
+ (Injector i, Animate animate) { |
- it('should write to input only if value is different', inject((Injector i, AstParser parser) { |
var scope = _.rootScope; |
var element = new dom.InputElement(); |
+ var ngElement = new NgElement(element, scope, animate); |
+ |
NodeAttrs nodeAttrs = new NodeAttrs(new DivElement()); |
nodeAttrs['ng-model'] = 'model'; |
- var model = new NgModel(scope, element, i.createChild([new Module()]), new NgNullForm(), parser, nodeAttrs); |
+ var model = new NgModel(scope, ngElement, i.createChild([new Module()]), |
+ nodeAttrs, new Animate()); |
dom.querySelector('body').append(element); |
- var input = new InputTextLikeDirective(element, model, scope); |
+ var input = new InputTextLike(element, model, scope); |
element |
..value = 'abc' |
..selectionStart = 1 |
..selectionEnd = 2; |
- model.render('abc'); |
+ scope.apply(() { |
+ scope.context['model'] = 'abc'; |
+ }); |
expect(element.value).toEqual('abc'); |
// No update. selectionStart/End is unchanged. |
expect(element.selectionStart).toEqual(1); |
expect(element.selectionEnd).toEqual(2); |
- model.render('xyz'); |
+ scope.apply(() { |
+ scope.context['model'] = 'xyz'; |
+ }); |
// Value updated. selectionStart/End changed. |
expect(element.value).toEqual('xyz'); |
expect(element.selectionStart).toEqual(3); |
expect(element.selectionEnd).toEqual(3); |
- })); |
+ }); |
+ |
+ it('should only render the input value upon the next digest', (Scope scope) { |
+ _.compile('<input type="search" ng-model="model" probe="p">'); |
+ Probe probe = _.rootScope.context['p']; |
+ var ngModel = probe.directive(NgModel); |
+ InputElement inputElement = probe.element; |
+ |
+ ngModel.render('xyz'); |
+ scope.context['model'] = 'xyz'; |
+ |
+ expect(inputElement.value).not.toEqual('xyz'); |
+ |
+ scope.apply(); |
+ |
+ expect(inputElement.value).toEqual('xyz'); |
+ }); |
}); |
describe('no type attribute', () { |
- it('should be set "text" as default value for "type" attribute', inject(() { |
+ it('should be set "text" as default value for "type" attribute', () { |
_.compile('<input ng-model="model">'); |
_.rootScope.apply(); |
expect((_.rootElement as dom.InputElement).attributes['type']).toEqual('text'); |
- })); |
+ }); |
- it('should update input value from model', inject(() { |
+ it('should update input value from model', () { |
_.compile('<input ng-model="model">'); |
_.rootScope.apply(); |
@@ -389,9 +521,9 @@ void main() { |
_.rootScope.apply('model = "misko"'); |
expect((_.rootElement as dom.InputElement).value).toEqual('misko'); |
- })); |
+ }); |
- it('should render null as the empty string', inject(() { |
+ it('should render null as the empty string', () { |
_.compile('<input ng-model="model">'); |
_.rootScope.apply(); |
@@ -399,9 +531,9 @@ void main() { |
_.rootScope.apply('model = null'); |
expect((_.rootElement as dom.InputElement).value).toEqual(''); |
- })); |
+ }); |
- it('should update model from the input value', inject(() { |
+ it('should update model from the input value', () { |
_.compile('<input ng-model="model" probe="p">'); |
Probe probe = _.rootScope.context['p']; |
var ngModel = probe.directive(NgModel); |
@@ -412,41 +544,66 @@ void main() { |
expect(_.rootScope.context['model']).toEqual('abc'); |
inputElement.value = 'def'; |
- var input = probe.directive(InputTextLikeDirective); |
+ var input = probe.directive(InputTextLike); |
input.processValue(); |
expect(_.rootScope.context['model']).toEqual('def'); |
- })); |
+ }); |
+ |
+ it('should write to input only if value is different', |
+ (Injector i, Animate animate) { |
- it('should write to input only if value is different', inject((Injector i, AstParser parser) { |
var scope = _.rootScope; |
var element = new dom.InputElement(); |
+ var ngElement = new NgElement(element, scope, animate); |
+ |
NodeAttrs nodeAttrs = new NodeAttrs(new DivElement()); |
nodeAttrs['ng-model'] = 'model'; |
- var model = new NgModel(scope, element, i.createChild([new Module()]), new NgNullForm(), parser, nodeAttrs); |
+ var model = new NgModel(scope, ngElement, i.createChild([new Module()]), |
+ nodeAttrs, new Animate()); |
dom.querySelector('body').append(element); |
- var input = new InputTextLikeDirective(element, model, scope); |
+ var input = new InputTextLike(element, model, scope); |
element |
..value = 'abc' |
..selectionStart = 1 |
..selectionEnd = 2; |
- model.render('abc'); |
+ scope.apply(() { |
+ scope.context['model'] = 'abc'; |
+ }); |
expect(element.value).toEqual('abc'); |
expect(element.selectionStart).toEqual(1); |
expect(element.selectionEnd).toEqual(2); |
- model.render('xyz'); |
+ scope.apply(() { |
+ scope.context['model'] = 'xyz'; |
+ }); |
expect(element.value).toEqual('xyz'); |
expect(element.selectionStart).toEqual(3); |
expect(element.selectionEnd).toEqual(3); |
- })); |
+ }); |
+ |
+ it('should only render the input value upon the next digest', (Scope scope) { |
+ _.compile('<input ng-model="model" probe="p">'); |
+ Probe probe = _.rootScope.context['p']; |
+ var ngModel = probe.directive(NgModel); |
+ InputElement inputElement = probe.element; |
+ |
+ ngModel.render('xyz'); |
+ scope.context['model'] = 'xyz'; |
+ |
+ expect(inputElement.value).not.toEqual('xyz'); |
+ |
+ scope.apply(); |
+ |
+ expect(inputElement.value).toEqual('xyz'); |
+ }); |
}); |
describe('type="checkbox"', () { |
- it('should update input value from model', inject((Scope scope) { |
+ it('should update input value from model', (Scope scope) { |
var element = _.compile('<input type="checkbox" ng-model="model">'); |
scope.apply(() { |
@@ -458,9 +615,9 @@ void main() { |
scope.context['model'] = false; |
}); |
expect(element.checked).toBe(false); |
- })); |
+ }); |
- it('should render as dirty when checked', inject((Scope scope) { |
+ it('should render as dirty when checked', (Scope scope) { |
var element = _.compile('<input type="text" ng-model="my_model" probe="i" />'); |
Probe probe = _.rootScope.context['i']; |
var model = probe.directive(NgModel); |
@@ -472,10 +629,9 @@ void main() { |
expect(model.pristine).toEqual(false); |
expect(model.dirty).toEqual(true); |
- })); |
- |
+ }); |
- it('should update input value from model using ng-true-value/false', inject((Scope scope) { |
+ it('should update input value from model using ng-true-value/false', (Scope scope) { |
var element = _.compile('<input type="checkbox" ng-model="model" ng-true-value="1" ng-false-value="0">'); |
scope.apply(() { |
@@ -495,10 +651,9 @@ void main() { |
element.checked = false; |
_.triggerEvent(element, 'change'); |
expect(scope.context['model']).toBe(0); |
- })); |
- |
+ }); |
- it('should allow non boolean values like null, 0, 1', inject((Scope scope) { |
+ it('should allow non boolean values like null, 0, 1', (Scope scope) { |
var element = _.compile('<input type="checkbox" ng-model="model">'); |
scope.apply(() { |
@@ -515,10 +670,9 @@ void main() { |
scope.context['model'] = null; |
}); |
expect(element.checked).toBe(false); |
- })); |
- |
+ }); |
- it('should update model from the input value', inject((Scope scope) { |
+ it('should update model from the input value', (Scope scope) { |
var element = _.compile('<input type="checkbox" ng-model="model">'); |
element.checked = true; |
@@ -528,11 +682,44 @@ void main() { |
element.checked = false; |
_.triggerEvent(element, 'change'); |
expect(scope.context['model']).toBe(false); |
- })); |
+ }); |
+ |
+ it('should update model from the input using ng-true-value/false', (Scope scope) { |
+ var element = _.compile('<input type="checkbox" ng-model="model" ' |
+ 'ng-true-value="yes" ng-false-value="no">'); |
+ scope.apply(() { |
+ scope.context['yes'] = 'yes sir!'; |
+ scope.context['no'] = 'no, sorry'; |
+ }); |
+ |
+ element.checked = true; |
+ _.triggerEvent(element, 'change'); |
+ expect(scope.context['model']).toEqual('yes sir!'); |
+ |
+ element.checked = false; |
+ _.triggerEvent(element, 'change'); |
+ expect(scope.context['model']).toEqual('no, sorry'); |
+ }); |
+ |
+ it('should only render the input value upon the next digest', (Scope scope) { |
+ _.compile('<input type="checkbox" ng-model="model" probe="p">'); |
+ Probe probe = _.rootScope.context['p']; |
+ var ngModel = probe.directive(NgModel); |
+ InputElement inputElement = probe.element; |
+ |
+ ngModel.render('xyz'); |
+ scope.context['model'] = true; |
+ |
+ expect(inputElement.checked).toBe(false); |
+ |
+ scope.apply(); |
+ |
+ expect(inputElement.checked).toBe(true); |
+ }); |
}); |
describe('textarea', () { |
- it('should update textarea value from model', inject(() { |
+ it('should update textarea value from model', () { |
_.compile('<textarea ng-model="model">'); |
_.rootScope.apply(); |
@@ -540,9 +727,9 @@ void main() { |
_.rootScope.apply('model = "misko"'); |
expect((_.rootElement as dom.TextAreaElement).value).toEqual('misko'); |
- })); |
+ }); |
- it('should render null as the empty string', inject(() { |
+ it('should render null as the empty string', () { |
_.compile('<textarea ng-model="model">'); |
_.rootScope.apply(); |
@@ -550,9 +737,9 @@ void main() { |
_.rootScope.apply('model = null'); |
expect((_.rootElement as dom.TextAreaElement).value).toEqual(''); |
- })); |
+ }); |
- it('should update model from the input value', inject(() { |
+ it('should update model from the input value', () { |
_.compile('<textarea ng-model="model" probe="p">'); |
Probe probe = _.rootScope.context['p']; |
var ngModel = probe.directive(NgModel); |
@@ -563,22 +750,27 @@ void main() { |
expect(_.rootScope.context['model']).toEqual('abc'); |
element.value = 'def'; |
- var textarea = probe.directive(InputTextLikeDirective); |
+ var textarea = probe.directive(InputTextLike); |
textarea.processValue(); |
expect(_.rootScope.context['model']).toEqual('def'); |
- })); |
+ }); |
// NOTE(deboer): This test passes on Dartium, but fails in the content_shell. |
// The Dart team is looking into this bug. |
- xit('should write to input only if value is different', inject((Injector i, AstParser parser) { |
+ xit('should write to input only if value is different', |
+ (Injector i, Animate animate) { |
+ |
var scope = _.rootScope; |
var element = new dom.TextAreaElement(); |
+ var ngElement = new NgElement(element, scope, animate); |
+ |
NodeAttrs nodeAttrs = new NodeAttrs(new DivElement()); |
nodeAttrs['ng-model'] = 'model'; |
- var model = new NgModel(scope, element, i.createChild([new Module()]), new NgNullForm(), parser, nodeAttrs); |
+ var model = new NgModel(scope, ngElement, i.createChild([new Module()]), |
+ nodeAttrs, new Animate()); |
dom.querySelector('body').append(element); |
- var input = new InputTextLikeDirective(element, model, scope); |
+ var input = new InputTextLike(element, model, scope); |
element |
..value = 'abc' |
@@ -598,11 +790,27 @@ void main() { |
expect(element.value).toEqual('xyz'); |
expect(element.selectionStart).toEqual(0); |
expect(element.selectionEnd).toEqual(0); |
- })); |
+ }); |
+ |
+ it('should only render the input value upon the next digest', (Scope scope) { |
+ _.compile('<textarea ng-model="model" probe="p"></textarea>'); |
+ Probe probe = _.rootScope.context['p']; |
+ var ngModel = probe.directive(NgModel); |
+ TextAreaElement inputElement = probe.element; |
+ |
+ ngModel.render('xyz'); |
+ scope.context['model'] = 'xyz'; |
+ |
+ expect(inputElement.value).not.toEqual('xyz'); |
+ |
+ scope.apply(); |
+ |
+ expect(inputElement.value).toEqual('xyz'); |
+ }); |
}); |
describe('type="radio"', () { |
- it('should update input value from model', inject(() { |
+ it('should update input value from model', () { |
_.compile('<input type="radio" name="color" value="red" ng-model="color" probe="r">' + |
'<input type="radio" name="color" value="green" ng-model="color" probe="g">' + |
'<input type="radio" name="color" value="blue" ng-model="color" probe="b">'); |
@@ -643,7 +851,7 @@ void main() { |
expect(redBtn.checked).toBe(false); |
expect(greenBtn.checked).toBe(true); |
expect(blueBtn.checked).toBe(false); |
- })); |
+ }); |
it('should support ng-value', () { |
_.compile('<input type="radio" name="color" ng-value="red" ng-model="color" probe="r">' + |
@@ -699,7 +907,7 @@ void main() { |
expect(blueBtn.checked).toBe(false); |
}); |
- it('should render as dirty when checked', inject((Scope scope) { |
+ it('should render as dirty when checked', (Scope scope) { |
var element = _.compile( |
'<div>' + |
' <input type="radio" id="on" ng-model="my_model" probe="i" value="on" />' + |
@@ -713,6 +921,8 @@ void main() { |
var input1 = element.querySelector("#on"); |
var input2 = element.querySelector("#off"); |
+ scope.apply(); |
+ |
expect(model.pristine).toEqual(true); |
expect(model.dirty).toEqual(false); |
@@ -723,6 +933,7 @@ void main() { |
input1.checked = true; |
_.triggerEvent(input1, 'click'); |
+ scope.apply(); |
expect(model.pristine).toEqual(false); |
expect(model.dirty).toEqual(true); |
@@ -730,16 +941,45 @@ void main() { |
input1.checked = false; |
input2.checked = true; |
_.triggerEvent(input2, 'click'); |
+ scope.apply(); |
expect(input1.classes.contains("ng-dirty")).toBe(true); |
expect(input2.classes.contains("ng-dirty")).toBe(true); |
expect(input1.classes.contains("ng-pristine")).toBe(false); |
expect(input1.classes.contains("ng-pristine")).toBe(false); |
- })); |
+ }); |
+ |
+ it('should only render the input value upon the next digest', (Scope scope) { |
+ var element = _.compile( |
+ '<div>' + |
+ ' <input type="radio" id="on" ng-model="model" probe="i" value="on" />' + |
+ ' <input type="radio" id="off" ng-model="model" probe="j" value="off" />' + |
+ '</div>' |
+ ); |
+ |
+ Probe probe1 = _.rootScope.context['i']; |
+ var ngModel1 = probe1.directive(NgModel); |
+ InputElement inputElement1 = probe1.element; |
+ |
+ Probe probe2 = _.rootScope.context['j']; |
+ var ngModel2 = probe2.directive(NgModel); |
+ InputElement inputElement2 = probe2.element; |
+ |
+ ngModel1.render('on'); |
+ scope.context['model'] = 'on'; |
+ |
+ expect(inputElement1.checked).toBe(false); |
+ expect(inputElement2.checked).toBe(false); |
+ |
+ scope.apply(); |
+ |
+ expect(inputElement1.checked).toBe(true); |
+ expect(inputElement2.checked).toBe(false); |
+ }); |
}); |
describe('type="search"', () { |
- it('should update input value from model', inject(() { |
+ it('should update input value from model', () { |
_.compile('<input type="search" ng-model="model">'); |
_.rootScope.apply(); |
@@ -747,9 +987,9 @@ void main() { |
_.rootScope.apply('model = "matias"'); |
expect((_.rootElement as dom.InputElement).value).toEqual('matias'); |
- })); |
+ }); |
- it('should render null as the empty string', inject(() { |
+ it('should render null as the empty string', () { |
_.compile('<input type="search" ng-model="model">'); |
_.rootScope.apply(); |
@@ -757,9 +997,9 @@ void main() { |
_.rootScope.apply('model = null'); |
expect((_.rootElement as dom.InputElement).value).toEqual(''); |
- })); |
+ }); |
- it('should update model from the input value', inject(() { |
+ it('should update model from the input value', () { |
_.compile('<input type="search" ng-model="model" probe="p">'); |
Probe probe = _.rootScope.context['p']; |
var ngModel = probe.directive(NgModel); |
@@ -770,14 +1010,30 @@ void main() { |
expect(_.rootScope.context['model']).toEqual('xzy'); |
inputElement.value = '123'; |
- var input = probe.directive(InputTextLikeDirective); |
+ var input = probe.directive(InputTextLike); |
input.processValue(); |
expect(_.rootScope.context['model']).toEqual('123'); |
- })); |
+ }); |
+ |
+ it('should only render the input value upon the next digest', (Scope scope) { |
+ _.compile('<input type="search" ng-model="model" probe="p">'); |
+ Probe probe = _.rootScope.context['p']; |
+ var ngModel = probe.directive(NgModel); |
+ InputElement inputElement = probe.element; |
+ |
+ ngModel.render('xyz'); |
+ scope.context['model'] = 'xyz'; |
+ |
+ expect(inputElement.value).not.toEqual('xyz'); |
+ |
+ scope.apply(); |
+ |
+ expect(inputElement.value).toEqual('xyz'); |
+ }); |
}); |
describe('contenteditable', () { |
- it('should update content from model', inject(() { |
+ it('should update content from model', () { |
_.compile('<p contenteditable ng-model="model">'); |
_.rootScope.apply(); |
@@ -785,9 +1041,9 @@ void main() { |
_.rootScope.apply('model = "misko"'); |
expect(_.rootElement.text).toEqual('misko'); |
- })); |
+ }); |
- it('should update model from the input value', inject(() { |
+ it('should update model from the input value', () { |
_.compile('<p contenteditable ng-model="model">'); |
Element element = _.rootElement; |
@@ -796,42 +1052,65 @@ void main() { |
expect(_.rootScope.context['model']).toEqual('abc'); |
element.innerHtml = 'def'; |
- var input = ngInjector(element).get(ContentEditableDirective); |
+ var input = ngInjector(element).get(ContentEditable); |
input.processValue(); |
expect(_.rootScope.context['model']).toEqual('def'); |
- })); |
+ }); |
+ |
+ it('should only render the input value upon the next digest', (Scope scope) { |
+ _.compile('<div contenteditable ng-model="model" probe="p"></div>'); |
+ Probe probe = _.rootScope.context['p']; |
+ var ngModel = probe.directive(NgModel); |
+ Element element = probe.element; |
+ |
+ ngModel.render('xyz'); |
+ scope.context['model'] = 'xyz'; |
+ |
+ expect(element.innerHtml).not.toEqual('xyz'); |
+ |
+ scope.apply(); |
+ |
+ expect(element.innerHtml).toEqual('xyz'); |
+ }); |
}); |
describe('pristine / dirty', () { |
- it('should be set to pristine by default', inject((Scope scope) { |
+ it('should be set to pristine by default', (Scope scope) { |
_.compile('<input type="text" ng-model="my_model" probe="i" />'); |
Probe probe = _.rootScope.context['i']; |
var model = probe.directive(NgModel); |
expect(model.pristine).toEqual(true); |
expect(model.dirty).toEqual(false); |
- })); |
+ }); |
- it('should add and remove the correct CSS classes when set to dirty and to pristine', inject((Scope scope) { |
+ it('should add and remove the correct CSS classes when set to dirty and to pristine', (Scope scope) { |
_.compile('<input type="text" ng-model="my_model" probe="i" />'); |
Probe probe = _.rootScope.context['i']; |
NgModel model = probe.directive(NgModel); |
InputElement element = probe.element; |
- model.dirty = true; |
+ model.addInfo('ng-dirty'); |
+ scope.apply(); |
+ |
expect(model.pristine).toEqual(false); |
expect(model.dirty).toEqual(true); |
- expect(element.classes.contains('ng-pristine')).toBe(false); |
- expect(element.classes.contains('ng-dirty')).toBe(true); |
+ expect(element).not.toHaveClass('ng-pristine'); |
+ expect(element).toHaveClass('ng-dirty'); |
+ |
+ model.removeInfo('ng-dirty'); |
+ scope.apply(); |
- model.pristine = true; |
expect(model.pristine).toEqual(true); |
expect(model.dirty).toEqual(false); |
- expect(element.classes.contains('ng-pristine')).toBe(true); |
- expect(element.classes.contains('ng-dirty')).toBe(false); |
- })); |
+ expect(element).toHaveClass('ng-pristine'); |
+ expect(element).not.toHaveClass('ng-dirty'); |
+ }); |
+ |
+ // TODO(matias): figure out why the 2nd apply is optional |
+ it('should render the parent form/fieldset as dirty but not the other models', |
+ (Scope scope) { |
- it('should render the parent form/fieldset as dirty but not the other models', inject((Scope scope) { |
_.compile('<form name="myForm">' + |
' <fieldset name="myFieldset">' + |
' <input type="text" ng-model="my_model1" probe="myModel1" />' + |
@@ -839,42 +1118,45 @@ void main() { |
' </fieldset>' + |
'</form>'); |
+ var formElement = _.rootScope.context['myForm'].element.node; |
+ var fieldsetElement = _.rootScope.context['myFieldset'].element.node; |
var inputElement1 = _.rootScope.context['myModel1'].element; |
var inputElement2 = _.rootScope.context['myModel2'].element; |
- var formElement = _.rootScope.context['myForm'].element; |
- var fieldsetElement = _.rootScope.context['myFieldset'].element; |
- expect(formElement.classes.contains('ng-pristine')).toBe(true); |
- expect(formElement.classes.contains('ng-dirty')).toBe(false); |
+ scope.apply(); |
- expect(fieldsetElement.classes.contains('ng-pristine')).toBe(true); |
- expect(fieldsetElement.classes.contains('ng-dirty')).toBe(false); |
+ expect(formElement).toHaveClass('ng-pristine'); |
+ expect(formElement).not.toHaveClass('ng-dirty'); |
- expect(inputElement1.classes.contains('ng-pristine')).toBe(true); |
- expect(inputElement1.classes.contains('ng-dirty')).toBe(false); |
+ expect(fieldsetElement).toHaveClass('ng-pristine'); |
+ expect(fieldsetElement).not.toHaveClass('ng-dirty'); |
- expect(inputElement2.classes.contains('ng-pristine')).toBe(true); |
- expect(inputElement2.classes.contains('ng-dirty')).toBe(false); |
+ expect(inputElement1).toHaveClass('ng-pristine'); |
+ expect(inputElement1).not.toHaveClass('ng-dirty'); |
+ |
+ expect(inputElement2).toHaveClass('ng-pristine'); |
+ expect(inputElement2).not.toHaveClass('ng-dirty'); |
inputElement1.value = '...hi...'; |
_.triggerEvent(inputElement1, 'change'); |
+ scope.apply(); |
- expect(formElement.classes.contains('ng-pristine')).toBe(false); |
- expect(formElement.classes.contains('ng-dirty')).toBe(true); |
+ expect(formElement).not.toHaveClass('ng-pristine'); |
+ expect(formElement).toHaveClass('ng-dirty'); |
- expect(fieldsetElement.classes.contains('ng-pristine')).toBe(false); |
- expect(fieldsetElement.classes.contains('ng-dirty')).toBe(true); |
+ expect(fieldsetElement).not.toHaveClass('ng-pristine'); |
+ expect(fieldsetElement).toHaveClass('ng-dirty'); |
- expect(inputElement1.classes.contains('ng-pristine')).toBe(false); |
- expect(inputElement1.classes.contains('ng-dirty')).toBe(true); |
+ expect(inputElement1).not.toHaveClass('ng-pristine'); |
+ expect(inputElement1).toHaveClass('ng-dirty'); |
- expect(inputElement2.classes.contains('ng-pristine')).toBe(true); |
- expect(inputElement2.classes.contains('ng-dirty')).toBe(false); |
- })); |
+ expect(inputElement2).toHaveClass('ng-pristine'); |
+ expect(inputElement2).not.toHaveClass('ng-dirty'); |
+ }); |
}); |
describe('validation', () { |
- it('should happen automatically when the scope changes', inject((Scope scope) { |
+ it('should happen automatically when the scope changes', (Scope scope) { |
_.compile('<input type="text" ng-model="model" probe="i" required>'); |
_.rootScope.apply(); |
@@ -888,14 +1170,15 @@ void main() { |
expect(model.invalid).toBe(false); |
expect(model.valid).toBe(true); |
- })); |
+ }); |
- it('should happen automatically upon user input via the onInput event', inject(() { |
+ it('should happen automatically upon user input via the onInput event', () { |
_.compile('<input type="text" ng-model="model" probe="i" required>'); |
+ _.rootScope.apply(); |
Probe probe = _.rootScope.context['i']; |
var model = probe.directive(NgModel); |
- InputElement inputElement = model.element; |
+ InputElement inputElement = model.element.node; |
expect(model.invalid).toBe(true); |
expect(model.valid).toBe(false); |
@@ -905,72 +1188,100 @@ void main() { |
expect(model.invalid).toBe(false); |
expect(model.valid).toBe(true); |
- })); |
+ }); |
}); |
describe('valid / invalid', () { |
- it('should add and remove the correct flags when set to valid and to invalid', inject((Scope scope) { |
+ it('should add and remove the correct flags when set to valid and to invalid', (Scope scope) { |
_.compile('<input type="text" ng-model="my_model" probe="i" />'); |
Probe probe = _.rootScope.context['i']; |
var model = probe.directive(NgModel); |
InputElement element = probe.element; |
- model.invalid = true; |
+ model.addError('ng-required'); |
+ model.validate(); |
+ scope.apply(); |
+ |
expect(model.valid).toEqual(false); |
expect(model.invalid).toEqual(true); |
- expect(element.classes.contains('ng-valid')).toBe(false); |
- expect(element.classes.contains('ng-invalid')).toBe(true); |
+ //expect(element).not.toHaveClass('ng-valid'); |
+ expect(element).toHaveClass('ng-invalid'); |
+ |
+ model.removeError('ng-required'); |
+ model.validate(); |
+ scope.apply(); |
- model.valid = true; |
expect(model.valid).toEqual(true); |
expect(model.invalid).toEqual(false); |
- expect(element.classes.contains('ng-invalid')).toBe(false); |
- expect(element.classes.contains('ng-valid')).toBe(true); |
- })); |
+ expect(element).not.toHaveClass('ng-invalid'); |
+ // expect(element).toHaveClass('ng-valid'); |
+ }); |
- it('should set the validity with respect to all existing validations when setValidity() is used', inject((Scope scope) { |
+ it('should set the validity with respect to all existing validations when setValidity() is used', (Scope scope) { |
_.compile('<input type="text" ng-model="my_model" probe="i" />'); |
Probe probe = _.rootScope.context['i']; |
var model = probe.directive(NgModel); |
- model.setValidity("required", false); |
+ model.addError("required"); |
expect(model.valid).toEqual(false); |
expect(model.invalid).toEqual(true); |
- model.setValidity("format", false); |
+ model.addError("format"); |
expect(model.valid).toEqual(false); |
expect(model.invalid).toEqual(true); |
- model.setValidity("format", true); |
+ model.removeError("format"); |
expect(model.valid).toEqual(false); |
expect(model.invalid).toEqual(true); |
- model.setValidity("required", true); |
+ model.removeError("required"); |
expect(model.valid).toEqual(true); |
expect(model.invalid).toEqual(false); |
- })); |
+ }); |
- it('should register each error only once when invalid', inject((Scope scope) { |
+ it('should register each error only once when invalid', (Scope scope) { |
_.compile('<input type="text" ng-model="my_model" probe="i" />'); |
Probe probe = _.rootScope.context['i']; |
var model = probe.directive(NgModel); |
- model.setValidity("distinct-error", false); |
+ model.addError("distinct-error"); |
expect(model.valid).toEqual(false); |
expect(model.invalid).toEqual(true); |
- model.setValidity("distinct-error", false); |
+ model.addError("distinct-error"); |
expect(model.valid).toEqual(false); |
expect(model.invalid).toEqual(true); |
- model.setValidity("distinct-error", true); |
+ model.removeError("distinct-error"); |
expect(model.valid).toEqual(true); |
expect(model.invalid).toEqual(false); |
- })); |
+ }); |
+ }); |
+ |
+ describe('error handling', () { |
+ it('should return true or false depending on if an error exists on a form', |
+ (Scope scope, TestBed _) { |
+ |
+ _.compile('<input type="text" ng-model="input" name="input" probe="i" />'); |
+ scope.apply(); |
+ |
+ Probe p = scope.context['i']; |
+ NgModel model = p.directive(NgModel); |
+ |
+ expect(model.hasErrorState('big-failure')).toBe(false); |
+ |
+ model.addError("big-failure"); |
+ |
+ expect(model.hasErrorState('big-failure')).toBe(true); |
+ |
+ model.removeError("big-failure"); |
+ |
+ expect(model.hasErrorState('big-failure')).toBe(false); |
+ }); |
}); |
describe('text-like events', () { |
- it('should update the binding on the "input" event', inject(() { |
+ it('should update the binding on the "input" event', () { |
_.compile('<input type="text" ng-model="model" probe="p">'); |
Probe probe = _.rootScope.context['p']; |
InputElement inputElement = probe.element; |
@@ -982,7 +1293,7 @@ void main() { |
_.triggerEvent(inputElement, 'input'); |
expect(_.rootScope.context['model']).toEqual('waaaah'); |
- })); |
+ }); |
}); |
describe('error messages', () { |
@@ -1043,12 +1354,264 @@ void main() { |
expect(model.touched).toBe(false); |
expect(model.untouched).toBe(true); |
}); |
+ |
+ describe('validators', () { |
+ it('should display the valid and invalid CSS classes on the element for each validation', |
+ (TestBed _, Scope scope) { |
+ |
+ var input = _.compile('<input type="email" ng-model="myModel" />'); |
+ |
+ scope.apply(() { |
+ scope.context['myModel'] = 'value'; |
+ }); |
+ |
+ expect(input).toHaveClass('ng-email-invalid'); |
+ expect(input).not.toHaveClass('ng-email-valid'); |
+ |
+ scope.apply(() { |
+ scope.context['myModel'] = 'value@email.com'; |
+ }); |
+ |
+ expect(input).toHaveClass('ng-email-valid'); |
+ expect(input).not.toHaveClass('ng-email-invalid'); |
+ }); |
+ |
+ it('should display the valid and invalid CSS classes on the element for custom validations', |
+ (TestBed _, Scope scope) { |
+ |
+ var input = _.compile('<input type="text" ng-model="myModel" custom-input-validation />'); |
+ |
+ scope.apply(); |
+ |
+ expect(input).toHaveClass('custom-invalid'); |
+ expect(input).not.toHaveClass('custom-valid'); |
+ |
+ scope.apply(() { |
+ scope.context['myModel'] = 'yes'; |
+ }); |
+ |
+ expect(input).toHaveClass('custom-valid'); |
+ expect(input).not.toHaveClass('custom-invalid'); |
+ }); |
+ |
+ it('should only validate twice during compilation and once upon scope digest', |
+ (TestBed _, Scope scope) { |
+ |
+ scope.context['required'] = true; |
+ _.compile('<input type="text" ' |
+ 'ng-model="model" ' |
+ 'ng-required="required" ' |
+ 'ng-pattern="pattern" ' |
+ 'counting-validator ' |
+ 'probe="i">'); |
+ |
+ scope.context['pattern'] = '^[aeiou]+\$'; |
+ scope.context['required'] = true; |
+ |
+ scope.apply(); |
+ |
+ var model = scope.context['i'].directive(NgModel); |
+ var counter = model.validators.firstWhere((validator) => validator.name == 'counting'); |
+ |
+ // TODO(#881): There is a bug in ngModel where the validators are validated too often. |
+ // Should be 2. One for ngModel and one for all the other ones |
+ // Currently, this count is 2 on Chrome and 3 on Firefox. |
+ expect(counter.count == 2 || counter.count == 3).toBe(true); |
+ expect(model.invalid).toBe(true); |
+ |
+ counter.count = 0; |
+ scope.context['pattern'] = ''; |
+ scope.context['required'] = false; |
+ scope.apply(); |
+ |
+ expect(counter.count).toBe(1); |
+ }); |
+ |
+ it('should only validate twice regardless of attribute order', (TestBed _, Scope scope) { |
+ scope.context['required'] = true; |
+ _.compile('<input type="text" ' |
+ 'ng-required="required" ' |
+ 'ng-pattern="pattern" ' |
+ 'counting-validator ' |
+ 'ng-model="model" ' |
+ 'probe="i">'); |
+ |
+ scope.context['pattern'] = '^[aeiou]+\$'; |
+ scope.context['required'] = true; |
+ |
+ scope.apply(); |
+ |
+ var model = scope.context['i'].directive(NgModel); |
+ |
+ var counter = model.validators.firstWhere((validator) => validator.name == 'counting'); |
+ |
+ // TODO(#881): There is a bug in ngModel where the validators are validated too often. |
+ // Should be 2. One for ngModel and one for all the other ones |
+ // Currently, this count is 3 on Chrome and 1 on Firefox. |
+ expect(counter.count == 2 || counter.count == 3).toBe(true); |
+ }); |
+ }); |
+ |
+ describe('converters', () { |
+ it('should parse the model value according to the given parser', (Scope scope) { |
+ _.compile('<input type="text" ng-model="model" probe="i">'); |
+ scope.apply(); |
+ |
+ var probe = scope.context['i']; |
+ var input = probe.element; |
+ var model = probe.directive(NgModel); |
+ model.converter = new LowercaseValueParser(); |
+ |
+ input.value = 'HELLO'; |
+ _.triggerEvent(input, 'change'); |
+ _.rootScope.apply(); |
+ |
+ expect(model.viewValue).toEqual('HELLO'); |
+ expect(model.modelValue).toEqual('hello'); |
+ }); |
+ |
+ it('should format the model value according to the given formatter', (Scope scope) { |
+ _.compile('<input type="text" ng-model="model" probe="i">'); |
+ scope.apply(); |
+ |
+ var probe = scope.context['i']; |
+ var input = probe.element; |
+ var model = probe.directive(NgModel); |
+ model.converter = new UppercaseValueFormatter(); |
+ |
+ scope.apply(() { |
+ scope.context['model'] = 'greetings'; |
+ }); |
+ |
+ expect(model.viewValue).toEqual('GREETINGS'); |
+ expect(model.modelValue).toEqual('greetings'); |
+ }); |
+ |
+ it('should retain the current input value if the parser fails', (Scope scope) { |
+ _.compile('<form name="myForm">' + |
+ ' <input type="text" ng-model="model1" name="myModel1" probe="i">' + |
+ ' <input type="text" ng-model="model2" name="myModel2" probe="j">' + |
+ '</form>'); |
+ scope.apply(); |
+ |
+ var probe1 = scope.context['i']; |
+ var input1 = probe1.element; |
+ var model1 = probe1.directive(NgModel); |
+ |
+ var probe2 = scope.context['j']; |
+ var input2 = probe2.element; |
+ var model2 = probe2.directive(NgModel); |
+ |
+ model1.converter = new FailedValueParser(); |
+ |
+ input1.value = '123'; |
+ _.triggerEvent(input1, 'change'); |
+ _.rootScope.apply(); |
+ |
+ expect(model1.viewValue).toEqual('123'); |
+ expect(input1.value).toEqual('123'); |
+ expect(model1.modelValue).toEqual(null); |
+ |
+ expect(model2.viewValue).toEqual(null); |
+ expect(input2.value).toEqual(''); |
+ expect(model2.modelValue).toEqual(null); |
+ }); |
+ |
+ it('should reformat the viewValue when the formatter is changed', (Scope scope) { |
+ _.compile('<input type="text" ng-model="model" probe="i">'); |
+ scope.apply(); |
+ |
+ var probe = scope.context['i']; |
+ var input = probe.element; |
+ var model = probe.directive(NgModel); |
+ model.converter = new LowercaseValueParser(); |
+ |
+ input.value = 'HI THERE'; |
+ _.triggerEvent(input, 'change'); |
+ _.rootScope.apply(); |
+ |
+ expect(model.viewValue).toEqual('HI THERE'); |
+ expect(model.modelValue).toEqual('hi there'); |
+ |
+ model.converter = new VowelValueParser(); |
+ |
+ expect(model.viewValue).toEqual('iee'); |
+ expect(model.modelValue).toEqual('hi there'); |
+ }); |
+ }); |
}); |
} |
-@NgController( |
+@Controller( |
selector: '[no-love]', |
publishAs: 'ctrl') |
class ControllerWithNoLove { |
var apathy = null; |
} |
+ |
+class LowercaseValueParser implements NgModelConverter { |
+ final name = 'lowercase'; |
+ format(value) => value; |
+ parse(value) { |
+ return value != null ? value.toLowerCase() : null; |
+ } |
+} |
+ |
+class UppercaseValueFormatter implements NgModelConverter { |
+ final name = 'uppercase'; |
+ parse(value) => value; |
+ format(value) { |
+ return value != null ? value.toUpperCase() : null; |
+ } |
+} |
+ |
+class FailedValueParser implements NgModelConverter { |
+ final name = 'failed'; |
+ format(value) => value; |
+ parse(value) { |
+ throw new Exception(); |
+ } |
+} |
+ |
+class VowelValueParser implements NgModelConverter { |
+ final name = 'vowel'; |
+ parse(value) => value; |
+ format(value) { |
+ if(value != null) { |
+ var exp = new RegExp("[^aeiouAEIOU]"); |
+ value = value.replaceAll(exp, ""); |
+ } |
+ return value; |
+ } |
+} |
+ |
+@Decorator( |
+ 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'; |
+ } |
+} |
+ |
+@Decorator( |
+ selector: '[counting-validator]') |
+class CountingValidator extends NgValidator { |
+ |
+ final String name = 'counting'; |
+ int count = 0; |
+ |
+ CountingValidator(NgModel ngModel) { |
+ ngModel.addValidator(this); |
+ } |
+ |
+ bool isValid(String modelValue) { |
+ count++; |
+ return true; |
+ } |
+} |