Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/authentication screens #10

Merged
merged 27 commits into from
Mar 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
f29d869
Create the authentication service interface
octogradiste Mar 19, 2024
2d325dc
Add supabase dependencies
octogradiste Mar 19, 2024
add7394
Create a authentication service implementation using supabase
octogradiste Mar 19, 2024
817b4a9
Add mockk as test dependency
octogradiste Mar 19, 2024
f05e52e
Use auth plugin instead of supabase client for easy mocking
octogradiste Mar 19, 2024
0d59a4b
Testing the supabase authentication service
octogradiste Mar 19, 2024
140223e
Using deprecated buildDir to fix jacoco report generation
octogradiste Mar 21, 2024
8947e02
Add test dependency for testing coroutines
octogradiste Mar 21, 2024
08cdb9b
Add tests for the login view models
octogradiste Mar 21, 2024
863d429
Copy build.gradle config from Android Sample Project
octogradiste Mar 25, 2024
6da7635
Grouping dependencies + using the version catalog
octogradiste Mar 25, 2024
840d6fb
Add mockk dependency for android tests
octogradiste Mar 25, 2024
9d786b1
Copy packaging resources configuration from Bootcamp
octogradiste Mar 25, 2024
c63ad23
Necessary options to make mockk work for android tests
octogradiste Mar 26, 2024
75696de
Delete example instrumented test
octogradiste Mar 26, 2024
72ffc94
Create email and password form
octogradiste Mar 26, 2024
79571f2
Create authentication screen, will be used by the login and register …
octogradiste Mar 26, 2024
ef30cc3
Create login screen
octogradiste Mar 26, 2024
6ad1dbf
Create register screen
octogradiste Mar 26, 2024
86de263
Additional tests for the AuthenticationForm
octogradiste Mar 26, 2024
cc62ac2
Some UI changes
octogradiste Mar 26, 2024
56030e1
Formatting
octogradiste Mar 26, 2024
3f3fd85
Additional tests to cover more branches
octogradiste Mar 28, 2024
205f28b
Show info when uploading to sonar
octogradiste Mar 28, 2024
79700b5
Trying to increase coverage
octogradiste Mar 28, 2024
19c5a90
Trying to increase coverage
octogradiste Mar 28, 2024
d6b14d7
Removing ugly code + increased number of lines
octogradiste Mar 28, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -111,4 +111,4 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: ./gradlew sonar --parallel --build-cache
run: ./gradlew sonar --info
12 changes: 12 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,22 @@ android {
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
merges += "META-INF/LICENSE.md"
merges += "META-INF/LICENSE-notice.md"
}
}
testCoverage {
jacocoVersion = "0.8.8"
}

// Necessary to make mockk work on integration tests
testOptions {
packaging {
jniLibs {
useLegacyPackaging = true
}
}
}
}

