client, explorer: Address review comments

This commit is contained in:
Andras Slemmer 2016-09-30 15:48:26 +01:00
parent 9a212a8714
commit 4da73e28c7
17 changed files with 481 additions and 341 deletions

View File

@ -1,19 +1,46 @@
package com.r3corda.client.fxutils
import co.paralleluniverse.common.util.VisibleForTesting
import javafx.collections.ListChangeListener
import javafx.collections.ObservableList
import javafx.collections.transformation.TransformationList
import java.util.*
import kotlin.comparisons.compareValues
/**
* [ConcatenatedList] takes a list of lists and concatenates them. Any change to the underlying lists or the outer list
* is propagated as expected.
*/
class ConcatenatedList<A>(sourceList: ObservableList<ObservableList<A>>) : TransformationList<A, ObservableList<A>>(sourceList) {
class WrappedObservableList<A>(
// A wrapper for input lists so we hash differently even if a list is reused in the input.
@VisibleForTesting
internal class WrappedObservableList<A>(
val observableList: ObservableList<A>
)
private val indexMap = HashMap<WrappedObservableList<out A>, Pair<Int, ListChangeListener<A>>>()
// Maps each list index to the offset of the next nested element
// Example: { {"a", "b"}, {"c"} } -> { 2, 3 }
private val nestedIndexOffsets = ArrayList<Int>(sourceList.size)
// First let's clarify some concepts as it's easy to confuse which list we're handling where.
// Throughout the commentary and the code we will refer to the lists contained in the source list as "nested lists",
// their elements "nested elements", whereas the containing list will be called "source list", its elements being
// the nested lists. We will refer to the final concatenated list as "result list".
// We maintain two bookkeeping data-structures.
// 'indexMap' stores a mapping from nested lists to their respective source list indices and listeners.
// 'nestedIndexOffsets' stores for each nested list the index of the *next* nested element in the result list.
// We also have a helper function 'startingOffsetOf', which given an index of a nested list in the source list
// returns the index of its first element in the result list, or where it would be if it had one.
// For example:
// nested lists = { {"a", "b"}, {"c"}, {} }
// result list = { "a", "b", "c" }
// indexMap = [ {"c"} -> (1, listener),
// {} -> (2, listener),
// {"a", "b"} -> (0, listener) ]
// nestedIndexOffsets = { 2, 3, 3 }
// startingOffsetOf = { 0, 2, 3 }
// Note that similar to 'nestedIndexOffsets', 'startingOffsetOf' also isn't a one-to-one mapping because of
// potentially several empty nested lists.
@VisibleForTesting
internal val indexMap = HashMap<WrappedObservableList<out A>, Pair<Int, ListChangeListener<A>>>()
@VisibleForTesting
internal val nestedIndexOffsets = ArrayList<Int>(sourceList.size)
init {
var offset = 0
sourceList.forEachIndexed { index, observableList ->
@ -24,26 +51,40 @@ class ConcatenatedList<A>(sourceList: ObservableList<ObservableList<A>>) : Trans
}
}
private fun startingOffsetOf(listIndex: Int): Int {
if (listIndex == 0) {
return 0
} else {
return nestedIndexOffsets[listIndex - 1]
}
}
// This is where we create a listener for a *nested* list. Note that 'indexMap' doesn't need to be adjusted on any
// of these changes as the indices of nested lists don't change, just their contents.
private fun createListener(wrapped: WrappedObservableList<A>): ListChangeListener<A> {
val listener = ListChangeListener<A> { change ->
beginChange()
while (change.next()) {
if (change.wasPermutated()) {
val listIndex = indexMap[wrapped]!!.first
// If a nested list is permuted we simply offset the permutation by the startingOffsetOf the list.
// Note that we don't need to invalidate offsets.
val nestedListIndex = indexMap[wrapped]!!.first
val permutation = IntArray(change.to)
if (listIndex >= firstInvalidatedPosition) {
recalculateOffsets()
}
val startingOffset = startingOffsetOf(listIndex)
val startingOffset = startingOffsetOf(nestedListIndex)
// firstTouched is the result list index of the beginning of the permutation.
val firstTouched = startingOffset + change.from
// We first set the non-permuted indices.
for (i in 0..firstTouched - 1) {
permutation[i] = i
}
for (i in startingOffset + change.from..startingOffset + change.to - 1) {
// Then the permuted ones.
for (i in firstTouched .. startingOffset + change.to - 1) {
permutation[startingOffset + i] = change.getPermutation(i)
}
nextPermutation(firstTouched, startingOffset + change.to, permutation)
} else if (change.wasUpdated()) {
// If a nested element is updated we simply propagate the update by offsetting the nested element index
// by the startingOffsetOf the nested list.
val listIndex = indexMap[wrapped]!!.first
val startingOffset = startingOffsetOf(listIndex)
for (i in change.from..change.to - 1) {
@ -51,14 +92,21 @@ class ConcatenatedList<A>(sourceList: ObservableList<ObservableList<A>>) : Trans
}
} else {
if (change.wasRemoved()) {
// If nested elements are removed we again simply offset the change. We also need to invalidate
// 'nestedIndexOffsets' unless we removed the same number of elements as we added
val listIndex = indexMap[wrapped]!!.first
invalidateOffsets(listIndex)
if (!(change.wasAdded() && change.addedSize == change.removedSize)) {
invalidateOffsets(listIndex)
}
val startingOffset = startingOffsetOf(listIndex)
nextRemove(startingOffset + change.from, change.removed)
}
if (change.wasAdded()) {
// Similar logic to remove.
val listIndex = indexMap[wrapped]!!.first
invalidateOffsets(listIndex)
if (!(change.wasRemoved() && change.addedSize == change.removedSize)) {
invalidateOffsets(listIndex)
}
val startingOffset = startingOffsetOf(listIndex)
nextAdd(startingOffset + change.from, startingOffset + change.to)
}
@ -71,23 +119,28 @@ class ConcatenatedList<A>(sourceList: ObservableList<ObservableList<A>>) : Trans
return listener
}
// Tracks the first position where the *nested* offset is invalid
private var firstInvalidatedPosition = sourceList.size
// This is where we handle changes to the *source* list.
override fun sourceChanged(change: ListChangeListener.Change<out ObservableList<A>>) {
beginChange()
while (change.next()) {
if (change.wasPermutated()) {
// Update indexMap
// If the source list was permuted we adjust 'nestedIndexOffsets' and translate the permutation to apply
// to the nested elements.
// For example:
// original list: { {"a", "b"}, {"c", "d"}, {} }
// original permutation: { 2, 1, 0 }
// permuted list: { {}, {"c", "d"}, {"a", "b"} }
// translated permutation: { 2, 3, 0, 1 }
// First we apply the permutation to the 'indexMap'
val iterator = indexMap.iterator()
for (entry in iterator) {
val (wrapped, pair) = entry
val (index, listener) = pair
val (index, listener) = entry.value
if (index >= change.from && index < change.to) {
entry.setValue(Pair(change.getPermutation(index), listener))
}
}
// Calculate the permuted sublist of nestedIndexOffsets
// We apply the permutation to the relevant part of 'nestedIndexOffsets'.
val newSubNestedIndexOffsets = IntArray(change.to - change.from)
val firstTouched = if (change.from == 0) 0 else nestedIndexOffsets[change.from - 1]
var currentOffset = firstTouched
@ -95,6 +148,7 @@ class ConcatenatedList<A>(sourceList: ObservableList<ObservableList<A>>) : Trans
currentOffset += source[change.from + i].size
newSubNestedIndexOffsets[i] = currentOffset
}
// Now we create the permutation array for the result list.
val concatenatedPermutation = IntArray(newSubNestedIndexOffsets.last())
// Set the non-permuted part
var offset = 0
@ -124,13 +178,14 @@ class ConcatenatedList<A>(sourceList: ObservableList<ObservableList<A>>) : Trans
throw UnsupportedOperationException("Updates not supported")
} else {
if (change.wasRemoved()) {
// Update indexMap
// If nested lists were removed we iterate over 'indexMap' and adjust the indices accordingly,
// remove listeners and remove relevant mappings as well. We also invalidate nested offsets.
val iterator = indexMap.iterator()
for (entry in iterator) {
val (wrapped, pair) = entry
val (index, listener) = pair
val removeEnd = change.from + change.removedSize
if (index >= change.from) {
val removeEnd = change.from + change.removedSize
if (index < removeEnd) {
wrapped.observableList.removeListener(listener)
iterator.remove()
@ -162,13 +217,9 @@ class ConcatenatedList<A>(sourceList: ObservableList<ObservableList<A>>) : Trans
indexMap[wrapped] = Pair(change.from + sublistIndex, createListener(wrapped))
}
invalidateOffsets(change.from)
// We recalculate offsets early as we need the range anyway.
recalculateOffsets()
nextAdd(startingOffsetOf(change.from), nestedIndexOffsets[change.to - 1])
for (i in change.from .. change.to - 1) {
source[i].addListener { change: ListChangeListener.Change<out A> ->
}
}
}
}
recalculateOffsets()
@ -176,18 +227,12 @@ class ConcatenatedList<A>(sourceList: ObservableList<ObservableList<A>>) : Trans
endChange()
}
// Tracks the first position where the *nested* offset is invalid
private var firstInvalidatedPosition = sourceList.size
private fun invalidateOffsets(index: Int) {
firstInvalidatedPosition = Math.min(firstInvalidatedPosition, index)
}
private fun startingOffsetOf(listIndex: Int): Int {
if (listIndex == 0) {
return 0
} else {
return nestedIndexOffsets[listIndex - 1]
}
}
private fun recalculateOffsets() {
if (firstInvalidatedPosition < source.size) {
val firstInvalid = firstInvalidatedPosition

View File

@ -8,6 +8,7 @@ import javafx.collections.ObservableList
import javafx.collections.transformation.TransformationList
import org.eclipse.jetty.server.Authentication
import java.util.*
import kotlin.test.assertEquals
/**
* [FlattenedList] flattens the passed in list of [ObservableValue]s so that changes in individual updates to the values
@ -106,7 +107,7 @@ class FlattenedList<A>(val sourceList: ObservableList<out ObservableValue<out A>
}
}
endChange()
require(sourceList.size == indexMap.size)
assertEquals(sourceList.size, indexMap.size)
}
override fun get(index: Int) = sourceList.get(index).value

View File

@ -17,6 +17,11 @@ class MapValuesList<K, A, C> private constructor(
) : ObservableList<C> by exposedList {
companion object {
/**
* [create] is the factory of [MapValuesList].
* @param sourceMap The source map.
* @param assemble The function to be called for map each entry to construct the final list elements.
*/
fun <K, A, C> create(sourceMap: ObservableMap<K, A>, assemble: (Map.Entry<K, A>) -> C): MapValuesList<K, A, C> {
val backingList = FXCollections.observableArrayList<Map.Entry<K, A>>(sourceMap.entries.sortedBy { it.key!!.hashCode() })
return MapValuesList(sourceMap, backingList, backingList.map { assemble(it) })

View File

@ -9,6 +9,7 @@ import java.util.*
/**
* This is a variant of [EasyBind.map] where the mapped list is backed, therefore the mapping function will only be run
* when an element is inserted or updated.
* Use this instead of [EasyBind.map] to trade off memory vs CPU, or if (god forbid) the mapped function is side-effecting.
*/
class MappedList<A, B>(list: ObservableList<A>, val function: (A) -> B) : TransformationList<B, A>(list) {
private val backingList = ArrayList<B>(list.size)
@ -23,12 +24,14 @@ class MappedList<A, B>(list: ObservableList<A>, val function: (A) -> B) : Transf
beginChange()
while (change.next()) {
if (change.wasPermutated()) {
// Note how we don't re-run the mapping function on a permutation. If we supported mapIndexed we would
// have to.
val from = change.from
val to = change.to
val permutation = IntArray(to, { change.getPermutation(it) })
val permutation = IntArray(to) { change.getPermutation(it) }
val permutedSubList = ArrayList<B?>(to - from)
permutedSubList.addAll(Collections.nCopies(to - from, null))
for (i in 0 .. (to - from - 1)) {
for (i in 0.until(to - from)) {
permutedSubList[permutation[from + i]] = backingList[from + i]
}
permutedSubList.forEachIndexed { i, element ->
@ -42,7 +45,7 @@ class MappedList<A, B>(list: ObservableList<A>, val function: (A) -> B) : Transf
if (change.wasRemoved()) {
val removePosition = change.from
val removed = ArrayList<B>(change.removedSize)
for (i in 0 .. change.removedSize - 1) {
for (i in 0.until(change.removedSize)) {
removed.add(backingList.removeAt(removePosition))
}
nextRemove(change.from, removed)
@ -50,7 +53,7 @@ class MappedList<A, B>(list: ObservableList<A>, val function: (A) -> B) : Transf
if (change.wasAdded()) {
val addStart = change.from
val addEnd = change.to
for (i in addStart .. addEnd - 1) {
for (i in addStart.until(addEnd)) {
backingList.add(i, function(change.list[i]))
}
nextAdd(addStart, addEnd)

View File

@ -12,6 +12,12 @@ import rx.Observable
* Simple utilities for converting an [rx.Observable] into a javafx [ObservableValue]/[ObservableList]
*/
/**
* [foldToObservableValue] takes an [rx.Observable] stream and creates an [ObservableValue] out of it.
* @param initial The initial value of the returned observable.
* @param folderFun The transformation function to be called on the observable value when a new element is emitted on
* the stream.
*/
fun <A, B> Observable<A>.foldToObservableValue(initial: B, folderFun: (A, B) -> B): ObservableValue<B> {
val result = SimpleObjectProperty<B>(initial)
subscribe {
@ -22,6 +28,13 @@ fun <A, B> Observable<A>.foldToObservableValue(initial: B, folderFun: (A, B) ->
return result
}
/**
* [foldToObservableList] takes an [rx.Observable] stream and creates an [ObservableList] out of it, while maintaining
* an accumulator.
* @param initialAccumulator The initial value of the accumulator.
* @param folderFun The transformation function to be called on the observable list when a new element is emitted on
* the stream, which should modify the list as needed.
*/
fun <A, B, C> Observable<A>.foldToObservableList(
initialAccumulator: C, folderFun: (A, C, ObservableList<B>) -> C
): ObservableList<B> {
@ -39,14 +52,21 @@ fun <A, B, C> Observable<A>.foldToObservableList(
}
/**
* This variant simply exposes all events in the list, in order of arrival.
* [recordInSequence] records incoming events on the [rx.Observable] in sequence.
*/
fun <A> Observable<A>.foldToObservableList(): ObservableList<A> {
fun <A> Observable<A>.recordInSequence(): ObservableList<A> {
return foldToObservableList(Unit) { newElement, _unit, list ->
list.add(newElement)
}
}
/**
* [foldToObservableMap] takes an [rx.Observable] stream and creates an [ObservableMap] out of it, while maintaining
* an accumulator.
* @param initialAccumulator The initial value of the accumulator.
* @param folderFun The transformation function to be called on the observable map when a new element is emitted on
* the stream, which should modify the map as needed.
*/
fun <A, B, K, C> Observable<A>.foldToObservableMap(
initialAccumulator: C, folderFun: (A, C, ObservableMap<K, B>) -> C
): ObservableMap<K, out B> {
@ -68,7 +88,7 @@ fun <A, B, K, C> Observable<A>.foldToObservableMap(
* @param toKey Function retrieving the key to associate with.
* @param merge The function to be called if there is an existing element at the key.
*/
fun <A, K> Observable<A>.foldToObservableMap(
fun <A, K> Observable<A>.recordAsAssociation(
toKey: (A) -> K,
merge: (K, oldValue: A, newValue: A) -> A = { _key, _oldValue, newValue -> newValue }
): ObservableMap<K, out A> {

View File

@ -28,9 +28,17 @@ fun <A, B> ObservableValue<out A>.map(function: (A) -> B): ObservableValue<B> =
/**
* val dogs: ObservableList<Dog> = (..)
* val dogOwners: ObservableList<Person> = dogs.map { it.owner }
*
* @param cached If true the results of the mapped function are cached in a backing list. If false each get() will
* re-run the function.
*/
fun <A, B> ObservableList<out A>.map(function: (A) -> B): ObservableList<B> = MappedList(this, function)
fun <A, B> ObservableList<out A>.mapNonBacked(function: (A) -> B): ObservableList<B> = EasyBind.map(this, function)
fun <A, B> ObservableList<out A>.map(cached: Boolean = true, function: (A) -> B): ObservableList<B> {
if (cached) {
return MappedList(this, function)
} else {
return EasyBind.map(this, function)
}
}
/**
* val aliceHeight: ObservableValue<Long> = (..)
@ -126,12 +134,14 @@ fun <A, B> ObservableList<out A>.foldObservable(initial: B, folderFunction: (B,
fun <A> ObservableList<out ObservableValue<out A>>.flatten(): ObservableList<A> = FlattenedList(this)
/**
* data class Person(val height: ObservableValue<Long>)
* val people: List<Person> = listOf(alice, bob)
* val heights: ObservableList<Long> = people.map(Person::height).sequence()
*/
fun <A> List<ObservableValue<out A>>.sequence(): ObservableList<A> = FlattenedList(FXCollections.observableArrayList(this))
fun <A> Collection<ObservableValue<out A>>.sequence(): ObservableList<A> = FlattenedList(FXCollections.observableArrayList(this))
/**
* data class Person(val height: Long)
* val people: ObservableList<Person> = (..)
* val nameToHeight: ObservableMap<String, Long> = people.associateBy(Person::name) { name, person -> person.height }
*/
@ -209,6 +219,7 @@ fun <A> ObservableList<ObservableList<A>>.concatenate(): ObservableList<A> {
}
/**
* data class Person(val name: String, val managerName: String)
* val people: ObservableList<Person> = (..)
* val managerEmployeeMapping: ObservableList<Pair<Person, ObservableList<Person>>> =
* people.leftOuterJoin(people, Person::name, Person::managerName) { manager, employees -> Pair(manager, employees) }
@ -225,6 +236,18 @@ fun <A : Any, B : Any, C, K : Any> ObservableList<A>.leftOuterJoin(
}.concatenate()
}
/**
* data class Person(name: String, favouriteSpecies: Species)
* data class Animal(name: String, species: Species)
* val people: ObservableList<Person> = (..)
* val animals: ObservableList<Animal> = (..)
* val peopleToFavouriteAnimals: ObservableMap<Species, Pair<ObservableList<Person>, ObservableList<Animal>>> =
* people.leftOuterJoin(animals, Person::favouriteSpecies, Animal::species)
*
* This is the most general left join, given a joining key it returns for each key a pair of relevant elements from the
* left and right tables. It is "left outer" in the sense that all members of the left table are guaranteed to be in
* the result, but this may not be the case for the right table.
*/
fun <A : Any, B : Any, K : Any> ObservableList<A>.leftOuterJoin(
rightTable: ObservableList<B>,
leftToJoinKey: (A) -> K,

View File

@ -1,6 +1,7 @@
package com.r3corda.client.model
import com.r3corda.client.fxutils.foldToObservableList
import com.r3corda.client.fxutils.recordInSequence
import com.r3corda.contracts.asset.Cash
import com.r3corda.core.contracts.ContractState
import com.r3corda.core.contracts.StateAndRef

View File

@ -5,6 +5,7 @@ import com.r3corda.client.fxutils.getObservableValue
import com.r3corda.core.contracts.ContractState
import com.r3corda.core.contracts.StateAndRef
import com.r3corda.core.contracts.StateRef
import com.r3corda.client.fxutils.recordInSequence
import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.protocols.StateMachineRunId
import com.r3corda.core.transactions.SignedTransaction

View File

@ -1,15 +1,17 @@
package com.r3corda.client.fxutils
import javafx.collections.FXCollections
import javafx.collections.ObservableList
import org.junit.Before
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.fail
class AggregatedListTest {
var sourceList = FXCollections.observableArrayList<Int>()
var aggregatedList = AggregatedList(sourceList, { it % 3 }) { mod3, group -> Pair(mod3, group) }
var replayedList = ReplayedList(aggregatedList)
lateinit var sourceList: ObservableList<Int>
lateinit var aggregatedList: ObservableList<Pair<Int, ObservableList<Int>>>
lateinit var replayedList: ObservableList<Pair<Int, ObservableList<Int>>>
@Before
fun setup() {
@ -20,22 +22,22 @@ class AggregatedListTest {
@Test
fun addWorks() {
require(replayedList.size == 0) { "Aggregation is empty is source list is" }
assertEquals(replayedList.size, 0)
sourceList.add(9)
require(replayedList.size == 1) { "Aggregation list has one element if one was added to source list" }
require(replayedList[0]!!.first == 0)
assertEquals(replayedList.size, 1)
assertEquals(replayedList[0]!!.first, 0)
sourceList.add(8)
require(replayedList.size == 2) { "Aggregation list has two elements if two were added to source list with different keys" }
assertEquals(replayedList.size, 2)
sourceList.add(6)
require(replayedList.size == 2) { "Aggregation list's size doesn't change if element with existing key is added" }
assertEquals(replayedList.size, 2)
replayedList.forEach {
when (it.first) {
0 -> require(it.second.toSet() == setOf(6, 9))
2 -> require(it.second.size == 1)
0 -> assertEquals(it.second.toSet(), setOf(6, 9))
2 -> assertEquals(it.second.size, 1)
else -> fail("No aggregation expected with key ${it.first}")
}
}
@ -45,51 +47,51 @@ class AggregatedListTest {
fun removeWorks() {
sourceList.addAll(0, 1, 2, 3, 4)
require(replayedList.size == 3)
assertEquals(replayedList.size, 3)
replayedList.forEach {
when (it.first) {
0 -> require(it.second.toSet() == setOf(0, 3))
1 -> require(it.second.toSet() == setOf(1, 4))
2 -> require(it.second.toSet() == setOf(2))
0 -> assertEquals(it.second.toSet(), setOf(0, 3))
1 -> assertEquals(it.second.toSet(), setOf(1, 4))
2 -> assertEquals(it.second.toSet(), setOf(2))
else -> fail("No aggregation expected with key ${it.first}")
}
}
sourceList.remove(4)
require(replayedList.size == 3)
assertEquals(replayedList.size, 3)
replayedList.forEach {
when (it.first) {
0 -> require(it.second.toSet() == setOf(0, 3))
1 -> require(it.second.toSet() == setOf(1))
2 -> require(it.second.toSet() == setOf(2))
0 -> assertEquals(it.second.toSet(), setOf(0, 3))
1 -> assertEquals(it.second.toSet(), setOf(1))
2 -> assertEquals(it.second.toSet(), setOf(2))
else -> fail("No aggregation expected with key ${it.first}")
}
}
sourceList.remove(2, 4)
require(replayedList.size == 2)
assertEquals(replayedList.size, 2)
replayedList.forEach {
when (it.first) {
0 -> require(it.second.toSet() == setOf(0))
1 -> require(it.second.toSet() == setOf(1))
0 -> assertEquals(it.second.toSet(), setOf(0))
1 -> assertEquals(it.second.toSet(), setOf(1))
else -> fail("No aggregation expected with key ${it.first}")
}
}
sourceList.removeAll(0, 1)
require(replayedList.size == 0)
assertEquals(replayedList.size, 0)
}
@Test
fun multipleElementsWithSameHashWorks() {
sourceList.addAll(0, 0)
require(replayedList.size == 1)
assertEquals(replayedList.size, 1)
replayedList.forEach {
when (it.first) {
0 -> {
require(it.second.size == 2)
require(it.second[0] == 0)
require(it.second[1] == 0)
assertEquals(it.second.size, 2)
assertEquals(it.second[0], 0)
assertEquals(it.second[1], 0)
}
else -> fail("No aggregation expected with key ${it.first}")
}

View File

@ -1,14 +1,17 @@
package com.r3corda.client.fxutils
import javafx.collections.FXCollections
import javafx.collections.ObservableList
import javafx.collections.ObservableMap
import org.junit.Before
import org.junit.Test
import kotlin.test.assertEquals
class AssociatedListTest {
var sourceList = FXCollections.observableArrayList(0)
var associatedList = AssociatedList(sourceList, { it % 3 }) { mod3, number -> number }
var replayedMap = ReplayedMap(associatedList)
lateinit var sourceList: ObservableList<Int>
lateinit var associatedList: ObservableMap<Int, Int>
lateinit var replayedMap: ObservableMap<Int, Int>
@Before
fun setup() {
@ -19,55 +22,55 @@ class AssociatedListTest {
@Test
fun addWorks() {
require(replayedMap.size == 1)
require(replayedMap[0] == 0)
assertEquals(replayedMap.size, 1)
assertEquals(replayedMap[0], 0)
sourceList.add(2)
require(replayedMap.size == 2)
require(replayedMap[0] == 0)
require(replayedMap[2] == 2)
assertEquals(replayedMap.size, 2)
assertEquals(replayedMap[0], 0)
assertEquals(replayedMap[2], 2)
sourceList.add(0, 4)
require(replayedMap.size == 3)
require(replayedMap[0] == 0)
require(replayedMap[2] == 2)
require(replayedMap[1] == 4)
assertEquals(replayedMap.size, 3)
assertEquals(replayedMap[0], 0)
assertEquals(replayedMap[2], 2)
assertEquals(replayedMap[1], 4)
}
@Test
fun removeWorks() {
sourceList.addAll(2, 4)
require(replayedMap.size == 3)
assertEquals(replayedMap.size, 3)
sourceList.removeAt(0)
require(replayedMap.size == 2)
require(replayedMap[2] == 2)
require(replayedMap[1] == 4)
assertEquals(replayedMap.size, 2)
assertEquals(replayedMap[2], 2)
assertEquals(replayedMap[1], 4)
sourceList.add(1, 12)
require(replayedMap.size == 3)
require(replayedMap[2] == 2)
require(replayedMap[1] == 4)
require(replayedMap[0] == 12)
assertEquals(replayedMap.size, 3)
assertEquals(replayedMap[2], 2)
assertEquals(replayedMap[1], 4)
assertEquals(replayedMap[0], 12)
sourceList.clear()
require(replayedMap.size == 0)
assertEquals(replayedMap.size, 0)
}
@Test
fun updateWorks() {
sourceList.addAll(2, 4)
require(replayedMap.size == 3)
assertEquals(replayedMap.size, 3)
sourceList[1] = 5
require(replayedMap.size == 3)
require(replayedMap[0] == 0)
require(replayedMap[2] == 5)
require(replayedMap[1] == 4)
assertEquals(replayedMap.size, 3)
assertEquals(replayedMap[0], 0)
assertEquals(replayedMap[2], 5)
assertEquals(replayedMap[1], 4)
sourceList.removeAt(1)
require(replayedMap.size == 2)
require(replayedMap[0] == 0)
require(replayedMap[1] == 4)
assertEquals(replayedMap.size, 2)
assertEquals(replayedMap[0], 0)
assertEquals(replayedMap[1], 4)
}
}

View File

@ -5,12 +5,13 @@ import javafx.collections.ObservableList
import org.junit.Before
import org.junit.Test
import java.util.*
import kotlin.test.assertEquals
class ConcatenatedListTest {
var sourceList = FXCollections.observableArrayList<ObservableList<String>>(FXCollections.observableArrayList("hello"))
var concatenatedList = ConcatenatedList(sourceList)
var replayedList = ReplayedList(concatenatedList)
lateinit var sourceList: ObservableList<ObservableList<String>>
lateinit var concatenatedList: ConcatenatedList<String>
lateinit var replayedList: ObservableList<String>
@Before
fun setup() {
@ -19,42 +20,70 @@ class ConcatenatedListTest {
replayedList = ReplayedList(concatenatedList)
}
// A helper function for tests that checks internal invariants.
fun <A> ConcatenatedList<A>.checkInvariants() {
assertEquals(nestedIndexOffsets.size, source.size)
var currentOffset = 0
for (i in 0 .. source.size - 1) {
currentOffset += source[i].size
assertEquals(nestedIndexOffsets[i], currentOffset)
}
assertEquals(indexMap.size, source.size)
for (entry in indexMap) {
val (wrapped, pair) = entry
val index = pair.first
val foundListIndices = ArrayList<Int>()
source.forEachIndexed { i, list ->
if (wrapped.observableList == list) {
foundListIndices.add(i)
}
}
require(foundListIndices.any { it == index })
}
}
@Test
fun addWorks() {
require(replayedList.size == 1)
require(replayedList[0] == "hello")
concatenatedList.checkInvariants()
assertEquals(replayedList.size, 1)
assertEquals(replayedList[0], "hello")
sourceList.add(FXCollections.observableArrayList("a", "b"))
require(replayedList.size == 3)
require(replayedList[0] == "hello")
require(replayedList[1] == "a")
require(replayedList[2] == "b")
concatenatedList.checkInvariants()
assertEquals(replayedList.size, 3)
assertEquals(replayedList[0], "hello")
assertEquals(replayedList[1], "a")
assertEquals(replayedList[2], "b")
sourceList.add(1, FXCollections.observableArrayList("c"))
require(replayedList.size == 4)
require(replayedList[0] == "hello")
require(replayedList[1] == "c")
require(replayedList[2] == "a")
require(replayedList[3] == "b")
concatenatedList.checkInvariants()
assertEquals(replayedList.size, 4)
assertEquals(replayedList[0], "hello")
assertEquals(replayedList[1], "c")
assertEquals(replayedList[2], "a")
assertEquals(replayedList[3], "b")
sourceList[0].addAll("d", "e")
require(replayedList.size == 6)
require(replayedList[0] == "hello")
require(replayedList[1] == "d")
require(replayedList[2] == "e")
require(replayedList[3] == "c")
require(replayedList[4] == "a")
require(replayedList[5] == "b")
concatenatedList.checkInvariants()
assertEquals(replayedList.size, 6)
assertEquals(replayedList[0], "hello")
assertEquals(replayedList[1], "d")
assertEquals(replayedList[2], "e")
assertEquals(replayedList[3], "c")
assertEquals(replayedList[4], "a")
assertEquals(replayedList[5], "b")
sourceList[1].add(0, "f")
require(replayedList.size == 7)
require(replayedList[0] == "hello")
require(replayedList[1] == "d")
require(replayedList[2] == "e")
require(replayedList[3] == "f")
require(replayedList[4] == "c")
require(replayedList[5] == "a")
require(replayedList[6] == "b")
concatenatedList.checkInvariants()
assertEquals(replayedList.size, 7)
assertEquals(replayedList[0], "hello")
assertEquals(replayedList[1], "d")
assertEquals(replayedList[2], "e")
assertEquals(replayedList[3], "f")
assertEquals(replayedList[4], "c")
assertEquals(replayedList[5], "a")
assertEquals(replayedList[6], "b")
}
@Test
@ -65,38 +94,47 @@ class ConcatenatedListTest {
sourceList[1].add(0, "f")
sourceList.removeAt(1)
require(replayedList.size == 5)
require(replayedList[0] == "hello")
require(replayedList[1] == "d")
require(replayedList[2] == "e")
require(replayedList[3] == "a")
require(replayedList[4] == "b")
concatenatedList.checkInvariants()
assertEquals(replayedList.size, 5)
assertEquals(replayedList[0], "hello")
assertEquals(replayedList[1], "d")
assertEquals(replayedList[2], "e")
assertEquals(replayedList[3], "a")
assertEquals(replayedList[4], "b")
sourceList[0].clear()
require(replayedList.size == 2)
require(replayedList[0] == "a")
require(replayedList[1] == "b")
concatenatedList.checkInvariants()
assertEquals(replayedList.size, 2)
assertEquals(replayedList[0], "a")
assertEquals(replayedList[1], "b")
sourceList[1].removeAt(0)
concatenatedList.checkInvariants()
assertEquals(replayedList.size, 1)
assertEquals(replayedList[0], "b")
}
@Test
fun permutationWorks() {
sourceList.addAll(FXCollections.observableArrayList("a", "b"), FXCollections.observableArrayList("c"))
require(replayedList.size == 4)
require(replayedList[0] == "hello")
require(replayedList[1] == "a")
require(replayedList[2] == "b")
require(replayedList[3] == "c")
concatenatedList.checkInvariants()
assertEquals(replayedList.size, 4)
assertEquals(replayedList[0], "hello")
assertEquals(replayedList[1], "a")
assertEquals(replayedList[2], "b")
assertEquals(replayedList[3], "c")
sourceList.sortWith(object : Comparator<ObservableList<String>> {
override fun compare(p0: ObservableList<String>, p1: ObservableList<String>): Int {
return p0.size - p1.size
}
})
require(replayedList.size == 4)
require(replayedList[0] == "hello")
require(replayedList[1] == "c")
require(replayedList[2] == "a")
require(replayedList[3] == "b")
concatenatedList.checkInvariants()
assertEquals(replayedList.size, 4)
assertEquals(replayedList[0], "hello")
assertEquals(replayedList[1], "c")
assertEquals(replayedList[2], "a")
assertEquals(replayedList[3], "b")
sourceList.add(0, FXCollections.observableArrayList("d", "e", "f"))
sourceList.sortWith(object : Comparator<ObservableList<String>> {
@ -104,14 +142,15 @@ class ConcatenatedListTest {
return p0.size - p1.size
}
})
require(replayedList.size == 7)
require(replayedList[0] == "hello")
require(replayedList[1] == "c")
require(replayedList[2] == "a")
require(replayedList[3] == "b")
require(replayedList[4] == "d")
require(replayedList[5] == "e")
require(replayedList[6] == "f")
concatenatedList.checkInvariants()
assertEquals(replayedList.size, 7)
assertEquals(replayedList[0], "hello")
assertEquals(replayedList[1], "c")
assertEquals(replayedList[2], "a")
assertEquals(replayedList[3], "b")
assertEquals(replayedList[4], "d")
assertEquals(replayedList[5], "e")
assertEquals(replayedList[6], "f")
}
}

View File

@ -2,14 +2,16 @@ package com.r3corda.client.fxutils
import javafx.beans.property.SimpleObjectProperty
import javafx.collections.FXCollections
import javafx.collections.ObservableList
import org.junit.Before
import org.junit.Test
import kotlin.test.assertEquals
class FlattenedListTest {
var sourceList = FXCollections.observableArrayList(SimpleObjectProperty(1234))
var flattenedList = FlattenedList(sourceList)
var replayedList = ReplayedList(flattenedList)
lateinit var sourceList: ObservableList<SimpleObjectProperty<Int>>
lateinit var flattenedList: ObservableList<Int>
lateinit var replayedList: ObservableList<Int>
@Before
fun setup() {
@ -20,73 +22,73 @@ class FlattenedListTest {
@Test
fun addWorks() {
require(replayedList.size == 1)
require(replayedList[0] == 1234)
assertEquals(replayedList.size, 1)
assertEquals(replayedList[0], 1234)
sourceList.add(SimpleObjectProperty(12))
require(replayedList.size == 2)
require(replayedList[0] == 1234)
require(replayedList[1] == 12)
assertEquals(replayedList.size, 2)
assertEquals(replayedList[0], 1234)
assertEquals(replayedList[1], 12)
sourceList.add(SimpleObjectProperty(34))
require(replayedList.size == 3)
require(replayedList[0] == 1234)
require(replayedList[1] == 12)
require(replayedList[2] == 34)
assertEquals(replayedList.size, 3)
assertEquals(replayedList[0], 1234)
assertEquals(replayedList[1], 12)
assertEquals(replayedList[2], 34)
sourceList.add(0, SimpleObjectProperty(56))
require(replayedList.size == 4)
require(replayedList[0] == 56)
require(replayedList[1] == 1234)
require(replayedList[2] == 12)
require(replayedList[3] == 34)
assertEquals(replayedList.size, 4)
assertEquals(replayedList[0], 56)
assertEquals(replayedList[1], 1234)
assertEquals(replayedList[2], 12)
assertEquals(replayedList[3], 34)
sourceList.addAll(2, listOf(SimpleObjectProperty(78), SimpleObjectProperty(910)))
require(replayedList.size == 6)
require(replayedList[0] == 56)
require(replayedList[1] == 1234)
require(replayedList[2] == 78)
require(replayedList[3] == 910)
require(replayedList[4] == 12)
require(replayedList[5] == 34)
assertEquals(replayedList.size, 6)
assertEquals(replayedList[0], 56)
assertEquals(replayedList[1], 1234)
assertEquals(replayedList[2], 78)
assertEquals(replayedList[3], 910)
assertEquals(replayedList[4], 12)
assertEquals(replayedList[5], 34)
}
@Test
fun removeWorks() {
val firstRemoved = sourceList.removeAt(0)
require(firstRemoved.get() == 1234)
require(replayedList.size == 0)
assertEquals(firstRemoved.get(), 1234)
assertEquals(replayedList.size, 0)
firstRemoved.set(123)
sourceList.add(SimpleObjectProperty(12))
sourceList.add(SimpleObjectProperty(34))
sourceList.add(SimpleObjectProperty(56))
require(replayedList.size == 3)
assertEquals(replayedList.size, 3)
val secondRemoved = sourceList.removeAt(1)
require(secondRemoved.get() == 34)
require(replayedList.size == 2)
require(replayedList[0] == 12)
require(replayedList[1] == 56)
assertEquals(secondRemoved.get(), 34)
assertEquals(replayedList.size, 2)
assertEquals(replayedList[0], 12)
assertEquals(replayedList[1], 56)
secondRemoved.set(123)
sourceList.clear()
require(replayedList.size == 0)
assertEquals(replayedList.size, 0)
}
@Test
fun updatingObservableWorks() {
require(replayedList[0] == 1234)
assertEquals(replayedList[0], 1234)
sourceList[0].set(4321)
require(replayedList[0] == 4321)
assertEquals(replayedList[0], 4321)
sourceList.add(0, SimpleObjectProperty(12))
sourceList[1].set(8765)
require(replayedList[0] == 12)
require(replayedList[1] == 8765)
assertEquals(replayedList[0], 12)
assertEquals(replayedList[1], 8765)
sourceList[0].set(34)
require(replayedList[0] == 34)
require(replayedList[1] == 8765)
assertEquals(replayedList[0], 34)
assertEquals(replayedList[1], 8765)
}
@Test
@ -94,20 +96,20 @@ class FlattenedListTest {
val observable = SimpleObjectProperty(12)
sourceList.add(observable)
sourceList.add(observable)
require(replayedList.size == 3)
require(replayedList[0] == 1234)
require(replayedList[1] == 12)
require(replayedList[2] == 12)
assertEquals(replayedList.size, 3)
assertEquals(replayedList[0], 1234)
assertEquals(replayedList[1], 12)
assertEquals(replayedList[2], 12)
observable.set(34)
require(replayedList.size == 3)
require(replayedList[0] == 1234)
require(replayedList[1] == 34)
require(replayedList[2] == 34)
assertEquals(replayedList.size, 3)
assertEquals(replayedList[0], 1234)
assertEquals(replayedList[1], 34)
assertEquals(replayedList[2], 34)
sourceList.removeAt(1)
require(replayedList.size == 2)
require(replayedList[0] == 1234)
require(replayedList[1] == 34)
assertEquals(replayedList.size, 2)
assertEquals(replayedList[0], 1234)
assertEquals(replayedList[1], 34)
}
}

View File

@ -5,71 +5,72 @@ import javafx.collections.ObservableList
import org.junit.Before
import org.junit.Test
import java.util.*
import kotlin.test.assertEquals
class LeftOuterJoinedMapTest {
data class Person(val name: String, val age: Int)
data class Dog(val name: String, val owner: String)
var people = FXCollections.observableArrayList<Person>(Person("Alice", 12))
var dogs = FXCollections.observableArrayList<Dog>(Dog("Scruffy", owner = "Bob"))
var joinedList = people.leftOuterJoin(dogs, Person::name, Dog::owner)
// We replay the nested observable as well
var replayedList = ReplayedList(joinedList.map { Pair(it.first, ReplayedList(it.second)) })
lateinit var people: ObservableList<Person>
lateinit var dogs: ObservableList<Dog>
lateinit var joinedList: ObservableList<Pair<Person, ObservableList<Dog>>>
lateinit var replayedList: ObservableList<out Pair<Person, ObservableList<Dog>>>
@Before
fun setup() {
people = FXCollections.observableArrayList<Person>(Person("Alice", 12))
dogs = FXCollections.observableArrayList<Dog>(Dog("Scruffy", owner = "Bob"))
joinedList = people.leftOuterJoin(dogs, Person::name, Dog::owner)
joinedList = people.leftOuterJoin(dogs, Person::name, Dog::owner) { person, dogs -> Pair(person, dogs) }
// We replay the nested observable as well
replayedList = ReplayedList(joinedList.map { Pair(it.first, ReplayedList(it.second)) })
}
// TODO perhaps these are too brittle because they test indices that are not stable. Use Expect dsl?
@Test
fun addWorks() {
require(replayedList.size == 1)
require(replayedList[0].first.name == "Alice")
require(replayedList[0].second.size == 0)
assertEquals(replayedList.size, 1)
assertEquals(replayedList[0].first.name, "Alice")
assertEquals(replayedList[0].second.size, 0)
dogs.add(Dog("Scooby", owner = "Alice"))
require(replayedList.size == 1)
require(replayedList[0].first.name == "Alice")
require(replayedList[0].second.size == 1)
require(replayedList[0].second[0].name == "Scooby")
assertEquals(replayedList.size, 1)
assertEquals(replayedList[0].first.name, "Alice")
assertEquals(replayedList[0].second.size, 1)
assertEquals(replayedList[0].second[0].name, "Scooby")
people.add(Person("Bob", 34))
require(replayedList.size == 2)
require(replayedList[0].first.name == "Alice")
require(replayedList[0].second.size == 1)
require(replayedList[0].second[0].name == "Scooby")
require(replayedList[1].first.name == "Bob")
require(replayedList[1].second.size == 1)
require(replayedList[1].second[0].name == "Scruffy")
assertEquals(replayedList.size, 2)
assertEquals(replayedList[0].first.name, "Alice")
assertEquals(replayedList[0].second.size, 1)
assertEquals(replayedList[0].second[0].name, "Scooby")
assertEquals(replayedList[1].first.name, "Bob")
assertEquals(replayedList[1].second.size, 1)
assertEquals(replayedList[1].second[0].name, "Scruffy")
dogs.add(Dog("Bella", owner = "Bob"))
require(replayedList.size == 2)
require(replayedList[0].first.name == "Alice")
require(replayedList[0].second.size == 1)
require(replayedList[0].second[0].name == "Scooby")
require(replayedList[1].first.name == "Bob")
require(replayedList[1].second.size == 2)
require(replayedList[1].second[0].name == "Bella")
require(replayedList[1].second[1].name == "Scruffy")
assertEquals(replayedList.size, 2)
assertEquals(replayedList[0].first.name, "Alice")
assertEquals(replayedList[0].second.size, 1)
assertEquals(replayedList[0].second[0].name, "Scooby")
assertEquals(replayedList[1].first.name, "Bob")
assertEquals(replayedList[1].second.size, 2)
assertEquals(replayedList[1].second[0].name, "Bella")
assertEquals(replayedList[1].second[1].name, "Scruffy")
// We have another Alice wat
people.add(Person("Alice", 91))
require(replayedList.size == 3)
require(replayedList[0].first.name == "Alice")
require(replayedList[0].second.size == 1)
require(replayedList[0].second[0].name == "Scooby")
require(replayedList[1].first.name == "Alice")
require(replayedList[1].second.size == 1)
require(replayedList[1].second[0].name == "Scooby")
require(replayedList[2].first.name == "Bob")
require(replayedList[2].second.size == 2)
require(replayedList[2].second[0].name == "Bella")
require(replayedList[2].second[1].name == "Scruffy")
assertEquals(replayedList.size, 3)
assertEquals(replayedList[0].first.name, "Alice")
assertEquals(replayedList[0].second.size, 1)
assertEquals(replayedList[0].second[0].name, "Scooby")
assertEquals(replayedList[1].first.name, "Alice")
assertEquals(replayedList[1].second.size, 1)
assertEquals(replayedList[1].second[0].name, "Scooby")
assertEquals(replayedList[2].first.name, "Bob")
assertEquals(replayedList[2].second.size, 2)
assertEquals(replayedList[2].second[0].name, "Bella")
assertEquals(replayedList[2].second[1].name, "Scruffy")
}
@ -79,37 +80,37 @@ class LeftOuterJoinedMapTest {
people.add(Person("Bob", 34))
dogs.add(Dog("Bella", owner = "Bob"))
require(people.removeAt(0).name == "Alice")
require(replayedList.size == 1)
require(replayedList[0].first.name == "Bob")
require(replayedList[0].second.size == 2)
require(replayedList[0].second[0].name == "Bella")
require(replayedList[0].second[1].name == "Scruffy")
assertEquals(people.removeAt(0).name, "Alice")
assertEquals(replayedList.size, 1)
assertEquals(replayedList[0].first.name, "Bob")
assertEquals(replayedList[0].second.size, 2)
assertEquals(replayedList[0].second[0].name, "Bella")
assertEquals(replayedList[0].second[1].name, "Scruffy")
require(dogs.removeAt(0).name == "Scruffy")
require(replayedList.size == 1)
require(replayedList[0].first.name == "Bob")
require(replayedList[0].second.size == 1)
require(replayedList[0].second[0].name == "Bella")
assertEquals(dogs.removeAt(0).name, "Scruffy")
assertEquals(replayedList.size, 1)
assertEquals(replayedList[0].first.name, "Bob")
assertEquals(replayedList[0].second.size, 1)
assertEquals(replayedList[0].second[0].name, "Bella")
people.add(Person("Alice", 213))
require(replayedList.size == 2)
require(replayedList[0].first.name == "Alice")
require(replayedList[0].second.size == 1)
require(replayedList[0].second[0].name == "Scooby")
require(replayedList[1].first.name == "Bob")
require(replayedList[1].second.size == 1)
require(replayedList[1].second[0].name == "Bella")
assertEquals(replayedList.size, 2)
assertEquals(replayedList[0].first.name, "Alice")
assertEquals(replayedList[0].second.size, 1)
assertEquals(replayedList[0].second[0].name, "Scooby")
assertEquals(replayedList[1].first.name, "Bob")
assertEquals(replayedList[1].second.size, 1)
assertEquals(replayedList[1].second[0].name, "Bella")
dogs.clear()
require(replayedList.size == 2)
require(replayedList[0].first.name == "Alice")
require(replayedList[0].second.size == 0)
require(replayedList[1].first.name == "Bob")
require(replayedList[1].second.size == 0)
assertEquals(replayedList.size, 2)
assertEquals(replayedList[0].first.name, "Alice")
assertEquals(replayedList[0].second.size, 0)
assertEquals(replayedList[1].first.name, "Bob")
assertEquals(replayedList[1].second.size, 0)
people.clear()
require(replayedList.size == 0)
assertEquals(replayedList.size, 0)
}
}

View File

@ -1,13 +0,0 @@
package com.r3corda.client.fxutils
import org.junit.Before
class MapValuesListTest {
@Before
fun setup() {
}
}

View File

@ -1,14 +1,16 @@
package com.r3corda.client.fxutils
import javafx.collections.FXCollections
import javafx.collections.ObservableList
import org.junit.Before
import org.junit.Test
import kotlin.test.assertEquals
class MappedListTest {
var sourceList = FXCollections.observableArrayList("Alice")
var mappedList = MappedList(sourceList) { it.length }
var replayedList = ReplayedList(mappedList)
lateinit var sourceList: ObservableList<String>
lateinit var mappedList: ObservableList<Int>
lateinit var replayedList: ObservableList<Int>
@Before
fun setup() {
@ -19,19 +21,19 @@ class MappedListTest {
@Test
fun addWorks() {
require(replayedList.size == 1)
require(replayedList[0] == 5)
assertEquals(replayedList.size, 1)
assertEquals(replayedList[0], 5)
sourceList.add("Bob")
require(replayedList.size == 2)
require(replayedList[0] == 5)
require(replayedList[1] == 3)
assertEquals(replayedList.size, 2)
assertEquals(replayedList[0], 5)
assertEquals(replayedList[1], 3)
sourceList.add(0, "Charlie")
require(replayedList.size == 3)
require(replayedList[0] == 7)
require(replayedList[1] == 5)
require(replayedList[2] == 3)
assertEquals(replayedList.size, 3)
assertEquals(replayedList[0], 7)
assertEquals(replayedList[1], 5)
assertEquals(replayedList[2], 3)
}
@ -39,12 +41,12 @@ class MappedListTest {
fun removeWorks() {
sourceList.add("Bob")
sourceList.add(0, "Charlie")
require(replayedList.size == 3)
assertEquals(replayedList.size, 3)
sourceList.removeAt(1)
require(replayedList.size == 2)
require(replayedList[0] == 7)
require(replayedList[1] == 3)
assertEquals(replayedList.size, 2)
assertEquals(replayedList[0], 7)
assertEquals(replayedList[1], 3)
}
@Test
@ -54,13 +56,13 @@ class MappedListTest {
sourceList.sortBy { it.length }
require(sourceList[0] == "Bob")
require(sourceList[1] == "Alice")
require(sourceList[2] == "Charlie")
assertEquals(sourceList[0], "Bob")
assertEquals(sourceList[1], "Alice")
assertEquals(sourceList[2], "Charlie")
require(replayedList.size == 3)
require(replayedList[0] == 3)
require(replayedList[1] == 5)
require(replayedList[2] == 7)
assertEquals(replayedList.size, 3)
assertEquals(replayedList[0], 3)
assertEquals(replayedList[1], 5)
assertEquals(replayedList[2], 7)
}
}

View File

@ -3,6 +3,7 @@ package com.r3corda.client.fxutils
import javafx.collections.FXCollections
import org.junit.Before
import org.junit.Test
import kotlin.test.assertEquals
class ReplayedListTest {
@ -17,70 +18,70 @@ class ReplayedListTest {
@Test
fun addWorks() {
require(replayedList.size == 1)
require(replayedList[0] == 1234)
assertEquals(replayedList.size, 1)
assertEquals(replayedList[0], 1234)
sourceList.add(12)
require(replayedList.size == 2)
require(replayedList[0] == 1234)
require(replayedList[1] == 12)
assertEquals(replayedList.size, 2)
assertEquals(replayedList[0], 1234)
assertEquals(replayedList[1], 12)
sourceList.add(34)
require(replayedList.size == 3)
require(replayedList[0] == 1234)
require(replayedList[1] == 12)
require(replayedList[2] == 34)
assertEquals(replayedList.size, 3)
assertEquals(replayedList[0], 1234)
assertEquals(replayedList[1], 12)
assertEquals(replayedList[2], 34)
sourceList.add(0, 56)
require(replayedList.size == 4)
require(replayedList[0] == 56)
require(replayedList[1] == 1234)
require(replayedList[2] == 12)
require(replayedList[3] == 34)
assertEquals(replayedList.size, 4)
assertEquals(replayedList[0], 56)
assertEquals(replayedList[1], 1234)
assertEquals(replayedList[2], 12)
assertEquals(replayedList[3], 34)
sourceList.addAll(2, listOf(78, 910))
require(replayedList.size == 6)
require(replayedList[0] == 56)
require(replayedList[1] == 1234)
require(replayedList[2] == 78)
require(replayedList[3] == 910)
require(replayedList[4] == 12)
require(replayedList[5] == 34)
assertEquals(replayedList.size, 6)
assertEquals(replayedList[0], 56)
assertEquals(replayedList[1], 1234)
assertEquals(replayedList[2], 78)
assertEquals(replayedList[3], 910)
assertEquals(replayedList[4], 12)
assertEquals(replayedList[5], 34)
}
@Test
fun removeWorks() {
val firstRemoved = sourceList.removeAt(0)
require(firstRemoved == 1234)
require(replayedList.size == 0)
assertEquals(firstRemoved, 1234)
assertEquals(replayedList.size, 0)
sourceList.add(12)
sourceList.add(34)
sourceList.add(56)
require(replayedList.size == 3)
assertEquals(replayedList.size, 3)
val secondRemoved = sourceList.removeAt(1)
require(secondRemoved == 34)
require(replayedList.size == 2)
require(replayedList[0] == 12)
require(replayedList[1] == 56)
assertEquals(secondRemoved, 34)
assertEquals(replayedList.size, 2)
assertEquals(replayedList[0], 12)
assertEquals(replayedList[1], 56)
sourceList.clear()
require(replayedList.size == 0)
assertEquals(replayedList.size, 0)
}
@Test
fun updateWorks() {
require(replayedList[0] == 1234)
assertEquals(replayedList[0], 1234)
sourceList[0] = 4321
require(replayedList[0] == 4321)
assertEquals(replayedList[0], 4321)
sourceList.add(0, 12)
sourceList[1] = 8765
require(replayedList[0] == 12)
require(replayedList[1] == 8765)
assertEquals(replayedList[0], 12)
assertEquals(replayedList[1], 8765)
sourceList[0] = 34
require(replayedList[0] == 34)
require(replayedList[1] == 8765)
assertEquals(replayedList[0], 34)
assertEquals(replayedList[1], 8765)
}
}

View File

@ -2,7 +2,11 @@ package com.r3corda.client.fxutils
import javafx.collections.MapChangeListener
import javafx.collections.ObservableMap
import kotlin.test.assertEquals
/**
* [ReplayedMap] simply replays changes done to the source map. Used for testing changes.
*/
class ReplayedMap<K, A>(sourceMap: ObservableMap<K, A>) : ReadOnlyBackedObservableMapBase<K, A, Unit>() {
init {
sourceMap.forEach {
@ -10,7 +14,7 @@ class ReplayedMap<K, A>(sourceMap: ObservableMap<K, A>) : ReadOnlyBackedObservab
}
sourceMap.addListener { change: MapChangeListener.Change<out K, out A> ->
if (change.wasRemoved()) {
require(backingMap.remove(change.key)!!.first == change.valueRemoved)
assertEquals(backingMap.remove(change.key)!!.first, change.valueRemoved)
}
if (change.wasAdded()) {
backingMap.set(change.key, Pair(change.valueAdded, Unit))