SHA256
feat(android): replace WebView with tested native Compose API slice
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.
This commit is contained in:
1 parent
81973c3c45
commit
8c480ddca3
28 files changed
+2628
-742
No files matched your search
@@ -0,0 +1,93 @@
|
||||
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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user