943bc09bed8c5e13f1bb20b41b648f88dd50d4ab227d6912857bd2e44803ea797f14f0cb46ffaf1f5563d3acdf387f57e8b31ae2683adceb4e1d9e2f552e7e 84 KB

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