svg-pan-zoom
Version:
JavaScript library for panning and zooming an SVG image from the mouse, touches and programmatically.
738 lines (543 loc) • 15.3 kB
JavaScript
var svg,
svgSelector = "#test-inline",
svgSelectorViewbox = "#test-viewbox",
svgSelectorTransform = "#test-transform",
svgSelectorViewboxTransform = "#test-viewbox-transform",
instance;
var initSvgPanZoom = function(options, alternativeSelector) {
if (options) {
return svgPanZoom(alternativeSelector || svgSelector, options);
} else {
return svgPanZoom(alternativeSelector || svgSelector);
}
};
/**
* Compare numbers taking in account an error
*
* @param {Float} number
* @param {Float} expected
* @param {Float} error Optional
* @param {String} message Optional
*/
var close = (QUnit.assert.close = function(number, expected, error, message) {
if (error === void 0 || error === null) {
error = 0.0001; // default error
}
/* eslint-disable eqeqeq */
var result =
number == expected ||
(number < expected + error && number > expected - error) ||
false;
/* eslint-enable eqeqeq */
QUnit.push(result, number, expected, message);
});
module("Test API", {
setup: function() {},
teardown: function() {
instance && instance.destroy && instance.destroy();
}
});
/**
* Pan state (enabled, disabled)
*/
test("by default pan should be enabled", function() {
expect(1);
instance = initSvgPanZoom();
equal(instance.isPanEnabled(), true);
});
test("disable pan via options", function() {
expect(1);
instance = initSvgPanZoom({ panEnabled: false });
equal(instance.isPanEnabled(), false);
});
test("disable and enable pan via API", function() {
expect(2);
instance = initSvgPanZoom();
instance.disablePan();
equal(instance.isPanEnabled(), false);
instance.enablePan();
equal(instance.isPanEnabled(), true);
});
/**
* Zoom state (enabled, disabled)
*/
test("by default zoom should be enabled", function() {
expect(1);
instance = initSvgPanZoom();
equal(instance.isZoomEnabled(), true);
});
test("disable zoom via options", function() {
expect(1);
instance = initSvgPanZoom({ zoomEnabled: false });
equal(instance.isZoomEnabled(), false);
});
test("disable and enable zoom via API", function() {
expect(2);
instance = initSvgPanZoom();
instance.disableZoom();
equal(instance.isZoomEnabled(), false);
instance.enableZoom();
equal(instance.isZoomEnabled(), true);
});
/**
* Controls state (enabled, disabled)
*/
test("by default controls are disabled", function() {
expect(1);
instance = initSvgPanZoom();
equal(instance.isControlIconsEnabled(), false);
});
test("enable controls via opions", function() {
expect(1);
instance = initSvgPanZoom({ controlIconsEnabled: true });
equal(instance.isControlIconsEnabled(), true);
});
test("disable and enable controls via API", function() {
expect(2);
instance = initSvgPanZoom();
instance.enableControlIcons();
equal(instance.isControlIconsEnabled(), true);
instance.disableControlIcons();
equal(instance.isControlIconsEnabled(), false);
});
/**
* Double click zoom state (enabled, disabled)
*/
test("by default double click zoom is enabled", function() {
expect(1);
instance = initSvgPanZoom();
equal(instance.isDblClickZoomEnabled(), true);
});
test("disable double click zoom via options", function() {
expect(1);
instance = initSvgPanZoom({ dblClickZoomEnabled: false });
equal(instance.isDblClickZoomEnabled(), false);
});
test("disable and enable double click zoom via API", function() {
expect(2);
instance = initSvgPanZoom();
instance.disableDblClickZoom();
equal(instance.isDblClickZoomEnabled(), false);
instance.enableDblClickZoom();
equal(instance.isDblClickZoomEnabled(), true);
});
/**
* Mouse wheel zoom state (enabled, disabled)
*/
test("by default mouse wheel zoom is enabled", function() {
expect(1);
instance = initSvgPanZoom();
equal(instance.isMouseWheelZoomEnabled(), true);
});
test("disable mouse wheel zoom via options", function() {
expect(1);
instance = initSvgPanZoom({ mouseWheelZoomEnabled: false });
equal(instance.isMouseWheelZoomEnabled(), false);
});
test("disable and enable mouse wheel zoom via API", function() {
expect(2);
instance = initSvgPanZoom();
instance.disableMouseWheelZoom();
equal(instance.isMouseWheelZoomEnabled(), false);
instance.enableMouseWheelZoom();
equal(instance.isMouseWheelZoomEnabled(), true);
});
/**
* Pan
*/
test("pan", function() {
expect(1);
instance = initSvgPanZoom();
instance.pan({ x: 100, y: 300 });
deepEqual(instance.getPan(), {
x: 100,
y: 300
});
});
test("pan through API should work even if pan is disabled", function() {
expect(1);
instance = initSvgPanZoom({ panEnabled: false });
instance.pan({ x: 100, y: 300 });
deepEqual(instance.getPan(), {
x: 100,
y: 300
});
});
test("pan by", function() {
expect(1);
instance = initSvgPanZoom();
var initialPan = instance.getPan();
instance.panBy({ x: 100, y: 300 });
deepEqual(instance.getPan(), {
x: initialPan.x + 100,
y: initialPan.y + 300
});
});
/**
* Pan callbacks
*/
test("before pan", function() {
expect(1);
instance = initSvgPanZoom();
var initialPan = instance.getPan();
instance.setBeforePan(function(point) {
deepEqual(point, initialPan);
});
instance.pan({ x: 100, y: 300 });
// Remove beforePan as it will be called on destroy
instance.setBeforePan(null);
// Pan one more time to test if it is really removed
instance.pan({ x: 50, y: 150 });
});
test("don't trigger on pan if canceld by before pan", function() {
expect(1);
instance = initSvgPanZoom({
onPan: function() {
QUnit.ok(true, "onUpdatedCTM got called");
}
});
instance.panBy({ x: 100, y: 300 });
instance.setBeforePan(function(oldPan, newPan) {
return false;
});
instance.panBy({ x: 100, y: 300 });
});
test("don't trigger on pan if canceld by before pan for each axis separately", function() {
expect(1);
instance = initSvgPanZoom({
onPan: function() {
QUnit.ok(true, "onUpdatedCTM got called");
}
});
instance.panBy({ x: 100, y: 300 });
instance.setBeforePan(function(oldPan, newPan) {
return { x: false, y: false };
});
instance.panBy({ x: 100, y: 300 });
});
test("don't trigger on pan if canceld by before pan for each axis separately", function() {
expect(1);
instance = initSvgPanZoom({
onPan: function() {
QUnit.ok(true, "onUpdatedCTM got called");
}
});
instance.panBy({ x: 100, y: 300 });
instance.setBeforePan(function(oldPan, newPan) {
return { x: false, y: false };
});
instance.panBy({ x: 100, y: 300 });
});
test("on pan", function() {
expect(1);
instance = initSvgPanZoom();
instance.setOnPan(function(point) {
deepEqual(point, { x: 100, y: 300 });
});
instance.pan({ x: 100, y: 300 });
// Remove onPan as it will be called on destroy
instance.setOnPan(null);
// Pan one more time to test if it is really removed
instance.pan({ x: 50, y: 150 });
});
test("change only X axis when Y axis change is prevented with before pan", function() {
expect(2);
instance = initSvgPanZoom();
var initialPan = instance.getPan();
instance.setOnPan(function(newPan) {
notEqual(newPan.x, initialPan.x);
equal(newPan.y, initialPan.y);
});
instance.setBeforePan(function(oldPan, newPan) {
return { y: false };
});
instance.panBy({ x: 100, y: 300 });
// Remove onPan as it will be called on destroy
instance.setOnPan(null);
});
test("change pan values from before pan", function() {
expect(1);
instance = initSvgPanZoom();
instance.setOnPan(function(newPan) {
deepEqual(newPan, { x: 1, y: 2 });
});
instance.setBeforePan(function(oldPan, newPan) {
return { x: 1, y: 2 };
});
instance.panBy({ x: 100, y: 300 });
// Remove onPan as it will be called on destroy
instance.setOnPan(null);
});
test("don't pan if before pan makes the pan unnecessary", function() {
expect(0);
instance = initSvgPanZoom();
var initialPan = instance.getPan();
instance.setOnPan(function() {
QUnit.ok(true, "onUpdatedCTM got called");
});
instance.setBeforePan(function(oldPan, newPan) {
return { x: false, y: initialPan.y };
});
instance.panBy({ x: 100, y: 300 });
// Remove onPan as it will be called on destroy
instance.setOnPan(null);
});
/**
* Zoom
*/
test("zoom", function() {
expect(1);
instance = initSvgPanZoom();
instance.zoom(3);
equal(instance.getZoom(), 3);
});
test("zoom by", function() {
expect(1);
instance = initSvgPanZoom();
var initialZoom = instance.getZoom();
instance.zoomBy(2);
equal(instance.getZoom(), initialZoom * 2);
});
test("zoom at point", function() {
expect(2);
instance = initSvgPanZoom({ fit: false });
instance.zoomAtPoint(2, { x: 200, y: 100 });
close(instance.getZoom(), 2);
deepEqual(instance.getPan(), { x: -300, y: -600 });
});
test("zoom at point by", function() {
expect(2);
instance = initSvgPanZoom({ fit: false });
instance.zoomAtPointBy(2, { x: 200, y: 100 });
close(instance.getZoom(), 2);
deepEqual(instance.getPan(), { x: -300, y: -600 });
});
test("zoom at point by (with SVG point)", function() {
expect(2);
instance = initSvgPanZoom({ fit: false });
var svgPoint = $(svgSelector)[0].createSVGPoint();
svgPoint.x = 200;
svgPoint.y = 100;
instance.zoomAtPointBy(2, svgPoint);
close(instance.getZoom(), 2);
deepEqual(instance.getPan(), { x: -300, y: -600 });
});
test("zoom in", function() {
expect(3);
instance = initSvgPanZoom({ fit: false });
instance.zoomIn();
close(instance.getZoom(), 1.1);
close(instance.getPan().x, -90);
close(instance.getPan().y, -290);
});
test("zoom out", function() {
expect(3);
instance = initSvgPanZoom({ fit: false });
instance.zoomOut();
close(instance.getZoom(), 0.90909);
close(instance.getPan().x, -13.636374);
close(instance.getPan().y, -213.636374);
});
/**
* Zoom settings (min, max, sensitivity)
*/
test("default min zoom", function() {
expect(1);
// Do not use fit as it will set original zoom different from 1
instance = initSvgPanZoom({ fit: false });
instance.zoom(0.1);
equal(instance.getZoom(), 0.5);
});
test("min zoom", function() {
expect(1);
// Do not use fit as it will set original zoom different from 1
instance = initSvgPanZoom({ fit: false, minZoom: 1 });
instance.zoom(0.01);
equal(instance.getZoom(), 1);
});
test("default max zoom", function() {
expect(1);
// Do not use fit as it will set original zoom different from 1
instance = initSvgPanZoom({ fit: false });
instance.zoom(50);
equal(instance.getZoom(), 10);
});
test("max zoom", function() {
expect(1);
// Do not use fit as it will set original zoom different from 1
instance = initSvgPanZoom({ fit: false, maxZoom: 20 });
instance.zoom(50);
equal(instance.getZoom(), 20);
});
test("test zoomScaleSensitivity using zoomIn and zoomOut", function() {
expect(2);
var sensitivity = 0.2;
// Do not use fit as it will set original zoom different from 1
instance = initSvgPanZoom({ fit: false, zoomScaleSensitivity: sensitivity });
// Get initial zoom
var initialZoom = instance.getZoom(); // should be one
instance.zoomIn();
close(
instance.getZoom(),
initialZoom * (1 + sensitivity),
null,
"Check if zoom in uses scale sensitivity right"
);
// Lets zoom to 2
instance.zoom(2);
// Now lets zoom out
instance.zoomOut();
close(
instance.getZoom(),
2 / (1 + sensitivity),
null,
"Check if zoom out uses scale sensitiviry right"
);
});
/**
* Zoom callbacks
*/
test("before zoom", function() {
expect(1);
instance = initSvgPanZoom();
var initialZoom = instance.getZoom();
instance.setBeforeZoom(function(scale) {
close(scale, initialZoom);
});
instance.zoom(2.3);
// Remove beforeZoom as it will be called on destroy
instance.setBeforeZoom(null);
// Zoom one more time to test if it is really removed
instance.zoom(2.4);
});
test("on zoom", function() {
expect(1);
instance = initSvgPanZoom();
instance.setOnZoom(function(scale) {
close(scale, 2.3);
});
instance.zoom(2.3);
// Remove onZoom as it will be called on destroy
instance.setOnZoom(null);
// Zoom one more time to test if it is really removed
instance.zoom(2.4);
});
/**
* Reseting
*/
test("reset zoom", function() {
expect(1);
instance = initSvgPanZoom();
var initialZoom = instance.getZoom();
instance.zoom(2.3);
instance.resetZoom();
close(instance.getZoom(), initialZoom);
});
test("reset pan", function() {
expect(1);
instance = initSvgPanZoom();
var initialPan = instance.getPan();
instance.panBy({ x: 100, y: 300 });
instance.resetPan();
deepEqual(instance.getPan(), initialPan);
});
test("reset (zoom and pan)", function() {
expect(2);
instance = initSvgPanZoom();
var initialZoom = instance.getZoom(),
initialPan = instance.getPan();
instance.zoom(2.3);
instance.panBy({ x: 100, y: 300 });
instance.reset();
close(instance.getZoom(), initialZoom);
deepEqual(instance.getPan(), initialPan);
});
/**
* Fit and center
*/
/**
* SVG size 700x300
* viewport zise 800x800
*
* If no viewBox attribute then initial zoom is always 1
*/
test("fit when initialized with fit: true", function() {
expect(1);
instance = initSvgPanZoom();
instance.fit();
close(instance.getZoom(), 1);
});
/**
* SVG size 700x300
* viewport zise 800x800
* zoom = Math.min(700/800, 300/800) = 0.375
*/
test("fit when initialized with fit: false", function() {
expect(1);
instance = initSvgPanZoom({ fit: false, minZoom: 0.1 });
instance.fit();
close(instance.getZoom(), 0.375);
});
/**
* SVG size 700x300
* viewport zise 800x800 (sides ratio is 1)
* zoom 1 => width = height = 300
*
* panX = (700 - 300)/2 = 200
* panY = (300 - 300)/2 = 0
*/
test("center when zoom is 1", function() {
expect(1);
instance = initSvgPanZoom();
instance.center();
deepEqual(instance.getPan(), { x: 200, y: 0 });
});
/**
* SVG size 700x300
* viewport zise 800x800 (sides ratio is 1)
* zoom 0.5 => width = height = 150
*
* panX = (700 - 150)/2 = 275
* panY = (300 - 150)/2 = 75
*/
test("center when zoom is 0.5", function() {
expect(1);
instance = initSvgPanZoom();
instance.zoom(0.5);
instance.center();
deepEqual(instance.getPan(), { x: 275, y: 75 });
});
/**
* Resize
*/
// TODO resize
/**
* On updated CTM callback
*/
asyncTest("onUpdatedCTM is called", function() {
// onUpdatedCTM will get called once on init and once after panBy
expect(2);
instance = initSvgPanZoom();
instance.setOnUpdatedCTM(function() {
QUnit.ok(true, "onUpdatedCTM got called");
});
instance.panBy({ x: 100, y: 300 });
setTimeout(function() {
start();
}, 100);
});
/**
* Destroy
*/
test("after destroy calling svgPanZoom again should return a new instance", function() {
expect(1);
instance = initSvgPanZoom();
instance.destroy();
var instance2 = initSvgPanZoom();
notStrictEqual(instance2, instance);
// Set it as null so teardown will not try to destroy it again
instance = null;
// Destroy second instance
instance2.destroy();
instance2 = null;
});