From 81b094b2c180d490c093dafe53a69f59e1f8afe7 Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Sat, 3 Nov 2012 00:06:50 -0400 Subject: [PATCH] No ticket: update test suite to pass QUnit globals check in most environments. Close gh-1016. --- test/data/testrunner.js | 9 ++- test/unit/ajax.js | 127 +++++++++++++++++++++++++++++++++++----- test/unit/attributes.js | 7 ++- test/unit/core.js | 89 ++++++++++++---------------- test/unit/event.js | 2 + test/unit/offset.js | 52 +++++++++------- 6 files changed, 196 insertions(+), 90 deletions(-) diff --git a/test/data/testrunner.js b/test/data/testrunner.js index 5f4893431..6304d5142 100644 --- a/test/data/testrunner.js +++ b/test/data/testrunner.js @@ -3,6 +3,11 @@ */ jQuery.noConflict(); +// For checking globals pollution +window[ jQuery.expando ] = undefined; +// ...in Gecko +this.getInterface = this.getInterface; + // Expose Sizzle for Sizzle's selector tests // We remove Sizzle's globalization in jQuery var Sizzle = Sizzle || jQuery.find; @@ -96,7 +101,7 @@ function testSubproject( label, url, risTests ) { } }); - function requireFixture( fnTest ) { + function requireFixture( fn ) { return function() { if ( !fixtureReplaced ) { // Make sure that we retrieved a fixture for the subproject @@ -124,7 +129,7 @@ function testSubproject( label, url, risTests ) { fixtureReplaced = true; } - fnTest.apply( this, arguments ); + fn.apply( this, arguments ); }; } } diff --git a/test/unit/ajax.js b/test/unit/ajax.js index 9367320d2..1f3654a60 100644 --- a/test/unit/ajax.js +++ b/test/unit/ajax.js @@ -1,10 +1,47 @@ -module( "ajax", { - teardown: moduleTeardown -}); +(function() { -if ( jQuery.ajax && ( !isLocal || hasPHP ) ) { + var isOpera = !!window.opera, + jsonpCallbacks = [], + jsonpCallback = jQuery.ajaxSettings.jsonpCallback, + deleteExpando = jQuery.support.deleteExpando, - var isOpera = !!window.opera; + // Ensure that we can cleanup generated JSONP callback functions if checking globals + newjsonpCallback = jsonpCallback && function() { + var callback = jsonpCallback.apply( this, arguments ); + + // Explanation at http://perfectionkills.com/understanding-delete/#ie_bugs + jQuery.globalEval( "var " + callback ); + + return callback; + }; + + module( "ajax", { + setup: deleteExpando ? + function() {} : + function() { + if ( QUnit.config.noglobals ) { + jQuery.ajaxSettings.jsonpCallback = newjsonpCallback; + } + }, + teardown: function() { + // Cleanup JSONP callback functions + jsonpCallbacks = jQuery.map( jsonpCallbacks, QUnit.config.noglobals ? + function( callback ) { + jQuery.globalEval( "try { " + + "delete " + ( deleteExpando ? "window['" + callback + "']" : callback ) + + "; } catch( x ) {}" ); + } : + function() {} + ); + jQuery.ajaxSettings.jsonpCallback = jsonpCallback; + + moduleTeardown.apply( this, arguments ); + } + }); + + if ( !jQuery.ajax || ( isLocal && !hasPHP ) ) { + return; + } test( "jQuery.ajax() - success callbacks", function() { expect( 8 ); @@ -597,6 +634,7 @@ if ( jQuery.ajax && ( !isLocal || hasPHP ) ) { url: loc.protocol + "//" + loc.host + ":" + samePort, beforeSend: function( _, s ) { ok( !s.crossDomain, "Test matching ports are not detected as cross-domain" ); + jsonpCallbacks.push( (/callback=([^&]*)/.exec( s.url ) || [])[1] ); return false; } }); @@ -606,6 +644,7 @@ if ( jQuery.ajax && ( !isLocal || hasPHP ) ) { url: otherProtocol + "//" + loc.host, beforeSend: function( _, s ) { ok( s.crossDomain, "Test different protocols are detected as cross-domain" ); + jsonpCallbacks.push( (/callback=([^&]*)/.exec( s.url ) || [])[1] ); return false; } }); @@ -615,6 +654,7 @@ if ( jQuery.ajax && ( !isLocal || hasPHP ) ) { url: "app:/path", beforeSend: function( _, s ) { ok( s.crossDomain, "Adobe AIR app:/ URL detected as cross-domain" ); + jsonpCallbacks.push( (/callback=([^&]*)/.exec( s.url ) || [])[1] ); return false; } }); @@ -624,6 +664,7 @@ if ( jQuery.ajax && ( !isLocal || hasPHP ) ) { url: loc.protocol + "//example.invalid:" + ( loc.port || 80 ), beforeSend: function( _, s ) { ok( s.crossDomain, "Test different hostnames are detected as cross-domain" ); + jsonpCallbacks.push( (/callback=([^&]*)/.exec( s.url ) || [])[1] ); return false; } }); @@ -633,6 +674,7 @@ if ( jQuery.ajax && ( !isLocal || hasPHP ) ) { url: loc.protocol + "//" + loc.hostname + ":" + otherPort, beforeSend: function( _, s ) { ok( s.crossDomain, "Test different ports are detected as cross-domain" ); + jsonpCallbacks.push( (/callback=([^&]*)/.exec( s.url ) || [])[1] ); return false; } }); @@ -642,6 +684,7 @@ if ( jQuery.ajax && ( !isLocal || hasPHP ) ) { url: "about:blank", beforeSend: function( _, s ) { ok( s.crossDomain, "Test about:blank is detected as cross-domain" ); + jsonpCallbacks.push( (/callback=([^&]*)/.exec( s.url ) || [])[1] ); return false; } }); @@ -652,10 +695,10 @@ if ( jQuery.ajax && ( !isLocal || hasPHP ) ) { crossDomain: true, beforeSend: function( _, s ) { ok( s.crossDomain, "Test forced crossDomain is detected as cross-domain" ); + jsonpCallbacks.push( (/callback=([^&]*)/.exec( s.url ) || [])[1] ); return false; } }); - }); test( ".load() - 404 error callbacks", function() { @@ -899,6 +942,9 @@ if ( jQuery.ajax && ( !isLocal || hasPHP ) ) { jQuery.ajax({ url: url("data/with_fries_over_jsonp.php"), dataType: "jsonp xml", + beforeSend: function( jqXHR, s ) { + jsonpCallbacks.push( (/callback=([^&]*)/.exec( s.url ) || [])[1] ); + }, success: function( resp ) { equal( jQuery( "properties", resp ).length, 1, "properties in responseXML" ); equal( jQuery( "jsconf", resp ).length, 1, "jsconf in responseXML" ); @@ -1466,6 +1512,9 @@ if ( jQuery.ajax && ( !isLocal || hasPHP ) ) { url: "data/jsonp.php?callback=?", dataType: "jsonp", crossDomain: crossDomain, + beforeSend: function( jqXHR, s ) { + jsonpCallbacks.push( (/callback=([^&]*)/.exec( s.url ) || [])[1] ); + }, success: function( data ) { ok( data.data, "JSON results returned (GET, url callback)" ); plus(); @@ -1480,6 +1529,9 @@ if ( jQuery.ajax && ( !isLocal || hasPHP ) ) { url: "data/jsonp.php?callback=??", dataType: "jsonp", crossDomain: crossDomain, + beforeSend: function( jqXHR, s ) { + jsonpCallbacks.push( (/callback=([^&]*)/.exec( s.url ) || [])[1] ); + }, success: function( data ) { ok( data.data, "JSON results returned (GET, url context-free callback)" ); plus(); @@ -1494,6 +1546,9 @@ if ( jQuery.ajax && ( !isLocal || hasPHP ) ) { url: "data/jsonp.php/??", dataType: "jsonp", crossDomain: crossDomain, + beforeSend: function( jqXHR, s ) { + jsonpCallbacks.push( (/jsonp\.php\/([^?]*)/.exec( s.url ) || [])[1] ); + }, success: function( data ) { ok( data.data, "JSON results returned (GET, REST-like)" ); plus(); @@ -1508,6 +1563,9 @@ if ( jQuery.ajax && ( !isLocal || hasPHP ) ) { url: "data/jsonp.php/???json=1", dataType: "jsonp", crossDomain: crossDomain, + beforeSend: function( jqXHR, s ) { + jsonpCallbacks.push( (/jsonp\.php\/([^?]*)/.exec( s.url ) || [])[1] ); + }, success: function( data ) { strictEqual( jQuery.type( data ), "array", "JSON results returned (GET, REST-like with param)" ); plus(); @@ -1534,6 +1592,9 @@ if ( jQuery.ajax && ( !isLocal || hasPHP ) ) { dataType: "jsonp", crossDomain: crossDomain, jsonp: "callback", + beforeSend: function( jqXHR, s ) { + jsonpCallbacks.push( (/callback=([^&]*)/.exec( s.url ) || [])[1] ); + }, success: function( data ) { ok( data["data"], "JSON results returned (GET, data obj callback)" ); plus(); @@ -1544,9 +1605,10 @@ if ( jQuery.ajax && ( !isLocal || hasPHP ) ) { } }); + jQuery.globalEval("var jsonpResults;"); window["jsonpResults"] = function( data ) { ok( data["data"], "JSON results returned (GET, custom callback function)" ); - window["jsonpResults"] = undefined; + jQuery.globalEval("delete jsonpResults;"); plus(); }; @@ -1565,11 +1627,15 @@ if ( jQuery.ajax && ( !isLocal || hasPHP ) ) { } }); + jQuery.globalEval("var functionToCleanUp;"); jQuery.ajax({ url: "data/jsonp.php", dataType: "jsonp", crossDomain: crossDomain, jsonpCallback: "functionToCleanUp", + beforeSend: function() { + jsonpCallbacks.push("functionToCleanUp"); + }, success: function( data ) { ok( data["data"], "JSON results returned (GET, custom callback name to be cleaned up)" ); strictEqual( window["functionToCleanUp"], undefined, "Callback was removed (GET, custom callback name to be cleaned up)" ); @@ -1598,6 +1664,7 @@ if ( jQuery.ajax && ( !isLocal || hasPHP ) ) { } }); + jQuery.globalEval("var XXX;"); jQuery.ajax({ url: "data/jsonp.php?callback=XXX", dataType: "jsonp", @@ -1606,6 +1673,7 @@ if ( jQuery.ajax && ( !isLocal || hasPHP ) ) { crossDomain: crossDomain, beforeSend: function() { ok( /^data\/jsonp.php\?callback=XXX&_=\d+$/.test( this.url ), "The URL wasn't messed with (GET, custom callback name with no url manipulation)" ); + jsonpCallbacks.push("XXX"); plus(); }, success: function( data ) { @@ -1634,6 +1702,9 @@ if ( jQuery.ajax && ( !isLocal || hasPHP ) ) { dataType: "jsonp", crossDomain: crossDomain, data: "callback=?", + beforeSend: function( jqXHR, s ) { + jsonpCallbacks.push( (/callback=([^&]*)/.exec( s.url ) || [])[1] ); + }, success: function( data ) { ok( data.data, "JSON results returned (GET, data callback)" ); plus(); @@ -1649,6 +1720,9 @@ if ( jQuery.ajax && ( !isLocal || hasPHP ) ) { dataType: "jsonp", crossDomain: crossDomain, data: "callback=??", + beforeSend: function( jqXHR, s ) { + jsonpCallbacks.push( (/callback=([^&]*)/.exec( s.url ) || [])[1] ); + }, success: function( data ) { ok( data.data, "JSON results returned (GET, data context-free callback)" ); plus(); @@ -1676,6 +1750,9 @@ if ( jQuery.ajax && ( !isLocal || hasPHP ) ) { url: "data/jsonp.php", dataType: "jsonp", crossDomain: crossDomain, + beforeSend: function( jqXHR, s ) { + jsonpCallbacks.push( (/callback=([^&]*)/.exec( s.url ) || [])[1] ); + }, success: function( data ) { ok( data["data"], "JSON results returned (POST, no callback)" ); plus(); @@ -1692,6 +1769,9 @@ if ( jQuery.ajax && ( !isLocal || hasPHP ) ) { data: "callback=?", dataType: "jsonp", crossDomain: crossDomain, + beforeSend: function( jqXHR, s ) { + jsonpCallbacks.push( (/callback=([^&]*)/.exec( crossDomain ? s.url : s.data ) || [])[1] ); + }, success: function( data ) { ok( data["data"], "JSON results returned (POST, data callback)" ); plus(); @@ -1708,6 +1788,9 @@ if ( jQuery.ajax && ( !isLocal || hasPHP ) ) { jsonp: "callback", dataType: "jsonp", crossDomain: crossDomain, + beforeSend: function( jqXHR, s ) { + jsonpCallbacks.push( (/callback=([^&]*)/.exec( s.url ) || [])[1] ); + }, success: function( data ) { ok( data["data"], "JSON results returned (POST, data obj callback)" ); plus(); @@ -1733,6 +1816,9 @@ if ( jQuery.ajax && ( !isLocal || hasPHP ) ) { url: "data/jsonp.php", dataType: "jsonp", crossDomain: crossDomain, + beforeSend: function( jqXHR, s ) { + jsonpCallbacks.push( (/callback=([^&]*)/.exec( s.url ) || [])[1] ); + }, success: function( data ) { ok( data.data, "JSON results returned (GET, no callback)" ); plus(); @@ -1747,6 +1833,9 @@ if ( jQuery.ajax && ( !isLocal || hasPHP ) ) { url: "data/jsonp.php", dataType: "jsonp", crossDomain: crossDomain, + beforeSend: function( jqXHR, s ) { + jsonpCallbacks.push( (/callback=([^&]*)/.exec( s.url ) || [])[1] ); + }, success: function( data ) { ok( data.data, ( this.alreadyDone ? "this re-used" : "first request" ) + ": JSON results returned (GET, no callback)" ); if ( !this.alreadyDone ) { @@ -1773,8 +1862,9 @@ if ( jQuery.ajax && ( !isLocal || hasPHP ) ) { url: "data/jsonp.php", dataType: "jsonp", crossDomain: crossDomain, - beforeSend: function() { + beforeSend: function( jqXHR, s ) { strictEqual( this.cache, false, "cache must be false on JSON request" ); + jsonpCallbacks.push( (/callback=([^&]*)/.exec( s.url ) || [])[1] ); start(); return false; } @@ -1788,8 +1878,9 @@ if ( jQuery.ajax && ( !isLocal || hasPHP ) ) { url: "data/jsonp.php", dataType: "jsonp", crossDomain: crossDomain, - beforeSend: function() { - this.callback = this.jsonpCallback; + beforeSend: function( jqXHR, s ) { + s.callback = s.jsonpCallback; + jsonpCallbacks.push( (/callback=([^&]*)/.exec( s.url ) || [])[1] ); } }).pipe(function() { var previous = this; @@ -2019,18 +2110,23 @@ if ( jQuery.ajax && ( !isLocal || hasPHP ) ) { asyncTest( "jQuery.getJSON - Using Native JSON", function() { expect( 2 ); - var old = window.JSON; + var restore = "JSON" in window, + old = window.JSON; + jQuery.globalEval("var JSON;"); window.JSON = { parse: function( str ) { ok( true, "Verifying that parse method was run" ); + window.JSON = old; + if ( !restore ) { + jQuery.globalEval("delete JSON;"); + } return true; } }; jQuery.getJSON( url("data/json.php"), function( json ) { - window.JSON = old; - equal( json, true, "Verifying return value" ); + strictEqual( json, true, "Verifying return value" ); start(); }); }); @@ -2760,7 +2856,7 @@ if ( jQuery.ajax && ( !isLocal || hasPHP ) ) { start(); }); }); - + test( "jQuery.ajax - empty json gets to error callback instead of success callback.", function() { expect( 1 ); @@ -2774,4 +2870,5 @@ if ( jQuery.ajax && ( !isLocal || hasPHP ) ) { dataType: "json" }); }); -} + +})(); diff --git a/test/unit/attributes.js b/test/unit/attributes.js index 3ad08a75a..3fa0e869b 100644 --- a/test/unit/attributes.js +++ b/test/unit/attributes.js @@ -340,9 +340,12 @@ test( "attr(String, Object)", function() { }); jQuery.each( [ window, document, obj, "#firstp" ], function( i, elem ) { - var $elem = jQuery( elem ); + // use iframeCallback to avoid generating a new global when testing window + var oldVal = elem.iframeCallback, + $elem = jQuery( elem ); strictEqual( $elem.attr("nonexisting"), undefined, "attr works correctly for non existing attributes (bug #7500)." ); - equal( $elem.attr( "something", "foo" ).attr("something"), "foo", "attr falls back to prop on unsupported arguments" ); + equal( $elem.attr( "iframeCallback", "foo" ).attr("iframeCallback"), "foo", "attr falls back to prop on unsupported arguments" ); + elem.iframeCallback = oldVal; }); var table = jQuery("#table").append("cellcellcellcellcell"), diff --git a/test/unit/core.js b/test/unit/core.js index 3d22b3d9e..14268d8c8 100644 --- a/test/unit/core.js +++ b/test/unit/core.js @@ -194,23 +194,18 @@ test( "selector state", function() { }); test( "globalEval", function() { - expect( 3 ); - jQuery.globalEval( "var globalEvalTest = true;" ); - ok( window.globalEvalTest, "Test variable declarations are global" ); - - window.globalEvalTest = false; + jQuery.globalEval("globalEvalTest = 1;"); + equal( window.globalEvalTest, 1, "Test variable assignments are global" ); - jQuery.globalEval( "globalEvalTest = true;" ); - ok( window.globalEvalTest, "Test variable assignments are global" ); + jQuery.globalEval("var globalEvalTest = 2;"); + equal( window.globalEvalTest, 2, "Test variable declarations are global" ); - window.globalEvalTest = false; + jQuery.globalEval("this.globalEvalTest = 3;"); + equal( window.globalEvalTest, 3, "Test context (this) is the window object" ); - jQuery.globalEval( "this.globalEvalTest = true;" ); - ok( window.globalEvalTest, "Test context (this) is the window object" ); - - window.globalEvalTest = undefined; + jQuery.globalEval("delete globalEvalTest;"); }); test("noConflict", function() { @@ -285,75 +280,67 @@ test("type", function() { asyncTest("isPlainObject", function() { expect(15); - var iframe; + var pass, iframe, doc, + fn = function() {}; // The use case that we want to match - ok(jQuery.isPlainObject({}), "{}"); + ok( jQuery.isPlainObject({}), "{}" ); // Not objects shouldn't be matched - ok(!jQuery.isPlainObject(""), "string"); - ok(!jQuery.isPlainObject(0) && !jQuery.isPlainObject(1), "number"); - ok(!jQuery.isPlainObject(true) && !jQuery.isPlainObject(false), "boolean"); - ok(!jQuery.isPlainObject(null), "null"); - ok(!jQuery.isPlainObject(undefined), "undefined"); + ok( !jQuery.isPlainObject(""), "string" ); + ok( !jQuery.isPlainObject(0) && !jQuery.isPlainObject(1), "number" ); + ok( !jQuery.isPlainObject(true) && !jQuery.isPlainObject(false), "boolean" ); + ok( !jQuery.isPlainObject(null), "null" ); + ok( !jQuery.isPlainObject(undefined), "undefined" ); // Arrays shouldn't be matched - ok(!jQuery.isPlainObject([]), "array"); + ok( !jQuery.isPlainObject([]), "array" ); // Instantiated objects shouldn't be matched - ok(!jQuery.isPlainObject(new Date()), "new Date"); - - var fnplain = function(){}; + ok( !jQuery.isPlainObject(new Date()), "new Date" ); // Functions shouldn't be matched - ok(!jQuery.isPlainObject(fnplain), "fn"); - - /** @constructor */ - var fn = function() {}; + ok( !jQuery.isPlainObject(fn), "fn" ); // Again, instantiated objects shouldn't be matched - ok(!jQuery.isPlainObject(new fn()), "new fn (no methods)"); + ok( !jQuery.isPlainObject(new fn()), "new fn (no methods)" ); // Makes the function a little more realistic // (and harder to detect, incidentally) fn.prototype["someMethod"] = function(){}; // Again, instantiated objects shouldn't be matched - ok(!jQuery.isPlainObject(new fn()), "new fn"); + ok( !jQuery.isPlainObject(new fn()), "new fn" ); // DOM Element - ok(!jQuery.isPlainObject(document.createElement("div")), "DOM Element"); + ok( !jQuery.isPlainObject( document.createElement("div") ), "DOM Element" ); // Window - ok(!jQuery.isPlainObject(window), "window"); + ok( !jQuery.isPlainObject( window ), "window" ); + pass = false; try { jQuery.isPlainObject( window.location ); - ok( true, "Does not throw exceptions on host objects"); - } catch ( e ) { - ok( false, "Does not throw exceptions on host objects -- FAIL"); - } + pass = true; + } catch ( e ) {} + ok( pass, "Does not throw exceptions on host objects" ); + + // Objects from other windows should be matched + window.iframeCallback = function( otherObject, detail ) { + window.iframeCallback = undefined; + iframe.parentNode.removeChild( iframe ); + ok( jQuery.isPlainObject(new otherObject()), "new otherObject" + ( detail ? " - " + detail : "" ) ); + start(); + }; try { - iframe = document.createElement("iframe"); - document.body.appendChild(iframe); - - window.iframeDone = function(otherObject){ - // Objects from other windows should be matched - ok(jQuery.isPlainObject(new otherObject()), "new otherObject"); - document.body.removeChild( iframe ); - start(); - }; - - var doc = iframe.contentDocument || iframe.contentWindow.document; + iframe = jQuery("#qunit-fixture")[0].appendChild( document.createElement("iframe") ); + doc = iframe.contentDocument || iframe.contentWindow.document; doc.open(); - doc.write(""); + doc.write(""); doc.close(); } catch(e) { - document.body.removeChild( iframe ); - - ok(true, "new otherObject - iframes not supported"); - start(); + window.iframeDone( Object, "iframes not supported" ); } }); diff --git a/test/unit/event.js b/test/unit/event.js index 23624731c..db968284f 100644 --- a/test/unit/event.js +++ b/test/unit/event.js @@ -1208,6 +1208,8 @@ test("trigger(eventObject, [data], [fn])", function() { equal( event.isDefaultPrevented(), false, "default not prevented" ); }); +// Explicitly introduce global variable for oldIE so QUnit doesn't complain if checking globals +window.onclick = undefined; test(".trigger() bubbling on disconnected elements (#10489)", function() { expect(2); diff --git a/test/unit/offset.js b/test/unit/offset.js index ac1cc0cbf..3516016e5 100644 --- a/test/unit/offset.js +++ b/test/unit/offset.js @@ -1,26 +1,38 @@ -if ( jQuery.fn.offset ) { +(function() { + +if ( !jQuery.fn.offset ) { + return; +} + +var supportsScroll, supportsFixedPosition, + forceScroll = jQuery("
").css({ width: 2000, height: 2000 }), + checkSupport = function() { + // Only run once + checkSupport = false; + + var checkFixed = jQuery("
").css({ position: "fixed", top: "20px" }).appendTo("#qunit-fixture"); + + // Must append to body because #qunit-fixture is hidden and elements inside it don't have a scrollTop + forceScroll.appendTo("body"); + window.scrollTo( 200, 200 ); + supportsScroll = document.documentElement.scrollTop || document.body.scrollTop; + forceScroll.detach(); + + // Safari subtracts parent border width here (which is 5px) + supportsFixedPosition = checkFixed[0].offsetTop === 20 || checkFixed[0].offsetTop === 15; + checkFixed.remove(); + }; module("offset", { setup: function(){ - // force a scroll value on the main window - // this insures that the results will be wrong - // if the offset method is using the scroll offset - // of the parent window - var forceScroll = jQuery("
").css({ "width": 2000, "height": 2000 }); - // this needs to be body, because #qunit-fixture is hidden and elements inside it don't have a scrollTop - forceScroll.appendTo("body"); - var checkDiv = jQuery("
").appendTo("#qunit-fixture")[0]; + if ( typeof checkSupport === "function" ) { + checkSupport(); + } - window.scrollTo( 200, 200 ); - window.supportsScroll = ( document.documentElement.scrollTop || document.body.scrollTop ); + // Force a scroll value on the main window to ensure incorrect results + // if offset is using the scroll offset of the parent window + forceScroll.appendTo("body"); window.scrollTo( 1, 1 ); - - checkDiv.style.position = "fixed"; - checkDiv.style.top = "20px"; - // safari subtracts parent border width here which is 5px - window.supportsFixedPosition = ( checkDiv.offsetTop === 20 || checkDiv.offsetTop === 15 ); - checkDiv.style.position = checkDiv.style.top = ""; - jQuery( checkDiv ).remove(); - forceScroll.remove(); + forceScroll.detach(); }, teardown: moduleTeardown }); /* @@ -529,4 +541,4 @@ test("fractions (see #7730 and #7885)", function() { div.remove(); }); -} +})(); -- 2.39.5