From 2d4a5e3328f1ab518e76150ea261ab211599a30e Mon Sep 17 00:00:00 2001 From: Serhii Kulykov Date: Tue, 24 Sep 2024 13:21:03 +0300 Subject: [PATCH] fix: wait for editor focusout on Tab to get updated value (#7822) --- .../vaadin-grid-pro-inline-editing-mixin.js | 22 +- .../grid-pro/test/edit-column-lit.test.js | 1 + .../grid-pro/test/edit-column-polymer.test.js | 1 + packages/grid-pro/test/edit-column.common.js | 132 +++++++----- .../test/keyboard-navigation.common.js | 198 +++++++++--------- .../grid-pro-custom-editor.test.js | 137 ++++++++++++ 6 files changed, 333 insertions(+), 158 deletions(-) create mode 100644 test/integration/grid-pro-custom-editor.test.js diff --git a/packages/grid-pro/src/vaadin-grid-pro-inline-editing-mixin.js b/packages/grid-pro/src/vaadin-grid-pro-inline-editing-mixin.js index ee7e3ee87d..38ee4ac779 100644 --- a/packages/grid-pro/src/vaadin-grid-pro-inline-editing-mixin.js +++ b/packages/grid-pro/src/vaadin-grid-pro-inline-editing-mixin.js @@ -306,6 +306,10 @@ export const InlineEditingMixin = (superClass) => /** @private */ _onEditorFocusOut() { + // Ignore focusout from internal tab event + if (this.__cancelCellSwitch) { + return; + } // Schedule stop on editor component focusout this._debouncerStopEdit = Debouncer.debounce(this._debouncerStopEdit, animationFrame, this._stopEdit.bind(this)); } @@ -424,7 +428,7 @@ export const InlineEditingMixin = (superClass) => * @param {!KeyboardEvent} e * @protected */ - _switchEditCell(e) { + async _switchEditCell(e) { if (this.__cancelCellSwitch || (e.defaultPrevented && e.keyCode === 9)) { return; } @@ -434,11 +438,23 @@ export const InlineEditingMixin = (superClass) => const editableColumns = this._getEditColumns(); const { cell, column, model } = this.__edited; - this._stopEdit(); - e.preventDefault(); // Prevent vaadin-grid handler from being called e.stopImmediatePropagation(); + const editor = column._getEditorComponent(cell); + + // Do not prevent Tab to allow native input blur and wait for it, + // unless the keydown event is from the edit cell select overlay. + if (e.key === 'Tab' && editor && editor.contains(e.target)) { + await new Promise((resolve) => { + editor.addEventListener('focusout', () => resolve(), { once: true }); + }); + } else { + e.preventDefault(); + } + + this._stopEdit(); + // Try to find the next editable cell let nextIndex = model.index; let nextColumn = column; diff --git a/packages/grid-pro/test/edit-column-lit.test.js b/packages/grid-pro/test/edit-column-lit.test.js index d55d167cde..3703b5de6f 100644 --- a/packages/grid-pro/test/edit-column-lit.test.js +++ b/packages/grid-pro/test/edit-column-lit.test.js @@ -1,3 +1,4 @@ +import './not-animated-styles.js'; import '../theme/lumo/vaadin-lit-grid-pro.js'; import '../theme/lumo/vaadin-lit-grid-pro-edit-column.js'; import './edit-column.common.js'; diff --git a/packages/grid-pro/test/edit-column-polymer.test.js b/packages/grid-pro/test/edit-column-polymer.test.js index 4cf8c5ea9c..9db3177f08 100644 --- a/packages/grid-pro/test/edit-column-polymer.test.js +++ b/packages/grid-pro/test/edit-column-polymer.test.js @@ -1,3 +1,4 @@ +import './not-animated-styles.js'; import '../theme/lumo/vaadin-grid-pro.js'; import '../theme/lumo/vaadin-grid-pro-edit-column.js'; import './edit-column.common.js'; diff --git a/packages/grid-pro/test/edit-column.common.js b/packages/grid-pro/test/edit-column.common.js index ad3db10dd9..c9575e46d1 100644 --- a/packages/grid-pro/test/edit-column.common.js +++ b/packages/grid-pro/test/edit-column.common.js @@ -1,5 +1,6 @@ import { expect } from '@vaadin/chai-plugins'; -import { enter, esc, fixtureSync, focusin, focusout, isIOS, nextFrame, tab } from '@vaadin/testing-helpers'; +import { enter, esc, fixtureSync, focusin, focusout, nextFrame } from '@vaadin/testing-helpers'; +import { sendKeys } from '@web/test-runner-commands'; import sinon from 'sinon'; import { createItems, @@ -20,8 +21,8 @@ function indexNameRenderer(root, _, { index, item }) { } describe('edit column', () => { - (isIOS ? describe.skip : describe)('select column', () => { - let grid, textCell, selectCell, checkboxCell; + describe('select column', () => { + let grid, textCell, selectCell, ageCell; beforeEach(async () => { grid = fixtureSync(` @@ -37,7 +38,7 @@ describe('edit column', () => { flushGrid(grid); textCell = getContainerCell(grid.$.items, 1, 0); selectCell = getContainerCell(grid.$.items, 1, 1); - checkboxCell = getContainerCell(grid.$.items, 1, 2); + ageCell = getContainerCell(grid.$.items, 1, 2); await nextFrame(); }); @@ -47,27 +48,31 @@ describe('edit column', () => { await nextFrame(); // Press Tab to edit the select cell - tab(document.activeElement); + await sendKeys({ press: 'Tab' }); expect(getCellEditor(selectCell)).to.be.ok; await nextFrame(); - // Press Tab to edit the checkbox cell - tab(document.activeElement); - expect(getCellEditor(checkboxCell)).to.be.ok; + // Press Tab to edit the age cell + await sendKeys({ press: 'Tab' }); + expect(getCellEditor(ageCell)).to.be.ok; }); it('should focus previous cell available for editing in edit mode on Shift Tab', async () => { - dblclick(checkboxCell._content); - expect(getCellEditor(checkboxCell)).to.be.ok; + dblclick(ageCell._content); + expect(getCellEditor(ageCell)).to.be.ok; await nextFrame(); // Press Shift + Tab to edit the select cell - tab(document.activeElement, ['shift']); + await sendKeys({ down: 'Shift' }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ up: 'Shift' }); expect(getCellEditor(selectCell)).to.be.ok; await nextFrame(); // Press Shift + Tab to edit the text cell - tab(document.activeElement, ['shift']); + await sendKeys({ down: 'Shift' }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ up: 'Shift' }); expect(getCellEditor(textCell)).to.be.ok; }); }); @@ -113,7 +118,7 @@ describe('edit column', () => { }); describe('horizontal scrolling to cell', () => { - let grid, input; + let grid; beforeEach(() => { grid = fixtureSync(` @@ -130,26 +135,24 @@ describe('edit column', () => { flushGrid(grid); }); - it('should scroll to the right on tab when editable cell is outside the viewport', () => { + it('should scroll to the right on tab when editable cell is outside the viewport', async () => { const firstCell = getContainerCell(grid.$.items, 1, 0); dblclick(firstCell._content); - input = getCellEditor(firstCell); - tab(input); + + await sendKeys({ press: 'Tab' }); expect(grid.$.table.scrollLeft).to.be.at.least(100); }); - it('should scroll to the left on tab when editable cell is outside the viewport', (done) => { + it('should scroll to the left on tab when editable cell is outside the viewport', async () => { const firstCell = getContainerCell(grid.$.items, 1, 1); dblclick(firstCell._content); - setTimeout(() => { - input = getCellEditor(firstCell); - tab(input, ['shift']); + await sendKeys({ down: 'Shift' }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ up: 'Shift' }); - expect(grid.$.table.scrollLeft).to.closeTo(1, 1); - done(); - }); + expect(grid.$.table.scrollLeft).to.closeTo(1, 1); }); }); @@ -429,78 +432,91 @@ describe('edit column', () => { }); describe('with Tab', () => { - it('should skip non-editable cells when navigating with Tab', () => { + it('should skip non-editable cells when navigating with Tab', async () => { let cell = getContainerCell(grid.$.items, 1, 2); - enter(cell); + cell.focus(); + await sendKeys({ press: 'Enter' }); expect(getCellEditor(cell)).to.be.ok; - tab(cell); + await sendKeys({ press: 'Tab' }); cell = getContainerCell(grid.$.items, 1, 3); expect(getCellEditor(cell)).to.be.ok; // Should skip non-editable row - tab(cell); + await sendKeys({ press: 'Tab' }); cell = getContainerCell(grid.$.items, 3, 1); expect(getCellEditor(cell)).to.be.ok; - tab(cell); + await sendKeys({ press: 'Tab' }); cell = getContainerCell(grid.$.items, 3, 2); expect(getCellEditor(cell)).to.be.ok; }); - it('should skip non-editable cells when navigating with Shift-Tab', () => { + it('should skip non-editable cells when navigating with Shift-Tab', async () => { let cell = getContainerCell(grid.$.items, 3, 2); - enter(cell); + cell.focus(); + await sendKeys({ press: 'Enter' }); expect(getCellEditor(cell)).to.be.ok; - tab(cell, ['shift']); + await sendKeys({ down: 'Shift' }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ up: 'Shift' }); cell = getContainerCell(grid.$.items, 3, 1); expect(getCellEditor(cell)).to.be.ok; // Should skip non-editable rows - tab(cell, ['shift']); + await sendKeys({ down: 'Shift' }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ up: 'Shift' }); cell = getContainerCell(grid.$.items, 1, 3); expect(getCellEditor(cell)).to.be.ok; - tab(cell, ['shift']); + await sendKeys({ down: 'Shift' }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ up: 'Shift' }); cell = getContainerCell(grid.$.items, 1, 2); expect(getCellEditor(cell)).to.be.ok; }); - it('should skip cells that become non-editable after editing current cell', () => { + it('should skip cells that become non-editable after editing current cell', async () => { // Edit status in row 2 to be completed, so none of the cells in this // row should be editable anymore let cell = getContainerCell(grid.$.items, 1, 1); - enter(cell); + cell.focus(); + await sendKeys({ press: 'Enter' }); const input = getCellEditor(cell); input.value = 'completed'; - tab(cell); + await sendKeys({ press: 'Tab' }); // Should skip to row 4 cell = getContainerCell(grid.$.items, 3, 1); expect(getCellEditor(cell)).to.be.ok; }); - it('should stop editing and focus last edited cell if there are no more editable cells with Tab', () => { + it('should stop editing and focus last edited cell if there are no more editable cells with Tab', async () => { const cell = getContainerCell(grid.$.items, 3, 3); - enter(cell); + cell.focus(); + await sendKeys({ press: 'Enter' }); expect(getCellEditor(cell)).to.be.ok; expect(grid.querySelector('vaadin-grid-pro-edit-text-field')).to.be.ok; - tab(cell); + await sendKeys({ press: 'Tab' }); expect(grid.querySelector('vaadin-grid-pro-edit-text-field')).to.not.be.ok; const target = cell._focusButton || cell; expect(grid.shadowRoot.activeElement).to.equal(target); expect(grid.hasAttribute('navigating')).to.be.true; }); - it('should stop editing and focus last edited cell if there are no more editable cells with Shift-Tab', () => { + it('should stop editing and focus last edited cell if there are no more editable cells with Shift-Tab', async () => { const cell = getContainerCell(grid.$.items, 1, 1); - enter(cell); + cell.focus(); + await sendKeys({ press: 'Enter' }); expect(getCellEditor(cell)).to.be.ok; expect(grid.querySelector('vaadin-grid-pro-edit-text-field')).to.be.ok; - tab(cell, ['shift']); + await sendKeys({ down: 'Shift' }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ up: 'Shift' }); expect(grid.querySelector('vaadin-grid-pro-edit-text-field')).to.not.be.ok; const target = cell._focusButton || cell; expect(grid.shadowRoot.activeElement).to.equal(target); @@ -513,46 +529,54 @@ describe('edit column', () => { grid.enterNextRow = true; }); - it('should skip non-editable cells when navigating with Enter', () => { + it('should skip non-editable cells when navigating with Enter', async () => { let cell = getContainerCell(grid.$.items, 1, 1); - enter(cell); + cell.focus(); + await sendKeys({ press: 'Enter' }); expect(getCellEditor(cell)).to.be.ok; - enter(cell); + await sendKeys({ press: 'Enter' }); cell = getContainerCell(grid.$.items, 3, 1); expect(getCellEditor(cell)).to.be.ok; }); - it('should skip non-editable cells when navigating with Shift-Enter', () => { + it('should skip non-editable cells when navigating with Shift-Enter', async () => { let cell = getContainerCell(grid.$.items, 3, 1); - enter(cell); + cell.focus(); + await sendKeys({ press: 'Enter' }); expect(getCellEditor(cell)).to.be.ok; - enter(cell, ['shift']); + await sendKeys({ down: 'Shift' }); + await sendKeys({ press: 'Enter' }); + await sendKeys({ up: 'Shift' }); cell = getContainerCell(grid.$.items, 1, 1); expect(getCellEditor(cell)).to.be.ok; }); - it('should stop editing and focus last edited cell if there are no more editable cells with Enter', () => { + it('should stop editing and focus last edited cell if there are no more editable cells with Enter', async () => { const cell = getContainerCell(grid.$.items, 3, 1); - enter(cell); + cell.focus(); + await sendKeys({ press: 'Enter' }); expect(getCellEditor(cell)).to.be.ok; expect(grid.querySelector('vaadin-grid-pro-edit-text-field')).to.be.ok; - enter(cell); + await sendKeys({ press: 'Enter' }); expect(grid.querySelector('vaadin-grid-pro-edit-text-field')).to.not.be.ok; const target = cell._focusButton || cell; expect(grid.shadowRoot.activeElement).to.equal(target); expect(grid.hasAttribute('navigating')).to.be.true; }); - it('should stop editing and focus last edited cell if there are no more editable cells with Shift-Enter', () => { + it('should stop editing and focus last edited cell if there are no more editable cells with Shift-Enter', async () => { const cell = getContainerCell(grid.$.items, 1, 1); - enter(cell); + cell.focus(); + await sendKeys({ press: 'Enter' }); expect(getCellEditor(cell)).to.be.ok; expect(grid.querySelector('vaadin-grid-pro-edit-text-field')).to.be.ok; - enter(cell, ['shift']); + await sendKeys({ down: 'Shift' }); + await sendKeys({ press: 'Enter' }); + await sendKeys({ up: 'Shift' }); expect(grid.querySelector('vaadin-grid-pro-edit-text-field')).to.not.be.ok; const target = cell._focusButton || cell; expect(grid.shadowRoot.activeElement).to.equal(target); diff --git a/packages/grid-pro/test/keyboard-navigation.common.js b/packages/grid-pro/test/keyboard-navigation.common.js index 5f71dd90ea..c5b9115446 100644 --- a/packages/grid-pro/test/keyboard-navigation.common.js +++ b/packages/grid-pro/test/keyboard-navigation.common.js @@ -1,10 +1,11 @@ import { expect } from '@vaadin/chai-plugins'; -import { enter, esc, fixtureSync, nextFrame, tab } from '@vaadin/testing-helpers'; +import { fixtureSync, nextFrame } from '@vaadin/testing-helpers'; +import { sendKeys } from '@web/test-runner-commands'; import sinon from 'sinon'; import { createItems, dblclick, dragAndDropOver, flushGrid, getCellEditor, getContainerCell } from './helpers.js'; describe('keyboard navigation', () => { - let grid, input; + let grid; beforeEach(() => { grid = fixtureSync(` @@ -27,93 +28,95 @@ describe('keyboard navigation', () => { grid.singleCellEdit = true; }); - it('should focus cell next available for editing within a same row in non-edit mode on Tab', () => { + it('should focus cell next available for editing within a same row in non-edit mode on Tab', async () => { const firstCell = getContainerCell(grid.$.items, 1, 0); dblclick(firstCell._content); - input = getCellEditor(firstCell); const secondCell = getContainerCell(grid.$.items, 1, 1); const spy = sinon.spy(secondCell, 'focus'); - tab(input); + await sendKeys({ press: 'Tab' }); expect(spy.calledOnce).to.be.true; }); - it('should focus previous cell available for editing within a same row in non-edit mode on Shift Tab', () => { + it('should focus previous cell available for editing within a same row in non-edit mode on Shift Tab', async () => { const firstCell = getContainerCell(grid.$.items, 1, 1); dblclick(firstCell._content); - input = getCellEditor(firstCell); const secondCell = getContainerCell(grid.$.items, 1, 0); const spy = sinon.spy(secondCell, 'focus'); - tab(input, ['shift']); + await sendKeys({ down: 'Shift' }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ up: 'Shift' }); expect(spy.calledOnce).to.be.true; }); - it('should focus cell next available for editing on the next row in non-edit mode on Tab', () => { + it('should focus cell next available for editing on the next row in non-edit mode on Tab', async () => { const firstCell = getContainerCell(grid.$.items, 1, 1); dblclick(firstCell._content); - input = getCellEditor(firstCell); const secondCell = getContainerCell(grid.$.items, 2, 0); const spy = sinon.spy(secondCell, 'focus'); - tab(input); + await sendKeys({ press: 'Tab' }); expect(spy.calledOnce).to.be.true; }); - it('should focus previous cell available for editing on the previous in non-edit mode on Shift Tab', () => { + it('should focus previous cell available for editing on the previous in non-edit mode on Shift Tab', async () => { const firstCell = getContainerCell(grid.$.items, 2, 0); dblclick(firstCell._content); - input = getCellEditor(firstCell); const secondCell = getContainerCell(grid.$.items, 1, 1); const spy = sinon.spy(secondCell, 'focus'); - tab(input, ['shift']); + await sendKeys({ down: 'Shift' }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ up: 'Shift' }); expect(spy.calledOnce).to.be.true; }); - it('should focus editable cell on the next row in non-edit mode on Enter, if `enterNextRow` is true', () => { + it('should focus editable cell on the next row in non-edit mode on Enter, if `enterNextRow` is true', async () => { grid.enterNextRow = true; const firstCell = getContainerCell(grid.$.items, 1, 0); - enter(firstCell._content); - input = getCellEditor(firstCell); + firstCell.focus(); + await sendKeys({ press: 'Enter' }); const secondCell = getContainerCell(grid.$.items, 2, 0); const spy = sinon.spy(secondCell, 'focus'); - enter(input); + await sendKeys({ press: 'Enter' }); expect(spy.calledOnce).to.be.true; }); - it('should exit the edit mode for the cell on Enter, if `enterNextRow` is false', () => { + it('should exit the edit mode for the cell on Enter, if `enterNextRow` is false', async () => { const firstCell = getContainerCell(grid.$.items, 1, 0); dblclick(firstCell._content); - input = getCellEditor(firstCell); - enter(input); + await sendKeys({ press: 'Enter' }); expect(getCellEditor(firstCell)).to.be.not.ok; }); - it('should focus editable cell on the previous row in non-edit mode on Shift Enter, if `enterNextRow` is true', () => { + it('should focus editable cell on the previous row in non-edit mode on Shift Enter, if `enterNextRow` is true', async () => { grid.enterNextRow = true; const firstCell = getContainerCell(grid.$.items, 1, 0); - enter(firstCell._content); - input = getCellEditor(firstCell); + firstCell.focus(); + await sendKeys({ press: 'Enter' }); const secondCell = getContainerCell(grid.$.items, 0, 0); const spy = sinon.spy(secondCell, 'focus'); - enter(input, ['shift']); + await sendKeys({ down: 'Shift' }); + await sendKeys({ press: 'Enter' }); + await sendKeys({ up: 'Shift' }); expect(spy.calledOnce).to.be.true; }); - it('should exit the edit mode for the cell on Shift Enter, if `enterNextRow` is false', () => { + it('should exit the edit mode for the cell on Shift Enter, if `enterNextRow` is false', async () => { const firstCell = getContainerCell(grid.$.items, 1, 0); dblclick(firstCell._content); - input = getCellEditor(firstCell); - enter(input, ['shift']); + await sendKeys({ down: 'Shift' }); + await sendKeys({ press: 'Enter' }); + await sendKeys({ up: 'Shift' }); expect(getCellEditor(firstCell)).to.be.not.ok; }); - it('should focus correct editable cell after column reordering', () => { + it('should focus correct editable cell after column reordering', async () => { grid.columnReorderingAllowed = true; const headerContent = [ getContainerCell(grid.$.header, 0, 0)._content, @@ -123,182 +126,175 @@ describe('keyboard navigation', () => { const firstCell = getContainerCell(grid.$.items, 1, 1); dblclick(firstCell._content); - input = getCellEditor(firstCell); const secondCell = getContainerCell(grid.$.items, 1, 0); const spy = sinon.spy(secondCell, 'focus'); - tab(input); + await sendKeys({ press: 'Tab' }); expect(spy.calledOnce).to.be.true; }); - it('should focus correct editable cell when column is hidden', () => { + it('should focus correct editable cell when column is hidden', async () => { const column = grid.querySelector('vaadin-grid-pro-edit-column'); column.hidden = true; + flushGrid(grid); - const firstCell = getContainerCell(grid.$.items, 1, 1); + const firstCell = getContainerCell(grid.$.items, 1, 0); dblclick(firstCell._content); - input = getCellEditor(firstCell); - const secondCell = getContainerCell(grid.$.items, 2, 1); + const secondCell = getContainerCell(grid.$.items, 2, 0); const spy = sinon.spy(secondCell, 'focus'); - tab(input); + await sendKeys({ press: 'Tab' }); expect(spy.calledOnce).to.be.true; }); }); describe('when `singleCellEdit` is false', () => { - it('should focus cell next available for editing within a same row in edit mode on Tab', () => { + it('should focus cell next available for editing within a same row in edit mode on Tab', async () => { const firstCell = getContainerCell(grid.$.items, 1, 0); dblclick(firstCell._content); - input = getCellEditor(firstCell); const secondCell = getContainerCell(grid.$.items, 1, 1); - tab(input); - input = getCellEditor(secondCell); - expect(input).to.be.ok; + await sendKeys({ press: 'Tab' }); + expect(getCellEditor(secondCell)).to.be.ok; }); - it('should not focus cell next available for editing on Tab if default prevented for it', () => { + it('should not focus cell next available for editing on Tab if default prevented for it', async () => { const firstCell = getContainerCell(grid.$.items, 1, 0); dblclick(firstCell._content); - input = getCellEditor(firstCell); - input.addEventListener('keydown', (e) => e.keyCode === 9 && e.preventDefault()); - tab(input); + getCellEditor(firstCell).addEventListener('keydown', (e) => e.keyCode === 9 && e.preventDefault()); + await sendKeys({ press: 'Tab' }); expect(getCellEditor(firstCell)).to.be.ok; }); - it('should not focus cell next available for editing on Tab if `internal-tab` was fired right before it', () => { + it('should not focus cell next available for editing on Tab if `internal-tab` was fired right before it', async () => { const firstCell = getContainerCell(grid.$.items, 1, 0); dblclick(firstCell._content); - input = getCellEditor(firstCell); - input.dispatchEvent(new CustomEvent('internal-tab')); - tab(input); + const editor = getCellEditor(firstCell); + editor.addEventListener('keydown', (e) => { + if (e.key === 'Tab') { + editor.dispatchEvent(new CustomEvent('internal-tab')); + } + }); + await sendKeys({ press: 'Tab' }); expect(getCellEditor(firstCell)).to.be.ok; }); - it('should be possible to switch edit cell on Tab with delay after `internal-tab` was fired', (done) => { + it('should be possible to switch edit cell on Tab with delay after `internal-tab` was fired', async () => { const firstCell = getContainerCell(grid.$.items, 1, 0); dblclick(firstCell._content); - input = getCellEditor(firstCell); - input.dispatchEvent(new CustomEvent('internal-tab')); + getCellEditor(firstCell).dispatchEvent(new CustomEvent('internal-tab')); const secondCell = getContainerCell(grid.$.items, 1, 1); - requestAnimationFrame(() => { - tab(input); - expect(getCellEditor(secondCell)).to.be.ok; - done(); - }); + await nextFrame(); + + await sendKeys({ press: 'Tab' }); + expect(getCellEditor(secondCell)).to.be.ok; }); - it('should focus previous cell available for editing within a same row in edit mode on Shift Tab', () => { + it('should focus previous cell available for editing within a same row in edit mode on Shift Tab', async () => { const firstCell = getContainerCell(grid.$.items, 1, 1); dblclick(firstCell._content); - input = getCellEditor(firstCell); const secondCell = getContainerCell(grid.$.items, 1, 0); - tab(input, ['shift']); - input = getCellEditor(secondCell); - expect(input).to.be.ok; + await sendKeys({ down: 'Shift' }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ up: 'Shift' }); + expect(getCellEditor(secondCell)).to.be.ok; }); - it('should focus cell next available for editing on the next row in edit mode on Tab', () => { + it('should focus cell next available for editing on the next row in edit mode on Tab', async () => { const firstCell = getContainerCell(grid.$.items, 1, 1); dblclick(firstCell._content); - input = getCellEditor(firstCell); const secondCell = getContainerCell(grid.$.items, 2, 0); - tab(input); - input = getCellEditor(secondCell); - expect(input).to.be.ok; + await sendKeys({ press: 'Tab' }); + expect(getCellEditor(secondCell)).to.be.ok; }); - it('should focus previous cell available for editing on the previous row in edit mode on Shift Tab', () => { + it('should focus previous cell available for editing on the previous row in edit mode on Shift Tab', async () => { const firstCell = getContainerCell(grid.$.items, 2, 0); dblclick(firstCell._content); - input = getCellEditor(firstCell); const secondCell = getContainerCell(grid.$.items, 1, 1); - tab(input, ['shift']); - input = getCellEditor(secondCell); - expect(input).to.be.ok; + await sendKeys({ down: 'Shift' }); + await sendKeys({ press: 'Tab' }); + await sendKeys({ up: 'Shift' }); + expect(getCellEditor(secondCell)).to.be.ok; }); - it('should focus editable cell on the next row in edit mode on Enter, if `enterNextRow` is true', () => { + it('should focus editable cell on the next row in edit mode on Enter, if `enterNextRow` is true', async () => { grid.enterNextRow = true; const firstCell = getContainerCell(grid.$.items, 1, 0); - enter(firstCell._content); - input = getCellEditor(firstCell); + firstCell.focus(); + await sendKeys({ press: 'Enter' }); const secondCell = getContainerCell(grid.$.items, 2, 0); - enter(input); - input = getCellEditor(secondCell); - expect(input).to.be.ok; + await sendKeys({ press: 'Enter' }); + expect(getCellEditor(secondCell)).to.be.ok; }); - it('should exit the edit mode for the cell on Enter, if `enterNextRow` is false', () => { + it('should exit the edit mode for the cell on Enter, if `enterNextRow` is false', async () => { const firstCell = getContainerCell(grid.$.items, 1, 0); dblclick(firstCell._content); - input = getCellEditor(firstCell); - enter(input); + await sendKeys({ press: 'Enter' }); expect(getCellEditor(firstCell)).to.be.not.ok; }); - it('should focus editable cell on the previous row in non-edit mode on Shift Enter, if `enterNextRow` is true', () => { + it('should focus editable cell on the previous row in non-edit mode on Shift Enter, if `enterNextRow` is true', async () => { grid.enterNextRow = true; const firstCell = getContainerCell(grid.$.items, 1, 0); - enter(firstCell._content); - input = getCellEditor(firstCell); + firstCell.focus(); + await sendKeys({ press: 'Enter' }); const secondCell = getContainerCell(grid.$.items, 0, 0); - enter(input, ['shift']); - input = getCellEditor(secondCell); - expect(input).to.be.ok; + await sendKeys({ down: 'Shift' }); + await sendKeys({ press: 'Enter' }); + await sendKeys({ up: 'Shift' }); + expect(getCellEditor(secondCell)).to.be.ok; }); it('should not re-focus previous cell in edit mode on Enter, if `enterNextRow` is true', async () => { grid.enterNextRow = true; const firstCell = getContainerCell(grid.$.items, 1, 0); - enter(firstCell._content); - await nextFrame(); - input = getCellEditor(firstCell).inputElement; + firstCell.focus(); + await sendKeys({ press: 'Enter' }); const spy = sinon.spy(firstCell, 'focus'); - enter(input); + await sendKeys({ press: 'Enter' }); expect(spy.called).to.be.false; }); - it('should exit the edit mode for the cell on Shift Enter, if `enterNextRow` is false', () => { + it('should exit the edit mode for the cell on Shift Enter, if `enterNextRow` is false', async () => { const firstCell = getContainerCell(grid.$.items, 1, 0); dblclick(firstCell._content); - input = getCellEditor(firstCell); - enter(input, ['shift']); + await sendKeys({ down: 'Shift' }); + await sendKeys({ press: 'Enter' }); + await sendKeys({ up: 'Shift' }); expect(getCellEditor(firstCell)).to.be.not.ok; }); }); describe('Esc key', () => { - it('should exit the edit mode for the cell when pressing ESC', () => { + it('should exit the edit mode for the cell when pressing ESC', async () => { const firstCell = getContainerCell(grid.$.items, 1, 0); dblclick(firstCell._content); - input = getCellEditor(firstCell); - esc(input); + await sendKeys({ press: 'Escape' }); expect(getCellEditor(firstCell)).to.be.not.ok; }); - it('should re-focus cell after exit edit mode on ESC', () => { + it('should re-focus cell after exit edit mode on ESC', async () => { const firstCell = getContainerCell(grid.$.items, 1, 0); firstCell.focus(); - enter(firstCell._content); - input = getCellEditor(firstCell); + await sendKeys({ press: 'Enter' }); const focusSpy = sinon.spy(firstCell, 'focus'); const stopSpy = sinon.spy(grid, '_stopEdit'); - esc(input); + await sendKeys({ press: 'Escape' }); expect(focusSpy.calledAfter(stopSpy)).to.be.true; }); diff --git a/test/integration/grid-pro-custom-editor.test.js b/test/integration/grid-pro-custom-editor.test.js new file mode 100644 index 0000000000..fef9e8c240 --- /dev/null +++ b/test/integration/grid-pro-custom-editor.test.js @@ -0,0 +1,137 @@ +import { expect } from '@vaadin/chai-plugins'; +import { fixtureSync, nextRender } from '@vaadin/testing-helpers'; +import { sendKeys } from '@web/test-runner-commands'; +import '@vaadin/combo-box'; +import '@vaadin/date-picker'; +import '@vaadin/grid-pro'; +import '@vaadin/grid-pro/vaadin-grid-pro-edit-column.js'; +import '@vaadin/time-picker'; +import { flushGrid, getContainerCell } from '@vaadin/grid-pro/test/helpers.js'; + +describe('grid-pro custom editor', () => { + let grid; + + beforeEach(async () => { + grid = fixtureSync(` + + + + + + + + `); + + grid.items = [ + { firstName: 'Aria', lastName: 'Bailey', birthday: '1984-01-13', status: 'suspended', time: '09:00' }, + { firstName: 'Aaliyah', lastName: 'Butler', birthday: '1977-07-12', status: 'active', time: '09:30' }, + { firstName: 'Eleanor', lastName: 'Price', birthday: '1976-12-14', status: 'suspended', time: '10:00' }, + { firstName: 'Allison', lastName: 'Torres', birthday: '1972-12-04', status: 'active', time: '10:00' }, + { firstName: 'Madeline', lastName: 'Lewis', birthday: '1978-02-03', status: 'active', time: '10:00' }, + ]; + + grid.querySelector('[path="birthday"]').editModeRenderer = (root, _, model) => { + root.innerHTML = ` + + + `; + }; + + grid.querySelector('[path="status"]').editModeRenderer = (root, _, model) => { + if (!root.firstChild) { + const comboBox = document.createElement('vaadin-combo-box'); + comboBox.autoOpenDisabled = true; + comboBox.items = ['active', 'suspended']; + root.appendChild(comboBox); + } + root.firstChild.value = model.item.status; + }; + + grid.querySelector('[path="time"]').editModeRenderer = (root, _, model) => { + root.innerHTML = ` + + `; + }; + + flushGrid(grid); + await nextRender(); + }); + + describe('date-picker', () => { + it('should apply the updated date to the cell when exiting on Tab', async () => { + const cell = getContainerCell(grid.$.items, 0, 2); + cell.focus(); + + await sendKeys({ press: 'Enter' }); + + await sendKeys({ type: '1/12/1984' }); + await sendKeys({ press: 'Tab' }); + + expect(cell._content.textContent).to.equal('1984-01-12'); + }); + + it('should apply the updated date to the cell when exiting on Enter', async () => { + const cell = getContainerCell(grid.$.items, 0, 2); + cell.focus(); + + await sendKeys({ press: 'Enter' }); + + await sendKeys({ type: '1/12/1984' }); + await sendKeys({ press: 'Enter' }); + + expect(cell._content.textContent).to.equal('1984-01-12'); + }); + }); + + describe('combo-box', () => { + it('should apply the updated value to the cell when exiting on Tab', async () => { + const cell = getContainerCell(grid.$.items, 0, 3); + cell.focus(); + + await sendKeys({ press: 'Enter' }); + + await sendKeys({ type: 'active' }); + await sendKeys({ press: 'Tab' }); + + expect(cell._content.textContent).to.equal('active'); + }); + + it('should apply the updated value to the cell when exiting on Enter', async () => { + const cell = getContainerCell(grid.$.items, 0, 3); + cell.focus(); + + await sendKeys({ press: 'Enter' }); + + await sendKeys({ type: 'active' }); + await sendKeys({ press: 'Enter' }); + + expect(cell._content.textContent).to.equal('active'); + }); + }); + + describe('time-picker', () => { + it('should apply the updated time to the cell when exiting on Tab', async () => { + const cell = getContainerCell(grid.$.items, 0, 4); + cell.focus(); + + await sendKeys({ press: 'Enter' }); + + await sendKeys({ type: '10:00' }); + await sendKeys({ press: 'Tab' }); + + expect(cell._content.textContent).to.equal('10:00'); + }); + + it('should apply the updated value to the cell when exiting on Enter', async () => { + const cell = getContainerCell(grid.$.items, 0, 4); + cell.focus(); + + await sendKeys({ press: 'Enter' }); + + await sendKeys({ type: '10:00' }); + await sendKeys({ press: 'Enter' }); + + expect(cell._content.textContent).to.equal('10:00'); + }); + }); +});