25f759fc483ee3467f5e1a7d101036c567017d7e8a6be05dcf03f07f6e28dd6ada3833186f977cf7796d49860602d369a9e5247733dc2f707f76e96b31c6f7 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633
  1. describe('Core_loadData', () => {
  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. var arrayOfArrays = function() {
  13. return [
  14. ['', 'Kia', 'Nissan', 'Toyota', 'Honda'],
  15. ['2008', 10, 11, 12, 13],
  16. ['2009', 20, 11, 14, 13],
  17. ['2010', 30, 15, 12, 13]
  18. ];
  19. };
  20. var arrayOfObjects = function() {
  21. return [
  22. {id: 1, name: 'Ted', lastName: 'Right'},
  23. {id: 2, name: 'Frank', lastName: 'Honest'},
  24. {id: 3, name: 'Joan', lastName: 'Well'},
  25. {id: 4, name: 'Sid', lastName: 'Strong'},
  26. {id: 5, name: 'Jane', lastName: 'Neat'},
  27. {id: 6, name: 'Chuck', lastName: 'Jackson'},
  28. {id: 7, name: 'Meg', lastName: 'Jansen'},
  29. {id: 8, name: 'Rob', lastName: 'Norris'},
  30. {id: 9, name: 'Sean', lastName: 'O\'Hara'},
  31. {id: 10, name: 'Eve', lastName: 'Branson'}
  32. ];
  33. };
  34. var arrayOfNestedObjects = function() {
  35. return [
  36. {
  37. id: 1,
  38. name: {
  39. first: 'Ted',
  40. last: 'Right'
  41. },
  42. 'full.street': 'Street I',
  43. },
  44. {
  45. id: 2,
  46. name: {
  47. first: 'Frank',
  48. last: 'Honest'
  49. },
  50. 'full.street': 'Street II',
  51. },
  52. {
  53. id: 3,
  54. name: {
  55. first: 'Joan',
  56. last: 'Well'
  57. },
  58. 'full.street': 'Street III',
  59. }
  60. ];
  61. };
  62. var htmlData = [
  63. ['<b>H&M</b>']
  64. ];
  65. it('should allow array of arrays', () => {
  66. handsontable();
  67. loadData(arrayOfArrays());
  68. expect(getDataAtCell(0, 2)).toEqual('Nissan');
  69. });
  70. it('should allow array of objects', () => {
  71. handsontable({
  72. columns: [
  73. {data: 'id'},
  74. {data: 'lastName'},
  75. {data: 'name'}
  76. ]
  77. });
  78. loadData(arrayOfObjects());
  79. expect(getDataAtCell(0, 2)).toEqual('Ted');
  80. });
  81. it('should allow array of objects when columns as a function', () => {
  82. handsontable({
  83. columns(column) {
  84. var colMeta = {};
  85. if (column === 0) {
  86. colMeta.data = 'id';
  87. } else if (column === 1) {
  88. colMeta.data = 'lastName';
  89. } else if (column === 2) {
  90. colMeta.data = 'name';
  91. } else {
  92. colMeta = null;
  93. }
  94. return colMeta;
  95. }
  96. });
  97. loadData(arrayOfObjects());
  98. expect(getDataAtCell(0, 2)).toEqual('Ted');
  99. });
  100. it('should allow array of nested objects', () => {
  101. handsontable({
  102. data: arrayOfNestedObjects(),
  103. colHeaders: true,
  104. columns: [
  105. {data: 'id'},
  106. {data: 'name.last'},
  107. {data: 'name.first'},
  108. {data: 'full.street'},
  109. ]
  110. });
  111. expect(getDataAtCell(0, 2)).toEqual('Ted');
  112. expect(getDataAtCell(1, 3)).toEqual('Street II');
  113. expect(getDataAtRowProp(2, 'full.street')).toEqual('Street III');
  114. });
  115. it('should allow array of nested objects when columns as a function', () => {
  116. handsontable({
  117. data: arrayOfNestedObjects(),
  118. colHeaders: true,
  119. columns(column) {
  120. var colMeta = {};
  121. if (column === 0) {
  122. colMeta.data = 'id';
  123. } else if (column === 1) {
  124. colMeta.data = 'name.last';
  125. } else if (column === 2) {
  126. colMeta.data = 'name.first';
  127. } else if (column === 3) {
  128. colMeta.data = 'full.street';
  129. } else {
  130. colMeta = null;
  131. }
  132. return colMeta;
  133. }
  134. });
  135. expect(getDataAtCell(0, 2)).toEqual('Ted');
  136. expect(getDataAtCell(1, 3)).toEqual('Street II');
  137. expect(getDataAtRowProp(2, 'full.street')).toEqual('Street III');
  138. });
  139. it('should figure out default column names for array of nested objects', () => {
  140. handsontable({
  141. data: arrayOfNestedObjects(),
  142. colHeaders: true
  143. });
  144. expect(getDataAtCell(0, 2)).toEqual('Right');
  145. });
  146. it('should trigger onChange callback when loaded array of arrays', () => {
  147. var called = false;
  148. handsontable({
  149. afterChange(changes, source) {
  150. if (source === 'loadData') {
  151. called = true;
  152. }
  153. }
  154. });
  155. loadData(arrayOfArrays());
  156. expect(called).toEqual(true);
  157. });
  158. it('should trigger onChange callback when loaded array of objects', () => {
  159. var called = false;
  160. handsontable({
  161. afterChange(changes, source) {
  162. if (source === 'loadData') {
  163. called = true;
  164. }
  165. }
  166. });
  167. loadData(arrayOfObjects());
  168. expect(called).toEqual(true);
  169. });
  170. it('should trigger onChange callback when loaded array of nested objects', () => {
  171. var called = false;
  172. handsontable({
  173. afterChange(changes, source) {
  174. if (source === 'loadData') {
  175. called = true;
  176. }
  177. }
  178. });
  179. loadData(arrayOfNestedObjects());
  180. expect(called).toEqual(true);
  181. });
  182. it('should create new rows for array of arrays (and respect minRows)', () => {
  183. handsontable({
  184. minRows: 20, // minRows should be respected
  185. data: arrayOfArrays()
  186. });
  187. expect(countRows()).toEqual(20); // TODO why this must be checked after render?
  188. });
  189. it('should create new rows for array of nested objects (and respect minRows)', () => {
  190. handsontable({
  191. minRows: 20, // minRows should be respected
  192. data: arrayOfNestedObjects()
  193. });
  194. expect(countRows()).toEqual(20); // TODO why this must be checked after render?
  195. });
  196. it('HTML special chars should be escaped by default', () => {
  197. handsontable();
  198. loadData(htmlData);
  199. expect(getCell(0, 0).innerHTML).toEqual('&lt;b&gt;H&amp;M&lt;/b&gt;');
  200. });
  201. it('should create as many rows as needed by array of objects', () => {
  202. handsontable({
  203. minRows: 6,
  204. data: arrayOfObjects()
  205. });
  206. expect(getCell(9, 1).innerHTML).toEqual('Eve');
  207. });
  208. // https://github.com/handsontable/handsontable/pull/233
  209. it('should not invoke the cells callback multiple times with the same row/col (without overlays)', () => {
  210. var cellsSpy = jasmine.createSpy('cellsSpy');
  211. handsontable({
  212. data: arrayOfNestedObjects(),
  213. colWidths: [90, 90, 90, 90],
  214. rowHeights: [23, 23, 23, 23],
  215. cells: cellsSpy
  216. });
  217. //
  218. expect(cellsSpy.calls.count()).toEqual(43);
  219. });
  220. it('should not invoke the cells callback multiple times with the same row/col (with overlays)', () => {
  221. var cellsSpy = jasmine.createSpy('cellsSpy');
  222. handsontable({
  223. data: arrayOfNestedObjects(),
  224. colHeaders: true,
  225. rowHeaders: true,
  226. colWidths: [90, 90, 90, 90],
  227. rowHeights: [90, 90, 90, 90],
  228. cells: cellsSpy
  229. });
  230. expect(cellsSpy.calls.count()).toEqual(56);
  231. });
  232. it('should remove grid rows if new data source has less of them', () => {
  233. var data1 = [
  234. ['a'],
  235. ['b'],
  236. ['c'],
  237. ['d'],
  238. ['e'],
  239. ['f'],
  240. ['g'],
  241. ['h']
  242. ];
  243. var data2 = [
  244. ['a'],
  245. ['b'],
  246. ['c'],
  247. ['d'],
  248. ['e']
  249. ];
  250. handsontable({
  251. data: data1,
  252. rowHeaders: true,
  253. colHeaders: true
  254. });
  255. selectCell(7, 0);
  256. loadData(data2);
  257. expect(countRows()).toBe(data2.length);
  258. expect(getSelected()).toEqual([4, 0, 4, 0]);
  259. });
  260. it('should remove grid rows if new data source has less of them (with minSpareRows)', () => {
  261. var data1 = [
  262. ['a'],
  263. ['b'],
  264. ['c'],
  265. ['d'],
  266. ['e'],
  267. ['f'],
  268. ['g'],
  269. ['h']
  270. ];
  271. var data2 = [
  272. ['a'],
  273. ['b'],
  274. ['c'],
  275. ['d'],
  276. ['e']
  277. ];
  278. handsontable({
  279. data: data1,
  280. minSpareCols: 1,
  281. minSpareRows: 1,
  282. rowHeaders: true,
  283. colHeaders: true
  284. });
  285. selectCell(8, 0);
  286. loadData(data2);
  287. expect(countRows()).toBe(6); // +1 because of minSpareRows
  288. expect(getSelected()).toEqual([5, 0, 5, 0]);
  289. });
  290. it('loading empty data should remove all rows', () => {
  291. var data1 = [
  292. ['a'],
  293. ['b'],
  294. ['c'],
  295. ['d'],
  296. ['e'],
  297. ['f'],
  298. ['g'],
  299. ['h']
  300. ];
  301. var data2 = [];
  302. handsontable({
  303. data: data1,
  304. rowHeaders: true,
  305. colHeaders: true
  306. });
  307. selectCell(7, 0);
  308. loadData(data2);
  309. expect(countRows()).toBe(0);
  310. expect(getSelected()).toBe(void 0);
  311. });
  312. it('should only have as many columns as in settings', () => {
  313. var data1 = arrayOfArrays();
  314. handsontable({
  315. data: data1,
  316. columns: [
  317. { data: 1 },
  318. { data: 3 }
  319. ]
  320. });
  321. expect(countCols()).toBe(2);
  322. });
  323. it('should only have as many columns as in settings when columns is a function', () => {
  324. var data1 = arrayOfArrays();
  325. handsontable({
  326. data: data1,
  327. columns(column) {
  328. var colMeta = {
  329. data: column
  330. };
  331. if ([1, 3].indexOf(column) < 0) {
  332. colMeta = null;
  333. }
  334. return colMeta;
  335. }
  336. });
  337. expect(countCols()).toBe(2);
  338. });
  339. it('should throw error when trying to load a string (constructor)', () => {
  340. var errors = 0;
  341. try {
  342. handsontable({
  343. data: 'string'
  344. });
  345. } catch (e) {
  346. errors++;
  347. }
  348. expect(errors).toBe(1);
  349. });
  350. it('should throw error when trying to load a string (loadData)', () => {
  351. var errors = 0;
  352. try {
  353. handsontable();
  354. loadData('string');
  355. } catch (e) {
  356. errors++;
  357. }
  358. expect(errors).toBe(1);
  359. });
  360. it('should load Backbone Collection as data source', () => {
  361. // code borrowed from demo/backbone.js
  362. var CarModel = Backbone.Model.extend({});
  363. var CarCollection = Backbone.Collection.extend({
  364. model: CarModel,
  365. // Backbone.Collection doesn't support `splice`, yet! Easy to add.
  366. splice: hackedSplice
  367. });
  368. var cars = new CarCollection();
  369. cars.add([
  370. {make: 'Dodge', model: 'Ram', year: 2012, weight: 6811},
  371. {make: 'Toyota', model: 'Camry', year: 2012, weight: 3190},
  372. {make: 'Smart', model: 'Fortwo', year: 2012, weight: 1808}
  373. ]);
  374. handsontable({
  375. data: cars,
  376. columns: [
  377. attr('make'),
  378. attr('model'),
  379. attr('year')
  380. ]
  381. });
  382. // use the "good" Collection methods to emulate Array.splice
  383. function hackedSplice(index, howMany /* model1, ... modelN */) {
  384. var args = _.toArray(arguments).slice(2).concat({at: index}),
  385. removed = this.models.slice(index, index + howMany);
  386. this.remove(removed).add.apply(this, args);
  387. return removed;
  388. }
  389. // normally, you'd get these from the server with .fetch()
  390. function attr(attr) {
  391. // this lets us remember `attr` for when when it is get/set
  392. return {
  393. data(car, value) {
  394. if (_.isUndefined(value)) {
  395. return car.get(attr);
  396. }
  397. car.set(attr, value);
  398. }
  399. };
  400. }
  401. expect(countRows()).toBe(3);
  402. });
  403. it('should load Backbone Collection as data source when columns is a function', () => {
  404. // code borrowed from demo/backbone.js
  405. var CarModel = Backbone.Model.extend({});
  406. var CarCollection = Backbone.Collection.extend({
  407. model: CarModel,
  408. // Backbone.Collection doesn't support `splice`, yet! Easy to add.
  409. splice: hackedSplice
  410. });
  411. var cars = new CarCollection();
  412. cars.add([
  413. {make: 'Dodge', model: 'Ram', year: 2012, weight: 6811},
  414. {make: 'Toyota', model: 'Camry', year: 2012, weight: 3190},
  415. {make: 'Smart', model: 'Fortwo', year: 2012, weight: 1808}
  416. ]);
  417. handsontable({
  418. data: cars,
  419. columns(column) {
  420. var colMeta = null;
  421. if (column === 0) {
  422. colMeta = attr('make');
  423. } else if (column === 1) {
  424. colMeta = attr('model');
  425. } else if (column === 2) {
  426. colMeta = attr('year');
  427. }
  428. return colMeta;
  429. }
  430. });
  431. // use the "good" Collection methods to emulate Array.splice
  432. function hackedSplice(index, howMany /* model1, ... modelN */) {
  433. var args = _.toArray(arguments).slice(2).concat({at: index}),
  434. removed = this.models.slice(index, index + howMany);
  435. this.remove(removed).add.apply(this, args);
  436. return removed;
  437. }
  438. // normally, you'd get these from the server with .fetch()
  439. function attr(attr) {
  440. // this lets us remember `attr` for when when it is get/set
  441. return {
  442. data(car, value) {
  443. if (_.isUndefined(value)) {
  444. return car.get(attr);
  445. }
  446. car.set(attr, value);
  447. }
  448. };
  449. }
  450. expect(countRows()).toBe(3);
  451. });
  452. it('should clear cell properties after loadData', () => {
  453. handsontable();
  454. loadData(arrayOfArrays());
  455. getCellMeta(0, 0).foo = 'bar';
  456. expect(getCellMeta(0, 0).foo).toEqual('bar');
  457. loadData(arrayOfArrays());
  458. expect(getCellMeta(0, 0).foo).toBeUndefined();
  459. });
  460. it('should clear cell properties after loadData, but before rendering new data', function() {
  461. handsontable();
  462. loadData(arrayOfArrays());
  463. getCellMeta(0, 0).valid = false;
  464. render();
  465. expect(this.$container.find('tbody tr:eq(0) td:eq(0)').hasClass('htInvalid')).toEqual(true);
  466. loadData(arrayOfArrays());
  467. expect(this.$container.find('tbody tr:eq(0) td:eq(0)').hasClass('htInvalid')).toEqual(false);
  468. });
  469. // https://github.com/handsontable/handsontable/issues/1700
  470. // can't edit anything after starting editing cell with no nested object
  471. it('should correct behave with cell with no nested object data source corresponding to column mapping', () => {
  472. var objectData = [
  473. {id: 1, user: {name: {first: 'Ted', last: 'Right'}}},
  474. {id: 2, user: {name: {}}},
  475. {id: 3}
  476. ];
  477. handsontable({
  478. data: objectData,
  479. columns: [
  480. {data: 'id'},
  481. {data: 'user.name.first'},
  482. {data: 'user.name.last'}
  483. ]
  484. });
  485. mouseDoubleClick(getCell(1, 1));
  486. document.activeElement.value = 'Harry';
  487. deselectCell();
  488. expect(objectData[1].user.name.first).toEqual('Harry');
  489. mouseDoubleClick(getCell(2, 1));
  490. document.activeElement.value = 'Barry';
  491. deselectCell();
  492. expect(objectData[2].user.name.first).toEqual('Barry');
  493. });
  494. it('should correct behave with cell with no nested object data source corresponding to column mapping when columns is a function', () => {
  495. var objectData = [
  496. {id: 1, user: {name: {first: 'Ted', last: 'Right'}}},
  497. {id: 2, user: {name: {}}},
  498. {id: 3}
  499. ];
  500. handsontable({
  501. data: objectData,
  502. columns(column) {
  503. var colMeta = null;
  504. if (column === 0) {
  505. colMeta = {data: 'id'};
  506. } else if (column === 1) {
  507. colMeta = {data: 'user.name.first'};
  508. } else if (column === 2) {
  509. colMeta = {data: 'user.name.last'};
  510. }
  511. return colMeta;
  512. }
  513. });
  514. mouseDoubleClick(getCell(1, 1));
  515. document.activeElement.value = 'Harry';
  516. deselectCell();
  517. expect(objectData[1].user.name.first).toEqual('Harry');
  518. mouseDoubleClick(getCell(2, 1));
  519. document.activeElement.value = 'Barry';
  520. deselectCell();
  521. expect(objectData[2].user.name.first).toEqual('Barry');
  522. });
  523. });