describe('AutocompleteEditor', () => { var id = 'testContainer'; var choices = ['yellow', 'red', 'orange', 'green', 'blue', 'gray', 'black', 'white', 'purple', 'lime', 'olive', 'cyan']; var hot; beforeEach(function() { this.$container = $(`
`).appendTo('body'); }); afterEach(function() { if (hot) { hot = null; } if (this.$container) { destroy(); this.$container.remove(); } }); describe('open editor', () => { it('should display editor (after hitting ENTER)', () => { handsontable({ columns: [ { editor: 'autocomplete', source: choices } ] }); selectCell(0, 0); var editor = $('.autocompleteEditor'); expect(editor.is(':visible')).toBe(false); keyDownUp('enter'); expect(editor.is(':visible')).toBe(true); }); it('should display editor (after hitting F2)', () => { handsontable({ columns: [ { editor: 'autocomplete', source: choices } ] }); selectCell(0, 0); var editor = $('.autocompleteEditor'); expect(editor.is(':visible')).toBe(false); keyDownUp('f2'); expect(editor.is(':visible')).toBe(true); }); it('should display editor (after doubleclicking)', () => { handsontable({ columns: [ { editor: 'autocomplete', source: choices } ] }); selectCell(0, 0); var editor = $('.autocompleteEditor'); expect(editor.is(':visible')).toBe(false); mouseDoubleClick($(getCell(0, 0))); expect(editor.is(':visible')).toBe(true); }); // see https://github.com/handsontable/handsontable/issues/3380 it('should not throw error while selecting the next cell by hitting enter key', () => { var spy = jasmine.createSpyObj('error', ['test']); var prevError = window.onerror; window.onerror = function(messageOrEvent, source, lineno, colno, error) { spy.test(); }; handsontable({ columns: [{ editor: 'autocomplete', source: choices }] }); selectCell(0, 0); keyDownUp('enter'); keyDownUp('enter'); keyDownUp('enter'); expect(spy.test.calls.count()).toBe(0); window.onerror = prevError; }); }); describe('choices', () => { it('should display given choices (array)', (done) => { handsontable({ columns: [ { editor: 'autocomplete', source: choices } ] }); selectCell(0, 0); var editor = $('.autocompleteEditor'); keyDownUp('enter'); setTimeout(() => { expect(editor.find('tbody td:eq(0)').text()).toEqual(choices[0]); expect(editor.find('tbody td:eq(1)').text()).toEqual(choices[1]); expect(editor.find('tbody td:eq(2)').text()).toEqual(choices[2]); expect(editor.find('tbody td:eq(3)').text()).toEqual(choices[3]); expect(editor.find('tbody td:eq(4)').text()).toEqual(choices[4]); done(); }, 100); }); it('should call source function with context set as cellProperties', (done) => { var source = jasmine.createSpy('source'); var context; source.and.callFake(function(query, process) { process(choices); context = this; }); var hot = handsontable({ columns: [ { editor: 'autocomplete', source } ] }); selectCell(0, 0); source.calls.reset(); keyDownUp('enter'); setTimeout(() => { expect(context.instance).toBe(hot); expect(context.row).toBe(0); expect(context.col).toBe(0); done(); }, 200); }); it('should display given choices (sync function)', (done) => { var syncSources = jasmine.createSpy('syncSources'); syncSources.and.callFake((query, process) => { process(choices); }); handsontable({ columns: [ { editor: 'autocomplete', source: syncSources } ] }); selectCell(0, 0); var editor = $('.autocompleteEditor'); syncSources.calls.reset(); keyDownUp('enter'); setTimeout(() => { expect(editor.find('tbody td:eq(0)').text()).toEqual(choices[0]); expect(editor.find('tbody td:eq(1)').text()).toEqual(choices[1]); expect(editor.find('tbody td:eq(2)').text()).toEqual(choices[2]); expect(editor.find('tbody td:eq(3)').text()).toEqual(choices[3]); expect(editor.find('tbody td:eq(4)').text()).toEqual(choices[4]); done(); }, 200); }); it('should display given choices (async function)', (done) => { var asyncSources = jasmine.createSpy('asyncSources'); asyncSources.and.callFake((process) => { process(choices); }); handsontable({ columns: [ { editor: 'autocomplete', source(query, process) { setTimeout(() => { asyncSources(process); }, 0); } } ] }); selectCell(0, 0); var editor = $('.autocompleteEditor'); keyDownUp('enter'); setTimeout(() => { expect(asyncSources.calls.count()).toEqual(1); expect(editor.find('tbody td:eq(0)').text()).toEqual(choices[0]); expect(editor.find('tbody td:eq(1)').text()).toEqual(choices[1]); expect(editor.find('tbody td:eq(2)').text()).toEqual(choices[2]); expect(editor.find('tbody td:eq(3)').text()).toEqual(choices[3]); expect(editor.find('tbody td:eq(4)').text()).toEqual(choices[4]); done(); }, 200); }); it('should NOT update choices list, after cursor leaves and enters the list (#1330)', (done) => { spyOn(Handsontable.editors.AutocompleteEditor.prototype, 'updateChoicesList').and.callThrough(); var updateChoicesList = Handsontable.editors.AutocompleteEditor.prototype.updateChoicesList; var hot = handsontable({ columns: [ { editor: 'autocomplete', source: choices } ] }); selectCell(0, 0); var editor = hot.getActiveEditor(); keyDownUp('enter'); setTimeout(() => { updateChoicesList.calls.reset(); $(editor.htContainer).find('.htCore tr:eq(0) td:eq(0)').mouseenter(); $(editor.htContainer).find('.htCore tr:eq(0) td:eq(0)').mouseleave(); $(editor.htContainer).find('.htCore tr:eq(0) td:eq(0)').mouseenter(); }, 200); setTimeout(() => { expect(updateChoicesList).not.toHaveBeenCalled(); done(); }, 300); }); it('should update choices list exactly once after a key is pressed (#1330)', (done) => { spyOn(Handsontable.editors.AutocompleteEditor.prototype, 'updateChoicesList').and.callThrough(); var updateChoicesList = Handsontable.editors.AutocompleteEditor.prototype.updateChoicesList; var hot = handsontable({ columns: [ { editor: 'autocomplete', source: choices } ] }); selectCell(0, 0); var editor = hot.getActiveEditor(); updateChoicesList.calls.reset(); keyDownUp('enter'); setTimeout(() => { updateChoicesList.calls.reset(); editor.TEXTAREA.value = 'red'; $(editor.TEXTAREA).simulate('keydown', { keyCode: 'd'.charCodeAt(0) }); }, 200); setTimeout(() => { expect(updateChoicesList.calls.count()).toEqual(1); done(); }, 100); }); it('should not initialize the dropdown with unneeded scrollbars (scrollbar causing a scrollbar issue)', (done) => { spyOn(Handsontable.editors.AutocompleteEditor.prototype, 'updateChoicesList').and.callThrough(); var updateChoicesList = Handsontable.editors.AutocompleteEditor.prototype.updateChoicesList; var hot = handsontable({ data: [ [ 'blue' ], [], [], [] ], columns: [ { editor: 'autocomplete', source: choices } ] }); selectCell(0, 0); var editor = hot.getActiveEditor(); updateChoicesList.calls.reset(); keyDownUp('enter'); setTimeout(() => { expect(editor.htContainer.scrollWidth).toEqual(editor.htContainer.clientWidth); done(); }, 200); }); it('autocomplete list should have textarea dimensions', (done) => { var syncSources = jasmine.createSpy('syncSources'); syncSources.and.callFake((query, process) => { process(choices); }); handsontable({ colWidths: [200], columns: [ { editor: 'autocomplete', source: syncSources } ] }); selectCell(0, 0); var editor = $('.handsontableInputHolder'); syncSources.calls.reset(); keyDownUp('enter'); setTimeout(() => { // -2 for transparent borders expect(editor.find('.autocompleteEditor .htCore td').width()).toEqual(editor.find('.handsontableInput').width() - 2); expect(editor.find('.autocompleteEditor .htCore td').width()).toBeGreaterThan(187); done(); }, 200); }); it('autocomplete list should have the suggestion table dimensions, when trimDropdown option is set to false', (done) => { var syncSources = jasmine.createSpy('syncSources'); syncSources.and.callFake((query, process) => { process(['long text', 'even longer text', 'extremely long text in the suggestion list', 'short text', 'text', 'another', 'yellow', 'black']); }); var hot = handsontable({ colWidths: [200], columns: [ { editor: 'autocomplete', source: syncSources } ], trimDropdown: false, }); selectCell(0, 0); var editor = $('.handsontableInputHolder'); syncSources.calls.reset(); keyDownUp('enter'); setTimeout(() => { expect(editor.find('.autocompleteEditor .htCore td').eq(0).width()).toBeGreaterThan(editor.find('.handsontableInput').width()); done(); }, 200); }); it('autocomplete textarea should have cell dimensions (after render)', (done) => { var data = [ ['a', 'b'], ['c', 'd'] ]; hot = handsontable({ data, minRows: 4, minCols: 4, minSpareRows: 4, minSpareCols: 4, cells() { return { type: Handsontable.AutocompleteCell }; } }); selectCell(1, 1); keyDownUp('enter'); data[1][1] = 'dddddddddddddddddddd'; render(); setTimeout(() => { var $td = spec().$container.find('.htCore tbody tr:eq(1) td:eq(1)'); expect(autocompleteEditor().width()).toEqual($td.width()); done(); }, 10); }); it('should invoke beginEditing only once after dobleclicking on a cell (#1011)', () => { var hot = handsontable({ columns: [ {}, {}, { type: 'autocomplete', source: choices } ] }); selectCell(0, 2); spyOn(hot.getActiveEditor(), 'beginEditing'); expect(hot.getActiveEditor().beginEditing.calls.count()).toBe(0); mouseDoubleClick(getCell(0, 2)); expect(hot.getActiveEditor().beginEditing.calls.count()).toBe(1); mouseDoubleClick(getCell(1, 2)); expect(hot.getActiveEditor().beginEditing.calls.count()).toBe(2); mouseDoubleClick(getCell(2, 2)); expect(hot.getActiveEditor().beginEditing.calls.count()).toBe(3); }); it('should not display all the choices from a long source list and not leave any unused space in the dropdown (YouTrack: #HOT-32)', (done) => { var hot = handsontable({ columns: [ { type: 'autocomplete', source: [ 'Acura', 'Audi', 'BMW', 'Buick', 'Cadillac', 'Chevrolet', 'Chrysler', 'Citroen', 'Dodge', 'Eagle', 'Ferrari', 'Ford', 'General Motors', 'GMC', 'Honda', 'Hummer', 'Hyundai', 'Infiniti', 'Isuzu', 'Jaguar', 'Jeep', 'Kia', 'Lamborghini', 'Land Rover', 'Lexus', 'Lincoln', 'Lotus', 'Mazda', 'Mercedes-Benz', 'Mercury', 'Mitsubishi', 'Nissan', 'Oldsmobile', 'Peugeot', 'Pontiac', 'Porsche', 'Regal', 'Renault', 'Saab', 'Saturn', 'Seat', 'Skoda', 'Subaru', 'Suzuki', 'Toyota', 'Volkswagen', 'Volvo'] } ] }); selectCell(0, 0); keyDownUp('enter'); var $autocomplete = autocomplete(); var $autocompleteHolder = $autocomplete.find('.ht_master .wtHolder').first(); setTimeout(() => { expect($autocomplete.find('td').first().text()).toEqual('Acura'); $autocompleteHolder.scrollTop($autocompleteHolder[0].scrollHeight); }, 100); setTimeout(() => { expect($autocomplete.find('td').last().text()).toEqual('Volvo'); done(); }, 200); }); it('should display the choices, regardless if they\'re declared as string or numeric', (done) => { handsontable({ columns: [ { editor: 'autocomplete', source: ['1', '2', 3, '4', 5, 6] } ] }); selectCell(0, 0); var editor = $('.autocompleteEditor'); keyDownUp('enter'); setTimeout(() => { expect(editor.find('tbody td:eq(0)').text()).toEqual('1'); expect(editor.find('tbody td:eq(1)').text()).toEqual('2'); expect(editor.find('tbody td:eq(2)').text()).toEqual('3'); expect(editor.find('tbody td:eq(3)').text()).toEqual('4'); expect(editor.find('tbody td:eq(4)').text()).toEqual('5'); expect(editor.find('tbody td:eq(5)').text()).toEqual('6'); done(); }, 100); }); it('should display the choices, regardless if they\'re declared as string or numeric, when data is present', (done) => { handsontable({ data: Handsontable.helper.createSpreadsheetData(10, 1), columns: [ { editor: 'autocomplete', source: ['1', '2', 3, '4', 5, 6] } ] }); selectCell(0, 0); keyDownUp('backspace'); var editor = $('.autocompleteEditor'); keyDownUp('enter'); setTimeout(() => { expect(editor.find('tbody td:eq(0)').text()).toEqual('1'); expect(editor.find('tbody td:eq(1)').text()).toEqual('2'); expect(editor.find('tbody td:eq(2)').text()).toEqual('3'); expect(editor.find('tbody td:eq(3)').text()).toEqual('4'); expect(editor.find('tbody td:eq(4)').text()).toEqual('5'); expect(editor.find('tbody td:eq(5)').text()).toEqual('6'); done(); }, 100); }); it('should display the dropdown above the editor, when there is not enough space below the cell AND there is more space above the cell', (done) => { var hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(30, 30), columns: [ { editor: 'autocomplete', source: choices }, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {} ], width: 400, height: 400 }); setDataAtCell(29, 0, ''); selectCell(29, 0); mouseDoubleClick($(getCell(29, 0))); setTimeout(() => { var autocompleteEditor = $('.autocompleteEditor'); expect(autocompleteEditor.css('position')).toEqual('absolute'); expect(autocompleteEditor.css('top')).toEqual(`${(-1) * autocompleteEditor.height()}px`); done(); }, 200); }); it('should flip the dropdown upwards when there is no more room left below the cell after filtering the choice list', (done) => { var hot = handsontable({ data: Handsontable.helper.createSpreadsheetData(30, 30), columns: [ { editor: 'autocomplete', source: choices }, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {} ], width: 400, height: 400 }); setDataAtCell(26, 0, 'b'); selectCell(26, 0); hot.view.wt.wtTable.holder.scrollTop = 999; mouseDoubleClick($(getCell(26, 0))); var autocompleteEditor = $('.autocompleteEditor'); setTimeout(() => { expect(autocompleteEditor.css('position')).toEqual('relative'); autocompleteEditor.siblings('textarea').first().val(''); keyDownUp('backspace'); }, 20); setTimeout(() => { expect(autocompleteEditor.css('position')).toEqual('absolute'); expect(autocompleteEditor.css('top')).toEqual(`${(-1) * autocompleteEditor.height()}px`); done(); }, 100); }); }); describe('closing editor', () => { it('should destroy editor when value change with mouse click on suggestion', (done) => { var syncSources = jasmine.createSpy('syncSources'); syncSources.and.callFake((query, process) => { process(choices); }); handsontable({ columns: [ { editor: 'autocomplete', source: syncSources } ] }); selectCell(0, 0); keyDownUp('enter'); setTimeout(() => { autocomplete().find('tbody td:eq(3)').simulate('mousedown'); expect(getDataAtCell(0, 0)).toEqual('green'); done(); }, 200); }); it('should not change value type from `numeric` to `string` after mouse click suggestion - ' + 'test no. 1 #4143', (done) => { handsontable({ columns: [ { editor: 'autocomplete', source: [1, 2, 3, 4, 5, 11, 14] } ] }); selectCell(0, 0); keyDownUp('enter'); setTimeout(() => { autocomplete().find('tbody td:eq(0)').simulate('mousedown'); expect(typeof getDataAtCell(0, 0)).toEqual('number'); done(); }, 200); }); it('should not change value type from `numeric` to `string` after mouse click on suggestion - ' + 'test no. 2 #4143', (done) => { const syncSources = jasmine.createSpy('syncSources'); const source = [1, 2, 3, 4, 5, 11, 14]; syncSources.and.callFake((query, process) => { process(source); }); handsontable({ columns: [ { editor: 'autocomplete', source: syncSources } ] }); selectCell(0, 0); keyDownUp('enter'); setTimeout(() => { autocomplete().find('tbody td:eq(0)').simulate('mousedown'); expect(typeof getDataAtCell(0, 0)).toEqual('number'); done(); }, 200); }); it('should call `afterChange` hook with proper value types - test no. 1 #4143', (done) => { let changesInside; let sourceInside; const afterChange = (changes, source) => { if (source !== 'loadData') { changesInside = changes; sourceInside = source; } }; handsontable({ columns: [ { editor: 'autocomplete', source: [1, 2, 3, 4, 5, 11, 14] } ], afterChange }); selectCell(0, 0); keyDownUp('enter'); setTimeout(() => { autocomplete().find('tbody td:eq(1)').simulate('mousedown'); expect(changesInside[0]).toEqual([0, 0, null, 2]); done(); }, 200); }); it('should call `afterChange` hook with proper value types - test no. 2 #4143', (done) => { let changesInside; let sourceInside; const afterChange = (changes, source) => { if (source !== 'loadData') { changesInside = changes; sourceInside = source; } }; const syncSources = jasmine.createSpy('syncSources'); const source = [1, 2, 3, 4, 5, 11, 14]; syncSources.and.callFake((query, process) => { process(source); }); handsontable({ columns: [ { editor: 'autocomplete', source: syncSources } ], afterChange }); selectCell(0, 0); keyDownUp('enter'); setTimeout(() => { autocomplete().find('tbody td:eq(1)').simulate('mousedown'); expect(changesInside[0]).toEqual([0, 0, null, 2]); done(); }, 200); }); it('should not change value type from `numeric` to `string` when written down value from set of suggestions #4143', (done) => { const syncSources = jasmine.createSpy('syncSources'); const source = [1, 2, 3, 4, 5, 11, 14]; syncSources.and.callFake((query, process) => { process(source); }); handsontable({ columns: [ { editor: 'autocomplete', source: syncSources } ] }); selectCell(0, 0); keyDownUp('enter'); keyDownUp('backspace'); document.activeElement.value = '1'; $(document.activeElement).simulate('keyup'); setTimeout(() => { keyDownUp('enter'); expect(getDataAtCell(0, 0)).toEqual(1); done(); }, 200); }); it('should destroy editor when value change with Enter on suggestion', (done) => { var syncSources = jasmine.createSpy('syncSources'); syncSources.and.callFake((query, process) => { process(choices); }); handsontable({ columns: [ { editor: 'autocomplete', source: syncSources } ] }); selectCell(0, 0); keyDownUp('enter'); setTimeout(() => { keyDownUp('arrow_down'); keyDownUp('arrow_down'); keyDownUp('arrow_down'); keyDownUp('arrow_down'); keyDownUp('enter'); expect(getDataAtCell(0, 0)).toEqual('green'); done(); }, 200); }); it('should destroy editor when pressed Enter then Esc', (done) => { var syncSources = jasmine.createSpy('syncSources'); syncSources.and.callFake((query, process) => { process(choices); }); handsontable({ columns: [ { editor: 'autocomplete', source: syncSources } ] }); selectCell(0, 0); keyDownUp('enter'); setTimeout(() => { expect(autocompleteEditor().is(':visible')).toBe(true); keyDownUp('esc'); expect(autocompleteEditor().is(':visible')).toBe(false); done(); }, 200); }); it('should destroy editor when mouse double clicked then Esc', (done) => { var syncSources = jasmine.createSpy('syncSources'); syncSources.and.callFake((query, process) => { process(choices); }); handsontable({ columns: [ { editor: 'autocomplete', source: syncSources } ] }); selectCell(0, 0); mouseDoubleClick(getCell(0, 0)); setTimeout(() => { expect(autocompleteEditor().is(':visible')).toBe(true); keyDownUp('esc'); expect(autocompleteEditor().is(':visible')).toBe(false); done(); }, 200); }); it('cancel editing (Esc) should restore the previous value', (done) => { var syncSources = jasmine.createSpy('syncSources'); syncSources.and.callFake((query, process) => { process(choices); }); handsontable({ columns: [ { editor: 'autocomplete', source: syncSources } ] }); setDataAtCell(0, 0, 'black'); selectCell(0, 0); keyDownUp('enter'); setTimeout(() => { autocomplete().siblings('.handsontableInput').val('ye'); keyDownUp(69); // e keyDownUp('esc'); expect(getDataAtCell(0, 0)).toEqual('black'); done(); }, 200); }); it('should destroy editor when clicked outside the table', (done) => { var syncSources = jasmine.createSpy('syncSources'); syncSources.and.callFake((query, process) => { process(choices); }); handsontable({ columns: [ { editor: 'autocomplete', source: syncSources } ] }); selectCell(0, 0); mouseDoubleClick(getCell(0, 0)); setTimeout(() => { expect(autocompleteEditor().is(':visible')).toBe(true); $('body').simulate('mousedown'); expect(autocompleteEditor().is(':visible')).toBe(false); done(); }, 200); }); it('should show fillHandle element again after close editor', (done) => { var syncSources = jasmine.createSpy('syncSources'); syncSources.plan = function(query, process) { process(choices.filter((choice) => choice.indexOf(query) != -1)); }; var hot = handsontable({ columns: [ { type: 'autocomplete', source: syncSources, strict: false }, {} ] }); selectCell(1, 0); keyDownUp('x'); // Trigger quick edit mode keyDownUp('enter'); setTimeout(() => { expect($('#testContainer.handsontable > .handsontable .wtBorder.current.corner:visible').length).toEqual(1); done(); }, 200); }); }); describe('non strict mode', () => { it('should allow any value in non strict mode (close editor with ENTER)', (done) => { var syncSources = jasmine.createSpy('syncSources'); syncSources.and.callFake((query, process) => { process(choices); }); handsontable({ columns: [ { editor: 'autocomplete', source: syncSources } ] }); selectCell(0, 0); keyDownUp('enter'); setTimeout(() => { var editor = $('.handsontableInput'); editor.val('foo'); keyDownUp('enter'); expect(getDataAtCell(0, 0)).toEqual('foo'); done(); }, 200); }); it('should allow any value in non strict mode (close editor by clicking on table)', (done) => { var syncSources = jasmine.createSpy('syncSources'); syncSources.and.callFake((query, process) => { process(choices); }); handsontable({ columns: [ { editor: 'autocomplete', source: syncSources } ] }); selectCell(0, 0); keyDownUp('enter'); setTimeout(() => { var editor = $('.handsontableInput'); editor.val('foo'); spec().$container.find('tbody tr:eq(1) td:eq(0)').simulate('mousedown'); expect(getDataAtCell(0, 0)).toEqual('foo'); done(); }, 200); }); it('should save the value from textarea after hitting ENTER', (done) => { var syncSources = jasmine.createSpy('syncSources'); syncSources.and.callFake((query, process) => { process(choices.filter((choice) => choice.indexOf(query) != -1)); }); hot = handsontable({ columns: [ { editor: 'autocomplete', source: syncSources } ] }); selectCell(0, 0); var editorInput = $('.handsontableInput'); expect(getDataAtCell(0, 0)).toBeNull(); keyDownUp('enter'); setTimeout(() => { syncSources.calls.reset(); editorInput.val('b'); keyDownUp('b'.charCodeAt(0)); }, 200); setTimeout(() => { var ac = hot.getActiveEditor(); var innerHot = ac.htEditor; expect(innerHot.getData()).toEqual([ ['blue'], ['black'] ]); var selected = innerHot.getSelected(); expect(selected).toBeUndefined(); keyDownUp('enter'); expect(getDataAtCell(0, 0)).toEqual('b'); done(); }, 400); }); }); describe('strict mode', () => { it('strict mode should NOT use value if it DOES NOT match the list (sync reponse is empty)', (done) => { var onAfterValidate = jasmine.createSpy('onAfterValidate'); var onAfterChange = jasmine.createSpy('onAfterChange'); var syncSources = jasmine.createSpy('syncSources'); syncSources.and.callFake((query, process) => { process([]); // hardcoded empty result }); handsontable({ data: [ ['one', 'two'], ['three', 'four'] ], columns: [ { type: 'autocomplete', source: syncSources, allowInvalid: false, strict: true }, {} ], afterValidate: onAfterValidate, afterChange: onAfterChange }); setDataAtCell(0, 0, 'unexistent'); setTimeout(() => { expect(getData()).toEqual([ ['one', 'two'], ['three', 'four'] ]); expect(syncSources.calls.count()).toEqual(1); expect(onAfterValidate.calls.count()).toEqual(1); expect(onAfterChange.calls.count()).toEqual(1); // 1 for loadData (it is not called after failed edit) done(); }, 200); }); it('strict mode should use value if it DOES match the list (sync reponse is not empty)', (done) => { var onAfterValidate = jasmine.createSpy('onAfterValidate'); var onAfterChange = jasmine.createSpy('onAfterChange'); var syncSources = jasmine.createSpy('asyncSources'); syncSources.and.callFake((query, process) => { process(choices); // hardcoded empty result }); handsontable({ data: [ ['one', 'two'], ['three', 'four'] ], columns: [ { type: 'autocomplete', source: syncSources, allowInvalid: false, strict: true }, {} ], afterValidate: onAfterValidate, afterChange: onAfterChange }); setDataAtCell(0, 0, 'yellow'); setTimeout(() => { expect(getData()).toEqual([ ['yellow', 'two'], ['three', 'four'] ]); expect(syncSources.calls.count()).toEqual(1); expect(onAfterValidate.calls.count()).toEqual(1); expect(onAfterChange.calls.count()).toEqual(2); // 1 for loadData and 1 for setDataAtCell done(); }, 200); }); it('strict mode should NOT use value if it DOES NOT match the list (async reponse is empty)', (done) => { var onAfterValidate = jasmine.createSpy('onAfterValidate'); var onAfterChange = jasmine.createSpy('onAfterChange'); var asyncSources = jasmine.createSpy('asyncSources'); asyncSources.and.callFake((query, process) => { setTimeout(() => { process([]); // hardcoded empty result }); }); handsontable({ data: [ ['one', 'two'], ['three', 'four'] ], columns: [ { type: 'autocomplete', source: asyncSources, allowInvalid: false, strict: true }, {} ], afterValidate: onAfterValidate, afterChange: onAfterChange }); setDataAtCell(0, 0, 'unexistent'); setTimeout(() => { expect(getData()).toEqual([ ['one', 'two'], ['three', 'four'] ]); expect(asyncSources.calls.count()).toEqual(1); expect(onAfterValidate.calls.count()).toEqual(1); expect(onAfterChange.calls.count()).toEqual(1); // 1 for loadData (it is not called after failed edit) done(); }, 200); }); it('strict mode should use value if it DOES match the list (async reponse is not empty)', (done) => { var onAfterValidate = jasmine.createSpy('onAfterValidate'); var onAfterChange = jasmine.createSpy('onAfterChange'); var asyncSources = jasmine.createSpy('asyncSources'); asyncSources.and.callFake((query, process) => { setTimeout(() => { process(choices); // hardcoded empty result }); }); handsontable({ data: [ ['one', 'two'], ['three', 'four'] ], columns: [ { type: 'autocomplete', source: asyncSources, allowInvalid: false, strict: true }, {} ], afterValidate: onAfterValidate, afterChange: onAfterChange }); setDataAtCell(0, 0, 'yellow'); setTimeout(() => { expect(getData()).toEqual([ ['yellow', 'two'], ['three', 'four'] ]); expect(asyncSources.calls.count()).toEqual(1); expect(onAfterValidate.calls.count()).toEqual(1); expect(onAfterChange.calls.count()).toEqual(2); // 1 for loadData and 1 for setDataAtCell done(); }, 200); }); it('strict mode mark value as invalid if it DOES NOT match the list (sync reponse is empty)', (done) => { var onAfterValidate = jasmine.createSpy('onAfterValidate'); var onAfterChange = jasmine.createSpy('onAfterChange'); var syncSources = jasmine.createSpy('syncSources'); syncSources.and.callFake((query, process) => { process([]); // hardcoded empty result }); handsontable({ data: [ ['one', 'two'], ['three', 'four'] ], columns: [ { type: 'autocomplete', source: syncSources, allowInvalid: true, strict: true }, {} ], afterValidate: onAfterValidate, afterChange: onAfterChange }); expect(getCellMeta(0, 0).valid).not.toBe(false); expect($(getCell(0, 0)).hasClass('htInvalid')).toBe(false); setDataAtCell(0, 0, 'unexistent'); setTimeout(() => { expect(getData()).toEqual([ ['unexistent', 'two'], ['three', 'four'] ]); expect(getCellMeta(0, 0).valid).toBe(false); expect($(getCell(0, 0)).hasClass('htInvalid')).toBe(true); done(); }, 200); }); it('should select the best matching option after hitting ENTER', (done) => { var onAfterValidate = jasmine.createSpy('onAfterValidate'); var syncSources = jasmine.createSpy('syncSources'); syncSources.and.callFake((query, process) => { process(choices.filter((choice) => choice.indexOf(query) != -1)); }); hot = handsontable({ columns: [ { editor: 'autocomplete', source: syncSources, strict: true } ], afterValidate: onAfterValidate }); selectCell(0, 0); var editorInput = $('.handsontableInput'); expect(getDataAtCell(0, 0)).toBeNull(); keyDownUp('enter'); setTimeout(() => { syncSources.calls.reset(); editorInput.val('b'); keyDownUp('b'.charCodeAt(0)); }, 200); setTimeout(() => { var ac = hot.getActiveEditor(); var innerHot = ac.htEditor; expect(innerHot.getData()).toEqual([ ['blue'], ['black'] ]); var selected = innerHot.getSelected(); var selectedData = innerHot.getDataAtCell(selected[0], selected[1]); expect(selectedData).toEqual('blue'); onAfterValidate.calls.reset(); keyDownUp('enter'); }, 400); setTimeout(() => { expect(getDataAtCell(0, 0)).toEqual('blue'); done(); }, 600); }); it('should select the best matching option after hitting TAB', (done) => { var onAfterValidate = jasmine.createSpy('onAfterValidate'); var syncSources = jasmine.createSpy('syncSources'); syncSources.and.callFake((query, process) => { process(choices.filter((choice) => choice.indexOf(query) != -1)); }); hot = handsontable({ columns: [ { editor: 'autocomplete', source: syncSources, strict: true } ], afterValidate: onAfterValidate }); selectCell(0, 0); var editorInput = $('.handsontableInput'); expect(getDataAtCell(0, 0)).toBeNull(); keyDownUp('enter'); setTimeout(() => { syncSources.calls.reset(); editorInput.val('b'); keyDownUp('b'.charCodeAt(0)); }, 200); setTimeout(() => { var ac = hot.getActiveEditor(); var innerHot = ac.htEditor; expect(innerHot.getData()).toEqual([ ['blue'], ['black'] ]); var selected = innerHot.getSelected(); var selectedData = innerHot.getDataAtCell(selected[0], selected[1]); expect(selectedData).toEqual('blue'); onAfterValidate.calls.reset(); keyDownUp('tab'); }, 400); setTimeout(() => { expect(getDataAtCell(0, 0)).toEqual('blue'); done(); }, 600); }); it('should mark list item corresponding to current cell value as selected', (done) => { var syncSources = jasmine.createSpy('syncSources'); syncSources.and.callFake((query, process) => { process(['red', 'dark-yellow', 'yellow', 'light-yellow', 'black']); }); handsontable({ columns: [ { editor: 'autocomplete', source: syncSources, strict: true } ], data: [ ['yellow'], ['red'], ['blue'] ] }); selectCell(0, 0); keyDownUp('enter'); setTimeout(() => { expect(autocomplete().find('.current').text()).toEqual(getDataAtCell(0, 0)); done(); }, 200); }); }); describe('filtering', () => { it('typing in textarea should filter the lookup list', (done) => { var syncSources = jasmine.createSpy('syncSources'); syncSources.and.callFake((query, process) => { process(choices.filter((choice) => choice.indexOf(query) != -1)); }); hot = handsontable({ columns: [ { editor: 'autocomplete', source: syncSources } ] }); selectCell(0, 0); var editorInput = $('.handsontableInput'); expect(getDataAtCell(0, 0)).toBeNull(); keyDownUp('enter'); setTimeout(() => { syncSources.calls.reset(); editorInput.val('e'); keyDownUp(69); // e }, 200); setTimeout(() => { var ac = hot.getActiveEditor(); var innerHot = ac.htEditor; expect(innerHot.getData()).toEqual([ ['red'], ['yellow'], ['green'], ['blue'], ['lime'], ['white'], ['olive'], ['orange'], ['purple'] ]); syncSources.calls.reset(); editorInput.val('ed'); keyDownUp(68); // d }, 400); setTimeout(() => { var ac = hot.getActiveEditor(); var innerHot = ac.htEditor; expect(innerHot.getData()).toEqual([ ['red'] ]); done(); }, 600); }); it('default filtering should be case insensitive', (done) => { hot = handsontable({ columns: [ { editor: 'autocomplete', source: choices } ] }); selectCell(0, 0); var editorInput = $('.handsontableInput'); expect(getDataAtCell(0, 0)).toBeNull(); keyDownUp('enter'); editorInput.val('e'); keyDownUp(69); // e setTimeout(() => { var ac = hot.getActiveEditor(); var innerHot = ac.htEditor; expect(innerHot.getData()).toEqual([ ['red'], ['yellow'], ['green'], ['blue'], ['lime'], ['white'], ['olive'], ['orange'], ['purple'] ]); editorInput.val('e'); keyDownUp(69); // E (same as 'e') }, 50); setTimeout(() => { var ac = hot.getActiveEditor(); var innerHot = ac.htEditor; expect(innerHot.getData()).toEqual([ ['red'], ['yellow'], ['green'], ['blue'], ['lime'], ['white'], ['olive'], ['orange'], ['purple'] ]); done(); }, 100); }); it('default filtering should be case sensitive when filteringCaseSensitive is false', (done) => { hot = handsontable({ columns: [ { editor: 'autocomplete', source: choices, filteringCaseSensitive: true } ] }); selectCell(0, 0); var editorInput = $('.handsontableInput'); expect(getDataAtCell(0, 0)).toBeNull(); keyDownUp('enter'); editorInput.val('e'); keyDownUp(69); // e setTimeout(() => { var ac = hot.getActiveEditor(); var innerHot = ac.htEditor; expect(innerHot.getData()).toEqual([ ['red'], ['yellow'], ['green'], ['blue'], ['lime'], ['white'], ['olive'], ['orange'], ['purple'] ]); editorInput.val('E'); keyDownUp(69); // E (same as 'e') }, 50); setTimeout(() => { var ac = hot.getActiveEditor(); var innerHot = ac.htEditor; expect(innerHot.getData()).toEqual([]); expect(innerHot.getSourceData()).toEqual([]); done(); }, 200); }); it('typing in textarea should NOT filter the lookup list when filtering is disabled', (done) => { hot = handsontable({ columns: [ { editor: 'autocomplete', source: choices, filter: false } ] }); selectCell(0, 0); var editorInput = $('.handsontableInput'); expect(getDataAtCell(0, 0)).toBeNull(); keyDownUp('enter'); setTimeout(() => { editorInput.val('e'); keyDownUp('e'.charCodeAt(0)); // e }, 20); setTimeout(() => { var ac = hot.getActiveEditor(); var innerHot = ac.htEditor; expect(innerHot.getData()).toEqual(Handsontable.helper.pivot([choices])); editorInput.val('ed'); keyDownUp('d'.charCodeAt(0)); // d }, 40); setTimeout(() => { var ac = hot.getActiveEditor(); var innerHot = ac.htEditor; expect(innerHot.getData()).toEqual(Handsontable.helper.pivot([choices])); done(); }, 60); }); it('typing in textarea should highlight the matching phrase', (done) => { var choices = ['Male', 'Female']; var syncSources = jasmine.createSpy('syncSources'); syncSources.and.callFake((query, process) => { process(choices.filter((choice) => choice.search(new RegExp(query, 'i')) != -1)); }); hot = handsontable({ columns: [ { editor: 'autocomplete', source: syncSources, filter: false } ] }); selectCell(0, 0); var editorInput = $('.handsontableInput'); expect(getDataAtCell(0, 0)).toBeNull(); keyDownUp('enter'); setTimeout(() => { syncSources.calls.reset(); editorInput.val('Male'); keyDownUp(69); // e }, 200); setTimeout(() => { var ac = hot.getActiveEditor(); var innerHot = ac.htEditor; var autocompleteList = $(innerHot.rootElement); expect(autocompleteList.find('td:eq(0)').html()).toMatch(/<(strong|STRONG)>Male<\/(strong|STRONG)>/); // IE8 makes the tag names UPPERCASE expect(autocompleteList.find('td:eq(1)').html()).toMatch(/Fe<(strong|STRONG)>male<\/(strong|STRONG)>/); done(); }, 400); }); it('text in textarea should not be interpreted as regexp', (done) => { spyOn(Handsontable.editors.AutocompleteEditor.prototype, 'queryChoices').and.callThrough(); var queryChoices = Handsontable.editors.AutocompleteEditor.prototype.queryChoices; hot = handsontable({ columns: [ { editor: 'autocomplete', source: choices } ] }); selectCell(0, 0); var editorInput = $('.handsontableInput'); expect(getDataAtCell(0, 0)).toBeNull(); keyDownUp('enter'); setTimeout(() => { queryChoices.calls.reset(); editorInput.val('yellow|red'); keyDownUp('d'.charCodeAt(0)); }, 200); setTimeout(() => { var ac = hot.getActiveEditor(); var innerHot = ac.htEditor; expect(innerHot.getData().length).toEqual(0); done(); }, 400); }); it('text in textarea should not be interpreted as regexp when highlighting the matching phrase', (done) => { var choices = ['Male', 'Female']; var syncSources = jasmine.createSpy('syncSources'); syncSources.and.callFake((query, process) => { process(choices.filter((choice) => choice.search(new RegExp(query, 'i')) != -1)); }); hot = handsontable({ columns: [ { editor: 'autocomplete', source: syncSources, filter: false } ] }); selectCell(0, 0); var editorInput = $('.handsontableInput'); expect(getDataAtCell(0, 0)).toBeNull(); keyDownUp('enter'); setTimeout(() => { syncSources.calls.reset(); editorInput.val('M|F'); keyDownUp('F'.charCodeAt(0)); }, 200); setTimeout(() => { var ac = hot.getActiveEditor(); var innerHot = ac.htEditor; var autocompleteList = $(innerHot.rootElement); expect(autocompleteList.find('td:eq(0)').html()).toEqual('Male'); expect(autocompleteList.find('td:eq(1)').html()).toEqual('Female'); done(); }, 400); }); it('should allow any value if filter === false and allowInvalid === true', (done) => { spyOn(Handsontable.editors.AutocompleteEditor.prototype, 'queryChoices').and.callThrough(); var queryChoices = Handsontable.editors.AutocompleteEditor.prototype.queryChoices; handsontable({ columns: [ { editor: 'autocomplete', source: choices, filter: false, strict: true, allowInvalid: true } ] }); selectCell(0, 0); var editorInput = $('.handsontableInput'); expect(getDataAtCell(0, 0)).toBeNull(); keyDownUp('enter'); setTimeout(() => { queryChoices.calls.reset(); editorInput.val('foobar'); keyDownUp(82); // r }, 200); setTimeout(() => { keyDownUp(Handsontable.helper.KEY_CODES.ENTER); expect(getDataAtCell(0, 0)).toEqual('foobar'); done(); }, 400); }); it('typing in textarea should highlight best choice, if strict === true', (done) => { var choices = ['Male', 'Female']; var syncSources = jasmine.createSpy('syncSources'); syncSources.and.callFake((query, process) => { process(choices.filter((choice) => choice.search(new RegExp(query, 'i')) != -1)); }); var hot = handsontable({ columns: [ { editor: 'autocomplete', source: syncSources, filter: false, strict: true } ] }); selectCell(0, 0); var editorInput = $('.handsontableInput'); expect(getDataAtCell(0, 0)).toBeNull(); keyDownUp('enter'); setTimeout(() => { syncSources.calls.reset(); editorInput.val('e'); keyDownUp(69); // e }, 200); setTimeout(() => { var ac = hot.getActiveEditor(); var innerHot = ac.htEditor; expect(innerHot.getSelected()).toEqual([1, 0, 1, 0]); done(); }, 400); }); }); it('should restore the old value when hovered over a autocomplete menu item and then clicked outside of the table', (done) => { var syncSources = jasmine.createSpy('syncSources'); syncSources.and.callFake((query, process) => { process(choices); }); handsontable({ columns: [ { editor: 'autocomplete', source: syncSources } ] }); selectCell(0, 0); expect(getDataAtCell(0, 0)).toBeNull(); keyDownUp('enter'); setTimeout(() => { autocomplete().find('tbody td:eq(1)').simulate('mouseenter'); autocomplete().find('tbody td:eq(1)').simulate('mouseleave'); spec().$container.simulate('mousedown'); expect(getDataAtCell(0, 0)).toBeNull(); done(); }, 200); }); it('should be able to use empty value ("")', (done) => { var syncSources = jasmine.createSpy('syncSources'); syncSources.and.callFake((query, process) => { process(['', 'BMW', 'Bentley']); }); handsontable({ data: [ ['one', 'two'], ['three', 'four'] ], columns: [ { editor: 'autocomplete', source: syncSources, filter: false } ] }); selectCell(0, 0); keyDownUp('enter'); setTimeout(() => { expect(getDataAtCell(0, 0)).toEqual('one'); autocomplete().find('tbody td:eq(0)').simulate('mousedown'); expect(getDataAtCell(0, 0)).toEqual(''); done(); }, 200); }); describe('allow html mode', () => { it('should allow inject html items (async mode)', (done) => { hot = handsontable({ columns: [ { type: 'autocomplete', source(query, cb) { cb(['foo zip', 'bar', 'baz']); }, allowHtml: true, } ] }); selectCell(0, 0); var editorInput = $('.handsontableInput'); expect(getDataAtCell(0, 0)).toBeNull(); keyDownUp('enter'); setTimeout(() => { editorInput.val('b'); keyDownUp('b'.charCodeAt(0)); }, 200); setTimeout(() => { var ac = hot.getActiveEditor(); var innerHot = ac.htEditor; expect(innerHot.getData()).toEqual([ ['bar'], ['baz'], ]); editorInput.val('bar'); keyDownUp('a'.charCodeAt(0)); keyDownUp('r'.charCodeAt(0)); }, 400); setTimeout(() => { var ac = hot.getActiveEditor(); var innerHot = ac.htEditor; expect(innerHot.getData()).toEqual([ ['bar'] ]); keyDownUp('arrow_down'); keyDownUp('enter'); }, 600); setTimeout(() => { expect(getCell(0, 0).querySelector('i').textContent).toBe('bar'); done(); }, 700); }); it('should allow inject html items (sync mode)', (done) => { hot = handsontable({ columns: [ { type: 'autocomplete', source: ['foo zip', 'bar', 'baz'], allowHtml: true, } ] }); selectCell(0, 0); var editorInput = $('.handsontableInput'); expect(getDataAtCell(0, 0)).toBeNull(); keyDownUp('enter'); setTimeout(() => { editorInput.val('b'); keyDownUp('b'.charCodeAt(0)); }, 200); setTimeout(() => { var ac = hot.getActiveEditor(); var innerHot = ac.htEditor; expect(innerHot.getData()).toEqual([ ['bar'], ['baz'], ]); editorInput.val('bar'); keyDownUp('a'.charCodeAt(0)); keyDownUp('r'.charCodeAt(0)); }, 400); setTimeout(() => { var ac = hot.getActiveEditor(); var innerHot = ac.htEditor; expect(innerHot.getData()).toEqual([ ['bar'] ]); keyDownUp('arrow_down'); keyDownUp('enter'); }, 600); setTimeout(() => { expect(getCell(0, 0).querySelector('i').textContent).toBe('bar'); done(); }, 700); }); }); describe('disallow html mode', () => { it('should be disabled by default', () => { hot = handsontable({ columns: [ { type: 'autocomplete', source(query, cb) { cb(['foo zip', 'bar', 'baz']); }, allowHtml: false, } ] }); expect(hot.getCellMeta(0, 0).allowHtml).toBeFalsy(); }); it('should strip html from strings provided in source (async mode)', (done) => { hot = handsontable({ columns: [ { type: 'autocomplete', source(query, cb) { cb(['foo zip', 'bar', 'baz']); }, allowHtml: false, } ] }); selectCell(0, 0); var editorInput = $('.handsontableInput'); expect(getDataAtCell(0, 0)).toBeNull(); keyDownUp('enter'); setTimeout(() => { editorInput.val('b'); keyDownUp('b'.charCodeAt(0)); }, 200); setTimeout(() => { var ac = hot.getActiveEditor(); var innerHot = ac.htEditor; expect(innerHot.getData()).toEqual([ ['bar'], ['baz'], ]); editorInput.val('bar'); keyDownUp('a'.charCodeAt(0)); keyDownUp('r'.charCodeAt(0)); }, 400); setTimeout(() => { var ac = hot.getActiveEditor(); var innerHot = ac.htEditor; expect(innerHot.getData()).toEqual([ ['bar'] ]); keyDownUp('arrow_down'); keyDownUp('enter'); }, 600); setTimeout(() => { expect(getCell(0, 0).querySelector('i')).toBeNull(); expect(getCell(0, 0).textContent).toMatch('bar'); done(); }, 700); }); it('should strip html from strings provided in source (sync mode)', (done) => { hot = handsontable({ columns: [ { type: 'autocomplete', source: ['foo zip', 'bar', 'baz'], allowHtml: false, } ] }); selectCell(0, 0); var editorInput = $('.handsontableInput'); expect(getDataAtCell(0, 0)).toBeNull(); keyDownUp('enter'); setTimeout(() => { editorInput.val('b'); keyDownUp('b'.charCodeAt(0)); }, 200); setTimeout(() => { var ac = hot.getActiveEditor(); var innerHot = ac.htEditor; expect(innerHot.getData()).toEqual([ ['bar'], ['baz'], ]); editorInput.val('bar'); keyDownUp('a'.charCodeAt(0)); keyDownUp('r'.charCodeAt(0)); }, 400); setTimeout(() => { var ac = hot.getActiveEditor(); var innerHot = ac.htEditor; expect(innerHot.getData()).toEqual([ ['bar'] ]); keyDownUp('arrow_down'); keyDownUp('enter'); }, 600); setTimeout(() => { expect(getCell(0, 0).querySelector('i')).toBeNull(); expect(getCell(0, 0).textContent).toMatch('bar'); done(); }, 700); }); }); describe('Autocomplete helper functions:', () => { describe('sortByRelevance', () => { it('should sort the provided array, so items more relevant to the provided value are listed first', () => { var choices = [ 'Wayne', // 0 'Draven', // 1 'Banner', // 2 'Stark', // 3 'Parker', // 4 'Kent', // 5 'Gordon', // 6 'Kyle', // 7 'Simmons'// 8 ]; let value = 'a'; var sorted = Handsontable.editors.AutocompleteEditor.sortByRelevance(value, choices); expect(sorted).toEqual([0, 2, 4, 3, 1]); value = 'o'; sorted = Handsontable.editors.AutocompleteEditor.sortByRelevance(value, choices); expect(sorted).toEqual([6, 8]); value = 'er'; sorted = Handsontable.editors.AutocompleteEditor.sortByRelevance(value, choices); expect(sorted).toEqual([2, 4]); }); }); }); it('should not modify the suggestion lists\' order, when the sortByRelevance option is set to false', (done) => { var choices = [ 'Wayne', 'Draven', 'Banner', 'Stark', 'Parker', 'Kent', 'Gordon', 'Kyle', 'Simmons' ]; var hot = handsontable({ columns: [ { editor: 'autocomplete', source: choices, sortByRelevance: false } ] }); selectCell(0, 0); keyDownUp('enter'); var $editorInput = $('.handsontableInput'); $editorInput.val('a'); keyDownUp('a'.charCodeAt(0)); Handsontable.dom.setCaretPosition($editorInput[0], 1); setTimeout(() => { var dropdownList = $('.autocompleteEditor tbody').first(); var listLength = dropdownList.find('tr').size(); expect(listLength).toBe(9); for (var i = 1; i <= listLength; i++) { expect(dropdownList.find(`tr:nth-child(${i}) td`).text()).toEqual(choices[i - 1]); } done(); }, 30); }); it('should fire one afterChange event when value is changed', (done) => { var onAfterChange = jasmine.createSpy('onAfterChange'); var syncSources = jasmine.createSpy('syncSources'); syncSources.and.callFake((query, process) => { process(choices); }); handsontable({ columns: [ { editor: 'autocomplete', source: syncSources } ], afterChange: onAfterChange }); selectCell(0, 0); keyDownUp('enter'); setTimeout(() => { onAfterChange.calls.reset(); autocomplete().find('tbody td:eq(1)').simulate('mousedown'); expect(getDataAtCell(0, 0)).toEqual('red'); expect(onAfterChange.calls.count()).toEqual(1); expect(onAfterChange).toHaveBeenCalledWith([[0, 0, null, 'red']], 'edit', undefined, undefined, undefined, undefined); done(); }, 200); }); it('should not affect other cell values after clicking on autocomplete cell (#1021)', (done) => { var syncSources = jasmine.createSpy('syncSources'); syncSources.and.callFake((query, process) => { process(choices); }); handsontable({ columns: [ {}, {}, { editor: 'autocomplete', source: syncSources }, {} ], data: [ [null, null, 'yellow', null], [null, null, 'red', null], [null, null, 'blue', null] ] }); expect($(getCell(0, 2)).text()).toMatch('yellow'); mouseDoubleClick(getCell(0, 2)); expect($(getCell(1, 2)).text()).toMatch('red'); mouseDoubleClick(getCell(1, 2)); expect($(getCell(2, 2)).text()).toMatch('blue'); mouseDoubleClick(getCell(2, 2)); setTimeout(() => { expect(getDataAtCol(2)).toEqual(['yellow', 'red', 'blue']); done(); }, 200); }); it('should handle editor if cell data is a function', (done) => { spyOn(Handsontable.editors.AutocompleteEditor.prototype, 'updateChoicesList').and.callThrough(); var updateChoicesList = Handsontable.editors.AutocompleteEditor.prototype.updateChoicesList; var afterValidateCallback = jasmine.createSpy('afterValidateCallbak'); var hot = handsontable({ data: [ new Model({ id: 1, name: 'Ted Right', address: '' }), new Model({ id: 2, name: 'Frank Honest', address: '' }), new Model({ id: 3, name: 'Joan Well', address: '' })], dataSchema: Model, colHeaders: ['ID', 'Name', 'Address'], columns: [ { data: createAccessorForProperty('id'), type: 'autocomplete', source: ['1', '2', '3'], filter: false, strict: true }, { data: createAccessorForProperty('name') }, { data: createAccessorForProperty('address') } ], minSpareRows: 1, afterValidate: afterValidateCallback }); selectCell(0, 0); expect(hot.getActiveEditor().isOpened()).toBe(false); keyDownUp('enter'); setTimeout(() => { expect(hot.getActiveEditor().isOpened()).toBe(true); afterValidateCallback.calls.reset(); $(hot.getActiveEditor().htContainer).find('tr:eq(1) td:eq(0)').simulate('mousedown'); }, 200); setTimeout(() => { expect(getDataAtCell(0, 0)).toEqual('2'); done(); }, 400); }); it('should not call the `source` has been selected', () => { var syncSources = jasmine.createSpy('syncSources'); syncSources.and.callFake((query, process) => { process([]); // hardcoded empty result }); handsontable({ data: [ ['one', 'two'], ['three', 'four'] ], columns: [ { type: 'autocomplete', source: syncSources, allowInvalid: false, strict: true }, {} ], cells(row, col) { var cellProperties = {}; if (row === 0 && col === 0) { cellProperties.readOnly = true; } return cellProperties; } }); expect(getCellMeta(0, 0).readOnly).toBe(true); expect(syncSources).not.toHaveBeenCalled(); selectCell(0, 0); expect(syncSources).not.toHaveBeenCalled(); expect(getCellMeta(1, 0).readOnly).toBeFalsy(); selectCell(1, 0); expect(syncSources).not.toHaveBeenCalled(); }); it('should not call the `source` method if cell is read only and the arrow has been clicked', (done) => { var syncSources = jasmine.createSpy('syncSources'); syncSources.and.callFake((query, process) => { process([]); // hardcoded empty result }); handsontable({ data: [ ['one', 'two'], ['three', 'four'] ], columns: [ { type: 'autocomplete', source: syncSources, allowInvalid: false, strict: true }, {} ], cells(row, col) { var cellProperties = {}; if (row === 0 && col === 0) { cellProperties.readOnly = true; } return cellProperties; } }); expect(getCellMeta(0, 0).readOnly).toBe(true); expect(syncSources).not.toHaveBeenCalled(); selectCell(0, 0); $(getCell(0, 0)).find('.htAutocompleteArrow').simulate('mousedown'); setTimeout(() => { expect(syncSources).not.toHaveBeenCalled(); syncSources.calls.reset(); expect(getCellMeta(1, 0).readOnly).toBeFalsy(); selectCell(1, 0); $(getCell(1, 0)).find('.htAutocompleteArrow').simulate('mousedown'); }, 100); setTimeout(() => { expect(syncSources).toHaveBeenCalled(); expect(syncSources.calls.count()).toEqual(1); done(); }, 200); }); it('should add a scrollbar to the autocomplete dropdown, only if number of displayed choices exceeds 10', function(done) { var hot = handsontable({ data: [ ['', 'two', 'three'], ['four', 'five', 'six'] ], columns: [ { type: 'autocomplete', source: choices, allowInvalid: false, strict: false }, {}, {} ] }); this.$container.css({ height: 600 }); expect(choices.length).toBeGreaterThan(10); selectCell(0, 0); $(getCell(0, 0)).find('.htAutocompleteArrow').simulate('mousedown'); var dropdown = hot.getActiveEditor().htContainer; var dropdownHolder = hot.getActiveEditor().htEditor.view.wt.wtTable.holder; setTimeout(() => { expect(dropdownHolder.scrollHeight).toBeGreaterThan(dropdownHolder.clientHeight); keyDownUp('esc'); hot.getSettings().columns[0].source = hot.getSettings().columns[0].source.slice(0).splice(3); hot.updateSettings({}); selectCell(0, 0); $(getCell(0, 0)).find('.htAutocompleteArrow').simulate('mousedown'); }, 30); setTimeout(() => { expect(dropdownHolder.scrollHeight > dropdownHolder.clientHeight).toBe(false); done(); }, 60); }); it('should not close editor on scrolling', (done) => { var hot = handsontable({ data: [ ['', 'two', 'three'], ['four', 'five', 'six'] ], columns: [ { type: 'autocomplete', source: choices, allowInvalid: false, strict: false }, {}, {} ] }); expect(choices.length).toBeGreaterThan(10); selectCell(0, 0); $(getCell(0, 0)).find('.htAutocompleteArrow').simulate('mousedown'); $(getCell(0, 0)).find('.htAutocompleteArrow').simulate('mouseup'); var dropdown = hot.getActiveEditor().htContainer; hot.view.wt.wtOverlays.topOverlay.scrollTo(1); setTimeout(() => { expect($(dropdown).is(':visible')).toBe(true); selectCell(0, 0); }, 30); setTimeout(() => { $(getCell(0, 0)).find('.htAutocompleteArrow').simulate('mousedown'); $(getCell(0, 0)).find('.htAutocompleteArrow').simulate('mouseup'); hot.view.wt.wtOverlays.topOverlay.scrollTo(3); }, 80); setTimeout(() => { expect($(dropdown).is(':visible')).toBe(true); done(); }, 120); }); it('should keep textarea caret position, after moving the selection to the suggestion list (pressing down arrow)', (done) => { var syncSources = jasmine.createSpy('syncSources'); syncSources.and.callFake((query, process) => { process(choices.filter((choice) => choice.indexOf(query) != -1)); }); handsontable({ columns: [ { type: 'autocomplete', source: syncSources, strict: false } ] }); selectCell(0, 0); keyDownUp('enter'); var $editorInput = $('.handsontableInput'); $editorInput.val('an'); keyDownUp(65); // a keyDownUp(78); // n Handsontable.dom.setCaretPosition($editorInput[0], 1); setTimeout(() => { keyDownUp('arrow_down'); expect(Handsontable.dom.getCaretPosition($editorInput[0])).toEqual(1); keyDownUp('arrow_down'); expect(Handsontable.dom.getCaretPosition($editorInput[0])).toEqual(1); done(); }, 200); }); it('should keep textarea selection, after moving the selection to the suggestion list (pressing down arrow)', (done) => { var syncSources = jasmine.createSpy('syncSources'); syncSources.and.callFake((query, process) => { process(choices.filter((choice) => choice.indexOf(query) != -1)); }); handsontable({ columns: [ { type: 'autocomplete', source: syncSources, strict: false } ] }); selectCell(0, 0); keyDownUp('enter'); var $editorInput = $('.handsontableInput'); $editorInput.val('an'); keyDownUp(65); // a keyDownUp(78); // n Handsontable.dom.setCaretPosition($editorInput[0], 1, 2); setTimeout(() => { keyDownUp('arrow_down'); expect(Handsontable.dom.getCaretPosition($editorInput[0])).toEqual(1); expect(Handsontable.dom.getSelectionEndPosition($editorInput[0])).toEqual(2); keyDownUp('arrow_down'); expect(Handsontable.dom.getCaretPosition($editorInput[0])).toEqual(1); expect(Handsontable.dom.getSelectionEndPosition($editorInput[0])).toEqual(2); done(); }, 200); }); it('should jump to the sibling cell, after pressing up key in quick edit mode', (done) => { var syncSources = jasmine.createSpy('syncSources'); syncSources.and.callFake((query, process) => { process(choices.filter((choice) => choice.indexOf(query) != -1)); }); handsontable({ columns: [ { type: 'autocomplete', source: syncSources, strict: false }, {} ] }); selectCell(1, 0); keyDownUp('x'); // trigger quick edit mode var $editorInput = $('.handsontableInput'); $editorInput.val('an'); keyDownUp(65); // a keyDownUp(78); // n setTimeout(() => { keyDownUp('arrow_up'); expect(getSelected()).toEqual([0, 0, 0, 0]); done(); }, 200); }); it('should jump to the next cell, after pressing right key in quick edit mode', (done) => { var syncSources = jasmine.createSpy('syncSources'); syncSources.plan = function(query, process) { process(choices.filter((choice) => choice.indexOf(query) != -1)); }; handsontable({ columns: [ { type: 'autocomplete', source: syncSources, strict: false }, {} ] }); selectCell(1, 0); keyDownUp('x'); // trigger quick edit mode var $editorInput = $('.handsontableInput'); $editorInput.val('an'); keyDownUp(65); // a keyDownUp(78); // n setTimeout(() => { keyDownUp('arrow_right'); expect(getSelected()).toEqual([1, 1, 1, 1]); done(); }, 200); }); it('should jump to the next cell, after pressing left key in quick edit mode', (done) => { var syncSources = jasmine.createSpy('syncSources'); syncSources.and.callFake((query, process) => { process(choices.filter((choice) => choice.indexOf(query) != -1)); }); handsontable({ columns: [ {}, { type: 'autocomplete', source: syncSources, strict: false } ] }); selectCell(1, 1); keyDownUp('x'); // trigger quick edit mode var $editorInput = $('.handsontableInput'); $editorInput.val('an'); keyDownUp(65); // a keyDownUp(78); // n // put caret on the end of the text to ensure that editor will be closed after hit left arrow key Handsontable.dom.setCaretPosition($editorInput[0], 2, 2); setTimeout(() => { keyDownUp('arrow_left'); expect(getSelected()).toEqual([1, 0, 1, 0]); done(); }, 200); }); it('should jump to the next cell, after pressing down key in quick edit mode', (done) => { var syncSources = jasmine.createSpy('syncSources'); syncSources.and.callFake((query, process) => { process(choices.filter((choice) => choice.indexOf(query) != -1)); }); handsontable({ columns: [ { type: 'autocomplete', source: syncSources, strict: false }, {} ] }); selectCell(1, 0); keyDownUp('x'); // trigger quick edit mode var $editorInput = $('.handsontableInput'); $editorInput.val('an'); keyDownUp(65); // a keyDownUp(78); // n setTimeout(() => { keyDownUp('arrow_down'); expect(getSelected()).toEqual([1, 0, 1, 0]); done(); }, 200); }); it('should jump to the next cell, after pressing down key in quick edit mode when no matching option list found', (done) => { var syncSources = jasmine.createSpy('syncSources'); syncSources.and.callFake((query, process) => { process(choices.filter((choice) => choice.indexOf(query) != -1)); }); handsontable({ columns: [ { type: 'autocomplete', source: syncSources, strict: false }, {} ] }); selectCell(1, 0); keyDownUp('x'); // trigger quick edit mode var $editorInput = $('.handsontableInput'); $editorInput.val('anananan'); keyDownUp(65); // a keyDownUp(78); // n keyDownUp(65); // a keyDownUp(78); // n keyDownUp(65); // a keyDownUp(78); // n keyDownUp(65); // a keyDownUp(78); // n setTimeout(() => { keyDownUp('arrow_down'); expect(getSelected()).toEqual([2, 0, 2, 0]); done(); }, 200); }); it('should not jump to the next cell, after pressing down key in quick edit mode when options list was opened', (done) => { var syncSources = jasmine.createSpy('syncSources'); syncSources.and.callFake((query, process) => { process(choices.filter((choice) => choice.indexOf(query) != -1)); }); handsontable({ columns: [ { type: 'autocomplete', source: syncSources, strict: false }, {} ] }); selectCell(1, 0); keyDownUp('x'); // trigger quick edit mode var $editorInput = $('.handsontableInput'); $editorInput.val('an'); keyDownUp(65); // a keyDownUp(78); // n setTimeout(() => { keyDownUp('arrow_down'); expect(getSelected()).toEqual([1, 0, 1, 0]); done(); }, 200); }); it('should select option in opened editor after pressing down key in quick edit mode', (done) => { var syncSources = jasmine.createSpy('syncSources'); syncSources.and.callFake((query, process) => { process(choices.filter((choice) => choice.indexOf(query) != -1)); }); var hot = handsontable({ columns: [ { type: 'autocomplete', source: syncSources, strict: false }, {} ] }); selectCell(1, 0); keyDownUp('x'); // Trigger quick edit mode setTimeout(() => { keyDownUp('arrow_down'); expect(hot.getActiveEditor().htEditor.getSelected()).toEqual([0, 0, 0, 0]); keyDownUp('arrow_down'); expect(hot.getActiveEditor().htEditor.getSelected()).toEqual([1, 0, 1, 0]); keyDownUp('arrow_down'); expect(hot.getActiveEditor().htEditor.getSelected()).toEqual([2, 0, 2, 0]); done(); }, 200); }); it('should select option in opened editor after pressing up key in quick edit mode', (done) => { var syncSources = jasmine.createSpy('syncSources'); syncSources.and.callFake((query, process) => { process(choices.filter((choice) => choice.indexOf(query) != -1)); }); var hot = handsontable({ columns: [ { type: 'autocomplete', source: syncSources, strict: false }, {} ] }); selectCell(1, 0); keyDownUp('x'); // Trigger quick edit mode setTimeout(() => { hot.getActiveEditor().htEditor.selectCell(2, 0); expect(hot.getActiveEditor().htEditor.getSelected()).toEqual([2, 0, 2, 0]); keyDownUp('arrow_up'); expect(hot.getActiveEditor().htEditor.getSelected()).toEqual([1, 0, 1, 0]); keyDownUp('arrow_up'); expect(hot.getActiveEditor().htEditor.getSelected()).toEqual([0, 0, 0, 0]); keyDownUp('arrow_up'); expect(hot.getActiveEditor().htEditor.getSelected()).toEqual([0, 0, 0, 0]); done(); }, 200); }); it('should not close editor in quick edit mode after pressing down key when last option is selected', (done) => { var syncSources = jasmine.createSpy('syncSources'); syncSources.and.callFake((query, process) => { process(choices.filter((choice) => choice.indexOf(query) != -1)); }); var hot = handsontable({ columns: [ { type: 'autocomplete', source: syncSources, strict: false }, {} ] }); selectCell(1, 0); keyDownUp('x'); // Trigger quick edit mode setTimeout(() => { hot.getActiveEditor().htEditor.selectCell(7, 0); hot.listen(); keyDownUp('arrow_down'); keyDownUp('arrow_down'); keyDownUp('arrow_down'); keyDownUp('arrow_down'); keyDownUp('arrow_down'); expect(hot.getActiveEditor().isOpened()).toBe(true); done(); }, 200); }); it('should close editor in quick edit mode after pressing up key when no option is selected', (done) => { var syncSources = jasmine.createSpy('syncSources'); syncSources.and.callFake((query, process) => { process(choices.filter((choice) => choice.indexOf(query) != -1)); }); var hot = handsontable({ columns: [ { type: 'autocomplete', source: syncSources, strict: false }, {} ] }); selectCell(1, 0); keyDownUp('x'); // Trigger quick edit mode setTimeout(() => { hot.getActiveEditor().htEditor.selectCell(1, 0); hot.listen(); keyDownUp('arrow_up'); keyDownUp('arrow_up'); keyDownUp('arrow_up'); expect(getSelected()).toEqual([0, 0, 0, 0]); done(); }, 200); }); });