<|start_filename|>Gruntfile.js<|end_filename|>
/*global module:false require:true*/
module.exports = function(grunt) {
	"use strict";
	
	var fs = require('fs');
	
	grunt.loadNpmTasks('grunt-contrib-jshint');
	grunt.loadNpmTasks('grunt-contrib-qunit');
	grunt.loadNpmTasks('grunt-contrib-concat');

	// Project configuration.
	grunt.initConfig({
		pkg: grunt.file.readJSON("package.json"),
		meta: {
			banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' +
				'<%= grunt.template.today("yyyy-mm-dd") %>' + "\n" +
				'<%= pkg.homepage ? " * " + pkg.homepage + "\\n" : "" %>' +
				' * Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.contributors[0].name %>;' +
				' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %>\n' +
				' */'
		},
		
		jshint: {
			options: {
				"-W106": true, // Ignore warning "Identifier 'ext_disableQuirkDetection' is not in camel case."
				globals: {
					jQuery: true,
					"$": true,
					bililiteRange: true
				},
			},
			tests: {
				options: {
					jshintrc: true
				},
				src: ['tests/drag-n-drop.js', 'tests/key-combo.js', 'tests/key-sequence.js', 'tests/testInfrastructure.js']
			},
			src: ['src/*.js', 'libs/jquery.simulate.js'],
			grunt: 'Gruntfile.js',
		},
		
		qunit: {
			files: ['tests/tests.html']
		},
		
		concat: {
			'ext-only': {
				src: ['src/jquery.simulate.ext.js', 'src/jquery.simulate.drag-n-drop.js', 'src/jquery.simulate.key-sequence.js', 'src/jquery.simulate.key-combo.js'],
				dest: 'dist/jquery.simulate.ext.<%= pkg.version %>.js'
			},
			complete: {
				src: ['libs/bililiteRange.js', 'libs/jquery.simulate.js', 'src/jquery.simulate.ext.js', 'src/jquery.simulate.drag-n-drop.js', 'src/jquery.simulate.key-sequence.js', 'src/jquery.simulate.key-combo.js'],
				dest: 'dist/jquery.simulate.ext.<%= pkg.version %>.complete.js'
			}
		},
		
/* uglify task is not working correctly at the moment (strips away copyright notices)
 * therefore, we cannot use it

		uglify: {
			'ext-only': {
				options: {
					banner: '<%= meta.banner %>'
				},
				src: ['<%= concat["ext-only"].dest %>'],
				dest: 'dist/jquery.simulate.ext.<%= pkg.version %>.min.js'
			},
			complete: {
				options: {
					preserveComments: function(node, comment) {
						if (comment.value.search(/Copyright/i) != -1)
							return true;
						else
							return false;
					}
				},
				src: ['<%= concat["complete"].dest %>'],
				dest: 'dist/jquery.simulate.ext.<%= pkg.version %>.complete.min.js'
			}
		},
*/		
		
		"multi-banner-min": {
			complete: {
				src: ['<banner:meta.simulate-banner>', 'libs/jquery.simulate.js', '<banner:meta.banner>', '<%= concat["ext-only"].dest %>'],
				dest: 'dist/jquery.simulate.ext.<%= pkg.version %>.complete.min.js'
			}
		}
		
	});
	

	// Default task.
	grunt.registerTask('default', ['jshint', 'qunit', 'concat']);

};


<|start_filename|>simulate-ext.jquery.json<|end_filename|>
{
	"name": "simulate-ext",
	"version": "1.3.0",
	"title": "jQuery Simulate Extended",
	"author": {
		"name": "<NAME>",
		"email": "<EMAIL>",
		"url": "https://github.com/j-ulrich"
	},
	"licenses": [
		{
			"type": "MIT",
			"url": "https://github.com/j-ulrich/jquery-simulate-ext/blob/master/MIT-LICENSE.txt"
		}
	],
	"dependencies": {
		"jquery": "~1.7"
	},
	
	
	
	"description": "jQuery Simulate - Extended. Simulating complex user interaction based on the jQuery Simulate plugin.",
	"keywords": ["jquery", "simulate", "key", "mouse", "press", "move", "drag", "drop", "user-interaction"],
	"homepage": "http://j-ulrich.github.com/jquery-simulate-ext",
	"docs": "https://github.com/j-ulrich/jquery-simulate-ext/blob/master/README.md",
	"demo": "http://jsfiddle.net/Ignitor/Psjhf/embedded/result/",
	"bugs": "https://github.com/j-ulrich/jquery-simulate-ext/issues"
}

<|start_filename|>tests/drag-n-drop.js<|end_filename|>
/*jshint camelcase:true, plusplus:true, forin:true, noarg:true, noempty:true, eqeqeq:true, bitwise:true, strict:true, undef:true, unused:true, curly:true, browser:true, devel:true, maxerr:100, white:false, onevar:false */
/*global jQuery:true $:true */

