diff --git a/scripts/wtr.js b/scripts/wtr.js index 278bd5f3461..5569272b880 100755 --- a/scripts/wtr.js +++ b/scripts/wtr.js @@ -53,7 +53,7 @@ function runTests() { }); // Install dependencies required to run the web-test-runner tests - execSync(`npm install @open-wc/testing @web/dev-server-esbuild @web/test-runner @web/test-runner-playwright @types/mocha sinon --save-dev`, { + execSync(`npm install @open-wc/testing @web/dev-server-esbuild @web/test-runner @web/test-runner-playwright @types/mocha sinon @vaadin/testing-helpers --save-dev`, { cwd: itFolder, stdio: 'inherit' }); diff --git a/vaadin-grid-flow-parent/vaadin-grid-flow-integration-tests/src/main/java/com/vaadin/flow/component/grid/it/GridClientItemToggleEventPage.java b/vaadin-grid-flow-parent/vaadin-grid-flow-integration-tests/src/main/java/com/vaadin/flow/component/grid/it/GridClientItemToggleEventPage.java new file mode 100644 index 00000000000..843395a6719 --- /dev/null +++ b/vaadin-grid-flow-parent/vaadin-grid-flow-integration-tests/src/main/java/com/vaadin/flow/component/grid/it/GridClientItemToggleEventPage.java @@ -0,0 +1,64 @@ +/* + * Copyright 2000-2024 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.flow.component.grid.it; + +import java.util.List; +import java.util.stream.IntStream; + +import com.vaadin.flow.component.grid.Grid; +import com.vaadin.flow.component.grid.GridMultiSelectionModel; +import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.component.html.NativeButton; +import com.vaadin.flow.router.Route; + +import elemental.json.Json; +import elemental.json.JsonObject; + +@Route("vaadin-grid/grid-client-item-toggle-event") +public class GridClientItemToggleEventPage extends Div { + public GridClientItemToggleEventPage() { + Grid grid = new Grid<>(); + grid.addColumn(s -> s); + grid.setItems(createItems(0, 1000)); + grid.setSelectionMode(Grid.SelectionMode.MULTI); + + Div eventLog = new Div(); + eventLog.setId("event-log"); + + ((GridMultiSelectionModel) grid.getSelectionModel()) + .addClientItemToggleListener(event -> { + JsonObject record = Json.createObject(); + record.put("isFromClient", event.isFromClient()); + record.put("item", event.getItem()); + record.put("isSelected", event.isSelected()); + record.put("isShiftKey", event.isShiftKey()); + eventLog.add(new Div(record.toString())); + }); + + NativeButton clearEventLog = new NativeButton("Clear event log", + event -> { + eventLog.removeAll(); + }); + clearEventLog.setId("clear-event-log"); + + add(grid, eventLog, clearEventLog); + } + + private List createItems(int start, int end) { + return IntStream.range(start, end).mapToObj((i) -> "Item " + i) + .toList(); + } +} diff --git a/vaadin-grid-flow-parent/vaadin-grid-flow-integration-tests/src/test/java/com/vaadin/flow/component/grid/it/GridClientItemToggleEventIT.java b/vaadin-grid-flow-parent/vaadin-grid-flow-integration-tests/src/test/java/com/vaadin/flow/component/grid/it/GridClientItemToggleEventIT.java new file mode 100644 index 00000000000..6b5b1539f43 --- /dev/null +++ b/vaadin-grid-flow-parent/vaadin-grid-flow-integration-tests/src/test/java/com/vaadin/flow/component/grid/it/GridClientItemToggleEventIT.java @@ -0,0 +1,98 @@ +/* + * Copyright 2000-2024 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.flow.component.grid.it; + +import java.util.List; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.Keys; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.interactions.Actions; + +import com.vaadin.flow.component.grid.testbench.GridElement; +import com.vaadin.flow.testutil.TestPath; +import com.vaadin.tests.AbstractComponentIT; + +import elemental.json.Json; +import elemental.json.JsonObject; + +@TestPath("vaadin-grid/grid-client-item-toggle-event") +public class GridClientItemToggleEventIT extends AbstractComponentIT { + private GridElement grid; + + @Before + public void init() { + open(); + grid = $(GridElement.class).first(); + } + + @After + public void tearDown() { + shiftUp(); + } + + @Test + public void toggleWithClick_eventIsFired() { + grid.select(0); + assertEvent("Item 0", true, false); + + grid.deselect(0); + assertEvent("Item 0", false, false); + } + + @Test + public void toggleWithShiftClick_eventIsFired() { + shiftDown(); + grid.select(0); + assertEvent("Item 0", true, true); + + grid.deselect(0); + assertEvent("Item 0", false, true); + } + + private void assertEvent(String item, boolean isSelected, + boolean isShiftKey) { + List records = findElements( + By.cssSelector("#event-log > div")); + Assert.assertEquals( + "GridClientItemToggleEvent should be fired only once", 1, + records.size()); + + JsonObject record = Json.parse(records.get(0).getText()); + Assert.assertTrue("isFromClient should be true", + record.getBoolean("isFromClient")); + Assert.assertEquals("Item should match the toggled item", item, + record.getString("item")); + Assert.assertEquals("isSelected should match the selected state", + isSelected, record.getBoolean("isSelected")); + Assert.assertEquals("isShiftKey should match the shift key state", + isShiftKey, record.getBoolean("isShiftKey")); + + findElement(By.id("clear-event-log")).click(); + } + + private void shiftDown() { + new Actions(getDriver()).keyDown(Keys.SHIFT).perform(); + } + + private void shiftUp() { + new Actions(getDriver()).keyUp(Keys.SHIFT).perform(); + } +} diff --git a/vaadin-grid-flow-parent/vaadin-grid-flow-integration-tests/test/grid-connector-selection-multi-mode.test.ts b/vaadin-grid-flow-parent/vaadin-grid-flow-integration-tests/test/grid-connector-selection-multi-mode.test.ts index 95885d7c041..bdfb4ba0bab 100644 --- a/vaadin-grid-flow-parent/vaadin-grid-flow-integration-tests/test/grid-connector-selection-multi-mode.test.ts +++ b/vaadin-grid-flow-parent/vaadin-grid-flow-integration-tests/test/grid-connector-selection-multi-mode.test.ts @@ -1,10 +1,14 @@ import { expect, fixtureSync, nextFrame } from '@open-wc/testing'; -import { init, getBodyCellContent, setRootItems, getHeaderCellContent, FlowGridSelectionColumn, initSelectionColumn } from './shared.js'; +import { sendKeys, sendMouse } from '@web/test-runner-commands'; +import { middleOfNode } from '@vaadin/testing-helpers'; +import { init, setRootItems, FlowGridSelectionColumn, initSelectionColumn } from './shared.js'; import type { FlowGrid } from './shared.js'; describe('grid connector - selection – multi mode', () => { let grid: FlowGrid; let selectionColumn: FlowGridSelectionColumn; + let selectAllCheckbox: HTMLElement; + let selectRowCheckboxes: HTMLElement[]; beforeEach(async () => { grid = fixtureSync(` @@ -25,59 +29,91 @@ describe('grid connector - selection – multi mode', () => { await nextFrame(); grid.$connector.setSelectionMode('MULTI'); - }); - function clickSelectCheckbox(row: number) { - getBodyCellContent(grid, row, 0)!.querySelector('vaadin-checkbox')!.click(); - } + [selectAllCheckbox, ...selectRowCheckboxes] = [...grid.querySelectorAll('vaadin-checkbox')]; + }); - function clickSelectAllCheckbox() { - getHeaderCellContent(selectionColumn).querySelector('vaadin-checkbox')!.click(); + async function mouseClick(element: HTMLElement) { + const { x, y } = middleOfNode(element); + await sendMouse({ type: 'click', position: [Math.floor(x), Math.floor(y)] }); } describe('client to server', () => { - it('should select items', () => { - clickSelectCheckbox(0); + it('should select items', async () => { + await mouseClick(selectRowCheckboxes[0]); expect(grid.selectedItems).to.have.lengthOf(1); expect(grid.selectedItems[0].key).to.equal('0'); - clickSelectCheckbox(1); + await mouseClick(selectRowCheckboxes[1]); expect(grid.selectedItems).to.have.lengthOf(2); expect(grid.selectedItems[0].key).to.equal('0'); expect(grid.selectedItems[1].key).to.equal('1'); }); - it('should deselect items', () => { - clickSelectCheckbox(0); - clickSelectCheckbox(1); + it('should deselect items', async () => { + await mouseClick(selectRowCheckboxes[0]); + await mouseClick(selectRowCheckboxes[1]); - clickSelectCheckbox(1); + await mouseClick(selectRowCheckboxes[1]); expect(grid.selectedItems).to.have.lengthOf(1); expect(grid.selectedItems[0].key).to.equal('0'); - clickSelectCheckbox(0); + await mouseClick(selectRowCheckboxes[0]); expect(grid.selectedItems).to.be.empty; }); - it('should select items on server', () => { - clickSelectCheckbox(0); + it('should select items on server', async () => { + await mouseClick(selectRowCheckboxes[0]); expect(grid.$server.select).to.be.calledWith('0'); }); - it('should deselect items on server', () => { - clickSelectCheckbox(0); - clickSelectCheckbox(0); + it('should deselect items on server', async () => { + await mouseClick(selectRowCheckboxes[0]); + await mouseClick(selectRowCheckboxes[0]); expect(grid.$server.deselect).to.be.calledWith('0'); }); - it('should select all items on server', () => { - clickSelectAllCheckbox(); + it('should set shift key flag on server when selecting with Shift', async () => { + await sendKeys({ down: 'Shift' }); + expect(grid.$server.setShiftKeyDown).to.be.not.called; + + await mouseClick(selectRowCheckboxes[0]); + expect(grid.$server.setShiftKeyDown).to.be.calledOnce; + expect(grid.$server.setShiftKeyDown).to.be.calledWith(true); + expect(grid.$server.setShiftKeyDown).to.be.calledBefore(grid.$server.select); + + grid.$server.setShiftKeyDown.resetHistory(); + + await sendKeys({ up: 'Shift' }); + expect(grid.$server.setShiftKeyDown).to.be.not.called; + }); + + it('should set shift key flag on server when deselecting with Shift', async () => { + await mouseClick(selectRowCheckboxes[0]); + grid.$server.setShiftKeyDown.resetHistory(); + + await sendKeys({ down: 'Shift' }); + expect(grid.$server.setShiftKeyDown).to.be.not.called; + + await mouseClick(selectRowCheckboxes[1]); + expect(grid.$server.setShiftKeyDown).to.be.calledOnce; + expect(grid.$server.setShiftKeyDown).to.be.calledWith(true); + expect(grid.$server.setShiftKeyDown).to.be.calledBefore(grid.$server.deselect); + + grid.$server.setShiftKeyDown.resetHistory(); + + await sendKeys({ up: 'Shift' }); + expect(grid.$server.setShiftKeyDown).to.be.not.called; + }); + + it('should select all items on server', async () => { + await mouseClick(selectAllCheckbox); expect(grid.$server.selectAll).to.be.calledOnce; }); - it('should deselect all items on server', () => { + it('should deselect all items on server', async () => { selectionColumn.selectAll = true; - clickSelectAllCheckbox(); + await mouseClick(selectAllCheckbox); expect(grid.$server.deselectAll).to.be.calledOnce; }); }); diff --git a/vaadin-grid-flow-parent/vaadin-grid-flow-integration-tests/test/shared.ts b/vaadin-grid-flow-parent/vaadin-grid-flow-integration-tests/test/shared.ts index 6163c4a42c2..4de5b50520a 100644 --- a/vaadin-grid-flow-parent/vaadin-grid-flow-integration-tests/test/shared.ts +++ b/vaadin-grid-flow-parent/vaadin-grid-flow-integration-tests/test/shared.ts @@ -40,6 +40,7 @@ export type GridServer = { setParentRequestedRanges: ((ranges: { firstIndex: number; size: number; parentKey: string }[]) => void) & sinon.SinonSpy; sortersChanged: ((sorters: { path: string, direction: string }[]) => void) & sinon.SinonSpy; + setShiftKeyDown: ((shiftKeyDown: boolean) => void) & sinon.SinonSpy; }; export type Item = { @@ -98,7 +99,8 @@ export function init(grid: FlowGrid): void { setDetailsVisible: sinon.spy(), setRequestedRange: sinon.spy(), setParentRequestedRanges: sinon.spy(), - sortersChanged: sinon.spy() + sortersChanged: sinon.spy(), + setShiftKeyDown: sinon.spy() }; gridConnector.initLazy(grid); diff --git a/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/AbstractGridMultiSelectionModel.java b/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/AbstractGridMultiSelectionModel.java index ff085bba7e0..4d9fc7164f6 100644 --- a/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/AbstractGridMultiSelectionModel.java +++ b/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/AbstractGridMultiSelectionModel.java @@ -120,6 +120,9 @@ public void selectFromClient(T item) { fireSelectionEvent(new MultiSelectionEvent<>(getGrid(), getGrid().asMultiSelect(), oldSelection, true)); + ComponentUtil.fireEvent(getGrid(), new ClientItemToggleEvent<>( + getGrid(), item, true, selectionColumn.isShiftKeyDown())); + if (!isSelectAllCheckboxVisible()) { // Skip changing the state of Select All checkbox if it was // meant to be hidden @@ -147,6 +150,9 @@ public void deselectFromClient(T item) { fireSelectionEvent(new MultiSelectionEvent<>(getGrid(), getGrid().asMultiSelect(), oldSelection, true)); + ComponentUtil.fireEvent(getGrid(), new ClientItemToggleEvent<>( + getGrid(), item, false, selectionColumn.isShiftKeyDown())); + long size = getDataProviderSize(); selectionColumn.setSelectAllCheckboxState(false); selectionColumn.setSelectAllCheckboxIndeterminateState( @@ -311,6 +317,15 @@ public Registration addMultiSelectionListener( .selectionChange((MultiSelectionEvent) event))); } + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public Registration addClientItemToggleListener( + ComponentEventListener> listener) { + Objects.requireNonNull(listener, "listener cannot be null"); + return ComponentUtil.addListener(getGrid(), ClientItemToggleEvent.class, + (ComponentEventListener) listener); + } + @Override public void setSelectAllCheckboxVisibility( SelectAllCheckboxVisibility selectAllCheckBoxVisibility) { diff --git a/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/ClientItemToggleEvent.java b/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/ClientItemToggleEvent.java new file mode 100644 index 00000000000..97e307173b7 --- /dev/null +++ b/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/ClientItemToggleEvent.java @@ -0,0 +1,89 @@ +/* + * Copyright 2000-2024 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.flow.component.grid; + +import com.vaadin.flow.component.ComponentEvent; +import com.vaadin.flow.data.selection.MultiSelectionEvent; + +/** + * Event fired when the user toggles the selection state of an item on the + * client-side. + *

