ReactFX
ReactFX copied to clipboard
List change event inconsistencies
I've noticed some strange behavior with list change events when using EventStreams
or LiveList
.
-
LiveList
does not support update events. - The
ListChangeListener.Change::next
contract is not followed.
import static org.hamcrest.Matchers.contains;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.verify;
import java.util.function.Consumer;
import javafx.beans.Observable;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.reactfx.EventStreams;
import org.reactfx.collection.ListChange;
import org.reactfx.collection.LiveList;
@RunWith(MockitoJUnitRunner.class)
public class LiveListUpdateTest {
private static class Data {
private final Property<String> string = new SimpleObjectProperty<>();
public String getString() {
return string.getValue();
}
public Property<String> stringProperty() {
return string;
}
}
@Captor
ArgumentCaptor<ListChangeListener.Change<? extends Data>> originalChange;
@Captor
ArgumentCaptor<ListChangeListener.Change<? extends Data>> eventStreamChange;
@Captor
ArgumentCaptor<ListChange<? extends Data>> liveListChange;
@Captor
ArgumentCaptor<ListChangeListener.Change<? extends String>> mappedChange;
@Mock
ListChangeListener<? super Data> originalListener;
@Mock
Consumer<ListChangeListener.Change<? extends Data>> eventStreamListener;
@Mock
Consumer<ListChange<? extends Data>> liveListListener;
@Mock
ListChangeListener<? super String> mappedListener;
Data data;
ObservableList<Data> updateableList;
ObservableList<String> mappedList;
@Before
public void before() {
data = new Data();
updateableList = FXCollections.observableArrayList(d -> new Observable[] { d.stringProperty() });
updateableList.add(data);
mappedList = LiveList.map(updateableList, Data::getString);
}
@Test
public void initialChangeState() {
updateableList.addListener(originalListener);
EventStreams.changesOf(updateableList).subscribe(eventStreamListener);
data.stringProperty().setValue("foo");
verify(originalListener).onChanged(originalChange.capture());
assertTrue(originalChange.getValue().next());
verify(eventStreamListener).accept(eventStreamChange.capture());
assertTrue(eventStreamChange.getValue().next()); // Fails
}
@Test
public void liveListShouldNotInvalidateInitialChangeState() {
updateableList.addListener(originalListener);
LiveList.changesOf(updateableList).subscribe(liveListListener);
data.stringProperty().setValue("foo");
verify(originalListener).onChanged(originalChange.capture());
assertTrue(originalChange.getValue().next()); // Fails
verify(liveListListener).accept(liveListChange.capture());
assertEquals(1, liveListChange.getValue().getModificationCount());
}
@Test
public void mappedUpdates() {
updateableList.addListener(originalListener);
mappedList.addListener(mappedListener);
data.stringProperty().setValue("foo");
assertThat(mappedList, contains("foo"));
verify(originalListener).onChanged(originalChange.capture());
assertTrue(originalChange.getValue().next()); // Fails
assertTrue(originalChange.getValue().wasUpdated());
assertFalse(originalChange.getValue().next());
verify(mappedListener).onChanged(mappedChange.capture());
assertTrue(originalChange.getValue().next()); // Fails
assertTrue(mappedChange.getValue().wasUpdated()); // Fails
assertFalse(originalChange.getValue().next());
}
}
Hi Jeffrey, thank you for the report and test cases. I could include them in ReactFX tests, if you could rewrite them without using Mockito.