SHA256
Add real native onboarding/auth/dashboard/tasks/notes/shopping, origin-bound HTTPS and encrypted AndroidKeyStore session persistence. Archive the superseded browser renderer and tests. Verify 10 JVM tests, 6 native API-35 emulator tests, lintDebug/lintRelease and debug/unsigned release APK/AAB builds; the real upstream suite passes 7 tests and exports a nonblank native fixture screenshot. Tink host-tool update removes the old generated-protobuf warning. Full module/visual parity remains explicitly incomplete.
94 lines
4.1 KiB
Kotlin
94 lines
4.1 KiB
Kotlin
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) }
|
|
}
|
|
}
|
|
}
|