+ * This event follows {@link MultiSelectionEvent} and provides details about the + * item that was toggled, its new selection state, and whether the shift key was + * pressed during the selection. This can be helpful for implementing features + * like range selection. + * + * @param + * the grid bean type + * @author Vaadin Ltd + * @see GridMultiSelectionModel#addClientItemToggleListener(com.vaadin.flow.component.ComponentEventListener) + */ +public class ClientItemToggleEvent extends ComponentEvent> { + private final T item; + private final boolean isSelected; + private final boolean isShiftKey; + + /** + * Creates a new item toggle event. + * + * @param source + * the source component + * @param item + * the item that was toggled + * @param isSelected + * {@code true} if the item was selected, {@code false} otherwise + * @param isShiftKey + * {@code true} if the shift key was pressed when the item was + * toggled + */ + public ClientItemToggleEvent(Grid source, T item, boolean isSelected, + boolean isShiftKey) { + super(source, true); + this.item = item; + this.isSelected = isSelected; + this.isShiftKey = isShiftKey; + } + + /** + * Gets the item that was toggled. + * + * @return the item that was toggled + */ + public T getItem() { + return item; + } + + /** + * Gets whether the item was selected. + * + * @return {@code true} if the item was selected, {@code false} if the item + * was deselected + */ + public boolean isSelected() { + return isSelected; + } + + /** + * Gets whether the shift key was pressed when the item was toggled. + * + * @return {@code true} if the shift key was pressed, {@code false} + * otherwise + */ + public boolean isShiftKey() { + return isShiftKey; + } +} diff --git a/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/GridMultiSelectionModel.java b/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/GridMultiSelectionModel.java index 8a91d044b95..fd36fa01ac9 100644 --- a/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/GridMultiSelectionModel.java +++ b/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/GridMultiSelectionModel.java @@ -15,8 +15,10 @@ */ package com.vaadin.flow.component.grid; +import com.vaadin.flow.component.ComponentEventListener; import com.vaadin.flow.data.binder.Binder; import com.vaadin.flow.data.selection.MultiSelect; +import com.vaadin.flow.data.selection.MultiSelectionEvent; import com.vaadin.flow.data.selection.MultiSelectionListener; import com.vaadin.flow.data.selection.SelectionModel; import com.vaadin.flow.function.SerializablePredicate; @@ -90,6 +92,22 @@ public enum SelectAllCheckboxVisibility { Registration addMultiSelectionListener( MultiSelectionListener, T> listener); + /** + * Adds a client item toggle listener that will be called when the user + * toggles the selection state of an item on the client-side. + *

