Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2025 TSI-mc <surinder.kumar@t-systems.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

package com.nmc.android

import android.content.Context
import android.content.res.Configuration
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.owncloud.android.R
import junit.framework.TestCase.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import java.util.Locale

/**
* Test class to verify the strings customized in this branch PR for NMC
*/
@RunWith(AndroidJUnit4::class)
class PrivacySettingsResourceTest {

private val baseContext = ApplicationProvider.getApplicationContext<Context>()

private val localizedStringMap = mapOf(
R.string.privacy_settings_intro_text to ExpectedLocalizedString(
translations = mapOf(
Locale.ENGLISH to "To optimize your app, we collect anonymous data. For this we use software solutions of different partners. We would like to give you full transparency and decision-making power over the processing and collection of your anonymized usage data. You can also change your settings at any time later in the app settings under data protection. Please note, however, that data collection makes a considerable contribution to the optimization of this app and you prevent this optimization by preventing data transmission.",
Locale.GERMAN to "Zur Optimierung unserer App erfassen wir anonymisierte Daten. Hierzu nutzen wir Software Lösungen verschiedener Partner. Wir möchten Ihnen volle Transparenz und Entscheidungsgewalt über die Verarbeitung und Erfassung Ihrer anonymisierten Nutzungsdaten geben. Ihre Einstellungen können Sie auch später jederzeit in den Einstellungen unter Datenschutz ändern. Bitte beachten Sie jedoch, dass die Datenerfassungen einen erheblichen Beitrag zur Optimierung dieser App leisten und Sie diese Optimierungen durch die Unterbindung der Datenübermittlung verhindern."
)
),
R.string.required_data_collection to ExpectedLocalizedString(
translations = mapOf(
Locale.ENGLISH to "Required data collection",
Locale.GERMAN to "Erforderliche Datenerfassung"
)
),
R.string.data_collection_info to ExpectedLocalizedString(
translations = mapOf(
Locale.ENGLISH to "The collection of this data is necessary to be able to use essential functions of the app.",
Locale.GERMAN to "Die Erfassung dieser Daten ist notwendig, um wesentliche Funktionen der App nutzen zu können."
)
),
R.string.data_analysis to ExpectedLocalizedString(
translations = mapOf(
Locale.ENGLISH to "Analysis-data acquisition for the design",
Locale.GERMAN to "Analyse-Datenerfassung zur bedarfsgerechten Gestaltung"
)
),
R.string.data_analysis_info to ExpectedLocalizedString(
translations = mapOf(
Locale.ENGLISH to "This data helps us to optimize the app usage for you and to identify system crashes and errors more quickly.",
Locale.GERMAN to "Diese Daten helfen uns, die App Nutzung für Sie zu optimieren und Systemabstürze und Fehler schneller zu identifizieren."
)
),
R.string.login_privacy_settings_intro_text to ExpectedLocalizedString(
translations = mapOf(
Locale.ENGLISH to "This app uses Cookies and similar technologies (tools). By " +
"clicking Accept, you accept the processing and also the Transfer of your data to third parties. The data will " +
"be used for Analysis, retargeting and to Display personalized Content and Advertising on sites and " +
"third-party sites. You can find further information, including Information on data processing by third-party " +
"Providers, in the Settings and in our %s. You can %s the use of the Tools or customize them at any time in the " +
"%s.",
Locale.GERMAN to "Diese App verwendet Cookies und ähnliche Technologien (Tools). " +
"Mit einem Klick auf Zustimmen akzeptieren Sie die Verarbeitung und auch die Weitergabe Ihrer Daten an " +
"Drittanbieter. Die Daten werden für Analysen, Retargeting und zur Ausspielung von personalisierten Inhalten " +
"und Werbung auf Seiten der Telekom, sowie auf Drittanbieterseiten genutzt. Weitere Informationen, auch zur " +
"Datenverarbeitung durch Drittanbieter, finden Sie in den Einstellungen sowie in unseren %s. Sie können die " +
"Verwendung der Tools %s oder jederzeit über ihre %s anpassen."
)
),
R.string.login_privacy_settings_header to ExpectedLocalizedString(
translations = mapOf(
Locale.ENGLISH to "Privacy Settings",
Locale.GERMAN to "Datenschutz-Einstellungen"
)
),
R.string.common_accept to ExpectedLocalizedString(
translations = mapOf(
Locale.ENGLISH to "Accept",
Locale.GERMAN to "Akzeptieren"
)
),
R.string.save_settings to ExpectedLocalizedString(
translations = mapOf(
Locale.ENGLISH to "Save Settings",
Locale.GERMAN to "Einstellungen speichern"
)
),
R.string.login_privacy_policy to ExpectedLocalizedString(
translations = mapOf(
Locale.ENGLISH to "Privacy Policy",
Locale.GERMAN to "Datenschutzhinweise"
)
),
R.string.login_privacy_reject to ExpectedLocalizedString(
translations = mapOf(
Locale.ENGLISH to "reject",
Locale.GERMAN to "ablehnen"
)
),
R.string.login_privacy_settings to ExpectedLocalizedString(
translations = mapOf(
Locale.ENGLISH to "Settings",
Locale.GERMAN to "Einstellungen"
)
),
R.string.sourcecode_url to ExpectedLocalizedString(
translations = mapOf(
Locale.ENGLISH to "https://static.magentacloud.de/licences/android.html",
)
),
)

@Test
fun verifyLocalizedStrings() {
localizedStringMap.forEach { (stringRes, expected) ->
expected.translations.forEach { (locale, expectedText) ->

val config = Configuration(baseContext.resources.configuration)
config.setLocale(locale)

val localizedContext = baseContext.createConfigurationContext(config)
val actualText = localizedContext.getString(stringRes)

assertEquals(
"Mismatch for ${baseContext.resources.getResourceEntryName(stringRes)} in $locale",
expectedText,
actualText
)
}
}
}

data class ExpectedLocalizedString(val translations: Map<Locale, String>)
}
115 changes: 115 additions & 0 deletions app/src/androidTest/java/com/nmc/android/ui/ClickableSpanTestHelper.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package com.nmc.android.ui

