describe('Core_validate', () => { const id = 'testContainer'; beforeEach(function() { this.$container = $(`
`).appendTo('body'); }); afterEach(function() { if (this.$container) { destroy(); this.$container.remove(); } }); const arrayOfObjects = function() { return [ { id: 1, name: 'Ted', lastName: 'Right' }, { id: 2, name: 'Frank', lastName: 'Honest' }, { id: 3, name: 'Joan', lastName: 'Well' }, { id: 4, name: 'Sid', lastName: 'Strong' }, { id: 5, name: 'Jane', lastName: 'Neat' }, { id: 6, name: 'Chuck', lastName: 'Jackson' }, { id: 7, name: 'Meg', lastName: 'Jansen' }, { id: 8, name: 'Rob', lastName: 'Norris' }, { id: 9, name: 'Sean', lastName: 'O\'Hara' }, { id: 10, name: 'Eve', lastName: 'Branson' } ]; }; it('should call beforeValidate', () => { let fired = null; handsontable({ data: arrayOfObjects(), columns: [ { data: 'id', type: 'numeric' }, { data: 'name' }, { data: 'lastName' } ], beforeValidate() { fired = true; } }); setDataAtCell(2, 0, 'test'); expect(fired).toEqual(true); }); it('should call beforeValidate when columns is a function', () => { let fired = null; handsontable({ data: arrayOfObjects(), columns(column) { let colMeta = {}; if (column === 0) { colMeta.data = 'id'; colMeta.type = 'numeric'; } else if (column === 1) { colMeta.data = 'name'; } else if (column === 2) { colMeta.data = 'lastName'; } else { colMeta = null; } return colMeta; }, beforeValidate() { fired = true; } }); setDataAtCell(2, 0, 'test'); expect(fired).toBe(true); }); it('should call afterValidate', (done) => { const onAfterValidate = jasmine.createSpy('onAfterValidate'); handsontable({ data: arrayOfObjects(), columns: [ { data: 'id', type: 'numeric' }, { data: 'name' }, { data: 'lastName' } ], afterValidate: onAfterValidate }); setDataAtCell(2, 0, 'test'); setTimeout(() => { expect(onAfterValidate.calls.count()).toBe(1); done(); }, 200); }); it('should call afterValidate when columns is a function', (done) => { const onAfterValidate = jasmine.createSpy('onAfterValidate'); handsontable({ data: arrayOfObjects(), columns(column) { let colMeta = {}; if (column === 0) { colMeta.data = 'id'; colMeta.type = 'numeric'; } else if (column === 1) { colMeta.data = 'name'; } else if (column === 2) { colMeta.data = 'lastName'; } else { colMeta = null; } return colMeta; }, afterValidate: onAfterValidate }); setDataAtCell(2, 0, 'test'); setTimeout(() => { expect(onAfterValidate.calls.count()).toBe(1); done(); }, 200); }); it('beforeValidate can manipulate value', (done) => { const onAfterValidate = jasmine.createSpy('onAfterValidate'); let result = null; onAfterValidate.and.callFake((valid, value) => { result = value; }); handsontable({ data: arrayOfObjects(), columns: [ { data: 'id', type: 'numeric' }, { data: 'name' }, { data: 'lastName' } ], beforeValidate() { return 999; }, afterValidate: onAfterValidate }); setDataAtCell(2, 0, 123); setTimeout(() => { expect(result).toBe(999); done(); }, 200); }); it('beforeValidate can manipulate value when columns is a function', (done) => { const onAfterValidate = jasmine.createSpy('onAfterValidate'); let result = null; onAfterValidate.and.callFake((valid, value) => { result = value; }); handsontable({ data: arrayOfObjects(), columns(column) { let colMeta = {}; if (column === 0) { colMeta.data = 'id'; colMeta.type = 'numeric'; } else if (column === 1) { colMeta.data = 'name'; } else if (column === 2) { colMeta.data = 'lastName'; } else { colMeta = null; } return colMeta; }, beforeValidate() { return 999; }, afterValidate: onAfterValidate }); setDataAtCell(2, 0, 123); setTimeout(() => { expect(result).toBe(999); done(); }, 200); }); it('should be able to define custom validator function', (done) => { const onAfterValidate = jasmine.createSpy('onAfterValidate'); handsontable({ data: arrayOfObjects(), columns: [ { data: 'id', validator(value, cb) { cb(true); } }, { data: 'name' }, { data: 'lastName' } ], afterValidate: onAfterValidate }); setDataAtCell(2, 0, 123); setTimeout(() => { expect(onAfterValidate).toHaveBeenCalledWith(true, 123, 2, 'id', undefined, undefined); done(); }, 200); }); it('should be able to define custom validator function when columns is a function', (done) => { const onAfterValidate = jasmine.createSpy('onAfterValidate'); handsontable({ data: arrayOfObjects(), columns(column) { let colMeta = null; if (column === 0) { colMeta = { data: 'id', validator(value, cb) { cb(true); } }; } else if (column === 1) { colMeta = { data: 'name' }; } else if (column === 2) { colMeta = { data: 'lastName' }; } return colMeta; }, afterValidate: onAfterValidate }); setDataAtCell(2, 0, 123); setTimeout(() => { expect(onAfterValidate).toHaveBeenCalledWith(true, 123, 2, 'id', undefined, undefined); done(); }, 200); }); it('should be able to define custom validator RegExp', (done) => { const onAfterValidate = jasmine.createSpy('onAfterValidate'); handsontable({ data: arrayOfObjects(), columns: [ { data: 'id', validator: /^\d+$/ }, { data: 'name' }, { data: 'lastName' } ], afterValidate: onAfterValidate }); setDataAtCell(2, 0, 'test'); setTimeout(() => { expect(onAfterValidate).toHaveBeenCalledWith(false, 'test', 2, 'id', undefined, undefined); done(); }, 200); }); it('should be able to define custom validator RegExp when columns is a function', (done) => { const onAfterValidate = jasmine.createSpy('onAfterValidate'); handsontable({ data: arrayOfObjects(), columns(column) { let colMeta = null; if (column === 0) { colMeta = { data: 'id', validator: /^\d+$/ }; } else if (column === 1) { colMeta = { data: 'name' }; } else if (column === 2) { colMeta = { data: 'lastName' }; } return colMeta; }, afterValidate: onAfterValidate }); setDataAtCell(2, 0, 'test'); setTimeout(() => { expect(onAfterValidate).toHaveBeenCalledWith(false, 'test', 2, 'id', undefined, undefined); done(); }, 200); }); it('this in validator should point to cellProperties', (done) => { const onAfterValidate = jasmine.createSpy('onAfterValidate'); let result = null; handsontable({ data: arrayOfObjects(), columns: [ { data: 'id', validator(value, cb) { result = this; cb(true); } }, { data: 'name' }, { data: 'lastName' } ], afterValidate: onAfterValidate }); setDataAtCell(2, 0, 123); setTimeout(() => { expect(result.instance).toEqual(getInstance()); done(); }, 200); }); it('this in validator should point to cellProperties when columns is a function', (done) => { const onAfterValidate = jasmine.createSpy('onAfterValidate'); let result = null; handsontable({ data: arrayOfObjects(), columns(column) { let colMeta = null; if (column === 0) { colMeta = { data: 'id', validator(value, cb) { result = this; cb(true); } }; } else if (column === 1) { colMeta = { data: 'name' }; } else if (column === 2) { colMeta = { data: 'lastName' }; } return colMeta; }, afterValidate: onAfterValidate }); setDataAtCell(2, 0, 123); setTimeout(() => { expect(result.instance).toEqual(getInstance()); done(); }, 200); }); it('should not throw error after calling validateCells without first argument', (done) => { const onAfterValidate = jasmine.createSpy('onAfterValidate'); const hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(2, 2), validator(value, callb) { if (value === 'B1') { callb(false); } else { callb(true); } }, afterValidate: onAfterValidate }); expect(() => hot.validateCells()).not.toThrow(); setTimeout(() => { expect(spec().$container.find('td.htInvalid').length).toEqual(1); expect(spec().$container.find('td:not(.htInvalid)').length).toEqual(3); done(); }, 200); }); it('should throw error after calling validateRows first argument not array', (done) => { const onAfterValidate = jasmine.createSpy('onAfterValidate'); const hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(2, 2), validator(value, callb) { callb(true); }, afterValidate: onAfterValidate }); expect(() => hot.validateRows()).toThrow(); expect(() => hot.validateRows(0, () => {})).toThrow(); expect(() => hot.validateRows({}, () => {})).toThrow(); expect(() => hot.validateRows(() => {})).toThrow(); done(); }); it('should throw error after calling validateColumns first argument not array', (done) => { const onAfterValidate = jasmine.createSpy('onAfterValidate'); const hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(2, 2), validator(value, callb) { callb(true); }, afterValidate: onAfterValidate }); expect(() => hot.validateColumns()).toThrow(); expect(() => hot.validateColumns(0, () => {})).toThrow(); expect(() => hot.validateColumns({}, () => {})).toThrow(); expect(() => hot.validateColumns(() => {})).toThrow(); done(); }); it('should not throw error after calling validateRows without second argument', (done) => { const onAfterValidate = jasmine.createSpy('onAfterValidate'); const hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(2, 2), validator(value, callb) { callb(true); }, afterValidate: onAfterValidate }); expect(() => hot.validateRows([])).not.toThrow(); expect(() => hot.validateRows([0, 1])).not.toThrow(); expect(() => hot.validateRows([100, 101])).not.toThrow(); done(); }); it('should not throw error after calling validateColumns without second argument', (done) => { const onAfterValidate = jasmine.createSpy('onAfterValidate'); const hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(2, 2), validator(value, callb) { callb(true); }, afterValidate: onAfterValidate }); expect(() => hot.validateColumns([])).not.toThrow(); expect(() => hot.validateColumns([0, 1])).not.toThrow(); expect(() => hot.validateColumns([100, 101])).not.toThrow(); done(); }); it('should add class name `htInvalid` to an cell that does not validate - on validateCells', (done) => { const onAfterValidate = jasmine.createSpy('onAfterValidate'); const hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(2, 2), validator(value, callb) { if (value === 'B1') { callb(false); } else { callb(true); } }, afterValidate: onAfterValidate }); hot.validateCells(() => { hot.render(); }); setTimeout(() => { expect(spec().$container.find('td.htInvalid').length).toEqual(1); expect(spec().$container.find('td:not(.htInvalid)').length).toEqual(3); done(); }, 200); }); it('should add class name `htInvalid` to an cell that does not validate - on validateRows', (done) => { const onAfterValidate = jasmine.createSpy('onAfterValidate'); const hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(2, 2), validator(value, callb) { if (value === 'B1') { callb(false); } else { callb(true); } }, afterValidate: onAfterValidate }); hot.validateRows([], () => { hot.render(); }); setTimeout(() => { expect(spec().$container.find('td.htInvalid').length).toEqual(0); expect(spec().$container.find('td:not(.htInvalid)').length).toEqual(4); hot.updateSettings({ data: Handsontable.helper.createSpreadsheetData(2, 2) }); hot.validateRows([0], () => { hot.render(); }); }, 100); setTimeout(() => { expect(spec().$container.find('td.htInvalid').length).toEqual(1); expect(spec().$container.find('td:not(.htInvalid)').length).toEqual(3); hot.updateSettings({ data: Handsontable.helper.createSpreadsheetData(2, 2) }); hot.validateRows([1], () => { hot.render(); }); }, 200); setTimeout(() => { expect(spec().$container.find('td.htInvalid').length).toEqual(0); expect(spec().$container.find('td:not(.htInvalid)').length).toEqual(4); hot.updateSettings({ data: Handsontable.helper.createSpreadsheetData(2, 2) }); hot.validateRows([0, 1], () => { hot.render(); }); }, 300); setTimeout(() => { expect(spec().$container.find('td.htInvalid').length).toEqual(1); expect(spec().$container.find('td:not(.htInvalid)').length).toEqual(3); hot.updateSettings({ data: Handsontable.helper.createSpreadsheetData(2, 2) }); hot.validateRows([0, 1, 100], () => { hot.render(); }); }, 400); setTimeout(() => { expect(spec().$container.find('td.htInvalid').length).toEqual(1); expect(spec().$container.find('td:not(.htInvalid)').length).toEqual(3); hot.updateSettings({ data: Handsontable.helper.createSpreadsheetData(2, 2) }); hot.validateRows([100, 101], () => { hot.render(); }); }, 500); setTimeout(() => { expect(spec().$container.find('td.htInvalid').length).toEqual(0); expect(spec().$container.find('td:not(.htInvalid)').length).toEqual(4); done(); }, 600); }); it('should add class name `htInvalid` to an cell that does not validate - on validateColumns', async() => { const onAfterValidate = jasmine.createSpy('onAfterValidate'); const hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(2, 2), validator(value, callb) { if (value === 'B1') { callb(false); } else { callb(true); } }, afterValidate: onAfterValidate }); await promisfy(resolve => hot.validateColumns([], resolve)); expect(spec().$container.find('td.htInvalid').length).toEqual(0); expect(spec().$container.find('td:not(.htInvalid)').length).toEqual(4); hot.updateSettings({ data: Handsontable.helper.createSpreadsheetData(2, 2) }); await promisfy(resolve => hot.validateColumns([0], resolve)); expect(spec().$container.find('td.htInvalid').length).toEqual(0); expect(spec().$container.find('td:not(.htInvalid)').length).toEqual(4); hot.updateSettings({ data: Handsontable.helper.createSpreadsheetData(2, 2) }); await promisfy(resolve => hot.validateColumns([1], resolve)); expect(spec().$container.find('td.htInvalid').length).toEqual(1); expect(spec().$container.find('td:not(.htInvalid)').length).toEqual(3); hot.updateSettings({ data: Handsontable.helper.createSpreadsheetData(2, 2) }); await promisfy(resolve => hot.validateColumns([0, 1], resolve)); expect(spec().$container.find('td.htInvalid').length).toEqual(1); expect(spec().$container.find('td:not(.htInvalid)').length).toEqual(3); hot.updateSettings({ data: Handsontable.helper.createSpreadsheetData(2, 2) }); await promisfy(resolve => hot.validateColumns([0, 1, 100], resolve)); expect(spec().$container.find('td.htInvalid').length).toEqual(1); expect(spec().$container.find('td:not(.htInvalid)').length).toEqual(3); hot.updateSettings({ data: Handsontable.helper.createSpreadsheetData(2, 2) }); await promisfy(resolve => hot.validateColumns([100, 101], resolve)); expect(spec().$container.find('td.htInvalid').length).toEqual(0); expect(spec().$container.find('td:not(.htInvalid)').length).toEqual(4); }); it('should add class name `htInvalid` to an cell that does not validate - when we trigger validateCell', async() => { const onAfterValidate = jasmine.createSpy('onAfterValidate'); const hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(2, 2), validator(value, cb) { cb(false); }, afterValidate: onAfterValidate }); expect(spec().$container.find('td:not(.htInvalid)').length).toEqual(4); hot.validateCell(hot.getDataAtCell(1, 1), hot.getCellMeta(1, 1), () => {}); await sleep(200); expect(spec().$container.find('td.htInvalid').length).toEqual(1); expect(spec().$container.find('td:not(.htInvalid)').length).toEqual(3); }); it('should remove class name `htInvalid` from an cell that does validate - when we change validator rules', (done) => { const onAfterValidate = jasmine.createSpy('onAfterValidate'); let isValid = false; const validator = function() { return isValid; }; const hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(2, 2), validator(value, cb) { cb(validator()); }, afterValidate: onAfterValidate }); hot.validateCells(() => {}); setTimeout(() => { expect(spec().$container.find('td.htInvalid').length).toEqual(4); expect(spec().$container.find('td:not(.htInvalid)').length).toEqual(0); isValid = true; onAfterValidate.calls.reset(); hot.validateCell(hot.getDataAtCell(1, 1), hot.getCellMeta(1, 1), () => {}); }, 200); setTimeout(() => { expect(spec().$container.find('td.htInvalid').length).toEqual(3); expect(spec().$container.find('td:not(.htInvalid)').length).toEqual(1); done(); }, 400); }); it('should add class name `htInvalid` to an cell that does not validate - on edit', (done) => { const onAfterValidate = jasmine.createSpy('onAfterValidate'); handsontable({ data: Handsontable.helper.createSpreadsheetData(2, 2), validator(value, callb) { if (value === 'test') { callb(false); } else { callb(true); } }, afterValidate: onAfterValidate }); setDataAtCell(0, 0, 'test'); setTimeout(() => { expect(spec().$container.find('td.htInvalid').length).toEqual(1); expect(spec().$container.find('tr:eq(0) td:eq(0)').hasClass('htInvalid')).toEqual(true); done(); }, 200); }); it('should add class name `htInvalid` to a cell without removing other classes', (done) => { const onAfterValidate = jasmine.createSpy('onAfterValidate'); const validator = jasmine.createSpy('validator'); validator.and.callFake((value, callb) => { if (value === 123) { callb(false); } else { callb(true); } }); handsontable({ data: Handsontable.helper.createSpreadsheetData(2, 2), type: 'numeric', validator, afterValidate: onAfterValidate }); setDataAtCell(0, 0, 123); setTimeout(() => { expect(validator.calls.count()).toEqual(1); expect(spec().$container.find('tr:eq(0) td:eq(0)').hasClass('htInvalid')).toEqual(true); expect(spec().$container.find('tr:eq(0) td:eq(0)').hasClass('htNumeric')).toEqual(true); onAfterValidate.calls.reset(); setDataAtCell(0, 0, 124); }, 200); setTimeout(() => { expect(spec().$container.find('tr:eq(0) td:eq(0)').hasClass('htInvalid')).toEqual(false); expect(spec().$container.find('tr:eq(0) td:eq(0)').hasClass('htNumeric')).toEqual(true); done(); }, 400); }); it('should add class name `htInvalid` to an cell that does not validate - after validateCells', (done) => { const onAfterValidate = jasmine.createSpy('onAfterValidate'); const hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(2, 2), afterValidate: onAfterValidate }); setDataAtCell(0, 0, 'test'); setTimeout(() => { expect(spec().$container.find('td.htInvalid').length).toEqual(0); updateSettings({ validator(value, callb) { if (value === 'test') { callb(false); } else { callb(true); } } }); onAfterValidate.calls.reset(); hot.validateCells(() => {}); }, 200); setTimeout(() => { expect(spec().$container.find('td.htInvalid').length).toEqual(1); expect(spec().$container.find('tr:eq(0) td:eq(0)').hasClass('htInvalid')).toEqual(true); done(); }, 400); }); it('should add class name `htInvalid` to an cell that does not validate - after validateRows', (done) => { const onAfterValidate = jasmine.createSpy('onAfterValidate'); const hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(2, 2), afterValidate: onAfterValidate }); setDataAtCell(0, 0, 'test'); setTimeout(() => { expect(spec().$container.find('td.htInvalid').length).toEqual(0); updateSettings({ validator(value, callb) { if (value === 'test') { callb(false); } else { callb(true); } } }); onAfterValidate.calls.reset(); hot.validateRows([0], () => {}); }, 200); setTimeout(() => { expect(spec().$container.find('td.htInvalid').length).toEqual(1); expect(spec().$container.find('tr:eq(0) td:eq(0)').hasClass('htInvalid')).toEqual(true); done(); }, 400); }); it('should add class name `htInvalid` to an cell that does not validate - after validateColumns', (done) => { const onAfterValidate = jasmine.createSpy('onAfterValidate'); const hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(2, 2), afterValidate: onAfterValidate }); setDataAtCell(0, 0, 'test'); setTimeout(() => { expect(spec().$container.find('td.htInvalid').length).toEqual(0); updateSettings({ validator(value, callb) { if (value === 'test') { callb(false); } else { callb(true); } } }); onAfterValidate.calls.reset(); hot.validateColumns([0], () => {}); }, 200); setTimeout(() => { expect(spec().$container.find('td.htInvalid').length).toEqual(1); expect(spec().$container.find('tr:eq(0) td:eq(0)').hasClass('htInvalid')).toEqual(true); done(); }, 400); }); it('should remove class name `htInvalid` when cell is edited to validate', (done) => { const onAfterValidate = jasmine.createSpy('onAfterValidate'); const hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(2, 2), validator(value, callb) { if (value === 'A1') { callb(false); } else { callb(true); } }, afterValidate: onAfterValidate }); hot.validateCells(() => { hot.render(); }); setTimeout(() => { expect(spec().$container.find('tr:eq(0) td:eq(0)').hasClass('htInvalid')).toEqual(true); onAfterValidate.calls.reset(); setDataAtCell(0, 0, 'test'); }, 200); setTimeout(() => { expect(spec().$container.find('tr:eq(0) td:eq(0)').hasClass('htInvalid')).toEqual(false); done(); }, 400); }); it('should call callback with first argument as `true` if all cells are valid', (done) => { const onValidate = jasmine.createSpy('onValidate'); const onAfterValidate = jasmine.createSpy('onAfterValidate'); const hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(2, 2), validator(value, callback) { callback(true); }, afterValidate: onAfterValidate }); hot.validateCells(onValidate); setTimeout(() => { expect(onValidate).toHaveBeenCalledWith(true); done(); }, 200); }); it('should call callback with first argument as `true` if all cells are valid - on validateRows', (done) => { const onValidate = jasmine.createSpy('onValidate'); const onAfterValidate = jasmine.createSpy('onAfterValidate'); const hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(2, 2), validator(value, callback) { callback(true); }, afterValidate: onAfterValidate }); hot.validateRows([0, 1], onValidate); setTimeout(() => { expect(onValidate).toHaveBeenCalledWith(true); done(); }, 200); }); it('should call callback with first argument as `true` if all cells are valid - on validateColumns', (done) => { const onValidate = jasmine.createSpy('onValidate'); const onAfterValidate = jasmine.createSpy('onAfterValidate'); const hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(2, 2), validator(value, callback) { callback(true); }, afterValidate: onAfterValidate }); hot.validateColumns([0, 1], onValidate); setTimeout(() => { expect(onValidate).toHaveBeenCalledWith(true); done(); }, 200); }); it('should call callback with first argument as `false` if one of cells is invalid', (done) => { const onValidate = jasmine.createSpy('onValidate'); const onAfterValidate = jasmine.createSpy('onAfterValidate'); const hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(2, 2), validator(value, callback) { callback(false); }, afterValidate: onAfterValidate }); hot.validateCells(onValidate); setTimeout(() => { expect(onValidate).toHaveBeenCalledWith(false); done(); }, 200); }); it('should call callback with first argument as `false` if one of cells is invalid - on validateRows', (done) => { const onValidate = jasmine.createSpy('onValidate'); const onAfterValidate = jasmine.createSpy('onAfterValidate'); const hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(2, 2), validator(value, callback) { callback(false); }, afterValidate: onAfterValidate }); hot.validateRows([0, 1], onValidate); setTimeout(() => { expect(onValidate).toHaveBeenCalledWith(false); done(); }, 200); }); it('should call callback with first argument as `false` if one of cells is invalid - on validateColumns', (done) => { const onValidate = jasmine.createSpy('onValidate'); const onAfterValidate = jasmine.createSpy('onAfterValidate'); const hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(2, 2), validator(value, callback) { callback(false); }, afterValidate: onAfterValidate }); hot.validateColumns([0, 1], onValidate); setTimeout(() => { expect(onValidate).toHaveBeenCalledWith(false); done(); }, 200); }); it('should not allow for changes where data is invalid (multiple changes, async)', (done) => { let validatedChanges; handsontable({ data: Handsontable.helper.createSpreadsheetData(5, 2), allowInvalid: false, validator(value, callb) { setTimeout(() => { if (value === 'fail') { callb(false); } else { callb(true); } }, 10); }, afterChange(changes, source) { if (source !== 'loadData') { validatedChanges = changes; } } }); populateFromArray(0, 0, [ ['A1-new'], ['fail'], ['A3-new'] ]); setTimeout(() => { expect(validatedChanges.length).toEqual(2); expect(validatedChanges[0]).toEqual([0, 0, 'A1', 'A1-new']); expect(validatedChanges[1]).toEqual([2, 0, 'A3', 'A3-new']); expect(getDataAtCell(0, 0)).toEqual('A1-new'); expect(getDataAtCell(1, 0)).toEqual('A2'); expect(getDataAtCell(2, 0)).toEqual('A3-new'); expect(getCellMeta(0, 0).valid).toBe(true); expect(getCellMeta(1, 0).valid).toBe(true); expect(getCellMeta(2, 0).valid).toBe(true); done(); }, 200); }); it('should call beforeChange exactly once after cell value edit and validator is synchronous', (done) => { const onAfterValidate = jasmine.createSpy('onAfterValidate'); const onBeforeChange = jasmine.createSpy('onBeforeChange'); const hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(5, 2), allowInvalid: false, validator(value, callback) { callback(true); }, beforeChange: onBeforeChange, afterValidate: onAfterValidate }); expect(onBeforeChange.calls.count()).toEqual(0); hot.setDataAtCell(0, 0, 10); setTimeout(() => { expect(onBeforeChange.calls.count()).toEqual(1); done(); }, 200); }); it('should call beforeChange exactly once after cell value edit and validator is asynchronous', (done) => { const onAfterValidate = jasmine.createSpy('onAfterValidate'); const onBeforeChange = jasmine.createSpy('onBeforeChange'); const hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(5, 2), allowInvalid: false, validator(value, callback) { setTimeout(() => { callback(true); }, 10); }, beforeChange: onBeforeChange, afterValidate: onAfterValidate }); expect(onBeforeChange.calls.count()).toEqual(0); hot.setDataAtCell(0, 0, 10); setTimeout(() => { expect(onBeforeChange.calls.count()).toEqual(1); done(); }, 200); }); it('should call afterChange exactly once after cell value edit and validator is synchronous', (done) => { const onAfterValidate = jasmine.createSpy('onAfterValidate'); const onAfterChange = jasmine.createSpy('onAfterChange'); const hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(5, 2), allowInvalid: false, validator(value, callback) { callback(true); }, afterChange: onAfterChange, afterValidate: onAfterValidate }); expect(onAfterChange.calls.count()).toEqual(1); // loadData hot.setDataAtCell(0, 0, 10); setTimeout(() => { expect(onAfterChange.calls.count()).toEqual(2); done(); }, 200); }); it('should call afterChange exactly once after cell value edit and validator is asynchronous', (done) => { const onAfterValidate = jasmine.createSpy('onAfterValidate'); const onAfterChange = jasmine.createSpy('onAfterChange'); const hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(5, 2), allowInvalid: false, validator(value, callback) { setTimeout(() => { callback(true); }, 10); }, afterChange: onAfterChange, afterValidate: onAfterValidate }); expect(onAfterChange.calls.count()).toEqual(1); // loadData hot.setDataAtCell(0, 0, 10); setTimeout(() => { expect(onAfterChange.calls.count()).toEqual(2); done(); }, 200); }); it('edited cell should stay on screen until value is validated', (done) => { const onAfterValidate = jasmine.createSpy('onAfterValidate'); const onAfterChange = jasmine.createSpy('onAfterChange'); let isEditorVisibleBeforeChange; let isEditorVisibleAfterChange; onAfterValidate.and.callFake(() => { isEditorVisibleBeforeChange = isEditorVisible(); }); onAfterChange.and.callFake(() => { isEditorVisibleAfterChange = isEditorVisible(); }); handsontable({ data: Handsontable.helper.createSpreadsheetData(5, 2), allowInvalid: false, afterValidate: onAfterValidate, afterChange: onAfterChange, validator(value, callback) { setTimeout(() => { callback(true); }, 100); } }); selectCell(0, 0); keyDown('enter'); document.activeElement.value = 'Ted'; onAfterValidate.calls.reset(); onAfterChange.calls.reset(); keyDown('enter'); expect(document.activeElement.nodeName).toEqual('TEXTAREA'); setTimeout(() => { expect(isEditorVisibleBeforeChange).toBe(true); expect(isEditorVisibleAfterChange).toBe(true); expect(isEditorVisible()).toBe(false); done(); }, 200); }); it('should validate edited cell after selecting another cell', async() => { let validatedValue; handsontable({ data: Handsontable.helper.createSpreadsheetData(5, 2), allowInvalid: false, validator(value, callback) { setTimeout(() => { validatedValue = value; callback(true); }, 50); } }); selectCell(0, 0); keyDown('enter'); document.activeElement.value = 'Ted'; selectCell(0, 1); await sleep(150); expect(validatedValue).toEqual('Ted'); }); it('should leave the new value in editor if it does not validate (async validation), after hitting ENTER', (done) => { let validationResult; handsontable({ data: Handsontable.helper.createSpreadsheetData(5, 2), allowInvalid: false, validator(value, callback) { setTimeout(() => { validationResult = value.length === 2; callback(validationResult); }, 100); } }); selectCell(0, 0); keyDown('enter'); document.activeElement.value = 'Ted'; keyDown('enter'); setTimeout(() => { expect(validationResult).toBe(false); expect(document.activeElement.value).toEqual('Ted'); done(); }, 200); }); it('should leave the new value in editor if it does not validate (sync validation), after hitting ENTER', (done) => { let validationResult; handsontable({ data: Handsontable.helper.createSpreadsheetData(5, 2), allowInvalid: false, validator(value, callback) { validationResult = value.length === 2; callback(validationResult); } }); selectCell(0, 0); keyDown('enter'); document.activeElement.value = 'Ted'; keyDown('enter'); setTimeout(() => { expect(validationResult).toBe(false); expect(document.activeElement.value).toEqual('Ted'); done(); }, 200); }); it('should leave the new value in editor if it does not validate (async validation), after selecting another cell', (done) => { let validationResult; handsontable({ data: Handsontable.helper.createSpreadsheetData(5, 2), allowInvalid: false, validator(value, callback) { setTimeout(() => { validationResult = value.length === 2; callback(validationResult); }, 100); } }); selectCell(0, 0); keyDown('enter'); document.activeElement.value = 'Ted'; selectCell(1, 0); setTimeout(() => { expect(validationResult).toBe(false); expect(document.activeElement.value).toEqual('Ted'); done(); }, 200); }); it('should leave the new value in editor if it does not validate (sync validation), after selecting another cell', (done) => { let validationResult; handsontable({ data: Handsontable.helper.createSpreadsheetData(5, 2), allowInvalid: false, validator(value, callback) { validationResult = value.length === 2; callback(validationResult); } }); selectCell(0, 0); keyDown('enter'); document.activeElement.value = 'Ted'; selectCell(1, 0); setTimeout(() => { expect(validationResult).toBe(false); expect(document.activeElement.value).toEqual('Ted'); done(); }, 200); }); it('should remove htInvalid class properly after cancelling change, when physical indexes are not equal to visual indexes', (done) => { handsontable({ data: Handsontable.helper.createSpreadsheetData(5, 2), columnSorting: { column: 0, sortOrder: 'desc' }, allowInvalid: false, validator(value, callback) { setTimeout(() => { callback(value.length === 2); }, 100); } }); selectCell(0, 0); keyDown('enter'); document.activeElement.value = 'Ted'; keyDown('enter'); setTimeout(() => { const $cell = $(getCell(0, 0)); expect($cell.hasClass('htInvalid')).toEqual(false); done(); }, 200); }); it('should not attempt to remove the htInvalid class if the validated cell is no longer rendered', (done) => { handsontable({ data: Handsontable.helper.createSpreadsheetData(20, 2), columnSorting: { column: 0, sortOrder: 'desc' }, allowInvalid: false, validator(value, callback) { setTimeout(() => { callback(value.length === 2); }, 100); }, height: 40 }); selectCell(0, 0); keyDown('enter'); document.activeElement.value = 'Ted'; selectCell(19, 0); setTimeout(() => { const $cell = $(getCell(0, 0)); expect($cell.hasClass('htInvalid')).toEqual(false); done(); }, 200); }); it('should close the editor and save the new value if validation fails and allowInvalid is set to "true"', (done) => { let validationResult; handsontable({ data: Handsontable.helper.createSpreadsheetData(5, 2), allowInvalid: true, validator(value, callback) { setTimeout(() => { validationResult = value.length === 2; callback(validationResult); }, 100); } }); selectCell(0, 0); keyDown('enter'); document.activeElement.value = 'Ted'; selectCell(1, 0); setTimeout(() => { expect(validationResult).toBe(false); expect(getDataAtCell(0, 0)).toEqual('Ted'); expect(getCell(0, 0).className).toMatch(/htInvalid/); done(); }, 200); }); it('should close the editor and save the new value after double clicking on a cell, if the previously edited cell validated correctly', async() => { let validationResult; handsontable({ data: Handsontable.helper.createSpreadsheetData(5, 2), allowInvalid: false, validator(value, callback) { setTimeout(() => { validationResult = value.length === 2; callback(validationResult); }, 100); } }); selectCell(0, 0); keyDown('enter'); expect(isEditorVisible()).toBe(true); document.activeElement.value = 'AA'; expect(document.activeElement.value).toEqual('AA'); const cell = $(getCell(1, 0)); await sleep(); mouseDown(cell); mouseUp(cell); await sleep(100); mouseDown(cell); mouseUp(cell); await sleep(200); expect(isEditorVisible()).toBe(false); expect(validationResult).toBe(true); expect(getDataAtCell(0, 0)).toEqual('AA'); }); it('should close the editor and restore the original value after double clicking on a cell, if the previously edited cell have not validated', (done) => { let validationResult; handsontable({ data: Handsontable.helper.createSpreadsheetData(5, 2), allowInvalid: false, validator(value, callback) { setTimeout(() => { validationResult = value.length === 2; callback(validationResult); }, 100); } }); selectCell(0, 0); keyDown('enter'); document.activeElement.value = 'AAA'; expect(document.activeElement.value).toEqual('AAA'); const cell = $(getCell(1, 0)); setTimeout(() => { mouseDown(cell); mouseUp(cell); }, 0); setTimeout(() => { mouseDown(cell); mouseUp(cell); }, 100); setTimeout(() => { expect(validationResult).toBe(false); expect(getDataAtCell(0, 0)).toEqual('A1'); done(); }, 300); }); it('should listen to key changes after cell is corrected (allowInvalid: false)', async() => { const onAfterValidate = jasmine.createSpy('onAfterValidate'); handsontable({ data: arrayOfObjects(), allowInvalid: false, columns: [ { data: 'id', type: 'numeric', validator(val, cb) { cb(parseInt(val, 10) > 100); } }, { data: 'name' }, { data: 'lastName' } ], afterValidate: onAfterValidate }); selectCell(2, 0); keyDownUp('enter'); document.activeElement.value = '99'; onAfterValidate.calls.reset(); keyDownUp('enter'); // should be ignored await sleep(200); expect(isEditorVisible()).toBe(true); document.activeElement.value = '999'; onAfterValidate.calls.reset(); keyDownUp('enter'); // should be accepted await sleep(200); expect(isEditorVisible()).toBe(false); expect(getSelected()).toEqual([[3, 0, 3, 0]]); keyDownUp('arrow_up'); expect(getSelected()).toEqual([[2, 0, 2, 0]]); }); it('should allow keyboard movement when cell is being validated (move DOWN)', async() => { const onAfterValidate = jasmine.createSpy('onAfterValidate'); handsontable({ data: arrayOfObjects(), allowInvalid: false, columns: [ { data: 'id', type: 'numeric', validator(val, cb) { setTimeout(() => { cb(parseInt(val, 10) > 100); }, 100); } }, { data: 'name' }, { data: 'lastName' } ], afterValidate: onAfterValidate }); selectCell(2, 0); keyDownUp('enter'); document.activeElement.value = '999'; keyDownUp('enter'); expect(getSelected()).toEqual([[3, 0, 3, 0]]); keyDownUp('arrow_down'); keyDownUp('arrow_down'); expect(isEditorVisible()).toBe(true); expect(getSelected()).toEqual([[5, 0, 5, 0]]); await sleep(200); expect(isEditorVisible()).toBe(false); expect(getSelected()).toEqual([[5, 0, 5, 0]]); // only enterMove and first arrow_down is performed }); it('should not allow keyboard movement until cell is validated (move UP)', (done) => { const onAfterValidate = jasmine.createSpy('onAfterValidate'); handsontable({ data: arrayOfObjects(), allowInvalid: false, columns: [ { data: 'id', type: 'numeric', validator(val, cb) { setTimeout(() => { cb(parseInt(val, 10) > 100); }, 100); } }, { data: 'name' }, { data: 'lastName' } ], afterValidate: onAfterValidate }); selectCell(2, 0); keyDownUp('enter'); document.activeElement.value = '999'; keyDownUp('enter'); expect(getSelected()).toEqual([[3, 0, 3, 0]]); keyDownUp('arrow_up'); keyDownUp('arrow_up'); expect(isEditorVisible()).toBe(true); expect(getSelected()).toEqual([[1, 0, 1, 0]]); setTimeout(() => { expect(isEditorVisible()).toBe(false); expect(getSelected()).toEqual([[1, 0, 1, 0]]); done(); }, 200); }); it('should not allow keyboard movement until cell is validated (move RIGHT)', (done) => { const onAfterValidate = jasmine.createSpy('onAfterValidate'); handsontable({ data: arrayOfObjects(), allowInvalid: false, columns: [ { data: 'id', type: 'numeric', validator(val, cb) { setTimeout(() => { cb(parseInt(val, 10) > 100); }, 100); } }, { data: 'name' }, { data: 'lastName' } ], afterValidate: onAfterValidate }); selectCell(2, 0); keyDownUp('enter'); document.activeElement.value = '999'; keyDownUp('enter'); // should be accepted but only after 100 ms expect(getSelected()).toEqual([[3, 0, 3, 0]]); keyDownUp('arrow_right'); keyDownUp('arrow_right'); expect(isEditorVisible()).toBe(true); expect(getSelected()).toEqual([[3, 2, 3, 2]]); setTimeout(() => { expect(isEditorVisible()).toBe(false); expect(getSelected()).toEqual([[3, 2, 3, 2]]); done(); }, 200); }); it('should not allow keyboard movement until cell is validated (move LEFT)', async() => { const onAfterValidate = jasmine.createSpy('onAfterValidate'); hot = handsontable({ data: arrayOfObjects(), allowInvalid: false, columns: [ { data: 'name' }, { data: 'lastName' }, { data: 'id', type: 'numeric', validator(val, cb) { setTimeout(() => { cb(parseInt(val, 10) > 100); }, 100); } } ], afterValidate: onAfterValidate }); selectCell(2, 2); keyDownUp('enter'); document.activeElement.value = '999'; keyDownUp('enter'); // should be accepted but only after 100 ms expect(getSelected()).toEqual([[3, 2, 3, 2]]); spec().$container.simulate('keydown', { keyCode: Handsontable.helper.KEY_CODES.ARROW_LEFT }); spec().$container.simulate('keyup', { keyCode: Handsontable.helper.KEY_CODES.ARROW_LEFT }); spec().$container.simulate('keydown', { keyCode: Handsontable.helper.KEY_CODES.ARROW_LEFT }); spec().$container.simulate('keyup', { keyCode: Handsontable.helper.KEY_CODES.ARROW_LEFT }); expect(isEditorVisible()).toBe(true); expect(getSelected()).toEqual([[3, 0, 3, 0]]); await sleep(200); expect(isEditorVisible()).toBe(false); expect(getSelected()).toEqual([[3, 0, 3, 0]]); }); it('should not validate cell if editing has been canceled', (done) => { const onAfterValidate = jasmine.createSpy('onAfterValidate'); handsontable({ data: arrayOfObjects(), columns: [ { data: 'id' }, { data: 'name' }, { data: 'lastName' } ], afterValidate: onAfterValidate }); selectCell(0, 0); keyDownUp(Handsontable.helper.KEY_CODES.ENTER); // open editor keyDownUp(Handsontable.helper.KEY_CODES.ESCAPE); // cancel editing setTimeout(() => { expect(onAfterValidate).not.toHaveBeenCalled(); done(); }, 100); }); it('should not validate cell if editing has been canceled when columns is a function', (done) => { const onAfterValidate = jasmine.createSpy('onAfterValidate'); handsontable({ data: arrayOfObjects(), columns(column) { let colMeta = null; if (column === 0) { colMeta = { data: 'id' }; } else if (column === 1) { colMeta = { data: 'name' }; } else if (column === 2) { colMeta = { data: 'lastName' }; } return colMeta; }, afterValidate: onAfterValidate }); selectCell(0, 0); keyDownUp(Handsontable.helper.KEY_CODES.ENTER); // open editor keyDownUp(Handsontable.helper.KEY_CODES.ESCAPE); // cancel editing setTimeout(() => { expect(onAfterValidate).not.toHaveBeenCalled(); done(); }, 100); }); it('should leave cell invalid if editing has been canceled', (done) => { const onAfterValidate = jasmine.createSpy('onAfterValidate'); handsontable({ data: arrayOfObjects(), columns: [ { data: 'id', validator(value, cb) { cb(false); } }, { data: 'name' }, { data: 'lastName' } ], afterValidate: onAfterValidate }); setDataAtCell(0, 0, 'foo'); setTimeout(() => { expect(getCellMeta(0, 0).valid).toBe(false); selectCell(0, 0); keyDownUp(Handsontable.helper.KEY_CODES.ENTER); // open editor keyDownUp(Handsontable.helper.KEY_CODES.ESCAPE); // cancel editing expect(getCellMeta(0, 0).valid).toBe(false); done(); }, 200); }); it('should leave cell invalid if editing has been canceled when columns is a function', (done) => { const onAfterValidate = jasmine.createSpy('onAfterValidate'); handsontable({ data: arrayOfObjects(), columns(column) { let colMeta = null; if (column === 0) { colMeta = { data: 'id', validator(value, cb) { cb(false); } }; } else if (column === 1) { colMeta = { data: 'name' }; } else if (column === 2) { colMeta = { data: 'lastName' }; } return colMeta; }, afterValidate: onAfterValidate }); setDataAtCell(0, 0, 'foo'); setTimeout(() => { expect(getCellMeta(0, 0).valid).toBe(false); selectCell(0, 0); keyDownUp(Handsontable.helper.KEY_CODES.ENTER); // open editor keyDownUp(Handsontable.helper.KEY_CODES.ESCAPE); // cancel editing expect(getCellMeta(0, 0).valid).toBe(false); done(); }, 200); }); it('should open an appropriate editor after cell value is valid again', (done) => { const onAfterValidate = jasmine.createSpy('onAfterValidate'); const hot = handsontable({ data: arrayOfObjects(), columns: [ { data: 'id', validator(value, cb) { // eslint-disable-next-line cb(value == parseInt(value, 10)); }, allowInvalid: false }, { data: 'name' }, { data: 'lastName' } ], afterValidate: onAfterValidate }); selectCell(0, 0); let activeEditor = hot.getActiveEditor(); expect(activeEditor.row).toEqual(0); expect(activeEditor.col).toEqual(0); keyDownUp(Handsontable.helper.KEY_CODES.ENTER); // open editor activeEditor.setValue('foo'); keyDownUp(Handsontable.helper.KEY_CODES.ENTER); // save changes, close editor setTimeout(() => { onAfterValidate.calls.reset(); activeEditor = hot.getActiveEditor(); expect(activeEditor.isOpened()).toBe(true); // value is invalid, so editor stays opened expect(activeEditor.row).toEqual(0); expect(activeEditor.col).toEqual(0); activeEditor.setValue(2); keyDownUp(Handsontable.helper.KEY_CODES.ENTER); // save changes and move to cell below (row: 1, col: ś0) }, 200); setTimeout(() => { keyDownUp(Handsontable.helper.KEY_CODES.ENTER); // open editor activeEditor = hot.getActiveEditor(); expect(activeEditor.row).toEqual(1); expect(activeEditor.col).toEqual(0); done(); }, 400); }); it('should open an appropriate editor after cell value is valid again when columns is a function', (done) => { const onAfterValidate = jasmine.createSpy('onAfterValidate'); const hot = handsontable({ data: arrayOfObjects(), columns(column) { let colMeta = null; if (column === 0) { colMeta = { data: 'id', validator(value, cb) { // eslint-disable-next-line cb(value == parseInt(value, 10)); }, allowInvalid: false }; } else if (column === 1) { colMeta = { data: 'name' }; } else if (column === 2) { colMeta = { data: 'lastName' }; } return colMeta; }, afterValidate: onAfterValidate }); selectCell(0, 0); let activeEditor = hot.getActiveEditor(); expect(activeEditor.row).toEqual(0); expect(activeEditor.col).toEqual(0); keyDownUp(Handsontable.helper.KEY_CODES.ENTER); // open editor activeEditor.setValue('foo'); keyDownUp(Handsontable.helper.KEY_CODES.ENTER); // save changes, close editor setTimeout(() => { onAfterValidate.calls.reset(); activeEditor = hot.getActiveEditor(); expect(activeEditor.isOpened()).toBe(true); // value is invalid, so editor stays opened expect(activeEditor.row).toEqual(0); expect(activeEditor.col).toEqual(0); activeEditor.setValue(2); keyDownUp(Handsontable.helper.KEY_CODES.ENTER); // save changes and move to cell below (row: 1, col: ś0) }, 200); setTimeout(() => { keyDownUp(Handsontable.helper.KEY_CODES.ENTER); // open editor activeEditor = hot.getActiveEditor(); expect(activeEditor.row).toEqual(1); expect(activeEditor.col).toEqual(0); done(); }, 400); }); it('should call the validation callback only once, when using the validateCells method on a mixed set of data', (done) => { const onValidate = jasmine.createSpy('onValidate'); const onAfterValidate = jasmine.createSpy('onAfterValidate'); const hot = handsontable({ data: [ { id: 'sth', name: 'Steve' }, { id: 'sth else', name: 'Bob' } ], columns: [ { data: 'id', validator(value, cb) { cb(value === parseInt(value, 10)); } }, { data: 'name' } ], afterValidate: onAfterValidate }); hot.validateCells(onValidate); setTimeout(() => { expect(onValidate).toHaveBeenCalledWith(false); expect(onValidate.calls.count()).toEqual(1); done(); }, 200); }); it('should call the validation callback only once, when using the validateRows method on a mixed set of data', (done) => { const onValidate = jasmine.createSpy('onValidate'); const onAfterValidate = jasmine.createSpy('onAfterValidate'); const hot = handsontable({ data: [ { id: 'sth', name: 'Steve' }, { id: 'sth else', name: 'Bob' } ], columns: [ { data: 'id', validator(value, cb) { cb(value === parseInt(value, 10)); } }, { data: 'name' } ], afterValidate: onAfterValidate }); hot.validateRows([0, 1], onValidate); setTimeout(() => { expect(onValidate).toHaveBeenCalledWith(false); expect(onValidate.calls.count()).toEqual(1); done(); }, 200); }); it('should call the validation callback only once, when using the validateColumns method on a mixed set of data', (done) => { const onValidate = jasmine.createSpy('onValidate'); const onAfterValidate = jasmine.createSpy('onAfterValidate'); const hot = handsontable({ data: [ { id: 'sth', name: 'Steve' }, { id: 'sth else', name: 'Bob' } ], columns: [ { data: 'id', validator(value, cb) { cb(value === parseInt(value, 10)); } }, { data: 'name' } ], afterValidate: onAfterValidate }); hot.validateColumns([0, 1], onValidate); setTimeout(() => { expect(onValidate).toHaveBeenCalledWith(false); expect(onValidate.calls.count()).toEqual(1); done(); }, 200); }); it('should call the validation callback only once, when using the validateCells method on a mixed set of data and when columns is a function', (done) => { const onValidate = jasmine.createSpy('onValidate'); const onAfterValidate = jasmine.createSpy('onAfterValidate'); const hot = handsontable({ data: [ { id: 'sth', name: 'Steve' }, { id: 'sth else', name: 'Bob' } ], columns(column) { let colMeta = null; if (column === 0) { colMeta = { data: 'id', validator(value, cb) { cb(value === parseInt(value, 10)); } }; } else if (column === 1) { colMeta = { data: 'name' }; } return colMeta; }, afterValidate: onAfterValidate }); hot.validateCells(onValidate); setTimeout(() => { expect(onValidate).toHaveBeenCalledWith(false); expect(onValidate.calls.count()).toEqual(1); done(); }, 200); }); it('should call the validation callback only once, when using the validateRows method on a mixed set of data and when columns is a function', (done) => { const onValidate = jasmine.createSpy('onValidate'); const onAfterValidate = jasmine.createSpy('onAfterValidate'); const hot = handsontable({ data: [ { id: 'sth', name: 'Steve' }, { id: 'sth else', name: 'Bob' } ], columns(column) { let colMeta = null; if (column === 0) { colMeta = { data: 'id', validator(value, cb) { cb(value === parseInt(value, 10)); } }; } else if (column === 1) { colMeta = { data: 'name' }; } return colMeta; }, afterValidate: onAfterValidate }); hot.validateRows([0, 1], onValidate); setTimeout(() => { expect(onValidate).toHaveBeenCalledWith(false); expect(onValidate.calls.count()).toEqual(1); done(); }, 200); }); it('should call the validation callback only once, when using the validateColumns method on a mixed set of data and when columns is a function', (done) => { const onValidate = jasmine.createSpy('onValidate'); const onAfterValidate = jasmine.createSpy('onAfterValidate'); const hot = handsontable({ data: [ { id: 'sth', name: 'Steve' }, { id: 'sth else', name: 'Bob' } ], columns(column) { let colMeta = null; if (column === 0) { colMeta = { data: 'id', validator(value, cb) { cb(value === parseInt(value, 10)); } }; } else if (column === 1) { colMeta = { data: 'name' }; } return colMeta; }, afterValidate: onAfterValidate }); hot.validateColumns([0, 1], onValidate); setTimeout(() => { expect(onValidate).toHaveBeenCalledWith(false); expect(onValidate.calls.count()).toEqual(1); done(); }, 200); }); });