diff --git a/cell/src/commonMain/kotlin/world/phantasmal/cell/CellUtils.kt b/cell/src/commonMain/kotlin/world/phantasmal/cell/Cells.kt similarity index 92% rename from cell/src/commonMain/kotlin/world/phantasmal/cell/CellUtils.kt rename to cell/src/commonMain/kotlin/world/phantasmal/cell/Cells.kt index 66aea72b..1578f775 100644 --- a/cell/src/commonMain/kotlin/world/phantasmal/cell/CellUtils.kt +++ b/cell/src/commonMain/kotlin/world/phantasmal/cell/Cells.kt @@ -1,6 +1,9 @@ +@file:JvmName("Cells") + package world.phantasmal.cell import world.phantasmal.core.disposable.Disposable +import kotlin.jvm.JvmName private val TRUE_CELL: Cell = ImmutableCell(true) private val FALSE_CELL: Cell = ImmutableCell(false) @@ -8,6 +11,8 @@ private val NULL_CELL: Cell = ImmutableCell(null) private val ZERO_INT_CELL: Cell = ImmutableCell(0) private val EMPTY_STRING_CELL: Cell = ImmutableCell("") +// Factory methods. + /** Returns an immutable cell containing [value]. */ fun cell(value: T): Cell = ImmutableCell(value) @@ -38,6 +43,8 @@ fun mutableCell(value: T): MutableCell = SimpleCell(value) fun mutableCell(getter: () -> T, setter: (T) -> Unit): MutableCell = DelegatingCell(getter, setter) +// Observation extensions. + fun Cell.observe(observer: (T) -> Unit): Disposable = observeChange { observer(it.value) } @@ -103,6 +110,8 @@ fun observeNow( return disposable } +// Generic extensions. + /** * Map a transformation function over this cell. * @@ -189,18 +198,28 @@ infix fun Cell.ne(other: Cell): Cell = fun Cell.orElse(defaultValue: () -> T): Cell = map { it ?: defaultValue() } -infix fun > Cell.gt(value: T): Cell = - map { it > value } +// Comparable extensions. infix fun > Cell.gt(other: Cell): Cell = map(this, other) { a, b -> a > b } -infix fun > Cell.lt(value: T): Cell = - map { it < value } +infix fun > Cell.gt(value: T): Cell = + map { it > value } + +infix fun > T.gt(other: Cell): Cell = + other.map { this > it } infix fun > Cell.lt(other: Cell): Cell = map(this, other) { a, b -> a < b } +infix fun > Cell.lt(value: T): Cell = + map { it < value } + +infix fun > T.lt(other: Cell): Cell = + other.map { this < it } + +// Boolean extensions. + infix fun Cell.and(other: Cell): Cell = map(this, other) { a, b -> a && b } @@ -223,13 +242,15 @@ infix fun Cell.xor(other: Cell): Cell = // Use != because of https://youtrack.jetbrains.com/issue/KT-31277. map(this, other) { a, b -> a != b } +infix fun Cell.xor(other: Boolean): Cell = + if (other) !this else this + +infix fun Boolean.xor(other: Cell): Cell = + if (this) !other else other + operator fun Cell.not(): Cell = map { !it } -operator fun Cell.plus(value: Int): Cell = - map { it + value } - -operator fun Cell.minus(value: Int): Cell = - map { it - value } +// String extensions. fun Cell.isEmpty(): Cell = map { it.isEmpty() } @@ -243,6 +264,8 @@ fun Cell.isBlank(): Cell = fun Cell.isNotBlank(): Cell = map { it.isNotBlank() } +// Other utilities. + fun cellToString(cell: Cell<*>): String { val className = cell::class.simpleName val value = cell.value diff --git a/cell/src/commonMain/kotlin/world/phantasmal/cell/DoubleCells.kt b/cell/src/commonMain/kotlin/world/phantasmal/cell/DoubleCells.kt new file mode 100644 index 00000000..ca7a9ac5 --- /dev/null +++ b/cell/src/commonMain/kotlin/world/phantasmal/cell/DoubleCells.kt @@ -0,0 +1,44 @@ +@file:JvmName("DoubleCells") + +package world.phantasmal.cell + +import kotlin.jvm.JvmName + +operator fun Cell.unaryMinus(): Cell = + map { -it } + +operator fun Cell.plus(other: Cell): Cell = + map(this, other) { a, b -> a + b } + +operator fun Cell.plus(other: Double): Cell = + map { it + other } + +operator fun Double.plus(other: Cell): Cell = + other.map { this + it } + +operator fun Cell.minus(other: Cell): Cell = + map(this, other) { a, b -> a - b } + +operator fun Cell.minus(other: Double): Cell = + map { it - other } + +operator fun Double.minus(other: Cell): Cell = + other.map { this - it } + +operator fun Cell.times(other: Cell): Cell = + map(this, other) { a, b -> a * b } + +operator fun Cell.times(other: Double): Cell = + map { it * other } + +operator fun Double.times(other: Cell): Cell = + other.map { this * it } + +operator fun Cell.div(other: Cell): Cell = + map(this, other) { a, b -> a / b } + +operator fun Cell.div(other: Double): Cell = + map { it / other } + +operator fun Double.div(other: Cell): Cell = + other.map { this / it } diff --git a/cell/src/commonMain/kotlin/world/phantasmal/cell/IntCells.kt b/cell/src/commonMain/kotlin/world/phantasmal/cell/IntCells.kt new file mode 100644 index 00000000..50e43894 --- /dev/null +++ b/cell/src/commonMain/kotlin/world/phantasmal/cell/IntCells.kt @@ -0,0 +1,44 @@ +@file:JvmName("IntCells") + +package world.phantasmal.cell + +import kotlin.jvm.JvmName + +operator fun Cell.unaryMinus(): Cell = + map { -it } + +operator fun Cell.plus(other: Cell): Cell = + map(this, other) { a, b -> a + b } + +operator fun Cell.plus(other: Int): Cell = + map { it + other } + +operator fun Int.plus(other: Cell): Cell = + other.map { this + it } + +operator fun Cell.minus(other: Cell): Cell = + map(this, other) { a, b -> a - b } + +operator fun Cell.minus(other: Int): Cell = + map { it - other } + +operator fun Int.minus(other: Cell): Cell = + other.map { this - it } + +operator fun Cell.times(other: Cell): Cell = + map(this, other) { a, b -> a * b } + +operator fun Cell.times(other: Int): Cell = + map { it * other } + +operator fun Int.times(other: Cell): Cell = + other.map { this * it } + +operator fun Cell.div(other: Cell): Cell = + map(this, other) { a, b -> a / b } + +operator fun Cell.div(other: Int): Cell = + map { it / other } + +operator fun Int.div(other: Cell): Cell = + other.map { this / it } diff --git a/cell/src/commonMain/kotlin/world/phantasmal/cell/list/ListCellUtils.kt b/cell/src/commonMain/kotlin/world/phantasmal/cell/list/ListCells.kt similarity index 98% rename from cell/src/commonMain/kotlin/world/phantasmal/cell/list/ListCellUtils.kt rename to cell/src/commonMain/kotlin/world/phantasmal/cell/list/ListCells.kt index e4613fce..83f91964 100644 --- a/cell/src/commonMain/kotlin/world/phantasmal/cell/list/ListCellUtils.kt +++ b/cell/src/commonMain/kotlin/world/phantasmal/cell/list/ListCells.kt @@ -1,8 +1,11 @@ +@file:JvmName("ListCells") + package world.phantasmal.cell.list import world.phantasmal.cell.Cell import world.phantasmal.cell.DependentCell import world.phantasmal.cell.ImmutableCell +import kotlin.jvm.JvmName private val EMPTY_LIST_CELL = ImmutableListCell(emptyList()) diff --git a/cell/src/commonTest/kotlin/world/phantasmal/cell/CellsTests.kt b/cell/src/commonTest/kotlin/world/phantasmal/cell/CellsTests.kt new file mode 100644 index 00000000..6b9bb072 --- /dev/null +++ b/cell/src/commonTest/kotlin/world/phantasmal/cell/CellsTests.kt @@ -0,0 +1,148 @@ +package world.phantasmal.cell + +import world.phantasmal.cell.test.CellTestSuite +import kotlin.test.* + +class CellsTests : CellTestSuite { + @Test + fun cell_contains_the_given_value() = test { + val c: Cell = cell("test_value") + assertEquals("test_value", c.value) + } + + @Test + fun trueCell_is_always_true() = test { + val c: Cell = trueCell() + assertEquals(true, c.value) + } + + @Test + fun falseCell_is_always_false() = test { + val c: Cell = falseCell() + assertEquals(false, c.value) + } + + @Test + fun nullCell_is_always_null() = test { + val c: Cell = nullCell() + assertNull(c.value) + } + + @Test + fun zeroIntCell_is_always_zero() = test { + val c: Cell = zeroIntCell() + assertEquals(0, c.value) + } + + @Test + fun emptyStringCell_is_always_empty() = test { + val c: Cell = emptyStringCell() + assertEquals("", c.value) + } + + @Test + fun mutableCell_contains_the_given_value() = test { + val c: MutableCell = mutableCell("test_value") + assertEquals("test_value", c.value) + } + + @Test + fun isNull() = test { + for (value in arrayOf(Any(), null)) { + val c = cell(value).isNull() + + assertEquals(value == null, c.value) + } + } + + @Test + fun isNotNull() = test { + for (value in arrayOf(Any(), null)) { + val c = cell(value).isNotNull() + + assertEquals(value != null, c.value) + } + } + + @Test + fun equality_infix_functions() = test { + val a = cell("equal") + val b = cell("equal") + val c = cell("NOT equal") + + assertTrue((a eq b).value) + assertTrue((a eq "equal").value) + assertFalse((a eq c).value) + assertFalse((a eq "NOT equal").value) + + assertFalse((a ne b).value) + assertFalse((a ne "equal").value) + assertTrue((a ne c).value) + assertTrue((a ne "NOT equal").value) + } + + @Test + fun orElse() = test { + for (value in arrayOf("value", null)) { + val c: Cell = cell(value) + val coe: Cell = c.orElse { "default" } + + assertEquals(value ?: "default", coe.value) + } + } + + @Test + fun comparable_extensions() = test { + val a = cell(1) + val b = cell(2) + + assertFalse((a gt b).value) + assertFalse((a gt 2).value) + assertFalse((1 gt b).value) + + assertTrue((a lt b).value) + assertTrue((a lt 2).value) + assertTrue((1 lt b).value) + } + + @Test + fun boolean_extensions() = test { + for (a in arrayOf(false, true)) { + val aCell = cell(a) + + assertEquals(!a, (!aCell).value) + + for (b in arrayOf(false, true)) { + val bCell = cell(b) + + assertEquals(a && b, (aCell and bCell).value) + assertEquals(a && b, (aCell and b).value) + assertEquals(a && b, (a and bCell).value) + + assertEquals(a || b, (aCell or bCell).value) + assertEquals(a || b, (aCell or b).value) + assertEquals(a || b, (a or bCell).value) + + // Use != because of https://youtrack.jetbrains.com/issue/KT-31277. + assertEquals(a != b, (aCell xor bCell).value) + assertEquals(a != b, (aCell xor b).value) + assertEquals(a != b, (a xor bCell).value) + } + } + } + + @Test + fun string_extensions() = test { + for (string in arrayOf("", " ", "\t\t", "non-empty-non-blank")) { + val stringCell = cell(string) + + assertEquals(string.isEmpty(), stringCell.isEmpty().value) + + assertEquals(string.isNotEmpty(), stringCell.isNotEmpty().value) + + assertEquals(string.isBlank(), stringCell.isBlank().value) + + assertEquals(string.isNotBlank(), stringCell.isNotBlank().value) + } + } +} diff --git a/cell/src/commonTest/kotlin/world/phantasmal/cell/DelegatingCellTests.kt b/cell/src/commonTest/kotlin/world/phantasmal/cell/DelegatingCellTests.kt index d114d667..187a4ab7 100644 --- a/cell/src/commonTest/kotlin/world/phantasmal/cell/DelegatingCellTests.kt +++ b/cell/src/commonTest/kotlin/world/phantasmal/cell/DelegatingCellTests.kt @@ -1,7 +1,7 @@ package world.phantasmal.cell @Suppress("unused") -class DelegatingCellTests : RegularCellTests, MutableCellTests { +class DelegatingCellTests : MutableCellTests { override fun createProvider() = object : MutableCellTests.Provider { private var v = 17 @@ -13,9 +13,4 @@ class DelegatingCellTests : RegularCellTests, MutableCellTests { override fun createValue(): Int = v + 1 } - - override fun createWithValue(value: T): Cell { - var v = value - return DelegatingCell({ v }, { v = it }) - } } diff --git a/cell/src/commonTest/kotlin/world/phantasmal/cell/DependentCellTests.kt b/cell/src/commonTest/kotlin/world/phantasmal/cell/DependentCellTests.kt index 5320633d..5219144c 100644 --- a/cell/src/commonTest/kotlin/world/phantasmal/cell/DependentCellTests.kt +++ b/cell/src/commonTest/kotlin/world/phantasmal/cell/DependentCellTests.kt @@ -1,14 +1,9 @@ package world.phantasmal.cell @Suppress("unused") -class DependentCellTests : RegularCellTests, CellWithDependenciesTests { +class DependentCellTests : CellWithDependenciesTests { override fun createProvider() = Provider() - override fun createWithValue(value: T): Cell { - val dependency = SimpleCell(value) - return DependentCell(dependency) { dependency.value } - } - override fun createWithDependencies( dependency1: Cell, dependency2: Cell, diff --git a/cell/src/commonTest/kotlin/world/phantasmal/cell/DoubleCellsTests.kt b/cell/src/commonTest/kotlin/world/phantasmal/cell/DoubleCellsTests.kt new file mode 100644 index 00000000..4957759c --- /dev/null +++ b/cell/src/commonTest/kotlin/world/phantasmal/cell/DoubleCellsTests.kt @@ -0,0 +1,36 @@ +package world.phantasmal.cell + +import world.phantasmal.cell.test.CellTestSuite +import kotlin.test.Test +import kotlin.test.assertEquals + +class DoubleCellsTests : CellTestSuite { + @Test + fun extensions() = test { + for (a in arrayOf(-5.0, -2.0, 0.0, 2.0, 5.0)) { + val aCell = cell(a) + + assertEquals(-a, (-aCell).value) + + for (b in arrayOf(-5.4, -3.2, 1.138724, 2.076283, 500.0)) { + val bCell = cell(b) + + assertEquals(a + b, (aCell + bCell).value) + assertEquals(a + b, (aCell + b).value) + assertEquals(a + b, (a + bCell).value) + + assertEquals(a - b, (aCell - bCell).value) + assertEquals(a - b, (aCell - b).value) + assertEquals(a - b, (a - bCell).value) + + assertEquals(a * b, (aCell * bCell).value) + assertEquals(a * b, (aCell * b).value) + assertEquals(a * b, (a * bCell).value) + + assertEquals(a / b, (aCell / bCell).value) + assertEquals(a / b, (aCell / b).value) + assertEquals(a / b, (a / bCell).value) + } + } + } +} diff --git a/cell/src/commonTest/kotlin/world/phantasmal/cell/FlatteningDependentCellDirectDependencyEmitsTests.kt b/cell/src/commonTest/kotlin/world/phantasmal/cell/FlatteningDependentCellDirectDependencyEmitsTests.kt index 7381e932..b96de353 100644 --- a/cell/src/commonTest/kotlin/world/phantasmal/cell/FlatteningDependentCellDirectDependencyEmitsTests.kt +++ b/cell/src/commonTest/kotlin/world/phantasmal/cell/FlatteningDependentCellDirectDependencyEmitsTests.kt @@ -4,7 +4,7 @@ package world.phantasmal.cell * In these tests the direct dependency of the [FlatteningDependentCell] changes. */ @Suppress("unused") -class FlatteningDependentCellDirectDependencyEmitsTests : RegularCellTests { +class FlatteningDependentCellDirectDependencyEmitsTests : CellTests { override fun createProvider() = object : CellTests.Provider { // The transitive dependency can't change. val transitiveDependency = ImmutableCell(5) @@ -21,9 +21,4 @@ class FlatteningDependentCellDirectDependencyEmitsTests : RegularCellTests { directDependency.value = ImmutableCell(oldTransitiveDependency.value + 5) } } - - override fun createWithValue(value: T): Cell { - val v = ImmutableCell(ImmutableCell(value)) - return FlatteningDependentCell(v) { v.value } - } } diff --git a/cell/src/commonTest/kotlin/world/phantasmal/cell/FlatteningDependentCellTransitiveDependencyEmitsTests.kt b/cell/src/commonTest/kotlin/world/phantasmal/cell/FlatteningDependentCellTransitiveDependencyEmitsTests.kt index a1052bbe..f92a858c 100644 --- a/cell/src/commonTest/kotlin/world/phantasmal/cell/FlatteningDependentCellTransitiveDependencyEmitsTests.kt +++ b/cell/src/commonTest/kotlin/world/phantasmal/cell/FlatteningDependentCellTransitiveDependencyEmitsTests.kt @@ -4,17 +4,10 @@ package world.phantasmal.cell * In these tests the dependency of the [FlatteningDependentCell]'s direct dependency changes. */ @Suppress("unused") -class FlatteningDependentCellTransitiveDependencyEmitsTests : - RegularCellTests, - CellWithDependenciesTests { +class FlatteningDependentCellTransitiveDependencyEmitsTests : CellWithDependenciesTests { override fun createProvider() = Provider() - override fun createWithValue(value: T): Cell { - val dependency = ImmutableCell(ImmutableCell(value)) - return FlatteningDependentCell(dependency) { dependency.value } - } - override fun createWithDependencies( dependency1: Cell, dependency2: Cell, diff --git a/cell/src/commonTest/kotlin/world/phantasmal/cell/IntCellsTests.kt b/cell/src/commonTest/kotlin/world/phantasmal/cell/IntCellsTests.kt new file mode 100644 index 00000000..0cc5a5c7 --- /dev/null +++ b/cell/src/commonTest/kotlin/world/phantasmal/cell/IntCellsTests.kt @@ -0,0 +1,36 @@ +package world.phantasmal.cell + +import world.phantasmal.cell.test.CellTestSuite +import kotlin.test.Test +import kotlin.test.assertEquals + +class IntCellsTests : CellTestSuite { + @Test + fun extensions() = test { + for (a in -5..5) { + val aCell = cell(a) + + assertEquals(-a, (-aCell).value) + + for (b in 1..5) { + val bCell = cell(b) + + assertEquals(a + b, (aCell + bCell).value) + assertEquals(a + b, (aCell + b).value) + assertEquals(a + b, (a + bCell).value) + + assertEquals(a - b, (aCell - bCell).value) + assertEquals(a - b, (aCell - b).value) + assertEquals(a - b, (a - bCell).value) + + assertEquals(a * b, (aCell * bCell).value) + assertEquals(a * b, (aCell * b).value) + assertEquals(a * b, (a * bCell).value) + + assertEquals(a / b, (aCell / bCell).value) + assertEquals(a / b, (aCell / b).value) + assertEquals(a / b, (a / bCell).value) + } + } + } +} diff --git a/cell/src/commonTest/kotlin/world/phantasmal/cell/RegularCellTests.kt b/cell/src/commonTest/kotlin/world/phantasmal/cell/RegularCellTests.kt deleted file mode 100644 index 7c60dad5..00000000 --- a/cell/src/commonTest/kotlin/world/phantasmal/cell/RegularCellTests.kt +++ /dev/null @@ -1,141 +0,0 @@ -package world.phantasmal.cell - -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertTrue - -/** - * Test suite for all [Cell] implementations that aren't ListCells. There is a subclass of this - * suite for every non-ListCell [Cell] implementation. - */ -interface RegularCellTests : CellTests { - fun createWithValue(value: T): Cell - - // TODO: Move this test to CellTests. - @Test - fun convenience_methods() = test { - listOf(Any(), null).forEach { any -> - val anyCell = createWithValue(any) - - // Test the test setup first. - assertEquals(any, anyCell.value) - - // Test `isNull`. - assertEquals(any == null, anyCell.isNull().value) - - // Test `isNotNull`. - assertEquals(any != null, anyCell.isNotNull().value) - } - } - - // TODO: Move this test to CellTests. - @Test - fun generic_extensions() = test { - listOf(Any(), null).forEach { any -> - val anyCell = createWithValue(any) - - // Test the test setup first. - assertEquals(any, anyCell.value) - - // Test `orElse`. - assertEquals(any ?: "default", anyCell.orElse { "default" }.value) - } - - fun testEqNe(a: T, b: T) { - val aCell = createWithValue(a) - val bCell = createWithValue(b) - - // Test the test setup first. - assertEquals(a, aCell.value) - assertEquals(b, bCell.value) - - // Test `eq`. - assertEquals(a == b, (aCell eq b).value) - assertEquals(a == b, (aCell eq bCell).value) - - // Test `ne`. - assertEquals(a != b, (aCell ne b).value) - assertEquals(a != b, (aCell ne bCell).value) - } - - testEqNe(null, null) - testEqNe(null, Unit) - testEqNe(Unit, Unit) - testEqNe(10, 10) - testEqNe(5, 99) - testEqNe("a", "a") - testEqNe("x", "y") - } - - @Test - fun comparable_extensions() = test { - fun > comparable_tests(a: T, b: T) { - val aCell = createWithValue(a) - val bCell = createWithValue(b) - - // Test the test setup first. - assertEquals(a, aCell.value) - assertEquals(b, bCell.value) - - // Test `gt`. - assertEquals(a > b, (aCell gt b).value) - assertEquals(a > b, (aCell gt bCell).value) - - // Test `lt`. - assertEquals(a < b, (aCell lt b).value) - assertEquals(a < b, (aCell lt bCell).value) - } - - comparable_tests(10, 10) - comparable_tests(7.0, 5.0) - comparable_tests((5000).toShort(), (7000).toShort()) - } - - @Test - fun boolean_extensions() = test { - listOf(true, false).forEach { bool -> - val boolCell = createWithValue(bool) - - // Test the test setup first. - assertEquals(bool, boolCell.value) - - // Test `and`. - assertEquals(bool, (boolCell and trueCell()).value) - assertFalse((boolCell and falseCell()).value) - - // Test `or`. - assertTrue((boolCell or trueCell()).value) - assertEquals(bool, (boolCell or falseCell()).value) - - // Test `xor`. - assertEquals(!bool, (boolCell xor trueCell()).value) - assertEquals(bool, (boolCell xor falseCell()).value) - - // Test `!` (unary not). - assertEquals(!bool, (!boolCell).value) - } - } - - @Test - fun string_extensions() = test { - listOf("", " ", "\t\t", "non-empty-non-blank").forEach { string -> - val stringCell = createWithValue(string) - - // Test the test setup first. - assertEquals(string, stringCell.value) - - // Test `isEmpty`. - assertEquals(string.isEmpty(), stringCell.isEmpty().value) - - // Test `isNotEmpty`. - assertEquals(string.isNotEmpty(), stringCell.isNotEmpty().value) - - // Test `isBlank`. - assertEquals(string.isBlank(), stringCell.isBlank().value) - - // Test `isNotBlank`. - assertEquals(string.isNotBlank(), stringCell.isNotBlank().value) - } - } -} diff --git a/cell/src/commonTest/kotlin/world/phantasmal/cell/SimpleCellTests.kt b/cell/src/commonTest/kotlin/world/phantasmal/cell/SimpleCellTests.kt index f65e8d6a..e31178b5 100644 --- a/cell/src/commonTest/kotlin/world/phantasmal/cell/SimpleCellTests.kt +++ b/cell/src/commonTest/kotlin/world/phantasmal/cell/SimpleCellTests.kt @@ -1,7 +1,7 @@ package world.phantasmal.cell @Suppress("unused") -class SimpleCellTests : RegularCellTests, MutableCellTests { +class SimpleCellTests : MutableCellTests { override fun createProvider() = object : MutableCellTests.Provider { override val cell = SimpleCell(1) @@ -11,6 +11,4 @@ class SimpleCellTests : RegularCellTests, MutableCellTests { override fun createValue(): Int = cell.value + 1 } - - override fun createWithValue(value: T): Cell = SimpleCell(value) }