LockScreenRoutingTest.kt

4.1 KB · raw

1package com.peachpasswords.ui.lock23import com.peachpasswords.ui.PeachNav4import org.junit.Assert.assertEquals5import org.junit.Assert.assertTrue6import org.junit.Test78/**9 * Pins the unlock-routing decision in [routeOnUnlock]. The LaunchedEffect body in10 * [LockScreen] is a thin dispatcher over this function, so the priority order11 * (created > autofill > postUnlock) and the lazy `postUnlock` semantics are the12 * only thing worth locking down here.13 *14 * Background: a previous v2.14.5 guard put the autofill-pending branch inside15 * LockScreen and was keyed on both `state.unlocked` and `autofillPending`. The16 * NavHost's autofill LaunchedEffect consumed `newEntryRequest` synchronously,17 * which flipped `autofillPending` to false mid-unlock and re-fired LockScreen's18 * effect — landing the user on the vault list instead of the new-entry editor.19 * The fix keyed LockScreen's effect on `state.unlocked` only; these tests pin20 * the decision so a future reorder can't silently re-introduce the regression.21 */22class LockScreenRoutingTest {2324    private fun state(unlocked: Boolean = true, created: Boolean = false) =25        LockUiState(unlocked = unlocked, created = created)2627    @Test fun created_vault_always_routes_to_recovery_setup_regardless_of_autofill() {28        // A freshly created vault must walk through recovery-word setup before29        // the list — even if an autofill nav request is queued, the user just30        // made the vault and the recovery phrase is the immediate priority.31        val dest = PeachNav.VaultList // any sentinel; must not be consulted32        assertEquals(33            UnlockRoute.RecoverySetup,34            routeOnUnlock(state(created = true), autofillPending = false, shortcutPending = false) { dest },35        )36        assertEquals(37            UnlockRoute.RecoverySetup,38            routeOnUnlock(state(created = true), autofillPending = true, shortcutPending = false) { dest },39        )40    }4142    @Test fun autofill_pending_unlock_defers_to_the_navhost() {43        // The regression: when autofillPending was true at the unlock transition,44        // LockScreen must NOT route to the post-unlock destination or the NavHost's45        // editor navigation gets clobbered with popUpTo(0).46        var postUnlockCalls = 047        val route = routeOnUnlock(state(created = false), autofillPending = true, shortcutPending = false) {48            postUnlockCalls++49            PeachNav.VaultList50        }51        assertEquals(UnlockRoute.Defer, route)52        assertEquals(53            "Defer must not consult postUnlock — lazy lookup matters when the " +54                "destination is expensive (lastViewed → resolve → firstActive)",55            0, postUnlockCalls,56        )57    }5859    @Test fun launcher_shortcut_unlock_defers_to_the_navhost() {60        var postUnlockCalls = 061        val route = routeOnUnlock(state(created = false), autofillPending = false, shortcutPending = true) {62            postUnlockCalls++63            PeachNav.VaultList64        }65        assertEquals(UnlockRoute.Defer, route)66        assertEquals(0, postUnlockCalls)67    }6869    @Test fun ordinary_unlock_routes_through_the_viewmodel_destination() {70        // The base case: a normal unlock lands on the destination the ViewModel71        // computed (last-viewed entry, or the vault list, or recovery setup).72        val expected = PeachNav.EntryDetail("abc-123")73        val route = routeOnUnlock(state(created = false), autofillPending = false, shortcutPending = false) { expected }74        assertTrue(route is UnlockRoute.PostUnlock)75        assertEquals(expected, (route as UnlockRoute.PostUnlock).destination)76    }7778    @Test fun postUnlock_lookup_runs_exactly_once_on_ordinary_unlock() {79        // Sanity: postUnlock is invoked exactly once per ordinary unlock so the80        // ViewModel-side resolution work (which can be non-trivial) is bounded.81        var calls = 082        val route = routeOnUnlock(state(created = false), autofillPending = false, shortcutPending = false) {83            calls++84            PeachNav.VaultList85        }86        assertTrue(route is UnlockRoute.PostUnlock)87        assertEquals(1, calls)88    }89}