Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 2ad88b7

Browse files
committed
test(*): introduce the toEqualMinErr() custom Jasmine matcher
1 parent e13eeab commit 2ad88b7

11 files changed

+139
-117
lines changed

test/AngularSpec.js

+7-10
Original file line numberDiff line numberDiff line change
@@ -1636,8 +1636,8 @@ describe('angular', function() {
16361636

16371637
expect(function() {
16381638
angularInit(appElement, angular.bootstrap);
1639-
}).toThrowError(
1640-
new RegExp('\\[\\$injector:modulerr] Failed to instantiate module doesntexist due to:\\n' +
1639+
}).toThrowMinErr('$injector', 'modulerr',
1640+
new RegExp('Failed to instantiate module doesntexist due to:\\n' +
16411641
'.*\\[\\$injector:nomod] Module \'doesntexist\' is not available! You either ' +
16421642
'misspelled the module name or forgot to load it\\.')
16431643
);
@@ -1650,9 +1650,8 @@ describe('angular', function() {
16501650

16511651
expect(function() {
16521652
angular.bootstrap(element);
1653-
}).toThrowError(
1654-
/\[ng:btstrpd\] App Already Bootstrapped with this Element '<div class="?ng\-scope"?( ng[0-9]+="?[0-9]+"?)?>'/i
1655-
);
1653+
}).toThrowMinErr('ng', 'btstrpd',
1654+
/App Already Bootstrapped with this Element '<div class="?ng-scope"?( ng\d+="?\d+"?)?>'/i);
16561655

16571656
dealoc(element);
16581657
});
@@ -1662,9 +1661,7 @@ describe('angular', function() {
16621661
angular.bootstrap(document.getElementsByTagName('html')[0]);
16631662
expect(function() {
16641663
angular.bootstrap(document);
1665-
}).toThrowError(
1666-
/\[ng:btstrpd\] App Already Bootstrapped with this Element 'document'/i
1667-
);
1664+
}).toThrowMinErr('ng', 'btstrpd', /App Already Bootstrapped with this Element 'document'/i);
16681665

16691666
dealoc(document);
16701667
});
@@ -1863,8 +1860,8 @@ describe('angular', function() {
18631860

18641861
expect(function() {
18651862
angular.bootstrap(element, ['doesntexist']);
1866-
}).toThrowError(
1867-
new RegExp('\\[\\$injector:modulerr\\] Failed to instantiate module doesntexist due to:\\n' +
1863+
}).toThrowMinErr('$injector', 'modulerr',
1864+
new RegExp('Failed to instantiate module doesntexist due to:\\n' +
18681865
'.*\\[\\$injector:nomod\\] Module \'doesntexist\' is not available! You either ' +
18691866
'misspelled the module name or forgot to load it\\.'));
18701867

test/auto/injectorSpec.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1062,7 +1062,7 @@ describe('injector', function() {
10621062
createInjector([function($provide) {
10631063
$provide.value('name', 'angular');
10641064
}, instanceLookupInModule]);
1065-
}).toThrowError(/\[\$injector:unpr] Unknown provider: name/);
1065+
}).toThrowMinErr('$injector', 'modulerr', '[$injector:unpr] Unknown provider: name');
10661066
});
10671067
});
10681068
});

test/helpers/matchers.js

