1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
package social.grain
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.v2.createComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import social.grain.data.model.AspectRatio
import social.grain.data.model.GrainGallery
import social.grain.data.model.GrainPhoto
import social.grain.data.model.GrainProfile
import social.grain.ui.components.GalleryCard
import social.grain.ui.theme.GrainTheme
private const val ALT = "A heron standing in shallow water at dawn"
/** Long enough to clear the platform's double-tap timeout. */
private const val DOUBLE_TAP_WINDOW_MS = 1000L
/**
* The card carries a photo's description itself.
*
* The feed no longer pushes a detail screen, so a tap on the photo has to be
* what reveals the alt text — if this regresses, the description becomes
* unreachable to anyone not using a screen reader.
*/
@RunWith(AndroidJUnit4::class)
class GalleryCardAltTextTest {
@get:Rule
val composeRule = createComposeRule()
private fun gallery(alt: String?) = GrainGallery(
uri = "at://did:plc:test/social.grain.gallery/1",
creator = GrainProfile(did = "did:plc:test", handle = "heron.example"),
items = listOf(
GrainPhoto(
uri = "at://did:plc:test/social.grain.photo/1",
alt = alt,
aspectRatio = AspectRatio(3, 2),
),
),
indexedAt = "2026-08-01T00:00:00Z",
)
@Test
fun tappingThePhotoTogglesTheAltText() {
composeRule.setContent { GrainTheme { GalleryCard(gallery = gallery(ALT)) } }
composeRule.onNodeWithText(ALT).assertDoesNotExist()
composeRule.onNodeWithContentDescription(ALT).performClick()
// The carousel also listens for a double tap, so a single tap is only
// reported once that window has passed.
composeRule.mainClock.advanceTimeBy(DOUBLE_TAP_WINDOW_MS)
composeRule.onNodeWithText(ALT).assertIsDisplayed()
composeRule.onNodeWithText(ALT).performClick()
composeRule.waitForIdle()
composeRule.onNodeWithText(ALT).assertDoesNotExist()
}
@Test
fun theBadgeAlsoOpensTheAltText() {
composeRule.setContent { GrainTheme { GalleryCard(gallery = gallery(ALT)) } }
composeRule.onNodeWithContentDescription("Show alt text").performClick()
composeRule.onNodeWithText(ALT).assertIsDisplayed()
}
@Test
fun aPhotoWithNoAltShowsNoBadge() {
composeRule.setContent { GrainTheme { GalleryCard(gallery = gallery(null)) } }
composeRule.onNodeWithText("ALT").assertDoesNotExist()
}
}