TomasMikula / ReactFX

Reactive event streams, observable values and more for JavaFX.
BSD 2-Clause "Simplified" License
376 stars 47 forks source link

List change event inconsistencies #51

Open JeffFaer opened 8 years ago

JeffFaer commented 8 years ago

I've noticed some strange behavior with list change events when using EventStreams or LiveList.

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());
  }
}
TomasMikula commented 8 years ago

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.