Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
463e490
Add LoginButton composable and integrate it into MainActivity
theMr17 Apr 27, 2025
d084a29
Add CLIENT_ID and CLIENT_SECRET to buildConfig in debug and release b…
theMr17 Apr 27, 2025
ed82b5a
Add GitHub OAuth integration with LoginButton and createGitHubAuthIntent
theMr17 Apr 27, 2025
7058ed3
Add LoginScreen composable and update MainActivity to use it
theMr17 Apr 27, 2025
bbedd44
Add authentication feature with ViewModel, data sources, and error ha…
theMr17 Apr 28, 2025
1b7b3b5
Add ConnectingToGitHubScreen and navigation setup in MainActivity
theMr17 Apr 28, 2025
9c6c7ac
Update build.gradle.kts to handle missing local.properties gracefully…
theMr17 Apr 28, 2025
2cda0cc
Add GitHub connection handling with state management and navigation s…
theMr17 Apr 29, 2025
2bb8b02
Rename authentication-related classes and update references to improv…
theMr17 Apr 29, 2025
b710a5e
Update SetupStep enum documentation to clarify token retrieval proces…
theMr17 Apr 29, 2025
99577cd
Merge branch 'main' into feat/setup-authentication-flow
theMr17 May 1, 2025
6e396b2
Fix daggerHilt variable name
theMr17 May 1, 2025
bbb0150
Refactor error handling in DataStore operations and update related cl…
theMr17 May 1, 2025
0d604e8
Update test script to include verbose output for connectedAndroidTest
theMr17 May 1, 2025
de4f862
Enhance test script to handle emulator termination on test failure
theMr17 May 1, 2025
a34fa01
Revert changes to test.yml
theMr17 May 1, 2025
fb5a0cd
Refactor DataStoreManager tests to assert Result type and improve cla…
theMr17 May 1, 2025
a52d591
Add splash screen support and update app theme
theMr17 May 2, 2025
3e586c4
Refactor splash screen implementation and set post-splash theme
theMr17 May 2, 2025
d15d412
Inject HttpClientFactory into provideHttpClient and retrieve access t…
theMr17 May 2, 2025
3dae001
Enhance login flow with navigation events and update LoginButton to a…
theMr17 May 2, 2025
62fe4ed
Implement GitHub OAuth flow with state management and DataStore integ…
theMr17 May 7, 2025
bff5863
Refactor login flow to enhance state management and navigation events
theMr17 May 8, 2025
6afc242
Enhance setup flow with detailed documentation for events and actions
theMr17 May 11, 2025
70d8aa1
Use Truth assertions and move HttpClientFactoryTest to AndroidTest
theMr17 May 22, 2025
ef3ad69
Add Ktor client mock dependency for enhanced testing capabilities
theMr17 May 22, 2025
3d16713
Implement BaseViewModel to streamline state management and event hand…
theMr17 May 30, 2025
cd217c8
Add test tags to CircularProgressIndicator and implement LoginScreen …
theMr17 May 30, 2025
eaf8dc1
Refactor test tag for CircularProgressIndicator in LoginScreen for co…
theMr17 May 30, 2025
85b58b6
Add unit tests for SetupScreen to verify UI states and interactions
theMr17 May 30, 2025
204c1c2
Add string resources for login and setup screen messages
theMr17 May 31, 2025
2421855
Refactor state management in ViewModels and enhance LoginScreen tests…
theMr17 May 31, 2025
ab617e5
Update text assertions in LoginScreen and SetupScreen tests to use el…
theMr17 May 31, 2025
fb731c8
Add LoginViewModel tests for authentication status and navigation events
theMr17 Jun 4, 2025
ffa69e8
Refactor LoginViewModelTest to initialize ViewModel only once in setup
theMr17 Jun 4, 2025
9ad2666
Rename test methods in LoginViewModelTest for clarity and consistency
theMr17 Jun 4, 2025
40adaa8
Add SetupViewModel tests for initial state and auth token handling
theMr17 Jun 4, 2025
132ef54
Refactor SetupViewModelTest to remove unnecessary map calls and impro…
theMr17 Jun 4, 2025
18192d4
Enhance BaseViewModel with detailed documentation and improve error h…
theMr17 Jun 5, 2025
478a1f3
Refactor SetupViewModelTest to improve test clarity and structure wit…
theMr17 Jun 5, 2025
9d01787
Refactor SetupViewModelTest to use mock methods for OAuth state handling
theMr17 Jun 5, 2025
e86b940
Refactor LoginViewModel to remove logging on auth error and simplify …
theMr17 Jun 5, 2025
26547ec
Revert "Refactor LoginViewModel to remove logging on auth error and s…
theMr17 Jun 5, 2025
ba8c99d
Add tests for getAuthToken in SetupViewModel to verify state transiti…
theMr17 Jun 5, 2025
3ecbbf6
Fix formatting in mockSetAccessTokenError function in SetupViewModelTest
theMr17 Jun 5, 2025
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
46 changes: 45 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import java.util.Properties

plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
Expand All @@ -22,12 +24,39 @@ android {
}

buildTypes {
val properties = Properties().apply {
val localPropertiesFile = project.rootProject.file("local.properties")
if (localPropertiesFile.exists()) {
load(localPropertiesFile.inputStream())
}
}

debug {
buildConfigField("String", "BASE_URL", "\"https://api.github.com/\"")
buildConfigField(
"String",
"CLIENT_ID",
"\"${properties.getProperty("CLIENT_ID", "dummy_client_id")}\""
)
buildConfigField(
"String",
"CLIENT_SECRET",
"\"${properties.getProperty("CLIENT_SECRET", "dummy_client_secret")}\""
)
Comment thread
theMr17 marked this conversation as resolved.
}
release {
isMinifyEnabled = false
buildConfigField("String", "BASE_URL", "\"https://api.github.com/\"")
buildConfigField(
"String",
"CLIENT_ID",
"\"${properties.getProperty("CLIENT_ID", "dummy_client_id")}\""
)
buildConfigField(
"String",
"CLIENT_SECRET",
"\"${properties.getProperty("CLIENT_SECRET", "dummy_client_secret")}\""
)
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
Expand All @@ -48,6 +77,13 @@ android {
buildConfig = true
compose = true
}

packaging {
resources {
excludes += "META-INF/LICENSE.md"
excludes += "META-INF/LICENSE-notice.md"
}
}
}

dependencies {
Expand All @@ -59,9 +95,14 @@ dependencies {
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
implementation(libs.androidx.navigation.compose)
implementation(libs.bundles.ktor)
implementation(libs.androidx.datastore.preferences)
implementation(libs.dagger.hilt)
implementation(libs.hilt.navigation.compose)
implementation(libs.androidx.splash.screen)
implementation(libs.androidx.junit.ktx)

ksp(libs.dagger.hilt.compiler)

testImplementation(libs.junit)
Expand All @@ -70,11 +111,14 @@ dependencies {
testImplementation(libs.ktor.client.mock)
testImplementation(libs.mockk)

androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.truth)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.ui.test.junit4)
androidTestImplementation(libs.dagger.hilt.testing)
androidTestImplementation(libs.ktor.client.mock)
androidTestImplementation(libs.kotlinx.coroutines.test)
androidTestImplementation(libs.mockk.android)
kspAndroidTest(libs.dagger.hilt.compiler)

debugImplementation(libs.androidx.ui.tooling)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package com.notifier.app.auth.presentation.login

import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.hasTestTag
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test

Comment thread
theMr17 marked this conversation as resolved.
class LoginScreenTest {
@get:Rule
val composeRule = createComposeRule()

@Test
fun testLoginScreen_whenStatusIsNull_showsProgressAndMessage() {
composeRule.setContent {
LoginScreen(
state = LoginState(status = null),
onLoginButtonClick = {}
)
}

composeRule
.onNode(hasTestTag(TEST_TAG_CIRCULAR_PROGRESS_INDICATOR))
.assertExists()

composeRule
.onNodeWithText("Verifying authentication status…")
.assertIsDisplayed()
}

@Test
fun testLoginScreen_whenLoading_showsProgressAndMessage() {
composeRule.setContent {
LoginScreen(
state = LoginState(status = LoginStatus.LOADING),
onLoginButtonClick = {}
)
}

composeRule
.onNode(hasTestTag(TEST_TAG_CIRCULAR_PROGRESS_INDICATOR))
.assertExists()

composeRule
.onNodeWithText("Verifying authentication status…")
.assertIsDisplayed()
}

@Test
fun testLoginScreen_whenLoggedOut_showsLoginButton_clickTriggersCallback() {
var loginButtonClicked = false

composeRule.setContent {
LoginScreen(
state = LoginState(status = LoginStatus.LOGGED_OUT),
onLoginButtonClick = { loginButtonClicked = true }
)
}

composeRule
.onNodeWithText("Login with GitHub")
.assertIsDisplayed()
.performClick()

assertThat(loginButtonClicked).isTrue()
}

@Test
fun testLoginScreen_whenLoggedIn_showsSuccessMessage() {
composeRule.setContent {
LoginScreen(
state = LoginState(status = LoginStatus.LOGGED_IN),
onLoginButtonClick = {}
)
}

composeRule
.onNodeWithText("Logged in successfully! Redirecting…")
.assertIsDisplayed()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package com.notifier.app.auth.presentation.login

import com.google.common.truth.Truth.assertThat
import com.notifier.app.core.data.persistence.DataStoreManager
import com.notifier.app.core.domain.util.PersistenceError
import com.notifier.app.core.domain.util.Result
import io.mockk.coEvery
import io.mockk.mockk
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test

class LoginViewModelTest {
private lateinit var dataStoreManager: DataStoreManager

private lateinit var viewModel: LoginViewModel

@Before
fun setUp() {
dataStoreManager = mockk()
viewModel = LoginViewModel(dataStoreManager)
}

@Test
fun testAuthStatus_tokenIsValid_setsStateToLoggedInEventually() = runTest {
coEvery {
dataStoreManager.getAccessToken()
} returns Result.Success("dummy_valid_token")

val stateStatuses = viewModel.state
.take(3)
.map { it.status }
.toList()

assertThat(stateStatuses).containsExactly(
null,
LoginStatus.LOADING,
LoginStatus.LOGGED_IN
).inOrder()
}

@Test
fun testAuthStatus_tokenIsBlank_setsStateToLoggedOutEventually() = runTest {
coEvery {
dataStoreManager.getAccessToken()
} returns Result.Success("")

val stateStatuses = viewModel.state
.take(3)
.map { it.status }
.toList()

assertThat(stateStatuses).containsExactly(
null,
LoginStatus.LOADING,
LoginStatus.LOGGED_OUT
).inOrder()
}

@Test
fun testAuthStatus_errorFetchingToken_setsStateToLoggedOutEventually() = runTest {
coEvery {
dataStoreManager.getAccessToken()
} returns Result.Error(PersistenceError.IO)

val stateStatuses = viewModel.state
.take(3)
.map { it.status }
.toList()

assertThat(stateStatuses).containsExactly(
null,
LoginStatus.LOADING,
LoginStatus.LOGGED_OUT
).inOrder()
}

@Test
fun testOnAction_onLoginButtonClick_emitsNavigateToGitHubAuthEvent() = runTest {
viewModel.onAction(LoginAction.OnLoginButtonClick)

val event = viewModel.events.first()
assertThat(event).isEqualTo(LoginEvent.NavigateToGitHubAuth)
}

@Test
fun testOnAction_onUserLoggedIn_emitsNavigateToHomeScreenEvent() = runTest {
viewModel.onAction(LoginAction.OnUserLoggedIn)

val event = viewModel.events.first()
assertThat(event).isEqualTo(LoginEvent.NavigateToHomeScreen)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package com.notifier.app.auth.presentation.setup

import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test

class SetupScreenTest {
@get:Rule
val composeRule = createComposeRule()

@Test
fun testSetupScreen_whenFetchingToken_showsConnectingMessage() {
composeRule.setContent {
SetupScreen(
state = SetupState(setupStep = SetupStep.FETCHING_TOKEN),
onAction = {}
)
}

composeRule
.onNodeWithText("Connecting to GitHub…")
.assertIsDisplayed()
}

@Test
fun testSetupScreen_whenSavingToken_showsSavingMessage() {
composeRule.setContent {
SetupScreen(
state = SetupState(setupStep = SetupStep.SAVING_TOKEN),
onAction = {}
)
}

composeRule
.onNodeWithText("Saving user information…")
.assertIsDisplayed()
}

@Test
fun testSetupScreen_whenSuccess_showsSuccessMessageAndContinueButton_clickTriggersCallback() {
var capturedAction: SetupAction? = null

composeRule.setContent {
SetupScreen(
state = SetupState(setupStep = SetupStep.SUCCESS),
onAction = {
capturedAction = it
}
)
}

composeRule
.onNodeWithText("Connected successfully!")
.assertIsDisplayed()

composeRule
.onNodeWithText("Continue")
.assertIsDisplayed()
.performClick()

assertThat(capturedAction).isEqualTo(SetupAction.OnContinueButtonClick)
}

@Test
fun testSetupScreen_whenFailed_showsErrorMessage() {
composeRule.setContent {
SetupScreen(
state = SetupState(setupStep = SetupStep.FAILED),
onAction = {}
)
}

composeRule
.onNodeWithText("Connection failed. Please try again.")
.assertIsDisplayed()
}
}
Loading
Loading