Improved observable test setup.

This commit is contained in:
Daan Vanden Bosch 2020-10-17 15:15:48 +02:00
parent 18e01f17c7
commit b78c516b0a
12 changed files with 201 additions and 251 deletions

View File

@ -0,0 +1,60 @@
package world.phantasmal.observable
import world.phantasmal.observable.test.withScope
import world.phantasmal.testUtils.TestSuite
import kotlin.test.Test
import kotlin.test.assertEquals
typealias ObservableAndEmit = Pair<Observable<*>, () -> Unit>
/**
* Test suite for all [Observable] implementations. There is a subclass of this suite for every
* [Observable] implementation.
*/
abstract class ObservableTests : TestSuite() {
abstract fun create(): ObservableAndEmit
@Test
fun observable_calls_observers_when_events_are_emitted() {
val (observable, emit) = create()
val changes = mutableListOf<ChangeEvent<*>>()
withScope { scope ->
observable.observe(scope) { c ->
changes.add(c)
}
emit()
assertEquals(1, changes.size)
emit()
emit()
emit()
assertEquals(4, changes.size)
}
}
@Test
fun observable_does_not_call_observers_after_they_are_disposed() {
val (observable, emit) = create()
val changes = mutableListOf<ChangeEvent<*>>()
withScope { scope ->
observable.observe(scope) { c ->
changes.add(c)
}
emit()
assertEquals(1, changes.size)
emit()
emit()
emit()
assertEquals(4, changes.size)
}
}
}

View File

