dfbd926502c87bcb8984f259e297d83cea2990796eb9fac31cccfaa803eb111f86ec3bfbda2964ba36f748a1da26089ef075d6d18692cea5d08bac9280c669 84 KB

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