Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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/android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ jobs:
- name: Run connected tests
uses: ReactiveCircus/android-emulator-runner@v2
with:
api-level: 28
api-level: 35
target: google_apis
arch: x86_64
profile: Nexus 6
Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/script/run_tests.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
#!/bin/bash

set +e
./gradlew connectedCheck --no-daemon
./gradlew assembleDebug assembleDebugAndroidTest --no-daemon
adb install app/build/outputs/apk/espresso/app-espresso.apk
adb install app/build/outputs/apk/androidTest/espresso/app-espresso-androidTest.apk
adb shell am instrument -w -m -e debug false \\n -e class 'com.mitteloupe.whoami.suite.SmokeTests' \\ncom.mitteloupe.whoami.test/com.mitteloupe.whoami.di.HiltTestRunner
GRADLE_EXIT_CODE=$?
set -e

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,10 @@ fun <ACTIVITY : AppCompatActivity> fromScreen(
) = AppLauncher {
fromComposable(composeContentTestRule) { activity ->
WhoAmITheme {
with(testAppDependenciesEntryPoint(activity).appNavHostDependencies) {
AppNavHost(
supportFragmentManager = activity.supportFragmentManager,
startDestination = startDestination
)
}
testAppDependenciesEntryPoint(activity).appNavHostDependencies.AppNavHost(
supportFragmentManager = activity.supportFragmentManager,
startDestination = startDestination
)
}
}.launch()
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.mitteloupe.whoami.screen

import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import androidx.test.espresso.Espresso
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition
Expand Down Expand Up @@ -72,14 +73,23 @@ class HistoryScreen {
)
}

