diff --git a/.gitignore b/.gitignore index 463dea426..eae5df6e6 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ /dist /node_modules + +/test/node_smoke_tests/lib/ensure_iterability.js diff --git a/.jscsrc b/.jscsrc index fa91a9d9e..14f349133 100644 --- a/.jscsrc +++ b/.jscsrc @@ -1,5 +1,6 @@ { "preset": "jquery", - "excludeFiles": [ "external", "src/intro.js", "src/outro.js" ] + "excludeFiles": [ "external", "src/intro.js", "src/outro.js", + "test/node_smoke_tests/lib/ensure_iterability.js" ] } diff --git a/.jshintignore b/.jshintignore index 19f1b9c52..1ddafd635 100644 --- a/.jshintignore +++ b/.jshintignore @@ -9,3 +9,4 @@ test/data/readywaitasset.js test/data/readywaitloader.js test/data/support/csp.js test/data/support/getComputedSupport.js +test/node_smoke_tests/lib/ensure_iterability.js diff --git a/Gruntfile.js b/Gruntfile.js index d22807bf4..0bf20d454 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -30,6 +30,18 @@ module.exports = function( grunt ) { cache: "build/.sizecache.json" } }, + babel: { + options: { + sourceMap: "inline", + retainLines: true + }, + nodeSmokeTests: { + files: { + "test/node_smoke_tests/lib/ensure_iterability.js": + "test/node_smoke_tests/lib/ensure_iterability_es6.js" + } + } + }, build: { all: { dest: "dist/jquery.js", diff --git a/build/tasks/node_smoke_tests.js b/build/tasks/node_smoke_tests.js index 5c23dae2b..9334516d9 100644 --- a/build/tasks/node_smoke_tests.js +++ b/build/tasks/node_smoke_tests.js @@ -5,7 +5,7 @@ module.exports = function( grunt ) { var fs = require( "fs" ), spawnTest = require( "./lib/spawn_test.js" ), testsDir = "./test/node_smoke_tests/", - nodeSmokeTests = [ "jsdom" ]; + nodeSmokeTests = [ "jsdom", "babel:nodeSmokeTests" ]; // Fire up all tests defined in test/node_smoke_tests/*.js in spawned sub-processes. // All the files under test/node_smoke_tests/*.js are supposed to exit with 0 code diff --git a/package.json b/package.json index c0ee70281..5d8299534 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,9 @@ "dependencies": {}, "devDependencies": { "commitplease": "2.0.0", + "core-js": "0.9.17", "grunt": "0.4.5", + "grunt-babel": "5.0.1", "grunt-cli": "0.1.13", "grunt-compare-size": "0.4.0", "grunt-contrib-jshint": "0.11.2", diff --git a/src/core.js b/src/core.js index 88b9a3c5d..ba6eeceb8 100644 --- a/src/core.js +++ b/src/core.js @@ -425,6 +425,16 @@ jQuery.extend({ support: support }); +// JSHint would error on this code due to the Symbol not being defined in ES5. +// Defining this global in .jshintrc would create a danger of using the global +// unguarded in another place, it seems safer to just disable JSHint for these +// three lines. +/* jshint ignore: start */ +if ( typeof Symbol === "function" ) { + jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; +} +/* jshint ignore: end */ + // Populate the class2type map jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) { diff --git a/test/.jshintrc b/test/.jshintrc index b55594f4f..7aef665c6 100644 --- a/test/.jshintrc +++ b/test/.jshintrc @@ -22,6 +22,7 @@ "define": false, "DOMParser": false, "Promise": false, + "Symbol": false, "QUnit": false, "ok": false, "equal": false, diff --git a/test/node_smoke_tests/iterable_with_native_symbol.js b/test/node_smoke_tests/iterable_with_native_symbol.js new file mode 100644 index 000000000..3376ebdc5 --- /dev/null +++ b/test/node_smoke_tests/iterable_with_native_symbol.js @@ -0,0 +1,8 @@ +"use strict"; + +if ( typeof Symbol === "undefined" ) { + console.log( "Symbols not supported, skipping the test..." ); + process.exit(); +} + +require( "./lib/ensure_iterability_es6" )(); diff --git a/test/node_smoke_tests/iterable_with_symbol_polyfill.js b/test/node_smoke_tests/iterable_with_symbol_polyfill.js new file mode 100644 index 000000000..dd377f19c --- /dev/null +++ b/test/node_smoke_tests/iterable_with_symbol_polyfill.js @@ -0,0 +1,13 @@ +/* jshint esnext: true */ + +"use strict"; + +var assert = require( "assert" ); + +delete global.Symbol; +require( "core-js" ); + +assert.strictEqual( typeof Symbol, "function", "Expected Symbol to be a function" ); +assert.notEqual( typeof Symbol.iterator, "symbol", "Expected Symbol.iterator to be polyfilled" ); + +require( "./lib/ensure_iterability" )(); diff --git a/test/node_smoke_tests/lib/ensure_iterability_es6.js b/test/node_smoke_tests/lib/ensure_iterability_es6.js new file mode 100644 index 000000000..ebe68539e --- /dev/null +++ b/test/node_smoke_tests/lib/ensure_iterability_es6.js @@ -0,0 +1,25 @@ +/* jshint esnext: true */ + +"use strict"; + +var assert = require( "assert" ); + +module.exports = function ensureIterability() { + require( "jsdom" ).env( "", function( errors, window ) { + assert.ifError( errors ); + + var i, + ensureJQuery = require( "./ensure_jquery" ), + jQuery = require( "../../../dist/jquery.js" )( window ), + elem = jQuery( "
" ), + result = ""; + + ensureJQuery( jQuery ); + + for ( i of elem ) { + result += i.nodeName; + } + + assert.strictEqual( result, "DIVSPANA", "for-of doesn't work on jQuery objects" ); + } ); +}; diff --git a/test/unit/core.js b/test/unit/core.js index 0a018dea4..d8370637b 100644 --- a/test/unit/core.js +++ b/test/unit/core.js @@ -1538,3 +1538,23 @@ testIframeWithCallback( "Don't call window.onready (#14802)", "core/onready.html equal( error, false, "no call to user-defined onready" ); } ); + +test( "Iterability of jQuery objects (gh-1693)", function() { + /* jshint unused: false */ + expect( 1 ); + + var i, elem, result; + + if ( typeof Symbol === "function" ) { + + elem = jQuery( "" ); + result = ""; + + try { + eval( "for ( i of elem ) { result += i.nodeName; }" ); + } catch ( e ) {} + equal( result, "DIVSPANA", "for-of works on jQuery objects" ); + } else { + ok( true, "The browser doesn't support Symbols" ); + } +} );