From 58845adfde3e0db39c56164b0ea03749acf0b0f6 Mon Sep 17 00:00:00 2001 From: "Mr. 17" Date: Mon, 19 Feb 2024 16:24:15 +0530 Subject: [PATCH 1/9] Fix #2749: Fixes Home Card UI (#5185) ## Explanation Fixes #2749 Modifies margin and padding values ## Essential Checklist - [x] The PR title and explanation each start with "Fix #bugnum: " (If this PR fixes part of an issue, prefix the title with "Fix part of #bugnum: ...".) - [x] Any changes to [scripts/assets](https://github.com/oppia/oppia-android/tree/develop/scripts/assets) files have their rationale included in the PR explanation. - [x] The PR follows the [style guide](https://github.com/oppia/oppia-android/wiki/Coding-style-guide). - [x] The PR does not contain any unnecessary code changes from Android Studio ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#undo-unnecessary-changes)). - [x] The PR is made from a branch that's **not** called "develop" and is up-to-date with "develop". - [x] The PR is **assigned** to the appropriate reviewers ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#clarification-regarding-assignees-and-reviewers-section)). ## For UI-specific PRs only If your PR includes UI-related changes, then: - Add screenshots for portrait/landscape for both a tablet & phone of the before & after UI changes - For the screenshots above, include both English and pseudo-localized (RTL) screenshots (see [RTL guide](https://github.com/oppia/oppia-android/wiki/RTL-Guidelines)) - Add a video showing the full UX flow with a screen reader enabled (see [accessibility guide](https://github.com/oppia/oppia-android/wiki/Accessibility-A11y-Guide)) - For PRs introducing new UI elements or color changes, both light and dark mode screenshots must be included - Add a screenshot demonstrating that you ran affected Espresso tests locally & that they're passing ## Tablet Screenshots ### 1920 x 1200 |Before|After| |--|--| |![1920 x 1200 port before](https://github.com/oppia/oppia-android/assets/84731134/dbfc50df-4ced-48f9-85bd-11ed2ca9d60f)|![1920 x 1200 port after](https://github.com/oppia/oppia-android/assets/84731134/ef208183-3198-442b-958e-7d2f85a37b9f)| |![1920 x 1200 before](https://github.com/oppia/oppia-android/assets/84731134/344d0ef7-1fac-4117-9adb-b85877a40b3f)|![1920 x 1200 after](https://github.com/oppia/oppia-android/assets/84731134/fa8faa1c-9337-4741-86d1-32a655275768)| ### 2560 x 1800 |Before|After| |--|--| |![2560 x 1800 port before](https://github.com/oppia/oppia-android/assets/84731134/700abf1e-951f-42a3-a465-1ee6852685e6)|![2560 x 1800 port after](https://github.com/oppia/oppia-android/assets/84731134/b34f72ae-0d0b-4e67-af54-cf9bf292036f)| |![2560 x 1800 before](https://github.com/oppia/oppia-android/assets/84731134/f05fee95-9d8d-4310-a9e0-c8223b2c4ed4)|![2560 x 1800 after](https://github.com/oppia/oppia-android/assets/84731134/a9a2cb09-bcd8-4b50-888c-fdfa89d23274)| ### 1024 x 600 |Before|After| |--|--| |![1024 x 600 before](https://github.com/oppia/oppia-android/assets/84731134/59f77ffd-5ac6-46e9-8c20-33287848acc1)|![1024 x 600 after](https://github.com/oppia/oppia-android/assets/84731134/bdc14f4a-9656-4549-8e98-28535c214a71)| ## Phone Screenshots |dpi|Screenshots| |--|--| |ldpi|![image](https://github.com/oppia/oppia-android/assets/84731134/eae24f4d-5245-4f23-8b22-734b6c363c95)| |mdpi|![image](https://github.com/oppia/oppia-android/assets/84731134/d38ef410-8cb1-4225-bb43-532db99d483f)| |hdpi|![image](https://github.com/oppia/oppia-android/assets/84731134/1dd8ae92-1fc8-4ece-827e-0ad09cf06e0e)| |xhdpi|![image](https://github.com/oppia/oppia-android/assets/84731134/01f7f665-c2e2-4e70-b679-2d81f5b18b07)![image](https://github.com/oppia/oppia-android/assets/84731134/5f0499ff-9bcd-4ec2-8d32-685420feabe4)| |xxhdpi|![image](https://github.com/oppia/oppia-android/assets/84731134/dc727cd4-eb60-4736-aab1-e280a33933df)![image](https://github.com/oppia/oppia-android/assets/84731134/3baebd40-402c-4fe0-a7cd-5c339bf786b2)| --------- Co-authored-by: Adhiambo Peres <59600948+adhiamboperes@users.noreply.github.com> --- .../main/res/values-sw600dp-land/dimens.xml | 14 +++++++------- .../main/res/values-sw600dp-port/dimens.xml | 18 +++++++++--------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/app/src/main/res/values-sw600dp-land/dimens.xml b/app/src/main/res/values-sw600dp-land/dimens.xml index 3f60f076e3b..d24a8c3c4e8 100644 --- a/app/src/main/res/values-sw600dp-land/dimens.xml +++ b/app/src/main/res/values-sw600dp-land/dimens.xml @@ -1,11 +1,11 @@ 64dp - 96dp - 96dp - 96dp + 72dp + 72dp + 72dp 8dp - 64dp + 48dp 96dp 64dp 32dp @@ -276,7 +276,7 @@ 176dp - 96dp + 72dp 68dp 76dp 24dp @@ -444,8 +444,8 @@ 112dp - 96dp - 96dp + 72dp + 72dp 132dp diff --git a/app/src/main/res/values-sw600dp-port/dimens.xml b/app/src/main/res/values-sw600dp-port/dimens.xml index 681f7e8b90e..325532ede04 100644 --- a/app/src/main/res/values-sw600dp-port/dimens.xml +++ b/app/src/main/res/values-sw600dp-port/dimens.xml @@ -1,11 +1,11 @@ 96dp - 120dp - 120dp - 120dp + 56dp + 64dp + 60dp 8dp - 60dp + 32dp 120dp 60dp 32dp @@ -281,10 +281,10 @@ 128dp - 120dp - 68dp + 60dp + 52dp 76dp - 24dp + 16dp 120dp @@ -441,8 +441,8 @@ 112dp - 120dp - 120dp + 60dp + 60dp 132dp From 6c9086b7da683351fd9e81890fd770301d723d44 Mon Sep 17 00:00:00 2001 From: Adhiambo Peres <59600948+adhiamboperes@users.noreply.github.com> Date: Tue, 20 Feb 2024 01:34:26 +0300 Subject: [PATCH 2/9] App language tests --- .../app/onboarding/OnboardingFragmentTest.kt | 213 +++++++++++++++++- 1 file changed, 206 insertions(+), 7 deletions(-) diff --git a/app/src/sharedTest/java/org/oppia/android/app/onboarding/OnboardingFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/onboarding/OnboardingFragmentTest.kt index 71ef07a4660..e47ca2fb551 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/onboarding/OnboardingFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/onboarding/OnboardingFragmentTest.kt @@ -114,6 +114,7 @@ import org.robolectric.annotation.Config import org.robolectric.annotation.LooperMode import javax.inject.Inject import javax.inject.Singleton +import org.oppia.android.app.onboarding.onboardingv2.OnboardingProfileTypeActivity /** Tests for [OnboardingFragment]. */ @RunWith(AndroidJUnit4::class) @@ -662,12 +663,12 @@ class OnboardingFragmentTest { } @Test - fun testOnboardingFragment_onboardingV2Enabled_languageSelectionScreenIsDisplayed() { + fun testOnboardingFragment_onboardingV2Enabled_languageSelectionDropdownIsDisplayed() { TestPlatformParameterModule.forceEnableOnboardingFlowV2(true) launch(OnboardingActivity::class.java).use { testCoroutineDispatchers.runCurrent() - onView(withText(R.string.onboarding_language_activity_select_label)).check( + onView(withId(R.id.onboarding_language_dropdown_background)).check( matches( isDisplayed() ) @@ -675,13 +676,15 @@ class OnboardingFragmentTest { } } + @Config(qualifiers = "land") @Test - fun testOnboardingFragment_onboardingV2Enabled_prefilledLanguageSelectionIsEnglish() { + fun testOnboardingFragment_onboardingV2Enabled_configChange_languageDropdownIsDisplayed() { TestPlatformParameterModule.forceEnableOnboardingFlowV2(true) launch(OnboardingActivity::class.java).use { + onView(isRoot()).perform(orientationLandscape()) testCoroutineDispatchers.runCurrent() - onView(withText(R.string.english_localized_language_name)).check( + onView(withId(R.id.onboarding_language_dropdown_background)).check( matches( isDisplayed() ) @@ -689,9 +692,205 @@ class OnboardingFragmentTest { } } - // TODO add tests for supported app language default locales: arabic, portuguese, nigeria - // Add test for unsupported default locale eg french - // Add test for langauge list dropdown contents + @Config(qualifiers = "sw600dp-port") + @Test + fun testOnboardingFragment_onboardingV2Enabled_tabletPortrait_languageDropdownIsDisplayed() { + TestPlatformParameterModule.forceEnableOnboardingFlowV2(true) + + launch(OnboardingActivity::class.java).use { + onView(withId(R.id.onboarding_language_dropdown_background)).check( + matches( + isDisplayed() + ) + ) + } + } + + @Config(qualifiers = "sw600dp-land") + @Test + fun testOnboardingFragment_onboardingV2Enabled_tabletConfigChange_languageDropdownIsDisplayed() { + TestPlatformParameterModule.forceEnableOnboardingFlowV2(true) + + launch(OnboardingActivity::class.java).use { + onView(isRoot()).perform(orientationLandscape()) + testCoroutineDispatchers.runCurrent() + onView(withId(R.id.onboarding_language_dropdown_background)).check( + matches( + isDisplayed() + ) + ) + } + } + + @Test + fun testOnboardingFragment_onboardingV2Enabled_letsGoButtonClicked_openProfileTypeScreen() { + TestPlatformParameterModule.forceEnableOnboardingFlowV2(true) + + launch(OnboardingActivity::class.java).use { + onView(withId(R.id.onboarding_language_lets_go_button)).perform(click()) + testCoroutineDispatchers.runCurrent() + intended(hasComponent(OnboardingProfileTypeActivity::class.java.name)) + } + } + + @Config(qualifiers = "land") + @Test + fun testFragment_onboardingV2Enabled_landscape_letsGoButtonClicked_openProfileTypeScreen() { + TestPlatformParameterModule.forceEnableOnboardingFlowV2(true) + + launch(OnboardingActivity::class.java).use { + onView(withId(R.id.onboarding_language_lets_go_button)).perform(click()) + testCoroutineDispatchers.runCurrent() + intended(hasComponent(OnboardingProfileTypeActivity::class.java.name)) + } + } + + @Config(qualifiers = "sw600dp-port") + @Test + fun testFragment_onboardingV2Enabled_tabletPortrait_letsGoButtonClicked_openProfileTypeScreen() { + TestPlatformParameterModule.forceEnableOnboardingFlowV2(true) + + launch(OnboardingActivity::class.java).use { + onView(withId(R.id.onboarding_language_lets_go_button)).perform(click()) + testCoroutineDispatchers.runCurrent() + intended(hasComponent(OnboardingProfileTypeActivity::class.java.name)) + } + } + + @Config(qualifiers = "sw600dp-land") + @Test + fun testFragment_onboardingV2Enabled_tabletLand_letsGoButtonClicked_openProfileTypeScreen() { + TestPlatformParameterModule.forceEnableOnboardingFlowV2(true) + + launch(OnboardingActivity::class.java).use { + onView(withId(R.id.onboarding_language_lets_go_button)).perform(click()) + testCoroutineDispatchers.runCurrent() + intended(hasComponent(OnboardingProfileTypeActivity::class.java.name)) + } + } + + @Test + fun testOnboardingFragment_onboardingV2Enabled_allIcons_haveCorrectContentDescriptions() { + TestPlatformParameterModule.forceEnableOnboardingFlowV2(true) + + launch(OnboardingActivity::class.java).use { + onView(withId(R.id.onboarding_language_dropdown_arrow)).check( + matches( + withContentDescription( + R.string.onboarding_language_dropdown_arrow_icon_description + ) + ) + ) + onView(withId(R.id.onboarding_app_language_image)).check( + matches( + withContentDescription( + R.string.onboarding_otter_content_description + ) + ) + ) + onView(withId(R.id.onboarding_language_dropdown_icon)).check( + matches( + withContentDescription( + R.string.onboarding_language_dropdown_icon_description + ) + ) + ) + } + } + + @Config(qualifiers = "land") + @Test + fun testFragment_onboardingV2Enabled_mobileLandscape_allIcons_haveCorrectContentDescriptions() { + TestPlatformParameterModule.forceEnableOnboardingFlowV2(true) + + launch(OnboardingActivity::class.java).use { + onView(isRoot()).perform(orientationLandscape()) + testCoroutineDispatchers.runCurrent() + onView(withId(R.id.onboarding_language_dropdown_arrow)).check( + matches( + withContentDescription( + R.string.onboarding_language_dropdown_arrow_icon_description + ) + ) + ) + onView(withId(R.id.onboarding_app_language_image)).check( + matches( + withContentDescription( + R.string.onboarding_otter_content_description + ) + ) + ) + onView(withId(R.id.onboarding_language_dropdown_icon)).check( + matches( + withContentDescription( + R.string.onboarding_language_dropdown_icon_description + ) + ) + ) + } + } + + @Config(qualifiers = "sw600dp-port") + @Test + fun testFragment_onboardingV2Enabled_mobilePortrait_allIcons_haveCorrectContentDescriptions() { + TestPlatformParameterModule.forceEnableOnboardingFlowV2(true) + + launch(OnboardingActivity::class.java).use { + onView(withId(R.id.onboarding_language_dropdown_arrow)).check( + matches( + withContentDescription( + R.string.onboarding_language_dropdown_arrow_icon_description + ) + ) + ) + onView(withId(R.id.onboarding_app_language_image)).check( + matches( + withContentDescription( + R.string.onboarding_otter_content_description + ) + ) + ) + onView(withId(R.id.onboarding_language_dropdown_icon)).check( + matches( + withContentDescription( + R.string.onboarding_language_dropdown_icon_description + ) + ) + ) + } + } + + @Config(qualifiers = "sw600dp-land") + @Test + fun testFragment_onboardingV2Enabled_tabletLandscape_allIcons_haveCorrectContentDescriptions() { + TestPlatformParameterModule.forceEnableOnboardingFlowV2(true) + + launch(OnboardingActivity::class.java).use { + onView(isRoot()).perform(orientationLandscape()) + testCoroutineDispatchers.runCurrent() + onView(withId(R.id.onboarding_language_dropdown_arrow)).check( + matches( + withContentDescription( + R.string.onboarding_language_dropdown_arrow_icon_description + ) + ) + ) + onView(withId(R.id.onboarding_app_language_image)).check( + matches( + withContentDescription( + R.string.onboarding_otter_content_description + ) + ) + ) + onView(withId(R.id.onboarding_language_dropdown_icon)).check( + matches( + withContentDescription( + R.string.onboarding_language_dropdown_icon_description + ) + ) + ) + } + } private fun getResources(): Resources = ApplicationProvider.getApplicationContext().resources From 5adb3e270d6bb7f6f6df4dba586aea56d72cb1a9 Mon Sep 17 00:00:00 2001 From: Adhiambo Peres <59600948+adhiamboperes@users.noreply.github.com> Date: Tue, 20 Feb 2024 02:08:46 +0300 Subject: [PATCH 3/9] Create test file exemptions --- .../onboardingv2/AudioLanguageFragmentPresenter.kt | 1 - .../onboardingv2/OnboardingFragmentPresenter.kt | 1 - .../onboardingv2/OnboardingLearnerIntroFragment.kt | 4 +--- .../android/app/onboarding/OnboardingFragmentTest.kt | 2 +- .../onboardingv2/NewLearnerProfileActivityTest.kt | 4 ++++ .../onboardingv2/NewLearnerProfileFragmentTest.kt | 4 ++++ .../onboardingv2/OnboardingLearnerIntroActivityTest.kt | 4 ++++ .../onboardingv2/OnboardingLearnerIntroFragmentTest.kt | 4 ++++ .../onboardingv2/OnboardingProfileTypeActivityTest.kt | 4 ++++ .../onboardingv2/OnboardingProfileTypeFragmentTest.kt | 4 ++++ scripts/assets/test_file_exemptions.textproto | 9 +++++++++ 11 files changed, 35 insertions(+), 6 deletions(-) create mode 100644 app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/NewLearnerProfileActivityTest.kt create mode 100644 app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/NewLearnerProfileFragmentTest.kt create mode 100644 app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingLearnerIntroActivityTest.kt create mode 100644 app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingLearnerIntroFragmentTest.kt create mode 100644 app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingProfileTypeActivityTest.kt create mode 100644 app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingProfileTypeFragmentTest.kt diff --git a/app/src/main/java/org/oppia/android/app/onboarding/onboardingv2/AudioLanguageFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/onboarding/onboardingv2/AudioLanguageFragmentPresenter.kt index 0c86d059d97..2436e0a95e7 100644 --- a/app/src/main/java/org/oppia/android/app/onboarding/onboardingv2/AudioLanguageFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/onboarding/onboardingv2/AudioLanguageFragmentPresenter.kt @@ -3,7 +3,6 @@ package org.oppia.android.app.onboarding.onboardingv2 import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.ArrayAdapter import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import com.google.android.material.appbar.AppBarLayout diff --git a/app/src/main/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingFragmentPresenter.kt index 177bfbed230..26671f02a1e 100644 --- a/app/src/main/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingFragmentPresenter.kt @@ -3,7 +3,6 @@ package org.oppia.android.app.onboarding.onboardingv2 import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.ArrayAdapter import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import org.oppia.android.R diff --git a/app/src/main/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingLearnerIntroFragment.kt b/app/src/main/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingLearnerIntroFragment.kt index 7e008fd5664..9c12a14f615 100644 --- a/app/src/main/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingLearnerIntroFragment.kt +++ b/app/src/main/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingLearnerIntroFragment.kt @@ -47,7 +47,5 @@ class OnboardingLearnerIntroFragment : ) } - override fun loadAudioLanguageFragment(audioLanguage: AudioLanguage) { - TODO("Not yet implemented") - } + override fun loadAudioLanguageFragment(audioLanguage: AudioLanguage) {} } diff --git a/app/src/sharedTest/java/org/oppia/android/app/onboarding/OnboardingFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/onboarding/OnboardingFragmentTest.kt index e47ca2fb551..d67388192db 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/onboarding/OnboardingFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/onboarding/OnboardingFragmentTest.kt @@ -50,6 +50,7 @@ import org.oppia.android.app.application.ApplicationStartupListenerModule import org.oppia.android.app.application.testing.TestingBuildFlavorModule import org.oppia.android.app.devoptions.DeveloperOptionsModule import org.oppia.android.app.devoptions.DeveloperOptionsStarterModule +import org.oppia.android.app.onboarding.onboardingv2.OnboardingProfileTypeActivity import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule import org.oppia.android.app.profile.ProfileChooserActivity import org.oppia.android.app.shim.ViewBindingShimModule @@ -114,7 +115,6 @@ import org.robolectric.annotation.Config import org.robolectric.annotation.LooperMode import javax.inject.Inject import javax.inject.Singleton -import org.oppia.android.app.onboarding.onboardingv2.OnboardingProfileTypeActivity /** Tests for [OnboardingFragment]. */ @RunWith(AndroidJUnit4::class) diff --git a/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/NewLearnerProfileActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/NewLearnerProfileActivityTest.kt new file mode 100644 index 00000000000..2d02fb7cea8 --- /dev/null +++ b/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/NewLearnerProfileActivityTest.kt @@ -0,0 +1,4 @@ +package org.oppia.android.app.onboarding.onboardingv2 + +/** Tests for [NewLearnerProfileActivity]. */ +class NewLearnerProfileActivityTest diff --git a/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/NewLearnerProfileFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/NewLearnerProfileFragmentTest.kt new file mode 100644 index 00000000000..4c5d6ce4f89 --- /dev/null +++ b/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/NewLearnerProfileFragmentTest.kt @@ -0,0 +1,4 @@ +package org.oppia.android.app.onboarding.onboardingv2 + +/** Tests for [NewLearnerProfileFragment]. */ +class NewLearnerProfileFragmentTest diff --git a/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingLearnerIntroActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingLearnerIntroActivityTest.kt new file mode 100644 index 00000000000..65ca4922ef3 --- /dev/null +++ b/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingLearnerIntroActivityTest.kt @@ -0,0 +1,4 @@ +package org.oppia.android.app.onboarding.onboardingv2 + +/** Tests for [OnboardingLearnerIntroActivity]. */ +class OnboardingLearnerIntroActivityTest diff --git a/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingLearnerIntroFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingLearnerIntroFragmentTest.kt new file mode 100644 index 00000000000..15634a0b021 --- /dev/null +++ b/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingLearnerIntroFragmentTest.kt @@ -0,0 +1,4 @@ +package org.oppia.android.app.onboarding.onboardingv2 + +/** Tests for [OnboardingLearnerIntroFragment]. */ +class OnboardingLearnerIntroFragmentTest diff --git a/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingProfileTypeActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingProfileTypeActivityTest.kt new file mode 100644 index 00000000000..7774c69836e --- /dev/null +++ b/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingProfileTypeActivityTest.kt @@ -0,0 +1,4 @@ +package org.oppia.android.app.onboarding.onboardingv2 + +/** Tests for [OnboardingProfileTypeActivity]. */ +class OnboardingProfileTypeActivityTest diff --git a/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingProfileTypeFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingProfileTypeFragmentTest.kt new file mode 100644 index 00000000000..ee17b3c5827 --- /dev/null +++ b/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingProfileTypeFragmentTest.kt @@ -0,0 +1,4 @@ +package org.oppia.android.app.onboarding.onboardingv2 + +/** Tests for [OnboardingProfileTypeFragment]. */ +class OnboardingProfileTypeFragmentTest diff --git a/scripts/assets/test_file_exemptions.textproto b/scripts/assets/test_file_exemptions.textproto index a47db0bbbf0..1115d2209b9 100644 --- a/scripts/assets/test_file_exemptions.textproto +++ b/scripts/assets/test_file_exemptions.textproto @@ -253,6 +253,15 @@ exempted_file_path: "app/src/main/java/org/oppia/android/app/onboarding/Onboardi exempted_file_path: "app/src/main/java/org/oppia/android/app/onboarding/OnboardingViewPagerViewModel.kt" exempted_file_path: "app/src/main/java/org/oppia/android/app/onboarding/RouteToProfileListListener.kt" exempted_file_path: "app/src/main/java/org/oppia/android/app/onboarding/ViewPagerSlide.kt" +exempted_file_path: "app/src/main/java/org/oppia/android/app/onboarding/onboardingv2/AudioLanguageFragmentPresenter.kt" +exempted_file_path: "app/src/main/java/org/oppia/android/app/onboarding/onboardingv2/CreateLearnerProfileViewModel.kt" +exempted_file_path: "app/src/main/java/org/oppia/android/app/onboarding/onboardingv2/NewLearnerProfileActivityPresenter.kt" +exempted_file_path: "app/src/main/java/org/oppia/android/app/onboarding/onboardingv2/NewLearnerProfileFragmentPresenter.kt" +exempted_file_path: "app/src/main/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingFragmentPresenter.kt" +exempted_file_path: "app/src/main/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingLearnerIntroActivityPresenter.kt" +exempted_file_path: "app/src/main/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingLearnerIntroFragmentPresenter.kt" +exempted_file_path: "app/src/main/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingProfileTypeActivityPresenter.kt" +exempted_file_path: "app/src/main/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingProfileTypeFragmentPresenter.kt" exempted_file_path: "app/src/main/java/org/oppia/android/app/ongoingtopiclist/OngoingTopicItemViewModel.kt" exempted_file_path: "app/src/main/java/org/oppia/android/app/ongoingtopiclist/OngoingTopicListActivityPresenter.kt" exempted_file_path: "app/src/main/java/org/oppia/android/app/ongoingtopiclist/OngoingTopicListFragment.kt" From c6c0174b852dfd8cfeff7b43a4d45f233d762ac9 Mon Sep 17 00:00:00 2001 From: Adhiambo Peres <59600948+adhiamboperes@users.noreply.github.com> Date: Tue, 20 Feb 2024 19:32:37 +0300 Subject: [PATCH 4/9] Wrap up activity tests --- app/src/main/res/values/strings.xml | 2 +- .../app/onboarding/OnboardingFragmentTest.kt | 2 - .../NewLearnerProfileActivityTest.kt | 214 +++++++++++++++++- .../OnboardingLearnerIntroActivityTest.kt | 204 ++++++++++++++++- .../OnboardingProfileTypeActivityTest.kt | 214 +++++++++++++++++- 5 files changed, 630 insertions(+), 6 deletions(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 120ed130a7b..32e761e821b 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -640,7 +640,7 @@ Let\'s go! - Profile Type + Select Profile Type Tell us more about you! I\'m a student and I want to learn new things! I\'m the parent, teacher or guardian of a student. diff --git a/app/src/sharedTest/java/org/oppia/android/app/onboarding/OnboardingFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/onboarding/OnboardingFragmentTest.kt index d67388192db..a05a3edc21f 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/onboarding/OnboardingFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/onboarding/OnboardingFragmentTest.kt @@ -712,8 +712,6 @@ class OnboardingFragmentTest { TestPlatformParameterModule.forceEnableOnboardingFlowV2(true) launch(OnboardingActivity::class.java).use { - onView(isRoot()).perform(orientationLandscape()) - testCoroutineDispatchers.runCurrent() onView(withId(R.id.onboarding_language_dropdown_background)).check( matches( isDisplayed() diff --git a/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/NewLearnerProfileActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/NewLearnerProfileActivityTest.kt index 2d02fb7cea8..ac0c41dd6a4 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/NewLearnerProfileActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/NewLearnerProfileActivityTest.kt @@ -1,4 +1,216 @@ package org.oppia.android.app.onboarding.onboardingv2 +import android.app.Application +import android.content.Context +import androidx.appcompat.app.AppCompatActivity +import androidx.test.core.app.ActivityScenario +import androidx.test.core.app.ApplicationProvider +import androidx.test.espresso.intent.Intents +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth +import dagger.Component +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.oppia.android.R +import org.oppia.android.app.activity.ActivityComponent +import org.oppia.android.app.activity.ActivityComponentFactory +import org.oppia.android.app.activity.route.ActivityRouterModule +import org.oppia.android.app.application.ApplicationComponent +import org.oppia.android.app.application.ApplicationInjector +import org.oppia.android.app.application.ApplicationInjectorProvider +import org.oppia.android.app.application.ApplicationModule +import org.oppia.android.app.application.ApplicationStartupListenerModule +import org.oppia.android.app.application.testing.TestingBuildFlavorModule +import org.oppia.android.app.devoptions.DeveloperOptionsModule +import org.oppia.android.app.devoptions.DeveloperOptionsStarterModule +import org.oppia.android.app.model.ScreenName +import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule +import org.oppia.android.app.shim.ViewBindingShimModule +import org.oppia.android.app.translation.testing.ActivityRecreatorTestModule +import org.oppia.android.data.backends.gae.NetworkConfigProdModule +import org.oppia.android.data.backends.gae.NetworkModule +import org.oppia.android.domain.classify.InteractionsModule +import org.oppia.android.domain.classify.rules.algebraicexpressioninput.AlgebraicExpressionInputModule +import org.oppia.android.domain.classify.rules.continueinteraction.ContinueModule +import org.oppia.android.domain.classify.rules.dragAndDropSortInput.DragDropSortInputModule +import org.oppia.android.domain.classify.rules.fractioninput.FractionInputModule +import org.oppia.android.domain.classify.rules.imageClickInput.ImageClickInputModule +import org.oppia.android.domain.classify.rules.itemselectioninput.ItemSelectionInputModule +import org.oppia.android.domain.classify.rules.mathequationinput.MathEquationInputModule +import org.oppia.android.domain.classify.rules.multiplechoiceinput.MultipleChoiceInputModule +import org.oppia.android.domain.classify.rules.numberwithunits.NumberWithUnitsRuleModule +import org.oppia.android.domain.classify.rules.numericexpressioninput.NumericExpressionInputModule +import org.oppia.android.domain.classify.rules.numericinput.NumericInputRuleModule +import org.oppia.android.domain.classify.rules.ratioinput.RatioInputModule +import org.oppia.android.domain.classify.rules.textinput.TextInputRuleModule +import org.oppia.android.domain.exploration.ExplorationProgressModule +import org.oppia.android.domain.exploration.ExplorationStorageModule +import org.oppia.android.domain.hintsandsolution.HintsAndSolutionConfigModule +import org.oppia.android.domain.hintsandsolution.HintsAndSolutionProdModule +import org.oppia.android.domain.onboarding.ExpirationMetaDataRetrieverModule +import org.oppia.android.domain.oppialogger.LogStorageModule +import org.oppia.android.domain.oppialogger.LoggingIdentifierModule +import org.oppia.android.domain.oppialogger.analytics.ApplicationLifecycleModule +import org.oppia.android.domain.oppialogger.analytics.CpuPerformanceSnapshotterModule +import org.oppia.android.domain.oppialogger.logscheduler.MetricLogSchedulerModule +import org.oppia.android.domain.oppialogger.loguploader.LogReportWorkerModule +import org.oppia.android.domain.platformparameter.PlatformParameterModule +import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModule +import org.oppia.android.domain.question.QuestionModule +import org.oppia.android.domain.topic.PrimeTopicAssetsControllerModule +import org.oppia.android.domain.workmanager.WorkManagerConfigurationModule +import org.oppia.android.testing.OppiaTestRule +import org.oppia.android.testing.TestLogReportingModule +import org.oppia.android.testing.firebase.TestAuthenticationModule +import org.oppia.android.testing.junit.InitializeDefaultLocaleRule +import org.oppia.android.testing.robolectric.RobolectricModule +import org.oppia.android.testing.threading.TestCoroutineDispatchers +import org.oppia.android.testing.threading.TestDispatcherModule +import org.oppia.android.testing.time.FakeOppiaClockModule +import org.oppia.android.util.accessibility.AccessibilityTestModule +import org.oppia.android.util.caching.AssetModule +import org.oppia.android.util.caching.testing.CachingTestModule +import org.oppia.android.util.gcsresource.GcsResourceModule +import org.oppia.android.util.locale.LocaleProdModule +import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName +import org.oppia.android.util.logging.EventLoggingConfigurationModule +import org.oppia.android.util.logging.LoggerModule +import org.oppia.android.util.logging.SyncStatusModule +import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule +import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule +import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule +import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule +import org.oppia.android.util.parser.image.GlideImageLoaderModule +import org.oppia.android.util.parser.image.ImageParsingModule +import org.robolectric.annotation.Config +import org.robolectric.annotation.LooperMode +import javax.inject.Inject +import javax.inject.Singleton + /** Tests for [NewLearnerProfileActivity]. */ -class NewLearnerProfileActivityTest +@RunWith(AndroidJUnit4::class) +@LooperMode(LooperMode.Mode.PAUSED) +@Config( + application = NewLearnerProfileActivityTest.TestApplication::class, + qualifiers = "port-xxhdpi" +) +class NewLearnerProfileActivityTest { + @get:Rule + val initializeDefaultLocaleRule = InitializeDefaultLocaleRule() + + @get:Rule + val oppiaTestRule = OppiaTestRule() + + @Inject + lateinit var context: Context + + @Inject + lateinit var testCoroutineDispatchers: TestCoroutineDispatchers + + @Before + fun setUp() { + Intents.init() + setUpTestApplicationComponent() + } + + @After + fun tearDown() { + Intents.release() + } + + @Test + fun testActivity_createIntent_verifyScreenNameInIntent() { + val screenName = + NewLearnerProfileActivity.createNewLearnerProfileActivity(context) + .extractCurrentAppScreenName() + + Truth.assertThat(screenName).isEqualTo(ScreenName.CREATE_NEW_LEARNER_PROFILE_ACTIVITY) + } + + @Test + fun testNewLearnerProfileActivity_hasCorrectActivityLabel() { + launchNewLearnerProfileActivity().use { scenario -> + lateinit var title: CharSequence + scenario?.onActivity { activity -> title = activity.title } + + // Verify that the activity label is correct as a proxy to verify TalkBack will announce the + // correct string when it's read out. + Truth.assertThat(title) + .isEqualTo(context.getString(R.string.create_profile_activity_title)) + } + } + + private fun launchNewLearnerProfileActivity(): + ActivityScenario? { + val scenario = ActivityScenario.launch( + NewLearnerProfileActivity.createNewLearnerProfileActivity(context) + ) + testCoroutineDispatchers.runCurrent() + return scenario + } + + private fun setUpTestApplicationComponent() { + ApplicationProvider.getApplicationContext().inject(this) + } + + // TODO(#59): Figure out a way to reuse modules instead of needing to re-declare them. + @Singleton + @Component( + modules = [ + RobolectricModule::class, + PlatformParameterModule::class, PlatformParameterSingletonModule::class, + TestDispatcherModule::class, ApplicationModule::class, + LoggerModule::class, ContinueModule::class, FractionInputModule::class, + ItemSelectionInputModule::class, MultipleChoiceInputModule::class, + NumberWithUnitsRuleModule::class, NumericInputRuleModule::class, TextInputRuleModule::class, + DragDropSortInputModule::class, ImageClickInputModule::class, InteractionsModule::class, + GcsResourceModule::class, GlideImageLoaderModule::class, ImageParsingModule::class, + HtmlParserEntityTypeModule::class, QuestionModule::class, TestLogReportingModule::class, + AccessibilityTestModule::class, LogStorageModule::class, CachingTestModule::class, + PrimeTopicAssetsControllerModule::class, ExpirationMetaDataRetrieverModule::class, + ViewBindingShimModule::class, RatioInputModule::class, WorkManagerConfigurationModule::class, + ApplicationStartupListenerModule::class, LogReportWorkerModule::class, + HintsAndSolutionConfigModule::class, HintsAndSolutionProdModule::class, + FirebaseLogUploaderModule::class, FakeOppiaClockModule::class, + DeveloperOptionsStarterModule::class, DeveloperOptionsModule::class, + ExplorationStorageModule::class, NetworkModule::class, NetworkConfigProdModule::class, + NetworkConnectionUtilDebugModule::class, NetworkConnectionDebugUtilModule::class, + AssetModule::class, LocaleProdModule::class, ActivityRecreatorTestModule::class, + NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, + MathEquationInputModule::class, SplitScreenInteractionModule::class, + LoggingIdentifierModule::class, ApplicationLifecycleModule::class, + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class, + EventLoggingConfigurationModule::class, ActivityRouterModule::class, + CpuPerformanceSnapshotterModule::class, ExplorationProgressModule::class, + TestAuthenticationModule::class + ] + ) + + interface TestApplicationComponent : ApplicationComponent { + @Component.Builder + interface Builder : ApplicationComponent.Builder + + fun inject(newLearnerProfileActivityTest: NewLearnerProfileActivityTest) + } + + class TestApplication : Application(), ActivityComponentFactory, ApplicationInjectorProvider { + private val component: TestApplicationComponent by lazy { + DaggerNewLearnerProfileActivity_TestApplicationComponent.builder() + .setApplication(this) + .build() as TestApplicationComponent + } + + fun inject(newLearnerProfileActivityTest: NewLearnerProfileActivityTest) { + component.inject(newLearnerProfileActivityTest) + } + + override fun createActivityComponent(activity: AppCompatActivity): ActivityComponent { + return component.getActivityComponentBuilderProvider().get().setActivity(activity).build() + } + + override fun getApplicationInjector(): ApplicationInjector = component + } +} diff --git a/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingLearnerIntroActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingLearnerIntroActivityTest.kt index 65ca4922ef3..70f2326950e 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingLearnerIntroActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingLearnerIntroActivityTest.kt @@ -1,4 +1,206 @@ package org.oppia.android.app.onboarding.onboardingv2 +import android.app.Application +import android.content.Context +import androidx.appcompat.app.AppCompatActivity +import androidx.test.core.app.ActivityScenario +import androidx.test.core.app.ApplicationProvider +import androidx.test.espresso.intent.Intents +import com.google.common.truth.Truth +import dagger.Component +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.oppia.android.R +import org.oppia.android.app.activity.ActivityComponent +import org.oppia.android.app.activity.ActivityComponentFactory +import org.oppia.android.app.activity.route.ActivityRouterModule +import org.oppia.android.app.application.ApplicationComponent +import org.oppia.android.app.application.ApplicationInjector +import org.oppia.android.app.application.ApplicationInjectorProvider +import org.oppia.android.app.application.ApplicationModule +import org.oppia.android.app.application.ApplicationStartupListenerModule +import org.oppia.android.app.application.testing.TestingBuildFlavorModule +import org.oppia.android.app.devoptions.DeveloperOptionsModule +import org.oppia.android.app.devoptions.DeveloperOptionsStarterModule +import org.oppia.android.app.model.ScreenName +import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule +import org.oppia.android.app.shim.ViewBindingShimModule +import org.oppia.android.app.translation.testing.ActivityRecreatorTestModule +import org.oppia.android.data.backends.gae.NetworkConfigProdModule +import org.oppia.android.data.backends.gae.NetworkModule +import org.oppia.android.domain.classify.InteractionsModule +import org.oppia.android.domain.classify.rules.algebraicexpressioninput.AlgebraicExpressionInputModule +import org.oppia.android.domain.classify.rules.continueinteraction.ContinueModule +import org.oppia.android.domain.classify.rules.dragAndDropSortInput.DragDropSortInputModule +import org.oppia.android.domain.classify.rules.fractioninput.FractionInputModule +import org.oppia.android.domain.classify.rules.imageClickInput.ImageClickInputModule +import org.oppia.android.domain.classify.rules.itemselectioninput.ItemSelectionInputModule +import org.oppia.android.domain.classify.rules.mathequationinput.MathEquationInputModule +import org.oppia.android.domain.classify.rules.multiplechoiceinput.MultipleChoiceInputModule +import org.oppia.android.domain.classify.rules.numberwithunits.NumberWithUnitsRuleModule +import org.oppia.android.domain.classify.rules.numericexpressioninput.NumericExpressionInputModule +import org.oppia.android.domain.classify.rules.numericinput.NumericInputRuleModule +import org.oppia.android.domain.classify.rules.ratioinput.RatioInputModule +import org.oppia.android.domain.classify.rules.textinput.TextInputRuleModule +import org.oppia.android.domain.exploration.ExplorationProgressModule +import org.oppia.android.domain.exploration.ExplorationStorageModule +import org.oppia.android.domain.hintsandsolution.HintsAndSolutionConfigModule +import org.oppia.android.domain.hintsandsolution.HintsAndSolutionProdModule +import org.oppia.android.domain.onboarding.ExpirationMetaDataRetrieverModule +import org.oppia.android.domain.oppialogger.LogStorageModule +import org.oppia.android.domain.oppialogger.LoggingIdentifierModule +import org.oppia.android.domain.oppialogger.analytics.ApplicationLifecycleModule +import org.oppia.android.domain.oppialogger.analytics.CpuPerformanceSnapshotterModule +import org.oppia.android.domain.oppialogger.logscheduler.MetricLogSchedulerModule +import org.oppia.android.domain.oppialogger.loguploader.LogReportWorkerModule +import org.oppia.android.domain.platformparameter.PlatformParameterModule +import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModule +import org.oppia.android.domain.question.QuestionModule +import org.oppia.android.domain.topic.PrimeTopicAssetsControllerModule +import org.oppia.android.domain.workmanager.WorkManagerConfigurationModule +import org.oppia.android.testing.OppiaTestRule +import org.oppia.android.testing.TestLogReportingModule +import org.oppia.android.testing.firebase.TestAuthenticationModule +import org.oppia.android.testing.junit.InitializeDefaultLocaleRule +import org.oppia.android.testing.robolectric.RobolectricModule +import org.oppia.android.testing.threading.TestCoroutineDispatchers +import org.oppia.android.testing.threading.TestDispatcherModule +import org.oppia.android.testing.time.FakeOppiaClockModule +import org.oppia.android.util.accessibility.AccessibilityTestModule +import org.oppia.android.util.caching.AssetModule +import org.oppia.android.util.caching.testing.CachingTestModule +import org.oppia.android.util.gcsresource.GcsResourceModule +import org.oppia.android.util.locale.LocaleProdModule +import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName +import org.oppia.android.util.logging.EventLoggingConfigurationModule +import org.oppia.android.util.logging.LoggerModule +import org.oppia.android.util.logging.SyncStatusModule +import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule +import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule +import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule +import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule +import org.oppia.android.util.parser.image.GlideImageLoaderModule +import org.oppia.android.util.parser.image.ImageParsingModule +import javax.inject.Inject +import javax.inject.Singleton + /** Tests for [OnboardingLearnerIntroActivity]. */ -class OnboardingLearnerIntroActivityTest +class OnboardingLearnerIntroActivityTest { + @get:Rule + val initializeDefaultLocaleRule = InitializeDefaultLocaleRule() + + @get:Rule + val oppiaTestRule = OppiaTestRule() + + @Inject + lateinit var context: Context + + @Inject + lateinit var testCoroutineDispatchers: TestCoroutineDispatchers + + @Before + fun setUp() { + Intents.init() + setUpTestApplicationComponent() + } + + @After + fun tearDown() { + Intents.release() + } + + @Test + fun testActivity_createIntent_verifyScreenNameInIntent() { + val screenName = + OnboardingLearnerIntroActivity.createOnboardingLearnerIntroActivity(context) + .extractCurrentAppScreenName() + + Truth.assertThat(screenName).isEqualTo(ScreenName.ONBOARDING_LEARNER_INTRO_ACTIVITY) + } + + @Test + fun testLearnerIntroActivity_hasCorrectActivityLabel() { + launchOnboardingLearnerIntroActivity().use { scenario -> + lateinit var title: CharSequence + scenario?.onActivity { activity -> title = activity.title } + + // Verify that the activity label is correct as a proxy to verify TalkBack will announce the + // correct string when it's read out. + Truth.assertThat(title) + .isEqualTo(context.getString(R.string.onboarding_learner_intro_activity_title)) + } + } + + private fun launchOnboardingLearnerIntroActivity(): + ActivityScenario? { + val scenario = ActivityScenario.launch( + OnboardingLearnerIntroActivity.createOnboardingLearnerIntroActivity(context) + ) + testCoroutineDispatchers.runCurrent() + return scenario + } + + private fun setUpTestApplicationComponent() { + ApplicationProvider.getApplicationContext().inject(this) + } + + // TODO(#59): Figure out a way to reuse modules instead of needing to re-declare them. + @Singleton + @Component( + modules = [ + RobolectricModule::class, + PlatformParameterModule::class, PlatformParameterSingletonModule::class, + TestDispatcherModule::class, ApplicationModule::class, + LoggerModule::class, ContinueModule::class, FractionInputModule::class, + ItemSelectionInputModule::class, MultipleChoiceInputModule::class, + NumberWithUnitsRuleModule::class, NumericInputRuleModule::class, TextInputRuleModule::class, + DragDropSortInputModule::class, ImageClickInputModule::class, InteractionsModule::class, + GcsResourceModule::class, GlideImageLoaderModule::class, ImageParsingModule::class, + HtmlParserEntityTypeModule::class, QuestionModule::class, TestLogReportingModule::class, + AccessibilityTestModule::class, LogStorageModule::class, CachingTestModule::class, + PrimeTopicAssetsControllerModule::class, ExpirationMetaDataRetrieverModule::class, + ViewBindingShimModule::class, RatioInputModule::class, WorkManagerConfigurationModule::class, + ApplicationStartupListenerModule::class, LogReportWorkerModule::class, + HintsAndSolutionConfigModule::class, HintsAndSolutionProdModule::class, + FirebaseLogUploaderModule::class, FakeOppiaClockModule::class, + DeveloperOptionsStarterModule::class, DeveloperOptionsModule::class, + ExplorationStorageModule::class, NetworkModule::class, NetworkConfigProdModule::class, + NetworkConnectionUtilDebugModule::class, NetworkConnectionDebugUtilModule::class, + AssetModule::class, LocaleProdModule::class, ActivityRecreatorTestModule::class, + NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, + MathEquationInputModule::class, SplitScreenInteractionModule::class, + LoggingIdentifierModule::class, ApplicationLifecycleModule::class, + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class, + EventLoggingConfigurationModule::class, ActivityRouterModule::class, + CpuPerformanceSnapshotterModule::class, ExplorationProgressModule::class, + TestAuthenticationModule::class + ] + ) + + interface TestApplicationComponent : ApplicationComponent { + @Component.Builder + interface Builder : ApplicationComponent.Builder + + fun inject(onboardingLearnerIntroActivityTest: OnboardingLearnerIntroActivityTest) + } + + class TestApplication : Application(), ActivityComponentFactory, ApplicationInjectorProvider { + private val component: TestApplicationComponent by lazy { + DaggerOnboardingLearnerIntroActivity_TestApplicationComponent.builder() + .setApplication(this) + .build() as TestApplicationComponent + } + + fun inject(onboardingLearnerIntroActivityTest: OnboardingLearnerIntroActivityTest) { + component.inject(onboardingLearnerIntroActivityTest) + } + + override fun createActivityComponent(activity: AppCompatActivity): ActivityComponent { + return component.getActivityComponentBuilderProvider().get().setActivity(activity).build() + } + + override fun getApplicationInjector(): ApplicationInjector = component + } +} diff --git a/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingProfileTypeActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingProfileTypeActivityTest.kt index 7774c69836e..cb54391c927 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingProfileTypeActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingProfileTypeActivityTest.kt @@ -1,4 +1,216 @@ package org.oppia.android.app.onboarding.onboardingv2 +import android.app.Application +import android.content.Context +import androidx.appcompat.app.AppCompatActivity +import androidx.test.core.app.ActivityScenario +import androidx.test.core.app.ApplicationProvider +import androidx.test.espresso.intent.Intents +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth +import dagger.Component +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.oppia.android.R +import org.oppia.android.app.activity.ActivityComponent +import org.oppia.android.app.activity.ActivityComponentFactory +import org.oppia.android.app.activity.route.ActivityRouterModule +import org.oppia.android.app.application.ApplicationComponent +import org.oppia.android.app.application.ApplicationInjector +import org.oppia.android.app.application.ApplicationInjectorProvider +import org.oppia.android.app.application.ApplicationModule +import org.oppia.android.app.application.ApplicationStartupListenerModule +import org.oppia.android.app.application.testing.TestingBuildFlavorModule +import org.oppia.android.app.devoptions.DeveloperOptionsModule +import org.oppia.android.app.devoptions.DeveloperOptionsStarterModule +import org.oppia.android.app.model.ScreenName +import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule +import org.oppia.android.app.shim.ViewBindingShimModule +import org.oppia.android.app.translation.testing.ActivityRecreatorTestModule +import org.oppia.android.data.backends.gae.NetworkConfigProdModule +import org.oppia.android.data.backends.gae.NetworkModule +import org.oppia.android.domain.classify.InteractionsModule +import org.oppia.android.domain.classify.rules.algebraicexpressioninput.AlgebraicExpressionInputModule +import org.oppia.android.domain.classify.rules.continueinteraction.ContinueModule +import org.oppia.android.domain.classify.rules.dragAndDropSortInput.DragDropSortInputModule +import org.oppia.android.domain.classify.rules.fractioninput.FractionInputModule +import org.oppia.android.domain.classify.rules.imageClickInput.ImageClickInputModule +import org.oppia.android.domain.classify.rules.itemselectioninput.ItemSelectionInputModule +import org.oppia.android.domain.classify.rules.mathequationinput.MathEquationInputModule +import org.oppia.android.domain.classify.rules.multiplechoiceinput.MultipleChoiceInputModule +import org.oppia.android.domain.classify.rules.numberwithunits.NumberWithUnitsRuleModule +import org.oppia.android.domain.classify.rules.numericexpressioninput.NumericExpressionInputModule +import org.oppia.android.domain.classify.rules.numericinput.NumericInputRuleModule +import org.oppia.android.domain.classify.rules.ratioinput.RatioInputModule +import org.oppia.android.domain.classify.rules.textinput.TextInputRuleModule +import org.oppia.android.domain.exploration.ExplorationProgressModule +import org.oppia.android.domain.exploration.ExplorationStorageModule +import org.oppia.android.domain.hintsandsolution.HintsAndSolutionConfigModule +import org.oppia.android.domain.hintsandsolution.HintsAndSolutionProdModule +import org.oppia.android.domain.onboarding.ExpirationMetaDataRetrieverModule +import org.oppia.android.domain.oppialogger.LogStorageModule +import org.oppia.android.domain.oppialogger.LoggingIdentifierModule +import org.oppia.android.domain.oppialogger.analytics.ApplicationLifecycleModule +import org.oppia.android.domain.oppialogger.analytics.CpuPerformanceSnapshotterModule +import org.oppia.android.domain.oppialogger.logscheduler.MetricLogSchedulerModule +import org.oppia.android.domain.oppialogger.loguploader.LogReportWorkerModule +import org.oppia.android.domain.platformparameter.PlatformParameterModule +import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModule +import org.oppia.android.domain.question.QuestionModule +import org.oppia.android.domain.topic.PrimeTopicAssetsControllerModule +import org.oppia.android.domain.workmanager.WorkManagerConfigurationModule +import org.oppia.android.testing.OppiaTestRule +import org.oppia.android.testing.TestLogReportingModule +import org.oppia.android.testing.firebase.TestAuthenticationModule +import org.oppia.android.testing.junit.InitializeDefaultLocaleRule +import org.oppia.android.testing.robolectric.RobolectricModule +import org.oppia.android.testing.threading.TestCoroutineDispatchers +import org.oppia.android.testing.threading.TestDispatcherModule +import org.oppia.android.testing.time.FakeOppiaClockModule +import org.oppia.android.util.accessibility.AccessibilityTestModule +import org.oppia.android.util.caching.AssetModule +import org.oppia.android.util.caching.testing.CachingTestModule +import org.oppia.android.util.gcsresource.GcsResourceModule +import org.oppia.android.util.locale.LocaleProdModule +import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName +import org.oppia.android.util.logging.EventLoggingConfigurationModule +import org.oppia.android.util.logging.LoggerModule +import org.oppia.android.util.logging.SyncStatusModule +import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule +import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule +import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule +import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule +import org.oppia.android.util.parser.image.GlideImageLoaderModule +import org.oppia.android.util.parser.image.ImageParsingModule +import org.robolectric.annotation.Config +import org.robolectric.annotation.LooperMode +import javax.inject.Inject +import javax.inject.Singleton + /** Tests for [OnboardingProfileTypeActivity]. */ -class OnboardingProfileTypeActivityTest +@RunWith(AndroidJUnit4::class) +@LooperMode(LooperMode.Mode.PAUSED) +@Config( + application = OnboardingProfileTypeActivityTest.TestApplication::class, + qualifiers = "port-xxhdpi" +) +class OnboardingProfileTypeActivityTest { + @get:Rule + val initializeDefaultLocaleRule = InitializeDefaultLocaleRule() + + @get:Rule + val oppiaTestRule = OppiaTestRule() + + @Inject + lateinit var context: Context + + @Inject + lateinit var testCoroutineDispatchers: TestCoroutineDispatchers + + @Before + fun setUp() { + Intents.init() + setUpTestApplicationComponent() + } + + @After + fun tearDown() { + Intents.release() + } + + @Test + fun testActivity_createIntent_verifyScreenNameInIntent() { + val screenName = + OnboardingProfileTypeActivity.createOnboardingProfileTypeActivityIntent(context) + .extractCurrentAppScreenName() + + Truth.assertThat(screenName).isEqualTo(ScreenName.ONBOARDING_PROFILE_TYPE_ACTIVITY) + } + + @Test + fun testProfileTypeActivity_hasCorrectActivityLabel() { + launchOnboardingProfileTypeActivity().use { scenario -> + lateinit var title: CharSequence + scenario?.onActivity { activity -> title = activity.title } + + // Verify that the activity label is correct as a proxy to verify TalkBack will announce the + // correct string when it's read out. + Truth.assertThat(title) + .isEqualTo(context.getString(R.string.onboarding_profile_type_activity_title)) + } + } + + private fun launchOnboardingProfileTypeActivity(): + ActivityScenario? { + val scenario = ActivityScenario.launch( + OnboardingProfileTypeActivity.createOnboardingProfileTypeActivityIntent(context) + ) + testCoroutineDispatchers.runCurrent() + return scenario + } + + private fun setUpTestApplicationComponent() { + ApplicationProvider.getApplicationContext().inject(this) + } + + // TODO(#59): Figure out a way to reuse modules instead of needing to re-declare them. + @Singleton + @Component( + modules = [ + RobolectricModule::class, + PlatformParameterModule::class, PlatformParameterSingletonModule::class, + TestDispatcherModule::class, ApplicationModule::class, + LoggerModule::class, ContinueModule::class, FractionInputModule::class, + ItemSelectionInputModule::class, MultipleChoiceInputModule::class, + NumberWithUnitsRuleModule::class, NumericInputRuleModule::class, TextInputRuleModule::class, + DragDropSortInputModule::class, ImageClickInputModule::class, InteractionsModule::class, + GcsResourceModule::class, GlideImageLoaderModule::class, ImageParsingModule::class, + HtmlParserEntityTypeModule::class, QuestionModule::class, TestLogReportingModule::class, + AccessibilityTestModule::class, LogStorageModule::class, CachingTestModule::class, + PrimeTopicAssetsControllerModule::class, ExpirationMetaDataRetrieverModule::class, + ViewBindingShimModule::class, RatioInputModule::class, WorkManagerConfigurationModule::class, + ApplicationStartupListenerModule::class, LogReportWorkerModule::class, + HintsAndSolutionConfigModule::class, HintsAndSolutionProdModule::class, + FirebaseLogUploaderModule::class, FakeOppiaClockModule::class, + DeveloperOptionsStarterModule::class, DeveloperOptionsModule::class, + ExplorationStorageModule::class, NetworkModule::class, NetworkConfigProdModule::class, + NetworkConnectionUtilDebugModule::class, NetworkConnectionDebugUtilModule::class, + AssetModule::class, LocaleProdModule::class, ActivityRecreatorTestModule::class, + NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, + MathEquationInputModule::class, SplitScreenInteractionModule::class, + LoggingIdentifierModule::class, ApplicationLifecycleModule::class, + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class, + EventLoggingConfigurationModule::class, ActivityRouterModule::class, + CpuPerformanceSnapshotterModule::class, ExplorationProgressModule::class, + TestAuthenticationModule::class + ] + ) + + interface TestApplicationComponent : ApplicationComponent { + @Component.Builder + interface Builder : ApplicationComponent.Builder + + fun inject(onboardingProfileTypeActivityTest: OnboardingProfileTypeActivityTest) + } + + class TestApplication : Application(), ActivityComponentFactory, ApplicationInjectorProvider { + private val component: TestApplicationComponent by lazy { + DaggerOnboardingProfileTypeActivity_TestApplicationComponent.builder() + .setApplication(this) + .build() as TestApplicationComponent + } + + fun inject(onboardingProfileTypeActivityTest: OnboardingProfileTypeActivityTest) { + component.inject(onboardingProfileTypeActivityTest) + } + + override fun createActivityComponent(activity: AppCompatActivity): ActivityComponent { + return component.getActivityComponentBuilderProvider().get().setActivity(activity).build() + } + + override fun getApplicationInjector(): ApplicationInjector = component + } +} From 8a271fa40920a24042f1cf46e559b77600bee892 Mon Sep 17 00:00:00 2001 From: Adhiambo Peres <59600948+adhiamboperes@users.noreply.github.com> Date: Tue, 20 Feb 2024 19:38:05 +0300 Subject: [PATCH 5/9] Fix missing test configs --- .../OnboardingLearnerIntroActivityTest.kt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingLearnerIntroActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingLearnerIntroActivityTest.kt index 70f2326950e..df1d08a9a9f 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingLearnerIntroActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingLearnerIntroActivityTest.kt @@ -6,12 +6,14 @@ import androidx.appcompat.app.AppCompatActivity import androidx.test.core.app.ActivityScenario import androidx.test.core.app.ApplicationProvider import androidx.test.espresso.intent.Intents +import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth import dagger.Component import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test +import org.junit.runner.RunWith import org.oppia.android.R import org.oppia.android.app.activity.ActivityComponent import org.oppia.android.app.activity.ActivityComponentFactory @@ -83,10 +85,19 @@ import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule import org.oppia.android.util.parser.image.GlideImageLoaderModule import org.oppia.android.util.parser.image.ImageParsingModule +import org.robolectric.annotation.Config +import org.robolectric.annotation.LooperMode import javax.inject.Inject import javax.inject.Singleton /** Tests for [OnboardingLearnerIntroActivity]. */ +@RunWith(AndroidJUnit4::class) +@LooperMode(LooperMode.Mode.PAUSED) +@Config( + application = OnboardingLearnerIntroActivityTest.TestApplication::class, + qualifiers = "port-xxhdpi" +) + class OnboardingLearnerIntroActivityTest { @get:Rule val initializeDefaultLocaleRule = InitializeDefaultLocaleRule() From dc9557b3b807bc349454322c4d37d47380e2611a Mon Sep 17 00:00:00 2001 From: Adhiambo Peres <59600948+adhiamboperes@users.noreply.github.com> Date: Thu, 22 Feb 2024 01:36:18 +0300 Subject: [PATCH 6/9] Fragment tests --- ...OnboardingLearnerIntroFragmentPresenter.kt | 7 +- .../OnboardingProfileTypeFragmentPresenter.kt | 7 + .../app/onboarding/OnboardingFragmentTest.kt | 4 - .../NewLearnerProfileActivityTest.kt | 2 +- .../OnboardingLearnerIntroActivityTest.kt | 2 +- .../OnboardingLearnerIntroFragmentTest.kt | 299 +++++++++++++++- .../OnboardingProfileTypeActivityTest.kt | 2 +- .../OnboardingProfileTypeFragmentTest.kt | 338 +++++++++++++++++- 8 files changed, 651 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingLearnerIntroFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingLearnerIntroFragmentPresenter.kt index bf5ad4ffba4..8b1ac09d583 100644 --- a/app/src/main/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingLearnerIntroFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingLearnerIntroFragmentPresenter.kt @@ -1,5 +1,7 @@ package org.oppia.android.app.onboarding.onboardingv2 +import android.content.res.Configuration +import android.content.res.Resources import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -29,6 +31,8 @@ class OnboardingLearnerIntroFragmentPresenter @Inject constructor( private lateinit var routeToAudioLanguageListListener: RouteToAudioLanguageListListener private lateinit var loadAudioLanguageListListener: LoadAudioLanguageListListener + private val orientation = Resources.getSystem().configuration.orientation + /** Handle creation and binding of the OnboardingLearnerIntroFragment layout. */ fun handleCreateView( inflater: LayoutInflater, @@ -63,7 +67,8 @@ class OnboardingLearnerIntroFragmentPresenter @Inject constructor( appLanguageResourceHandler.getStringInLocale(R.string.app_name) ) - observeProfileLivedata(ProfileId.newBuilder().setInternalId(-1).build()) + binding.onboardingStepsCount.visibility = + if (orientation == Configuration.ORIENTATION_PORTRAIT) View.VISIBLE else View.GONE return binding.root } diff --git a/app/src/main/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingProfileTypeFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingProfileTypeFragmentPresenter.kt index 1be42f58c6f..148aa17903c 100644 --- a/app/src/main/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingProfileTypeFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingProfileTypeFragmentPresenter.kt @@ -1,5 +1,7 @@ package org.oppia.android.app.onboarding.onboardingv2 +import android.content.res.Configuration +import android.content.res.Resources import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -16,6 +18,8 @@ class OnboardingProfileTypeFragmentPresenter @Inject constructor( ) { private lateinit var binding: OnboardingProfileTypeFragmentBinding + private val orientation = Resources.getSystem().configuration.orientation + /** Handle creation and binding of the OnboardingProfileTypeFragment layout. */ fun handleCreateView(inflater: LayoutInflater, container: ViewGroup?): View { binding = OnboardingProfileTypeFragmentBinding.inflate( @@ -41,6 +45,9 @@ class OnboardingProfileTypeFragmentPresenter @Inject constructor( activity.finish() } + binding.onboardingStepsCount.visibility = + if (orientation == Configuration.ORIENTATION_PORTRAIT) View.VISIBLE else View.GONE + return binding.root } } diff --git a/app/src/sharedTest/java/org/oppia/android/app/onboarding/OnboardingFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/onboarding/OnboardingFragmentTest.kt index a05a3edc21f..b75bc56d7a3 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/onboarding/OnboardingFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/onboarding/OnboardingFragmentTest.kt @@ -802,8 +802,6 @@ class OnboardingFragmentTest { TestPlatformParameterModule.forceEnableOnboardingFlowV2(true) launch(OnboardingActivity::class.java).use { - onView(isRoot()).perform(orientationLandscape()) - testCoroutineDispatchers.runCurrent() onView(withId(R.id.onboarding_language_dropdown_arrow)).check( matches( withContentDescription( @@ -864,8 +862,6 @@ class OnboardingFragmentTest { TestPlatformParameterModule.forceEnableOnboardingFlowV2(true) launch(OnboardingActivity::class.java).use { - onView(isRoot()).perform(orientationLandscape()) - testCoroutineDispatchers.runCurrent() onView(withId(R.id.onboarding_language_dropdown_arrow)).check( matches( withContentDescription( diff --git a/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/NewLearnerProfileActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/NewLearnerProfileActivityTest.kt index ac0c41dd6a4..919375d2bb2 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/NewLearnerProfileActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/NewLearnerProfileActivityTest.kt @@ -198,7 +198,7 @@ class NewLearnerProfileActivityTest { class TestApplication : Application(), ActivityComponentFactory, ApplicationInjectorProvider { private val component: TestApplicationComponent by lazy { - DaggerNewLearnerProfileActivity_TestApplicationComponent.builder() + DaggerNewLearnerProfileActivityTest_TestApplicationComponent.builder() .setApplication(this) .build() as TestApplicationComponent } diff --git a/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingLearnerIntroActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingLearnerIntroActivityTest.kt index df1d08a9a9f..8e82f6e5c3b 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingLearnerIntroActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingLearnerIntroActivityTest.kt @@ -199,7 +199,7 @@ class OnboardingLearnerIntroActivityTest { class TestApplication : Application(), ActivityComponentFactory, ApplicationInjectorProvider { private val component: TestApplicationComponent by lazy { - DaggerOnboardingLearnerIntroActivity_TestApplicationComponent.builder() + DaggerOnboardingLearnerIntroActivityTest_TestApplicationComponent.builder() .setApplication(this) .build() as TestApplicationComponent } diff --git a/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingLearnerIntroFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingLearnerIntroFragmentTest.kt index 15634a0b021..c8f79c2d233 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingLearnerIntroFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingLearnerIntroFragmentTest.kt @@ -1,4 +1,301 @@ package org.oppia.android.app.onboarding.onboardingv2 +import android.app.Application +import android.content.Context +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.Lifecycle +import androidx.test.core.app.ActivityScenario +import androidx.test.core.app.ApplicationProvider +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.intent.Intents +import androidx.test.espresso.intent.Intents.intended +import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent +import androidx.test.espresso.matcher.ViewMatchers.Visibility +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.isRoot +import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import dagger.Component +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.oppia.android.R +import org.oppia.android.app.activity.ActivityComponent +import org.oppia.android.app.activity.ActivityComponentFactory +import org.oppia.android.app.activity.route.ActivityRouterModule +import org.oppia.android.app.application.ApplicationComponent +import org.oppia.android.app.application.ApplicationInjector +import org.oppia.android.app.application.ApplicationInjectorProvider +import org.oppia.android.app.application.ApplicationModule +import org.oppia.android.app.application.ApplicationStartupListenerModule +import org.oppia.android.app.application.testing.TestingBuildFlavorModule +import org.oppia.android.app.devoptions.DeveloperOptionsModule +import org.oppia.android.app.devoptions.DeveloperOptionsStarterModule +import org.oppia.android.app.options.AudioLanguageActivity +import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule +import org.oppia.android.app.shim.ViewBindingShimModule +import org.oppia.android.app.translation.testing.ActivityRecreatorTestModule +import org.oppia.android.app.utility.OrientationChangeAction.Companion.orientationLandscape +import org.oppia.android.data.backends.gae.NetworkConfigProdModule +import org.oppia.android.data.backends.gae.NetworkModule +import org.oppia.android.domain.classify.InteractionsModule +import org.oppia.android.domain.classify.rules.algebraicexpressioninput.AlgebraicExpressionInputModule +import org.oppia.android.domain.classify.rules.continueinteraction.ContinueModule +import org.oppia.android.domain.classify.rules.dragAndDropSortInput.DragDropSortInputModule +import org.oppia.android.domain.classify.rules.fractioninput.FractionInputModule +import org.oppia.android.domain.classify.rules.imageClickInput.ImageClickInputModule +import org.oppia.android.domain.classify.rules.itemselectioninput.ItemSelectionInputModule +import org.oppia.android.domain.classify.rules.mathequationinput.MathEquationInputModule +import org.oppia.android.domain.classify.rules.multiplechoiceinput.MultipleChoiceInputModule +import org.oppia.android.domain.classify.rules.numberwithunits.NumberWithUnitsRuleModule +import org.oppia.android.domain.classify.rules.numericexpressioninput.NumericExpressionInputModule +import org.oppia.android.domain.classify.rules.numericinput.NumericInputRuleModule +import org.oppia.android.domain.classify.rules.ratioinput.RatioInputModule +import org.oppia.android.domain.classify.rules.textinput.TextInputRuleModule +import org.oppia.android.domain.exploration.ExplorationProgressModule +import org.oppia.android.domain.exploration.ExplorationStorageModule +import org.oppia.android.domain.hintsandsolution.HintsAndSolutionConfigModule +import org.oppia.android.domain.hintsandsolution.HintsAndSolutionProdModule +import org.oppia.android.domain.onboarding.ExpirationMetaDataRetrieverModule +import org.oppia.android.domain.oppialogger.LogStorageModule +import org.oppia.android.domain.oppialogger.LoggingIdentifierModule +import org.oppia.android.domain.oppialogger.analytics.ApplicationLifecycleModule +import org.oppia.android.domain.oppialogger.analytics.CpuPerformanceSnapshotterModule +import org.oppia.android.domain.oppialogger.logscheduler.MetricLogSchedulerModule +import org.oppia.android.domain.oppialogger.loguploader.LogReportWorkerModule +import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModule +import org.oppia.android.domain.question.QuestionModule +import org.oppia.android.domain.topic.PrimeTopicAssetsControllerModule +import org.oppia.android.domain.workmanager.WorkManagerConfigurationModule +import org.oppia.android.testing.OppiaTestRule +import org.oppia.android.testing.TestLogReportingModule +import org.oppia.android.testing.firebase.TestAuthenticationModule +import org.oppia.android.testing.junit.InitializeDefaultLocaleRule +import org.oppia.android.testing.platformparameter.TestPlatformParameterModule +import org.oppia.android.testing.robolectric.RobolectricModule +import org.oppia.android.testing.threading.TestCoroutineDispatchers +import org.oppia.android.testing.threading.TestDispatcherModule +import org.oppia.android.testing.time.FakeOppiaClockModule +import org.oppia.android.util.accessibility.AccessibilityTestModule +import org.oppia.android.util.caching.AssetModule +import org.oppia.android.util.caching.testing.CachingTestModule +import org.oppia.android.util.gcsresource.GcsResourceModule +import org.oppia.android.util.locale.LocaleProdModule +import org.oppia.android.util.logging.EventLoggingConfigurationModule +import org.oppia.android.util.logging.LoggerModule +import org.oppia.android.util.logging.SyncStatusModule +import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule +import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule +import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule +import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule +import org.oppia.android.util.parser.image.GlideImageLoaderModule +import org.oppia.android.util.parser.image.ImageParsingModule +import org.robolectric.annotation.Config +import org.robolectric.annotation.LooperMode +import javax.inject.Inject +import javax.inject.Singleton + /** Tests for [OnboardingLearnerIntroFragment]. */ -class OnboardingLearnerIntroFragmentTest +// FunctionName: test names are conventionally named with underscores. +@Suppress("FunctionName") +@RunWith(AndroidJUnit4::class) +@LooperMode(LooperMode.Mode.PAUSED) +@Config( + application = OnboardingLearnerIntroFragmentTest.TestApplication::class, + qualifiers = "port-xxhdpi" +) +class OnboardingLearnerIntroFragmentTest { + @get:Rule + val initializeDefaultLocaleRule = InitializeDefaultLocaleRule() + + @get:Rule + val oppiaTestRule = OppiaTestRule() + + @Inject + lateinit var testCoroutineDispatchers: TestCoroutineDispatchers + + @Inject + lateinit var context: Context + + @Before + fun setUp() { + Intents.init() + setUpTestApplicationComponent() + testCoroutineDispatchers.registerIdlingResource() + } + + @After + fun tearDown() { + testCoroutineDispatchers.unregisterIdlingResource() + Intents.release() + } + + @Test + fun testFragment_welcomeMessageText_isDisplayed() { + launchOnboardingLearnerIntroActivity().use { + onView(withId(R.id.onboarding_learner_intro_title)) + .check(matches(isDisplayed())) + } + } + + @Test + fun testFragment_appInformationText_isDisplayed() { + launchOnboardingLearnerIntroActivity().use { + onView(withText(R.string.onboarding_learner_intro_classroom_text)) + .check(matches(isDisplayed())) + onView(withText(R.string.onboarding_learner_intro_practice_text)) + .check(matches(isDisplayed())) + onView( + withText( + context.getString( + R.string.onboarding_learner_intro_feedback_text, + context.getString(R.string.app_name) + ) + ) + ) + .check(matches(isDisplayed())) + } + } + + @Test + fun testFragment_stepCountText_isDisplayed() { + launchOnboardingLearnerIntroActivity().use { + onView(withId(R.id.onboarding_steps_count)) + .check(matches(isDisplayed())) + onView(withId(R.id.onboarding_steps_count)) + .check(matches(withText(R.string.onboarding_step_count_four))) + } + } + + @Test + fun testFragment_backButtonClicked_currentScreenIsDestroyed() { + launchOnboardingLearnerIntroActivity().use { scenario -> + onView(withId(R.id.onboarding_navigation_back)).perform(click()) + testCoroutineDispatchers.runCurrent() + if (scenario != null) { + assertThat(scenario.state).isEqualTo(Lifecycle.State.DESTROYED) + } + } + } + + @Test + fun testFragment_continueButtonClicked_launchesAudioLanguageScreen() { + launchOnboardingLearnerIntroActivity().use { + onView(withId(R.id.onboarding_navigation_continue)).perform(click()) + testCoroutineDispatchers.runCurrent() + intended(hasComponent(AudioLanguageActivity::class.java.name)) + } + } + + @Config(qualifiers = "land") + @Test + fun testFragment_landscapeMode_stepCountText_isNotDisplayed() { + launchOnboardingLearnerIntroActivity().use { + onView(isRoot()).perform(orientationLandscape()) + testCoroutineDispatchers.runCurrent() + onView(withId(R.id.onboarding_steps_count)) + .check(matches(withEffectiveVisibility(Visibility.GONE))) + } + } + + @Config(qualifiers = "land") + @Test + fun testFragment_landscapeMode_backButtonClicked_currentScreenIsDestroyed() { + launchOnboardingLearnerIntroActivity().use { scenario -> + onView(isRoot()).perform(orientationLandscape()) + onView(withId(R.id.onboarding_navigation_back)).perform(click()) + testCoroutineDispatchers.runCurrent() + if (scenario != null) { + assertThat(scenario.state).isEqualTo(Lifecycle.State.DESTROYED) + } + } + } + + @Config(qualifiers = "land") + @Test + fun testFragment_landscapeMode_continueButtonClicked_launchesAudioLanguageScreen() { + launchOnboardingLearnerIntroActivity().use { + onView(isRoot()).perform(orientationLandscape()) + onView(withId(R.id.onboarding_navigation_continue)).perform(click()) + testCoroutineDispatchers.runCurrent() + intended(hasComponent(AudioLanguageActivity::class.java.name)) + } + } + + private fun launchOnboardingLearnerIntroActivity(): + ActivityScenario? { + val scenario = ActivityScenario.launch( + OnboardingLearnerIntroActivity.createOnboardingLearnerIntroActivity(context) + ) + testCoroutineDispatchers.runCurrent() + return scenario + } + + private fun setUpTestApplicationComponent() { + ApplicationProvider.getApplicationContext().inject(this) + } + + // TODO(#59): Figure out a way to reuse modules instead of needing to re-declare them. + @Singleton + @Component( + modules = [ + TestPlatformParameterModule::class, RobolectricModule::class, + TestDispatcherModule::class, ApplicationModule::class, + LoggerModule::class, ContinueModule::class, FractionInputModule::class, + ItemSelectionInputModule::class, MultipleChoiceInputModule::class, + NumberWithUnitsRuleModule::class, NumericInputRuleModule::class, TextInputRuleModule::class, + DragDropSortInputModule::class, ImageClickInputModule::class, InteractionsModule::class, + GcsResourceModule::class, GlideImageLoaderModule::class, ImageParsingModule::class, + HtmlParserEntityTypeModule::class, QuestionModule::class, TestLogReportingModule::class, + AccessibilityTestModule::class, LogStorageModule::class, CachingTestModule::class, + PrimeTopicAssetsControllerModule::class, ExpirationMetaDataRetrieverModule::class, + ViewBindingShimModule::class, RatioInputModule::class, WorkManagerConfigurationModule::class, + ApplicationStartupListenerModule::class, LogReportWorkerModule::class, + HintsAndSolutionConfigModule::class, HintsAndSolutionProdModule::class, + FirebaseLogUploaderModule::class, FakeOppiaClockModule::class, + DeveloperOptionsStarterModule::class, DeveloperOptionsModule::class, + ExplorationStorageModule::class, NetworkModule::class, NetworkConfigProdModule::class, + NetworkConnectionUtilDebugModule::class, NetworkConnectionDebugUtilModule::class, + AssetModule::class, LocaleProdModule::class, ActivityRecreatorTestModule::class, + PlatformParameterSingletonModule::class, + NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, + MathEquationInputModule::class, SplitScreenInteractionModule::class, + LoggingIdentifierModule::class, ApplicationLifecycleModule::class, + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class, + EventLoggingConfigurationModule::class, ActivityRouterModule::class, + CpuPerformanceSnapshotterModule::class, ExplorationProgressModule::class, + TestAuthenticationModule::class + ] + ) + interface TestApplicationComponent : ApplicationComponent { + @Component.Builder + interface Builder : ApplicationComponent.Builder + + fun inject(onboardingLearnerIntroFragmentTest: OnboardingLearnerIntroFragmentTest) + } + + class TestApplication : Application(), ActivityComponentFactory, ApplicationInjectorProvider { + private val component: TestApplicationComponent by lazy { + DaggerOnboardingLearnerIntroFragmentTest_TestApplicationComponent.builder() + .setApplication(this) + .build() as TestApplicationComponent + } + + fun inject(onboardingLearnerIntroFragmentTest: OnboardingLearnerIntroFragmentTest) { + component.inject(onboardingLearnerIntroFragmentTest) + } + + override fun createActivityComponent(activity: AppCompatActivity): ActivityComponent { + return component.getActivityComponentBuilderProvider().get().setActivity(activity).build() + } + + override fun getApplicationInjector(): ApplicationInjector = component + } +} diff --git a/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingProfileTypeActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingProfileTypeActivityTest.kt index cb54391c927..7f25e1550fd 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingProfileTypeActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingProfileTypeActivityTest.kt @@ -198,7 +198,7 @@ class OnboardingProfileTypeActivityTest { class TestApplication : Application(), ActivityComponentFactory, ApplicationInjectorProvider { private val component: TestApplicationComponent by lazy { - DaggerOnboardingProfileTypeActivity_TestApplicationComponent.builder() + DaggerOnboardingProfileTypeActivityTest_TestApplicationComponent.builder() .setApplication(this) .build() as TestApplicationComponent } diff --git a/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingProfileTypeFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingProfileTypeFragmentTest.kt index ee17b3c5827..ae18d0b46d8 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingProfileTypeFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingProfileTypeFragmentTest.kt @@ -1,4 +1,340 @@ package org.oppia.android.app.onboarding.onboardingv2 +import android.app.Application +import android.content.Context +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.Lifecycle +import androidx.test.core.app.ActivityScenario +import androidx.test.core.app.ApplicationProvider +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.intent.Intents +import androidx.test.espresso.intent.Intents.intended +import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent +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.isRoot +import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import dagger.Component +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.oppia.android.R +import org.oppia.android.app.activity.ActivityComponent +import org.oppia.android.app.activity.ActivityComponentFactory +import org.oppia.android.app.activity.route.ActivityRouterModule +import org.oppia.android.app.application.ApplicationComponent +import org.oppia.android.app.application.ApplicationInjector +import org.oppia.android.app.application.ApplicationInjectorProvider +import org.oppia.android.app.application.ApplicationModule +import org.oppia.android.app.application.ApplicationStartupListenerModule +import org.oppia.android.app.application.testing.TestingBuildFlavorModule +import org.oppia.android.app.devoptions.DeveloperOptionsModule +import org.oppia.android.app.devoptions.DeveloperOptionsStarterModule +import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule +import org.oppia.android.app.profile.ProfileChooserActivity +import org.oppia.android.app.shim.ViewBindingShimModule +import org.oppia.android.app.translation.testing.ActivityRecreatorTestModule +import org.oppia.android.app.utility.OrientationChangeAction.Companion.orientationLandscape +import org.oppia.android.data.backends.gae.NetworkConfigProdModule +import org.oppia.android.data.backends.gae.NetworkModule +import org.oppia.android.domain.classify.InteractionsModule +import org.oppia.android.domain.classify.rules.algebraicexpressioninput.AlgebraicExpressionInputModule +import org.oppia.android.domain.classify.rules.continueinteraction.ContinueModule +import org.oppia.android.domain.classify.rules.dragAndDropSortInput.DragDropSortInputModule +import org.oppia.android.domain.classify.rules.fractioninput.FractionInputModule +import org.oppia.android.domain.classify.rules.imageClickInput.ImageClickInputModule +import org.oppia.android.domain.classify.rules.itemselectioninput.ItemSelectionInputModule +import org.oppia.android.domain.classify.rules.mathequationinput.MathEquationInputModule +import org.oppia.android.domain.classify.rules.multiplechoiceinput.MultipleChoiceInputModule +import org.oppia.android.domain.classify.rules.numberwithunits.NumberWithUnitsRuleModule +import org.oppia.android.domain.classify.rules.numericexpressioninput.NumericExpressionInputModule +import org.oppia.android.domain.classify.rules.numericinput.NumericInputRuleModule +import org.oppia.android.domain.classify.rules.ratioinput.RatioInputModule +import org.oppia.android.domain.classify.rules.textinput.TextInputRuleModule +import org.oppia.android.domain.exploration.ExplorationProgressModule +import org.oppia.android.domain.exploration.ExplorationStorageModule +import org.oppia.android.domain.hintsandsolution.HintsAndSolutionConfigModule +import org.oppia.android.domain.hintsandsolution.HintsAndSolutionProdModule +import org.oppia.android.domain.onboarding.ExpirationMetaDataRetrieverModule +import org.oppia.android.domain.oppialogger.LogStorageModule +import org.oppia.android.domain.oppialogger.LoggingIdentifierModule +import org.oppia.android.domain.oppialogger.analytics.ApplicationLifecycleModule +import org.oppia.android.domain.oppialogger.analytics.CpuPerformanceSnapshotterModule +import org.oppia.android.domain.oppialogger.logscheduler.MetricLogSchedulerModule +import org.oppia.android.domain.oppialogger.loguploader.LogReportWorkerModule +import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModule +import org.oppia.android.domain.question.QuestionModule +import org.oppia.android.domain.topic.PrimeTopicAssetsControllerModule +import org.oppia.android.domain.workmanager.WorkManagerConfigurationModule +import org.oppia.android.testing.OppiaTestRule +import org.oppia.android.testing.TestLogReportingModule +import org.oppia.android.testing.firebase.TestAuthenticationModule +import org.oppia.android.testing.junit.InitializeDefaultLocaleRule +import org.oppia.android.testing.platformparameter.TestPlatformParameterModule +import org.oppia.android.testing.robolectric.RobolectricModule +import org.oppia.android.testing.threading.TestCoroutineDispatchers +import org.oppia.android.testing.threading.TestDispatcherModule +import org.oppia.android.testing.time.FakeOppiaClockModule +import org.oppia.android.util.accessibility.AccessibilityTestModule +import org.oppia.android.util.caching.AssetModule +import org.oppia.android.util.caching.testing.CachingTestModule +import org.oppia.android.util.gcsresource.GcsResourceModule +import org.oppia.android.util.locale.LocaleProdModule +import org.oppia.android.util.logging.EventLoggingConfigurationModule +import org.oppia.android.util.logging.LoggerModule +import org.oppia.android.util.logging.SyncStatusModule +import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule +import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule +import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule +import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule +import org.oppia.android.util.parser.image.GlideImageLoaderModule +import org.oppia.android.util.parser.image.ImageParsingModule +import org.robolectric.annotation.Config +import org.robolectric.annotation.LooperMode +import javax.inject.Inject +import javax.inject.Singleton + /** Tests for [OnboardingProfileTypeFragment]. */ -class OnboardingProfileTypeFragmentTest +// FunctionName: test names are conventionally named with underscores. +@Suppress("FunctionName") +@RunWith(AndroidJUnit4::class) +@LooperMode(LooperMode.Mode.PAUSED) +@Config( + application = OnboardingProfileTypeFragmentTest.TestApplication::class, + qualifiers = "port-xxhdpi" +) +class OnboardingProfileTypeFragmentTest { + @get:Rule + val initializeDefaultLocaleRule = InitializeDefaultLocaleRule() + + @get:Rule + val oppiaTestRule = OppiaTestRule() + + @Inject + lateinit var testCoroutineDispatchers: TestCoroutineDispatchers + + @Inject + lateinit var context: Context + + @Before + fun setUp() { + Intents.init() + setUpTestApplicationComponent() + testCoroutineDispatchers.registerIdlingResource() + } + + @After + fun tearDown() { + testCoroutineDispatchers.unregisterIdlingResource() + Intents.release() + } + + @Test + fun testFragment_headerTextIsDisplayed() { + launchOnboardingProfileTypeActivity().use { + onView(withId(R.id.profile_type_title)) + .check(matches(isDisplayed())) + onView(withId(R.id.profile_type_title)) + .check( + matches( + withText( + R.string.onboarding_profile_type_activity_header + ) + ) + ) + } + } + + @Test + fun testFragment_navigationCardsAreDisplayed() { + launchOnboardingProfileTypeActivity().use { + onView(withId(R.id.profile_type_learner_navigation_card)) + .check(matches(isDisplayed())) + onView(withId(R.id.profile_type_learner_navigation_card)) + .check( + matches( + hasDescendant( + withText(R.string.onboarding_profile_type_activity_student_text) + ) + ) + ) + + onView(withId(R.id.profile_type_supervisor_navigation_card)) + .check(matches(isDisplayed())) + onView(withId(R.id.profile_type_supervisor_navigation_card)) + .check( + matches( + hasDescendant( + withText(R.string.onboarding_profile_type_activity_parent_text) + ) + ) + ) + } + } + + @Test + fun testFragment_portrait_stepCountTextIsDisplayed() { + launchOnboardingProfileTypeActivity().use { + onView(withId(R.id.onboarding_steps_count)) + .check(matches(isDisplayed())) + onView(withId(R.id.onboarding_steps_count)) + .check(matches(withText(R.string.onboarding_step_count_two))) + } + } + + @Test + fun testFragment_backButtonClicked_currentScreenIsDestroyed() { + launchOnboardingProfileTypeActivity().use { scenario -> + onView(withId(R.id.onboarding_navigation_back)) + .perform(click()) + testCoroutineDispatchers.runCurrent() + if (scenario != null) { + assertThat(scenario.state).isEqualTo(Lifecycle.State.DESTROYED) + } + } + } + + @Config(qualifiers = "land") + @Test + fun testFragment_landscapeMode_stepCountText_isNotDisplayed() { + launchOnboardingProfileTypeActivity().use { + onView(isRoot()).perform(orientationLandscape()) + testCoroutineDispatchers.runCurrent() + onView(withId(R.id.onboarding_steps_count)) + .check(matches(withEffectiveVisibility(Visibility.GONE))) + } + } + + @Config(qualifiers = "land") + @Test + fun testFragment_landscapeMode_backButtonClicked_currentScreenIsDestroyed() { + launchOnboardingProfileTypeActivity().use { scenario -> + onView(isRoot()).perform(orientationLandscape()) + onView(withId(R.id.onboarding_navigation_back)) + .perform(click()) + testCoroutineDispatchers.runCurrent() + if (scenario != null) { + assertThat(scenario.state).isEqualTo(Lifecycle.State.DESTROYED) + } + } + } + + @Test + fun testFragment_studentNavigationCardClicked_launchesNewProfileScreen() { + launchOnboardingProfileTypeActivity().use { + onView(withId(R.id.profile_type_learner_navigation_card)).perform(click()) + testCoroutineDispatchers.runCurrent() + intended(hasComponent(NewLearnerProfileActivity::class.java.name)) + } + } + + @Config(qualifiers = "land") + @Test + fun testFragment_landscapeMode_studentNavigationCardClicked_launchesNewProfileScreen() { + launchOnboardingProfileTypeActivity().use { + onView(isRoot()).perform(orientationLandscape()) + onView(withId(R.id.profile_type_learner_navigation_card)).perform(click()) + testCoroutineDispatchers.runCurrent() + intended(hasComponent(NewLearnerProfileActivity::class.java.name)) + } + } + + @Test + fun testFragment_supervisorNavigationCardClicked_launchesProfileChooserScreen() { + launchOnboardingProfileTypeActivity().use { + onView(withId(R.id.profile_type_supervisor_navigation_card)).perform(click()) + testCoroutineDispatchers.runCurrent() + intended(hasComponent(ProfileChooserActivity::class.java.name)) + } + } + + @Config(qualifiers = "land") + @Test + fun testFragment_landscapeMode_supervisorNavigationCardClicked_launchesProfileChooserScreen() { + launchOnboardingProfileTypeActivity().use { + onView(isRoot()).perform(orientationLandscape()) + onView(withId(R.id.profile_type_supervisor_navigation_card)).perform(click()) + testCoroutineDispatchers.runCurrent() + intended(hasComponent(ProfileChooserActivity::class.java.name)) + } + } + + private fun launchOnboardingProfileTypeActivity(): + ActivityScenario? { + val scenario = ActivityScenario.launch( + OnboardingProfileTypeActivity.createOnboardingProfileTypeActivityIntent(context) + ) + testCoroutineDispatchers.runCurrent() + return scenario + } + + private fun setUpTestApplicationComponent() { + ApplicationProvider.getApplicationContext().inject(this) + } + + // TODO(#59): Figure out a way to reuse modules instead of needing to re-declare them. + @Singleton + @Component( + modules = [ + TestPlatformParameterModule::class, RobolectricModule::class, + TestDispatcherModule::class, ApplicationModule::class, + LoggerModule::class, ContinueModule::class, FractionInputModule::class, + ItemSelectionInputModule::class, MultipleChoiceInputModule::class, + NumberWithUnitsRuleModule::class, NumericInputRuleModule::class, TextInputRuleModule::class, + DragDropSortInputModule::class, ImageClickInputModule::class, InteractionsModule::class, + GcsResourceModule::class, GlideImageLoaderModule::class, ImageParsingModule::class, + HtmlParserEntityTypeModule::class, QuestionModule::class, TestLogReportingModule::class, + AccessibilityTestModule::class, LogStorageModule::class, CachingTestModule::class, + PrimeTopicAssetsControllerModule::class, ExpirationMetaDataRetrieverModule::class, + ViewBindingShimModule::class, RatioInputModule::class, WorkManagerConfigurationModule::class, + ApplicationStartupListenerModule::class, LogReportWorkerModule::class, + HintsAndSolutionConfigModule::class, HintsAndSolutionProdModule::class, + FirebaseLogUploaderModule::class, FakeOppiaClockModule::class, + DeveloperOptionsStarterModule::class, DeveloperOptionsModule::class, + ExplorationStorageModule::class, NetworkModule::class, NetworkConfigProdModule::class, + NetworkConnectionUtilDebugModule::class, NetworkConnectionDebugUtilModule::class, + AssetModule::class, LocaleProdModule::class, ActivityRecreatorTestModule::class, + PlatformParameterSingletonModule::class, + NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, + MathEquationInputModule::class, SplitScreenInteractionModule::class, + LoggingIdentifierModule::class, ApplicationLifecycleModule::class, + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class, + EventLoggingConfigurationModule::class, ActivityRouterModule::class, + CpuPerformanceSnapshotterModule::class, ExplorationProgressModule::class, + TestAuthenticationModule::class + ] + ) + interface TestApplicationComponent : ApplicationComponent { + @Component.Builder + interface Builder : ApplicationComponent.Builder + + fun inject(onboardingProfileTypeFragmentTest: OnboardingProfileTypeFragmentTest) + } + + class TestApplication : Application(), ActivityComponentFactory, ApplicationInjectorProvider { + private val component: TestApplicationComponent by lazy { + DaggerOnboardingProfileTypeFragmentTest_TestApplicationComponent.builder() + .setApplication(this) + .build() as TestApplicationComponent + } + + fun inject(onboardingProfileTypeFragmentTest: OnboardingProfileTypeFragmentTest) { + component.inject(onboardingProfileTypeFragmentTest) + } + + override fun createActivityComponent(activity: AppCompatActivity): ActivityComponent { + return component.getActivityComponentBuilderProvider().get().setActivity(activity).build() + } + + override fun getApplicationInjector(): ApplicationInjector = component + } +} From 69c1eae38bdd9750aa1beb2f20ec64fb228fa18a Mon Sep 17 00:00:00 2001 From: Adhiambo Peres <59600948+adhiamboperes@users.noreply.github.com> Date: Thu, 22 Feb 2024 14:34:13 +0300 Subject: [PATCH 7/9] Fragment tests audio and new profile --- .../AudioLanguageFragmentPresenter.kt | 6 + .../NewLearnerProfileFragmentPresenter.kt | 6 + .../res/layout/create_profile_fragment.xml | 1 + .../NewLearnerProfileFragmentTest.kt | 389 +++++++++++++++++- .../app/options/AudioLanguageFragmentTest.kt | 103 +++++ 5 files changed, 504 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/oppia/android/app/onboarding/onboardingv2/AudioLanguageFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/onboarding/onboardingv2/AudioLanguageFragmentPresenter.kt index 2436e0a95e7..c260d8eb7b6 100644 --- a/app/src/main/java/org/oppia/android/app/onboarding/onboardingv2/AudioLanguageFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/onboarding/onboardingv2/AudioLanguageFragmentPresenter.kt @@ -1,5 +1,7 @@ package org.oppia.android.app.onboarding.onboardingv2 +import android.content.res.Configuration +import android.content.res.Resources import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -19,6 +21,7 @@ class AudioLanguageFragmentPresenter @Inject constructor( private val appLanguageResourceHandler: AppLanguageResourceHandler ) { private lateinit var binding: AudioLanguageSelectionFragmentBinding + private val orientation = Resources.getSystem().configuration.orientation /** * Returns a newly inflated view to render the fragment with the specified [audioLanguage] as the @@ -53,6 +56,9 @@ class AudioLanguageFragmentPresenter @Inject constructor( activity.finish() } + binding.onboardingStepsCount?.visibility = + if (orientation == Configuration.ORIENTATION_PORTRAIT) View.VISIBLE else View.GONE + return binding.root } } diff --git a/app/src/main/java/org/oppia/android/app/onboarding/onboardingv2/NewLearnerProfileFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/onboarding/onboardingv2/NewLearnerProfileFragmentPresenter.kt index 25b2dfc0c40..d3c65cc9825 100644 --- a/app/src/main/java/org/oppia/android/app/onboarding/onboardingv2/NewLearnerProfileFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/onboarding/onboardingv2/NewLearnerProfileFragmentPresenter.kt @@ -1,6 +1,8 @@ package org.oppia.android.app.onboarding.onboardingv2 import android.content.Intent +import android.content.res.Configuration +import android.content.res.Resources import android.graphics.PorterDuff import android.graphics.drawable.Drawable import android.provider.MediaStore @@ -31,6 +33,7 @@ class NewLearnerProfileFragmentPresenter @Inject constructor( ) { private lateinit var binding: CreateProfileFragmentBinding private lateinit var uploadImageView: ImageView + private val orientation = Resources.getSystem().configuration.orientation /** Initialize layout bindings. */ fun handleCreateView(inflater: LayoutInflater, container: ViewGroup?): View { @@ -93,6 +96,9 @@ class NewLearnerProfileFragmentPresenter @Inject constructor( binding.createProfilePicturePrompt.setOnClickListener { openGalleryIntent() } binding.createProfileUserImageView.setOnClickListener { openGalleryIntent() } + binding.onboardingStepsCount.visibility = + if (orientation == Configuration.ORIENTATION_PORTRAIT) View.VISIBLE else View.GONE + return binding.root } diff --git a/app/src/main/res/layout/create_profile_fragment.xml b/app/src/main/res/layout/create_profile_fragment.xml index 3f0c0bd78fe..e53ec059798 100644 --- a/app/src/main/res/layout/create_profile_fragment.xml +++ b/app/src/main/res/layout/create_profile_fragment.xml @@ -123,6 +123,7 @@ android:fontFamily="sans-serif" android:imeOptions="actionDone" android:inputType="text|textCapSentences" + android:minHeight="@dimen/clickable_item_min_height" android:paddingStart="@dimen/onboarding_shared_padding_medium" android:paddingTop="@dimen/onboarding_shared_padding_medium_small" android:paddingEnd="@dimen/onboarding_shared_padding_medium" diff --git a/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/NewLearnerProfileFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/NewLearnerProfileFragmentTest.kt index 4c5d6ce4f89..9aa278adcdf 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/NewLearnerProfileFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/onboarding/onboardingv2/NewLearnerProfileFragmentTest.kt @@ -1,4 +1,391 @@ package org.oppia.android.app.onboarding.onboardingv2 +import android.app.Application +import android.content.Context +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.Lifecycle +import androidx.test.core.app.ActivityScenario +import androidx.test.core.app.ApplicationProvider +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.action.ViewActions.closeSoftKeyboard +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.intent.Intents +import androidx.test.espresso.intent.Intents.intended +import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent +import androidx.test.espresso.matcher.ViewMatchers.Visibility +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.isRoot +import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import dagger.Component +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.oppia.android.R +import org.oppia.android.app.activity.ActivityComponent +import org.oppia.android.app.activity.ActivityComponentFactory +import org.oppia.android.app.activity.route.ActivityRouterModule +import org.oppia.android.app.application.ApplicationComponent +import org.oppia.android.app.application.ApplicationInjector +import org.oppia.android.app.application.ApplicationInjectorProvider +import org.oppia.android.app.application.ApplicationModule +import org.oppia.android.app.application.ApplicationStartupListenerModule +import org.oppia.android.app.application.testing.TestingBuildFlavorModule +import org.oppia.android.app.devoptions.DeveloperOptionsModule +import org.oppia.android.app.devoptions.DeveloperOptionsStarterModule +import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule +import org.oppia.android.app.shim.ViewBindingShimModule +import org.oppia.android.app.translation.testing.ActivityRecreatorTestModule +import org.oppia.android.app.utility.OrientationChangeAction.Companion.orientationLandscape +import org.oppia.android.data.backends.gae.NetworkConfigProdModule +import org.oppia.android.data.backends.gae.NetworkModule +import org.oppia.android.domain.classify.InteractionsModule +import org.oppia.android.domain.classify.rules.algebraicexpressioninput.AlgebraicExpressionInputModule +import org.oppia.android.domain.classify.rules.continueinteraction.ContinueModule +import org.oppia.android.domain.classify.rules.dragAndDropSortInput.DragDropSortInputModule +import org.oppia.android.domain.classify.rules.fractioninput.FractionInputModule +import org.oppia.android.domain.classify.rules.imageClickInput.ImageClickInputModule +import org.oppia.android.domain.classify.rules.itemselectioninput.ItemSelectionInputModule +import org.oppia.android.domain.classify.rules.mathequationinput.MathEquationInputModule +import org.oppia.android.domain.classify.rules.multiplechoiceinput.MultipleChoiceInputModule +import org.oppia.android.domain.classify.rules.numberwithunits.NumberWithUnitsRuleModule +import org.oppia.android.domain.classify.rules.numericexpressioninput.NumericExpressionInputModule +import org.oppia.android.domain.classify.rules.numericinput.NumericInputRuleModule +import org.oppia.android.domain.classify.rules.ratioinput.RatioInputModule +import org.oppia.android.domain.classify.rules.textinput.TextInputRuleModule +import org.oppia.android.domain.exploration.ExplorationProgressModule +import org.oppia.android.domain.exploration.ExplorationStorageModule +import org.oppia.android.domain.hintsandsolution.HintsAndSolutionConfigModule +import org.oppia.android.domain.hintsandsolution.HintsAndSolutionProdModule +import org.oppia.android.domain.onboarding.ExpirationMetaDataRetrieverModule +import org.oppia.android.domain.oppialogger.LogStorageModule +import org.oppia.android.domain.oppialogger.LoggingIdentifierModule +import org.oppia.android.domain.oppialogger.analytics.ApplicationLifecycleModule +import org.oppia.android.domain.oppialogger.analytics.CpuPerformanceSnapshotterModule +import org.oppia.android.domain.oppialogger.logscheduler.MetricLogSchedulerModule +import org.oppia.android.domain.oppialogger.loguploader.LogReportWorkerModule +import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModule +import org.oppia.android.domain.question.QuestionModule +import org.oppia.android.domain.topic.PrimeTopicAssetsControllerModule +import org.oppia.android.domain.workmanager.WorkManagerConfigurationModule +import org.oppia.android.testing.OppiaTestRule +import org.oppia.android.testing.TestLogReportingModule +import org.oppia.android.testing.espresso.EditTextInputAction +import org.oppia.android.testing.firebase.TestAuthenticationModule +import org.oppia.android.testing.junit.InitializeDefaultLocaleRule +import org.oppia.android.testing.platformparameter.TestPlatformParameterModule +import org.oppia.android.testing.robolectric.RobolectricModule +import org.oppia.android.testing.threading.TestCoroutineDispatchers +import org.oppia.android.testing.threading.TestDispatcherModule +import org.oppia.android.testing.time.FakeOppiaClockModule +import org.oppia.android.util.accessibility.AccessibilityTestModule +import org.oppia.android.util.caching.AssetModule +import org.oppia.android.util.caching.testing.CachingTestModule +import org.oppia.android.util.gcsresource.GcsResourceModule +import org.oppia.android.util.locale.LocaleProdModule +import org.oppia.android.util.logging.EventLoggingConfigurationModule +import org.oppia.android.util.logging.LoggerModule +import org.oppia.android.util.logging.SyncStatusModule +import org.oppia.android.util.logging.firebase.FirebaseLogUploaderModule +import org.oppia.android.util.networking.NetworkConnectionDebugUtilModule +import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule +import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule +import org.oppia.android.util.parser.image.GlideImageLoaderModule +import org.oppia.android.util.parser.image.ImageParsingModule +import org.robolectric.annotation.Config +import org.robolectric.annotation.LooperMode +import javax.inject.Inject +import javax.inject.Singleton + /** Tests for [NewLearnerProfileFragment]. */ -class NewLearnerProfileFragmentTest +// FunctionName: test names are conventionally named with underscores. +@Suppress("FunctionName") +@RunWith(AndroidJUnit4::class) +@LooperMode(LooperMode.Mode.PAUSED) +@Config( + application = NewLearnerProfileFragmentTest.TestApplication::class, + qualifiers = "port-xxhdpi" +) +class NewLearnerProfileFragmentTest { + @get:Rule + val initializeDefaultLocaleRule = InitializeDefaultLocaleRule() + + @get:Rule + val oppiaTestRule = OppiaTestRule() + + @Inject + lateinit var testCoroutineDispatchers: TestCoroutineDispatchers + + @Inject + lateinit var context: Context + + @Inject + lateinit var editTextInputAction: EditTextInputAction + + @Before + fun setUp() { + Intents.init() + setUpTestApplicationComponent() + testCoroutineDispatchers.registerIdlingResource() + } + + @After + fun tearDown() { + testCoroutineDispatchers.unregisterIdlingResource() + Intents.release() + } + + @Test + fun testFragment_nicknameLabelIsDisplayed() { + launchNewLearnerProfileActivity().use { + onView(withId(R.id.create_profile_nickname_label)) + .check(matches(isDisplayed())) + + onView(withId(R.id.create_profile_nickname_label)) + .check( + matches( + withText( + context.getString( + R.string.create_profile_activity_nickname_label + ) + ) + ) + ) + } + } + + @Test + fun testFragment_nicknameEditTextIsDisplayed() { + launchNewLearnerProfileActivity().use { + onView(withId(R.id.create_profile_nickname_edittext)) + .check(matches(isDisplayed())) + } + } + + @Test + fun testFragment_stepCountText_isDisplayed() { + launchNewLearnerProfileActivity().use { + onView(withId(R.id.onboarding_steps_count)) + .check(matches(isDisplayed())) + onView(withId(R.id.onboarding_steps_count)) + .check(matches(withText(R.string.onboarding_step_count_three))) + } + } + + @Test + fun testFragment_backButtonClicked_currentScreenIsDestroyed() { + launchNewLearnerProfileActivity().use { scenario -> + onView(withId(R.id.onboarding_navigation_back)) + .perform(click()) + testCoroutineDispatchers.runCurrent() + if (scenario != null) { + assertThat(scenario.state).isEqualTo(Lifecycle.State.DESTROYED) + } + } + } + + @Test + fun testFragment_continueButtonClicked_filledNickname_launchesLearnerIntroScreen() { + launchNewLearnerProfileActivity().use { + onView(withId(R.id.create_profile_nickname_edittext)) + .perform( + editTextInputAction.appendText("John"), + closeSoftKeyboard() + ) + testCoroutineDispatchers.runCurrent() + onView(withId(R.id.onboarding_navigation_continue)) + .perform(click()) + testCoroutineDispatchers.runCurrent() + intended(hasComponent(OnboardingLearnerIntroActivity::class.java.name)) + } + } + + @Test + fun testFragment_continueButtonClicked_emptyNickname_showNicknameErrorText() { + launchNewLearnerProfileActivity().use { + onView(withId(R.id.onboarding_navigation_continue)) + .perform(click()) + testCoroutineDispatchers.runCurrent() + onView(withText(R.string.create_profile_activity_nickname_error)) + .check(matches(isDisplayed())) + } + } + + @Test + fun testFragment_continueButtonClicked_filledNickname_afterError_launchesLearnerIntroScreen() { + launchNewLearnerProfileActivity().use { + onView(withId(R.id.onboarding_navigation_continue)) + .perform(click()) + testCoroutineDispatchers.runCurrent() + onView(withText(R.string.create_profile_activity_nickname_error)) + .check(matches(isDisplayed())) + + onView(withId(R.id.create_profile_nickname_edittext)) + .perform( + editTextInputAction.appendText("John"), + closeSoftKeyboard() + ) + testCoroutineDispatchers.runCurrent() + onView(withId(R.id.onboarding_navigation_continue)) + .perform(click()) + testCoroutineDispatchers.runCurrent() + intended(hasComponent(OnboardingLearnerIntroActivity::class.java.name)) + } + } + + @Config(qualifiers = "land") + @Test + fun testFragment_landscapeMode_stepCountText_isNotDisplayed() { + launchNewLearnerProfileActivity().use { + onView(isRoot()).perform(orientationLandscape()) + testCoroutineDispatchers.runCurrent() + onView(withId(R.id.onboarding_steps_count)) + .check(matches(withEffectiveVisibility(Visibility.GONE))) + } + } + + @Config(qualifiers = "land") + @Test + fun testFragment_landscapeMode_backButtonClicked_currentScreenIsDestroyed() { + launchNewLearnerProfileActivity().use { scenario -> + onView(isRoot()).perform(orientationLandscape()) + onView(withId(R.id.onboarding_navigation_back)) + .perform(click()) + testCoroutineDispatchers.runCurrent() + if (scenario != null) { + assertThat(scenario.state).isEqualTo(Lifecycle.State.DESTROYED) + } + } + } + + @Config(qualifiers = "land") + @Test + fun testFragment_landscapeMode_continueButtonClicked_launchesLearnerIntroScreen() { + launchNewLearnerProfileActivity().use { + onView(withId(R.id.create_profile_nickname_edittext)) + .perform( + editTextInputAction.appendText("John"), + closeSoftKeyboard() + ) + testCoroutineDispatchers.runCurrent() + onView(withId(R.id.onboarding_navigation_continue)) + .perform(click()) + testCoroutineDispatchers.runCurrent() + intended(hasComponent(OnboardingLearnerIntroActivity::class.java.name)) + } + } + + @Config(qualifiers = "land") + @Test + fun testFragment_landscapeMode_continueButtonClicked_emptyNickname_showNicknameErrorText() { + launchNewLearnerProfileActivity().use { + onView(isRoot()).perform(orientationLandscape()) + onView(withId(R.id.onboarding_navigation_continue)) + .perform(click()) + testCoroutineDispatchers.runCurrent() + onView(withText(R.string.create_profile_activity_nickname_error)) + .check(matches(isDisplayed())) + } + } + + @Config(qualifiers = "land") + @Test + fun testFragment_landscape_continueButtonClicked_afterErrorShown_launchesLearnerIntroScreen() { + launchNewLearnerProfileActivity().use { + onView(isRoot()).perform(orientationLandscape()) + testCoroutineDispatchers.runCurrent() + onView(withId(R.id.onboarding_navigation_continue)) + .perform(click()) + testCoroutineDispatchers.runCurrent() + onView(withText(R.string.create_profile_activity_nickname_error)) + .check(matches(isDisplayed())) + + onView(withId(R.id.create_profile_nickname_edittext)) + .perform( + editTextInputAction.appendText("John"), + closeSoftKeyboard() + ) + testCoroutineDispatchers.runCurrent() + onView(withId(R.id.onboarding_navigation_continue)) + .perform(click()) + testCoroutineDispatchers.runCurrent() + intended(hasComponent(OnboardingLearnerIntroActivity::class.java.name)) + } + } + + private fun launchNewLearnerProfileActivity(): + ActivityScenario? { + val scenario = ActivityScenario.launch( + NewLearnerProfileActivity.createNewLearnerProfileActivity(context) + ) + testCoroutineDispatchers.runCurrent() + return scenario + } + + private fun setUpTestApplicationComponent() { + ApplicationProvider.getApplicationContext().inject(this) + } + + // TODO(#59): Figure out a way to reuse modules instead of needing to re-declare them. + @Singleton + @Component( + modules = [ + TestPlatformParameterModule::class, RobolectricModule::class, + TestDispatcherModule::class, ApplicationModule::class, + LoggerModule::class, ContinueModule::class, FractionInputModule::class, + ItemSelectionInputModule::class, MultipleChoiceInputModule::class, + NumberWithUnitsRuleModule::class, NumericInputRuleModule::class, TextInputRuleModule::class, + DragDropSortInputModule::class, ImageClickInputModule::class, InteractionsModule::class, + GcsResourceModule::class, GlideImageLoaderModule::class, ImageParsingModule::class, + HtmlParserEntityTypeModule::class, QuestionModule::class, TestLogReportingModule::class, + AccessibilityTestModule::class, LogStorageModule::class, CachingTestModule::class, + PrimeTopicAssetsControllerModule::class, ExpirationMetaDataRetrieverModule::class, + ViewBindingShimModule::class, RatioInputModule::class, WorkManagerConfigurationModule::class, + ApplicationStartupListenerModule::class, LogReportWorkerModule::class, + HintsAndSolutionConfigModule::class, HintsAndSolutionProdModule::class, + FirebaseLogUploaderModule::class, FakeOppiaClockModule::class, + DeveloperOptionsStarterModule::class, DeveloperOptionsModule::class, + ExplorationStorageModule::class, NetworkModule::class, NetworkConfigProdModule::class, + NetworkConnectionUtilDebugModule::class, NetworkConnectionDebugUtilModule::class, + AssetModule::class, LocaleProdModule::class, ActivityRecreatorTestModule::class, + PlatformParameterSingletonModule::class, + NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, + MathEquationInputModule::class, SplitScreenInteractionModule::class, + LoggingIdentifierModule::class, ApplicationLifecycleModule::class, + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class, + EventLoggingConfigurationModule::class, ActivityRouterModule::class, + CpuPerformanceSnapshotterModule::class, ExplorationProgressModule::class, + TestAuthenticationModule::class + ] + ) + interface TestApplicationComponent : ApplicationComponent { + @Component.Builder + interface Builder : ApplicationComponent.Builder + + fun inject(newLearnerProfileFragmentTest: NewLearnerProfileFragmentTest) + } + + class TestApplication : Application(), ActivityComponentFactory, ApplicationInjectorProvider { + private val component: TestApplicationComponent by lazy { + DaggerNewLearnerProfileFragmentTest_TestApplicationComponent.builder() + .setApplication(this) + .build() as TestApplicationComponent + } + + fun inject(newLearnerProfileFragmentTest: NewLearnerProfileFragmentTest) { + component.inject(newLearnerProfileFragmentTest) + } + + override fun createActivityComponent(activity: AppCompatActivity): ActivityComponent { + return component.getActivityComponentBuilderProvider().get().setActivity(activity).build() + } + + override fun getApplicationInjector(): ApplicationInjector = component + } +} diff --git a/app/src/sharedTest/java/org/oppia/android/app/options/AudioLanguageFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/options/AudioLanguageFragmentTest.kt index c7b5bfe0d86..756d85c2466 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/options/AudioLanguageFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/options/AudioLanguageFragmentTest.kt @@ -3,16 +3,24 @@ package org.oppia.android.app.options import android.app.Application import android.content.Context import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.Lifecycle import androidx.test.core.app.ActivityScenario import androidx.test.core.app.ActivityScenario.launch import androidx.test.core.app.ApplicationProvider import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.intent.Intents.intended +import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent +import androidx.test.espresso.matcher.ViewMatchers.Visibility import androidx.test.espresso.matcher.ViewMatchers.isChecked +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.isRoot +import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility +import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth import dagger.Component import dagger.Module import dagger.Provides @@ -32,6 +40,7 @@ import org.oppia.android.app.application.ApplicationStartupListenerModule import org.oppia.android.app.application.testing.TestingBuildFlavorModule import org.oppia.android.app.devoptions.DeveloperOptionsModule import org.oppia.android.app.devoptions.DeveloperOptionsStarterModule +import org.oppia.android.app.home.HomeActivity import org.oppia.android.app.model.AudioLanguage import org.oppia.android.app.model.AudioLanguage.BRAZILIAN_PORTUGUESE_LANGUAGE import org.oppia.android.app.model.AudioLanguage.ENGLISH_AUDIO_LANGUAGE @@ -77,6 +86,7 @@ import org.oppia.android.testing.OppiaTestRule import org.oppia.android.testing.TestLogReportingModule import org.oppia.android.testing.firebase.TestAuthenticationModule import org.oppia.android.testing.junit.InitializeDefaultLocaleRule +import org.oppia.android.testing.platformparameter.TestPlatformParameterModule import org.oppia.android.testing.profile.ProfileTestHelper import org.oppia.android.testing.robolectric.RobolectricModule import org.oppia.android.testing.threading.TestCoroutineDispatchers @@ -212,6 +222,95 @@ class AudioLanguageFragmentTest { } } + @Test + fun testAudioLanguage_onboardingV2Enabled_languageSelectionDropdownIsDisplayed() { + forceEnableOnboardingFlowV2() + launchActivityWithLanguage(ENGLISH_AUDIO_LANGUAGE).use { + testCoroutineDispatchers.runCurrent() + onView(withId(R.id.audio_language_dropdown_background)).check( + matches( + isDisplayed() + ) + ) + } + } + + @Config(qualifiers = "land") + @Test + fun testAudioLanguage_onboardingV2Enabled_configChange_languageDropdownIsDisplayed() { + forceEnableOnboardingFlowV2() + launchActivityWithLanguage(ENGLISH_AUDIO_LANGUAGE).use { + onView(isRoot()).perform(orientationLandscape()) + testCoroutineDispatchers.runCurrent() + onView(withId(R.id.audio_language_dropdown_background)).check( + matches( + isDisplayed() + ) + ) + } + } + + @Config(qualifiers = "sw600dp-land") + @Test + fun testAudioLanguage_onboardingV2Enabled_tabletConfigChange_languageDropdownIsDisplayed() { + forceEnableOnboardingFlowV2() + launchActivityWithLanguage(ENGLISH_AUDIO_LANGUAGE).use { + onView(withId(R.id.audio_language_dropdown_background)).check( + matches( + isDisplayed() + ) + ) + } + } + + @Config(qualifiers = "land") + @Test + fun testFragment_landscapeMode_stepCountText_isNotDisplayed() { + forceEnableOnboardingFlowV2() + launchActivityWithLanguage(ENGLISH_AUDIO_LANGUAGE).use { + onView(isRoot()).perform(orientationLandscape()) + testCoroutineDispatchers.runCurrent() + onView(withId(R.id.onboarding_steps_count)) + .check(matches(withEffectiveVisibility(Visibility.GONE))) + } + } + + @Config(qualifiers = "sw600dp-land") + @Test + fun testFragment_tabletLandscapeMode_stepCountText_isNotDisplayed() { + forceEnableOnboardingFlowV2() + launchActivityWithLanguage(ENGLISH_AUDIO_LANGUAGE).use { + onView(isRoot()).perform(orientationLandscape()) + testCoroutineDispatchers.runCurrent() + onView(withId(R.id.onboarding_steps_count)) + .check(matches(withEffectiveVisibility(Visibility.GONE))) + } + } + + @Test + fun testFragment_backButtonClicked_currentScreenIsDestroyed() { + forceEnableOnboardingFlowV2() + launchActivityWithLanguage(ENGLISH_AUDIO_LANGUAGE).use { scenario -> + onView(withId(R.id.onboarding_navigation_back)) + .perform(click()) + testCoroutineDispatchers.runCurrent() + if (scenario != null) { + Truth.assertThat(scenario.state).isEqualTo(Lifecycle.State.DESTROYED) + } + } + } + + @Test + fun testFragment_continueButtonClicked_launchesHomeScreen() { + forceEnableOnboardingFlowV2() + launchActivityWithLanguage(ENGLISH_AUDIO_LANGUAGE).use { + onView(withId(R.id.onboarding_navigation_continue)) + .perform(click()) + testCoroutineDispatchers.runCurrent() + intended(hasComponent(HomeActivity::class.java.name)) + } + } + private fun launchActivityWithLanguage( audioLanguage: AudioLanguage ): ActivityScenario { @@ -276,6 +375,10 @@ class AudioLanguageFragmentTest { ).check(matches(withText(expectedLanguageName))) } + private fun forceEnableOnboardingFlowV2() { + TestPlatformParameterModule.forceEnableOnboardingFlowV2(true) + } + private fun setUpTestApplicationComponent() { ApplicationProvider.getApplicationContext().inject(this) } From 6a0fd4485a2b3c35680a7c802c64e8b505d14b1a Mon Sep 17 00:00:00 2001 From: Adhiambo Peres <59600948+adhiamboperes@users.noreply.github.com> Date: Tue, 27 Feb 2024 04:03:49 +0300 Subject: [PATCH 8/9] Fix profile type tests and display on very small devices --- .../OnboardingProfileTypeFragmentPresenter.kt | 8 - .../onboarding_profile_type_fragment.xml | 139 ++++++++++++++++++ .../onboarding_profile_type_fragment.xml | 22 +-- .../OnboardingProfileTypeFragmentTest.kt | 54 ++++--- gradle.properties | 1 - 5 files changed, 187 insertions(+), 37 deletions(-) create mode 100644 app/src/main/res/layout-land/onboarding_profile_type_fragment.xml diff --git a/app/src/main/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingProfileTypeFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingProfileTypeFragmentPresenter.kt index 148aa17903c..57960ee1535 100644 --- a/app/src/main/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingProfileTypeFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/onboarding/onboardingv2/OnboardingProfileTypeFragmentPresenter.kt @@ -1,7 +1,5 @@ package org.oppia.android.app.onboarding.onboardingv2 -import android.content.res.Configuration -import android.content.res.Resources import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -18,8 +16,6 @@ class OnboardingProfileTypeFragmentPresenter @Inject constructor( ) { private lateinit var binding: OnboardingProfileTypeFragmentBinding - private val orientation = Resources.getSystem().configuration.orientation - /** Handle creation and binding of the OnboardingProfileTypeFragment layout. */ fun handleCreateView(inflater: LayoutInflater, container: ViewGroup?): View { binding = OnboardingProfileTypeFragmentBinding.inflate( @@ -44,10 +40,6 @@ class OnboardingProfileTypeFragmentPresenter @Inject constructor( binding.onboardingNavigationBack.setOnClickListener { activity.finish() } - - binding.onboardingStepsCount.visibility = - if (orientation == Configuration.ORIENTATION_PORTRAIT) View.VISIBLE else View.GONE - return binding.root } } diff --git a/app/src/main/res/layout-land/onboarding_profile_type_fragment.xml b/app/src/main/res/layout-land/onboarding_profile_type_fragment.xml new file mode 100644 index 00000000000..4b58a3b475b --- /dev/null +++ b/app/src/main/res/layout-land/onboarding_profile_type_fragment.xml @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +