mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2024-12-29 09:22:22 -05:00
Add mouse interaction tests
- Tests common mouse interactions scenarios to prevent regressions when making changes. These tests are not run in node context. - Prevent name collision between Javascript native classes and Paper.js classes (Event and MouseEvent) by patching load.js. - Uses a polyfill for MouseEvent which is missing in PhantomJS. - Adds View._clearState() method and use it in tests to make sure that each new test is started with a fresh state.
This commit is contained in:
parent
44a31c9399
commit
1bd67b2d9b
5 changed files with 447 additions and 6 deletions
|
@ -14,7 +14,7 @@
|
|||
// the browser, avoiding the step of having to manually preprocess it after each
|
||||
// change. This is very useful during development of the library itself.
|
||||
if (typeof window === 'object') {
|
||||
// Browser based loading through Prepro.js:
|
||||
// Browser based loading through Prepro.js:
|
||||
if (!window.include) {
|
||||
// Get the last script tag and assume it's the one that loaded this file
|
||||
// then get its src attribute and figure out the location of our root.
|
||||
|
@ -36,6 +36,13 @@ if (typeof window === 'object') {
|
|||
// the code the 2nd time around.
|
||||
load(root + 'src/load.js');
|
||||
} else {
|
||||
// Some native javascript classes have name collisions with Paper.js
|
||||
// classes. Store them to be able to use them later in tests.
|
||||
NativeClasses = {
|
||||
Event: Event,
|
||||
MouseEvent: MouseEvent
|
||||
};
|
||||
|
||||
include('options.js');
|
||||
// Load constants.js, required by the on-the-fly preprocessing:
|
||||
include('constants.js');
|
||||
|
|
|
@ -1498,7 +1498,31 @@ new function() { // Injection scope for event handling on the browser
|
|||
* Loops through all views and sets the focus on the first
|
||||
* active one.
|
||||
*/
|
||||
updateFocus: updateFocus
|
||||
updateFocus: updateFocus,
|
||||
|
||||
/**
|
||||
* Clear all events handling state informations. Made for testing
|
||||
* purpose, to have a way to start with a fresh state before each
|
||||
* test.
|
||||
* @private
|
||||
*/
|
||||
_clearState: function() {
|
||||
prevFocus = null;
|
||||
tempFocus = null;
|
||||
dragging = false;
|
||||
mouseDown = false;
|
||||
called = false;
|
||||
wasInView = false;
|
||||
overView = null;
|
||||
downPoint = null;
|
||||
lastPoint = null;
|
||||
downItem = null;
|
||||
overItem = null;
|
||||
dragItem = null;
|
||||
clickItem = null;
|
||||
clickTime = null;
|
||||
dblClick = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -48,12 +48,16 @@ console.error = function() {
|
|||
errorHandler.apply(this, arguments);
|
||||
};
|
||||
|
||||
var currentProject;
|
||||
|
||||
QUnit.done(function(details) {
|
||||
console.error = errorHandler;
|
||||
// Clear all event listeners after final test.
|
||||
if (currentProject) {
|
||||
currentProject.remove();
|
||||
}
|
||||
});
|
||||
|
||||
var currentProject;
|
||||
|
||||
// NOTE: In order to "export" all methods into the shared Prepro.js scope when
|
||||
// using node-qunit, we need to define global functions as:
|
||||
// `var name = function() {}`. `function name() {}` does not work!
|
||||
|
@ -61,9 +65,16 @@ var test = function(testName, expected) {
|
|||
return QUnit.test(testName, function(assert) {
|
||||
// Since tests can be asynchronous, remove the old project before
|
||||
// running the next test.
|
||||
if (currentProject)
|
||||
if (currentProject) {
|
||||
currentProject.remove();
|
||||
currentProject = new Project();
|
||||
// This is needed for interactions tests, to make sure that test is
|
||||
// run with a fresh state.
|
||||
View._clearState();
|
||||
}
|
||||
|
||||
// Instantiate project with 100x100 pixels canvas instead of default
|
||||
// 1x1 to make interactions tests simpler by working with integers.
|
||||
currentProject = new Project(CanvasProvider.getCanvas(100, 100));
|
||||
expected(assert);
|
||||
});
|
||||
};
|
||||
|
@ -550,3 +561,63 @@ var compareSVG = function(done, actual, expected, message, options) {
|
|||
compare();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Interactions helpers
|
||||
//
|
||||
var MouseEventPolyfill = function(type, params) {
|
||||
var mouseEvent = document.createEvent('MouseEvent');
|
||||
mouseEvent.initMouseEvent(
|
||||
type,
|
||||
params.bubbles,
|
||||
params.cancelable,
|
||||
window,
|
||||
0,
|
||||
params.screenX,
|
||||
params.screenY,
|
||||
params.clientX,
|
||||
params.clientY,
|
||||
params.ctrlKey,
|
||||
params.altKey,
|
||||
params.shiftKey,
|
||||
params.metaKey,
|
||||
params.button,
|
||||
params.relatedTarget
|
||||
);
|
||||
return mouseEvent;
|
||||
};
|
||||
MouseEventPolyfill.prototype = typeof NativeClasses !== 'undefined'
|
||||
&& NativeClasses.Event.prototype || Event.prototype;
|
||||
|
||||
var triggerMouseEvent = function(type, point, target) {
|
||||
// Depending on event type, events have to be triggered on different
|
||||
// elements due to the event handling implementation (see `viewEvents`
|
||||
// and `docEvents` in View.js). And we cannot rely on the fact that event
|
||||
// will bubble from canvas to document, since the canvas used in tests is
|
||||
// not inserted in DOM.
|
||||
target = target || (type === 'mousedown' ? view._element : document);
|
||||
|
||||
// If `gulp load` was run, there is a name collision between paper Event /
|
||||
// MouseEvent and native javascript classes. In this case, we need to use
|
||||
// native classes stored in global NativeClasses object instead.
|
||||
var constructor = typeof NativeClasses !== 'undefined'
|
||||
&& NativeClasses.MouseEvent || MouseEvent;
|
||||
|
||||
// MouseEvent class does not exist in PhantomJS, so in that case, we need to
|
||||
// use a polyfill method.
|
||||
if (typeof constructor !== 'function') {
|
||||
constructor = MouseEventPolyfill;
|
||||
}
|
||||
|
||||
var event = new constructor(type, {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
composed: true,
|
||||
clientX: point.x,
|
||||
clientY: point.y,
|
||||
screenX: point.x,
|
||||
screenY: point.y
|
||||
});
|
||||
target.dispatchEvent(event);
|
||||
};
|
||||
|
|
336
test/tests/Interactions.js
Normal file
336
test/tests/Interactions.js
Normal file
|
@ -0,0 +1,336 @@
|
|||
/*
|
||||
* Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
|
||||
* http://paperjs.org/
|
||||
*
|
||||
* Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey
|
||||
* http://scratchdisk.com/ & http://jonathanpuckey.com/
|
||||
*
|
||||
* Distributed under the MIT license. See LICENSE file for details.
|
||||
*
|
||||
* All rights reserved.
|
||||
*/
|
||||
|
||||
/**
|
||||
* These tests are focused on user interactions.
|
||||
* They trigger events and check callbacks.
|
||||
* Warning: when running tests locally from `gulp test:browser` command, don't
|
||||
* move your mouse over the window because that could perturbate tests
|
||||
* execution.
|
||||
*/
|
||||
QUnit.module('Interactions');
|
||||
|
||||
//
|
||||
// Mouse
|
||||
//
|
||||
test('Item#onMouseDown()', function(assert) {
|
||||
var done = assert.async();
|
||||
var item = new Path.Rectangle(new Point(0, 0), new Size(10));
|
||||
item.fillColor = 'red';
|
||||
var point = new Point(5, 5);
|
||||
item.onMouseDown = function(event) {
|
||||
equals(event.type, 'mousedown');
|
||||
equals(event.point, point);
|
||||
equals(event.target, item);
|
||||
equals(event.currentTarget, item);
|
||||
equals(event.delta, null);
|
||||
done();
|
||||
};
|
||||
triggerMouseEvent('mousedown', point);
|
||||
});
|
||||
|
||||
test('Item#onMouseDown() with stroked item', function(assert) {
|
||||
var done = assert.async();
|
||||
var item = new Path.Rectangle(new Point(0, 0), new Size(10));
|
||||
item.strokeColor = 'red';
|
||||
var point = new Point(0, 0);
|
||||
item.onMouseDown = function(event) {
|
||||
equals(event.type, 'mousedown');
|
||||
equals(event.point, point);
|
||||
equals(event.target, item);
|
||||
equals(event.currentTarget, item);
|
||||
equals(event.delta, null);
|
||||
done();
|
||||
};
|
||||
triggerMouseEvent('mousedown', point);
|
||||
});
|
||||
|
||||
test('Item#onMouseDown() is not triggered when item is not filled', function(assert) {
|
||||
var item = new Path.Rectangle(new Point(0, 0), new Size(10));
|
||||
item.onMouseDown = function(event) {
|
||||
throw 'this should not be called';
|
||||
};
|
||||
triggerMouseEvent('mousedown', new Point(5, 5));
|
||||
expect(0);
|
||||
});
|
||||
|
||||
test('Item#onMouseDown() is not triggered when item is not visible', function(assert) {
|
||||
var item = new Path.Rectangle(new Point(0, 0), new Size(10));
|
||||
item.fillColor = 'red';
|
||||
item.visible = false;
|
||||
item.onMouseDown = function(event) {
|
||||
throw 'this should not be called';
|
||||
};
|
||||
triggerMouseEvent('mousedown', new Point(5, 5));
|
||||
expect(0);
|
||||
});
|
||||
|
||||
test('Item#onMouseDown() is not triggered when item is locked', function(assert) {
|
||||
var item = new Path.Rectangle(new Point(0, 0), new Size(10));
|
||||
item.fillColor = 'red';
|
||||
item.locked = true;
|
||||
item.onMouseDown = function(event) {
|
||||
throw 'this should not be called';
|
||||
};
|
||||
triggerMouseEvent('mousedown', new Point(5, 5));
|
||||
expect(0);
|
||||
});
|
||||
|
||||
test('Item#onMouseDown() is not triggered when another item is in front', function(assert) {
|
||||
var item = new Path.Rectangle(new Point(0, 0), new Size(10));
|
||||
item.fillColor = 'red';
|
||||
var item2 = item.clone();
|
||||
item.onMouseDown = function(event) {
|
||||
throw 'this should not be called';
|
||||
};
|
||||
triggerMouseEvent('mousedown', new Point(5, 5));
|
||||
expect(0);
|
||||
});
|
||||
|
||||
test('Item#onMouseDown() is not triggered if event target is document', function(assert) {
|
||||
var item = new Path.Rectangle(new Point(0, 0), new Size(10));
|
||||
item.fillColor = 'red';
|
||||
item.onMouseDown = function(event) {
|
||||
throw 'this should not be called';
|
||||
};
|
||||
triggerMouseEvent('mousedown', new Point(5, 5), document);
|
||||
expect(0);
|
||||
});
|
||||
|
||||
test('Item#onMouseMove()', function(assert) {
|
||||
var done = assert.async();
|
||||
var item = new Path.Rectangle(new Point(0, 0), new Size(10));
|
||||
item.fillColor = 'red';
|
||||
var point = new Point(5, 5);
|
||||
item.onMouseMove = function(event) {
|
||||
equals(event.type, 'mousemove');
|
||||
equals(event.point, point);
|
||||
equals(event.target, item);
|
||||
equals(event.currentTarget, item);
|
||||
equals(event.delta, null);
|
||||
done();
|
||||
};
|
||||
triggerMouseEvent('mousemove', point);
|
||||
});
|
||||
|
||||
test('Item#onMouseMove() is not re-triggered if point is the same', function(assert) {
|
||||
var item = new Path.Rectangle(new Point(0, 0), new Size(10));
|
||||
item.fillColor = 'red';
|
||||
var point = new Point(5, 5);
|
||||
var counter = 0;
|
||||
item.onMouseMove = function(event) {
|
||||
equals(true, true);
|
||||
};
|
||||
triggerMouseEvent('mousemove', point);
|
||||
triggerMouseEvent('mousemove', point);
|
||||
expect(1);
|
||||
});
|
||||
|
||||
test('Item#onMouseUp()', function(assert) {
|
||||
var done = assert.async();
|
||||
var item = new Path.Rectangle(new Point(0, 0), new Size(10));
|
||||
item.fillColor = 'red';
|
||||
var point = new Point(5, 5);
|
||||
item.onMouseUp = function(event) {
|
||||
equals(event.type, 'mouseup');
|
||||
equals(event.point, point);
|
||||
equals(event.target, item);
|
||||
equals(event.currentTarget, item);
|
||||
equals(event.delta, new Point(0, 0));
|
||||
done();
|
||||
};
|
||||
triggerMouseEvent('mousedown', point);
|
||||
triggerMouseEvent('mouseup', point);
|
||||
});
|
||||
|
||||
test('Item#onMouseUp() is only triggered after mouse down', function(assert) {
|
||||
var item = new Path.Rectangle(new Point(0, 0), new Size(10));
|
||||
item.fillColor = 'red';
|
||||
item.onMouseUp = function(event) {
|
||||
throw 'this should not be called';
|
||||
};
|
||||
triggerMouseEvent('mouseup', new Point(5, 5));
|
||||
expect(0);
|
||||
});
|
||||
|
||||
test('Item#onClick()', function(assert) {
|
||||
var done = assert.async();
|
||||
var item = new Path.Rectangle(new Point(0, 0), new Size(10));
|
||||
item.fillColor = 'red';
|
||||
var point = new Point(5, 5);
|
||||
item.onClick = function(event) {
|
||||
equals(event.type, 'click');
|
||||
equals(event.point, point);
|
||||
equals(event.target, item);
|
||||
equals(event.currentTarget, item);
|
||||
equals(event.delta, new Point(0, 0));
|
||||
done();
|
||||
};
|
||||
triggerMouseEvent('mousedown', point);
|
||||
triggerMouseEvent('mouseup', point);
|
||||
});
|
||||
|
||||
test('Item#onClick() is not triggered if up point is not on item', function(assert) {
|
||||
var item = new Path.Rectangle(new Point(0, 0), new Size(10));
|
||||
item.fillColor = 'red';
|
||||
item.onClick = function(event) {
|
||||
throw 'this should not be called';
|
||||
};
|
||||
triggerMouseEvent('mousedown', new Point(5, 5));
|
||||
triggerMouseEvent('mouseup', new Point(15, 15));
|
||||
expect(0);
|
||||
});
|
||||
|
||||
test('Item#onClick() is not triggered if down point is not on item', function(assert) {
|
||||
var item = new Path.Rectangle(new Point(0, 0), new Size(10));
|
||||
item.fillColor = 'red';
|
||||
item.onClick = function(event) {
|
||||
throw 'this should not be called';
|
||||
};
|
||||
triggerMouseEvent('mousedown', new Point(15, 15));
|
||||
triggerMouseEvent('mouseup', new Point(5, 5));
|
||||
expect(0);
|
||||
});
|
||||
|
||||
test('Item#onDoubleClick()', function(assert) {
|
||||
var done = assert.async();
|
||||
var item = new Path.Rectangle(new Point(0, 0), new Size(10));
|
||||
item.fillColor = 'red';
|
||||
var point = new Point(5, 5);
|
||||
item.onDoubleClick = function(event) {
|
||||
equals(event.type, 'doubleclick');
|
||||
equals(event.point, point);
|
||||
equals(event.target, item);
|
||||
equals(event.currentTarget, item);
|
||||
equals(event.delta, new Point(0, 0));
|
||||
done();
|
||||
};
|
||||
triggerMouseEvent('mousedown', point);
|
||||
triggerMouseEvent('mouseup', point);
|
||||
triggerMouseEvent('mousedown', point);
|
||||
triggerMouseEvent('mouseup', point);
|
||||
});
|
||||
|
||||
test('Item#onDoubleClick() is not triggered if both clicks are not on same item', function(assert) {
|
||||
var item = new Path.Rectangle(new Point(0, 0), new Size(10));
|
||||
item.fillColor = 'red';
|
||||
var item2 = item.clone().translate(5);
|
||||
item.onDoubleClick = function(event) {
|
||||
throw 'this should not be called';
|
||||
};
|
||||
triggerMouseEvent('mousedown', new Point(5, 5));
|
||||
triggerMouseEvent('mouseup', new Point(5, 5));
|
||||
triggerMouseEvent('mousedown', new Point(6, 6));
|
||||
triggerMouseEvent('mouseup', new Point(6, 6));
|
||||
expect(0);
|
||||
});
|
||||
|
||||
test('Item#onDoubleClick() is not triggered if time between both clicks is too long', function(assert) {
|
||||
var done = assert.async();
|
||||
var item = new Path.Rectangle(new Point(0, 0), new Size(10));
|
||||
item.fillColor = 'red';
|
||||
var point = new Point(5, 5);
|
||||
item.onDoubleClick = function(event) {
|
||||
throw 'this should not be called';
|
||||
};
|
||||
triggerMouseEvent('mousedown', point);
|
||||
triggerMouseEvent('mouseup', point);
|
||||
setTimeout(function() {
|
||||
triggerMouseEvent('mousedown', point);
|
||||
triggerMouseEvent('mouseup', point);
|
||||
done();
|
||||
}, 301);
|
||||
expect(0);
|
||||
});
|
||||
|
||||
test('Item#onMouseEnter()', function(assert) {
|
||||
var done = assert.async();
|
||||
var item = new Path.Rectangle(new Point(0, 0), new Size(10));
|
||||
item.fillColor = 'red';
|
||||
var point = new Point(5, 5);
|
||||
item.onMouseEnter = function(event) {
|
||||
equals(event.type, 'mouseenter');
|
||||
equals(event.point, point);
|
||||
equals(event.target, item);
|
||||
equals(event.currentTarget, item);
|
||||
equals(event.delta, null);
|
||||
done();
|
||||
};
|
||||
triggerMouseEvent('mousemove', point);
|
||||
});
|
||||
|
||||
test('Item#onMouseEnter() is only re-triggered after mouse leave', function(assert) {
|
||||
var item = new Path.Rectangle(new Point(0, 0), new Size(10));
|
||||
item.fillColor = 'red';
|
||||
item.onMouseEnter = function(event) {
|
||||
equals(true, true);
|
||||
};
|
||||
// enter
|
||||
triggerMouseEvent('mousemove', new Point(5, 5));
|
||||
triggerMouseEvent('mousemove', new Point(6, 6));
|
||||
triggerMouseEvent('mousemove', new Point(7, 7));
|
||||
// leave
|
||||
triggerMouseEvent('mousemove', new Point(11, 11));
|
||||
// re-enter
|
||||
triggerMouseEvent('mousemove', new Point(10, 10));
|
||||
expect(2);
|
||||
});
|
||||
|
||||
test('Item#onMouseLeave()', function(assert) {
|
||||
var done = assert.async();
|
||||
var item = new Path.Rectangle(new Point(0, 0), new Size(10));
|
||||
item.fillColor = 'red';
|
||||
var point1 = new Point(5, 5);
|
||||
var point2 = new Point(15, 15);
|
||||
item.onMouseLeave = function(event) {
|
||||
equals(event.type, 'mouseleave');
|
||||
equals(event.point, point2);
|
||||
equals(event.target, item);
|
||||
equals(event.currentTarget, item);
|
||||
equals(event.delta, null);
|
||||
done();
|
||||
};
|
||||
triggerMouseEvent('mousemove', point1);
|
||||
triggerMouseEvent('mousemove', point2);
|
||||
});
|
||||
|
||||
test('Item#onMouseDrag()', function(assert) {
|
||||
var done = assert.async();
|
||||
var item = new Path.Rectangle(new Point(0, 0), new Size(10));
|
||||
item.fillColor = 'red';
|
||||
var point1 = new Point(5, 5);
|
||||
var point2 = new Point(15, 15);
|
||||
item.onMouseDrag = function(event) {
|
||||
equals(event.type, 'mousedrag');
|
||||
equals(event.point, point2);
|
||||
equals(event.target, item);
|
||||
equals(event.currentTarget, item);
|
||||
equals(event.delta, new Point(10, 10));
|
||||
done();
|
||||
};
|
||||
triggerMouseEvent('mousedown', point1);
|
||||
triggerMouseEvent('mousemove', point2);
|
||||
});
|
||||
|
||||
test('Item#onMouseDrag() is not triggered after mouse up', function(assert) {
|
||||
var item = new Path.Rectangle(new Point(0, 0), new Size(10));
|
||||
item.fillColor = 'red';
|
||||
item.onMouseDrag = function(event) {
|
||||
equals(true, true);
|
||||
};
|
||||
triggerMouseEvent('mousedown', new Point(5, 5));
|
||||
triggerMouseEvent('mousemove', new Point(6, 6));
|
||||
triggerMouseEvent('mouseup', new Point(7, 7));
|
||||
triggerMouseEvent('mousemove', new Point(8, 8));
|
||||
expect(1);
|
||||
});
|
||||
|
|
@ -62,3 +62,6 @@
|
|||
/*#*/ include('SvgExport.js');
|
||||
|
||||
/*#*/ include('Numerical.js');
|
||||
|
||||
// There is no need to test interactions in node context.
|
||||
if (!isNode) /*#*/ include('Interactions.js');
|
||||
|
|
Loading…
Reference in a new issue