@ -1,14 +1,8 @@
package world.phantasmal.observable
import world.phantasmal.testUtils.TestSuite
import kotlin.test.Test
class SimpleEmitterTests : TestSuite() {
@Test
fun observable_tests() {
observableTests {
val observable = SimpleEmitter<Any>()
ObservableAndEmit(observable) { observable.emit(ChangeEvent(Any())) }
}
class SimpleEmitterTests : ObservableTests() {
override fun create(): ObservableAndEmit {
val observable = SimpleEmitter<Any>()
return ObservableAndEmit(observable) { observable.emit(ChangeEvent(Any())) }
}
}

View File

@ -1,56 +0,0 @@
package world.phantasmal.observable
// Test suite for all Observable implementations.
// These functions are called from type-specific unit tests.
import world.phantasmal.observable.test.withScope
import kotlin.test.assertEquals
typealias ObservableAndEmit = Pair<Observable<*>, () -> Unit>
fun observableTests(create: () -> ObservableAndEmit) {
observableShouldCallObserversWhenEventsAreEmitted(create)
observableShouldNotCallObserversAfterTheyAreDisposed(create)
}
private fun observableShouldCallObserversWhenEventsAreEmitted(create: () -> ObservableAndEmit) {
val (observable, emit) = create()
val changes = mutableListOf<ChangeEvent<*>>()
withScope { scope ->
observable.observe(scope) { c ->
changes.add(c)
}
emit()
assertEquals(1, changes.size)
emit()
emit()
emit()
assertEquals(4, changes.size)
}
}
private fun observableShouldNotCallObserversAfterTheyAreDisposed(create: () -> ObservableAndEmit) {
val (observable, emit) = create()
val changes = mutableListOf<ChangeEvent<*>>()
withScope { scope ->
observable.observe(scope) { c ->
changes.add(c)
}
emit()
assertEquals(1, changes.size)
emit()
emit()
emit()
assertEquals(4, changes.size)
}
}

View File

@ -1,27 +1,13 @@
package world.phantasmal.observable.value
import world.phantasmal.observable.observableTests
import world.phantasmal.testUtils.TestSuite
import kotlin.test.Test
class DelegatingValTests : TestSuite() {
@Test
fun observable_tests() {
observableTests(::create)
}
@Test
fun val_tests() {
valTests(::create, ::createBoolean)
}
private fun create(): ValAndEmit<*> {
class DelegatingValTests : RegularValTests() {
override fun create(): ValAndEmit<*> {
var v = 0
val value = DelegatingVal({ v }, { v = it })
return ValAndEmit(value) { value.value += 2 }
}
private fun createBoolean(bool: Boolean): ValAndEmit<Boolean> {
override fun createBoolean(bool: Boolean): ValAndEmit<Boolean> {
var v = bool
val value = DelegatingVal({ v }, { v = it })
return ValAndEmit(value) { value.value = !value.value }

View File

@ -1,27 +1,13 @@
package world.phantasmal.observable.value
import world.phantasmal.observable.observableTests
import world.phantasmal.testUtils.TestSuite
import kotlin.test.Test
class DependentValTests : TestSuite() {
@Test
fun observable_tests() {
observableTests(::create)
}
@Test
fun val_tests() {
valTests(::create, ::createBoolean)
}
private fun create(): ValAndEmit<*> {
class DependentValTests : RegularValTests() {
override fun create(): ValAndEmit<*> {
val v = SimpleVal(0)
val value = DependentVal(listOf(v)) { 2 * v.value }
return ValAndEmit(value) { v.value += 2 }
}
private fun createBoolean(bool: Boolean): ValAndEmit<Boolean> {
override fun createBoolean(bool: Boolean): ValAndEmit<Boolean> {
val v = SimpleVal(bool)
val value = DependentVal(listOf(v)) { v.value }
return ValAndEmit(value) { v.value = !v.value }

View File

@ -0,0 +1,39 @@
package world.phantasmal.observable.value
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
/**
* Test suite for all [Val] implementations that aren't ListVals. There is a subclass of this suite
* for every non-ListVal [Val] implementation.
*/
abstract class RegularValTests : ValTests() {
protected abstract fun createBoolean(bool: Boolean): ValAndEmit<Boolean>
@Test
fun val_boolean_extensions() {
listOf(true, false).forEach { bool ->
val (value) = createBoolean(bool)
// Test the test setup first.
assertEquals(bool, value.value)
// Test `and`.
assertEquals(bool, (value and trueVal()).value)
assertFalse((value and falseVal()).value)
// Test `or`.
assertTrue((value or trueVal()).value)
assertEquals(bool, (value or falseVal()).value)
// Test `xor`.
assertEquals(!bool, (value xor trueVal()).value)
assertEquals(bool, (value xor falseVal()).value)
// Test `!` (unary not).
assertEquals(!bool, (!value).value)
}
}
}

View File

@ -1,26 +1,12 @@
package world.phantasmal.observable.value
import world.phantasmal.observable.observableTests
import world.phantasmal.testUtils.TestSuite
import kotlin.test.Test
class SimpleValTests : TestSuite() {
@Test
fun observable_tests() {
observableTests(::create)
}
@Test
fun val_tests() {
valTests(::create, ::createBoolean)
}
private fun create(): ValAndEmit<*> {
class SimpleValTests : RegularValTests() {
override fun create(): ValAndEmit<*> {
val value = SimpleVal(1)
return ValAndEmit(value) { value.value += 2 }
}
private fun createBoolean(bool: Boolean): ValAndEmit<Boolean> {
override fun createBoolean(bool: Boolean): ValAndEmit<Boolean> {
val value = SimpleVal(bool)
return ValAndEmit(value) { value.value = !value.value }
}

View File

@ -1,80 +0,0 @@
package world.phantasmal.observable.value
// Test suite for all Val implementations.
// These functions are called from type-specific unit tests.
import world.phantasmal.observable.ChangeEvent
import world.phantasmal.observable.test.withScope
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
typealias ValAndEmit<T> = Pair<Val<T>, () -> Unit>
fun valTests(
create: () -> ValAndEmit<*>,
createBoolean: ((Boolean) -> ValAndEmit<Boolean>)?,
) {
valShouldRespectCallNowArgument(create)
if (createBoolean != null) {
testValBooleanExtensions(createBoolean)
}
}
/**
* When Val::observe is called with callNow = true, it should call the observer immediately.
* Otherwise it should only call the observer when it changes.
*/
private fun valShouldRespectCallNowArgument(create: () -> ValAndEmit<*>) {
val (value, emit) = create()
val changes = mutableListOf<ChangeEvent<*>>()
withScope { scope ->
// Test callNow = false
value.observe(scope, callNow = false) { c ->
changes.add(c)
}
emit()
assertEquals(1, changes.size)
}
withScope { scope ->
// Test callNow = true
changes.clear()
value.observe(scope, callNow = true) { c ->
changes.add(c)
}
emit()
assertEquals(2, changes.size)
}
}
private fun testValBooleanExtensions(create: (Boolean) -> ValAndEmit<Boolean>) {
listOf(true, false).forEach { bool ->
val (value) = create(bool)
// Test the test setup first.
assertEquals(bool, value.value)
// Test `and`.
assertEquals(bool, (value and trueVal()).value)
assertFalse((value and falseVal()).value)
// Test `or`.
assertTrue((value or trueVal()).value)
assertEquals(bool, (value or falseVal()).value)
// Test `xor`.
assertEquals(!bool, (value xor trueVal()).value)
assertEquals(bool, (value xor falseVal()).value)
// Test `!` (unary not).
assertEquals(!bool, (!value).value)
}
}

View File

@ -0,0 +1,51 @@
package world.phantasmal.observable.value
import world.phantasmal.observable.ChangeEvent
import world.phantasmal.observable.ObservableTests
import world.phantasmal.observable.test.withScope
import kotlin.test.Test
import kotlin.test.assertEquals
typealias ValAndEmit<T> = Pair<Val<T>, () -> Unit>
/**
* Test suite for all [Val] implementations. There is a subclass of this suite for every [Val]
* implementation.
*/
abstract class ValTests : ObservableTests() {
abstract override fun create(): ValAndEmit<*>
/**
* When [Val.observe] is called with callNow = true, it should call the observer immediately.
* Otherwise it should only call the observer when it changes.
*/
@Test
fun val_respects_call_now_argument() {
val (value, emit) = create()
val changes = mutableListOf<ChangeEvent<*>>()
withScope { scope ->
// Test callNow = false
value.observe(scope, callNow = false) { c ->
changes.add(c)
}
emit()
assertEquals(1, changes.size)
}
withScope { scope ->
// Test callNow = true
changes.clear()
value.observe(scope, callNow = true) { c ->
changes.add(c)
}
emit()
assertEquals(2, changes.size)
}
}
}

View File

@ -0,0 +1,36 @@
package world.phantasmal.observable.value.list
import world.phantasmal.observable.test.withScope
import world.phantasmal.observable.value.ValTests
import kotlin.test.Test
import kotlin.test.assertEquals
typealias ListValAndAdd = Pair<ListVal<*>, () -> Unit>
/**
* Test suite for all [ListVal] implementations. There is a subclass of this suite for every
* [ListVal] implementation.
*/
abstract class ListValTests : ValTests() {
abstract override fun create(): ListValAndAdd
@Test
fun listVal_updates_sizeVal_correctly() {
val (list: List<*>, add) = create()
assertEquals(0, list.sizeVal.value)
var observedSize = 0
withScope { scope ->
list.sizeVal.observe(scope) { observedSize = it.value }
for (i in 1..3) {
add()
assertEquals(i, list.sizeVal.value)
assertEquals(i, observedSize)
}
}
}
}

View File

@ -1,27 +1,7 @@
package world.phantasmal.observable.value.list
import world.phantasmal.observable.observableTests
import world.phantasmal.observable.value.valTests
import world.phantasmal.testUtils.TestSuite
import kotlin.test.Test
class SimpleListValTests : TestSuite() {
@Test
fun observable_tests() {
observableTests(::create)
}
@Test
fun val_tests() {
valTests(::create, createBoolean = null)
}
@Test
fun list_val_tests() {
listValTests(::create)
}
private fun create(): ListValAndAdd {
class SimpleListValTests : ListValTests() {
override fun create(): ListValAndAdd {
val value = SimpleListVal(mutableListOf<Int>())
return ListValAndAdd(value) { value.add(7) }
}

View File

@ -1,32 +0,0 @@
package world.phantasmal.observable.value.list
// Test suite for all ListVal implementations.
// These functions are called from type-specific unit tests.
import world.phantasmal.observable.test.withScope
import kotlin.test.assertEquals
typealias ListValAndAdd = Pair<ListVal<*>, () -> Unit>
fun listValTests(create: () -> ListValAndAdd) {
listValShouldUpdateSizeValCorrectly(create)
}
private fun listValShouldUpdateSizeValCorrectly(create: () -> ListValAndAdd) {
val (list: List<*>, add) = create()
assertEquals(0, list.sizeVal.value)
var observedSize = 0
withScope { scope ->
list.sizeVal.observe(scope) { observedSize = it.value }
for (i in 1..3) {
add()
assertEquals(i, list.sizeVal.value)
assertEquals(i, observedSize)
}
}
}