diff --git a/app/src/main/kotlin/br/com/colman/petals/hittimer/HitTimer.kt b/app/src/main/kotlin/br/com/colman/petals/hittimer/HitTimer.kt index b99eaed6..63179c0a 100644 --- a/app/src/main/kotlin/br/com/colman/petals/hittimer/HitTimer.kt +++ b/app/src/main/kotlin/br/com/colman/petals/hittimer/HitTimer.kt @@ -33,9 +33,10 @@ class HitTimer(val durationMillis: Long = 10_000L) : Parcelable { startDate = null } + @Suppress("UnsafeCallOnNullableType") private fun calculateMillisLeft(): Long { if (startDate == null) return durationMillis - val elapsed = startDate?.until(now(), MILLIS) ?: 0 + val elapsed = startDate!!.until(now(), MILLIS) return (durationMillis - elapsed).coerceAtLeast(0) } diff --git a/app/src/test/kotlin/br/com/colman/petals/hittimer/HitTimerTest.kt b/app/src/test/kotlin/br/com/colman/petals/hittimer/HitTimerTest.kt index 2f41a050..0a5c3470 100644 --- a/app/src/test/kotlin/br/com/colman/petals/hittimer/HitTimerTest.kt +++ b/app/src/test/kotlin/br/com/colman/petals/hittimer/HitTimerTest.kt @@ -5,8 +5,12 @@ import io.kotest.datatest.withData import io.kotest.inspectors.forAll import io.kotest.matchers.collections.shouldBeMonotonicallyDecreasing import io.kotest.matchers.ints.shouldBeGreaterThanOrEqual +import io.kotest.matchers.longs.shouldBeLessThanOrEqual import io.kotest.matchers.shouldBe +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.take +import kotlinx.coroutines.flow.takeWhile import kotlinx.coroutines.flow.toList class HitTimerTest : FunSpec({ @@ -35,17 +39,72 @@ class HitTimerTest : FunSpec({ allResults.size shouldBeGreaterThanOrEqual (duration / 10).toInt() } - context("Duration with milliseconds disabled should not show 0 while there are still milliseconds") { + context("Error Cases") { + test("Should return max duration if timer wasn't started") { + target.millisLeft.first() shouldBe duration + } + } + + context("Reset functionality") { + test("Should reset to initial duration when reset is called after starting") { + target.start() + target.reset() + target.millisLeft.first() shouldBe duration + } + + test("After timer completes, reset should restore duration") { + target.start() + target.millisLeft.takeWhile { it > 0 }.toList() + target.reset() + target.millisLeft.first() shouldBe duration + } + } + + context("Start multiple times") { + test("Calling start multiple times resets the timer") { + target.start() + delay(20L) + val firstValue = target.millisLeft.first() + target.start() + val secondValue = target.millisLeft.first() + + secondValue shouldBe duration + firstValue shouldBeLessThanOrEqual (duration - 20) + } + } + + context("Duration formatting") { withData( - nameFn = { (millis, string) -> "$millis milliseconds should be converted to $string" }, - 0L to "0.0", - 100L to "0.1", - 249L to "0.2", - 250L to "0.3", - 666L to "0.7", - 1200L to "1" + nameFn = { (millis, expected) -> "$millis ms -> $expected" }, + 0L to "00:000", + 1000L to "01:000", + 1234L to "01:234", + 9999L to "09:999", + 10_000L to "10:000" + ) { (millis, expected) -> + HitTimer.duration(millis) shouldBe expected + } + } + + context("Duration with milliseconds disabled edge cases") { + withData( + nameFn = { (millis, string) -> "$millis ms -> $string" }, + 999L to "1.0", + 1000L to "1.0", + 1001L to "1", + 500L to "0.5", + 499L to "0.5", + 1L to "0.0", + 0L to "0.0" ) { (millis, string) -> HitTimer.durationMillisecondsDisabled(millis) shouldBe string } } + + test("Timer stays at zero after duration has passed") { + target.start() + delay(duration) + target.millisLeft.first() shouldBe 0 + target.millisLeft.take(3).toList().forAll { it shouldBe 0 } + } })