From 072cb223618947342cfd6e08d6c5f1cfb799a5cf Mon Sep 17 00:00:00 2001 From: Alejandro Serrano Date: Mon, 10 Jul 2023 10:15:21 +0200 Subject: [PATCH] Additional tests for `copy` in Optics --- .../kotlin/arrow/optics/plugin/CopyTest.kt | 99 ++++++++++++++++ .../kotlin/arrow/optics/CopyTest.kt | 111 ++++++++++++++++++ 2 files changed, 210 insertions(+) create mode 100644 arrow-libs/optics/arrow-optics-ksp-plugin/src/test/kotlin/arrow/optics/plugin/CopyTest.kt create mode 100644 arrow-libs/optics/arrow-optics/src/commonTest/kotlin/arrow/optics/CopyTest.kt diff --git a/arrow-libs/optics/arrow-optics-ksp-plugin/src/test/kotlin/arrow/optics/plugin/CopyTest.kt b/arrow-libs/optics/arrow-optics-ksp-plugin/src/test/kotlin/arrow/optics/plugin/CopyTest.kt new file mode 100644 index 00000000000..b9cf3ef208d --- /dev/null +++ b/arrow-libs/optics/arrow-optics-ksp-plugin/src/test/kotlin/arrow/optics/plugin/CopyTest.kt @@ -0,0 +1,99 @@ +package arrow.optics.plugin + +import org.junit.jupiter.api.Test + +// from https://kotlinlang.slack.com/archives/C5UPMM0A0/p1688822411819599 +// and https://github.com/overfullstack/my-lab/blob/master/arrow/src/test/kotlin/ga/overfullstack/optics/OpticsLab.kt + +val copyCode = """ +@optics data class Person(val name: String, val age: Int, val address: Address) { + companion object +} +@optics data class Address(val street: Street, val city: City, val coordinates: List) { + companion object +} +@optics data class Street(val name: String, val number: Int?) { + companion object +} +@optics data class City(val name: String, val country: String) { + companion object +} + +fun Person.moveToAmsterdamCopy(): Person = copy { + Person.address.city.name set "Amsterdam" + Person.address.city.country set "Netherlands" + Person.address .coordinates set listOf(2, 3) +} + +fun Person.moveToAmsterdamInside(): Person = copy { + inside(Person.address.city) { + City.name set "Amsterdam" + City.country set "Netherlands" + } +} + +val me = + Person( + "Gopal", + 99, + Address(Street("Kotlinstraat", 1), City("Hilversum", "Netherlands"), listOf(1, 2)) + ) +""" + +class CopyTest { + @Test + fun `code compiles`() { + """ + |package PersonTest + |$imports + |$copyCode + """.compilationSucceeds() + } + + @Test + fun `birthday increments`() { + """ + |package PersonTest + |$imports + |$copyCode + |val meAfterBirthdayParty = Person.age.modify(me) { it + 1 } + |val r = Person.age.get(meAfterBirthdayParty) + """.evals("r" to 100) + } + + @Test + fun `moving to another city`() { + """ + |package PersonTest + |$imports + |$copyCode + |val newAddress = + | Address(Street("Kotlinplein", null), City("Amsterdam", "Netherlands"), listOf(1, 2)) + |val meAfterMoving = Person.address.set(me, newAddress) + |val r = Person.address.get(meAfterMoving).street.name + """.evals("r" to "Kotlinplein") + } + + @Test + fun `optics composition`() { + """ + |package PersonTest + |$imports + |$copyCode + |val personCity: Lens = Person.address compose Address.city compose City.name + |val meAtTheCapital = personCity.set(me, "Amsterdam") + |val r = meAtTheCapital.address.city.name + """.evals("r" to "Amsterdam") + } + + @Test + fun `optics copy to modify multiple fields`() { + """ + |package PersonTest + |$imports + |$copyCode + |val meAfterMoving = me.moveToAmsterdamInside() + |val r = meAfterMoving.address.city.name + """.evals("r" to "Amsterdam") + } +} diff --git a/arrow-libs/optics/arrow-optics/src/commonTest/kotlin/arrow/optics/CopyTest.kt b/arrow-libs/optics/arrow-optics/src/commonTest/kotlin/arrow/optics/CopyTest.kt new file mode 100644 index 00000000000..2426b4730ac --- /dev/null +++ b/arrow-libs/optics/arrow-optics/src/commonTest/kotlin/arrow/optics/CopyTest.kt @@ -0,0 +1,111 @@ +package arrow.optics + +import io.kotest.core.spec.style.StringSpec +import io.kotest.matchers.shouldBe + +// from https://kotlinlang.slack.com/archives/C5UPMM0A0/p1688822411819599 +// and https://github.com/overfullstack/my-lab/blob/master/arrow/src/test/kotlin/ga/overfullstack/optics/OpticsLab.kt + +data class Person(val name: String, val age: Int, val address: Address) { + companion object { + val name: Lens = Lens( + get = { it.name }, + set = { p, x -> p.copy(name = x) } + ) + val age: Lens = Lens( + get = { it.age }, + set = { p, x -> p.copy(age = x) } + ) + val address: Lens = Lens( + get = { it.address }, + set = { p, x -> p.copy(address = x) } + ) + } +} +data class Address(val street: Street, val city: City, val coordinates: List) { + companion object { + val city: Lens = Lens( + get = { it.city }, + set = { a, x -> a.copy(city = x) } + ) + val coordinates: Lens> = Lens( + get = { it.coordinates }, + set = { a, x -> a.copy(coordinates = x) } + ) + } +} +data class Street(val name: String, val number: Int?) +data class City(val name: String, val country: String) { + companion object { + val name: Lens = Lens( + get = { it.name }, + set = { c, x -> c.copy(name = x) } + ) + val country: Lens = Lens( + get = { it.country }, + set = { c, x -> c.copy(country = x) } + ) + } +} + +fun Person.moveToAmsterdamCopy(): Person = copy { + Person.address + Address.city + City.name set "Amsterdam" + Person.address + Address.city + City.country set "Netherlands" + Person.address + Address.coordinates set listOf(2, 3) +} + +fun Person.moveToAmsterdamInside(): Person = copy { + inside(Person.address + Address.city) { + City.name set "Amsterdam" + City.country set "Netherlands" + } +} + +class CopyTest : StringSpec({ + "optics" { + val me = + Person( + "Gopal", + 99, + Address(Street("Kotlinstraat", 1), City("Hilversum", "Netherlands"), listOf(1, 2)) + ) + + Person.name.get(me) shouldBe "Gopal" + + val meAfterBirthdayParty = Person.age.modify(me) { it + 1 } + Person.age.get(meAfterBirthdayParty) shouldBe 100 + + val newAddress = + Address(Street("Kotlinplein", null), City("Amsterdam", "Netherlands"), listOf(1, 2)) + val meAfterMoving = Person.address.set(me, newAddress) + Person.address.get(meAfterMoving) shouldBe newAddress + } + + "optics composition" { + val personCity: Lens = Person.address compose Address.city compose City.name + + val me = + Person( + "Alejandro", + 35, + Address(Street("Kotlinstraat", 1), City("Hilversum", "Netherlands"), listOf(1, 2)) + ) + + personCity.get(me) shouldBe "Hilversum" + val meAtTheCapital = personCity.set(me, "Amsterdam") + meAtTheCapital.address.city.name shouldBe "Amsterdam" + } + + "optics copy to modify multiple fields" { + val me = + Person( + "Alejandro", + 35, + Address(Street("Kotlinstraat", 1), City("Hilversum", "Netherlands"), listOf(1, 2)) + ) + val meAfterMoving1 = me.moveToAmsterdamInside() + val meAfterMoving2 = me.moveToAmsterdamInside() + meAfterMoving1 shouldBe meAfterMoving2 + meAfterMoving1.address.city.name shouldBe "Amsterdam" + } +})