diff options
Diffstat (limited to 'test')
| -rw-r--r-- | test/BinderSpec.js | 16 | ||||
| -rw-r--r-- | test/ScopeSpec.js | 24 | ||||
| -rw-r--r-- | test/angular-mocks.js | 320 | ||||
| -rw-r--r-- | test/servicesSpec.js | 41 | ||||
| -rw-r--r-- | test/testabilityPatch.js | 50 | ||||
| -rw-r--r-- | test/widgetsSpec.js | 17 |
6 files changed, 116 insertions, 352 deletions
diff --git a/test/BinderSpec.js b/test/BinderSpec.js index f12c1a74..73650bd6 100644 --- a/test/BinderSpec.js +++ b/test/BinderSpec.js @@ -273,6 +273,7 @@ describe('Binder', function(){ it('IfTextBindingThrowsErrorDecorateTheSpan', function(){ var a = this.compile('<div>{{error.throw()}}</div>'); var doc = a.node; + var errorLogs = a.scope.$service('$log').error.logs; a.scope.$set('error.throw', function(){throw "ErrorMsg1";}); a.scope.$eval(); @@ -280,6 +281,7 @@ describe('Binder', function(){ assertTrue(span.hasClass('ng-exception')); assertTrue(!!span.text().match(/ErrorMsg1/)); assertTrue(!!span.attr('ng-exception').match(/ErrorMsg1/)); + assertEquals(['ErrorMsg1'], errorLogs.shift()); a.scope.$set('error.throw', function(){throw "MyError";}); a.scope.$eval(); @@ -287,30 +289,34 @@ describe('Binder', function(){ assertTrue(span.hasClass('ng-exception')); assertTrue(span.text(), span.text().match('MyError') !== null); assertEquals('MyError', span.attr('ng-exception')); + assertEquals(['MyError'], errorLogs.shift()); a.scope.$set('error.throw', function(){return "ok";}); a.scope.$eval(); assertFalse(span.hasClass('ng-exception')); assertEquals('ok', span.text()); assertEquals(null, span.attr('ng-exception')); + assertEquals(0, errorLogs.length); }); it('IfAttrBindingThrowsErrorDecorateTheAttribute', function(){ var a = this.compile('<div attr="before {{error.throw()}} after"></div>'); var doc = a.node; + var errorLogs = a.scope.$service('$log').error.logs; a.scope.$set('error.throw', function(){throw "ErrorMsg";}); a.scope.$eval(); assertTrue('ng-exception', doc.hasClass('ng-exception')); assertEquals('"ErrorMsg"', doc.attr('ng-exception')); assertEquals('before "ErrorMsg" after', doc.attr('attr')); + assertEquals(['ErrorMsg'], errorLogs.shift()); a.scope.$set('error.throw', function(){ return 'X';}); a.scope.$eval(); assertFalse('!ng-exception', doc.hasClass('ng-exception')); assertEquals('before X after', doc.attr('attr')); assertEquals(null, doc.attr('ng-exception')); - + assertEquals(0, errorLogs.length); }); it('NestedRepeater', function(){ @@ -447,6 +453,7 @@ describe('Binder', function(){ var error = input.attr('ng-exception'); assertTrue(!!error.match(/MyError/)); assertTrue("should have an error class", input.hasClass('ng-exception')); + assertTrue(!!c.scope.$service('$log').error.logs.shift()[0].message.match(/MyError/)); // TODO: I think that exception should never get cleared so this portion of test makes no sense //c.scope.action = noop; @@ -491,7 +498,7 @@ describe('Binder', function(){ it('FillInOptionValueWhenMissing', function(){ var c = this.compile( - '<select><option selected="true">{{a}}</option><option value="">{{b}}</option><option>C</option></select>'); + '<select name="foo"><option selected="true">{{a}}</option><option value="">{{b}}</option><option>C</option></select>'); c.scope.$set('a', 'A'); c.scope.$set('b', 'B'); c.scope.$eval(); @@ -569,18 +576,21 @@ describe('Binder', function(){ assertChild(5, false); }); - it('ItShouldDisplayErrorWhenActionIsSyntacticlyIncorect', function(){ + it('ItShouldDisplayErrorWhenActionIsSyntacticlyIncorrect', function(){ var c = this.compile('<div>' + '<input type="button" ng:click="greeting=\'ABC\'"/>' + '<input type="button" ng:click=":garbage:"/></div>'); var first = jqLite(c.node[0].childNodes[0]); var second = jqLite(c.node[0].childNodes[1]); + var errorLogs = c.scope.$service('$log').error.logs; browserTrigger(first, 'click'); assertEquals("ABC", c.scope.greeting); + expect(errorLogs).toEqual([]); browserTrigger(second, 'click'); assertTrue(second.hasClass("ng-exception")); + expect(errorLogs.shift()[0]).toMatchError(/Parse Error: Token ':' not a primary expression/); }); it('ItShouldSelectTheCorrectRadioBox', function(){ diff --git a/test/ScopeSpec.js b/test/ScopeSpec.js index 8984f605..ab1ba124 100644 --- a/test/ScopeSpec.js +++ b/test/ScopeSpec.js @@ -146,30 +146,34 @@ describe('scope/model', function(){ }); describe('$tryEval', function(){ - it('should report error on element', function(){ - var scope = createScope(); + it('should report error using the provided error handler and $log.error', function(){ + var scope = createScope(), + errorLogs = scope.$service('$log').error.logs; + scope.$tryEval(function(){throw "myError";}, function(error){ scope.error = error; }); expect(scope.error).toEqual('myError'); + expect(errorLogs.shift()[0]).toBe("myError"); }); it('should report error on visible element', function(){ - var element = jqLite('<div></div>'); - var scope = createScope(); + var element = jqLite('<div></div>'), + scope = createScope(), + errorLogs = scope.$service('$log').error.logs; + scope.$tryEval(function(){throw "myError";}, element); expect(element.attr('ng-exception')).toEqual('myError'); expect(element.hasClass('ng-exception')).toBeTruthy(); + expect(errorLogs.shift()[0]).toBe("myError"); }); it('should report error on $excetionHandler', function(){ - var errors = [], - errorLogs = [], - scope = createScope(null, {}, {$exceptionHandler: function(e) {errors.push(e)}, - $log: {error: function(e) {errorLogs.push(e)}}}); + var scope = createScope(null, {$exceptionHandler: $exceptionHandlerMockFactory}, + {$log: $logMock}); scope.$tryEval(function(){throw "myError";}); - expect(errors).toEqual(["myError"]); - expect(errorLogs).toEqual(["myError"]); + expect(scope.$service('$exceptionHandler').errors.shift()).toEqual("myError"); + expect(scope.$service('$log').error.logs.shift()).toEqual(["myError"]); }); }); diff --git a/test/angular-mocks.js b/test/angular-mocks.js deleted file mode 100644 index e601d9ab..00000000 --- a/test/angular-mocks.js +++ /dev/null @@ -1,320 +0,0 @@ -/** - * The MIT License - * - * Copyright (c) 2010 Adam Abrons and Misko Hevery http://getangular.com - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - - -/* - - NUGGGGGH MUST TONGUE WANGS - \ - ..... - C C / - /< / - ___ __________/_#__=o - /(- /(\_\________ \ - \ ) \ )_ \o \ - /|\ /|\ |' | - | _| - /o __\ - / ' | - / / | - /_/\______| - ( _( < - \ \ \ - \ \ | - \____\____\ - ____\_\__\_\ - /` /` o\ - |___ |_______|.. . b'ger - - - IN THE FINAL BUILD THIS FILE DOESN'T HAVE DIRECT ACCESS TO GLOBAL FUNCTIONS - DEFINED IN Angular.js YOU *MUST* REFER TO THEM VIA angular OBJECT - (e.g. angular.forEach(...)) AND MAKE SURE THAT THE GIVEN FUNCTION IS EXPORTED - TO THE angular NAMESPACE in AngularPublic.js - - */ - - -function MockBrowser() { - var self = this, - expectations = {}, - requests = []; - - this.isMock = true; - self.url = "http://server"; - self.lastUrl = self.url; // used by url polling fn - self.pollFns = []; - - - // register url polling fn - - self.onHashChange = function(listener) { - self.pollFns.push( - function() { - if (self.lastUrl != self.url) { - listener(); - } - } - ); - - return listener; - }; - - - self.xhr = function(method, url, data, callback) { - if (angular.isFunction(data)) { - callback = data; - data = null; - } - if (data && angular.isObject(data)) data = angular.toJson(data); - if (data && angular.isString(data)) url += "|" + data; - var expect = expectations[method] || {}; - var response = expect[url]; - if (!response) { - throw { - message: "Unexpected request for method '" + method + "' and url '" + url + "'.", - name: "Unexpected Request" - }; - } - requests.push(function(){ - callback(response.code, response.response); - }); - }; - self.xhr.expectations = expectations; - self.xhr.requests = requests; - self.xhr.expect = function(method, url, data) { - if (data && angular.isObject(data)) data = angular.toJson(data); - if (data && angular.isString(data)) url += "|" + data; - var expect = expectations[method] || (expectations[method] = {}); - return { - respond: function(code, response) { - if (!angular.isNumber(code)) { - response = code; - code = 200; - } - expect[url] = {code:code, response:response}; - } - }; - }; - self.xhr.expectGET = angular.bind(self, self.xhr.expect, 'GET'); - self.xhr.expectPOST = angular.bind(self, self.xhr.expect, 'POST'); - self.xhr.expectDELETE = angular.bind(self, self.xhr.expect, 'DELETE'); - self.xhr.expectPUT = angular.bind(self, self.xhr.expect, 'PUT'); - self.xhr.expectJSON = angular.bind(self, self.xhr.expect, 'JSON'); - self.xhr.flush = function() { - while(requests.length) { - requests.pop()(); - } - }; - - self.cookieHash = {}; - self.lastCookieHash = {}; - self.deferredFns = []; - - self.defer = function(fn) { - self.deferredFns.push(fn); - }; - - self.defer.flush = function() { - while (self.deferredFns.length) self.deferredFns.shift()(); - }; -} -MockBrowser.prototype = { - - poll: function poll(){ - angular.forEach(this.pollFns, function(pollFn){ - pollFn(); - }); - }, - - addPollFn: function(pollFn) { - this.pollFns.push(pollFn); - return pollFn; - }, - - hover: function(onHover) { - }, - - getUrl: function(){ - return this.url; - }, - - setUrl: function(url){ - this.url = url; - }, - - cookies: function(name, value) { - if (name) { - if (value == undefined) { - delete this.cookieHash[name]; - } else { - if (angular.isString(value) && //strings only - value.length <= 4096) { //strict cookie storage limits - this.cookieHash[name] = value; - } - } - } else { - if (!angular.equals(this.cookieHash, this.lastCookieHash)) { - this.lastCookieHash = angular.copy(this.cookieHash); - this.cookieHash = angular.copy(this.cookieHash); - } - return this.cookieHash; - } - } -}; - -angular.service('$browser', function(){ - return new MockBrowser(); -}); - - -/** - * Mock of the Date type which has its timezone specified via constroctor arg. - * - * The main purpose is to create Date-like instances with timezone fixed to the specified timezone - * offset, so that we can test code that depends on local timezone settings without dependency on - * the time zone settings of the machine where the code is running. - * - * @param {number} offset Offset of the *desired* timezone in hours (fractions will be honored) - * @param {(number|string)} timestamp Timestamp representing the desired time in *UTC* - * - * @example - * var newYearInBratislava = new TzDate(-1, '2009-12-31T23:00:00Z'); - * newYearInBratislava.getTimezoneOffset() => -60; - * newYearInBratislava.getFullYear() => 2010; - * newYearInBratislava.getMonth() => 0; - * newYearInBratislava.getDate() => 1; - * newYearInBratislava.getHours() => 0; - * newYearInBratislava.getMinutes() => 0; - * - * - * !!!! WARNING !!!!! - * This is not a complete Date object so only methods that were implemented can be called safely. - * To make matters worse, TzDate instances inherit stuff from Date via a prototype. - * - * We do our best to intercept calls to "unimplemented" methods, but since the list of methods is - * incomplete we might be missing some non-standard methods. This can result in errors like: - * "Date.prototype.foo called on incompatible Object". - */ -function TzDate(offset, timestamp) { - if (angular.isString(timestamp)) { - var tsStr = timestamp; - - this.origDate = angular.String.toDate(timestamp); - - timestamp = this.origDate.getTime(); - if (isNaN(timestamp)) - throw { - name: "Illegal Argument", - message: "Arg '" + tsStr + "' passed into TzDate constructor is not a valid date string" - }; - } else { - this.origDate = new Date(timestamp); - } - - var localOffset = new Date(timestamp).getTimezoneOffset(); - this.offsetDiff = localOffset*60*1000 - offset*1000*60*60; - this.date = new Date(timestamp + this.offsetDiff); - - this.getTime = function() { - return this.date.getTime() - this.offsetDiff; - }; - - this.toLocaleDateString = function() { - return this.date.toLocaleDateString(); - }; - - this.getFullYear = function() { - return this.date.getFullYear(); - }; - - this.getMonth = function() { - return this.date.getMonth(); - }; - - this.getDate = function() { - return this.date.getDate(); - }; - - this.getHours = function() { - return this.date.getHours(); - }; - - this.getMinutes = function() { - return this.date.getMinutes(); - }; - - this.getSeconds = function() { - return this.date.getSeconds(); - }; - - this.getTimezoneOffset = function() { - return offset * 60; - }; - - this.getUTCFullYear = function() { - return this.origDate.getUTCFullYear(); - }; - - this.getUTCMonth = function() { - return this.origDate.getUTCMonth(); - }; - - this.getUTCDate = function() { - return this.origDate.getUTCDate(); - }; - - this.getUTCHours = function() { - return this.origDate.getUTCHours(); - }; - - this.getUTCMinutes = function() { - return this.origDate.getUTCMinutes(); - }; - - this.getUTCSeconds = function() { - return this.origDate.getUTCSeconds(); - }; - - - //hide all methods not implemented in this mock that the Date prototype exposes - var unimplementedMethods = ['getDay', 'getMilliseconds', 'getTime', 'getUTCDay', - 'getUTCMilliseconds', 'getYear', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds', - 'setMinutes', 'setMonth', 'setSeconds', 'setTime', 'setUTCDate', 'setUTCFullYear', - 'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds', - 'setYear', 'toDateString', 'toJSON', 'toGMTString', 'toLocaleFormat', 'toLocaleString', - 'toLocaleTimeString', 'toSource', 'toString', 'toTimeString', 'toUTCString', 'valueOf']; - - angular.forEach(unimplementedMethods, function(methodName) { - this[methodName] = function() { - throw { - name: "MethodNotImplemented", - message: "Method '" + methodName + "' is not implemented in the TzDate mock" - }; - }; - }); -} - -//make "tzDateInstance instanceof Date" return true -TzDate.prototype = Date.prototype; diff --git a/test/servicesSpec.js b/test/servicesSpec.js index e9b16621..c3c10552 100644 --- a/test/servicesSpec.js +++ b/test/servicesSpec.js @@ -31,8 +31,14 @@ describe("service", function(){ function warn(){ logger+= 'warn;'; } function info(){ logger+= 'info;'; } function error(){ logger+= 'error;'; } - var scope = createScope({}, angularService, {$window: {console:{log:log, warn:warn, info:info, error:error}}, $document:[{cookie:''}]}), + var scope = createScope({}, {$log: $logFactory}, + {$exceptionHandler: rethrow, + $window: {console: {log: log, + warn: warn, + info: info, + error: error}}}), $log = scope.$service('$log'); + $log.log(); $log.warn(); $log.info(); @@ -40,10 +46,12 @@ describe("service", function(){ expect(logger).toEqual('log;warn;info;error;'); }); - it('should use console.log if other not present', function(){ + it('should use console.log() if other not present', function(){ var logger = ""; function log(){ logger+= 'log;'; } - var scope = createScope({}, angularService, {$window: {console:{log:log}}, $document:[{cookie:''}]}); + var scope = createScope({}, {$log: $logFactory}, + {$window: {console:{log:log}}, + $exceptionHandler: rethrow}); var $log = scope.$service('$log'); $log.log(); $log.warn(); @@ -53,7 +61,9 @@ describe("service", function(){ }); it('should use noop if no console', function(){ - var scope = createScope({}, angularService, {$window: {}, $document:[{cookie:''}]}), + var scope = createScope({}, {$log: $logFactory}, + {$window: {}, + $exceptionHandler: rethrow}), $log = scope.$service('$log'); $log.log(); $log.warn(); @@ -61,8 +71,8 @@ describe("service", function(){ $log.error(); }); - describe('Error', function(){ - var e, $log, $console, errorArgs; + describe('error', function(){ + var e, $log, errorArgs; beforeEach(function(){ e = new Error(''); e.message = undefined; @@ -70,19 +80,19 @@ describe("service", function(){ e.line = undefined; e.stack = undefined; - $console = angular.service('$log')({console:{error:function(){ + $log = $logFactory({console:{error:function(){ errorArgs = arguments; }}}); }); it('should pass error if does not have trace', function(){ - $console.error('abc', e); + $log.error('abc', e); expect(errorArgs).toEqual(['abc', e]); }); it('should print stack', function(){ e.stack = 'stack'; - $console.error('abc', e); + $log.error('abc', e); expect(errorArgs).toEqual(['abc', 'stack']); }); @@ -90,7 +100,7 @@ describe("service", function(){ e.message = 'message'; e.sourceURL = 'sourceURL'; e.line = '123'; - $console.error('abc', e); + $log.error('abc', e); expect(errorArgs).toEqual(['abc', 'message\nsourceURL:123']); }); @@ -100,10 +110,13 @@ describe("service", function(){ describe("$exceptionHandler", function(){ it('should log errors', function(){ - var error = ''; - $log.error = function(m) { error += m; }; - scope.$service('$exceptionHandler')('myError'); - expect(error).toEqual('myError'); + var scope = createScope({}, {$exceptionHandler: $exceptionHandlerFactory}, + {$log: $logMock}), + $log = scope.$service('$log'), + $exceptionHandler = scope.$service('$exceptionHandler'); + + $exceptionHandler('myError'); + expect($log.error.logs.shift()).toEqual(['myError']); }); }); diff --git a/test/testabilityPatch.js b/test/testabilityPatch.js index 7029b213..78cb767f 100644 --- a/test/testabilityPatch.js +++ b/test/testabilityPatch.js @@ -59,11 +59,59 @@ beforeEach(function(){ return this.actual.hasClass ? this.actual.hasClass(clazz) : jqLite(this.actual).hasClass(clazz); + }, + + toEqualError: function(message) { + this.message = function() { + var expected; + if (this.actual.message && this.actual.name == 'Error') { + expected = toJson(this.actual.message); + } else { + expected = toJson(this.actual); + } + return "Expected " + expected + " to be an Error with message " + toJson(message); + } + return this.actual.name == 'Error' && this.actual.message == message; + }, + + toMatchError: function(messageRegexp) { + this.message = function() { + var expected; + if (this.actual.message && this.actual.name == 'Error') { + expected = toJson(this.actual.message); + } else { + expected = toJson(this.actual); + } + return "Expected " + expected + " to match an Error with message " + toJson(messageRegexp); + } + return this.actual.name == 'Error' && messageRegexp.test(this.actual.message); } }); + + $logMock.log.logs = []; + $logMock.warn.logs = []; + $logMock.info.logs = []; + $logMock.error.logs = []; }); -afterEach(clearJqCache); +afterEach(function() { + // check $log mock + forEach(['error', 'warn', 'info', 'log'], function(logLevel) { + if ($logMock[logLevel].logs.length) { + forEach($logMock[logLevel].logs, function(log) { + forEach(log, function deleteStack(logItem) { + if (logItem instanceof Error) delete logItem.stack; + }); + }); + + throw new Error("Exprected $log." + logLevel + ".logs array to be empty. " + + "Either a message was logged unexpectedly, or an expected log message was not checked " + + "and removed. Array contents: " + toJson($logMock[logLevel].logs)); + } + }); + + clearJqCache(); +}); function clearJqCache(){ var count = 0; diff --git a/test/widgetsSpec.js b/test/widgetsSpec.js index 2812614f..ee339e89 100644 --- a/test/widgetsSpec.js +++ b/test/widgetsSpec.js @@ -546,12 +546,16 @@ describe("widget", function(){ it('should report error on assignment error', function(){ compile('<input type="text" name="throw \'\'" value="x"/>'); expect(element.hasClass('ng-exception')).toBeTruthy(); + expect(scope.$service('$log').error.logs.shift()[0]). + toMatchError(/Parse Error: Token '''' is extra token not part of expression/); }); it('should report error on ng:change exception', function(){ compile('<button ng:change="a-2=x">click</button>'); browserTrigger(element); expect(element.hasClass('ng-exception')).toBeTruthy(); + expect(scope.$service('$log').error.logs.shift()[0]). + toMatchError(/Parse Error: Token '=' implies assignment but \[a-2\] can not be assigned to/); }); }); @@ -750,10 +754,15 @@ describe("widget", function(){ it('should error on wrong parsing of ng:repeat', function(){ var scope = compile('<ul><li ng:repeat="i dont parse"></li></ul>'); - var log = ""; - log += element.attr('ng-exception') + ';'; - log += element.hasClass('ng-exception') + ';'; - expect(log).toMatch(/Expected ng:repeat in form of 'item in collection' but got 'i dont parse'./); + + expect(scope.$service('$log').error.logs.shift()[0]). + toEqualError("Expected ng:repeat in form of 'item in collection' but got 'i dont parse'."); + + expect(scope.$element.attr('ng-exception')). + toMatch(/Expected ng:repeat in form of 'item in collection' but got 'i dont parse'/); + expect(scope.$element).toHaveClass('ng-exception'); + + dealoc(scope); }); it('should expose iterator offset as $index when iterating over arrays', function() { |
