mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-02 12:07:28 +00:00
Merge pull request #7488 from element-hq/feature/bma/fixColdStartDeepLink
Fix cold start with a mobile deep link
This commit is contained in:
@@ -61,17 +61,23 @@ dependencies {
|
||||
testCommonDependencies(libs)
|
||||
testImplementation(projects.features.login.test)
|
||||
testImplementation(projects.features.share.test)
|
||||
testImplementation(projects.libraries.accountselect.test)
|
||||
testImplementation(projects.libraries.featureflag.test)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
testImplementation(projects.libraries.matrixmedia.test)
|
||||
testImplementation(projects.libraries.oauth.test)
|
||||
testImplementation(projects.libraries.permissions.test)
|
||||
testImplementation(projects.libraries.preferences.test)
|
||||
testImplementation(projects.libraries.push.test)
|
||||
testImplementation(projects.libraries.pushproviders.test)
|
||||
testImplementation(projects.libraries.sessionStorage.test)
|
||||
testImplementation(projects.features.announcement.test)
|
||||
testImplementation(projects.features.forward.test)
|
||||
testImplementation(projects.features.messages.test)
|
||||
testImplementation(projects.features.networkmonitor.test)
|
||||
testImplementation(projects.features.preferences.test)
|
||||
testImplementation(projects.features.rageshake.test)
|
||||
testImplementation(projects.features.signedout.test)
|
||||
testImplementation(projects.services.apperror.test)
|
||||
testImplementation(projects.services.appnavstate.test)
|
||||
testImplementation(projects.services.analytics.test)
|
||||
|
||||
@@ -32,7 +32,6 @@ import dev.zacsweers.metro.Assisted
|
||||
import dev.zacsweers.metro.AssistedInject
|
||||
import im.vector.app.features.analytics.plan.JoinedRoom
|
||||
import io.element.android.annotations.ContributesNode
|
||||
import io.element.android.appnav.di.MatrixSessionCache
|
||||
import io.element.android.appnav.intent.IntentResolver
|
||||
import io.element.android.appnav.intent.ResolvedIntent
|
||||
import io.element.android.appnav.room.RoomFlowNode
|
||||
@@ -40,6 +39,7 @@ import io.element.android.appnav.room.RoomNavigationTarget
|
||||
import io.element.android.appnav.root.RootNavStateFlowFactory
|
||||
import io.element.android.appnav.root.RootPresenter
|
||||
import io.element.android.appnav.root.RootView
|
||||
import io.element.android.appnav.session.MatrixSessionCache
|
||||
import io.element.android.features.announcement.api.AnnouncementService
|
||||
import io.element.android.features.login.api.LoginParams
|
||||
import io.element.android.features.login.api.accesscontrol.AccountProviderAccessControl
|
||||
@@ -109,6 +109,13 @@ class RootFlowNode(
|
||||
buildContext = buildContext,
|
||||
plugins = plugins
|
||||
) {
|
||||
/**
|
||||
* Login params coming from a launch or new [Intent], waiting to be consumed by the not logged in flow.
|
||||
* Kept here so that the root nav target can be computed from both the logged in state and the pending
|
||||
* login params, whatever the order in which the intent and the first nav state emission are processed.
|
||||
*/
|
||||
private var pendingLoginParams: LoginParams? = null
|
||||
|
||||
override fun onBuilt() {
|
||||
analyticsColdStartWatcher.start()
|
||||
appCoroutineScope.launch {
|
||||
@@ -152,7 +159,7 @@ class RootFlowNode(
|
||||
}
|
||||
}
|
||||
LoggedInState.NotLoggedIn -> {
|
||||
switchToNotLoggedInFlow(null)
|
||||
switchToNotLoggedInFlow(pendingLoginParams)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -194,15 +201,18 @@ class RootFlowNode(
|
||||
}
|
||||
|
||||
private fun switchToLoggedInFlow(sessionId: SessionId, navId: Int) {
|
||||
pendingLoginParams = null
|
||||
backstack.safeRoot(NavTarget.LoggedInFlow(sessionId, navId))
|
||||
}
|
||||
|
||||
private fun switchToNotLoggedInFlow(params: LoginParams?) {
|
||||
Timber.d("switchToNotLoggedInFlow, hasLoginParams=${params != null}")
|
||||
matrixSessionCache.removeAll()
|
||||
backstack.safeRoot(NavTarget.NotLoggedInFlow(params))
|
||||
}
|
||||
|
||||
private fun switchToSignedOutFlow(sessionId: SessionId) {
|
||||
pendingLoginParams = null
|
||||
backstack.safeRoot(NavTarget.SignedOutFlow(sessionId))
|
||||
}
|
||||
|
||||
@@ -319,6 +329,7 @@ class RootFlowNode(
|
||||
}
|
||||
|
||||
override fun onDone() {
|
||||
pendingLoginParams = null
|
||||
backstack.pop()
|
||||
}
|
||||
}
|
||||
@@ -418,6 +429,7 @@ class RootFlowNode(
|
||||
Timber.w("Login link ignored, multi account is disabled")
|
||||
}
|
||||
} else {
|
||||
pendingLoginParams = params
|
||||
switchToNotLoggedInFlow(params)
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -11,7 +11,7 @@ package io.element.android.appnav.root
|
||||
import com.bumble.appyx.core.state.MutableSavedStateMap
|
||||
import com.bumble.appyx.core.state.SavedStateMap
|
||||
import dev.zacsweers.metro.Inject
|
||||
import io.element.android.appnav.di.MatrixSessionCache
|
||||
import io.element.android.appnav.session.MatrixSessionCache
|
||||
import io.element.android.features.preferences.api.CacheService
|
||||
import io.element.android.libraries.matrix.ui.media.ImageLoaderHolder
|
||||
import io.element.android.libraries.preferences.api.store.SessionPreferencesStoreFactory
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.appnav.di
|
||||
package io.element.android.appnav.session
|
||||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.bumble.appyx.core.state.MutableSavedStateMap
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.appnav.di
|
||||
package io.element.android.appnav.session
|
||||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import dev.zacsweers.metro.Assisted
|
||||
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalCoroutinesApi::class)
|
||||
|
||||
package io.element.android.appnav
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||
import com.bumble.appyx.core.modality.AncestryInfo
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.node
|
||||
import com.bumble.appyx.navmodel.backstack.activeElement
|
||||
import com.bumble.appyx.testing.junit4.util.MainDispatcherRule
|
||||
import com.bumble.appyx.testing.unit.common.helper.parentNodeTestHelper
|
||||
import com.bumble.appyx.utils.customisations.NodeCustomisationDirectoryImpl
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.appnav.intent.IntentResolver
|
||||
import io.element.android.appnav.root.RootNavStateFlowFactory
|
||||
import io.element.android.appnav.root.RootPresenter
|
||||
import io.element.android.appnav.session.FakeSyncOrchestratorFactory
|
||||
import io.element.android.appnav.session.MatrixSessionCache
|
||||
import io.element.android.features.login.api.LoginEntryPoint
|
||||
import io.element.android.features.login.api.LoginParams
|
||||
import io.element.android.features.login.test.FakeLoginEntryPoint
|
||||
import io.element.android.features.login.test.FakeLoginIntentResolver
|
||||
import io.element.android.features.login.test.accesscontrol.FakeAccountProviderAccessControl
|
||||
import io.element.android.features.preferences.test.FakeCacheService
|
||||
import io.element.android.features.rageshake.test.FakeBugReportEntryPoint
|
||||
import io.element.android.features.rageshake.test.logs.FakeAnnouncementService
|
||||
import io.element.android.features.share.test.FakeShareIntentHandler
|
||||
import io.element.android.features.signedout.test.FakeSignedOutEntryPoint
|
||||
import io.element.android.libraries.accountselect.test.FakeAccountSelectEntryPoint
|
||||
import io.element.android.libraries.architecture.AssistedNodeFactory
|
||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||
import io.element.android.libraries.matrix.test.FakeSdkMetadata
|
||||
import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
|
||||
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
|
||||
import io.element.android.libraries.matrix.ui.media.test.FakeImageLoaderHolder
|
||||
import io.element.android.libraries.oauth.test.FakeOAuthActionFlow
|
||||
import io.element.android.libraries.oauth.test.FakeOAuthIntentResolver
|
||||
import io.element.android.libraries.preferences.test.FakeSessionPreferencesStoreFactory
|
||||
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||
import io.element.android.libraries.sessionstorage.test.InMemorySessionStore
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
import io.element.android.services.analytics.test.watchers.FakeAnalyticsColdStartWatcher
|
||||
import io.element.android.services.apperror.test.FakeAppErrorStateService
|
||||
import io.element.android.tests.testutils.node.FakeNodeFactoriesBindings
|
||||
import io.element.android.tests.testutils.node.FakeParentNode
|
||||
import io.element.android.tests.testutils.presenter.NotUsedPresenter
|
||||
import io.element.android.tests.testutils.robolectric.RobolectricTest
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runCurrent
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
private const val A_LOGIN_LINK = "https://mobile.element.io/element/?account_provider=example.com&login_hint=mxid:@alice:example.com"
|
||||
|
||||
private val A_LOGIN_PARAMS = LoginParams(
|
||||
accountProvider = "example.com",
|
||||
loginHint = "mxid:@alice:example.com",
|
||||
)
|
||||
|
||||
private val A_LOGIN_ENTRY_POINT_PARAMS = LoginEntryPoint.Params(
|
||||
accountProvider = A_LOGIN_PARAMS.accountProvider,
|
||||
loginHint = A_LOGIN_PARAMS.loginHint,
|
||||
)
|
||||
|
||||
class RootFlowNodeTest : RobolectricTest() {
|
||||
@get:Rule
|
||||
val instantTaskExecutorRule = InstantTaskExecutorRule()
|
||||
|
||||
@get:Rule
|
||||
val mainDispatcherRule = MainDispatcherRule()
|
||||
|
||||
@Test
|
||||
fun `given no session, when a login link is handled before the nav state is observed, then the login params are kept`() = runTest {
|
||||
var loginEntryPointParams: LoginEntryPoint.Params? = null
|
||||
val rootFlowNode = createRootFlowNode(
|
||||
loginEntryPoint = FakeLoginEntryPoint { buildContext, params ->
|
||||
loginEntryPointParams = params
|
||||
node(buildContext) {}
|
||||
},
|
||||
)
|
||||
rootFlowNode.parentNodeTestHelper()
|
||||
// The intent is handled first, this is what happens when the deep link cold starts the app.
|
||||
rootFlowNode.handleIntent(aLoginIntent())
|
||||
assertThat(rootFlowNode.backstack.activeElement).isEqualTo(RootFlowNode.NavTarget.NotLoggedInFlow(A_LOGIN_PARAMS))
|
||||
// Then the first nav state emission lands, it must not override the login params.
|
||||
runCurrent()
|
||||
assertThat(rootFlowNode.backstack.activeElement).isEqualTo(RootFlowNode.NavTarget.NotLoggedInFlow(A_LOGIN_PARAMS))
|
||||
assertThat(loginEntryPointParams).isEqualTo(A_LOGIN_ENTRY_POINT_PARAMS)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given no session, when a login link is handled after the nav state is observed, then the login params are applied`() = runTest {
|
||||
var loginEntryPointParams: LoginEntryPoint.Params? = null
|
||||
val rootFlowNode = createRootFlowNode(
|
||||
loginEntryPoint = FakeLoginEntryPoint { buildContext, params ->
|
||||
loginEntryPointParams = params
|
||||
node(buildContext) {}
|
||||
},
|
||||
)
|
||||
rootFlowNode.parentNodeTestHelper()
|
||||
// The nav state is observed first, this is what happens when the app is already running.
|
||||
runCurrent()
|
||||
assertThat(rootFlowNode.backstack.activeElement).isEqualTo(RootFlowNode.NavTarget.NotLoggedInFlow(null))
|
||||
rootFlowNode.handleIntent(aLoginIntent())
|
||||
runCurrent()
|
||||
assertThat(rootFlowNode.backstack.activeElement).isEqualTo(RootFlowNode.NavTarget.NotLoggedInFlow(A_LOGIN_PARAMS))
|
||||
assertThat(loginEntryPointParams).isEqualTo(A_LOGIN_ENTRY_POINT_PARAMS)
|
||||
}
|
||||
|
||||
private fun aLoginIntent() = Intent(Intent.ACTION_VIEW, Uri.parse(A_LOGIN_LINK))
|
||||
|
||||
private fun TestScope.createRootFlowNode(
|
||||
loginEntryPoint: LoginEntryPoint,
|
||||
sessionStore: SessionStore = InMemorySessionStore(),
|
||||
): RootFlowNode {
|
||||
val matrixSessionCache = MatrixSessionCache(
|
||||
authenticationService = FakeMatrixAuthenticationService(),
|
||||
syncOrchestratorFactory = FakeSyncOrchestratorFactory(),
|
||||
analyticsService = FakeAnalyticsService(),
|
||||
)
|
||||
val parentNode = FakeParentNode(
|
||||
graph = FakeNodeFactoriesBindings(
|
||||
mapOf(
|
||||
NotLoggedInFlowNode::class to AssistedNodeFactory { buildContext, plugins ->
|
||||
NotLoggedInFlowNode(
|
||||
buildContext = buildContext,
|
||||
plugins = plugins,
|
||||
loginEntryPoint = loginEntryPoint,
|
||||
imageLoaderHolder = FakeImageLoaderHolder(),
|
||||
analyticsColdStartWatcher = FakeAnalyticsColdStartWatcher(),
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
return RootFlowNode(
|
||||
buildContext = BuildContext(
|
||||
ancestryInfo = AncestryInfo.Child(anchor = parentNode),
|
||||
savedStateMap = null,
|
||||
customisations = NodeCustomisationDirectoryImpl(),
|
||||
),
|
||||
plugins = emptyList(),
|
||||
sessionStore = sessionStore,
|
||||
accountProviderAccessControl = FakeAccountProviderAccessControl { true },
|
||||
navStateFlowFactory = RootNavStateFlowFactory(
|
||||
sessionStore = sessionStore,
|
||||
cacheService = FakeCacheService(),
|
||||
matrixSessionCache = matrixSessionCache,
|
||||
imageLoaderHolder = FakeImageLoaderHolder(),
|
||||
sessionPreferencesStoreFactory = FakeSessionPreferencesStoreFactory(),
|
||||
),
|
||||
matrixSessionCache = matrixSessionCache,
|
||||
presenter = RootPresenter(
|
||||
crashDetectionPresenter = NotUsedPresenter(),
|
||||
rageshakeDetectionPresenter = NotUsedPresenter(),
|
||||
appErrorStateService = FakeAppErrorStateService(),
|
||||
analyticsService = FakeAnalyticsService(),
|
||||
sdkMetadata = FakeSdkMetadata("sha"),
|
||||
),
|
||||
bugReportEntryPoint = FakeBugReportEntryPoint(),
|
||||
signedOutEntryPoint = FakeSignedOutEntryPoint(),
|
||||
accountSelectEntryPoint = FakeAccountSelectEntryPoint(),
|
||||
intentResolver = IntentResolver(
|
||||
deeplinkParser = { null },
|
||||
loginIntentResolver = FakeLoginIntentResolver { A_LOGIN_PARAMS },
|
||||
oAuthIntentResolver = FakeOAuthIntentResolver { null },
|
||||
permalinkParser = FakePermalinkParser(),
|
||||
shareIntentHandler = FakeShareIntentHandler(),
|
||||
),
|
||||
oAuthActionFlow = FakeOAuthActionFlow(),
|
||||
featureFlagService = FakeFeatureFlagService(),
|
||||
announcementService = FakeAnnouncementService(),
|
||||
analyticsService = FakeAnalyticsService(),
|
||||
analyticsColdStartWatcher = FakeAnalyticsColdStartWatcher(),
|
||||
appCoroutineScope = backgroundScope,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
package io.element.android.appnav
|
||||
|
||||
import io.element.android.appnav.di.SyncOrchestrator
|
||||
import io.element.android.appnav.session.SyncOrchestrator
|
||||
import io.element.android.features.networkmonitor.api.NetworkStatus
|
||||
import io.element.android.features.networkmonitor.test.FakeNetworkMonitor
|
||||
import io.element.android.libraries.matrix.api.sync.SyncState
|
||||
|
||||
+19
@@ -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.
|
||||
*/
|
||||
|
||||
package io.element.android.appnav.session
|
||||
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
||||
class FakeSyncOrchestratorFactory : SyncOrchestrator.Factory {
|
||||
override fun create(
|
||||
matrixClient: MatrixClient,
|
||||
sessionCoroutineScope: CoroutineScope,
|
||||
): SyncOrchestrator = lambdaError()
|
||||
}
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.appnav.di
|
||||
package io.element.android.appnav.session
|
||||
|
||||
import com.bumble.appyx.core.state.MutableSavedStateMapImpl
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
+1
-1
@@ -16,7 +16,7 @@ import io.element.android.features.login.api.LoginEntryPoint
|
||||
import io.element.android.features.login.impl.accountprovider.anAccountProviderDataSource
|
||||
import io.element.android.features.login.impl.classic.FakeElementClassicConnection
|
||||
import io.element.android.features.preferences.test.FakePreferencesEntryPoint
|
||||
import io.element.android.libraries.oauth.test.customtab.FakeOAuthActionFlow
|
||||
import io.element.android.libraries.oauth.test.FakeOAuthActionFlow
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
import io.element.android.tests.testutils.node.TestParentNode
|
||||
import kotlinx.coroutines.test.runTest
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationSer
|
||||
import io.element.android.libraries.matrix.test.auth.aMatrixHomeServerDetails
|
||||
import io.element.android.libraries.oauth.api.OAuthAction
|
||||
import io.element.android.libraries.oauth.api.OAuthActionFlow
|
||||
import io.element.android.libraries.oauth.test.customtab.FakeOAuthActionFlow
|
||||
import io.element.android.libraries.oauth.test.FakeOAuthActionFlow
|
||||
import io.element.android.libraries.permissions.test.FakeLocalNetworkPermissionAdvisor
|
||||
import io.element.android.libraries.permissions.test.FakePermissionsPresenter
|
||||
import io.element.android.libraries.permissions.test.FakePermissionsPresenterFactory
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ import io.element.android.libraries.matrix.test.A_LOGIN_HINT
|
||||
import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
|
||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||
import io.element.android.libraries.oauth.api.OAuthActionFlow
|
||||
import io.element.android.libraries.oauth.test.customtab.FakeOAuthActionFlow
|
||||
import io.element.android.libraries.oauth.test.FakeOAuthActionFlow
|
||||
import io.element.android.libraries.permissions.api.PermissionsPresenter
|
||||
import io.element.android.libraries.permissions.api.localnetwork.LocalNetworkPermissionAdvisor
|
||||
import io.element.android.libraries.permissions.test.FakeLocalNetworkPermissionAdvisor
|
||||
|
||||
@@ -15,6 +15,7 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.features.login.api)
|
||||
implementation(projects.tests.testutils)
|
||||
}
|
||||
|
||||
+24
@@ -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)
|
||||
}
|
||||
+19
@@ -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.
|
||||
*/
|
||||
|
||||
package io.element.android.features.login.test.accesscontrol
|
||||
|
||||
import io.element.android.features.login.api.accesscontrol.AccountProviderAccessControl
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
|
||||
class FakeAccountProviderAccessControl(
|
||||
private val isAllowedToConnectToAccountProviderResult: (String) -> Boolean = { lambdaError() },
|
||||
) : AccountProviderAccessControl {
|
||||
override suspend fun isAllowedToConnectToAccountProvider(accountProviderUrl: String): Boolean {
|
||||
return isAllowedToConnectToAccountProviderResult(accountProviderUrl)
|
||||
}
|
||||
}
|
||||
@@ -16,5 +16,7 @@ android {
|
||||
dependencies {
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.features.preferences.api)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(libs.coroutines.core)
|
||||
implementation(projects.tests.testutils)
|
||||
}
|
||||
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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.preferences.test
|
||||
|
||||
import io.element.android.features.preferences.api.CacheService
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
|
||||
class FakeCacheService(
|
||||
override val clearedCacheEventFlow: Flow<SessionId> = emptyFlow(),
|
||||
) : CacheService
|
||||
@@ -14,6 +14,7 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.features.rageshake.api)
|
||||
implementation(libs.coroutines.core)
|
||||
implementation(projects.tests.testutils)
|
||||
|
||||
+23
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
+23
@@ -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)
|
||||
}
|
||||
+23
@@ -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)
|
||||
}
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.oauth.test.customtab
|
||||
package io.element.android.libraries.oauth.test
|
||||
|
||||
import io.element.android.libraries.oauth.api.OAuthAction
|
||||
import io.element.android.libraries.oauth.api.OAuthActionFlow
|
||||
@@ -104,6 +104,7 @@ class KonsistClassNameTest {
|
||||
"FakeImageLoader",
|
||||
"FakeKeyStore",
|
||||
"FakeListenableFuture",
|
||||
"FakeParentNode",
|
||||
)
|
||||
.assertTrue {
|
||||
val interfaceName = it.name
|
||||
|
||||
+19
@@ -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.
|
||||
*/
|
||||
|
||||
package io.element.android.tests.testutils.node
|
||||
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import io.element.android.libraries.architecture.AssistedNodeFactory
|
||||
import io.element.android.libraries.architecture.NodeFactoriesBindings
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
class FakeNodeFactoriesBindings(
|
||||
private val nodeFactories: Map<KClass<out Node>, AssistedNodeFactory<*>>,
|
||||
) : NodeFactoriesBindings {
|
||||
override fun nodeFactories() = nodeFactories
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.tests.testutils.node
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.navigation.model.permanent.PermanentNavModel
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.node.ParentNode
|
||||
import com.bumble.appyx.core.node.node
|
||||
import io.element.android.libraries.di.DependencyInjectionGraphOwner
|
||||
|
||||
/**
|
||||
* A Node creates its children using the dependency injection graph of its ancestors, so a Node under test
|
||||
* needs such a parent to be able to create its children.
|
||||
*/
|
||||
class FakeParentNode(
|
||||
override val graph: Any,
|
||||
) : ParentNode<Unit>(
|
||||
navModel = PermanentNavModel(navTargets = setOf(Unit), savedStateMap = null),
|
||||
buildContext = BuildContext.root(savedStateMap = null),
|
||||
),
|
||||
DependencyInjectionGraphOwner {
|
||||
override fun resolve(navTarget: Unit, buildContext: BuildContext): Node = node(buildContext) {}
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) = Unit
|
||||
}
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.tests.testutils
|
||||
package io.element.android.tests.testutils.presenter
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
+19
@@ -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.
|
||||
*/
|
||||
|
||||
package io.element.android.tests.testutils.presenter
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
|
||||
/**
|
||||
* A presenter that cannot be used, util for testing Nodes.
|
||||
*/
|
||||
class NotUsedPresenter<T> : Presenter<T> {
|
||||
@Composable
|
||||
override fun present(): T = error("Not used")
|
||||
}
|
||||
Executable
+12
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# 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_name="io.element.android.x.debug"
|
||||
|
||||
echo " => Force stop ${package_name}"
|
||||
|
||||
adb shell am force-stop "${package_name}"
|
||||
Reference in New Issue
Block a user