test_api.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737
  1. var svg,
  2. svgSelector = "#test-inline",
  3. svgSelectorViewbox = "#test-viewbox",
  4. svgSelectorTransform = "#test-transform",
  5. svgSelectorViewboxTransform = "#test-viewbox-transform",
  6. instance;
  7. var initSvgPanZoom = function(options, alternativeSelector) {
  8. if (options) {
  9. return svgPanZoom(alternativeSelector || svgSelector, options);
  10. } else {
  11. return svgPanZoom(alternativeSelector || svgSelector);
  12. }
  13. };
  14. /**
  15. * Compare numbers taking in account an error
  16. *
  17. * @param {Float} number
  18. * @param {Float} expected
  19. * @param {Float} error Optional
  20. * @param {String} message Optional
  21. */
  22. var close = (QUnit.assert.close = function(number, expected, error, message) {
  23. if (error === void 0 || error === null) {
  24. error = 0.0001; // default error
  25. }
  26. /* eslint-disable eqeqeq */
  27. var result =
  28. number == expected ||
  29. (number < expected + error && number > expected - error) ||
  30. false;
  31. /* eslint-enable eqeqeq */
  32. QUnit.push(result, number, expected, message);
  33. });
  34. module("Test API", {
  35. setup: function() {},
  36. teardown: function() {
  37. instance && instance.destroy && instance.destroy();
  38. }
  39. });
  40. /**
  41. * Pan state (enabled, disabled)
  42. */
  43. test("by default pan should be enabled", function() {
  44. expect(1);
  45. instance = initSvgPanZoom();
  46. equal(instance.isPanEnabled(), true);
  47. });
  48. test("disable pan via options", function() {
  49. expect(1);
  50. instance = initSvgPanZoom({ panEnabled: false });
  51. equal(instance.isPanEnabled(), false);
  52. });
  53. test("disable and enable pan via API", function() {
  54. expect(2);
  55. instance = initSvgPanZoom();
  56. instance.disablePan();
  57. equal(instance.isPanEnabled(), false);
  58. instance.enablePan();
  59. equal(instance.isPanEnabled(), true);
  60. });
  61. /**
  62. * Zoom state (enabled, disabled)
  63. */
  64. test("by default zoom should be enabled", function() {
  65. expect(1);
  66. instance = initSvgPanZoom();
  67. equal(instance.isZoomEnabled(), true);
  68. });
  69. test("disable zoom via options", function() {
  70. expect(1);
  71. instance = initSvgPanZoom({ zoomEnabled: false });
  72. equal(instance.isZoomEnabled(), false);
  73. });
  74. test("disable and enable zoom via API", function() {
  75. expect(2);
  76. instance = initSvgPanZoom();
  77. instance.disableZoom();
  78. equal(instance.isZoomEnabled(), false);
  79. instance.enableZoom();
  80. equal(instance.isZoomEnabled(), true);
  81. });
  82. /**
  83. * Controls state (enabled, disabled)
  84. */
  85. test("by default controls are disabled", function() {
  86. expect(1);
  87. instance = initSvgPanZoom();
  88. equal(instance.isControlIconsEnabled(), false);
  89. });
  90. test("enable controls via opions", function() {
  91. expect(1);
  92. instance = initSvgPanZoom({ controlIconsEnabled: true });
  93. equal(instance.isControlIconsEnabled(), true);
  94. });
  95. test("disable and enable controls via API", function() {
  96. expect(2);
  97. instance = initSvgPanZoom();
  98. instance.enableControlIcons();
  99. equal(instance.isControlIconsEnabled(), true);
  100. instance.disableControlIcons();
  101. equal(instance.isControlIconsEnabled(), false);
  102. });
  103. /**
  104. * Double click zoom state (enabled, disabled)
  105. */
  106. test("by default double click zoom is enabled", function() {
  107. expect(1);
  108. instance = initSvgPanZoom();
  109. equal(instance.isDblClickZoomEnabled(), true);
  110. });
  111. test("disable double click zoom via options", function() {
  112. expect(1);
  113. instance = initSvgPanZoom({ dblClickZoomEnabled: false });
  114. equal(instance.isDblClickZoomEnabled(), false);
  115. });
  116. test("disable and enable double click zoom via API", function() {
  117. expect(2);
  118. instance = initSvgPanZoom();
  119. instance.disableDblClickZoom();
  120. equal(instance.isDblClickZoomEnabled(), false);
  121. instance.enableDblClickZoom();
  122. equal(instance.isDblClickZoomEnabled(), true);
  123. });
  124. /**
  125. * Mouse wheel zoom state (enabled, disabled)
  126. */
  127. test("by default mouse wheel zoom is enabled", function() {
  128. expect(1);
  129. instance = initSvgPanZoom();
  130. equal(instance.isMouseWheelZoomEnabled(), true);
  131. });
  132. test("disable mouse wheel zoom via options", function() {
  133. expect(1);
  134. instance = initSvgPanZoom({ mouseWheelZoomEnabled: false });
  135. equal(instance.isMouseWheelZoomEnabled(), false);
  136. });
  137. test("disable and enable mouse wheel zoom via API", function() {
  138. expect(2);
  139. instance = initSvgPanZoom();
  140. instance.disableMouseWheelZoom();
  141. equal(instance.isMouseWheelZoomEnabled(), false);
  142. instance.enableMouseWheelZoom();
  143. equal(instance.isMouseWheelZoomEnabled(), true);
  144. });
  145. /**
  146. * Pan
  147. */
  148. test("pan", function() {
  149. expect(1);
  150. instance = initSvgPanZoom();
  151. instance.pan({ x: 100, y: 300 });
  152. deepEqual(instance.getPan(), {
  153. x: 100,
  154. y: 300
  155. });
  156. });
  157. test("pan through API should work even if pan is disabled", function() {
  158. expect(1);
  159. instance = initSvgPanZoom({ panEnabled: false });
  160. instance.pan({ x: 100, y: 300 });
  161. deepEqual(instance.getPan(), {
  162. x: 100,
  163. y: 300
  164. });
  165. });
  166. test("pan by", function() {
  167. expect(1);
  168. instance = initSvgPanZoom();
  169. var initialPan = instance.getPan();
  170. instance.panBy({ x: 100, y: 300 });
  171. deepEqual(instance.getPan(), {
  172. x: initialPan.x + 100,
  173. y: initialPan.y + 300
  174. });
  175. });
  176. /**
  177. * Pan callbacks
  178. */
  179. test("before pan", function() {
  180. expect(1);
  181. instance = initSvgPanZoom();
  182. var initialPan = instance.getPan();
  183. instance.setBeforePan(function(point) {
  184. deepEqual(point, initialPan);
  185. });
  186. instance.pan({ x: 100, y: 300 });
  187. // Remove beforePan as it will be called on destroy
  188. instance.setBeforePan(null);
  189. // Pan one more time to test if it is really removed
  190. instance.pan({ x: 50, y: 150 });
  191. });
  192. test("don't trigger on pan if canceld by before pan", function() {
  193. expect(1);
  194. instance = initSvgPanZoom({
  195. onPan: function() {
  196. QUnit.ok(true, "onUpdatedCTM got called");
  197. }
  198. });
  199. instance.panBy({ x: 100, y: 300 });
  200. instance.setBeforePan(function(oldPan, newPan) {
  201. return false;
  202. });
  203. instance.panBy({ x: 100, y: 300 });
  204. });
  205. test("don't trigger on pan if canceld by before pan for each axis separately", function() {
  206. expect(1);
  207. instance = initSvgPanZoom({
  208. onPan: function() {
  209. QUnit.ok(true, "onUpdatedCTM got called");
  210. }
  211. });
  212. instance.panBy({ x: 100, y: 300 });
  213. instance.setBeforePan(function(oldPan, newPan) {
  214. return { x: false, y: false };
  215. });
  216. instance.panBy({ x: 100, y: 300 });
  217. });
  218. test("don't trigger on pan if canceld by before pan for each axis separately", function() {
  219. expect(1);
  220. instance = initSvgPanZoom({
  221. onPan: function() {
  222. QUnit.ok(true, "onUpdatedCTM got called");
  223. }
  224. });
  225. instance.panBy({ x: 100, y: 300 });
  226. instance.setBeforePan(function(oldPan, newPan) {
  227. return { x: false, y: false };
  228. });
  229. instance.panBy({ x: 100, y: 300 });
  230. });
  231. test("on pan", function() {
  232. expect(1);
  233. instance = initSvgPanZoom();
  234. instance.setOnPan(function(point) {
  235. deepEqual(point, { x: 100, y: 300 });
  236. });
  237. instance.pan({ x: 100, y: 300 });
  238. // Remove onPan as it will be called on destroy
  239. instance.setOnPan(null);
  240. // Pan one more time to test if it is really removed
  241. instance.pan({ x: 50, y: 150 });
  242. });
  243. test("change only X axis when Y axis change is prevented with before pan", function() {
  244. expect(2);
  245. instance = initSvgPanZoom();
  246. var initialPan = instance.getPan();
  247. instance.setOnPan(function(newPan) {
  248. notEqual(newPan.x, initialPan.x);
  249. equal(newPan.y, initialPan.y);
  250. });
  251. instance.setBeforePan(function(oldPan, newPan) {
  252. return { y: false };
  253. });
  254. instance.panBy({ x: 100, y: 300 });
  255. // Remove onPan as it will be called on destroy
  256. instance.setOnPan(null);
  257. });
  258. test("change pan values from before pan", function() {
  259. expect(1);
  260. instance = initSvgPanZoom();
  261. instance.setOnPan(function(newPan) {
  262. deepEqual(newPan, { x: 1, y: 2 });
  263. });
  264. instance.setBeforePan(function(oldPan, newPan) {
  265. return { x: 1, y: 2 };
  266. });
  267. instance.panBy({ x: 100, y: 300 });
  268. // Remove onPan as it will be called on destroy
  269. instance.setOnPan(null);
  270. });
  271. test("don't pan if before pan makes the pan unnecessary", function() {
  272. expect(0);
  273. instance = initSvgPanZoom();
  274. var initialPan = instance.getPan();
  275. instance.setOnPan(function() {
  276. QUnit.ok(true, "onUpdatedCTM got called");
  277. });
  278. instance.setBeforePan(function(oldPan, newPan) {
  279. return { x: false, y: initialPan.y };
  280. });
  281. instance.panBy({ x: 100, y: 300 });
  282. // Remove onPan as it will be called on destroy
  283. instance.setOnPan(null);
  284. });
  285. /**
  286. * Zoom
  287. */
  288. test("zoom", function() {
  289. expect(1);
  290. instance = initSvgPanZoom();
  291. instance.zoom(3);
  292. equal(instance.getZoom(), 3);
  293. });
  294. test("zoom by", function() {
  295. expect(1);
  296. instance = initSvgPanZoom();
  297. var initialZoom = instance.getZoom();
  298. instance.zoomBy(2);
  299. equal(instance.getZoom(), initialZoom * 2);
  300. });
  301. test("zoom at point", function() {
  302. expect(2);
  303. instance = initSvgPanZoom({ fit: false });
  304. instance.zoomAtPoint(2, { x: 200, y: 100 });
  305. close(instance.getZoom(), 2);
  306. deepEqual(instance.getPan(), { x: -300, y: -600 });
  307. });
  308. test("zoom at point by", function() {
  309. expect(2);
  310. instance = initSvgPanZoom({ fit: false });
  311. instance.zoomAtPointBy(2, { x: 200, y: 100 });
  312. close(instance.getZoom(), 2);
  313. deepEqual(instance.getPan(), { x: -300, y: -600 });
  314. });
  315. test("zoom at point by (with SVG point)", function() {
  316. expect(2);
  317. instance = initSvgPanZoom({ fit: false });
  318. var svgPoint = $(svgSelector)[0].createSVGPoint();
  319. svgPoint.x = 200;
  320. svgPoint.y = 100;
  321. instance.zoomAtPointBy(2, svgPoint);
  322. close(instance.getZoom(), 2);
  323. deepEqual(instance.getPan(), { x: -300, y: -600 });
  324. });
  325. test("zoom in", function() {
  326. expect(3);
  327. instance = initSvgPanZoom({ fit: false });
  328. instance.zoomIn();
  329. close(instance.getZoom(), 1.1);
  330. close(instance.getPan().x, -90);
  331. close(instance.getPan().y, -290);
  332. });
  333. test("zoom out", function() {
  334. expect(3);
  335. instance = initSvgPanZoom({ fit: false });
  336. instance.zoomOut();
  337. close(instance.getZoom(), 0.90909);
  338. close(instance.getPan().x, -13.636374);
  339. close(instance.getPan().y, -213.636374);
  340. });
  341. /**
  342. * Zoom settings (min, max, sensitivity)
  343. */
  344. test("default min zoom", function() {
  345. expect(1);
  346. // Do not use fit as it will set original zoom different from 1
  347. instance = initSvgPanZoom({ fit: false });
  348. instance.zoom(0.1);
  349. equal(instance.getZoom(), 0.5);
  350. });
  351. test("min zoom", function() {
  352. expect(1);
  353. // Do not use fit as it will set original zoom different from 1
  354. instance = initSvgPanZoom({ fit: false, minZoom: 1 });
  355. instance.zoom(0.01);
  356. equal(instance.getZoom(), 1);
  357. });
  358. test("default max zoom", function() {
  359. expect(1);
  360. // Do not use fit as it will set original zoom different from 1
  361. instance = initSvgPanZoom({ fit: false });
  362. instance.zoom(50);
  363. equal(instance.getZoom(), 10);
  364. });
  365. test("max zoom", function() {
  366. expect(1);
  367. // Do not use fit as it will set original zoom different from 1
  368. instance = initSvgPanZoom({ fit: false, maxZoom: 20 });
  369. instance.zoom(50);
  370. equal(instance.getZoom(), 20);
  371. });
  372. test("test zoomScaleSensitivity using zoomIn and zoomOut", function() {
  373. expect(2);
  374. var sensitivity = 0.2;
  375. // Do not use fit as it will set original zoom different from 1
  376. instance = initSvgPanZoom({ fit: false, zoomScaleSensitivity: sensitivity });
  377. // Get initial zoom
  378. var initialZoom = instance.getZoom(); // should be one
  379. instance.zoomIn();
  380. close(
  381. instance.getZoom(),
  382. initialZoom * (1 + sensitivity),
  383. null,
  384. "Check if zoom in uses scale sensitivity right"
  385. );
  386. // Lets zoom to 2
  387. instance.zoom(2);
  388. // Now lets zoom out
  389. instance.zoomOut();
  390. close(
  391. instance.getZoom(),
  392. 2 / (1 + sensitivity),
  393. null,
  394. "Check if zoom out uses scale sensitiviry right"
  395. );
  396. });
  397. /**
  398. * Zoom callbacks
  399. */
  400. test("before zoom", function() {
  401. expect(1);
  402. instance = initSvgPanZoom();
  403. var initialZoom = instance.getZoom();
  404. instance.setBeforeZoom(function(scale) {
  405. close(scale, initialZoom);
  406. });
  407. instance.zoom(2.3);
  408. // Remove beforeZoom as it will be called on destroy
  409. instance.setBeforeZoom(null);
  410. // Zoom one more time to test if it is really removed
  411. instance.zoom(2.4);
  412. });
  413. test("on zoom", function() {
  414. expect(1);
  415. instance = initSvgPanZoom();
  416. instance.setOnZoom(function(scale) {
  417. close(scale, 2.3);
  418. });
  419. instance.zoom(2.3);
  420. // Remove onZoom as it will be called on destroy
  421. instance.setOnZoom(null);
  422. // Zoom one more time to test if it is really removed
  423. instance.zoom(2.4);
  424. });
  425. /**
  426. * Reseting
  427. */
  428. test("reset zoom", function() {
  429. expect(1);
  430. instance = initSvgPanZoom();
  431. var initialZoom = instance.getZoom();
  432. instance.zoom(2.3);
  433. instance.resetZoom();
  434. close(instance.getZoom(), initialZoom);
  435. });
  436. test("reset pan", function() {
  437. expect(1);
  438. instance = initSvgPanZoom();
  439. var initialPan = instance.getPan();
  440. instance.panBy({ x: 100, y: 300 });
  441. instance.resetPan();
  442. deepEqual(instance.getPan(), initialPan);
  443. });
  444. test("reset (zoom and pan)", function() {
  445. expect(2);
  446. instance = initSvgPanZoom();
  447. var initialZoom = instance.getZoom(),
  448. initialPan = instance.getPan();
  449. instance.zoom(2.3);
  450. instance.panBy({ x: 100, y: 300 });
  451. instance.reset();
  452. close(instance.getZoom(), initialZoom);
  453. deepEqual(instance.getPan(), initialPan);
  454. });
  455. /**
  456. * Fit and center
  457. */
  458. /**
  459. * SVG size 700x300
  460. * viewport zise 800x800
  461. *
  462. * If no viewBox attribute then initial zoom is always 1
  463. */
  464. test("fit when initialized with fit: true", function() {
  465. expect(1);
  466. instance = initSvgPanZoom();
  467. instance.fit();
  468. close(instance.getZoom(), 1);
  469. });
  470. /**
  471. * SVG size 700x300
  472. * viewport zise 800x800
  473. * zoom = Math.min(700/800, 300/800) = 0.375
  474. */
  475. test("fit when initialized with fit: false", function() {
  476. expect(1);
  477. instance = initSvgPanZoom({ fit: false, minZoom: 0.1 });
  478. instance.fit();
  479. close(instance.getZoom(), 0.375);
  480. });
  481. /**
  482. * SVG size 700x300
  483. * viewport zise 800x800 (sides ratio is 1)
  484. * zoom 1 => width = height = 300
  485. *
  486. * panX = (700 - 300)/2 = 200
  487. * panY = (300 - 300)/2 = 0
  488. */
  489. test("center when zoom is 1", function() {
  490. expect(1);
  491. instance = initSvgPanZoom();
  492. instance.center();
  493. deepEqual(instance.getPan(), { x: 200, y: 0 });
  494. });
  495. /**
  496. * SVG size 700x300
  497. * viewport zise 800x800 (sides ratio is 1)
  498. * zoom 0.5 => width = height = 150
  499. *
  500. * panX = (700 - 150)/2 = 275
  501. * panY = (300 - 150)/2 = 75
  502. */
  503. test("center when zoom is 0.5", function() {
  504. expect(1);
  505. instance = initSvgPanZoom();
  506. instance.zoom(0.5);
  507. instance.center();
  508. deepEqual(instance.getPan(), { x: 275, y: 75 });
  509. });
  510. /**
  511. * Resize
  512. */
  513. // TODO resize
  514. /**
  515. * On updated CTM callback
  516. */
  517. asyncTest("onUpdatedCTM is called", function() {
  518. // onUpdatedCTM will get called once on init and once after panBy
  519. expect(2);
  520. instance = initSvgPanZoom();
  521. instance.setOnUpdatedCTM(function() {
  522. QUnit.ok(true, "onUpdatedCTM got called");
  523. });
  524. instance.panBy({ x: 100, y: 300 });
  525. setTimeout(function() {
  526. start();
  527. }, 100);
  528. });
  529. /**
  530. * Destroy
  531. */
  532. test("after destroy calling svgPanZoom again should return a new instance", function() {
  533. expect(1);
  534. instance = initSvgPanZoom();
  535. instance.destroy();
  536. var instance2 = initSvgPanZoom();
  537. notStrictEqual(instance2, instance);
  538. // Set it as null so teardown will not try to destroy it again
  539. instance = null;
  540. // Destroy second instance
  541. instance2.destroy();
  542. instance2 = null;
  543. });