+66-44
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,41 @@ beforeEach(function() {
4949
return hidden;
5050
}
5151

52+
function MinErrMatcher(isNot, namespace, code, content, wording) {
53+
var codeRegex = new RegExp('^' + escapeRegexp('[' + namespace + ':' + code + ']'));
54+
var contentRegex = angular.isUndefined(content) || jasmine.isA_('RegExp', content) ?
55+
content : new RegExp(escapeRegexp(content));
56+
57+
this.test = test;
58+
59+
function escapeRegexp(str) {
60+
// This function escapes all special regex characters.
61+
// We use it to create matching regex from arbitrary strings.
62+
// http://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex
63+
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
64+
}
65+
66+
function test(exception) {
67+
var exceptionMessage = (exception && exception.message) || exception || '';
68+
69+
var codeMatches = codeRegex.test(exceptionMessage);
70+
var contentMatches = angular.isUndefined(contentRegex) || contentRegex.test(exceptionMessage);
71+
var matches = codeMatches && contentMatches;
72+
73+
return {
74+
pass: isNot ? !matches : matches,
75+
message: message
76+
};
77+
78+
function message() {
79+
return 'Expected ' + wording.inputType + (isNot ? ' not' : '') + ' to ' +
80+
wording.expectedAction + ' ' + namespace + 'MinErr(\'' + code + '\')' +
81+
(contentRegex ? ' matching ' + contentRegex.toString() : '') +
82+
(!exception ? '.' : ', but it ' + wording.actualAction + ': ' + exceptionMessage);
83+
}
84+
}
85+
}
86+
5287
jasmine.addMatchers({
5388
toBeEmpty: cssMatcher('ng-empty', 'ng-not-empty'),
5489
toBeNotEmpty: cssMatcher('ng-not-empty', 'ng-empty'),
@@ -58,6 +93,7 @@ beforeEach(function() {
5893
toBePristine: cssMatcher('ng-pristine', 'ng-dirty'),
5994
toBeUntouched: cssMatcher('ng-untouched', 'ng-touched'),
6095
toBeTouched: cssMatcher('ng-touched', 'ng-untouched'),
96+
6197
toBeAPromise: function() {
6298
return {
6399
compare: generateCompare(false),
@@ -71,6 +107,7 @@ beforeEach(function() {
71107
};
72108
}
73109
},
110+
74111
toBeShown: function() {
75112
return {
76113
compare: generateCompare(false),
@@ -87,6 +124,7 @@ beforeEach(function() {
87124
};
88125
}
89126
},
127+
90128
toBeHidden: function() {
91129
return {
92130
compare: generateCompare(false),
@@ -267,26 +305,34 @@ beforeEach(function() {
267305
}
268306
},
269307

308+
toEqualMinErr: function() {
309+
return {
310+
compare: generateCompare(false),
311+
negativeCompare: generateCompare(true)
312+
};
313+
314+
function generateCompare(isNot) {
315+
return function(actual, namespace, code, content) {
316+
var matcher = new MinErrMatcher(isNot, namespace, code, content, {
317+
inputType: 'error',
318+
expectedAction: 'equal',
319+
actualAction: 'was'
320+
});
321+
322+
return matcher.test(actual);
323+
};
324+
}
325+
},
326+
270327
toThrowMinErr: function() {
271328
return {
272329
compare: generateCompare(false),
273330
negativeCompare: generateCompare(true)
274331
};
332+
275333
function generateCompare(isNot) {
276334
return function(actual, namespace, code, content) {
277-
var result,
278-
exception,
279-
exceptionMessage = '',
280-
escapeRegexp = function(str) {
281-
// This function escapes all special regex characters.
282-
// We use it to create matching regex from arbitrary strings.
283-
// http://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex
284-
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
285-
},
286-
codeRegex = new RegExp('^\\[' + escapeRegexp(namespace) + ':' + escapeRegexp(code) + '\\]'),
287-
not = isNot ? 'not ' : '',
288-
regex = jasmine.isA_('RegExp', content) ? content :
289-
angular.isDefined(content) ? new RegExp(escapeRegexp(content)) : undefined;
335+
var exception;
290336

291337
if (!angular.isFunction(actual)) {
292338
throw new Error('Actual is not a function');
@@ -298,41 +344,17 @@ beforeEach(function() {
298344
exception = e;
299345
}
300346

301-
if (exception) {
302-
exceptionMessage = exception.message || exception;
303-
}
304-
305-
var message = function() {
306-
return 'Expected function ' + not + 'to throw ' +
307-
namespace + 'MinErr(\'' + code + '\')' +
308-
(regex ? ' matching ' + regex.toString() : '') +
309-
(exception ? ', but it threw ' + exceptionMessage : '.');
310-
};
311-
312-
result = codeRegex.test(exceptionMessage);
313-
if (!result) {
314-
if (isNot) {
315-
return { pass: !result, message: message };
316-
} else {
317-
return { pass: result, message: message };
318-
}
319-
}
347+
var matcher = new MinErrMatcher(isNot, namespace, code, content, {
348+
inputType: 'function',
349+
expectedAction: 'throw',
350+
actualAction: 'threw'
351+
});
320352

321-
if (angular.isDefined(regex)) {
322-
if (isNot) {
323-
return { pass: !regex.test(exceptionMessage), message: message };
324-
} else {
325-
return { pass: regex.test(exceptionMessage), message: message };
326-
}
327-
}
328-
if (isNot) {
329-
return { pass: !result, message: message };
330-
} else {
331-
return { pass: result, message: message };
332-
}
353+
return matcher.test(exception);
333354
};
334355
}
335356
},
357+
336358
toBeMarkedAsSelected: function() {
337359
// Selected is special because the element property and attribute reflect each other's state.
338360
// IE9 will wrongly report hasAttribute('selected') === true when the property is

test/jqLiteSpec.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1712,7 +1712,7 @@ describe('jqLite', function() {
17121712
aElem.on('click', noop);
17131713
expect(function() {
17141714
aElem.off('click', noop, '.test');
1715-
}).toThrowError(/\[jqLite:offargs\]/);
1715+
}).toThrowMinErr('jqLite', 'offargs');
17161716
});
17171717
});
17181718

test/ng/compileSpec.js

+29-25
Original file line numberDiff line numberDiff line change
@@ -1862,8 +1862,8 @@ describe('$compile', function() {
18621862
$httpBackend.flush();
18631863

18641864
expect(sortedHtml(element)).toBe('<div><b class="hello"></b></div>');
1865-
expect($exceptionHandler.errors[0].message).toMatch(
1866-
/^\[\$compile:tpload] Failed to load template: hello\.html/);
1865+
expect($exceptionHandler.errors[0]).toEqualMinErr('$compile', 'tpload',
1866+
'Failed to load template: hello.html');
18671867
})
18681868
);
18691869

@@ -1885,9 +1885,9 @@ describe('$compile', function() {
18851885
$compile('<div><div class="sync async"></div></div>');
18861886
$httpBackend.flush();
18871887

1888-
expect($exceptionHandler.errors[0].message).toMatch(new RegExp(
1889-
'^\\[\\$compile:multidir] Multiple directives \\[async, sync] asking for ' +
1890-
'template on: <div class="sync async">'));
1888+
expect($exceptionHandler.errors[0]).toEqualMinErr('$compile', 'multidir',
1889+
'Multiple directives [async, sync] asking for template on: ' +
1890+
'<div class="sync async">');
18911891
});
18921892
});
18931893

@@ -2096,15 +2096,17 @@ describe('$compile', function() {
20962096
$templateCache.put('template.html', 'dada');
20972097
$compile('<p template></p>');
20982098
$rootScope.$digest();
2099-
expect($exceptionHandler.errors.pop().message).
2100-
toMatch(/\[\$compile:tplrt\] Template for directive 'template' must have exactly one root element\. template\.html/);
2099+
expect($exceptionHandler.errors.pop()).toEqualMinErr('$compile', 'tplrt',
2100+
'Template for directive \'template\' must have exactly one root element. ' +
2101+
'template.html');
21012102

21022103
// multi root
21032104
$templateCache.put('template.html', '<div></div><div></div>');
21042105
$compile('<p template></p>');
21052106
$rootScope.$digest();
2106-
expect($exceptionHandler.errors.pop().message).
2107-
toMatch(/\[\$compile:tplrt\] Template for directive 'template' must have exactly one root element\. template\.html/);
2107+
expect($exceptionHandler.errors.pop()).toEqualMinErr('$compile', 'tplrt',
2108+
'Template for directive \'template\' must have exactly one root element. ' +
2109+
'template.html');
21082110

21092111
// ws is ok
21102112
$templateCache.put('template.html', ' <div></div> \n');
@@ -2676,9 +2678,9 @@ describe('$compile', function() {
26762678
compile('<div class="tiscope-a; scope-b"></div>');
26772679
$httpBackend.flush();
26782680

2679-
expect($exceptionHandler.errors[0].message).toMatch(new RegExp(
2680-
'^\\[\\$compile:multidir] Multiple directives \\[scopeB, tiscopeA] ' +
2681-
'asking for new/isolated scope on: <div class="tiscope-a; scope-b ng-scope">'));
2681+
expect($exceptionHandler.errors[0]).toEqualMinErr('$compile', 'multidir',
2682+
'Multiple directives [scopeB, tiscopeA] asking for new/isolated scope on: ' +
2683+
'<div class="tiscope-a; scope-b ng-scope">');
26822684
})
26832685
);
26842686

@@ -8821,16 +8823,19 @@ describe('$compile', function() {
88218823

88228824
it('should throw on an ng-transclude element inside no transclusion directive', function() {
88238825
inject(function($rootScope, $compile) {
8824-
// we need to do this because different browsers print empty attributes differently
8826+
var error;
8827+
88258828
try {
88268829
$compile('<div><div ng-transclude></div></div>')($rootScope);
88278830
} catch (e) {
8828-
expect(e.message).toMatch(new RegExp(
8829-
'^\\[ngTransclude:orphan\\] ' +
8830-
'Illegal use of ngTransclude directive in the template! ' +
8831-
'No parent directive that requires a transclusion found\\. ' +
8832-
'Element: <div ng-transclude.+'));
8831+
error = e;
88338832
}
8833+
8834+
expect(error).toEqualMinErr('ngTransclude', 'orphan',
8835+
'Illegal use of ngTransclude directive in the template! ' +
8836+
'No parent directive that requires a transclusion found. ' +
8837+
'Element: <div ng-transclude');
8838+
// we need to do this because different browsers print empty attributes differently
88348839
});
88358840
});
88368841

@@ -8898,10 +8903,10 @@ describe('$compile', function() {
88988903
$rootScope.$digest();
88998904

89008905
expect($exceptionHandler.errors[0][1]).toBe('<div class="bar" ng-transclude="">');
8901-
expect($exceptionHandler.errors[0][0].message).toMatch(new RegExp(
8902-
'^\\[ngTransclude:orphan] Illegal use of ngTransclude directive in the ' +
8903-
'template! No parent directive that requires a transclusion found. Element: ' +
8904-
'<div class="bar" ng-transclude="">'));
8906+
expect($exceptionHandler.errors[0][0]).toEqualMinErr('ngTransclude', 'orphan',
8907+
'Illegal use of ngTransclude directive in the template! ' +
8908+
'No parent directive that requires a transclusion found. ' +
8909+
'Element: <div class="bar" ng-transclude="">');
89058910
});
89068911
});
89078912

@@ -9717,9 +9722,8 @@ describe('$compile', function() {
97179722
$compile('<div template first></div>');
97189723
$httpBackend.flush();
97199724

9720-
expect($exceptionHandler.errors[0].message).toMatch(new RegExp(
9721-
'^\\[\\$compile:multidir] Multiple directives \\[first, second] asking for ' +
9722-
'transclusion on: <p '));
9725+
expect($exceptionHandler.errors[0]).toEqualMinErr('$compile', 'multidir',
9726+
'Multiple directives [first, second] asking for transclusion on: <p ');
97239727
});
97249728
});
97259729

0 commit comments

Comments
 (0)