package community.yuvomi.mobile import android.content.Context import android.webkit.WebView 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.closeSoftKeyboard import androidx.test.espresso.action.ViewActions.replaceText import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.hasErrorText import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.isEnabled import androidx.test.espresso.matcher.ViewMatchers.withContentDescription import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.ext.junit.runners.AndroidJUnit4 import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertNotNull import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class OnboardingTest { private val context: Context get() = ApplicationProvider.getApplicationContext() @Before fun clearConfiguration() { context.getSharedPreferences("server", Context.MODE_PRIVATE).edit().clear().commit() } @Test fun rejectsHttpAndSubpathsLocally() { ActivityScenario.launch(MainActivity::class.java).use { onView( withContentDescription("Yuvomi server URL"), ).perform(replaceText("http://family.example"), closeSoftKeyboard()) onView(withText("Connect securely")).perform(click()) onView( withContentDescription("Yuvomi server URL"), ).check(matches(hasErrorText("Use HTTPS with a valid server hostname."))) onView( withContentDescription("Yuvomi server URL"), ).perform(replaceText("https://family.example/subpath"), closeSoftKeyboard()) onView(withText("Connect securely")).perform(click()) onView( withContentDescription("Yuvomi server URL"), ).check(matches(hasErrorText("Yuvomi requires a dedicated hostname, not a subpath."))) } } @Test fun survivesRecreationWithoutLosingOnboarding() { ActivityScenario.launch(MainActivity::class.java).use { scenario -> scenario.recreate() onView(withText("Connect securely")).check(matches(isDisplayed())) onView(withContentDescription("Yuvomi server URL")).check(matches(isEnabled())) } } @Test fun webViewIsHardenedAndBackgroundingPreservesInstance() { context.getSharedPreferences("server", Context.MODE_PRIVATE).edit() .putString("origin", "https://family.example").commit() ActivityScenario.launch(MainActivity::class.java).use { scenario -> var initial: WebView? = null scenario.onActivity { activity -> fun find(view: android.view.View): WebView? { if (view is WebView) return view if (view is android.view.ViewGroup) { for (i in 0 until view.childCount) { find(view.getChildAt(i))?.let { return it } } } return null } initial = find(activity.window.decorView) val web = requireNotNull(initial) assertFalse(web.settings.allowFileAccess) assertFalse(web.settings.allowContentAccess) assertEquals( android.webkit.WebSettings.MIXED_CONTENT_NEVER_ALLOW, web.settings.mixedContentMode, ) assertTrue(web.settings.javaScriptEnabled) } scenario.moveToState(androidx.lifecycle.Lifecycle.State.CREATED) scenario.moveToState(androidx.lifecycle.Lifecycle.State.RESUMED) scenario.onActivity { assertNotNull(initial?.parent) } } } }