ContextPadSpec.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  1. /* global sinon */
  2. import {
  3. createEvent as globalEvent
  4. } from '../../../util/MockEvents';
  5. import {
  6. bootstrapDiagram,
  7. getDiagramJS,
  8. inject
  9. } from 'test/TestHelper';
  10. import {
  11. query as domQuery,
  12. queryAll as domQueryAll,
  13. classes as domClasses
  14. } from 'min-dom';
  15. import contextPadModule from 'lib/features/context-pad';
  16. import ContextPadProvider from './ContextPadProvider';
  17. var providerModule = {
  18. __init__: [ 'contextPadProvider' ],
  19. contextPadProvider: ['type', ContextPadProvider ]
  20. };
  21. var initPadModule = {
  22. __init__: [ 'contextPad' ]
  23. };
  24. describe('features/context-pad', function() {
  25. describe('bootstrap', function() {
  26. beforeEach(bootstrapDiagram({ modules: [ contextPadModule, initPadModule ] }));
  27. it('should bootstrap diagram with component', inject(function(canvas, contextPad) {
  28. canvas.addShape({ id: 's1', width: 100, height: 100, x: 10, y: 10 });
  29. canvas.addShape({ id: 's2', width: 50, height: 50, x: 200, y: 10 });
  30. canvas.addShape({ id: 's3', width: 150, height: 150, x: 300, y: 300 });
  31. expect(contextPad).to.exist;
  32. }));
  33. });
  34. describe('providers', function() {
  35. beforeEach(bootstrapDiagram({ modules: [ contextPadModule, initPadModule ] }));
  36. function Provider(entries) {
  37. this.getContextPadEntries = function(element) {
  38. return entries || {};
  39. };
  40. }
  41. it('should register provider', inject(function(contextPad) {
  42. // given
  43. var provider = new Provider();
  44. // when
  45. contextPad.registerProvider(provider);
  46. // then
  47. expect(contextPad._providers).to.eql([ provider ]);
  48. }));
  49. it('should query provider for entries', inject(function(contextPad) {
  50. // given
  51. var provider = new Provider();
  52. contextPad.registerProvider(provider);
  53. sinon.spy(provider, 'getContextPadEntries');
  54. // when
  55. var entries = contextPad.getEntries('FOO');
  56. // then
  57. expect(entries).to.eql({});
  58. // pass over providers
  59. expect(provider.getContextPadEntries).to.have.been.calledWith('FOO');
  60. }));
  61. describe('entry className', function() {
  62. function testClassName(options) {
  63. var set = options.set,
  64. expected = options.expect;
  65. return inject(function(contextPad, canvas) {
  66. // given
  67. var entries = {
  68. 'entryA': {
  69. alt: 'A',
  70. className: set
  71. }
  72. };
  73. var provider = new Provider(entries);
  74. // when
  75. contextPad.registerProvider(provider);
  76. // given
  77. var shape = { id: 's1', width: 100, height: 100, x: 10, y: 10 };
  78. canvas.addShape(shape);
  79. // when
  80. contextPad.open(shape);
  81. var pad = contextPad.getPad(shape),
  82. padContainer = pad.html;
  83. // then DOM should contain entries
  84. var entryA = domQuery('[data-action="entryA"]', padContainer);
  85. expect(entryA).to.exist;
  86. // expect all classes to be set
  87. expected.forEach(function(cls) {
  88. expect(domClasses(entryA).has(cls)).to.be.true;
  89. });
  90. });
  91. }
  92. it('should recognize Array<String> as className', testClassName({
  93. set: [ 'FOO', 'BAAAR' ],
  94. expect: [ 'FOO', 'BAAAR' ]
  95. }));
  96. it('should recognize <space separated classes> as className', testClassName({
  97. set: 'FOO BAAAR blub',
  98. expect: [ 'FOO', 'BAAAR', 'blub' ]
  99. }));
  100. });
  101. });
  102. describe('lifecycle', function() {
  103. beforeEach(bootstrapDiagram({ modules: [ contextPadModule, providerModule ] }));
  104. function expectEntries(contextPad, element, entries) {
  105. var pad = contextPad.getPad(element),
  106. html = pad.html;
  107. entries.forEach(function(e) {
  108. var entry = domQuery('[data-action="' + e + '"]', html);
  109. expect(entry).not.to.be.null;
  110. });
  111. expect(domQueryAll('.entry', html).length).to.equal(entries.length);
  112. }
  113. it('should open', inject(function(canvas, contextPad) {
  114. // given
  115. var shape = { id: 's1', width: 100, height: 100, x: 10, y: 10 };
  116. canvas.addShape(shape);
  117. // when
  118. contextPad.open(shape);
  119. // then
  120. expect(contextPad.isOpen()).to.be.true;
  121. }));
  122. it('should provide context dependent entries', inject(function(canvas, contextPad) {
  123. // given
  124. var shapeA = { id: 's1', type: 'A', width: 100, height: 100, x: 10, y: 10 };
  125. var shapeB = { id: 's2', type: 'B', width: 100, height: 100, x: 210, y: 10 };
  126. canvas.addShape(shapeA);
  127. canvas.addShape(shapeB);
  128. // when (1)
  129. contextPad.open(shapeA);
  130. // then (1)
  131. expectEntries(contextPad, shapeA, [ 'action.a', 'action.b' ]);
  132. // when (2)
  133. contextPad.open(shapeB);
  134. // then (2)
  135. expectEntries(contextPad, shapeB, [ 'action.c', 'action.no-image' ]);
  136. // when (3)
  137. contextPad.open(shapeA);
  138. contextPad.close();
  139. // then (3)
  140. expect(contextPad.isOpen()).to.be.false;
  141. }));
  142. it('should close', inject(function(canvas, contextPad, overlays) {
  143. // given
  144. var shape = { id: 's1', width: 100, height: 100, x: 10, y: 10 };
  145. canvas.addShape(shape);
  146. contextPad.open(shape);
  147. // when
  148. contextPad.close();
  149. // then
  150. expect(overlays.get({ element: shape })).to.have.length(0);
  151. expect(!!contextPad.isOpen()).to.be.false;
  152. }));
  153. it('should reopen, resetting entries', inject(function(canvas, contextPad) {
  154. // given
  155. var shape = { id: 's1', width: 100, height: 100, x: 10, y: 10 };
  156. canvas.addShape(shape);
  157. contextPad.open(shape);
  158. contextPad.close();
  159. // when
  160. contextPad.open(shape);
  161. // then
  162. expectEntries(contextPad, shape, [ 'action.c', 'action.no-image' ]);
  163. }));
  164. it('should reopen if current element changed', inject(function(eventBus, canvas, contextPad) {
  165. // given
  166. var shape = { id: 's1', width: 100, height: 100, x: 10, y: 10 };
  167. canvas.addShape(shape);
  168. contextPad.open(shape);
  169. var open = sinon.spy(contextPad, 'open');
  170. // when
  171. eventBus.fire('element.changed', { element: shape });
  172. // then
  173. expect(open).to.have.been.calledWith(shape, true);
  174. }));
  175. it('should not reopen if other element changed', inject(function(eventBus, canvas, contextPad) {
  176. // given
  177. var shape = { id: 's1', width: 100, height: 100, x: 10, y: 10 };
  178. canvas.addShape(shape);
  179. contextPad.open(shape);
  180. var open = sinon.spy(contextPad, 'open');
  181. // when
  182. eventBus.fire('element.changed', { element: canvas.getRootElement() });
  183. // then
  184. expect(open).not.to.have.been.called;
  185. }));
  186. });
  187. describe('event handling', function() {
  188. beforeEach(bootstrapDiagram({ modules: [ contextPadModule, providerModule ] }));
  189. it('should handle click event', inject(function(canvas, contextPad) {
  190. // given
  191. var shape = canvas.addShape({ id: 's1', width: 100, height: 100, x: 10, y: 10 });
  192. contextPad.open(shape);
  193. var pad = contextPad.getPad(shape),
  194. html = pad.html,
  195. target = domQuery('[data-action="action.c"]', html);
  196. var event = globalEvent(target, { x: 0, y: 0 });
  197. // when
  198. contextPad.trigger('click', event);
  199. // then
  200. expect(event.__handled).to.be.true;
  201. }));
  202. it('should prevent unhandled events', inject(function(canvas, contextPad) {
  203. // given
  204. var shape = canvas.addShape({ id: 's1', width: 100, height: 100, x: 10, y: 10 });
  205. contextPad.open(shape);
  206. var pad = contextPad.getPad(shape),
  207. html = pad.html,
  208. target = domQuery('[data-action="action.c"]', html);
  209. var event = globalEvent(target, { x: 0, y: 0 });
  210. // when
  211. contextPad.trigger('dragstart', event);
  212. // then
  213. expect(event.defaultPrevented).to.be.true;
  214. }));
  215. it('should handle drag event', inject(function(canvas, contextPad) {
  216. // given
  217. var shape = canvas.addShape({
  218. id: 's1',
  219. width: 100, height: 100,
  220. x: 10, y: 10,
  221. type: 'drag'
  222. });
  223. contextPad.open(shape);
  224. var pad = contextPad.getPad(shape),
  225. html = pad.html,
  226. target = domQuery('[data-action="action.dragstart"]', html);
  227. var event = globalEvent(target, { x: 0, y: 0 });
  228. // when
  229. contextPad.trigger('dragstart', event);
  230. // then
  231. expect(event.__handled).to.be.true;
  232. }));
  233. });
  234. describe('scaling', function() {
  235. var NUM_REGEX = /([+-]?\d*[.]?\d+)(?=,|\))/g;
  236. var zoomLevels = [ 1.0, 1.2, 3.5, 10, 0.5 ];
  237. function asVector(scaleStr) {
  238. if (scaleStr && scaleStr !== 'none') {
  239. var m = scaleStr.match(NUM_REGEX);
  240. var x = parseFloat(m[0], 10);
  241. var y = m[1] ? parseFloat(m[1], 10) : x;
  242. return {
  243. x: x,
  244. y: y
  245. };
  246. }
  247. }
  248. function scaleVector(element) {
  249. return asVector(element.style.transform);
  250. }
  251. function verifyScales(expectedScales) {
  252. return getDiagramJS().invoke(function(canvas, contextPad) {
  253. // given
  254. var shape = canvas.addShape({
  255. id: 's1',
  256. width: 100, height: 100,
  257. x: 10, y: 10,
  258. type: 'drag'
  259. });
  260. contextPad.open(shape);
  261. var pad = contextPad.getPad(shape);
  262. var padParent = pad.html.parentNode;
  263. // test multiple zoom steps
  264. zoomLevels.forEach(function(zoom, idx) {
  265. var expectedScale = expectedScales[idx];
  266. // when
  267. canvas.zoom(zoom);
  268. var actualScale = scaleVector(padParent) || { x: 1, y: 1 };
  269. var effectiveScale = zoom * actualScale.x;
  270. // then
  271. expect(actualScale.x).to.eql(actualScale.y);
  272. expect(effectiveScale).to.be.closeTo(expectedScale, 0.00001);
  273. });
  274. });
  275. }
  276. it('should scale [ 1.0, 1.5 ] by default', function() {
  277. // given
  278. var expectedScales = [ 1.0, 1.2, 1.5, 1.5, 1.0 ];
  279. bootstrapDiagram({
  280. modules: [ contextPadModule, providerModule ]
  281. })();
  282. // when
  283. verifyScales(expectedScales);
  284. });
  285. it('should scale [ 1.0, 1.5 ] without scale config', function() {
  286. // given
  287. var expectedScales = [ 1.0, 1.2, 1.5, 1.5, 1.0 ];
  288. bootstrapDiagram({
  289. modules: [ contextPadModule, providerModule ],
  290. contextPad: {}
  291. })();
  292. // when
  293. verifyScales(expectedScales);
  294. });
  295. it('should scale within the limits set in config', function() {
  296. // given
  297. var expectedScales = [ 1.0, 1.2, 1.2, 1.2, 1.0 ];
  298. var config = {
  299. scale: {
  300. min: 1.0,
  301. max: 1.2
  302. }
  303. };
  304. bootstrapDiagram({
  305. modules: [ contextPadModule, providerModule ],
  306. contextPad: config
  307. })();
  308. // when
  309. verifyScales(expectedScales);
  310. });
  311. it('should scale with scale = true', function() {
  312. // given
  313. var expectedScales = zoomLevels;
  314. var config = {
  315. scale: true
  316. };
  317. bootstrapDiagram({
  318. modules: [ contextPadModule, providerModule ],
  319. contextPad: config
  320. })();
  321. // when
  322. verifyScales(expectedScales);
  323. });
  324. it('should not scale with scale = false', function() {
  325. // given
  326. var expectedScales = [ 1.0, 1.0, 1.0, 1.0, 1.0 ];
  327. var config = {
  328. scale: false
  329. };
  330. bootstrapDiagram({
  331. modules: [ contextPadModule, providerModule ],
  332. contextPad: config
  333. })();
  334. // when
  335. verifyScales(expectedScales);
  336. });
  337. });
  338. });