Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
Kotlin
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205package social.grain
import androidx.test.ext.junit.runners.AndroidJUnit4import org.junit.Afterimport org.junit.Assert.assertEqualsimport org.junit.Assert.assertFalseimport org.junit.Assert.assertNullimport org.junit.Assert.assertTrueimport org.junit.Beforeimport org.junit.Ruleimport org.junit.Testimport org.junit.runner.RunWithimport social.grain.data.local.Appearanceimport social.grain.data.local.AppearanceStoreimport social.grain.data.push.PushManagerimport social.grain.feature.settings.SettingsViewModelimport social.grain.support.Harnessimport social.grain.support.MainDispatcherRuleimport social.grain.support.appContextimport social.grain.support.awaitCallimport social.grain.support.awaitValueimport social.grain.support.settle
/** * Settings: three switches, an account, and the two ways out of it. * * The upload defaults live on the server, so each switch is a write that can * fail — and a switch that stays flipped after a failed write is a lie about * what the account actually holds. */@RunWith(AndroidJUnit4::class)class SettingsViewModelTest {
@get:Rule val mainDispatcher = MainDispatcherRule()
private lateinit var harness: Harness private lateinit var appearance: AppearanceStore private lateinit var push: PushManager
@Before fun setUp() { harness = Harness() appearance = AppearanceStore(appContext) appearance.set(Appearance.AUTOMATIC) push = PushManager( appContext, harness.client, harness.auth, social.grain.data.auth.SecureStore(appContext), harness.scope, ) harness.server.on( "dev.hatk.getPreferences", """{"preferences":{"includeExif":false,"includeLocation":true}}""", ) }
@After fun tearDown() { appearance.set(Appearance.AUTOMATIC) harness.tearDown() }
private fun viewModel() = SettingsViewModel(harness.client, harness.auth, push, appearance)
@Test fun theSignedInAccountAndItsUploadDefaultsAreShown() { val model = viewModel() val state = model.state.awaitValue("the preferences") { it.did != null && !it.includeExif }
assertEquals(harness.handle, state.handle) assertEquals(harness.did, state.did) assertFalse(state.includeExif) assertTrue(state.includeLocation) assertEquals(Appearance.AUTOMATIC, state.appearance) }
@Test fun preferencesTheServerHasNeverStoredDefaultToOn() { harness.server.on("dev.hatk.getPreferences", """{"preferences":{}}""") val model = viewModel() val state = model.state.awaitValue("the account") { it.did != null }
assertTrue(state.includeExif) assertTrue(state.includeLocation) }
@Test fun aFailedPreferenceReadLeavesTheDefaultsAlone() { harness.server.on("dev.hatk.getPreferences", """{"error":"no"}""", code = 500) val model = viewModel() val state = model.state.awaitValue("the account") { it.did != null }
assertTrue(state.includeExif) assertNull(state.errorMessage) }
@Test fun theAppearanceChoiceIsRememberedAndObserved() { val model = viewModel() model.state.awaitValue("the account") { it.did != null }
model.setAppearance(Appearance.DARK)
assertEquals( Appearance.DARK, model.state.awaitValue("the appearance") { it.appearance == Appearance.DARK }.appearance, ) assertEquals( "It has to survive the process, not just the screen", Appearance.DARK, AppearanceStore(appContext).appearance.value, ) }
@Test fun aSwitchFlipsImmediatelyAndWritesThePreference() { val model = viewModel() // Not just the account: the stored preferences land after it, and would // otherwise overwrite the flip under test. model.state.awaitValue("the preferences") { it.did != null && !it.includeExif }
model.setIncludeExif(true) assertTrue("The switch moves before the write", model.state.value.includeExif)
val call = harness.server.awaitCall("dev.hatk.putPreference") assertTrue(call.body.contains("\"key\":\"includeExif\"")) assertTrue(call.body.contains("\"value\":true")) }
@Test fun aSwitchThatCannotBeSavedFlipsBackAndSaysSo() { harness.server.on("dev.hatk.putPreference", """{"error":"no"}""", code = 500) val model = viewModel() model.state.awaitValue("the preferences") { it.did != null && !it.includeExif }
model.setIncludeLocation(false)
val state = model.state.awaitValue("the revert") { it.errorMessage != null } assertTrue(state.includeLocation) assertEquals("Couldn't save that setting.", state.errorMessage)
model.dismissError() assertNull(model.state.value.errorMessage) }
@Test fun signingOutClearsTheSessionAndTakesTheDeviceOffPush() { val model = viewModel() model.state.awaitValue("the account") { it.did != null }
model.signOut()
model.state.awaitValue("the sign-out") { it.did == null } assertFalse(harness.auth.state.value.isAuthenticated) }
@Test fun deletingTheAccountSignsOutOnceTheServerAgrees() { val model = viewModel() model.state.awaitValue("the account") { it.did != null }
model.deleteAccount()
val state = model.state.awaitValue("the deletion") { it.isDeleted } assertFalse(state.isDeleting) assertFalse(harness.auth.state.value.isAuthenticated) harness.server.awaitCall("social.grain.unspecced.deleteAccount") }
@Test fun aDeletionThatFailsLeavesTheAccountSignedIn() { harness.server.on( "social.grain.unspecced.deleteAccount", """{"error":"no"}""", code = 500, ) val model = viewModel() model.state.awaitValue("the account") { it.did != null }
model.deleteAccount()
val state = model.state.awaitValue("the error") { it.errorMessage != null } assertFalse(state.isDeleted) assertEquals("Couldn't delete the account. Try again.", state.errorMessage) assertTrue(harness.auth.state.value.isAuthenticated) }
@Test fun aSignedOutViewerCannotDeleteAnAccount() { harness.tearDown() harness = Harness(signedIn = false) val model = viewModel() settle()
model.deleteAccount()
val state = model.state.awaitValue("the error") { it.errorMessage != null } assertEquals("You're signed out.", state.errorMessage) assertEquals(0, harness.server.countOf("social.grain.unspecced.deleteAccount")) }}