Move the fake entry points of RootFlowNodeTest to the test modules

The fakes were declared in RootFlowNodeTest, so other tests could not use
them. Move each of them next to the API it fakes, following the existing
convention of a createNodeResult lambda defaulting to lambdaError():

- FakeLoginEntryPoint in features/login/test
- FakeBugReportEntryPoint in features/rageshake/test
- FakeSignedOutEntryPoint in features/signedout/test (new module)
- FakeAccountSelectEntryPoint in libraries/accountselect/test (new module)

The login and rageshake test modules did not have Appyx on their
classpath, so add the architecture module to their dependencies.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benoit Marty
2026-08-20 15:11:00 +02:00
parent b3f53da7d9
commit f0e575e25e
10 changed files with 156 additions and 37 deletions
+2
View File
@@ -61,6 +61,7 @@ dependencies {
testCommonDependencies(libs) testCommonDependencies(libs)
testImplementation(projects.features.login.test) testImplementation(projects.features.login.test)
testImplementation(projects.features.share.test) testImplementation(projects.features.share.test)
testImplementation(projects.libraries.accountselect.test)
testImplementation(projects.libraries.featureflag.test) testImplementation(projects.libraries.featureflag.test)
testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.matrix.test)
testImplementation(projects.libraries.matrixmedia.test) testImplementation(projects.libraries.matrixmedia.test)
@@ -75,6 +76,7 @@ dependencies {
testImplementation(projects.features.messages.test) testImplementation(projects.features.messages.test)
testImplementation(projects.features.networkmonitor.test) testImplementation(projects.features.networkmonitor.test)
testImplementation(projects.features.rageshake.test) testImplementation(projects.features.rageshake.test)
testImplementation(projects.features.signedout.test)
testImplementation(projects.services.apperror.test) testImplementation(projects.services.apperror.test)
testImplementation(projects.services.appnavstate.test) testImplementation(projects.services.appnavstate.test)
testImplementation(projects.services.analytics.test) testImplementation(projects.services.analytics.test)
@@ -33,13 +33,14 @@ import io.element.android.appnav.root.RootPresenter
import io.element.android.features.login.api.LoginEntryPoint import io.element.android.features.login.api.LoginEntryPoint
import io.element.android.features.login.api.LoginParams import io.element.android.features.login.api.LoginParams
import io.element.android.features.login.api.accesscontrol.AccountProviderAccessControl import io.element.android.features.login.api.accesscontrol.AccountProviderAccessControl
import io.element.android.features.login.test.FakeLoginEntryPoint
import io.element.android.features.login.test.FakeLoginIntentResolver import io.element.android.features.login.test.FakeLoginIntentResolver
import io.element.android.features.preferences.api.CacheService import io.element.android.features.preferences.api.CacheService
import io.element.android.features.rageshake.api.bugreport.BugReportEntryPoint import io.element.android.features.rageshake.test.FakeBugReportEntryPoint
import io.element.android.features.rageshake.test.logs.FakeAnnouncementService import io.element.android.features.rageshake.test.logs.FakeAnnouncementService
import io.element.android.features.share.test.FakeShareIntentHandler import io.element.android.features.share.test.FakeShareIntentHandler
import io.element.android.features.signedout.api.SignedOutEntryPoint import io.element.android.features.signedout.test.FakeSignedOutEntryPoint
import io.element.android.libraries.accountselect.api.AccountSelectEntryPoint import io.element.android.libraries.accountselect.test.FakeAccountSelectEntryPoint
import io.element.android.libraries.architecture.AssistedNodeFactory import io.element.android.libraries.architecture.AssistedNodeFactory
import io.element.android.libraries.architecture.NodeFactoriesBindings import io.element.android.libraries.architecture.NodeFactoriesBindings
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
@@ -81,8 +82,13 @@ class RootFlowNodeTest : RobolectricTest() {
@Test @Test
fun `given no session, when a login link is handled before the nav state is observed, then the login params are kept`() = runTest { fun `given no session, when a login link is handled before the nav state is observed, then the login params are kept`() = runTest {
val loginEntryPoint = FakeLoginEntryPoint() var loginEntryPointParams: LoginEntryPoint.Params? = null
val rootFlowNode = createRootFlowNode(loginEntryPoint = loginEntryPoint) val rootFlowNode = createRootFlowNode(
loginEntryPoint = FakeLoginEntryPoint { buildContext, params ->
loginEntryPointParams = params
node(buildContext) {}
},
)
rootFlowNode.parentNodeTestHelper() rootFlowNode.parentNodeTestHelper()
// The intent is handled first, this is what happens when the deep link cold starts the app. // The intent is handled first, this is what happens when the deep link cold starts the app.
rootFlowNode.handleIntent(aLoginIntent()) rootFlowNode.handleIntent(aLoginIntent())
@@ -90,13 +96,18 @@ class RootFlowNodeTest : RobolectricTest() {
// Then the first nav state emission lands, it must not override the login params. // Then the first nav state emission lands, it must not override the login params.
runCurrent() runCurrent()
assertThat(rootFlowNode.backstack.activeElement).isEqualTo(RootFlowNode.NavTarget.NotLoggedInFlow(A_LOGIN_PARAMS)) assertThat(rootFlowNode.backstack.activeElement).isEqualTo(RootFlowNode.NavTarget.NotLoggedInFlow(A_LOGIN_PARAMS))
assertThat(loginEntryPoint.params).isEqualTo(A_LOGIN_ENTRY_POINT_PARAMS) assertThat(loginEntryPointParams).isEqualTo(A_LOGIN_ENTRY_POINT_PARAMS)
} }
@Test @Test
fun `given no session, when a login link is handled after the nav state is observed, then the login params are applied`() = runTest { fun `given no session, when a login link is handled after the nav state is observed, then the login params are applied`() = runTest {
val loginEntryPoint = FakeLoginEntryPoint() var loginEntryPointParams: LoginEntryPoint.Params? = null
val rootFlowNode = createRootFlowNode(loginEntryPoint = loginEntryPoint) val rootFlowNode = createRootFlowNode(
loginEntryPoint = FakeLoginEntryPoint { buildContext, params ->
loginEntryPointParams = params
node(buildContext) {}
},
)
rootFlowNode.parentNodeTestHelper() rootFlowNode.parentNodeTestHelper()
// The nav state is observed first, this is what happens when the app is already running. // The nav state is observed first, this is what happens when the app is already running.
runCurrent() runCurrent()
@@ -104,13 +115,13 @@ class RootFlowNodeTest : RobolectricTest() {
rootFlowNode.handleIntent(aLoginIntent()) rootFlowNode.handleIntent(aLoginIntent())
runCurrent() runCurrent()
assertThat(rootFlowNode.backstack.activeElement).isEqualTo(RootFlowNode.NavTarget.NotLoggedInFlow(A_LOGIN_PARAMS)) assertThat(rootFlowNode.backstack.activeElement).isEqualTo(RootFlowNode.NavTarget.NotLoggedInFlow(A_LOGIN_PARAMS))
assertThat(loginEntryPoint.params).isEqualTo(A_LOGIN_ENTRY_POINT_PARAMS) assertThat(loginEntryPointParams).isEqualTo(A_LOGIN_ENTRY_POINT_PARAMS)
} }
private fun aLoginIntent() = Intent(Intent.ACTION_VIEW, Uri.parse(A_LOGIN_LINK)) private fun aLoginIntent() = Intent(Intent.ACTION_VIEW, Uri.parse(A_LOGIN_LINK))
private fun TestScope.createRootFlowNode( private fun TestScope.createRootFlowNode(
loginEntryPoint: LoginEntryPoint = FakeLoginEntryPoint(), loginEntryPoint: LoginEntryPoint,
sessionStore: SessionStore = InMemorySessionStore(), sessionStore: SessionStore = InMemorySessionStore(),
): RootFlowNode { ): RootFlowNode {
val matrixSessionCache = MatrixSessionCache( val matrixSessionCache = MatrixSessionCache(
@@ -215,33 +226,6 @@ private class FakeAccountProviderAccessControl : AccountProviderAccessControl {
override suspend fun isAllowedToConnectToAccountProvider(accountProviderUrl: String) = true override suspend fun isAllowedToConnectToAccountProvider(accountProviderUrl: String) = true
} }
private class FakeBugReportEntryPoint : BugReportEntryPoint {
override fun createNode(parentNode: Node, buildContext: BuildContext, callback: BugReportEntryPoint.Callback): Node = node(buildContext) {}
}
private class FakeSignedOutEntryPoint : SignedOutEntryPoint {
override fun createNode(parentNode: Node, buildContext: BuildContext, params: SignedOutEntryPoint.Params): Node = node(buildContext) {}
}
private class FakeAccountSelectEntryPoint : AccountSelectEntryPoint {
override fun createNode(parentNode: Node, buildContext: BuildContext, callback: AccountSelectEntryPoint.Callback): Node = node(buildContext) {}
}
private class FakeLoginEntryPoint : LoginEntryPoint {
var params: LoginEntryPoint.Params? = null
private set
override fun createNode(
parentNode: Node,
buildContext: BuildContext,
params: LoginEntryPoint.Params,
callback: LoginEntryPoint.Callback,
): Node {
this.params = params
return node(buildContext) {}
}
}
private class FakeNodeFactoriesBindings( private class FakeNodeFactoriesBindings(
private val nodeFactories: Map<KClass<out Node>, AssistedNodeFactory<*>>, private val nodeFactories: Map<KClass<out Node>, AssistedNodeFactory<*>>,
) : NodeFactoriesBindings { ) : NodeFactoriesBindings {
+1
View File
@@ -15,6 +15,7 @@ android {
} }
dependencies { dependencies {
implementation(projects.libraries.architecture)
implementation(projects.features.login.api) implementation(projects.features.login.api)
implementation(projects.tests.testutils) implementation(projects.tests.testutils)
} }
@@ -0,0 +1,24 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.login.test
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import io.element.android.features.login.api.LoginEntryPoint
import io.element.android.tests.testutils.lambda.lambdaError
class FakeLoginEntryPoint(
private val createNodeResult: (BuildContext, LoginEntryPoint.Params) -> Node = { _, _ -> lambdaError() },
) : LoginEntryPoint {
override fun createNode(
parentNode: Node,
buildContext: BuildContext,
params: LoginEntryPoint.Params,
callback: LoginEntryPoint.Callback,
): Node = createNodeResult(buildContext, params)
}
+1
View File
@@ -14,6 +14,7 @@ android {
} }
dependencies { dependencies {
implementation(projects.libraries.architecture)
implementation(projects.features.rageshake.api) implementation(projects.features.rageshake.api)
implementation(libs.coroutines.core) implementation(libs.coroutines.core)
implementation(projects.tests.testutils) implementation(projects.tests.testutils)
@@ -0,0 +1,23 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.rageshake.test
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import io.element.android.features.rageshake.api.bugreport.BugReportEntryPoint
import io.element.android.tests.testutils.lambda.lambdaError
class FakeBugReportEntryPoint(
private val createNodeResult: (BuildContext, BugReportEntryPoint.Callback) -> Node = { _, _ -> lambdaError() },
) : BugReportEntryPoint {
override fun createNode(
parentNode: Node,
buildContext: BuildContext,
callback: BugReportEntryPoint.Callback,
): Node = createNodeResult(buildContext, callback)
}
+19
View File
@@ -0,0 +1,19 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
plugins {
id("io.element.android-library")
}
android {
namespace = "io.element.android.features.signedout.test"
}
dependencies {
implementation(projects.libraries.architecture)
implementation(projects.features.signedout.api)
implementation(projects.tests.testutils)
}
@@ -0,0 +1,23 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.signedout.test
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import io.element.android.features.signedout.api.SignedOutEntryPoint
import io.element.android.tests.testutils.lambda.lambdaError
class FakeSignedOutEntryPoint(
private val createNodeResult: (BuildContext, SignedOutEntryPoint.Params) -> Node = { _, _ -> lambdaError() },
) : SignedOutEntryPoint {
override fun createNode(
parentNode: Node,
buildContext: BuildContext,
params: SignedOutEntryPoint.Params,
): Node = createNodeResult(buildContext, params)
}
@@ -0,0 +1,19 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
plugins {
id("io.element.android-library")
}
android {
namespace = "io.element.android.libraries.accountselect.test"
}
dependencies {
implementation(projects.libraries.architecture)
implementation(projects.libraries.accountselect.api)
implementation(projects.tests.testutils)
}
@@ -0,0 +1,23 @@
/*
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.accountselect.test
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import io.element.android.libraries.accountselect.api.AccountSelectEntryPoint
import io.element.android.tests.testutils.lambda.lambdaError
class FakeAccountSelectEntryPoint(
private val createNodeResult: (BuildContext, AccountSelectEntryPoint.Callback) -> Node = { _, _ -> lambdaError() },
) : AccountSelectEntryPoint {
override fun createNode(
parentNode: Node,
buildContext: BuildContext,
callback: AccountSelectEntryPoint.Callback,
): Node = createNodeResult(buildContext, callback)
}