Merge branch 'develop' of https://github.com/vector-im/element-x-android into dla/feature/connect_sdk_to_global_notifications_ui

This commit is contained in:
David Langley 2023-09-12 16:30:36 +01:00
commit c3fbac4678
686 changed files with 7212 additions and 2257 deletions

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.preferences.impl
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.features.preferences.api.ConfigureTracingEntryPoint
import io.element.android.features.preferences.impl.developer.tracing.ConfigureTracingNode
import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.di.AppScope
import javax.inject.Inject
@ContributesBinding(AppScope::class)
class DefaultConfigureTracingEntryPoint @Inject constructor() : ConfigureTracingEntryPoint {
override fun createNode(parentNode: Node, buildContext: BuildContext): Node {
return parentNode.createNode<ConfigureTracingNode>(buildContext)
}
}

View file

@ -35,6 +35,7 @@ import io.element.android.features.preferences.impl.analytics.AnalyticsSettingsN
import io.element.android.features.preferences.impl.developer.DeveloperSettingsNode
import io.element.android.features.preferences.impl.notifications.NotificationSettingsNode
import io.element.android.features.preferences.impl.notifications.edit.EditDefaultNotificationSettingNode
import io.element.android.features.preferences.impl.developer.tracing.ConfigureTracingNode
import io.element.android.features.preferences.impl.root.PreferencesRootNode
import io.element.android.libraries.architecture.BackstackNode
import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler
@ -63,6 +64,9 @@ class PreferencesFlowNode @AssistedInject constructor(
@Parcelize
data object DeveloperSettings : NavTarget
@Parcelize
data object ConfigureTracing : NavTarget
@Parcelize
data object AnalyticsSettings : NavTarget
@ -108,7 +112,15 @@ class PreferencesFlowNode @AssistedInject constructor(
createNode<PreferencesRootNode>(buildContext, plugins = listOf(callback))
}
NavTarget.DeveloperSettings -> {
createNode<DeveloperSettingsNode>(buildContext)
val callback = object : DeveloperSettingsNode.Callback {
override fun openConfigureTracing() {
backstack.push(NavTarget.ConfigureTracing)
}
}
createNode<DeveloperSettingsNode>(buildContext, listOf(callback))
}
NavTarget.ConfigureTracing -> {
createNode<ConfigureTracingNode>(buildContext)
}
NavTarget.About -> {
createNode<AboutNode>(buildContext)

View file

@ -24,6 +24,7 @@ import com.airbnb.android.showkase.models.Showkase
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin
import com.bumble.appyx.core.plugin.plugins
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode
@ -37,6 +38,14 @@ class DeveloperSettingsNode @AssistedInject constructor(
private val presenter: DeveloperSettingsPresenter,
) : Node(buildContext, plugins = plugins) {
interface Callback : Plugin {
fun openConfigureTracing()
}
private fun onOpenConfigureTracing() {
plugins<Callback>().forEach { it.openConfigureTracing() }
}
@Composable
override fun View(modifier: Modifier) {
val activity = LocalContext.current as Activity
@ -50,6 +59,7 @@ class DeveloperSettingsNode @AssistedInject constructor(
state = state,
modifier = modifier,
onOpenShowkase = ::openShowkase,
onOpenConfigureTracing = ::onOpenConfigureTracing,
onBackPressed = ::navigateUp
)
}

View file

@ -83,7 +83,8 @@ class DeveloperSettingsPresenter @Inject constructor(
features,
enabledFeatures,
event.feature,
event.isEnabled
event.isEnabled,
triggerClearCache = { handleEvents(DeveloperSettingsEvents.ClearCache) }
)
DeveloperSettingsEvents.ClearCache -> coroutineScope.clearCache(clearCacheAction)
}
@ -122,7 +123,8 @@ class DeveloperSettingsPresenter @Inject constructor(
features: SnapshotStateMap<String, Feature>,
enabledFeatures: SnapshotStateMap<String, Boolean>,
featureUiModel: FeatureUiModel,
enabled: Boolean
enabled: Boolean,
@Suppress("UNUSED_PARAMETER") triggerClearCache: () -> Unit,
) = launch {
val feature = features[featureUiModel.key] ?: return@launch
if (featureFlagService.setFeatureEnabled(feature, enabled)) {

View file

@ -35,6 +35,7 @@ import io.element.android.libraries.ui.strings.CommonStrings
fun DeveloperSettingsView(
state: DeveloperSettingsState,
onOpenShowkase: () -> Unit,
onOpenConfigureTracing: () -> Unit,
onBackPressed: () -> Unit,
modifier: Modifier = Modifier,
) {
@ -47,6 +48,12 @@ fun DeveloperSettingsView(
PreferenceCategory(title = "Feature flags") {
FeatureListContent(state)
}
PreferenceCategory(title = "Rust SDK") {
PreferenceText(
title = "Configure tracing",
onClick = onOpenConfigureTracing,
)
}
PreferenceCategory(title = "Showkase") {
PreferenceText(
title = "Open Showkase browser",
@ -109,6 +116,7 @@ private fun ContentToPreview(state: DeveloperSettingsState) {
DeveloperSettingsView(
state = state,
onOpenShowkase = {},
onOpenConfigureTracing = {},
onBackPressed = {}
)
}

View file

@ -0,0 +1,25 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.preferences.impl.developer.tracing
import io.element.android.libraries.matrix.api.tracing.LogLevel
import io.element.android.libraries.matrix.api.tracing.Target
sealed interface ConfigureTracingEvents {
data class UpdateFilter(val target: Target, val logLevel: LogLevel) : ConfigureTracingEvents
data object ResetFilters : ConfigureTracingEvents
}

View file

@ -0,0 +1,45 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.preferences.impl.developer.tracing
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.bumble.appyx.core.modality.BuildContext
import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode
import io.element.android.libraries.di.AppScope
@ContributesNode(AppScope::class)
class ConfigureTracingNode @AssistedInject constructor(
@Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>,
private val presenter: ConfigureTracingPresenter,
) : Node(buildContext, plugins = plugins) {
@Composable
override fun View(modifier: Modifier) {
val state = presenter.present()
ConfigureTracingView(
state = state,
onBackPressed = ::navigateUp,
modifier = modifier
)
}
}

View file

@ -0,0 +1,54 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.preferences.impl.developer.tracing
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import io.element.android.libraries.architecture.Presenter
import kotlinx.collections.immutable.toImmutableMap
import javax.inject.Inject
class ConfigureTracingPresenter @Inject constructor(
private val tracingConfigurationStore: TracingConfigurationStore,
private val targetLogLevelMapBuilder: TargetLogLevelMapBuilder,
) : Presenter<ConfigureTracingState> {
@Composable
override fun present(): ConfigureTracingState {
val modifiedMap = remember { mutableStateOf(targetLogLevelMapBuilder.getCurrentMap()) }
fun handleEvents(event: ConfigureTracingEvents) {
when (event) {
is ConfigureTracingEvents.UpdateFilter -> {
modifiedMap.value = modifiedMap.value.toMutableMap()
.apply { this[event.target] = event.logLevel }
tracingConfigurationStore.storeLogLevel(event.target, event.logLevel)
}
ConfigureTracingEvents.ResetFilters -> {
modifiedMap.value = targetLogLevelMapBuilder.getDefaultMap()
tracingConfigurationStore.reset()
}
}
}
return ConfigureTracingState(
targetsToLogLevel = modifiedMap.value.toImmutableMap(),
eventSink = ::handleEvents
)
}
}

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.preferences.impl.developer.tracing
import io.element.android.libraries.matrix.api.tracing.LogLevel
import io.element.android.libraries.matrix.api.tracing.Target
import kotlinx.collections.immutable.ImmutableMap
data class ConfigureTracingState(
val targetsToLogLevel: ImmutableMap<Target, LogLevel>,
val eventSink: (ConfigureTracingEvents) -> Unit
)

View file

@ -0,0 +1,38 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.preferences.impl.developer.tracing
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.matrix.api.tracing.LogLevel
import io.element.android.libraries.matrix.api.tracing.Target
import kotlinx.collections.immutable.persistentMapOf
open class ConfigureTracingStateProvider : PreviewParameterProvider<ConfigureTracingState> {
override val values: Sequence<ConfigureTracingState>
get() = sequenceOf(
aConfigureTracingState(),
)
}
fun aConfigureTracingState() = ConfigureTracingState(
targetsToLogLevel = persistentMapOf(
Target.COMMON to LogLevel.INFO,
Target.MATRIX_SDK_FFI to LogLevel.WARN,
Target.MATRIX_SDK_BASE_SLIDING_SYNC to LogLevel.ERROR,
),
eventSink = {}
)

View file

@ -0,0 +1,250 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.preferences.impl.developer.tracing
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.material.icons.filled.ArrowDropUp
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.components.list.ListItemContent
import io.element.android.libraries.designsystem.preview.DayNightPreviews
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.theme.aliasScreenTitle
import io.element.android.libraries.designsystem.theme.components.DropdownMenu
import io.element.android.libraries.designsystem.theme.components.DropdownMenuItem
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.IconButton
import io.element.android.libraries.designsystem.theme.components.ListItem
import io.element.android.libraries.designsystem.theme.components.Scaffold
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.matrix.api.tracing.LogLevel
import io.element.android.libraries.matrix.api.tracing.Target
import io.element.android.libraries.theme.ElementTheme
import kotlinx.collections.immutable.ImmutableMap
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ConfigureTracingView(
state: ConfigureTracingState,
onBackPressed: () -> Unit,
modifier: Modifier = Modifier,
) {
var showMenu by remember { mutableStateOf(false) }
Scaffold(
modifier = modifier
.fillMaxSize()
.systemBarsPadding()
.imePadding(),
contentWindowInsets = WindowInsets.statusBars,
topBar = {
TopAppBar(
navigationIcon = {
BackButton(onClick = onBackPressed)
},
title = {
Text(
text = "Configure tracing",
style = ElementTheme.typography.aliasScreenTitle,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
},
actions = {
IconButton(
onClick = { showMenu = !showMenu }
) {
Icon(
imageVector = Icons.Default.MoreVert,
tint = ElementTheme.materialColors.secondary,
contentDescription = null,
)
}
DropdownMenu(
expanded = showMenu,
onDismissRequest = { showMenu = false }
) {
DropdownMenuItem(
onClick = {
showMenu = false
state.eventSink.invoke(ConfigureTracingEvents.ResetFilters)
},
text = { Text("Reset to default") },
leadingIcon = {
Icon(
Icons.Outlined.Delete,
tint = ElementTheme.materialColors.secondary,
contentDescription = null,
)
}
)
}
}
)
},
content = {
Column(
modifier = Modifier
.padding(it)
.consumeWindowInsets(it)
.verticalScroll(state = rememberScrollState())
) {
CrateListContent(state)
ListItem(
headlineContent = {
Text(
text = "Kill and restart the app for the change to take effect.",
style = ElementTheme.typography.fontHeadingSmMedium,
)
},
)
}
}
)
}
@Composable
fun CrateListContent(
state: ConfigureTracingState,
modifier: Modifier = Modifier
) {
fun onLogLevelChange(target: Target, logLevel: LogLevel) {
state.eventSink(ConfigureTracingEvents.UpdateFilter(target, logLevel))
}
TargetAndLogLevelListView(
modifier = modifier,
data = state.targetsToLogLevel,
onLogLevelChange = ::onLogLevelChange,
)
}
@Composable
private fun TargetAndLogLevelListView(
data: ImmutableMap<Target, LogLevel>,
onLogLevelChange: (Target, LogLevel) -> Unit,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier,
) {
data.forEach { item ->
fun onLogLevelChange(logLevel: LogLevel) {
onLogLevelChange(item.key, logLevel)
}
TargetAndLogLevelView(
target = item.key,
logLevel = item.value,
onLogLevelChange = ::onLogLevelChange
)
}
}
}
@Composable
fun TargetAndLogLevelView(
target: Target,
logLevel: LogLevel,
onLogLevelChange: (LogLevel) -> Unit,
modifier: Modifier = Modifier
) {
ListItem(
modifier = modifier,
headlineContent = { Text(text = target.filter.takeIf { it.isNotEmpty() } ?: "(common)") },
trailingContent = ListItemContent.Custom {
LogLevelDropdownMenu(
logLevel = logLevel,
onLogLevelChange = onLogLevelChange,
)
},
)
}
@Composable
fun LogLevelDropdownMenu(
logLevel: LogLevel,
onLogLevelChange: (LogLevel) -> Unit,
modifier: Modifier = Modifier,
) {
var expanded by remember { mutableStateOf(false) }
Box(modifier = modifier) {
DropdownMenuItem(
modifier = Modifier.widthIn(max = 120.dp),
text = { Text(text = logLevel.filter) },
onClick = { expanded = !expanded },
trailingIcon = {
if (expanded) {
Icon(Icons.Default.ArrowDropUp, contentDescription = null)
} else {
Icon(Icons.Default.ArrowDropDown, contentDescription = null)
}
},
)
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
) {
LogLevel.values().forEach { logLevel ->
DropdownMenuItem(
text = {
Text(text = logLevel.filter)
},
onClick = {
expanded = false
onLogLevelChange(logLevel)
}
)
}
}
}
}
@DayNightPreviews
@Composable
internal fun ConfigureTracingViewPreview(
@PreviewParameter(ConfigureTracingStateProvider::class) state: ConfigureTracingState
) = ElementPreview {
ConfigureTracingView(
state = state,
onBackPressed = {},
)
}

View file

@ -0,0 +1,41 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.preferences.impl.developer.tracing
import io.element.android.libraries.matrix.api.tracing.LogLevel
import io.element.android.libraries.matrix.api.tracing.Target
import io.element.android.libraries.matrix.api.tracing.TracingFilterConfigurations
import javax.inject.Inject
class TargetLogLevelMapBuilder @Inject constructor(
private val tracingConfigurationStore: TracingConfigurationStore,
) {
private val defaultConfig = TracingFilterConfigurations.debug
fun getDefaultMap(): Map<Target, LogLevel> {
return Target.entries.associateWith { target ->
defaultConfig.getLogLevel(target)
}
}
fun getCurrentMap(): Map<Target, LogLevel> {
return Target.entries.associateWith { target ->
tracingConfigurationStore.getLogLevel(target)
?: defaultConfig.getLogLevel(target)
}
}
}

View file

@ -0,0 +1,60 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.preferences.impl.developer.tracing
import android.content.SharedPreferences
import androidx.core.content.edit
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.DefaultPreferences
import io.element.android.libraries.matrix.api.tracing.LogLevel
import io.element.android.libraries.matrix.api.tracing.Target
import javax.inject.Inject
interface TracingConfigurationStore {
fun getLogLevel(target: Target): LogLevel?
fun storeLogLevel(target: Target, logLevel: LogLevel)
fun reset()
}
@ContributesBinding(AppScope::class)
class SharedPrefTracingConfigurationStore @Inject constructor(
@DefaultPreferences private val sharedPreferences: SharedPreferences
) : TracingConfigurationStore {
override fun getLogLevel(target: Target): LogLevel? {
return sharedPreferences.getString("$KEY_PREFIX${target.name}", null)
?.let { LogLevel.valueOf(it) }
}
override fun storeLogLevel(target: Target, logLevel: LogLevel) {
sharedPreferences.edit {
putString("$KEY_PREFIX${target.name}", logLevel.name)
}
}
override fun reset() {
sharedPreferences.edit {
sharedPreferences.all.keys.filter { it.startsWith(KEY_PREFIX) }.forEach {
remove(it)
}
}
}
companion object {
private const val KEY_PREFIX = "tracing_log_level_"
}
}

View file

@ -20,10 +20,17 @@ import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.tests.testutils.WarmUpRule
import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
class AboutPresenterTest {
@Rule
@JvmField
val warmUpRule = WarmUpRule()
@Test
fun `present - initial state`() = runTest {
val presenter = AboutPresenter()

View file

@ -23,10 +23,17 @@ import com.google.common.truth.Truth.assertThat
import io.element.android.features.analytics.impl.preferences.DefaultAnalyticsPreferencesPresenter
import io.element.android.libraries.matrix.test.core.aBuildMeta
import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.tests.testutils.WarmUpRule
import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
class AnalyticsSettingsPresenterTest {
@Rule
@JvmField
val warmUpRule = WarmUpRule()
@Test
fun `present - initial state`() = runTest {
val analyticsPresenter = DefaultAnalyticsPreferencesPresenter(FakeAnalyticsService(), aBuildMeta())

View file

@ -28,10 +28,17 @@ import io.element.android.features.rageshake.test.rageshake.FakeRageshakeDataSto
import io.element.android.libraries.architecture.Async
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
import io.element.android.tests.testutils.WarmUpRule
import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
class DeveloperSettingsPresenterTest {
@Rule
@JvmField
val warmUpRule = WarmUpRule()
@Test
fun `present - ensures initial state is correct`() = runTest {
val rageshakePresenter = DefaultRageshakePreferencesPresenter(FakeRageShake(), FakeRageshakeDataStore())

View file

@ -0,0 +1,110 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.preferences.impl.developer.tracing
import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.api.tracing.LogLevel
import io.element.android.libraries.matrix.api.tracing.Target
import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.waitForPredicate
import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
class ConfigureTracingPresenterTest {
@Rule
@JvmField
val warmUpRule = WarmUpRule()
@Test
fun `present - initial state`() = runTest {
val store = InMemoryTracingConfigurationStore()
val presenter = ConfigureTracingPresenter(
store,
TargetLogLevelMapBuilder(store),
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
assertThat(initialState.targetsToLogLevel).isNotEmpty()
assertThat(initialState.targetsToLogLevel[Target.MATRIX_SDK_CRYPTO]).isEqualTo(LogLevel.DEBUG)
}
}
@Test
fun `present - store is taken into account`() = runTest {
val store = InMemoryTracingConfigurationStore()
store.givenLogLevel(LogLevel.ERROR)
val presenter = ConfigureTracingPresenter(
store,
TargetLogLevelMapBuilder(store),
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
assertThat(initialState.targetsToLogLevel).isNotEmpty()
assertThat(initialState.targetsToLogLevel[Target.MATRIX_SDK_CRYPTO]).isEqualTo(LogLevel.ERROR)
}
}
@Test
fun `present - change a value`() = runTest {
val store = InMemoryTracingConfigurationStore()
val presenter = ConfigureTracingPresenter(
store,
TargetLogLevelMapBuilder(store),
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
assertThat(initialState.targetsToLogLevel[Target.MATRIX_SDK_CRYPTO]).isEqualTo(LogLevel.DEBUG)
initialState.eventSink.invoke(ConfigureTracingEvents.UpdateFilter(Target.MATRIX_SDK_CRYPTO, LogLevel.WARN))
val finalState = awaitItem()
assertThat(finalState.targetsToLogLevel[Target.MATRIX_SDK_CRYPTO]).isEqualTo(LogLevel.WARN)
waitForPredicate { store.hasStoreLogLevelBeenCalled }
}
}
@Test
fun `present - reset`() = runTest {
val store = InMemoryTracingConfigurationStore()
val presenter = ConfigureTracingPresenter(
store,
TargetLogLevelMapBuilder(store),
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
assertThat(initialState.targetsToLogLevel[Target.MATRIX_SDK_CRYPTO]).isEqualTo(LogLevel.DEBUG)
initialState.eventSink.invoke(ConfigureTracingEvents.UpdateFilter(Target.MATRIX_SDK_CRYPTO, LogLevel.WARN))
val finalState = awaitItem()
assertThat(finalState.targetsToLogLevel[Target.MATRIX_SDK_CRYPTO]).isEqualTo(LogLevel.WARN)
waitForPredicate { store.hasStoreLogLevelBeenCalled }
finalState.eventSink.invoke(ConfigureTracingEvents.ResetFilters)
val resetState = awaitItem()
assertThat(resetState.targetsToLogLevel[Target.MATRIX_SDK_CRYPTO]).isEqualTo(LogLevel.DEBUG)
waitForPredicate { store.hasResetBeenCalled }
}
}
}

View file

@ -0,0 +1,44 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.preferences.impl.developer.tracing
import io.element.android.libraries.matrix.api.tracing.LogLevel
import io.element.android.libraries.matrix.api.tracing.Target
class InMemoryTracingConfigurationStore : TracingConfigurationStore {
var hasResetBeenCalled = false
private set
var hasStoreLogLevelBeenCalled = false
private set
private var logLevel: LogLevel? = null
fun givenLogLevel(logLevel: LogLevel?) {
this.logLevel = logLevel
}
override fun getLogLevel(target: Target): LogLevel? {
return logLevel
}
override fun storeLogLevel(target: Target, logLevel: LogLevel) {
hasStoreLogLevelBeenCalled = true
}
override fun reset() {
hasResetBeenCalled = true
}
}

View file

@ -30,10 +30,17 @@ import io.element.android.libraries.matrix.test.A_USER_NAME
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService
import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.tests.testutils.WarmUpRule
import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
class PreferencesRootPresenterTest {
@Rule
@JvmField
val warmUpRule = WarmUpRule()
@Test
fun `present - initial state`() = runTest {
val matrixClient = FakeMatrixClient()