diff --git a/.travis.yml b/.travis.yml index 06dcd0246372..7f9e64a277fb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,7 @@ env: - JOB=unit BROWSER_PROVIDER=saucelabs - JOB=docs-e2e BROWSER_PROVIDER=saucelabs - JOB=e2e TEST_TARGET=jqlite BROWSER_PROVIDER=saucelabs + - JOB=e2e TEST_TARGET=jquery-old BROWSER_PROVIDER=saucelabs - JOB=e2e TEST_TARGET=jquery BROWSER_PROVIDER=saucelabs global: - CXX=g++-4.8 # node 4 likes the G++ v4.8 compiler diff --git a/Gruntfile.js b/Gruntfile.js index 86139b9d03e9..32abd4932cca 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -79,6 +79,7 @@ module.exports = function(grunt) { tests: { jqlite: 'karma-jqlite.conf.js', jquery: 'karma-jquery.conf.js', + 'jquery-old': 'karma-jquery-old.conf.js', docs: 'karma-docs.conf.js', modules: 'karma-modules.conf.js' }, @@ -87,6 +88,7 @@ module.exports = function(grunt) { autotest: { jqlite: 'karma-jqlite.conf.js', jquery: 'karma-jquery.conf.js', + 'jquery-old': 'karma-jquery-old.conf.js', modules: 'karma-modules.conf.js', docs: 'karma-docs.conf.js' }, @@ -344,10 +346,11 @@ module.exports = function(grunt) { //alias tasks grunt.registerTask('test', 'Run unit, docs and e2e tests with Karma', ['jshint', 'jscs', 'package', 'test:unit', 'test:promises-aplus', 'tests:docs', 'test:protractor']); grunt.registerTask('test:jqlite', 'Run the unit tests with Karma' , ['tests:jqlite']); - grunt.registerTask('test:jquery', 'Run the jQuery unit tests with Karma', ['tests:jquery']); + grunt.registerTask('test:jquery', 'Run the jQuery (latest) unit tests with Karma', ['tests:jquery']); + grunt.registerTask('test:jquery-old', 'Run the jQuery 2.1 unit tests with Karma', ['tests:jquery-old']); grunt.registerTask('test:modules', 'Run the Karma module tests with Karma', ['build', 'tests:modules']); grunt.registerTask('test:docs', 'Run the doc-page tests with Karma', ['package', 'tests:docs']); - grunt.registerTask('test:unit', 'Run unit, jQuery and Karma module tests with Karma', ['test:jqlite', 'test:jquery', 'test:modules']); + grunt.registerTask('test:unit', 'Run unit, jQuery and Karma module tests with Karma', ['test:jqlite', 'test:jquery', 'test:jquery-old', 'test:modules']); grunt.registerTask('test:protractor', 'Run the end to end tests with Protractor and keep a test server running in the background', ['webdriver', 'connect:testserver', 'protractor:normal']); grunt.registerTask('test:travis-protractor', 'Run the end to end tests with Protractor for Travis CI builds', ['connect:testserver', 'protractor:travis']); grunt.registerTask('test:ci-protractor', 'Run the end to end tests with Protractor for Jenkins CI builds', ['webdriver', 'connect:testserver', 'protractor:jenkins']); diff --git a/angularFiles.js b/angularFiles.js index 6be3f249bb2f..12915af380f2 100755 --- a/angularFiles.js +++ b/angularFiles.js @@ -226,6 +226,15 @@ var angularFiles = { '@angularTest' ], + 'karmaJqueryOld': [ + 'bower_components/jquery-old/dist/jquery.js', + 'test/jquery_alias.js', + '@angularSrc', + '@angularSrcModules', + '@angularScenario', + '@angularTest' + ], + 'karmaJqueryExclude': [ 'src/angular-bootstrap.js', 'src/ngScenario/angular-bootstrap.js', diff --git a/bower.json b/bower.json index ec7cfbbadcce..44782de6c294 100644 --- a/bower.json +++ b/bower.json @@ -2,7 +2,8 @@ "name": "AngularJS", "license": "MIT", "devDependencies": { - "jquery": "2.1.1", + "jquery": "2.2.1", + "jquery-old": "jquery#2.1.4", "closure-compiler": "https://dl.google.com/closure-compiler/compiler-20140814.zip", "ng-closure-runner": "https://raw.github.com/angular/ng-closure-runner/v0.2.3/assets/ng-closure-runner.zip" } diff --git a/docs/bower.json b/docs/bower.json index d5a6a99b1e71..dc07141a77d4 100644 --- a/docs/bower.json +++ b/docs/bower.json @@ -1,7 +1,7 @@ { "name": "AngularJS-docs-app", "dependencies": { - "jquery": "2.1.1", + "jquery": "2.2.1", "lunr.js": "0.5.12", "open-sans-fontface": "1.0.4", "google-code-prettify": "1.0.1", diff --git a/docs/content/tutorial/step_07.ngdoc b/docs/content/tutorial/step_07.ngdoc index 8f9b654e08c8..d001388a99db 100644 --- a/docs/content/tutorial/step_07.ngdoc +++ b/docs/content/tutorial/step_07.ngdoc @@ -35,7 +35,7 @@ We are using [Bower][bower] to install client-side dependencies. This step upda "dependencies": { "angular": "1.4.x", "angular-mocks": "1.4.x", - "jquery": "~2.1.1", + "jquery": "~2.2.1", "bootstrap": "~3.1.1", "angular-route": "1.4.x" } diff --git a/docs/content/tutorial/step_11.ngdoc b/docs/content/tutorial/step_11.ngdoc index 946a7185ecd0..7259dd297836 100644 --- a/docs/content/tutorial/step_11.ngdoc +++ b/docs/content/tutorial/step_11.ngdoc @@ -34,7 +34,7 @@ We are using [Bower][bower] to install client side dependencies. This step upda "dependencies": { "angular": "1.4.x", "angular-mocks": "1.4.x", - "jquery": "~2.1.1", + "jquery": "~2.2.1", "bootstrap": "~3.1.1", "angular-route": "1.4.x", "angular-resource": "1.4.x" diff --git a/docs/content/tutorial/step_12.ngdoc b/docs/content/tutorial/step_12.ngdoc index 79b3233b78a3..51d804d3373e 100644 --- a/docs/content/tutorial/step_12.ngdoc +++ b/docs/content/tutorial/step_12.ngdoc @@ -38,7 +38,7 @@ We are using [Bower][bower] to install client side dependencies. This step upda "dependencies": { "angular": "1.4.x", "angular-mocks": "1.4.x", - "jquery": "~2.1.1", + "jquery": "~2.2.1", "bootstrap": "~3.1.1", "angular-route": "1.4.x", "angular-resource": "1.4.x", @@ -49,7 +49,7 @@ We are using [Bower][bower] to install client side dependencies. This step upda * `"angular-animate": "1.4.x"` tells bower to install a version of the angular-animate component that is compatible with version 1.4.x. -* `"jquery": "~2.1.1"` tells bower to install the 2.1.1 version of jQuery. Note that this is not an +* `"jquery": "~2.2.1"` tells bower to install the 2.2.1 version of jQuery. Note that this is not an Angular library, it is the standard jQuery library. We can use bower to install a wide range of 3rd party libraries. diff --git a/karma-jquery-old.conf.js b/karma-jquery-old.conf.js new file mode 100644 index 000000000000..7ea14d91dd8e --- /dev/null +++ b/karma-jquery-old.conf.js @@ -0,0 +1,18 @@ +'use strict'; + +var angularFiles = require('./angularFiles'); +var sharedConfig = require('./karma-shared.conf'); + +module.exports = function(config) { + sharedConfig(config, {testName: 'AngularJS: jQuery', logFile: 'karma-jquery.log'}); + + config.set({ + files: angularFiles.mergeFilesFor('karmaJqueryOld'), + exclude: angularFiles.mergeFilesFor('karmaJqueryExclude'), + + junitReporter: { + outputFile: 'test_out/jquery.xml', + suite: 'jQuery' + } + }); +}; diff --git a/scripts/travis/build.sh b/scripts/travis/build.sh index 0dbaa6bbe609..a34d0599a150 100755 --- a/scripts/travis/build.sh +++ b/scripts/travis/build.sh @@ -20,12 +20,12 @@ elif [ $JOB = "unit" ]; then elif [ $JOB = "docs-e2e" ]; then grunt test:travis-protractor --specs "docs/app/e2e/**/*.scenario.js" elif [ $JOB = "e2e" ]; then - if [ $TEST_TARGET = "jquery" ]; then + if [ $TEST_TARGET = "jquery" -o $TEST_TARGET = "jquery-old" ]; then export USE_JQUERY=1 fi export TARGET_SPECS="build/docs/ptore2e/**/default_test.js" - if [ $TEST_TARGET = "jquery" ]; then + if [ $TEST_TARGET = "jquery" -o $TEST_TARGET = "jquery-old" ]; then TARGET_SPECS="build/docs/ptore2e/**/jquery_test.js" fi diff --git a/src/.jshintrc b/src/.jshintrc index fc2ae74034a5..70c481c3010e 100644 --- a/src/.jshintrc +++ b/src/.jshintrc @@ -143,8 +143,6 @@ "getAliasedAttrName": false, "createEventHandler": false, "JQLitePrototype": false, - "addEventListenerFn": false, - "removeEventListenerFn": false, "jqLiteIsTextNode": false, "jqLiteDocumentLoaded": false, diff --git a/src/jqLite.js b/src/jqLite.js index da55bae864ad..59219ae6ad8c 100644 --- a/src/jqLite.js +++ b/src/jqLite.js @@ -12,8 +12,6 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* global JQLitePrototype: true, - addEventListenerFn: true, - removeEventListenerFn: true, BOOLEAN_ATTR: true, ALIASED_ATTR: true, */ @@ -121,13 +119,7 @@ JQLite.expando = 'ng339'; var jqCache = JQLite.cache = {}, - jqId = 1, - addEventListenerFn = function(element, type, fn) { - element.addEventListener(type, fn, false); - }, - removeEventListenerFn = function(element, type, fn) { - element.removeEventListener(type, fn, false); - }; + jqId = 1; /* * !!! This is an undocumented "private" function !!! @@ -325,7 +317,7 @@ function jqLiteOff(element, type, fn, unsupported) { if (!type) { for (type in events) { if (type !== '$destroy') { - removeEventListenerFn(element, type, handle); + element.removeEventListener(type, handle); } delete events[type]; } @@ -337,7 +329,7 @@ function jqLiteOff(element, type, fn, unsupported) { arrayRemove(listenerFns || [], fn); } if (!(isDefined(fn) && listenerFns && listenerFns.length > 0)) { - removeEventListenerFn(element, type, handle); + element.removeEventListener(type, handle); delete events[type]; } }; @@ -874,7 +866,7 @@ forEach({ eventFns = events[type] = []; eventFns.specialHandlerWrapper = specialHandlerWrapper; if (type !== '$destroy' && !noEventListener) { - addEventListenerFn(element, type, handle); + element.addEventListener(type, handle); } } diff --git a/src/ng/directive/form.js b/src/ng/directive/form.js index 30a932a46347..d88b02c5508f 100644 --- a/src/ng/directive/form.js +++ b/src/ng/directive/form.js @@ -497,13 +497,13 @@ var formDirectiveFactory = function(isNgForm) { event.preventDefault(); }; - addEventListenerFn(formElement[0], 'submit', handleFormSubmission); + formElement[0].addEventListener('submit', handleFormSubmission); // unregister the preventDefault listener so that we don't not leak memory but in a // way that will achieve the prevention of the default action. formElement.on('$destroy', function() { $timeout(function() { - removeEventListenerFn(formElement[0], 'submit', handleFormSubmission); + formElement[0].removeEventListener('submit', handleFormSubmission); }, 0, false); }); } diff --git a/src/ng/httpBackend.js b/src/ng/httpBackend.js index 0b16b34a7748..6d17fd44f7bf 100644 --- a/src/ng/httpBackend.js +++ b/src/ng/httpBackend.js @@ -172,8 +172,8 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc script.async = true; callback = function(event) { - removeEventListenerFn(script, "load", callback); - removeEventListenerFn(script, "error", callback); + script.removeEventListener('load', callback); + script.removeEventListener('error', callback); rawDocument.body.removeChild(script); script = null; var status = -1; @@ -192,8 +192,8 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc } }; - addEventListenerFn(script, "load", callback); - addEventListenerFn(script, "error", callback); + script.addEventListener('load', callback); + script.addEventListener('error', callback); rawDocument.body.appendChild(script); return callback; } diff --git a/test/.jshintrc b/test/.jshintrc index 0d85795b3545..74f54c34f267 100644 --- a/test/.jshintrc +++ b/test/.jshintrc @@ -117,8 +117,6 @@ "getBooleanAttrName": false, "createEventHandler": false, "JQLitePrototype": false, - "addEventListenerFn": false, - "removeEventListenerFn": false, "jqLiteDocumentLoaded": false, /* apis.js */ diff --git a/test/helpers/testabilityPatch.js b/test/helpers/testabilityPatch.js index 84c766577e36..cd95a6cfe11f 100644 --- a/test/helpers/testabilityPatch.js +++ b/test/helpers/testabilityPatch.js @@ -1,14 +1,6 @@ /* global jQuery: true, uid: true, jqCache: true */ 'use strict'; -/** - * Here is the problem: http://bugs.jquery.com/ticket/7292 - * basically jQuery treats change event on some browsers (IE) as a - * special event and changes it form 'change' to 'click/keydown' and - * few others. This horrible hack removes the special treatment - */ -if (window._jQuery) _jQuery.event.special.change = undefined; - if (window.bindJQuery) bindJQuery(); var supportTests = { diff --git a/test/jqLiteSpec.js b/test/jqLiteSpec.js index a059bf8f93e3..80c6f0d153fb 100644 --- a/test/jqLiteSpec.js +++ b/test/jqLiteSpec.js @@ -707,8 +707,12 @@ describe('jqLite', function() { describe('class', function() { it('should properly do with SVG elements', function() { - // this is a jqLite & SVG only test (jquery doesn't behave this way right now, which is a bug) - if (!window.SVGElement || !_jqLiteMode) return; + // This is not working correctly in jQuery prior to v3.0. + // See https://github.com/jquery/jquery/issues/2199 for details. + var jQueryVersion = window.jQuery && window.jQuery.fn.jquery.split('.')[0]; + var jQuery3xOrNewer = jQueryVersion && (Number(jQueryVersion) >= 3); + if (!_jqLiteMode && !jQuery3xOrNewer) return; + var svg = jqLite(''); var rect = svg.children(); @@ -1532,6 +1536,10 @@ describe('jqLite', function() { describe('native listener deregistration', function() { + var jQueryVersionString = window.jQuery && window.jQuery.fn.jquery; + var jQueryMajor = jQueryVersionString && Number(jQueryVersionString.split('.')[0]); + var jQueryMinor = jQueryVersionString && Number(jQueryVersionString.split('.')[1]); + var jQuery21 = jQueryMajor === 2 && jQueryMinor === 1; it('should deregister the native listener when all jqLite listeners for given type are gone ' + 'after off("eventName", listener) call', function() { @@ -1543,12 +1551,22 @@ describe('jqLite', function() { var jqLiteListener = function() {}; aElem.on('click', jqLiteListener); - expect(addEventListenerSpy).toHaveBeenCalledOnceWith('click', jasmine.any(Function), false); + // jQuery <2.2 passes the non-needed `false` useCapture parameter. + // See https://github.com/jquery/jquery/issues/2199 for details. + if (jQuery21) { + expect(addEventListenerSpy).toHaveBeenCalledOnceWith('click', jasmine.any(Function), false); + } else { + expect(addEventListenerSpy).toHaveBeenCalledOnceWith('click', jasmine.any(Function)); + } nativeListenerFn = addEventListenerSpy.mostRecentCall.args[1]; expect(removeEventListenerSpy).not.toHaveBeenCalled(); aElem.off('click', jqLiteListener); - expect(removeEventListenerSpy).toHaveBeenCalledOnceWith('click', nativeListenerFn, false); + if (jQuery21) { + expect(removeEventListenerSpy).toHaveBeenCalledOnceWith('click', nativeListenerFn, false); + } else { + expect(removeEventListenerSpy).toHaveBeenCalledOnceWith('click', nativeListenerFn); + } }); @@ -1560,12 +1578,20 @@ describe('jqLite', function() { var nativeListenerFn; aElem.on('click', function() {}); - expect(addEventListenerSpy).toHaveBeenCalledOnceWith('click', jasmine.any(Function), false); + if (jQuery21) { + expect(addEventListenerSpy).toHaveBeenCalledOnceWith('click', jasmine.any(Function), false); + } else { + expect(addEventListenerSpy).toHaveBeenCalledOnceWith('click', jasmine.any(Function)); + } nativeListenerFn = addEventListenerSpy.mostRecentCall.args[1]; expect(removeEventListenerSpy).not.toHaveBeenCalled(); aElem.off('click'); - expect(removeEventListenerSpy).toHaveBeenCalledOnceWith('click', nativeListenerFn, false); + if (jQuery21) { + expect(removeEventListenerSpy).toHaveBeenCalledOnceWith('click', nativeListenerFn, false); + } else { + expect(removeEventListenerSpy).toHaveBeenCalledOnceWith('click', nativeListenerFn); + } }); @@ -1577,19 +1603,32 @@ describe('jqLite', function() { var nativeListenerFn; aElem.on('click', function() {}); - expect(addEventListenerSpy).toHaveBeenCalledOnceWith('click', jasmine.any(Function), false); + if (jQuery21) { + expect(addEventListenerSpy).toHaveBeenCalledOnceWith('click', jasmine.any(Function), false); + } else { + expect(addEventListenerSpy).toHaveBeenCalledOnceWith('click', jasmine.any(Function)); + } nativeListenerFn = addEventListenerSpy.mostRecentCall.args[1]; addEventListenerSpy.reset(); aElem.on('dblclick', function() {}); - expect(addEventListenerSpy).toHaveBeenCalledOnceWith('dblclick', nativeListenerFn, false); + if (jQuery21) { + expect(addEventListenerSpy).toHaveBeenCalledOnceWith('dblclick', nativeListenerFn, false); + } else { + expect(addEventListenerSpy).toHaveBeenCalledOnceWith('dblclick', nativeListenerFn); + } expect(removeEventListenerSpy).not.toHaveBeenCalled(); aElem.off('click dblclick'); - expect(removeEventListenerSpy).toHaveBeenCalledWith('click', nativeListenerFn, false); - expect(removeEventListenerSpy).toHaveBeenCalledWith('dblclick', nativeListenerFn, false); + if (jQuery21) { + expect(removeEventListenerSpy).toHaveBeenCalledWith('click', nativeListenerFn, false); + expect(removeEventListenerSpy).toHaveBeenCalledWith('dblclick', nativeListenerFn, false); + } else { + expect(removeEventListenerSpy).toHaveBeenCalledWith('click', nativeListenerFn); + expect(removeEventListenerSpy).toHaveBeenCalledWith('dblclick', nativeListenerFn); + } expect(removeEventListenerSpy.callCount).toBe(2); }); @@ -1602,17 +1641,30 @@ describe('jqLite', function() { var nativeListenerFn; aElem.on('click', function() {}); - expect(addEventListenerSpy).toHaveBeenCalledOnceWith('click', jasmine.any(Function), false); + if (jQuery21) { + expect(addEventListenerSpy).toHaveBeenCalledOnceWith('click', jasmine.any(Function), false); + } else { + expect(addEventListenerSpy).toHaveBeenCalledOnceWith('click', jasmine.any(Function)); + } nativeListenerFn = addEventListenerSpy.mostRecentCall.args[1]; addEventListenerSpy.reset(); aElem.on('dblclick', function() {}); - expect(addEventListenerSpy).toHaveBeenCalledOnceWith('dblclick', nativeListenerFn, false); + if (jQuery21) { + expect(addEventListenerSpy).toHaveBeenCalledOnceWith('dblclick', nativeListenerFn, false); + } else { + expect(addEventListenerSpy).toHaveBeenCalledOnceWith('dblclick', nativeListenerFn); + } aElem.off(); - expect(removeEventListenerSpy).toHaveBeenCalledWith('click', nativeListenerFn, false); - expect(removeEventListenerSpy).toHaveBeenCalledWith('dblclick', nativeListenerFn, false); + if (jQuery21) { + expect(removeEventListenerSpy).toHaveBeenCalledWith('click', nativeListenerFn, false); + expect(removeEventListenerSpy).toHaveBeenCalledWith('dblclick', nativeListenerFn, false); + } else { + expect(removeEventListenerSpy).toHaveBeenCalledWith('click', nativeListenerFn); + expect(removeEventListenerSpy).toHaveBeenCalledWith('dblclick', nativeListenerFn); + } expect(removeEventListenerSpy.callCount).toBe(2); }); }); diff --git a/test/ng/directive/formSpec.js b/test/ng/directive/formSpec.js index 500be66d25d3..b0038688f1ec 100644 --- a/test/ng/directive/formSpec.js +++ b/test/ng/directive/formSpec.js @@ -396,7 +396,7 @@ describe('form', function() { submitted = true; }; - addEventListenerFn(doc[0], 'submit', assertPreventDefaultListener); + doc[0].addEventListener('submit', assertPreventDefaultListener); browserTrigger(doc.find('input')); @@ -410,7 +410,7 @@ describe('form', function() { expect(submitted).toBe(true); // prevent mem leak in test - removeEventListenerFn(doc[0], 'submit', assertPreventDefaultListener); + doc[0].removeEventListener('submit', assertPreventDefaultListener); }); }); @@ -447,7 +447,7 @@ describe('form', function() { $compile(doc)(scope); - addEventListenerFn(form[0], 'submit', assertPreventDefaultListener); + form[0].addEventListener('submit', assertPreventDefaultListener); browserTrigger(doc.find('button'), 'click'); @@ -466,7 +466,7 @@ describe('form', function() { // now. (i) // prevent mem leak in test - removeEventListenerFn(form[0], 'submit', assertPreventDefaultListener); + form[0].removeEventListener('submit', assertPreventDefaultListener); }); }));