Skip to content

Commit

Permalink
Add new QR reader widget support (#3367)
Browse files Browse the repository at this point in the history
* Override barcode layout to contain required missing views

* Update qr code scanning implementation

* Document qr_code widget extension

* Isolate qr scanning functionality to QrCodeCameraDialogFragment

* Use QrCodeScanUtils to abstract barcode scanning implementation

* Support search by QR code config in register search

* Move clear button to show as last in register search

* Hide qr/barcode icon when showing clear text icon

to match design

* Wrap search text in a SearchQuery class to track mode of search

* Support going to profile directly after QR scan for single result

* Add search by QR code option in register (#3379)

* Support search by QR code config in register search

* Move clear button to show as last in register search

* Hide qr/barcode icon when showing clear text icon

to match design

* Refactor replace 'barcode' with 'qrCode'

* Add relevant tests

* Upload ci test results

to get info on test fails

* Refactor qr code single result action

to support available workflows

* Upload test results as ci artifacts

to save test logs

* test(EditTextQrCodeViewHolderFactory): Fix InstantionError

* Update qr-code widget extension to use codeableConcept

* Add ActionTrigger ON_SEARCH_SINGLE_RESULT

* Fix indefinite loop on ActionConfig handleClick

* Extend QR Code widget to support adding multiple QR codes

* Add kdoc to UiSearchQuery data class

* Fix test compilation errors

* Resolve initial review comments

* Fix qr code scan when repeat false

Questionnaire item with linkId qr-code-uuid-widget does not allow repeated answers

* Fix ON_SEARCH_SINGLE_RESULT action trigger

for profile launch

* Rename ConfigExtensionsTest to ConfigExtensionsKtTest

to fix coverage ??

* Update tests for EditTextQrCodeItemViewHolderFactory

* Update tests for EditTextQrCodeViewHolderFactory

* Resolve requested changes

* Fix profile launch always redirecting on single search result

* Revert to use find

since Iterable#find internally seems to just use firstOrNull

* Add ui android tests for RegisterScreen

* Refactor test for QrCodeScanUtils#scanQrCode

---------

Co-authored-by: Peter Lubell-Doughtie <peter@ona.io>
Co-authored-by: Allan Onchuru <16164649+allan-on@users.noreply.github.com>
  • Loading branch information
3 people committed Aug 7, 2024
1 parent 8711045 commit 9f50d19
Show file tree
Hide file tree
Showing 45 changed files with 2,526 additions and 268 deletions.
48 changes: 35 additions & 13 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ jobs:
- name: Grant execute permission for gradlew
run: chmod +x gradlew
working-directory: android

- name: Copy CI gradle.properties
run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties && cat ~/.gradle/gradle.properties

Expand All @@ -63,8 +63,8 @@ jobs:

- name: Spotless check engine module
run: ./gradlew -PlocalPropertiesFile=local.properties :engine:spotlessCheck :engine:ktlintCheck --stacktrace
working-directory: android
working-directory: android

- name: Load AVD cache
uses: actions/cache@v4
id: avd-cache
Expand All @@ -85,10 +85,10 @@ jobs:
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: false
script: echo "Generated AVD snapshot for caching."

- name: Run Engine module unit and instrumentation tests and generate coverage report
uses: reactivecircus/android-emulator-runner@v2
with:
with:
working-directory: android
api-level: ${{ matrix.api-level }}
arch: x86_64
Expand All @@ -97,6 +97,14 @@ jobs:
disable-animations: true
script: ./gradlew -PlocalPropertiesFile=local.properties :engine:clean :engine:fhircoreJacocoReport --stacktrace

- name: Upload Test reports
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v4
with:
name: engine-test-reports
path: android/engine/build/reports


- name: Upload Engine module test coverage report to Codecov
if: matrix.api-level == 30 # Only upload coverage on API level 30
working-directory: android
Expand Down Expand Up @@ -139,7 +147,7 @@ jobs:
- name: Grant execute permission for gradlew
run: chmod +x gradlew
working-directory: android

- name: Copy CI gradle.properties
run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties && cat ~/.gradle/gradle.properties

Expand All @@ -148,8 +156,8 @@ jobs:

- name: Spotless check geowidget module
run: ./gradlew -PlocalPropertiesFile=local.properties :geowidget:spotlessCheck --stacktrace
working-directory: android
working-directory: android

- name: Load AVD cache
uses: actions/cache@v4
id: avd-cache
Expand All @@ -170,10 +178,10 @@ jobs:
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: false
script: echo "Generated AVD snapshot for caching."

- name: Run Geowidget module unit and instrumentation tests and generate coverage report
uses: reactivecircus/android-emulator-runner@v2
with:
with:
working-directory: android
api-level: ${{ matrix.api-level }}
arch: x86_64
Expand All @@ -182,6 +190,13 @@ jobs:
disable-animations: true
script: ./gradlew -PlocalPropertiesFile=local.properties :geowidget:clean :geowidget:fhircoreJacocoReport --stacktrace

- name: Upload Test reports
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v4
with:
name: geowidget-test-reports
path: android/geowidget/build/reports

- name: Upload Geowidget module test coverage report to Codecov
if: matrix.api-level == 30 # Only upload coverage on API level 30
working-directory: android
Expand Down Expand Up @@ -265,11 +280,11 @@ jobs:
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: true
script: ./gradlew clean -PlocalPropertiesFile=local.properties :quest:fhircoreJacocoReport --stacktrace -Pandroid.testInstrumentationRunnerArguments.notPackage=org.smartregister.fhircore.quest.performance

- name: Run Quest module unit and instrumentation tests and generate aggregated coverage report (Disabled)
if: false
uses: reactivecircus/android-emulator-runner@v2
with:
with:
working-directory: android
api-level: ${{ matrix.api-level }}
arch: x86_64
Expand All @@ -291,7 +306,14 @@ jobs:
/Users/martin/Library/Android/sdk/platform-tools/adb shell run-as org.smartregister.opensrp \
cat "/data/user/0/org.smartregister.opensrp/files/coverage.ec" > quest/coverage.ec
./gradlew -PlocalPropertiesFile=local.properties :quest:fhircoreJacocoReport --stacktrace
- name: Upload Test reports
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v4
with:
name: quest-test-reports
path: android/quest/build/reports

- name: Upload Quest module test coverage report to Codecov
if: matrix.api-level == 30 # Only upload coverage on API level 30
working-directory: android
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import org.hl7.fhir.r4.model.ResourceType
import org.smartregister.fhircore.engine.configuration.ConfigType
import org.smartregister.fhircore.engine.configuration.Configuration
import org.smartregister.fhircore.engine.configuration.navigation.NavigationMenuConfig
import org.smartregister.fhircore.engine.configuration.workflow.ActionTrigger
import org.smartregister.fhircore.engine.domain.model.ActionConfig
import org.smartregister.fhircore.engine.domain.model.FhirResourceConfig
import org.smartregister.fhircore.engine.domain.model.RuleConfig
import org.smartregister.fhircore.engine.domain.model.TopScreenSectionConfig
Expand All @@ -47,4 +49,11 @@ data class RegisterConfiguration(
val registerFilter: RegisterFilterConfig? = null,
val filterDataByRelatedEntityLocation: Boolean = false,
val topScreenSection: TopScreenSectionConfig? = null,
) : Configuration()
val onSearchByQrSingleResultActions: List<ActionConfig>? = null,
) : Configuration() {
val onSearchByQrSingleResultValidActions =
onSearchByQrSingleResultActions?.filter { it.trigger == ActionTrigger.ON_SEARCH_SINGLE_RESULT }

val showSearchByQrCode =
!onSearchByQrSingleResultValidActions.isNullOrEmpty() || searchBar?.searchByQrCode == true
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@ data class RegisterContentConfig(
val rules: List<RuleConfig>? = null,
val visible: Boolean? = null,
val computedRules: List<String>? = null,
val searchByQrCode: Boolean? = null,
)
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,7 @@ enum class ActionTrigger {

/** Action that is triggered when a Questionnaire has been submitted */
ON_QUESTIONNAIRE_SUBMISSION,

/** Action triggered on search returning single result */
ON_SEARCH_SINGLE_RESULT,
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,31 @@

package org.smartregister.fhircore.engine.util.test

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.google.android.fhir.sync.CurrentSyncJobStatus
import dagger.hilt.android.AndroidEntryPoint
import org.smartregister.fhircore.engine.R
import org.smartregister.fhircore.engine.sync.OnSyncListener
import org.smartregister.fhircore.engine.util.annotation.ExcludeFromJacocoGeneratedReport

@ExcludeFromJacocoGeneratedReport
@AndroidEntryPoint
class HiltActivityForTest : AppCompatActivity(), OnSyncListener {
override fun onCreate(savedInstanceState: Bundle?) {
if (intent.hasExtra(THEME_EXTRAS_BUNDLE_KEY)) {
setTheme(intent.getIntExtra(THEME_EXTRAS_BUNDLE_KEY, R.style.AppTheme))
}

super.onCreate(savedInstanceState)
}

override fun onSync(syncJobStatus: CurrentSyncJobStatus) {
// DO nothing. This activity implements OnSyncListener for testing purposes
}

companion object {
const val THEME_EXTRAS_BUNDLE_KEY =
"org.smartregister.fhircore.engine.util.test.HiltActivityForTest.THEME_EXTRAS_BUNDLE_KEY"
}
}
1 change: 0 additions & 1 deletion android/engine/src/main/res/values/styles.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
<item name="cardElevation">8dp</item>
</style>


<style name="AlertDialogTheme" parent="ThemeOverlay.MaterialComponents.Dialog.Alert">
<item name="buttonBarNegativeButtonStyle">@style/NegativeButtonStyle</item>
<item name="buttonBarPositiveButtonStyle">@style/PositiveButtonStyle</item>
Expand Down
2 changes: 1 addition & 1 deletion android/quest/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,7 @@ dependencies {
implementation(libs.material)
implementation(libs.dagger.hilt.android)
implementation(libs.hilt.work)
implementation(libs.play.services.location)
implementation(libs.gms.play.services.location)

// Annotation processors
kapt(libs.hilt.compiler)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@

package org.smartregister.fhircore.quest.integration.ui.main.components

import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.test.assertIsDisplayed
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.navigation.NavController
import io.mockk.mockk
import androidx.navigation.testing.TestNavHostController
import org.junit.Assert
import org.junit.Rule
import org.junit.Test
Expand All @@ -33,22 +33,23 @@ import org.smartregister.fhircore.quest.ui.main.components.TOP_ROW_ICON_TEST_TAG
import org.smartregister.fhircore.quest.ui.main.components.TOP_ROW_TEXT_TEST_TAG
import org.smartregister.fhircore.quest.ui.main.components.TRAILING_ICON_BUTTON_TEST_TAG
import org.smartregister.fhircore.quest.ui.main.components.TRAILING_ICON_TEST_TAG
import org.smartregister.fhircore.quest.ui.main.components.TRAILING_QR_SCAN_ICON_BUTTON_TEST_TAG
import org.smartregister.fhircore.quest.ui.main.components.TopScreenSection
import org.smartregister.fhircore.quest.ui.shared.models.SearchQuery

class TopScreenSectionTest {
private val listener: (String) -> Unit = {}
private val listener: (SearchQuery) -> Unit = {}

@get:Rule val composeTestRule = createComposeRule()
private val navController: NavController = mockk(relaxUnitFun = true)

@Test
fun testTopScreenSectionRendersTitleRowCorrectly() {
composeTestRule.setContent {
TopScreenSection(
title = "All Clients",
searchText = "search text",
searchQuery = SearchQuery("search text"),
onSearchTextChanged = listener,
navController = navController,
navController = TestNavHostController(LocalContext.current),
isSearchBarVisible = true,
onClick = {},
)
Expand Down Expand Up @@ -77,9 +78,9 @@ class TopScreenSectionTest {
composeTestRule.setContent {
TopScreenSection(
title = "All Clients",
searchText = "search text",
searchQuery = SearchQuery("search text"),
onSearchTextChanged = listener,
navController = navController,
navController = TestNavHostController(LocalContext.current),
isSearchBarVisible = true,
onClick = {},
)
Expand Down Expand Up @@ -110,9 +111,9 @@ class TopScreenSectionTest {
composeTestRule.setContent {
TopScreenSection(
title = "All Clients",
searchText = "search text",
searchQuery = SearchQuery("search text"),
onSearchTextChanged = { clicked = true },
navController = navController,
navController = TestNavHostController(LocalContext.current),
isSearchBarVisible = true,
onClick = {},
)
Expand All @@ -123,4 +124,34 @@ class TopScreenSectionTest {
trailingIcon.performClick()
Assert.assertTrue(clicked)
}

@Test
fun thatTopScreenSectionHideQrCodeIconWhenShowSearchByQrCodeIsTrueAndSearchQueryIsNotBlank() {
composeTestRule.setContent {
TopScreenSection(
title = "All Clients",
searchQuery = SearchQuery("search text"),
showSearchByQrCode = true,
navController = TestNavHostController(LocalContext.current),
isSearchBarVisible = true,
onClick = {},
)
}
composeTestRule.onNodeWithTag(TRAILING_QR_SCAN_ICON_BUTTON_TEST_TAG).assertDoesNotExist()
}

@Test
fun thatTopScreenSectionShowsQrCodeIconWhenShowSearchByQrCodeIsTrueAndSearchQueryIsBlank() {
composeTestRule.setContent {
TopScreenSection(
title = "All Clients",
searchQuery = SearchQuery(""),
showSearchByQrCode = true,
navController = TestNavHostController(LocalContext.current),
isSearchBarVisible = true,
onClick = {},
)
}
composeTestRule.onNodeWithTag(TRAILING_QR_SCAN_ICON_BUTTON_TEST_TAG).assertIsDisplayed()
}
}
Loading

0 comments on commit 9f50d19

Please sign in to comment.