diff --git a/src/minErr.js b/src/minErr.js index 89be9b1bffea..4ebcf7221226 100644 --- a/src/minErr.js +++ b/src/minErr.js @@ -30,10 +30,21 @@ function minErr(module) { return function () { - var prefix = '[' + (module ? module + ':' : '') + arguments[0] + '] ', + var code = arguments[0], + prefix = '[' + (module ? module + ':' : '') + code + '] ', template = arguments[1], templateArgs = arguments, - message; + stringify = function (obj) { + if (isFunction(obj)) { + return obj.toString().replace(/ \{[\s\S]*$/, ''); + } else if (isUndefined(obj)) { + return 'undefined'; + } else if (!isString(obj)) { + return JSON.stringify(obj); + } + return obj; + }, + message, i; message = prefix + template.replace(/\{\d+\}/g, function (match) { var index = +match.slice(1, -1), arg; @@ -52,6 +63,13 @@ function minErr(module) { return match; }); + message = message + '\nhttp://errors.angularjs.org/' + version.full + '/' + + (module ? module + '/' : '') + code; + for (i = 2; i < arguments.length; i++) { + message = message + (i == 2 ? '?' : '&') + 'p' + (i-2) + '=' + + encodeURIComponent(stringify(arguments[i])); + } + return new Error(message); }; } diff --git a/test/AngularSpec.js b/test/AngularSpec.js index f049c2fd0c05..415ff25dc718 100644 --- a/test/AngularSpec.js +++ b/test/AngularSpec.js @@ -108,20 +108,20 @@ describe('angular', function() { it('should throw an exception if a Scope is being copied', inject(function($rootScope) { expect(function() { copy($rootScope.$new()); }). - toThrow("[ng:cpws] Can't copy! Making copies of Window or Scope instances is not supported."); + toThrowMinErr("ng", "cpws", "Can't copy! Making copies of Window or Scope instances is not supported."); })); it('should throw an exception if a Window is being copied', function() { expect(function() { copy(window); }). - toThrow("[ng:cpws] Can't copy! Making copies of Window or Scope instances is not supported."); + toThrowMinErr("ng", "cpws", "Can't copy! Making copies of Window or Scope instances is not supported."); }); it('should throw an exception when source and destination are equivalent', function() { var src, dst; src = dst = {key: 'value'}; - expect(function() { copy(src, dst); }).toThrow("[ng:cpi] Can't copy! Source and destination are identical."); + expect(function() { copy(src, dst); }).toThrowMinErr("ng", "cpi", "Can't copy! Source and destination are identical."); src = dst = [2, 4]; - expect(function() { copy(src, dst); }).toThrow("[ng:cpi] Can't copy! Source and destination are identical."); + expect(function() { copy(src, dst); }).toThrowMinErr("ng", "cpi", "Can't copy! Source and destination are identical."); }); it('should not copy the private $$hashKey', function() { @@ -901,7 +901,7 @@ describe('angular', function() { expect(function() { element.injector().get('foo'); - }).toThrow('[$injector:unpr] Unknown provider: fooProvider <- foo'); + }).toThrowMinErr('$injector', 'unpr', 'Unknown provider: fooProvider <- foo'); expect(element.injector().get('$http')).toBeDefined(); }); diff --git a/test/BinderSpec.js b/test/BinderSpec.js index c03b8ace10c3..3c204b64c48b 100644 --- a/test/BinderSpec.js +++ b/test/BinderSpec.js @@ -175,7 +175,7 @@ describe('Binder', function() { $rootScope.error['throw'] = function() {throw 'MyError';}; errorLogs.length = 0; $rootScope.$apply(); - expect(errorLogs.shift().message).toBe("[$interpolate:interr] Can't interpolate: {{error.throw()}}\nMyError"); + expect(errorLogs.shift().message).toMatch(/^\[\$interpolate:interr\] Can't interpolate: \{\{error.throw\(\)\}\}\nMyError/); $rootScope.error['throw'] = function() {return 'ok';}; $rootScope.$apply(); diff --git a/test/auto/injectorSpec.js b/test/auto/injectorSpec.js index 2c4856550bae..efed2aa40f9c 100644 --- a/test/auto/injectorSpec.js +++ b/test/auto/injectorSpec.js @@ -70,7 +70,7 @@ describe('injector', function() { it('should provide useful message if no provider', function() { expect(function() { injector.get('idontexist'); - }).toThrow("[$injector:unpr] Unknown provider: idontexistProvider <- idontexist"); + }).toThrowMinErr("$injector", "unpr", "Unknown provider: idontexistProvider <- idontexist"); }); @@ -79,7 +79,7 @@ describe('injector', function() { providers('b', function(a) {return 2;}); expect(function() { injector.get('b'); - }).toThrow("[$injector:unpr] Unknown provider: idontexistProvider <- idontexist <- a <- b"); + }).toThrowMinErr("$injector", "unpr", "Unknown provider: idontexistProvider <- idontexist <- a <- b"); }); @@ -127,10 +127,10 @@ describe('injector', function() { it('should fail with errors if not function or array', function() { expect(function() { injector.invoke({}); - }).toThrow("[ng:areq] Argument 'fn' is not a function, got Object"); + }).toThrowMinErr("ng", "areq", "Argument 'fn' is not a function, got Object"); expect(function() { injector.invoke(['a', 123], {}); - }).toThrow("[ng:areq] Argument 'fn' is not a function, got number"); + }).toThrowMinErr("ng", "areq", "Argument 'fn' is not a function, got number"); }); }); @@ -268,9 +268,8 @@ describe('injector', function() { it('should error on invalid module name', function() { expect(function() { createInjector(['IDontExist'], {}); - }).toThrowMatching( - /\[\$injector:modulerr\].+\n.*\[\$injector:nomod] Module 'IDontExist' is not available! You either misspelled the module name or forgot to load it/ - ); + }).toThrowMinErr('$injector', 'modulerr', + /\[\$injector:nomod\] Module 'IDontExist' is not available! You either misspelled the module name or forgot to load it/); }); @@ -553,7 +552,7 @@ describe('injector', function() { createInjector([ {} ], {}); - }).toThrowMatching(/\[\$injector:modulerr\] Failed to instantiate module {} due to:\n.*\[ng\:areq] Argument 'module' is not a function, got Object/); + }).toThrowMinErr('$injector', 'modulerr', /Failed to instantiate module \{\} due to:\n.*\[ng:areq\] Argument 'module' is not a function, got Object/); }); @@ -562,7 +561,7 @@ describe('injector', function() { createInjector([function() { throw 'MyError'; }], {}); - }).toThrowMatching(/\[\$injector:modulerr\] Failed to instantiate module .+ due to:\n.*MyError/); + }).toThrowMinErr('$injector', 'modulerr', /Failed to instantiate module .+ due to:\n.*MyError/); }); @@ -570,8 +569,8 @@ describe('injector', function() { angular.module('TestModule', [], function(xyzzy) {}); expect(function() { createInjector(['TestModule' ]); - }).toThrowMatching( - /\[\$injector:modulerr\] Failed to instantiate module TestModule due to:\n.*\[\$injector:unpr] Unknown provider: xyzzy/ + }).toThrowMinErr( + '$injector', 'modulerr', /Failed to instantiate module TestModule due to:\n.*\[\$injector:unpr] Unknown provider: xyzzy/ ); }); @@ -580,8 +579,8 @@ describe('injector', function() { function myModule(xyzzy){} expect(function() { createInjector([myModule]); - }).toThrowMatching( - /\[\$injector:modulerr\] Failed to instantiate module function myModule\(xyzzy\) due to:\n.*\[\$injector:unpr] Unknown provider: xyzzy/ + }).toThrowMinErr( + '$injector', 'modulerr', /Failed to instantiate module function myModule\(xyzzy\) due to:\n.*\[\$injector:unpr] Unknown provider: xyzzy/ ); }); @@ -590,8 +589,8 @@ describe('injector', function() { function myModule(xyzzy){} expect(function() { createInjector([['xyzzy', myModule]]); - }).toThrowMatching( - /\[\$injector:modulerr\] Failed to instantiate module function myModule\(xyzzy\) due to:\n.*\[\$injector:unpr] Unknown provider: xyzzy/ + }).toThrowMinErr( + '$injector', 'modulerr', /Failed to instantiate module function myModule\(xyzzy\) due to:\n.*\[\$injector:unpr] Unknown provider: xyzzy/ ); }); @@ -602,7 +601,7 @@ describe('injector', function() { $provide.factory('service', function(service){}); return function(service) {} }]) - }).toThrow("[$injector:cdep] Circular dependency found: service"); + }).toThrowMinErr('$injector', 'cdep', 'Circular dependency found: service'); }); @@ -613,7 +612,7 @@ describe('injector', function() { $provide.factory('b', function(a){}); return function(a) {} }]) - }).toThrow('[$injector:cdep] Circular dependency found: b <- a'); + }).toThrowMinErr('$injector', 'cdep', 'Circular dependency found: b <- a'); }); }); }); @@ -703,7 +702,7 @@ describe('injector', function() { it('should throw usefull error on wrong argument type]', function() { expect(function() { $injector.invoke({}); - }).toThrow("[ng:areq] Argument 'fn' is not a function, got Object"); + }).toThrowMinErr("ng", "areq", "Argument 'fn' is not a function, got Object"); }); }); @@ -790,7 +789,7 @@ describe('injector', function() { }]); expect(function() { $injector.get('nameProvider'); - }).toThrow("[$injector:unpr] Unknown provider: nameProviderProvider <- nameProvider"); + }).toThrowMinErr("$injector", "unpr", "Unknown provider: nameProviderProvider <- nameProvider"); }); @@ -798,7 +797,7 @@ describe('injector', function() { var $injector = createInjector([]); expect(function() { $injector.get('$provide').value('a', 'b'); - }).toThrow("[$injector:unpr] Unknown provider: $provideProvider <- $provide"); + }).toThrowMinErr("$injector", "unpr", "Unknown provider: $provideProvider <- $provide"); }); diff --git a/test/jqLiteSpec.js b/test/jqLiteSpec.js index 913e61924726..79c0d0c6a433 100644 --- a/test/jqLiteSpec.js +++ b/test/jqLiteSpec.js @@ -900,15 +900,15 @@ describe('jqLite', function() { expect(function() { elm.on('click', anObj, callback); - }).toThrowMatching(/\[jqLite\:onargs\]/); + }).toThrowMinErr('jqLite', 'onargs'); expect(function() { elm.on('click', null, aString, callback); - }).toThrowMatching(/\[jqLite\:onargs\]/); + }).toThrowMinErr('jqLite', 'onargs'); expect(function() { elm.on('click', aValue, callback); - }).toThrowMatching(/\[jqLite\:onargs\]/); + }).toThrowMinErr('jqLite', 'onargs'); }); } diff --git a/test/loaderSpec.js b/test/loaderSpec.js index 73e942151696..302852cb6db2 100644 --- a/test/loaderSpec.js +++ b/test/loaderSpec.js @@ -68,7 +68,7 @@ describe('module loader', function() { it('should complain of no module', function() { expect(function() { window.angular.module('dontExist'); - }).toThrow("[$injector:nomod] Module 'dontExist' is not available! You either misspelled the module name " + + }).toThrowMinErr("$injector", "nomod", "Module 'dontExist' is not available! You either misspelled the module name " + "or forgot to load it. If registering a module ensure that you specify the dependencies as the second " + "argument."); }); diff --git a/test/matchers.js b/test/matchers.js index b7d336b7ee5b..57bf35c7cd9e 100644 --- a/test/matchers.js +++ b/test/matchers.js @@ -165,6 +165,53 @@ beforeEach(function() { toThrowMatching: function(expected) { return jasmine.Matchers.prototype.toThrow.call(this, expected); + }, + + toThrowMinErr: function(namespace, code, content) { + var result, + exception, + exceptionMessage = '', + escapeRegexp = function (str) { + // This function escapes all special regex characters. + // We use it to create matching regex from arbitrary strings. + // http://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex + return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); + }, + codeRegex = new RegExp('^\\[' + escapeRegexp(namespace) + ':' + escapeRegexp(code) + '\\]'), + not = this.isNot ? "not " : "", + regex = jasmine.isA_("RegExp", content) ? content : + isDefined(content) ? new RegExp(escapeRegexp(content)) : undefined; + + if(!isFunction(this.actual)) { + throw new Error('Actual is not a function'); + } + + try { + this.actual(); + } catch (e) { + exception = e; + } + + if (exception) { + exceptionMessage = exception.message || exception; + } + + this.message = function () { + return "Expected function " + not + "to throw " + + namespace + "MinErr('" + code + "')" + + (regex ? " matching " + regex.toString() : "") + + (exception ? ", but it threw " + exceptionMessage : "."); + }; + + result = codeRegex.test(exceptionMessage); + if (!result) { + return result; + } + + if (isDefined(regex)) { + return regex.test(exceptionMessage); + } + return result; } }); }); diff --git a/test/minErrSpec.js b/test/minErrSpec.js index 4ee569e0ebe5..6b7d93b894e4 100644 --- a/test/minErrSpec.js +++ b/test/minErrSpec.js @@ -1,12 +1,12 @@ 'use strict'; describe('minErr', function () { - + var supportStackTraces = function() { var e = new Error(); return isDefined(e.stack); }; - var emptyTestError = minErr(), + var emptyTestError = minErr(), testError = minErr('test'); it('should return an Error factory', function() { @@ -34,7 +34,7 @@ describe('minErr', function () { it('should interpolate string arguments without quotes', function() { var myError = testError('1', 'This {0} is "{1}"', 'foo', 'bar'); - expect(myError.message).toBe('[test:1] This foo is "bar"'); + expect(myError.message).toMatch(/^\[test:1\] This foo is "bar"/); }); it('should interpolate non-string arguments', function() { @@ -57,7 +57,7 @@ describe('minErr', function () { var myError = testError('26', 'false: {0}; zero: {1}; null: {2}; undefined: {3}; emptyStr: {4}', false, 0, null, undefined, ''); expect(myError.message). - toBe('[test:26] false: false; zero: 0; null: null; undefined: undefined; emptyStr: '); + toMatch(/^\[test:26\] false: false; zero: 0; null: null; undefined: undefined; emptyStr: /); }); @@ -67,19 +67,19 @@ describe('minErr', function () { var foo = 'Fooooo', myError = testError('26', 'This {0} is {1} on {2}', foo); - expect(myError.message).toBe('[test:26] This Fooooo is {1} on {2}'); + expect(myError.message).toMatch(/^\[test:26\] This Fooooo is \{1\} on \{2\}/); }); it('should pass through the message if no interpolation is needed', function() { var myError = testError('26', 'Something horrible happened!'); - expect(myError.message).toBe('[test:26] Something horrible happened!'); + expect(myError.message).toMatch(/^\[test:26\] Something horrible happened!/); }); it('should include a namespace in the message only if it is namespaced', function () { var myError = emptyTestError('26', 'This is a {0}', 'Foo'); var myNamespacedError = testError('26', 'That is a {0}', 'Bar'); - expect(myError.message).toBe('[26] This is a Foo'); - expect(myNamespacedError.message).toBe('[test:26] That is a Bar'); + expect(myError.message).toMatch(/^\[26\] This is a Foo/); + expect(myNamespacedError.message).toMatch(/^\[test:26\] That is a Bar/); }); }); diff --git a/test/ng/animateSpec.js b/test/ng/animateSpec.js index 2e9034e4b9b2..79115c1e8e4c 100644 --- a/test/ng/animateSpec.js +++ b/test/ng/animateSpec.js @@ -44,7 +44,7 @@ describe("$animate", function() { module(function($animateProvider) { expect(function() { $animateProvider.register('abc', null); - }).toThrow("[$animate:notcsel] Expecting class selector starting with '.' got 'abc'."); + }).toThrowMinErr("$animate", "notcsel", "Expecting class selector starting with '.' got 'abc'."); }); inject(); }); diff --git a/test/ng/cacheFactorySpec.js b/test/ng/cacheFactorySpec.js index c398a55eadbc..b1a018dab879 100644 --- a/test/ng/cacheFactorySpec.js +++ b/test/ng/cacheFactorySpec.js @@ -15,7 +15,7 @@ describe('$cacheFactory', function() { it('should complain if the cache id is being reused', inject(function($cacheFactory) { $cacheFactory('cache1'); expect(function() { $cacheFactory('cache1'); }). - toThrow("[$cacheFactory:iid] CacheId 'cache1' is already taken!"); + toThrowMinErr("$cacheFactory", "iid", "CacheId 'cache1' is already taken!"); })); diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js index ba94c48bbfcf..28dc57fef0f8 100755 --- a/test/ng/compileSpec.js +++ b/test/ng/compileSpec.js @@ -632,11 +632,11 @@ describe('$compile', function() { inject(function($compile) { expect(function() { $compile('

'); - }).toThrow("[$compile:tplrt] Template for directive 'noRootElem' must have exactly one root element. "); + }).toThrowMinErr("$compile", "tplrt", "Template for directive 'noRootElem' must have exactly one root element. "); expect(function() { $compile('

'); - }).toThrow("[$compile:tplrt] Template for directive 'multiRootElem' must have exactly one root element. "); + }).toThrowMinErr("$compile", "tplrt", "Template for directive 'multiRootElem' must have exactly one root element. "); // ws is ok expect(function() { @@ -748,7 +748,7 @@ describe('$compile', function() { expect(function() { $templateCache.put('http://example.com/should-not-load.html', 'Should not load even if in cache.'); $compile('
')($rootScope); - }).toThrow('[$sce:insecurl] Blocked loading resource from url not allowed by $sceDelegate policy. URL: http://example.com/should-not-load.html'); + }).toThrowMinErr('$sce', 'insecurl', 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: http://example.com/should-not-load.html'); })); it('should load cross domain templates when trusted', inject( @@ -1011,7 +1011,7 @@ describe('$compile', function() { expect(function() { $httpBackend.flush(); - }).toThrow('[$compile:tpload] Failed to load template: hello.html'); + }).toThrowMinErr('$compile', 'tpload', 'Failed to load template: hello.html'); expect(sortedHtml(element)).toBe('
'); } )); @@ -1031,7 +1031,7 @@ describe('$compile', function() { inject(function($compile){ expect(function() { $compile('
'); - }).toThrow('[$compile:multidir] Multiple directives [sync, async] asking for template on: '+ + }).toThrowMinErr('$compile', 'multidir', 'Multiple directives [sync, async] asking for template on: '+ '
'); }); }); @@ -1215,14 +1215,14 @@ describe('$compile', function() { $compile('

'); $rootScope.$digest(); expect($exceptionHandler.errors.pop().message). - toBe("[$compile:tplrt] Template for directive 'template' must have exactly one root element. template.html"); + toMatch(/\[\$compile:tplrt\] Template for directive 'template' must have exactly one root element\. template\.html/); // multi root $templateCache.put('template.html', '
'); $compile('

'); $rootScope.$digest(); expect($exceptionHandler.errors.pop().message). - toBe("[$compile:tplrt] Template for directive 'template' must have exactly one root element. template.html"); + toMatch(/\[\$compile:tplrt\] Template for directive 'template' must have exactly one root element\. template\.html/); // ws is ok $templateCache.put('template.html', '
\n'); @@ -1482,7 +1482,7 @@ describe('$compile', function() { function($rootScope, $compile) { expect(function(){ $compile('
'); - }).toThrow('[$compile:multidir] Multiple directives [iscopeA, scopeB] asking for isolated scope on: ' + + }).toThrowMinErr('$compile', 'multidir', 'Multiple directives [iscopeA, scopeB] asking for isolated scope on: ' + '
'); }) ); @@ -1492,7 +1492,7 @@ describe('$compile', function() { function($rootScope, $compile) { expect(function(){ $compile('
'); - }).toThrow('[$compile:multidir] Multiple directives [iscopeA, iscopeB] asking for isolated scope on: ' + + }).toThrowMinErr('$compile', 'multidir', 'Multiple directives [iscopeA, iscopeB] asking for isolated scope on: ' + '
'); }) ); @@ -2110,7 +2110,7 @@ describe('$compile', function() { componentScope.ref = 'ignore me'; expect($rootScope.$apply). - toThrow("[$compile:nonassign] Expression ''hello ' + name' used with directive 'myComponent' is non-assignable!"); + toThrowMinErr("$compile", "nonassign", "Expression ''hello ' + name' used with directive 'myComponent' is non-assignable!"); expect(componentScope.ref).toBe('hello world'); // reset since the exception was rethrown which prevented phase clearing $rootScope.$$phase = null; @@ -2186,7 +2186,7 @@ describe('$compile', function() { it('should throw on unknown definition', inject(function() { expect(function() { compile('
'); - }).toThrow("[$compile:iscp] Invalid isolate scope definition for directive 'badDeclaration'. Definition: {... attr: 'xxx' ...}"); + }).toThrowMinErr("$compile", "iscp", "Invalid isolate scope definition for directive 'badDeclaration'. Definition: {... attr: 'xxx' ...}"); })); it('should expose a $$isolateBindings property onto the scope', inject(function() { @@ -2329,7 +2329,7 @@ describe('$compile', function() { inject(function(log, $compile, $rootScope) { expect(function() { $compile('
')($rootScope); - }).toThrow("[$compile:ctreq] Controller 'main', required by directive 'dep', can't be found!"); + }).toThrowMinErr("$compile", "ctreq", "Controller 'main', required by directive 'dep', can't be found!"); }); }); @@ -2724,7 +2724,7 @@ describe('$compile', function() { inject(function($compile) { expect(function() { $compile('
'); - }).toThrow('[$compile:multidir] Multiple directives [first, second] asking for transclusion on: ' + + }).toThrowMinErr('$compile', 'multidir', 'Multiple directives [first, second] asking for transclusion on: ' + '
'); }); }); @@ -3147,18 +3147,18 @@ describe('$compile', function() { $rootScope.onClickJs = ""; expect(function() { $compile('