diff --git a/docs/content/error/$animate/nongcls.ngdoc b/docs/content/error/$animate/nongcls.ngdoc new file mode 100644 index 000000000000..b5774bb46627 --- /dev/null +++ b/docs/content/error/$animate/nongcls.ngdoc @@ -0,0 +1,8 @@ +@ngdoc error +@name $animate:nongcls +@fullName `ng-animate` class not allowed +@description + +This error occurs, when trying to set `$animateProvider.classNameFilter()` to a RegExp containing +the reserved `ng-animate` class. Since `.ng-animate` will be added/removed by `$animate` itself, +using it as part of the `classNameFilter` RegExp is not allowed. diff --git a/src/ng/animate.js b/src/ng/animate.js index 923126eed045..1f8054b5fade 100644 --- a/src/ng/animate.js +++ b/src/ng/animate.js @@ -179,6 +179,7 @@ var $$CoreAnimateQueueProvider = /** @this */ function() { */ var $AnimateProvider = ['$provide', /** @this */ function($provide) { var provider = this; + var classNameFilter = null; this.$$registeredAnimations = Object.create(null); @@ -247,15 +248,16 @@ var $AnimateProvider = ['$provide', /** @this */ function($provide) { */ this.classNameFilter = function(expression) { if (arguments.length === 1) { - this.$$classNameFilter = (expression instanceof RegExp) ? expression : null; - if (this.$$classNameFilter) { - var reservedRegex = new RegExp('(\\s+|\\/)' + NG_ANIMATE_CLASSNAME + '(\\s+|\\/)'); - if (reservedRegex.test(this.$$classNameFilter.toString())) { - throw $animateMinErr('nongcls','$animateProvider.classNameFilter(regex) prohibits accepting a regex value which matches/contains the "{0}" CSS class.', NG_ANIMATE_CLASSNAME); + classNameFilter = (expression instanceof RegExp) ? expression : null; + if (classNameFilter) { + var reservedRegex = new RegExp('[(\\s|\\/)]' + NG_ANIMATE_CLASSNAME + '[(\\s|\\/)]'); + if (reservedRegex.test(classNameFilter.toString())) { + classNameFilter = null; + throw $animateMinErr('nongcls', '$animateProvider.classNameFilter(regex) prohibits accepting a regex value which matches/contains the "{0}" CSS class.', NG_ANIMATE_CLASSNAME); } } } - return this.$$classNameFilter; + return classNameFilter; }; this.$get = ['$$animateQueue', function($$animateQueue) { diff --git a/test/ngAnimate/animateSpec.js b/test/ngAnimate/animateSpec.js index e1f37c4e4462..dfa7fffbc2c5 100644 --- a/test/ngAnimate/animateSpec.js +++ b/test/ngAnimate/animateSpec.js @@ -255,29 +255,41 @@ describe('animations', function() { }); }); - it('should throw a minErr if a regex value is used which partially contains or fully matches the `ng-animate` CSS class', function() { + it('should throw a minErr if a regex value is used which partially contains or fully matches the `ng-animate` CSS class', module(function($animateProvider) { - assertError(/ng-animate/, true); - assertError(/first ng-animate last/, true); - assertError(/ng-animate-special/, false); - assertError(/first ng-animate-special last/, false); - assertError(/first ng-animate ng-animate-special last/, true); - - function assertError(regex, bool) { - var expectation = expect(function() { + expect(setFilter(/ng-animate/)).toThrowMinErr('$animate', 'nongcls'); + expect(setFilter(/first ng-animate last/)).toThrowMinErr('$animate', 'nongcls'); + expect(setFilter(/first ng-animate ng-animate-special last/)).toThrowMinErr('$animate', 'nongcls'); + expect(setFilter(/(ng-animate)/)).toThrowMinErr('$animate', 'nongcls'); + expect(setFilter(/(foo|ng-animate|bar)/)).toThrowMinErr('$animate', 'nongcls'); + expect(setFilter(/(foo|)ng-animate(|bar)/)).toThrowMinErr('$animate', 'nongcls'); + + expect(setFilter(/ng-animater/)).not.toThrow(); + expect(setFilter(/my-ng-animate/)).not.toThrow(); + expect(setFilter(/first ng-animater last/)).not.toThrow(); + expect(setFilter(/first my-ng-animate last/)).not.toThrow(); + + function setFilter(regex) { + return function() { $animateProvider.classNameFilter(regex); - }); + }; + } + }) + ); - var message = '$animateProvider.classNameFilter(regex) prohibits accepting a regex value which matches/contains the "ng-animate" CSS class.'; + it('should clear the `classNameFilter` if a disallowed RegExp is passed', + module(function($animateProvider) { + var validRegex = /no-ng-animate/; + var invalidRegex = /no ng-animate/; - if (bool) { - expectation.toThrowMinErr('$animate', 'nongcls', message); - } else { - expectation.not.toThrowMinErr('$animate', 'nongcls', message); - } - } - }); - }); + $animateProvider.classNameFilter(validRegex); + expect($animateProvider.classNameFilter()).toEqual(validRegex); + + // eslint-disable-next-line no-empty + try { $animateProvider.classNameFilter(invalidRegex); } catch (err) {} + expect($animateProvider.classNameFilter()).toBeNull(); + }) + ); it('should complete the leave DOM operation in case the classNameFilter fails', function() { module(function($animateProvider) {