fun tapDeleteForRecord(position: Int) {
fun seeRecord(position: Int, ipAddress: String, city: String, postCode: String) {
seeIpRecord(ipAddress = ipAddress, position = position)
seeLocation(city = city, postCode = postCode, position = position)
}

fun tapDeleteButtonForRecord(position: Int) {
onView(recordsList)
.perform(scrollToPosition<ViewHolder>(position.zeroBased))
.perform(
actionOnItemAtPosition<ViewHolder>(position.zeroBased, clickChildView(deleteButton))
)
}

fun tapBackButton() {
Espresso.pressBack()
}

private val Int.zeroBased
get() = this - 1
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class HomeScreen {
private val postCodeLabel = hasText("CM14")
private val timeZoneLabel = hasText("Europe/London")
private val internetServiceProviderLabel = hasText("TalkTalk Limited")
private val saveDetailsButton = hasText("Save Details")
private val openSourceNoticesButton = hasText("Open Source Notices")

fun ComposeContentTestRule.seeIpAddressLabel() {
Expand Down Expand Up @@ -58,7 +59,11 @@ class HomeScreen {
assertIsDisplayed(internetServiceProviderLabel)
}

fun ComposeContentTestRule.tapOpenSourceNotices() {
fun ComposeContentTestRule.tapSaveDetailsButton() {
onNode(saveDetailsButton).performTouchInput { click() }
}

fun ComposeContentTestRule.tapOpenSourceNoticesButton() {
onNode(openSourceNoticesButton).performTouchInput { click() }
}

Expand Down
18 changes: 18 additions & 0 deletions app/src/androidTest/java/com/mitteloupe/whoami/suite/SmokeTests.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.mitteloupe.whoami.suite

import androidx.compose.ui.test.ExperimentalTestApi
import com.mitteloupe.whoami.test.HistoryHighlightedIpAddressTest
import com.mitteloupe.whoami.test.HistoryTest
import com.mitteloupe.whoami.test.HomeTest
import org.junit.runner.RunWith
import org.junit.runners.Suite
import org.junit.runners.Suite.SuiteClasses

@OptIn(ExperimentalTestApi::class)
@RunWith(Suite::class)
@SuiteClasses(
HistoryTest::class,
HistoryHighlightedIpAddressTest::class,
HomeTest::class
)
class SmokeTests
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,4 @@ class HistoryHighlightedIpAddressTest : BaseTest() {
seeNonHighlightedRecord(ipAddress = ipAddress2, position = 2)
}
}

private fun HistoryScreen.seeRecord(
position: Int,
ipAddress: String,
city: String,
postCode: String
) {
seeIpRecord(ipAddress = ipAddress, position = position)
seeLocation(city = city, postCode = postCode, position = position)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class HistoryTest : BaseTest() {
)
}
seeRecord(position = 2, ipAddress = "1.1.1.1", city = "Aberdeen", postCode = "AA11 2BB")
tapDeleteForRecord(position = 1)
tapDeleteButtonForRecord(position = 1)
retry(repeat = 20) {
seeRecord(
position = 1,
Expand Down Expand Up @@ -92,14 +92,4 @@ class HistoryTest : BaseTest() {
}
}
}

private fun HistoryScreen.seeRecord(
position: Int,
ipAddress: String,
city: String,
postCode: String
) {
seeIpRecord(ipAddress = ipAddress, position = position)
seeLocation(city = city, postCode = postCode, position = position)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.mitteloupe.whoami.test

import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import com.mitteloupe.whoami.di.TestActivity
import com.mitteloupe.whoami.launcher.fromScreen
import com.mitteloupe.whoami.localstore.KEY_VALUE_NO_HISTORY
import com.mitteloupe.whoami.screen.HistoryScreen
import com.mitteloupe.whoami.screen.HomeScreen
import com.mitteloupe.whoami.server.REQUEST_RESPONSE_GET_IP
import com.mitteloupe.whoami.server.REQUEST_RESPONSE_GET_IP_DETAILS
import com.mitteloupe.whoami.test.annotation.LocalStore
import com.mitteloupe.whoami.test.annotation.ServerRequestResponse
import com.mitteloupe.whoami.test.launcher.AppLauncher
import com.mitteloupe.whoami.test.test.BaseTest
import com.mitteloupe.whoami.test.test.retry
import com.mitteloupe.whoami.ui.main.route.Home
import dagger.hilt.android.testing.HiltAndroidTest
import javax.inject.Inject
import org.junit.Test

@HiltAndroidTest
@ExperimentalTestApi
class HomeSaveRecordTest : BaseTest() {
override val composeTestRule = createAndroidComposeRule<TestActivity>()

override val startActivityLauncher: AppLauncher by lazy {
fromScreen(composeTestRule, Home)
}

@Inject
lateinit var homeScreen: HomeScreen

@Inject
lateinit var historyScreen: HistoryScreen

@Test
@ServerRequestResponse([REQUEST_RESPONSE_GET_IP, REQUEST_RESPONSE_GET_IP_DETAILS])
@LocalStore(localStoreDataIds = [KEY_VALUE_NO_HISTORY])
fun givenConnectedWhenNavigatingAwayAndBackThenSavesRecord() {
with(composeTestRule) {
with(homeScreen) {
seeCityLabel()
tapSaveDetailsButton()
}

with(historyScreen) {
retry(repeat = 20) {
seeRecord(
position = 1,
ipAddress = "1.2.3.4",
city = "Brentwood",
postCode = "CM14"
)
}
tapBackButton()
}

with(homeScreen) {
seeCityLabel()
tapSaveDetailsButton()
}

with(historyScreen) {
retry(repeat = 20) {
seeRecord(
position = 1,
ipAddress = "1.2.3.4",
city = "Brentwood",
postCode = "CM14"
)
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ class HomeTest : BaseTest() {
Intents.init()
with(composeTestRule) {
with(homeScreen) {
tapOpenSourceNotices()
tapOpenSourceNoticesButton()
}

with(openSourceNoticesScreen) {
Expand Down
6 changes: 6 additions & 0 deletions app/src/main/java/com/mitteloupe/whoami/di/HomeUiModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.mitteloupe.whoami.architecture.ui.navigation.mapper.NavigationEventDe
import com.mitteloupe.whoami.home.presentation.navigation.HomePresentationNavigationEvent
import com.mitteloupe.whoami.home.presentation.viewmodel.HomeViewModel
import com.mitteloupe.whoami.home.ui.di.HomeDependencies
import com.mitteloupe.whoami.home.ui.mapper.ConnectionDetailsPresentationMapper
import com.mitteloupe.whoami.home.ui.mapper.ConnectionDetailsUiMapper
import com.mitteloupe.whoami.home.ui.mapper.ErrorUiMapper
import com.mitteloupe.whoami.home.ui.mapper.HomeNotificationUiMapper
Expand Down Expand Up @@ -39,13 +40,17 @@ object HomeUiModule {
@Provides
fun providesErrorUiMapper(resources: Resources) = ErrorUiMapper(resources)

@Provides
fun providesConnectionDetailsPresentationMapper() = ConnectionDetailsPresentationMapper()

@Provides
fun providesConnectionDetailsUiMapper() = ConnectionDetailsUiMapper()

@Provides
fun providesHomeDependencies(
homeViewModel: HomeViewModel,
homeViewStateUiMapper: HomeViewStateUiMapper,
connectionDetailsPresentationMapper: ConnectionDetailsPresentationMapper,
connectionDetailsUiMapper: ConnectionDetailsUiMapper,
homeNavigationMapper: @JvmSuppressWildcards
NavigationEventDestinationMapper<HomePresentationNavigationEvent>,
Expand All @@ -55,6 +60,7 @@ object HomeUiModule {
) = HomeDependencies(
homeViewModel,
homeViewStateUiMapper,
connectionDetailsPresentationMapper,
connectionDetailsUiMapper,
homeNavigationMapper,
homeNotificationMapper,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package com.mitteloupe.whoami.ui.main

import android.view.View
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
Expand Down Expand Up @@ -39,7 +42,9 @@ fun AppNavHostDependencies.AppNavHost(
val history: History = backStackEntry.toRoute()
FragmentContainer(
containerId = containerId,
modifier = Modifier.fillMaxSize(),
modifier = Modifier
.fillMaxSize()
.windowInsetsPadding(WindowInsets.statusBars),
fragmentManager = supportFragmentManager,
commit = { containerId ->
replace(containerId, HistoryFragment.newInstance(history.highlightedIpAddress))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import androidx.compose.ui.viewinterop.AndroidView
import androidx.fragment.app.FragmentContainerView
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentTransaction
import androidx.fragment.app.commit
import androidx.fragment.app.commitNow

@Composable
fun FragmentContainer(
Expand All @@ -33,7 +33,7 @@ fun FragmentContainer(
fragmentManager.onContainerAvailable(view)
onFragmentViewCreated(view.id)
} else {
fragmentManager.commit { commit(view.id) }
fragmentManager.commitNow { commit(view.id) }
initialized = true
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import com.mitteloupe.whoami.home.presentation.model.HomePresentationNotificatio
import com.mitteloupe.whoami.home.presentation.model.HomeViewState
import com.mitteloupe.whoami.home.presentation.navigation.HomePresentationNavigationEvent
import com.mitteloupe.whoami.home.presentation.viewmodel.HomeViewModel
import com.mitteloupe.whoami.home.ui.mapper.ConnectionDetailsPresentationMapper
import com.mitteloupe.whoami.home.ui.mapper.ConnectionDetailsUiMapper
import com.mitteloupe.whoami.home.ui.mapper.ErrorUiMapper
import com.mitteloupe.whoami.home.ui.mapper.HomeViewStateUiMapper

data class HomeDependencies(
val homeViewModel: HomeViewModel,
val homeViewStateUiMapper: HomeViewStateUiMapper,
val connectionDetailsPresentationMapper: ConnectionDetailsPresentationMapper,
val connectionDetailsUiMapper: ConnectionDetailsUiMapper,
private val homeNavigationMapper:
NavigationEventDestinationMapper<HomePresentationNavigationEvent>,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.mitteloupe.whoami.home.ui.mapper

import com.mitteloupe.whoami.home.presentation.model.HomeViewState
import com.mitteloupe.whoami.home.ui.model.ConnectionDetailsUiModel
import java.util.*

private val locales by lazy {
Locale.getISOCountries().map { country -> Locale("en", country) }
}

class ConnectionDetailsPresentationMapper {
fun toPresentation(connectionDetails: ConnectionDetailsUiModel) = HomeViewState.Connected(
ipAddress = connectionDetails.ipAddress,
city = connectionDetails.cityIconLabel?.label,
region = connectionDetails.regionIconLabel?.label,
geolocation = connectionDetails.geolocationIconLabel?.label?.replace(", ", ","),
postCode = connectionDetails.postCode?.label,
timeZone = connectionDetails.timeZone?.label,
internetServiceProviderName = connectionDetails.internetServiceProviderName?.label,
countryCode = connectionDetails.countryIconLabel?.label?.let { countryName ->
locales.firstOrNull { locale ->
locale.displayCountry == countryName
}
}?.country
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,10 @@ import com.mitteloupe.whoami.home.ui.view.widget.LoadingAnimationContainer

@Composable
fun HomeDependencies.Home(navController: NavController, modifier: Modifier = Modifier) {
fun relaySavingToViewModel(connectionDetails: HomeViewState) {
require(connectionDetails is HomeViewState.Connected) {
"Unexpected click, not connected."
}
homeViewModel.onSaveDetailsAction(connectionDetails)
fun relaySavingToViewModel(connectionDetails: ConnectionDetailsUiModel) {
val presentationConnection = connectionDetailsPresentationMapper
.toPresentation(connectionDetails)
homeViewModel.onSaveDetailsAction(presentationConnection)
}

ScreenEnterObserver {
Expand Down Expand Up @@ -68,7 +67,11 @@ fun HomeDependencies.Home(navController: NavController, modifier: Modifier = Mod
connectionDetails = connectionDetails,
errorMessage = errorMessage,
analytics = analytics,
onSaveDetailsClick = { relaySavingToViewModel(viewState) },
onSaveDetailsClick = {
val latestConnectionDetails = connectionDetails
requireNotNull(latestConnectionDetails)
relaySavingToViewModel(latestConnectionDetails)
},
onViewHistoryClick = { homeViewModel.onViewHistoryAction() },
onOpenSourceNoticesClick = { homeViewModel.onOpenSourceNoticesAction() },
modifier = modifier
Expand Down
Loading