numericEditor.spec.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714
  1. describe('NumericEditor', () => {
  2. const 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. const arrayOfObjects = function() {
  13. return [
  14. { id: 1, name: 'Ted', lastName: 'Right' },
  15. { id: 2, name: 'Frank', lastName: 'Honest' },
  16. { id: 3, name: 'Joan', lastName: 'Well' },
  17. { id: 4, name: 'Sid', lastName: 'Strong' },
  18. { id: 5, name: 'Jane', lastName: 'Neat' },
  19. { id: 6, name: 'Chuck', lastName: 'Jackson' },
  20. { id: 7, name: 'Meg', lastName: 'Jansen' },
  21. { id: 8, name: 'Rob', lastName: 'Norris' },
  22. { id: 9, name: 'Sean', lastName: 'O\'Hara' },
  23. { id: 10, name: 'Eve', lastName: 'Branson' }
  24. ];
  25. };
  26. it('should convert "integer like" input value to number (object data source)', async() => {
  27. handsontable({
  28. data: arrayOfObjects(),
  29. columns: [
  30. { data: 'id', type: 'numeric' },
  31. { data: 'name' },
  32. { data: 'lastName' }
  33. ]
  34. });
  35. selectCell(2, 0);
  36. keyDown('enter');
  37. document.activeElement.value = '999';
  38. destroyEditor();
  39. await sleep(100);
  40. expect(typeof getDataAtCell(2, 0)).toEqual('number');
  41. expect(getDataAtCell(2, 0)).toEqual(999);
  42. });
  43. it('should not convert formatted "float like" input value to number (object data source) #4706', async() => {
  44. handsontable({
  45. data: arrayOfObjects(),
  46. columns: [
  47. { data: 'id' },
  48. { data: 'price_eur', type: 'numeric' },
  49. { data: 'price_pln', type: 'numeric', numericFormat: { pattern: '$0,0.00', culture: 'en-US' } },
  50. { data: 'price_aud', type: 'numeric', numericFormat: { pattern: '$0,0.00', culture: 'de-DE' } }
  51. ]
  52. });
  53. selectCell(0, 1);
  54. keyDown('enter');
  55. document.activeElement.value = '100.000,0';
  56. destroyEditor();
  57. await sleep(100);
  58. selectCell(1, 1);
  59. keyDown('enter');
  60. document.activeElement.value = '200,000.5';
  61. destroyEditor();
  62. await sleep(100);
  63. selectCell(0, 2);
  64. keyDown('enter');
  65. document.activeElement.value = '300,000.5';
  66. destroyEditor();
  67. await sleep(100);
  68. selectCell(1, 2);
  69. keyDown('enter');
  70. document.activeElement.value = '300.000,5';
  71. destroyEditor();
  72. await sleep(100);
  73. selectCell(0, 3);
  74. keyDown('enter');
  75. document.activeElement.value = '400.000,5';
  76. destroyEditor();
  77. await sleep(100);
  78. selectCell(1, 3);
  79. keyDown('enter');
  80. document.activeElement.value = '400,000.5';
  81. destroyEditor();
  82. await sleep(100);
  83. expect(getDataAtCell(0, 1)).toEqual('100.000,0');
  84. expect(getDataAtCell(1, 1)).toEqual('200,000.5');
  85. expect(getDataAtCell(0, 2)).toEqual('300,000.5');
  86. expect(getDataAtCell(1, 2)).toEqual('300.000,5');
  87. expect(getDataAtCell(0, 3)).toEqual('400.000,5');
  88. expect(getDataAtCell(1, 3)).toEqual('400,000.5');
  89. });
  90. it('should convert "float like" input value with dot as determiner to number (object data source)', async() => {
  91. handsontable({
  92. data: arrayOfObjects(),
  93. columns: [
  94. { data: 'id', type: 'numeric' },
  95. { data: 'price' },
  96. { data: 'lastName' }
  97. ]
  98. });
  99. selectCell(2, 0);
  100. keyDown('enter');
  101. document.activeElement.value = '77.70';
  102. destroyEditor();
  103. await sleep(100);
  104. expect(typeof getDataAtCell(2, 0)).toEqual('number');
  105. expect(getDataAtCell(2, 0)).toEqual(77.7);
  106. });
  107. it('should convert "float like" input value with comma as determiner to number (object data source)', async() => {
  108. handsontable({
  109. data: arrayOfObjects(),
  110. columns: [
  111. { data: 'id', type: 'numeric' },
  112. { data: 'name' },
  113. { data: 'lastName' }
  114. ]
  115. });
  116. selectCell(2, 0);
  117. keyDown('enter');
  118. document.activeElement.value = '77,70';
  119. destroyEditor();
  120. await sleep(100);
  121. expect(typeof getDataAtCell(2, 0)).toEqual('number');
  122. expect(getDataAtCell(2, 0)).toEqual(77.7);
  123. });
  124. it('should convert "float like" input without leading zero to a float', async() => {
  125. handsontable({
  126. data: arrayOfObjects(),
  127. columns: [
  128. { data: 'id', type: 'numeric' },
  129. { data: 'name' },
  130. { data: 'lastName' }
  131. ]
  132. });
  133. selectCell(2, 0);
  134. keyDown('enter');
  135. document.activeElement.value = '.74';
  136. destroyEditor();
  137. await sleep(100);
  138. expect(getDataAtCell(2, 0)).toEqual(0.74);
  139. });
  140. it('should apply changes to editor after validation', async() => {
  141. handsontable({
  142. data: arrayOfObjects(),
  143. columns: [
  144. { data: 'id', type: 'numeric' },
  145. ]
  146. });
  147. selectCell(0, 0);
  148. keyDown('delete');
  149. await sleep(100);
  150. expect(getActiveEditor().originalValue).toEqual('');
  151. });
  152. it('should not validate string input data containing numbers ', async() => {
  153. handsontable({
  154. data: arrayOfObjects(),
  155. columns: [
  156. { data: 'id', type: 'numeric' },
  157. { data: 'price', type: 'numeric', numericFormat: { pattern: '$0,0.00', culture: 'de-DE' } },
  158. { data: 'lastName' }
  159. ]
  160. });
  161. // Column with default formatting
  162. selectCell(0, 0);
  163. keyDown('enter');
  164. document.activeElement.value = '12aaa34';
  165. destroyEditor();
  166. await sleep(100);
  167. selectCell(1, 0);
  168. keyDown('enter');
  169. document.activeElement.value = 'aaa34';
  170. destroyEditor();
  171. await sleep(100);
  172. selectCell(2, 0);
  173. keyDown('enter');
  174. document.activeElement.value = '12aaa';
  175. destroyEditor();
  176. // Column with specified formatting
  177. await sleep(100);
  178. selectCell(0, 1);
  179. keyDown('enter');
  180. document.activeElement.value = '12aaa34';
  181. destroyEditor();
  182. await sleep(100);
  183. selectCell(1, 1);
  184. keyDown('enter');
  185. document.activeElement.value = 'aaa34';
  186. destroyEditor();
  187. await sleep(100);
  188. selectCell(2, 1);
  189. keyDown('enter');
  190. document.activeElement.value = '12aaa';
  191. destroyEditor();
  192. await sleep(100);
  193. expect($(getCell(0, 0)).hasClass('htInvalid')).toBe(true);
  194. expect(getDataAtCell(0, 0)).toEqual('12aaa34');
  195. expect($(getCell(1, 0)).hasClass('htInvalid')).toBe(true);
  196. expect(getDataAtCell(1, 0)).toEqual('aaa34');
  197. expect($(getCell(2, 0)).hasClass('htInvalid')).toBe(true);
  198. expect(getDataAtCell(2, 0)).toEqual('12aaa');
  199. expect($(getCell(0, 1)).hasClass('htInvalid')).toBe(true);
  200. expect(getDataAtCell(0, 1)).toEqual('12aaa34');
  201. expect($(getCell(1, 1)).hasClass('htInvalid')).toBe(true);
  202. expect(getDataAtCell(1, 1)).toEqual('aaa34');
  203. expect($(getCell(2, 1)).hasClass('htInvalid')).toBe(true);
  204. expect(getDataAtCell(2, 1)).toEqual('12aaa');
  205. });
  206. it('should display a string in a format \'$X,XXX.XX\' when using language=en, appropriate format in column settings and \'XXXX.XX\' as ' +
  207. 'an input string', async() => {
  208. handsontable({
  209. data: arrayOfObjects(),
  210. columns: [
  211. { data: 'id', type: 'numeric', numericFormat: { pattern: '$0,0.00', culture: 'en-US' } },
  212. { data: 'name' },
  213. { data: 'lastName' }
  214. ]
  215. });
  216. selectCell(2, 0);
  217. keyDown('enter');
  218. document.activeElement.value = '2456.22';
  219. destroyEditor();
  220. await sleep(100);
  221. expect(getCell(2, 0).innerHTML).toEqual('$2,456.22');
  222. });
  223. it('should display a string in a format \'X.XXX,XX €\' when using language=de, appropriate format in column settings and \'XXXX,XX\' as an ' +
  224. 'input string (that comes from manual input)', async() => {
  225. handsontable({
  226. data: arrayOfObjects(),
  227. columns: [
  228. { data: 'id', type: 'numeric', numericFormat: { pattern: '0,0.00 $', culture: 'de-DE' } },
  229. { data: 'name' },
  230. { data: 'lastName' }
  231. ]
  232. });
  233. selectCell(2, 0);
  234. keyDown('enter');
  235. document.activeElement.value = '2456,22';
  236. destroyEditor();
  237. await sleep(100);
  238. expect(getCell(2, 0).innerHTML).toEqual('2.456,22 €');
  239. });
  240. it('should display a string in a format \'X.XXX,XX €\' when using language=de, appropriate format in column settings and \'XXXX.XX\' as an ' +
  241. 'input string (that comes from paste)', async() => {
  242. const onAfterValidate = jasmine.createSpy('onAfterValidate');
  243. handsontable({
  244. data: arrayOfObjects(),
  245. columns: [
  246. { data: 'id', type: 'numeric', numericFormat: { pattern: '0,0.00 $', culture: 'de-DE' } },
  247. { data: 'name' },
  248. { data: 'lastName' }
  249. ],
  250. afterValidate: onAfterValidate
  251. });
  252. selectCell(2, 0);
  253. keyDown('enter');
  254. document.activeElement.value = '2456.22';
  255. destroyEditor();
  256. await sleep(100);
  257. expect(getCell(2, 0).innerHTML).toEqual('2.456,22 €');
  258. });
  259. it('should display a string in a format \'X XXX,XX €\' when using language=de, appropriate format in column settings and \'XXXX,XX\' as an ' +
  260. 'input string and ignore not needed zeros at the end', async() => {
  261. handsontable({
  262. data: [
  263. { id: 1, name: 'Ted', lastName: 'Right', money: 0 },
  264. { id: 2, name: 'Frank', lastName: 'Honest', money: 0 },
  265. { id: 3, name: 'Joan', lastName: 'Well', money: 0 },
  266. { id: 4, name: 'Sid', lastName: 'Strong', money: 0 },
  267. { id: 5, name: 'Jane', lastName: 'Neat', money: 0 },
  268. { id: 6, name: 'Chuck', lastName: 'Jackson', money: 0 },
  269. { id: 7, name: 'Meg', lastName: 'Jansen', money: 0 },
  270. { id: 8, name: 'Rob', lastName: 'Norris', money: 0 },
  271. { id: 9, name: 'Sean', lastName: 'O\'Hara', money: 0 },
  272. { id: 10, name: 'Eve', lastName: 'Branson', money: 0 }
  273. ],
  274. columns: [
  275. { data: 'id', type: 'numeric', numericFormat: { pattern: '0,0.00 $', culture: 'de-DE' } },
  276. { data: 'name' },
  277. { data: 'lastName' },
  278. { data: 'money', type: 'numeric', numericFormat: { pattern: '$0,0.00', culture: 'en-US' } }
  279. ]
  280. });
  281. selectCell(2, 0);
  282. keyDown('enter');
  283. document.activeElement.value = '2456,220';
  284. destroyEditor();
  285. await sleep(100);
  286. expect(getCell(2, 0).innerHTML).toEqual('2.456,22 €');
  287. selectCell(2, 3);
  288. keyDown('enter');
  289. document.activeElement.value = '2456.220';
  290. destroyEditor();
  291. await sleep(100);
  292. expect(getCell(2, 3).innerHTML).toEqual('$2,456.22');
  293. });
  294. it('should display values as "float like" string with dot as determiner after pressing enter ' +
  295. 'and not change value after closing editor', async() => {
  296. handsontable({
  297. data: [
  298. { id: 1, price_eur: 222.5, price_pln: 1222.6, price_aud: 1333.5 }
  299. ],
  300. columns: [
  301. { data: 'id', type: 'numeric' },
  302. { data: 'price_eur', type: 'numeric' },
  303. { data: 'price_pln', type: 'numeric', numericFormat: { pattern: '$0,0.00', culture: 'en-US' } },
  304. { data: 'price_aud', type: 'numeric', numericFormat: { pattern: '$0,0.00', culture: 'de-DE' } }
  305. ]
  306. });
  307. selectCell(0, 1);
  308. keyDown('enter');
  309. await sleep(100);
  310. expect(document.activeElement.value).toEqual('222.5');
  311. // closing editor
  312. keyDown('enter');
  313. await sleep(100);
  314. expect(getDataAtCell(0, 1)).toEqual(222.5);
  315. selectCell(0, 2);
  316. keyDown('enter');
  317. await sleep(100);
  318. expect(document.activeElement.value).toEqual('1222.6');
  319. // closing editor
  320. keyDown('enter');
  321. await sleep(100);
  322. expect(getDataAtCell(0, 2)).toEqual(1222.6);
  323. selectCell(0, 3);
  324. keyDown('enter');
  325. await sleep(100);
  326. expect(document.activeElement.value).toEqual('1333.5');
  327. // closing editor
  328. keyDown('enter');
  329. await sleep(100);
  330. expect(getDataAtCell(0, 3)).toEqual(1333.5);
  331. });
  332. it('should display values as "float like" string with dot as determiner after double click ' +
  333. 'and not change value after closing editor', async() => {
  334. handsontable({
  335. data: [
  336. { id: 1, price_eur: 222.5, price_pln: 1222.6, price_aud: 1333.5 }
  337. ],
  338. columns: [
  339. { data: 'id', type: 'numeric' },
  340. { data: 'price_eur', type: 'numeric' },
  341. { data: 'price_pln', type: 'numeric', numericFormat: { pattern: '$0,0.00', culture: 'en-US' } },
  342. { data: 'price_aud', type: 'numeric', numericFormat: { pattern: '$0,0.00', culture: 'de-DE' } }
  343. ]
  344. });
  345. mouseDoubleClick(getCell(0, 1));
  346. await sleep(100);
  347. expect(document.activeElement.value).toEqual('222.5');
  348. // closing editor
  349. keyDown('enter');
  350. await sleep(100);
  351. expect(getDataAtCell(0, 1)).toEqual(222.5);
  352. mouseDoubleClick(getCell(0, 2));
  353. await sleep(100);
  354. expect(document.activeElement.value).toEqual('1222.6');
  355. // closing editor
  356. keyDown('enter');
  357. await sleep(100);
  358. expect(getDataAtCell(0, 2)).toEqual(1222.6);
  359. mouseDoubleClick(getCell(0, 3));
  360. await sleep(100);
  361. expect(document.activeElement.value).toEqual('1333.5');
  362. // closing editor
  363. keyDown('enter');
  364. await sleep(100);
  365. expect(getDataAtCell(0, 3)).toEqual(1333.5);
  366. });
  367. it('should mark text as invalid without removing when using `setDataAtCell`', async() => {
  368. const hot = handsontable({
  369. data: arrayOfObjects(),
  370. columns: [
  371. { data: 'id', type: 'numeric' },
  372. { data: 'name' },
  373. { data: 'lastName' }
  374. ],
  375. });
  376. hot.setDataAtCell(0, 0, 'abc');
  377. await sleep(200);
  378. expect(hot.getDataAtCell(0, 0)).toEqual('abc');
  379. expect($(getCell(0, 0)).hasClass('htInvalid')).toBe(true);
  380. });
  381. it('should allow custom validator', async() => {
  382. handsontable({
  383. data: arrayOfObjects(),
  384. allowInvalid: false,
  385. columns: [
  386. {
  387. data: 'id',
  388. type: 'numeric',
  389. validator(val, cb) {
  390. cb(parseInt(val, 10) > 100);
  391. }
  392. },
  393. { data: 'name' },
  394. { data: 'lastName' }
  395. ]
  396. });
  397. selectCell(2, 0);
  398. keyDown('enter');
  399. document.activeElement.value = '99';
  400. destroyEditor();
  401. await sleep(100);
  402. expect(getDataAtCell(2, 0)).not.toEqual(99); // should be ignored
  403. document.activeElement.value = '999';
  404. destroyEditor();
  405. await sleep(100);
  406. expect(getDataAtCell(2, 0)).toEqual(999);
  407. });
  408. // Input element can not lose the focus while entering new characters. It breaks IME editor functionality for Asian users.
  409. it('should not lose the focus on input element while inserting new characters (#839)', async() => {
  410. let blured = false;
  411. const listener = () => {
  412. blured = true;
  413. };
  414. const hot = handsontable({
  415. data: arrayOfObjects(),
  416. columns: [
  417. { data: 'id', type: 'numeric', numericFormat: { pattern: '0,0.00', culture: 'en-US' } },
  418. { data: 'name' },
  419. { data: 'lastName' }
  420. ],
  421. });
  422. selectCell(0, 0);
  423. keyDownUp('enter');
  424. hot.getActiveEditor().TEXTAREA.addEventListener('blur', listener);
  425. await sleep(200);
  426. hot.getActiveEditor().TEXTAREA.value = '1';
  427. keyDownUp('1'.charCodeAt(0));
  428. hot.getActiveEditor().TEXTAREA.value = '12';
  429. keyDownUp('2'.charCodeAt(0));
  430. hot.getActiveEditor().TEXTAREA.value = '123';
  431. keyDownUp('3'.charCodeAt(0));
  432. expect(blured).toBeFalsy();
  433. hot.getActiveEditor().TEXTAREA.removeEventListener('blur', listener);
  434. });
  435. it('should not throw error on closing editor when column data is defined as \'length\'', () => {
  436. hot = handsontable({
  437. data: [
  438. { length: 4 },
  439. { length: 5 },
  440. ],
  441. columns: [
  442. {
  443. data: 'length', type: 'numeric'
  444. },
  445. {},
  446. {}
  447. ]
  448. });
  449. selectCell(1, 0);
  450. keyDown('enter');
  451. document.activeElement.value = '999';
  452. expect(() => {
  453. destroyEditor();
  454. }).not.toThrow();
  455. });
  456. describe('Cell corner is showed properly when changing focused cells #3877', () => {
  457. const isFocusedCellDisplayingCornerTest = function(settings) {
  458. const moveFromRow = settings.moveFromRow;
  459. const moveFromCol = settings.moveFromCol;
  460. const moveToRow = settings.moveToRow;
  461. const moveToCol = settings.moveToCol;
  462. const doneFunc = settings.doneFunc;
  463. const $corner = settings.$container.find('.wtBorder.current.corner');
  464. selectCell(moveFromRow, moveFromCol);
  465. keyDown('enter');
  466. selectCell(moveToRow, moveToCol);
  467. setTimeout(() => {
  468. expect($corner.css('display')).toEqual('block');
  469. doneFunc();
  470. }, 100);
  471. };
  472. it('Moving from numeric editor to text editor', (done) => {
  473. handsontable({
  474. data: [
  475. { id: 1, name: 'Ted', lastName: 'Right', money: 0 }
  476. ],
  477. columns: [
  478. { data: 'id' },
  479. { data: 'name' },
  480. { data: 'lastName' },
  481. { data: 'money', type: 'numeric', numericFormat: { pattern: '$0,0.00', culture: 'en-US' } }
  482. ]
  483. });
  484. isFocusedCellDisplayingCornerTest({
  485. moveFromRow: 0,
  486. moveFromCol: 3,
  487. moveToRow: 0,
  488. moveToCol: 0,
  489. $container: spec().$container,
  490. doneFunc: done
  491. });
  492. });
  493. it('Moving from text editor to numeric editor', (done) => {
  494. handsontable({
  495. data: [
  496. { id: 1, name: 'Ted', lastName: 'Right', money: 0 }
  497. ],
  498. columns: [
  499. { data: 'id' },
  500. { data: 'name' },
  501. { data: 'lastName' },
  502. { data: 'money', type: 'numeric', numericFormat: { pattern: '$0,0.00', culture: 'en-US' } }
  503. ]
  504. });
  505. isFocusedCellDisplayingCornerTest({
  506. moveFromRow: 0,
  507. moveFromCol: 1,
  508. moveToRow: 0,
  509. moveToCol: 3,
  510. $container: spec().$container,
  511. doneFunc: done
  512. });
  513. });
  514. });
  515. describe('IME support', () => {
  516. it('should focus editable element after selecting the cell', async() => {
  517. handsontable({
  518. type: 'numeric', numericFormat: { pattern: '$0,0.00', culture: 'en-US' }
  519. });
  520. selectCell(0, 0, 0, 0, true, false);
  521. await sleep(10);
  522. expect(document.activeElement).toBe(getActiveEditor().TEXTAREA);
  523. });
  524. });
  525. });