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

Commit f7c5643

Browse files
committed
test(*): introduce the toEqualMinErr() custom Jasmine matcher
1 parent fd0c423 commit f7c5643

File tree

7 files changed

+128
-103
lines changed

7 files changed

+128
-103
lines changed

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/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

test/ng/directive/ngRepeatSpec.js

+18-17
Original file line numberDiff line numberDiff line change
@@ -516,15 +516,12 @@ describe('ngRepeat', function() {
516516
' <div ng-repeat="' + expression + '">{{item}}</div>' +
517517
'</div>')(scope);
518518

519-
var expected = new RegExp('^\\[ngRepeat:badident\\] alias \'' + escape(expr) + '\' is invalid --- must be a valid JS identifier which is not a reserved name');
520-
expect($exceptionHandler.errors.shift()[0].message).
521-
toMatch(expected);
519+
expect($exceptionHandler.errors.shift()[0]).toEqualMinErr('ngRepeat', 'badident',
520+
'alias \'' + expr + '\' is invalid --- must be a valid JS identifier which is not a ' +
521+
'reserved name');
522+
522523
dealoc(element);
523524
});
524-
525-
function escape(text) {
526-
return text.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
527-
}
528525
}));
529526
});
530527

@@ -580,16 +577,18 @@ describe('ngRepeat', function() {
580577
it('should error on wrong parsing of ngRepeat', function() {
581578
element = jqLite('<ul><li ng-repeat="i dont parse"></li></ul>');
582579
$compile(element)(scope);
583-
expect($exceptionHandler.errors.shift()[0].message).
584-
toMatch(/^\[ngRepeat:iexp\] Expected expression in form of '_item_ in _collection_\[ track by _id_\]' but got 'i dont parse'\./);
580+
expect($exceptionHandler.errors.shift()[0]).toEqualMinErr('ngRepeat', 'iexp',
581+
'Expected expression in form of \'_item_ in _collection_[ track by _id_]\' but got ' +
582+
'\'i dont parse\'.');
585583
});
586584

587585

588586
it('should throw error when left-hand-side of ngRepeat can\'t be parsed', function() {
589587
element = jqLite('<ul><li ng-repeat="i dont parse in foo"></li></ul>');
590588
$compile(element)(scope);
591-
expect($exceptionHandler.errors.shift()[0].message).
592-
toMatch(/^\[ngRepeat:iidexp\] '_item_' in '_item_ in _collection_' should be an identifier or '\(_key_, _value_\)' expression, but got 'i dont parse'\./);
589+
expect($exceptionHandler.errors.shift()[0]).toEqualMinErr('ngRepeat', 'iidexp',
590+
'\'_item_\' in \'_item_ in _collection_\' should be an identifier or ' +
591+
'\'(_key_, _value_)\' expression, but got \'i dont parse\'.');
593592
});
594593

595594

@@ -1141,9 +1140,10 @@ describe('ngRepeat', function() {
11411140
it('should throw error on adding existing duplicates and recover', function() {
11421141
scope.items = [a, a, a];
11431142
scope.$digest();
1144-
expect($exceptionHandler.errors.shift().message).
1145-
toMatch(
1146-
/^\[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: {}/);
1143+
expect($exceptionHandler.errors.shift()).toEqualMinErr('ngRepeat', 'dupes',
1144+
'Duplicates in a repeater are not allowed. ' +
1145+
'Use \'track by\' expression to specify unique keys. ' +
1146+
'Repeater: item in items, Duplicate key: object:3, Duplicate value: {}');
11471147

11481148
// recover
11491149
scope.items = [a];
@@ -1162,9 +1162,10 @@ describe('ngRepeat', function() {
11621162
it('should throw error on new duplicates and recover', function() {
11631163
scope.items = [d, d, d];
11641164
scope.$digest();
1165-
expect($exceptionHandler.errors.shift().message).
1166-
toMatch(
1167-
/^\[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: {}/);
1165+
expect($exceptionHandler.errors.shift()).toEqualMinErr('ngRepeat', 'dupes',
1166+
'Duplicates in a repeater are not allowed. ' +
1167+
'Use \'track by\' expression to specify unique keys. ' +
1168+
'Repeater: item in items, Duplicate key: object:9, Duplicate value: {}');
11681169

11691170
// recover
11701171
scope.items = [a];

test/ng/qSpec.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -844,8 +844,8 @@ describe('q', function() {
844844

845845
expect(resolveSpy).not.toHaveBeenCalled();
846846
expect(rejectSpy).toHaveBeenCalled();
847-
expect(rejectSpy.calls.argsFor(0)[0].message).
848-
toMatch(/\[\$q:qcycle\] Expected promise to be resolved with value other than itself/);
847+
expect(rejectSpy.calls.argsFor(0)[0]).toEqualMinErr('$q', 'qcycle',
848+
'Expected promise to be resolved with value other than itself');
849849
});
850850

851851

test/ng/templateRequestSpec.js

+4-6
Original file line numberDiff line numberDiff line change
@@ -126,12 +126,10 @@ describe('$templateRequest', function() {
126126
$templateRequest('tpl.html').catch(function(reason) { err = reason; });
127127
$httpBackend.flush();
128128

129-
expect(err.message).toMatch(new RegExp(
130-
'^\\[\\$compile:tpload] Failed to load template: tpl\\.html ' +
131-
'\\(HTTP status: 404 Not Found\\)'));
132-
expect($exceptionHandler.errors[0].message).toMatch(new RegExp(
133-
'^\\[\\$compile:tpload] Failed to load template: tpl\\.html ' +
134-
'\\(HTTP status: 404 Not Found\\)'));
129+
expect(err).toEqualMinErr('$compile', 'tpload',
130+
'Failed to load template: tpl.html (HTTP status: 404 Not Found)');
131+
expect($exceptionHandler.errors[0]).toEqualMinErr('$compile', 'tpload',
132+
'Failed to load template: tpl.html (HTTP status: 404 Not Found)');
135133
});
136134
});
137135

test/ngResource/resourceSpec.js

+6-6
Original file line numberDiff line numberDiff line change
@@ -1550,9 +1550,9 @@ describe('errors', function() {
15501550

15511551
expect(successSpy).not.toHaveBeenCalled();
15521552
expect(failureSpy).toHaveBeenCalled();
1553-
expect(failureSpy.calls.mostRecent().args[0]).toMatch(
1554-
/^\[\$resource:badcfg\] Error in resource configuration for action `query`\. Expected response to contain an array but got an object \(Request: GET \/Customer\/123\)/
1555-
);
1553+
expect(failureSpy.calls.mostRecent().args[0]).toEqualMinErr('$resource', 'badcfg',
1554+
'Error in resource configuration for action `query`. ' +
1555+
'Expected response to contain an array but got an object (Request: GET /Customer/123)');
15561556
});
15571557

15581558
it('should fail if action expects an array but response is an object', function() {
@@ -1567,9 +1567,9 @@ describe('errors', function() {
15671567

15681568
expect(successSpy).not.toHaveBeenCalled();
15691569
expect(failureSpy).toHaveBeenCalled();
1570-
expect(failureSpy.calls.mostRecent().args[0]).toMatch(
1571-
/^\[\$resource:badcfg\] Error in resource configuration for action `get`\. Expected response to contain an object but got an array \(Request: GET \/Customer\/123\)/
1572-
);
1570+
expect(failureSpy.calls.mostRecent().args[0]).toEqualMinErr('$resource', 'badcfg',
1571+
'Error in resource configuration for action `get`. ' +
1572+
'Expected response to contain an object but got an array (Request: GET /Customer/123)');
15731573
});
15741574
});
15751575

0 commit comments

Comments
 (0)