diff options
| author | Igor Minar | 2011-01-25 20:44:44 -0800 |
|---|---|---|
| committer | Igor Minar | 2011-01-26 15:46:05 -0800 |
| commit | f5d08963b0c836b10133a94d03a81254242661eb (patch) | |
| tree | 14c525d0aa22193e5b1a1f8aa791b96608dc83b2 /src/angular-mocks.js | |
| parent | 7a48ee6aa949e8d4338033de8a0c6a079ceb17a3 (diff) | |
| download | angular.js-f5d08963b0c836b10133a94d03a81254242661eb.tar.bz2 | |
split mocks and create $log and $exceptionHandler mocks
- split mocks between angular-mocks.js and mocks.js
- src/angular-mocks.js now contains only mocks that we want to ship
- test/mocks.js contains mocks that we use internally for testing
angular
- created angular.mock namespace
- created public $exceptionHandler mock rethrows errors
- created public $log mock stores all logs messages in an array that can
be accessed to make assertions
- internally we now have factory to create $exceptionHandler
that we can assert on
- internally we also keep track of all messages logged and
fail tests if messages were not expected and cleaned up (checked
via global beforeEach and afterEach)
- updated RakeFile and docs reader.js to point to the new
angular-mocks.js location
- made real $exceptionHandler and $log factories accessible from tests
and simplified their specs
- fixed typos in several spec descriptions
- added log assertions throughout the test suite
Diffstat (limited to 'src/angular-mocks.js')
| -rw-r--r-- | src/angular-mocks.js | 384 |
1 files changed, 384 insertions, 0 deletions
diff --git a/src/angular-mocks.js b/src/angular-mocks.js new file mode 100644 index 00000000..b9e1dd5c --- /dev/null +++ b/src/angular-mocks.js @@ -0,0 +1,384 @@ +/** + * 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 + + */ + + +/** + * @ngdoc overview + * @name angular.mock + * @namespace Namespace for all built-in angular mocks. + * + * @description + * `angular.mock` is a namespace for all built-in mocks that ship with angular and automatically + * replace real services if `angular-mocks.js` file is loaded after `angular.js` and before any + * tests. + */ +angular.mock = {}; + + +/** + * @workInProgress + * @ngdoc service + * @name angular.mock.service.$browser + */ +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(); +}); + + +/** + * @workInProgress + * @ngdoc service + * @name angular.mock.service.$exceptionHandler + * + * @description + * Mock implementation of {@link angular.service.$exceptionHandler} that rethrows any error passed + * into `$exceptionHandler`. If any errors are are passed into the handler in tests, it typically + * means that there is a bug in the application or test, so this mock will make these tests fail. + * + * See {@link angular.mock} for more info on angular mocks. + */ +angular.service('$exceptionHandler', function(e) { + return function(e) {throw e;}; +}); + + +/** + * @workInProgress + * @ngdoc service + * @name angular.mock.service.$log + * + * @description + * Mock implementation of {@link angular.service.$log} that gathers all logged messages in arrays + * (one array per logging level). These arrays are exposed as `logs` property of each of the + * level-specific log function, e.g. for level `error` the array is exposed as `$log.error.logs`. + * + * See {@link angular.mock} for more info on angular mocks. + */ +angular.service('$log', function() { + var $log = { + log: function log(){ log.logs.push(arguments) }, + warn: function warn(){ warn.logs.push(arguments) }, + info: function info(){ info.logs.push(arguments) }, + error: function error(){ error.logs.push(arguments) } + }; + + $log.log.logs = []; + $log.warn.logs = []; + $log.info.logs = []; + $log.error.logs = []; + + return $log; +}); + + +/** + * 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; |
