diff --git a/test/AngularSpec.js b/test/AngularSpec.js index 6fa00567139a..4e16d15550a6 100644 --- a/test/AngularSpec.js +++ b/test/AngularSpec.js @@ -1636,8 +1636,8 @@ describe('angular', function() { expect(function() { angularInit(appElement, angular.bootstrap); - }).toThrowError( - new RegExp('\\[\\$injector:modulerr] Failed to instantiate module doesntexist due to:\\n' + + }).toThrowMinErr('$injector', 'modulerr', + new RegExp('Failed to instantiate module doesntexist due to:\\n' + '.*\\[\\$injector:nomod] Module \'doesntexist\' is not available! You either ' + 'misspelled the module name or forgot to load it\\.') ); @@ -1650,9 +1650,8 @@ describe('angular', function() { expect(function() { angular.bootstrap(element); - }).toThrowError( - /\[ng:btstrpd\] App Already Bootstrapped with this Element '<div class="?ng\-scope"?( ng[0-9]+="?[0-9]+"?)?>'/i - ); + }).toThrowMinErr('ng', 'btstrpd', + /App Already Bootstrapped with this Element '<div class="?ng-scope"?( ng\d+="?\d+"?)?>'/i); dealoc(element); }); @@ -1662,9 +1661,7 @@ describe('angular', function() { angular.bootstrap(document.getElementsByTagName('html')[0]); expect(function() { angular.bootstrap(document); - }).toThrowError( - /\[ng:btstrpd\] App Already Bootstrapped with this Element 'document'/i - ); + }).toThrowMinErr('ng', 'btstrpd', /App Already Bootstrapped with this Element 'document'/i); dealoc(document); }); @@ -1863,8 +1860,8 @@ describe('angular', function() { expect(function() { angular.bootstrap(element, ['doesntexist']); - }).toThrowError( - new RegExp('\\[\\$injector:modulerr\\] Failed to instantiate module doesntexist due to:\\n' + + }).toThrowMinErr('$injector', 'modulerr', + new RegExp('Failed to instantiate module doesntexist due to:\\n' + '.*\\[\\$injector:nomod\\] Module \'doesntexist\' is not available! You either ' + 'misspelled the module name or forgot to load it\\.')); diff --git a/test/auto/injectorSpec.js b/test/auto/injectorSpec.js index 2e93498bb819..807a47847293 100644 --- a/test/auto/injectorSpec.js +++ b/test/auto/injectorSpec.js @@ -1062,7 +1062,7 @@ describe('injector', function() { createInjector([function($provide) { $provide.value('name', 'angular'); }, instanceLookupInModule]); - }).toThrowError(/\[\$injector:unpr] Unknown provider: name/); + }).toThrowMinErr('$injector', 'modulerr', '[$injector:unpr] Unknown provider: name'); }); }); }); diff --git a/test/helpers/matchers.js b/test/helpers/matchers.js index 9161e612c0ee..5a21ec52756d 100644 --- a/test/helpers/matchers.js +++ b/test/helpers/matchers.js @@ -49,6 +49,41 @@ beforeEach(function() { return hidden; } + function MinErrMatcher(isNot, namespace, code, content, wording) { + var codeRegex = new RegExp('^' + escapeRegexp('[' + namespace + ':' + code + ']')); + var contentRegex = angular.isUndefined(content) || jasmine.isA_('RegExp', content) ? + content : new RegExp(escapeRegexp(content)); + + this.test = test; + + function escapeRegexp(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, '\\$&'); + } + + function test(exception) { + var exceptionMessage = (exception && exception.message) || exception || ''; + + var codeMatches = codeRegex.test(exceptionMessage); + var contentMatches = angular.isUndefined(contentRegex) || contentRegex.test(exceptionMessage); + var matches = codeMatches && contentMatches; + + return { + pass: isNot ? !matches : matches, + message: message + }; + + function message() { + return 'Expected ' + wording.inputType + (isNot ? ' not' : '') + ' to ' + + wording.expectedAction + ' ' + namespace + 'MinErr(\'' + code + '\')' + + (contentRegex ? ' matching ' + contentRegex.toString() : '') + + (!exception ? '.' : ', but it ' + wording.actualAction + ': ' + exceptionMessage); + } + } + } + jasmine.addMatchers({ toBeEmpty: cssMatcher('ng-empty', 'ng-not-empty'), toBeNotEmpty: cssMatcher('ng-not-empty', 'ng-empty'), @@ -58,6 +93,7 @@ beforeEach(function() { toBePristine: cssMatcher('ng-pristine', 'ng-dirty'), toBeUntouched: cssMatcher('ng-untouched', 'ng-touched'), toBeTouched: cssMatcher('ng-touched', 'ng-untouched'), + toBeAPromise: function() { return { compare: generateCompare(false), @@ -71,6 +107,7 @@ beforeEach(function() { }; } }, + toBeShown: function() { return { compare: generateCompare(false), @@ -87,6 +124,7 @@ beforeEach(function() { }; } }, + toBeHidden: function() { return { compare: generateCompare(false), @@ -267,26 +305,34 @@ beforeEach(function() { } }, + toEqualMinErr: function() { + return { + compare: generateCompare(false), + negativeCompare: generateCompare(true) + }; + + function generateCompare(isNot) { + return function(actual, namespace, code, content) { + var matcher = new MinErrMatcher(isNot, namespace, code, content, { + inputType: 'error', + expectedAction: 'equal', + actualAction: 'was' + }); + + return matcher.test(actual); + }; + } + }, + toThrowMinErr: function() { return { compare: generateCompare(false), negativeCompare: generateCompare(true) }; + function generateCompare(isNot) { return function(actual, 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 = isNot ? 'not ' : '', - regex = jasmine.isA_('RegExp', content) ? content : - angular.isDefined(content) ? new RegExp(escapeRegexp(content)) : undefined; + var exception; if (!angular.isFunction(actual)) { throw new Error('Actual is not a function'); @@ -298,41 +344,17 @@ beforeEach(function() { exception = e; } - if (exception) { - exceptionMessage = exception.message || exception; - } - - var 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) { - if (isNot) { - return { pass: !result, message: message }; - } else { - return { pass: result, message: message }; - } - } + var matcher = new MinErrMatcher(isNot, namespace, code, content, { + inputType: 'function', + expectedAction: 'throw', + actualAction: 'threw' + }); - if (angular.isDefined(regex)) { - if (isNot) { - return { pass: !regex.test(exceptionMessage), message: message }; - } else { - return { pass: regex.test(exceptionMessage), message: message }; - } - } - if (isNot) { - return { pass: !result, message: message }; - } else { - return { pass: result, message: message }; - } + return matcher.test(exception); }; } }, + toBeMarkedAsSelected: function() { // Selected is special because the element property and attribute reflect each other's state. // IE9 will wrongly report hasAttribute('selected') === true when the property is diff --git a/test/jqLiteSpec.js b/test/jqLiteSpec.js index b00703c9bc04..502cfe67c5fa 100644 --- a/test/jqLiteSpec.js +++ b/test/jqLiteSpec.js @@ -1712,7 +1712,7 @@ describe('jqLite', function() { aElem.on('click', noop); expect(function() { aElem.off('click', noop, '.test'); - }).toThrowError(/\[jqLite:offargs\]/); + }).toThrowMinErr('jqLite', 'offargs'); }); }); diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js index 45a7147b4e77..1786d3cd603b 100755 --- a/test/ng/compileSpec.js +++ b/test/ng/compileSpec.js @@ -1862,8 +1862,8 @@ describe('$compile', function() { $httpBackend.flush(); expect(sortedHtml(element)).toBe('
'); - expect($exceptionHandler.errors[0].message).toMatch( - /^\[\$compile:tpload] Failed to load template: hello\.html/); + expect($exceptionHandler.errors[0]).toEqualMinErr('$compile', 'tpload', + 'Failed to load template: hello.html'); }) ); @@ -1885,9 +1885,9 @@ describe('$compile', function() { $compile('
'); $httpBackend.flush(); - expect($exceptionHandler.errors[0].message).toMatch(new RegExp( - '^\\[\\$compile:multidir] Multiple directives \\[async, sync] asking for ' + - 'template on:
')); + expect($exceptionHandler.errors[0]).toEqualMinErr('$compile', 'multidir', + 'Multiple directives [async, sync] asking for template on: ' + + '
'); }); }); @@ -2096,15 +2096,17 @@ describe('$compile', function() { $templateCache.put('template.html', 'dada'); $compile('

'); $rootScope.$digest(); - expect($exceptionHandler.errors.pop().message). - toMatch(/\[\$compile:tplrt\] Template for directive 'template' must have exactly one root element\. template\.html/); + expect($exceptionHandler.errors.pop()).toEqualMinErr('$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). - toMatch(/\[\$compile:tplrt\] Template for directive 'template' must have exactly one root element\. template\.html/); + expect($exceptionHandler.errors.pop()).toEqualMinErr('$compile', 'tplrt', + 'Template for directive \'template\' must have exactly one root element. ' + + 'template.html'); // ws is ok $templateCache.put('template.html', '
\n'); @@ -2676,9 +2678,9 @@ describe('$compile', function() { compile('
'); $httpBackend.flush(); - expect($exceptionHandler.errors[0].message).toMatch(new RegExp( - '^\\[\\$compile:multidir] Multiple directives \\[scopeB, tiscopeA] ' + - 'asking for new/isolated scope on:
')); + expect($exceptionHandler.errors[0]).toEqualMinErr('$compile', 'multidir', + 'Multiple directives [scopeB, tiscopeA] asking for new/isolated scope on: ' + + '
'); }) ); @@ -4628,7 +4630,8 @@ describe('$compile', function() { // Update val to trigger the unstable onChanges, which will result in an error $rootScope.$apply('a = 42'); expect($exceptionHandler.errors.length).toEqual(1); - expect($exceptionHandler.errors[0].toString()).toContain('[$compile:infchng] 10 $onChanges() iterations reached.'); + expect($exceptionHandler.errors[0]). + toEqualMinErr('$compile', 'infchng', '10 $onChanges() iterations reached.'); }); }); @@ -8821,16 +8824,19 @@ describe('$compile', function() { it('should throw on an ng-transclude element inside no transclusion directive', function() { inject(function($rootScope, $compile) { - // we need to do this because different browsers print empty attributes differently + var error; + try { $compile('
')($rootScope); } catch (e) { - expect(e.message).toMatch(new RegExp( - '^\\[ngTransclude:orphan\\] ' + - 'Illegal use of ngTransclude directive in the template! ' + - 'No parent directive that requires a transclusion found\\. ' + - 'Element:
'); - expect($exceptionHandler.errors[0][0].message).toMatch(new RegExp( - '^\\[ngTransclude:orphan] Illegal use of ngTransclude directive in the ' + - 'template! No parent directive that requires a transclusion found. Element: ' + - '
')); + expect($exceptionHandler.errors[0][0]).toEqualMinErr('ngTransclude', 'orphan', + 'Illegal use of ngTransclude directive in the template! ' + + 'No parent directive that requires a transclusion found. ' + + 'Element:
'); }); }); @@ -9717,9 +9723,8 @@ describe('$compile', function() { $compile('
'); $httpBackend.flush(); - expect($exceptionHandler.errors[0].message).toMatch(new RegExp( - '^\\[\\$compile:multidir] Multiple directives \\[first, second] asking for ' + - 'transclusion on:

{{item}}

' + '
')(scope); - var expected = new RegExp('^\\[ngRepeat:badident\\] alias \'' + escape(expr) + '\' is invalid --- must be a valid JS identifier which is not a reserved name'); - expect($exceptionHandler.errors.shift()[0].message). - toMatch(expected); + expect($exceptionHandler.errors.shift()[0]).toEqualMinErr('ngRepeat', 'badident', + 'alias \'' + expr + '\' is invalid --- must be a valid JS identifier which is not a ' + + 'reserved name'); + dealoc(element); }); - - function escape(text) { - return text.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); - } })); }); @@ -580,16 +577,18 @@ describe('ngRepeat', function() { it('should error on wrong parsing of ngRepeat', function() { element = jqLite('
'); $compile(element)(scope); - expect($exceptionHandler.errors.shift()[0].message). - toMatch(/^\[ngRepeat:iexp\] Expected expression in form of '_item_ in _collection_\[ track by _id_\]' but got 'i dont parse'\./); + expect($exceptionHandler.errors.shift()[0]).toEqualMinErr('ngRepeat', 'iexp', + 'Expected expression in form of \'_item_ in _collection_[ track by _id_]\' but got ' + + '\'i dont parse\'.'); }); it('should throw error when left-hand-side of ngRepeat can\'t be parsed', function() { element = jqLite('
'); $compile(element)(scope); - expect($exceptionHandler.errors.shift()[0].message). - toMatch(/^\[ngRepeat:iidexp\] '_item_' in '_item_ in _collection_' should be an identifier or '\(_key_, _value_\)' expression, but got 'i dont parse'\./); + expect($exceptionHandler.errors.shift()[0]).toEqualMinErr('ngRepeat', 'iidexp', + '\'_item_\' in \'_item_ in _collection_\' should be an identifier or ' + + '\'(_key_, _value_)\' expression, but got \'i dont parse\'.'); }); @@ -1141,9 +1140,10 @@ describe('ngRepeat', function() { it('should throw error on adding existing duplicates and recover', function() { scope.items = [a, a, a]; scope.$digest(); - expect($exceptionHandler.errors.shift().message). - toMatch( - /^\[ngRepeat:dupes] Duplicates in a repeater are not allowed\. Use 'track by' expression to specify unique keys\. Repeater: item in items, Duplicate key: object:3, Duplicate value: {}/); + expect($exceptionHandler.errors.shift()).toEqualMinErr('ngRepeat', 'dupes', + 'Duplicates in a repeater are not allowed. ' + + 'Use \'track by\' expression to specify unique keys. ' + + 'Repeater: item in items, Duplicate key: object:3, Duplicate value: {}'); // recover scope.items = [a]; @@ -1162,9 +1162,10 @@ describe('ngRepeat', function() { it('should throw error on new duplicates and recover', function() { scope.items = [d, d, d]; scope.$digest(); - expect($exceptionHandler.errors.shift().message). - toMatch( - /^\[ngRepeat:dupes] Duplicates in a repeater are not allowed\. Use 'track by' expression to specify unique keys\. Repeater: item in items, Duplicate key: object:9, Duplicate value: {}/); + expect($exceptionHandler.errors.shift()).toEqualMinErr('ngRepeat', 'dupes', + 'Duplicates in a repeater are not allowed. ' + + 'Use \'track by\' expression to specify unique keys. ' + + 'Repeater: item in items, Duplicate key: object:9, Duplicate value: {}'); // recover scope.items = [a]; diff --git a/test/ng/directive/validatorsSpec.js b/test/ng/directive/validatorsSpec.js index 341c837ad6d9..224b848a42cc 100644 --- a/test/ng/directive/validatorsSpec.js +++ b/test/ng/directive/validatorsSpec.js @@ -136,7 +136,7 @@ describe('validators', function() { expect(function() { var inputElm = helper.compileInput(''); $rootScope.$apply('foo = \'bar\''); - }).not.toThrowError(/^\[ngPattern:noregexp\] Expected fooRegexp to be a RegExp but was/); + }).not.toThrow(); }); @@ -147,7 +147,7 @@ describe('validators', function() { $rootScope.fooRegexp = {}; $rootScope.foo = 'bar'; }); - }).toThrowError(/^\[ngPattern:noregexp\] Expected fooRegexp to be a RegExp but was/); + }).toThrowMinErr('ngPattern', 'noregexp', 'Expected fooRegexp to be a RegExp but was'); }); diff --git a/test/ng/httpSpec.js b/test/ng/httpSpec.js index da2f26401660..9033953dd583 100644 --- a/test/ng/httpSpec.js +++ b/test/ng/httpSpec.js @@ -1045,7 +1045,7 @@ describe('$http', function() { $http({method: 'JSONP', url: 'http://example.org/path'}) .catch(function(e) { error = e; }); $rootScope.$digest(); - expect(error.message).toContain('[$sce:insecurl]'); + expect(error).toEqualMinErr('$sce', 'insecurl'); }); it('should accept an explicitly trusted resource url', function() { @@ -1066,13 +1066,13 @@ describe('$http', function() { $http({ method: 'JSONP', url: $sce.trustAsResourceUrl('http://example.org/path?callback=JSON_CALLBACK')}) .catch(function(e) { error = e; }); $rootScope.$digest(); - expect(error.message).toContain('[$http:badjsonp]'); + expect(error).toEqualMinErr('$http', 'badjsonp'); error = undefined; $http({ method: 'JSONP', url: $sce.trustAsResourceUrl('http://example.org/path?other=JSON_CALLBACK')}) .catch(function(e) { error = e; }); $rootScope.$digest(); - expect(error.message).toContain('[$http:badjsonp]'); + expect(error).toEqualMinErr('$http', 'badjsonp'); }); it('should error if a param contains a JSON_CALLBACK value', function() { @@ -1080,13 +1080,13 @@ describe('$http', function() { $http({ method: 'JSONP', url: $sce.trustAsResourceUrl('http://example.org/path'), params: {callback: 'JSON_CALLBACK'}}) .catch(function(e) { error = e; }); $rootScope.$digest(); - expect(error.message).toContain('[$http:badjsonp]'); + expect(error).toEqualMinErr('$http', 'badjsonp'); error = undefined; $http({ method: 'JSONP', url: $sce.trustAsResourceUrl('http://example.org/path'), params: {other: 'JSON_CALLBACK'}}) .catch(function(e) { error = e; }); $rootScope.$digest(); - expect(error.message).toContain('[$http:badjsonp]'); + expect(error).toEqualMinErr('$http', 'badjsonp'); }); it('should error if there is already a param matching the jsonpCallbackParam key', function() { @@ -1094,13 +1094,13 @@ describe('$http', function() { $http({ method: 'JSONP', url: $sce.trustAsResourceUrl('http://example.org/path'), params: {callback: 'evilThing'}}) .catch(function(e) { error = e; }); $rootScope.$digest(); - expect(error.message).toContain('[$http:badjsonp]'); + expect(error).toEqualMinErr('$http', 'badjsonp'); error = undefined; $http({ method: 'JSONP', jsonpCallbackParam: 'cb', url: $sce.trustAsResourceUrl('http://example.org/path'), params: {cb: 'evilThing'}}) .catch(function(e) { error = e; }); $rootScope.$digest(); - expect(error.message).toContain('[$http:badjsonp]'); + expect(error).toEqualMinErr('$http', 'badjsonp'); }); }); diff --git a/test/ng/qSpec.js b/test/ng/qSpec.js index 82e8caab79c6..3aeb139fed47 100644 --- a/test/ng/qSpec.js +++ b/test/ng/qSpec.js @@ -844,8 +844,8 @@ describe('q', function() { expect(resolveSpy).not.toHaveBeenCalled(); expect(rejectSpy).toHaveBeenCalled(); - expect(rejectSpy.calls.argsFor(0)[0].message). - toMatch(/\[\$q:qcycle\] Expected promise to be resolved with value other than itself/); + expect(rejectSpy.calls.argsFor(0)[0]).toEqualMinErr('$q', 'qcycle', + 'Expected promise to be resolved with value other than itself'); }); diff --git a/test/ng/templateRequestSpec.js b/test/ng/templateRequestSpec.js index 5c741e5e52a6..cb9c1c6f6ce8 100644 --- a/test/ng/templateRequestSpec.js +++ b/test/ng/templateRequestSpec.js @@ -126,12 +126,10 @@ describe('$templateRequest', function() { $templateRequest('tpl.html').catch(function(reason) { err = reason; }); $httpBackend.flush(); - expect(err.message).toMatch(new RegExp( - '^\\[\\$compile:tpload] Failed to load template: tpl\\.html ' + - '\\(HTTP status: 404 Not Found\\)')); - expect($exceptionHandler.errors[0].message).toMatch(new RegExp( - '^\\[\\$compile:tpload] Failed to load template: tpl\\.html ' + - '\\(HTTP status: 404 Not Found\\)')); + expect(err).toEqualMinErr('$compile', 'tpload', + 'Failed to load template: tpl.html (HTTP status: 404 Not Found)'); + expect($exceptionHandler.errors[0]).toEqualMinErr('$compile', 'tpload', + 'Failed to load template: tpl.html (HTTP status: 404 Not Found)'); }); }); diff --git a/test/ngResource/resourceSpec.js b/test/ngResource/resourceSpec.js index a632abdbeaa1..2747253dbe1c 100644 --- a/test/ngResource/resourceSpec.js +++ b/test/ngResource/resourceSpec.js @@ -1550,9 +1550,9 @@ describe('errors', function() { expect(successSpy).not.toHaveBeenCalled(); expect(failureSpy).toHaveBeenCalled(); - expect(failureSpy.calls.mostRecent().args[0]).toMatch( - /^\[\$resource:badcfg\] Error in resource configuration for action `query`\. Expected response to contain an array but got an object \(Request: GET \/Customer\/123\)/ - ); + expect(failureSpy.calls.mostRecent().args[0]).toEqualMinErr('$resource', 'badcfg', + 'Error in resource configuration for action `query`. ' + + 'Expected response to contain an array but got an object (Request: GET /Customer/123)'); }); it('should fail if action expects an array but response is an object', function() { @@ -1567,9 +1567,9 @@ describe('errors', function() { expect(successSpy).not.toHaveBeenCalled(); expect(failureSpy).toHaveBeenCalled(); - expect(failureSpy.calls.mostRecent().args[0]).toMatch( - /^\[\$resource:badcfg\] Error in resource configuration for action `get`\. Expected response to contain an object but got an array \(Request: GET \/Customer\/123\)/ - ); + expect(failureSpy.calls.mostRecent().args[0]).toEqualMinErr('$resource', 'badcfg', + 'Error in resource configuration for action `get`. ' + + 'Expected response to contain an object but got an array (Request: GET /Customer/123)'); }); }); diff --git a/test/ngRoute/routeSpec.js b/test/ngRoute/routeSpec.js index 75e670b66243..9f0544bd4b2a 100644 --- a/test/ngRoute/routeSpec.js +++ b/test/ngRoute/routeSpec.js @@ -793,9 +793,9 @@ describe('$route', function() { expect(onSuccess).not.toHaveBeenCalled(); expect(onError).toHaveBeenCalled(); - expect(onError.calls.mostRecent().args[3].message).toMatch(new RegExp( - '^\\[\\$sce:insecurl] Blocked loading resource from url not allowed by ' + - '\\$sceDelegate policy\\. URL: http:\\/\\/example\\.com\\/foo\\.html')); + expect(onError.calls.mostRecent().args[3]).toEqualMinErr('$sce', 'insecurl', + 'Blocked loading resource from url not allowed by $sceDelegate policy. ' + + 'URL: http://example.com/foo.html'); }); }); @@ -891,7 +891,8 @@ describe('$route', function() { $rootScope.$digest(); $httpBackend.flush(); - expect($exceptionHandler.errors.pop().message).toContain('[$compile:tpload] Failed to load template: r1.html'); + expect($exceptionHandler.errors.pop()). + toEqualMinErr('$compile', 'tpload', 'Failed to load template: r1.html'); $httpBackend.expectGET('r2.html').respond(''); $location.path('/r2');