@Test
public void observableList() throws Exception {

    ObservableList<String> source = FXCollections.observableArrayList();
    source.addAll("alpha", "beta");
    ObservableList<String> dest = ObservableMirrors.mirrorList(source, gate);
    Queue<ListChangeListener.Change<? extends String>> changes = new LinkedList<>();
    dest.addListener(changes::add);
    // Expect a single change with two added items.
    source.addAll("gamma", "delta");
    assertEquals(1, gate.getTaskQueueSize());
    gate.waitAndRun();
    ListChangeListener.Change<? extends String> change = changes.poll();
    change.next();
    assertTrue(change.wasAdded());
    assertEquals(2, change.getAddedSize());
    assertEquals(ImmutableList.of("gamma", "delta"), change.getAddedSubList());
    // Expect four queued changes with coherent/correct deltas unaffected by later changes to the src list.
    source.remove(3);   // remove delta
    source.add("phi");
    source.remove(3);   // remove phi
    source.add("epsilon");
    assertEquals(4, gate.getTaskQueueSize());
    gate.waitAndRun();
    gate.waitAndRun();
    change = changes.poll();
    change.next();
    assertTrue(change.wasRemoved());
    assertEquals(1, change.getRemovedSize());
    assertEquals("delta", change.getRemoved().get(0));
    change = changes.poll();
    change.next();
    assertTrue(change.wasAdded());
    assertEquals("phi", change.getAddedSubList().get(0));
    gate.waitAndRun();
    changes.poll();
    gate.waitAndRun();
    changes.poll();
    // Expect a set on the source list to show up as a replace event on the dest list.
    source.set(0, "zero");
    gate.waitAndRun();
    change = changes.poll();
    change.next();
    assertTrue(change.toString(), change.wasReplaced());
    assertEquals(0, change.getFrom());
    assertEquals(1, change.getTo());
    assertEquals("zero", change.getAddedSubList().get(0));
}