(function ($, undefined) {
	"use strict";

$(document).ready(function() {
	
	//####################################################################
	//#######                                                      #######
	//#######  WARNING: DO NOT MOVE THE MOUSE DURING THE TESTS !!! #######
	//#######                                                      #######
	//####################################################################
	console.warn('!!! WARNING: Do not move the mouse during the tests or some of the tests might fail !!!');

	module("drag-n-drop", {
		setup: function() {
			tests.testSetup();
			$(document).on("simulate-drag simulate-drop", '#qunit-fixture', tests.assertExpectedEvent);
		},
		
		teardown: function() {
			$(document).off("simulate-drag simulate-drop", '#qunit-fixture');
			tests.testTearDown();
			if ($.simulate.activeDrag()) {
				$(document).simulate("drop", {debug: false});
			}
		}
	});
	
	//####### Test Functions #######
	test("drag", function() {
		var $testElement = $('#dragArea');
		
		var dragX = 50,
			dragY = 10;
		
		var elementX = Math.round($testElement.offset().left+$testElement.outerWidth()/2),
			elementY = Math.round($testElement.offset().top+$testElement.outerHeight()/2);
		
		var expectedX = Math.round($testElement.offset().left+$testElement.outerWidth()/2)+dragX,
			expectedY = Math.round($testElement.offset().top+$testElement.outerHeight()/2)+dragY;
		tests.expectedEvents = [
			{type: "mousedown", pageX: elementX, pageY: elementY},
			{type: "mousemove", pageX: expectedX, pageY: expectedY},
			{type: "simulate-drag"}
		];
		
		$testElement.simulate("drag", {dx: dragX, dy: dragY});
	});
	
	test("drag callback", function() {
		var $testElement = $('#dragArea');
		var dragX = 50,
			dragY = 10;
		var expectedX = Math.round($testElement.offset().left+$testElement.outerWidth()/2)+dragX,
			expectedY = Math.round($testElement.offset().top+$testElement.outerHeight()/2)+dragY;
		tests.expectedEvents = [
			{type: "mousedown"},
			{type: "mousemove", pageX: expectedX, pageY: expectedY},
			{type: "simulate-drag"},
			{type: "callback"}
		];
		
		$testElement.simulate("drag", {dx: dragX, dy: dragY, callback: function() {
			tests.assertExpectedEvent({type: "callback"});
		}});
	});

	test("drag on target", function() {
		var $testElement = $('#dragArea'),
			$dropElement = $('#dropArea');

		var expectedX = Math.round($dropElement.offset().left+$dropElement.outerWidth()/2),
			expectedY = Math.round($dropElement.offset().top+$dropElement.outerHeight()/2);
		
		tests.expectedEvents = [
			{type: "mousedown"},
			{type: "mousemove", pageX: expectedX, pageY: expectedY},
			{type: "simulate-drag"}
		];
		
		$testElement.simulate("drag", {dragTarget: $dropElement});
	});

	test("drop", function() {
		var $testElement = $('#dropArea');
		
		var expectedX = Math.round($testElement.offset().left+$testElement.outerWidth()/2),
			expectedY = Math.round($testElement.offset().top+$testElement.outerHeight()/2);
		
		tests.expectedEvents = [
			{type: "mousemove", pageX: expectedX, pageY: expectedY}, // A drop without an active drag moves the mouse onto the target before dropping
			{type: "mouseup", pageX: expectedX, pageY: expectedY},
			{type: "simulate-drop"}
		];
		
		$testElement.simulate("drop");
	});
	
	test("drop callback", function() {
		var $testElement = $('#dropArea');
		
		var expectedX = Math.round($testElement.offset().left+$testElement.outerWidth()/2),
			expectedY = Math.round($testElement.offset().top+$testElement.outerHeight()/2);
		
		tests.expectedEvents = [
			{type: "mousemove", pageX: expectedX, pageY: expectedY}, // A drop without an active drag moves the mouse onto the target before dropping
			{type: "mouseup"},
			{type: "simulate-drop"},
			{type: "callback"}
		];
		
		$testElement.simulate("drop", {callback: function() {
			tests.assertExpectedEvent({type: "callback"});
		}});
	});
	
	test("drag-n-drop", function() {
		var $testElement = $('#dragArea');
		
		var dragX = 50,
			dragY = 10;
		var expectedX = Math.round($testElement.offset().left+$testElement.outerWidth()/2)+dragX,
			expectedY = Math.round($testElement.offset().top+$testElement.outerHeight()/2)+dragY;

		tests.expectedEvents = [
			{type: "mousedown"},
			{type: "mousemove", pageX: expectedX, pageY: expectedY},
			{type: "simulate-drag"},
			{type: "mouseup", pageX: expectedX, pageY: expectedY},
			{type: "simulate-drop"}
		];
		
		$testElement.simulate("drag-n-drop", {dx: dragX, dy: dragY});
	});
	
	test("drag-n-drop callback", function() {
		var $testElement = $('#dragArea');
		
		var dragX = 50,
			dragY = 10;
		var expectedX = Math.round($testElement.offset().left+$testElement.outerWidth()/2)+dragX,
			expectedY = Math.round($testElement.offset().top+$testElement.outerHeight()/2)+dragY;

		tests.expectedEvents = [
			{type: "mousedown"},
			{type: "mousemove", pageX: expectedX, pageY: expectedY},
			{type: "simulate-drag"},
			{type: "mouseup", pageX: expectedX, pageY: expectedY},
			{type: "simulate-drop"},
			{type: "callback"}
		];
		
		$testElement.simulate("drag-n-drop", {dx: dragX, dy: dragY, callback: function() {
			tests.assertExpectedEvent({type: "callback"});
		}});
	});
	
	test("drag-n-drop on scrolled viewport", function() {
		var $testElement = $('#dragArea'),
			$dropElement = $('#dropArea');
		
		// Check if we have to move the #qunit-fixture div into the viewport to be able to test this
		if (document.elementFromPoint(-1,-1) === null) {
			$('#qunit-fixture').css({
				top: "auto",
				left: "auto",
				position: "inherit"
			});
			$(document).scrollTop($dropElement.offset().top);
		}
		else {
			$(document).scrollTop(500);
		}
		
		var dragX = 50,
			dragY = Math.round($dropElement.offset().top - $testElement.offset().top);
		
		var elementX = Math.round($testElement.offset().left+$testElement.outerWidth()/2),
			elementY = Math.round($testElement.offset().top+$testElement.outerHeight()/2);
		
		var expectedX = Math.round($testElement.offset().left+$testElement.outerWidth()/2)+dragX,
			expectedY = Math.round($testElement.offset().top+$testElement.outerHeight()/2)+dragY;
		
		tests.expectedEvents = [
			{type: "mousedown", pageX: elementX, pageY: elementY},
			{type: "mousemove", pageX: expectedX, pageY: expectedY},
			{type: "simulate-drag"},
			{type: "mouseup", pageX: expectedX, pageY: expectedY, target: $dropElement[0]},
			{type: "simulate-drop"}
		];
		
		$testElement.simulate("drag-n-drop", {dx: dragX, dy: dragY});
		
		// Reset #qunit-fixture
		$('#qunit-fixture').css({
			top: "",
			left: "",
			position: ""
		});
		$(document).scrollTop(0);
	});

	test("multiple drags, then drop", function() {
		var $testElement = $('#dragArea');
		
		var drag = [ {x: 50, y: 10}, {x: 7, y: -30}, {x: -20, y: -5}];
		
		var expectedX = Math.round($testElement.offset().left+$testElement.outerWidth()/2),
			expectedY = Math.round($testElement.offset().top+$testElement.outerHeight()/2);
		
		tests.expectedEvents = [{type: "mousedown"}];
		var i;
		for (i=0; i < drag.length; i+=1) {
			expectedX += drag[i].x;
			expectedY += drag[i].y;
			tests.expectedEvents.push({type: "mousemove", pageX: expectedX, pageY: expectedY},
				{type: "simulate-drag"});
		}
		tests.expectedEvents.push({type: "mouseup", pageX: expectedX, pageY: expectedY}, {type: "simulate-drop"});
			
		
		for (i=0; i < drag.length; i+=1) {
			$testElement.simulate("drag", {dx: drag[i].x, dy: drag[i].y});
		}
		$testElement.simulate("drop");
	});
	
	test("drag on target, then drop", function() {
		var $testElement = $('#dragArea'),
			$dropElement = $('#dropArea');

		var expectedX = Math.round($dropElement.offset().left+$dropElement.outerWidth()/2),
			expectedY = Math.round($dropElement.offset().top+$dropElement.outerHeight()/2);
		
		tests.expectedEvents = [
			{type: "mousedown"},
			{type: "mousemove", pageX: expectedX, pageY: expectedY},
			{type: "simulate-drag"},
			{type: "mouseup", pageX: expectedX, pageY: expectedY},
			{type: "simulate-drop"}
		];
		
		$testElement.simulate("drag", {dragTarget: $dropElement});
		$testElement.simulate("drop");
	});

	test("drag, then drop on different target", function() {
		var $dragElement = $('#dragArea'),
			$dropElement = $('#dropArea');
		
		var dx = 10,
			dy = 20;

		var endOfDragX = Math.round($dragElement.offset().left+$dragElement.outerWidth()/2)+dx,
			endOfDragY = Math.round($dragElement.offset().top+$dragElement.outerHeight()/2)+dy,
			dropX = Math.round($dropElement.offset().left+$dropElement.outerWidth()/2),
			dropY = Math.round($dropElement.offset().top+$dropElement.outerHeight()/2);
		
		tests.expectedEvents = [
			{type: "mousedown"},
			{type: "mousemove", pageX: endOfDragX, pageY: endOfDragY},
			{type: "simulate-drag"},
			{type: "mousemove", pageX: dropX, pageY: dropY},
			{type: "mouseup", pageX: dropX, pageY: dropY},
			{type: "simulate-drop"}
		];
		
		$dragElement.simulate("drag", {dx: dx, dy: dy});
		$dropElement.simulate("drop");
	});

	test("drop before another drag", function() {
		var $dragElement1 = $('#dragArea'),
			$dragElement2 = $('#dropArea');
		
		var drag1X = 10,
			drag1Y = 5,
			drag2X = 50,
			drag2Y = 10;
		
		var expected1X = Math.round($dragElement1.offset().left+$dragElement1.outerWidth()/2)+drag1X,
			expected1Y = Math.round($dragElement1.offset().top+$dragElement1.outerHeight()/2)+drag1Y,
			expected2X = Math.round($dragElement2.offset().left+$dragElement2.outerWidth()/2)+drag2X,
			expected2Y = Math.round($dragElement2.offset().top+$dragElement2.outerHeight()/2)+drag2Y;
		
		tests.expectedEvents = [
			{type: "mousedown"},
			{type: "mousemove", pageX: expected1X, pageY: expected1Y},
			{type: "simulate-drag"},
			{type: "mouseup", pageX: expected1X, pageY: expected1Y},
			{type: "simulate-drop"},
			{type: "mousedown"},
			{type: "mousemove", pageX: expected2X, pageY: expected2Y},
			{type: "simulate-drag"}
		];
		
		$dragElement1.simulate("drag", {dx: drag1X, dy: drag1Y});
		$dragElement2.simulate("drag", {dx: drag2X, dy: drag2Y});
	});

	
	test("move before drop", function() {
		var $dragElement = $('#dragArea'),
			$dropElement = $('#dropArea');
		
		var expectedX = Math.round($dropElement.offset().left+$dropElement.outerWidth()/2),
			expectedY = Math.round($dropElement.offset().top+$dropElement.outerHeight()/2);
		
		tests.expectedEvents = [
			{type: "mousedown"},
			{type: "simulate-drag"},
			{type: "mousemove", pageX: expectedX, pageY: expectedY},
			{type: "mouseup"},
			{type: "simulate-drop"}
		];
		
		$dragElement.simulate("drag");
		$dropElement.simulate("drop");
	});

	test("document drop", function() {
		var $dragElement = $('#dragArea');
		
		var dx = 17,
			dy = 102;
		
		var expectedX = Math.round($dragElement.offset().left+$dragElement.outerWidth()/2)+dx,
			expectedY = Math.round($dragElement.offset().top+$dragElement.outerHeight()/2)+dy;
		
		tests.expectedEvents = [
			{type: "mousedown"},
			{type: "mousemove", pageX: expectedX, pageY: expectedY},
			{type: "simulate-drag"},
			{type: "mouseup", pageX: expectedX, pageY: expectedY},
			{type: "simulate-drop"}
		];
		
		$dragElement.simulate("drag", {dx: dx, dy: dy});
		$(document).simulate("drop");
	});

	test("drag-n-drop on target", function() {
		var $dragElement = $('#dragArea'),
			$dropElement = $('#dropArea');
		
		var expectedX = Math.round($dropElement.offset().left+$dropElement.outerWidth()/2),
			expectedY = Math.round($dropElement.offset().top+$dropElement.outerHeight()/2);
		
		tests.expectedEvents = [
			{type: "mousedown"},
			{type: "mousemove", pageX: expectedX, pageY: expectedY},
			{type: "simulate-drag"},
			{type: "mouseup", pageX: expectedX, pageY: expectedY},
			{type: "simulate-drop"}
		];
		
		$dragElement.simulate("drag-n-drop", {dropTarget: $dropElement});
	});

	test("drag-n-drop with drag and drop target", function() {
		var $dragElement = $('#dragArea'),
			$dropElement = $('#dropArea');
		
		var dx = 10,
			dy = -132;
		
		var dragX = Math.round($dragElement.offset().left+$dragElement.outerWidth()/2)+dx,
			dragY = Math.round($dragElement.offset().top+$dragElement.outerHeight()/2)+dy,
			dropX = Math.round($dropElement.offset().left+$dropElement.outerWidth()/2),
			dropY = Math.round($dropElement.offset().top+$dropElement.outerHeight()/2);
		
		tests.expectedEvents = [
			{type: "mousedown"},
			{type: "mousemove", pageX: dragX, pageY: dragY},
			{type: "simulate-drag"},
			{type: "mousemove", pageX: dropX, pageY: dropY},
			{type: "mouseup", pageX: dropX, pageY: dropY},
			{type: "simulate-drop"}
		];
		
		$dragElement.simulate("drag-n-drop", {dx: dx, dy: dy, dropTarget: $dropElement});
	});

	test("drag-n-drop with drag target and drop target", function() {
		var $dragElement = $('#dragArea'),
			$dragTarget = $('#dropArea'),
			$dropElement = $('#dropArea2');
		
		var dragX = Math.round($dragTarget.offset().left+$dragTarget.outerWidth()/2),
			dragY = Math.round($dragTarget.offset().top+$dragTarget.outerHeight()/2),
			dropX = Math.round($dropElement.offset().left+$dropElement.outerWidth()/2),
			dropY = Math.round($dropElement.offset().top+$dropElement.outerHeight()/2);
		
		tests.expectedEvents = [
			{type: "mousedown"},
			{type: "mousemove", pageX: dragX, pageY: dragY},
			{type: "simulate-drag"},
			{type: "mousemove", pageX: dropX, pageY: dropY},
			{type: "mouseup", pageX: dropX, pageY: dropY},
			{type: "simulate-drop"}
		];
		
		$dragElement.simulate("drag-n-drop", {dragTarget: $dragTarget, dropTarget: $dropElement});
	});

	test("interpolated drag", function() {
		var $dragElement = $('#dragArea');
		
		var dragStartX = Math.round($dragElement.offset().left+$dragElement.outerWidth()/2),
			dragStartY = Math.round($dragElement.offset().top+$dragElement.outerHeight()/2);
		
		var stepCount = 5,
			dragX = 60,
			dragY = 40;
		
		tests.expectedEvents = [{type: "mousedown"}];
		for (var i=1; i <= stepCount; i+=1) {
			tests.expectedEvents.push({type: "mousemove", pageX: Math.round(dragStartX+i*dragX/(stepCount+1)), pageY: Math.round(dragStartY+i*dragY/(stepCount+1)) });
		}
		tests.expectedEvents.push(
			{type: "mousemove", pageX: dragStartX+dragX, pageY: dragStartY+dragY },
			{type: "simulate-drag"}
		);
		
		$dragElement.simulate("drag", {dx: dragX, dy: dragY, interpolation: {stepCount: stepCount}});
	});
	
	test("interpolated drag across elements", function() {
		var $dragElement = $('#dragArea'),
			$dragTarget = $('#dropArea');
		
		// Check if we have to move the #qunit-fixture div into the viewport to be able to test this
		if (document.elementFromPoint(-1,-1) === null) {
			$('#qunit-fixture').css({
				top: "auto",
				left: "auto",
				position: "inherit"
			});
			$(document).scrollTop($dragTarget.offset().top);
		}

		
		var dragStartX = Math.round($dragElement.offset().left+$dragElement.outerWidth()/2),
			dragStartY = Math.round($dragElement.offset().top+$dragElement.outerHeight()/2),
			dragEndX = Math.round($dragTarget.offset().left+$dragTarget.outerWidth()/2),
			dragEndY = Math.round($dragTarget.offset().top+$dragTarget.outerHeight()/2);
		
		var stepCount = 5,
			dragX = dragEndX - dragStartX,
			dragY = dragEndY - dragStartY,
			eventTarget = $dragElement[0],
			eventX,
			eventY;
		
		tests.expectedEvents = [{type: "mousedown"}];
		for (var i=1; i <= stepCount; i+=1) {
			eventX = Math.round(dragStartX+i*dragX/(stepCount+1));
			eventY = Math.round(dragStartY+i*dragY/(stepCount+1));
			if (eventX >= Math.round($dragTarget.offset().left) && eventY >= Math.round($dragTarget.offset().top)) {
				eventTarget = $dragTarget[0];
			}
			tests.expectedEvents.push({type: "mousemove", pageX: eventX, pageY: eventY, target: eventTarget});
		}
		tests.expectedEvents.push(
			{type: "mousemove", pageX: dragStartX+dragX, pageY: dragStartY+dragY },
			{type: "simulate-drag"}
		);
		
		$dragElement.simulate("drag", {dragTarget: $dragTarget, interpolation: {stepCount: stepCount}});
		
		// Reset #qunit-fixture
		$('#qunit-fixture').css({
			top: "",
			left: "",
			position: ""
		});
		$(document).scrollTop(0);
	});

	test("interpolated drag using stepWidth", function() {
		var $dragElement = $('#dragArea');
		
		var dragStartX = Math.round($dragElement.offset().left+$dragElement.outerWidth()/2),
			dragStartY = Math.round($dragElement.offset().top+$dragElement.outerHeight()/2);
		
		var stepCount = 2,
			stepWidth = 30,
			dragWidth = (stepCount+1)*stepWidth;
		
		tests.expectedEvents = [{type: "mousedown"}];
		for (var i=1; i <= stepCount+1; i+=1) {
			tests.expectedEvents.push({type: "mousemove", pageX: dragStartX+i*stepWidth, pageY: dragStartY });
		}
		tests.expectedEvents.push({type: "simulate-drag"});
		
		$dragElement.simulate("drag", {dx: dragWidth, interpolation: {stepWidth: stepWidth}});
	});

	test("interpolated drag using stepCount", function() {
		var $dragElement = $('#dragArea');
		
		var dragStartX = Math.round($dragElement.offset().left+$dragElement.outerWidth()/2),
			dragStartY = Math.round($dragElement.offset().top+$dragElement.outerHeight()/2);
		
		var stepCount = 2,
			dragWidth = 90,
			stepWidth = dragWidth / (stepCount+1);
		
		tests.expectedEvents = [{type: "mousedown"}];
		for (var i=1; i <= stepCount+1; i+=1) {
			tests.expectedEvents.push({type: "mousemove", pageX: dragStartX+i*stepWidth, pageY: dragStartY });
		}
		tests.expectedEvents.push({type: "simulate-drag"});
		
		$dragElement.simulate("drag", {dx: dragWidth, interpolation: {stepCount: stepCount}});
	});

	test("interpolated, shaky drag", function() {
		var $dragElement = $('#dragArea');
		var actualYPositions = [];
		
		var shakyAmplitude = 10;
		
		// Unbind "normal" assertExpectedEvent function and replace with a fuzzy variant of it
		function assertExpectedEventShaky(event) {
			if (tests.expectedEvents.length === 0) {
				ok(false, "Unexpected event: "+event.type);
				return;
			}
			for (var prop in tests.expectedEvents[0]) {
				if (tests.expectedEvents[0].hasOwnProperty(prop)) {
					if (prop === "pageX" || prop === "pageY") {
						QUnit.close(event[prop], tests.expectedEvents[0][prop], shakyAmplitude, "Comparing "+prop+" (expected: "+tests.expectedEvents[0][prop]+"±"+shakyAmplitude+")");
						if (prop === "pageY") {
							actualYPositions[event[prop]] = actualYPositions[event[prop]]+1 || 1;
						}
					}
					else {
						strictEqual(event[prop], tests.expectedEvents[0][prop], "Comparing "+prop+" (expected: "+tests.expectedEvents[0][prop]+")");
					}
				}
			}
			if (event.type === tests.expectedEvents[0].type) {
				tests.expectedEvents.shift();
			}

		}
		$(document).off("mousemove", "#qunit-fixture", tests.assertExpectedEvent).on("mousemove", "#qunit-fixture", assertExpectedEventShaky);
		
		var dragStartX = Math.round($dragElement.offset().left+$dragElement.outerWidth()/2),
			dragStartY = Math.round($dragElement.offset().top+$dragElement.outerHeight()/2);
		
		var stepCount = 2,
			dragWidth = 90,
			stepWidth = dragWidth / (stepCount+1);
		
		tests.expectedEvents = [{type: "mousedown"}];
		var i;
		for (i=1; i <= stepCount+1; i+=1) {
			tests.expectedEvents.push({type: "mousemove", pageX: dragStartX+i*stepWidth, pageY: dragStartY });
		}
		tests.expectedEvents.push({type: "simulate-drag"});
		
		$dragElement.simulate("drag", {dx: dragWidth, interpolation: {stepCount: stepCount, shaky: shakyAmplitude}});
		
		var posCounter = 0;
		for (i in actualYPositions) {
			if (actualYPositions.hasOwnProperty(i)) {
				posCounter += 1;
			}
		}
		ok(posCounter > 1, "Verify shaky positions are random (if this test fails, rerun to rule out the unlikely case that all random positions were equal)");
		
	});

	
	test("interpolated, delayed drag", function() {
		var $dragElement = $('#dragArea');
		
		var stepDelay = 100,
			lastEventOccurrence;
		
		// Unbind "normal" assertExpectedEvent function and replace with a fuzzy variant of it
		function assertExpectedEventDelay(event) {
			var delay = Date.now() - lastEventOccurrence;
			ok(delay >= stepDelay-10, "Verify events occur delayed (delay: "+delay+")"); // stepDelay-10 means tolerance of 10ms
			if (tests.expectedEvents.length === 0) {
				ok(false, "Unexpected event: "+event.type);
				return;
			}
			for (var prop in tests.expectedEvents[0]) {
				if (tests.expectedEvents[0].hasOwnProperty(prop)) {
					strictEqual(event[prop], tests.expectedEvents[0][prop], "Comparing "+prop+" (expected: "+tests.expectedEvents[0][prop]+")");
				}
			}
			if (event.type === tests.expectedEvents[0].type) {
				tests.expectedEvents.shift();
			}
			lastEventOccurrence = Date.now();
		}
		$(document).off("mousemove", "#qunit-fixture", tests.assertExpectedEvent).on("mousemove", "#qunit-fixture", assertExpectedEventDelay);
		
		var dragStartX = Math.round($dragElement.offset().left+$dragElement.outerWidth()/2),
			dragStartY = Math.round($dragElement.offset().top+$dragElement.outerHeight()/2);
		
		var stepCount = 2,
			dragWidth = 90,
			stepWidth = dragWidth / (stepCount+1);
		
		tests.expectedEvents = [{type: "mousedown"}];
		for (var i=1; i <= stepCount+1; i+=1) {
			tests.expectedEvents.push({type: "mousemove", pageX: dragStartX+i*stepWidth, pageY: dragStartY });
		}
		tests.expectedEvents.push({type: "simulate-drag"});
		
		stop();
		lastEventOccurrence = Date.now();
		$dragElement.simulate("drag", {dx: dragWidth, interpolation: {stepCount: stepCount, stepDelay: stepDelay}});
		
		setTimeout(function() {
			start();
		},(stepCount+2)*stepDelay);
	});

	test("interpolated, delayed drag using duration", function() {
		var $dragElement = $('#dragArea');
		
		var stepDelay = 100,
			lastEventOccurrence;
		
		// Unbind "normal" assertExpectedEvent function and replace with a fuzzy variant of it
		function assertExpectedEventDelay(event) {
			var delay = Date.now() - lastEventOccurrence;
			ok(delay >= stepDelay-10, "Verify events occur delayed (delay: "+delay+")"); // stepDelay-10 means tolerance of 10ms
			if (tests.expectedEvents.length === 0) {
				ok(false, "Unexpected event: "+event.type);
				return;
			}
			for (var prop in tests.expectedEvents[0]) {
				if (tests.expectedEvents[0].hasOwnProperty(prop)) {
					strictEqual(event[prop], tests.expectedEvents[0][prop], "Comparing "+prop+" (expected: "+tests.expectedEvents[0][prop]+")");
				}
			}
			if (event.type === tests.expectedEvents[0].type) {
				tests.expectedEvents.shift();
			}
			lastEventOccurrence = Date.now();
		}
		$(document).off("mousemove", "#qunit-fixture", tests.assertExpectedEvent).on("mousemove", "#qunit-fixture", assertExpectedEventDelay);
		
		var dragStartX = Math.round($dragElement.offset().left+$dragElement.outerWidth()/2),
			dragStartY = Math.round($dragElement.offset().top+$dragElement.outerHeight()/2);
		
		var stepCount = 2,
			dragWidth = 90,
			stepWidth = dragWidth / (stepCount+1);
		
		tests.expectedEvents = [{type: "mousedown"}];
		for (var i=1; i <= stepCount+1; i+=1) {
			tests.expectedEvents.push({type: "mousemove", pageX: dragStartX+i*stepWidth, pageY: dragStartY });
		}
		tests.expectedEvents.push({type: "simulate-drag"});
		
		stop();
		lastEventOccurrence = Date.now();
		$dragElement.simulate("drag", {dx: dragWidth, interpolation: {stepCount: stepCount, duration: (stepCount+1)*stepDelay}});
		
		setTimeout(function() {
			start();
		},(stepCount+2)*stepDelay);
	});

	test("interpolated, delayed drag-n-drop", function() {
		var $dragElement = $('#dragArea');
		
		var stepDelay = 100,
			lastEventOccurrence;
		
		// Unbind "normal" assertExpectedEvent function and replace with a fuzzy variant of it
		function assertExpectedEventDelay(event) {
			var delay = Date.now() - lastEventOccurrence;
			ok(delay >= stepDelay-10, "Verify events occur delayed (delay: "+delay+")"); // stepDelay-10 means tolerance of 10ms
			if (tests.expectedEvents.length === 0) {
				ok(false, "Unexpected event: "+event.type);
				return;
			}
			for (var prop in tests.expectedEvents[0]) {
				if (tests.expectedEvents[0].hasOwnProperty(prop)) {
					strictEqual(event[prop], tests.expectedEvents[0][prop], "Comparing "+prop+" (expected: "+tests.expectedEvents[0][prop]+")");
				}
			}
			if (event.type === tests.expectedEvents[0].type) {
				tests.expectedEvents.shift();
			}
			lastEventOccurrence = Date.now();
		}
		$(document).off("mousemove", "#qunit-fixture", tests.assertExpectedEvent).on("mousemove", "#qunit-fixture", assertExpectedEventDelay);
		
		var dragStartX = Math.round($dragElement.offset().left+$dragElement.outerWidth()/2),
			dragStartY = Math.round($dragElement.offset().top+$dragElement.outerHeight()/2);
		
		var stepCount = 2,
			dragWidth = 90,
			stepWidth = dragWidth / (stepCount+1);
		
		tests.expectedEvents = [{type: "mousedown"}];
		for (var i=1; i <= stepCount+1; i+=1) {
			tests.expectedEvents.push({type: "mousemove", pageX: dragStartX+i*stepWidth, pageY: dragStartY });
		}
		tests.expectedEvents.push(
			{type: "simulate-drag"},
			{type: "mouseup", pageX: dragStartX+(stepCount+1)*stepWidth, pageY: dragStartY},
			{type: "simulate-drop"}
		);
		
		stop();
		lastEventOccurrence = Date.now();
		$dragElement.simulate("drag-n-drop", {dx: dragWidth, interpolation: {stepCount: stepCount, stepDelay: stepDelay}});
		
		setTimeout(function() {
			start();
		},(stepCount+2)*stepDelay);
	});

	test("interpolated, delayed drag-n-drop on target", function() {
		var $dragElement = $('#dragArea'),
			$dropTarget = $('#dropArea');
		
		var stepDelay = 100,
			lastEventOccurrence;
		
		// Unbind "normal" assertExpectedEvent function and replace with a fuzzy variant of it
		function assertExpectedEventDelay(event) {
			var delay = Date.now() - lastEventOccurrence;
			ok(delay >= stepDelay-10, "Verify events occur delayed (delay: "+delay+")"); // stepDelay-10 means tolerance of 10ms
			if (tests.expectedEvents.length === 0) {
				ok(false, "Unexpected event: "+event.type);
				return;
			}
			for (var prop in tests.expectedEvents[0]) {
				if (tests.expectedEvents[0].hasOwnProperty(prop)) {
					strictEqual(event[prop], tests.expectedEvents[0][prop], "Comparing "+prop+" (expected: "+tests.expectedEvents[0][prop]+")");
				}
			}
			if (event.type === tests.expectedEvents[0].type) {
				tests.expectedEvents.shift();
			}
			lastEventOccurrence = Date.now();
		}
		$(document).off("mousemove", "#qunit-fixture", tests.assertExpectedEvent).on("mousemove", "#qunit-fixture", assertExpectedEventDelay);
		
		var dragStartX = Math.round($dragElement.offset().left+$dragElement.outerWidth()/2),
			dragStartY = Math.round($dragElement.offset().top+$dragElement.outerHeight()/2),
			dragEndX = Math.round($dropTarget.offset().left+$dropTarget.outerWidth()/2),
			dragEndY = Math.round($dropTarget.offset().top+$dropTarget.outerHeight()/2);
		
		var stepCount = 2,
			stepWidthX = (dragEndX - dragStartX) / (stepCount+1),
			stepWidthY = (dragEndY - dragStartY) / (stepCount+1);
		
		tests.expectedEvents = [{type: "mousedown"}];
		for (var i=1; i <= stepCount+1; i+=1) {
			tests.expectedEvents.push({type: "mousemove", pageX: Math.round(dragStartX+i*stepWidthX), pageY: Math.round(dragStartY+i*stepWidthY) });
		}
		tests.expectedEvents.push(
			{type: "simulate-drag"},
			{type: "mouseup", pageX: dragEndX, pageY: dragEndY},
			{type: "simulate-drop"}
		);
		
		stop();
		lastEventOccurrence = Date.now();
		$dragElement.simulate("drag-n-drop", {dropTarget: $dropTarget, interpolation: {stepCount: stepCount, stepDelay: stepDelay}});
		
		setTimeout(function() {
			start();
		},(stepCount+2)*stepDelay);
	});
	
	test("drag-n-drop within iFrame", function() {
		
		function actualTest() {
			var iFrameDoc = window.frames[0].document,
				$dragElement = $(iFrameDoc.getElementById("iframe-dragArea")),
				$dropElement = $(iFrameDoc.getElementById("iframe-dropArea"));
		
			$(document).off("keyup keydown keypress mousedown mouseup mousemove simulate-drag simulate-drop", '#qunit-fixture', tests.assertExpectedEvent);
			$(iFrameDoc).on("keyup keydown keypress mousedown mouseup mousemove simulate-drag simulate-drop", 'body', tests.assertExpectedEvent);

			var expectedX = Math.round($dropElement.offset().left+$dropElement.outerWidth()/2),
				expectedY = Math.round($dropElement.offset().top+$dropElement.outerHeight()/2);

			tests.expectedEvents = [
				{type: "mousedown"},
				{type: "mousemove", pageX: expectedX, pageY: expectedY},
				{type: "simulate-drag"},
				{type: "mouseup", pageX: expectedX, pageY: expectedY},
				{type: "simulate-drop"}
			];

			$($dragElement).simulate("drag-n-drop", {dropTarget: $dropElement});

			$(iFrameDoc).off("keyup keydown keypress mousedown mouseup mousemove simulate-drag simulate-drop", tests.assertExpectedEvent);
		}
		
		// Delay test until iframe is loaded
		stop();
		function delayUntilIFrameLoad() {
			var iFrameDoc = window.frames[0].document,
				$dropElement = $(iFrameDoc.getElementById("iframe-dropArea"));
			
			if ($dropElement.length !== 0) {
				$(document).scrollTop(500);
				actualTest();
				$(document).scrollTop(0);
				start();
			}
			else {
				setTimeout(delayUntilIFrameLoad, 100);
			}
		}
		delayUntilIFrameLoad();
	});
	
	test("clickToDrag / clickToDrop", function() {
		var $testElement = $('#dragArea');
		
		var dragX = 50,
			dragY = 10;
		var expectedX = Math.round($testElement.offset().left+$testElement.outerWidth()/2)+dragX,
			expectedY = Math.round($testElement.offset().top+$testElement.outerHeight()/2)+dragY;

		tests.expectedEvents = [
			{type: "mousedown"},
			{type: "mouseup"},
			{type: "click"},
			{type: "mousemove", pageX: expectedX, pageY: expectedY},
			{type: "simulate-drag"},
			{type: "mousedown"},
			{type: "mouseup", pageX: expectedX, pageY: expectedY},
			{type: "click"},
			{type: "simulate-drop"}
		];
		
		$testElement.simulate("drag-n-drop", {dx: dragX, dy: dragY, clickToDrag: true, clickToDrop: true});
	});
	
	test("drag-n-drop with eventProps", function() {
		var $testElement = $('#dragArea');
		
		var dragX = 50,
			dragY = 10,
			customProp = "test!";
		var expectedX = Math.round($testElement.offset().left+$testElement.outerWidth()/2)+dragX,
			expectedY = Math.round($testElement.offset().top+$testElement.outerHeight()/2)+dragY;

		tests.expectedEvents = [
			{type: "mousedown", myCustomProp: customProp},
			{type: "mousemove", pageX: expectedX, pageY: expectedY, myCustomProp: customProp},
			{type: "simulate-drag"},
			{type: "mouseup", pageX: expectedX, pageY: expectedY, myCustomProp: customProp},
			{type: "simulate-drop"}
		];
		
		$testElement.simulate("drag-n-drop", {dx: dragX, dy: dragY, eventProps: {jQueryTrigger: true, myCustomProp: customProp}});
	});
	
	test("drag-n-drop with eventProps without jQueryTrigger", function() {
		var $testElement = $('#dragArea');
		
		var dragX = 50,
			dragY = 10,
			customProp = "test!";
		var expectedX = Math.round($testElement.offset().left+$testElement.outerWidth()/2)+dragX,
			expectedY = Math.round($testElement.offset().top+$testElement.outerHeight()/2)+dragY;

		tests.expectedEvents = [
			{type: "mousedown", myCustomProp: undefined},
			{type: "mousemove", pageX: expectedX, pageY: expectedY, myCustomProp: undefined},
			{type: "simulate-drag"},
			{type: "mouseup", pageX: expectedX, pageY: expectedY, myCustomProp: undefined},
			{type: "simulate-drop"}
		];
		
		$testElement.simulate("drag-n-drop", {dx: dragX, dy: dragY, eventProps: {jQueryTrigger: false, myCustomProp: customProp}});
	});


});
	
}(jQuery));