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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
package social.grain
import androidx.compose.ui.test.hasSetTextAction
import androidx.compose.ui.test.junit4.v2.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTextInput
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import social.grain.feature.login.LoginScreen
import social.grain.feature.login.LoginViewModel
import social.grain.support.stubSignIn
import social.grain.support.Harness
import social.grain.support.awaitContentDescription
import social.grain.support.awaitText
import social.grain.support.MainDispatcherRule
import social.grain.support.awaitCall
/**
* The sign-in screen, which is the only screen a signed-out reader ever sees.
*
* It forces its own dark scheme over a photograph rather than following the
* device, so it is also the one screen where a theme regression would be
* invisible everywhere else.
*/
@RunWith(AndroidJUnit4::class)
class LoginScreenTest {
@get:Rule
val mainDispatcher = MainDispatcherRule()
@get:Rule
val composeRule = createComposeRule()
private lateinit var harness: Harness
@Before
fun setUp() {
harness = Harness(signedIn = false)
harness.server
.on(
"social.grain.actor.searchActorsTypeahead",
"""{"actors":[{"did":"did:plc:alice","handle":"alice.grain.social"}]}""",
)
harness.server.stubSignIn(did = "did:plc:alice", handle = "alice.grain.social")
}
@After
fun tearDown() = harness.tearDown()
private fun show() {
val model = LoginViewModel(harness.auth, harness.client)
composeRule.setContent { LoginScreen(viewModel = model) }
composeRule.waitForIdle()
}
@Test
fun theScreenInvitesAnAtmosphereAccount() {
show()
composeRule.awaitText("Sign in with your atmosphere account")
composeRule.awaitText("Handle")
composeRule.awaitText("Sign in")
composeRule.awaitText("Create account")
}
@Test
fun typingAHandleOffersMatchesFromTheServer() {
show()
composeRule.onNode(hasSetTextAction()).performTextInput("ali")
composeRule.waitUntil(10_000) { harness.server.countOf("social.grain.actor.searchActorsTypeahead") >= 1 }
composeRule.waitForIdle()
composeRule.awaitText("alice.grain.social", substring = true)
}
@Test
fun signingInRunsThePushedAuthorizationRequest() {
show()
composeRule.onNode(hasSetTextAction()).performTextInput("alice.grain.social")
composeRule.onNodeWithText("Sign in").performClick()
composeRule.waitUntil(10_000) { harness.server.countOf("par") >= 1 }
}
@Test
fun creatingAnAccountAsksForTheSignUpPage() {
show()
composeRule.onNodeWithText("Create account").performClick()
composeRule.waitUntil(10_000) { harness.server.countOf("par") >= 1 }
val par = harness.server.lastCall("par")
assert(par.body.contains("prompt=create"))
}
}