import android.text.Spannable
import android.text.style.ClickableSpan
import android.view.View
import android.widget.TextView
import androidx.test.espresso.UiController
import androidx.test.espresso.ViewAction
import androidx.test.espresso.ViewInteraction
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.BoundedMatcher
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import org.hamcrest.Description
import org.hamcrest.Matcher

object ClickableSpanTestHelper {

/**
* method to get clickable span form a text view
* example: val clickableSpan = getClickableSpan("Link text", onView(withId(R.id.text_id)))
*/
fun getClickableSpan(spanText: String, matcher: ViewInteraction?): ClickableSpan? {
val clickableSpans = arrayOf<ClickableSpan?>(null)

// Get the SpannableString from the TextView
matcher?.check(matches(isDisplayed()))
matcher?.perform(object : ViewAction {
override fun getConstraints(): Matcher<View> {
return isAssignableFrom(TextView::class.java)
}

override fun getDescription(): String {
return "get text from TextView"
}

override fun perform(uiController: UiController, view: View) {
val textView = view as TextView
val text = textView.text
if (text is Spannable) {
val spans = text.getSpans(
0, text.length,
ClickableSpan::class.java
)
for (span in spans) {
val start = text.getSpanStart(span)
val end = text.getSpanEnd(span)
val spanString = text.subSequence(start, end).toString()
if (spanString == spanText) {
clickableSpans[0] = span
return
}
}
}
throw java.lang.RuntimeException("ClickableSpan not found")
}
})
return clickableSpans[0]
}

/**
* perform click on the spanned string
* @link getClickableSpan() method to get clickable span
*/
fun performClickSpan(clickableSpan: ClickableSpan?): ViewAction {
return object : ViewAction {
override fun getConstraints(): Matcher<View> {
return ViewMatchers.isAssignableFrom(TextView::class.java)
}

override fun getDescription(): String {
return "clicking on a span"
}

override fun perform(uiController: UiController, view: View) {
val textView = view as TextView
val spannable = textView.text as Spannable
val spans = spannable.getSpans(
0, spannable.length,
ClickableSpan::class.java
)
for (span in spans) {
if (span == clickableSpan) {
span.onClick(textView)
return
}
}
throw RuntimeException("ClickableSpan not found")
}
}
}

fun verifyClickSpan(clickableSpan: ClickableSpan?): Matcher<View?> {
return object : BoundedMatcher<View?, TextView>(TextView::class.java) {
override fun describeTo(description: Description) {
description.appendText("clickable span")
}

override fun matchesSafely(textView: TextView): Boolean {
val spannable = textView.text as Spannable
val spans = spannable.getSpans(
0, spannable.length,
ClickableSpan::class.java
)
for (span in spans) {
if (span == clickableSpan) {
return true
}
}
return false
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package com.nmc.android.ui

import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.Espresso.pressBack
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.isClickable
import androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.nextcloud.client.preferences.AppPreferencesImpl
import com.nmc.android.ui.ClickableSpanTestHelper.getClickableSpan
import com.owncloud.android.AbstractIT
import com.owncloud.android.R
import com.owncloud.android.ui.activity.ExternalSiteWebView
import com.owncloud.android.ui.activity.FileDisplayActivity
import org.junit.Assert.*
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class LoginPrivacySettingsActivityIT : AbstractIT() {

@get:Rule
val activityRule = ActivityScenarioRule(LoginPrivacySettingsActivity::class.java)

@Test
fun verifyNothingHappensOnBackPress() {
pressBack()
shortSleep()

//check any one view to check the activity is not destroyed
onView(withId(R.id.tv_privacy_setting_title)).check(matches(isCompletelyDisplayed()))
}

@Test
fun verifyUIElements() {
onView(withId(R.id.ic_privacy)).check(matches(isCompletelyDisplayed()))

onView(withId(R.id.tv_privacy_setting_title)).check(matches(isCompletelyDisplayed()))

onView(withId(R.id.tv_login_privacy_intro_text)).check(matches(isCompletelyDisplayed()))

onView(withId(R.id.privacy_accept_btn)).check(matches(isCompletelyDisplayed()))
onView(withId(R.id.privacy_accept_btn)).check(matches(isClickable()))
}

@Test
fun verifyAcceptButtonRedirection() {
Intents.init()
onView(withId(R.id.privacy_accept_btn)).perform(click())

//check if the policy action saved correct --> 2 for Accept action
assertEquals(2, AppPreferencesImpl.fromContext(targetContext).privacyPolicyAction)

intended(hasComponent(FileDisplayActivity::class.java.canonicalName))
Intents.release()
}

@Test
fun verifySettingsTextClick() {
Intents.init()
val settingsClickableSpan = getClickableSpan("Settings", onView(withId(R.id.tv_login_privacy_intro_text)))
onView(withId(R.id.tv_login_privacy_intro_text)).perform(
ClickableSpanTestHelper.performClickSpan(
settingsClickableSpan
)
)
intended(hasComponent(PrivacySettingsActivity::class.java.canonicalName))
Intents.release()
}

@Test
fun verifyPrivacyPolicyTextClick() {
Intents.init()
val privacyPolicyClickableSpan =
getClickableSpan("Privacy Policy", onView(withId(R.id.tv_login_privacy_intro_text)))
onView(withId(R.id.tv_login_privacy_intro_text)).perform(
ClickableSpanTestHelper.performClickSpan(
privacyPolicyClickableSpan
)
)
intended(hasComponent(ExternalSiteWebView::class.java.canonicalName))
Intents.release()
}

@Test
fun verifyRejectTextClick() {
Intents.init()
val rejectClickableSpan =
getClickableSpan("reject", onView(withId(R.id.tv_login_privacy_intro_text)))
onView(withId(R.id.tv_login_privacy_intro_text)).perform(
ClickableSpanTestHelper.performClickSpan(
rejectClickableSpan
)
)

//check if the policy action saved correct --> 1 for Reject action
assertEquals(1, AppPreferencesImpl.fromContext(targetContext).privacyPolicyAction)

intended(hasComponent(FileDisplayActivity::class.java.canonicalName))
Intents.release()
}
}
Loading