blob: a7fcd39b8379d730752a9914761e6d38af6e423b [file] [log] [blame]
/*****************************************************************************
* Copyright (c) 2020 Dirk Fauth.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Dirk Fauth <dirk.fauth@googlemail.com> - Initial API and implementation
*****************************************************************************/
package org.eclipse.nebula.widgets.nattable.tree;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.eclipse.nebula.widgets.nattable.coordinate.Range;
import org.eclipse.nebula.widgets.nattable.data.IColumnPropertyAccessor;
import org.eclipse.nebula.widgets.nattable.data.ListDataProvider;
import org.eclipse.nebula.widgets.nattable.data.ReflectiveColumnPropertyAccessor;
import org.eclipse.nebula.widgets.nattable.dataset.person.Person;
import org.eclipse.nebula.widgets.nattable.dataset.person.PersonService;
import org.eclipse.nebula.widgets.nattable.hideshow.RowHideShowLayer;
import org.eclipse.nebula.widgets.nattable.hideshow.command.RowHideCommand;
import org.eclipse.nebula.widgets.nattable.hideshow.event.HideRowPositionsEvent;
import org.eclipse.nebula.widgets.nattable.hideshow.event.ShowRowPositionsEvent;
import org.eclipse.nebula.widgets.nattable.layer.DataLayer;
import org.eclipse.nebula.widgets.nattable.layer.cell.ColumnLabelAccumulator;
import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer;
import org.eclipse.nebula.widgets.nattable.test.fixture.layer.LayerListenerFixture;
import org.eclipse.nebula.widgets.nattable.tree.command.TreeCollapseAllCommand;
import org.eclipse.nebula.widgets.nattable.tree.command.TreeExpandAllCommand;
import org.eclipse.nebula.widgets.nattable.tree.command.TreeExpandCollapseCommand;
import org.junit.Before;
import org.junit.Test;
public class TreeLayerTest {
private TreeLayer treeLayer;
private LayerListenerFixture listener;
@Before
public void setup() {
String[] propertyNames = { "lastName", "firstName", "gender", "married", "birthday" };
IColumnPropertyAccessor<Person> columnPropertyAccessor = new ReflectiveColumnPropertyAccessor<>(propertyNames);
List<Person> values = PersonService.getFixedPersons();
ListDataProvider<Person> bodyDataProvider = new ListDataProvider<>(values, columnPropertyAccessor);
DataLayer bodyDataLayer = new DataLayer(bodyDataProvider);
// simply apply labels for every column by index
bodyDataLayer.setConfigLabelAccumulator(new ColumnLabelAccumulator());
ITreeRowModel<Person> treeRowModel = new TreeRowModel<>(new PersonTreeData(values));
RowHideShowLayer hideShowLayer = new RowHideShowLayer(bodyDataLayer);
SelectionLayer selectionLayer = new SelectionLayer(hideShowLayer);
this.treeLayer = new TreeLayer(selectionLayer, treeRowModel);
this.listener = new LayerListenerFixture();
this.treeLayer.addLayerListener(this.listener);
}
@Test
public void shouldHaveTreeNodes() {
assertTrue(this.treeLayer.isTreeColumn(0));
assertTrue(this.treeLayer.getModel().hasChildren(0));
assertTrue(this.treeLayer.getModel().hasChildren(8));
}
@Test
public void shouldCollapseAll() {
this.treeLayer.collapseAll();
assertEquals(2, this.treeLayer.getRowCount());
assertEquals(1, this.listener.getEventsCount());
assertTrue(this.listener.containsInstanceOf(HideRowPositionsEvent.class));
HideRowPositionsEvent event = (HideRowPositionsEvent) this.listener.getReceivedEvents().get(0);
assertEquals(2, event.getRowPositionRanges().size());
Iterator<Range> iterator = event.getRowPositionRanges().iterator();
assertEquals(new Range(1, 8), iterator.next());
assertEquals(new Range(9, 18), iterator.next());
}
@Test
public void shouldHideAndCollapse() {
this.treeLayer.doCommand(new RowHideCommand(this.treeLayer, 2));
assertEquals(17, this.treeLayer.getRowCount());
this.listener.clearReceivedEvents();
this.treeLayer.doCommand(new TreeExpandCollapseCommand(0));
// 10 Simpsons + 1 Flanders parent node
assertEquals(11, this.treeLayer.getRowCount());
assertTrue(this.treeLayer.hasHiddenRows());
int[] hiddenRowIndexes = this.treeLayer.getHiddenRowIndexesArray();
assertEquals(7, hiddenRowIndexes.length);
assertEquals(1, hiddenRowIndexes[0]);
assertEquals(2, hiddenRowIndexes[1]);
assertEquals(3, hiddenRowIndexes[2]);
assertEquals(4, hiddenRowIndexes[3]);
assertEquals(5, hiddenRowIndexes[4]);
assertEquals(6, hiddenRowIndexes[5]);
assertEquals(7, hiddenRowIndexes[6]);
assertEquals(1, this.listener.getEventsCount());
assertTrue(this.listener.containsInstanceOf(HideRowPositionsEvent.class));
HideRowPositionsEvent receivedEvent = (HideRowPositionsEvent) this.listener.getReceivedEvent(HideRowPositionsEvent.class);
Collection<Range> rowPositionRanges = receivedEvent.getRowPositionRanges();
assertEquals(1, rowPositionRanges.size());
assertEquals(new Range(1, 7), rowPositionRanges.iterator().next());
int[] rowIndexes = receivedEvent.getRowIndexes();
// only 6 indexes hidden as 1 index was already hidden in an underlying
// layer before
assertEquals(6, rowIndexes.length);
assertEquals(1, rowIndexes[0]);
assertEquals(3, rowIndexes[1]);
assertEquals(4, rowIndexes[2]);
assertEquals(5, rowIndexes[3]);
assertEquals(6, rowIndexes[4]);
assertEquals(7, rowIndexes[5]);
}
@Test
public void shouldCollapseAllWithHidden() {
this.treeLayer.doCommand(new RowHideCommand(this.treeLayer, 2));
assertEquals(17, this.treeLayer.getRowCount());
this.listener.clearReceivedEvents();
this.treeLayer.doCommand(new TreeCollapseAllCommand());
// 1 Simpsons parent node + 1 Flanders parent node
assertEquals(2, this.treeLayer.getRowCount());
int[] hiddenRowIndexes = this.treeLayer.getHiddenRowIndexesArray();
assertEquals(16, hiddenRowIndexes.length);
assertEquals(1, this.listener.getEventsCount());
assertTrue(this.listener.containsInstanceOf(HideRowPositionsEvent.class));
HideRowPositionsEvent receivedEvent = (HideRowPositionsEvent) this.listener.getReceivedEvent(HideRowPositionsEvent.class);
Collection<Range> rowPositionRanges = receivedEvent.getRowPositionRanges();
assertEquals(2, rowPositionRanges.size());
Iterator<Range> iterator = rowPositionRanges.iterator();
assertEquals(new Range(1, 7), iterator.next());
assertEquals(new Range(8, 17), iterator.next());
int[] rowIndexes = receivedEvent.getRowIndexes();
// 15 because 1 row was already hidden
assertEquals(15, rowIndexes.length);
assertEquals(1, rowIndexes[0]);
assertEquals(3, rowIndexes[1]);
assertEquals(4, rowIndexes[2]);
assertEquals(5, rowIndexes[3]);
assertEquals(6, rowIndexes[4]);
assertEquals(7, rowIndexes[5]);
assertEquals(9, rowIndexes[6]);
assertEquals(10, rowIndexes[7]);
assertEquals(11, rowIndexes[8]);
assertEquals(12, rowIndexes[9]);
assertEquals(13, rowIndexes[10]);
assertEquals(14, rowIndexes[11]);
assertEquals(15, rowIndexes[12]);
assertEquals(16, rowIndexes[13]);
assertEquals(17, rowIndexes[14]);
}
@Test
public void shouldExpandWithHidden() {
this.treeLayer.doCommand(new RowHideCommand(this.treeLayer, 2));
// collapse
this.treeLayer.doCommand(new TreeExpandCollapseCommand(0));
this.listener.clearReceivedEvents();
// expand again
this.treeLayer.doCommand(new TreeExpandCollapseCommand(0));
// 10 Simpsons + 7 Flanders (1 Flanders still hidden)
assertEquals(17, this.treeLayer.getRowCount());
assertFalse(this.treeLayer.hasHiddenRows());
assertTrue(this.treeLayer.isRowIndexHidden(2));
int[] hiddenRowIndexes = this.treeLayer.getHiddenRowIndexesArray();
assertEquals(0, hiddenRowIndexes.length);
assertEquals(1, this.listener.getEventsCount());
assertTrue(this.listener.containsInstanceOf(ShowRowPositionsEvent.class));
ShowRowPositionsEvent receivedEvent = (ShowRowPositionsEvent) this.listener.getReceivedEvent(ShowRowPositionsEvent.class);
Collection<Range> rowPositionRanges = receivedEvent.getRowPositionRanges();
assertEquals(1, rowPositionRanges.size());
assertEquals(new Range(1, 7), rowPositionRanges.iterator().next());
int[] rowIndexes = receivedEvent.getRowIndexes();
// only 6 indexes shown again as 1 index is still hidden in an
// underlying layer
assertEquals(6, rowIndexes.length);
assertEquals(1, rowIndexes[0]);
assertEquals(3, rowIndexes[1]);
assertEquals(4, rowIndexes[2]);
assertEquals(5, rowIndexes[3]);
assertEquals(6, rowIndexes[4]);
assertEquals(7, rowIndexes[5]);
}
@Test
public void shouldExpandAllWithHidden() {
this.treeLayer.doCommand(new RowHideCommand(this.treeLayer, 2));
// collapse
this.treeLayer.doCommand(new TreeCollapseAllCommand());
this.listener.clearReceivedEvents();
// expand again
this.treeLayer.doCommand(new TreeExpandAllCommand());
// 10 Simpsons + 7 Flanders (1 Flanders still hidden)
assertEquals(17, this.treeLayer.getRowCount());
assertTrue(this.treeLayer.isRowIndexHidden(2));
int[] hiddenRowIndexes = this.treeLayer.getHiddenRowIndexesArray();
assertEquals(0, hiddenRowIndexes.length);
assertEquals(1, this.listener.getEventsCount());
assertTrue(this.listener.containsInstanceOf(ShowRowPositionsEvent.class));
ShowRowPositionsEvent receivedEvent = (ShowRowPositionsEvent) this.listener.getReceivedEvent(ShowRowPositionsEvent.class);
Collection<Range> rowPositionRanges = receivedEvent.getRowPositionRanges();
Iterator<Range> iterator = rowPositionRanges.iterator();
assertEquals(new Range(1, 7), iterator.next());
assertEquals(new Range(8, 17), iterator.next());
int[] rowIndexes = receivedEvent.getRowIndexes();
// 15 because 1 row was already hidden
assertEquals(15, rowIndexes.length);
assertEquals(1, rowIndexes[0]);
assertEquals(3, rowIndexes[1]);
assertEquals(4, rowIndexes[2]);
assertEquals(5, rowIndexes[3]);
assertEquals(6, rowIndexes[4]);
assertEquals(7, rowIndexes[5]);
assertEquals(9, rowIndexes[6]);
assertEquals(10, rowIndexes[7]);
assertEquals(11, rowIndexes[8]);
assertEquals(12, rowIndexes[9]);
assertEquals(13, rowIndexes[10]);
assertEquals(14, rowIndexes[11]);
assertEquals(15, rowIndexes[12]);
assertEquals(16, rowIndexes[13]);
assertEquals(17, rowIndexes[14]);
}
private static class PersonTreeData implements ITreeData<Person> {
private List<Person> values;
private Map<String, List<Person>> parentMapping;
private Map<String, Person> firstElementMapping = new HashMap<>();
public PersonTreeData(List<Person> values) {
this.values = values;
// first we need to sort by lastname to ensure all elements with the
// same lastname are grouped together
this.values.sort(Comparator.comparing(Person::getLastName));
// then we build up the mapping from lastname to all child elements
this.parentMapping = values.stream().collect(Collectors.groupingBy(Person::getLastName));
// identify the parent node element
String current = null;
for (Person p : this.values) {
if (!p.getLastName().equals(current)) {
this.firstElementMapping.put(p.getLastName(), p);
current = p.getLastName();
}
}
// remove the parent node element from the children list
this.firstElementMapping.forEach((lastname, parent) -> {
this.parentMapping.get(lastname).remove(parent);
});
}
@Override
public int getDepthOfData(Person object) {
Person firstElement = this.firstElementMapping.get(object.getLastName());
return firstElement.equals(object) ? 0 : 1;
}
@Override
public int getDepthOfData(int index) {
return getDepthOfData(getDataAtIndex(index));
}
@Override
public Person getDataAtIndex(int index) {
if (!isValidIndex(index)) {
return null;
}
return this.values.get(index);
}
@Override
public int indexOf(Person child) {
return this.values.indexOf(child);
}
@Override
public boolean hasChildren(Person object) {
if (object != null && getDepthOfData(object) == 0) {
List<Person> children = this.parentMapping.get(object.getLastName());
return children != null && !children.isEmpty();
}
return false;
}
@Override
public boolean hasChildren(int index) {
return hasChildren(getDataAtIndex(index));
}
@Override
public List<Person> getChildren(Person object) {
if (object != null && getDepthOfData(object) == 0) {
return this.parentMapping.get(object.getLastName());
}
return new ArrayList<>(0);
}
@Override
public List<Person> getChildren(Person object, boolean fullDepth) {
// since we only support one level here it is the same as
// getChildren(PersonWithAddress)
return getChildren(object);
}
@Override
public List<Person> getChildren(int index) {
return getChildren(getDataAtIndex(index));
}
@Override
public int getElementCount() {
return this.values.size();
}
@Override
public boolean isValidIndex(int index) {
return (!(index < 0) && index < this.values.size());
}
}
}