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

test(*): introduce the toEqualMinErr() custom Jasmine matcher #15216

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 7 additions & 10 deletions test/AngularSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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\\.')
);
Expand All @@ -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);
});
Expand All @@ -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);
});
Expand Down Expand Up @@ -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\\.'));

Expand Down
2 changes: 1 addition & 1 deletion test/auto/injectorSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
});
});
Expand Down
110 changes: 66 additions & 44 deletions test/helpers/matchers.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand All @@ -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),
Expand All @@ -71,6 +107,7 @@ beforeEach(function() {
};
}
},

toBeShown: function() {
return {
compare: generateCompare(false),
Expand All @@ -87,6 +124,7 @@ beforeEach(function() {
};
}
},

toBeHidden: function() {
return {
compare: generateCompare(false),
Expand Down Expand Up @@ -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');
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion test/jqLiteSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1712,7 +1712,7 @@ describe('jqLite', function() {
aElem.on('click', noop);
expect(function() {
aElem.off('click', noop, '.test');
}).toThrowError(/\[jqLite:offargs\]/);
}).toThrowMinErr('jqLite', 'offargs');
});
});

Expand Down
57 changes: 31 additions & 26 deletions test/ng/compileSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1862,8 +1862,8 @@ describe('$compile', function() {
$httpBackend.flush();

expect(sortedHtml(element)).toBe('<div><b class="hello"></b></div>');
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');
})
);

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

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

Expand Down Expand Up @@ -2096,15 +2096,17 @@ describe('$compile', function() {
$templateCache.put('template.html', 'dada');
$compile('<p template></p>');
$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', '<div></div><div></div>');
$compile('<p template></p>');
$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', ' <div></div> \n');
Expand Down Expand Up @@ -2676,9 +2678,9 @@ describe('$compile', function() {
compile('<div class="tiscope-a; scope-b"></div>');
$httpBackend.flush();

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

Expand Down Expand Up @@ -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.');
});
});

Expand Down Expand Up @@ -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('<div><div ng-transclude></div></div>')($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: <div ng-transclude.+'));
error = e;
}

expect(error).toEqualMinErr('ngTransclude', 'orphan',
'Illegal use of ngTransclude directive in the template! ' +
'No parent directive that requires a transclusion found. ' +
'Element: <div ng-transclude');
// we need to do this because different browsers print empty attributes differently
});
});

Expand Down Expand Up @@ -8898,10 +8904,10 @@ describe('$compile', function() {
$rootScope.$digest();

expect($exceptionHandler.errors[0][1]).toBe('<div class="bar" ng-transclude="">');
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: ' +
'<div class="bar" ng-transclude="">'));
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: <div class="bar" ng-transclude="">');
});
});

Expand Down Expand Up @@ -9717,9 +9723,8 @@ describe('$compile', function() {
$compile('<div template first></div>');
$httpBackend.flush();

expect($exceptionHandler.errors[0].message).toMatch(new RegExp(
'^\\[\\$compile:multidir] Multiple directives \\[first, second] asking for ' +
'transclusion on: <p '));
expect($exceptionHandler.errors[0]).toEqualMinErr('$compile', 'multidir',
'Multiple directives [first, second] asking for transclusion on: <p ');
});
});

Expand Down
Loading