diff options
| -rw-r--r-- | lib/jasmine-jstd-adapter/JasmineAdapter.js | 12 | ||||
| -rw-r--r-- | scenario/widgets.html | 5 | ||||
| -rw-r--r-- | src/formatters.js | 23 | ||||
| -rw-r--r-- | src/widgets.js | 78 | ||||
| -rw-r--r-- | test/testabilityPatch.js | 25 | ||||
| -rw-r--r-- | test/widgetsSpec.js | 169 |
6 files changed, 208 insertions, 104 deletions
diff --git a/lib/jasmine-jstd-adapter/JasmineAdapter.js b/lib/jasmine-jstd-adapter/JasmineAdapter.js index ba54251a..0fdc4612 100644 --- a/lib/jasmine-jstd-adapter/JasmineAdapter.js +++ b/lib/jasmine-jstd-adapter/JasmineAdapter.js @@ -9,7 +9,7 @@ function bind(_this, _function){ return function(){ return _function.call(_this); - } + }; } var currentFrame = frame(null, null); @@ -49,14 +49,22 @@ })(jasmine.Env.prototype.describe); + var id = 0; jasmine.Env.prototype.it = (function(it){ return function(desc, itFn){ var self = this; var spec = it.apply(this, arguments); var currentSpec = this.currentSpec; + if (!currentSpec.$id) { + currentSpec.$id = id++; + } var frame = this.jstdFrame = currentFrame; - this.jstdFrame.testCase.prototype['test that it ' + desc] = function(){ + var name = 'test that it ' + desc; + if (this.jstdFrame.testCase.prototype[name]) + throw "Spec with name '" + desc + "' already exists."; + this.jstdFrame.testCase.prototype[name] = function(){ + jasmine.getEnv().currentSpec = currentSpec; frame.runBefore.apply(currentSpec); try { itFn.apply(currentSpec); diff --git a/scenario/widgets.html b/scenario/widgets.html index 61badf1c..242fd9e6 100644 --- a/scenario/widgets.html +++ b/scenario/widgets.html @@ -15,7 +15,10 @@ <tr><th colspan="3">Input text field</th></tr> <tr> <td>basic</td> - <td><input type="text" name="text.basic" ng-required ng-validate="number" ng-format="number"/></td> + <td> + <input type="text" name="text.basic" ng-required ng-validate="number" ng-format="number"/> + <input type="text" name="text.basic" ng-format="number"/> + </td> <td>text.basic={{text.basic}}</td> </tr> <tr> diff --git a/src/formatters.js b/src/formatters.js index ee63c1a5..40462cf3 100644 --- a/src/formatters.js +++ b/src/formatters.js @@ -1,11 +1,20 @@ -function formater(format, parse) {return {'format':format, 'parse':parse || format};} -function toString(obj) {return isDefined(obj) ? "" + obj : obj;} +function formatter(format, parse) {return {'format':format, 'parse':parse || format};} +function toString(obj) {return (isDefined(obj) && obj !== null) ? "" + obj : obj;} + +var NUMBER = /^\s*[-+]?\d*(\.\d*)?\s*$/; + extend(angularFormatter, { - 'noop':formater(identity, identity), - 'boolean':formater(toString, toBoolean), - 'number':formater(toString, function(obj){return 1*obj;}), + 'noop':formatter(identity, identity), + 'boolean':formatter(toString, toBoolean), + 'number':formatter(toString, + function(obj){ + if (isString(obj) && NUMBER.exec(obj)) { + return obj ? 1*obj : null; + } + throw "Not a number"; + }), - 'list':formater( + 'list':formatter( function(obj) { return obj ? obj.join(", ") : obj; }, function(value) { var list = []; @@ -17,7 +26,7 @@ extend(angularFormatter, { } ), - 'trim':formater( + 'trim':formatter( function(obj) { return obj ? trim("" + obj) : ""; } ) }); diff --git a/src/widgets.js b/src/widgets.js index 064b27fe..48898a9a 100644 --- a/src/widgets.js +++ b/src/widgets.js @@ -1,15 +1,14 @@ function modelAccessor(scope, element) { - var expr = element.attr('name'), - farmatterName = element.attr('ng-format') || NOOP, - formatter = angularFormatter(farmatterName); + var expr = element.attr('name'); if (!expr) throw "Required field 'name' not found."; - if (!formatter) throw "Formatter named '" + farmatterName + "' not found."; return { get: function() { - return formatter['format'](scope.$eval(expr)); + return scope.$eval(expr); }, set: function(value) { - scope.$tryEval(expr + '=' + toJson(formatter['parse'](value)), element); + if (value !== undefined) { + scope.$tryEval(expr + '=' + toJson(value), element); + } } }; } @@ -22,27 +21,49 @@ function valueAccessor(scope, element) { var validatorName = element.attr('ng-validate') || NOOP, validator = compileValidator(validatorName), required = element.attr('ng-required'), - lastError, + farmatterName = element.attr('ng-format') || NOOP, + formatter = angularFormatter(farmatterName), + format, parse, lastError; invalidWidgets = scope.$invalidWidgets || {markValid:noop, markInvalid:noop}; - required = required || required === ''; if (!validator) throw "Validator named '" + validatorName + "' not found."; - function validate(value) { - var force = false; - if (isUndefined(value)) { - value = element.val(); - force = true; + if (!formatter) throw "Formatter named '" + farmatterName + "' not found."; + format = formatter.format; + parse = formatter.parse; + required = required || required === ''; + + element.data('$validate', validate); + return { + get: function(){ + if (lastError) + elementError(element, NG_VALIDATION_ERROR, null); + try { + return parse(element.val()); + } catch (e) { + lastError = e; + elementError(element, NG_VALIDATION_ERROR, e); + } + }, + set: function(value) { + var oldValue = element.val(), + newValue = format(value); + if (oldValue != newValue) { + element.val(newValue); + } + validate(); } + }; + + function validate() { + var value = trim(element.val()); if (element[0].disabled || element[0].readOnly) { elementError(element, NG_VALIDATION_ERROR, null); invalidWidgets.markValid(element); - return value; - } - var error, - validateScope = extend(new (extend(function(){}, {prototype:scope}))(), {$element:element}); - error = required && !trim(value) ? - "Required" : - (trim(value) ? validator({state:validateScope, scope:{get:validateScope.$get, set:validateScope.$set}}, value) : null); - if (error !== lastError || force) { + } else { + var error, + validateScope = extend(new (extend(function(){}, {prototype:scope}))(), {$element:element}); + error = required && !value ? + "Required" : + (value ? validator({state:validateScope, scope:{get:validateScope.$get, set:validateScope.$set}}, value) : null); elementError(element, NG_VALIDATION_ERROR, error); lastError = error; if (error) { @@ -51,13 +72,7 @@ function valueAccessor(scope, element) { invalidWidgets.markValid(element); } } - return value; } - element.data('$validate', validate); - return { - get: function(){ return validate(element.val()); }, - set: function(value){ element.val(validate(value)); } - }; } function checkedAccessor(scope, element) { @@ -106,7 +121,7 @@ function optionsAccessor(scope, element) { function noopAccessor() { return { get: noop, set: noop }; } -var textWidget = inputWidget('keyup change', modelAccessor, valueAccessor, initWidgetValue('')), +var textWidget = inputWidget('keyup change', modelAccessor, valueAccessor, initWidgetValue()), buttonWidget = inputWidget('click', noopAccessor, noopAccessor, noop), INPUT_TYPE = { 'text': textWidget, @@ -126,9 +141,12 @@ var textWidget = inputWidget('keyup change', modelAccessor, valueAccessor, initW function initWidgetValue(initValue) { return function (model, view) { - var value = view.get() || copy(initValue); - if (isUndefined(model.get()) && isDefined(value)) + var value = view.get(); + if (!value && isDefined(initValue)) + value = copy(initValue); + if (isUndefined(model.get()) && isDefined(value)) { model.set(value); + } }; } diff --git a/test/testabilityPatch.js b/test/testabilityPatch.js index 4d129f60..d621b1f1 100644 --- a/test/testabilityPatch.js +++ b/test/testabilityPatch.js @@ -1,6 +1,31 @@ jstd = jstestdriver; dump = bind(jstd.console, jstd.console.log); +beforeEach(function(){ + this.addMatchers({ + toBeInvalid: function(){ + var element = jqLite(this.actual); + var hasClass = element.hasClass('ng-validation-error'); + var validationError = element.attr('ng-validation-error'); + this.message = function(){ + if (!hasClass) + return "Expected class 'ng-validation-error' not found."; + return "Expected an error message, but none was found."; + }; + return hasClass && validationError; + }, + + toBeValid: function(){ + var element = jqLite(this.actual); + var hasClass = element.hasClass('ng-validation-error'); + this.message = function(){ + return "Expected to not have class 'ng-validation-error' but found."; + }; + return !hasClass; + } + }); +}); + function nakedExpect(obj) { return expect(angular.fromJson(angular.toJson(obj))); } diff --git a/test/widgetsSpec.js b/test/widgetsSpec.js index c9665f1e..b365175d 100644 --- a/test/widgetsSpec.js +++ b/test/widgetsSpec.js @@ -20,74 +20,115 @@ describe("widget", function(){ describe("input", function(){ - it('should input-text auto init and handle keyup/change events', function(){ - compile('<input type="Text" name="name" value="Misko" ng-change="count = count + 1" ng-init="count=0"/>'); - expect(scope.$get('name')).toEqual("Misko"); - expect(scope.$get('count')).toEqual(0); - - scope.$set('name', 'Adam'); - scope.$eval(); - expect(element.val()).toEqual("Adam"); - - element.val('Shyam'); - element.trigger('keyup'); - expect(scope.$get('name')).toEqual('Shyam'); - expect(scope.$get('count')).toEqual(1); - - element.val('Kai'); - element.trigger('change'); - expect(scope.$get('name')).toEqual('Kai'); - expect(scope.$get('count')).toEqual(2); - }); - - it("should process ng-format", function(){ - compile('<input type="Text" name="list" value="a,b,c" ng-format="list"/>'); - expect(scope.$get('list')).toEqual(['a', 'b', 'c']); - - scope.$set('list', ['x', 'y', 'z']); - scope.$eval(); - expect(element.val()).toEqual("x, y, z"); - - element.val('1, 2, 3'); - element.trigger('keyup'); - expect(scope.$get('list')).toEqual(['1', '2', '3']); - }); - - it("should process ng-format for booleans", function(){ - compile('<input type="checkbox" name="name" value="true" ng-format="boolean"/>', function(){ - scope.name = false; + describe("text", function(){ + it('should input-text auto init and handle keyup/change events', function(){ + compile('<input type="Text" name="name" value="Misko" ng-change="count = count + 1" ng-init="count=0"/>'); + expect(scope.$get('name')).toEqual("Misko"); + expect(scope.$get('count')).toEqual(0); + + scope.$set('name', 'Adam'); + scope.$eval(); + expect(element.val()).toEqual("Adam"); + + element.val('Shyam'); + element.trigger('keyup'); + expect(scope.$get('name')).toEqual('Shyam'); + expect(scope.$get('count')).toEqual(1); + + element.val('Kai'); + element.trigger('change'); + expect(scope.$get('name')).toEqual('Kai'); + expect(scope.$get('count')).toEqual(2); }); - expect(scope.name).toEqual(false); - expect(scope.$element[0].checked).toEqual(false); - }); - it("should process ng-validate", function(){ - compile('<input type="text" name="price" value="abc" ng-validate="number"/>'); - expect(element.hasClass('ng-validation-error')).toBeTruthy(); - expect(element.attr('ng-validation-error')).toEqual('Not a number'); - - scope.$set('price', '123'); - scope.$eval(); - expect(element.hasClass('ng-validation-error')).toBeFalsy(); - expect(element.attr('ng-validation-error')).toBeFalsy(); + describe("ng-format", function(){ + + it("should farmat text", function(){ + compile('<input type="Text" name="list" value="a,b,c" ng-format="list"/>'); + expect(scope.$get('list')).toEqual(['a', 'b', 'c']); + + scope.$set('list', ['x', 'y', 'z']); + scope.$eval(); + expect(element.val()).toEqual("x, y, z"); + + element.val('1, 2, 3'); + element.trigger('keyup'); + expect(scope.$get('list')).toEqual(['1', '2', '3']); + }); + + it("should format booleans", function(){ + compile('<input type="checkbox" name="name" value="true" ng-format="boolean"/>', function(){ + scope.name = false; + }); + expect(scope.name).toEqual(false); + expect(scope.$element[0].checked).toEqual(false); + }); + + it("should come up blank if null", function(){ + compile('<input type="text" name="age" ng-format="number"/>', function(){ + scope.age = null; + }); + expect(scope.age).toBeNull(); + expect(scope.$element[0].value).toEqual(''); + }); + + it("should show incorect text while number does not parse", function(){ + compile('<input type="text" name="age" ng-format="number"/>'); + scope.age = 123; + scope.$eval(); + scope.$element.val('123X'); + scope.$element.trigger('change'); + expect(scope.$element.val()).toEqual('123X'); + expect(scope.age).toEqual(123); + expect(scope.$element).toBeInvalid(); + }); + + it("should clober incorect text if model changes", function(){ + compile('<input type="text" name="age" ng-format="number" value="123X"/>'); + scope.age = 456; + scope.$eval(); + expect(scope.$element.val()).toEqual('456'); + }); + + it("should come up blank when no value specifiend", function(){ + compile('<input type="text" name="age" ng-format="number"/>'); + scope.$eval(); + expect(scope.$element.val()).toEqual(''); + expect(scope.age).toEqual(null); + }); - element.val('x'); - element.trigger('keyup'); - expect(element.hasClass('ng-validation-error')).toBeTruthy(); - expect(element.attr('ng-validation-error')).toEqual('Not a number'); - }); - - it("should not call validator if undefinde/empty", function(){ - var lastValue = "NOT_CALLED"; - angularValidator.myValidator = function(value){lastValue = value;}; - compile('<input type="text" name="url" ng-validate="myValidator"/>'); - expect(lastValue).toEqual("NOT_CALLED"); - - scope.url = 'http://server'; - scope.$eval(); - expect(lastValue).toEqual("http://server"); + }); - delete angularValidator.myValidator; + describe("ng-validate", function(){ + it("should process ng-validate", function(){ + compile('<input type="text" name="price" value="abc" ng-validate="number"/>'); + expect(element.hasClass('ng-validation-error')).toBeTruthy(); + expect(element.attr('ng-validation-error')).toEqual('Not a number'); + + scope.$set('price', '123'); + scope.$eval(); + expect(element.hasClass('ng-validation-error')).toBeFalsy(); + expect(element.attr('ng-validation-error')).toBeFalsy(); + + element.val('x'); + element.trigger('keyup'); + expect(element.hasClass('ng-validation-error')).toBeTruthy(); + expect(element.attr('ng-validation-error')).toEqual('Not a number'); + }); + + it("should not call validator if undefinde/empty", function(){ + var lastValue = "NOT_CALLED"; + angularValidator.myValidator = function(value){lastValue = value;}; + compile('<input type="text" name="url" ng-validate="myValidator"/>'); + expect(lastValue).toEqual("NOT_CALLED"); + + scope.url = 'http://server'; + scope.$eval(); + expect(lastValue).toEqual("http://server"); + + delete angularValidator.myValidator; + }); + }); }); it("should ignore disabled widgets", function(){ |
