mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-03-16 23:30:23 +00:00
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
This commit is contained in:
parent
7a48ee6aa9
commit
f5d08963b0
11 changed files with 193 additions and 40 deletions
2
Rakefile
2
Rakefile
|
|
@ -174,7 +174,7 @@ task :package => [:clean, :compile, :docs] do
|
|||
FileUtils.rm_r(path_to('pkg'), :force => true)
|
||||
FileUtils.mkdir_p(pkg_dir)
|
||||
|
||||
['test/angular-mocks.js',
|
||||
['src/angular-mocks.js',
|
||||
path_to('angular.js'),
|
||||
path_to('angular.min.js'),
|
||||
path_to('angular-ie-compat.js'),
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ load:
|
|||
- src/scenario/Scenario.js
|
||||
- src/scenario/output/*.js
|
||||
- src/scenario/*.js
|
||||
- test/angular-mocks.js
|
||||
- src/angular-mocks.js
|
||||
- test/mocks.js
|
||||
- test/scenario/*.js
|
||||
- test/scenario/output/*.js
|
||||
- test/*.js
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ load:
|
|||
- src/scenario/Scenario.js
|
||||
- src/scenario/output/*.js
|
||||
- src/scenario/*.js
|
||||
- test/angular-mocks.js
|
||||
- src/angular-mocks.js
|
||||
- test/mocks.js
|
||||
- test/scenario/*.js
|
||||
- test/scenario/output/*.js
|
||||
- test/*.js
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ load:
|
|||
- src/scenario/Scenario.js
|
||||
- src/scenario/output/*.js
|
||||
- src/scenario/*.js
|
||||
- test/angular-mocks.js
|
||||
- src/angular-mocks.js
|
||||
- test/mocks.js
|
||||
- test/scenario/*.js
|
||||
- test/scenario/output/*.js
|
||||
- test/*.js
|
||||
|
|
|
|||
64
test/angular-mocks.js → src/angular-mocks.js
vendored
64
test/angular-mocks.js → src/angular-mocks.js
vendored
|
|
@ -56,6 +56,24 @@
|
|||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @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 = {},
|
||||
|
|
@ -189,6 +207,52 @@ angular.service('$browser', function(){
|
|||
});
|
||||
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*
|
||||
|
|
@ -290,10 +290,10 @@ angularServiceInject("$location", function($browser) {
|
|||
* @requires $window
|
||||
*
|
||||
* @description
|
||||
* Is simple service for logging. Default implementation writes the message
|
||||
* Simple service for logging. Default implementation writes the message
|
||||
* into the browser's console (if present).
|
||||
*
|
||||
* This is useful for debugging.
|
||||
* The main purpose of this service is to simplify debugging and troubleshooting.
|
||||
*
|
||||
* @example
|
||||
<p>Reload this page with open console, enter text and hit the log button...</p>
|
||||
|
|
@ -304,7 +304,8 @@ angularServiceInject("$location", function($browser) {
|
|||
<button ng:click="$log.info(message)">info</button>
|
||||
<button ng:click="$log.error(message)">error</button>
|
||||
*/
|
||||
angularServiceInject("$log", function($window){
|
||||
var $logFactory; //reference to be used only in tests
|
||||
angularServiceInject("$log", $logFactory = function($window){
|
||||
return {
|
||||
/**
|
||||
* @workInProgress
|
||||
|
|
@ -387,7 +388,8 @@ angularServiceInject("$log", function($window){
|
|||
*
|
||||
* @example
|
||||
*/
|
||||
angularServiceInject('$exceptionHandler', function($log){
|
||||
var $exceptionHandlerFactory; //reference to be used only in tests
|
||||
angularServiceInject('$exceptionHandler', $exceptionHandlerFactory = function($log){
|
||||
return function(e) {
|
||||
$log.error(e);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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(){
|
||||
|
|
|
|||
|
|
@ -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"]);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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']);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
Loading…
Reference in a new issue