Skip to content
Open
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
Binary file modified app/src/androidTest/assets/databases/dhis_test.db
Binary file not shown.
143 changes: 141 additions & 2 deletions app/src/androidTest/java/org/dhis2/usescases/main/MainRobot.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,44 @@ package org.dhis2.usescases.main
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.assert
import androidx.compose.ui.test.assertCountEquals
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.hasSetTextAction
import androidx.compose.ui.test.hasTestTag
import androidx.compose.ui.test.junit4.ComposeTestRule
import androidx.compose.ui.test.onAllNodesWithText
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performImeAction
import androidx.compose.ui.test.performTextInput
import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition
import androidx.test.espresso.matcher.RootMatchers.isPlatformPopup
import androidx.test.espresso.matcher.ViewMatchers.Visibility
import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import dhis2.org.analytics.charts.data.ChartType
import org.dhis2.R
import org.dhis2.common.BaseRobot
import org.dhis2.common.matchers.ChartMatchers
import org.dhis2.common.matchers.RecyclerviewMatchers.Companion.atPosition
import org.dhis2.common.viewactions.clickChildViewWithId
import org.dhis2.usescases.main.program.HOME_ITEMS
import org.dhis2.usescases.main.program.hasPrograms
import org.hamcrest.core.AllOf.allOf
import org.hamcrest.core.IsNot.not

private const val HOME_NAVIGATION_TIMEOUT = 15000L

fun homeRobot(
composeTestRule: ComposeTestRule,
robotBody: MainRobot.() -> Unit
robotBody: MainRobot.() -> Unit,
) {
MainRobot(composeTestRule).apply {
robotBody()
Expand All @@ -31,7 +56,7 @@ class MainRobot(val composeTestRule: ComposeTestRule) : BaseRobot() {
HOME_NAVIGATION_TIMEOUT,
)
composeTestRule.onNodeWithTag(HOME_ITEMS).assert(
SemanticsMatcher.expectValue(hasPrograms, true)
SemanticsMatcher.expectValue(hasPrograms, true),
)
}

Expand All @@ -40,4 +65,118 @@ class MainRobot(val composeTestRule: ComposeTestRule) : BaseRobot() {
composeTestRule.waitUntilAtLeastOneExists(hasTestTag(HOME_ITEMS), HOME_NAVIGATION_TIMEOUT)
composeTestRule.onNodeWithTag(HOME_ITEMS).assertIsDisplayed()
}

fun clickChartsTab() {
composeTestRule.onNodeWithTag("NAVIGATION_BAR_ITEM_Charts").performClick()
composeTestRule.waitForIdle()
}

fun checkLineListing(name: String) {
onView(withId(R.id.analytics_recycler))
.check(matches(hasDescendant(withText(name))))
.check(matches(isDisplayed()))
}

fun checkChartName(itemPosition: Int, chartName: String) {
onView(withId(R.id.analytics_recycler)).check(
matches(
atPosition(
itemPosition,
hasDescendant(allOf(withId(R.id.chart_title), withText(chartName))),
),
),
)
}

fun clickLineListingMenu(itemPosition: Int) {
onView(withId(R.id.analytics_recycler))
.perform(
actionOnItemAtPosition<RecyclerView.ViewHolder>(
itemPosition,
clickChildViewWithId(R.id.chartVisualizationButton),
),
)
}

fun clickColumnFilter(columnName: String) {
onView(withText(columnName)).inRoot(isPlatformPopup()).perform(click())
}

@OptIn(ExperimentalTestApi::class)
fun typeColumnFilterValue(composeTestRule: ComposeTestRule, value: String) {
composeTestRule.waitUntilAtLeastOneExists(hasSetTextAction(), 5000L)
composeTestRule.onNode(hasSetTextAction()).performTextInput(value)
}

fun clickSearchButton(composeTestRule: ComposeTestRule) {
composeTestRule.onNode(hasSetTextAction()).performImeAction()
composeTestRule.waitForIdle()
}

fun checkColumnContainsOnly(composeTestRule: ComposeTestRule, excludedValue: String) {
composeTestRule.onAllNodesWithText(excludedValue).assertCountEquals(0)
}

fun checkFilterBadgeVisible(itemPosition: Int) {
onView(withId(R.id.analytics_recycler)).check(
matches(
atPosition(
itemPosition,
hasDescendant(
allOf(withId(R.id.chartFilters), withEffectiveVisibility(Visibility.VISIBLE)),
),
),
),
)
}

fun checkFilterBadgeGone(itemPosition: Int) {
onView(withId(R.id.analytics_recycler)).check(
matches(
atPosition(
itemPosition,
hasDescendant(
allOf(withId(R.id.chartFilters), withEffectiveVisibility(Visibility.GONE)),
),
),
),
)
}

fun clickReset() {
onView(withText(R.string.reset)).inRoot(isPlatformPopup()).perform(click())
}

fun clickChartGroup(groupName: String) {
onView(withText(groupName)).perform(click())
composeTestRule.waitForIdle()
}

fun checkChartGroupTabDisplayed(groupName: String) {
onView(withText(groupName)).check(matches(isDisplayed()))
}

fun checkChartTypeRendered(itemPosition: Int, chartType: ChartType) {
onView(withId(R.id.analytics_recycler)).check(
matches(
atPosition(
itemPosition,
hasDescendant(ChartMatchers.hasChartType(chartType)),
),
),
)
}

fun checkNoErrorIcon(itemPosition: Int) {
onView(withId(R.id.analytics_recycler)).check(
matches(
atPosition(
itemPosition,
hasDescendant(
not(allOf(withId(R.id.error_data_icon), withEffectiveVisibility(Visibility.VISIBLE))),
),
),
),
)
}
}
49 changes: 41 additions & 8 deletions app/src/androidTest/java/org/dhis2/usescases/main/MainTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.content.Intent
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import dhis2.org.analytics.charts.data.ChartType
import org.dhis2.lazyActivityScenarioRule
import org.dhis2.usescases.BaseTest
import org.junit.Rule
Expand All @@ -25,30 +26,62 @@ class MainTest : BaseTest() {
}

