From d06ec9099b4bef51b6911852826c0a912b26427c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 23 Apr 2024 13:28:37 +0200 Subject: [PATCH] Add Ui test on DeveloperSettingsView --- .../DeveloperSettingsStateProvider.kt | 32 +++-- .../developer/DeveloperSettingsViewTest.kt | 127 ++++++++++++++++++ .../preferences/PreferenceTextField.kt | 5 +- 3 files changed, 153 insertions(+), 11 deletions(-) create mode 100644 features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsViewTest.kt diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsStateProvider.kt index e4643182f4..fb93a63ffc 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsStateProvider.kt @@ -25,22 +25,36 @@ open class DeveloperSettingsStateProvider : PreviewParameterProvider get() = sequenceOf( aDeveloperSettingsState(), - aDeveloperSettingsState().copy(clearCacheAction = AsyncData.Loading()), - aDeveloperSettingsState().copy( - customElementCallBaseUrlState = CustomElementCallBaseUrlState( + aDeveloperSettingsState( + clearCacheAction = AsyncData.Loading() + ), + aDeveloperSettingsState( + customElementCallBaseUrlState = aCustomElementCallBaseUrlState( baseUrl = "https://call.element.ahoy", - defaultUrl = "https://call.element.io", - validator = { true } ) ), ) } -fun aDeveloperSettingsState() = DeveloperSettingsState( +fun aDeveloperSettingsState( + clearCacheAction: AsyncData = AsyncData.Uninitialized, + customElementCallBaseUrlState: CustomElementCallBaseUrlState = aCustomElementCallBaseUrlState(), + eventSink: (DeveloperSettingsEvents) -> Unit = {}, +) = DeveloperSettingsState( features = aFeatureUiModelList(), rageshakeState = aRageshakePreferencesState(), cacheSize = AsyncData.Success("1.2 MB"), - clearCacheAction = AsyncData.Uninitialized, - customElementCallBaseUrlState = CustomElementCallBaseUrlState(baseUrl = null, defaultUrl = "https://call.element.io", validator = { true }), - eventSink = {} + clearCacheAction = clearCacheAction, + customElementCallBaseUrlState = customElementCallBaseUrlState, + eventSink = eventSink, +) + +fun aCustomElementCallBaseUrlState( + baseUrl: String? = null, + defaultUrl: String = "https://call.element.io", + validator: (String?) -> Boolean = { true }, +) = CustomElementCallBaseUrlState( + baseUrl = baseUrl, + defaultUrl = defaultUrl, + validator = validator, ) diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsViewTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsViewTest.kt new file mode 100644 index 0000000000..ea120a27c1 --- /dev/null +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsViewTest.kt @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2024 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 + +import androidx.activity.ComponentActivity +import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.element.android.features.preferences.impl.R +import io.element.android.libraries.ui.strings.CommonStrings +import io.element.android.tests.testutils.EnsureNeverCalled +import io.element.android.tests.testutils.EventsRecorder +import io.element.android.tests.testutils.clickOn +import io.element.android.tests.testutils.ensureCalledOnce +import io.element.android.tests.testutils.pressBack +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TestRule +import org.junit.runner.RunWith +import org.robolectric.annotation.Config + +@RunWith(AndroidJUnit4::class) +class DeveloperSettingsViewTest { + @get:Rule + val rule = createAndroidComposeRule() + + @Test + fun `clicking on back invokes the expected callback`() { + val eventsRecorder = EventsRecorder(expectEvents = false) + ensureCalledOnce { + rule.setDeveloperSettingsView( + state = aDeveloperSettingsState( + eventSink = eventsRecorder + ), + onBackPressed = it + ) + rule.pressBack() + } + } + + @Test + fun `clicking on element call url open the dialogs and submit emits the expected event`() { + val eventsRecorder = EventsRecorder() + rule.setDeveloperSettingsView( + state = aDeveloperSettingsState( + eventSink = eventsRecorder + ), + ) + rule.clickOn(R.string.screen_advanced_settings_element_call_base_url) + rule.clickOn(CommonStrings.action_ok) + eventsRecorder.assertSingle(DeveloperSettingsEvents.SetCustomElementCallBaseUrl("https://call.element.io")) + } + + @Config(qualifiers = "h1024dp") + @Test + fun `clicking on open showkase invokes the expected callback`() { + val eventsRecorder = EventsRecorder(expectEvents = false) + ensureCalledOnce { + rule.setDeveloperSettingsView( + state = aDeveloperSettingsState( + eventSink = eventsRecorder + ), + onOpenShowkase = it + ) + rule.onNodeWithText("Open Showkase browser").performClick() + } + } + + @Test + fun `clicking on configure tracing invokes the expected callback`() { + val eventsRecorder = EventsRecorder(expectEvents = false) + ensureCalledOnce { + rule.setDeveloperSettingsView( + state = aDeveloperSettingsState( + eventSink = eventsRecorder + ), + onOpenConfigureTracing = it + ) + rule.onNodeWithText("Configure tracing").performClick() + } + } + + @Config(qualifiers = "h1024dp") + @Test + fun `clicking on clear cache emits the expected event`() { + val eventsRecorder = EventsRecorder() + rule.setDeveloperSettingsView( + state = aDeveloperSettingsState( + eventSink = eventsRecorder + ), + ) + rule.onNodeWithText("Clear cache").performClick() + eventsRecorder.assertSingle(DeveloperSettingsEvents.ClearCache) + } +} + +private fun AndroidComposeTestRule.setDeveloperSettingsView( + state: DeveloperSettingsState, + onOpenShowkase: () -> Unit = EnsureNeverCalled(), + onOpenConfigureTracing: () -> Unit = EnsureNeverCalled(), + onBackPressed: () -> Unit = EnsureNeverCalled() +) { + setContent { + DeveloperSettingsView( + state = state, + onOpenShowkase = onOpenShowkase, + onOpenConfigureTracing = onOpenConfigureTracing, + onBackPressed = onBackPressed, + ) + } +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceTextField.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceTextField.kt index f616c20a72..5b319e41d8 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceTextField.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceTextField.kt @@ -98,11 +98,11 @@ private fun TextFieldDialog( keyboardOptions: KeyboardOptions = KeyboardOptions.Default, ) { val focusRequester = remember { FocusRequester() } - var textFieldContents by rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue(value.orEmpty(), selection = TextRange(value.orEmpty().length))) } var error by rememberSaveable { mutableStateOf(null) } + var canRequestFocus by rememberSaveable { mutableStateOf(false) } val canSubmit by remember { derivedStateOf { validation(textFieldContents.text) } } ListDialog( title = title, @@ -128,10 +128,11 @@ private fun TextFieldDialog( maxLines = maxLines, modifier = Modifier.focusRequester(focusRequester), ) + canRequestFocus = true } } - if (autoSelectOnDisplay) { + if (autoSelectOnDisplay && canRequestFocus) { LaunchedEffect(Unit) { focusRequester.requestFocus() }