Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
Kotlin
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299package social.grain
import androidx.test.ext.junit.runners.AndroidJUnit4import org.junit.Afterimport org.junit.Assert.assertEqualsimport org.junit.Assert.assertFalseimport org.junit.Assert.assertTrueimport org.junit.Beforeimport org.junit.Ruleimport org.junit.Testimport org.junit.runner.RunWithimport social.grain.data.api.ActorListItemimport social.grain.feature.search.SearchTabimport social.grain.feature.search.SearchViewModelimport social.grain.support.Fixturesimport social.grain.support.Harnessimport social.grain.support.MainDispatcherRuleimport social.grain.support.applySnapshotimport social.grain.support.awaitCallimport social.grain.support.awaitValueimport social.grain.support.jsonimport social.grain.support.settleimport social.grain.support.type
/** * Search, which is two searches behind one field. * * The query is debounced and the collector cancels the request in flight when * the next keystroke arrives, so what this pins down is that a slow answer for * "fo" can't land after a fast one for "fog" — and that recents are per-account * and survive being edited. */@RunWith(AndroidJUnit4::class)class SearchViewModelTest {
@get:Rule val mainDispatcher = MainDispatcherRule()
private lateinit var harness: Harness
@Before fun setUp() { harness = Harness() harness.server .on("dev.hatk.describeLabels", """{"definitions":[]}""") .on( "social.grain.unspecced.searchGalleries", """{"items":[${json(Fixtures.gallery(title = "Fog"))}]}""", ) .on( "social.grain.unspecced.searchProfiles", """{"items":[{"did":"did:plc:bob","handle":"bob.test","displayName":"Bob"}]}""", ) }
@After fun tearDown() { harness.recentSearches.switchAccount(harness.did) harness.recentSearches.clear() harness.tearDown() }
private fun viewModel(): SearchViewModel { harness.recentSearches.switchAccount(harness.did) harness.recentSearches.clear() return SearchViewModel( client = harness.client, auth = harness.auth, recents = harness.recentSearches, labelDefsCache = harness.labelDefinitions, ) }
@Test fun typingAQuerySearchesGalleriesOnceTheTypingStops() { val model = viewModel() model.queryState.type("fog")
val state = model.state.awaitValue("the results") { it.hasSearched } assertEquals("Fog", state.galleries.single().title) assertEquals("fog", state.query) assertFalse(state.isSearching) assertFalse(state.isEmptyQuery) assertEquals("fog", harness.server.lastCall("social.grain.unspecced.searchGalleries").params["q"]) }
@Test fun clearingTheFieldEmptiesTheResultsWithoutSearching() { val model = viewModel() model.queryState.type("fog") model.state.awaitValue("the results") { it.hasSearched } val before = harness.server.countOf("social.grain.unspecced.searchGalleries")
model.clearQuery() applySnapshot()
val state = model.state.awaitValue("the results to clear") { !it.hasSearched } assertTrue(state.galleries.isEmpty()) assertTrue(state.isEmptyQuery) assertEquals(before, harness.server.countOf("social.grain.unspecced.searchGalleries")) }
@Test fun theProfilesTabSearchesItsOwnEndpoint() { val model = viewModel() model.queryState.type("bob") model.state.awaitValue("the gallery results") { it.hasSearched }
model.selectTab(SearchTab.PROFILES)
val state = model.state.awaitValue("the profile results") { it.profiles.isNotEmpty() } assertEquals(SearchTab.PROFILES, state.tab) assertEquals("Bob", state.profiles.single().displayName) }
@Test fun switchingToTheTabAlreadyShowingDoesNothing() { val model = viewModel() model.queryState.type("fog") model.state.awaitValue("the results") { it.hasSearched } val before = harness.server.countOf("social.grain.unspecced.searchGalleries")
model.selectTab(SearchTab.GALLERIES) settle()
assertEquals(before, harness.server.countOf("social.grain.unspecced.searchGalleries")) }
@Test fun switchingTabsWithAnEmptyFieldFetchesNothing() { val model = viewModel() model.selectTab(SearchTab.PROFILES) settle()
assertEquals(0, harness.server.countOf("social.grain.unspecced.searchProfiles")) }
@Test fun aSearchThatFailsStillCountsAsHavingSearched() { harness.server.on("social.grain.unspecced.searchGalleries", """{"error":"no"}""", code = 500) val model = viewModel() model.queryState.type("fog")
val state = model.state.awaitValue("the failed search") { it.hasSearched } assertFalse(state.isSearching) assertTrue(state.galleries.isEmpty()) }
@Test fun committingASearchRemembersTheTerm() { val model = viewModel() model.queryState.type("fog") model.commitQuery()
assertEquals(listOf("fog"), model.state.value.recentTerms) assertTrue(model.state.value.hasRecents) }
@Test fun anEmptyQueryIsNotRemembered() { val model = viewModel() model.commitQuery() assertTrue(model.state.value.recentTerms.isEmpty()) assertFalse(model.state.value.hasRecents) }
@Test fun pickingARecentTermRunsItAgain() { val model = viewModel() model.searchFor("fog") applySnapshot()
val state = model.state.awaitValue("the results") { it.hasSearched } assertEquals("fog", state.query) assertEquals(listOf("fog"), state.recentTerms) }
@Test fun recentTermsAndProfilesCanBeRemovedOneAtATimeOrAllAtOnce() { val model = viewModel() model.searchFor("fog") applySnapshot() model.state.awaitValue("the results") { it.hasSearched } model.rememberProfile( ActorListItem(did = "did:plc:bob", handle = "bob.test", displayName = "Bob"), )
assertEquals(1, model.state.value.recentProfiles.size) assertEquals("Bob", model.state.value.recentProfiles.single().displayName)
model.removeRecentTerm("fog") assertTrue(model.state.value.recentTerms.isEmpty())
model.removeRecentProfile("did:plc:bob") assertTrue(model.state.value.recentProfiles.isEmpty())
model.searchFor("mist") applySnapshot() model.state.awaitValue("the second search") { it.recentTerms.isNotEmpty() } model.clearRecents() assertFalse(model.state.value.hasRecents) }
@Test fun favoritingAResultFlipsTheHeartAndKeepsTheRecordUri() { harness.server.on("dev.hatk.createRecord", """{"uri":"at://did:plc:me/fav/1"}""") val model = viewModel() model.queryState.type("fog") val gallery = model.state.awaitValue("the results") { it.hasSearched }.galleries.single()
model.toggleFavorite(gallery.uri)
assertEquals(13, model.state.value.galleries.single().favCount) model.state.awaitValue("the record uri") { it.galleries.single().viewer?.fav == "at://did:plc:me/fav/1" } }
@Test fun aFavoriteThatFailsIsPutBack() { harness.server.on("dev.hatk.createRecord", """{"error":"no"}""", code = 500) val model = viewModel() model.queryState.type("fog") val gallery = model.state.awaitValue("the results") { it.hasSearched }.galleries.single()
model.toggleFavorite(gallery.uri)
val state = model.state.awaitValue("the revert") { !it.galleries.single().isFavorited } assertEquals(12, state.galleries.single().favCount) }
@Test fun unfavoritingAResultDeletesTheRecord() { harness.server.on( "social.grain.unspecced.searchGalleries", """{"items":[${json(Fixtures.gallery(favUri = "at://did:plc:me/social.grain.favorite/f1"))}]}""", ) val model = viewModel() model.queryState.type("fog") val gallery = model.state.awaitValue("the results") { it.hasSearched }.galleries.single()
model.toggleFavorite(gallery.uri)
assertTrue(harness.server.awaitCall("dev.hatk.deleteRecord").body.contains("\"rkey\":\"f1\"")) }
@Test fun favoritingSomethingNotInTheResultsIsIgnored() { val model = viewModel() model.queryState.type("fog") model.state.awaitValue("the results") { it.hasSearched }
model.toggleFavorite("at://nothing") settle()
assertEquals(0, harness.server.countOf("dev.hatk.createRecord")) }
@Test fun aCommentCountFollowsTheSheetOpenedOverAResult() { val model = viewModel() model.queryState.type("fog") val gallery = model.state.awaitValue("the results") { it.hasSearched }.galleries.single()
model.setCommentCount(gallery.uri, 11)
assertEquals(11, model.state.value.galleries.single().commentCount) }
@Test fun deletingAResultRemovesTheCardOnceTheWriteLands() { val model = viewModel() model.queryState.type("fog") val gallery = model.state.awaitValue("the results") { it.hasSearched }.galleries.single()
model.deleteGallery(gallery.uri)
model.state.awaitValue("the card to go") { it.galleries.isEmpty() } }
@Test fun aResultDeleteThatFailsLeavesTheCard() { harness.server.on( "social.grain.unspecced.deleteGallery", """{"error":"no"}""", code = 500, ) val model = viewModel() model.queryState.type("fog") val gallery = model.state.awaitValue("the results") { it.hasSearched }.galleries.single()
model.deleteGallery(gallery.uri) settle() Thread.sleep(200)
assertEquals(1, model.state.value.galleries.size) }}