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

Commit dd6d04d

Browse files
gkalpakpetebacondarwin
authored andcommitted
test(*): introduce the toEqualMinErr() custom Jasmine matcher
Closes #15216
1 parent 5ba5b0d commit dd6d04d

File tree

6 files changed

+113
-83
lines changed

6 files changed

+113
-83
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,40 +344,16 @@ 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
}
356+
335357
}
336358
});
337359
});

test/ng/compileSpec.js

+19-13
Original file line numberDiff line numberDiff line change
@@ -2081,15 +2081,17 @@ describe('$compile', function() {
20812081
$templateCache.put('template.html', 'dada');
20822082
$compile('<p template></p>');
20832083
$rootScope.$digest();
2084-
expect($exceptionHandler.errors.pop().message).
2085-
toMatch(/\[\$compile:tplrt\] Template for directive 'template' must have exactly one root element\. template\.html/);
2084+
expect($exceptionHandler.errors.pop()).toEqualMinErr('$compile', 'tplrt',
2085+
'Template for directive \'template\' must have exactly one root element. ' +
2086+
'template.html');
20862087

20872088
// multi root
20882089
$templateCache.put('template.html', '<div></div><div></div>');
20892090
$compile('<p template></p>');
20902091
$rootScope.$digest();
2091-
expect($exceptionHandler.errors.pop().message).
2092-
toMatch(/\[\$compile:tplrt\] Template for directive 'template' must have exactly one root element\. template\.html/);
2092+
expect($exceptionHandler.errors.pop()).toEqualMinErr('$compile', 'tplrt',
2093+
'Template for directive \'template\' must have exactly one root element. ' +
2094+
'template.html');
20932095

20942096
// ws is ok
20952097
$templateCache.put('template.html', ' <div></div> \n');
@@ -4584,7 +4586,8 @@ describe('$compile', function() {
45844586
// Update val to trigger the unstable onChanges, which will result in an error
45854587
$rootScope.$apply('a = 42');
45864588
expect($exceptionHandler.errors.length).toEqual(1);
4587-
expect($exceptionHandler.errors[0].toString()).toContain('[$compile:infchng] 10 $onChanges() iterations reached.');
4589+
expect($exceptionHandler.errors[0]).
4590+
toEqualMinErr('$compile', 'infchng', '10 $onChanges() iterations reached.');
45884591
});
45894592
});
45904593

@@ -8146,8 +8149,8 @@ describe('$compile', function() {
81468149
$rootScope.x = 'root';
81478150
$rootScope.$apply();
81488151
expect(element.text()).toEqual('W:iso-1-2;T:root-2-3;');
8149-
expect(jqLite(element.find('span')[0]).text()).toEqual('T:root-2-3');
8150-
expect(jqLite(element.find('span')[1]).text()).toEqual(';');
8152+
expect(jqLite(element.find('span')[0]).text()).toEqual('T:root-2-3');
8153+
expect(jqLite(element.find('span')[1]).text()).toEqual(';');
81518154
});
81528155
});
81538156

@@ -8667,16 +8670,19 @@ describe('$compile', function() {
86678670

86688671
it('should throw on an ng-transclude element inside no transclusion directive', function() {
86698672
inject(function($rootScope, $compile) {
8670-
// we need to do this because different browsers print empty attributes differently
8673+
var error;
8674+
86718675
try {
86728676
$compile('<div><div ng-transclude></div></div>')($rootScope);
86738677
} catch (e) {
8674-
expect(e.message).toMatch(new RegExp(
8675-
'^\\[ngTransclude:orphan\\] ' +
8676-
'Illegal use of ngTransclude directive in the template! ' +
8677-
'No parent directive that requires a transclusion found\\. ' +
8678-
'Element: <div ng-transclude.+'));
8678+
error = e;
86798679
}
8680+
8681+
expect(error).toEqualMinErr('ngTransclude', 'orphan',
8682+
'Illegal use of ngTransclude directive in the template! ' +
8683+
'No parent directive that requires a transclusion found. ' +
8684+
'Element: <div ng-transclude');
8685+
// we need to do this because different browsers print empty attributes differently
86808686
});
86818687
});
86828688

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
@@ -832,8 +832,8 @@ describe('q', function() {
832832

833833
expect(resolveSpy).not.toHaveBeenCalled();
834834
expect(rejectSpy).toHaveBeenCalled();
835-
expect(rejectSpy.calls.argsFor(0)[0].message).
836-
toMatch(/\[\$q:qcycle\] Expected promise to be resolved with value other than itself/);
835+
expect(rejectSpy.calls.argsFor(0)[0]).toEqualMinErr('$q', 'qcycle',
836+
'Expected promise to be resolved with value other than itself');
837837
});
838838

839839

test/ngResource/resourceSpec.js

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

14871487
expect(successSpy).not.toHaveBeenCalled();
14881488
expect(failureSpy).toHaveBeenCalled();
1489-
expect(failureSpy.calls.mostRecent().args[0]).toMatch(
1490-
/^\[\$resource:badcfg\] Error in resource configuration for action `query`\. Expected response to contain an array but got an object \(Request: GET \/Customer\/123\)/
1491-
);
1489+
expect(failureSpy.calls.mostRecent().args[0]).toEqualMinErr('$resource', 'badcfg',
1490+
'Error in resource configuration for action `query`. ' +
1491+
'Expected response to contain an array but got an object (Request: GET /Customer/123)');
14921492
});
14931493

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

15041504
expect(successSpy).not.toHaveBeenCalled();
15051505
expect(failureSpy).toHaveBeenCalled();
1506-
expect(failureSpy.calls.mostRecent().args[0]).toMatch(
1507-
/^\[\$resource:badcfg\] Error in resource configuration for action `get`\. Expected response to contain an object but got an array \(Request: GET \/Customer\/123\)/
1508-
);
1506+
expect(failureSpy.calls.mostRecent().args[0]).toEqualMinErr('$resource', 'badcfg',
1507+
'Error in resource configuration for action `get`. ' +
1508+
'Expected response to contain an object but got an array (Request: GET /Customer/123)');
15091509
});
15101510
});
15111511

test/ngRoute/routeSpec.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -829,7 +829,8 @@ describe('$route', function() {
829829
$rootScope.$digest();
830830

831831
$httpBackend.flush();
832-
expect($exceptionHandler.errors.pop().message).toContain('[$compile:tpload] Failed to load template: r1.html');
832+
expect($exceptionHandler.errors.pop()).
833+
toEqualMinErr('$compile', 'tpload', 'Failed to load template: r1.html');
833834

834835
$httpBackend.expectGET('r2.html').respond('');
835836
$location.path('/r2');

0 commit comments

Comments
 (0)