jquery/test/unit/offset.js
Timmy Willison 8eb35e8513
Tests: fix supportsScroll feature test in offset
- iOS 12 and below do not support scrollTop/Left on the
  window or document. Before 82169df, window.supportsScroll
  was always undefined. There were 2 issues: (1) iOS <=12 do
  support scroll props on the body, so the support test
  wasn't helpful. (2) one test checked the wrong window and
  the value was always undefined, which meant those tests
  never ran in any browser.

Closes gh-5506
2024-06-10 14:50:28 -04:00

846 lines
35 KiB
JavaScript

( function() {
if ( !includesModule( "offset" ) ) {
return;
}
var alwaysScrollable, supportsFixedPosition, supportsScroll,
forceScroll = supportjQuery( "<div></div>" ).css( { width: 2000, height: 2000 } ),
checkSupport = function( assert ) {
// Only run once
checkSupport = false;
var checkFixed = supportjQuery( "<div/>" )
.css( { position: "fixed", top: "20px" } )
.appendTo( "#qunit-fixture" );
supportsFixedPosition = checkFixed[ 0 ].offsetTop === 20;
checkFixed.remove();
// Append forceScroll to the body instead of #qunit-fixture because the latter is hidden
forceScroll.appendTo( "body" );
window.scrollTo( 200, 200 );
supportsScroll = !!document.documentElement.scrollTop;
forceScroll.detach();
// Support: iOS <=7
// Hijack the iframe test infrastructure to detect viewport scrollability
// for pages with position:fixed document element
var done = assert.async();
testIframe(
null,
"offset/boxes.html",
function( assert, $, win, doc ) {
var scrollTop = win.pageYOffset,
scrollLeft = win.pageXOffset;
doc.documentElement.style.position = "fixed";
win.scrollTo( scrollLeft, scrollTop );
alwaysScrollable = win.pageXOffset !== 0;
done();
},
function mockQUnit_test( _, testCallback ) {
setTimeout( function() {
testCallback( assert );
} );
}
);
};
QUnit.module( "offset", { beforeEach: function( assert ) {
if ( typeof checkSupport === "function" ) {
checkSupport( assert );
}
// 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 );
forceScroll.detach();
}, afterEach: moduleTeardown } );
QUnit.test( "empty set", function( assert ) {
assert.expect( 2 );
assert.strictEqual( jQuery().offset(), undefined, "offset() returns undefined for empty set (trac-11962)" );
assert.strictEqual( jQuery().position(), undefined, "position() returns undefined for empty set (trac-11962)" );
} );
QUnit.test( "disconnected element", function( assert ) {
assert.expect( 4 );
var result = jQuery( document.createElement( "div" ) ).offset();
// These tests are solely for main/compat consistency
// Retrieving offset on disconnected/hidden elements is not officially
// valid input, but will return zeros for back-compat
assert.equal( result.top, 0, "Retrieving offset on disconnected elements returns zeros (gh-2310)" );
assert.equal( result.left, 0, "Retrieving offset on disconnected elements returns zeros (gh-2310)" );
assert.equal( Object.keys( result ).length, 2, "Retrieving offset on disconnected elements returns offset object (gh-3167)" );
assert.equal( jQuery.isPlainObject( result ), true, "Retrieving offset on disconnected elements returns plain object (gh-3612)" );
} );
QUnit.test( "hidden (display: none) element", function( assert ) {
assert.expect( 4 );
var node = jQuery( "<div style='display: none'></div>" ).appendTo( "#qunit-fixture" ),
result = node.offset();
node.remove();
// These tests are solely for main/compat consistency
// Retrieving offset on disconnected/hidden elements is not officially
// valid input, but will return zeros for back-compat
assert.equal( result.top, 0, "Retrieving offset on hidden elements returns zeros (gh-2310)" );
assert.equal( result.left, 0, "Retrieving offset on hidden elements returns zeros (gh-2310)" );
assert.equal( Object.keys( result ).length, 2, "Retrieving offset on hidden elements returns offset object (gh-3167)" );
assert.equal( jQuery.isPlainObject( result ), true, "Retrieving offset on hidden elements returns plain object (gh-3612)" );
} );
QUnit.test( "0 sized element", function( assert ) {
assert.expect( 4 );
var node = jQuery( "<div style='margin: 5px; width: 0; height: 0'></div>" ).appendTo( "#qunit-fixture" ),
result = node.offset();
node.remove();
assert.notEqual( result.top, 0, "Retrieving offset on 0 sized elements (gh-3167)" );
assert.notEqual( result.left, 0, "Retrieving offset on 0 sized elements (gh-3167)" );
assert.equal( Object.keys( result ).length, 2, "Retrieving offset on 0 sized elements returns offset object (gh-3167)" );
assert.equal( jQuery.isPlainObject( result ), true, "Retrieving offset on 0 sized elements returns plain object (gh-3612)" );
} );
QUnit.test( "hidden (visibility: hidden) element", function( assert ) {
assert.expect( 4 );
var node = jQuery( "<div style='margin: 5px; visibility: hidden'></div>" ).appendTo( "#qunit-fixture" ),
result = node.offset();
node.remove();
assert.notEqual( result.top, 0, "Retrieving offset on visibility:hidden elements (gh-3167)" );
assert.notEqual( result.left, 0, "Retrieving offset on visibility:hidden elements (gh-3167)" );
assert.equal( Object.keys( result ).length, 2, "Retrieving offset on visibility:hidden elements returns offset object (gh-3167)" );
assert.equal( jQuery.isPlainObject( result ), true, "Retrieving offset on visibility:hidden elements returns plain object (gh-3612)" );
} );
QUnit.test( "normal element", function( assert ) {
assert.expect( 4 );
var node = jQuery( "<div>" ).appendTo( "#qunit-fixture" ),
offset = node.offset(),
position = node.position();
node.remove();
assert.equal( Object.keys( offset ).length, 2, "Retrieving offset on normal elements returns offset object (gh-3612)" );
assert.equal( jQuery.isPlainObject( offset ), true, "Retrieving offset on normal elements returns plain object (gh-3612)" );
assert.equal( Object.keys( position ).length, 2, "Retrieving position on normal elements returns offset object (gh-3612)" );
assert.equal( jQuery.isPlainObject( position ), true, "Retrieving position on normal elements returns plain object (gh-3612)" );
} );
testIframe( "absolute", "offset/absolute.html", function( assert, $, iframe ) {
assert.expect( 4 );
var doc = iframe.document,
tests;
// get offset
tests = [
{ "id": "#absolute-1", "top": 1, "left": 1 }
];
jQuery.each( tests, function() {
assert.equal( jQuery( this.id, doc ).offset().top, this.top, "jQuery('" + this.id + "').offset().top" );
assert.equal( jQuery( this.id, doc ).offset().left, this.left, "jQuery('" + this.id + "').offset().left" );
} );
// get position
tests = [
{ "id": "#absolute-1", "top": 0, "left": 0 }
];
jQuery.each( tests, function() {
assert.equal( jQuery( this.id, doc ).position().top, this.top, "jQuery('" + this.id + "').position().top" );
assert.equal( jQuery( this.id, doc ).position().left, this.left, "jQuery('" + this.id + "').position().left" );
} );
} );
testIframe( "absolute", "offset/absolute.html", function( assert, $ ) {
assert.expect( 178 );
var tests, offset;
// get offset tests
tests = [
{ "id": "#absolute-1", "top": 1, "left": 1 },
{ "id": "#absolute-1-1", "top": 5, "left": 5 },
{ "id": "#absolute-1-1-1", "top": 9, "left": 9 },
{ "id": "#absolute-2", "top": 20, "left": 20 }
];
jQuery.each( tests, function() {
assert.equal( $( this.id ).offset().top, this.top, "jQuery('" + this.id + "').offset().top" );
assert.equal( $( this.id ).offset().left, this.left, "jQuery('" + this.id + "').offset().left" );
} );
// get position
tests = [
{ "id": "#absolute-1", "top": 0, "left": 0 },
{ "id": "#absolute-1-1", "top": 1, "left": 1 },
{ "id": "#absolute-1-1-1", "top": 1, "left": 1 },
{ "id": "#absolute-2", "top": 19, "left": 19 }
];
jQuery.each( tests, function() {
assert.equal( $( this.id ).position().top, this.top, "jQuery('" + this.id + "').position().top" );
assert.equal( $( this.id ).position().left, this.left, "jQuery('" + this.id + "').position().left" );
} );
// test trac-5781
offset = $( "#positionTest" ).offset( { "top": 10, "left": 10 } ).offset();
assert.equal( offset.top, 10, "Setting offset on element with position absolute but 'auto' values." );
assert.equal( offset.left, 10, "Setting offset on element with position absolute but 'auto' values." );
// set offset
tests = [
{ "id": "#absolute-2", "top": 30, "left": 30 },
{ "id": "#absolute-2", "top": 10, "left": 10 },
{ "id": "#absolute-2", "top": -1, "left": -1 },
{ "id": "#absolute-2", "top": 19, "left": 19 },
{ "id": "#absolute-1-1-1", "top": 15, "left": 15 },
{ "id": "#absolute-1-1-1", "top": 5, "left": 5 },
{ "id": "#absolute-1-1-1", "top": -1, "left": -1 },
{ "id": "#absolute-1-1-1", "top": 9, "left": 9 },
{ "id": "#absolute-1-1", "top": 10, "left": 10 },
{ "id": "#absolute-1-1", "top": 0, "left": 0 },
{ "id": "#absolute-1-1", "top": -1, "left": -1 },
{ "id": "#absolute-1-1", "top": 5, "left": 5 },
{ "id": "#absolute-1", "top": 2, "left": 2 },
{ "id": "#absolute-1", "top": 0, "left": 0 },
{ "id": "#absolute-1", "top": -1, "left": -1 },
{ "id": "#absolute-1", "top": 1, "left": 1 }
];
jQuery.each( tests, function() {
$( this.id ).offset( { "top": this.top, "left": this.left } );
assert.equal( $( this.id ).offset().top, this.top, "jQuery('" + this.id + "').offset({ top: " + this.top + " })" );
assert.equal( $( this.id ).offset().left, this.left, "jQuery('" + this.id + "').offset({ left: " + this.left + " })" );
var top = this.top, left = this.left;
$( this.id ).offset( function( i, val ) {
assert.equal( val.top, top, "Verify incoming top position." );
assert.equal( val.left, left, "Verify incoming top position." );
return { "top": top + 1, "left": left + 1 };
} );
assert.equal( $( this.id ).offset().top, this.top + 1, "jQuery('" + this.id + "').offset({ top: " + ( this.top + 1 ) + " })" );
assert.equal( $( this.id ).offset().left, this.left + 1, "jQuery('" + this.id + "').offset({ left: " + ( this.left + 1 ) + " })" );
$( this.id )
.offset( { "left": this.left + 2 } )
.offset( { "top": this.top + 2 } );
assert.equal( $( this.id ).offset().top, this.top + 2, "Setting one property at a time." );
assert.equal( $( this.id ).offset().left, this.left + 2, "Setting one property at a time." );
$( this.id ).offset( { "top": this.top, "left": this.left, "using": function( props ) {
$( this ).css( {
"top": props.top + 1,
"left": props.left + 1
} );
} } );
assert.equal( $( this.id ).offset().top, this.top + 1, "jQuery('" + this.id + "').offset({ top: " + ( this.top + 1 ) + ", using: fn })" );
assert.equal( $( this.id ).offset().left, this.left + 1, "jQuery('" + this.id + "').offset({ left: " + ( this.left + 1 ) + ", using: fn })" );
} );
} );
testIframe( "relative", "offset/relative.html", function( assert, $ ) {
assert.expect( 64 );
// get offset
var tests = [
{ "id": "#relative-1", "top": 7, "left": 7 },
{ "id": "#relative-1-1", "top": 15, "left": 15 },
{ "id": "#relative-2", "top": 142, "left": 27 },
{ "id": "#relative-2-1", "top": 149, "left": 52 }
];
jQuery.each( tests, function() {
assert.equal( $( this.id ).offset().top, this.top, "jQuery('" + this.id + "').offset().top" );
assert.equal( $( this.id ).offset().left, this.left, "jQuery('" + this.id + "').offset().left" );
} );
// get position
tests = [
{ "id": "#relative-1", "top": 6, "left": 6 },
{ "id": "#relative-1-1", "top": 5, "left": 5 },
{ "id": "#relative-2", "top": 141, "left": 26 },
{ "id": "#relative-2-1", "top": 5, "left": 5 }
];
jQuery.each( tests, function() {
assert.equal( $( this.id ).position().top, this.top, "jQuery('" + this.id + "').position().top" );
assert.equal( $( this.id ).position().left, this.left, "jQuery('" + this.id + "').position().left" );
} );
// set offset
tests = [
{ "id": "#relative-2", "top": 200, "left": 50 },
{ "id": "#relative-2", "top": 100, "left": 10 },
{ "id": "#relative-2", "top": -5, "left": -5 },
{ "id": "#relative-2", "top": 142, "left": 27 },
{ "id": "#relative-1-1", "top": 100, "left": 100 },
{ "id": "#relative-1-1", "top": 5, "left": 5 },
{ "id": "#relative-1-1", "top": -1, "left": -1 },
{ "id": "#relative-1-1", "top": 15, "left": 15 },
{ "id": "#relative-1", "top": 100, "left": 100 },
{ "id": "#relative-1", "top": 0, "left": 0 },
{ "id": "#relative-1", "top": -1, "left": -1 },
{ "id": "#relative-1", "top": 7, "left": 7 }
];
jQuery.each( tests, function() {
$( this.id ).offset( { "top": this.top, "left": this.left } );
assert.equal( $( this.id ).offset().top, this.top, "jQuery('" + this.id + "').offset({ top: " + this.top + " })" );
assert.equal( $( this.id ).offset().left, this.left, "jQuery('" + this.id + "').offset({ left: " + this.left + " })" );
$( this.id ).offset( { "top": this.top, "left": this.left, "using": function( props ) {
$( this ).css( {
"top": props.top + 1,
"left": props.left + 1
} );
} } );
assert.equal( $( this.id ).offset().top, this.top + 1, "jQuery('" + this.id + "').offset({ top: " + ( this.top + 1 ) + ", using: fn })" );
assert.equal( $( this.id ).offset().left, this.left + 1, "jQuery('" + this.id + "').offset({ left: " + ( this.left + 1 ) + ", using: fn })" );
} );
} );
testIframe( "static", "offset/static.html", function( assert, $ ) {
assert.expect( 80 );
// get offset
var tests = [
{ "id": "#static-1", "top": 7, "left": 7 },
{ "id": "#static-1-1", "top": 15, "left": 15 },
{ "id": "#static-1-1-1", "top": 23, "left": 23 },
{ "id": "#static-2", "top": 122, left: 7 }
];
jQuery.each( tests, function() {
assert.equal( $( this.id ).offset().top, this.top, "jQuery('" + this.id + "').offset().top" );
assert.equal( $( this.id ).offset().left, this.left, "jQuery('" + this.id + "').offset().left" );
} );
// get position
tests = [
{ "id": "#static-1", "top": 6, "left": 6 },
{ "id": "#static-1-1", "top": 14, "left": 14 },
{ "id": "#static-1-1-1", "top": 22, "left": 22 },
{ "id": "#static-2", "top": 121, "left": 6 }
];
jQuery.each( tests, function() {
assert.equal( $( this.id ).position().top, this.top, "jQuery('" + this.top + "').position().top" );
assert.equal( $( this.id ).position().left, this.left, "jQuery('" + this.left + "').position().left" );
} );
// set offset
tests = [
{ "id": "#static-2", "top": 200, "left": 200 },
{ "id": "#static-2", "top": 100, "left": 100 },
{ "id": "#static-2", "top": -2, "left": -2 },
{ "id": "#static-2", "top": 121, "left": 6 },
{ "id": "#static-1-1-1", "top": 50, "left": 50 },
{ "id": "#static-1-1-1", "top": 10, "left": 10 },
{ "id": "#static-1-1-1", "top": -1, "left": -1 },
{ "id": "#static-1-1-1", "top": 22, "left": 22 },
{ "id": "#static-1-1", "top": 25, "left": 25 },
{ "id": "#static-1-1", "top": 10, "left": 10 },
{ "id": "#static-1-1", "top": -3, "left": -3 },
{ "id": "#static-1-1", "top": 14, "left": 14 },
{ "id": "#static-1", "top": 30, "left": 30 },
{ "id": "#static-1", "top": 2, "left": 2 },
{ "id": "#static-1", "top": -2, "left": -2 },
{ "id": "#static-1", "top": 7, "left": 7 }
];
jQuery.each( tests, function() {
$( this.id ).offset( { "top": this.top, "left": this.left } );
assert.equal( $( this.id ).offset().top, this.top, "jQuery('" + this.id + "').offset({ top: " + this.top + " })" );
assert.equal( $( this.id ).offset().left, this.left, "jQuery('" + this.id + "').offset({ left: " + this.left + " })" );
$( this.id ).offset( { "top": this.top, "left": this.left, "using": function( props ) {
$( this ).css( {
"top": props.top + 1,
"left": props.left + 1
} );
} } );
assert.equal( $( this.id ).offset().top, this.top + 1, "jQuery('" + this.id + "').offset({ top: " + ( this.top + 1 ) + ", using: fn })" );
assert.equal( $( this.id ).offset().left, this.left + 1, "jQuery('" + this.id + "').offset({ left: " + ( this.left + 1 ) + ", using: fn })" );
} );
} );
testIframe( "fixed", "offset/fixed.html", function( assert, $ ) {
assert.expect( 38 );
var tests, $noTopLeft;
tests = [
{
"id": "#fixed-1",
"offsetTop": 1001,
"offsetLeft": 1001,
"positionTop": 0,
"positionLeft": 0
},
{
"id": "#fixed-2",
"offsetTop": 1021,
"offsetLeft": 1021,
"positionTop": 20,
"positionLeft": 20
}
];
jQuery.each( tests, function() {
if ( !supportsScroll ) {
assert.ok( true, "Browser doesn't support scroll position." );
assert.ok( true, "Browser doesn't support scroll position." );
assert.ok( true, "Browser doesn't support scroll position." );
assert.ok( true, "Browser doesn't support scroll position." );
assert.ok( true, "Browser doesn't support scroll position." );
assert.ok( true, "Browser doesn't support scroll position." );
} else if ( supportsFixedPosition ) {
assert.equal( jQuery.isPlainObject( $( this.id ).offset() ), true, "jQuery('" + this.id + "').offset() is plain object" );
assert.equal( jQuery.isPlainObject( $( this.id ).position() ), true, "jQuery('" + this.id + "').position() is plain object" );
assert.equal( $( this.id ).offset().top, this.offsetTop, "jQuery('" + this.id + "').offset().top" );
assert.equal( $( this.id ).position().top, this.positionTop, "jQuery('" + this.id + "').position().top" );
assert.equal( $( this.id ).offset().left, this.offsetLeft, "jQuery('" + this.id + "').offset().left" );
assert.equal( $( this.id ).position().left, this.positionLeft, "jQuery('" + this.id + "').position().left" );
} else {
// need to have same number of assertions
assert.ok( true, "Fixed position is not supported" );
assert.ok( true, "Fixed position is not supported" );
assert.ok( true, "Fixed position is not supported" );
assert.ok( true, "Fixed position is not supported" );
assert.ok( true, "Fixed position is not supported" );
assert.ok( true, "Fixed position is not supported" );
}
} );
tests = [
{ "id": "#fixed-1", "top": 100, "left": 100 },
{ "id": "#fixed-1", "top": 0, "left": 0 },
{ "id": "#fixed-1", "top": -4, "left": -4 },
{ "id": "#fixed-2", "top": 200, "left": 200 },
{ "id": "#fixed-2", "top": 0, "left": 0 },
{ "id": "#fixed-2", "top": -5, "left": -5 }
];
jQuery.each( tests, function() {
if ( supportsFixedPosition ) {
$( this.id ).offset( { "top": this.top, "left": this.left } );
assert.equal( $( this.id ).offset().top, this.top, "jQuery('" + this.id + "').offset({ top: " + this.top + " })" );
assert.equal( $( this.id ).offset().left, this.left, "jQuery('" + this.id + "').offset({ left: " + this.left + " })" );
$( this.id ).offset( { "top": this.top, "left": this.left, "using": function( props ) {
$( this ).css( {
"top": props.top + 1,
"left": props.left + 1
} );
} } );
assert.equal( $( this.id ).offset().top, this.top + 1, "jQuery('" + this.id + "').offset({ top: " + ( this.top + 1 ) + ", using: fn })" );
assert.equal( $( this.id ).offset().left, this.left + 1, "jQuery('" + this.id + "').offset({ left: " + ( this.left + 1 ) + ", using: fn })" );
} else {
// need to have same number of assertions
assert.ok( true, "Fixed position is not supported" );
assert.ok( true, "Fixed position is not supported" );
assert.ok( true, "Fixed position is not supported" );
assert.ok( true, "Fixed position is not supported" );
}
} );
// Bug 8316
$noTopLeft = $( "#fixed-no-top-left" );
if ( supportsFixedPosition ) {
assert.equal( $noTopLeft.offset().top, 1007, "Check offset top for fixed element with no top set" );
assert.equal( $noTopLeft.offset().left, 1007, "Check offset left for fixed element with no left set" );
} else {
// need to have same number of assertions
assert.ok( true, "Fixed position is not supported" );
assert.ok( true, "Fixed position is not supported" );
}
} );
testIframe( "table", "offset/table.html", function( assert, $ ) {
assert.expect( 4 );
assert.equal( $( "#table-1" ).offset().top, 6, "jQuery('#table-1').offset().top" );
assert.equal( $( "#table-1" ).offset().left, 6, "jQuery('#table-1').offset().left" );
assert.equal( $( "#th-1" ).offset().top, 10, "jQuery('#th-1').offset().top" );
assert.equal( $( "#th-1" ).offset().left, 10, "jQuery('#th-1').offset().left" );
} );
testIframe( "scroll", "offset/scroll.html", function( assert, $, win ) {
assert.expect( 26 );
assert.equal( $( "#scroll-1" ).offset().top, 7, "jQuery('#scroll-1').offset().top" );
assert.equal( $( "#scroll-1" ).offset().left, 7, "jQuery('#scroll-1').offset().left" );
assert.equal( $( "#scroll-1-1" ).offset().top, 11, "jQuery('#scroll-1-1').offset().top" );
assert.equal( $( "#scroll-1-1" ).offset().left, 11, "jQuery('#scroll-1-1').offset().left" );
// These tests are solely for main/compat consistency
// Retrieving offset on disconnected/hidden elements is not officially
// valid input, but will return zeros for back-compat
assert.equal( $( "#hidden" ).offset().top, 0, "Hidden elements do not subtract scroll" );
assert.equal( $( "#hidden" ).offset().left, 0, "Hidden elements do not subtract scroll" );
// scroll offset tests .scrollTop/Left
assert.equal( $( "#scroll-1" ).scrollTop(), 5, "jQuery('#scroll-1').scrollTop()" );
assert.equal( $( "#scroll-1" ).scrollLeft(), 5, "jQuery('#scroll-1').scrollLeft()" );
assert.equal( $( "#scroll-1-1" ).scrollTop(), 0, "jQuery('#scroll-1-1').scrollTop()" );
assert.equal( $( "#scroll-1-1" ).scrollLeft(), 0, "jQuery('#scroll-1-1').scrollLeft()" );
// scroll method chaining
assert.equal( $( "#scroll-1" ).scrollTop( undefined ).scrollTop(), 5, ".scrollTop(undefined) is chainable (trac-5571)" );
assert.equal( $( "#scroll-1" ).scrollLeft( undefined ).scrollLeft(), 5, ".scrollLeft(undefined) is chainable (trac-5571)" );
win.name = "test";
if ( !supportsScroll ) {
assert.ok( true, "Browser doesn't support scroll position." );
assert.ok( true, "Browser doesn't support scroll position." );
assert.ok( true, "Browser doesn't support scroll position." );
assert.ok( true, "Browser doesn't support scroll position." );
} else {
assert.equal( $( win ).scrollTop(), 1000, "jQuery(window).scrollTop()" );
assert.equal( $( win ).scrollLeft(), 1000, "jQuery(window).scrollLeft()" );
assert.equal( $( win.document ).scrollTop(), 1000, "jQuery(document).scrollTop()" );
assert.equal( $( win.document ).scrollLeft(), 1000, "jQuery(document).scrollLeft()" );
}
// test jQuery using parent window/document
// jQuery reference here is in the iframe
window.scrollTo( 0, 0 );
assert.equal( $( window ).scrollTop(), 0, "jQuery(window).scrollTop() other window" );
assert.equal( $( window ).scrollLeft(), 0, "jQuery(window).scrollLeft() other window" );
assert.equal( $( document ).scrollTop(), 0, "jQuery(window).scrollTop() other document" );
assert.equal( $( document ).scrollLeft(), 0, "jQuery(window).scrollLeft() other document" );
// Tests scrollTop/Left with empty jquery objects
assert.notEqual( $().scrollTop( 100 ), null, "jQuery().scrollTop(100) testing setter on empty jquery object" );
assert.notEqual( $().scrollLeft( 100 ), null, "jQuery().scrollLeft(100) testing setter on empty jquery object" );
assert.notEqual( $().scrollTop( null ), null, "jQuery().scrollTop(null) testing setter on empty jquery object" );
assert.notEqual( $().scrollLeft( null ), null, "jQuery().scrollLeft(null) testing setter on empty jquery object" );
assert.strictEqual( $().scrollTop(), undefined, "jQuery().scrollTop() testing getter on empty jquery object" );
assert.strictEqual( $().scrollLeft(), undefined, "jQuery().scrollLeft() testing getter on empty jquery object" );
} );
testIframe( "body", "offset/body.html", function( assert, $ ) {
assert.expect( 4 );
assert.equal( $( "body" ).offset().top, 1, "jQuery('#body').offset().top" );
assert.equal( $( "body" ).offset().left, 1, "jQuery('#body').offset().left" );
assert.equal( $( "#firstElement" ).position().left, 5, "$('#firstElement').position().left" );
assert.equal( $( "#firstElement" ).position().top, 5, "$('#firstElement').position().top" );
} );
QUnit.test( "chaining", function( assert ) {
assert.expect( 3 );
var coords = { "top": 1, "left": 1 };
assert.equal( jQuery( "#absolute-1" ).offset( coords ).jquery, jQuery.fn.jquery, "offset(coords) returns jQuery object" );
assert.equal( jQuery( "#non-existent" ).offset( coords ).jquery, jQuery.fn.jquery, "offset(coords) with empty jQuery set returns jQuery object" );
assert.equal( jQuery( "#absolute-1" ).offset( undefined ).jquery, jQuery.fn.jquery, "offset(undefined) returns jQuery object (trac-5571)" );
} );
// Test complex content under a variety of <html>/<body> positioning styles
( function() {
var POSITION_VALUES = [ "static", "relative", "absolute", "fixed" ],
// Use shorthands for describing an element's relevant properties
BOX_PROPS =
( "top left marginTop marginLeft borderTop borderLeft paddingTop paddingLeft" +
" style parent" ).split( /\s+/g ),
props = function() {
var propObj = {};
supportjQuery.each( arguments, function( i, value ) {
propObj[ BOX_PROPS[ i ] ] = value;
} );
return propObj;
},
// Values must stay synchronized with test/data/offset/boxes.html
divProps = function( position, parentId ) {
return props( 8, 4, 16, 8, 4, 2, 32, 16, position, parentId );
},
htmlProps = function( position ) {
return props( position === "static" ? 0 : 4096, position === "static" ? 0 : 2048,
64, 32, 128, 64, 256, 128, position );
},
bodyProps = function( position ) {
return props( position === "static" ? 0 : 8192, position === "static" ? 0 : 4096,
512, 256, 1024, 512, 2048, 1024, position,
position !== "fixed" && "documentElement" );
};
function getExpectations( htmlPos, bodyPos, scrollTop, scrollLeft ) {
// Initialize data about page elements
var expectations = {
"documentElement": htmlProps( htmlPos ),
"body": bodyProps( bodyPos ),
"relative": divProps( "relative", "body" ),
"relative-relative": divProps( "relative", "relative" ),
"relative-absolute": divProps( "absolute", "relative" ),
"absolute": divProps( "absolute", "body" ),
"absolute-relative": divProps( "relative", "absolute" ),
"absolute-absolute": divProps( "absolute", "absolute" ),
"fixed": divProps( "fixed" ),
"fixed-relative": divProps( "relative", "fixed" ),
"fixed-absolute": divProps( "absolute", "fixed" )
};
// Define position and offset expectations for page elements
supportjQuery.each( expectations, function( id, props ) {
var parent = expectations[ props.parent ],
// position() relates an element's margin box to its offset parent's padding box
pos = props.pos = {
top: props.top,
left: props.left
},
// offset() relates an element's border box to the document origin
offset = props.offset = {
top: pos.top + props.marginTop,
left: pos.left + props.marginLeft
};
// Account for ancestors differently by element position
// fixed: ignore them
// absolute: offset includes offsetParent offset+border
// relative: position includes parent padding (and also position+margin+border when
// parent is not offsetParent); offset includes parent offset+border+padding
// static: same as relative
for ( ; parent; parent = expectations[ parent.parent ] ) {
// position:fixed
if ( props.style === "fixed" ) {
break;
}
// position:absolute bypass
if ( props.style === "absolute" && parent.style === "static" ) {
continue;
}
// Offset update
offset.top += parent.offset.top + parent.borderTop;
offset.left += parent.offset.left + parent.borderLeft;
if ( props.style !== "absolute" ) {
offset.top += parent.paddingTop;
offset.left += parent.paddingLeft;
// position:relative or position:static position update
pos.top += parent.paddingTop;
pos.left += parent.paddingLeft;
if ( parent.style === "static" ) {
pos.top += parent.pos.top + parent.marginTop + parent.borderTop;
pos.left += parent.pos.left + parent.marginLeft + parent.borderLeft;
}
}
break;
}
// Viewport scroll affects position:fixed elements, except when the page is
// unscrollable.
if ( props.style === "fixed" &&
( alwaysScrollable || expectations.documentElement.style !== "fixed" ) ) {
offset.top += scrollTop;
offset.left += scrollLeft;
}
} );
// Support: IE<=10 only
// Fudge the tests to work around <html>.gBCR() erroneously including margins
if ( /MSIE (?:9|10)\./.test( navigator.userAgent ) ) {
expectations.documentElement.pos.top -= expectations.documentElement.marginTop -
scrollTop;
expectations.documentElement.offset.top -= expectations.documentElement.marginTop -
scrollTop;
expectations.documentElement.pos.left -= expectations.documentElement.marginLeft -
scrollLeft;
expectations.documentElement.offset.left -= expectations.documentElement.marginLeft -
scrollLeft;
if ( htmlPos !== "static" ) {
delete expectations.documentElement;
delete expectations.body;
delete expectations.relative;
delete expectations.absolute;
}
}
return expectations;
}
// Cover each combination of <html> position and <body> position
supportjQuery.each( POSITION_VALUES, function( _, htmlPos ) {
supportjQuery.each( POSITION_VALUES, function( _, bodyPos ) {
var label = "nonzero box properties - html." + htmlPos + " body." + bodyPos;
testIframe( label, "offset/boxes.html", function( assert, $, win, doc ) {
// Define expectations at runtime to properly account for scrolling
var scrollTop = win.pageYOffset,
scrollLeft = win.pageXOffset,
expectations = getExpectations( htmlPos, bodyPos, scrollTop, scrollLeft );
assert.expect( 3 * Object.keys( expectations ).length );
// Setup documentElement and body styles, preserving scroll position
doc.documentElement.style.position = htmlPos;
doc.body.style.position = bodyPos;
win.scrollTo( scrollLeft, scrollTop );
// Verify expected document offset
supportjQuery.each( expectations, function( id, descriptor ) {
assert.deepEqual(
supportjQuery.extend( {}, $( "#" + id ).offset() ),
descriptor.offset,
"jQuery('#" + id + "').offset(): top " + descriptor.offset.top +
", left " + descriptor.offset.left );
} );
// Verify expected relative position
supportjQuery.each( expectations, function( id, descriptor ) {
assert.deepEqual(
supportjQuery.extend( {}, $( "#" + id ).position() ),
descriptor.pos,
"jQuery('#" + id + "').position(): top " + descriptor.pos.top +
", left " + descriptor.pos.left );
} );
// Verify that values round-trip
supportjQuery.each( Object.keys( expectations ).reverse(), function( _, id ) {
var $el = $( "#" + id ),
pos = supportjQuery.extend( {}, $el.position() );
$el.css( { top: pos.top, left: pos.left } );
if ( $el.css( "position" ) === "relative" ) {
// $relative.position() includes parent padding; switch to absolute
// positioning so we don't double its effects.
$el.css( { position: "absolute" } );
}
assert.deepEqual( supportjQuery.extend( {}, $el.position() ), pos,
"jQuery('#" + id + "').position() round-trips" );
// TODO Verify .offset(...)
// assert.deepEqual( $el.offset( offset ).offset(), offset )
// assert.deepEqual( $el.offset( adjustedOffset ).offset(), adjustedOffset )
// assert.deepEqual( $new.offset( offset ).offset(), offset )
} );
} );
} );
} );
} )();
QUnit.test( "offsetParent", function( assert ) {
assert.expect( 13 );
var body, header, div, area;
body = jQuery( "body" ).offsetParent();
assert.equal( body.length, 1, "Only one offsetParent found." );
assert.equal( body[ 0 ], document.documentElement, "The html element is the offsetParent of the body." );
header = jQuery( "#qunit" ).offsetParent();
assert.equal( header.length, 1, "Only one offsetParent found." );
assert.equal( header[ 0 ], document.documentElement, "The html element is the offsetParent of #qunit." );
jQuery( "#qunit-fixture" ).css( "position", "absolute" );
div = jQuery( "#nothiddendivchild" ).offsetParent();
assert.equal( div.length, 1, "Only one offsetParent found." );
assert.equal( div[ 0 ], document.getElementById( "qunit-fixture" ), "The #qunit-fixture is the offsetParent of #nothiddendivchild." );
jQuery( "#qunit-fixture" ).css( "position", "" );
jQuery( "#nothiddendiv" ).css( "position", "relative" );
div = jQuery( "#nothiddendivchild" ).offsetParent();
assert.equal( div.length, 1, "Only one offsetParent found." );
assert.equal( div[ 0 ], jQuery( "#nothiddendiv" )[ 0 ], "The div is the offsetParent." );
div = jQuery( "body, #nothiddendivchild" ).offsetParent();
assert.equal( div.length, 2, "Two offsetParent found." );
assert.equal( div[ 0 ], document.documentElement, "The html element is the offsetParent of the body." );
assert.equal( div[ 1 ], jQuery( "#nothiddendiv" )[ 0 ], "The div is the offsetParent." );
area = jQuery( "<map name=\"imgmap\"><area shape=\"rect\" coords=\"0,0,200,50\"></map>" ).appendTo( "body" ).find( "area" );
assert.equal( area.offsetParent()[ 0 ], document.documentElement, "The html element is the offsetParent of a map area." );
area.remove();
div = jQuery( "<div>" ).css( { "position": "absolute" } ).appendTo( "body" );
assert.equal( div.offsetParent()[ 0 ], document.documentElement, "Absolutely positioned div returns html as offset parent, see trac-12139" );
div.remove();
} );
QUnit.test( "fractions (see trac-7730 and trac-7885)", function( assert ) {
assert.expect( 2 );
jQuery( "body" ).append( "<div id='fractions'></div>" );
var result,
expected = { "top": 1000, "left": 1000 },
div = jQuery( "#fractions" );
div.css( {
"position": "absolute",
"left": "1000.7432222px",
"top": "1000.532325px",
"width": 100,
"height": 100
} );
div.offset( expected );
result = div.offset();
// Support: Chrome <=45 - 46
// In recent Chrome these values differ a little.
assert.ok( Math.abs( result.top - expected.top ) < 0.25, "Check top within 0.25 of expected" );
assert.ok( Math.abs( result.left - expected.left ) < 0.25, "Check left within 0.25 of expected" );
div.remove();
} );
QUnit.test( "iframe scrollTop/Left (see gh-1945)", function( assert ) {
assert.expect( 2 );
var ifDoc = jQuery( "#iframe" )[ 0 ].contentDocument;
// Mobile Safari resize the iframe by its content meaning it's not possible to scroll
// the iframe but only its parent element.
// It seems (not confirmed) in android 4.0 it's not possible to scroll iframes from the code.
if (
/iphone os|ipad/i.test( navigator.userAgent ) ||
/android 4\.0/i.test( navigator.userAgent )
) {
assert.equal( true, true, "Can't scroll iframes in this environment" );
assert.equal( true, true, "Can't scroll iframes in this environment" );
} else {
// Tests scrollTop/Left with iframes
jQuery( "#iframe" ).css( "width", "50px" ).css( "height", "50px" );
ifDoc.write( "<div style='width: 1000px; height: 1000px;'></div>" );
jQuery( ifDoc ).scrollTop( 200 );
jQuery( ifDoc ).scrollLeft( 500 );
assert.equal( jQuery( ifDoc ).scrollTop(), 200, "$($('#iframe')[0].contentDocument).scrollTop()" );
assert.equal( jQuery( ifDoc ).scrollLeft(), 500, "$($('#iframe')[0].contentDocument).scrollLeft()" );
}
} );
} )();