From a87f2fb9e4d65ac5d260e914b5e31aa0e0f47b2c Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Fri, 4 Nov 2011 12:33:01 -0700 Subject: refactor(mock): moved mocks into its own module --- src/Angular.js | 14 +++- src/angular-mocks.js | 106 ++++++++++++++++++++++++++---- test/BinderSpec.js | 5 ++ test/ResourceSpec.js | 4 ++ test/angular-mocksSpec.js | 54 +++++++++++++++ test/matchers.js | 17 +++++ test/scenario/RunnerSpec.js | 4 +- test/scenario/matchersSpec.js | 6 ++ test/service/filter/orderBySpec.js | 15 ++--- test/service/logSpec.js | 2 +- test/testabilityPatch.js | 131 +++++-------------------------------- 11 files changed, 219 insertions(+), 139 deletions(-) diff --git a/src/Angular.js b/src/Angular.js index fcb13881..af441e1c 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -426,6 +426,17 @@ function trim(value) { return isString(value) ? value.replace(/^\s*/, '').replace(/\s*$/, '') : value; } +/** + * @ngdoc function + * @name angular.isElement + * @function + * + * @description + * Determines if a reference is a DOM element (or wrapped jQuery element). + * + * @param {*} value Reference to check. + * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element). + */ function isElement(node) { return node && (node.nodeName // we are a direct element @@ -1012,7 +1023,7 @@ function assertArg(arg, name, reason) { function assertArgFn(arg, name) { assertArg(isFunction(arg), name, 'not a function, got ' + - (typeof arg == 'object' ? arg.constructor.name : typeof arg)); + (typeof arg == 'object' ? arg.constructor.name || 'Object' : typeof arg)); return arg; } @@ -1034,6 +1045,7 @@ function publishExternalAPI(angular){ 'isFunction': isFunction, 'isObject': isObject, 'isNumber': isNumber, + 'isElement': isElement, 'isArray': isArray, 'version': version, 'isDate': isDate, diff --git a/src/angular-mocks.js b/src/angular-mocks.js index 497fdc58..970a1b6d 100644 --- a/src/angular-mocks.js +++ b/src/angular-mocks.js @@ -363,16 +363,21 @@ angular.mock.$ExceptionHandlerProvider = function(){ var handler; this.mode = function(mode){ - handler = { - rethrow: function(e) { - throw e; - }, - log: angular.extend(function log(e) { - log.errors.push(e); - }, {errors:[]}) - }[mode]; - if (!handler) { - throw Error("Unknown mode '" + mode + "', only 'log'/'rethrow' modes are allowed!"); + switch(mode) { + case 'rethrow': + handler = function(e) { + throw e; + } + break; + case 'log': + var errors = []; + handler = function(e) { + errors.push(e); + } + handler.errors = errors; + break; + default: + throw Error("Unknown mode '" + mode + "', only 'log'/'rethrow' modes are allowed!"); } }; @@ -396,6 +401,12 @@ angular.mock.$ExceptionHandlerProvider = function(){ * See {@link angular.mock} for more info on angular mocks. */ angular.mock.$LogProvider = function(){ + + function concat(array1, array2, index) { + return array1.concat(Array.prototype.slice.call(array2, index)); + } + + this.$get = function () { var $log = { log: function() { $log.log.logs.push(concat([], arguments, 0)); }, @@ -416,7 +427,7 @@ angular.mock.$LogProvider = function(){ angular.forEach(['error', 'warn', 'info', 'log'], function(logLevel) { angular.forEach($log[logLevel].logs, function(log) { angular.forEach(log, function (logItem) { - errors.push('MOCK $log (' + logLevel + '): ' + (logItem.stack || logItem)); + errors.push('MOCK $log (' + logLevel + '): ' + String(logItem) + '\n' + (logItem.stack || '')); }); }); }); @@ -569,3 +580,76 @@ angular.mock.TzDate = function (offset, timestamp) { //make "tzDateInstance instanceof Date" return true angular.mock.TzDate.prototype = Date.prototype; + + +/** + * Method for serializing common objects into strings, useful for debugging. + * @param {*} object - any object to turn into string. + * @return a serialized string of the argument + */ +angular.mock.dump = function(object){ + var out; + if (angular.isElement(object)) { + object = angular.element(object); + out = angular.element('
') + angular.forEach(object, function(element){ + out.append(angular.element(element).clone()); + }); + out = out.html(); + } else if (angular.isObject(object)) { + if (angular.isFunction(object.$eval) && angular.isFunction(object.$apply)) { + out = serializeScope(object); + } else { + out = angular.toJson(object, true); + } + } else { + out = String(object); + } + return out; + + function serializeScope(scope, offset) { + offset = offset || ' '; + var log = [offset + 'Scope(' + scope.$id + '): {']; + for ( var key in scope ) { + if (scope.hasOwnProperty(key) && !key.match(/^(\$|this)/)) { + log.push(' ' + key + ': ' + angular.toJson(scope[key])); + } + } + var child = scope.$$childHead; + while(child) { + log.push(serializeScope(child, offset + ' ')); + child = child.$$nextSibling; + } + log.push('}'); + return log.join('\n' + offset); + } +}; + +window.jstestdriver && (function(window){ + /** + * Global method to output any number of objects into JSTD console. Useful for debugging. + */ + window.dump = function() { + var args = []; + angular.forEach(arguments, function(arg){ + args.push(angular.mock.dump(arg)); + }); + jstestdriver.console.log.apply(jstestdriver.console, args); + }; +})(window); + + +window.jasmine && (function(window){ + window.inject = function (){ + var blockFns = Array.prototype.slice.call(arguments, 0); + return function(){ + var injector = this.$injector; + if (!injector) { + injector = this.$injector = angular.injector('NG', 'NG_MOCK'); + } + for(var i = 0, ii = blockFns.length; i < ii; i++) { + injector.invoke(this, blockFns[i]); + } + }; + } +})(window); diff --git a/test/BinderSpec.js b/test/BinderSpec.js index 01e61b57..691e2db8 100644 --- a/test/BinderSpec.js +++ b/test/BinderSpec.js @@ -1,6 +1,11 @@ 'use strict'; describe('Binder', function() { + + function childNode(element, index) { + return jqLite(element[0].childNodes[index]); + } + beforeEach(function() { this.compileToHtml = function (content) { var html; diff --git a/test/ResourceSpec.js b/test/ResourceSpec.js index 2091a936..57aaffe0 100644 --- a/test/ResourceSpec.js +++ b/test/ResourceSpec.js @@ -3,6 +3,10 @@ describe("resource", function() { var resource, CreditCard, callback; + function nakedExpect(obj) { + return expect(angular.fromJson(angular.toJson(obj))); + } + beforeEach(inject( function($provide) { $provide.value('$xhr.error', jasmine.createSpy('xhr.error')); diff --git a/test/angular-mocksSpec.js b/test/angular-mocksSpec.js index b4ceb275..1668d500 100644 --- a/test/angular-mocksSpec.js +++ b/test/angular-mocksSpec.js @@ -283,4 +283,58 @@ describe('mocks', function() { }).toThrow("Unknown mode 'XXX', only 'log'/'rethrow' modes are allowed!"); })); }); + + + describe('angular.mock.debug', function(){ + var d = angular.mock.dump; + + + it('should serialize primitive types', function(){ + expect(d(undefined)).toEqual('undefined'); + expect(d(1)).toEqual('1'); + expect(d(null)).toEqual('null'); + expect(d('abc')).toEqual('abc'); + }); + + + it('should serialize element', function(){ + var e = angular.element('
abc
xyz'); + expect(d(e).toLowerCase()).toEqual('
abc
xyz'); + expect(d(e[0]).toLowerCase()).toEqual('
abc
'); + }); + + it('should serialize scope', inject(function($rootScope){ + $rootScope.obj = {abc:'123'}; + expect(d($rootScope)).toMatch(/Scope\(.*\): \{/); + expect(d($rootScope)).toMatch(/{"abc":"123"}/); + })); + + + it('should be published on window', function(){ + expect(window.dump instanceof Function).toBe(true); + }); + }); + + describe('jasmine inject', function(){ + it('should call invoke', function(){ + var count = 0; + function fn1(){ + expect(this).toBe(self); + count++; + } + function fn2(){ + expect(this).toBe(self); + count++; + } + var fn = inject(fn1, fn2); + var self = { + $injector: { + invoke: function(self, fn) { fn.call(self); } + } + }; + + fn.call(self); + expect(count).toBe(2); + }); + }); }); diff --git a/test/matchers.js b/test/matchers.js index 9923bd7e..fbe86a5a 100644 --- a/test/matchers.js +++ b/test/matchers.js @@ -23,6 +23,13 @@ beforeEach(function() { }; } + function indexOf(array, obj) { + for ( var i = 0; i < array.length; i++) { + if (obj === array[i]) return i; + } + return -1; + } + this.addMatchers({ toBeInvalid: cssMatcher('ng-invalid', 'ng-valid'), toBeValid: cssMatcher('ng-valid', 'ng-invalid'), @@ -84,6 +91,16 @@ beforeEach(function() { toBeOneOf: function() { return indexOf(arguments, this.actual) !== -1; + }, + + toHaveClass: function(clazz) { + this.message = function() { + return "Expected '" + angular.mock.dump(this.actual) + "' to have class '" + clazz + "'."; + }; + return this.actual.hasClass ? + this.actual.hasClass(clazz) : + angular.element(this.actual).hasClass(clazz); } + }); }); diff --git a/test/scenario/RunnerSpec.js b/test/scenario/RunnerSpec.js index 62d84fca..15bcc4b0 100644 --- a/test/scenario/RunnerSpec.js +++ b/test/scenario/RunnerSpec.js @@ -46,8 +46,8 @@ describe('angular.scenario.Runner', function() { runner.createSpecRunner_ = function(scope) { return scope.$new(MockSpecRunner); }; - runner.on('SpecError', rethrow); - runner.on('StepError', rethrow); + runner.on('SpecError', angular.mock.rethrow); + runner.on('StepError', angular.mock.rethrow); }); afterEach(function() { diff --git a/test/scenario/matchersSpec.js b/test/scenario/matchersSpec.js index 7ab41cf2..7a5217d7 100644 --- a/test/scenario/matchersSpec.js +++ b/test/scenario/matchersSpec.js @@ -42,4 +42,10 @@ describe('angular.scenario.matchers', function () { expectMatcher(3, function() { matchers.toBeLessThan(10); }); expectMatcher(3, function() { matchers.toBeGreaterThan(-5); }); }); + + it('should have toHaveClass matcher', function(){ + var e = angular.element('
'); + expect(e).not.toHaveClass('none'); + expect(e).toHaveClass('abc'); + }); }); diff --git a/test/service/filter/orderBySpec.js b/test/service/filter/orderBySpec.js index f59fad55..5c117891 100644 --- a/test/service/filter/orderBySpec.js +++ b/test/service/filter/orderBySpec.js @@ -12,18 +12,15 @@ describe('Filter: orderBy', function() { }); it('shouldSortArrayInReverse', function() { - assertJsonEquals([{a:15},{a:2}], orderBy([{a:15},{a:2}], 'a', true)); - assertJsonEquals([{a:15},{a:2}], orderBy([{a:15},{a:2}], 'a', "T")); - assertJsonEquals([{a:15},{a:2}], orderBy([{a:15},{a:2}], 'a', "reverse")); + expect(orderBy([{a:15}, {a:2}], 'a', true)).toEqualData([{a:15}, {a:2}]); + expect(orderBy([{a:15}, {a:2}], 'a', "T")).toEqualData([{a:15}, {a:2}]); + expect(orderBy([{a:15}, {a:2}], 'a', "reverse")).toEqualData([{a:15}, {a:2}]); }); it('should sort array by predicate', function() { - assertJsonEquals([{a:2, b:1},{a:15, b:1}], - orderBy([{a:15, b:1},{a:2, b:1}], ['a', 'b'])); - assertJsonEquals([{a:2, b:1},{a:15, b:1}], - orderBy([{a:15, b:1},{a:2, b:1}], ['b', 'a'])); - assertJsonEquals([{a:15, b:1},{a:2, b:1}], - orderBy([{a:15, b:1},{a:2, b:1}], ['+b', '-a'])); + expect(orderBy([{a:15, b:1}, {a:2, b:1}], ['a', 'b'])).toEqualData([{a:2, b:1}, {a:15, b:1}]); + expect(orderBy([{a:15, b:1}, {a:2, b:1}], ['b', 'a'])).toEqualData([{a:2, b:1}, {a:15, b:1}]); + expect(orderBy([{a:15, b:1}, {a:2, b:1}], ['+b', '-a'])).toEqualData([{a:15, b:1}, {a:2, b:1}]); }); it('should use function', function() { diff --git a/test/service/logSpec.js b/test/service/logSpec.js index 93e705a9..ee250b66 100644 --- a/test/service/logSpec.js +++ b/test/service/logSpec.js @@ -13,7 +13,7 @@ describe('$log', function() { $window = {}; logger = ''; $provide.service('$log', $LogProvider); - $provide.value('$exceptionHandler', rethrow); + $provide.value('$exceptionHandler', angular.mock.rethrow); $provide.value('$window', $window); })); diff --git a/test/testabilityPatch.js b/test/testabilityPatch.js index 96deac5a..b2bcb519 100644 --- a/test/testabilityPatch.js +++ b/test/testabilityPatch.js @@ -9,46 +9,10 @@ _jQuery.event.special.change = undefined; -if (window.jstestdriver) { - window.jstd = jstestdriver; - window.dump = function dump() { - var args = []; - forEach(arguments, function(arg){ - if (isElement(arg)) { - arg = sortedHtml(arg); - } else if (isObject(arg)) { - if (isFunction(arg.$eval) && isFunction(arg.$apply)) { - arg = dumpScope(arg); - } else { - arg = toJson(arg, true); - } - } - args.push(arg); - }); - jstd.console.log.apply(jstd.console, args); - }; -} - -function dumpScope(scope, offset) { - offset = offset || ' '; - var log = [offset + 'Scope(' + scope.$id + '): {']; - for ( var key in scope ) { - if (scope.hasOwnProperty(key) && !key.match(/^(\$|this)/)) { - log.push(' ' + key + ': ' + toJson(scope[key])); - } - } - var child = scope.$$childHead; - while(child) { - log.push(dumpScope(child, offset + ' ')); - child = child.$$nextSibling; - } - log.push('}'); - return log.join('\n' + offset); -} - -publishExternalAPI(angular) +publishExternalAPI(angular); +bindJQuery(); beforeEach(function() { - publishExternalAPI(angular) + publishExternalAPI(angular); // workaround for IE bug https://plus.google.com/104744871076396904202/posts/Kqjuj6RSbbT // IE overwrite window.jQuery with undefined because of empty jQuery var statement, so we have to @@ -63,43 +27,20 @@ beforeEach(function() { // reset to jQuery or default to us. bindJQuery(); jqLite(document.body).html(''); - - this.addMatchers({ - toHaveClass: function(clazz) { - this.message = function() { - return "Expected '" + sortedHtml(this.actual) + "' to have class '" + clazz + "'."; - }; - return this.actual.hasClass ? - this.actual.hasClass(clazz) : - jqLite(this.actual).hasClass(clazz); - } - }); - }); -function inject(){ - var blockFns = sliceArgs(arguments); - return function(){ - var spec = this; - spec.$injector = spec.$injector || angular.injector('NG', 'NG_MOCK'); - angular.forEach(blockFns, function(fn){ - spec.$injector.invoke(spec, fn); - }); - }; -} - - -afterEach(inject(function($rootScope, $log) { - // release the injector - dealoc($rootScope); - - // check $log mock - $log.assertEmpty && $log.assertEmpty(); +afterEach(function() { + if (this.$injector) { + var $rootScope = this.$injector('$rootScope'); + var $log = this.$injector('$log'); + // release the injector + dealoc($rootScope); - clearJqCache(); -})); + // check $log mock + $log.assertEmpty && $log.assertEmpty(); + } -function clearJqCache() { + // complain about uncleared jqCache references var count = 0; forEachSorted(jqCache, function(value, key){ count ++; @@ -115,15 +56,8 @@ function clearJqCache() { if (count) { fail('Found jqCache references that were not deallocated!'); } -} - -function nakedExpect(obj) { - return expect(angular.fromJson(angular.toJson(obj))); -} +}); -function childNode(element, index) { - return jqLite(element[0].childNodes[index]); -} function dealoc(obj) { if (obj) { @@ -240,43 +174,10 @@ function isCssVisible(node) { } function assertHidden(node) { - assertFalse("Node should be hidden but vas visible: " + sortedHtml(node), isCssVisible(node)); + assertFalse("Node should be hidden but vas visible: " + angular.mock.dump(node), isCssVisible(node)); } function assertVisible(node) { - assertTrue("Node should be visible but vas hidden: " + sortedHtml(node), isCssVisible(node)); -} - -function assertJsonEquals(expected, actual) { - assertEquals(toJson(expected), toJson(actual)); -} - -function assertUndefined(value) { - assertEquals('undefined', typeof value); + assertTrue("Node should be visible but vas hidden: " + angular.mock.dump(node), isCssVisible(node)); } -function assertDefined(value) { - assertTrue(toJson(value), !!value); -} - -function assertThrows(error, fn){ - var exception = null; - try { - fn(); - } catch(e) { - exception = e; - } - if (!exception) { - fail("Expecting exception, none thrown"); - } - assertEquals(error, exception); -} - -window.log = noop; -window.error = noop; - -function rethrow(e) { - if(e) { - throw e; - } -} -- cgit v1.2.3