diff --git a/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/MiniRobots.kt b/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/MiniRobots.kt index 483692792..1a213d7be 100644 --- a/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/MiniRobots.kt +++ b/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/MiniRobots.kt @@ -1,6 +1,7 @@ package io.github.droidkaigi.confsched.testing import androidx.compose.ui.test.junit4.ComposeTestRule +import com.github.takahirom.roborazzi.roboOutputName import io.github.droidkaigi.confsched.data.contributors.ContributorsApiClient import io.github.droidkaigi.confsched.data.contributors.FakeContributorsApiClient import io.github.droidkaigi.confsched.data.eventmap.EventMapApiClient @@ -66,6 +67,13 @@ fun todoChecks(@Suppress("UNUSED_PARAMETER") reason: String): () -> Unit { class DefaultCaptureScreenRobot @Inject constructor(private val robotTestRule: RobotTestRule) : CaptureScreenRobot { override fun captureScreenWithChecks(checks: () -> Unit) { + val roboOutputName = roboOutputName() + if (roboOutputName.contains("[") && roboOutputName.contains("]")) { + val name = roboOutputName.substringAfter("[").substringBefore("]") + robotTestRule.captureScreen(name) + checks() + return + } robotTestRule.captureScreen() checks() } @@ -75,12 +83,13 @@ class DefaultCaptureScreenRobot @Inject constructor(private val robotTestRule: R replaceWith = ReplaceWith("captureScreenWithChecks(checks)"), ) override fun captureScreenWithChecks() { - robotTestRule.captureScreen() + captureScreenWithChecks { } } } interface WaitRobot { fun waitUntilIdle() + fun wait5Seconds() } class DefaultWaitRobot @Inject constructor( @@ -94,6 +103,14 @@ class DefaultWaitRobot @Inject constructor( ShadowLooper.runUiThreadTasksIncludingDelayedTasks() } } + + override fun wait5Seconds() { + repeat(5) { + testDispatcher.scheduler.advanceTimeBy(1.seconds) + robotTestRule.composeTestRule.mainClock.advanceTimeBy(1000) + ShadowLooper.runUiThreadTasksIncludingDelayedTasks() + } + } } interface FontScaleRobot { diff --git a/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/RobolectricDescribeSpecParameterBuilder.kt b/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/RobolectricDescribeSpecParameterBuilder.kt index 36152a414..4751c2c6a 100644 --- a/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/RobolectricDescribeSpecParameterBuilder.kt +++ b/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/RobolectricDescribeSpecParameterBuilder.kt @@ -1,18 +1,21 @@ package io.github.droidkaigi.confsched.testing -inline fun describeTests(block: TestCaseTreeBuilder.() -> Unit): List> { +inline fun describeBehaviors( + name: String, + block: TestCaseTreeBuilder.() -> Unit, +): List> { val builder = TestCaseTreeBuilder() builder.block() - val root = builder.build() - return generateTestCases(root) + val root = builder.build(name = name) + return generateBehaviors(root) } -suspend fun DescribedTestCase.execute(robot: T) { +suspend fun DescribedBehavior.execute(robot: T) { for ((index, step) in steps.withIndex()) { println("Executing step: $index ($description)") when (step) { is TestNode.Run -> step.action(robot) - is TestNode.It -> { + is TestNode.ItShould -> { if (step.description == targetCheckDescription) { step.action(robot) } @@ -27,10 +30,10 @@ suspend fun DescribedTestCase.execute(robot: T) { sealed class TestNode { data class Describe(val description: String, val children: List>) : TestNode() data class Run(val action: suspend T.() -> Unit) : TestNode() - data class It(val description: String, val action: suspend T.() -> Unit) : TestNode() + data class ItShould(val description: String, val action: suspend T.() -> Unit) : TestNode() } -data class DescribedTestCase( +data class DescribedBehavior( val description: String, val steps: List>, val targetCheckDescription: String, @@ -46,7 +49,7 @@ data class AncestryNode( data class CheckNode( val description: String, val fullDescription: String, - val node: TestNode.It, + val node: TestNode.ItShould, val ancestry: List>, ) @@ -63,21 +66,20 @@ class TestCaseTreeBuilder { children.add(TestNode.Run { action() }) } - fun it(description: String, action: suspend T.() -> Unit) { - children.add(TestNode.It(description) { action() }) + fun itShould(description: String, action: suspend T.() -> Unit) { + children.add(TestNode.ItShould(description) { action() }) } - fun build(): TestNode.Describe = TestNode.Describe("", children) + fun build(name: String): TestNode.Describe = TestNode.Describe(name, children) } -fun generateTestCases(root: TestNode.Describe): List> { +fun generateBehaviors(root: TestNode.Describe): List> { val checkNodes = collectCheckNodes(root) return checkNodes.map { createTestCase(it) } } /** * Collect all check nodes from the test tree - * it will be O(N) */ private fun collectCheckNodes(root: TestNode.Describe): List> { val checkNodes = mutableListOf>() @@ -93,9 +95,9 @@ private fun collectCheckNodes(root: TestNode.Describe): List } } - is TestNode.It -> { + is TestNode.ItShould -> { val fullDescription = if (parentDescription.isNotBlank()) { - "$parentDescription - it ${node.description}" + "$parentDescription - it should ${node.description}" } else { node.description } @@ -112,10 +114,8 @@ private fun collectCheckNodes(root: TestNode.Describe): List /** * Create a test case from a check node - * We only run the steps that are necessary to reach the check node - * so the time complexity might be O(logN) */ -private fun createTestCase(checkNode: CheckNode): DescribedTestCase { +private fun createTestCase(checkNode: CheckNode): DescribedBehavior { val steps = mutableListOf>() fun processNode(node: TestNode, ancestry: List>, depth: Int) { @@ -136,7 +136,7 @@ private fun createTestCase(checkNode: CheckNode): DescribedTestCase { steps.add(node) } - is TestNode.It -> { + is TestNode.ItShould -> { if (node == checkNode.node) { steps.add(node) } @@ -146,5 +146,5 @@ private fun createTestCase(checkNode: CheckNode): DescribedTestCase { processNode(checkNode.ancestry.first().node, emptyList(), 0) - return DescribedTestCase(checkNode.fullDescription, steps, checkNode.description) + return DescribedBehavior(checkNode.fullDescription, steps, checkNode.description) } diff --git a/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/RobotTestRule.kt b/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/RobotTestRule.kt index 7c1d24986..662ff66ed 100644 --- a/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/RobotTestRule.kt +++ b/core/testing/src/main/java/io/github/droidkaigi/confsched/testing/RobotTestRule.kt @@ -119,9 +119,10 @@ class RobotTestRule( val engine = FakeImageLoaderEngine.Builder() .default(ColorDrawable(android.graphics.Color.BLUE)) .build() - val imageLoader = ImageLoader.Builder(ApplicationProvider.getApplicationContext()) - .components { add(engine) } - .build() + val imageLoader = + ImageLoader.Builder(ApplicationProvider.getApplicationContext()) + .components { add(engine) } + .build() SingletonImageLoader.setUnsafe(imageLoader) } }) @@ -165,8 +166,12 @@ class RobotTestRule( } } - fun captureScreen() { - captureScreenRoboImage() + fun captureScreen(name: String? = null) { + if (name != null) { + captureScreenRoboImage("$name.png") + } else { + captureScreenRoboImage() + } } } diff --git a/feature/sessions/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/sessions/TimetableItemDetailScreenTest.kt b/feature/sessions/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/sessions/TimetableItemDetailScreenTest.kt index 37dea477e..306273eb5 100644 --- a/feature/sessions/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/sessions/TimetableItemDetailScreenTest.kt +++ b/feature/sessions/src/androidUnitTest/kotlin/io/github/droidkaigi/confsched/sessions/TimetableItemDetailScreenTest.kt @@ -3,10 +3,10 @@ package io.github.droidkaigi.confsched.sessions import android.os.Bundle import dagger.hilt.android.testing.BindValue import dagger.hilt.android.testing.HiltAndroidTest -import io.github.droidkaigi.confsched.testing.DescribedTestCase +import io.github.droidkaigi.confsched.testing.DescribedBehavior import io.github.droidkaigi.confsched.testing.RobotTestRule import io.github.droidkaigi.confsched.testing.TimetableServerRobot.ServerStatus -import io.github.droidkaigi.confsched.testing.describeTests +import io.github.droidkaigi.confsched.testing.describeBehaviors import io.github.droidkaigi.confsched.testing.execute import io.github.droidkaigi.confsched.testing.robot.TimetableItemDetailScreenRobot import io.github.droidkaigi.confsched.testing.runRobot @@ -18,7 +18,7 @@ import javax.inject.Inject @RunWith(ParameterizedRobolectricTestRunner::class) @HiltAndroidTest -class TimetableItemDetailScreenTest(private val testCase: DescribedTestCase) { +class TimetableItemDetailScreenTest(private val testCase: DescribedBehavior) { @get:Rule @BindValue val robotTestRule: RobotTestRule = RobotTestRule( @@ -35,7 +35,7 @@ class TimetableItemDetailScreenTest(private val testCase: DescribedTestCase