summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--client/src/main/java/com/vaadin/client/connectors/grid/ColumnConnector.java85
-rw-r--r--client/src/main/java/com/vaadin/client/connectors/grid/GridConnector.java136
-rw-r--r--server/src/main/java/com/vaadin/ui/AbstractListing.java28
-rw-r--r--server/src/main/java/com/vaadin/ui/Grid.java408
-rw-r--r--shared/src/main/java/com/vaadin/shared/ui/grid/ColumnState.java25
-rw-r--r--uitest/src/main/java/com/vaadin/tests/components/grid/basics/DataObject.java93
-rw-r--r--uitest/src/main/java/com/vaadin/tests/components/grid/basics/GridBasics.java32
-rw-r--r--uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridBasicsTest.java45
-rw-r--r--uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridContentTest.java17
-rw-r--r--uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridSortingTest.java52
10 files changed, 919 insertions, 2 deletions
diff --git a/client/src/main/java/com/vaadin/client/connectors/grid/ColumnConnector.java b/client/src/main/java/com/vaadin/client/connectors/grid/ColumnConnector.java
new file mode 100644
index 0000000000..4b61b62dc7
--- /dev/null
+++ b/client/src/main/java/com/vaadin/client/connectors/grid/ColumnConnector.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2000-2016 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.client.connectors.grid;
+
+import com.vaadin.client.ServerConnector;
+import com.vaadin.client.annotations.OnStateChange;
+import com.vaadin.client.extensions.AbstractExtensionConnector;
+import com.vaadin.client.widgets.Grid.Column;
+import com.vaadin.shared.data.DataCommunicatorConstants;
+import com.vaadin.shared.ui.Connect;
+import com.vaadin.shared.ui.grid.ColumnState;
+
+import elemental.json.JsonObject;
+import elemental.json.JsonValue;
+
+/**
+ * A connector class for columns of the Grid component.
+ *
+ * @author Vaadin Ltd
+ * @since
+ */
+@Connect(com.vaadin.ui.Grid.Column.class)
+public class ColumnConnector extends AbstractExtensionConnector {
+
+ private Column<JsonValue, JsonObject> column;
+
+ /* This parent is needed because it's no longer available in onUnregister */
+ private GridConnector parent;
+
+ @Override
+ protected void extend(ServerConnector target) {
+ parent = getParent();
+ column = new Column<JsonValue, JsonObject>() {
+
+ @Override
+ public JsonValue getValue(JsonObject row) {
+ return row.getObject(DataCommunicatorConstants.DATA)
+ .get(getState().id);
+ }
+ };
+ getParent().addColumn(column, getState().id);
+ }
+
+ @OnStateChange("caption")
+ void updateCaption() {
+ column.setHeaderCaption(getState().caption);
+ }
+
+ @OnStateChange("sortable")
+ void updateSortable() {
+ column.setSortable(getState().sortable);
+ }
+
+ @Override
+ public void onUnregister() {
+ super.onUnregister();
+
+ parent.removeColumn(column);
+ column = null;
+ }
+
+ @Override
+ public GridConnector getParent() {
+ return (GridConnector) super.getParent();
+ }
+
+ @Override
+ public ColumnState getState() {
+ return (ColumnState) super.getState();
+ }
+
+}
diff --git a/client/src/main/java/com/vaadin/client/connectors/grid/GridConnector.java b/client/src/main/java/com/vaadin/client/connectors/grid/GridConnector.java
new file mode 100644
index 0000000000..6c809f731c
--- /dev/null
+++ b/client/src/main/java/com/vaadin/client/connectors/grid/GridConnector.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2000-2016 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.client.connectors.grid;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.vaadin.client.DeferredWorker;
+import com.vaadin.client.connectors.AbstractListingConnector;
+import com.vaadin.client.data.DataSource;
+import com.vaadin.client.ui.SimpleManagedLayout;
+import com.vaadin.client.widget.grid.selection.ClickSelectHandler;
+import com.vaadin.client.widget.grid.sort.SortEvent;
+import com.vaadin.client.widget.grid.sort.SortOrder;
+import com.vaadin.client.widgets.Grid;
+import com.vaadin.client.widgets.Grid.Column;
+import com.vaadin.shared.data.sort.SortDirection;
+import com.vaadin.shared.ui.Connect;
+import com.vaadin.shared.ui.grid.GridServerRpc;
+
+import elemental.json.JsonObject;
+
+/**
+ * A connector class for the typed Grid component.
+ *
+ * @author Vaadin Ltd
+ * @since
+ */
+@Connect(com.vaadin.ui.Grid.class)
+public class GridConnector extends AbstractListingConnector
+ implements SimpleManagedLayout, DeferredWorker {
+ /* Map to keep track of all added columns */
+ private Map<Column<?, JsonObject>, String> columnToIdMap = new HashMap<>();
+
+ @Override
+ public Grid<JsonObject> getWidget() {
+ return (Grid<JsonObject>) super.getWidget();
+ }
+
+ @Override
+ protected void init() {
+ super.init();
+
+ new ClickSelectHandler<JsonObject>(getWidget());
+ getWidget().addSortHandler(this::handleSortEvent);
+
+ layout();
+ }
+
+ @Override
+ public void setDataSource(DataSource<JsonObject> dataSource) {
+ getWidget().setDataSource(dataSource);
+ }
+
+ /**
+ * Adds a column to the Grid widget. For each column a communication id
+ * stored for client to server communication.
+ *
+ * @param column
+ * column to add
+ * @param id
+ * communication id
+ */
+ public void addColumn(Column<?, JsonObject> column, String id) {
+ assert !columnToIdMap.containsKey(column) && !columnToIdMap
+ .containsValue(id) : "Column with given id already exists.";
+ getWidget().addColumn(column);
+ columnToIdMap.put(column, id);
+ }
+
+ /**
+ * Removes a column from Grid widget. This method also removes communication
+ * id mapping for the column.
+ *
+ * @param column
+ * column to remove
+ */
+ public void removeColumn(Column<?, JsonObject> column) {
+ assert columnToIdMap
+ .containsKey(column) : "Given Column does not exist.";
+ getWidget().removeColumn(column);
+ columnToIdMap.remove(column);
+ }
+
+ @Override
+ public void onUnregister() {
+ super.onUnregister();
+
+ columnToIdMap.clear();
+ }
+
+ @Override
+ public boolean isWorkPending() {
+ return getWidget().isWorkPending();
+ }
+
+ @Override
+ public void layout() {
+ getWidget().onResize();
+ }
+
+ /**
+ * Sends sort information from an event to the server-side of the Grid.
+ *
+ * @param event
+ * the sort event
+ */
+ private void handleSortEvent(SortEvent<JsonObject> event) {
+ List<String> columnIds = new ArrayList<>();
+ List<SortDirection> sortDirections = new ArrayList<>();
+ for (SortOrder so : event.getOrder()) {
+ if (columnToIdMap.containsKey(so.getColumn())) {
+ columnIds.add(columnToIdMap.get(so.getColumn()));
+ sortDirections.add(so.getDirection());
+ }
+ }
+ getRpcProxy(GridServerRpc.class).sort(columnIds.toArray(new String[0]),
+ sortDirections.toArray(new SortDirection[0]),
+ event.isUserOriginated());
+ }
+}
diff --git a/server/src/main/java/com/vaadin/ui/AbstractListing.java b/server/src/main/java/com/vaadin/ui/AbstractListing.java
index 64d55926d5..05f31d7b81 100644
--- a/server/src/main/java/com/vaadin/ui/AbstractListing.java
+++ b/server/src/main/java/com/vaadin/ui/AbstractListing.java
@@ -21,7 +21,7 @@ import com.vaadin.server.data.DataSource;
import com.vaadin.server.data.TypedDataGenerator;
/**
- * Base class for Listing components. Provides common handling for
+ * Base class for {@link Listing} components. Provides common handling for
* {@link DataCommunicator} and {@link TypedDataGenerator}s.
*
* @param <T>
@@ -31,7 +31,31 @@ public abstract class AbstractListing<T> extends AbstractComponent
implements Listing<T> {
/* DataCommunicator for this Listing component */
- private final DataCommunicator<T> dataCommunicator = new DataCommunicator<>();
+ private final DataCommunicator<T> dataCommunicator;
+
+ /**
+ * Constructs an {@link AbstractListing}, extending it with a
+ * {@link DataCommunicator}.
+ */
+ protected AbstractListing() {
+ this(new DataCommunicator<>());
+ }
+
+ /**
+ * Constructs an {@link AbstractListing}, extending it with given
+ * {@link DataCommunicator}.
+ * <p>
+ * <strong>Note:</strong> This method is for creating an
+ * {@link AbstractListing} with a custom {@link DataCommunicator}. In the
+ * common case {@link AbstractListing#AbstractListing()} should be used.
+ *
+ * @param dataCommunicator
+ * a customized data communicator instance
+ */
+ protected AbstractListing(DataCommunicator<T> dataCommunicator) {
+ this.dataCommunicator = dataCommunicator;
+ addExtension(dataCommunicator);
+ }
@Override
public void setDataSource(DataSource<T> dataSource) {
diff --git a/server/src/main/java/com/vaadin/ui/Grid.java b/server/src/main/java/com/vaadin/ui/Grid.java
new file mode 100644
index 0000000000..553d5e0761
--- /dev/null
+++ b/server/src/main/java/com/vaadin/ui/Grid.java
@@ -0,0 +1,408 @@
+/*
+ * Copyright 2000-2016 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.ui;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+import com.vaadin.server.AbstractExtension;
+import com.vaadin.server.KeyMapper;
+import com.vaadin.server.data.DataSource;
+import com.vaadin.server.data.SortOrder;
+import com.vaadin.server.data.TypedDataGenerator;
+import com.vaadin.shared.MouseEventDetails;
+import com.vaadin.shared.data.DataCommunicatorConstants;
+import com.vaadin.shared.data.sort.SortDirection;
+import com.vaadin.shared.ui.grid.ColumnState;
+import com.vaadin.shared.ui.grid.GridConstants.Section;
+import com.vaadin.shared.ui.grid.GridServerRpc;
+
+import elemental.json.Json;
+import elemental.json.JsonObject;
+
+/**
+ * A grid component for displaying tabular data.
+ *
+ * @author Vaadin Ltd
+ * @since
+ *
+ * @param <T>
+ * the grid bean type
+ */
+public class Grid<T> extends AbstractListing<T> {
+
+ private final class GridServerRpcImpl implements GridServerRpc {
+ @Override
+ public void sort(String[] columnIds, SortDirection[] directions,
+ boolean isUserOriginated) {
+ assert columnIds.length == directions.length : "Column and sort direction counts don't match.";
+ sortOrder.clear();
+ if (columnIds.length == 0) {
+ // Grid is not sorted anymore.
+ getDataCommunicator()
+ .setBackEndSorting(Collections.emptyList());
+ getDataCommunicator().setInMemorySorting(null);
+ return;
+ }
+
+ for (int i = 0; i < columnIds.length; ++i) {
+ Column<T, ?> column = columnKeys.get(columnIds[i]);
+ sortOrder.add(new SortOrder<>(column, directions[i]));
+ }
+
+ // Set sort orders
+ // In-memory comparator
+ Comparator<T> comparator = sortOrder.stream()
+ .map(order -> order.getSorted()
+ .getComparator(order.getDirection()))
+ .reduce((x, y) -> 0, Comparator::thenComparing);
+ getDataCommunicator().setInMemorySorting(comparator);
+
+ // Back-end sort properties
+ List<SortOrder<String>> sortProperties = new ArrayList<>();
+ sortOrder.stream()
+ .map(order -> order.getSorted()
+ .getSortOrder(order.getDirection()))
+ .forEach(s -> s.forEach(sortProperties::add));
+ getDataCommunicator().setBackEndSorting(sortProperties);
+ }
+
+ @Override
+ public void itemClick(String rowKey, String columnId,
+ MouseEventDetails details) {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void contextClick(int rowIndex, String rowKey, String columnId,
+ Section section, MouseEventDetails details) {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void columnsReordered(List<String> newColumnOrder,
+ List<String> oldColumnOrder) {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void columnVisibilityChanged(String id, boolean hidden,
+ boolean userOriginated) {
+ // TODO Auto-generated method stub
+ }
+
+ @Override
+ public void columnResized(String id, double pixels) {
+ // TODO Auto-generated method stub
+ }
+ }
+
+ /**
+ * This extension manages the configuration and data communication for a
+ * Column inside of a Grid component.
+ *
+ * @param <T>
+ * the grid bean type
+ * @param <V>
+ * the column value type
+ */
+ public static class Column<T, V> extends AbstractExtension
+ implements TypedDataGenerator<T> {
+
+ private Function<T, V> valueProvider;
+ private Function<SortDirection, Stream<SortOrder<String>>> sortOrderProvider;
+ private Comparator<T> comparator;
+
+ /**
+ * Constructs a new Column configuration with given header caption and
+ * value provider.
+ *
+ * @param caption
+ * the header caption
+ * @param valueType
+ * the type of value
+ * @param valueProvider
+ * the function to get values from data objects
+ */
+ protected Column(String caption, Class<V> valueType,
+ Function<T, V> valueProvider) {
+ Objects.requireNonNull(caption, "Header caption can't be null");
+ Objects.requireNonNull(valueProvider,
+ "Value provider can't be null");
+ Objects.requireNonNull(valueType, "Value type can't be null");
+
+ this.valueProvider = valueProvider;
+ getState().caption = caption;
+ sortOrderProvider = d -> Stream.of();
+
+ if (Comparable.class.isAssignableFrom(valueType)) {
+ comparator = (a, b) -> {
+ Comparable<V> comp = (Comparable<V>) valueProvider.apply(a);
+ return comp.compareTo(valueProvider.apply(b));
+ };
+ getState().sortable = true;
+ } else {
+ getState().sortable = false;
+ }
+ }
+
+ @Override
+ public void generateData(T data, JsonObject jsonObject) {
+ assert getState(
+ false).id != null : "No communication ID set for column "
+ + getState(false).caption;
+
+ if (!jsonObject.hasKey(DataCommunicatorConstants.DATA)) {
+ jsonObject.put(DataCommunicatorConstants.DATA,
+ Json.createObject());
+ }
+ JsonObject obj = jsonObject
+ .getObject(DataCommunicatorConstants.DATA);
+ // Since we dont' have renderers yet, use a dummy toString for data.
+ obj.put(getState(false).id, valueProvider.apply(data).toString());
+ }
+
+ @Override
+ public void destroyData(T data) {
+ }
+
+ @Override
+ protected ColumnState getState() {
+ return getState(true);
+ }
+
+ @Override
+ protected ColumnState getState(boolean markAsDirty) {
+ return (ColumnState) super.getState(markAsDirty);
+ }
+
+ /**
+ * This method extends the given Grid with this Column.
+ *
+ * @param grid
+ * the grid to extend
+ */
+ private void extend(Grid<T> grid) {
+ super.extend(grid);
+ }
+
+ /**
+ * Sets the identifier to use with this Column in communication.
+ *
+ * @param id
+ * the identifier string
+ */
+ private void setId(String id) {
+ Objects.requireNonNull(id, "Communication ID can't be null");
+ getState().id = id;
+ }
+
+ /**
+ * Sets whether the user can sort this column or not.
+ *
+ * @param sortable
+ * {@code true} if the column can be sorted by the user;
+ * {@code false} if not
+ * @return this column
+ */
+ public Column<T, V> setSortable(boolean sortable) {
+ getState().sortable = sortable;
+ return this;
+ }
+
+ /**
+ * Gets whether the user can sort this column or not.
+ *
+ * @return {@code true} if the column can be sorted by the user;
+ * {@code false} if not
+ */
+ public boolean isSortable() {
+ return getState(false).sortable;
+ }
+
+ /**
+ * Sets the header caption for this column.
+ *
+ * @param caption
+ * the header caption
+ *
+ * @return this column
+ */
+ public Column<T, V> setCaption(String caption) {
+ Objects.requireNonNull(caption, "Header caption can't be null");
+ getState().caption = caption;
+ return this;
+ }
+
+ /**
+ * Gets the header caption for this column.
+ *
+ * @return header caption
+ */
+ public String getCaption() {
+ return getState(false).caption;
+ }
+
+ /**
+ * Sets a comparator to use with in-memory sorting with this column.
+ * Sorting with a back-end is done using
+ * {@link Column#setSortProperty(String...)}.
+ *
+ * @param comparator
+ * the comparator to use when sorting data in this column
+ * @return this column
+ */
+ public Column<T, V> setComparator(Comparator<T> comparator) {
+ Objects.requireNonNull(comparator, "Comparator can't be null");
+ this.comparator = comparator;
+ return this;
+ }
+
+ /**
+ * Gets the comparator to use with in-memory sorting for this column
+ * when sorting in the given direction.
+ *
+ * @param sortDirection
+ * the direction this column is sorted by
+ * @return comparator for this column
+ */
+ public Comparator<T> getComparator(SortDirection sortDirection) {
+ Objects.requireNonNull(comparator,
+ "No comparator defined for sorted column.");
+ boolean reverse = sortDirection != SortDirection.ASCENDING;
+ return reverse ? comparator.reversed() : comparator;
+ }
+
+ /**
+ * Sets strings describing back end properties to be used when sorting
+ * this column. This method is a short hand for
+ * {@link #setSortBuilder(Function)} that takes an array of strings and
+ * uses the same sorting direction for all of them.
+ *
+ * @param properties
+ * the array of strings describing backend properties
+ * @return this column
+ */
+ public Column<T, V> setSortProperty(String... properties) {
+ Objects.requireNonNull(properties, "Sort properties can't be null");
+ sortOrderProvider = dir -> Arrays.stream(properties)
+ .map(s -> new SortOrder<>(s, dir));
+ return this;
+ }
+
+ /**
+ * Sets the sort orders when sorting this column. The sort order
+ * provider is a function which provides {@link SortOrder} objects to
+ * describe how to sort by this column.
+ *
+ * @param provider
+ * the function to use when generating sort orders with the
+ * given direction
+ * @return this column
+ */
+ public Column<T, V> setSortOrderProvider(
+ Function<SortDirection, Stream<SortOrder<String>>> provider) {
+ Objects.requireNonNull(provider,
+ "Sort order provider can't be null");
+ sortOrderProvider = provider;
+ return this;
+ }
+
+ /**
+ * Gets the sort orders to use with back-end sorting for this column
+ * when sorting in the given direction.
+ *
+ * @param direction
+ * the sorting direction
+ * @return stream of sort orders
+ */
+ public Stream<SortOrder<String>> getSortOrder(SortDirection direction) {
+ return sortOrderProvider.apply(direction);
+ }
+ }
+
+ private KeyMapper<Column<T, ?>> columnKeys = new KeyMapper<>();
+ private Set<Column<T, ?>> columnSet = new HashSet<>();
+ private List<SortOrder<Column<T, ?>>> sortOrder = new ArrayList<>();
+
+ /**
+ * Constructor for the {@link Grid} component.
+ */
+ public Grid() {
+ setDataSource(DataSource.create());
+ registerRpc(new GridServerRpcImpl());
+ }
+
+ /**
+ * Adds a new column to this {@link Grid} with given header caption and
+ * value provider.
+ *
+ * @param caption
+ * the header caption
+ * @param valueType
+ * the column value class
+ * @param valueProvider
+ * the value provider
+ * @param <V>
+ * the column value type
+ *
+ * @return the new column
+ */
+ public <V> Column<T, V> addColumn(String caption, Class<V> valueType,
+ Function<T, V> valueProvider) {
+ Column<T, V> c = new Column<T, V>(caption, valueType, valueProvider);
+
+ c.extend(this);
+ c.setId(columnKeys.key(c));
+ columnSet.add(c);
+ addDataGenerator(c);
+
+ return c;
+ }
+
+ /**
+ * Removes the given column from this {@link Grid}.
+ *
+ * @param column
+ * the column to remove
+ */
+ public void removeColumn(Column<T, ?> column) {
+ if (columnSet.remove(column)) {
+ columnKeys.remove(column);
+ removeDataGenerator(column);
+ column.remove();
+ }
+ }
+
+ /**
+ * Gets an unmodifiable collection of all columns currently in this
+ * {@link Grid}.
+ *
+ * @return unmodifiable collection of columns
+ */
+ public Collection<Column<T, ?>> getColumns() {
+ return Collections.unmodifiableSet(columnSet);
+ }
+}
diff --git a/shared/src/main/java/com/vaadin/shared/ui/grid/ColumnState.java b/shared/src/main/java/com/vaadin/shared/ui/grid/ColumnState.java
new file mode 100644
index 0000000000..bc04c4acb2
--- /dev/null
+++ b/shared/src/main/java/com/vaadin/shared/ui/grid/ColumnState.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2000-2016 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.shared.ui.grid;
+
+import com.vaadin.shared.communication.SharedState;
+
+public class ColumnState extends SharedState {
+
+ public String caption;
+ public String id;
+ public boolean sortable;
+}
diff --git a/uitest/src/main/java/com/vaadin/tests/components/grid/basics/DataObject.java b/uitest/src/main/java/com/vaadin/tests/components/grid/basics/DataObject.java
new file mode 100644
index 0000000000..ebcdc34d7f
--- /dev/null
+++ b/uitest/src/main/java/com/vaadin/tests/components/grid/basics/DataObject.java
@@ -0,0 +1,93 @@
+package com.vaadin.tests.components.grid.basics;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Random;
+
+class DataObject {
+
+ private static final int ROWS = 1000;
+
+ private Integer rowNumber;
+ private String coordinates;
+ private String htmlString;
+ private Integer smallRandom;
+ private Integer bigRandom;
+ private Date date;
+
+ public Integer getRowNumber() {
+ return rowNumber;
+ }
+
+ public void setRowNumber(Integer rowNumber) {
+ this.rowNumber = rowNumber;
+ }
+
+ public String getCoordinates() {
+ return coordinates;
+ }
+
+ public void setCoordinates(String coordinates) {
+ this.coordinates = coordinates;
+ }
+
+ public String getHtmlString() {
+ return htmlString;
+ }
+
+ public void setHtmlString(String htmlString) {
+ this.htmlString = htmlString;
+ }
+
+ public Integer getSmallRandom() {
+ return smallRandom;
+ }
+
+ public void setSmallRandom(Integer smallRandom) {
+ this.smallRandom = smallRandom;
+ }
+
+ public Integer getBigRandom() {
+ return bigRandom;
+ }
+
+ public void setBigRandom(Integer bigRandom) {
+ this.bigRandom = bigRandom;
+ }
+
+ public Date getDate() {
+ return date;
+ }
+
+ public void setDate(Date date) {
+ this.date = date;
+ }
+
+ static List<DataObject> generateObjects() {
+ List<DataObject> data = new ArrayList<>();
+
+ {
+ Random rand = new Random();
+ rand.setSeed(13334);
+ long timestamp = 0;
+ for (int row = 0; row < ROWS; row++) {
+ DataObject obj = new DataObject();
+ obj.setRowNumber(row);
+ obj.setCoordinates("(" + row + ", " + 0 + ")");
+ obj.setHtmlString("<b>" + row + "</b>");
+ // Random numbers
+ obj.setBigRandom(rand.nextInt());
+ obj.setSmallRandom(rand.nextInt(5));
+
+ obj.setDate(new Date(timestamp));
+ timestamp += 91250000; // a bit over a day, just to get
+ // variation
+
+ data.add(obj);
+ }
+ }
+ return data;
+ }
+
+}
diff --git a/uitest/src/main/java/com/vaadin/tests/components/grid/basics/GridBasics.java b/uitest/src/main/java/com/vaadin/tests/components/grid/basics/GridBasics.java
new file mode 100644
index 0000000000..8ef56e7f64
--- /dev/null
+++ b/uitest/src/main/java/com/vaadin/tests/components/grid/basics/GridBasics.java
@@ -0,0 +1,32 @@
+package com.vaadin.tests.components.grid.basics;
+
+import java.util.Date;
+import java.util.List;
+
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.tests.components.AbstractTestUIWithLog;
+import com.vaadin.ui.Grid;
+
+public class GridBasics extends AbstractTestUIWithLog {
+
+ private Grid<DataObject> grid;
+
+ @Override
+ protected void setup(VaadinRequest request) {
+ List<DataObject> data = DataObject.generateObjects();
+
+ // Create grid
+ grid = new Grid<>();
+ grid.setItems(data);
+
+ grid.addColumn("Row Number", Integer.class, DataObject::getRowNumber);
+ grid.addColumn("Date", Date.class, DataObject::getDate);
+ grid.addColumn("HTML String", String.class, DataObject::getHtmlString);
+ grid.addColumn("Big Random", Integer.class, DataObject::getBigRandom);
+ grid.addColumn("Small Random", Integer.class,
+ DataObject::getSmallRandom);
+
+ addComponent(grid);
+ }
+
+}
diff --git a/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridBasicsTest.java b/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridBasicsTest.java
new file mode 100644
index 0000000000..63f2c60f21
--- /dev/null
+++ b/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridBasicsTest.java
@@ -0,0 +1,45 @@
+package com.vaadin.tests.components.grid.basics;
+
+import java.util.List;
+import java.util.stream.Stream;
+
+import org.junit.Before;
+import org.openqa.selenium.remote.DesiredCapabilities;
+
+import com.vaadin.testbench.elements.GridElement;
+import com.vaadin.testbench.parallel.Browser;
+import com.vaadin.tests.tb3.MultiBrowserTest;
+
+/**
+ * Base class for all {@link GridBasics} UI tests
+ */
+public abstract class GridBasicsTest extends MultiBrowserTest {
+
+ /* Identical List of test data */
+ private List<DataObject> testData;
+
+ @Override
+ public List<DesiredCapabilities> getBrowsersToTest() {
+ // Most tests are run with only one browser.
+ return getBrowserCapabilities(Browser.PHANTOMJS);
+ }
+
+ @Override
+ protected Class<?> getUIClass() {
+ return GridBasics.class;
+ }
+
+ @Before
+ public void setUp() {
+ openTestURL();
+ testData = DataObject.generateObjects();
+ }
+
+ protected GridElement getGrid() {
+ return $(GridElement.class).first();
+ }
+
+ protected Stream<DataObject> getTestData() {
+ return testData.stream();
+ }
+}
diff --git a/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridContentTest.java b/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridContentTest.java
new file mode 100644
index 0000000000..315282439a
--- /dev/null
+++ b/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridContentTest.java
@@ -0,0 +1,17 @@
+package com.vaadin.tests.components.grid.basics;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class GridContentTest extends GridBasicsTest {
+
+ @Test(expected = AssertionError.class)
+ public void testHtmlRenderer() {
+ DataObject first = getTestData().findFirst().orElse(null);
+ Assert.assertEquals("Text content should match row number",
+ first.getRowNumber().toString(),
+ getGrid().getCell(0, 2).getText());
+ Assert.assertEquals("HTML content did not match", first.getHtmlString(),
+ getGrid().getCell(0, 2).getAttribute("innerHTML"));
+ }
+}
diff --git a/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridSortingTest.java b/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridSortingTest.java
new file mode 100644
index 0000000000..e9cd744719
--- /dev/null
+++ b/uitest/src/test/java/com/vaadin/tests/components/grid/basics/GridSortingTest.java
@@ -0,0 +1,52 @@
+package com.vaadin.tests.components.grid.basics;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.openqa.selenium.Keys;
+import org.openqa.selenium.remote.DesiredCapabilities;
+
+public class GridSortingTest extends GridBasicsTest {
+
+ public static final Comparator<DataObject> BIG_RANDOM = Comparator
+ .comparing(DataObject::getBigRandom);
+ public static final Comparator<DataObject> SMALL_RANDOM = Comparator
+ .comparing(DataObject::getSmallRandom);
+
+ @Override
+ public List<DesiredCapabilities> getBrowsersToTest() {
+ return getBrowsersSupportingShiftClick();
+ }
+
+ @Test
+ public void testSortBySingleColumnByUser() {
+ getGrid().getHeaderCell(0, 3).click();
+ int i = 0;
+ for (Integer rowNumber : getTestData().sorted(BIG_RANDOM)
+ .map(DataObject::getRowNumber).limit(5)
+ .collect(Collectors.toList())) {
+ Assert.assertEquals(
+ "Grid was not sorted as expected, row number mismatch",
+ rowNumber.toString(), getGrid().getCell(i++, 0).getText());
+ }
+ }
+
+ @Test
+ public void testSortByMultipleColumnsByUser() {
+ getGrid().getHeaderCell(0, 4).click();
+ getGrid().getHeaderCell(0, 3).click(15, 15, Keys.SHIFT);
+
+ int i = 0;
+ for (Integer rowNumber : getTestData()
+ .sorted(SMALL_RANDOM.thenComparing(BIG_RANDOM))
+ .map(DataObject::getRowNumber).limit(5)
+ .collect(Collectors.toList())) {
+ Assert.assertEquals(
+ "Grid was not sorted as expected, row number mismatch",
+ rowNumber.toString(), getGrid().getCell(i++, 0).getText());
+ }
+ }
+}