Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291package social.grain
import androidx.compose.ui.test.assertCountEqualsimport androidx.compose.ui.test.hasTextimport androidx.compose.ui.test.junit4.v2.createComposeRuleimport androidx.compose.ui.test.onAllNodesWithTextimport androidx.compose.ui.test.onNodeWithContentDescriptionimport androidx.compose.ui.test.onNodeWithTextimport androidx.compose.ui.test.performClickimport androidx.lifecycle.SavedStateHandleimport androidx.test.ext.junit.runners.AndroidJUnit4import org.junit.Afterimport org.junit.Assert.assertEqualsimport org.junit.Beforeimport org.junit.Ruleimport org.junit.Testimport org.junit.runner.RunWithimport social.grain.data.local.ProfileInvalidationimport social.grain.feature.profile.ProfileScreenimport social.grain.feature.profile.ProfileViewModelimport social.grain.support.Fixturesimport social.grain.support.Harnessimport social.grain.support.awaitContentDescriptionimport social.grain.support.awaitTextimport social.grain.support.MainDispatcherRuleimport social.grain.support.awaitCallimport social.grain.support.jsonimport social.grain.ui.theme.GrainTheme
/** * A profile, which is really two screens: your own and somebody else's. * * The differences are all in the composable rather than the view model — a * settings button instead of a follow button, three tabs instead of one, and an * overflow that offers to block an account you can't block yourself with. */@RunWith(AndroidJUnit4::class)class ProfileScreenTest {
@get:Rule val mainDispatcher = MainDispatcherRule()
@get:Rule val composeRule = createComposeRule()
private lateinit var harness: Harness
private val alice = "did:plc:alice"
@Before fun setUp() { harness = Harness() harness.server .on("dev.hatk.describeLabels", """{"definitions":[]}""") .on("social.grain.unspecced.getActorProfile", json(Fixtures.profileDetailed())) .on("dev.hatk.getFeed", """{"items":[${json(Fixtures.gallery())}]}""") .on("social.grain.unspecced.getStories", """{"stories":[]}""") .on("social.grain.unspecced.getActorFavorites", """{"items":[]}""") .on("social.grain.unspecced.getStoryArchive", """{"stories":[]}""") }
@After fun tearDown() = harness.tearDown()
private fun show( did: String? = alice, onOpenGallery: (String) -> Unit = {}, onOpenFollowers: (String) -> Unit = {}, onOpenFollowing: (String) -> Unit = {}, onBack: (() -> Unit)? = {}, onOpenSettings: (() -> Unit)? = null, ) { val model = ProfileViewModel( client = harness.client, auth = harness.auth, profileInvalidation = ProfileInvalidation(), labelDefsCache = harness.labelDefinitions, savedStateHandle = SavedStateHandle( if (did == null) emptyMap() else mapOf("did" to did), ), ) composeRule.setContent { GrainTheme { ProfileScreen( onOpenGallery = onOpenGallery, onOpenFollowers = onOpenFollowers, onOpenFollowing = onOpenFollowing, onBack = onBack, onOpenSettings = onOpenSettings, viewModel = model, ) } } // The header is what everything else hangs off, and under a loaded // machine the first fetch can outlast the first frame. composeRule.waitUntil(10_000) { composeRule.onAllNodes(hasText("@", substring = true)) .fetchSemanticsNodes().isNotEmpty() } }
private fun ownProfile() = json( Fixtures.profileDetailed(did = harness.did, handle = harness.handle, displayName = "Me"), )
@Test fun aProfileShowsItsNameBioAndCounts() { show()
composeRule.awaitText("Alice") composeRule.awaitText("@alice.grain.social") composeRule.awaitText("Photographs of things") composeRule.awaitText("Galleries") composeRule.awaitText("Followers") composeRule.awaitText("Following") }
@Test fun theFollowerCountsLeadToTheirLists() { var followers = 0 var following = 0 show(onOpenFollowers = { followers += 1 }, onOpenFollowing = { following += 1 })
composeRule.onNodeWithText("Followers").performClick() composeRule.onNodeWithText("Following").performClick()
assertEquals(1, followers) assertEquals(1, following) }
@Test fun someoneYouDoNotFollowIsOfferedAPlainFollowButton() { show() composeRule.awaitText("Follow") }
@Test fun someoneWhoFollowsYouIsOfferedAFollowBack() { harness.server.on( "social.grain.unspecced.getActorProfile", json(Fixtures.profileDetailed(followedBy = "at://follow/2")), ) show()
composeRule.awaitText("Follows you") composeRule.awaitText("Follow back") }
@Test fun tappingFollowWritesTheRecord() { harness.server.on("dev.hatk.createRecord", """{"uri":"at://did:plc:me/follow/1"}""") show()
composeRule.onNodeWithText("Follow").performClick()
composeRule.waitUntil(10_000) { harness.server.countOf("dev.hatk.createRecord") >= 1 } }
@Test fun anAccountYouAlreadyFollowSaysFollowing() { harness.server.on( "social.grain.unspecced.getActorProfile", json(Fixtures.profileDetailed(following = "at://follow/1")), ) show()
// Two of them: the follow button and the stat it sits beside. composeRule.onAllNodesWithText("Following").assertCountEquals(2) }
@Test fun theOverflowOffersModerationForSomebodyElse() { show()
composeRule.onNodeWithContentDescription("More options").performClick()
composeRule.awaitText("Share profile") composeRule.awaitText("Copy username") composeRule.awaitText("Mute") composeRule.awaitText("Block") composeRule.awaitText("Report account") }
@Test fun blockingGoesThroughAConfirmationNamingTheAccount() { show()
composeRule.onNodeWithContentDescription("More options").performClick() composeRule.onNodeWithText("Block").performClick()
composeRule.awaitText("Block @alice.grain.social?") composeRule.onNodeWithText("Cancel").performClick() composeRule.waitForIdle()
assertEquals(0, harness.server.countOf("dev.hatk.createRecord")) }
@Test fun anAlreadyMutedAccountIsOfferedTheUndo() { harness.server.on( "social.grain.unspecced.getActorProfile", json(Fixtures.profileDetailed(muted = true)), ) show()
composeRule.onNodeWithContentDescription("More options").performClick() composeRule.onNodeWithText("Unmute").performClick()
composeRule.waitUntil(10_000) { harness.server.countOf("social.grain.graph.unmuteActor") >= 1 } }
@Test fun aBlockedAccountShowsNoGalleriesAndSaysWhy() { harness.server.on( "social.grain.unspecced.getActorProfile", json(Fixtures.profileDetailed(blocking = "at://block/1")), ) show()
composeRule.awaitText("Account blocked") composeRule.onNodeWithText("A walk in the fog").assertDoesNotExist() }
@Test fun anAccountThatBlockedYouSaysSoTheOtherWayRound() { harness.server.on( "social.grain.unspecced.getActorProfile", json(Fixtures.profileDetailed()).replace( """"muted":null""", """"blockedBy":true""", ).let { json(Fixtures.profileDetailed()).dropLast(1) + ""","viewer":{"blockedBy":true}}""" }, ) show()
composeRule.onNodeWithText("This user has blocked you", useUnmergedTree = true) .assertExists() }
@Test fun theViewersOwnProfileGetsSettingsRatherThanFollow() { harness.server.on("social.grain.unspecced.getActorProfile", ownProfile()) var settings = 0 show(did = harness.did, onBack = null, onOpenSettings = { settings += 1 })
composeRule.onNodeWithText("Follow").assertDoesNotExist() composeRule.onNodeWithContentDescription("Settings").performClick()
assertEquals(1, settings) }
@Test fun theViewersOwnProfileHasFavoritesAndStoriesTabs() { harness.server.on("social.grain.unspecced.getActorProfile", ownProfile()) show(did = harness.did, onBack = null, onOpenSettings = {})
composeRule.awaitContentDescription("Favorites") composeRule.awaitContentDescription("Stories") }
@Test fun aGridWithNothingInItSaysWhichTabIsEmpty() { harness.server.on("dev.hatk.getFeed", """{"items":[]}""") show()
composeRule.awaitText("No galleries yet") }
@Test fun tappingATileOpensThatGallery() { var opened: String? = null show(onOpenGallery = { opened = it })
composeRule.onNodeWithContentDescription("Multiple photos").performClick()
assertEquals("at://did:plc:alice/social.grain.gallery/gallery1", opened) }
@Test fun theBackButtonIsThereOnSomebodyElsesProfile() { var back = 0 show(onBack = { back += 1 })
composeRule.onNodeWithContentDescription("Back").performClick()
assertEquals(1, back) }}