+ * This event follows {@link MultiSelectionEvent} and provides details about + * the item that was toggled, its new selection state, and whether the shift + * key was pressed during the selection. This can be helpful for + * implementing features like range selection. + * + * @param listener + * the client item toggle listener, not {@code null} + * @return a registration for the listener + */ + Registration addClientItemToggleListener( + ComponentEventListener> listener); + /** * Sets the select all checkbox visibility mode. *

diff --git a/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/GridSelectionColumn.java b/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/GridSelectionColumn.java index 8135b028487..b890b68fa81 100644 --- a/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/GridSelectionColumn.java +++ b/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/java/com/vaadin/flow/component/grid/GridSelectionColumn.java @@ -36,6 +36,7 @@ public class GridSelectionColumn extends Component { private final SerializableRunnable selectAllCallback; private final SerializableRunnable deselectAllCallback; + private boolean shiftKeyDown = false; /** * Constructs a new grid selection column configured to use the given @@ -127,6 +128,15 @@ public boolean isDragSelect() { return getElement().getProperty("dragSelect", false); } + @ClientCallable + private void setShiftKeyDown(boolean shiftKeyDown) { + this.shiftKeyDown = shiftKeyDown; + } + + boolean isShiftKeyDown() { + return shiftKeyDown; + } + @ClientCallable private void selectAll() { selectAllCallback.run(); diff --git a/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/resources/META-INF/resources/frontend/vaadin-grid-flow-selection-column.js b/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/resources/META-INF/resources/frontend/vaadin-grid-flow-selection-column.js index c2b821bc86e..22b549971a9 100644 --- a/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/resources/META-INF/resources/frontend/vaadin-grid-flow-selection-column.js +++ b/vaadin-grid-flow-parent/vaadin-grid-flow/src/main/resources/META-INF/resources/frontend/vaadin-grid-flow-selection-column.js @@ -24,7 +24,7 @@ export class GridFlowSelectionColumn extends GridSelectionColumnBaseMixin(GridCo width: { type: String, value: '56px' - } + }, }; } @@ -42,7 +42,6 @@ export class GridFlowSelectionColumn extends GridSelectionColumnBaseMixin(GridCo } } - /** * Override a method from `GridSelectionColumnBaseMixin` to handle the user * selecting all items. @@ -76,6 +75,7 @@ export class GridFlowSelectionColumn extends GridSelectionColumnBaseMixin(GridCo * @override */ _selectItem(item) { + this.$server.setShiftKeyDown(this._shiftKeyDown); this._grid.$connector.doSelection([item], true); } @@ -88,6 +88,7 @@ export class GridFlowSelectionColumn extends GridSelectionColumnBaseMixin(GridCo * @override */ _deselectItem(item) { + this.$server.setShiftKeyDown(this._shiftKeyDown); this._grid.$connector.doDeselection([item], true); // Optimistically update select all state this.selectAll = false; diff --git a/vaadin-grid-flow-parent/vaadin-grid-flow/src/test/java/com/vaadin/flow/component/grid/AbstractGridMultiSelectionModelTest.java b/vaadin-grid-flow-parent/vaadin-grid-flow/src/test/java/com/vaadin/flow/component/grid/AbstractGridMultiSelectionModelTest.java index 83baba9a24a..47ad8bab879 100644 --- a/vaadin-grid-flow-parent/vaadin-grid-flow/src/test/java/com/vaadin/flow/component/grid/AbstractGridMultiSelectionModelTest.java +++ b/vaadin-grid-flow-parent/vaadin-grid-flow/src/test/java/com/vaadin/flow/component/grid/AbstractGridMultiSelectionModelTest.java @@ -23,11 +23,14 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.mockito.InOrder; import org.mockito.Mockito; import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.ComponentEventListener; import com.vaadin.flow.component.grid.Grid.SelectionMode; import com.vaadin.flow.data.provider.*; +import com.vaadin.flow.data.selection.SelectionListener; import com.vaadin.flow.dom.Element; public class AbstractGridMultiSelectionModelTest { @@ -509,6 +512,62 @@ public void deselectFromClient_withItemSelectableProvider_preventsDeselection() Assert.assertEquals(Set.of("foo"), grid.getSelectedItems()); } + @SuppressWarnings("unchecked") + @Test + public void selectFromClient_clientItemToggleEventIsFired() { + grid.setItems("Item 0", "Item 1"); + grid.setSelectionMode(SelectionMode.MULTI); + + AbstractGridMultiSelectionModel selectionModel = (AbstractGridMultiSelectionModel) grid + .getSelectionModel(); + + SelectionListener, String> selectionListenerSpy = Mockito + .spy(SelectionListener.class); + selectionModel.addSelectionListener(selectionListenerSpy); + + ComponentEventListener> clientItemToggleListenerSpy = Mockito + .spy(ComponentEventListener.class); + selectionModel.addClientItemToggleListener(clientItemToggleListenerSpy); + + selectionModel.selectFromClient("Item 0"); + + InOrder inOrder = Mockito.inOrder(selectionListenerSpy, + clientItemToggleListenerSpy); + inOrder.verify(selectionListenerSpy, Mockito.times(1)) + .selectionChange(Mockito.any()); + inOrder.verify(clientItemToggleListenerSpy, Mockito.times(1)) + .onComponentEvent(Mockito.any()); + } + + @SuppressWarnings("unchecked") + @Test + public void deselectFromClient_clientItemToggleEventIsFired() { + grid.setItems("Item 0", "Item 1"); + grid.setSelectionMode(SelectionMode.MULTI); + + AbstractGridMultiSelectionModel selectionModel = (AbstractGridMultiSelectionModel) grid + .getSelectionModel(); + + selectionModel.selectFromClient("Item 0"); + + SelectionListener, String> selectionListenerSpy = Mockito + .spy(SelectionListener.class); + selectionModel.addSelectionListener(selectionListenerSpy); + + ComponentEventListener> clientItemToggleListenerSpy = Mockito + .spy(ComponentEventListener.class); + selectionModel.addClientItemToggleListener(clientItemToggleListenerSpy); + + selectionModel.deselectFromClient("Item 0"); + + InOrder inOrder = Mockito.inOrder(selectionListenerSpy, + clientItemToggleListenerSpy); + inOrder.verify(selectionListenerSpy, Mockito.times(1)) + .selectionChange(Mockito.any()); + inOrder.verify(clientItemToggleListenerSpy, Mockito.times(1)) + .onComponentEvent(Mockito.any()); + } + @Test public void select_withItemSelectableProvider_allowsSelection() { grid.setItems("foo", "bar"); diff --git a/vaadin-grid-flow-parent/vaadin-grid-testbench/src/main/java/com/vaadin/flow/component/grid/testbench/GridElement.java b/vaadin-grid-flow-parent/vaadin-grid-testbench/src/main/java/com/vaadin/flow/component/grid/testbench/GridElement.java index 30a43be8511..46742da4387 100644 --- a/vaadin-grid-flow-parent/vaadin-grid-testbench/src/main/java/com/vaadin/flow/component/grid/testbench/GridElement.java +++ b/vaadin-grid-flow-parent/vaadin-grid-testbench/src/main/java/com/vaadin/flow/component/grid/testbench/GridElement.java @@ -388,7 +388,7 @@ void select(GridTRElement row) { CheckboxElement checkbox = wrapElement(cell.getFirstChildElement(), getCommandExecutor()).wrap(CheckboxElement.class); if (!checkbox.isChecked()) { - checkbox.click(); + checkbox.getWrappedElement().click(); } } else { setActiveItem(row); @@ -418,7 +418,7 @@ void deselect(GridTRElement row) { CheckboxElement checkbox = wrapElement(cell.getFirstChildElement(), getCommandExecutor()).wrap(CheckboxElement.class); if (checkbox.isChecked()) { - checkbox.click(); + checkbox.getWrappedElement().click(); } } else { removeActiveItem(row);