diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionConsentFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionConsentFragmentTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..aa8dbed36474a8b139559f3aaaf18029c27e38fb --- /dev/null +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionConsentFragmentTest.kt @@ -0,0 +1,67 @@ +package de.rki.coronawarnapp.ui.submission + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import dagger.Module +import dagger.android.ContributesAndroidInjector +import de.rki.coronawarnapp.storage.interoperability.InteroperabilityRepository +import de.rki.coronawarnapp.submission.SubmissionRepository +import de.rki.coronawarnapp.ui.submission.qrcode.consent.SubmissionConsentFragment +import de.rki.coronawarnapp.ui.submission.qrcode.consent.SubmissionConsentViewModel +import io.mockk.MockKAnnotations +import io.mockk.every +import io.mockk.impl.annotations.MockK +import kotlinx.coroutines.flow.flowOf +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import testhelpers.BaseUITest +import testhelpers.Screenshot +import testhelpers.SystemUIDemoModeRule +import testhelpers.TestDispatcherProvider +import testhelpers.captureScreenshot +import tools.fastlane.screengrab.locale.LocaleTestRule + +@RunWith(AndroidJUnit4::class) +class SubmissionConsentFragmentTest : BaseUITest() { + + @MockK lateinit var submissionRepository: SubmissionRepository + @MockK lateinit var interoperabilityRepository: InteroperabilityRepository + + @Rule + @JvmField + val localeTestRule = LocaleTestRule() + + @get:Rule + val systemUIDemoModeRule = SystemUIDemoModeRule() + + private lateinit var viewModel: SubmissionConsentViewModel + + @Before + fun setup() { + MockKAnnotations.init(this, relaxed = true) + every { interoperabilityRepository.countryList } returns flowOf() + viewModel = SubmissionConsentViewModel(submissionRepository, interoperabilityRepository, TestDispatcherProvider) + setupMockViewModel(object : SubmissionConsentViewModel.Factory { + override fun create(): SubmissionConsentViewModel = viewModel + }) + } + + @After + fun teardown() { + clearAllViewModels() + } + + @Test + @Screenshot + fun capture_fragment_results() { + captureScreenshot<SubmissionConsentFragment>() + } +} + +@Module +abstract class SubmissionConsentFragmentTestModule { + @ContributesAndroidInjector + abstract fun submissionConsentScreen(): SubmissionConsentFragment +} diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionContactFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionContactFragmentTest.kt index 5e262d08eb3ddcf76f2b7b40760a1ab8fa91dc08..7c5182bb74e9040eb7e2560a0a53d617276f35b6 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionContactFragmentTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionContactFragmentTest.kt @@ -12,23 +12,34 @@ import de.rki.coronawarnapp.R import de.rki.coronawarnapp.ui.submission.fragment.SubmissionContactFragment import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionContactViewModel import io.mockk.MockKAnnotations -import io.mockk.impl.annotations.MockK import org.junit.After import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import testhelpers.BaseUITest +import testhelpers.Screenshot +import testhelpers.SystemUIDemoModeRule +import testhelpers.captureScreenshot +import tools.fastlane.screengrab.locale.LocaleTestRule @RunWith(AndroidJUnit4::class) class SubmissionContactFragmentTest : BaseUITest() { - @MockK lateinit var viewModel: SubmissionContactViewModel + @Rule + @JvmField + val localeTestRule = LocaleTestRule() + + @get:Rule + val systemUIDemoModeRule = SystemUIDemoModeRule() + + private fun createViewModel() = SubmissionContactViewModel() @Before fun setup() { MockKAnnotations.init(this, relaxed = true) setupMockViewModel(object : SubmissionContactViewModel.Factory { - override fun create(): SubmissionContactViewModel = viewModel + override fun create(): SubmissionContactViewModel = createViewModel() }) } @@ -42,7 +53,8 @@ class SubmissionContactFragmentTest : BaseUITest() { launchFragment<SubmissionContactFragment>() } - @Test fun testContactCallClicked() { + @Test + fun testContactCallClicked() { val scenario = launchFragmentInContainer<SubmissionContactFragment>() onView(withId(R.id.submission_contact_button_call)) .perform(click()) @@ -50,13 +62,20 @@ class SubmissionContactFragmentTest : BaseUITest() { // TODO verify result } - @Test fun testContactEnterTanClicked() { + @Test + fun testContactEnterTanClicked() { val scenario = launchFragmentInContainer<SubmissionContactFragment>() onView(withId(R.id.submission_contact_button_enter)) .perform(click()) // TODO verify result } + + @Test + @Screenshot + fun capture_fragment() { + captureScreenshot<SubmissionContactFragment>() + } } @Module diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionDispatcherFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionDispatcherFragmentTest.kt index fc417a990b5c53a98167039c839d67bbee6fcb07..6d9559b36e679e267af1ed08072986bd442e803a 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionDispatcherFragmentTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionDispatcherFragmentTest.kt @@ -13,23 +13,36 @@ import de.rki.coronawarnapp.R import de.rki.coronawarnapp.ui.submission.fragment.SubmissionDispatcherFragment import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionDispatcherViewModel import io.mockk.MockKAnnotations -import io.mockk.impl.annotations.MockK import org.junit.After import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import testhelpers.BaseUITest +import testhelpers.SCREENSHOT_DELAY_TIME +import testhelpers.Screenshot +import testhelpers.SystemUIDemoModeRule +import testhelpers.captureScreenshot +import tools.fastlane.screengrab.Screengrab +import tools.fastlane.screengrab.locale.LocaleTestRule @RunWith(AndroidJUnit4::class) class SubmissionDispatcherFragmentTest : BaseUITest() { - @MockK lateinit var viewModel: SubmissionDispatcherViewModel + @Rule + @JvmField + val localeTestRule = LocaleTestRule() + + @get:Rule + val systemUIDemoModeRule = SystemUIDemoModeRule() + + private fun createViewModel() = SubmissionDispatcherViewModel() @Before fun setup() { MockKAnnotations.init(this, relaxed = true) setupMockViewModel(object : SubmissionDispatcherViewModel.Factory { - override fun create(): SubmissionDispatcherViewModel = viewModel + override fun create(): SubmissionDispatcherViewModel = createViewModel() }) } @@ -43,7 +56,8 @@ class SubmissionDispatcherFragmentTest : BaseUITest() { launchFragment<SubmissionDispatcherFragment>() } - @Test fun testEventQRClicked() { + @Test + fun testEventQRClicked() { val scenario = launchFragmentInContainer<SubmissionDispatcherFragment>() onView(withId(R.id.submission_dispatcher_qr)) .perform(scrollTo()) @@ -52,7 +66,8 @@ class SubmissionDispatcherFragmentTest : BaseUITest() { // TODO verify result } - @Test fun testEventTeleClicked() { + @Test + fun testEventTeleClicked() { val scenario = launchFragmentInContainer<SubmissionDispatcherFragment>() onView(withId(R.id.submission_dispatcher_tan_tele)) .perform(scrollTo()) @@ -61,7 +76,8 @@ class SubmissionDispatcherFragmentTest : BaseUITest() { // TODO verify result } - @Test fun testEventTanClicked() { + @Test + fun testEventTanClicked() { val scenario = launchFragmentInContainer<SubmissionDispatcherFragment>() onView(withId(R.id.submission_dispatcher_tan_code)) .perform(scrollTo()) @@ -69,6 +85,16 @@ class SubmissionDispatcherFragmentTest : BaseUITest() { // TODO verify result } + + @Test + @Screenshot + fun capture_fragment() { + captureScreenshot<SubmissionDispatcherFragment>() + onView(withId(R.id.submission_dispatcher_tan_tele)) + .perform(scrollTo()) + Thread.sleep(SCREENSHOT_DELAY_TIME) + Screengrab.screenshot(SubmissionDispatcherFragment::class.simpleName.plus("2")) + } } @Module diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionSymptomCalendarFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionSymptomCalendarFragmentTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..a2ad56bdd115aaae9624bd742d6ab2b67ad67952 --- /dev/null +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionSymptomCalendarFragmentTest.kt @@ -0,0 +1,95 @@ +package de.rki.coronawarnapp.ui.submission + +import androidx.lifecycle.MutableLiveData +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions +import androidx.test.espresso.matcher.ViewMatchers +import androidx.test.ext.junit.runners.AndroidJUnit4 +import dagger.Module +import dagger.android.ContributesAndroidInjector +import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.submission.SubmissionRepository +import de.rki.coronawarnapp.submission.Symptoms +import de.rki.coronawarnapp.submission.auto.AutoSubmission +import de.rki.coronawarnapp.ui.submission.symptoms.calendar.SubmissionSymptomCalendarFragment +import de.rki.coronawarnapp.ui.submission.symptoms.calendar.SubmissionSymptomCalendarFragmentArgs +import de.rki.coronawarnapp.ui.submission.symptoms.calendar.SubmissionSymptomCalendarViewModel +import io.mockk.MockKAnnotations +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.spyk +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import testhelpers.BaseUITest +import testhelpers.SCREENSHOT_DELAY_TIME +import testhelpers.Screenshot +import testhelpers.SystemUIDemoModeRule +import testhelpers.TestDispatcherProvider +import testhelpers.captureScreenshot +import tools.fastlane.screengrab.Screengrab +import tools.fastlane.screengrab.locale.LocaleTestRule + +@RunWith(AndroidJUnit4::class) +class SubmissionSymptomCalendarFragmentTest : BaseUITest() { + + @MockK lateinit var submissionRepository: SubmissionRepository + @MockK lateinit var autoSubmission: AutoSubmission + + @Rule + @JvmField + val localeTestRule = LocaleTestRule() + + @get:Rule + val systemUIDemoModeRule = SystemUIDemoModeRule() + + private lateinit var viewModel: SubmissionSymptomCalendarViewModel + + @Before + fun setup() { + MockKAnnotations.init(this, relaxed = true) + viewModel = spyk( + SubmissionSymptomCalendarViewModel( + Symptoms.Indication.POSITIVE, + TestDispatcherProvider, + submissionRepository, + autoSubmission + ) + ) + with(viewModel) { + every { symptomStart } returns MutableLiveData(Symptoms.StartOf.LastSevenDays) + } + setupMockViewModel(object : SubmissionSymptomCalendarViewModel.Factory { + override fun create(symptomIndication: Symptoms.Indication): SubmissionSymptomCalendarViewModel = viewModel + }) + } + + @After + fun teardown() { + clearAllViewModels() + } + + @Test + @Screenshot + fun capture_fragment() { + captureScreenshot<SubmissionSymptomCalendarFragment>( + fragmentArgs = SubmissionSymptomCalendarFragmentArgs( + Symptoms.Indication.POSITIVE + ).toBundle() + ) + + onView(ViewMatchers.withId(R.id.symptom_button_next)) + .perform(ViewActions.scrollTo()) + + Thread.sleep(SCREENSHOT_DELAY_TIME) + Screengrab.screenshot(SubmissionSymptomCalendarFragment::class.simpleName.plus("2")) + } +} + +@Module +abstract class SubmissionSymptomCalendarFragmentTestModule { + @ContributesAndroidInjector + abstract fun submissionSymptomIntroScreen(): SubmissionSymptomCalendarFragment +} diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionSymptomIntroFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionSymptomIntroFragmentTest.kt index ed959a210f9b3b33f3821b245fc5b0af3900cf1f..4b8224920c6278fcfd1c878b9883e16bd7911a15 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionSymptomIntroFragmentTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionSymptomIntroFragmentTest.kt @@ -2,6 +2,7 @@ package de.rki.coronawarnapp.ui.submission import androidx.fragment.app.testing.launchFragment import androidx.fragment.app.testing.launchFragmentInContainer +import androidx.lifecycle.MutableLiveData import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.action.ViewActions.scrollTo @@ -10,24 +11,52 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import dagger.Module import dagger.android.ContributesAndroidInjector import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.submission.SubmissionRepository +import de.rki.coronawarnapp.submission.Symptoms +import de.rki.coronawarnapp.submission.auto.AutoSubmission import de.rki.coronawarnapp.ui.submission.symptoms.introduction.SubmissionSymptomIntroductionFragment import de.rki.coronawarnapp.ui.submission.symptoms.introduction.SubmissionSymptomIntroductionViewModel import io.mockk.MockKAnnotations +import io.mockk.every import io.mockk.impl.annotations.MockK +import io.mockk.spyk import org.junit.After import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import testhelpers.BaseUITest +import testhelpers.SCREENSHOT_DELAY_TIME +import testhelpers.Screenshot +import testhelpers.SystemUIDemoModeRule +import testhelpers.TestDispatcherProvider +import testhelpers.captureScreenshot +import tools.fastlane.screengrab.Screengrab +import tools.fastlane.screengrab.locale.LocaleTestRule @RunWith(AndroidJUnit4::class) class SubmissionSymptomIntroFragmentTest : BaseUITest() { - @MockK lateinit var viewModel: SubmissionSymptomIntroductionViewModel + @MockK lateinit var submissionRepository: SubmissionRepository + @MockK lateinit var autoSubmission: AutoSubmission + + @Rule + @JvmField + val localeTestRule = LocaleTestRule() + + @get:Rule + val systemUIDemoModeRule = SystemUIDemoModeRule() + + private lateinit var viewModel: SubmissionSymptomIntroductionViewModel @Before fun setup() { MockKAnnotations.init(this, relaxed = true) + viewModel = + spyk(SubmissionSymptomIntroductionViewModel(TestDispatcherProvider, submissionRepository, autoSubmission)) + with(viewModel) { + every { symptomIndication } returns MutableLiveData(Symptoms.Indication.POSITIVE) + } setupMockViewModel(object : SubmissionSymptomIntroductionViewModel.Factory { override fun create(): SubmissionSymptomIntroductionViewModel = viewModel }) @@ -43,7 +72,8 @@ class SubmissionSymptomIntroFragmentTest : BaseUITest() { launchFragment<SubmissionSymptomIntroductionFragment>() } - @Test fun testSymptomNextClicked() { + @Test + fun testSymptomNextClicked() { val scenario = launchFragmentInContainer<SubmissionSymptomIntroductionFragment>() onView(withId(R.id.symptom_button_next)) .perform(scrollTo()) @@ -51,6 +81,16 @@ class SubmissionSymptomIntroFragmentTest : BaseUITest() { // TODO verify result } + + @Test + @Screenshot + fun capture_fragment() { + captureScreenshot<SubmissionSymptomIntroductionFragment>() + onView(withId(R.id.symptom_button_next)) + .perform(scrollTo()) + Thread.sleep(SCREENSHOT_DELAY_TIME) + Screengrab.screenshot(SubmissionSymptomIntroductionFragment::class.simpleName.plus("2")) + } } @Module diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTanFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTanFragmentTest.kt index b2dc3033d936ec5bcbb81754816dcbf80498e5c5..efe766a2ed753f65626bc3f29b44d0eec7fce3ac 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTanFragmentTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTanFragmentTest.kt @@ -5,33 +5,56 @@ import androidx.fragment.app.testing.launchFragmentInContainer import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.action.ViewActions.closeSoftKeyboard import androidx.test.espresso.action.ViewActions.scrollTo +import androidx.test.espresso.action.ViewActions.typeText import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.ext.junit.runners.AndroidJUnit4 import dagger.Module import dagger.android.ContributesAndroidInjector import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.ui.submission.tan.SubmissionTanFragment import de.rki.coronawarnapp.ui.submission.tan.SubmissionTanViewModel import io.mockk.MockKAnnotations import io.mockk.impl.annotations.MockK import org.junit.After import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import testhelpers.BaseUITest +import testhelpers.SCREENSHOT_DELAY_TIME +import testhelpers.Screenshot +import testhelpers.SystemUIDemoModeRule +import testhelpers.TestDispatcherProvider +import testhelpers.launchFragmentInContainer2 +import tools.fastlane.screengrab.Screengrab +import tools.fastlane.screengrab.locale.LocaleTestRule @RunWith(AndroidJUnit4::class) class SubmissionTanFragmentTest : BaseUITest() { - @MockK lateinit var viewModel: SubmissionTanViewModel + @MockK lateinit var submissionRepository: SubmissionRepository + + private fun createViewModel() = SubmissionTanViewModel( + dispatcherProvider = TestDispatcherProvider, + submissionRepository = submissionRepository + ) + + @Rule + @JvmField + val localeTestRule = LocaleTestRule() + + @get:Rule + val systemUIDemoModeRule = SystemUIDemoModeRule() @Before fun setup() { MockKAnnotations.init(this, relaxed = true) setupMockViewModel(object : SubmissionTanViewModel.Factory { - override fun create(): SubmissionTanViewModel = viewModel + override fun create(): SubmissionTanViewModel = createViewModel() }) } @@ -45,7 +68,8 @@ class SubmissionTanFragmentTest : BaseUITest() { launchFragment<SubmissionTanFragment>() } - @Test fun testEventTanNextClicked() { + @Test + fun testEventTanNextClicked() { val scenario = launchFragmentInContainer<SubmissionTanFragment>() ViewActions.closeSoftKeyboard() onView(withId(R.id.submission_tan_button_enter)) @@ -54,6 +78,39 @@ class SubmissionTanFragmentTest : BaseUITest() { // TODO verify result } + + @Test + @Screenshot + fun capture_fragment_empty() { + launchFragmentInContainer2<SubmissionTanFragment>() + onView(withId(R.id.tan_input_edittext)) + .perform(click()) + .perform(closeSoftKeyboard()) + Thread.sleep(SCREENSHOT_DELAY_TIME) + Screengrab.screenshot(SubmissionTanFragment::class.simpleName) + } + + @Test + @Screenshot + fun capture_fragment_done() { + launchFragmentInContainer2<SubmissionTanFragment>() + onView(withId(R.id.tan_input_edittext)) + .perform(click()) + .perform(typeText("AC9UHD65AF"), closeSoftKeyboard()) + Thread.sleep(SCREENSHOT_DELAY_TIME) + Screengrab.screenshot(SubmissionTanFragment::class.simpleName.plus("_done")) + } + + @Test + @Screenshot + fun capture_fragment_invalid() { + launchFragmentInContainer2<SubmissionTanFragment>() + onView(withId(R.id.tan_input_edittext)) + .perform(click()) + .perform(typeText("AC9U0"), closeSoftKeyboard()) + Thread.sleep(SCREENSHOT_DELAY_TIME) + Screengrab.screenshot(SubmissionTanFragment::class.simpleName.plus("_invalid")) + } } @Module diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultAvailableFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultAvailableFragmentTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..b0370de7b7dba3d4e1d61130911b146fe23b5ee9 --- /dev/null +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultAvailableFragmentTest.kt @@ -0,0 +1,89 @@ +package de.rki.coronawarnapp.ui.submission + +import androidx.lifecycle.MutableLiveData +import androidx.test.ext.junit.runners.AndroidJUnit4 +import dagger.Module +import dagger.android.ContributesAndroidInjector +import de.rki.coronawarnapp.submission.SubmissionRepository +import de.rki.coronawarnapp.submission.auto.AutoSubmission +import de.rki.coronawarnapp.submission.data.tekhistory.TEKHistoryUpdater_AssistedFactory +import de.rki.coronawarnapp.ui.submission.resultavailable.SubmissionTestResultAvailableFragment +import de.rki.coronawarnapp.ui.submission.resultavailable.SubmissionTestResultAvailableViewModel +import io.mockk.MockKAnnotations +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.spyk +import kotlinx.coroutines.flow.flowOf +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import testhelpers.BaseUITest +import testhelpers.Screenshot +import testhelpers.SystemUIDemoModeRule +import testhelpers.TestDispatcherProvider +import testhelpers.captureScreenshot +import tools.fastlane.screengrab.locale.LocaleTestRule + +@RunWith(AndroidJUnit4::class) +class SubmissionTestResultAvailableFragmentTest : BaseUITest() { + + lateinit var viewModel: SubmissionTestResultAvailableViewModel + @MockK lateinit var submissionRepository: SubmissionRepository + @MockK lateinit var tekHistoryUpdaterFactory: TEKHistoryUpdater_AssistedFactory + @MockK lateinit var autoSubmission: AutoSubmission + + @Rule + @JvmField + val localeTestRule = LocaleTestRule() + + @get:Rule + val systemUIDemoModeRule = SystemUIDemoModeRule() + + @Before + fun setup() { + MockKAnnotations.init(this, relaxed = true) + + every { submissionRepository.deviceUIStateFlow } returns flowOf() + every { submissionRepository.testResultReceivedDateFlow } returns flowOf() + + viewModel = spyk( + SubmissionTestResultAvailableViewModel( + TestDispatcherProvider, + tekHistoryUpdaterFactory, + submissionRepository, + autoSubmission + ) + ) + + setupMockViewModel(object : SubmissionTestResultAvailableViewModel.Factory { + override fun create(): SubmissionTestResultAvailableViewModel = viewModel + }) + } + + @After + fun teardown() { + clearAllViewModels() + } + + @Test + @Screenshot + fun capture_fragment_with_consent() { + every { viewModel.consent } returns MutableLiveData(true) + captureScreenshot<SubmissionTestResultAvailableFragment>("_consent") + } + + @Test + @Screenshot + fun capture_fragment_without_consent() { + every { viewModel.consent } returns MutableLiveData(false) + captureScreenshot<SubmissionTestResultAvailableFragment>("_no_consent") + } +} + +@Module +abstract class SubmissionTestResultTestAvailableModule { + @ContributesAndroidInjector + abstract fun submissionTestResultScreen(): SubmissionTestResultAvailableFragment +} diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultConsentGivenFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultConsentGivenFragmentTest.kt index ff0c7209479314d11f2a5d7cb1e32bc2141d635a..87c51b46a4bcef15ec7daa619bd1d9cc33ce825b 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultConsentGivenFragmentTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultConsentGivenFragmentTest.kt @@ -2,6 +2,7 @@ package de.rki.coronawarnapp.ui.submission import androidx.fragment.app.testing.launchFragment import androidx.fragment.app.testing.launchFragmentInContainer +import androidx.lifecycle.MutableLiveData import androidx.navigation.NavController import androidx.navigation.Navigation import androidx.test.espresso.Espresso.onView @@ -11,26 +12,58 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import dagger.Module import dagger.android.ContributesAndroidInjector import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.submission.SubmissionRepository +import de.rki.coronawarnapp.submission.auto.AutoSubmission +import de.rki.coronawarnapp.ui.submission.testresult.TestResultUIState import de.rki.coronawarnapp.ui.submission.testresult.positive.SubmissionTestResultConsentGivenFragment import de.rki.coronawarnapp.ui.submission.testresult.positive.SubmissionTestResultConsentGivenViewModel +import de.rki.coronawarnapp.util.DeviceUIState +import de.rki.coronawarnapp.util.NetworkRequestWrapper import io.mockk.MockKAnnotations +import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.mockk +import io.mockk.spyk import io.mockk.verify import org.junit.After import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import testhelpers.BaseUITest +import testhelpers.Screenshot +import testhelpers.SystemUIDemoModeRule +import testhelpers.TestDispatcherProvider +import testhelpers.captureScreenshot +import tools.fastlane.screengrab.locale.LocaleTestRule +import java.util.Date @RunWith(AndroidJUnit4::class) class SubmissionTestResultConsentGivenFragmentTest : BaseUITest() { - @MockK lateinit var viewModel: SubmissionTestResultConsentGivenViewModel + @MockK lateinit var submissionRepository: SubmissionRepository + @MockK lateinit var autoSubmission: AutoSubmission + + @Rule + @JvmField + val localeTestRule = LocaleTestRule() + + @get:Rule + val systemUIDemoModeRule = SystemUIDemoModeRule() + + private lateinit var viewModel: SubmissionTestResultConsentGivenViewModel @Before fun setup() { MockKAnnotations.init(this, relaxed = true) + viewModel = + spyk( + SubmissionTestResultConsentGivenViewModel( + submissionRepository, + autoSubmission, + TestDispatcherProvider + ) + ) setupMockViewModel(object : SubmissionTestResultConsentGivenViewModel.Factory { override fun create(): SubmissionTestResultConsentGivenViewModel = viewModel }) @@ -61,6 +94,20 @@ class SubmissionTestResultConsentGivenFragmentTest : BaseUITest() { mockNavController.navigate(R.id.action_submissionTestResultConsentGivenFragment_to_submissionSymptomIntroductionFragment) } } + + @Test + @Screenshot + fun capture_fragment() { + every { viewModel.uiState } returns MutableLiveData( + TestResultUIState( + NetworkRequestWrapper.RequestSuccessful( + DeviceUIState.PAIRED_POSITIVE + ), Date() + ) + ) + + captureScreenshot<SubmissionTestResultConsentGivenFragment>() + } } @Module diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultFragmentTest.kt index 7d7a1032e9ae0a51b8b5d7e7339fd71d04d3411e..fcffa178151600f2dd5505f24724b7386112d467 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultFragmentTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultFragmentTest.kt @@ -10,32 +10,68 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import dagger.Module import dagger.android.ContributesAndroidInjector import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.notification.TestResultNotificationService +import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.ui.submission.testresult.TestResultUIState import de.rki.coronawarnapp.ui.submission.testresult.pending.SubmissionTestResultPendingFragment import de.rki.coronawarnapp.ui.submission.testresult.pending.SubmissionTestResultPendingViewModel +import de.rki.coronawarnapp.util.DeviceUIState +import de.rki.coronawarnapp.util.NetworkRequestWrapper import io.mockk.MockKAnnotations +import io.mockk.Runs import io.mockk.every import io.mockk.impl.annotations.MockK +import io.mockk.just +import io.mockk.spyk +import kotlinx.coroutines.flow.flowOf import org.junit.After import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import testhelpers.BaseUITest +import testhelpers.Screenshot +import testhelpers.SystemUIDemoModeRule +import testhelpers.TestDispatcherProvider +import testhelpers.captureScreenshot +import tools.fastlane.screengrab.locale.LocaleTestRule +import java.util.Date @RunWith(AndroidJUnit4::class) class SubmissionTestResultFragmentTest : BaseUITest() { - @MockK lateinit var pendingViewModel: SubmissionTestResultPendingViewModel - @MockK lateinit var uiState: TestResultUIState + lateinit var viewModel: SubmissionTestResultPendingViewModel + @MockK lateinit var submissionRepository: SubmissionRepository + @MockK lateinit var testResultNotificationService: TestResultNotificationService + + @Rule + @JvmField + val localeTestRule = LocaleTestRule() + + @get:Rule + val systemUIDemoModeRule = SystemUIDemoModeRule() @Before fun setup() { MockKAnnotations.init(this, relaxed = true) - every { pendingViewModel.testState } returns MutableLiveData() + every { submissionRepository.deviceUIStateFlow } returns flowOf() + every { submissionRepository.testResultReceivedDateFlow } returns flowOf() + + viewModel = spyk( + SubmissionTestResultPendingViewModel( + TestDispatcherProvider, + testResultNotificationService, + submissionRepository + ) + ) + + with(viewModel) { + every { observeTestResultToSchedulePositiveTestResultReminder() } just Runs + } setupMockViewModel(object : SubmissionTestResultPendingViewModel.Factory { - override fun create(): SubmissionTestResultPendingViewModel = pendingViewModel + override fun create(): SubmissionTestResultPendingViewModel = viewModel }) } @@ -88,6 +124,19 @@ class SubmissionTestResultFragmentTest : BaseUITest() { // TODO verify result } + + @Test + @Screenshot + fun capture_fragment() { + every { viewModel.testState } returns MutableLiveData( + TestResultUIState( + NetworkRequestWrapper.RequestSuccessful( + DeviceUIState.PAIRED_NO_RESULT + ), Date() + ) + ) + captureScreenshot<SubmissionTestResultPendingFragment>() + } } @Module diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultNegativeFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultNegativeFragmentTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..8cee4e05bf24ac19e6d5a25131d8a477802812e2 --- /dev/null +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultNegativeFragmentTest.kt @@ -0,0 +1,86 @@ +package de.rki.coronawarnapp.ui.submission + +import androidx.lifecycle.MutableLiveData +import androidx.test.ext.junit.runners.AndroidJUnit4 +import dagger.Module +import dagger.android.ContributesAndroidInjector +import de.rki.coronawarnapp.submission.SubmissionRepository +import de.rki.coronawarnapp.ui.submission.testresult.TestResultUIState +import de.rki.coronawarnapp.ui.submission.testresult.negative.SubmissionTestResultNegativeFragment +import de.rki.coronawarnapp.ui.submission.testresult.negative.SubmissionTestResultNegativeViewModel +import de.rki.coronawarnapp.util.DeviceUIState +import de.rki.coronawarnapp.util.NetworkRequestWrapper +import io.mockk.MockKAnnotations +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.spyk +import kotlinx.coroutines.flow.flowOf +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import testhelpers.BaseUITest +import testhelpers.Screenshot +import testhelpers.SystemUIDemoModeRule +import testhelpers.TestDispatcherProvider +import testhelpers.captureScreenshot +import tools.fastlane.screengrab.locale.LocaleTestRule +import java.util.Date + +@RunWith(AndroidJUnit4::class) +class SubmissionTestResultNegativeFragmentTest : BaseUITest() { + + lateinit var viewModel: SubmissionTestResultNegativeViewModel + @MockK lateinit var submissionRepository: SubmissionRepository + + @Rule + @JvmField + val localeTestRule = LocaleTestRule() + + @get:Rule + val systemUIDemoModeRule = SystemUIDemoModeRule() + + @Before + fun setup() { + MockKAnnotations.init(this, relaxed = true) + + every { submissionRepository.deviceUIStateFlow } returns flowOf() + every { submissionRepository.testResultReceivedDateFlow } returns flowOf() + + viewModel = spyk( + SubmissionTestResultNegativeViewModel( + TestDispatcherProvider, + submissionRepository + ) + ) + + setupMockViewModel(object : SubmissionTestResultNegativeViewModel.Factory { + override fun create(): SubmissionTestResultNegativeViewModel = viewModel + }) + } + + @After + fun teardown() { + clearAllViewModels() + } + + @Test + @Screenshot + fun capture_fragment() { + every { viewModel.testResult } returns MutableLiveData( + TestResultUIState( + NetworkRequestWrapper.RequestSuccessful( + DeviceUIState.PAIRED_NEGATIVE + ), Date() + ) + ) + captureScreenshot<SubmissionTestResultNegativeFragment>() + } +} + +@Module +abstract class SubmissionTestResultTestNegativeModule { + @ContributesAndroidInjector + abstract fun submissionTestResultScreen(): SubmissionTestResultNegativeFragment +} diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultNoConsentGivenFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultNoConsentGivenFragmentTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..7d975ac18a2e190e9882291c5479c0f9c809237d --- /dev/null +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionTestResultNoConsentGivenFragmentTest.kt @@ -0,0 +1,77 @@ +package de.rki.coronawarnapp.ui.submission + +import androidx.lifecycle.MutableLiveData +import androidx.test.ext.junit.runners.AndroidJUnit4 +import dagger.Module +import dagger.android.ContributesAndroidInjector +import de.rki.coronawarnapp.submission.SubmissionRepository +import de.rki.coronawarnapp.ui.submission.testresult.TestResultUIState +import de.rki.coronawarnapp.ui.submission.testresult.positive.SubmissionTestResultNoConsentFragment +import de.rki.coronawarnapp.ui.submission.testresult.positive.SubmissionTestResultNoConsentViewModel +import de.rki.coronawarnapp.util.DeviceUIState +import de.rki.coronawarnapp.util.NetworkRequestWrapper +import io.mockk.MockKAnnotations +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.spyk +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import testhelpers.BaseUITest +import testhelpers.Screenshot +import testhelpers.SystemUIDemoModeRule +import testhelpers.captureScreenshot +import tools.fastlane.screengrab.locale.LocaleTestRule +import java.util.Date + +@RunWith(AndroidJUnit4::class) +class SubmissionTestResultNoConsentGivenFragmentTest : BaseUITest() { + + @MockK lateinit var submissionRepository: SubmissionRepository + + @Rule + @JvmField + val localeTestRule = LocaleTestRule() + + @get:Rule + val systemUIDemoModeRule = SystemUIDemoModeRule() + + private lateinit var viewModel: SubmissionTestResultNoConsentViewModel + + @Before + fun setup() { + MockKAnnotations.init(this, relaxed = true) + viewModel = + spyk(SubmissionTestResultNoConsentViewModel(submissionRepository)) + setupMockViewModel(object : SubmissionTestResultNoConsentViewModel.Factory { + override fun create(): SubmissionTestResultNoConsentViewModel = viewModel + }) + } + + @After + fun teardown() { + clearAllViewModels() + } + + @Test + @Screenshot + fun capture_fragment() { + every { viewModel.uiState } returns MutableLiveData( + TestResultUIState( + NetworkRequestWrapper.RequestSuccessful( + DeviceUIState.PAIRED_POSITIVE + ), Date() + ) + ) + + captureScreenshot<SubmissionTestResultNoConsentFragment>() + } +} + +@Module +abstract class SubmissionTestResultNoConsentModel { + @ContributesAndroidInjector + abstract fun submissionTestResultScreen(): SubmissionTestResultNoConsentFragment +} diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionYourConsentFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionYourConsentFragmentTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..909eb1c5d30e9ee41d79d828e83d7d0a0ff8894a --- /dev/null +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionYourConsentFragmentTest.kt @@ -0,0 +1,71 @@ +package de.rki.coronawarnapp.ui.submission + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import dagger.Module +import dagger.android.ContributesAndroidInjector +import de.rki.coronawarnapp.storage.interoperability.InteroperabilityRepository +import de.rki.coronawarnapp.submission.SubmissionRepository +import de.rki.coronawarnapp.ui.submission.yourconsent.SubmissionYourConsentFragment +import de.rki.coronawarnapp.ui.submission.yourconsent.SubmissionYourConsentFragmentArgs +import de.rki.coronawarnapp.ui.submission.yourconsent.SubmissionYourConsentViewModel +import io.mockk.MockKAnnotations +import io.mockk.every +import io.mockk.impl.annotations.MockK +import kotlinx.coroutines.flow.flowOf +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import testhelpers.BaseUITest +import testhelpers.Screenshot +import testhelpers.SystemUIDemoModeRule +import testhelpers.TestDispatcherProvider +import testhelpers.captureScreenshot +import tools.fastlane.screengrab.locale.LocaleTestRule + +@RunWith(AndroidJUnit4::class) +class SubmissionYourConsentFragmentTest : BaseUITest() { + + @MockK lateinit var submissionRepository: SubmissionRepository + @MockK lateinit var interoperabilityRepository: InteroperabilityRepository + + @Rule + @JvmField + val localeTestRule = LocaleTestRule() + + @get:Rule + val systemUIDemoModeRule = SystemUIDemoModeRule() + + private lateinit var viewModel: SubmissionYourConsentViewModel + + @Before + fun setup() { + MockKAnnotations.init(this, relaxed = true) + every { submissionRepository.hasGivenConsentToSubmission } returns flowOf() + viewModel = + SubmissionYourConsentViewModel(TestDispatcherProvider, interoperabilityRepository, submissionRepository) + setupMockViewModel(object : SubmissionYourConsentViewModel.Factory { + override fun create(): SubmissionYourConsentViewModel = viewModel + }) + } + + @After + fun teardown() { + clearAllViewModels() + } + + @Test + @Screenshot + fun capture_fragment_results() { + captureScreenshot<SubmissionYourConsentFragment>( + fragmentArgs = SubmissionYourConsentFragmentArgs(true).toBundle() + ) + } +} + +@Module +abstract class SubmissionYourConsentFragmentTestModule { + @ContributesAndroidInjector + abstract fun submissionYourConsentScreen(): SubmissionYourConsentFragment +} diff --git a/Corona-Warn-App/src/androidTest/java/testhelpers/FragmentTestModuleRegistrar.kt b/Corona-Warn-App/src/androidTest/java/testhelpers/FragmentTestModuleRegistrar.kt index 436786aa9d80bb70e3c18252fa482ad43eef88c7..ba8f5e736abc16dd8681efc2df4e29ed8d6b0a0d 100644 --- a/Corona-Warn-App/src/androidTest/java/testhelpers/FragmentTestModuleRegistrar.kt +++ b/Corona-Warn-App/src/androidTest/java/testhelpers/FragmentTestModuleRegistrar.kt @@ -8,13 +8,19 @@ import de.rki.coronawarnapp.ui.onboarding.OnboardingNotificationsTestModule import de.rki.coronawarnapp.ui.onboarding.OnboardingPrivacyTestModule import de.rki.coronawarnapp.ui.onboarding.OnboardingTestFragmentModule import de.rki.coronawarnapp.ui.onboarding.OnboardingTracingFragmentTestModule +import de.rki.coronawarnapp.ui.submission.SubmissionConsentFragmentTestModule import de.rki.coronawarnapp.ui.submission.SubmissionContactTestModule import de.rki.coronawarnapp.ui.submission.SubmissionDispatcherTestModule import de.rki.coronawarnapp.ui.submission.SubmissionQRScanFragmentModule +import de.rki.coronawarnapp.ui.submission.SubmissionSymptomCalendarFragmentTestModule import de.rki.coronawarnapp.ui.submission.SubmissionSymptomIntroFragmentTestModule import de.rki.coronawarnapp.ui.submission.SubmissionTanTestModule import de.rki.coronawarnapp.ui.submission.SubmissionTestResultConsentGivenTestModule +import de.rki.coronawarnapp.ui.submission.SubmissionTestResultNoConsentModel +import de.rki.coronawarnapp.ui.submission.SubmissionTestResultTestAvailableModule import de.rki.coronawarnapp.ui.submission.SubmissionTestResultTestModule +import de.rki.coronawarnapp.ui.submission.SubmissionTestResultTestNegativeModule +import de.rki.coronawarnapp.ui.submission.SubmissionYourConsentFragmentTestModule import de.rki.coronawarnapp.ui.tracing.TracingDetailsFragmentTestTestModule @Module( @@ -31,10 +37,16 @@ import de.rki.coronawarnapp.ui.tracing.TracingDetailsFragmentTestTestModule SubmissionDispatcherTestModule::class, SubmissionTanTestModule::class, SubmissionTestResultTestModule::class, + SubmissionTestResultTestNegativeModule::class, + SubmissionTestResultTestAvailableModule::class, + SubmissionTestResultNoConsentModel::class, SubmissionTestResultConsentGivenTestModule::class, SubmissionSymptomIntroFragmentTestModule::class, SubmissionContactTestModule::class, SubmissionQRScanFragmentModule::class, + SubmissionConsentFragmentTestModule::class, + SubmissionYourConsentFragmentTestModule::class, + SubmissionSymptomCalendarFragmentTestModule::class, // Tracing TracingDetailsFragmentTestTestModule::class ] diff --git a/Corona-Warn-App/src/androidTest/java/testhelpers/TestExtensions.kt b/Corona-Warn-App/src/androidTest/java/testhelpers/TestExtensions.kt index 9dc088f42d7578a595ca7d0dcb95a33d2d9ef6bf..303faa4aade55d47c1e39c3b7b7359f0a23012d9 100644 --- a/Corona-Warn-App/src/androidTest/java/testhelpers/TestExtensions.kt +++ b/Corona-Warn-App/src/androidTest/java/testhelpers/TestExtensions.kt @@ -6,6 +6,7 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentFactory import androidx.fragment.app.testing.FragmentScenario import de.rki.coronawarnapp.R +import tools.fastlane.screengrab.Screengrab /** Delay time before taking screenshot */ @@ -26,3 +27,15 @@ inline fun <reified F : Fragment> launchFragmentInContainer2( @StyleRes themeResId: Int = R.style.AppTheme, factory: FragmentFactory? = null ) = FragmentScenario.launchInContainer(F::class.java, fragmentArgs, themeResId, factory) + +inline fun <reified F : Fragment> captureScreenshot( + suffix: String = "", + fragmentArgs: Bundle? = null, + @StyleRes themeResId: Int = R.style.AppTheme, + factory: FragmentFactory? = null +) { + val name = F::class.simpleName.plus(suffix) + launchFragmentInContainer2<F>(fragmentArgs, themeResId, factory) + Thread.sleep(SCREENSHOT_DELAY_TIME) + Screengrab.screenshot(name) +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/SubmissionStateProvider.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/SubmissionStateProvider.kt index cbb701de15b06a0af099a671f6b967659fc2f5fd..474e84b1bf79bf3bda4d15f3bf3224644c40e8c7 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/SubmissionStateProvider.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/ui/homecards/SubmissionStateProvider.kt @@ -68,7 +68,7 @@ class SubmissionStateProvider @Inject constructor( fun isFetching(): Boolean = isDeviceRegistered && when (deviceUiState) { - is NetworkRequestWrapper.RequestFailed -> deviceUiState.error is CwaServerError + is NetworkRequestWrapper.RequestFailed -> false is NetworkRequestWrapper.RequestStarted -> true is NetworkRequestWrapper.RequestIdle -> true else -> false @@ -112,11 +112,13 @@ class SubmissionStateProvider @Inject constructor( } fun isPending(): Boolean = - deviceUiState.withSuccess(false) { - when (it) { - DeviceUIState.PAIRED_ERROR, DeviceUIState.PAIRED_NO_RESULT -> true - else -> false + when (deviceUiState) { + is NetworkRequestWrapper.RequestFailed -> true + is NetworkRequestWrapper.RequestSuccessful -> { + deviceUiState.data == DeviceUIState.PAIRED_ERROR || + deviceUiState.data == DeviceUIState.PAIRED_NO_RESULT } + else -> false } } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingFragment.kt index 2a2949b43996845f734ae09a5fc95c9b1facc289..38721c989499cdeda4c57cd2380797df07d6a146 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingFragment.kt @@ -1,9 +1,9 @@ package de.rki.coronawarnapp.ui.submission.testresult.pending -import android.app.AlertDialog import android.os.Bundle import android.view.View import android.view.accessibility.AccessibilityEvent +import androidx.appcompat.app.AlertDialog import androidx.fragment.app.Fragment import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.FragmentSubmissionTestResultPendingBinding @@ -12,11 +12,11 @@ import de.rki.coronawarnapp.exception.http.CwaServerError import de.rki.coronawarnapp.exception.http.CwaWebException import de.rki.coronawarnapp.util.ContextExtensions.getColorCompat import de.rki.coronawarnapp.util.DialogHelper -import de.rki.coronawarnapp.util.NetworkRequestWrapper.Companion.withFailure -import de.rki.coronawarnapp.util.NetworkRequestWrapper.Companion.withSuccess +import de.rki.coronawarnapp.util.NetworkRequestWrapper import de.rki.coronawarnapp.util.di.AutoInject import de.rki.coronawarnapp.util.ui.doNavigate import de.rki.coronawarnapp.util.ui.observe2 +import de.rki.coronawarnapp.util.ui.observeOnce import de.rki.coronawarnapp.util.ui.popBackStack import de.rki.coronawarnapp.util.ui.setInvisible import de.rki.coronawarnapp.util.ui.viewBindingLazy @@ -33,6 +33,8 @@ class SubmissionTestResultPendingFragment : Fragment(R.layout.fragment_submissio private var skipInitialTestResultRefresh = false + private var errorDialog: AlertDialog? = null + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -41,19 +43,10 @@ class SubmissionTestResultPendingFragment : Fragment(R.layout.fragment_submissio } pendingViewModel.testState.observe2(this) { result -> - result.deviceUiState.withFailure { - if (it is CwaWebException) { - DialogHelper.showDialog(buildErrorDialog(it)) - } - } - - val hasResult = result.deviceUiState.withSuccess(false) { true } - + val hasResult = result.deviceUiState is NetworkRequestWrapper.RequestSuccessful binding.apply { submissionTestResultSection.setTestResultSection(result.deviceUiState, result.testResultReceivedDate) - submissionTestResultSpinner.setInvisible(hasResult) - submissionTestResultContent.setInvisible(!hasResult) buttonContainer.setInvisible(!hasResult) } @@ -98,8 +91,16 @@ class SubmissionTestResultPendingFragment : Fragment(R.layout.fragment_submissio super.onResume() binding.submissionTestResultContainer.sendAccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT) pendingViewModel.refreshDeviceUIState(refreshTestResult = !skipInitialTestResultRefresh) - skipInitialTestResultRefresh = false + pendingViewModel.cwaWebExceptionLiveData.observeOnce(this.viewLifecycleOwner) { exception -> + handleError(exception) + } + } + + override fun onPause() { + pendingViewModel.cwaWebExceptionLiveData.removeObservers(this.viewLifecycleOwner) + errorDialog?.dismiss() + super.onPause() } private fun removeTestAfterConfirmation() { @@ -118,33 +119,42 @@ class SubmissionTestResultPendingFragment : Fragment(R.layout.fragment_submissio } } + private fun handleError(exception: CwaWebException) { + errorDialog = when (exception) { + is CwaClientError, is CwaServerError -> { + DialogHelper.showDialog(buildErrorDialog(exception)) + } + else -> { + DialogHelper.showDialog(genericErrorDialog) + } + } + } + private fun navigateToMainScreen() { popBackStack() } - private fun buildErrorDialog(exception: CwaWebException): DialogHelper.DialogInstance { - return when (exception) { - is CwaClientError, is CwaServerError -> DialogHelper.DialogInstance( - requireActivity(), - R.string.submission_error_dialog_web_generic_error_title, - getString( - R.string.submission_error_dialog_web_generic_network_error_body, - exception.statusCode - ), - R.string.submission_error_dialog_web_generic_error_button_positive, - null, - true, - ::navigateToMainScreen - ) - else -> DialogHelper.DialogInstance( - requireActivity(), - R.string.submission_error_dialog_web_generic_error_title, - R.string.submission_error_dialog_web_generic_error_body, - R.string.submission_error_dialog_web_generic_error_button_positive, - null, - true, - ::navigateToMainScreen - ) - } - } + private fun buildErrorDialog(exception: CwaWebException) = DialogHelper.DialogInstance( + requireActivity(), + R.string.submission_error_dialog_web_generic_error_title, + getString( + R.string.submission_error_dialog_web_generic_network_error_body, + exception.statusCode + ), + R.string.submission_error_dialog_web_generic_error_button_positive, + null, + true, + ::navigateToMainScreen + ) + + private val genericErrorDialog: DialogHelper.DialogInstance + get() = DialogHelper.DialogInstance( + requireActivity(), + R.string.submission_error_dialog_web_generic_error_title, + R.string.submission_error_dialog_web_generic_error_body, + R.string.submission_error_dialog_web_generic_error_button_positive, + null, + true, + ::navigateToMainScreen + ) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingViewModel.kt index 6149c922a10258b789653dfe864f22178685d770..b77a82130c1a636d56fb819b735a7a8113b5f8df 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/pending/SubmissionTestResultPendingViewModel.kt @@ -1,9 +1,9 @@ package de.rki.coronawarnapp.ui.submission.testresult.pending -import androidx.lifecycle.LiveData import androidx.lifecycle.asLiveData import androidx.navigation.NavDirections import com.squareup.inject.assisted.AssistedInject +import de.rki.coronawarnapp.exception.http.CwaWebException import de.rki.coronawarnapp.notification.TestResultNotificationService import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.ui.submission.testresult.TestResultUIState @@ -16,7 +16,9 @@ import de.rki.coronawarnapp.util.ui.SingleLiveEvent import de.rki.coronawarnapp.util.viewmodel.CWAViewModel import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -57,7 +59,8 @@ class SubmissionTestResultPendingViewModel @AssistedInject constructor( testResultReceivedDate = resultDate ) } - val testState: LiveData<TestResultUIState> = testResultFlow + + val testState = testResultFlow .onEach { testResultUIState -> testResultUIState.deviceUiState.withSuccess { deviceState -> when (deviceState) { @@ -85,9 +88,10 @@ class SubmissionTestResultPendingViewModel @AssistedInject constructor( } .asLiveData(context = dispatcherProvider.Default) - fun onTestOpened() { - submissionRepository.setViewedTestResult() - } + val cwaWebExceptionLiveData = submissionRepository.deviceUIStateFlow + .filterIsInstance<NetworkRequestWrapper.RequestFailed<DeviceUIState, CwaWebException>>() + .map { it.error } + .asLiveData() fun observeTestResultToSchedulePositiveTestResultReminder() = launch { submissionRepository.deviceUIStateFlow @@ -103,7 +107,6 @@ class SubmissionTestResultPendingViewModel @AssistedInject constructor( Timber.d("deregisterTestFromDevice()") launch { submissionRepository.removeTestFromDevice() - routeToScreen.postValue(null) } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/WatchdogService.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/WatchdogService.kt index 562cfe70cf01b4bcf8fffa47de66ff1b0dd39bcd..3365de8462647f20759cd2856b946090ccac19e2 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/WatchdogService.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/WatchdogService.kt @@ -3,7 +3,7 @@ package de.rki.coronawarnapp.util import android.content.Context import android.net.wifi.WifiManager import android.os.PowerManager -import androidx.lifecycle.ProcessLifecycleOwner +import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope import de.rki.coronawarnapp.diagnosiskeys.download.DownloadDiagnosisKeysTask import de.rki.coronawarnapp.storage.LocalData @@ -12,6 +12,7 @@ import de.rki.coronawarnapp.task.common.DefaultTaskRequest import de.rki.coronawarnapp.task.submitBlocking import de.rki.coronawarnapp.util.device.BackgroundModeStatus import de.rki.coronawarnapp.util.di.AppContext +import de.rki.coronawarnapp.util.di.ProcessLifecycle import de.rki.coronawarnapp.worker.BackgroundWorkScheduler import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch @@ -25,7 +26,8 @@ import javax.inject.Singleton class WatchdogService @Inject constructor( @AppContext private val context: Context, private val taskController: TaskController, - private val backgroundModeStatus: BackgroundModeStatus + private val backgroundModeStatus: BackgroundModeStatus, + @ProcessLifecycle private val processLifecycleOwner: LifecycleOwner ) { private val powerManager by lazy { @@ -44,7 +46,7 @@ class WatchdogService @Inject constructor( } Timber.tag(TAG).v("Acquiring wakelocks for watchdog routine.") - ProcessLifecycleOwner.get().lifecycleScope.launch { + processLifecycleOwner.lifecycleScope.launch { // A wakelock as the OS does not handle this for us like in the background job execution val wakeLock = createWakeLock() // A wifi lock to wake up the wifi connection in case the device is dozing diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/device/ForegroundState.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/device/ForegroundState.kt index 2980ccbc29947ac6b79fb6a44b7996e515099c1c..1babb17239d19f618d42c9a1253efc1a24616137 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/device/ForegroundState.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/device/ForegroundState.kt @@ -2,9 +2,9 @@ package de.rki.coronawarnapp.util.device import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleObserver +import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.OnLifecycleEvent -import androidx.lifecycle.ProcessLifecycleOwner -import de.rki.coronawarnapp.CoronaWarnApplication +import de.rki.coronawarnapp.util.di.ProcessLifecycle import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.onCompletion @@ -15,7 +15,9 @@ import javax.inject.Inject import javax.inject.Singleton @Singleton -class ForegroundState @Inject constructor() { +class ForegroundState @Inject constructor( + @ProcessLifecycle val processLifecycleOwner: LifecycleOwner +) { val isInForeground: Flow<Boolean> by lazy { MutableStateFlow(false).apply { @@ -23,19 +25,19 @@ class ForegroundState @Inject constructor() { @Suppress("unused") @OnLifecycleEvent(Lifecycle.Event.ON_START) fun onAppForegrounded() { - CoronaWarnApplication.isAppInForeground = true Timber.v("App is in the foreground") + tryEmit(true) } @Suppress("unused") @OnLifecycleEvent(Lifecycle.Event.ON_STOP) fun onAppBackgrounded() { - CoronaWarnApplication.isAppInForeground = false Timber.v("App is in the background") + tryEmit(false) } } - val processLifecycle = ProcessLifecycleOwner.get().lifecycle + val processLifecycle = processLifecycleOwner.lifecycle processLifecycle.addObserver(foregroundStateUpdater) } .onStart { Timber.v("isInForeground FLOW start") } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/AndroidModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/AndroidModule.kt index 245e67fe97c08c7a37764e64f12f44e9bdbd4122..3d3cda2e2a3d9f3367d6a69c28b02a7823440477 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/AndroidModule.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/AndroidModule.kt @@ -8,6 +8,8 @@ import android.content.Context import android.content.SharedPreferences import androidx.core.app.NotificationManagerCompat import androidx.core.content.getSystemService +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.ProcessLifecycleOwner import androidx.navigation.NavDeepLinkBuilder import androidx.work.WorkManager import dagger.Module @@ -63,4 +65,9 @@ class AndroidModule { @Provides @Singleton fun activityManager(@AppContext context: Context): ActivityManager = context.getSystemService()!! + + @Provides + @Singleton + @ProcessLifecycle + fun procressLifecycleOwner(): LifecycleOwner = ProcessLifecycleOwner.get() } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/ProcessLifecycle.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/ProcessLifecycle.kt new file mode 100644 index 0000000000000000000000000000000000000000..ada39e1d77afd61f9990e55bcf4acdb402eb1de5 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/ProcessLifecycle.kt @@ -0,0 +1,8 @@ +package de.rki.coronawarnapp.util.di + +import javax.inject.Qualifier + +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +annotation class ProcessLifecycle diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/LiveDataExtensions.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/LiveDataExtensions.kt index f31e15bfcf090fb98d8aa99266a90c7463ebb57d..e1d652cd060853b2ced95dcce8c5be835e7b7347 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/LiveDataExtensions.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/LiveDataExtensions.kt @@ -4,15 +4,18 @@ import androidx.fragment.app.Fragment import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LiveData import androidx.lifecycle.Observer +import androidx.lifecycle.observe fun <T> LiveData<T>.observe2(fragment: Fragment, callback: (T) -> Unit) { - observe(fragment.viewLifecycleOwner, { callback.invoke(it) }) + observe(fragment.viewLifecycleOwner) { + callback.invoke(it) + } } -fun <T> LiveData<T>.observeOnce(lifecycleOwner: LifecycleOwner? = null, observer: Observer<T>) { +fun <T> LiveData<T>.observeOnce(lifecycleOwner: LifecycleOwner? = null, onValueChanged: (t: T) -> Unit) { val internalObserver = object : Observer<T> { - override fun onChanged(t: T?) { - observer.onChanged(t) + override fun onChanged(t: T) { + onValueChanged(t) removeObserver(this) } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/device/ForegroundStateTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/device/ForegroundStateTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..57ef31fc9325943dba13c67b8875303be582671a --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/device/ForegroundStateTest.kt @@ -0,0 +1,56 @@ +package de.rki.coronawarnapp.util.device + +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.LifecycleRegistry +import io.kotest.matchers.shouldBe +import io.mockk.MockKAnnotations +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.impl.annotations.MockK +import kotlinx.coroutines.test.runBlockingTest +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import testhelpers.BaseTest +import testhelpers.coroutines.test + +class ForegroundStateTest : BaseTest() { + + @MockK lateinit var lifecycleOwner: LifecycleOwner + lateinit var lifecycle: LifecycleRegistry + + @BeforeEach + fun setup() { + MockKAnnotations.init(this) + lifecycle = LifecycleRegistry(lifecycleOwner) + every { lifecycleOwner.lifecycle } returns lifecycle + } + + @AfterEach + fun teardown() { + clearAllMocks() + } + + fun createInstance() = ForegroundState( + processLifecycleOwner = lifecycleOwner + ) + + @Test + fun `test emissions`() = runBlockingTest { + val instance = createInstance() + + val testCollector = instance.isInForeground.test(startOnScope = this) + + testCollector.latestValue shouldBe false + + lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_START) + testCollector.latestValue shouldBe true + + lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_STOP) + testCollector.latestValue shouldBe false + + testCollector.cancel() + advanceUntilIdle() + } +} diff --git a/README.md b/README.md index c428edc0319c2b86667bbde1cda7260a2fb6326e..4d0d652d9a0ba3f922be1d91cd0e43693d8d8c32 100644 --- a/README.md +++ b/README.md @@ -71,18 +71,26 @@ The German government has asked SAP and Deutsche Telekom to develop the Corona-W | Repository | Description | | ------------------- | --------------------------------------------------------------------- | | [cwa-documentation] | Project overview, general documentation, and white papers. | -| [cwa-wishlist] | Community feature requests. | | [cwa-app-ios] | Native iOS app using the Apple/Google exposure notification API. | | [cwa-app-android] | Native Android app using the Apple/Google exposure notification API. | +| [cwa-wishlist] | Community feature requests. | +| [cwa-website] | The official website for the Corona-Warn-App | | [cwa-server] | Backend implementation for the Apple/Google exposure notification API.| -| [cwa-verification-server] | Backend implementation of the verification process. | +| [cwa-verification-server] | Backend implementation of the verification process. | +| [cwa-verification-portal] | The portal to interact with the verification server | +| [cwa-verification-iam] | The identity and access management to interact with the verification server | +| [cwa-testresult-server] | Receives the test results from connected laboratories | -[cwa-verification-server]: https://github.com/corona-warn-app/cwa-verification-server [cwa-documentation]: https://github.com/corona-warn-app/cwa-documentation -[cwa-wishlist]: https://github.com/corona-warn-app/cwa-wishlist [cwa-app-ios]: https://github.com/corona-warn-app/cwa-app-ios [cwa-app-android]: https://github.com/corona-warn-app/cwa-app-android +[cwa-wishlist]: https://github.com/corona-warn-app/cwa-wishlist +[cwa-website]: https://github.com/corona-warn-app/cwa-website [cwa-server]: https://github.com/corona-warn-app/cwa-server +[cwa-verification-server]: https://github.com/corona-warn-app/cwa-verification-server +[cwa-verification-portal]: https://github.com/corona-warn-app/cwa-verification-portal +[cwa-verification-iam]: https://github.com/corona-warn-app/cwa-verification-iam +[cwa-testresult-server]: https://github.com/corona-warn-app/cwa-testresult-server ## Licensing diff --git a/gradle.properties b/gradle.properties index 0f38b47a0cea2ab273e5c05db4cf77ad8aed2e07..07f8e960eeb36e270e65671e4e722efb48083382 100644 --- a/gradle.properties +++ b/gradle.properties @@ -20,4 +20,4 @@ org.gradle.dependency.verification.console=verbose VERSION_MAJOR=1 VERSION_MINOR=11 VERSION_PATCH=0 -VERSION_BUILD=2 +VERSION_BUILD=3