Initial QSfera import
This commit is contained in:
+826
@@ -0,0 +1,826 @@
|
||||
/**
|
||||
* qsfera Android client application
|
||||
*
|
||||
* @author Abel García de Prada
|
||||
* @author Juan Carlos Garrote Gascón
|
||||
* @author Jorge Aguado Recio
|
||||
*
|
||||
* Copyright (C) 2024 ownCloud GmbH.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
* <p>
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* <p>
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package eu.qsfera.android.authentication
|
||||
|
||||
import android.accounts.AccountManager
|
||||
import android.accounts.AccountManager.KEY_ACCOUNT_NAME
|
||||
import android.accounts.AccountManager.KEY_ACCOUNT_TYPE
|
||||
import android.app.Activity.RESULT_OK
|
||||
import android.app.Instrumentation
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.espresso.Espresso.closeSoftKeyboard
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.intent.Intents
|
||||
import androidx.test.espresso.intent.Intents.intended
|
||||
import androidx.test.espresso.intent.matcher.IntentMatchers
|
||||
import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
|
||||
import androidx.test.espresso.matcher.RootMatchers.isDialog
|
||||
import androidx.test.espresso.matcher.ViewMatchers.Visibility
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||
import eu.qsfera.android.R
|
||||
import eu.qsfera.android.domain.exceptions.NoNetworkConnectionException
|
||||
import eu.qsfera.android.domain.exceptions.QSferaVersionNotSupportedException
|
||||
import eu.qsfera.android.domain.exceptions.ServerNotReachableException
|
||||
import eu.qsfera.android.domain.exceptions.UnauthorizedException
|
||||
import eu.qsfera.android.domain.server.model.ServerInfo
|
||||
import eu.qsfera.android.domain.utils.Event
|
||||
import eu.qsfera.android.extensions.parseError
|
||||
import eu.qsfera.android.presentation.authentication.ACTION_UPDATE_EXPIRED_TOKEN
|
||||
import eu.qsfera.android.presentation.authentication.ACTION_UPDATE_TOKEN
|
||||
import eu.qsfera.android.presentation.authentication.AuthenticationViewModel
|
||||
import eu.qsfera.android.presentation.authentication.BASIC_TOKEN_TYPE
|
||||
import eu.qsfera.android.presentation.authentication.EXTRA_ACCOUNT
|
||||
import eu.qsfera.android.presentation.authentication.EXTRA_ACTION
|
||||
import eu.qsfera.android.presentation.authentication.KEY_AUTH_TOKEN_TYPE
|
||||
import eu.qsfera.android.presentation.authentication.LoginActivity
|
||||
import eu.qsfera.android.presentation.authentication.OAUTH_TOKEN_TYPE
|
||||
import eu.qsfera.android.presentation.authentication.oauth.OAuthViewModel
|
||||
import eu.qsfera.android.presentation.common.UIResult
|
||||
import eu.qsfera.android.presentation.settings.SettingsActivity
|
||||
import eu.qsfera.android.presentation.settings.SettingsViewModel
|
||||
import eu.qsfera.android.providers.ContextProvider
|
||||
import eu.qsfera.android.providers.MdmProvider
|
||||
import eu.qsfera.android.testutil.OC_ACCOUNT
|
||||
import eu.qsfera.android.testutil.OC_AUTH_TOKEN_TYPE
|
||||
import eu.qsfera.android.testutil.OC_BASIC_PASSWORD
|
||||
import eu.qsfera.android.testutil.OC_BASIC_USERNAME
|
||||
import eu.qsfera.android.testutil.OC_INSECURE_SERVER_INFO_BASIC_AUTH
|
||||
import eu.qsfera.android.testutil.OC_SECURE_SERVER_INFO_BASIC_AUTH
|
||||
import eu.qsfera.android.testutil.OC_SECURE_SERVER_INFO_BEARER_AUTH
|
||||
import eu.qsfera.android.utils.CONFIGURATION_SERVER_URL
|
||||
import eu.qsfera.android.utils.CONFIGURATION_SERVER_URL_INPUT_VISIBILITY
|
||||
import eu.qsfera.android.utils.NO_MDM_RESTRICTION_YET
|
||||
import eu.qsfera.android.utils.matchers.assertVisibility
|
||||
import eu.qsfera.android.utils.matchers.isDisplayed
|
||||
import eu.qsfera.android.utils.matchers.isEnabled
|
||||
import eu.qsfera.android.utils.matchers.isFocusable
|
||||
import eu.qsfera.android.utils.matchers.withText
|
||||
import eu.qsfera.android.utils.mockIntentToComponent
|
||||
import eu.qsfera.android.utils.replaceText
|
||||
import eu.qsfera.android.utils.scrollAndClick
|
||||
import eu.qsfera.android.utils.typeText
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.unmockkAll
|
||||
import io.mockk.verify
|
||||
import org.hamcrest.Matchers.allOf
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Before
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.core.context.startKoin
|
||||
import org.koin.core.context.stopKoin
|
||||
import org.koin.dsl.module
|
||||
|
||||
class LoginActivityTest {
|
||||
|
||||
private lateinit var activityScenario: ActivityScenario<LoginActivity>
|
||||
|
||||
private lateinit var authenticationViewModel: AuthenticationViewModel
|
||||
private lateinit var oauthViewModel: OAuthViewModel
|
||||
private lateinit var settingsViewModel: SettingsViewModel
|
||||
private lateinit var ocContextProvider: ContextProvider
|
||||
private lateinit var mdmProvider: MdmProvider
|
||||
private lateinit var context: Context
|
||||
|
||||
private lateinit var loginResultLiveData: MutableLiveData<Event<UIResult<String>>>
|
||||
private lateinit var serverInfoLiveData: MutableLiveData<Event<UIResult<ServerInfo>>>
|
||||
private lateinit var supportsOauth2LiveData: MutableLiveData<Event<UIResult<Boolean>>>
|
||||
private lateinit var baseUrlLiveData: MutableLiveData<Event<UIResult<String>>>
|
||||
private lateinit var accountDiscoveryLiveData: MutableLiveData<Event<UIResult<Unit>>>
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
context = ApplicationProvider.getApplicationContext()
|
||||
|
||||
authenticationViewModel = mockk(relaxed = true)
|
||||
oauthViewModel = mockk(relaxed = true)
|
||||
settingsViewModel = mockk(relaxUnitFun = true)
|
||||
ocContextProvider = mockk(relaxed = true)
|
||||
mdmProvider = mockk(relaxed = true)
|
||||
val accountManager = mockk<AccountManager>(relaxed = true)
|
||||
every { accountManager.getUserData(any(), any()) } returns null
|
||||
every { accountManager.getPassword(any()) } returns null
|
||||
|
||||
mockkStatic(AccountManager::class)
|
||||
every { AccountManager.get(any()) } returns accountManager
|
||||
|
||||
loginResultLiveData = MutableLiveData()
|
||||
serverInfoLiveData = MutableLiveData()
|
||||
supportsOauth2LiveData = MutableLiveData()
|
||||
baseUrlLiveData = MutableLiveData()
|
||||
accountDiscoveryLiveData = MutableLiveData()
|
||||
|
||||
every { authenticationViewModel.loginResult } returns loginResultLiveData
|
||||
every { authenticationViewModel.serverInfo } returns serverInfoLiveData
|
||||
every { authenticationViewModel.supportsOAuth2 } returns supportsOauth2LiveData
|
||||
every { authenticationViewModel.baseUrl } returns baseUrlLiveData
|
||||
every { authenticationViewModel.accountDiscovery } returns accountDiscoveryLiveData
|
||||
every { settingsViewModel.isThereAttachedAccount() } returns false
|
||||
|
||||
stopKoin()
|
||||
|
||||
startKoin {
|
||||
context
|
||||
allowOverride(override = true)
|
||||
modules(
|
||||
module {
|
||||
viewModel {
|
||||
authenticationViewModel
|
||||
}
|
||||
viewModel {
|
||||
oauthViewModel
|
||||
}
|
||||
viewModel {
|
||||
settingsViewModel
|
||||
}
|
||||
factory {
|
||||
ocContextProvider
|
||||
}
|
||||
factory {
|
||||
mdmProvider
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
unmockkAll()
|
||||
}
|
||||
|
||||
private fun launchTest(
|
||||
showServerUrlInput: Boolean = true,
|
||||
serverUrl: String = "",
|
||||
showLoginBackGroundImage: Boolean = true,
|
||||
showWelcomeLink: Boolean = true,
|
||||
accountType: String = "qsfera",
|
||||
loginWelcomeText: String = "",
|
||||
webfingerLookupServer: String = "",
|
||||
intent: Intent? = null
|
||||
) {
|
||||
every { mdmProvider.getBrandingBoolean(CONFIGURATION_SERVER_URL_INPUT_VISIBILITY, R.bool.show_server_url_input) } returns showServerUrlInput
|
||||
every { mdmProvider.getBrandingString(CONFIGURATION_SERVER_URL, R.string.server_url) } returns serverUrl
|
||||
every { mdmProvider.getBrandingString(NO_MDM_RESTRICTION_YET, R.string.webfinger_lookup_server) } returns webfingerLookupServer
|
||||
every { ocContextProvider.getBoolean(R.bool.use_login_background_image) } returns showLoginBackGroundImage
|
||||
every { ocContextProvider.getBoolean(R.bool.show_welcome_link) } returns showWelcomeLink
|
||||
every { ocContextProvider.getString(R.string.account_type) } returns accountType
|
||||
every { ocContextProvider.getString(R.string.login_welcome_text) } returns loginWelcomeText
|
||||
every { ocContextProvider.getString(R.string.app_name) } returns BRANDED_APP_NAME
|
||||
|
||||
activityScenario = if (intent == null) {
|
||||
ActivityScenario.launch(LoginActivity::class.java)
|
||||
} else {
|
||||
ActivityScenario.launch(intent)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun initialViewStatus_notBrandedOptions() {
|
||||
launchTest()
|
||||
|
||||
assertViewsDisplayed()
|
||||
assertWebfingerFlowDisplayed(webfingerEnabled = false)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun initialViewStatus_brandedOptions_webfinger() {
|
||||
launchTest(webfingerLookupServer = OC_SECURE_SERVER_INFO_BASIC_AUTH.baseUrl)
|
||||
|
||||
assertWebfingerFlowDisplayed(webfingerEnabled = true)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun initialViewStatus_brandedOptions_serverInfoInSetup() {
|
||||
launchTest(showServerUrlInput = false, serverUrl = OC_SECURE_SERVER_INFO_BASIC_AUTH.baseUrl)
|
||||
|
||||
assertViewsDisplayed(
|
||||
showHostUrlFrame = false,
|
||||
showHostUrlInput = false,
|
||||
showCenteredRefreshButton = true,
|
||||
showEmbeddedCheckServerButton = false
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun initialViewStatus_brandedOptions_serverInfoInSetup_connectionFails() {
|
||||
|
||||
launchTest(showServerUrlInput = false, serverUrl = OC_SECURE_SERVER_INFO_BASIC_AUTH.baseUrl)
|
||||
|
||||
serverInfoLiveData.postValue(Event(UIResult.Error(NoNetworkConnectionException())))
|
||||
|
||||
R.id.centeredRefreshButton.isDisplayed(true)
|
||||
R.id.centeredRefreshButton.scrollAndClick()
|
||||
|
||||
verify(exactly = 1) { authenticationViewModel.getServerInfo(OC_SECURE_SERVER_INFO_BASIC_AUTH.baseUrl, true) }
|
||||
serverInfoLiveData.postValue(Event(UIResult.Success(SECURE_SERVER_INFO_BASIC)))
|
||||
|
||||
R.id.centeredRefreshButton.isDisplayed(false)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun initialViewStatus_brandedOptions_dontUseLoginBackgroundImage() {
|
||||
launchTest(showLoginBackGroundImage = false)
|
||||
|
||||
assertViewsDisplayed(showLoginBackGroundImage = false)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun initialViewStatus_brandedOptions_dontShowWelcomeLink() {
|
||||
launchTest(showWelcomeLink = false)
|
||||
|
||||
assertViewsDisplayed(showWelcomeLink = false)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun initialViewStatus_brandedOptions_customWelcomeText() {
|
||||
launchTest(showWelcomeLink = true, loginWelcomeText = CUSTOM_WELCOME_TEXT)
|
||||
|
||||
assertViewsDisplayed(showWelcomeLink = true)
|
||||
|
||||
R.id.welcome_link.withText(CUSTOM_WELCOME_TEXT)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun initialViewStatus_brandedOptions_defaultWelcomeText() {
|
||||
launchTest(showWelcomeLink = true, loginWelcomeText = "")
|
||||
|
||||
assertViewsDisplayed(showWelcomeLink = true)
|
||||
|
||||
R.id.welcome_link.withText(String.format(ocContextProvider.getString(R.string.auth_register), BRANDED_APP_NAME))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkServerInfo_clickButton_callGetServerInfo() {
|
||||
launchTest()
|
||||
|
||||
R.id.hostUrlInput.typeText(OC_SECURE_SERVER_INFO_BASIC_AUTH.baseUrl)
|
||||
|
||||
R.id.embeddedCheckServerButton.scrollAndClick()
|
||||
|
||||
verify(exactly = 1) { authenticationViewModel.getServerInfo(OC_SECURE_SERVER_INFO_BASIC_AUTH.baseUrl, true) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkServerInfo_clickLogo_callGetServerInfo() {
|
||||
launchTest()
|
||||
R.id.hostUrlInput.typeText(OC_SECURE_SERVER_INFO_BASIC_AUTH.baseUrl)
|
||||
|
||||
R.id.thumbnail.scrollAndClick()
|
||||
|
||||
verify(exactly = 1) { authenticationViewModel.getServerInfo(OC_SECURE_SERVER_INFO_BASIC_AUTH.baseUrl, true) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkServerInfo_isLoading_show() {
|
||||
launchTest()
|
||||
serverInfoLiveData.postValue(Event(UIResult.Loading()))
|
||||
|
||||
with(R.id.server_status_text) {
|
||||
isDisplayed(true)
|
||||
withText(R.string.auth_testing_connection)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkServerInfo_isSuccess_updateUrlInput() {
|
||||
launchTest()
|
||||
R.id.hostUrlInput.typeText("demo.qsfera.eu")
|
||||
|
||||
serverInfoLiveData.postValue(Event(UIResult.Success(SECURE_SERVER_INFO_BASIC)))
|
||||
|
||||
R.id.hostUrlInput.withText(SECURE_SERVER_INFO_BASIC.baseUrl)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkServerInfo_isSuccess_Secure() {
|
||||
launchTest()
|
||||
serverInfoLiveData.postValue(Event(UIResult.Success(SECURE_SERVER_INFO_BASIC)))
|
||||
|
||||
with(R.id.server_status_text) {
|
||||
isDisplayed(true)
|
||||
assertVisibility(Visibility.VISIBLE)
|
||||
withText(R.string.auth_secure_connection)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkServerInfo_isSuccess_NotSecure() {
|
||||
launchTest()
|
||||
serverInfoLiveData.postValue(Event(UIResult.Success(INSECURE_SERVER_INFO_BASIC)))
|
||||
|
||||
onView(withText(R.string.insecure_http_url_title_dialog)).check(matches(isDisplayed()))
|
||||
onView(withText(R.string.insecure_http_url_message_dialog)).check(matches(isDisplayed()))
|
||||
onView(withText(R.string.insecure_http_url_continue_button)).inRoot(isDialog()).check(matches(isDisplayed())).perform(click())
|
||||
|
||||
with(R.id.server_status_text) {
|
||||
isDisplayed(true)
|
||||
assertVisibility(Visibility.VISIBLE)
|
||||
withText(R.string.auth_connection_established)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkServerInfo_isSuccess_Basic() {
|
||||
launchTest()
|
||||
|
||||
serverInfoLiveData.postValue(Event(UIResult.Success(SECURE_SERVER_INFO_BASIC)))
|
||||
|
||||
checkBasicFieldsVisibility(loginButtonShouldBeVisible = false)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkServerInfo_isSuccess_Bearer() {
|
||||
Intents.init()
|
||||
launchTest()
|
||||
avoidOpeningChromeCustomTab()
|
||||
|
||||
serverInfoLiveData.postValue(Event(UIResult.Success(SECURE_SERVER_INFO_BEARER)))
|
||||
|
||||
checkBearerFieldsVisibility()
|
||||
Intents.release()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkServerInfo_isSuccess_basicModifyUrlInput() {
|
||||
launchTest()
|
||||
|
||||
serverInfoLiveData.postValue(Event(UIResult.Success(SECURE_SERVER_INFO_BASIC)))
|
||||
|
||||
checkBasicFieldsVisibility()
|
||||
|
||||
R.id.account_username.typeText(OC_BASIC_USERNAME)
|
||||
|
||||
R.id.account_password.typeText(OC_BASIC_PASSWORD)
|
||||
|
||||
R.id.hostUrlInput.typeText("anything")
|
||||
|
||||
with(R.id.account_username) {
|
||||
withText("")
|
||||
assertVisibility(Visibility.GONE)
|
||||
}
|
||||
|
||||
with(R.id.account_password) {
|
||||
withText("")
|
||||
assertVisibility(Visibility.GONE)
|
||||
}
|
||||
|
||||
R.id.loginButton.assertVisibility(Visibility.GONE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkServerInfo_isSuccess_bearerModifyUrlInput() {
|
||||
Intents.init()
|
||||
launchTest()
|
||||
avoidOpeningChromeCustomTab()
|
||||
|
||||
serverInfoLiveData.postValue(Event(UIResult.Success(SECURE_SERVER_INFO_BEARER)))
|
||||
|
||||
checkBearerFieldsVisibility()
|
||||
|
||||
R.id.hostUrlInput.typeText("anything")
|
||||
|
||||
R.id.auth_status_text.assertVisibility(Visibility.GONE)
|
||||
Intents.release()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkServerInfo_isError_emptyUrl() {
|
||||
launchTest()
|
||||
|
||||
R.id.hostUrlInput.typeText("")
|
||||
|
||||
R.id.embeddedCheckServerButton.scrollAndClick()
|
||||
|
||||
with(R.id.server_status_text) {
|
||||
isDisplayed(true)
|
||||
assertVisibility(Visibility.VISIBLE)
|
||||
withText(R.string.auth_can_not_auth_against_server)
|
||||
}
|
||||
|
||||
verify(exactly = 0) { authenticationViewModel.getServerInfo(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkServerInfo_isError_qsferaVersionNotSupported() {
|
||||
launchTest()
|
||||
|
||||
serverInfoLiveData.postValue(Event(UIResult.Error(QSferaVersionNotSupportedException())))
|
||||
|
||||
with(R.id.server_status_text) {
|
||||
isDisplayed(true)
|
||||
assertVisibility(Visibility.VISIBLE)
|
||||
withText(R.string.server_not_supported)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkServerInfo_isError_noNetworkConnection() {
|
||||
launchTest()
|
||||
|
||||
serverInfoLiveData.postValue(Event(UIResult.Error(NoNetworkConnectionException())))
|
||||
|
||||
with(R.id.server_status_text) {
|
||||
isDisplayed(true)
|
||||
assertVisibility(Visibility.VISIBLE)
|
||||
withText(R.string.error_no_network_connection)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkServerInfo_isError_otherExceptions() {
|
||||
launchTest()
|
||||
|
||||
serverInfoLiveData.postValue(Event(UIResult.Error(ServerNotReachableException())))
|
||||
|
||||
with(R.id.server_status_text) {
|
||||
isDisplayed(true)
|
||||
assertVisibility(Visibility.VISIBLE)
|
||||
withText(R.string.network_host_not_available)
|
||||
}
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
fun loginBasic_callLoginBasic() {
|
||||
launchTest()
|
||||
|
||||
serverInfoLiveData.postValue(Event(UIResult.Success(SECURE_SERVER_INFO_BASIC)))
|
||||
|
||||
R.id.account_username.typeText(OC_BASIC_USERNAME)
|
||||
|
||||
R.id.account_password.typeText(OC_BASIC_PASSWORD)
|
||||
|
||||
with(R.id.loginButton) {
|
||||
isDisplayed(true)
|
||||
scrollAndClick()
|
||||
}
|
||||
|
||||
verify(exactly = 1) { authenticationViewModel.loginBasic(OC_BASIC_USERNAME, OC_BASIC_PASSWORD, null) }
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
fun loginBasic_callLoginBasic_trimUsername() {
|
||||
launchTest()
|
||||
|
||||
serverInfoLiveData.postValue(Event(UIResult.Success(SECURE_SERVER_INFO_BASIC)))
|
||||
|
||||
R.id.account_username.typeText(" $OC_BASIC_USERNAME ")
|
||||
|
||||
R.id.account_password.typeText(OC_BASIC_PASSWORD)
|
||||
|
||||
with(R.id.loginButton) {
|
||||
isDisplayed(true)
|
||||
scrollAndClick()
|
||||
}
|
||||
|
||||
verify(exactly = 1) { authenticationViewModel.loginBasic(OC_BASIC_USERNAME, OC_BASIC_PASSWORD, null) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun loginBasic_showOrHideFields() {
|
||||
launchTest()
|
||||
|
||||
serverInfoLiveData.postValue(Event(UIResult.Success(SECURE_SERVER_INFO_BASIC)))
|
||||
|
||||
R.id.account_username.typeText(OC_BASIC_USERNAME)
|
||||
|
||||
R.id.loginButton.isDisplayed(false)
|
||||
|
||||
R.id.account_password.typeText(OC_BASIC_PASSWORD)
|
||||
|
||||
R.id.loginButton.isDisplayed(true)
|
||||
|
||||
R.id.account_username.replaceText("")
|
||||
|
||||
R.id.loginButton.isDisplayed(false)
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun login_isLoading() {
|
||||
launchTest()
|
||||
|
||||
loginResultLiveData.postValue(Event(UIResult.Loading()))
|
||||
|
||||
with(R.id.auth_status_text) {
|
||||
withText(R.string.auth_trying_to_login)
|
||||
isDisplayed(true)
|
||||
assertVisibility(Visibility.VISIBLE)
|
||||
}
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
fun login_isSuccess_finishResultCode() {
|
||||
launchTest()
|
||||
|
||||
loginResultLiveData.postValue(Event(UIResult.Success(data = "Account_name")))
|
||||
accountDiscoveryLiveData.postValue(Event(UIResult.Success()))
|
||||
|
||||
assertEquals(activityScenario.result.resultCode, RESULT_OK)
|
||||
val accountName: String? = activityScenario.result?.resultData?.extras?.getString(KEY_ACCOUNT_NAME)
|
||||
val accountType: String? = activityScenario.result?.resultData?.extras?.getString(KEY_ACCOUNT_TYPE)
|
||||
|
||||
assertNotNull(accountName)
|
||||
assertNotNull(accountType)
|
||||
assertEquals("Account_name", accountName)
|
||||
assertEquals("qsfera", accountType)
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
fun login_isSuccess_finishResultCodeBrandedAccountType() {
|
||||
launchTest(accountType = "notQSfera")
|
||||
|
||||
loginResultLiveData.postValue(Event(UIResult.Success(data = "Account_name")))
|
||||
accountDiscoveryLiveData.postValue(Event(UIResult.Success()))
|
||||
|
||||
assertEquals(activityScenario.result.resultCode, RESULT_OK)
|
||||
val accountName: String? = activityScenario.result?.resultData?.extras?.getString(KEY_ACCOUNT_NAME)
|
||||
val accountType: String? = activityScenario.result?.resultData?.extras?.getString(KEY_ACCOUNT_TYPE)
|
||||
|
||||
assertNotNull(accountName)
|
||||
assertNotNull(accountType)
|
||||
assertEquals("Account_name", accountName)
|
||||
assertEquals("notQSfera", accountType)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun login_isError_NoNetworkConnectionException() {
|
||||
launchTest()
|
||||
|
||||
loginResultLiveData.postValue(Event(UIResult.Error(NoNetworkConnectionException())))
|
||||
|
||||
R.id.server_status_text.withText(R.string.error_no_network_connection)
|
||||
|
||||
checkBasicFieldsVisibility(fieldsShouldBeVisible = false)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun login_isError_ServerNotReachableException() {
|
||||
launchTest()
|
||||
|
||||
loginResultLiveData.postValue(Event(UIResult.Error(ServerNotReachableException())))
|
||||
|
||||
R.id.server_status_text.withText(R.string.error_no_network_connection)
|
||||
|
||||
checkBasicFieldsVisibility(fieldsShouldBeVisible = false)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun login_isError_OtherException() {
|
||||
launchTest()
|
||||
|
||||
val exception = UnauthorizedException()
|
||||
|
||||
loginResultLiveData.postValue(Event(UIResult.Error(exception)))
|
||||
|
||||
R.id.auth_status_text.withText(exception.parseError("", context.resources, true) as String)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun intent_withSavedAccount_viewModelCalls() {
|
||||
val intentWithAccount = Intent(context, LoginActivity::class.java).apply {
|
||||
putExtra(EXTRA_ACCOUNT, OC_ACCOUNT)
|
||||
}
|
||||
|
||||
launchTest(intent = intentWithAccount)
|
||||
|
||||
verify(exactly = 1) { authenticationViewModel.supportsOAuth2(OC_ACCOUNT.name) }
|
||||
verify(exactly = 1) { authenticationViewModel.getBaseUrl(OC_ACCOUNT.name) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun supportsOAuth_isSuccess_actionUpdateExpiredTokenOAuth() {
|
||||
val intentWithAccount = Intent(context, LoginActivity::class.java).apply {
|
||||
putExtra(EXTRA_ACCOUNT, OC_ACCOUNT)
|
||||
putExtra(EXTRA_ACTION, ACTION_UPDATE_EXPIRED_TOKEN)
|
||||
putExtra(KEY_AUTH_TOKEN_TYPE, OAUTH_TOKEN_TYPE)
|
||||
}
|
||||
|
||||
launchTest(intent = intentWithAccount)
|
||||
|
||||
supportsOauth2LiveData.postValue(Event(UIResult.Success(true)))
|
||||
|
||||
with(R.id.instructions_message) {
|
||||
isDisplayed(true)
|
||||
assertVisibility(Visibility.VISIBLE)
|
||||
withText(context.getString(R.string.auth_expired_oauth_token_toast))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun supportsOAuth_isSuccess_actionUpdateToken() {
|
||||
val intentWithAccount = Intent(context, LoginActivity::class.java).apply {
|
||||
putExtra(EXTRA_ACCOUNT, OC_ACCOUNT)
|
||||
putExtra(EXTRA_ACTION, ACTION_UPDATE_TOKEN)
|
||||
putExtra(KEY_AUTH_TOKEN_TYPE, OC_AUTH_TOKEN_TYPE)
|
||||
}
|
||||
|
||||
launchTest(intent = intentWithAccount)
|
||||
|
||||
supportsOauth2LiveData.postValue(Event(UIResult.Success(false)))
|
||||
|
||||
R.id.instructions_message.assertVisibility(Visibility.GONE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun supportsOAuth_isSuccess_actionUpdateExpiredTokenBasic() {
|
||||
val intentWithAccount = Intent(context, LoginActivity::class.java).apply {
|
||||
putExtra(EXTRA_ACCOUNT, OC_ACCOUNT)
|
||||
putExtra(EXTRA_ACTION, ACTION_UPDATE_EXPIRED_TOKEN)
|
||||
putExtra(KEY_AUTH_TOKEN_TYPE, BASIC_TOKEN_TYPE)
|
||||
}
|
||||
|
||||
launchTest(intent = intentWithAccount)
|
||||
|
||||
supportsOauth2LiveData.postValue(Event(UIResult.Success(false)))
|
||||
|
||||
with(R.id.instructions_message) {
|
||||
isDisplayed(true)
|
||||
assertVisibility(Visibility.VISIBLE)
|
||||
withText(context.getString(R.string.auth_expired_basic_auth_toast))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getBaseUrl_isSuccess_updatesBaseUrl() {
|
||||
val intentWithAccount = Intent(context, LoginActivity::class.java).apply {
|
||||
putExtra(EXTRA_ACCOUNT, OC_ACCOUNT)
|
||||
}
|
||||
|
||||
launchTest(intent = intentWithAccount)
|
||||
|
||||
baseUrlLiveData.postValue(Event(UIResult.Success(OC_SECURE_SERVER_INFO_BASIC_AUTH.baseUrl)))
|
||||
|
||||
with(R.id.hostUrlInput) {
|
||||
withText(OC_SECURE_SERVER_INFO_BASIC_AUTH.baseUrl)
|
||||
assertVisibility(Visibility.VISIBLE)
|
||||
isDisplayed(true)
|
||||
isEnabled(false)
|
||||
isFocusable(false)
|
||||
}
|
||||
|
||||
verify(exactly = 0) { authenticationViewModel.getServerInfo(OC_SECURE_SERVER_INFO_BASIC_AUTH.baseUrl) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getBaseUrlAndActionNotCreate_isSuccess_updatesBaseUrl() {
|
||||
val intentWithAccount = Intent(context, LoginActivity::class.java).apply {
|
||||
putExtra(EXTRA_ACCOUNT, OC_ACCOUNT)
|
||||
putExtra(EXTRA_ACTION, ACTION_UPDATE_EXPIRED_TOKEN)
|
||||
}
|
||||
|
||||
launchTest(intent = intentWithAccount)
|
||||
|
||||
baseUrlLiveData.postValue(Event(UIResult.Success(OC_SECURE_SERVER_INFO_BASIC_AUTH.baseUrl)))
|
||||
|
||||
with(R.id.hostUrlInput) {
|
||||
withText(OC_SECURE_SERVER_INFO_BASIC_AUTH.baseUrl)
|
||||
assertVisibility(Visibility.VISIBLE)
|
||||
isDisplayed(true)
|
||||
isEnabled(false)
|
||||
isFocusable(false)
|
||||
}
|
||||
|
||||
verify(exactly = 1) { authenticationViewModel.getServerInfo(OC_SECURE_SERVER_INFO_BASIC_AUTH.baseUrl) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun settingsLink() {
|
||||
Intents.init()
|
||||
launchTest()
|
||||
|
||||
closeSoftKeyboard()
|
||||
|
||||
mockIntentToComponent(RESULT_OK, SettingsActivity::class.java.name)
|
||||
onView(withId(R.id.settings_link)).perform(click())
|
||||
intended(hasComponent(SettingsActivity::class.java.name))
|
||||
|
||||
Intents.release()
|
||||
}
|
||||
|
||||
private fun avoidOpeningChromeCustomTab() {
|
||||
Intents.intending(allOf(IntentMatchers.hasAction(Intent.ACTION_VIEW)))
|
||||
.respondWith(Instrumentation.ActivityResult(RESULT_OK, null))
|
||||
}
|
||||
|
||||
private fun checkBasicFieldsVisibility(
|
||||
fieldsShouldBeVisible: Boolean = true,
|
||||
loginButtonShouldBeVisible: Boolean = false
|
||||
) {
|
||||
val visibilityMatcherFields = if (fieldsShouldBeVisible) Visibility.VISIBLE else Visibility.GONE
|
||||
val visibilityMatcherLoginButton = if (loginButtonShouldBeVisible) Visibility.VISIBLE else Visibility.GONE
|
||||
|
||||
with(R.id.account_username) {
|
||||
isDisplayed(fieldsShouldBeVisible)
|
||||
assertVisibility(visibilityMatcherFields)
|
||||
withText("")
|
||||
}
|
||||
|
||||
with(R.id.account_password) {
|
||||
isDisplayed(fieldsShouldBeVisible)
|
||||
assertVisibility(visibilityMatcherFields)
|
||||
withText("")
|
||||
}
|
||||
|
||||
R.id.loginButton.assertVisibility(visibilityMatcherLoginButton)
|
||||
R.id.auth_status_text.assertVisibility(visibilityMatcherLoginButton)
|
||||
}
|
||||
|
||||
private fun checkBearerFieldsVisibility() {
|
||||
R.id.account_username.assertVisibility(Visibility.GONE)
|
||||
R.id.account_password.assertVisibility(Visibility.GONE)
|
||||
R.id.auth_status_text.assertVisibility(Visibility.GONE)
|
||||
|
||||
with(R.id.server_status_text) {
|
||||
isDisplayed(true)
|
||||
assertVisibility(Visibility.VISIBLE)
|
||||
}
|
||||
}
|
||||
|
||||
private fun assertWebfingerFlowDisplayed(
|
||||
webfingerEnabled: Boolean,
|
||||
) {
|
||||
R.id.webfinger_layout.isDisplayed(webfingerEnabled)
|
||||
R.id.webfinger_username.isDisplayed(webfingerEnabled)
|
||||
R.id.webfinger_button.isDisplayed(webfingerEnabled)
|
||||
}
|
||||
|
||||
private fun assertViewsDisplayed(
|
||||
showLoginBackGroundImage: Boolean = true,
|
||||
showThumbnail: Boolean = true,
|
||||
showCenteredRefreshButton: Boolean = false,
|
||||
showInstructionsMessage: Boolean = false,
|
||||
showHostUrlFrame: Boolean = true,
|
||||
showHostUrlInput: Boolean = true,
|
||||
showEmbeddedCheckServerButton: Boolean = true,
|
||||
showEmbeddedRefreshButton: Boolean = false,
|
||||
showServerStatusText: Boolean = false,
|
||||
showAccountUsername: Boolean = false,
|
||||
showAccountPassword: Boolean = false,
|
||||
showAuthStatus: Boolean = false,
|
||||
showLoginButton: Boolean = false,
|
||||
showWelcomeLink: Boolean = true
|
||||
) {
|
||||
R.id.login_background_image.isDisplayed(displayed = showLoginBackGroundImage)
|
||||
R.id.thumbnail.isDisplayed(displayed = showThumbnail)
|
||||
R.id.centeredRefreshButton.isDisplayed(displayed = showCenteredRefreshButton)
|
||||
R.id.instructions_message.isDisplayed(displayed = showInstructionsMessage)
|
||||
R.id.hostUrlFrame.isDisplayed(displayed = showHostUrlFrame)
|
||||
R.id.hostUrlInput.isDisplayed(displayed = showHostUrlInput)
|
||||
R.id.embeddedCheckServerButton.isDisplayed(displayed = showEmbeddedCheckServerButton)
|
||||
R.id.embeddedRefreshButton.isDisplayed(displayed = showEmbeddedRefreshButton)
|
||||
R.id.server_status_text.isDisplayed(displayed = showServerStatusText)
|
||||
R.id.account_username_container.isDisplayed(displayed = showAccountUsername)
|
||||
R.id.account_username.isDisplayed(displayed = showAccountUsername)
|
||||
R.id.account_password_container.isDisplayed(displayed = showAccountPassword)
|
||||
R.id.account_password.isDisplayed(displayed = showAccountPassword)
|
||||
R.id.auth_status_text.isDisplayed(displayed = showAuthStatus)
|
||||
R.id.loginButton.isDisplayed(displayed = showLoginButton)
|
||||
R.id.welcome_link.isDisplayed(displayed = showWelcomeLink)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val SECURE_SERVER_INFO_BASIC = OC_SECURE_SERVER_INFO_BASIC_AUTH
|
||||
val INSECURE_SERVER_INFO_BASIC = OC_INSECURE_SERVER_INFO_BASIC_AUTH
|
||||
val SECURE_SERVER_INFO_BEARER = OC_SECURE_SERVER_INFO_BEARER_AUTH
|
||||
private const val CUSTOM_WELCOME_TEXT = "Welcome to this test"
|
||||
private const val BRANDED_APP_NAME = "BrandedAppName"
|
||||
}
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* qsfera Android client application
|
||||
*
|
||||
* @author Abel García de Prada
|
||||
*
|
||||
* Copyright (C) 2020 ownCloud GmbH.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
* <p>
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* <p>
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package eu.qsfera.android.files
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.testing.FragmentScenario
|
||||
import androidx.fragment.app.testing.launchFragment
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.action.ViewActions
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.matcher.RootMatchers
|
||||
import androidx.test.espresso.matcher.ViewMatchers
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import eu.qsfera.android.R
|
||||
import eu.qsfera.android.presentation.files.SortBottomSheetFragment
|
||||
import eu.qsfera.android.presentation.files.SortOrder
|
||||
import eu.qsfera.android.presentation.files.SortType
|
||||
import eu.qsfera.android.utils.matchers.bsfItemWithIcon
|
||||
import eu.qsfera.android.utils.matchers.bsfItemWithTitle
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class SortBottomSheetFragmentTest {
|
||||
|
||||
private lateinit var fragmentScenario: FragmentScenario<SortBottomSheetFragment>
|
||||
private val fragmentListener = mockk<SortBottomSheetFragment.SortDialogListener>()
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
val fragmentArgs = Bundle().apply {
|
||||
putParcelable(SortBottomSheetFragment.ARG_SORT_TYPE, SortType.SORT_TYPE_BY_NAME)
|
||||
putParcelable(SortBottomSheetFragment.ARG_SORT_ORDER, SortOrder.SORT_ORDER_ASCENDING)
|
||||
}
|
||||
fragmentScenario = launchFragment(fragmentArgs)
|
||||
every { fragmentListener.onSortSelected(any()) } returns Unit
|
||||
fragmentScenario.onFragment { it.sortDialogListener = fragmentListener }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_initial_view() {
|
||||
onView(withId(R.id.title))
|
||||
.inRoot(RootMatchers.isDialog())
|
||||
.check(matches(ViewMatchers.withText(R.string.actionbar_sort_title)))
|
||||
.check(matches(ViewMatchers.hasTextColor(R.color.bottom_sheet_fragment_title_color)))
|
||||
|
||||
with(R.id.sort_by_name) {
|
||||
bsfItemWithTitle(R.string.global_name, R.color.primary)
|
||||
bsfItemWithIcon(R.drawable.ic_sort_by_name, R.color.primary)
|
||||
}
|
||||
with(R.id.sort_by_size) {
|
||||
bsfItemWithTitle(R.string.global_size, R.color.bottom_sheet_fragment_item_color)
|
||||
bsfItemWithIcon(R.drawable.ic_sort_by_size, R.color.bottom_sheet_fragment_item_color)
|
||||
}
|
||||
with(R.id.sort_by_date) {
|
||||
bsfItemWithTitle(R.string.global_date, R.color.bottom_sheet_fragment_item_color)
|
||||
bsfItemWithIcon(R.drawable.ic_sort_by_date, R.color.bottom_sheet_fragment_item_color)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_sort_by_name_click() {
|
||||
onView(withId(R.id.sort_by_name)).inRoot(RootMatchers.isDialog()).perform(ViewActions.click())
|
||||
verify { fragmentListener.onSortSelected(SortType.SORT_TYPE_BY_NAME) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_sort_by_date_click() {
|
||||
onView(withId(R.id.sort_by_date)).inRoot(RootMatchers.isDialog()).perform(ViewActions.click())
|
||||
verify { fragmentListener.onSortSelected(SortType.SORT_TYPE_BY_DATE) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_sort_by_size_click() {
|
||||
onView(withId(R.id.sort_by_size)).inRoot(RootMatchers.isDialog()).perform(ViewActions.click())
|
||||
verify { fragmentListener.onSortSelected(SortType.SORT_TYPE_BY_SIZE) }
|
||||
}
|
||||
}
|
||||
+216
@@ -0,0 +1,216 @@
|
||||
package eu.qsfera.android.files.details
|
||||
|
||||
import android.content.Context
|
||||
import androidx.test.core.app.ActivityScenario.launch
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.matcher.ViewMatchers
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import eu.qsfera.android.R
|
||||
import eu.qsfera.android.domain.files.model.OCFileWithSyncInfo
|
||||
import eu.qsfera.android.presentation.files.details.FileDetailsFragment
|
||||
import eu.qsfera.android.presentation.files.details.FileDetailsViewModel
|
||||
import eu.qsfera.android.presentation.files.operations.FileOperationsViewModel
|
||||
import eu.qsfera.android.sharing.shares.ui.TestShareFileActivity
|
||||
import eu.qsfera.android.testutil.OC_ACCOUNT
|
||||
import eu.qsfera.android.testutil.OC_FILE
|
||||
import eu.qsfera.android.testutil.OC_FILE_WITH_SYNC_INFO_AVAILABLE_OFFLINE
|
||||
import eu.qsfera.android.testutil.OC_FILE_WITH_SYNC_INFO
|
||||
import eu.qsfera.android.testutil.OC_FILE_WITH_SYNC_INFO_AND_SPACE
|
||||
import eu.qsfera.android.testutil.OC_FILE_WITH_SYNC_INFO_AND_WITHOUT_PERSONAL_SPACE
|
||||
import eu.qsfera.android.utils.DisplayUtils
|
||||
import eu.qsfera.android.utils.matchers.assertVisibility
|
||||
import eu.qsfera.android.utils.matchers.isDisplayed
|
||||
import eu.qsfera.android.utils.matchers.withDrawable
|
||||
import eu.qsfera.android.utils.matchers.withText
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import org.junit.Before
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.core.context.startKoin
|
||||
import org.koin.core.context.stopKoin
|
||||
import org.koin.dsl.module
|
||||
|
||||
@Ignore
|
||||
class FileDetailsFragmentTest {
|
||||
|
||||
private lateinit var fileDetailsViewModel: FileDetailsViewModel
|
||||
private lateinit var fileOperationsViewModel: FileOperationsViewModel
|
||||
private lateinit var context: Context
|
||||
|
||||
private var currentFile: MutableStateFlow<OCFileWithSyncInfo?> = MutableStateFlow(OC_FILE_WITH_SYNC_INFO_AND_SPACE)
|
||||
private var currentFileWithoutPersonalSpace: MutableStateFlow<OCFileWithSyncInfo?> =
|
||||
MutableStateFlow(OC_FILE_WITH_SYNC_INFO_AND_WITHOUT_PERSONAL_SPACE)
|
||||
private var currentFileSyncInfo: MutableStateFlow<OCFileWithSyncInfo?> = MutableStateFlow(OC_FILE_WITH_SYNC_INFO)
|
||||
private var currentFileAvailableOffline: MutableStateFlow<OCFileWithSyncInfo?> = MutableStateFlow(OC_FILE_WITH_SYNC_INFO_AVAILABLE_OFFLINE)
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
context = ApplicationProvider.getApplicationContext()
|
||||
fileDetailsViewModel = mockk(relaxed = true)
|
||||
fileOperationsViewModel = mockk(relaxed = true)
|
||||
every { fileDetailsViewModel.currentFile } returns currentFile
|
||||
stopKoin()
|
||||
|
||||
startKoin {
|
||||
context
|
||||
allowOverride(override = true)
|
||||
modules(
|
||||
module {
|
||||
viewModel {
|
||||
fileDetailsViewModel
|
||||
}
|
||||
viewModel {
|
||||
fileOperationsViewModel
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
val fileDetailsFragment = FileDetailsFragment.newInstance(
|
||||
OC_FILE,
|
||||
OC_ACCOUNT,
|
||||
syncFileAtOpen = false
|
||||
)
|
||||
|
||||
launch(TestShareFileActivity::class.java).onActivity {
|
||||
it.startFragment(fileDetailsFragment)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun display_visibility_of_detail_view_when_it_is_displayed() {
|
||||
assertViewsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun show_space_personal_when_it_has_value() {
|
||||
R.id.fdSpace.assertVisibility(ViewMatchers.Visibility.VISIBLE)
|
||||
R.id.fdSpaceLabel.assertVisibility(ViewMatchers.Visibility.VISIBLE)
|
||||
R.id.fdIconSpace.assertVisibility(ViewMatchers.Visibility.VISIBLE)
|
||||
|
||||
R.id.fdSpace.withText(R.string.bottom_nav_personal)
|
||||
R.id.fdSpaceLabel.withText(R.string.space_label)
|
||||
onView(withId(R.id.fdIconSpace))
|
||||
.check(matches(withDrawable(R.drawable.ic_spaces)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun hide_space_when_it_has_no_value() {
|
||||
every { fileDetailsViewModel.currentFile } returns currentFileSyncInfo
|
||||
|
||||
R.id.fdSpace.assertVisibility(ViewMatchers.Visibility.GONE)
|
||||
R.id.fdSpaceLabel.assertVisibility(ViewMatchers.Visibility.GONE)
|
||||
R.id.fdIconSpace.assertVisibility(ViewMatchers.Visibility.GONE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun show_space_not_personal_when_it_has_value() {
|
||||
every { fileDetailsViewModel.currentFile } returns currentFileWithoutPersonalSpace
|
||||
|
||||
R.id.fdSpace.assertVisibility(ViewMatchers.Visibility.VISIBLE)
|
||||
R.id.fdSpaceLabel.assertVisibility(ViewMatchers.Visibility.VISIBLE)
|
||||
R.id.fdIconSpace.assertVisibility(ViewMatchers.Visibility.VISIBLE)
|
||||
|
||||
R.id.fdSpace.withText(currentFileWithoutPersonalSpace.value?.space?.name.toString())
|
||||
R.id.fdSpaceLabel.withText(R.string.space_label)
|
||||
onView(withId(R.id.fdIconSpace))
|
||||
.check(matches(withDrawable(R.drawable.ic_spaces)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun show_last_sync_when_it_has_value() {
|
||||
currentFile.value?.file?.lastSyncDateForData = 1212121212212
|
||||
R.id.fdLastSync.assertVisibility(ViewMatchers.Visibility.VISIBLE)
|
||||
R.id.fdLastSyncLabel.assertVisibility(ViewMatchers.Visibility.VISIBLE)
|
||||
|
||||
R.id.fdLastSyncLabel.withText(R.string.filedetails_last_sync)
|
||||
R.id.fdLastSync.withText(DisplayUtils.unixTimeToHumanReadable(currentFile.value?.file?.lastSyncDateForData!!))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun hide_last_sync_when_it_has_no_value() {
|
||||
every { fileDetailsViewModel.currentFile } returns currentFile
|
||||
|
||||
R.id.fdLastSync.assertVisibility(ViewMatchers.Visibility.GONE)
|
||||
R.id.fdLastSyncLabel.assertVisibility(ViewMatchers.Visibility.GONE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun verifyTests() {
|
||||
R.id.fdCreatedLabel.withText(R.string.filedetails_created)
|
||||
R.id.fdCreated.withText(DisplayUtils.unixTimeToHumanReadable(currentFile.value?.file?.creationTimestamp!!))
|
||||
|
||||
R.id.fdModifiedLabel.withText(R.string.filedetails_modified)
|
||||
R.id.fdModified.withText(DisplayUtils.unixTimeToHumanReadable(currentFile.value?.file?.modificationTimestamp!!))
|
||||
|
||||
R.id.fdPathLabel.withText(R.string.ssl_validator_label_L)
|
||||
R.id.fdPath.withText(currentFile.value?.file?.getParentRemotePath()!!)
|
||||
|
||||
R.id.fdname.withText(currentFile.value?.file?.fileName!!)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun badge_available_offline_in_image_is_not_viewed_when_file_does_not_change_state() {
|
||||
every { fileDetailsViewModel.currentFile } returns currentFileAvailableOffline
|
||||
|
||||
R.id.badgeDetailFile.assertVisibility(ViewMatchers.Visibility.VISIBLE)
|
||||
onView(withId(R.id.badgeDetailFile))
|
||||
.check(matches(withDrawable(R.drawable.offline_available_pin)))
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun show_badge_isAvailableLocally_in_image_when_file_change_state() {
|
||||
currentFile.value?.file?.etagInConflict = "error"
|
||||
|
||||
R.id.badgeDetailFile.assertVisibility(ViewMatchers.Visibility.VISIBLE)
|
||||
onView(withId(R.id.badgeDetailFile))
|
||||
.check(matches(withDrawable(R.drawable.error_pin)))
|
||||
}
|
||||
|
||||
private fun assertViewsDisplayed(
|
||||
showImage: Boolean = true,
|
||||
showFdName: Boolean = true,
|
||||
showFdProgressText: Boolean = false,
|
||||
showFdProgressBar: Boolean = false,
|
||||
showFdCancelBtn: Boolean = false,
|
||||
showDivider: Boolean = true,
|
||||
showDivider2: Boolean = true,
|
||||
showFdTypeLabel: Boolean = true,
|
||||
showFdType: Boolean = true,
|
||||
showFdSizeLabel: Boolean = true,
|
||||
showFdSize: Boolean = true,
|
||||
showFdModifiedLabel: Boolean = true,
|
||||
showFdModified: Boolean = true,
|
||||
showFdCreatedLabel: Boolean = true,
|
||||
showFdCreated: Boolean = true,
|
||||
showDivider3: Boolean = true,
|
||||
showFdPathLabel: Boolean = true,
|
||||
showFdPath: Boolean = true
|
||||
) {
|
||||
R.id.fdImageDetailFile.isDisplayed(displayed = showImage)
|
||||
R.id.fdname.isDisplayed(displayed = showFdName)
|
||||
R.id.fdProgressText.isDisplayed(displayed = showFdProgressText)
|
||||
R.id.fdProgressBar.isDisplayed(displayed = showFdProgressBar)
|
||||
R.id.fdCancelBtn.isDisplayed(displayed = showFdCancelBtn)
|
||||
R.id.divider.isDisplayed(displayed = showDivider)
|
||||
R.id.fdTypeLabel.isDisplayed(displayed = showFdTypeLabel)
|
||||
R.id.fdType.isDisplayed(displayed = showFdType)
|
||||
R.id.fdSizeLabel.isDisplayed(displayed = showFdSizeLabel)
|
||||
R.id.fdSize.isDisplayed(displayed = showFdSize)
|
||||
R.id.divider2.isDisplayed(displayed = showDivider2)
|
||||
R.id.fdModifiedLabel.isDisplayed(displayed = showFdModifiedLabel)
|
||||
R.id.fdModified.isDisplayed(displayed = showFdModified)
|
||||
R.id.fdCreatedLabel.isDisplayed(displayed = showFdCreatedLabel)
|
||||
R.id.fdCreated.isDisplayed(displayed = showFdCreated)
|
||||
R.id.divider3.isDisplayed(displayed = showDivider3)
|
||||
R.id.fdPathLabel.isDisplayed(displayed = showFdPathLabel)
|
||||
R.id.fdPath.isDisplayed(displayed = showFdPath)
|
||||
}
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* qsfera Android client application
|
||||
*
|
||||
* @author Fernando Sanz Velasco
|
||||
* Copyright (C) 2021 ownCloud GmbH.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package eu.qsfera.android.logging
|
||||
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import eu.qsfera.android.R
|
||||
import eu.qsfera.android.presentation.logging.LogsListActivity
|
||||
import eu.qsfera.android.presentation.logging.LogListViewModel
|
||||
import eu.qsfera.android.utils.matchers.assertChildCount
|
||||
import eu.qsfera.android.utils.matchers.isDisplayed
|
||||
import eu.qsfera.android.utils.matchers.withText
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.unmockkAll
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.core.context.startKoin
|
||||
import org.koin.core.context.stopKoin
|
||||
import org.koin.dsl.module
|
||||
import java.io.File
|
||||
|
||||
@Ignore
|
||||
class LogsListActivityTest {
|
||||
|
||||
private lateinit var activityScenario: ActivityScenario<LogsListActivity>
|
||||
|
||||
private lateinit var logListViewModel: LogListViewModel
|
||||
|
||||
private fun launchTest(logs: List<File>) {
|
||||
every { logListViewModel.getLogsFiles() } returns logs
|
||||
activityScenario = ActivityScenario.launch(LogsListActivity::class.java)
|
||||
}
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
logListViewModel = mockk(relaxed = true)
|
||||
|
||||
stopKoin()
|
||||
|
||||
startKoin {
|
||||
allowOverride(override = true)
|
||||
modules(
|
||||
module {
|
||||
viewModel {
|
||||
logListViewModel
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
unmockkAll()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_visibility_toolbar() {
|
||||
launchTest(logs = emptyList())
|
||||
R.id.toolbar_activity_logs_list.isDisplayed(true)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_isRecyclerViewEmpty_show_label() {
|
||||
launchTest(logs = emptyList())
|
||||
R.id.logs_list_empty.isDisplayed(true)
|
||||
R.id.list_empty_dataset_title.withText(R.string.prefs_log_no_logs_list_view)
|
||||
R.id.list_empty_dataset_sub_title.withText(R.string.prefs_log_empty_subtitle)
|
||||
R.id.recyclerView_activity_logs_list.isDisplayed(false)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_isRecyclerViewNotEmpty_hide_label() {
|
||||
launchTest(logs = listOf(File("path")))
|
||||
R.id.logs_list_empty.isDisplayed(false)
|
||||
R.id.recyclerView_activity_logs_list.isDisplayed(true)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_childCount() {
|
||||
launchTest(logs = listOf(File("qsfera.2021-01.01.log"), File("qsfera.2021-01-02.log")))
|
||||
R.id.recyclerView_activity_logs_list.assertChildCount(2)
|
||||
}
|
||||
}
|
||||
+300
@@ -0,0 +1,300 @@
|
||||
/**
|
||||
* qsfera Android client application
|
||||
*
|
||||
* @author Juan Carlos Garrote Gascón
|
||||
*
|
||||
* Copyright (C) 2021 ownCloud GmbH.
|
||||
* <p>
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
* <p>
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* <p>
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package eu.qsfera.android.settings
|
||||
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import androidx.fragment.app.testing.FragmentScenario
|
||||
import androidx.fragment.app.testing.launchFragmentInContainer
|
||||
import androidx.preference.Preference
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.intent.Intents
|
||||
import androidx.test.espresso.intent.matcher.IntentMatchers
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isEnabled
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import eu.qsfera.android.BuildConfig
|
||||
import eu.qsfera.android.R
|
||||
import eu.qsfera.android.presentation.releasenotes.ReleaseNotesActivity
|
||||
import eu.qsfera.android.presentation.settings.privacypolicy.PrivacyPolicyActivity
|
||||
import eu.qsfera.android.presentation.settings.SettingsFragment
|
||||
import eu.qsfera.android.presentation.releasenotes.ReleaseNotesViewModel
|
||||
import eu.qsfera.android.presentation.settings.more.SettingsMoreViewModel
|
||||
import eu.qsfera.android.presentation.settings.SettingsViewModel
|
||||
import eu.qsfera.android.utils.matchers.verifyPreference
|
||||
import eu.qsfera.android.utils.releaseNotesList
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.unmockkAll
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.core.context.startKoin
|
||||
import org.koin.core.context.stopKoin
|
||||
import org.koin.dsl.module
|
||||
|
||||
class SettingsFragmentTest {
|
||||
|
||||
private lateinit var fragmentScenario: FragmentScenario<SettingsFragment>
|
||||
|
||||
private var subsectionSecurity: Preference? = null
|
||||
private var subsectionLogging: Preference? = null
|
||||
private var subsectionPictureUploads: Preference? = null
|
||||
private var subsectionVideoUploads: Preference? = null
|
||||
private var subsectionMore: Preference? = null
|
||||
private var prefPrivacyPolicy: Preference? = null
|
||||
private var subsectionWhatsNew: Preference? = null
|
||||
private var prefAboutApp: Preference? = null
|
||||
|
||||
private lateinit var settingsViewModel: SettingsViewModel
|
||||
private lateinit var moreViewModel: SettingsMoreViewModel
|
||||
private lateinit var releaseNotesViewModel: ReleaseNotesViewModel
|
||||
private lateinit var context: Context
|
||||
|
||||
private lateinit var version: String
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
settingsViewModel = mockk(relaxed = true)
|
||||
moreViewModel = mockk(relaxed = true)
|
||||
releaseNotesViewModel = mockk(relaxed = true)
|
||||
|
||||
stopKoin()
|
||||
|
||||
startKoin {
|
||||
context
|
||||
allowOverride(override = true)
|
||||
modules(
|
||||
module {
|
||||
viewModel {
|
||||
settingsViewModel
|
||||
}
|
||||
viewModel {
|
||||
moreViewModel
|
||||
}
|
||||
viewModel {
|
||||
releaseNotesViewModel
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
version = String.format(
|
||||
context.getString(R.string.prefs_app_version_summary),
|
||||
context.getString(R.string.app_name),
|
||||
BuildConfig.BUILD_TYPE,
|
||||
BuildConfig.VERSION_NAME,
|
||||
BuildConfig.COMMIT_SHA1
|
||||
)
|
||||
|
||||
Intents.init()
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
Intents.release()
|
||||
unmockkAll()
|
||||
}
|
||||
|
||||
private fun launchTest(
|
||||
attachedAccount: Boolean,
|
||||
moreSectionVisible: Boolean = true,
|
||||
privacyPolicyEnabled: Boolean = true,
|
||||
whatsNewSectionVisible: Boolean = true
|
||||
) {
|
||||
every { settingsViewModel.isThereAttachedAccount() } returns attachedAccount
|
||||
every { moreViewModel.shouldMoreSectionBeVisible() } returns moreSectionVisible
|
||||
every { moreViewModel.isPrivacyPolicyEnabled() } returns privacyPolicyEnabled
|
||||
every { releaseNotesViewModel.shouldWhatsNewSectionBeVisible() } returns whatsNewSectionVisible
|
||||
|
||||
fragmentScenario = launchFragmentInContainer(themeResId = R.style.Theme_qsfera)
|
||||
fragmentScenario.onFragment { fragment ->
|
||||
subsectionSecurity = fragment.findPreference(SUBSECTION_SECURITY)
|
||||
subsectionLogging = fragment.findPreference(SUBSECTION_LOGGING)
|
||||
subsectionPictureUploads = fragment.findPreference(SUBSECTION_PICTURE_UPLOADS)
|
||||
subsectionVideoUploads = fragment.findPreference(SUBSECTION_VIDEO_UPLOADS)
|
||||
subsectionMore = fragment.findPreference(SUBSECTION_MORE)
|
||||
prefPrivacyPolicy = fragment.findPreference(PREFERENCE_PRIVACY_POLICY)
|
||||
subsectionWhatsNew = fragment.findPreference(SUBSECTION_WHATSNEW)
|
||||
prefAboutApp = fragment.findPreference(PREFERENCE_ABOUT_APP)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun settingsViewCommon() {
|
||||
launchTest(attachedAccount = false)
|
||||
|
||||
subsectionSecurity?.verifyPreference(
|
||||
keyPref = SUBSECTION_SECURITY,
|
||||
titlePref = context.getString(R.string.prefs_subsection_security),
|
||||
summaryPref = context.getString(R.string.prefs_subsection_security_summary),
|
||||
visible = true,
|
||||
enabled = true
|
||||
)
|
||||
|
||||
subsectionLogging?.verifyPreference(
|
||||
keyPref = SUBSECTION_LOGGING,
|
||||
titlePref = context.getString(R.string.prefs_subsection_logging),
|
||||
summaryPref = context.getString(R.string.prefs_subsection_logging_summary),
|
||||
visible = true,
|
||||
enabled = true
|
||||
)
|
||||
|
||||
subsectionMore?.verifyPreference(
|
||||
keyPref = SUBSECTION_MORE,
|
||||
titlePref = context.getString(R.string.prefs_subsection_more),
|
||||
summaryPref = context.getString(R.string.prefs_subsection_more_summary),
|
||||
visible = true,
|
||||
enabled = true
|
||||
)
|
||||
|
||||
prefPrivacyPolicy?.verifyPreference(
|
||||
keyPref = PREFERENCE_PRIVACY_POLICY,
|
||||
titlePref = context.getString(R.string.prefs_privacy_policy),
|
||||
visible = true,
|
||||
enabled = true
|
||||
)
|
||||
|
||||
subsectionWhatsNew?.verifyPreference(
|
||||
keyPref = SUBSECTION_WHATSNEW,
|
||||
titlePref = context.getString(R.string.prefs_subsection_whatsnew),
|
||||
visible = true,
|
||||
enabled = true
|
||||
)
|
||||
|
||||
prefAboutApp?.verifyPreference(
|
||||
keyPref = PREFERENCE_ABOUT_APP,
|
||||
titlePref = context.getString(R.string.prefs_app_version),
|
||||
summaryPref = version,
|
||||
visible = true,
|
||||
enabled = true
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun settingsViewNoAccountAttached() {
|
||||
launchTest(attachedAccount = false)
|
||||
|
||||
subsectionPictureUploads?.verifyPreference(
|
||||
keyPref = SUBSECTION_PICTURE_UPLOADS,
|
||||
titlePref = context.getString(R.string.prefs_subsection_picture_uploads),
|
||||
summaryPref = context.getString(R.string.prefs_subsection_picture_uploads_summary),
|
||||
visible = false
|
||||
)
|
||||
|
||||
subsectionVideoUploads?.verifyPreference(
|
||||
keyPref = SUBSECTION_VIDEO_UPLOADS,
|
||||
titlePref = context.getString(R.string.prefs_subsection_video_uploads),
|
||||
summaryPref = context.getString(R.string.prefs_subsection_video_uploads_summary),
|
||||
visible = false
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun settingsViewAccountAttached() {
|
||||
launchTest(attachedAccount = true)
|
||||
|
||||
subsectionPictureUploads?.verifyPreference(
|
||||
keyPref = SUBSECTION_PICTURE_UPLOADS,
|
||||
titlePref = context.getString(R.string.prefs_subsection_picture_uploads),
|
||||
summaryPref = context.getString(R.string.prefs_subsection_picture_uploads_summary),
|
||||
visible = true,
|
||||
enabled = true
|
||||
)
|
||||
|
||||
subsectionVideoUploads?.verifyPreference(
|
||||
keyPref = SUBSECTION_VIDEO_UPLOADS,
|
||||
titlePref = context.getString(R.string.prefs_subsection_video_uploads),
|
||||
summaryPref = context.getString(R.string.prefs_subsection_video_uploads_summary),
|
||||
visible = true,
|
||||
enabled = true
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun settingsMoreSectionHidden() {
|
||||
launchTest(attachedAccount = false, moreSectionVisible = false)
|
||||
|
||||
subsectionMore?.verifyPreference(
|
||||
keyPref = SUBSECTION_MORE,
|
||||
titlePref = context.getString(R.string.prefs_subsection_more),
|
||||
summaryPref = context.getString(R.string.prefs_subsection_more_summary),
|
||||
visible = false
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun settingsWhatsNewSectionHidden() {
|
||||
launchTest(attachedAccount = false, whatsNewSectionVisible = false)
|
||||
|
||||
subsectionWhatsNew?.verifyPreference(
|
||||
keyPref = SUBSECTION_WHATSNEW,
|
||||
titlePref = context.getString(R.string.prefs_subsection_whatsnew),
|
||||
visible = false
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun privacyPolicyOpensPrivacyPolicyActivity() {
|
||||
launchTest(attachedAccount = false)
|
||||
|
||||
onView(withText(R.string.prefs_privacy_policy)).perform(click())
|
||||
Intents.intended(IntentMatchers.hasComponent(PrivacyPolicyActivity::class.java.name))
|
||||
}
|
||||
|
||||
@Ignore("Flaky test")
|
||||
@Test
|
||||
fun whatsnewOpensReleaseNotesActivity() {
|
||||
launchTest(attachedAccount = false)
|
||||
every { releaseNotesViewModel.getReleaseNotes() } returns releaseNotesList
|
||||
|
||||
onView(withText(R.string.prefs_subsection_whatsnew)).perform(click())
|
||||
Intents.intended(IntentMatchers.hasComponent(ReleaseNotesActivity::class.java.name))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun clickOnAppVersion() {
|
||||
launchTest(attachedAccount = false)
|
||||
|
||||
onView(withText(R.string.prefs_app_version)).perform(click())
|
||||
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager?
|
||||
|
||||
onView(withText(R.string.clipboard_text_copied)).check(matches(isEnabled()))
|
||||
assertEquals(version, clipboard?.primaryClip?.getItemAt(0)?.coerceToText(context))
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val SUBSECTION_SECURITY = "security_subsection"
|
||||
private const val SUBSECTION_LOGGING = "logging_subsection"
|
||||
private const val SUBSECTION_PICTURE_UPLOADS = "picture_uploads_subsection"
|
||||
private const val SUBSECTION_VIDEO_UPLOADS = "video_uploads_subsection"
|
||||
private const val SUBSECTION_MORE = "more_subsection"
|
||||
private const val PREFERENCE_PRIVACY_POLICY = "privacyPolicy"
|
||||
private const val PREFERENCE_ABOUT_APP = "about_app"
|
||||
private const val SUBSECTION_WHATSNEW = "whatsNew"
|
||||
}
|
||||
}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* qsfera Android client application
|
||||
*
|
||||
* @author David Crespo Ríos
|
||||
* Copyright (C) 2022 ownCloud GmbH.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package eu.qsfera.android.settings.advanced
|
||||
|
||||
import android.content.Context
|
||||
import androidx.fragment.app.testing.FragmentScenario
|
||||
import androidx.fragment.app.testing.launchFragmentInContainer
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import eu.qsfera.android.R
|
||||
import eu.qsfera.android.presentation.settings.advanced.SettingsAdvancedFragment
|
||||
import eu.qsfera.android.presentation.settings.advanced.SettingsAdvancedFragment.Companion.PREF_SHOW_HIDDEN_FILES
|
||||
import eu.qsfera.android.presentation.settings.advanced.SettingsAdvancedViewModel
|
||||
import eu.qsfera.android.utils.matchers.verifyPreference
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.unmockkAll
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Before
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.core.context.startKoin
|
||||
import org.koin.core.context.stopKoin
|
||||
import org.koin.dsl.module
|
||||
|
||||
class SettingsAdvancedFragmentTest {
|
||||
|
||||
private lateinit var fragmentScenario: FragmentScenario<SettingsAdvancedFragment>
|
||||
|
||||
private var prefShowHiddenFiles: SwitchPreferenceCompat? = null
|
||||
|
||||
private lateinit var advancedViewModel: SettingsAdvancedViewModel
|
||||
private lateinit var context: Context
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
advancedViewModel = mockk(relaxed = true)
|
||||
|
||||
stopKoin()
|
||||
|
||||
startKoin {
|
||||
context
|
||||
allowOverride(override = true)
|
||||
modules(
|
||||
module {
|
||||
viewModel {
|
||||
advancedViewModel
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
every { advancedViewModel.isHiddenFilesShown() } returns true
|
||||
|
||||
fragmentScenario = launchFragmentInContainer(themeResId = R.style.Theme_qsfera)
|
||||
|
||||
fragmentScenario.onFragment { fragment ->
|
||||
prefShowHiddenFiles = fragment.findPreference(PREF_SHOW_HIDDEN_FILES)
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
unmockkAll()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun advancedView() {
|
||||
assertNotNull(prefShowHiddenFiles)
|
||||
prefShowHiddenFiles?.verifyPreference(
|
||||
keyPref = PREF_SHOW_HIDDEN_FILES,
|
||||
titlePref = context.getString(R.string.prefs_show_hidden_files),
|
||||
visible = true,
|
||||
enabled = true
|
||||
)
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
fun disableShowHiddenFiles() {
|
||||
prefShowHiddenFiles?.isChecked = advancedViewModel.isHiddenFilesShown()
|
||||
|
||||
onView(withText(context.getString(R.string.prefs_show_hidden_files))).perform(click())
|
||||
|
||||
prefShowHiddenFiles?.isChecked?.let { assertFalse(it) }
|
||||
}
|
||||
}
|
||||
+209
@@ -0,0 +1,209 @@
|
||||
/**
|
||||
* qsfera Android client application
|
||||
*
|
||||
* @author Juan Carlos Garrote Gascón
|
||||
*
|
||||
* Copyright (C) 2021 ownCloud GmbH.
|
||||
* <p>
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
* <p>
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* <p>
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package eu.qsfera.android.settings.logs
|
||||
|
||||
import android.content.Context
|
||||
import androidx.fragment.app.testing.FragmentScenario
|
||||
import androidx.fragment.app.testing.launchFragmentInContainer
|
||||
import androidx.preference.CheckBoxPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
import androidx.test.espresso.intent.Intents
|
||||
import androidx.test.espresso.intent.Intents.intended
|
||||
import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import eu.qsfera.android.R
|
||||
import eu.qsfera.android.presentation.logging.LogsListActivity
|
||||
import eu.qsfera.android.presentation.settings.logging.SettingsLogsFragment
|
||||
import eu.qsfera.android.presentation.logging.LogListViewModel
|
||||
import eu.qsfera.android.presentation.settings.logging.SettingsLogsViewModel
|
||||
import eu.qsfera.android.utils.matchers.verifyPreference
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.unmockkAll
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.core.context.startKoin
|
||||
import org.koin.core.context.stopKoin
|
||||
import org.koin.dsl.module
|
||||
|
||||
class SettingsLogsFragmentTest {
|
||||
|
||||
private lateinit var fragmentScenario: FragmentScenario<SettingsLogsFragment>
|
||||
|
||||
private lateinit var prefEnableLogging: SwitchPreferenceCompat
|
||||
private lateinit var prefHttpLogs: CheckBoxPreference
|
||||
private lateinit var prefLogsListActivity: Preference
|
||||
|
||||
private lateinit var logsViewModel: SettingsLogsViewModel
|
||||
private lateinit var logListViewModel: LogListViewModel
|
||||
private lateinit var context: Context
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
logsViewModel = mockk(relaxed = true)
|
||||
logListViewModel = mockk(relaxed = true)
|
||||
|
||||
stopKoin()
|
||||
|
||||
startKoin {
|
||||
context
|
||||
allowOverride(override = true)
|
||||
modules(
|
||||
module {
|
||||
viewModel {
|
||||
logsViewModel
|
||||
}
|
||||
viewModel {
|
||||
logListViewModel
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Intents.init()
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
Intents.release()
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit().clear().commit()
|
||||
unmockkAll()
|
||||
}
|
||||
|
||||
private fun launchTest(enabledLogging: Boolean) {
|
||||
every { logsViewModel.isLoggingEnabled() } returns enabledLogging
|
||||
|
||||
fragmentScenario = launchFragmentInContainer(themeResId = R.style.Theme_qsfera)
|
||||
fragmentScenario.onFragment { fragment ->
|
||||
prefEnableLogging = fragment.findPreference(SettingsLogsFragment.PREFERENCE_ENABLE_LOGGING)!!
|
||||
prefHttpLogs = fragment.findPreference(SettingsLogsFragment.PREFERENCE_LOG_HTTP)!!
|
||||
prefLogsListActivity = fragment.findPreference(SettingsLogsFragment.PREFERENCE_LOGS_LIST)!!
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun logsViewLoggingDisabled() {
|
||||
launchTest(enabledLogging = false)
|
||||
|
||||
prefEnableLogging.verifyPreference(
|
||||
keyPref = SettingsLogsFragment.PREFERENCE_ENABLE_LOGGING,
|
||||
titlePref = context.getString(R.string.prefs_enable_logging),
|
||||
summaryPref = context.getString(R.string.prefs_enable_logging_summary),
|
||||
visible = true,
|
||||
enabled = true
|
||||
)
|
||||
prefHttpLogs.verifyPreference(
|
||||
keyPref = SettingsLogsFragment.PREFERENCE_LOG_HTTP,
|
||||
titlePref = context.getString(R.string.prefs_http_logs),
|
||||
visible = true,
|
||||
enabled = false
|
||||
)
|
||||
|
||||
prefLogsListActivity.verifyPreference(
|
||||
keyPref = SettingsLogsFragment.PREFERENCE_LOGS_LIST,
|
||||
titlePref = context.getString(R.string.prefs_log_open_logs_list_view),
|
||||
visible = true,
|
||||
enabled = true,
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun logsViewLoggingEnabled() {
|
||||
launchTest(enabledLogging = true)
|
||||
|
||||
prefEnableLogging.verifyPreference(
|
||||
keyPref = SettingsLogsFragment.PREFERENCE_ENABLE_LOGGING,
|
||||
titlePref = context.getString(R.string.prefs_enable_logging),
|
||||
summaryPref = context.getString(R.string.prefs_enable_logging_summary),
|
||||
visible = true,
|
||||
enabled = true
|
||||
)
|
||||
prefHttpLogs.verifyPreference(
|
||||
keyPref = SettingsLogsFragment.PREFERENCE_LOG_HTTP,
|
||||
titlePref = context.getString(R.string.prefs_http_logs),
|
||||
visible = true,
|
||||
enabled = true
|
||||
)
|
||||
|
||||
prefLogsListActivity.verifyPreference(
|
||||
keyPref = SettingsLogsFragment.PREFERENCE_LOGS_LIST,
|
||||
titlePref = context.getString(R.string.prefs_log_open_logs_list_view),
|
||||
visible = true,
|
||||
enabled = true,
|
||||
)
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
fun enableLoggingMakesSettingsEnable() {
|
||||
launchTest(enabledLogging = false)
|
||||
|
||||
onView(withText(R.string.prefs_enable_logging)).perform(click())
|
||||
assertTrue(prefHttpLogs.isEnabled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun disableLoggingMakesSettingsDisable() {
|
||||
launchTest(enabledLogging = false)
|
||||
|
||||
onView(withText(R.string.prefs_enable_logging)).perform(click())
|
||||
onView(withText(R.string.prefs_enable_logging)).perform(click())
|
||||
assertFalse(prefHttpLogs.isEnabled)
|
||||
assertTrue(prefLogsListActivity.isEnabled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkHttpLogs() {
|
||||
launchTest(enabledLogging = true)
|
||||
onView(withText(R.string.prefs_http_logs)).perform(click())
|
||||
assertTrue(prefHttpLogs.isChecked)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun disableLoggingMakesHttpLogsNotChecked() {
|
||||
launchTest(enabledLogging = false)
|
||||
|
||||
onView(withText(R.string.prefs_enable_logging)).perform(click())
|
||||
onView(withText(R.string.prefs_http_logs)).perform(click())
|
||||
onView(withText(R.string.prefs_enable_logging)).perform(click())
|
||||
assertFalse(prefHttpLogs.isChecked)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun loggerOpen() {
|
||||
launchTest(enabledLogging = true)
|
||||
|
||||
onView(withText(R.string.prefs_log_open_logs_list_view)).perform(click())
|
||||
intended(hasComponent(LogsListActivity::class.java.name))
|
||||
}
|
||||
}
|
||||
+365
@@ -0,0 +1,365 @@
|
||||
/**
|
||||
* qsfera Android client application
|
||||
*
|
||||
* @author Juan Carlos Garrote Gascón
|
||||
*
|
||||
* Copyright (C) 2023 ownCloud GmbH.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package eu.qsfera.android.settings.more
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.Intent.EXTRA_SUBJECT
|
||||
import android.content.Intent.EXTRA_TEXT
|
||||
import android.net.Uri
|
||||
import androidx.fragment.app.testing.FragmentScenario
|
||||
import androidx.fragment.app.testing.launchFragmentInContainer
|
||||
import androidx.preference.Preference
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
import androidx.test.espresso.assertion.ViewAssertions
|
||||
import androidx.test.espresso.intent.Intents
|
||||
import androidx.test.espresso.intent.Intents.intended
|
||||
import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction
|
||||
import androidx.test.espresso.intent.matcher.IntentMatchers.hasData
|
||||
import androidx.test.espresso.intent.matcher.IntentMatchers.hasExtra
|
||||
import androidx.test.espresso.intent.matcher.IntentMatchers.hasFlag
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import eu.qsfera.android.BuildConfig
|
||||
import eu.qsfera.android.R
|
||||
import eu.qsfera.android.presentation.settings.more.SettingsMoreFragment
|
||||
import eu.qsfera.android.presentation.settings.more.SettingsMoreViewModel
|
||||
import eu.qsfera.android.utils.matchers.verifyPreference
|
||||
import eu.qsfera.android.utils.mockIntent
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.unmockkAll
|
||||
import org.hamcrest.Matchers.allOf
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Before
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.core.context.startKoin
|
||||
import org.koin.core.context.stopKoin
|
||||
import org.koin.dsl.module
|
||||
|
||||
class SettingsMoreFragmentTest {
|
||||
|
||||
private lateinit var fragmentScenario: FragmentScenario<SettingsMoreFragment>
|
||||
|
||||
private var prefHelp: Preference? = null
|
||||
private var prefSync: Preference? = null
|
||||
private var prefAccessDocProvider: Preference? = null
|
||||
private var prefRecommend: Preference? = null
|
||||
private var prefFeedback: Preference? = null
|
||||
private var prefImprint: Preference? = null
|
||||
|
||||
private lateinit var moreViewModel: SettingsMoreViewModel
|
||||
private lateinit var context: Context
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
moreViewModel = mockk(relaxed = true)
|
||||
|
||||
stopKoin()
|
||||
|
||||
startKoin {
|
||||
context
|
||||
allowOverride(override = true)
|
||||
modules(
|
||||
module {
|
||||
viewModel {
|
||||
moreViewModel
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Intents.init()
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
Intents.release()
|
||||
unmockkAll()
|
||||
}
|
||||
|
||||
private fun getPreference(key: String): Preference? {
|
||||
var preference: Preference? = null
|
||||
fragmentScenario.onFragment { fragment ->
|
||||
preference = fragment.findPreference(key)
|
||||
}
|
||||
return preference
|
||||
}
|
||||
|
||||
private fun launchTest(
|
||||
helpEnabled: Boolean = true,
|
||||
syncEnabled: Boolean = true,
|
||||
docProviderAppEnabled: Boolean = true,
|
||||
recommendEnabled: Boolean = true,
|
||||
feedbackEnabled: Boolean = true,
|
||||
imprintEnabled: Boolean = true
|
||||
) {
|
||||
every { moreViewModel.isHelpEnabled() } returns helpEnabled
|
||||
every { moreViewModel.isSyncEnabled() } returns syncEnabled
|
||||
every { moreViewModel.isDocProviderAppEnabled() } returns docProviderAppEnabled
|
||||
every { moreViewModel.isRecommendEnabled() } returns recommendEnabled
|
||||
every { moreViewModel.isFeedbackEnabled() } returns feedbackEnabled
|
||||
every { moreViewModel.isImprintEnabled() } returns imprintEnabled
|
||||
|
||||
fragmentScenario = launchFragmentInContainer(themeResId = R.style.Theme_qsfera)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun moreView() {
|
||||
launchTest()
|
||||
|
||||
prefHelp = getPreference(PREFERENCE_HELP)
|
||||
assertNotNull(prefHelp)
|
||||
prefHelp?.verifyPreference(
|
||||
keyPref = PREFERENCE_HELP,
|
||||
titlePref = context.getString(R.string.prefs_help),
|
||||
visible = true,
|
||||
enabled = true
|
||||
)
|
||||
|
||||
prefSync = getPreference(PREFERENCE_SYNC_CALENDAR_CONTACTS)
|
||||
assertNotNull(prefSync)
|
||||
prefSync?.verifyPreference(
|
||||
keyPref = PREFERENCE_SYNC_CALENDAR_CONTACTS,
|
||||
titlePref = context.getString(R.string.prefs_sync_calendar_contacts),
|
||||
summaryPref = context.getString(R.string.prefs_sync_calendar_contacts_summary),
|
||||
visible = true,
|
||||
enabled = true
|
||||
)
|
||||
|
||||
prefAccessDocProvider = getPreference(PREFERENCE_ACCESS_DOCUMENT_PROVIDER)
|
||||
assertNotNull(prefAccessDocProvider)
|
||||
prefAccessDocProvider?.verifyPreference(
|
||||
keyPref = PREFERENCE_ACCESS_DOCUMENT_PROVIDER,
|
||||
titlePref = context.getString(R.string.prefs_access_document_provider),
|
||||
summaryPref = context.getString(R.string.prefs_access_document_provider_summary),
|
||||
visible = true,
|
||||
enabled = true
|
||||
)
|
||||
|
||||
prefRecommend = getPreference(PREFERENCE_RECOMMEND)
|
||||
assertNotNull(prefRecommend)
|
||||
prefRecommend?.verifyPreference(
|
||||
keyPref = PREFERENCE_RECOMMEND,
|
||||
titlePref = context.getString(R.string.prefs_recommend),
|
||||
visible = true,
|
||||
enabled = true
|
||||
)
|
||||
|
||||
prefFeedback = getPreference(PREFERENCE_FEEDBACK)
|
||||
assertNotNull(prefFeedback)
|
||||
prefFeedback?.verifyPreference(
|
||||
keyPref = PREFERENCE_FEEDBACK,
|
||||
titlePref = context.getString(R.string.prefs_send_feedback),
|
||||
visible = true,
|
||||
enabled = true
|
||||
)
|
||||
|
||||
prefImprint = getPreference(PREFERENCE_IMPRINT)
|
||||
assertNotNull(prefImprint)
|
||||
prefImprint?.verifyPreference(
|
||||
keyPref = PREFERENCE_IMPRINT,
|
||||
titlePref = context.getString(R.string.prefs_imprint),
|
||||
visible = true,
|
||||
enabled = true
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun helpNotEnabledView() {
|
||||
launchTest(helpEnabled = false)
|
||||
prefHelp = getPreference(PREFERENCE_HELP)
|
||||
|
||||
assertNull(prefHelp)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun syncNotEnabledView() {
|
||||
launchTest(syncEnabled = false)
|
||||
prefSync = getPreference(PREFERENCE_SYNC_CALENDAR_CONTACTS)
|
||||
|
||||
assertNull(prefSync)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun accessDocumentProviderNotEnabledView() {
|
||||
launchTest(docProviderAppEnabled = false)
|
||||
prefAccessDocProvider = getPreference(PREFERENCE_ACCESS_DOCUMENT_PROVIDER)
|
||||
|
||||
assertNull(prefAccessDocProvider)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun recommendNotEnabledView() {
|
||||
launchTest(recommendEnabled = false)
|
||||
prefRecommend = getPreference(PREFERENCE_RECOMMEND)
|
||||
|
||||
assertNull(prefRecommend)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun feedbackNotEnabledView() {
|
||||
launchTest(feedbackEnabled = false)
|
||||
prefFeedback = getPreference(PREFERENCE_FEEDBACK)
|
||||
|
||||
assertNull(prefFeedback)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun imprintNotEnabledView() {
|
||||
launchTest(imprintEnabled = false)
|
||||
prefImprint = getPreference(PREFERENCE_IMPRINT)
|
||||
|
||||
assertNull(prefImprint)
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
fun helpOpensNotEmptyUrl() {
|
||||
every { moreViewModel.getHelpUrl() } returns context.getString(R.string.url_help)
|
||||
|
||||
launchTest()
|
||||
|
||||
mockIntent(action = Intent.ACTION_VIEW)
|
||||
onView(withText(R.string.prefs_help)).perform(click())
|
||||
|
||||
intended(hasData(context.getString(R.string.url_help)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun syncOpensNotEmptyUrl() {
|
||||
every { moreViewModel.getSyncUrl() } returns context.getString(R.string.url_sync_calendar_contacts)
|
||||
|
||||
launchTest()
|
||||
|
||||
mockIntent(action = Intent.ACTION_VIEW)
|
||||
onView(withText(R.string.prefs_sync_calendar_contacts)).perform(click())
|
||||
|
||||
intended(hasData(context.getString(R.string.url_sync_calendar_contacts)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun accessDocumentProviderOpensNotEmptyUrl() {
|
||||
every { moreViewModel.getDocProviderAppUrl() } returns context.getString(R.string.url_document_provider_app)
|
||||
|
||||
launchTest()
|
||||
|
||||
mockIntent(action = Intent.ACTION_VIEW)
|
||||
onView(withText(R.string.prefs_access_document_provider)).perform(click())
|
||||
|
||||
intended(hasData(context.getString(R.string.url_document_provider_app)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun recommendOpensSender() {
|
||||
launchTest()
|
||||
|
||||
mockIntent(action = Intent.ACTION_SENDTO)
|
||||
|
||||
onView(withText(R.string.prefs_recommend)).perform(click())
|
||||
// Delay needed since depending on the performance of the device where tests are executed,
|
||||
// sender can interfere with the subsequent tests
|
||||
Thread.sleep(1000)
|
||||
intended(
|
||||
allOf(
|
||||
hasAction(Intent.ACTION_SENDTO), hasExtra(
|
||||
EXTRA_SUBJECT, String.format(
|
||||
context.getString(R.string.recommend_subject),
|
||||
context.getString(R.string.app_name)
|
||||
)
|
||||
),
|
||||
hasExtra(
|
||||
EXTRA_TEXT,
|
||||
String.format(
|
||||
context.getString(R.string.recommend_text),
|
||||
context.getString(R.string.app_name),
|
||||
context.getString(R.string.url_app_download)
|
||||
)
|
||||
),
|
||||
hasFlag(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun feedbackOpensSenderIfFeedbackMailExists() {
|
||||
launchTest()
|
||||
every { moreViewModel.getFeedbackMail() } returns FEEDBACK_MAIL
|
||||
mockIntent(action = Intent.ACTION_SENDTO)
|
||||
|
||||
onView(withText(R.string.prefs_send_feedback)).perform(click())
|
||||
// Delay needed since depending on the performance of the device where tests are executed,
|
||||
// sender can interfere with the subsequent tests
|
||||
Thread.sleep(1000)
|
||||
intended(
|
||||
allOf(
|
||||
hasAction(Intent.ACTION_SENDTO),
|
||||
hasExtra(
|
||||
EXTRA_SUBJECT,
|
||||
"Android v" + BuildConfig.VERSION_NAME + " - " + context.getText(R.string.prefs_feedback)
|
||||
),
|
||||
hasData(Uri.parse(FEEDBACK_MAIL)),
|
||||
hasFlag(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun feedbackOpensAlertDialogIfFeedbackMailIsEmpty() {
|
||||
launchTest()
|
||||
every { moreViewModel.getFeedbackMail() } returns ""
|
||||
|
||||
onView(withText(R.string.prefs_send_feedback)).perform(click())
|
||||
|
||||
onView(withText(R.string.drawer_feedback)).check(ViewAssertions.matches(isDisplayed()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun imprintOpensUrl() {
|
||||
every { moreViewModel.getImprintUrl() } returns "https://qsfera.eu/mobile"
|
||||
|
||||
launchTest()
|
||||
|
||||
mockIntent(action = Intent.ACTION_VIEW)
|
||||
|
||||
onView(withText(R.string.prefs_imprint)).perform(click())
|
||||
intended(hasData("https://qsfera.eu/mobile"))
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PREFERENCE_HELP = "help"
|
||||
private const val PREFERENCE_SYNC_CALENDAR_CONTACTS = "syncCalendarContacts"
|
||||
private const val PREFERENCE_ACCESS_DOCUMENT_PROVIDER = "accessDocumentProvider"
|
||||
private const val PREFERENCE_RECOMMEND = "recommend"
|
||||
private const val PREFERENCE_FEEDBACK = "feedback"
|
||||
private const val PREFERENCE_IMPRINT = "imprint"
|
||||
private const val FEEDBACK_MAIL = "mailto:mail@qsfera.eu"
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+363
@@ -0,0 +1,363 @@
|
||||
/*
|
||||
* qsfera Android client application
|
||||
*
|
||||
* @author Jesus Recio (@jesmrec)
|
||||
* @author Christian Schabesberger (@theScrabi)
|
||||
* @author Juan Carlos Garrote Gascón (@JuancaG05)
|
||||
* @author David Crespo Ríos (@davcres)
|
||||
*
|
||||
* Copyright (C) 2021 ownCloud GmbH.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package eu.qsfera.android.settings.security
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
import androidx.test.espresso.assertion.ViewAssertions.doesNotExist
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||
import eu.qsfera.android.R
|
||||
import eu.qsfera.android.db.PreferenceManager
|
||||
import eu.qsfera.android.domain.utils.Event
|
||||
import eu.qsfera.android.presentation.security.passcode.PassCodeActivity
|
||||
import eu.qsfera.android.presentation.security.passcode.PasscodeAction
|
||||
import eu.qsfera.android.presentation.security.passcode.PasscodeType
|
||||
import eu.qsfera.android.presentation.security.passcode.Status
|
||||
import eu.qsfera.android.presentation.security.biometric.BiometricViewModel
|
||||
import eu.qsfera.android.presentation.security.passcode.PassCodeViewModel
|
||||
import eu.qsfera.android.testutil.security.OC_PASSCODE_4_DIGITS
|
||||
import eu.qsfera.android.utils.matchers.isDisplayed
|
||||
import eu.qsfera.android.utils.matchers.withChildCountAndId
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.core.context.startKoin
|
||||
import org.koin.core.context.stopKoin
|
||||
import org.koin.dsl.module
|
||||
|
||||
class PassCodeActivityTest {
|
||||
|
||||
private lateinit var activityScenario: ActivityScenario<PassCodeActivity>
|
||||
|
||||
private lateinit var context: Context
|
||||
|
||||
private lateinit var timeToUnlockLiveData: MutableLiveData<Event<String>>
|
||||
private lateinit var finishTimeToUnlockLiveData: MutableLiveData<Event<Boolean>>
|
||||
private lateinit var statusLiveData: MutableLiveData<Status>
|
||||
private lateinit var passcodeLiveData: MutableLiveData<String>
|
||||
|
||||
private lateinit var passCodeViewModel: PassCodeViewModel
|
||||
private lateinit var biometricViewModel: BiometricViewModel
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
context = ApplicationProvider.getApplicationContext()
|
||||
passCodeViewModel = mockk(relaxed = true)
|
||||
biometricViewModel = mockk(relaxed = true)
|
||||
|
||||
timeToUnlockLiveData = MutableLiveData()
|
||||
finishTimeToUnlockLiveData = MutableLiveData()
|
||||
statusLiveData = MutableLiveData()
|
||||
passcodeLiveData = MutableLiveData()
|
||||
|
||||
stopKoin()
|
||||
|
||||
startKoin {
|
||||
allowOverride(override = true)
|
||||
context
|
||||
modules(
|
||||
module {
|
||||
viewModel {
|
||||
passCodeViewModel
|
||||
}
|
||||
viewModel {
|
||||
biometricViewModel
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
every { passCodeViewModel.getPassCode() } returns OC_PASSCODE_4_DIGITS
|
||||
every { passCodeViewModel.getNumberOfPassCodeDigits() } returns 4
|
||||
every { passCodeViewModel.getNumberOfAttempts() } returns 0
|
||||
every { passCodeViewModel.getTimeToUnlockLiveData } returns timeToUnlockLiveData
|
||||
every { passCodeViewModel.getFinishedTimeToUnlockLiveData } returns finishTimeToUnlockLiveData
|
||||
every { passCodeViewModel.status } returns statusLiveData
|
||||
every { passCodeViewModel.passcode } returns passcodeLiveData
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
// Clean preferences
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit().clear().commit()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun passcodeCheckNotLockedView() {
|
||||
// Open Activity in passcode check mode
|
||||
openPasscodeActivity(PassCodeActivity.ACTION_CHECK)
|
||||
|
||||
with(R.id.header) {
|
||||
isDisplayed(true)
|
||||
withText(R.string.pass_code_enter_pass_code)
|
||||
}
|
||||
|
||||
R.id.explanation.isDisplayed(false)
|
||||
|
||||
// Check if required amount of input fields are actually displayed
|
||||
with(R.id.layout_code) {
|
||||
isDisplayed(true)
|
||||
withChildCountAndId(passCodeViewModel.getNumberOfPassCodeDigits(), R.id.passCodeEditText)
|
||||
}
|
||||
|
||||
R.id.lock_time.isDisplayed(false)
|
||||
|
||||
R.id.numberKeyboard.isDisplayed(true)
|
||||
R.id.key0.isDisplayed(true)
|
||||
R.id.key1.isDisplayed(true)
|
||||
R.id.key2.isDisplayed(true)
|
||||
R.id.key3.isDisplayed(true)
|
||||
R.id.key4.isDisplayed(true)
|
||||
R.id.key5.isDisplayed(true)
|
||||
R.id.key6.isDisplayed(true)
|
||||
R.id.key7.isDisplayed(true)
|
||||
R.id.key8.isDisplayed(true)
|
||||
R.id.key9.isDisplayed(true)
|
||||
R.id.backspaceBtn.isDisplayed(true)
|
||||
R.id.biometricBtn.isDisplayed(false)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun passcodeCheckLockedView() {
|
||||
every { passCodeViewModel.getNumberOfAttempts() } returns 3
|
||||
every { passCodeViewModel.getTimeToUnlockLeft() } returns 3000
|
||||
|
||||
timeToUnlockLiveData.postValue(Event("00:03"))
|
||||
|
||||
// Open Activity in passcode check mode
|
||||
openPasscodeActivity(PassCodeActivity.ACTION_CHECK)
|
||||
|
||||
with(R.id.header) {
|
||||
isDisplayed(true)
|
||||
withText(R.string.pass_code_enter_pass_code)
|
||||
}
|
||||
|
||||
R.id.explanation.isDisplayed(false)
|
||||
|
||||
// Check if required amount of input fields are actually displayed
|
||||
with(R.id.layout_code) {
|
||||
isDisplayed(true)
|
||||
withChildCountAndId(passCodeViewModel.getNumberOfPassCodeDigits(), R.id.passCodeEditText)
|
||||
}
|
||||
|
||||
R.id.lock_time.isDisplayed(true)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun passcodeView() {
|
||||
// Open Activity in passcode creation mode
|
||||
openPasscodeActivity(PassCodeActivity.ACTION_CREATE)
|
||||
|
||||
with(R.id.header) {
|
||||
isDisplayed(true)
|
||||
withText(R.string.pass_code_configure_your_pass_code)
|
||||
}
|
||||
with(R.id.explanation) {
|
||||
isDisplayed(true)
|
||||
withText(R.string.pass_code_configure_your_pass_code_explanation)
|
||||
}
|
||||
|
||||
// Check if required amount of input fields are actually displayed
|
||||
with(R.id.layout_code) {
|
||||
isDisplayed(true)
|
||||
withChildCountAndId(passCodeViewModel.getNumberOfPassCodeDigits(), R.id.passCodeEditText)
|
||||
}
|
||||
|
||||
R.id.lock_time.isDisplayed(false)
|
||||
|
||||
R.id.error.isDisplayed(false)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun firstTry() {
|
||||
// Open Activity in passcode creation mode
|
||||
openPasscodeActivity(PassCodeActivity.ACTION_CREATE)
|
||||
|
||||
statusLiveData.postValue(Status(PasscodeAction.CREATE, PasscodeType.NO_CONFIRM))
|
||||
|
||||
with(R.id.header) {
|
||||
isDisplayed(true)
|
||||
withText(R.string.pass_code_reenter_your_pass_code)
|
||||
}
|
||||
onView(withText(R.string.pass_code_configure_your_pass_code)).check(doesNotExist())
|
||||
|
||||
R.id.error.isDisplayed(false)
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
fun secondTryCorrect() {
|
||||
every { biometricViewModel.isBiometricLockAvailable() } returns true
|
||||
|
||||
// Open Activity in passcode creation mode
|
||||
openPasscodeActivity(PassCodeActivity.ACTION_CREATE)
|
||||
|
||||
statusLiveData.postValue(Status(PasscodeAction.CREATE, PasscodeType.CONFIRM))
|
||||
|
||||
// Click dialog's enable option
|
||||
onView(withText(R.string.common_yes)).perform(click())
|
||||
|
||||
// Checking that the result returned is OK
|
||||
assertEquals(activityScenario.result.resultCode, Activity.RESULT_OK)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun secondTryIncorrect() {
|
||||
// Open Activity in passcode creation mode
|
||||
openPasscodeActivity(PassCodeActivity.ACTION_CREATE)
|
||||
|
||||
statusLiveData.postValue(Status(PasscodeAction.CREATE, PasscodeType.ERROR))
|
||||
|
||||
with(R.id.header) {
|
||||
isDisplayed(true)
|
||||
withText(R.string.pass_code_configure_your_pass_code)
|
||||
}
|
||||
with(R.id.explanation) {
|
||||
isDisplayed(true)
|
||||
withText(R.string.pass_code_configure_your_pass_code_explanation)
|
||||
}
|
||||
with(R.id.error) {
|
||||
isDisplayed(true)
|
||||
withText(R.string.pass_code_mismatch)
|
||||
}
|
||||
|
||||
R.id.lock_time.isDisplayed(false)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deletePasscodeView() {
|
||||
// Open Activity in passcode deletion mode
|
||||
openPasscodeActivity(PassCodeActivity.ACTION_REMOVE)
|
||||
|
||||
with(R.id.header) {
|
||||
isDisplayed(true)
|
||||
withText(R.string.pass_code_remove_your_pass_code)
|
||||
}
|
||||
|
||||
R.id.explanation.isDisplayed(false)
|
||||
|
||||
R.id.error.isDisplayed(false)
|
||||
|
||||
R.id.lock_time.isDisplayed(false)
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
fun deletePasscodeCorrect() {
|
||||
// Open Activity in passcode deletion mode
|
||||
openPasscodeActivity(PassCodeActivity.ACTION_REMOVE)
|
||||
|
||||
statusLiveData.postValue(Status(PasscodeAction.REMOVE, PasscodeType.OK))
|
||||
|
||||
assertEquals(activityScenario.result.resultCode, Activity.RESULT_OK)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deletePasscodeIncorrect() {
|
||||
// Open Activity in passcode deletion mode
|
||||
openPasscodeActivity(PassCodeActivity.ACTION_REMOVE)
|
||||
|
||||
statusLiveData.postValue(Status(PasscodeAction.REMOVE, PasscodeType.ERROR))
|
||||
|
||||
with(R.id.header) {
|
||||
isDisplayed(true)
|
||||
withText(R.string.pass_code_enter_pass_code)
|
||||
}
|
||||
with(R.id.error) {
|
||||
isDisplayed(true)
|
||||
withText(R.string.pass_code_wrong)
|
||||
}
|
||||
|
||||
R.id.explanation.isDisplayed(false)
|
||||
|
||||
R.id.lock_time.isDisplayed(false)
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
fun checkEnableBiometricDialogIsVisible() {
|
||||
every { biometricViewModel.isBiometricLockAvailable() } returns true
|
||||
|
||||
// Open Activity in passcode creation mode
|
||||
openPasscodeActivity(PassCodeActivity.ACTION_CREATE)
|
||||
|
||||
statusLiveData.postValue(Status(PasscodeAction.CREATE, PasscodeType.CONFIRM))
|
||||
|
||||
onView(withText(R.string.biometric_dialog_title)).check(matches(isDisplayed()))
|
||||
onView(withText(R.string.common_yes)).check(matches(isDisplayed()))
|
||||
onView(withText(R.string.common_no)).check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
fun checkEnableBiometricDialogYesOption() {
|
||||
every { biometricViewModel.isBiometricLockAvailable() } returns true
|
||||
|
||||
// Open Activity in passcode creation mode
|
||||
openPasscodeActivity(PassCodeActivity.ACTION_CREATE)
|
||||
|
||||
statusLiveData.postValue(Status(PasscodeAction.CREATE, PasscodeType.CONFIRM))
|
||||
|
||||
onView(withText(R.string.common_yes)).perform(click())
|
||||
|
||||
// Checking that the result returned is OK
|
||||
assertEquals(activityScenario.result.resultCode, Activity.RESULT_OK)
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
fun checkEnableBiometricDialogNoOption() {
|
||||
every { biometricViewModel.isBiometricLockAvailable() } returns true
|
||||
|
||||
// Open Activity in passcode creation mode
|
||||
openPasscodeActivity(PassCodeActivity.ACTION_CREATE)
|
||||
|
||||
statusLiveData.postValue(Status(PasscodeAction.CREATE, PasscodeType.CONFIRM))
|
||||
|
||||
onView(withText(R.string.common_no)).perform(click())
|
||||
|
||||
// Checking that the result returned is OK
|
||||
assertEquals(activityScenario.result.resultCode, Activity.RESULT_OK)
|
||||
}
|
||||
|
||||
private fun openPasscodeActivity(mode: String) {
|
||||
val intent = Intent(context, PassCodeActivity::class.java).apply {
|
||||
action = mode
|
||||
}
|
||||
activityScenario = ActivityScenario.launch(intent)
|
||||
}
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* qsfera Android client application
|
||||
*
|
||||
* @author Jesus Recio (@jesmrec)
|
||||
* @author Juan Carlos Garrote Gascón (@JuancaG05)
|
||||
*
|
||||
* Copyright (C) 2021 ownCloud GmbH.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package eu.qsfera.android.settings.security
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import eu.qsfera.android.R
|
||||
import eu.qsfera.android.db.PreferenceManager
|
||||
import eu.qsfera.android.presentation.security.pattern.PatternActivity
|
||||
import eu.qsfera.android.presentation.security.pattern.PatternViewModel
|
||||
import eu.qsfera.android.testutil.security.OC_PATTERN
|
||||
import eu.qsfera.android.utils.matchers.isDisplayed
|
||||
import eu.qsfera.android.utils.matchers.withText
|
||||
import io.mockk.mockk
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.core.context.startKoin
|
||||
import org.koin.core.context.stopKoin
|
||||
import org.koin.dsl.module
|
||||
|
||||
class PatternActivityTest {
|
||||
|
||||
private lateinit var activityScenario: ActivityScenario<PatternActivity>
|
||||
|
||||
private lateinit var context: Context
|
||||
|
||||
private lateinit var patternViewModel: PatternViewModel
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
context = ApplicationProvider.getApplicationContext()
|
||||
patternViewModel = mockk(relaxUnitFun = true)
|
||||
|
||||
stopKoin()
|
||||
|
||||
startKoin {
|
||||
context
|
||||
allowOverride(override = true)
|
||||
modules(
|
||||
module {
|
||||
viewModel {
|
||||
patternViewModel
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
// Clean preferences
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit().clear().commit()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun patternLockView() {
|
||||
// Open Activity in pattern creation mode
|
||||
openPatternActivity(PatternActivity.ACTION_REQUEST_WITH_RESULT)
|
||||
|
||||
with(R.id.header_pattern) {
|
||||
isDisplayed(true)
|
||||
withText(R.string.pattern_configure_pattern)
|
||||
}
|
||||
with(R.id.explanation_pattern) {
|
||||
isDisplayed(true)
|
||||
withText(R.string.pattern_configure_your_pattern_explanation)
|
||||
}
|
||||
R.id.pattern_lock_view.isDisplayed(true)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun removePatternLock() {
|
||||
// Save a pattern in Preferences
|
||||
storePattern()
|
||||
|
||||
// Open Activity in pattern deletion mode
|
||||
openPatternActivity(PatternActivity.ACTION_CHECK_WITH_RESULT)
|
||||
|
||||
with(R.id.header_pattern) {
|
||||
isDisplayed(true)
|
||||
withText(R.string.pattern_remove_pattern)
|
||||
}
|
||||
with(R.id.explanation_pattern) {
|
||||
isDisplayed(true)
|
||||
withText(R.string.pattern_no_longer_required)
|
||||
}
|
||||
}
|
||||
|
||||
private fun storePattern() {
|
||||
val appPrefs = PreferenceManager.getDefaultSharedPreferences(context).edit()
|
||||
appPrefs.apply {
|
||||
putString(PatternActivity.PREFERENCE_PATTERN, OC_PATTERN)
|
||||
putBoolean(PatternActivity.PREFERENCE_SET_PATTERN, true)
|
||||
apply()
|
||||
}
|
||||
}
|
||||
|
||||
private fun openPatternActivity(mode: String) {
|
||||
val intent = Intent(context, PatternActivity::class.java).apply {
|
||||
action = mode
|
||||
}
|
||||
activityScenario = ActivityScenario.launch(intent)
|
||||
}
|
||||
}
|
||||
+499
@@ -0,0 +1,499 @@
|
||||
/**
|
||||
* qsfera Android client application
|
||||
*
|
||||
* @author Juan Carlos Garrote Gascón
|
||||
*
|
||||
* Copyright (C) 2021 ownCloud GmbH.
|
||||
* <p>
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
* <p>
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* <p>
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package eu.qsfera.android.settings.security
|
||||
|
||||
import android.app.Activity.RESULT_OK
|
||||
import android.content.Context
|
||||
import androidx.biometric.BiometricViewModel
|
||||
import androidx.fragment.app.testing.FragmentScenario
|
||||
import androidx.fragment.app.testing.launchFragmentInContainer
|
||||
import androidx.preference.CheckBoxPreference
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.intent.Intents
|
||||
import androidx.test.espresso.intent.Intents.intended
|
||||
import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isEnabled
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import eu.qsfera.android.R
|
||||
import eu.qsfera.android.presentation.security.biometric.BiometricActivity
|
||||
import eu.qsfera.android.presentation.security.biometric.BiometricManager
|
||||
import eu.qsfera.android.presentation.security.PREFERENCE_LOCK_TIMEOUT
|
||||
import eu.qsfera.android.presentation.security.pattern.PatternActivity
|
||||
import eu.qsfera.android.presentation.security.passcode.PassCodeActivity
|
||||
import eu.qsfera.android.presentation.settings.security.SettingsSecurityFragment
|
||||
import eu.qsfera.android.presentation.settings.security.SettingsSecurityFragment.Companion.PREFERENCE_LOCK_ACCESS_FROM_DOCUMENT_PROVIDER
|
||||
import eu.qsfera.android.presentation.settings.security.SettingsSecurityViewModel
|
||||
import eu.qsfera.android.testutil.security.OC_PATTERN
|
||||
import eu.qsfera.android.utils.matchers.verifyPreference
|
||||
import eu.qsfera.android.utils.mockIntent
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkObject
|
||||
import org.hamcrest.Matchers.not
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.core.context.startKoin
|
||||
import org.koin.core.context.stopKoin
|
||||
import org.koin.dsl.module
|
||||
|
||||
class SettingsSecurityFragmentTest {
|
||||
|
||||
private lateinit var fragmentScenario: FragmentScenario<SettingsSecurityFragment>
|
||||
|
||||
private lateinit var prefPasscode: CheckBoxPreference
|
||||
private lateinit var prefPattern: CheckBoxPreference
|
||||
private var prefBiometric: CheckBoxPreference? = null
|
||||
private lateinit var prefLockApplication: ListPreference
|
||||
private lateinit var prefLockAccessDocumentProvider: CheckBoxPreference
|
||||
private lateinit var prefTouchesWithOtherVisibleWindows: CheckBoxPreference
|
||||
|
||||
private lateinit var securityViewModel: SettingsSecurityViewModel
|
||||
private lateinit var biometricViewModel: BiometricViewModel
|
||||
private lateinit var context: Context
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
securityViewModel = mockk(relaxed = true)
|
||||
biometricViewModel = mockk(relaxed = true)
|
||||
mockkObject(BiometricManager)
|
||||
|
||||
stopKoin()
|
||||
|
||||
startKoin {
|
||||
context
|
||||
allowOverride(override = true)
|
||||
modules(
|
||||
module {
|
||||
viewModel {
|
||||
securityViewModel
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
every { securityViewModel.isSecurityEnforcedEnabled() } returns false
|
||||
every { securityViewModel.getBiometricsState() } returns false
|
||||
every { securityViewModel.isLockDelayEnforcedEnabled() } returns false
|
||||
|
||||
Intents.init()
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
Intents.release()
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit().clear().commit()
|
||||
}
|
||||
|
||||
private fun launchTest(withBiometrics: Boolean = true) {
|
||||
every { BiometricManager.isHardwareDetected() } returns withBiometrics
|
||||
|
||||
fragmentScenario = launchFragmentInContainer(themeResId = R.style.Theme_qsfera)
|
||||
fragmentScenario.onFragment { fragment ->
|
||||
prefPasscode = fragment.findPreference(PassCodeActivity.PREFERENCE_SET_PASSCODE)!!
|
||||
prefPattern = fragment.findPreference(PatternActivity.PREFERENCE_SET_PATTERN)!!
|
||||
prefBiometric = fragment.findPreference(BiometricActivity.PREFERENCE_SET_BIOMETRIC)
|
||||
prefLockApplication = fragment.findPreference(PREFERENCE_LOCK_TIMEOUT)!!
|
||||
prefLockAccessDocumentProvider = fragment.findPreference(PREFERENCE_LOCK_ACCESS_FROM_DOCUMENT_PROVIDER)!!
|
||||
prefTouchesWithOtherVisibleWindows =
|
||||
fragment.findPreference(SettingsSecurityFragment.PREFERENCE_TOUCHES_WITH_OTHER_VISIBLE_WINDOWS)!!
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkCommonPreferences() {
|
||||
prefPasscode.verifyPreference(
|
||||
keyPref = PassCodeActivity.PREFERENCE_SET_PASSCODE,
|
||||
titlePref = context.getString(R.string.prefs_passcode),
|
||||
visible = true,
|
||||
enabled = true
|
||||
)
|
||||
assertFalse(prefPasscode.isChecked)
|
||||
|
||||
prefPattern.verifyPreference(
|
||||
keyPref = PatternActivity.PREFERENCE_SET_PATTERN,
|
||||
titlePref = context.getString(R.string.prefs_pattern),
|
||||
visible = true,
|
||||
enabled = true
|
||||
)
|
||||
assertFalse(prefPattern.isChecked)
|
||||
|
||||
prefLockApplication.verifyPreference(
|
||||
keyPref = PREFERENCE_LOCK_TIMEOUT,
|
||||
titlePref = context.getString(R.string.prefs_lock_application),
|
||||
visible = true,
|
||||
enabled = false
|
||||
)
|
||||
|
||||
prefLockAccessDocumentProvider.verifyPreference(
|
||||
keyPref = PREFERENCE_LOCK_ACCESS_FROM_DOCUMENT_PROVIDER,
|
||||
titlePref = context.getString(R.string.prefs_lock_access_from_document_provider),
|
||||
summaryPref = context.getString(R.string.prefs_lock_access_from_document_provider_summary),
|
||||
visible = true,
|
||||
enabled = true
|
||||
)
|
||||
assertFalse(prefLockAccessDocumentProvider.isChecked)
|
||||
|
||||
prefTouchesWithOtherVisibleWindows.verifyPreference(
|
||||
keyPref = SettingsSecurityFragment.PREFERENCE_TOUCHES_WITH_OTHER_VISIBLE_WINDOWS,
|
||||
titlePref = context.getString(R.string.prefs_touches_with_other_visible_windows),
|
||||
summaryPref = context.getString(R.string.prefs_touches_with_other_visible_windows_summary),
|
||||
visible = true,
|
||||
enabled = true
|
||||
)
|
||||
assertFalse(prefTouchesWithOtherVisibleWindows.isChecked)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun securityViewDeviceWithBiometrics() {
|
||||
launchTest()
|
||||
|
||||
checkCommonPreferences()
|
||||
|
||||
assertNotNull(prefBiometric)
|
||||
prefBiometric?.run {
|
||||
verifyPreference(
|
||||
keyPref = BiometricActivity.PREFERENCE_SET_BIOMETRIC,
|
||||
titlePref = context.getString(R.string.prefs_biometric),
|
||||
summaryPref = context.getString(R.string.prefs_biometric_summary),
|
||||
visible = true,
|
||||
enabled = false
|
||||
)
|
||||
assertFalse(isChecked)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun securityViewDeviceWithNoBiometrics() {
|
||||
launchTest(withBiometrics = false)
|
||||
|
||||
checkCommonPreferences()
|
||||
|
||||
assertNull(prefBiometric)
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
fun passcodeOpen() {
|
||||
every { securityViewModel.isPatternSet() } returns false
|
||||
|
||||
launchTest()
|
||||
|
||||
mockIntent(RESULT_OK, PassCodeActivity.ACTION_CREATE)
|
||||
onView(withText(R.string.prefs_passcode)).perform(click())
|
||||
intended(hasComponent(PassCodeActivity::class.java.name))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun patternOpen() {
|
||||
every { securityViewModel.isPasscodeSet() } returns false
|
||||
|
||||
launchTest()
|
||||
|
||||
onView(withText(R.string.prefs_pattern)).perform(click())
|
||||
intended(hasComponent(PatternActivity::class.java.name))
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
fun passcodeLockEnabledOk() {
|
||||
every { securityViewModel.isPatternSet() } returns false
|
||||
|
||||
launchTest()
|
||||
|
||||
mockIntent(
|
||||
action = PassCodeActivity.ACTION_CREATE
|
||||
)
|
||||
onView(withText(R.string.prefs_passcode)).perform(click())
|
||||
assertTrue(prefPasscode.isChecked)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun patternLockEnabledOk() {
|
||||
every { securityViewModel.isPasscodeSet() } returns false
|
||||
|
||||
launchTest()
|
||||
|
||||
mockIntent(
|
||||
extras = Pair(PatternActivity.PREFERENCE_PATTERN, OC_PATTERN),
|
||||
action = PatternActivity.ACTION_REQUEST_WITH_RESULT
|
||||
)
|
||||
onView(withText(R.string.prefs_pattern)).perform(click())
|
||||
assertTrue(prefPattern.isChecked)
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
fun enablePasscodeEnablesBiometricLockAndLockApplication() {
|
||||
launchTest()
|
||||
|
||||
firstEnablePasscode()
|
||||
onView(withText(R.string.prefs_biometric)).check(matches(isEnabled()))
|
||||
assertTrue(prefBiometric!!.isEnabled)
|
||||
assertFalse(prefBiometric!!.isChecked)
|
||||
assertTrue(prefLockApplication.isEnabled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun enablePatternEnablesBiometricLockAndLockApplication() {
|
||||
launchTest()
|
||||
|
||||
firstEnablePattern()
|
||||
onView(withText(R.string.prefs_biometric)).check(matches(isEnabled()))
|
||||
assertTrue(prefBiometric!!.isEnabled)
|
||||
assertFalse(prefBiometric!!.isChecked)
|
||||
assertTrue(prefLockApplication.isEnabled)
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
fun onlyOneMethodEnabledPattern() {
|
||||
every { securityViewModel.isPatternSet() } returns true
|
||||
|
||||
launchTest()
|
||||
|
||||
firstEnablePattern()
|
||||
onView(withText(R.string.prefs_passcode)).perform(click())
|
||||
onView(withText(R.string.pattern_already_set)).check(matches(isEnabled()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onlyOneMethodEnabledPasscode() {
|
||||
every { securityViewModel.isPasscodeSet() } returns true
|
||||
|
||||
launchTest()
|
||||
|
||||
firstEnablePasscode()
|
||||
onView(withText(R.string.prefs_pattern)).perform(click())
|
||||
onView(withText(R.string.passcode_already_set)).check(matches(isEnabled()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun disablePasscodeOk() {
|
||||
launchTest()
|
||||
|
||||
firstEnablePasscode()
|
||||
mockIntent(
|
||||
action = PassCodeActivity.ACTION_REMOVE
|
||||
)
|
||||
onView(withText(R.string.prefs_passcode)).perform(click())
|
||||
assertFalse(prefPasscode.isChecked)
|
||||
onView(withText(R.string.prefs_biometric)).check(matches(not(isEnabled())))
|
||||
assertFalse(prefBiometric!!.isEnabled)
|
||||
assertFalse(prefBiometric!!.isChecked)
|
||||
assertFalse(prefLockApplication.isEnabled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun disablePatternOk() {
|
||||
launchTest()
|
||||
|
||||
firstEnablePattern()
|
||||
mockIntent(
|
||||
action = PatternActivity.ACTION_CHECK_WITH_RESULT
|
||||
)
|
||||
onView(withText(R.string.prefs_pattern)).perform(click())
|
||||
assertFalse(prefPattern.isChecked)
|
||||
onView(withText(R.string.prefs_biometric)).check(matches(not(isEnabled())))
|
||||
assertFalse(prefBiometric!!.isEnabled)
|
||||
assertFalse(prefBiometric!!.isChecked)
|
||||
assertFalse(prefLockApplication.isEnabled)
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
fun enableBiometricLockWithPasscodeEnabled() {
|
||||
every { BiometricManager.hasEnrolledBiometric() } returns true
|
||||
|
||||
launchTest()
|
||||
|
||||
firstEnablePasscode()
|
||||
onView(withText(R.string.prefs_biometric)).perform(click())
|
||||
assertTrue(prefBiometric!!.isChecked)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun enableBiometricLockWithPatternEnabled() {
|
||||
every { BiometricManager.hasEnrolledBiometric() } returns true
|
||||
|
||||
launchTest()
|
||||
|
||||
firstEnablePattern()
|
||||
onView(withText(R.string.prefs_biometric)).perform(click())
|
||||
assertTrue(prefBiometric!!.isChecked)
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
fun enableBiometricLockNoEnrolledBiometric() {
|
||||
every { BiometricManager.hasEnrolledBiometric() } returns false
|
||||
|
||||
launchTest()
|
||||
|
||||
firstEnablePasscode()
|
||||
onView(withText(R.string.prefs_biometric)).perform(click())
|
||||
assertFalse(prefBiometric!!.isChecked)
|
||||
onView(withText(R.string.biometric_not_enrolled)).check(matches(isEnabled()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun disableBiometricLock() {
|
||||
every { BiometricManager.hasEnrolledBiometric() } returns true
|
||||
|
||||
launchTest()
|
||||
|
||||
firstEnablePasscode()
|
||||
onView(withText(R.string.prefs_biometric)).perform(click())
|
||||
onView(withText(R.string.prefs_biometric)).perform(click())
|
||||
assertFalse(prefBiometric!!.isChecked)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun lockAccessFromDocumentProviderEnable() {
|
||||
launchTest()
|
||||
|
||||
onView(withText(R.string.prefs_lock_access_from_document_provider)).perform(click())
|
||||
assertTrue(prefLockAccessDocumentProvider.isChecked)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun lockAccessFromDocumentProviderDisable() {
|
||||
launchTest()
|
||||
|
||||
onView(withText(R.string.prefs_lock_access_from_document_provider)).perform(click())
|
||||
onView(withText(R.string.prefs_lock_access_from_document_provider)).perform(click())
|
||||
assertFalse(prefLockAccessDocumentProvider.isChecked)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun touchesDialog() {
|
||||
launchTest()
|
||||
|
||||
onView(withText(R.string.prefs_touches_with_other_visible_windows)).perform(click())
|
||||
onView(withText(R.string.confirmation_touches_with_other_windows_title)).check(matches(isDisplayed()))
|
||||
onView(withText(R.string.confirmation_touches_with_other_windows_message)).check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun touchesEnable() {
|
||||
launchTest()
|
||||
|
||||
onView(withText(R.string.prefs_touches_with_other_visible_windows)).perform(click())
|
||||
onView(withText(R.string.common_yes)).perform(click())
|
||||
assertTrue(prefTouchesWithOtherVisibleWindows.isChecked)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun touchesRefuse() {
|
||||
launchTest()
|
||||
|
||||
onView(withText(R.string.prefs_touches_with_other_visible_windows)).perform(click())
|
||||
onView(withText(R.string.common_no)).perform(click())
|
||||
assertFalse(prefTouchesWithOtherVisibleWindows.isChecked)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun touchesDisable() {
|
||||
launchTest()
|
||||
|
||||
onView(withText(R.string.prefs_touches_with_other_visible_windows)).perform(click())
|
||||
onView(withText(R.string.common_yes)).perform(click())
|
||||
onView(withText(R.string.prefs_touches_with_other_visible_windows)).perform(click())
|
||||
assertFalse(prefTouchesWithOtherVisibleWindows.isChecked)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun passcodeLockNotVisible() {
|
||||
every { securityViewModel.isSecurityEnforcedEnabled() } returns true
|
||||
launchTest()
|
||||
assertFalse(prefPasscode.isVisible)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun patternLockNotVisible() {
|
||||
every { securityViewModel.isSecurityEnforcedEnabled() } returns true
|
||||
launchTest()
|
||||
assertFalse(prefPattern.isVisible)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun passcodeLockVisible() {
|
||||
launchTest()
|
||||
assertTrue(prefPasscode.isVisible)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun patternLockVisible() {
|
||||
launchTest()
|
||||
assertTrue(prefPattern.isVisible)
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
fun checkIfUserEnabledBiometricRecommendation() {
|
||||
every { securityViewModel.getBiometricsState() } returns true
|
||||
|
||||
launchTest()
|
||||
|
||||
firstEnablePasscode()
|
||||
|
||||
assertTrue(prefBiometric!!.isChecked)
|
||||
assertTrue(prefBiometric!!.isEnabled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkIfUserNotEnabledBiometricRecommendation() {
|
||||
launchTest()
|
||||
|
||||
firstEnablePasscode()
|
||||
|
||||
assertFalse(prefBiometric!!.isChecked)
|
||||
}
|
||||
|
||||
private fun firstEnablePasscode() {
|
||||
every { securityViewModel.isPatternSet() } returns false
|
||||
|
||||
mockIntent(
|
||||
action = PassCodeActivity.ACTION_CREATE
|
||||
)
|
||||
onView(withText(R.string.prefs_passcode)).perform(click())
|
||||
}
|
||||
|
||||
private fun firstEnablePattern() {
|
||||
every { securityViewModel.isPasscodeSet() } returns false
|
||||
|
||||
mockIntent(
|
||||
action = PatternActivity.ACTION_REQUEST_WITH_RESULT
|
||||
)
|
||||
onView(withText(R.string.prefs_pattern)).perform(click())
|
||||
}
|
||||
}
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* qsfera Android client application
|
||||
*
|
||||
* @author David González Verdugo
|
||||
* Copyright (C) 2020 ownCloud GmbH.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package eu.qsfera.android.sharing.sharees.ui
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.matcher.ViewMatchers
|
||||
import androidx.test.espresso.matcher.ViewMatchers.hasSibling
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||
import eu.qsfera.android.R
|
||||
import eu.qsfera.android.domain.sharing.shares.model.OCShare
|
||||
import eu.qsfera.android.domain.sharing.shares.model.ShareType
|
||||
import eu.qsfera.android.domain.utils.Event
|
||||
import eu.qsfera.android.presentation.common.UIResult
|
||||
import eu.qsfera.android.presentation.sharing.sharees.SearchShareesFragment
|
||||
import eu.qsfera.android.presentation.sharing.ShareViewModel
|
||||
import eu.qsfera.android.sharing.shares.ui.TestShareFileActivity
|
||||
import eu.qsfera.android.testutil.OC_SHARE
|
||||
import io.mockk.every
|
||||
import io.mockk.mockkClass
|
||||
import org.hamcrest.CoreMatchers
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.core.context.startKoin
|
||||
import org.koin.core.context.stopKoin
|
||||
import org.koin.dsl.module
|
||||
|
||||
class SearchShareesFragmentTest {
|
||||
private val shareViewModel = mockkClass(ShareViewModel::class, relaxed = true)
|
||||
private val sharesLiveData = MutableLiveData<Event<UIResult<List<OCShare>>>>()
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
every { shareViewModel.shares } returns sharesLiveData
|
||||
|
||||
stopKoin()
|
||||
|
||||
startKoin {
|
||||
androidContext(ApplicationProvider.getApplicationContext())
|
||||
allowOverride(override = true)
|
||||
modules(
|
||||
module {
|
||||
viewModel {
|
||||
shareViewModel
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
ActivityScenario.launch(TestShareFileActivity::class.java).onActivity {
|
||||
val searchShareesFragment = SearchShareesFragment()
|
||||
it.startFragment(searchShareesFragment)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showSearchBar() {
|
||||
onView(withId(R.id.search_mag_icon)).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.search_plate)).check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showUserShares() {
|
||||
sharesLiveData.postValue(
|
||||
Event(
|
||||
UIResult.Success(
|
||||
listOf(
|
||||
OC_SHARE.copy(sharedWithDisplayName = "Sheldon"),
|
||||
OC_SHARE.copy(sharedWithDisplayName = "Penny")
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
onView(withText("Sheldon"))
|
||||
.check(matches(isDisplayed()))
|
||||
.check(matches(hasSibling(withId(R.id.unshareButton))))
|
||||
.check(matches(hasSibling(withId(R.id.editShareButton))))
|
||||
onView(withText("Penny")).check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showGroupShares() {
|
||||
sharesLiveData.postValue(
|
||||
Event(
|
||||
UIResult.Success(
|
||||
listOf(
|
||||
OC_SHARE.copy(
|
||||
shareType = ShareType.GROUP,
|
||||
sharedWithDisplayName = "Friends"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
onView(withText("Friends (group)"))
|
||||
.check(matches(isDisplayed()))
|
||||
.check(matches(hasSibling(withId(R.id.icon))))
|
||||
onView(ViewMatchers.withTagValue(CoreMatchers.equalTo(R.drawable.ic_group))).check(matches(isDisplayed()))
|
||||
}
|
||||
}
|
||||
+278
@@ -0,0 +1,278 @@
|
||||
/**
|
||||
* qsfera Android client application
|
||||
*
|
||||
* @author David González Verdugo
|
||||
* Copyright (C) 2020 ownCloud GmbH.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package eu.qsfera.android.sharing.shares.ui
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isChecked
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isNotChecked
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import eu.qsfera.android.R
|
||||
import eu.qsfera.android.domain.sharing.shares.model.OCShare
|
||||
import eu.qsfera.android.domain.utils.Event
|
||||
import eu.qsfera.android.presentation.common.UIResult
|
||||
import eu.qsfera.android.presentation.sharing.sharees.EditPrivateShareFragment
|
||||
import eu.qsfera.android.presentation.sharing.ShareViewModel
|
||||
import eu.qsfera.android.testutil.OC_ACCOUNT
|
||||
import eu.qsfera.android.testutil.OC_FILE
|
||||
import eu.qsfera.android.testutil.OC_FOLDER
|
||||
import eu.qsfera.android.testutil.OC_SHARE
|
||||
import eu.qsfera.android.utils.Permissions
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import org.hamcrest.CoreMatchers.not
|
||||
import org.junit.Assert
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.core.context.startKoin
|
||||
import org.koin.core.context.stopKoin
|
||||
import org.koin.dsl.module
|
||||
|
||||
class EditPrivateShareFragmentTest {
|
||||
private val targetContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
private val defaultSharedWithDisplayName = "user"
|
||||
private val shareViewModel = mockk<ShareViewModel>(relaxed = true)
|
||||
private val privateShareAsLiveData = MutableLiveData<Event<UIResult<OCShare>>>()
|
||||
|
||||
private lateinit var activityScenario: ActivityScenario<TestShareFileActivity>
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
every { shareViewModel.privateShare } returns privateShareAsLiveData
|
||||
every { shareViewModel.isResharingAllowed() } returns true
|
||||
|
||||
stopKoin()
|
||||
|
||||
startKoin {
|
||||
androidContext(ApplicationProvider.getApplicationContext())
|
||||
allowOverride(override = true)
|
||||
modules(
|
||||
module {
|
||||
viewModel {
|
||||
shareViewModel
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showDialogTitle() {
|
||||
loadEditPrivateShareFragment()
|
||||
onView(withId(R.id.editShareTitle))
|
||||
.check(
|
||||
matches(
|
||||
withText(
|
||||
targetContext.getString(R.string.share_with_edit_title, defaultSharedWithDisplayName)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun closeDialog() {
|
||||
loadEditPrivateShareFragment()
|
||||
onView(withId(R.id.closeButton)).perform(click())
|
||||
activityScenario.onActivity { Assert.assertNull(it.getTestFragment()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showToggles() {
|
||||
loadEditPrivateShareFragment()
|
||||
onView(withId(R.id.canEditSwitch)).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.canShareSwitch)).check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showFileShareWithNoPermissions() {
|
||||
loadEditPrivateShareFragment()
|
||||
onView(withId(R.id.canEditSwitch)).check(matches(isNotChecked()))
|
||||
onView(withId(R.id.canShareSwitch)).check(matches(isNotChecked()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showFileShareWithEditPermissions() {
|
||||
loadEditPrivateShareFragment(permissions = Permissions.EDIT_PERMISSIONS.value)
|
||||
onView(withId(R.id.canEditSwitch)).check(matches(isChecked()))
|
||||
onView(withId(R.id.canShareSwitch)).check(matches(isNotChecked()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showFileShareWithSharePermissions() {
|
||||
loadEditPrivateShareFragment(permissions = Permissions.SHARE_PERMISSIONS.value)
|
||||
onView(withId(R.id.canEditSwitch)).check(matches(isNotChecked()))
|
||||
onView(withId(R.id.canShareSwitch)).check(matches(isChecked()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showFileShareWithAllPermissions() {
|
||||
loadEditPrivateShareFragment(permissions = Permissions.ALL_PERMISSIONS.value)
|
||||
onView(withId(R.id.canEditSwitch)).check(matches(isChecked()))
|
||||
onView(withId(R.id.canShareSwitch)).check(matches(isChecked()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showFolderShareWithCreatePermissions() {
|
||||
loadEditPrivateShareFragment(true, permissions = Permissions.EDIT_CREATE_PERMISSIONS.value)
|
||||
onView(withId(R.id.canEditSwitch)).check(matches(isChecked()))
|
||||
onView(withId(R.id.canEditCreateCheckBox)).check(matches(isChecked()))
|
||||
onView(withId(R.id.canEditChangeCheckBox)).check(matches(isNotChecked()))
|
||||
onView(withId(R.id.canEditDeleteCheckBox)).check(matches(isNotChecked()))
|
||||
onView(withId(R.id.canShareSwitch)).check(matches(isNotChecked()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showFolderShareWithCreateChangePermissions() {
|
||||
loadEditPrivateShareFragment(true, permissions = Permissions.EDIT_CREATE_CHANGE_PERMISSIONS.value)
|
||||
onView(withId(R.id.canEditSwitch)).check(matches(isChecked()))
|
||||
onView(withId(R.id.canEditCreateCheckBox)).check(matches(isChecked()))
|
||||
onView(withId(R.id.canEditChangeCheckBox)).check(matches(isChecked()))
|
||||
onView(withId(R.id.canEditDeleteCheckBox)).check(matches(isNotChecked()))
|
||||
onView(withId(R.id.canShareSwitch)).check(matches(isNotChecked()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showFolderShareWithCreateDeletePermissions() {
|
||||
loadEditPrivateShareFragment(true, permissions = Permissions.EDIT_CREATE_DELETE_PERMISSIONS.value)
|
||||
onView(withId(R.id.canEditSwitch)).check(matches(isChecked()))
|
||||
onView(withId(R.id.canEditCreateCheckBox)).check(matches(isChecked()))
|
||||
onView(withId(R.id.canEditChangeCheckBox)).check(matches(isNotChecked()))
|
||||
onView(withId(R.id.canEditDeleteCheckBox)).check(matches(isChecked()))
|
||||
onView(withId(R.id.canShareSwitch)).check(matches(isNotChecked()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showFolderShareWithCreateChangeDeletePermissions() {
|
||||
loadEditPrivateShareFragment(true, permissions = Permissions.EDIT_CREATE_CHANGE_DELETE_PERMISSIONS.value)
|
||||
onView(withId(R.id.canEditSwitch)).check(matches(isChecked()))
|
||||
onView(withId(R.id.canEditCreateCheckBox)).check(matches(isChecked()))
|
||||
onView(withId(R.id.canEditChangeCheckBox)).check(matches(isChecked()))
|
||||
onView(withId(R.id.canEditDeleteCheckBox)).check(matches(isChecked()))
|
||||
onView(withId(R.id.canShareSwitch)).check(matches(isNotChecked()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showFolderShareWithChangePermissions() {
|
||||
loadEditPrivateShareFragment(true, permissions = Permissions.EDIT_CHANGE_PERMISSIONS.value)
|
||||
onView(withId(R.id.canEditSwitch)).check(matches(isChecked()))
|
||||
onView(withId(R.id.canEditCreateCheckBox)).check(matches(isNotChecked()))
|
||||
onView(withId(R.id.canEditChangeCheckBox)).check(matches(isChecked()))
|
||||
onView(withId(R.id.canEditDeleteCheckBox)).check(matches(isNotChecked()))
|
||||
onView(withId(R.id.canShareSwitch)).check(matches(isNotChecked()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showFolderShareWithChangeDeletePermissions() {
|
||||
loadEditPrivateShareFragment(true, permissions = Permissions.EDIT_CHANGE_DELETE_PERMISSIONS.value)
|
||||
onView(withId(R.id.canEditSwitch)).check(matches(isChecked()))
|
||||
onView(withId(R.id.canEditCreateCheckBox)).check(matches(isNotChecked()))
|
||||
onView(withId(R.id.canEditChangeCheckBox)).check(matches(isChecked()))
|
||||
onView(withId(R.id.canEditDeleteCheckBox)).check(matches(isChecked()))
|
||||
onView(withId(R.id.canShareSwitch)).check(matches(isNotChecked()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showFolderShareWithDeletePermissions() {
|
||||
loadEditPrivateShareFragment(true, permissions = Permissions.EDIT_DELETE_PERMISSIONS.value)
|
||||
onView(withId(R.id.canEditSwitch)).check(matches(isChecked()))
|
||||
onView(withId(R.id.canEditCreateCheckBox)).check(matches(isNotChecked()))
|
||||
onView(withId(R.id.canEditChangeCheckBox)).check(matches(isNotChecked()))
|
||||
onView(withId(R.id.canEditDeleteCheckBox)).check(matches(isChecked()))
|
||||
onView(withId(R.id.canShareSwitch)).check(matches(isNotChecked()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun disableEditPermissionWithFile() {
|
||||
loadEditPrivateShareFragment(permissions = Permissions.EDIT_PERMISSIONS.value)
|
||||
onView(withId(R.id.canEditSwitch)).check(matches(isChecked()))
|
||||
onView(withId(R.id.canShareSwitch)).check(matches(isNotChecked()))
|
||||
|
||||
onView(withId(R.id.canEditSwitch)).perform(click())
|
||||
|
||||
onView(withId(R.id.canEditSwitch)).check(matches(isNotChecked())) // "Can edit" changes
|
||||
onView(withId(R.id.canEditCreateCheckBox)).check(matches(not(isDisplayed()))) // No suboptions
|
||||
onView(withId(R.id.canEditChangeCheckBox)).check(matches(not(isDisplayed())))
|
||||
onView(withId(R.id.canEditDeleteCheckBox)).check(matches(not(isDisplayed())))
|
||||
onView(withId(R.id.canShareSwitch)).check(matches(isNotChecked())) // "Can share" does not change
|
||||
}
|
||||
|
||||
@Test
|
||||
fun disableEditPermissionWithFolder() {
|
||||
loadEditPrivateShareFragment(true, permissions = Permissions.EDIT_PERMISSIONS.value)
|
||||
onView(withId(R.id.canEditSwitch)).check(matches(isChecked()))
|
||||
onView(withId(R.id.canEditCreateCheckBox)).check(matches(isDisplayed())) // Suboptions appear
|
||||
onView(withId(R.id.canEditChangeCheckBox)).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.canEditDeleteCheckBox)).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.canShareSwitch)).check(matches(isNotChecked()))
|
||||
|
||||
onView(withId(R.id.canEditSwitch)).perform(click())
|
||||
|
||||
onView(withId(R.id.canEditSwitch)).check(matches(isNotChecked())) // "Can edit" changes
|
||||
onView(withId(R.id.canEditCreateCheckBox)).check(matches(not(isDisplayed()))) // Suboptions hidden
|
||||
onView(withId(R.id.canEditChangeCheckBox)).check(matches(not(isDisplayed())))
|
||||
onView(withId(R.id.canEditDeleteCheckBox)).check(matches(not(isDisplayed())))
|
||||
onView(withId(R.id.canShareSwitch)).check(matches(isNotChecked())) // "Can share" does not change
|
||||
}
|
||||
|
||||
private fun loadEditPrivateShareFragment(
|
||||
isFolder: Boolean = false,
|
||||
permissions: Int = Permissions.READ_PERMISSIONS.value
|
||||
) {
|
||||
val shareToEdit = OC_SHARE.copy(
|
||||
sharedWithDisplayName = defaultSharedWithDisplayName,
|
||||
permissions = permissions
|
||||
)
|
||||
|
||||
val sharedFile = if (isFolder) OC_FOLDER else OC_FILE
|
||||
|
||||
val editPrivateShareFragment = EditPrivateShareFragment.newInstance(
|
||||
shareToEdit,
|
||||
sharedFile,
|
||||
OC_ACCOUNT
|
||||
)
|
||||
|
||||
activityScenario = ActivityScenario.launch(TestShareFileActivity::class.java).onActivity {
|
||||
it.startFragment(editPrivateShareFragment)
|
||||
}
|
||||
|
||||
privateShareAsLiveData.postValue(
|
||||
Event(
|
||||
UIResult.Success(
|
||||
OC_SHARE.copy(
|
||||
shareWith = "user",
|
||||
sharedWithDisplayName = "User",
|
||||
path = "/Videos",
|
||||
isFolder = isFolder,
|
||||
permissions = permissions
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
+493
@@ -0,0 +1,493 @@
|
||||
/**
|
||||
* qsfera Android client application
|
||||
*
|
||||
* @author David González Verdugo
|
||||
* Copyright (C) 2020 ownCloud GmbH.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package eu.qsfera.android.sharing.shares.ui
|
||||
|
||||
import android.text.InputType.TYPE_CLASS_TEXT
|
||||
import android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
import androidx.test.espresso.action.ViewActions.scrollTo
|
||||
import androidx.test.espresso.action.ViewActions.typeText
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.matcher.ViewMatchers
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isEnabled
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withInputType
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||
import eu.qsfera.android.R
|
||||
import eu.qsfera.android.domain.capabilities.model.CapabilityBooleanType
|
||||
import eu.qsfera.android.domain.capabilities.model.OCCapability
|
||||
import eu.qsfera.android.domain.utils.Event
|
||||
import eu.qsfera.android.presentation.common.UIResult
|
||||
import eu.qsfera.android.presentation.sharing.shares.PublicShareDialogFragment
|
||||
import eu.qsfera.android.presentation.capabilities.CapabilityViewModel
|
||||
import eu.qsfera.android.presentation.sharing.ShareViewModel
|
||||
import eu.qsfera.android.testutil.OC_ACCOUNT
|
||||
import eu.qsfera.android.testutil.OC_CAPABILITY
|
||||
import eu.qsfera.android.testutil.OC_FILE
|
||||
import eu.qsfera.android.testutil.OC_FOLDER
|
||||
import eu.qsfera.android.utils.DateUtils
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import org.hamcrest.CoreMatchers.not
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.core.context.startKoin
|
||||
import org.koin.core.context.stopKoin
|
||||
import org.koin.dsl.module
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
|
||||
class PublicShareCreationDialogFragmentTest {
|
||||
private val capabilityViewModel = mockk<CapabilityViewModel>(relaxed = true)
|
||||
private val capabilitiesLiveData = MutableLiveData<Event<UIResult<OCCapability>>>()
|
||||
private val shareViewModel = mockk<ShareViewModel>(relaxed = true)
|
||||
private val publicShareCreationStatus = MutableLiveData<Event<UIResult<Unit>>>()
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
every { capabilityViewModel.capabilities } returns capabilitiesLiveData
|
||||
every { shareViewModel.publicShareCreationStatus } returns publicShareCreationStatus
|
||||
|
||||
stopKoin()
|
||||
|
||||
startKoin {
|
||||
androidContext(ApplicationProvider.getApplicationContext())
|
||||
allowOverride(override = true)
|
||||
modules(
|
||||
module {
|
||||
viewModel {
|
||||
capabilityViewModel
|
||||
}
|
||||
viewModel {
|
||||
shareViewModel
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showDialogTitle() {
|
||||
loadPublicShareDialogFragment()
|
||||
onView(withId(R.id.publicShareDialogTitle)).check(matches(withText(R.string.share_via_link_create_title)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showMandatoryFields() {
|
||||
loadPublicShareDialogFragment()
|
||||
onView(withId(R.id.shareViaLinkNameSection)).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.shareViaLinkPasswordSection)).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.shareViaLinkExpirationSection)).check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showDialogButtons() {
|
||||
loadPublicShareDialogFragment()
|
||||
onView(withId(R.id.cancelButton)).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.saveButton)).check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showFolderAdditionalFields() {
|
||||
loadPublicShareDialogFragment(
|
||||
isFolder = true,
|
||||
capabilities = OC_CAPABILITY.copy(
|
||||
versionString = "10.0.1",
|
||||
filesSharingPublicUpload = CapabilityBooleanType.TRUE,
|
||||
filesSharingPublicSupportsUploadOnly = CapabilityBooleanType.TRUE
|
||||
)
|
||||
)
|
||||
onView(withId(R.id.shareViaLinkEditPermissionGroup)).check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showDefaultLinkName() {
|
||||
loadPublicShareDialogFragment()
|
||||
onView(withId(R.id.shareViaLinkNameValue)).check(matches(withText("DOC_12112018.jpg link")))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun enablePasswordSwitch() {
|
||||
loadPublicShareDialogFragment()
|
||||
onView(withId(R.id.shareViaLinkPasswordSwitch)).perform(click())
|
||||
onView(withId(R.id.shareViaLinkPasswordValue)).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.saveButton)).check(matches(not(isEnabled())))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkPasswordNotVisible() {
|
||||
loadPublicShareDialogFragment()
|
||||
onView(withId(R.id.shareViaLinkPasswordSwitch)).perform(click())
|
||||
onView(withId(R.id.shareViaLinkPasswordValue)).perform(typeText("supersecure"))
|
||||
onView(withId(R.id.shareViaLinkPasswordValue)).check(
|
||||
matches(
|
||||
withInputType(
|
||||
TYPE_CLASS_TEXT or TYPE_TEXT_VARIATION_PASSWORD
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkPasswordEnforced() {
|
||||
loadPublicShareDialogFragment(
|
||||
capabilities = OC_CAPABILITY.copy(
|
||||
filesSharingPublicPasswordEnforced = CapabilityBooleanType.TRUE
|
||||
)
|
||||
)
|
||||
onView(withId(R.id.shareViaLinkPasswordLabel)).check(
|
||||
matches(withText(R.string.share_via_link_password_enforced_label))
|
||||
)
|
||||
onView(withId(R.id.shareViaLinkPasswordSwitch))
|
||||
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE)))
|
||||
onView(withId(R.id.shareViaLinkPasswordValue))
|
||||
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
|
||||
onView(withId(R.id.saveButton)).check(matches(not(isEnabled())))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkExpireDateEnforced() {
|
||||
loadPublicShareDialogFragment(
|
||||
capabilities = OC_CAPABILITY.copy(
|
||||
filesSharingPublicExpireDateEnforced = CapabilityBooleanType.TRUE
|
||||
)
|
||||
)
|
||||
onView(withId(R.id.shareViaLinkExpirationLabel))
|
||||
.check(matches(withText(R.string.share_via_link_expiration_date_enforced_label)))
|
||||
onView(withId(R.id.shareViaLinkExpirationSwitch))
|
||||
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE)))
|
||||
onView(withId(R.id.shareViaLinkExpirationExplanationLabel))
|
||||
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkExpireDateNotEnforced() {
|
||||
loadPublicShareDialogFragment(
|
||||
capabilities = OC_CAPABILITY.copy(
|
||||
filesSharingPublicExpireDateEnforced = CapabilityBooleanType.FALSE
|
||||
)
|
||||
)
|
||||
onView(withId(R.id.shareViaLinkExpirationLabel))
|
||||
.check(matches(withText(R.string.share_via_link_expiration_date_label)))
|
||||
onView(withId(R.id.shareViaLinkExpirationSwitch))
|
||||
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
|
||||
onView(withId(R.id.shareViaLinkExpirationExplanationLabel))
|
||||
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun enableExpirationSwitch() {
|
||||
loadPublicShareDialogFragment()
|
||||
onView(withId(R.id.shareViaLinkExpirationSwitch)).perform(click())
|
||||
onView(withId(android.R.id.button1)).perform(click())
|
||||
onView(withId(R.id.shareViaLinkExpirationValue))
|
||||
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)))
|
||||
//TODO: check the date form the picker
|
||||
}
|
||||
|
||||
@Test
|
||||
fun cancelExpirationSwitch() {
|
||||
loadPublicShareDialogFragment()
|
||||
onView(withId(R.id.shareViaLinkExpirationSwitch)).perform(click())
|
||||
onView(withId(android.R.id.button2)).perform(click())
|
||||
onView(withId(R.id.shareViaLinkExpirationValue))
|
||||
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.INVISIBLE)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showError() {
|
||||
loadPublicShareDialogFragment()
|
||||
|
||||
onView(withId(R.id.saveButton)).perform(click())
|
||||
|
||||
publicShareCreationStatus.postValue(
|
||||
Event(
|
||||
UIResult.Error(
|
||||
error = Throwable("It was not possible to share this file or folder")
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
onView(withId(R.id.public_link_error_message)).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.public_link_error_message)).check(
|
||||
matches(
|
||||
withText(R.string.share_link_file_error)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun uploadPermissionsWithFolderDisplayed() {
|
||||
loadPublicShareDialogFragment(
|
||||
isFolder = true,
|
||||
capabilities = OC_CAPABILITY.copy(
|
||||
versionString = "10.1.1",
|
||||
filesSharingPublicSupportsUploadOnly = CapabilityBooleanType.TRUE,
|
||||
filesSharingPublicUpload = CapabilityBooleanType.TRUE
|
||||
)
|
||||
)
|
||||
|
||||
onView(withId(R.id.shareViaLinkEditPermissionGroup)).check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun uploadPermissionsWithFolderNotDisplayed() {
|
||||
loadPublicShareDialogFragment(
|
||||
isFolder = true,
|
||||
capabilities = OC_CAPABILITY.copy(
|
||||
versionString = "10.1.1",
|
||||
filesSharingPublicSupportsUploadOnly = CapabilityBooleanType.TRUE,
|
||||
filesSharingPublicUpload = CapabilityBooleanType.FALSE
|
||||
)
|
||||
)
|
||||
|
||||
onView(withId(R.id.shareViaLinkEditPermissionGroup)).check(matches(not(isDisplayed())))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun expirationDateDays() {
|
||||
val daysToTest = 15
|
||||
loadPublicShareDialogFragment(
|
||||
capabilities = OC_CAPABILITY.copy(
|
||||
versionString = "10.1.1",
|
||||
filesSharingPublicExpireDateDays = daysToTest
|
||||
)
|
||||
)
|
||||
val formattedDate = SimpleDateFormat.getDateInstance().format(
|
||||
DateUtils.addDaysToDate(
|
||||
Date(),
|
||||
daysToTest
|
||||
)
|
||||
)
|
||||
onView(withId(R.id.shareViaLinkExpirationSwitch))
|
||||
.check(matches(isEnabled()))
|
||||
onView(withId(R.id.shareViaLinkExpirationValue))
|
||||
.check(matches(withText(formattedDate)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun passwordNotEnforced() {
|
||||
loadPublicShareDialogFragment(
|
||||
capabilities = OC_CAPABILITY.copy(
|
||||
versionString = "10.1.1",
|
||||
filesSharingPublicPasswordEnforced = CapabilityBooleanType.FALSE
|
||||
)
|
||||
)
|
||||
onView(withId(R.id.shareViaLinkPasswordLabel))
|
||||
.check(matches(withText(R.string.share_via_link_password_label)))
|
||||
onView(withId(R.id.saveButton)).check(matches(isEnabled()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun passwordEnforced() {
|
||||
loadPublicShareDialogFragment(
|
||||
capabilities = OC_CAPABILITY.copy(
|
||||
versionString = "10.1.1",
|
||||
filesSharingPublicPasswordEnforced = CapabilityBooleanType.TRUE
|
||||
)
|
||||
)
|
||||
onView(withId(R.id.shareViaLinkPasswordLabel))
|
||||
.check(matches(withText(R.string.share_via_link_password_enforced_label)))
|
||||
onView(withId(R.id.saveButton)).check(matches(not(isEnabled())))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun passwordEnforcedReadOnlyFolders() {
|
||||
loadPublicShareDialogFragment(
|
||||
isFolder = true,
|
||||
capabilities = OC_CAPABILITY.copy(
|
||||
versionString = "10.1.1",
|
||||
filesSharingPublicSupportsUploadOnly = CapabilityBooleanType.TRUE,
|
||||
filesSharingPublicUpload = CapabilityBooleanType.TRUE,
|
||||
filesSharingPublicPasswordEnforcedReadOnly = CapabilityBooleanType.TRUE,
|
||||
filesSharingPublicPasswordEnforced = CapabilityBooleanType.TRUE
|
||||
)
|
||||
)
|
||||
|
||||
onView(withId(R.id.shareViaLinkEditPermissionReadOnly)).perform(scrollTo())
|
||||
onView(withId(R.id.shareViaLinkEditPermissionReadOnly)).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.shareViaLinkEditPermissionReadOnly)).perform(click())
|
||||
onView(withId(R.id.shareViaLinkPasswordLabel))
|
||||
.check(matches(withText(R.string.share_via_link_password_enforced_label)))
|
||||
onView(withId(R.id.saveButton)).check(matches(not(isEnabled())))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun passwordNotEnforcedReadOnlyFolders() {
|
||||
loadPublicShareDialogFragment(
|
||||
isFolder = true,
|
||||
capabilities = OC_CAPABILITY.copy(
|
||||
versionString = "10.1.1",
|
||||
filesSharingPublicSupportsUploadOnly = CapabilityBooleanType.TRUE,
|
||||
filesSharingPublicUpload = CapabilityBooleanType.TRUE,
|
||||
filesSharingPublicPasswordEnforcedReadOnly = CapabilityBooleanType.FALSE,
|
||||
filesSharingPublicPasswordEnforced = CapabilityBooleanType.FALSE
|
||||
)
|
||||
)
|
||||
onView(withId(R.id.shareViaLinkEditPermissionReadOnly)).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.shareViaLinkEditPermissionReadOnly)).perform(click())
|
||||
onView(withId(R.id.shareViaLinkPasswordLabel))
|
||||
.check(matches(withText(R.string.share_via_link_password_label)))
|
||||
onView(withId(R.id.saveButton)).check(matches(isEnabled()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun passwordEnforcedReadWriteFolders() {
|
||||
loadPublicShareDialogFragment(
|
||||
isFolder = true,
|
||||
capabilities = OC_CAPABILITY.copy(
|
||||
versionString = "10.1.1",
|
||||
filesSharingPublicSupportsUploadOnly = CapabilityBooleanType.TRUE,
|
||||
filesSharingPublicUpload = CapabilityBooleanType.TRUE,
|
||||
filesSharingPublicPasswordEnforcedReadWrite = CapabilityBooleanType.TRUE,
|
||||
filesSharingPublicPasswordEnforced = CapabilityBooleanType.TRUE
|
||||
)
|
||||
)
|
||||
onView(withId(R.id.shareViaLinkEditPermissionReadAndWrite)).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.shareViaLinkEditPermissionReadAndWrite)).perform(click())
|
||||
onView(withId(R.id.shareViaLinkPasswordLabel))
|
||||
.check(matches(withText(R.string.share_via_link_password_enforced_label)))
|
||||
onView(withId(R.id.saveButton)).check(matches(not(isEnabled())))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun passwordNotEnforcedReadWriteFolders() {
|
||||
loadPublicShareDialogFragment(
|
||||
isFolder = true,
|
||||
capabilities = OC_CAPABILITY.copy(
|
||||
versionString = "10.1.1",
|
||||
filesSharingPublicSupportsUploadOnly = CapabilityBooleanType.TRUE,
|
||||
filesSharingPublicUpload = CapabilityBooleanType.TRUE,
|
||||
filesSharingPublicPasswordEnforcedReadWrite = CapabilityBooleanType.FALSE,
|
||||
filesSharingPublicPasswordEnforced = CapabilityBooleanType.FALSE
|
||||
)
|
||||
)
|
||||
onView(withId(R.id.shareViaLinkEditPermissionReadAndWrite)).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.shareViaLinkEditPermissionReadAndWrite)).perform(click())
|
||||
onView(withId(R.id.shareViaLinkPasswordLabel))
|
||||
.check(matches(withText(R.string.share_via_link_password_label)))
|
||||
onView(withId(R.id.saveButton)).check(matches(isEnabled()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun passwordEnforcedUploadOnlyFolders() {
|
||||
loadPublicShareDialogFragment(
|
||||
isFolder = true,
|
||||
capabilities = OC_CAPABILITY.copy(
|
||||
versionString = "10.1.1",
|
||||
filesSharingPublicSupportsUploadOnly = CapabilityBooleanType.TRUE,
|
||||
filesSharingPublicUpload = CapabilityBooleanType.TRUE,
|
||||
filesSharingPublicPasswordEnforcedUploadOnly = CapabilityBooleanType.TRUE,
|
||||
filesSharingPublicPasswordEnforced = CapabilityBooleanType.FALSE
|
||||
)
|
||||
)
|
||||
onView(withId(R.id.shareViaLinkEditPermissionUploadFiles)).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.shareViaLinkEditPermissionUploadFiles)).perform(click())
|
||||
onView(withId(R.id.shareViaLinkPasswordLabel))
|
||||
.check(matches(withText(R.string.share_via_link_password_enforced_label)))
|
||||
onView(withId(R.id.saveButton)).check(matches(not(isEnabled())))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun passwordNotEnforcedUploadOnlyFolders() {
|
||||
loadPublicShareDialogFragment(
|
||||
isFolder = true,
|
||||
capabilities = OC_CAPABILITY.copy(
|
||||
versionString = "10.1.1",
|
||||
filesSharingPublicSupportsUploadOnly = CapabilityBooleanType.TRUE,
|
||||
filesSharingPublicUpload = CapabilityBooleanType.TRUE,
|
||||
filesSharingPublicPasswordEnforcedUploadOnly = CapabilityBooleanType.FALSE,
|
||||
filesSharingPublicPasswordEnforced = CapabilityBooleanType.FALSE
|
||||
)
|
||||
)
|
||||
onView(withId(R.id.shareViaLinkEditPermissionUploadFiles)).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.shareViaLinkEditPermissionUploadFiles)).perform(click())
|
||||
onView(withId(R.id.shareViaLinkPasswordLabel))
|
||||
.check(matches(withText(R.string.share_via_link_password_label)))
|
||||
onView(withId(R.id.saveButton)).check(matches(isEnabled()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun passwordEnforcedClearErrorMessageIfSwitchesToNotEnforced() {
|
||||
val commonError = "Common error"
|
||||
|
||||
//One permission with password enforced. Error is cleaned after switching permission
|
||||
//to a non-forced one
|
||||
loadPublicShareDialogFragment(
|
||||
isFolder = true,
|
||||
capabilities = OC_CAPABILITY.copy(
|
||||
versionString = "10.1.1",
|
||||
filesSharingPublicSupportsUploadOnly = CapabilityBooleanType.TRUE,
|
||||
filesSharingPublicUpload = CapabilityBooleanType.TRUE,
|
||||
filesSharingPublicPasswordEnforcedUploadOnly = CapabilityBooleanType.FALSE,
|
||||
filesSharingPublicPasswordEnforcedReadOnly = CapabilityBooleanType.FALSE,
|
||||
filesSharingPublicPasswordEnforced = CapabilityBooleanType.TRUE
|
||||
)
|
||||
)
|
||||
|
||||
onView(withId(R.id.saveButton)).perform(scrollTo())
|
||||
onView(withId(R.id.saveButton)).perform(click())
|
||||
|
||||
publicShareCreationStatus.postValue(
|
||||
Event(
|
||||
UIResult.Error(
|
||||
error = Throwable(commonError)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
onView(withId(R.id.public_link_error_message)).perform(scrollTo())
|
||||
onView(withText(commonError)).check(matches(isDisplayed()))
|
||||
|
||||
onView(withId(R.id.shareViaLinkEditPermissionUploadFiles)).perform(scrollTo(), click())
|
||||
|
||||
onView(withText(commonError)).check(matches(not(isDisplayed())))
|
||||
onView(withId(R.id.saveButton)).check(matches(not(isEnabled())))
|
||||
}
|
||||
|
||||
private fun loadPublicShareDialogFragment(
|
||||
isFolder: Boolean = false,
|
||||
capabilities: OCCapability = OC_CAPABILITY
|
||||
) {
|
||||
val file = if (isFolder) OC_FOLDER else OC_FILE
|
||||
|
||||
val publicShareDialogFragment = PublicShareDialogFragment.newInstanceToCreate(
|
||||
file,
|
||||
OC_ACCOUNT,
|
||||
"DOC_12112018.jpg link"
|
||||
)
|
||||
|
||||
ActivityScenario.launch(TestShareFileActivity::class.java).onActivity {
|
||||
it.startFragment(publicShareDialogFragment)
|
||||
}
|
||||
|
||||
capabilitiesLiveData.postValue(
|
||||
Event(UIResult.Success(capabilities))
|
||||
)
|
||||
}
|
||||
}
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* qsfera Android client application
|
||||
*
|
||||
* @author David González Verdugo
|
||||
* Copyright (C) 2020 ownCloud GmbH.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package eu.qsfera.android.sharing.shares.ui
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.matcher.ViewMatchers
|
||||
import androidx.test.espresso.matcher.ViewMatchers.Visibility.VISIBLE
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isChecked
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withHint
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||
import eu.qsfera.android.R
|
||||
import eu.qsfera.android.domain.capabilities.model.OCCapability
|
||||
import eu.qsfera.android.domain.sharing.shares.model.ShareType
|
||||
import eu.qsfera.android.domain.utils.Event
|
||||
import eu.qsfera.android.lib.resources.shares.RemoteShare
|
||||
import eu.qsfera.android.presentation.common.UIResult
|
||||
import eu.qsfera.android.presentation.sharing.shares.PublicShareDialogFragment
|
||||
import eu.qsfera.android.presentation.capabilities.CapabilityViewModel
|
||||
import eu.qsfera.android.presentation.sharing.ShareViewModel
|
||||
import eu.qsfera.android.testutil.OC_ACCOUNT
|
||||
import eu.qsfera.android.testutil.OC_FILE
|
||||
import eu.qsfera.android.testutil.OC_SHARE
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.core.context.startKoin
|
||||
import org.koin.core.context.stopKoin
|
||||
import org.koin.dsl.module
|
||||
import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.GregorianCalendar
|
||||
import java.util.TimeZone
|
||||
|
||||
class PublicShareEditionDialogFragmentTest {
|
||||
private val capabilityViewModel = mockk<CapabilityViewModel>(relaxed = true)
|
||||
private val capabilitiesLiveData = MutableLiveData<Event<UIResult<OCCapability>>>()
|
||||
private val shareViewModel = mockk<ShareViewModel>(relaxed = true)
|
||||
|
||||
private val expirationDate = 1556575200000 // GMT: Monday, April 29, 2019 10:00:00 PM
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
every { capabilityViewModel.capabilities } returns capabilitiesLiveData
|
||||
|
||||
stopKoin()
|
||||
|
||||
startKoin {
|
||||
androidContext(ApplicationProvider.getApplicationContext())
|
||||
allowOverride(override = true)
|
||||
modules(
|
||||
module {
|
||||
viewModel {
|
||||
capabilityViewModel
|
||||
}
|
||||
viewModel {
|
||||
shareViewModel
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
val publicShareDialogFragment = PublicShareDialogFragment.newInstanceToUpdate(
|
||||
OC_FILE,
|
||||
OC_ACCOUNT,
|
||||
OC_SHARE.copy(
|
||||
shareType = ShareType.PUBLIC_LINK,
|
||||
shareWith = "user",
|
||||
name = "Docs link",
|
||||
permissions = RemoteShare.CREATE_PERMISSION_FLAG,
|
||||
expirationDate = expirationDate,
|
||||
isFolder = true
|
||||
)
|
||||
)
|
||||
|
||||
ActivityScenario.launch(TestShareFileActivity::class.java).onActivity {
|
||||
it.startFragment(publicShareDialogFragment)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showEditionDialogTitle() {
|
||||
onView(withId(R.id.publicShareDialogTitle)).check(matches(withText(R.string.share_via_link_edit_title)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkLinkNameSet() {
|
||||
onView(withText(R.string.share_via_link_name_label)).check(matches(ViewMatchers.isDisplayed()))
|
||||
onView(withId(R.id.shareViaLinkNameValue)).check(matches(withText("Docs link")))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkUploadOnly() {
|
||||
onView(withId(R.id.shareViaLinkEditPermissionUploadFiles)).check(matches(isChecked()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkPasswordSet() {
|
||||
onView(withId(R.id.shareViaLinkPasswordLabel)).check(matches(withText(R.string.share_via_link_password_label)))
|
||||
onView(withId(R.id.shareViaLinkPasswordSwitch)).check(matches(withEffectiveVisibility(VISIBLE)))
|
||||
onView(withId(R.id.shareViaLinkPasswordValue)).check(matches(withEffectiveVisibility(VISIBLE)))
|
||||
onView(withId(R.id.shareViaLinkPasswordValue)).check(matches(withHint(R.string.share_via_link_default_password)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkExpirationDateSet() {
|
||||
val calendar = GregorianCalendar()
|
||||
calendar.timeInMillis = expirationDate
|
||||
|
||||
val formatter: DateFormat = SimpleDateFormat.getDateInstance()
|
||||
formatter.timeZone = TimeZone.getDefault()
|
||||
|
||||
val time = formatter.format(calendar.time)
|
||||
|
||||
onView(withId(R.id.shareViaLinkExpirationLabel)).check(matches(withText(R.string.share_via_link_expiration_date_label)))
|
||||
onView(withId(R.id.shareViaLinkExpirationSwitch)).check(matches(withEffectiveVisibility(VISIBLE)))
|
||||
onView(withId(R.id.shareViaLinkExpirationValue)).check(matches(withEffectiveVisibility(VISIBLE)))
|
||||
onView(withId(R.id.shareViaLinkExpirationValue)).check(matches(withText(time)))
|
||||
}
|
||||
}
|
||||
+320
@@ -0,0 +1,320 @@
|
||||
/**
|
||||
* qsfera Android client application
|
||||
*
|
||||
* @author David González Verdugo
|
||||
* Copyright (C) 2020 ownCloud GmbH.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package eu.qsfera.android.sharing.shares.ui
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.matcher.ViewMatchers
|
||||
import androidx.test.espresso.matcher.ViewMatchers.hasSibling
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withTagValue
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||
import eu.qsfera.android.R
|
||||
import eu.qsfera.android.domain.capabilities.model.CapabilityBooleanType
|
||||
import eu.qsfera.android.domain.capabilities.model.OCCapability
|
||||
import eu.qsfera.android.domain.sharing.shares.model.OCShare
|
||||
import eu.qsfera.android.domain.sharing.shares.model.ShareType
|
||||
import eu.qsfera.android.domain.utils.Event
|
||||
import eu.qsfera.android.presentation.common.UIResult
|
||||
import eu.qsfera.android.presentation.sharing.ShareFileFragment
|
||||
import eu.qsfera.android.presentation.capabilities.CapabilityViewModel
|
||||
import eu.qsfera.android.presentation.sharing.ShareViewModel
|
||||
import eu.qsfera.android.testutil.OC_ACCOUNT
|
||||
import eu.qsfera.android.testutil.OC_CAPABILITY
|
||||
import eu.qsfera.android.testutil.OC_FILE
|
||||
import eu.qsfera.android.testutil.OC_SHARE
|
||||
import eu.qsfera.android.utils.matchers.assertVisibility
|
||||
import eu.qsfera.android.utils.matchers.isDisplayed
|
||||
import eu.qsfera.android.utils.matchers.withText
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import org.hamcrest.CoreMatchers
|
||||
import org.junit.Before
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.core.context.startKoin
|
||||
import org.koin.core.context.stopKoin
|
||||
import org.koin.dsl.module
|
||||
|
||||
@Ignore
|
||||
class ShareFileFragmentTest {
|
||||
private val capabilityViewModel = mockk<CapabilityViewModel>(relaxed = true)
|
||||
private val capabilitiesLiveData = MutableLiveData<Event<UIResult<OCCapability>>>()
|
||||
private val shareViewModel = mockk<ShareViewModel>(relaxed = true)
|
||||
private val sharesLiveData = MutableLiveData<Event<UIResult<List<OCShare>>>>()
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
every { capabilityViewModel.capabilities } returns capabilitiesLiveData
|
||||
every { shareViewModel.shares } returns sharesLiveData
|
||||
|
||||
stopKoin()
|
||||
|
||||
startKoin {
|
||||
androidContext(ApplicationProvider.getApplicationContext())
|
||||
allowOverride(override = true)
|
||||
modules(
|
||||
module {
|
||||
viewModel {
|
||||
capabilityViewModel
|
||||
}
|
||||
viewModel {
|
||||
shareViewModel
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showHeader() {
|
||||
loadShareFileFragment()
|
||||
onView(withId(R.id.shareFileName)).check(matches(withText(OC_FILE.fileName)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fileSizeVisible() {
|
||||
loadShareFileFragment()
|
||||
R.id.shareFileSize.isDisplayed(displayed = true)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showPrivateLink() {
|
||||
loadShareFileFragment()
|
||||
R.id.getPrivateLinkButton.isDisplayed(displayed = true)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun hidePrivateLink() {
|
||||
loadShareFileFragment(capabilities = OC_CAPABILITY.copy(filesPrivateLinks = CapabilityBooleanType.FALSE))
|
||||
R.id.getPrivateLinkButton.isDisplayed(displayed = false)
|
||||
}
|
||||
|
||||
/******************************************************************************************************
|
||||
******************************************* PRIVATE SHARES *******************************************
|
||||
******************************************************************************************************/
|
||||
|
||||
private var userSharesList = listOf(
|
||||
OC_SHARE.copy(sharedWithDisplayName = "Batman"),
|
||||
OC_SHARE.copy(sharedWithDisplayName = "Joker")
|
||||
)
|
||||
|
||||
private var groupSharesList = listOf(
|
||||
OC_SHARE.copy(
|
||||
shareType = ShareType.GROUP,
|
||||
sharedWithDisplayName = "Suicide Squad"
|
||||
),
|
||||
OC_SHARE.copy(
|
||||
shareType = ShareType.GROUP,
|
||||
sharedWithDisplayName = "Avengers"
|
||||
)
|
||||
)
|
||||
|
||||
@Test
|
||||
fun showUsersAndGroupsSectionTitle() {
|
||||
loadShareFileFragment(shares = userSharesList)
|
||||
onView(withText(R.string.share_with_user_section_title)).check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showNoPrivateShares() {
|
||||
loadShareFileFragment(shares = listOf())
|
||||
onView(withText(R.string.share_no_users)).check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showUserShares() {
|
||||
loadShareFileFragment(shares = userSharesList)
|
||||
onView(withText("Batman")).check(matches(isDisplayed()))
|
||||
onView(withText("Batman")).check(matches(hasSibling(withId(R.id.unshareButton))))
|
||||
.check(matches(isDisplayed()))
|
||||
onView(withText("Batman")).check(matches(hasSibling(withId(R.id.editShareButton))))
|
||||
.check(matches(isDisplayed()))
|
||||
onView(withText("Joker")).check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showGroupShares() {
|
||||
loadShareFileFragment(shares = listOf(groupSharesList.first()))
|
||||
onView(withText("Suicide Squad (group)")).check(matches(isDisplayed()))
|
||||
onView(withText("Suicide Squad (group)")).check(matches(hasSibling(withId(R.id.icon))))
|
||||
.check(matches(isDisplayed()))
|
||||
onView(withTagValue(CoreMatchers.equalTo(R.drawable.ic_group))).check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
/******************************************************************************************************
|
||||
******************************************* PUBLIC SHARES ********************************************
|
||||
******************************************************************************************************/
|
||||
|
||||
private var publicShareList = listOf(
|
||||
OC_SHARE.copy(
|
||||
shareType = ShareType.PUBLIC_LINK,
|
||||
path = "/Photos/image.jpg",
|
||||
isFolder = false,
|
||||
name = "Image link",
|
||||
shareLink = "http://server:port/s/1"
|
||||
),
|
||||
OC_SHARE.copy(
|
||||
shareType = ShareType.PUBLIC_LINK,
|
||||
path = "/Photos/image.jpg",
|
||||
isFolder = false,
|
||||
name = "Image link 2",
|
||||
shareLink = "http://server:port/s/2"
|
||||
),
|
||||
OC_SHARE.copy(
|
||||
shareType = ShareType.PUBLIC_LINK,
|
||||
path = "/Photos/image.jpg",
|
||||
isFolder = false,
|
||||
name = "Image link 3",
|
||||
shareLink = "http://server:port/s/3"
|
||||
)
|
||||
)
|
||||
|
||||
@Test
|
||||
fun showNoPublicShares() {
|
||||
loadShareFileFragment(shares = listOf())
|
||||
onView(withText(R.string.share_no_public_links)).check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showPublicShares() {
|
||||
loadShareFileFragment(shares = publicShareList)
|
||||
onView(withText("Image link")).check(matches(isDisplayed()))
|
||||
onView(withText("Image link")).check(matches(hasSibling(withId(R.id.getPublicLinkButton))))
|
||||
.check(matches(isDisplayed()))
|
||||
onView(withText("Image link")).check(matches(hasSibling(withId(R.id.deletePublicLinkButton))))
|
||||
.check(matches(isDisplayed()))
|
||||
onView(withText("Image link")).check(matches(hasSibling(withId(R.id.editPublicLinkButton))))
|
||||
.check(matches(isDisplayed()))
|
||||
onView(withText("Image link 2")).check(matches(isDisplayed()))
|
||||
onView(withText("Image link 3")).check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showPublicSharesSharingEnabled() {
|
||||
loadShareFileFragment(
|
||||
capabilities = OC_CAPABILITY.copy(filesSharingPublicEnabled = CapabilityBooleanType.TRUE),
|
||||
shares = publicShareList
|
||||
)
|
||||
|
||||
onView(withText("Image link")).check(matches(isDisplayed()))
|
||||
onView(withText("Image link 2")).check(matches(isDisplayed()))
|
||||
onView(withText("Image link 3")).check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun hidePublicSharesSharingDisabled() {
|
||||
loadShareFileFragment(
|
||||
capabilities = OC_CAPABILITY.copy(filesSharingPublicEnabled = CapabilityBooleanType.FALSE),
|
||||
shares = publicShareList
|
||||
)
|
||||
|
||||
R.id.shareViaLinkSection.assertVisibility(ViewMatchers.Visibility.GONE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun createPublicShareMultipleCapability() {
|
||||
loadShareFileFragment(
|
||||
capabilities = OC_CAPABILITY.copy(
|
||||
versionString = "10.1.1",
|
||||
filesSharingPublicMultiple = CapabilityBooleanType.TRUE
|
||||
),
|
||||
shares = listOf(publicShareList[0])
|
||||
)
|
||||
|
||||
R.id.addPublicLinkButton.assertVisibility(ViewMatchers.Visibility.VISIBLE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun cannotCreatePublicShareMultipleCapability() {
|
||||
loadShareFileFragment(
|
||||
capabilities = OC_CAPABILITY.copy(
|
||||
versionString = "10.1.1",
|
||||
filesSharingPublicMultiple = CapabilityBooleanType.FALSE
|
||||
),
|
||||
shares = listOf(publicShareList[0])
|
||||
)
|
||||
|
||||
R.id.addPublicLinkButton.assertVisibility(ViewMatchers.Visibility.INVISIBLE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun cannotCreatePublicShareServerCapability() {
|
||||
loadShareFileFragment(
|
||||
capabilities = OC_CAPABILITY.copy(
|
||||
versionString = "9.3.1"
|
||||
),
|
||||
shares = listOf(publicShareList[0])
|
||||
)
|
||||
|
||||
R.id.addPublicLinkButton.assertVisibility(ViewMatchers.Visibility.INVISIBLE)
|
||||
}
|
||||
|
||||
/******************************************************************************************************
|
||||
*********************************************** COMMON ***********************************************
|
||||
******************************************************************************************************/
|
||||
|
||||
@Test
|
||||
fun hideSharesSharingApiDisabled() {
|
||||
loadShareFileFragment(
|
||||
capabilities = OC_CAPABILITY.copy(
|
||||
filesSharingApiEnabled = CapabilityBooleanType.FALSE
|
||||
)
|
||||
)
|
||||
R.id.shareWithUsersSection.assertVisibility(ViewMatchers.Visibility.GONE)
|
||||
|
||||
R.id.shareViaLinkSection.assertVisibility(ViewMatchers.Visibility.GONE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun showError() {
|
||||
loadShareFileFragment(
|
||||
sharesUIResult = UIResult.Error(
|
||||
error = Throwable("It was not possible to retrieve the shares from the server")
|
||||
)
|
||||
)
|
||||
com.google.android.material.R.id.snackbar_text.withText(R.string.get_shares_error)
|
||||
}
|
||||
|
||||
private fun loadShareFileFragment(
|
||||
capabilities: OCCapability = OC_CAPABILITY,
|
||||
capabilitiesEvent: Event<UIResult<OCCapability>> = Event(UIResult.Success(capabilities)),
|
||||
shares: List<OCShare> = listOf(OC_SHARE),
|
||||
sharesUIResult: UIResult<List<OCShare>> = UIResult.Success(shares)
|
||||
) {
|
||||
val shareFileFragment = ShareFileFragment.newInstance(
|
||||
OC_FILE,
|
||||
OC_ACCOUNT
|
||||
)
|
||||
|
||||
ActivityScenario.launch(TestShareFileActivity::class.java).onActivity {
|
||||
it.startFragment(shareFileFragment)
|
||||
}
|
||||
|
||||
capabilitiesLiveData.postValue(capabilitiesEvent)
|
||||
sharesLiveData.postValue(Event(sharesUIResult))
|
||||
}
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* qsfera Android client application
|
||||
*
|
||||
* @author David González Verdugo
|
||||
* @author Jesus Recio Rincon
|
||||
* Copyright (C) 2020 ownCloud GmbH.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package eu.qsfera.android.sharing.shares.ui
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import eu.qsfera.android.R
|
||||
import eu.qsfera.android.domain.capabilities.model.OCCapability
|
||||
import eu.qsfera.android.domain.sharing.shares.model.OCShare
|
||||
import eu.qsfera.android.domain.utils.Event
|
||||
import eu.qsfera.android.presentation.common.UIResult
|
||||
import eu.qsfera.android.presentation.sharing.ShareFileFragment
|
||||
import eu.qsfera.android.presentation.capabilities.CapabilityViewModel
|
||||
import eu.qsfera.android.presentation.sharing.ShareViewModel
|
||||
import eu.qsfera.android.testutil.OC_ACCOUNT
|
||||
import eu.qsfera.android.testutil.OC_CAPABILITY
|
||||
import eu.qsfera.android.testutil.OC_FOLDER
|
||||
import eu.qsfera.android.testutil.OC_SHARE
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import org.hamcrest.CoreMatchers.not
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.core.context.startKoin
|
||||
import org.koin.core.context.stopKoin
|
||||
import org.koin.dsl.module
|
||||
|
||||
class ShareFolderFragmentTest {
|
||||
private val capabilityViewModel = mockk<CapabilityViewModel>(relaxed = true)
|
||||
private val capabilitiesLiveData = MutableLiveData<Event<UIResult<OCCapability>>>()
|
||||
private val shareViewModel = mockk<ShareViewModel>(relaxed = true)
|
||||
private val sharesLiveData = MutableLiveData<Event<UIResult<List<OCShare>>>>()
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
every { capabilityViewModel.capabilities } returns capabilitiesLiveData
|
||||
every { shareViewModel.shares } returns sharesLiveData
|
||||
|
||||
stopKoin()
|
||||
|
||||
startKoin {
|
||||
androidContext(ApplicationProvider.getApplicationContext())
|
||||
allowOverride(override = true)
|
||||
modules(
|
||||
module {
|
||||
viewModel {
|
||||
capabilityViewModel
|
||||
}
|
||||
viewModel {
|
||||
shareViewModel
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
val shareFileFragment = ShareFileFragment.newInstance(
|
||||
OC_FOLDER.copy(privateLink = null),
|
||||
OC_ACCOUNT
|
||||
)
|
||||
|
||||
ActivityScenario.launch(TestShareFileActivity::class.java).onActivity {
|
||||
it.startFragment(shareFileFragment)
|
||||
}
|
||||
|
||||
capabilitiesLiveData.postValue(Event(UIResult.Success(OC_CAPABILITY)))
|
||||
|
||||
sharesLiveData.postValue(Event(UIResult.Success(listOf(OC_SHARE))))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun folderSizeVisible() {
|
||||
onView(withId(R.id.shareFileSize)).check(matches(not(isDisplayed())))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun hidePrivateLink() {
|
||||
onView(withId(R.id.getPrivateLinkButton)).check(matches(not(isDisplayed())))
|
||||
}
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* qsfera Android client application
|
||||
*
|
||||
* @author David González Verdugo
|
||||
* Copyright (C) 2020 ownCloud GmbH.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package eu.qsfera.android.sharing.shares.ui
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.commit
|
||||
import eu.qsfera.android.R
|
||||
import eu.qsfera.android.domain.files.model.OCFile
|
||||
import eu.qsfera.android.domain.sharing.shares.model.OCShare
|
||||
import eu.qsfera.android.presentation.sharing.ShareFragmentListener
|
||||
import eu.qsfera.android.services.OperationsService
|
||||
import eu.qsfera.android.testing.SingleFragmentActivity
|
||||
import eu.qsfera.android.ui.fragment.FileFragment.ContainerActivity
|
||||
import eu.qsfera.android.ui.helpers.FileOperationsHelper
|
||||
|
||||
class TestShareFileActivity : SingleFragmentActivity(), ShareFragmentListener, ContainerActivity {
|
||||
fun startFragment(fragment: Fragment) {
|
||||
supportFragmentManager.commit(allowStateLoss = true) {
|
||||
add(R.id.container, fragment, TEST_FRAGMENT_TAG)
|
||||
}
|
||||
}
|
||||
|
||||
fun getTestFragment(): Fragment? = supportFragmentManager.findFragmentByTag(TEST_FRAGMENT_TAG)
|
||||
|
||||
override fun copyOrSendPrivateLink(file: OCFile) {
|
||||
}
|
||||
|
||||
override fun deleteShare(remoteId: String) {
|
||||
}
|
||||
|
||||
override fun showLoading() {
|
||||
}
|
||||
|
||||
override fun dismissLoading() {
|
||||
}
|
||||
|
||||
override fun showAddPublicShare(defaultLinkName: String) {
|
||||
}
|
||||
|
||||
override fun showEditPublicShare(share: OCShare) {
|
||||
}
|
||||
|
||||
override fun showRemoveShare(share: OCShare) {
|
||||
}
|
||||
|
||||
override fun copyOrSendPublicLink(share: OCShare) {
|
||||
}
|
||||
|
||||
override fun showSearchUsersAndGroups() {
|
||||
}
|
||||
|
||||
override fun showEditPrivateShare(share: OCShare) {
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TEST_FRAGMENT_TAG = "TEST FRAGMENT"
|
||||
}
|
||||
|
||||
override fun getOperationsServiceBinder(): OperationsService.OperationsServiceBinder {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun getFileOperationsHelper(): FileOperationsHelper {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun showDetails(file: OCFile?) {
|
||||
}
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* qsfera Android client application
|
||||
*
|
||||
* @author David Crespo Ríos
|
||||
* Copyright (C) 2022 ownCloud GmbH.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package eu.qsfera.android.ui.activity
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import eu.qsfera.android.R
|
||||
import eu.qsfera.android.presentation.releasenotes.ReleaseNotesActivity
|
||||
import eu.qsfera.android.presentation.releasenotes.ReleaseNotesViewModel
|
||||
import eu.qsfera.android.utils.click
|
||||
import eu.qsfera.android.utils.matchers.assertChildCount
|
||||
import eu.qsfera.android.utils.matchers.isDisplayed
|
||||
import eu.qsfera.android.utils.matchers.withText
|
||||
import eu.qsfera.android.utils.releaseNotesList
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.core.context.startKoin
|
||||
import org.koin.core.context.stopKoin
|
||||
import org.koin.dsl.module
|
||||
|
||||
class ReleaseNotesActivityTest {
|
||||
private lateinit var activityScenario: ActivityScenario<ReleaseNotesActivity>
|
||||
private lateinit var context: Context
|
||||
|
||||
private lateinit var releaseNotesViewModel: ReleaseNotesViewModel
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
context = ApplicationProvider.getApplicationContext()
|
||||
releaseNotesViewModel = mockk(relaxed = true)
|
||||
|
||||
stopKoin()
|
||||
|
||||
startKoin {
|
||||
context
|
||||
allowOverride(override = true)
|
||||
modules(
|
||||
module {
|
||||
viewModel {
|
||||
releaseNotesViewModel
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
every { releaseNotesViewModel.getReleaseNotes() } returns releaseNotesList
|
||||
|
||||
val intent = Intent(context, ReleaseNotesActivity::class.java)
|
||||
activityScenario = ActivityScenario.launch(intent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun releaseNotesView() {
|
||||
val header = String.format(
|
||||
context.getString(R.string.release_notes_header),
|
||||
context.getString(R.string.app_name)
|
||||
)
|
||||
|
||||
val footer = String.format(
|
||||
context.getString(R.string.release_notes_footer),
|
||||
context.getString(R.string.app_name)
|
||||
)
|
||||
|
||||
with(R.id.txtHeader) {
|
||||
isDisplayed(true)
|
||||
withText(header)
|
||||
}
|
||||
|
||||
R.id.releaseNotes.isDisplayed(true)
|
||||
|
||||
with(R.id.txtFooter) {
|
||||
isDisplayed(true)
|
||||
withText(footer)
|
||||
}
|
||||
|
||||
R.id.btnProceed.isDisplayed(true)
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
fun releaseNotesProceedButton() {
|
||||
R.id.btnProceed.click()
|
||||
|
||||
assertEquals(activityScenario.result.resultCode, Activity.RESULT_OK)
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
fun test_childCount() {
|
||||
R.id.releaseNotes.assertChildCount(3)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* qsfera Android client application
|
||||
*
|
||||
* @author Abel García de Prada
|
||||
*
|
||||
* Copyright (C) 2020 ownCloud GmbH.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
* <p>
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* <p>
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package eu.qsfera.android.utils
|
||||
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.action.ViewActions
|
||||
import androidx.test.espresso.action.ViewActions.scrollTo
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
|
||||
fun Int.typeText(text: String) {
|
||||
onView(withId(this)).perform(scrollTo(), ViewActions.typeText(text))
|
||||
}
|
||||
|
||||
fun Int.replaceText(text: String) {
|
||||
onView(withId(this)).perform(scrollTo(), ViewActions.replaceText(text))
|
||||
}
|
||||
|
||||
fun Int.scrollAndClick() {
|
||||
onView(withId(this)).perform(scrollTo(), ViewActions.click())
|
||||
}
|
||||
|
||||
fun Int.click() {
|
||||
onView(withId(this)).perform(ViewActions.click())
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* qsfera Android client application
|
||||
*
|
||||
* @author Juan Carlos Garrote Gascón
|
||||
*
|
||||
* Copyright (C) 2021 ownCloud GmbH.
|
||||
* <p>
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
* <p>
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* <p>
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package eu.qsfera.android.utils
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.Instrumentation
|
||||
import android.content.Intent
|
||||
import androidx.test.espresso.intent.Intents.intending
|
||||
import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction
|
||||
import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
|
||||
|
||||
fun mockIntent(
|
||||
extras: Pair<String, String>,
|
||||
resultCode: Int = Activity.RESULT_OK,
|
||||
action: String
|
||||
) {
|
||||
val result = Intent()
|
||||
result.putExtra(extras.first, extras.second)
|
||||
val intentResult = Instrumentation.ActivityResult(resultCode, result)
|
||||
intending(hasAction(action)).respondWith(intentResult)
|
||||
}
|
||||
|
||||
@JvmName("mockIntentNoExtras")
|
||||
fun mockIntent(
|
||||
resultCode: Int = Activity.RESULT_OK,
|
||||
action: String
|
||||
) {
|
||||
val result = Intent()
|
||||
val intentResult = Instrumentation.ActivityResult(resultCode, result)
|
||||
intending(hasAction(action)).respondWith(intentResult)
|
||||
}
|
||||
|
||||
fun mockIntentToComponent(
|
||||
resultCode: Int = Activity.RESULT_OK,
|
||||
packageName: String
|
||||
) {
|
||||
val result = Intent()
|
||||
val intentResult = Instrumentation.ActivityResult(resultCode, result)
|
||||
intending(hasComponent(packageName)).respondWith(intentResult)
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* qsfera Android client application
|
||||
*
|
||||
* Copyright (C) 2022 ownCloud GmbH.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package eu.qsfera.android.utils
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.test.runner.AndroidJUnitRunner
|
||||
import com.github.tmurakami.dexopener.DexOpener
|
||||
|
||||
/**
|
||||
* We need to use DexOpener for executing instrumented tests on <P Android devices,
|
||||
* as Mockk documentation suggests https://mockk.io/ANDROID.html
|
||||
*/
|
||||
class OCTestAndroidJUnitRunner : AndroidJUnitRunner() {
|
||||
override fun newApplication(cl: ClassLoader, className: String, context: Context): Application {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
|
||||
DexOpener.install(this)
|
||||
}
|
||||
return super.newApplication(cl, className, context)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* qsfera Android client application
|
||||
*
|
||||
* @author David González Verdugo
|
||||
* Copyright (C) 2020 ownCloud GmbH.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package eu.qsfera.android.utils
|
||||
|
||||
enum class Permissions(val value: Int) {
|
||||
READ_PERMISSIONS(1),
|
||||
EDIT_PERMISSIONS(3),
|
||||
SHARE_PERMISSIONS(17),
|
||||
ALL_PERMISSIONS(19),
|
||||
|
||||
// FOLDERS
|
||||
EDIT_CREATE_PERMISSIONS(5),
|
||||
EDIT_CREATE_CHANGE_PERMISSIONS(7),
|
||||
EDIT_CREATE_DELETE_PERMISSIONS(13),
|
||||
EDIT_CREATE_CHANGE_DELETE_PERMISSIONS(15),
|
||||
EDIT_CHANGE_PERMISSIONS(3),
|
||||
EDIT_CHANGE_DELETE_PERMISSIONS(11),
|
||||
EDIT_DELETE_PERMISSIONS(9),
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* qsfera Android client application
|
||||
*
|
||||
* @author David Crespo Ríos
|
||||
* Copyright (C) 2022 ownCloud GmbH.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package eu.qsfera.android.utils
|
||||
|
||||
import eu.qsfera.android.R
|
||||
import eu.qsfera.android.presentation.releasenotes.ReleaseNote
|
||||
import eu.qsfera.android.presentation.releasenotes.ReleaseNoteType
|
||||
|
||||
val releaseNotesList = listOf(
|
||||
ReleaseNote(
|
||||
title = R.string.release_notes_header,
|
||||
subtitle = R.string.release_notes_footer,
|
||||
type = ReleaseNoteType.BUGFIX
|
||||
),
|
||||
ReleaseNote(
|
||||
title = R.string.release_notes_header,
|
||||
subtitle = R.string.release_notes_footer,
|
||||
type = ReleaseNoteType.BUGFIX
|
||||
),
|
||||
ReleaseNote(
|
||||
title = R.string.release_notes_header,
|
||||
subtitle = R.string.release_notes_footer,
|
||||
type = ReleaseNoteType.ENHANCEMENT
|
||||
)
|
||||
)
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* qsfera Android client application
|
||||
*
|
||||
* @author Abel García de Prada
|
||||
*
|
||||
* Copyright (C) 2020 ownCloud GmbH.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
* <p>
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* <p>
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package eu.qsfera.android.utils.matchers
|
||||
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.ColorRes
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.test.espresso.Espresso
|
||||
import androidx.test.espresso.assertion.ViewAssertions
|
||||
import androidx.test.espresso.matcher.BoundedMatcher
|
||||
import androidx.test.espresso.matcher.RootMatchers
|
||||
import androidx.test.espresso.matcher.ViewMatchers
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||
import eu.qsfera.android.R
|
||||
import eu.qsfera.android.presentation.common.BottomSheetFragmentItemView
|
||||
import org.hamcrest.Description
|
||||
import org.hamcrest.Matcher
|
||||
|
||||
fun Int.bsfItemWithTitle(@StringRes title: Int, @ColorRes tintColor: Int?) {
|
||||
Espresso.onView(ViewMatchers.withId(this)).inRoot(RootMatchers.isDialog())
|
||||
.check(ViewAssertions.matches(withTitle(title, tintColor)))
|
||||
}
|
||||
|
||||
fun Int.bsfItemWithIcon(@DrawableRes drawable: Int, @ColorRes tintColor: Int?) {
|
||||
Espresso.onView(ViewMatchers.withId(this)).inRoot(RootMatchers.isDialog())
|
||||
.check(ViewAssertions.matches(withIcon(drawable, tintColor)))
|
||||
}
|
||||
|
||||
private fun withTitle(@StringRes title: Int, @ColorRes tintColor: Int?): Matcher<View> =
|
||||
object : BoundedMatcher<View, BottomSheetFragmentItemView>(BottomSheetFragmentItemView::class.java) {
|
||||
|
||||
override fun describeTo(description: Description) {
|
||||
description.appendText("BottomSheetFragmentItemView with text: $title")
|
||||
tintColor?.let { description.appendText(" and tint color id: $tintColor") }
|
||||
}
|
||||
|
||||
override fun matchesSafely(item: BottomSheetFragmentItemView): Boolean {
|
||||
val itemTitleView = item.findViewById<TextView>(R.id.item_title)
|
||||
val textMatches = withText(title).matches(itemTitleView)
|
||||
val textColorMatches = tintColor?.let { withTextColor(tintColor).matches(itemTitleView) } ?: true
|
||||
return textMatches && textColorMatches
|
||||
}
|
||||
}
|
||||
|
||||
private fun withIcon(@DrawableRes drawable: Int, @ColorRes tintColor: Int?): Matcher<View> =
|
||||
object : BoundedMatcher<View, BottomSheetFragmentItemView>(BottomSheetFragmentItemView::class.java) {
|
||||
|
||||
override fun describeTo(description: Description) {
|
||||
description.appendText("BottomSheetFragmentItemView with icon: $drawable")
|
||||
tintColor?.let { description.appendText(" and tint color id: $tintColor") }
|
||||
}
|
||||
|
||||
override fun matchesSafely(item: BottomSheetFragmentItemView): Boolean {
|
||||
val itemIconView = item.findViewById<ImageView>(R.id.item_icon)
|
||||
return withDrawable(drawable, tintColor).matches(itemIconView)
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* qsfera Android client application
|
||||
*
|
||||
* @author Christian Schabesberger
|
||||
* Copyright (C) 2021 ownCloud GmbH.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package eu.qsfera.android.utils.matchers
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.test.espresso.matcher.BoundedMatcher
|
||||
import org.hamcrest.Matcher
|
||||
import org.hamcrest.TypeSafeMatcher
|
||||
import org.hamcrest.Description
|
||||
|
||||
fun withChildViewCount(count: Int, childMatcher: Matcher<View>): Matcher<View> {
|
||||
return object : BoundedMatcher<View, ViewGroup>(ViewGroup::class.java) {
|
||||
override fun matchesSafely(viewGroup: ViewGroup): Boolean {
|
||||
var matchCount = 0
|
||||
for (i in 0 until viewGroup.childCount) {
|
||||
if (childMatcher.matches(viewGroup.getChildAt(i))) {
|
||||
matchCount++
|
||||
}
|
||||
}
|
||||
return matchCount == count
|
||||
}
|
||||
|
||||
override fun describeTo(description: Description?) {
|
||||
description?.appendText("ViewGroup with child-count=$count and")
|
||||
childMatcher.describeTo(description)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun nthChildOf(parentMatcher: Matcher<View>, childPosition: Int): Matcher<View> {
|
||||
return object : TypeSafeMatcher<View>() {
|
||||
override fun matchesSafely(view: View): Boolean {
|
||||
if (view.parent !is ViewGroup) {
|
||||
return parentMatcher.matches(view.parent)
|
||||
}
|
||||
val group = view.parent as ViewGroup
|
||||
return parentMatcher.matches(view.parent) && group.getChildAt(childPosition) == view
|
||||
}
|
||||
|
||||
override fun describeTo(description: Description) {
|
||||
description.appendText("with $childPosition child view of type parentMatcher")
|
||||
}
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* qsfera Android client application
|
||||
*
|
||||
* @author Abel García de Prada
|
||||
*
|
||||
* Copyright (C) 2020 ownCloud GmbH.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
* <p>
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* <p>
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package eu.qsfera.android.utils.matchers
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.ColorRes
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import org.hamcrest.Description
|
||||
import org.hamcrest.TypeSafeMatcher
|
||||
|
||||
fun withDrawable(
|
||||
@DrawableRes id: Int,
|
||||
@ColorRes tint: Int? = null,
|
||||
tintMode: PorterDuff.Mode = PorterDuff.Mode.SRC_IN
|
||||
) = object : TypeSafeMatcher<View>() {
|
||||
override fun describeTo(description: Description) {
|
||||
description.appendText("ImageView with drawable same as drawable with id $id")
|
||||
tint?.let { description.appendText(", tint color id: $tint, mode: $tintMode") }
|
||||
}
|
||||
|
||||
override fun matchesSafely(view: View): Boolean {
|
||||
val context = view.context
|
||||
val tintColor = tint?.toColor(context)
|
||||
val expectedBitmap = context.getDrawable(id)?.tinted(tintColor, tintMode)?.toBitmap()
|
||||
|
||||
return view is ImageView && view.drawable.toBitmap().sameAs(expectedBitmap)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Int.toColor(context: Context) = ContextCompat.getColor(context, this)
|
||||
|
||||
private fun Drawable.tinted(@ColorInt tintColor: Int? = null, tintMode: PorterDuff.Mode = PorterDuff.Mode.SRC_IN) =
|
||||
apply {
|
||||
setTintList(tintColor?.toColorStateList())
|
||||
setTintMode(tintMode)
|
||||
}
|
||||
|
||||
private fun Int.toColorStateList() = ColorStateList.valueOf(this)
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* qsfera Android client application
|
||||
*
|
||||
* @author Abel García de Prada
|
||||
*
|
||||
* Copyright (C) 2020 ownCloud GmbH.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
* <p>
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* <p>
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package eu.qsfera.android.utils.matchers
|
||||
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.matcher.ViewMatchers
|
||||
import androidx.test.espresso.matcher.ViewMatchers.hasChildCount
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import org.hamcrest.CoreMatchers
|
||||
|
||||
fun Int.isDisplayed(displayed: Boolean) {
|
||||
val displayMatcher = if (displayed) ViewMatchers.isDisplayed() else CoreMatchers.not(ViewMatchers.isDisplayed())
|
||||
|
||||
onView(withId(this))
|
||||
.check(matches(displayMatcher))
|
||||
}
|
||||
|
||||
fun Int.isEnabled(enabled: Boolean) {
|
||||
val enableMatcher = if (enabled) ViewMatchers.isEnabled() else CoreMatchers.not(ViewMatchers.isEnabled())
|
||||
|
||||
onView(withId(this))
|
||||
.check(matches(enableMatcher))
|
||||
}
|
||||
|
||||
fun Int.isFocusable(focusable: Boolean) {
|
||||
val focusableMatcher = if (focusable) ViewMatchers.isFocusable() else CoreMatchers.not(ViewMatchers.isFocusable())
|
||||
|
||||
onView(withId(this))
|
||||
.check(matches(focusableMatcher))
|
||||
}
|
||||
|
||||
fun Int.withText(text: String) {
|
||||
onView(withId(this))
|
||||
.check(matches(ViewMatchers.withText(text)))
|
||||
}
|
||||
|
||||
fun Int.withText(resourceId: Int) {
|
||||
onView(withId(this))
|
||||
.check(matches(ViewMatchers.withText(resourceId)))
|
||||
}
|
||||
|
||||
fun Int.withChildCountAndId(count: Int, resourceId: Int) {
|
||||
onView(withId(this))
|
||||
.check(matches(withChildViewCount(count, withId(resourceId))))
|
||||
}
|
||||
|
||||
fun Int.assertVisibility(visibility: ViewMatchers.Visibility) {
|
||||
onView(withId(this))
|
||||
.check(matches(ViewMatchers.withEffectiveVisibility(visibility)))
|
||||
}
|
||||
|
||||
fun Int.assertChildCount(childs: Int) {
|
||||
onView(withId(this))
|
||||
.check(matches(hasChildCount(childs)))
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* qsfera Android client application
|
||||
*
|
||||
* @author Juan Carlos Garrote Gascón
|
||||
*
|
||||
* Copyright (C) 2021 ownCloud GmbH.
|
||||
* <p>
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
* <p>
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* <p>
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package eu.qsfera.android.utils.matchers
|
||||
|
||||
import androidx.preference.Preference
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||
import org.junit.Assert.assertEquals
|
||||
|
||||
fun Preference.verifyPreference(
|
||||
keyPref: String,
|
||||
titlePref: String,
|
||||
summaryPref: String? = null,
|
||||
visible: Boolean,
|
||||
enabled: Boolean? = null
|
||||
) {
|
||||
if (visible) onView(withText(titlePref)).check(matches(isDisplayed()))
|
||||
summaryPref?.let {
|
||||
if (visible) onView(withText(it)).check(matches(isDisplayed()))
|
||||
assertEquals(it, summary)
|
||||
}
|
||||
assertEquals(keyPref, key)
|
||||
assertEquals(titlePref, title)
|
||||
assertEquals(visible, isVisible)
|
||||
enabled?.let {
|
||||
assertEquals(enabled, isEnabled)
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* qsfera Android client application
|
||||
*
|
||||
* @author Abel García de Prada
|
||||
*
|
||||
* Copyright (C) 2020 ownCloud GmbH.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
* <p>
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
* <p>
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package eu.qsfera.android.utils.matchers
|
||||
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.ColorRes
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.test.espresso.matcher.BoundedMatcher
|
||||
import org.hamcrest.Description
|
||||
import org.hamcrest.Matcher
|
||||
|
||||
fun withTextColor(
|
||||
@ColorRes textColor: Int
|
||||
): Matcher<View> =
|
||||
object : BoundedMatcher<View, TextView>(TextView::class.java) {
|
||||
override fun describeTo(description: Description) {
|
||||
description.appendText("TextView with text color: $textColor")
|
||||
}
|
||||
|
||||
override fun matchesSafely(view: TextView): Boolean {
|
||||
val expectedColor = ContextCompat.getColor(view.context, textColor)
|
||||
val actualColor = view.currentTextColor
|
||||
return actualColor == expectedColor
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user