panzoom
Version:
Extensible, mobile friendly pan and zoom framework (supports DOM and SVG).
299 lines (246 loc) • 9.42 kB
JavaScript
var test = require('tap').test;
var JSDOM = require('jsdom').JSDOM;
var globalDom = new JSDOM('', { pretendToBeVisual: true });
global.window = globalDom.window;
global.document = globalDom.window.document;
global.HTMLElement = globalDom.window.HTMLElement;
global.SVGElement = globalDom.window.SVGElement;
var createPanzoom = require('../');
test('it can be created', (t) => {
// Note - have to do it after globals initialized.
var dom = new JSDOM(`<body><div class='content'></div></body>`);
const document = dom.window.document;
var content = document.querySelector('.content');
var panzoom = createPanzoom(content);
t.ok(panzoom, 'Created');
t.end();
});
test('can get min/max zoom', (t) => {
// Note - have to do it after globals initialized.
var dom = new JSDOM(`<body><div class='content'></div></body>`);
const document = dom.window.document;
var content = document.querySelector('.content');
var panzoom = createPanzoom(content, {
minZoom: 1,
maxZoom: 2
});
t.equal(panzoom.getMinZoom(), 1, 'min zoom is valid');
t.equal(panzoom.getMaxZoom(), 2, 'max zoom is valid');
t.end();
});
test('it updates transformation matrix on wheel event', t => {
var dom = new JSDOM(`<body><div class='content'></div></body>`);
const document = dom.window.document;
var content = document.querySelector('.content');
var panzoom = createPanzoom(content);
var wheelEvent = new dom.window.WheelEvent('wheel', {deltaY: 1});
document.body.dispatchEvent(wheelEvent);
setTimeout(() => {
var transform = panzoom.getTransform();
t.ok(transform.scale !== 1, 'Scale is updated');
t.ok(content.style.transform, 'transform applied');
t.end();
}, 40);
});
test('it can pause/resume', t => {
var dom = new JSDOM(`<body><div class='content'></div></body>`);
const document = dom.window.document;
var content = document.querySelector('.content');
var panzoom = createPanzoom(content);
t.ok(panzoom.isPaused() === false, 'not paused by default');
panzoom.pause();
t.ok(panzoom.isPaused() === true, 'Paused when requested');
var wheelEvent = new dom.window.WheelEvent('wheel', {deltaY: 1});
document.body.dispatchEvent(wheelEvent);
var originalTransform = panzoom.getTransform();
setTimeout(() => {
var transform = panzoom.getTransform();
t.ok(originalTransform.x === transform.x, 'x transform is the same');
t.ok(originalTransform.y === transform.y, 'y transform is the same' );
t.ok(originalTransform.scale === transform.scale, 'scale is the same');
panzoom.resume();
t.ok(panzoom.isPaused() === false, 'not paused by default');
wheelEvent = new dom.window.WheelEvent('wheel', {deltaY: 1});
document.body.dispatchEvent(wheelEvent);
setTimeout(() => {
transform = panzoom.getTransform();
t.ok(transform.scale !== 1, 'Scale is updated');
t.ok(content.style.transform, 'transform applied');
t.end();
}, 40);
}, 40);
});
test('it disposes correctly', t => {
var dom = new JSDOM(`<body><div class='content'></div></body>`);
const document = dom.window.document;
var content = document.querySelector('.content');
var panzoom = createPanzoom(content);
var wheelEvent = new dom.window.WheelEvent('wheel', {deltaY: 1});
content.dispatchEvent(wheelEvent);
var originalTransform;
setTimeout(verifyFirstChangeAndDispose, 40);
function verifyFirstChangeAndDispose() {
originalTransform = content.style.transform;
t.ok(originalTransform, 'transform applied first time');
panzoom.dispose();
var secondWheel = new dom.window.WheelEvent('wheel', {deltaY: 1});
content.dispatchEvent(secondWheel);
setTimeout(verifyTransformIsNotChanged, 40);
}
function verifyTransformIsNotChanged() {
t.equal(content.style.transform, originalTransform, 'Transform has not changed after dispose');
t.end();
}
});
test('it can use keyboard', t => {
var dom = new JSDOM(`<body><div class='content'></div></body>`);
const document = dom.window.document;
var content = document.querySelector('.content');
// JSDOM does not support this, have to override:
content.parentElement.getBoundingClientRect = makeBoundingRect(100, 100);
var panzoom = createPanzoom(content);
var counter = {};
panzoom.on('pan', countEvent(counter, 'pan'));
panzoom.on('transform', countEvent(counter, 'transform'));
panzoom.on('zoom', countEvent(counter, 'zoom'));
var DOWN_ARROW = 40;
var keyEvent = new dom.window.KeyboardEvent('keydown', {
keyCode: DOWN_ARROW,
bubbles: true
});
content.dispatchEvent(keyEvent);
setTimeout(verifyTransformIsChanged, 40);
function verifyTransformIsChanged() {
t.equal(counter.pan, 1, 'pan called');
t.equal(counter.transform, 1, 'transform called');
t.notOk(counter.zoom, 'Zoom should not have been called');
t.equal(content.style.transform.toString(), 'matrix(1, 0, 0, 1, 0, -5)', 'keydown changed the y position');
panzoom.dispose();
t.end();
}
});
test('it allows to cancel keyboard events', t => {
var dom = new JSDOM(`<body><div class='content'></div></body>`);
const document = dom.window.document;
var content = document.querySelector('.content');
// JSDOM does not support this, have to override:
content.parentElement.getBoundingClientRect = makeBoundingRect(100, 100);
var DOWN_ARROW = 40;
var filterKeyCalledCorrectly = false;
var panzoom = createPanzoom(content, {
filterKey(e, x, y, z) {
t.equal(e.keyCode, DOWN_ARROW, 'down arrow is used');
t.equal(x, 0, 'x has not changed');
t.equal(y, -1, 'y changed!');
t.equal(z, 0, 'z has not changed');
filterKeyCalledCorrectly = true;
// don't let panzoom to handle this event
return true;
}
});
var keyEvent = new dom.window.KeyboardEvent('keydown', {
keyCode: DOWN_ARROW,
bubbles: true
});
content.dispatchEvent(keyEvent);
setTimeout(verifyTransformIsChanged, 40);
function verifyTransformIsChanged() {
t.equal(content.style.transform.toString(), 'matrix(1, 0, 0, 1, 0, 0)', 'keydown does not change');
t.equal(filterKeyCalledCorrectly, true, 'filter key called correctly');
panzoom.dispose();
t.end();
}
});
test('double click zooms in', t => {
var dom = new JSDOM(`<body><div class='content'></div></body>`);
const document = dom.window.document;
var content = document.querySelector('.content');
// JSDOM does not support this, have to override:
content.parentElement.getBoundingClientRect = makeBoundingRect(100, 100);
var panzoom = createPanzoom(content);
var calledTimes = 0;
panzoom.on('zoom', function() {
calledTimes += 1;
});
var doubleClick = new dom.window.MouseEvent('dblclick', {
bubbles: true,
cancelable: true,
clientX: 50,
clientY: 50
});
content.dispatchEvent(doubleClick);
t.ok(doubleClick.defaultPrevented, 'default prevented');
setTimeout(verifyTransformIsChanged, 40);
function verifyTransformIsChanged() {
var transform = parseMatrixTransform(content.style.transform);
t.ok(transform, 'Transform is defined');
t.ok(transform.scaleX !== 1, 'Scale has changed');
t.ok(transform.scaleX === transform.scaleY, 'Scale is proportional');
t.ok(transform.dx !== 0 && transform.dy !== 0, 'translated a bit');
t.ok(calledTimes > 0, 'zoom event triggered');
panzoom.dispose();
t.end();
}
});
test('Can cancel preventDefault', t => {
var dom = new JSDOM(`<body><div class='content'></div></body>`);
const document = dom.window.document;
var content = document.querySelector('.content');
// JSDOM does not support this, have to override:
content.parentElement.getBoundingClientRect = makeBoundingRect(100, 100);
var panzoom = createPanzoom(content, {
onDoubleClick() {
// we don't want to prevent default!
return false;
}
});
var calledTimes = 0;
panzoom.on('zoom', function() {
calledTimes += 1;
});
var doubleClick = new dom.window.MouseEvent('dblclick', {
bubbles: true,
cancelable: true,
clientX: 50,
clientY: 50
});
content.dispatchEvent(doubleClick);
t.notOk(doubleClick.defaultPrevented, 'default should not be prevented');
setTimeout(verifyTransformIsChanged, 40);
function verifyTransformIsChanged() {
var transform = parseMatrixTransform(content.style.transform);
t.ok(transform, 'Transform is defined');
t.ok(transform.scaleX !== 1, 'Scale has changed');
t.ok(transform.scaleX === transform.scaleY, 'Scale is proportional');
t.ok(transform.dx !== 0 && transform.dy !== 0, 'translated a bit');
t.ok(calledTimes > 0, 'zoom event triggered');
panzoom.dispose();
t.end();
}
});
function makeBoundingRect(width, height) {
return function getBoundingClientRect() {
return {
left: 0,
top: 0,
width: width,
height: height
};
};
}
function parseMatrixTransform(transformString) {
if (!transformString) return;
var matches = transformString.match(/matrix\(([-+]?\d*\.?\d*), 0, 0, ([-+]?\d*\.?\d*), ([-+]?\d*\.?\d*), ([-+]?\d*\.?\d*)\)/);
if (!matches) return;
return {
scaleX: parseFloat(matches[1]),
scaleY: parseFloat(matches[2]),
dx: parseFloat(matches[3]),
dy: parseFloat(matches[4])
};
}
function countEvent(counter, name) {
return function() {
counter[name] = (counter[name] || 0) + 1;
};
}