textEditor.spec.js 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325
  1. describe('TextEditor', () => {
  2. const id = 'testContainer';
  3. beforeEach(function() {
  4. this.$container = $(`<div id="${id}" style="width: 300px; height: 200px; overflow: hidden;"></div>`).appendTo('body');
  5. });
  6. afterEach(function() {
  7. if (this.$container) {
  8. destroy();
  9. this.$container.remove();
  10. }
  11. });
  12. it('should begin editing when enterBeginsEditing equals true', () => {
  13. handsontable({
  14. enterBeginsEditing: true,
  15. editor: 'text'
  16. });
  17. selectCell(2, 2);
  18. keyDown('enter');
  19. const selection = getSelected();
  20. expect(selection).toEqual([[2, 2, 2, 2]]);
  21. expect(isEditorVisible()).toEqual(true);
  22. });
  23. it('should move down after editing', () => {
  24. handsontable({
  25. editor: 'text'
  26. });
  27. selectCell(2, 2);
  28. keyDown('enter');
  29. keyDown('enter');
  30. const selection = getSelected();
  31. expect(selection).toEqual([[3, 2, 3, 2]]);
  32. });
  33. it('should move down when enterBeginsEditing equals false', () => {
  34. handsontable({
  35. enterBeginsEditing: false
  36. });
  37. selectCell(2, 2);
  38. keyDown('enter');
  39. const selection = getSelected();
  40. expect(selection).toEqual([[3, 2, 3, 2]]);
  41. expect(isEditorVisible()).toEqual(false);
  42. });
  43. it('should create editor holder after cell selection', () => {
  44. handsontable({
  45. editor: 'text',
  46. });
  47. const container = spec().$container;
  48. expect(container.find('.handsontableInputHolder').length).toBe(0);
  49. selectCell(0, 0);
  50. expect(container.find('.handsontableInputHolder').length).toBe(1);
  51. });
  52. it('should prepare editor with proper styles after selection', () => {
  53. handsontable({
  54. editor: 'text',
  55. });
  56. selectCell(0, 0);
  57. const { left, position, top, zIndex } = spec().$container.find('.handsontableInputHolder').css(['left', 'position', 'top', 'zIndex']);
  58. expect(left).toBe('-9999px');
  59. expect(position).toBe('fixed');
  60. expect(top).toBe('-9999px');
  61. expect(zIndex).toBe('-1');
  62. });
  63. it('should change editor\'s CSS properties during switching to being visible', () => {
  64. handsontable({
  65. editor: 'text',
  66. });
  67. selectCell(0, 0);
  68. keyDownUp('enter');
  69. const cell = getCell(0, 0);
  70. const [cellOffsetTop, cellOffsetLeft] = [cell.offsetTop, cell.offsetLeft];
  71. const { left, position, top, zIndex } = spec().$container.find('.handsontableInputHolder').css(['left', 'position', 'top', 'zIndex']);
  72. expect(parseInt(left, 10)).toBeAroundValue(cellOffsetLeft);
  73. expect(position).toBe('absolute');
  74. expect(parseInt(top, 10)).toBeAroundValue(cellOffsetTop);
  75. expect(zIndex).not.toBe('-1');
  76. });
  77. it('should render string in textarea', () => {
  78. handsontable();
  79. setDataAtCell(2, 2, 'string');
  80. selectCell(2, 2);
  81. keyDown('enter');
  82. expect(keyProxy().val()).toEqual('string');
  83. });
  84. it('should render proper value after cell coords manipulation', () => {
  85. handsontable({
  86. data: Handsontable.helper.createSpreadsheetData(5, 5),
  87. modifyRow(row) { return row === 4 ? 0 : row + 1; },
  88. modifyCol(column) { return column === 4 ? 0 : column + 1; },
  89. });
  90. selectCell(0, 0);
  91. getActiveEditor().beginEditing();
  92. getActiveEditor().refreshValue();
  93. expect(getActiveEditor().originalValue).toEqual('B2');
  94. });
  95. it('should render textarea editor with tabindex=-1 attribute', async() => {
  96. const hot = handsontable();
  97. selectCell(0, 0);
  98. keyDown('enter');
  99. await sleep(10);
  100. expect(hot.getActiveEditor().TEXTAREA.getAttribute('tabindex')).toBe('-1');
  101. });
  102. it('should render textarea editor in specified size at cell 0, 0 without headers', (done) => {
  103. const hot = handsontable();
  104. selectCell(0, 0);
  105. keyDown('enter');
  106. setTimeout(() => {
  107. expect(hot.getActiveEditor().TEXTAREA.style.height).toBe('23px');
  108. expect(hot.getActiveEditor().TEXTAREA.style.width).toBe('40px');
  109. done();
  110. }, 200);
  111. });
  112. it('should render textarea editor in specified size at cell 1, 0 without headers', (done) => {
  113. const hot = handsontable();
  114. selectCell(1, 1);
  115. keyDown('enter');
  116. setTimeout(() => {
  117. expect(hot.getActiveEditor().TEXTAREA.style.height).toBe('23px');
  118. done();
  119. }, 200);
  120. });
  121. it('should render textarea editor in specified size at cell 0, 0 with headers', (done) => {
  122. const hot = handsontable({
  123. rowHeaders: true,
  124. colHeaders: true
  125. });
  126. selectCell(0, 0);
  127. keyDown('enter');
  128. setTimeout(() => {
  129. expect(hot.getActiveEditor().TEXTAREA.style.height).toBe('23px');
  130. expect(hot.getActiveEditor().TEXTAREA.style.width).toBe('40px');
  131. expect(hot.getActiveEditor().textareaParentStyle.top).toBe('26px');
  132. done();
  133. }, 200);
  134. });
  135. it('should render textarea editor in specified size at cell 0, 0 with headers defined in columns', (done) => {
  136. const hot = handsontable({
  137. data: Handsontable.helper.createSpreadsheetObjectData(10, 10),
  138. columns: [{
  139. data: 'prop0',
  140. title: 'Prop 0'
  141. }, {
  142. data: 'prop1',
  143. title: 'Prop 1'
  144. }, {
  145. data: 'prop2',
  146. title: 'Prop 2'
  147. }, {
  148. data: 'prop3',
  149. title: 'Prop 3'
  150. }],
  151. });
  152. selectCell(0, 0);
  153. keyDown('enter');
  154. setTimeout(() => {
  155. expect(hot.getActiveEditor().TEXTAREA.style.height).toBe('23px');
  156. expect(parseInt(hot.getActiveEditor().TEXTAREA.style.width, 10)).toBeAroundValue(50, 4);
  157. expect(hot.getActiveEditor().textareaParentStyle.top).toBe('26px');
  158. done();
  159. }, 200);
  160. });
  161. it('should hide whole editor when it is higher then header', (done) => {
  162. const hot = handsontable({
  163. data: Handsontable.helper.createSpreadsheetData(50, 50),
  164. rowHeaders: true,
  165. colHeaders: true
  166. });
  167. setDataAtCell(2, 2, 'string\nstring\nstring');
  168. selectCell(2, 2);
  169. keyDown('enter');
  170. keyUp('enter');
  171. const mainHolder = hot.view.wt.wtTable.holder;
  172. mainHolder.scrollTop = 150;
  173. mainHolder.scrollLeft = 150;
  174. setTimeout(() => {
  175. expect(parseInt(hot.getActiveEditor().textareaParentStyle.top, 10)).toBeAroundValue(-77);
  176. expect(parseInt(hot.getActiveEditor().textareaParentStyle.left, 10)).toBeAroundValue(-1);
  177. done();
  178. }, 200);
  179. });
  180. it('should hide editor when quick navigation by click scrollbar was triggered', async() => {
  181. const hot = handsontable({
  182. data: Handsontable.helper.createSpreadsheetData(50, 50),
  183. rowHeaders: true,
  184. colHeaders: true
  185. });
  186. setDataAtCell(2, 2, 'string\nstring\nstring');
  187. selectCell(2, 2);
  188. keyDown('enter');
  189. keyUp('enter');
  190. hot.scrollViewportTo(49);
  191. await sleep(100);
  192. expect(isEditorVisible()).toBe(false);
  193. });
  194. it('should render textarea editor in specified height (single line)', (done) => {
  195. const hot = handsontable();
  196. setDataAtCell(2, 2, 'first line');
  197. selectCell(2, 2);
  198. keyDown('enter');
  199. setTimeout(() => {
  200. expect(hot.getActiveEditor().TEXTAREA.style.height).toBe('23px');
  201. done();
  202. }, 200);
  203. });
  204. it('should render textarea editor in specified height (multi line)', (done) => {
  205. const hot = handsontable();
  206. setDataAtCell(2, 2, 'first line\n second line\n third line...');
  207. selectCell(2, 2);
  208. keyDown('enter');
  209. setTimeout(() => {
  210. expect(hot.getActiveEditor().TEXTAREA.style.height).toBe('64px');
  211. done();
  212. }, 200);
  213. });
  214. it('should render number in textarea', () => {
  215. handsontable();
  216. setDataAtCell(2, 2, 13);
  217. selectCell(2, 2);
  218. keyDown('enter');
  219. expect(keyProxy().val()).toEqual('13');
  220. });
  221. it('should render boolean true in textarea', () => {
  222. handsontable();
  223. setDataAtCell(2, 2, true);
  224. selectCell(2, 2);
  225. keyDown('enter');
  226. expect(keyProxy().val()).toEqual('true');
  227. });
  228. it('should render boolean false in textarea', () => {
  229. handsontable();
  230. setDataAtCell(2, 2, false);
  231. selectCell(2, 2);
  232. keyDown('enter');
  233. expect(keyProxy().val()).toEqual('false');
  234. });
  235. it('should render null in textarea', () => {
  236. handsontable();
  237. setDataAtCell(2, 2, null);
  238. selectCell(2, 2);
  239. keyDown('enter');
  240. expect(keyProxy().val()).toEqual('');
  241. });
  242. it('should render undefined in textarea', () => {
  243. handsontable();
  244. setDataAtCell(2, 2, void 0);
  245. selectCell(2, 2);
  246. keyDown('enter');
  247. expect(keyProxy().val()).toEqual('');
  248. });
  249. it('should render nested object value in textarea', () => {
  250. handsontable({
  251. data: [{
  252. name: {
  253. first: 'Tom',
  254. last: 'Kowalski',
  255. obj: {}
  256. }
  257. }, {
  258. name: {
  259. first: 'John',
  260. last: 'Cage',
  261. obj: {
  262. foo: 'bar'
  263. }
  264. }
  265. }],
  266. columns: [{
  267. data: 'name.last'
  268. }, {
  269. data: 'name.obj.foo'
  270. }]
  271. });
  272. selectCell(0, 0);
  273. keyDown('enter');
  274. expect(keyProxy().val()).toEqual('Kowalski');
  275. selectCell(1, 1);
  276. keyDown('enter');
  277. expect(keyProxy().val()).toEqual('bar');
  278. });
  279. it('should render nested object value in textarea after change rows order', () => {
  280. const hot = handsontable({
  281. data: [{
  282. name: {
  283. first: 'Tom',
  284. last: 'Kowalski',
  285. obj: {}
  286. }
  287. }, {
  288. name: {
  289. first: 'John',
  290. last: 'Cage',
  291. obj: {
  292. foo: 'bar'
  293. }
  294. }
  295. }],
  296. columns: [{
  297. data: 'name.last'
  298. }, {
  299. data: 'name.obj.foo'
  300. }],
  301. manualRowMove: true
  302. });
  303. hot.getPlugin('manualRowMove').moveRow(1, 0);
  304. hot.render();
  305. selectCell(0, 0);
  306. keyDown('enter');
  307. expect(keyProxy().val()).toEqual('Cage');
  308. keyDown('enter');
  309. expect(hot.getDataAtCell(0, 0)).toEqual('Cage');
  310. selectCell(1, 1);
  311. keyDown('enter');
  312. expect(keyProxy().val()).toEqual('');
  313. keyDown('enter');
  314. expect(hot.getDataAtCell(1, 1)).toEqual('');
  315. });
  316. it('should render nested object value in textarea after change columns order', () => {
  317. const hot = handsontable({
  318. data: [{
  319. name: {
  320. first: 'Tom',
  321. last: 'Kowalski',
  322. obj: {}
  323. }
  324. }, {
  325. name: {
  326. first: 'John',
  327. last: 'Cage',
  328. obj: {
  329. foo: 'bar'
  330. }
  331. }
  332. }],
  333. columns: [{
  334. data: 'name.last'
  335. }, {
  336. data: 'name.obj.foo'
  337. }],
  338. manualColumnMove: true
  339. });
  340. hot.getPlugin('manualColumnMove').moveColumn(1, 0);
  341. hot.render();
  342. selectCell(0, 0);
  343. keyDown('enter');
  344. expect(keyProxy().val()).toEqual('');
  345. keyDown('enter');
  346. expect(hot.getDataAtCell(0, 0)).toEqual('');
  347. selectCell(1, 1);
  348. keyDown('enter');
  349. expect(keyProxy().val()).toEqual('Cage');
  350. keyDown('enter');
  351. expect(hot.getDataAtCell(1, 1)).toEqual('Cage');
  352. });
  353. it('should render array value defined by columns settings in textarea', () => {
  354. handsontable({
  355. data: [
  356. ['', 'Kia'],
  357. ['2012', 10],
  358. ['2013', 10],
  359. ],
  360. columns: [{
  361. data: '1'
  362. }, {
  363. data: '0'
  364. }],
  365. });
  366. selectCell(0, 0);
  367. keyDown('enter');
  368. expect(keyProxy().val()).toEqual('Kia');
  369. selectCell(1, 1);
  370. keyDown('enter');
  371. expect(keyProxy().val()).toEqual('2012');
  372. });
  373. it('should open editor after hitting F2', () => {
  374. handsontable();
  375. selectCell(2, 2);
  376. expect(isEditorVisible()).toEqual(false);
  377. keyDown('f2');
  378. expect(isEditorVisible()).toEqual(true);
  379. });
  380. it('should close editor after hitting ESC', () => {
  381. handsontable();
  382. selectCell(2, 2);
  383. expect(isEditorVisible()).toEqual(false);
  384. keyDown('f2');
  385. expect(isEditorVisible()).toEqual(true);
  386. keyDown('esc');
  387. expect(isEditorVisible()).toEqual(false);
  388. });
  389. it('should NOT open editor after hitting CapsLock', () => {
  390. handsontable();
  391. selectCell(2, 2);
  392. expect(isEditorVisible()).toEqual(false);
  393. keyDown(Handsontable.helper.KEY_CODES.CAPS_LOCK);
  394. expect(isEditorVisible()).toEqual(false);
  395. });
  396. it('should open editor after cancelling edit and beginning it again', () => {
  397. handsontable();
  398. selectCell(2, 2);
  399. expect(isEditorVisible()).toEqual(false);
  400. keyDown('f2');
  401. expect(isEditorVisible()).toEqual(true);
  402. keyDown('esc');
  403. expect(isEditorVisible()).toEqual(false);
  404. keyDown('f2');
  405. expect(isEditorVisible()).toEqual(true);
  406. });
  407. it('loadData should not destroy editor', () => {
  408. handsontable();
  409. selectCell(2, 2);
  410. keyDown('f2');
  411. loadData(getData());
  412. expect(isEditorVisible()).toEqual(true);
  413. });
  414. it('updateSettings should not destroy editor', () => {
  415. handsontable();
  416. selectCell(2, 2);
  417. keyDown('f2');
  418. updateSettings({
  419. data: getData()
  420. });
  421. expect(isEditorVisible()).toEqual(true);
  422. });
  423. it('textarea should have cell dimensions (after render)', () => {
  424. const data = [
  425. ['a', 'b'],
  426. ['c', 'd']
  427. ];
  428. handsontable({
  429. data,
  430. minRows: 4,
  431. minCols: 4,
  432. minSpareRows: 4,
  433. minSpareCols: 4,
  434. enterMoves: false
  435. });
  436. selectCell(1, 1);
  437. const $td = getHtCore().find('tbody tr:eq(1) td:eq(1)');
  438. keyDownUp('enter');
  439. expect(keyProxy().width()).toEqual($td.width());
  440. keyDownUp('enter');
  441. data[1][1] = 'dddddddddddddddddddd';
  442. render();
  443. keyDownUp('enter');
  444. expect(keyProxy().width()).toEqual($td.width());
  445. });
  446. it('global shortcuts (like CTRL+A) should be blocked when cell is being edited', () => {
  447. handsontable();
  448. selectCell(2, 2);
  449. keyDownUp('enter');
  450. keyDown(65, {
  451. ctrlKey: true
  452. }); // CTRL+A should NOT select all table when cell is edited
  453. const selection = getSelected();
  454. expect(selection).toEqual([[2, 2, 2, 2]]);
  455. expect(isEditorVisible()).toEqual(true);
  456. });
  457. it('should open editor after double clicking on a cell', (done) => {
  458. const hot = handsontable({
  459. data: Handsontable.helper.createSpreadsheetData(5, 2)
  460. });
  461. const cell = $(getCell(0, 0));
  462. let clicks = 0;
  463. window.scrollTo(0, cell.offset().top);
  464. setTimeout(() => {
  465. mouseDown(cell);
  466. mouseUp(cell);
  467. clicks += 1;
  468. }, 0);
  469. setTimeout(() => {
  470. mouseDown(cell);
  471. mouseUp(cell);
  472. clicks += 1;
  473. }, 100);
  474. setTimeout(() => {
  475. const editor = hot.getActiveEditor();
  476. expect(clicks).toBe(2);
  477. expect(editor.isOpened()).toBe(true);
  478. expect(editor.isInFullEditMode()).toBe(true);
  479. done();
  480. }, 200);
  481. });
  482. it('should call editor focus() method after opening an editor', () => {
  483. const hot = handsontable();
  484. selectCell(2, 2);
  485. const editor = hot.getActiveEditor();
  486. spyOn(editor, 'focus');
  487. expect(editor.isOpened()).toEqual(false);
  488. expect(editor.focus).not.toHaveBeenCalled();
  489. keyDown('f2');
  490. expect(editor.isOpened()).toEqual(true);
  491. expect(editor.focus).toHaveBeenCalled();
  492. });
  493. it('editor size should not exceed the viewport after text edit', () => {
  494. handsontable({
  495. data: Handsontable.helper.createSpreadsheetData(10, 5),
  496. width: 200,
  497. height: 200
  498. });
  499. selectCell(2, 2);
  500. keyDown('enter');
  501. expect(isEditorVisible()).toEqual(true);
  502. document.activeElement.value = 'Very very very very very very very very very very very very very very very very very long text';
  503. keyDownUp(32); // space - trigger textarea resize
  504. const $textarea = $(document.activeElement);
  505. const $wtHider = spec().$container.find('.wtHider');
  506. expect($textarea.offset().left + $textarea.outerWidth()).not.toBeGreaterThan($wtHider.offset().left + spec().$container.outerWidth());
  507. expect($textarea.offset().top + $textarea.outerHeight()).not.toBeGreaterThan($wtHider.offset().top + $wtHider.outerHeight());
  508. });
  509. it('should open editor after selecting cell in another table and hitting enter', function() {
  510. spec().$container2 = $(`<div id="${id}-2"></div>`).appendTo('body');
  511. const hot1 = handsontable();
  512. const hot2 = handsontable2.call(this);
  513. spec().$container.find('tbody tr:eq(0) td:eq(0)').simulate('mousedown');
  514. spec().$container.find('tbody tr:eq(0) td:eq(0)').simulate('mouseup');
  515. // Open editor in HOT1
  516. keyDown('enter');
  517. expect(isEditorVisible($(hot1.getActiveEditor().TEXTAREA))).toBe(true);
  518. // Close editor in HOT1
  519. keyDown('enter');
  520. expect(isEditorVisible($(hot1.getActiveEditor().TEXTAREA))).toBe(false);
  521. spec().$container2.find('tbody tr:eq(0) td:eq(0)').simulate('mousedown');
  522. spec().$container2.find('tbody tr:eq(0) td:eq(0)').simulate('mouseup');
  523. expect(hot1.getSelected()).toBeUndefined();
  524. expect(hot2.getSelected()).toEqual([[0, 0, 0, 0]]);
  525. // Open editor in HOT2
  526. keyDown('enter');
  527. expect(isEditorVisible($(hot2.getActiveEditor().TEXTAREA))).toBe(true);
  528. spec().$container2.handsontable('destroy');
  529. spec().$container2.remove();
  530. function handsontable2(options) {
  531. const container = spec().$container2;
  532. container.handsontable(options);
  533. container[0].focus(); // otherwise TextEditor tests do not pass in IE8
  534. return container.data('handsontable');
  535. }
  536. });
  537. it('should open editor after pressing a printable character', () => {
  538. handsontable({
  539. data: Handsontable.helper.createSpreadsheetData(3, 3)
  540. });
  541. selectCell(0, 0);
  542. expect(isEditorVisible()).toBe(false);
  543. spec().$container.simulate('keydown', {
  544. keyCode: 'A'.charCodeAt(0)
  545. });
  546. expect(isEditorVisible()).toBe(true);
  547. });
  548. it('should open editor after pressing a printable character with shift key', () => {
  549. handsontable({
  550. data: Handsontable.helper.createSpreadsheetData(3, 3)
  551. });
  552. selectCell(0, 0);
  553. expect(isEditorVisible()).toBe(false);
  554. spec().$container.simulate('keydown', {
  555. keyCode: 'A'.charCodeAt(0),
  556. shiftKey: true
  557. });
  558. expect(isEditorVisible()).toBe(true);
  559. });
  560. it('should be able to open editor after clearing cell data with DELETE', () => {
  561. handsontable({
  562. data: Handsontable.helper.createSpreadsheetData(3, 3)
  563. });
  564. selectCell(0, 0);
  565. expect(isEditorVisible()).toBe(false);
  566. spec().$container.simulate('keydown', {
  567. keyCode: 46
  568. });
  569. spec().$container.simulate('keydown', {
  570. keyCode: 'A'.charCodeAt(0)
  571. });
  572. expect(isEditorVisible()).toBe(true);
  573. });
  574. it('should be able to open editor after clearing cell data with BACKSPACE', () => {
  575. handsontable({
  576. data: Handsontable.helper.createSpreadsheetData(3, 3)
  577. });
  578. selectCell(0, 0);
  579. expect(isEditorVisible()).toBe(false);
  580. spec().$container.simulate('keydown', {
  581. keyCode: 8 // backspace
  582. });
  583. spec().$container.simulate('keydown', {
  584. keyCode: 'A'.charCodeAt(0)
  585. });
  586. expect(isEditorVisible()).toBe(true);
  587. });
  588. it('should scroll editor to a cell, if trying to edit cell that is outside of the viewport', () => {
  589. const hot = handsontable({
  590. data: Handsontable.helper.createSpreadsheetData(20, 20),
  591. width: 100,
  592. height: 50
  593. });
  594. selectCell(0, 0);
  595. expect(getCell(0, 0)).not.toBeNull();
  596. expect(getCell(19, 19)).toBeNull();
  597. hot.view.scrollViewport({ row: 19, col: 19 });
  598. hot.render();
  599. expect(getCell(0, 0)).toBeNull();
  600. expect(getCell(19, 19)).not.toBeNull();
  601. keyDown('enter');
  602. expect(getCell(0, 0)).not.toBeNull();
  603. expect(getCell(19, 19)).toBeNull();
  604. });
  605. it('should open empty editor after clearing cell value width BACKSPACE', () => {
  606. const hot = handsontable({
  607. data: Handsontable.helper.createSpreadsheetData(4, 4)
  608. });
  609. expect(getDataAtCell(0, 0)).toEqual('A1');
  610. selectCell(0, 0);
  611. keyDown(Handsontable.helper.KEY_CODES.BACKSPACE);
  612. expect(getDataAtCell(0, 0)).toEqual('');
  613. expect(hot.getActiveEditor().isOpened()).toBe(false);
  614. keyDown(Handsontable.helper.KEY_CODES.ENTER);
  615. expect(hot.getActiveEditor().isOpened()).toBe(true);
  616. expect(hot.getActiveEditor().getValue()).toEqual('');
  617. });
  618. it('should open empty editor after clearing cell value width DELETE', () => {
  619. const hot = handsontable({
  620. data: Handsontable.helper.createSpreadsheetData(4, 4)
  621. });
  622. expect(getDataAtCell(0, 0)).toEqual('A1');
  623. selectCell(0, 0);
  624. keyDown(Handsontable.helper.KEY_CODES.DELETE);
  625. expect(getDataAtCell(0, 0)).toEqual('');
  626. expect(hot.getActiveEditor().isOpened()).toBe(false);
  627. keyDown(Handsontable.helper.KEY_CODES.ENTER);
  628. expect(hot.getActiveEditor().isOpened()).toBe(true);
  629. expect(hot.getActiveEditor().getValue()).toEqual('');
  630. });
  631. it('should not open editor after hitting ALT (#1239)', () => {
  632. const hot = handsontable({
  633. data: Handsontable.helper.createSpreadsheetData(4, 4)
  634. });
  635. expect(getDataAtCell(0, 0)).toEqual('A1');
  636. selectCell(0, 0);
  637. keyDown(Handsontable.helper.KEY_CODES.ALT);
  638. expect(hot.getActiveEditor().isOpened()).toBe(false);
  639. });
  640. it('should open editor at the same coordinates as the edited cell', () => {
  641. const hot = handsontable({
  642. data: Handsontable.helper.createSpreadsheetData(16, 8),
  643. fixedColumnsLeft: 2,
  644. fixedRowsTop: 2
  645. });
  646. const mainHolder = hot.view.wt.wtTable.holder;
  647. // corner
  648. selectCell(1, 1);
  649. keyDown(Handsontable.helper.KEY_CODES.ENTER);
  650. const $inputHolder = $('.handsontableInputHolder');
  651. expect($(getCell(1, 1)).offset().left).toEqual($inputHolder.offset().left + 1);
  652. expect($(getCell(1, 1)).offset().top).toEqual($inputHolder.offset().top + 1);
  653. // top
  654. selectCell(1, 4);
  655. keyDown(Handsontable.helper.KEY_CODES.ENTER);
  656. expect($(getCell(1, 4)).offset().left).toEqual($inputHolder.offset().left + 1);
  657. expect($(getCell(1, 4)).offset().top).toEqual($inputHolder.offset().top + 1);
  658. // left
  659. selectCell(4, 1);
  660. keyDown(Handsontable.helper.KEY_CODES.ENTER);
  661. expect($(getCell(4, 1)).offset().left).toEqual($inputHolder.offset().left + 1);
  662. expect($(getCell(4, 1)).offset().top).toEqual($inputHolder.offset().top + 1);
  663. // non-fixed
  664. selectCell(4, 4);
  665. keyDown(Handsontable.helper.KEY_CODES.ENTER);
  666. expect($(getCell(4, 4)).offset().left).toEqual($inputHolder.offset().left + 1);
  667. expect($(getCell(4, 4)).offset().top).toEqual($inputHolder.offset().top + 1);
  668. $(mainHolder).scrollTop(1000);
  669. });
  670. it('should open editor at the same coordinates as the edited cell if preventOverflow is set as horizontal after the table had been scrolled', async() => {
  671. spec().$container[0].style = 'width: 400px';
  672. const hot = handsontable({
  673. data: Handsontable.helper.createSpreadsheetData(30, 30),
  674. preventOverflow: 'horizontal',
  675. fixedColumnsLeft: 2,
  676. fixedRowsTop: 2,
  677. rowHeaders: true,
  678. colHeaders: true,
  679. });
  680. const $holder = $(hot.view.wt.wtTable.holder);
  681. $holder.scrollTop(100);
  682. $holder.scrollLeft(100);
  683. hot.render();
  684. await sleep(50);
  685. // corner
  686. selectCell(1, 1);
  687. keyDownUp(Handsontable.helper.KEY_CODES.ENTER);
  688. const $inputHolder = $('.handsontableInputHolder');
  689. expect($(getCell(1, 1, true)).offset().left).toEqual($inputHolder.offset().left + 1);
  690. expect($(getCell(1, 1, true)).offset().top).toEqual($inputHolder.offset().top + 1);
  691. // top
  692. selectCell(1, 4);
  693. keyDownUp(Handsontable.helper.KEY_CODES.ENTER);
  694. expect($(getCell(1, 4, true)).offset().left).toEqual($inputHolder.offset().left + 1);
  695. expect($(getCell(1, 4, true)).offset().top).toEqual($inputHolder.offset().top + 1);
  696. // left
  697. selectCell(4, 1);
  698. keyDownUp(Handsontable.helper.KEY_CODES.ENTER);
  699. expect($(getCell(4, 1, true)).offset().left).toEqual($inputHolder.offset().left + 1);
  700. expect($(getCell(4, 1, true)).offset().top).toEqual($inputHolder.offset().top + 1);
  701. // non-fixed
  702. selectCell(4, 4);
  703. keyDownUp(Handsontable.helper.KEY_CODES.ENTER);
  704. expect($(getCell(4, 4, true)).offset().left).toEqual($inputHolder.offset().left + 1);
  705. expect($(getCell(4, 4, true)).offset().top).toEqual($inputHolder.offset().top + 1);
  706. });
  707. it('should open editor at the same coordinates as the edited cell after the table had been scrolled (corner)', () => {
  708. const hot = handsontable({
  709. data: Handsontable.helper.createSpreadsheetData(16, 8),
  710. fixedColumnsLeft: 2,
  711. fixedRowsTop: 2
  712. });
  713. const $holder = $(hot.view.wt.wtTable.holder);
  714. $holder.scrollTop(100);
  715. $holder.scrollLeft(100);
  716. hot.render();
  717. // corner
  718. selectCell(1, 1);
  719. const currentCell = hot.getCell(1, 1, true);
  720. const left = $(currentCell).offset().left;
  721. const top = $(currentCell).offset().top;
  722. const $inputHolder = $('.handsontableInputHolder');
  723. keyDown(Handsontable.helper.KEY_CODES.ENTER);
  724. expect(left).toEqual($inputHolder.offset().left + 1);
  725. expect(top).toEqual($inputHolder.offset().top + 1);
  726. });
  727. it('should open editor at the same coordinates as the edited cell after the table had been scrolled (top)', async() => {
  728. const hot = handsontable({
  729. data: Handsontable.helper.createSpreadsheetData(50, 50),
  730. fixedColumnsLeft: 2,
  731. fixedRowsTop: 2
  732. });
  733. const $holder = $(hot.view.wt.wtTable.holder);
  734. $holder[0].scrollTop = 500;
  735. await sleep(100);
  736. $holder[0].scrollLeft = 500;
  737. await sleep(100);
  738. // top
  739. selectCell(1, 6);
  740. await sleep(100);
  741. const currentCell = hot.getCell(1, 6, true);
  742. const left = $(currentCell).offset().left;
  743. const top = $(currentCell).offset().top;
  744. const $inputHolder = $('.handsontableInputHolder');
  745. keyDown(Handsontable.helper.KEY_CODES.ENTER);
  746. expect(left).toEqual($inputHolder.offset().left + 1);
  747. expect(top).toEqual($inputHolder.offset().top + 1);
  748. });
  749. it('should open editor at the same coordinates as the edited cell after the table had been scrolled (left)', async() => {
  750. const hot = handsontable({
  751. data: Handsontable.helper.createSpreadsheetData(50, 50),
  752. fixedColumnsLeft: 2,
  753. fixedRowsTop: 2
  754. });
  755. const $holder = $(hot.view.wt.wtTable.holder);
  756. $holder.scrollTop(500);
  757. $holder.scrollLeft(500);
  758. await sleep(100);
  759. selectCell(6, 1);
  760. await sleep(100);
  761. const currentCell = hot.getCell(6, 1, true);
  762. const left = $(currentCell).offset().left;
  763. const top = $(currentCell).offset().top;
  764. const $inputHolder = $('.handsontableInputHolder');
  765. keyDown(Handsontable.helper.KEY_CODES.ENTER);
  766. expect(left).toEqual($inputHolder.offset().left + 1);
  767. expect(top).toEqual($inputHolder.offset().top + 1);
  768. });
  769. it('should open editor at the same coordinates as the edited cell after the table had been scrolled (non-fixed)', () => {
  770. const hot = handsontable({
  771. data: Handsontable.helper.createSpreadsheetData(50, 50),
  772. fixedColumnsLeft: 2,
  773. fixedRowsTop: 2
  774. });
  775. const $holder = $(hot.view.wt.wtTable.holder);
  776. $holder.scrollTop(500);
  777. $holder.scrollLeft(500);
  778. hot.render();
  779. // non-fixed
  780. selectCell(7, 7);
  781. const currentCell = hot.getCell(7, 7, true);
  782. const left = $(currentCell).offset().left;
  783. const top = $(currentCell).offset().top;
  784. const $inputHolder = $('.handsontableInputHolder');
  785. keyDown(Handsontable.helper.KEY_CODES.ENTER);
  786. expect(left).toEqual($inputHolder.offset().left + 1);
  787. expect(top).toEqual($inputHolder.offset().top + 1);
  788. });
  789. it('should open editor at the same backgroundColor as the edited cell', async() => {
  790. handsontable({
  791. data: [
  792. ['', 5, 12, 13]
  793. ],
  794. renderer(instance, td, row, col, prop, value) {
  795. if (!value || value === '') {
  796. td.style.background = '#EEE';
  797. }
  798. }
  799. });
  800. mouseDoubleClick(getCell(0, 0));
  801. await sleep(100);
  802. expect($('.handsontableInput')[0].style.backgroundColor).toEqual('rgb(238, 238, 238)');
  803. mouseDoubleClick(getCell(0, 1));
  804. await sleep(100);
  805. expect($('.handsontableInput')[0].style.backgroundColor).toEqual('');
  806. mouseDoubleClick(getCell(0, 2));
  807. await sleep(100);
  808. expect($('.handsontableInput')[0].style.backgroundColor).toEqual('');
  809. });
  810. it('should display editor with the proper size, when the edited column is beyond the tables container', () => {
  811. spec().$container.css('overflow', '');
  812. const hot = handsontable({
  813. data: Handsontable.helper.createSpreadsheetData(3, 9)
  814. });
  815. selectCell(0, 7);
  816. keyDown(Handsontable.helper.KEY_CODES.ENTER);
  817. expect(Handsontable.dom.outerWidth(hot.getActiveEditor().TEXTAREA)).toBeAroundValue(Handsontable.dom.outerWidth(hot.getCell(0, 7)));
  818. });
  819. it('should display editor with the proper size, when editing a last row after the table is scrolled to the bottom', () => {
  820. const hot = handsontable({
  821. data: Handsontable.helper.createSpreadsheetData(3, 8),
  822. minSpareRows: 1,
  823. height: 100
  824. });
  825. selectCell(0, 2);
  826. keyDown(Handsontable.helper.KEY_CODES.ENTER);
  827. const regularHeight = Handsontable.dom.outerHeight(hot.getActiveEditor().TEXTAREA);
  828. selectCell(3, 2);
  829. keyDown(Handsontable.helper.KEY_CODES.ENTER);
  830. keyDown(Handsontable.helper.KEY_CODES.ENTER);
  831. keyDown(Handsontable.helper.KEY_CODES.ENTER);
  832. // lame check, needs investigating why sometimes it leaves 2px error
  833. if (Handsontable.dom.outerHeight(hot.getActiveEditor().TEXTAREA) === regularHeight) {
  834. expect(Handsontable.dom.outerHeight(hot.getActiveEditor().TEXTAREA)).toEqual(regularHeight);
  835. } else {
  836. expect(Handsontable.dom.outerHeight(hot.getActiveEditor().TEXTAREA)).toEqual(regularHeight - 2);
  837. }
  838. });
  839. it('should render the text without trimming out the whitespace, if trimWhitespace is set to false', () => {
  840. spec().$container.css('overflow', '');
  841. const hot = handsontable({
  842. data: Handsontable.helper.createSpreadsheetData(3, 9),
  843. trimWhitespace: false
  844. });
  845. selectCell(0, 2);
  846. keyDown(Handsontable.helper.KEY_CODES.ENTER);
  847. hot.getActiveEditor().TEXTAREA.value = ' test of whitespace ';
  848. keyDown(Handsontable.helper.KEY_CODES.ENTER);
  849. expect(getDataAtCell(0, 2).length).toEqual(37);
  850. });
  851. it('should insert new line on caret position when pressing ALT + ENTER', () => {
  852. const data = [
  853. ['Maserati', 'Mazda'],
  854. ['Honda', 'Mini']
  855. ];
  856. const hot = handsontable({
  857. data
  858. });
  859. selectCell(0, 0);
  860. keyDown(Handsontable.helper.KEY_CODES.ENTER);
  861. const $editorInput = $('.handsontableInput');
  862. Handsontable.dom.setCaretPosition($editorInput[0], 2);
  863. $editorInput.simulate('keydown', {
  864. altKey: true,
  865. keyCode: Handsontable.helper.KEY_CODES.ENTER
  866. });
  867. expect(hot.getActiveEditor().TEXTAREA.value).toEqual('Ma\nserati');
  868. });
  869. it('should be displayed and resized properly, so it doesn\'t exceed the viewport dimensions', () => {
  870. const data = [
  871. ['', '', '', '', ''],
  872. ['', 'The Dude abides. I don\'t know about you but I take comfort in that. It\'s good knowin\' he\'s out there. The ' +
  873. 'Dude. Takin\' \'er easy for all us sinners. Shoosh. I sure hope he makes the finals.', '', '', ''],
  874. ['', '', '', '', '']
  875. ];
  876. const hot = handsontable({
  877. data,
  878. colWidths: 40,
  879. width: 300,
  880. height: 200,
  881. minSpareRows: 20,
  882. minSpareCols: 20
  883. });
  884. selectCell(1, 1);
  885. keyDown(Handsontable.helper.KEY_CODES.ENTER);
  886. const $editorInput = $('.handsontableInput');
  887. const $editedCell = $(hot.getCell(1, 1));
  888. expect($editorInput.outerWidth()).toEqual(hot.view.wt.wtTable.holder.clientWidth - $editedCell.position().left + 1);
  889. hot.scrollViewportTo(void 0, 3);
  890. hot.render();
  891. expect($editorInput.width() + $editorInput.offset().left).toBeLessThan(hot.view.wt.wtTable.holder.clientWidth);
  892. });
  893. it('should resize editor to properly size after focus', (done) => {
  894. const data = [
  895. ['', '', '', '', '', '', '', '', '', '', ''],
  896. ['', '', '', '', '', '', '', '', '', '', ''],
  897. ['', '', '', '', '', '', '', '', '', '', ''],
  898. ['', '', '', '', '', '', '', '', '', '', ''],
  899. ['', '', '', '', '', '', '', '', '', '', 'sadiasdoadoajdoasjdoij doi ajdoiasjdasoidasoid'],
  900. ['', '', '', '', '', '', '', '', '', '', ''],
  901. ['', '', '', '', '', '', '', '', '', '', ''],
  902. ['', '', '', '', '', '', '', '', '', '', ''],
  903. ['', '', '', '', '', '', '', '', '', '', ''],
  904. ['', '', '', '', '', '', '', '', '', '', ''],
  905. ];
  906. handsontable({
  907. data,
  908. colWidths: 40,
  909. rowHeights: 25,
  910. width: 500,
  911. height: 220
  912. });
  913. selectCell(4, 10);
  914. keyDown(Handsontable.helper.KEY_CODES.ENTER);
  915. const $editorInput = $('.handsontableInput');
  916. setTimeout(() => {
  917. expect([105, 119]).toEqual(jasmine.arrayContaining([$editorInput.height()]));
  918. done();
  919. }, 150);
  920. });
  921. // Input element can not lose the focus while entering new characters. It breaks IME editor functionality.
  922. it('should not lose the focus on input element while inserting new characters (#839)', async() => {
  923. let blured = false;
  924. const listener = () => {
  925. blured = true;
  926. };
  927. const hot = handsontable({
  928. data: [['']],
  929. });
  930. selectCell(0, 0);
  931. keyDownUp('enter');
  932. hot.getActiveEditor().TEXTAREA.addEventListener('blur', listener);
  933. await sleep(200);
  934. hot.getActiveEditor().TEXTAREA.value = 'a';
  935. keyDownUp('a'.charCodeAt(0));
  936. hot.getActiveEditor().TEXTAREA.value = 'ab';
  937. keyDownUp('b'.charCodeAt(0));
  938. hot.getActiveEditor().TEXTAREA.value = 'abc';
  939. keyDownUp('c'.charCodeAt(0));
  940. expect(blured).toBeFalsy();
  941. hot.getActiveEditor().TEXTAREA.removeEventListener('blur', listener);
  942. });
  943. it('should not throw an exception when window.attachEvent is defined but the text area does not have attachEvent', (done) => {
  944. const hot = handsontable();
  945. window.attachEvent = true;
  946. selectCell(1, 1);
  947. expect(() => {
  948. hot.getActiveEditor().autoResize.init(hot.getActiveEditor().TEXTAREA);
  949. }).not.toThrow();
  950. done();
  951. });
  952. it('should keep editor open, focusable and with untouched value when allowInvalid is set as false', async() => {
  953. handsontable({
  954. data: Handsontable.helper.createSpreadsheetData(5, 5),
  955. allowInvalid: false,
  956. validator(val, cb) {
  957. cb(false);
  958. },
  959. });
  960. selectCell(0, 0);
  961. keyDown('enter');
  962. destroyEditor();
  963. document.activeElement.value = '999';
  964. await sleep(10);
  965. expect(document.activeElement).toBe(getActiveEditor().TEXTAREA);
  966. expect(isEditorVisible()).toBe(true);
  967. expect(getActiveEditor().TEXTAREA.value).toBe('999');
  968. keyDown('enter');
  969. expect(document.activeElement).toBe(getActiveEditor().TEXTAREA);
  970. expect(isEditorVisible()).toBe(true);
  971. expect(getActiveEditor().TEXTAREA.value).toBe('999');
  972. const cell = $(getCell(1, 1));
  973. mouseDown(cell);
  974. mouseUp(cell);
  975. mouseDown(cell);
  976. mouseUp(cell);
  977. await sleep(10);
  978. expect(document.activeElement).toBe(getActiveEditor().TEXTAREA);
  979. expect(isEditorVisible()).toBe(true);
  980. expect(getActiveEditor().TEXTAREA.value).toBe('999');
  981. });
  982. describe('IME support', () => {
  983. it('should focus editable element after selecting the cell', async() => {
  984. handsontable({
  985. type: 'text',
  986. });
  987. selectCell(0, 0, 0, 0, true, false);
  988. await sleep(10);
  989. expect(document.activeElement).toBe(getActiveEditor().TEXTAREA);
  990. });
  991. it('editor size should change after composition started', async() => {
  992. handsontable({
  993. data: Handsontable.helper.createSpreadsheetData(10, 5),
  994. width: 400,
  995. height: 400,
  996. });
  997. selectCell(2, 2);
  998. keyDownUp('enter');
  999. const textarea = getActiveEditor().TEXTAREA;
  1000. textarea.value = 'test, test, test, test, test, test';
  1001. textarea.dispatchEvent(new CompositionEvent('compositionstart')); // Trigger textarea resize
  1002. textarea.dispatchEvent(new CompositionEvent('compositionupdate')); // Trigger textarea resize
  1003. textarea.dispatchEvent(new CompositionEvent('compositionend')); // Trigger textarea resize
  1004. await sleep(100);
  1005. expect($(textarea).width()).toBe(201);
  1006. expect($(textarea).height()).toBe(23);
  1007. });
  1008. });
  1009. });