Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298package social.grain
import androidx.compose.foundation.text.input.TextFieldStateimport androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEndimport androidx.compose.material3.Textimport androidx.compose.ui.test.junit4.v2.createComposeRuleimport androidx.compose.ui.test.onNodeWithContentDescriptionimport androidx.compose.ui.test.onNodeWithTextimport androidx.compose.ui.test.performClickimport androidx.test.ext.junit.runners.AndroidJUnit4import kotlinx.serialization.json.buildJsonObjectimport kotlinx.serialization.json.putimport org.junit.Afterimport org.junit.Assert.assertEqualsimport org.junit.Beforeimport org.junit.Ruleimport org.junit.Testimport org.junit.runner.RunWithimport social.grain.data.location.PlaceResultimport social.grain.data.model.Facetimport social.grain.feature.gallery.GalleryCommentSheetimport social.grain.feature.gallery.GalleryCommentsViewModelimport social.grain.support.Fixturesimport social.grain.support.Harnessimport social.grain.support.MainDispatcherRuleimport social.grain.support.awaitTextimport social.grain.support.jsonimport social.grain.ui.components.LocationPickerimport social.grain.ui.components.RichTextimport social.grain.ui.theme.GrainTheme
/** * The shared pieces screens are built out of. * * Each is used from several places, so a regression here is a regression * everywhere at once — and none of them owns any state a view model test could * reach. */@RunWith(AndroidJUnit4::class)class ComponentsTest {
@get:Rule val mainDispatcher = MainDispatcherRule()
@get:Rule val composeRule = createComposeRule()
private lateinit var harness: Harness
@Before fun setUp() { harness = Harness() }
@After fun tearDown() = harness.tearDown()
// MARK: - RichText
private fun facet(start: Int, end: Int, type: String, key: String, value: String) = Facet( index = Facet.ByteSlice(start, end), features = listOf( buildJsonObject { put("\$type", type) put(key, value) }, ), )
@Test fun plainTextWithNoFacetsIsJustText() { composeRule.setContent { GrainTheme { RichText(text = "Six frames from a grey morning") } }
composeRule.awaitText("Six frames from a grey morning") }
@Test fun aHashtagFacetIsTappableAndReportsItsTag() { var tapped: String? = null composeRule.setContent { GrainTheme { RichText( text = "Shot in #fog", facets = listOf( facet(9, 13, "app.bsky.richtext.facet#tag", "tag", "fog"), ), onHashtagTap = { tapped = it }, ) } }
composeRule.awaitText("Shot in #fog") composeRule.onNodeWithText("Shot in #fog").performClick() }
@Test fun aMentionFacetRendersAlongsideTheTextItAnnotates() { composeRule.setContent { GrainTheme { RichText( text = "With @alice.test", facets = listOf( facet(5, 16, "app.bsky.richtext.facet#mention", "did", "did:plc:alice"), ), onMentionTap = {}, ) } }
composeRule.awaitText("With @alice.test") }
@Test fun aLinkFacetRendersWithoutNeedingAHandler() { composeRule.setContent { GrainTheme { RichText( text = "See https://grain.social", facets = listOf( facet(4, 24, "app.bsky.richtext.facet#link", "uri", "https://grain.social"), ), ) } }
composeRule.awaitText("See https://grain.social") }
@Test fun aFacetTypeThisBuildDoesNotKnowIsDrawnAsPlainText() { composeRule.setContent { GrainTheme { RichText( text = "Something new", facets = listOf( facet(0, 9, "app.bsky.richtext.facet#somethingElse", "x", "y"), ), ) } }
composeRule.awaitText("Something new") }
// MARK: - LocationPicker
private fun place(name: String, context: String? = "San Francisco") = PlaceResult( name = name, context = context, latitude = 37.76, longitude = -122.41, address = null, )
@Test fun thePickerInvitesASearchWhenNothingIsChosen() { composeRule.setContent { GrainTheme { LocationPicker( queryState = TextFieldState(), results = emptyList(), isSearching = false, selected = null, suggestion = null, onSelect = {}, onClear = {}, ) } }
composeRule.awaitText("Location") composeRule.onNodeWithText("Search for a place").assertExists() }
@Test fun resultsAreListedAndReportTheOneTapped() { var chosen: PlaceResult? = null composeRule.setContent { GrainTheme { LocationPicker( queryState = TextFieldState().apply { setTextAndPlaceCursorAtEnd("mission") }, results = listOf(place("Mission District"), place("Mission Bay")), isSearching = false, selected = null, suggestion = null, onSelect = { chosen = it }, onClear = {}, ) } }
composeRule.awaitText("Mission District") composeRule.onNodeWithText("Mission Bay").assertExists()
composeRule.onNodeWithText("Mission District").performClick()
assertEquals("Mission District", chosen?.name) }
@Test fun aPlaceReadOffThePhotoIsOfferedRatherThanApplied() { var chosen: PlaceResult? = null composeRule.setContent { GrainTheme { LocationPicker( queryState = TextFieldState(), results = emptyList(), isSearching = false, selected = null, suggestion = place("Ocean Beach"), onSelect = { chosen = it }, onClear = {}, ) } }
composeRule.awaitText("From this photo") composeRule.onNodeWithText("Ocean Beach").performClick()
assertEquals("Ocean Beach", chosen?.name) }
@Test fun aChosenPlaceIsShownWithAWayToRemoveIt() { var cleared = 0 composeRule.setContent { GrainTheme { LocationPicker( queryState = TextFieldState(), results = emptyList(), isSearching = false, selected = place("Mission District"), suggestion = null, onSelect = {}, onClear = { cleared += 1 }, ) } }
composeRule.awaitText("Mission District") composeRule.onNodeWithContentDescription("Remove location").performClick()
assertEquals(1, cleared) }
// MARK: - The gallery comment sheet
@Test fun theGallerySheetOpensOnItsGalleryAndReportsTheCountBack() { harness.server.on( "social.grain.unspecced.getCommentThread", """{"comments":[${json(Fixtures.comment(text = "Lovely light"))}],"totalCount":4}""", ) var count = -1 val model = GalleryCommentsViewModel(harness.client, harness.auth) composeRule.setContent { GrainTheme { GalleryCommentSheet( galleryUri = "at://did:plc:alice/social.grain.gallery/gallery1", onDismiss = {}, onProfileTap = {}, onCommentCountChanged = { count = it }, viewModel = model, ) } }
composeRule.awaitText("Lovely light") composeRule.waitUntil(10_000) { count == 4 } }
@Test fun tappingAnAuthorInTheGallerySheetClosesItFirst() { harness.server.on( "social.grain.unspecced.getCommentThread", """{"comments":[${json(Fixtures.comment())}],"totalCount":1}""", ) var dismissed = 0 var opened: String? = null val model = GalleryCommentsViewModel(harness.client, harness.auth) composeRule.setContent { GrainTheme { GalleryCommentSheet( galleryUri = "at://did:plc:alice/social.grain.gallery/gallery1", onDismiss = { dismissed += 1 }, onProfileTap = { opened = it }, viewModel = model, ) } } composeRule.awaitText("Lovely light")
composeRule.onNodeWithContentDescription("alice.grain.social's avatar").performClick()
composeRule.waitUntil(10_000) { opened == "did:plc:alice" && dismissed == 1 } }}