contextMenu.e2e.js 84 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825
  1. describe('ContextMenu', function () {
  2. var id = 'testContainer';
  3. beforeEach(function () {
  4. this.$container = $('<div id="' + id + '"></div>').appendTo('body');
  5. });
  6. afterEach(function () {
  7. if (this.$container) {
  8. destroy();
  9. this.$container.remove();
  10. }
  11. });
  12. it('should update context menu items by calling `updateSettings` method', function () {
  13. var hot = handsontable({
  14. contextMenu: ['row_above', 'row_below', '---------', 'remove_row'],
  15. height: 100
  16. });
  17. contextMenu();
  18. var items = $('.htContextMenu tbody td');
  19. var actions = items.not('.htSeparator');
  20. var separators = items.filter('.htSeparator');
  21. expect(actions.length).toEqual(3);
  22. expect(separators.length).toEqual(1);
  23. expect(actions.text()).toEqual(['Insert row above', 'Insert row below', 'Remove row'].join(''));
  24. hot.updateSettings({
  25. contextMenu: ['remove_row']
  26. });
  27. contextMenu();
  28. items = $('.htContextMenu tbody td');
  29. actions = items.not('.htSeparator');
  30. separators = items.filter('.htSeparator');
  31. expect(actions.length).toEqual(1);
  32. expect(separators.length).toEqual(0);
  33. expect(actions.text()).toEqual(['Remove row'].join(''));
  34. hot.updateSettings({
  35. contextMenu: {
  36. items: {
  37. remove_col: true,
  38. hsep1: '---------',
  39. custom: { name: 'My custom item' }
  40. }
  41. }
  42. });
  43. contextMenu();
  44. items = $('.htContextMenu tbody td');
  45. actions = items.not('.htSeparator');
  46. separators = items.filter('.htSeparator');
  47. expect(actions.length).toEqual(2);
  48. expect(separators.length).toEqual(1);
  49. expect(actions.text()).toEqual(['Remove column', 'My custom item'].join(''));
  50. });
  51. describe('menu opening', function () {
  52. it('should open menu after right click on table cell', function () {
  53. var hot = handsontable({
  54. contextMenu: true,
  55. height: 100
  56. });
  57. expect(hot.getPlugin('contextMenu')).toBeDefined();
  58. expect($('.htContextMenu').is(':visible')).toBe(false);
  59. contextMenu();
  60. expect($('.htContextMenu').is(':visible')).toBe(true);
  61. });
  62. it('should not open the menu after clicking an open editor', function () {
  63. var hot = handsontable({
  64. data: Handsontable.helper.createSpreadsheetData(4, 4),
  65. contextMenu: true,
  66. height: 100
  67. });
  68. selectCell(2, 2);
  69. keyDownUp('enter');
  70. expect(hot.getPlugin('contextMenu')).toBeDefined();
  71. expect($('.htContextMenu').is(':visible')).toBe(false);
  72. contextMenu(hot.getActiveEditor().TEXTAREA);
  73. expect($('.htContextMenu').is(':visible')).toBe(false);
  74. });
  75. it('should open menu after right click on header cell when only header cells are visible', function () {
  76. var hot = handsontable({
  77. data: [],
  78. colHeaders: ['Year', 'Kia'],
  79. columns: [{ data: 0 }, { data: 1 }],
  80. contextMenu: true,
  81. height: 100
  82. });
  83. expect(hot.getPlugin('contextMenu')).toBeDefined();
  84. expect($('.htContextMenu').is(':visible')).toBe(false);
  85. contextMenu(hot.rootElement.querySelector('.ht_clone_top thead th'));
  86. expect($('.htContextMenu').is(':visible')).toBe(true);
  87. });
  88. it('should open menu after right click on header corner', function () {
  89. var hot = handsontable({
  90. data: [],
  91. colHeaders: true,
  92. rowHeaders: true,
  93. contextMenu: true,
  94. height: 100
  95. });
  96. expect(hot.getPlugin('contextMenu')).toBeDefined();
  97. expect($('.htContextMenu').is(':visible')).toBe(false);
  98. contextMenu(hot.rootElement.querySelector('.ht_clone_top_left_corner thead th'));
  99. expect($('.htContextMenu').is(':visible')).toBe(true);
  100. });
  101. it('should open menu after right click active cell border', function () {
  102. var hot = handsontable({
  103. contextMenu: true,
  104. height: 100
  105. });
  106. expect(hot.getPlugin('contextMenu')).toBeDefined();
  107. expect($('.htContextMenu').is(':visible')).toBe(false);
  108. selectCell(0, 0);
  109. this.$container.find('.wtBorder.current:eq(0)').simulate('contextmenu');
  110. expect($('.htContextMenu').is(':visible')).toBe(true);
  111. });
  112. });
  113. describe('menu closing', function () {
  114. it('should close menu after click', function () {
  115. var hot = handsontable({
  116. contextMenu: true,
  117. height: 100
  118. });
  119. contextMenu();
  120. expect($('.htContextMenu').is(':visible')).toBe(true);
  121. mouseDown(this.$container);
  122. expect($('.htContextMenu').is(':visible')).toBe(false);
  123. });
  124. it('should close menu after click under the menu', function () {
  125. var hot = handsontable({
  126. data: Handsontable.helper.createSpreadsheetData(500, 10),
  127. contextMenu: true,
  128. height: 500
  129. });
  130. contextMenu();
  131. expect($('.htContextMenu').is(':visible')).toBe(true);
  132. var rect = $('.htContextMenu')[0].getBoundingClientRect();
  133. var x = parseInt(rect.left + rect.width / 2, 10);
  134. var y = parseInt(rect.top + rect.height, 10);
  135. mouseDown(document.elementFromPoint(x, y));
  136. expect($('.htContextMenu').is(':visible')).toBe(false);
  137. });
  138. });
  139. describe('menu disabled', function () {
  140. it('should not open menu after right click', function () {
  141. var hot = handsontable({
  142. contextMenu: true,
  143. height: 100
  144. });
  145. hot.getPlugin('contextMenu').disablePlugin();
  146. expect($('.htContextMenu').is(':visible')).toBe(false);
  147. contextMenu();
  148. expect($('.htContextMenu').is(':visible')).toBe(false);
  149. });
  150. it('should not create context menu if it\'s disabled in constructor options', function () {
  151. var hot = handsontable({
  152. contextMenu: false,
  153. height: 100
  154. });
  155. expect(hot.getPlugin('contextMenu').isEnabled()).toBe(false);
  156. });
  157. it('should reenable menu', function () {
  158. var hot = handsontable({
  159. contextMenu: true,
  160. height: 100
  161. });
  162. hot.getPlugin('contextMenu').disablePlugin();
  163. expect($('.htContextMenu').is(':visible')).toBe(false);
  164. contextMenu();
  165. expect($('.htContextMenu').is(':visible')).toBe(false);
  166. hot.getPlugin('contextMenu').enablePlugin();
  167. contextMenu();
  168. expect($('.htContextMenu').is(':visible')).toBe(true);
  169. });
  170. it('should reenable menu with updateSettings when it was disabled in constructor', function () {
  171. var hot = handsontable({
  172. contextMenu: false,
  173. height: 100
  174. });
  175. expect(hot.getPlugin('contextMenu').isEnabled()).toBe(false);
  176. updateSettings({
  177. contextMenu: true
  178. });
  179. expect(hot.getPlugin('contextMenu').isEnabled()).toBe(true);
  180. expect($('.htContextMenu').is(':visible')).toBe(false);
  181. contextMenu();
  182. expect($('.htContextMenu').is(':visible')).toBe(true);
  183. });
  184. it('should disable menu with updateSettings when it was enabled in constructor', function () {
  185. var hot = handsontable({
  186. contextMenu: true,
  187. height: 100
  188. });
  189. expect(hot.getPlugin('contextMenu').isEnabled()).toBe(true);
  190. updateSettings({
  191. contextMenu: false
  192. });
  193. expect(hot.getPlugin('contextMenu').isEnabled()).toBe(false);
  194. });
  195. it('should work properly (remove row) after destroy and new init', function () {
  196. var test = function test() {
  197. handsontable({
  198. startRows: 5,
  199. contextMenu: ['remove_row'],
  200. height: 100
  201. });
  202. selectCell(0, 0);
  203. contextMenu();
  204. $('.htContextMenu .ht_master .htCore tbody').find('td').not('.htSeparator').eq(0).simulate('mousedown');
  205. expect(getData().length).toEqual(4);
  206. };
  207. test();
  208. destroy();
  209. test();
  210. });
  211. });
  212. describe('menu hidden items', function () {
  213. it('should remove separators from top, bottom and duplicated', function () {
  214. var hot = handsontable({
  215. contextMenu: ['---------', '---------', 'row_above', '---------', '---------', 'row_below', '---------', 'remove_row'],
  216. height: 100
  217. });
  218. contextMenu();
  219. var items = $('.htContextMenu tbody td');
  220. var actions = items.not('.htSeparator');
  221. var separators = items.filter('.htSeparator');
  222. expect(actions.length).toEqual(3);
  223. expect(separators.length).toEqual(2);
  224. });
  225. it('should hide option if hidden function return true', function () {
  226. var hot = handsontable({
  227. startCols: 5,
  228. colHeaders: true,
  229. contextMenu: [{
  230. key: '',
  231. name: 'Custom option',
  232. hidden: function hidden() {
  233. return !this.selection.selectedHeader.cols;
  234. }
  235. }]
  236. });
  237. contextMenu();
  238. var items = $('.htContextMenu tbody td');
  239. var actions = items.not('.htSeparator');
  240. expect(actions.length).toEqual(0);
  241. var header = $('.ht_clone_top thead th').eq(1);
  242. header.simulate('mousedown');
  243. contextMenu();
  244. items = $('.htContextMenu tbody td');
  245. actions = items.not('.htSeparator');
  246. expect(actions.length).toEqual(1);
  247. });
  248. });
  249. describe('menu destroy', function () {
  250. it('should close context menu when HOT is being destroyed', function () {
  251. var hot = handsontable({
  252. contextMenu: true,
  253. height: 100
  254. });
  255. contextMenu();
  256. expect($('.htContextMenu').is(':visible')).toBe(true);
  257. destroy();
  258. expect($('.htContextMenu').is(':visible')).toBe(false);
  259. });
  260. });
  261. describe('subMenu', function () {
  262. it('should not open subMenu immediately', function (done) {
  263. var hot = handsontable({
  264. data: Handsontable.helper.createSpreadsheetData(4, 4),
  265. contextMenu: true,
  266. height: 100
  267. });
  268. contextMenu();
  269. var item = $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(9);
  270. item.simulate('mouseover');
  271. var contextSubMenu = $('.htContextMenuSub_' + item.text()).find('tbody td');
  272. expect(contextSubMenu.length).toEqual(0);
  273. setTimeout(function () {
  274. var contextSubMenu = $('.htContextMenuSub_' + item.text()).find('tbody td');
  275. expect(contextSubMenu.length).toEqual(0);
  276. done();
  277. }, 100);
  278. });
  279. it('should open subMenu with delay', function (done) {
  280. var hot = handsontable({
  281. data: Handsontable.helper.createSpreadsheetData(4, 4),
  282. contextMenu: true,
  283. height: 100
  284. });
  285. contextMenu();
  286. var item = $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(9);
  287. item.simulate('mouseover');
  288. setTimeout(function () {
  289. var contextSubMenu = $('.htContextMenuSub_' + item.text());
  290. expect(contextSubMenu.length).toEqual(1);
  291. done();
  292. }, 350); // menu opens after 300ms
  293. });
  294. it('should NOT open subMenu if there is no subMenu for item', function () {
  295. var hot = handsontable({
  296. data: Handsontable.helper.createSpreadsheetData(4, 4),
  297. contextMenu: true,
  298. height: 100
  299. });
  300. contextMenu();
  301. var item = $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(8);
  302. item.simulate('mouseover');
  303. expect(item.hasClass('htSubmenu')).toBe(false);
  304. var contextSubMenu = $('.htContextMenuSub_' + item.text());
  305. expect(contextSubMenu.length).toEqual(0);
  306. });
  307. it('should open subMenu on the left of main menu if on the right there\'s no space left', function () {
  308. var hot = handsontable({
  309. data: Handsontable.helper.createSpreadsheetData(4, Math.floor(window.innerWidth / 50)),
  310. contextMenu: true,
  311. width: window.innerWidth
  312. });
  313. selectCell(0, countCols() - 1);
  314. contextMenu();
  315. var item = $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(9);
  316. var contextMenuRoot = $('.htContextMenu');
  317. item.simulate('mouseover');
  318. expect(item.text()).toBe('Alignment');
  319. expect(item.hasClass('htSubmenu')).toBe(true);
  320. var contextSubMenu = $('.htContextMenuSub_' + item.text());
  321. expect(contextSubMenu.offset().left).toBeLessThan(contextMenuRoot.offset().left - contextSubMenu.width() + 30); // 30 - scroll
  322. });
  323. it('should open subMenu on the right of main menu if there\'s free space', function (done) {
  324. var hot = handsontable({
  325. data: Handsontable.helper.createSpreadsheetData(4, Math.floor(window.innerWidth / 50)),
  326. contextMenu: true,
  327. width: window.innerWidth
  328. });
  329. selectCell(0, countCols() - 9);
  330. contextMenu();
  331. var item = $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(9);
  332. var contextMenuRoot = $('.htContextMenu');
  333. item.simulate('mouseover');
  334. setTimeout(function () {
  335. expect(item.text()).toBe('Alignment');
  336. expect(item.hasClass('htSubmenu')).toBe(true);
  337. var contextSubMenu = $('.htContextMenuSub_' + item.text());
  338. expect(contextSubMenu.offset().left).toBeGreaterThan(contextMenuRoot.offset().left + contextMenuRoot.width() - 30); // 30 - scroll
  339. done();
  340. }, 350); // menu opens after 300ms
  341. });
  342. it('should open subMenu on the left-bottom of main menu if there\'s free space', function (done) {
  343. var hot = handsontable({
  344. data: Handsontable.helper.createSpreadsheetData(Math.floor(window.innerHeight / 23), Math.floor(window.innerWidth / 50)),
  345. contextMenu: true,
  346. height: window.innerHeight
  347. });
  348. window.scrollTo(0, document.body.clientHeight);
  349. selectCell(0, countCols() - 1);
  350. contextMenu();
  351. var item = $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(9);
  352. var contextMenuRoot = $('.htContextMenu');
  353. item.simulate('mouseover');
  354. setTimeout(function () {
  355. expect(item.text()).toBe('Alignment');
  356. expect(item.hasClass('htSubmenu')).toBe(true);
  357. var contextSubMenu = $('.htContextMenuSub_' + item.text());
  358. expect(parseInt(contextSubMenu.offset().top, 10)).toBeAroundValue(parseInt(item.offset().top, 10) - 1);
  359. expect(parseInt(contextSubMenu.offset().left, 10)).toBeLessThan(contextMenuRoot.offset().left - contextSubMenu.width() + 30); // 30 - scroll
  360. done();
  361. }, 350); // menu opens after 300ms
  362. });
  363. it('should open subMenu on the right-bottom of main menu if there\'s free space', function (done) {
  364. var hot = handsontable({
  365. data: Handsontable.helper.createSpreadsheetData(Math.floor(window.innerHeight / 23), Math.floor(window.innerWidth / 50)),
  366. contextMenu: true,
  367. height: window.innerHeight
  368. });
  369. window.scrollTo(0, document.body.clientHeight);
  370. selectCell(0, countCols() - 9);
  371. contextMenu();
  372. var item = $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(9);
  373. var contextMenuRoot = $('.htContextMenu');
  374. item.simulate('mouseover');
  375. setTimeout(function () {
  376. expect(item.text()).toBe('Alignment');
  377. expect(item.hasClass('htSubmenu')).toBe(true);
  378. var contextSubMenu = $('.htContextMenuSub_' + item.text());
  379. expect(parseInt(contextSubMenu.offset().top, 10)).toBeAroundValue(parseInt(item.offset().top, 10) - 1);
  380. expect(parseInt(contextSubMenu.offset().left, 10)).toBeGreaterThan(contextMenuRoot.offset().left + contextMenuRoot.width() - 30); // 30 - scroll
  381. done();
  382. }, 350); // menu opens after 300ms
  383. });
  384. it('should open subMenu on the left-top of main menu if there\'s no free space on bottom', function (done) {
  385. var hot = handsontable({
  386. data: Handsontable.helper.createSpreadsheetData(Math.floor(window.innerHeight / 23), Math.floor(window.innerWidth / 50)),
  387. contextMenu: true,
  388. height: window.innerHeight
  389. });
  390. selectCell(countRows() - 1, countCols() - 1);
  391. contextMenu();
  392. var item = $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(9);
  393. var contextMenuRoot = $('.htContextMenu');
  394. item.simulate('mouseover');
  395. setTimeout(function () {
  396. expect(item.text()).toBe('Alignment');
  397. expect(item.hasClass('htSubmenu')).toBe(true);
  398. var contextSubMenu = $('.htContextMenuSub_' + item.text());
  399. expect(contextSubMenu.offset().top + contextSubMenu.height() - 28).toBeAroundValue(item.offset().top);
  400. expect(contextSubMenu.offset().left).toBeLessThan(contextMenuRoot.offset().left - contextSubMenu.width() + 30); // 30 - scroll
  401. done();
  402. }, 350); // menu opens after 300ms
  403. });
  404. it('should open subMenu on the right-top of main menu if there\'s no free space on bottom', function (done) {
  405. var hot = handsontable({
  406. data: Handsontable.helper.createSpreadsheetData(Math.floor(window.innerHeight / 23), Math.floor(window.innerWidth / 50)),
  407. contextMenu: true,
  408. height: window.innerHeight
  409. });
  410. selectCell(countRows() - 1, countCols() - 9);
  411. contextMenu();
  412. var item = $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(9);
  413. var contextMenuRoot = $('.htContextMenu');
  414. item.simulate('mouseover');
  415. setTimeout(function () {
  416. expect(item.text()).toBe('Alignment');
  417. expect(item.hasClass('htSubmenu')).toBe(true);
  418. var contextSubMenu = $('.htContextMenuSub_' + item.text());
  419. expect(contextSubMenu.offset().top + contextSubMenu.height() - 28).toBeAroundValue(item.offset().top);
  420. expect(contextSubMenu.offset().left).toBeGreaterThan(contextMenuRoot.offset().left + contextMenuRoot.width() - 30); // 30 - scroll
  421. done();
  422. }, 350); // menu opens after 300ms
  423. });
  424. });
  425. describe('default context menu actions', function () {
  426. it('should display the default set of actions', function () {
  427. var hot = handsontable({
  428. contextMenu: true,
  429. comments: true,
  430. height: 100
  431. });
  432. contextMenu();
  433. var items = $('.htContextMenu tbody td');
  434. var actions = items.not('.htSeparator');
  435. var separators = items.filter('.htSeparator');
  436. expect(actions.length).toEqual(13);
  437. expect(separators.length).toEqual(6);
  438. expect(actions.text()).toEqual(['Insert row above', 'Insert row below', 'Insert column on the left', 'Insert column on the right', 'Remove row', 'Remove column', 'Undo', 'Redo', 'Read only', 'Alignment', 'Add comment', 'Delete comment', 'Read only comment'].join(''));
  439. });
  440. it('should disable column manipulation when row header selected', function () {
  441. var hot = handsontable({
  442. data: Handsontable.helper.createSpreadsheetData(4, 4),
  443. contextMenu: true,
  444. colHeaders: true,
  445. rowHeaders: true,
  446. height: 100
  447. });
  448. $('.ht_clone_left .htCore').eq(0).find('tbody').find('th').eq(0).simulate('mousedown', { which: 3 });
  449. contextMenu();
  450. expect($('.htContextMenu tbody td.htDisabled').text()).toBe(['Insert column on the left', 'Insert column on the right', 'Remove column', 'Undo', 'Redo'].join(''));
  451. });
  452. it('should disable row manipulation when column header selected', function () {
  453. var hot = handsontable({
  454. data: Handsontable.helper.createSpreadsheetData(4, 4),
  455. contextMenu: true,
  456. colHeaders: true,
  457. rowHeaders: true,
  458. height: 100
  459. });
  460. $('.ht_clone_top .htCore').find('thead').find('th').eq(2).simulate('mousedown', { which: 3 });
  461. contextMenu();
  462. expect($('.htContextMenu tbody td.htDisabled').text()).toBe(['Insert row above', 'Insert row below', 'Remove row', 'Undo', 'Redo'].join(''));
  463. });
  464. it('should disable cells manipulation when corner header selected', function () {
  465. var hot = handsontable({
  466. data: Handsontable.helper.createSpreadsheetData(4, 4),
  467. contextMenu: true,
  468. colHeaders: true,
  469. rowHeaders: true,
  470. height: 100
  471. });
  472. $('.ht_clone_top_left_corner .htCore').find('thead').find('th').eq(0).simulate('mousedown', { which: 3 });
  473. contextMenu();
  474. expect($('.htContextMenu tbody td.htDisabled').text()).toBe(['Remove row', 'Remove column', 'Undo', 'Redo', 'Read only', 'Alignment'].join(''));
  475. });
  476. it('should insert row above selection', function () {
  477. var hot = handsontable({
  478. data: Handsontable.helper.createSpreadsheetData(4, 4),
  479. contextMenu: true,
  480. height: 400
  481. });
  482. var afterCreateRowCallback = jasmine.createSpy('afterCreateRowCallback');
  483. hot.addHook('afterCreateRow', afterCreateRowCallback);
  484. expect(countRows()).toEqual(4);
  485. selectCell(1, 0, 3, 0);
  486. contextMenu();
  487. $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(0).simulate('mousedown'); // Insert row above
  488. expect(afterCreateRowCallback).toHaveBeenCalledWith(1, 1, 'ContextMenu.rowAbove', undefined, undefined, undefined);
  489. expect(countRows()).toEqual(5);
  490. expect($('.htContextMenu').is(':visible')).toBe(false);
  491. });
  492. it('should insert row above selection when initial data is empty', function () {
  493. var hot = handsontable({
  494. rowHeaders: true,
  495. colHeaders: true,
  496. data: [],
  497. dataSchema: [],
  498. contextMenu: true,
  499. height: 400
  500. });
  501. var afterCreateRowCallback = jasmine.createSpy('afterCreateRowCallback');
  502. hot.addHook('afterCreateRow', afterCreateRowCallback);
  503. expect(countRows()).toEqual(0);
  504. var cell = $('.ht_clone_top_left_corner .htCore').find('thead').find('th').eq(0);
  505. cell.simulate('mousedown', { which: 3 });
  506. contextMenu(cell[0]);
  507. $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(0).simulate('mousedown'); // Insert row above
  508. expect(afterCreateRowCallback).toHaveBeenCalledWith(0, 1, 'ContextMenu.rowAbove', undefined, undefined, undefined);
  509. expect(countRows()).toEqual(1);
  510. expect($('.htContextMenu').is(':visible')).toBe(false);
  511. });
  512. it('should NOT display insert row selection', function () {
  513. var hot = handsontable({
  514. contextMenu: true,
  515. allowInsertRow: false
  516. });
  517. contextMenu();
  518. var items = $('.htContextMenu tbody td');
  519. var actions = items.not('.htSeparator');
  520. var separators = items.filter('.htSeparator');
  521. expect(actions.length).toEqual(8);
  522. expect(separators.length).toEqual(4);
  523. expect(actions.text()).toEqual(['Insert column on the left', 'Insert column on the right', 'Remove row', 'Remove column', 'Undo', 'Redo', 'Read only', 'Alignment'].join(''));
  524. });
  525. it('should NOT display insert column selection', function () {
  526. var hot = handsontable({
  527. contextMenu: true,
  528. allowInsertColumn: false
  529. });
  530. contextMenu();
  531. var items = $('.htContextMenu tbody td');
  532. var actions = items.not('.htSeparator');
  533. expect(actions.length).toEqual(8);
  534. expect(actions.text()).toEqual(['Insert row above', 'Insert row below', 'Remove row', 'Remove column', 'Undo', 'Redo', 'Read only', 'Alignment'].join(''));
  535. });
  536. it('should insert row above selection (reverse selection)', function () {
  537. var hot = handsontable({
  538. data: Handsontable.helper.createSpreadsheetData(4, 4),
  539. contextMenu: true,
  540. height: 100
  541. });
  542. var afterCreateRowCallback = jasmine.createSpy('afterCreateRowCallback');
  543. hot.addHook('afterCreateRow', afterCreateRowCallback);
  544. expect(countRows()).toEqual(4);
  545. selectCell(3, 0, 1, 0);
  546. contextMenu();
  547. $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(0).simulate('mousedown'); // Insert row above
  548. expect(afterCreateRowCallback).toHaveBeenCalledWith(1, 1, 'ContextMenu.rowAbove', undefined, undefined, undefined);
  549. expect(countRows()).toEqual(5);
  550. expect($('.htContextMenu').is(':visible')).toBe(false);
  551. });
  552. it('should insert row below selection', function () {
  553. var hot = handsontable({
  554. data: Handsontable.helper.createSpreadsheetData(4, 4),
  555. contextMenu: true,
  556. height: 100
  557. });
  558. var afterCreateRowCallback = jasmine.createSpy('afterCreateRowCallback');
  559. hot.addHook('afterCreateRow', afterCreateRowCallback);
  560. expect(countRows()).toEqual(4);
  561. selectCell(1, 0, 3, 0);
  562. contextMenu();
  563. $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(1).simulate('mousedown'); // Insert row above
  564. expect(afterCreateRowCallback).toHaveBeenCalledWith(4, 1, 'ContextMenu.rowBelow', undefined, undefined, undefined);
  565. expect(countRows()).toEqual(5);
  566. expect($('.htContextMenu').is(':visible')).toBe(false);
  567. });
  568. it('should insert row below selection when initial data is empty', function () {
  569. var hot = handsontable({
  570. rowHeaders: true,
  571. colHeaders: true,
  572. data: [],
  573. dataSchema: [],
  574. contextMenu: true,
  575. height: 400
  576. });
  577. var afterCreateRowCallback = jasmine.createSpy('afterCreateRowCallback');
  578. hot.addHook('afterCreateRow', afterCreateRowCallback);
  579. expect(countRows()).toEqual(0);
  580. var cell = $('.ht_clone_top_left_corner .htCore').find('thead').find('th').eq(0);
  581. cell.simulate('mousedown', { which: 3 });
  582. contextMenu(cell[0]);
  583. $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(1).simulate('mousedown'); // Insert row below
  584. expect(afterCreateRowCallback).toHaveBeenCalledWith(0, 1, 'ContextMenu.rowBelow', undefined, undefined, undefined);
  585. expect(countRows()).toEqual(1);
  586. expect($('.htContextMenu').is(':visible')).toBe(false);
  587. });
  588. it('should insert row below selection (reverse selection)', function () {
  589. var hot = handsontable({
  590. data: Handsontable.helper.createSpreadsheetData(4, 4),
  591. contextMenu: true,
  592. height: 100
  593. });
  594. var afterCreateRowCallback = jasmine.createSpy('afterCreateRowCallback');
  595. hot.addHook('afterCreateRow', afterCreateRowCallback);
  596. expect(countRows()).toEqual(4);
  597. selectCell(3, 0, 1, 0);
  598. contextMenu();
  599. $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(1).simulate('mousedown'); // Insert row below
  600. expect(afterCreateRowCallback).toHaveBeenCalledWith(4, 1, 'ContextMenu.rowBelow', undefined, undefined, undefined);
  601. expect(countRows()).toEqual(5);
  602. expect($('.htContextMenu').is(':visible')).toBe(false);
  603. });
  604. it('should insert column on the left of selection', function () {
  605. var hot = handsontable({
  606. data: Handsontable.helper.createSpreadsheetData(4, 4),
  607. contextMenu: true,
  608. width: 400,
  609. height: 400
  610. });
  611. var afterCreateColCallback = jasmine.createSpy('afterCreateColCallback');
  612. hot.addHook('afterCreateCol', afterCreateColCallback);
  613. expect(countCols()).toEqual(4);
  614. selectCell(0, 1, 0, 3);
  615. contextMenu();
  616. $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(2).simulate('mousedown'); // Insert col left
  617. expect(afterCreateColCallback).toHaveBeenCalledWith(1, 1, 'ContextMenu.columnLeft', undefined, undefined, undefined);
  618. expect(countCols()).toEqual(5);
  619. expect($('.htContextMenu').is(':visible')).toBe(false);
  620. });
  621. it('should insert column on the left of selection when initial data is empty', function () {
  622. var hot = handsontable({
  623. rowHeaders: true,
  624. colHeaders: true,
  625. data: [],
  626. dataSchema: [],
  627. contextMenu: true,
  628. height: 400
  629. });
  630. var afterCreateColCallback = jasmine.createSpy('afterCreateColCallback');
  631. hot.addHook('afterCreateCol', afterCreateColCallback);
  632. expect(countCols()).toEqual(0);
  633. var cell = $('.ht_clone_top_left_corner .htCore').find('thead').find('th').eq(0);
  634. cell.simulate('mousedown', { which: 3 });
  635. contextMenu(cell[0]);
  636. $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(3).simulate('mousedown'); // Insert column on the left
  637. expect(afterCreateColCallback).toHaveBeenCalledWith(0, 1, 'ContextMenu.columnRight', undefined, undefined, undefined);
  638. expect(countCols()).toEqual(1);
  639. expect($('.htContextMenu').is(':visible')).toBe(false);
  640. });
  641. it('should insert column on the left of selection (reverse selection)', function () {
  642. var hot = handsontable({
  643. data: Handsontable.helper.createSpreadsheetData(4, 4),
  644. contextMenu: true,
  645. height: 100
  646. });
  647. var afterCreateColCallback = jasmine.createSpy('afterCreateColCallback');
  648. hot.addHook('afterCreateCol', afterCreateColCallback);
  649. expect(countCols()).toEqual(4);
  650. selectCell(0, 3, 0, 1);
  651. contextMenu();
  652. $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(2).simulate('mousedown'); // Insert col left
  653. expect(afterCreateColCallback).toHaveBeenCalledWith(1, 1, 'ContextMenu.columnLeft', undefined, undefined, undefined);
  654. expect(countCols()).toEqual(5);
  655. expect($('.htContextMenu').is(':visible')).toBe(false);
  656. });
  657. it('should insert column on the right of selection', function () {
  658. var hot = handsontable({
  659. data: Handsontable.helper.createSpreadsheetData(4, 4),
  660. contextMenu: true,
  661. height: 100
  662. });
  663. var afterCreateColCallback = jasmine.createSpy('afterCreateColCallback');
  664. hot.addHook('afterCreateCol', afterCreateColCallback);
  665. expect(countCols()).toEqual(4);
  666. selectCell(0, 1, 0, 3);
  667. contextMenu();
  668. $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(2).simulate('mousedown'); // Insert col right
  669. expect(afterCreateColCallback).toHaveBeenCalledWith(1, 1, 'ContextMenu.columnLeft', undefined, undefined, undefined);
  670. expect(countCols()).toEqual(5);
  671. expect($('.htContextMenu').is(':visible')).toBe(false);
  672. });
  673. it('should insert column on the right of selection when initial data is empty', function () {
  674. var hot = handsontable({
  675. rowHeaders: true,
  676. colHeaders: true,
  677. data: [],
  678. dataSchema: [],
  679. contextMenu: true,
  680. height: 400
  681. });
  682. var afterCreateColCallback = jasmine.createSpy('afterCreateColCallback');
  683. hot.addHook('afterCreateCol', afterCreateColCallback);
  684. expect(countCols()).toEqual(0);
  685. var cell = $('.ht_clone_top_left_corner .htCore').find('thead').find('th').eq(0);
  686. cell.simulate('mousedown', { which: 3 });
  687. contextMenu(cell[0]);
  688. $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(3).simulate('mousedown'); // Insert column on the right
  689. expect(afterCreateColCallback).toHaveBeenCalledWith(0, 1, 'ContextMenu.columnRight', undefined, undefined, undefined);
  690. expect(countCols()).toEqual(1);
  691. expect($('.htContextMenu').is(':visible')).toBe(false);
  692. });
  693. it('should insert column on the right of selection (reverse selection)', function () {
  694. var hot = handsontable({
  695. data: Handsontable.helper.createSpreadsheetData(4, 4),
  696. contextMenu: true,
  697. height: 100
  698. });
  699. var afterCreateColCallback = jasmine.createSpy('afterCreateColCallback');
  700. hot.addHook('afterCreateCol', afterCreateColCallback);
  701. expect(countCols()).toEqual(4);
  702. selectCell(0, 3, 0, 1);
  703. contextMenu();
  704. $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(3).simulate('mousedown'); // Insert col right
  705. expect(afterCreateColCallback).toHaveBeenCalledWith(4, 1, 'ContextMenu.columnRight', undefined, undefined, undefined);
  706. expect(countCols()).toEqual(5);
  707. expect($('.htContextMenu').is(':visible')).toBe(false);
  708. });
  709. it('should remove selected rows', function () {
  710. var hot = handsontable({
  711. data: Handsontable.helper.createSpreadsheetData(4, 4),
  712. contextMenu: true,
  713. height: 100
  714. });
  715. var afterRemoveRowCallback = jasmine.createSpy('afterRemoveRowCallback');
  716. hot.addHook('afterRemoveRow', afterRemoveRowCallback);
  717. expect(countRows()).toEqual(4);
  718. selectCell(1, 0, 3, 0);
  719. contextMenu();
  720. $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(4).simulate('mousedown'); // Remove row
  721. expect(afterRemoveRowCallback).toHaveBeenCalledWith(1, 3, [1, 2, 3], 'ContextMenu.removeRow', undefined, undefined);
  722. expect(countRows()).toEqual(1);
  723. expect($('.htContextMenu').is(':visible')).toBe(false);
  724. });
  725. it('should allow to remove the latest row', function () {
  726. var hot = handsontable({
  727. data: Handsontable.helper.createSpreadsheetData(1, 4),
  728. contextMenu: true,
  729. height: 100
  730. });
  731. var afterRemoveRowCallback = jasmine.createSpy('afterRemoveRowCallback');
  732. hot.addHook('afterRemoveRow', afterRemoveRowCallback);
  733. expect(countRows()).toBe(1);
  734. selectCell(0, 0, 0, 0);
  735. contextMenu();
  736. $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(4).simulate('mousedown'); // Remove row
  737. expect(afterRemoveRowCallback).toHaveBeenCalledWith(0, 1, [0], 'ContextMenu.removeRow', undefined, undefined);
  738. expect(countRows()).toBe(0);
  739. expect($('.htContextMenu').is(':visible')).toBe(false);
  740. });
  741. it('should remove selected rows (reverse selection)', function () {
  742. var hot = handsontable({
  743. data: Handsontable.helper.createSpreadsheetData(4, 4),
  744. contextMenu: true,
  745. height: 100
  746. });
  747. var afterRemoveRowCallback = jasmine.createSpy('afterRemoveRowCallback');
  748. hot.addHook('afterRemoveRow', afterRemoveRowCallback);
  749. expect(countRows()).toBe(4);
  750. selectCell(3, 0, 1, 0);
  751. contextMenu();
  752. $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(4).simulate('mousedown'); // Remove row
  753. expect(afterRemoveRowCallback).toHaveBeenCalledWith(1, 3, [1, 2, 3], 'ContextMenu.removeRow', undefined, undefined);
  754. expect(countRows()).toBe(1);
  755. expect($('.htContextMenu').is(':visible')).toBe(false);
  756. });
  757. it('should remove selected columns', function () {
  758. var hot = handsontable({
  759. data: Handsontable.helper.createSpreadsheetData(4, 4),
  760. contextMenu: true,
  761. height: 100
  762. });
  763. var afterRemoveColCallback = jasmine.createSpy('afterRemoveColCallback');
  764. hot.addHook('afterRemoveCol', afterRemoveColCallback);
  765. expect(countCols()).toBe(4);
  766. selectCell(0, 1, 0, 3);
  767. contextMenu();
  768. $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(5).simulate('mousedown'); // Remove col
  769. expect(afterRemoveColCallback).toHaveBeenCalledWith(1, 3, [1, 2, 3], 'ContextMenu.removeColumn', undefined, undefined);
  770. expect(countCols()).toBe(1);
  771. expect($('.htContextMenu').is(':visible')).toBe(false);
  772. });
  773. it('should allow to remove the latest column', function () {
  774. var hot = handsontable({
  775. data: Handsontable.helper.createSpreadsheetData(4, 1),
  776. contextMenu: true,
  777. height: 100
  778. });
  779. var afterRemoveColCallback = jasmine.createSpy('afterRemoveColCallback');
  780. hot.addHook('afterRemoveCol', afterRemoveColCallback);
  781. expect(countCols()).toBe(1);
  782. selectCell(0, 0, 0, 0);
  783. contextMenu();
  784. $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(5).simulate('mousedown'); // Remove column
  785. expect(afterRemoveColCallback).toHaveBeenCalledWith(0, 1, [0], 'ContextMenu.removeColumn', undefined, undefined);
  786. expect(countCols()).toBe(0);
  787. expect($('.htContextMenu').is(':visible')).toBe(false);
  788. });
  789. it('should remove selected columns (reverse selection)', function () {
  790. var hot = handsontable({
  791. data: Handsontable.helper.createSpreadsheetData(4, 4),
  792. contextMenu: true,
  793. height: 100
  794. });
  795. var afterRemoveColCallback = jasmine.createSpy('afterRemoveColCallback');
  796. hot.addHook('afterRemoveCol', afterRemoveColCallback);
  797. expect(countCols()).toEqual(4);
  798. selectCell(0, 3, 0, 1);
  799. contextMenu();
  800. $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(5).simulate('mousedown'); // Remove col
  801. expect(afterRemoveColCallback).toHaveBeenCalledWith(1, 3, [1, 2, 3], 'ContextMenu.removeColumn', undefined, undefined);
  802. expect(countCols()).toEqual(1);
  803. });
  804. it('should undo changes', function () {
  805. var hot = handsontable({
  806. data: Handsontable.helper.createSpreadsheetData(4, 4),
  807. contextMenu: true,
  808. height: 100
  809. });
  810. selectCell(0, 0);
  811. expect(getDataAtCell(0, 0)).toEqual('A1');
  812. setDataAtCell(0, 0, 'XX');
  813. expect(getDataAtCell(0, 0)).toEqual('XX');
  814. contextMenu();
  815. $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(6).simulate('mousedown'); // Undo
  816. expect(getDataAtCell(0, 0)).toEqual('A1');
  817. });
  818. it('should redo changes', function () {
  819. var hot = handsontable({
  820. data: Handsontable.helper.createSpreadsheetData(4, 4),
  821. contextMenu: true,
  822. height: 100
  823. });
  824. selectCell(0, 0);
  825. expect(getDataAtCell(0, 0)).toEqual('A1');
  826. setDataAtCell(0, 0, 'XX');
  827. expect(getDataAtCell(0, 0)).toEqual('XX');
  828. hot.undo();
  829. expect(getDataAtCell(0, 0)).toEqual('A1');
  830. contextMenu();
  831. $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(7).simulate('mousedown'); // Redo
  832. expect(getDataAtCell(0, 0)).toEqual('XX');
  833. });
  834. it('should display only the specified actions', function () {
  835. var hot = handsontable({
  836. data: Handsontable.helper.createSpreadsheetData(4, 4),
  837. contextMenu: ['remove_row', 'undo'],
  838. height: 100
  839. });
  840. contextMenu();
  841. expect($('.htContextMenu .ht_master .htCore').find('tbody td').length).toEqual(2);
  842. });
  843. it('should make a single selected cell read-only', function () {
  844. var hot = handsontable({
  845. data: Handsontable.helper.createSpreadsheetData(4, 4),
  846. contextMenu: true,
  847. height: 100
  848. });
  849. selectCell(0, 0);
  850. expect(getDataAtCell(0, 0)).toEqual('A1');
  851. expect(hot.getCellMeta(0, 0).readOnly).toBe(false);
  852. selectCell(0, 0);
  853. contextMenu();
  854. var menu = $('.htContextMenu .ht_master .htCore tbody');
  855. menu.find('td').not('.htSeparator').eq(8).simulate('mousedown'); // Make read-only
  856. expect(hot.getCellMeta(0, 0).readOnly).toBe(true);
  857. });
  858. it('should make a single selected cell writable, when it\'s set to read-only', function () {
  859. var hot = handsontable({
  860. data: Handsontable.helper.createSpreadsheetData(4, 4),
  861. contextMenu: true,
  862. height: 100
  863. });
  864. selectCell(0, 0);
  865. expect(getDataAtCell(0, 0)).toEqual('A1');
  866. hot.getCellMeta(0, 0).readOnly = true;
  867. selectCell(0, 0);
  868. contextMenu();
  869. var menu = $('.htContextMenu .ht_master .htCore tbody');
  870. menu.find('td').not('.htSeparator').eq(8).simulate('mousedown');
  871. // $(hot.contextMenu.menu).find('tbody td').not('.htSeparator').eq(8).trigger('mousedown'); //Make writable
  872. expect(hot.getCellMeta(0, 0).readOnly).toBe(false);
  873. });
  874. it('should make a group of selected cells read-only, if all of them are writable', function () {
  875. var hot = handsontable({
  876. data: Handsontable.helper.createSpreadsheetData(4, 4),
  877. contextMenu: true,
  878. height: 100
  879. });
  880. for (var i = 0; i < 2; i++) {
  881. for (var j = 0; j < 2; j++) {
  882. expect(hot.getCellMeta(i, j).readOnly).toEqual(false);
  883. }
  884. }
  885. selectCell(0, 0, 2, 2);
  886. contextMenu();
  887. var menu = $('.htContextMenu .ht_master .htCore tbody');
  888. menu.find('td').not('.htSeparator').eq(8).simulate('mousedown');
  889. // $(hot.contextMenu.menu).find('tbody td').not('.htSeparator').eq(8).trigger('mousedown'); //Make read-only
  890. for (var _i = 0; _i < 2; _i++) {
  891. for (var _j = 0; _j < 2; _j++) {
  892. expect(hot.getCellMeta(_i, _j).readOnly).toEqual(true);
  893. }
  894. }
  895. });
  896. it('should not close menu after clicking on submenu root item', function () {
  897. var hot = handsontable({
  898. data: Handsontable.helper.createSpreadsheetData(4, 4),
  899. contextMenu: ['row_above', 'remove_row', '---------', 'alignment'],
  900. height: 400
  901. });
  902. selectCell(1, 0, 3, 0);
  903. contextMenu();
  904. $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(2).simulate('mousedown'); // Alignment
  905. expect($('.htContextMenu').is(':visible')).toBe(true);
  906. });
  907. it('should make a group of selected cells read-only, if all of them are writable (reverse selection)', function () {
  908. var hot = handsontable({
  909. data: Handsontable.helper.createSpreadsheetData(4, 4),
  910. contextMenu: true,
  911. height: 100
  912. });
  913. for (var i = 0; i < 2; i++) {
  914. for (var j = 0; j < 2; j++) {
  915. expect(hot.getCellMeta(i, j).readOnly).toEqual(false);
  916. }
  917. }
  918. selectCell(2, 2, 0, 0);
  919. contextMenu();
  920. var menu = $('.htContextMenu .ht_master .htCore tbody');
  921. menu.find('td').not('.htSeparator').eq(8).simulate('mousedown'); // Make read-only
  922. for (var _i2 = 0; _i2 < 2; _i2++) {
  923. for (var _j2 = 0; _j2 < 2; _j2++) {
  924. expect(hot.getCellMeta(_i2, _j2).readOnly).toEqual(true);
  925. }
  926. }
  927. });
  928. it('should make a group of selected cells writable if at least one of them is read-only', function () {
  929. var hot = handsontable({
  930. data: Handsontable.helper.createSpreadsheetData(4, 4),
  931. contextMenu: true,
  932. height: 100
  933. });
  934. for (var i = 0; i < 2; i++) {
  935. for (var j = 0; j < 2; j++) {
  936. expect(hot.getCellMeta(i, j).readOnly).toEqual(false);
  937. }
  938. }
  939. hot.getCellMeta(1, 1).readOnly = true;
  940. selectCell(0, 0, 2, 2);
  941. contextMenu();
  942. $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(8).simulate('mousedown'); // Make writable
  943. for (var _i3 = 0; _i3 < 2; _i3++) {
  944. for (var _j3 = 0; _j3 < 2; _j3++) {
  945. expect(hot.getCellMeta(_i3, _j3).readOnly).toEqual(false);
  946. }
  947. }
  948. });
  949. it('should make a group of selected cells writable if at least one of them is read-only (reverse selection)', function () {
  950. var hot = handsontable({
  951. data: Handsontable.helper.createSpreadsheetData(4, 4),
  952. contextMenu: true,
  953. height: 100
  954. });
  955. for (var i = 0; i < 2; i++) {
  956. for (var j = 0; j < 2; j++) {
  957. expect(hot.getCellMeta(i, j).readOnly).toEqual(false);
  958. }
  959. }
  960. hot.getCellMeta(1, 1).readOnly = true;
  961. selectCell(2, 2, 0, 0);
  962. contextMenu();
  963. $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(8).simulate('mousedown'); // Make writable
  964. for (var _i4 = 0; _i4 < 2; _i4++) {
  965. for (var _j4 = 0; _j4 < 2; _j4++) {
  966. expect(hot.getCellMeta(_i4, _j4).readOnly).toEqual(false);
  967. }
  968. }
  969. });
  970. });
  971. describe('disabling actions', function () {
  972. it('should not close menu after clicking on disabled item', function () {
  973. var hot = handsontable({
  974. data: Handsontable.helper.createSpreadsheetData(4, 4),
  975. contextMenu: ['undo', 'redo'],
  976. height: 400
  977. });
  978. selectCell(1, 0, 3, 0);
  979. contextMenu();
  980. $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(0).simulate('mousedown'); // Undo
  981. expect($('.htContextMenu').is(':visible')).toBe(true);
  982. });
  983. it('should disable undo and redo action if undoRedo plugin is not enabled ', function () {
  984. var hot = handsontable({
  985. contextMenu: true,
  986. undoRedo: false,
  987. height: 100
  988. });
  989. contextMenu();
  990. var $menu = $('.htContextMenu .ht_master .htCore');
  991. expect($menu.find('tbody td:eq(9)').text()).toEqual('Undo');
  992. expect($menu.find('tbody td:eq(9)').hasClass('htDisabled')).toBe(true);
  993. expect($menu.find('tbody td:eq(10)').text()).toEqual('Redo');
  994. expect($menu.find('tbody td:eq(10)').hasClass('htDisabled')).toBe(true);
  995. });
  996. it('should disable undo when there is nothing to undo ', function () {
  997. var hot = handsontable({
  998. contextMenu: true,
  999. height: 100
  1000. });
  1001. contextMenu();
  1002. var $menu = $('.htContextMenu .ht_master .htCore');
  1003. expect(hot.undoRedo.isUndoAvailable()).toBe(false);
  1004. expect($menu.find('tbody td:eq(9)').text()).toEqual('Undo');
  1005. expect($menu.find('tbody td:eq(9)').hasClass('htDisabled')).toBe(true);
  1006. closeContextMenu();
  1007. setDataAtCell(0, 0, 'foo');
  1008. contextMenu();
  1009. $menu = $('.htContextMenu .ht_master .htCore');
  1010. expect(hot.undoRedo.isUndoAvailable()).toBe(true);
  1011. expect($menu.find('tbody td:eq(9)').hasClass('htDisabled')).toBe(false);
  1012. });
  1013. it('should disable redo when there is nothing to redo ', function () {
  1014. var hot = handsontable({
  1015. contextMenu: true,
  1016. height: 100
  1017. });
  1018. contextMenu();
  1019. var $menu = $('.htContextMenu .ht_master .htCore');
  1020. expect(hot.undoRedo.isRedoAvailable()).toBe(false);
  1021. expect($menu.find('tbody td:eq(10)').text()).toEqual('Redo');
  1022. expect($menu.find('tbody td:eq(10)').hasClass('htDisabled')).toBe(true);
  1023. closeContextMenu();
  1024. setDataAtCell(0, 0, 'foo');
  1025. hot.undo();
  1026. contextMenu();
  1027. $menu = $('.htContextMenu .ht_master .htCore');
  1028. expect(hot.undoRedo.isRedoAvailable()).toBe(true);
  1029. expect($menu.find('tbody td:eq(10)').hasClass('htDisabled')).toBe(false);
  1030. });
  1031. it('should disable Insert row in context menu when maxRows is reached', function () {
  1032. var hot = handsontable({
  1033. contextMenu: true,
  1034. maxRows: 6,
  1035. height: 100
  1036. });
  1037. contextMenu();
  1038. var $menu = $('.htContextMenu .ht_master .htCore');
  1039. expect($menu.find('tbody td:eq(0)').text()).toEqual('Insert row above');
  1040. expect($menu.find('tbody td:eq(0)').hasClass('htDisabled')).toBe(false);
  1041. expect($menu.find('tbody td:eq(1)').text()).toEqual('Insert row below');
  1042. expect($menu.find('tbody td:eq(1)').hasClass('htDisabled')).toBe(false);
  1043. closeContextMenu();
  1044. alter('insert_row');
  1045. contextMenu();
  1046. $menu = $('.htContextMenu .ht_master .htCore');
  1047. expect($menu.find('tbody td:eq(0)').hasClass('htDisabled')).toBe(true);
  1048. expect($menu.find('tbody td:eq(1)').hasClass('htDisabled')).toBe(true);
  1049. });
  1050. it('should disable Insert col in context menu when maxCols is reached', function () {
  1051. var hot = handsontable({
  1052. contextMenu: true,
  1053. maxCols: 6,
  1054. height: 100
  1055. });
  1056. contextMenu();
  1057. var $menu = $('.htContextMenu .ht_master .htCore');
  1058. expect($menu.find('tbody td:eq(3)').text()).toEqual('Insert column on the left');
  1059. expect($menu.find('tbody td:eq(3)').hasClass('htDisabled')).toBe(false);
  1060. expect($menu.find('tbody td:eq(4)').text()).toEqual('Insert column on the right');
  1061. expect($menu.find('tbody td:eq(4)').hasClass('htDisabled')).toBe(false);
  1062. closeContextMenu();
  1063. alter('insert_col');
  1064. contextMenu();
  1065. $menu = $('.htContextMenu .ht_master .htCore');
  1066. expect($menu.find('tbody td:eq(3)').hasClass('htDisabled')).toBe(true);
  1067. expect($menu.find('tbody td:eq(4)').hasClass('htDisabled')).toBe(true);
  1068. });
  1069. it('should NOT disable Insert col in context menu when only one column exists', function () {
  1070. var hot = handsontable({
  1071. data: [['single col']],
  1072. contextMenu: true,
  1073. maxCols: 10,
  1074. height: 100
  1075. });
  1076. selectCell(0, 0);
  1077. contextMenu();
  1078. var $menu = $('.htContextMenu .ht_master .htCore');
  1079. expect($menu.find('tbody td:eq(3)').text()).toEqual('Insert column on the left');
  1080. expect($menu.find('tbody td:eq(3)').hasClass('htDisabled')).toBe(false);
  1081. expect($menu.find('tbody td:eq(4)').text()).toEqual('Insert column on the right');
  1082. expect($menu.find('tbody td:eq(4)').hasClass('htDisabled')).toBe(false);
  1083. });
  1084. it('should disable Remove col in context menu when rows are selected by headers', function () {
  1085. var hot = handsontable({
  1086. contextMenu: ['remove_col', 'remove_row'],
  1087. height: 100,
  1088. colHeaders: true,
  1089. rowHeaders: true
  1090. });
  1091. var $rowsHeaders = this.$container.find('.ht_clone_left tr th');
  1092. $rowsHeaders.eq(1).simulate('mousedown');
  1093. $rowsHeaders.eq(2).simulate('mouseover');
  1094. $rowsHeaders.eq(3).simulate('mouseover');
  1095. $rowsHeaders.eq(3).simulate('mousemove');
  1096. $rowsHeaders.eq(3).simulate('mouseup');
  1097. contextMenu();
  1098. var $menu = $('.htContextMenu .ht_master .htCore');
  1099. expect($menu.find('tbody td:eq(0)').text()).toEqual('Remove column');
  1100. expect($menu.find('tbody td:eq(0)').hasClass('htDisabled')).toBe(true);
  1101. });
  1102. it('should disable Remove row in context menu when columns are selected by headers', function () {
  1103. var hot = handsontable({
  1104. contextMenu: ['remove_col', 'remove_row'],
  1105. height: 100,
  1106. colHeaders: true,
  1107. rowHeaders: true
  1108. });
  1109. this.$container.find('thead tr:eq(0) th:eq(1)').simulate('mousedown');
  1110. this.$container.find('thead tr:eq(0) th:eq(2)').simulate('mouseover');
  1111. this.$container.find('thead tr:eq(0) th:eq(3)').simulate('mouseover');
  1112. this.$container.find('thead tr:eq(0) th:eq(3)').simulate('mousemove');
  1113. this.$container.find('thead tr:eq(0) th:eq(3)').simulate('mouseup');
  1114. contextMenu();
  1115. var $menu = $('.htContextMenu .ht_master .htCore');
  1116. expect($menu.find('tbody td:eq(1)').text()).toEqual('Remove row');
  1117. expect($menu.find('tbody td:eq(1)').hasClass('htDisabled')).toBe(true);
  1118. });
  1119. });
  1120. describe('custom options', function () {
  1121. it('should have custom items list', function () {
  1122. var callback1 = jasmine.createSpy('callback1');
  1123. var callback2 = jasmine.createSpy('callback2');
  1124. var hot = handsontable({
  1125. contextMenu: {
  1126. items: {
  1127. cust1: {
  1128. name: 'CustomItem1',
  1129. callback: callback1
  1130. },
  1131. cust2: {
  1132. name: 'CustomItem2',
  1133. callback: callback2
  1134. }
  1135. }
  1136. },
  1137. height: 100
  1138. });
  1139. contextMenu();
  1140. expect($('.htContextMenu .ht_master .htCore').find('tbody td').length).toEqual(2);
  1141. expect($('.htContextMenu .ht_master .htCore').find('tbody td').text()).toEqual(['CustomItem1', 'CustomItem2'].join(''));
  1142. $('.htContextMenu .ht_master .htCore').find('tbody td:eq(0)').simulate('mousedown');
  1143. expect(callback1.calls.count()).toEqual(1);
  1144. expect(callback2.calls.count()).toEqual(0);
  1145. contextMenu();
  1146. $('.htContextMenu .ht_master .htCore').find('tbody td:eq(1)').simulate('mousedown');
  1147. expect(callback1.calls.count()).toEqual(1);
  1148. expect(callback2.calls.count()).toEqual(1);
  1149. });
  1150. it('should have custom items list (defined as a function)', function () {
  1151. var enabled = false;
  1152. var hot = handsontable({
  1153. contextMenu: {
  1154. items: {
  1155. cust1: {
  1156. name: function name() {
  1157. if (!enabled) {
  1158. return 'Enable my custom option';
  1159. }
  1160. return 'Disable my custom option';
  1161. },
  1162. callback: function callback() {}
  1163. }
  1164. }
  1165. },
  1166. height: 100
  1167. });
  1168. contextMenu();
  1169. expect($('.htContextMenu .ht_master .htCore').find('tbody td').text()).toEqual('Enable my custom option');
  1170. $('.htContextMenu .ht_master .htCore').find('tbody td:eq(0)').simulate('mousedown');
  1171. enabled = true;
  1172. contextMenu();
  1173. expect($('.htContextMenu .ht_master .htCore').find('tbody td').text()).toEqual('Disable my custom option');
  1174. $('.htContextMenu .ht_master .htCore').find('tbody td:eq(0)').simulate('mousedown');
  1175. });
  1176. it('should enable to define item options globally', function () {
  1177. var callback = jasmine.createSpy('callback');
  1178. var hot = handsontable({
  1179. contextMenu: {
  1180. callback: callback,
  1181. items: {
  1182. cust1: {
  1183. name: 'CustomItem1'
  1184. },
  1185. cust2: {
  1186. name: 'CustomItem2'
  1187. }
  1188. }
  1189. },
  1190. height: 100
  1191. });
  1192. contextMenu();
  1193. $('.htContextMenu .ht_master .htCore').find('tbody td:eq(0)').simulate('mousedown');
  1194. expect(callback.calls.count()).toEqual(1);
  1195. contextMenu();
  1196. $('.htContextMenu .ht_master .htCore').find('tbody td:eq(1)').simulate('mousedown');
  1197. expect(callback.calls.count()).toEqual(2);
  1198. });
  1199. it('should override default items options', function () {
  1200. var callback = jasmine.createSpy('callback');
  1201. var hot = handsontable({
  1202. contextMenu: {
  1203. items: {
  1204. remove_row: {
  1205. callback: callback
  1206. },
  1207. remove_col: {
  1208. name: 'Delete column'
  1209. }
  1210. }
  1211. },
  1212. height: 100
  1213. });
  1214. contextMenu();
  1215. expect($('.htContextMenu .ht_master .htCore').find('tbody td').length).toEqual(2);
  1216. expect($('.htContextMenu .ht_master .htCore').find('tbody td').text()).toEqual(['Remove row', 'Delete column'].join(''));
  1217. $('.htContextMenu .ht_master .htCore').find('tbody td:eq(0)').simulate('mousedown');
  1218. expect(callback.calls.count()).toEqual(1);
  1219. expect(countCols()).toEqual(5);
  1220. contextMenu();
  1221. $('.htContextMenu .ht_master .htCore').find('tbody td:eq(1)').simulate('mousedown');
  1222. expect(countCols()).toEqual(4);
  1223. });
  1224. it('should fire item callback after item has been clicked', function () {
  1225. var customItem = {
  1226. name: 'Custom item',
  1227. callback: function callback() {}
  1228. };
  1229. spyOn(customItem, 'callback');
  1230. var hot = handsontable({
  1231. contextMenu: {
  1232. items: {
  1233. customItemKey: customItem
  1234. }
  1235. },
  1236. height: 100
  1237. });
  1238. contextMenu();
  1239. $('.htContextMenu .ht_master .htCore').find('tbody td:eq(0)').simulate('mousedown');
  1240. expect(customItem.callback.calls.count()).toEqual(1);
  1241. expect(customItem.callback.calls.argsFor(0)[0]).toEqual('customItemKey');
  1242. });
  1243. });
  1244. describe('keyboard navigation', function () {
  1245. describe('no item selected', function () {
  1246. it('should select the first item in menu, when user hits ARROW_DOWN', function () {
  1247. var hot = handsontable({
  1248. contextMenu: true,
  1249. height: 100
  1250. });
  1251. contextMenu();
  1252. var menuHot = hot.getPlugin('contextMenu').menu.hotMenu;
  1253. expect(menuHot.getSelected()).toBeUndefined();
  1254. keyDownUp('arrow_down');
  1255. expect(menuHot.getSelected()).toEqual([0, 0, 0, 0]);
  1256. });
  1257. it('should scroll down, when user hits ARROW_DOWN for item in menu below the viewport', function () {
  1258. var hot = handsontable({
  1259. height: 100,
  1260. contextMenu: {
  1261. items: {
  1262. item1: {
  1263. name: 'Item1'
  1264. },
  1265. item2: {
  1266. name: 'Item2'
  1267. },
  1268. item3: {
  1269. name: 'Item3'
  1270. },
  1271. item4: {
  1272. name: 'Item4'
  1273. },
  1274. item5: {
  1275. name: 'Item5'
  1276. },
  1277. item6: {
  1278. name: 'Item6'
  1279. },
  1280. item7: {
  1281. name: 'Item7'
  1282. },
  1283. item8: {
  1284. name: 'Item8'
  1285. },
  1286. item9: {
  1287. name: 'Item9'
  1288. },
  1289. item10: {
  1290. name: 'Item10'
  1291. },
  1292. item11: {
  1293. name: 'Item11'
  1294. },
  1295. item12: {
  1296. name: 'Item12'
  1297. },
  1298. item13: {
  1299. name: 'Item13'
  1300. },
  1301. item14: {
  1302. name: 'Item14'
  1303. },
  1304. item15: {
  1305. name: 'Item15'
  1306. },
  1307. item16: {
  1308. name: 'Item16'
  1309. },
  1310. item17: {
  1311. name: 'Item17'
  1312. },
  1313. item18: {
  1314. name: 'Item18'
  1315. },
  1316. item19: {
  1317. name: 'Item19'
  1318. },
  1319. item20: {
  1320. name: 'Item20'
  1321. },
  1322. item21: {
  1323. name: 'Item21'
  1324. },
  1325. item22: {
  1326. name: 'Item22'
  1327. },
  1328. item23: {
  1329. name: 'Item23'
  1330. },
  1331. item24: {
  1332. name: 'Item24'
  1333. },
  1334. item25: {
  1335. name: 'Item25'
  1336. },
  1337. item26: {
  1338. name: 'Item26'
  1339. },
  1340. item27: {
  1341. name: 'Item27'
  1342. },
  1343. item28: {
  1344. name: 'Item28'
  1345. },
  1346. item29: {
  1347. name: 'Item29'
  1348. },
  1349. item30: {
  1350. name: 'Item30'
  1351. },
  1352. item31: {
  1353. name: 'Item31'
  1354. },
  1355. item32: {
  1356. name: 'Item32'
  1357. },
  1358. item33: {
  1359. name: 'Item33'
  1360. },
  1361. item34: {
  1362. name: 'Item34'
  1363. },
  1364. item35: {
  1365. name: 'Item35'
  1366. },
  1367. item36: {
  1368. name: 'Item36'
  1369. },
  1370. item37: {
  1371. name: 'Item37'
  1372. },
  1373. item38: {
  1374. name: 'Item38'
  1375. },
  1376. item39: {
  1377. name: 'Item39'
  1378. },
  1379. item40: {
  1380. name: 'Item40'
  1381. }
  1382. }
  1383. }
  1384. }),
  1385. scrollHeight;
  1386. contextMenu();
  1387. keyDownUp('arrow_down');
  1388. keyDownUp('arrow_down');
  1389. keyDownUp('arrow_down');
  1390. keyDownUp('arrow_down');
  1391. keyDownUp('arrow_down');
  1392. keyDownUp('arrow_down');
  1393. keyDownUp('arrow_down');
  1394. keyDownUp('arrow_down');
  1395. keyDownUp('arrow_down');
  1396. keyDownUp('arrow_down');
  1397. keyDownUp('arrow_down');
  1398. keyDownUp('arrow_down');
  1399. keyDownUp('arrow_down');
  1400. keyDownUp('arrow_down');
  1401. keyDownUp('arrow_down');
  1402. keyDownUp('arrow_down');
  1403. keyDownUp('arrow_down');
  1404. keyDownUp('arrow_down');
  1405. keyDownUp('arrow_down');
  1406. keyDownUp('arrow_down');
  1407. keyDownUp('arrow_down');
  1408. keyDownUp('arrow_down');
  1409. keyDownUp('arrow_down');
  1410. keyDownUp('arrow_down');
  1411. keyDownUp('arrow_down');
  1412. keyDownUp('arrow_down');
  1413. keyDownUp('arrow_down');
  1414. keyDownUp('arrow_down');
  1415. keyDownUp('arrow_down');
  1416. keyDownUp('arrow_down');
  1417. keyDownUp('arrow_down');
  1418. keyDownUp('arrow_down');
  1419. keyDownUp('arrow_down');
  1420. keyDownUp('arrow_down');
  1421. keyDownUp('arrow_down');
  1422. keyDownUp('arrow_down');
  1423. keyDownUp('arrow_down');
  1424. keyDownUp('arrow_down');
  1425. keyDownUp('arrow_down');
  1426. keyDownUp('arrow_down');
  1427. if (typeof window.scrollY !== 'undefined') {
  1428. scrollHeight = window.scrollY;
  1429. } else {
  1430. scrollHeight = document.documentElement.scrollTop;
  1431. }
  1432. expect(scrollHeight).not.toBe(0);
  1433. });
  1434. it('should select the first NOT DISABLED item in menu, when user hits ARROW_DOWN', function () {
  1435. var hot = handsontable({
  1436. contextMenu: {
  1437. items: {
  1438. item1: {
  1439. name: 'Item1',
  1440. disabled: true
  1441. },
  1442. item2: {
  1443. name: 'Item2',
  1444. disabled: true
  1445. },
  1446. item3: {
  1447. name: 'Item3'
  1448. }
  1449. }
  1450. },
  1451. height: 100
  1452. });
  1453. contextMenu();
  1454. var menuHot = hot.getPlugin('contextMenu').menu.hotMenu;
  1455. expect(menuHot.getSelected()).toBeUndefined();
  1456. keyDownUp('arrow_down');
  1457. expect(menuHot.getSelected()).toEqual([2, 0, 2, 0]);
  1458. });
  1459. it('should NOT select any items in menu, when user hits ARROW_DOWN and there is no items enabled', function () {
  1460. var hot = handsontable({
  1461. contextMenu: {
  1462. items: {
  1463. item1: {
  1464. name: 'Item1',
  1465. disabled: true
  1466. },
  1467. item2: {
  1468. name: 'Item2',
  1469. disabled: true
  1470. },
  1471. item3: {
  1472. name: 'Item3',
  1473. disabled: true
  1474. }
  1475. }
  1476. },
  1477. height: 100
  1478. });
  1479. contextMenu();
  1480. var menuHot = hot.getPlugin('contextMenu').menu.hotMenu;
  1481. expect(menuHot.getSelected()).toBeUndefined();
  1482. keyDownUp('arrow_down');
  1483. expect(menuHot.getSelected()).toBeUndefined();
  1484. });
  1485. it('should select the last item in menu, when user hits ARROW_UP', function () {
  1486. var hot = handsontable({
  1487. contextMenu: {
  1488. items: {
  1489. item1: 'Item1',
  1490. item2: 'Item2',
  1491. item3: 'Item3'
  1492. }
  1493. },
  1494. height: 100
  1495. });
  1496. contextMenu();
  1497. var menuHot = hot.getPlugin('contextMenu').menu.hotMenu;
  1498. expect(menuHot.getSelected()).toBeUndefined();
  1499. keyDownUp('arrow_up');
  1500. expect(menuHot.getSelected()).toEqual([2, 0, 2, 0]);
  1501. });
  1502. it('should select the last NOT DISABLED item in menu, when user hits ARROW_UP', function () {
  1503. var hot = handsontable({
  1504. contextMenu: {
  1505. items: {
  1506. item1: {
  1507. name: 'Item1'
  1508. },
  1509. item2: {
  1510. name: 'Item2',
  1511. disabled: true
  1512. },
  1513. item3: {
  1514. name: 'Item3',
  1515. disabled: true
  1516. }
  1517. }
  1518. },
  1519. height: 100
  1520. });
  1521. contextMenu();
  1522. var menuHot = hot.getPlugin('contextMenu').menu.hotMenu;
  1523. expect(menuHot.getSelected()).toBeUndefined();
  1524. keyDownUp('arrow_up');
  1525. expect(menuHot.getSelected()).toEqual([0, 0, 0, 0]);
  1526. });
  1527. it('should NOT select any items in menu, when user hits ARROW_UP and there is no items enabled', function () {
  1528. var hot = handsontable({
  1529. contextMenu: {
  1530. items: {
  1531. item1: {
  1532. name: 'Item1',
  1533. disabled: true
  1534. },
  1535. item2: {
  1536. name: 'Item2',
  1537. disabled: true
  1538. },
  1539. item3: {
  1540. name: 'Item3',
  1541. disabled: true
  1542. }
  1543. }
  1544. },
  1545. height: 100
  1546. });
  1547. contextMenu();
  1548. var id = $('.htContextMenu')[0].id;
  1549. var menuHot = hot.getPlugin('contextMenu').menu.hotMenu;
  1550. expect(menuHot.getSelected()).toBeUndefined();
  1551. keyDownUp('arrow_up');
  1552. expect(menuHot.getSelected()).toBeUndefined();
  1553. });
  1554. });
  1555. describe('item selected', function () {
  1556. it('should select next item when user hits ARROW_DOWN', function () {
  1557. var hot = handsontable({
  1558. contextMenu: {
  1559. items: {
  1560. item1: {
  1561. name: 'Item1'
  1562. },
  1563. item2: {
  1564. name: 'Item2'
  1565. },
  1566. item3: {
  1567. name: 'Item3'
  1568. }
  1569. }
  1570. },
  1571. height: 100
  1572. });
  1573. contextMenu();
  1574. var menuHot = hot.getPlugin('contextMenu').menu.hotMenu;
  1575. keyDownUp('arrow_down');
  1576. expect(menuHot.getSelected()).toEqual([0, 0, 0, 0]);
  1577. keyDownUp('arrow_down');
  1578. expect(menuHot.getSelected()).toEqual([1, 0, 1, 0]);
  1579. keyDownUp('arrow_down');
  1580. expect(menuHot.getSelected()).toEqual([2, 0, 2, 0]);
  1581. });
  1582. it('should select next item (skipping disabled items) when user hits ARROW_DOWN', function () {
  1583. var hot = handsontable({
  1584. contextMenu: {
  1585. items: {
  1586. item1: {
  1587. name: 'Item1'
  1588. },
  1589. item2: {
  1590. name: 'Item2',
  1591. disabled: true
  1592. },
  1593. item3: {
  1594. name: 'Item3'
  1595. }
  1596. }
  1597. },
  1598. height: 100
  1599. });
  1600. contextMenu();
  1601. var menuHot = hot.getPlugin('contextMenu').menu.hotMenu;
  1602. keyDownUp('arrow_down');
  1603. expect(menuHot.getSelected()).toEqual([0, 0, 0, 0]);
  1604. keyDownUp('arrow_down');
  1605. expect(menuHot.getSelected()).toEqual([2, 0, 2, 0]);
  1606. });
  1607. it('should select next item (skipping separators) when user hits ARROW_DOWN', function () {
  1608. var hot = handsontable({
  1609. contextMenu: {
  1610. items: {
  1611. item1: {
  1612. name: 'Item1'
  1613. },
  1614. sep1: Handsontable.plugins.ContextMenu.SEPARATOR,
  1615. item2: {
  1616. name: 'Item2'
  1617. },
  1618. item3: {
  1619. name: 'Item3'
  1620. }
  1621. }
  1622. },
  1623. height: 100
  1624. });
  1625. contextMenu();
  1626. var menuHot = hot.getPlugin('contextMenu').menu.hotMenu;
  1627. keyDownUp('arrow_down');
  1628. expect(menuHot.getSelected()).toEqual([0, 0, 0, 0]);
  1629. keyDownUp('arrow_down');
  1630. expect(menuHot.getSelected()).toEqual([2, 0, 2, 0]);
  1631. keyDownUp('arrow_down');
  1632. expect(menuHot.getSelected()).toEqual([3, 0, 3, 0]);
  1633. });
  1634. it('should not change selection when last item is selected and user hits ARROW_DOWN', function () {
  1635. var hot = handsontable({
  1636. contextMenu: {
  1637. items: {
  1638. item1: {
  1639. name: 'Item1'
  1640. },
  1641. item2: {
  1642. name: 'Item2'
  1643. },
  1644. item3: {
  1645. name: 'Item3'
  1646. }
  1647. }
  1648. },
  1649. height: 100
  1650. });
  1651. contextMenu();
  1652. var menuHot = hot.getPlugin('contextMenu').menu.hotMenu;
  1653. keyDownUp('arrow_down');
  1654. expect(menuHot.getSelected()).toEqual([0, 0, 0, 0]);
  1655. keyDownUp('arrow_down');
  1656. expect(menuHot.getSelected()).toEqual([1, 0, 1, 0]);
  1657. keyDownUp('arrow_down');
  1658. expect(menuHot.getSelected()).toEqual([2, 0, 2, 0]);
  1659. keyDownUp('arrow_down');
  1660. expect(menuHot.getSelected()).toEqual([2, 0, 2, 0]);
  1661. });
  1662. it('should not change selection when last enabled item is selected and user hits ARROW_DOWN', function () {
  1663. var hot = handsontable({
  1664. contextMenu: {
  1665. items: {
  1666. item1: {
  1667. name: 'Item1'
  1668. },
  1669. item2: {
  1670. name: 'Item2'
  1671. },
  1672. item3: {
  1673. name: 'Item3',
  1674. disabled: true
  1675. }
  1676. }
  1677. },
  1678. height: 100
  1679. });
  1680. contextMenu();
  1681. var menuHot = hot.getPlugin('contextMenu').menu.hotMenu;
  1682. keyDownUp('arrow_down');
  1683. expect(menuHot.getSelected()).toEqual([0, 0, 0, 0]);
  1684. keyDownUp('arrow_down');
  1685. expect(menuHot.getSelected()).toEqual([1, 0, 1, 0]);
  1686. keyDownUp('arrow_down');
  1687. expect(menuHot.getSelected()).toEqual([1, 0, 1, 0]);
  1688. });
  1689. it('should select next item when user hits ARROW_UP', function () {
  1690. var hot = handsontable({
  1691. contextMenu: {
  1692. items: {
  1693. item1: {
  1694. name: 'Item1'
  1695. },
  1696. item2: {
  1697. name: 'Item2'
  1698. },
  1699. item3: {
  1700. name: 'Item3'
  1701. }
  1702. }
  1703. },
  1704. height: 100
  1705. });
  1706. contextMenu();
  1707. var menuHot = hot.getPlugin('contextMenu').menu.hotMenu;
  1708. keyDownUp('arrow_up');
  1709. expect(menuHot.getSelected()).toEqual([2, 0, 2, 0]);
  1710. keyDownUp('arrow_up');
  1711. expect(menuHot.getSelected()).toEqual([1, 0, 1, 0]);
  1712. keyDownUp('arrow_up');
  1713. expect(menuHot.getSelected()).toEqual([0, 0, 0, 0]);
  1714. });
  1715. it('should select next item (skipping disabled items) when user hits ARROW_UP', function () {
  1716. var hot = handsontable({
  1717. contextMenu: {
  1718. items: {
  1719. item1: {
  1720. name: 'Item1'
  1721. },
  1722. item2: {
  1723. name: 'Item2',
  1724. disabled: true
  1725. },
  1726. item3: {
  1727. name: 'Item3'
  1728. }
  1729. }
  1730. },
  1731. height: 100
  1732. });
  1733. contextMenu();
  1734. var menuHot = hot.getPlugin('contextMenu').menu.hotMenu;
  1735. keyDownUp('arrow_up');
  1736. expect(menuHot.getSelected()).toEqual([2, 0, 2, 0]);
  1737. keyDownUp('arrow_up');
  1738. expect(menuHot.getSelected()).toEqual([0, 0, 0, 0]);
  1739. });
  1740. it('should select next item (skipping separators) when user hits ARROW_UP', function () {
  1741. var hot = handsontable({
  1742. contextMenu: {
  1743. items: {
  1744. item1: {
  1745. name: 'Item1'
  1746. },
  1747. sep1: Handsontable.plugins.ContextMenu.SEPARATOR,
  1748. item2: {
  1749. name: 'Item2'
  1750. },
  1751. item3: {
  1752. name: 'Item3'
  1753. }
  1754. }
  1755. },
  1756. height: 100
  1757. });
  1758. contextMenu();
  1759. var menuHot = hot.getPlugin('contextMenu').menu.hotMenu;
  1760. keyDownUp('arrow_up');
  1761. expect(menuHot.getSelected()).toEqual([3, 0, 3, 0]);
  1762. keyDownUp('arrow_up');
  1763. expect(menuHot.getSelected()).toEqual([2, 0, 2, 0]);
  1764. keyDownUp('arrow_up');
  1765. expect(menuHot.getSelected()).toEqual([0, 0, 0, 0]);
  1766. });
  1767. it('should not change selection when first item is selected and user hits ARROW_UP', function () {
  1768. var hot = handsontable({
  1769. contextMenu: {
  1770. items: {
  1771. item1: {
  1772. name: 'Item1'
  1773. },
  1774. item2: {
  1775. name: 'Item2'
  1776. },
  1777. item3: {
  1778. name: 'Item3'
  1779. }
  1780. }
  1781. },
  1782. height: 100
  1783. });
  1784. contextMenu();
  1785. var menuHot = hot.getPlugin('contextMenu').menu.hotMenu;
  1786. keyDownUp('arrow_up');
  1787. expect(menuHot.getSelected()).toEqual([2, 0, 2, 0]);
  1788. keyDownUp('arrow_up');
  1789. expect(menuHot.getSelected()).toEqual([1, 0, 1, 0]);
  1790. keyDownUp('arrow_up');
  1791. expect(menuHot.getSelected()).toEqual([0, 0, 0, 0]);
  1792. keyDownUp('arrow_up');
  1793. expect(menuHot.getSelected()).toEqual([0, 0, 0, 0]);
  1794. });
  1795. it('should not change selection when first enabled item is selected and user hits ARROW_UP', function () {
  1796. var hot = handsontable({
  1797. contextMenu: {
  1798. items: {
  1799. item1: {
  1800. name: 'Item1',
  1801. disabled: true
  1802. },
  1803. item2: {
  1804. name: 'Item2'
  1805. },
  1806. item3: {
  1807. name: 'Item3'
  1808. }
  1809. }
  1810. },
  1811. height: 100
  1812. });
  1813. contextMenu();
  1814. var menuHot = hot.getPlugin('contextMenu').menu.hotMenu;
  1815. keyDownUp('arrow_up');
  1816. expect(menuHot.getSelected()).toEqual([2, 0, 2, 0]);
  1817. keyDownUp('arrow_up');
  1818. expect(menuHot.getSelected()).toEqual([1, 0, 1, 0]);
  1819. keyDownUp('arrow_up');
  1820. expect(menuHot.getSelected()).toEqual([1, 0, 1, 0]);
  1821. });
  1822. it('should perform a selected item action, when user hits ENTER', function () {
  1823. var itemAction = jasmine.createSpy('itemAction');
  1824. var hot = handsontable({
  1825. contextMenu: {
  1826. items: {
  1827. item1: {
  1828. name: 'Item1',
  1829. callback: itemAction
  1830. },
  1831. item2: 'Item2'
  1832. }
  1833. },
  1834. height: 100
  1835. });
  1836. contextMenu();
  1837. var menuHot = hot.getPlugin('contextMenu').menu.hotMenu;
  1838. keyDownUp('arrow_down');
  1839. expect(menuHot.getSelected()).toEqual([0, 0, 0, 0]);
  1840. expect(itemAction).not.toHaveBeenCalled();
  1841. keyDownUp('enter');
  1842. expect(itemAction).toHaveBeenCalled();
  1843. expect($(hot.getPlugin('contextMenu').menu).is(':visible')).toBe(false);
  1844. });
  1845. });
  1846. it('should close menu when user hits ESC', function () {
  1847. handsontable({
  1848. contextMenu: true,
  1849. height: 100
  1850. });
  1851. contextMenu();
  1852. expect($('.htContextMenu').is(':visible')).toBe(true);
  1853. keyDownUp('esc');
  1854. expect($('.htContextMenu').is(':visible')).toBe(false);
  1855. });
  1856. it('should close sub-menu and parent menu in proper order when user hits ESC twice', function (done) {
  1857. handsontable({
  1858. contextMenu: true,
  1859. height: 100
  1860. });
  1861. contextMenu();
  1862. var item = $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(9);
  1863. var contextMenuRoot = $('.htContextMenu');
  1864. item.simulate('mouseover');
  1865. setTimeout(function () {
  1866. expect($('.htContextMenuSub_Alignment').is(':visible')).toBe(true);
  1867. keyDownUp('esc');
  1868. expect($('.htContextMenuSub_Alignment').is(':visible')).toBe(false);
  1869. keyDownUp('esc');
  1870. expect($('.htContextMenu').is(':visible')).toBe(false);
  1871. done();
  1872. }, 350); // waits for submenu open delay
  1873. });
  1874. });
  1875. describe('mouse navigation', function () {
  1876. it('should not scroll window position after fireing mouseenter on menu item', function () {
  1877. var hot = handsontable({
  1878. data: Handsontable.helper.createSpreadsheetData(1000, 5),
  1879. contextMenu: true
  1880. }),
  1881. scrollHeight;
  1882. hot.selectCell(100, 0);
  1883. contextMenu();
  1884. window.scrollTo(0, 0);
  1885. $('.htContextMenu .ht_master .htCore').find('tr td:eq("0")').simulate('mouseenter');
  1886. if (typeof window.scrollY !== 'undefined') {
  1887. scrollHeight = window.scrollY;
  1888. } else {
  1889. scrollHeight = document.documentElement.scrollTop;
  1890. }
  1891. expect(scrollHeight).toBe(0);
  1892. });
  1893. it('should not scroll window position after fireing click on menu', function () {
  1894. var hot = handsontable({
  1895. data: Handsontable.helper.createSpreadsheetData(1000, 5),
  1896. contextMenu: {
  1897. items: {
  1898. item1: {
  1899. name: 'Item1'
  1900. },
  1901. sep1: Handsontable.plugins.ContextMenu.SEPARATOR,
  1902. item2: {
  1903. name: 'Item2'
  1904. },
  1905. item3: {
  1906. name: 'Item3'
  1907. }
  1908. }
  1909. }
  1910. }),
  1911. scrollHeight;
  1912. hot.selectCell(100, 0);
  1913. contextMenu();
  1914. window.scrollTo(0, 0);
  1915. $('.htContextMenu .ht_master .htCore').find('tbody td').not('.htSeparator').eq(0).simulate('mousedown');
  1916. if (typeof window.scrollY !== 'undefined') {
  1917. scrollHeight = window.scrollY;
  1918. } else {
  1919. scrollHeight = document.documentElement.scrollTop;
  1920. }
  1921. expect(scrollHeight).toBe(0);
  1922. });
  1923. });
  1924. describe('working with multiple tables', function () {
  1925. beforeEach(function () {
  1926. this.$container2 = $('<div id="' + id + '-2"></div>').appendTo('body');
  1927. });
  1928. afterEach(function () {
  1929. if (this.$container2) {
  1930. this.$container2.handsontable('destroy');
  1931. this.$container2.remove();
  1932. }
  1933. });
  1934. it('should apply enabling/disabling contextMenu using updateSetting only to particular instance of HOT ', function () {
  1935. var hot1 = handsontable({
  1936. contextMenu: false,
  1937. height: 100
  1938. });
  1939. var hot2 = this.$container2.handsontable({
  1940. contextMenu: true,
  1941. height: 100
  1942. });
  1943. hot2 = hot2.handsontable('getInstance');
  1944. var contextMenuContainer = $('.htContextMenu');
  1945. contextMenu();
  1946. expect(hot1.getPlugin('contextMenu').isEnabled()).toBe(false);
  1947. expect(contextMenuContainer.is(':visible')).toBe(false);
  1948. contextMenu2();
  1949. expect(hot2.getPlugin('contextMenu').isEnabled()).toBe(true);
  1950. expect($('.htContextMenu').is(':visible')).toBe(true);
  1951. mouseDown(hot2.rootElement); // close menu
  1952. hot1.updateSettings({
  1953. contextMenu: true
  1954. });
  1955. hot2.updateSettings({
  1956. contextMenu: false
  1957. });
  1958. contextMenu2();
  1959. expect(hot2.getPlugin('contextMenu').isEnabled()).toBe(false);
  1960. contextMenu();
  1961. expect($('.htContextMenu').is(':visible')).toBe(true);
  1962. function contextMenu2() {
  1963. var hot = spec().$container2.data('handsontable');
  1964. var selected = hot.getSelected();
  1965. if (!selected) {
  1966. hot.selectCell(0, 0);
  1967. selected = hot.getSelected();
  1968. }
  1969. var cell = hot.getCell(selected[0], selected[1]);
  1970. var cellOffset = $(cell).offset();
  1971. $(cell).simulate('contextmenu', {
  1972. pageX: cellOffset.left,
  1973. pageY: cellOffset.top
  1974. });
  1975. }
  1976. });
  1977. it('should perform a contextMenu action only for particular instance of HOT ', function () {
  1978. var hot1 = handsontable({
  1979. contextMenu: true,
  1980. height: 100
  1981. });
  1982. var hot2 = this.$container2.handsontable({
  1983. contextMenu: true,
  1984. height: 100
  1985. });
  1986. hot2 = hot2.handsontable('getInstance');
  1987. hot1.selectCell(0, 0);
  1988. contextMenu();
  1989. expect(hot1.countRows()).toEqual(5);
  1990. expect(hot2.countRows()).toEqual(5);
  1991. $('.htContextMenu .ht_master .htCore').find('tr td:eq("0")').simulate('mousedown'); // insert row above
  1992. expect(hot1.countRows()).toEqual(6);
  1993. expect(hot2.countRows()).toEqual(5);
  1994. hot2.selectCell(0, 0);
  1995. contextMenu2();
  1996. expect(hot1.countRows()).toEqual(6);
  1997. expect(hot2.countRows()).toEqual(5);
  1998. $('.htContextMenu .ht_master .htCore').find('tr td:eq("0")').simulate('mousedown'); // insert row above
  1999. expect(hot1.countRows()).toEqual(6);
  2000. expect(hot2.countRows()).toEqual(6);
  2001. function contextMenu2() {
  2002. var hot = spec().$container2.data('handsontable');
  2003. var selected = hot.getSelected();
  2004. if (!selected) {
  2005. hot.selectCell(0, 0);
  2006. selected = hot.getSelected();
  2007. }
  2008. var cell = hot.getCell(selected[0], selected[1]);
  2009. var cellOffset = $(cell).offset();
  2010. $(cell).simulate('contextmenu', {
  2011. pageX: cellOffset.left,
  2012. pageY: cellOffset.top
  2013. });
  2014. }
  2015. });
  2016. });
  2017. describe('context menu with native scroll', function () {
  2018. beforeEach(function () {
  2019. var wrapper = $('<div></div>').css({
  2020. width: 400,
  2021. height: 200,
  2022. overflow: 'scroll'
  2023. });
  2024. this.$wrapper = this.$container.wrap(wrapper).parent();
  2025. });
  2026. afterEach(function () {
  2027. if (this.$container) {
  2028. destroy();
  2029. this.$container.remove();
  2030. }
  2031. this.$wrapper.remove();
  2032. });
  2033. it('should display menu table is not scrolled', function () {
  2034. var hot = handsontable({
  2035. data: Handsontable.helper.createSpreadsheetData(40, 30),
  2036. colWidths: 50, // can also be a number or a function
  2037. rowHeaders: true,
  2038. colHeaders: true,
  2039. contextMenu: true,
  2040. height: 100
  2041. });
  2042. contextMenu();
  2043. expect($('.htContextMenu').is(':visible')).toBe(true);
  2044. });
  2045. it('should display menu table is scrolled', function () {
  2046. var hot = handsontable({
  2047. data: Handsontable.helper.createSpreadsheetData(40, 30),
  2048. colWidths: 50, // can also be a number or a function
  2049. rowHeaders: true,
  2050. colHeaders: true,
  2051. contextMenu: true,
  2052. height: 100
  2053. });
  2054. var mainHolder = hot.view.wt.wtTable.holder;
  2055. $(mainHolder).scrollTop(300);
  2056. $(mainHolder).scroll();
  2057. selectCell(15, 3);
  2058. contextMenu();
  2059. expect($('.htContextMenu').is(':visible')).toBe(true);
  2060. });
  2061. it('should not close the menu, when table is scrolled', function () {
  2062. var hot = handsontable({
  2063. data: Handsontable.helper.createSpreadsheetData(40, 30),
  2064. colWidths: 50, // can also be a number or a function
  2065. rowHeaders: true,
  2066. colHeaders: true,
  2067. contextMenu: true,
  2068. height: 100
  2069. });
  2070. var $mainHolder = $(hot.view.wt.wtTable.holder);
  2071. selectCell(15, 3);
  2072. var scrollTop = $mainHolder.scrollTop();
  2073. contextMenu();
  2074. expect($('.htContextMenu').is(':visible')).toBe(true);
  2075. $mainHolder.scrollTop(scrollTop + 60).scroll();
  2076. expect($('.htContextMenu').is(':visible')).toBe(true);
  2077. contextMenu();
  2078. expect($('.htContextMenu').is(':visible')).toBe(true);
  2079. $mainHolder.scrollTop(scrollTop + 100).scroll();
  2080. expect($('.htContextMenu').is(':visible')).toBe(true);
  2081. });
  2082. it('should not attempt to close menu, when table is scrolled and the menu is already closed', function () {
  2083. var hot = handsontable({
  2084. data: Handsontable.helper.createSpreadsheetData(40, 30),
  2085. colWidths: 50, // can also be a number or a function
  2086. rowHeaders: true,
  2087. colHeaders: true,
  2088. contextMenu: true,
  2089. height: 100
  2090. });
  2091. var mainHolder = $(hot.view.wt.wtTable.holder);
  2092. selectCell(15, 3);
  2093. var scrollTop = mainHolder.scrollTop();
  2094. contextMenu();
  2095. var $menu = $('.htContextMenu');
  2096. spyOn(hot.getPlugin('contextMenu'), 'close');
  2097. mainHolder.scrollTop(scrollTop + 100).scroll();
  2098. expect(hot.getPlugin('contextMenu').close).not.toHaveBeenCalled();
  2099. });
  2100. it('should not scroll the window when hovering over context menu items (#1897 reopen)', function () {
  2101. this.$wrapper.css('overflow', 'visible');
  2102. var hot = handsontable({
  2103. data: Handsontable.helper.createSpreadsheetData(403, 303),
  2104. colWidths: 50, // can also be a number or a function
  2105. contextMenu: true
  2106. });
  2107. var beginningScrollX = window.scrollX;
  2108. selectCell(2, 4);
  2109. contextMenu();
  2110. var cmInstance = hot.getPlugin('contextMenu').menu.hotMenu;
  2111. cmInstance.selectCell(3, 0);
  2112. expect(window.scrollX).toEqual(beginningScrollX);
  2113. cmInstance.selectCell(4, 0);
  2114. expect(window.scrollX).toEqual(beginningScrollX);
  2115. cmInstance.selectCell(6, 0);
  2116. expect(window.scrollX).toEqual(beginningScrollX);
  2117. });
  2118. });
  2119. describe('afterContextMenuDefaultOptions hook', function () {
  2120. it('should call afterContextMenuDefaultOptions hook with context menu options as the first param', function () {
  2121. var options;
  2122. var afterContextMenuDefaultOptions = function afterContextMenuDefaultOptions(options_) {
  2123. options = options_;
  2124. options.items.cust1 = {
  2125. name: 'My custom item',
  2126. callback: function callback() {}
  2127. };
  2128. };
  2129. Handsontable.hooks.add('afterContextMenuDefaultOptions', afterContextMenuDefaultOptions);
  2130. var hot = handsontable({
  2131. contextMenu: true,
  2132. height: 100
  2133. });
  2134. contextMenu();
  2135. var $menu = $('.htContextMenu .ht_master .htCore');
  2136. expect(options).toBeDefined();
  2137. expect(options.items).toBeDefined();
  2138. expect($menu.find('tbody td').text()).toContain('My custom item');
  2139. $menu.find('tbody td:eq(0)').simulate('mousedown');
  2140. Handsontable.hooks.remove('afterContextMenuDefaultOptions', afterContextMenuDefaultOptions);
  2141. });
  2142. });
  2143. describe('beforeContextMenuSetItems hook', function () {
  2144. it('should add new menu item even when item is excluded from plugin settings', function () {
  2145. Handsontable.hooks.add('beforeContextMenuSetItems', function (options) {
  2146. if (this === hot || !hot) {
  2147. options.push({
  2148. key: 'test',
  2149. name: 'Test'
  2150. });
  2151. }
  2152. });
  2153. var hot = handsontable({
  2154. contextMenu: ['make_read_only'],
  2155. height: 100
  2156. });
  2157. contextMenu();
  2158. var items = $('.htContextMenu tbody td');
  2159. var actions = items.not('.htSeparator');
  2160. expect(actions.text()).toEqual(['Read only', 'Test'].join(''));
  2161. });
  2162. it('should be called only with items selected in plugin settings', function () {
  2163. var keys = [];
  2164. Handsontable.hooks.add('beforeContextMenuSetItems', function (items) {
  2165. if (this === hot || !hot) {
  2166. keys = items.map(function (v) {
  2167. return v.key;
  2168. });
  2169. }
  2170. });
  2171. var hot = handsontable({
  2172. contextMenu: ['make_read_only', 'col_left'],
  2173. height: 100
  2174. });
  2175. contextMenu();
  2176. expect(keys).toEqual(['make_read_only', 'col_left']);
  2177. });
  2178. });
  2179. });