dependencies {
Expand Down Expand Up @@ -86,6 +97,7 @@ dependencies {
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.ui.test.junit4)
androidTestImplementation(libs.mockk.android)

debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package com.github.swent.echo.compose.authentication

import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTextInput
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Rule
import org.junit.Test

class AuthenticationFormTest {

companion object {
const val ACTION = "Action Button"
const val EMAIL = "test@test.com"
const val PASSWORD = "password"
}

@get:Rule val composeTestRule = createComposeRule()

private var authenticationCount = 0
private var email = ""
private var password = ""

private fun onAuthenticate(email: String, password: String) {
this.email = email
this.password = password
authenticationCount++
}

@Before
fun setUp() {
authenticationCount = 0
email = ""
password = ""
}

@Test
fun shouldNotDisplayErrorWhenErrorIsNull() {
composeTestRule.setContent { AuthenticationForm(ACTION, this::onAuthenticate, null) }
composeTestRule.onNodeWithTag("error-message").assertDoesNotExist()
}

@Test
fun shouldDisplayErrorMessageWhenErrorIsNotNull() {
val error = "An error occurred"
composeTestRule.setContent { AuthenticationForm(ACTION, this::onAuthenticate, error) }
composeTestRule.onNodeWithTag("error-message").assertExists()
}

@Test
fun shouldDisplayLabelsAndActionButton() {
composeTestRule.setContent { AuthenticationForm(ACTION, this::onAuthenticate) }
composeTestRule.onNodeWithTag("action-button").assertExists()
composeTestRule.onNodeWithText(ACTION).assertExists()
composeTestRule.onNodeWithText("Email").assertExists()
composeTestRule.onNodeWithText("Password").assertExists()
}

@Test
fun shouldCallOnAuthenticateWithCorrectParametersWhenActionButtonIsPressed() {
composeTestRule.setContent { AuthenticationForm(ACTION, this::onAuthenticate) }

composeTestRule.onNodeWithTag("email-field").performTextInput(EMAIL)
composeTestRule.onNodeWithTag("password-field").performTextInput(PASSWORD)
assertEquals(0, authenticationCount)
composeTestRule.onNodeWithTag("action-button").performClick()
assertEquals(1, authenticationCount)
assertEquals(EMAIL, email)
assertEquals(PASSWORD, password)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package com.github.swent.echo.compose.authentication

import androidx.compose.ui.test.assertHasClickAction
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTextInput
import com.github.swent.echo.viewmodels.authentication.AuthenticationState
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Rule
import org.junit.Test

class AuthenticationScreenTest {

companion object {
const val ACTION = "Action Button"
}

@get:Rule val composeTestRule = createComposeRule()

private var authenticationCount = 0
private var email = ""
private var password = ""

private fun onAuthenticate(email: String, password: String) {
this.email = email
this.password = password
authenticationCount++
}

@Before
fun setUp() {
authenticationCount = 0
email = ""
password = ""
}

@Test
fun shouldHaveLoginButtonAndInputFieldsWhenIsSignedOut() {
composeTestRule.setContent {
AuthenticationScreen(ACTION, AuthenticationState.SignedOut, this::onAuthenticate)
}
composeTestRule.onNodeWithText(ACTION).assertExists().assertHasClickAction()
composeTestRule.onNodeWithTag("email-field").assertExists()
composeTestRule.onNodeWithTag("password-field").assertExists()
composeTestRule.onNodeWithTag("error-message").assertDoesNotExist()
}

@Test
fun shouldCallOnAuthenticateWithCorrectParametersWhenActionButtonIsPressedInSignedOutState() {
composeTestRule.setContent {
AuthenticationScreen(ACTION, AuthenticationState.SignedOut, this::onAuthenticate)
}
composeTestRule.onNodeWithTag("email-field").performTextInput("test@test.test")
composeTestRule.onNodeWithTag("password-field").performTextInput("password")
assertEquals(0, authenticationCount)
composeTestRule.onNodeWithTag("action-button").performClick()
assertEquals(1, authenticationCount)
assertEquals("test@test.test", email)
assertEquals("password", password)
}

@Test
fun shouldHaveSigningInTextWhenIsSigningIn() {
composeTestRule.setContent {
AuthenticationScreen(ACTION, AuthenticationState.SigningIn, this::onAuthenticate)
}
composeTestRule.onNodeWithText("Signing in...").assertExists()
assertEquals(0, authenticationCount)
}

@Test
fun shouldHaveSignedInTextWhenIsSignedIn() {
composeTestRule.setContent {
AuthenticationScreen(ACTION, AuthenticationState.SignedIn, this::onAuthenticate)
}
composeTestRule.onNodeWithText("Signed in").assertExists()
assertEquals(0, authenticationCount)
}

@Test
fun shouldHaveErrorTextWhenIsError() {
val message = "Error message"
composeTestRule.setContent {
AuthenticationScreen(ACTION, AuthenticationState.Error(message), this::onAuthenticate)
}
composeTestRule.onNodeWithText(message).assertExists()
}

@Test
fun shouldHaveLoginButtonAndInputFieldsWhenIsError() {
val message = "Error message"
composeTestRule.setContent {
AuthenticationScreen(ACTION, AuthenticationState.Error(message), this::onAuthenticate)
}
composeTestRule.onNodeWithText(ACTION).assertExists().assertHasClickAction()
composeTestRule.onNodeWithTag("email-field").assertExists()
composeTestRule.onNodeWithTag("password-field").assertExists()
composeTestRule.onNodeWithTag("error-message").assertExists()
}

@Test
fun shouldCallOnAuthenticateWithCorrectParametersWhenActionButtonIsPressedInErrorState() {
composeTestRule.setContent {
AuthenticationScreen(ACTION, AuthenticationState.Error("Error"), this::onAuthenticate)
}
composeTestRule.onNodeWithTag("email-field").performTextInput("test@test.test")
composeTestRule.onNodeWithTag("password-field").performTextInput("password")
composeTestRule.onNodeWithTag("action-button").performClick()
assertEquals(1, authenticationCount)
assertEquals("test@test.test", email)
assertEquals("password", password)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.github.swent.echo.compose.authentication

import androidx.compose.ui.test.assertHasClickAction
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTextInput
import com.github.swent.echo.viewmodels.authentication.AuthenticationState
import com.github.swent.echo.viewmodels.authentication.LoginViewModel
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import org.junit.Before
import org.junit.Rule
import org.junit.Test

class LoginScreenTest {

@get:Rule val composeTestRule = createComposeRule()

private lateinit var loginViewModel: LoginViewModel
private lateinit var state: MutableStateFlow<AuthenticationState>

@Before
fun setUp() {
loginViewModel = mockk(relaxed = true)
state = MutableStateFlow(AuthenticationState.SignedOut)
every { loginViewModel.state } answers { state.asStateFlow() }
composeTestRule.setContent { LoginScreen(loginViewModel) }
}

@Test
fun shouldHaveLoginButtonAndInputFieldsWhenIsSignedOut() {
state.value = AuthenticationState.SignedOut
composeTestRule.onNodeWithText("Login").assertExists().assertHasClickAction()
}

@Test
fun shouldCallLoginOnViewModelWithEmailPasswordWhenLoginButtonIsClickedInSignedOutState() {
state.value = AuthenticationState.SignedOut
composeTestRule.onNodeWithTag("email-field").performTextInput("test@test.test")
composeTestRule.onNodeWithTag("password-field").performTextInput("password")
composeTestRule.onNodeWithTag("action-button").performClick()
verify { loginViewModel.login("test@test.test", "password") }
}

@Test
fun shouldCallLoginOnViewModelWithEmailPasswordWhenLoginButtonIsClickedInErrorState() {
state.value = AuthenticationState.Error("Error message")
composeTestRule.onNodeWithTag("email-field").performTextInput("test@test.test")
composeTestRule.onNodeWithTag("password-field").performTextInput("password")
composeTestRule.onNodeWithTag("action-button").performClick()
verify { loginViewModel.login("test@test.test", "password") }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.github.swent.echo.compose.authentication

import androidx.compose.ui.test.assertHasClickAction
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTextInput
import com.github.swent.echo.viewmodels.authentication.AuthenticationState
import com.github.swent.echo.viewmodels.authentication.RegisterViewModel
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import org.junit.Before
import org.junit.Rule
import org.junit.Test

class RegisterScreenTest {

@get:Rule val composeTestRule = createComposeRule()

private lateinit var registerViewModel: RegisterViewModel
private lateinit var state: MutableStateFlow<AuthenticationState>

@Before
fun setUp() {
registerViewModel = mockk(relaxed = true)
state = MutableStateFlow(AuthenticationState.SignedOut)
every { registerViewModel.state } answers { state.asStateFlow() }
composeTestRule.setContent { RegisterScreen(registerViewModel) }
}

@Test
fun shouldHaveRegisterButtonAndInputFieldsWhenIsSignedOut() {
state.value = AuthenticationState.SignedOut
composeTestRule.onNodeWithText("Register").assertExists().assertHasClickAction()
}

@Test
fun shouldCallRegisterOnViewModelWithEmailPasswordWhenRegisterButtonIsClickedInSignedOutState() {
state.value = AuthenticationState.SignedOut
composeTestRule.onNodeWithTag("email-field").performTextInput("test@test.test")
composeTestRule.onNodeWithTag("password-field").performTextInput("password")
composeTestRule.onNodeWithTag("action-button").performClick()
verify { registerViewModel.register("test@test.test", "password") }
}

@Test
fun shouldCallRegisterOnViewModelWithEmailPasswordWhenRegisterButtonIsClickedInErrorState() {
state.value = AuthenticationState.Error("Error message")
composeTestRule.onNodeWithTag("email-field").performTextInput("test@test.test")
composeTestRule.onNodeWithTag("password-field").performTextInput("password")
composeTestRule.onNodeWithTag("action-button").performClick()
verify { registerViewModel.register("test@test.test", "password") }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,5 @@ interface AuthenticationService {
*
* @return The current user's ID, or null if the user is not signed in.
*/
fun getCurrentUserID(): String?
suspend fun getCurrentUserID(): String?
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class AuthenticationServiceImpl(private val auth: Auth) : AuthenticationService
return AuthenticationResult.Success
}

override fun getCurrentUserID(): String? {
override suspend fun getCurrentUserID(): String? {
return auth.currentSessionOrNull()?.user?.id
}
}
Loading