@Test
fun checkHomeScreenRecyclerviewHasElements() {
fun shouldNavigateToHomeWhenBackPressed() {
startActivity(
MainActivity.intent(
ApplicationProvider.getApplicationContext(),
MainScreenType.Home(HomeScreen.Programs),
MainScreenType.Settings,
)
)
homeRobot(composeTestRule) {
composeTestRule.waitForIdle()
checkViewIsNotEmpty(composeTestRule)
pressBack()
checkHomeIsDisplayed(composeTestRule)
}
}

@Test
fun shouldNavigateToHomeWhenBackPressed() {
fun shouldShowHomeVisualizations() {
startActivity(
MainActivity.intent(
ApplicationProvider.getApplicationContext(),
MainScreenType.Settings,
MainScreenType.Home(HomeScreen.Programs),
)
)
homeRobot(composeTestRule) {
pressBack()
checkHomeIsDisplayed(composeTestRule)
checkViewIsNotEmpty(composeTestRule)
// [ANDROAPP-4356] Visualizations - Location: the charts/analytics tab is present
clickChartsTab()
// [ANDROAPP-4605] Visualizations - Group name: the "Line listing" group tab is labelled
checkChartGroupTabDisplayed("Line listing")
// [ANDROAPP-5964] Line Listing - Rendering: the line listing loads and is displayed
checkLineListing("Automated Test: Line Listing")
// [ANDROAPP-4606] Visualizations - Chart name: the line listing title is displayed
checkChartName(0, "Automated Test: Line Listing")
clickLineListingMenu(0)
// [ANDROAPP-5967] Line Listing - custom filter (search): filter a column by a typed value
clickColumnFilter("Gender")
typeColumnFilterValue(composeTestRule, "Female")
clickSearchButton(composeTestRule)
// [ANDROAPP-5966] Line Listing - filters: only matching rows remain after filtering
checkColumnContainsOnly(composeTestRule, "Male")
// [ANDROAPP-5966] Line Listing - filters: active-filter badge is shown
checkFilterBadgeVisible(0)
// [ANDROAPP-4363] Visualizations - Filter - Reset: clear the filter and badge disappears
clickLineListingMenu(0)
clickReset()
checkFilterBadgeGone(0)

// [ANDROAPP-4661] Visualizations - Type: Radar
clickChartGroup("Radar")
checkChartName(0, "Android: ANC visits (radar)")
checkChartTypeRendered(0, ChartType.RADAR)
checkNoErrorIcon(0)

// [ANDROAPP-4662] Visualizations - Type: Pie
clickChartGroup("Pie chart")
checkChartName(0, "Android: Malaria case count pie")
checkChartTypeRendered(0, ChartType.PIE_CHART)
checkNoErrorIcon(0)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
@file:OptIn(ExperimentalTestApi::class)

package org.dhis2.usescases.orgunitselector

import androidx.compose.ui.semantics.SemanticsProperties.TestTag
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.ComposeTestRule
import androidx.compose.ui.test.onNodeWithTag
Expand All @@ -12,7 +17,7 @@ import org.dhis2.common.BaseRobot

fun orgUnitSelectorRobot(
composeTestRule: ComposeTestRule,
robotBody: OrgUnitSelectorRobot.() -> Unit
robotBody: OrgUnitSelectorRobot.() -> Unit,
) {
OrgUnitSelectorRobot(composeTestRule).apply {
robotBody()
Expand All @@ -32,4 +37,22 @@ class OrgUnitSelectorRobot(private val composeTestRule: ComposeTestRule) : BaseR
.performClick()
composeTestRule.waitForIdle()
}
}

fun clickFirstOrgUnitCheckbox() {
composeTestRule.onAllNodes(
SemanticsMatcher("tag starts with ORG_TREE_ITEM_CHECKBOX_") {
runCatching { it.config[TestTag] }.getOrNull()?.startsWith("ORG_TREE_ITEM_CHECKBOX_") == true
},
)[0].performClick()
composeTestRule.waitForIdle()
}

fun clickDone() {
val doneText =
InstrumentationRegistry.getInstrumentation().targetContext.getString(R.string.done)
composeTestRule.onNodeWithText(doneText)
.assertIsDisplayed()
.performClick()
composeTestRule.waitForIdle()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,9 @@ class SearchTeiRobot(val composeTestRule: ComposeTestRule) : BaseRobot() {
composeTestRule.waitUntilAtLeastOneExists(hasTestTag("MAP_CAROUSEL"), TIMEOUT)
composeTestRule.onNodeWithTag("MAP_CAROUSEL", true)
.assertIsDisplayed()
// Wait for at least one MAP_ITEM to be rendered
composeTestRule.waitUntilAtLeastOneExists(hasTestTag("MAP_ITEM"), TIMEOUT)
// Wait for at least one MAP_ITEM to be rendered. The carousel is filled by an async fetch
// that can take longer than the default 5s on the test device, so allow 10s here.
composeTestRule.waitUntilAtLeastOneExists(hasTestTag("MAP_ITEM"), 10_000L)
// Wait extra time for data to populate in the cards
composeTestRule.waitForIdle()
// Finally assert that the text is displayed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -342,14 +342,7 @@ class TeiDashboardTest : BaseTest() {
sdkResource = API_TB_IDENTIFIER_TRACKED_ENTITY_ATTRIBUTES_RESERVED_VALUES_RESPONSE,
responseCode = 200,
)
mockWebServerRobot.addResponse(
method = ResponseController.GET,
path = API_UNIQUE_ID_TRACKED_ENTITY_ATTRIBUTES_RESERVED_VALUES_PATH,
sdkResource = API_UNIQUE_ID_TRACKED_ENTITY_ATTRIBUTES_RESERVED_VALUES_RESPONSE,
responseCode = 200,
)

val chartName = "Daily-TB smear microscopy number of specimen"
setupCredentials()
prepareTeiForAnalyticsAndLaunchActivity(rule)

Expand All @@ -358,11 +351,67 @@ class TeiDashboardTest : BaseTest() {
}

indicatorsRobot(composeTestRule) {
checkGraphIsRendered(chartName)
checkGraphIsRendered("Automated TEI chart")
}

analyticsRobot {
// [ANDROAPP-4606] Visualizations - Chart name: the chart title is displayed
checkChartName(1, "Automated TEI chart")

// [ANDROAPP-4358] Visualizations - View as Line / [ANDROAPP-4659] Type: Line:
// the chart initially renders as a line chart
checkGraphType(1, ChartType.LINE_CHART)

// [ANDROAPP-4357] Visualizations - View as Bar: switch the chart to a bar chart
clickChartVisualizationMenu(1)
clickViewAsBar()
checkGraphType(1, ChartType.BAR_CHART)

// [ANDROAPP-4360] Visualizations - View as Table: switch the chart to a table
clickChartVisualizationMenu(1)
clickViewAsTable()
checkGraphType(1, ChartType.TABLE)

// [ANDROAPP-4359] Visualizations - View as Value / [ANDROAPP-4657] Type: Single Value:
// switch the chart to a single value
clickChartVisualizationMenu(1)
clickViewAsValue()
checkGraphType(1, ChartType.SINGLE_VALUE)

// [ANDROAPP-4361] Visualizations - Filter - Period: filter the visualization by period
clickChartVisualizationMenu(1)
clickPeriodFilter()
clickDailyPeriod()
clickToday()
checkNoVisualisationError(1)

// [ANDROAPP-4362] Visualizations - Filter - OrgUnit: open the org unit selection filter
clickChartVisualizationMenu(1)
clickOrgUnitFilter()
clickOrgUnitSelection()
}

orgUnitSelectorRobot(composeTestRule) {
// [ANDROAPP-4362] Visualizations - Filter - OrgUnit: choose an org unit and apply
clickFirstOrgUnitCheckbox()
clickDone()
}

analyticsRobot {
checkNoVisualisationError(1)

// [ANDROAPP-4363] Visualizations - Filter - Reset: reset the period filter
clickChartVisualizationMenu(1)
clickPeriodFilter()
clickReset()
checkFilterBadgeVisible(1)

// [ANDROAPP-4363] Visualizations - Filter - Reset: reset the org unit filter
clickChartVisualizationMenu(1)
clickOrgUnitFilter()
clickReset()
checkFilterBadgeGone(1)
checkNoErrorIcon(1)
}
}

Expand Down
Loading
Loading