Skip to content

Commit

Permalink
feat: add ClientItemToggleEvent to notify when user toggles an item (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
vursen authored Dec 16, 2024
1 parent 57f2c64 commit b981411
Show file tree
Hide file tree
Showing 12 changed files with 422 additions and 30 deletions.
2 changes: 1 addition & 1 deletion scripts/wtr.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
});
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String> 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<String>) 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<String> createItems(int start, int end) {
return IntStream.range(start, end).mapToObj((i) -> "Item " + i)
.toList();
}
}
Original file line number Diff line number Diff line change
@@ -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<WebElement> 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();
}
}
Original file line number Diff line number Diff line change
@@ -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(`
Expand All @@ -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;
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -311,6 +317,15 @@ public Registration addMultiSelectionListener(
.selectionChange((MultiSelectionEvent) event)));
}

@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public Registration addClientItemToggleListener(
ComponentEventListener<ClientItemToggleEvent<T>> listener) {
Objects.requireNonNull(listener, "listener cannot be null");
return ComponentUtil.addListener(getGrid(), ClientItemToggleEvent.class,
(ComponentEventListener) listener);
}

@Override
public void setSelectAllCheckboxVisibility(
SelectAllCheckboxVisibility selectAllCheckBoxVisibility) {
Expand Down
Loading

0 comments on commit b981411

Please sign in to comment.