From d22f502dbf01b21dd9b54002dd564b23f1c12043 Mon Sep 17 00:00:00 2001 From: Ben Henning Date: Wed, 7 Sep 2022 22:40:40 -0700 Subject: [PATCH] Fix #4300, #2432: Introduce beta & general availability update notices and build flavors (#4417) ## Explanation Fixes #4300 Fixes #2432 This PR introduces support for showing notices when users change from certain flavors of the app to the beta and/or GA (general availability) flavors (both of which are newly introduced in this PR). The user behavior is as follows: - Changing from any flavor _to_ beta will result in the beta notice showing for the user the *first* time they open the app. - Changing from alpha or beta flavors _to_ the GA flavor will result in the GA notice being shown. - The user will always see the notice after the first open following one of these transitions, and in no other cases (including the first open for beta). - The user will not see one or both notices if they select the "never show again" checkbox(es) prior to dismissing the notice(s). - The app deprecation notice will always take logical precedence over other notices (i.e. it will always show in cases when the app is deprecated regardless of flavor transitions). In addition to the above, text was added to the splash screen for the developer, alpha, and beta flavors of the app to make it a bit more explicit to the user. The alpha and beta splash experiences have been configured to wait 2 seconds so that the user has a moment to see the actual text before the app finishes loading. Note also that existing alpha users will not see the beta notice after installing the first beta version (since no flavor has been recorded). While this is technically fixable, it doesn't seem worthwhile given the current small user audience that would be affected. Subsequent transitions from alpha to beta will work as expected (and lead to the beta notice being shown). As a side note: #2432 is fixed as of this PR since the final planned build flavors are being introduced here. While more flavors may be added in the future, it seems sufficient to consider "build flavors are introduced" to be done now that there are developer, alpha, beta, and GA flavors of the app. Technical things of note: - The splash screen now has a contingency fallback of 5 seconds in case the startup state doesn't ever load/the app gets stuck (it will fall back to a default state). - The transitions required a new BuildFlavor proto to be included and bound to Dagger. For tests and the e2e test application component, a new testing-specific flavor has been introduced (though it's never expected to correspond to a distributable build of the app). - The existing 'deprecation' package under the app module has been repurposed to be the location for all notices in the app (including the two new ones). We'll likely add more here in the future, as well (such as the GA deprecation notices). - The new beta and GA flavors of the app have been added to the build_tests CI workflow--you can see the artifacts of the workflow in this PR or on develop after the PR is merged for downloads to the corresponding built AABs. - The Kenya-specific alpha version of the app has the same 'alpha' build flavor as the default alpha version of the app since there doesn't seem to be any reason why they should differ here. - Some places were updated to use Application instead of Context to make things a bit tighter in expectation for *what* type of context is available to use (for cases when casting to injector providers is used). This helps to avoid a particularly challenging bug that I ran into during test development wherein the wrong context was being used after creating a new application (since that application's application context was pointing to the original application). This means that ``Application.getApplicationContext()`` may actually not return itself in instrumentation cases. The changes help avoid this leading to actual issues in tests (but they should have no impact on the app's behavior in production environments). - PersistentCacheStore had a few significant changes to better facilitate the needs of this PR's domain changes: - Changes in the store are only published to its in-memory cache (and subsequently notified to subscribers of the store) if the cache store actually changes. - The store's existing two priming functions were combined into a single new one with more flexibility as it can be used to tweak both changing the on-disk cache and publishing the in-memory one. The documentation around priming has also been significantly improved in terms of detail as I now think it's a legitimate use case that we'll want to use in more places in the future. - InMemoryBlockingCache was updated to include publishing before-and-after values to its registered observer callback to avoid over-notifying for alterations to the cache that don't logically change it (this can actually lead to subtle differences in behavior that may not always be expected, so the change should improve robustness of the cache). Details regarding test changes & exemptions: - SplashActivityTest and AppStartupStateControllerTest were updated to enable parameterized testing since they have to test many different cases of build flavor transitions. - The only test exemptions were: renames (deprecation notice classes), files that can't be tested (applications, application components, and listeners), and files that are not conventionally tested (test activities and fragment presenters). - A new BUILD file was added for PersistentCacheStoreTest (which made development a bit easier when verifying some of the changes that ended up breaking the test suite). - One of SplashActivityTest's error case tests were removed since it now seems impossible to trigger a situation that could actually cause it. Similarly, the pending case with 5-second timeout also seems impossible to test, though it's theoretically possible to occur if something catastrophically goes wrong in a production environment with a real clock. ## 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 This PR absolutely should include screenshots & a demo video, but for the sake of optimizing time this will be done post-merge. Commits: * Create dedicated alpha application component. This simplifies application component management significantly and allows individual build flavors to have their own unique module lists. * Add beta & GA update notices. This also introduces dedicated beta & GA build flavors which is a necessary prerequisite. It also introduces an extra beta, alpha, and dev mode labels for the splash screen (the latter 2 were extra) with 2 second minimum wait timers for beta and alpha to ensure they are seen. A 5-second safety timer was added to ensure the splash screen can always be passed even if something goes wrong at the domain level (since there are now quite a few moving pieces to determine the user's current onboarding state). * Add build tests for the new beta & GA flavors. * Fix broken test per earlier changes. * Fix general broken tests & builds. Tests broken due to changes to the app startup experience haven't yet been fixed. * Lint fixes. * First part of adding tests for GA notices. There's a bunch left to do here, this is mainly needed so that I can transfer changes to a different machine. * Update TransformAndroidManifestTest.kt Correct typos. * Fix tests & static checks. This also removes temporary debug code and TODOs, and finishes the tests for SplashActivity. * Post-merge fixes. * Test fixes. * Fix Gradle test. * Follow-up adjustments after self-review. --- .github/CODEOWNERS | 4 +- .github/workflows/build_tests.yml | 260 ++++ app/BUILD.bazel | 2 + app/src/main/AndroidManifest.xml | 8 + .../alpha/AlphaApplicationComponent.kt | 2 +- .../alpha/AlphaBuildFlavorModule.kt | 12 + .../android/app/application/alpha/BUILD.bazel | 16 + .../AlphaKenyaApplicationComponent.kt | 3 +- .../app/application/alphakenya/BUILD.bazel | 1 + .../android/app/application/beta/BUILD.bazel | 30 + .../beta/BetaApplicationComponent.kt | 110 ++ .../application/beta/BetaBuildFlavorModule.kt | 12 + .../application/beta/BetaOppiaApplication.kt | 6 + .../android/app/application/dev/BUILD.bazel | 6 +- .../dev/DeveloperApplicationComponent.kt | 3 +- .../dev/DeveloperBuildFlavorModule.kt | 12 + .../android/app/application/ga/BUILD.bazel | 30 + .../application/ga/GaApplicationComponent.kt | 110 ++ .../app/application/ga/GaBuildFlavorModule.kt | 12 + .../app/application/ga/GaOppiaApplication.kt | 6 + .../app/application/testing/BUILD.bazel | 22 + .../testing/TestingBuildFlavorModule.kt | 15 + .../app/fragment/FragmentComponentImpl.kt | 6 +- ...maticAppDeprecationNoticeDialogFragment.kt | 2 +- ...eprecationNoticeDialogFragmentPresenter.kt | 2 +- .../app/notice/BetaNoticeClosedListener.kt | 11 + .../app/notice/BetaNoticeDialogFragment.kt | 30 + .../BetaNoticeDialogFragmentPresenter.kt | 32 + .../DeprecationNoticeExitAppListener.kt | 2 +- ...AvailabilityUpgradeNoticeClosedListener.kt | 11 + ...AvailabilityUpgradeNoticeDialogFragment.kt | 31 + ...ityUpgradeNoticeDialogFragmentPresenter.kt | 42 + .../android/app/notice/testing/BUILD.bazel | 34 + .../BetaNoticeDialogFragmentTestActivity.kt | 26 + ...UpgradeNoticeDialogFragmentTestActivity.kt | 28 + .../android/app/splash/SplashActivity.kt | 21 +- .../app/splash/SplashActivityPresenter.kt | 196 ++- app/src/main/res/drawable/full_oppia_logo.xml | 24 + .../res/layout/beta_notice_dialog_content.xml | 33 + ...lability_upgrade_notice_dialog_content.xml | 33 + app/src/main/res/layout/splash_activity.xml | 59 +- .../main/res/values/untranslated_strings.xml | 2 + .../AdministratorControlsActivityTest.kt | 3 +- .../AdministratorControlsFragmentTest.kt | 3 +- .../AppVersionActivityTest.kt | 3 +- .../learneranalytics/BUILD.bazel | 2 + .../ProfileAndDeviceIdActivityTest.kt | 4 +- .../ProfileAndDeviceIdFragmentTest.kt | 4 +- .../CompletedStoryListActivityTest.kt | 3 +- .../LessonThumbnailImageViewTest.kt | 3 +- .../app/customview/interaction/BUILD.bazel | 1 + .../MathExpressionInteractionsViewTest.kt | 3 +- .../oppia/android/app/databinding/BUILD.bazel | 7 + .../DrawableBindingAdaptersTest.kt | 3 +- .../ImageViewBindingAdaptersTest.kt | 3 +- .../databinding/MarginBindingAdaptersTest.kt | 3 +- ...StateAssemblerMarginBindingAdaptersTest.kt | 3 +- ...tateAssemblerPaddingBindingAdaptersTest.kt | 3 +- .../TextViewBindingAdaptersTest.kt | 3 +- .../databinding/ViewBindingAdaptersTest.kt | 3 +- .../DeveloperOptionsActivityTest.kt | 3 +- .../DeveloperOptionsFragmentTest.kt | 3 +- .../MarkChaptersCompletedActivityTest.kt | 3 +- .../MarkChaptersCompletedFragmentTest.kt | 3 +- .../MarkStoriesCompletedActivityTest.kt | 3 +- .../MarkStoriesCompletedFragmentTest.kt | 3 +- .../MarkTopicsCompletedActivityTest.kt | 3 +- .../MarkTopicsCompletedFragmentTest.kt | 3 +- .../devoptions/ViewEventLogsActivityTest.kt | 4 +- .../devoptions/ViewEventLogsFragmentTest.kt | 3 +- .../ForceNetworkTypeActivityTest.kt | 3 +- .../ForceNetworkTypeFragmentTest.kt | 3 +- .../mathexpressionparser/BUILD.bazel | 2 + .../MathExpressionParserActivityTest.kt | 3 +- .../MathExpressionParserFragmentTest.kt | 3 +- .../android/app/faq/FAQListFragmentTest.kt | 3 +- .../android/app/faq/FAQSingleActivityTest.kt | 3 +- .../android/app/faq/FaqListActivityTest.kt | 3 +- .../android/app/help/HelpActivityTest.kt | 3 +- .../android/app/help/HelpFragmentTest.kt | 3 +- .../android/app/home/HomeActivityTest.kt | 3 +- .../app/home/RecentlyPlayedFragmentTest.kt | 3 +- .../app/home/TopicSummaryViewModelTest.kt | 3 +- .../android/app/home/WelcomeViewModelTest.kt | 3 +- .../PromotedStoryListViewModelTest.kt | 3 +- .../PromotedStoryViewModelTest.kt | 3 +- .../mydownloads/MyDownloadsActivityTest.kt | 3 +- .../mydownloads/MyDownloadsFragmentTest.kt | 3 +- .../org/oppia/android/app/notice/BUILD.bazel | 81 ++ .../notice/BetaNoticeDialogFragmentTest.kt | 278 ++++ ...labilityUpgradeNoticeDialogFragmentTest.kt | 285 ++++ .../app/onboarding/OnboardingActivityTest.kt | 3 +- .../app/onboarding/OnboardingFragmentTest.kt | 3 +- .../OngoingTopicListActivityTest.kt | 3 +- .../app/options/AppLanguageActivityTest.kt | 3 +- .../app/options/AppLanguageFragmentTest.kt | 3 +- .../app/options/AudioLanguageActivityTest.kt | 3 +- .../app/options/AudioLanguageFragmentTest.kt | 3 +- .../app/options/OptionsActivityTest.kt | 3 +- .../app/options/OptionsFragmentTest.kt | 3 +- .../options/ReadingTextSizeActivityTest.kt | 3 +- .../options/ReadingTextSizeFragmentTest.kt | 3 +- .../android/app/parser/HtmlParserTest.kt | 3 +- .../app/player/audio/AudioFragmentTest.kt | 3 +- .../exploration/ExplorationActivityTest.kt | 3 +- .../android/app/player/state/BUILD.bazel | 1 + .../app/player/state/StateFragmentTest.kt | 3 +- .../app/policies/PoliciesActivityTest.kt | 3 +- .../app/policies/PoliciesFragmentTest.kt | 3 +- .../app/profile/AddProfileActivityTest.kt | 3 +- .../app/profile/AdminAuthActivityTest.kt | 3 +- .../app/profile/AdminPinActivityTest.kt | 3 +- .../app/profile/PinPasswordActivityTest.kt | 3 +- .../app/profile/ProfileChooserFragmentTest.kt | 3 +- .../ProfilePictureActivityTest.kt | 3 +- .../ProfileProgressActivityTest.kt | 3 +- .../ProfileProgressFragmentTest.kt | 3 +- .../app/recyclerview/BindableAdapterTest.kt | 3 +- .../resumelesson/ResumeLessonActivityTest.kt | 3 +- .../resumelesson/ResumeLessonFragmentTest.kt | 3 +- .../profile/ProfileEditActivityTest.kt | 3 +- .../profile/ProfileEditFragmentTest.kt | 3 +- .../profile/ProfileListActivityTest.kt | 3 +- .../profile/ProfileListFragmentTest.kt | 3 +- .../profile/ProfileRenameActivityTest.kt | 3 +- .../profile/ProfileRenameFragmentTest.kt | 3 +- .../profile/ProfileResetPinActivityTest.kt | 3 +- .../profile/ProfileResetPinFragmentTest.kt | 3 +- .../org/oppia/android/app/splash/BUILD.bazel | 53 + .../android/app/splash/SplashActivityTest.kt | 1065 +++++++++++--- .../android/app/story/StoryActivityTest.kt | 3 +- .../android/app/story/StoryFragmentTest.kt | 3 +- .../app/testing/DragDropTestActivityTest.kt | 3 +- ...ImageRegionSelectionInteractionViewTest.kt | 3 +- .../InputInteractionViewTestActivityTest.kt | 3 +- .../NavigationDrawerActivityDebugTest.kt | 3 +- .../NavigationDrawerActivityProdTest.kt | 3 +- ...tFontScaleConfigurationUtilActivityTest.kt | 3 +- .../testing/TopicTestActivityForStoryTest.kt | 3 +- .../app/thirdparty/LicenseListActivityTest.kt | 3 +- .../app/thirdparty/LicenseListFragmentTest.kt | 3 +- .../LicenseTextViewerActivityTest.kt | 3 +- .../LicenseTextViewerFragmentTest.kt | 3 +- .../ThirdPartyDependencyListActivityTest.kt | 3 +- .../ThirdPartyDependencyListFragmentTest.kt | 3 +- .../android/app/topic/TopicActivityTest.kt | 3 +- .../android/app/topic/TopicFragmentTest.kt | 3 +- .../conceptcard/ConceptCardFragmentTest.kt | 3 +- .../app/topic/info/TopicInfoFragmentTest.kt | 3 +- .../topic/lessons/TopicLessonsFragmentTest.kt | 3 +- .../practice/TopicPracticeFragmentTest.kt | 3 +- .../QuestionPlayerActivityTest.kt | 3 +- .../revision/TopicRevisionFragmentTest.kt | 3 +- .../revisioncard/RevisionCardActivityTest.kt | 3 +- .../revisioncard/RevisionCardFragmentTest.kt | 3 +- .../app/utility/RatioExtensionsTest.kt | 3 +- .../walkthrough/WalkthroughActivityTest.kt | 3 +- .../WalkthroughFinalFragmentTest.kt | 3 +- .../WalkthroughTopicListFragmentTest.kt | 3 +- .../WalkthroughWelcomeFragmentTest.kt | 3 +- .../activity/ActivityIntentFactoriesTest.kt | 3 +- .../oppia/android/app/activity/BUILD.bazel | 1 + .../alpha/AlphaBuildFlavorModuleTest.kt | 81 ++ .../android/app/application/alpha/BUILD.bazel | 26 + .../android/app/application/beta/BUILD.bazel | 26 + .../beta/BetaBuildFlavorModuleTest.kt | 81 ++ .../android/app/application/dev/BUILD.bazel | 26 + .../dev/DeveloperBuildFlavorModuleTest.kt | 81 ++ .../android/app/application/ga/BUILD.bazel | 26 + .../application/ga/GaBuildFlavorModuleTest.kt | 81 ++ .../app/application/testing/BUILD.bazel | 26 + .../testing/TestingBuildFlavorModuleTest.kt | 81 ++ .../android/app/home/HomeActivityLocalTest.kt | 3 +- .../app/parser/FractionParsingUiErrorTest.kt | 3 +- .../parser/ListItemLeadingMarginSpanTest.kt | 3 +- .../app/parser/StringToRatioParserTest.kt | 3 +- .../ExplorationActivityLocalTest.kt | 3 +- .../player/state/StateFragmentLocalTest.kt | 3 +- .../ProfileChooserFragmentLocalTest.kt | 3 +- .../app/story/StoryActivityLocalTest.kt | 3 +- .../app/testing/CompletedStoryListSpanTest.kt | 3 +- .../oppia/android/app/testing/HomeSpanTest.kt | 3 +- .../app/testing/OngoingTopicListSpanTest.kt | 3 +- .../PlatformParameterIntegrationTest.kt | 3 +- .../app/testing/ProfileChooserSpanTest.kt | 3 +- .../testing/ProfileProgressSpanCountTest.kt | 3 +- .../app/testing/RecentlyPlayedSpanTest.kt | 3 +- .../app/testing/TopicRevisionSpanTest.kt | 3 +- .../android/app/testing/activity/BUILD.bazel | 1 + .../app/testing/activity/TestActivityTest.kt | 3 +- .../AdministratorControlsFragmentTest.kt | 3 +- .../testing/options/OptionsFragmentTest.kt | 3 +- .../player/split/PlayerSplitScreenTest.kt | 3 +- .../state/StateFragmentAccessibilityTest.kt | 3 +- .../topic/info/TopicInfoFragmentLocalTest.kt | 3 +- .../lessons/TopicLessonsFragmentLocalTest.kt | 3 +- .../QuestionPlayerActivityLocalTest.kt | 3 +- .../RevisionCardActivityLocalTest.kt | 3 +- .../AppLanguageResourceHandlerTest.kt | 3 +- .../AppLanguageWatcherMixinTest.kt | 3 +- .../oppia/android/app/translation/BUILD.bazel | 2 + .../app/utility/datetime/DateTimeUtilTest.kt | 3 +- .../android/app/utility/math/BUILD.bazel | 1 + .../MathExpressionAccessibilityUtilTest.kt | 3 +- build_flavors.bzl | 30 +- data/BUILD.bazel | 6 + .../data/persistence/PersistentCacheStore.kt | 203 ++- .../android/data/persistence/BUILD.bazel | 32 + .../persistence/PersistentCacheStoreTest.kt | 1278 +++++++++++++++-- domain/BUILD.bazel | 1 + .../ExplorationCheckpointController.kt | 7 +- .../onboarding/AppStartupStateController.kt | 130 +- .../LoggingIdentifierController.kt | 11 +- .../profile/ProfileManagementController.kt | 7 +- .../topic/PrimeTopicAssetsControllerImpl.kt | 4 +- .../domain/topic/StoryProgressController.kt | 9 +- .../AppStartupStateControllerTest.kt | 536 ++++++- .../android/domain/onboarding/BUILD.bazel | 39 + .../instrumentation/application/BUILD.bazel | 1 + .../application/TestApplicationComponent.kt | 4 +- model/src/main/proto/BUILD.bazel | 13 + model/src/main/proto/onboarding.proto | 38 + model/src/main/proto/version.proto | 32 + .../file_content_validation_checks.textproto | 2 + .../assets/kdoc_validity_exemptions.textproto | 2 +- scripts/assets/test_file_exemptions.textproto | 16 +- .../oppia/android/testing/OppiaTestRunner.kt | 2 +- .../oppia/android/testing/junit/BUILD.bazel | 3 + ...alizeDefaultLocaleRuleCustomContextTest.kt | 3 +- ...InitializeDefaultLocaleRuleOmissionTest.kt | 3 +- .../junit/InitializeDefaultLocaleRuleTest.kt | 3 +- .../oppia/android/util/data/DataProvider.kt | 5 +- .../oppia/android/util/data/DataProviders.kt | 28 +- .../util/data/InMemoryBlockingCache.kt | 10 +- .../logging/firebase/FirebaseEventLogger.kt | 8 +- .../parser/image/RepositoryGlideModule.kt | 5 +- .../util/parser/math/MathBitmapModelLoader.kt | 20 +- .../android/util/data/DataProvidersTest.kt | 18 +- version.bzl | 2 + 239 files changed, 5845 insertions(+), 705 deletions(-) create mode 100644 app/src/main/java/org/oppia/android/app/application/alpha/AlphaBuildFlavorModule.kt create mode 100644 app/src/main/java/org/oppia/android/app/application/beta/BUILD.bazel create mode 100644 app/src/main/java/org/oppia/android/app/application/beta/BetaApplicationComponent.kt create mode 100644 app/src/main/java/org/oppia/android/app/application/beta/BetaBuildFlavorModule.kt create mode 100644 app/src/main/java/org/oppia/android/app/application/beta/BetaOppiaApplication.kt create mode 100644 app/src/main/java/org/oppia/android/app/application/dev/DeveloperBuildFlavorModule.kt create mode 100644 app/src/main/java/org/oppia/android/app/application/ga/BUILD.bazel create mode 100644 app/src/main/java/org/oppia/android/app/application/ga/GaApplicationComponent.kt create mode 100644 app/src/main/java/org/oppia/android/app/application/ga/GaBuildFlavorModule.kt create mode 100644 app/src/main/java/org/oppia/android/app/application/ga/GaOppiaApplication.kt create mode 100644 app/src/main/java/org/oppia/android/app/application/testing/BUILD.bazel create mode 100644 app/src/main/java/org/oppia/android/app/application/testing/TestingBuildFlavorModule.kt rename app/src/main/java/org/oppia/android/app/{deprecation => notice}/AutomaticAppDeprecationNoticeDialogFragment.kt (97%) rename app/src/main/java/org/oppia/android/app/{deprecation => notice}/AutomaticAppDeprecationNoticeDialogFragmentPresenter.kt (96%) create mode 100644 app/src/main/java/org/oppia/android/app/notice/BetaNoticeClosedListener.kt create mode 100644 app/src/main/java/org/oppia/android/app/notice/BetaNoticeDialogFragment.kt create mode 100644 app/src/main/java/org/oppia/android/app/notice/BetaNoticeDialogFragmentPresenter.kt rename app/src/main/java/org/oppia/android/app/{deprecation => notice}/DeprecationNoticeExitAppListener.kt (82%) create mode 100644 app/src/main/java/org/oppia/android/app/notice/GeneralAvailabilityUpgradeNoticeClosedListener.kt create mode 100644 app/src/main/java/org/oppia/android/app/notice/GeneralAvailabilityUpgradeNoticeDialogFragment.kt create mode 100644 app/src/main/java/org/oppia/android/app/notice/GeneralAvailabilityUpgradeNoticeDialogFragmentPresenter.kt create mode 100644 app/src/main/java/org/oppia/android/app/notice/testing/BUILD.bazel create mode 100644 app/src/main/java/org/oppia/android/app/notice/testing/BetaNoticeDialogFragmentTestActivity.kt create mode 100644 app/src/main/java/org/oppia/android/app/notice/testing/GeneralAvailabilityUpgradeNoticeDialogFragmentTestActivity.kt create mode 100644 app/src/main/res/drawable/full_oppia_logo.xml create mode 100755 app/src/main/res/layout/beta_notice_dialog_content.xml create mode 100755 app/src/main/res/layout/general_availability_upgrade_notice_dialog_content.xml create mode 100644 app/src/sharedTest/java/org/oppia/android/app/notice/BUILD.bazel create mode 100644 app/src/sharedTest/java/org/oppia/android/app/notice/BetaNoticeDialogFragmentTest.kt create mode 100644 app/src/sharedTest/java/org/oppia/android/app/notice/GeneralAvailabilityUpgradeNoticeDialogFragmentTest.kt create mode 100644 app/src/sharedTest/java/org/oppia/android/app/splash/BUILD.bazel create mode 100644 app/src/test/java/org/oppia/android/app/application/alpha/AlphaBuildFlavorModuleTest.kt create mode 100644 app/src/test/java/org/oppia/android/app/application/alpha/BUILD.bazel create mode 100644 app/src/test/java/org/oppia/android/app/application/beta/BUILD.bazel create mode 100644 app/src/test/java/org/oppia/android/app/application/beta/BetaBuildFlavorModuleTest.kt create mode 100644 app/src/test/java/org/oppia/android/app/application/dev/BUILD.bazel create mode 100644 app/src/test/java/org/oppia/android/app/application/dev/DeveloperBuildFlavorModuleTest.kt create mode 100644 app/src/test/java/org/oppia/android/app/application/ga/BUILD.bazel create mode 100644 app/src/test/java/org/oppia/android/app/application/ga/GaBuildFlavorModuleTest.kt create mode 100644 app/src/test/java/org/oppia/android/app/application/testing/BUILD.bazel create mode 100644 app/src/test/java/org/oppia/android/app/application/testing/TestingBuildFlavorModuleTest.kt create mode 100644 data/src/test/java/org/oppia/android/data/persistence/BUILD.bazel create mode 100644 domain/src/test/java/org/oppia/android/domain/onboarding/BUILD.bazel create mode 100644 model/src/main/proto/version.proto diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 0c77e9bcc31..a59584e77f2 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -125,8 +125,8 @@ config/kitkat_main_dex_class_list.txt @BenHenning # Databinding adapters. /app/src/main/java/org/oppia/android/app/databinding/ @BenHenning -# App deprecation functionality. -/app/src/*/java/org/oppia/android/app/deprecation/ @BenHenning +# App notices functionality (such as for deprecations). +/app/src/*/java/org/oppia/android/app/notice/ @BenHenning # Parsing functionality needed for interactions. /app/src/*/java/org/oppia/android/app/parser/ @BenHenning diff --git a/.github/workflows/build_tests.yml b/.github/workflows/build_tests.yml index b95d906cbc3..fdaebd4af7b 100644 --- a/.github/workflows/build_tests.yml +++ b/.github/workflows/build_tests.yml @@ -476,3 +476,263 @@ jobs: with: name: oppia_alpha_kenya.aab path: /home/runner/work/oppia-android/oppia-android/oppia_alpha_kenya.aab + + build_oppia_beta_aab: + name: Build Oppia AAB (beta flavor) + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-18.04] + env: + ENABLE_CACHING: false + CACHE_DIRECTORY: ~/.bazel_cache + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Set up JDK 9 + uses: actions/setup-java@v1 + with: + java-version: 9 + + - name: Set up Bazel + uses: abhinavsingh/setup-bazel@v3 + with: + version: 4.0.0 + + - name: Set up build environment + uses: ./.github/actions/set-up-android-bazel-build-environment + + # For reference on this & the later cache actions, see: + # https://github.com/actions/cache/issues/239#issuecomment-606950711 & + # https://github.com/actions/cache/issues/109#issuecomment-558771281. Note that these work + # with Bazel since Bazel can share the most recent cache from an unrelated build and still + # benefit from incremental build performance (assuming that actions/cache aggressively removes + # older caches due to the 5GB cache limit size & Bazel's large cache size). + - uses: actions/cache@v2 + id: cache + with: + path: ${{ env.CACHE_DIRECTORY }} + key: ${{ runner.os }}-${{ env.CACHE_DIRECTORY }}-bazel-binary-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-${{ env.CACHE_DIRECTORY }}-bazel-binary- + ${{ runner.os }}-${{ env.CACHE_DIRECTORY }}-bazel-tests- + ${{ runner.os }}-${{ env.CACHE_DIRECTORY }}-bazel- + + # This check is needed to ensure that Bazel's unbounded cache growth doesn't result in a + # situation where the cache never updates (e.g. due to exceeding GitHub's cache size limit) + # thereby only ever using the last successful cache version. This solution will result in a + # few slower CI actions around the time cache is detected to be too large, but it should + # incrementally improve thereafter. + - name: Ensure cache size + env: + BAZEL_CACHE_DIR: ${{ env.CACHE_DIRECTORY }} + run: | + # See https://stackoverflow.com/a/27485157 for reference. + EXPANDED_BAZEL_CACHE_PATH="${BAZEL_CACHE_DIR/#\~/$HOME}" + CACHE_SIZE_MB=$(du -smc $EXPANDED_BAZEL_CACHE_PATH | grep total | cut -f1) + echo "Total size of Bazel cache (rounded up to MBs): $CACHE_SIZE_MB" + # Use a 4.5GB threshold since actions/cache compresses the results, and Bazel caches seem + # to only increase by a few hundred megabytes across changes for unrelated branches. This + # is also a reasonable upper-bound (local tests as of 2021-03-31 suggest that a full build + # of the codebase (e.g. //...) from scratch only requires a ~2.1GB uncompressed/~900MB + # compressed cache). + if [[ "$CACHE_SIZE_MB" -gt 4500 ]]; then + echo "Cache exceeds cut-off; resetting it (will result in a slow build)" + rm -rf $EXPANDED_BAZEL_CACHE_PATH + fi + + - name: Configure Bazel to use a local cache + env: + BAZEL_CACHE_DIR: ${{ env.CACHE_DIRECTORY }} + run: | + EXPANDED_BAZEL_CACHE_PATH="${BAZEL_CACHE_DIR/#\~/$HOME}" + echo "Using $EXPANDED_BAZEL_CACHE_PATH as Bazel's cache path" + echo "build --disk_cache=$EXPANDED_BAZEL_CACHE_PATH" >> $HOME/.bazelrc + shell: bash + + - name: Check Bazel environment + run: bazel info + + # See https://git-secret.io/installation for details on installing git-secret. Note that the + # apt-get method isn't used since it's much slower to update & upgrade apt before installation + # versus just directly cloning & installing the project. Further, the specific version + # shouldn't matter since git-secret relies on a future-proof storage mechanism for secrets. + # This also uses a different directory to install git-secret to avoid requiring root access + # when running the git secret command. + - name: Install git-secret (non-fork only) + if: ${{ env.ENABLE_CACHING == 'true' && github.event.pull_request.head.repo.full_name == 'oppia/oppia-android' }} + shell: bash + run: | + cd $HOME + mkdir -p $HOME/gitsecret + git clone https://github.com/sobolevn/git-secret.git git-secret + cd git-secret && make build + PREFIX="$HOME/gitsecret" make install + echo "$HOME/gitsecret" >> $GITHUB_PATH + echo "$HOME/gitsecret/bin" >> $GITHUB_PATH + + - name: Decrypt secrets (non-fork only) + if: ${{ env.ENABLE_CACHING == 'true' && github.event.pull_request.head.repo.full_name == 'oppia/oppia-android' }} + env: + GIT_SECRET_GPG_PRIVATE_KEY: ${{ secrets.GIT_SECRET_GPG_PRIVATE_KEY }} + run: | + cd $HOME + # NOTE TO DEVELOPERS: Make sure to never print this key directly to stdout! + echo $GIT_SECRET_GPG_PRIVATE_KEY | base64 --decode > ./git_secret_private_key.gpg + gpg --import ./git_secret_private_key.gpg + cd $GITHUB_WORKSPACE + git secret reveal + + # Note that caching only works on non-forks. + - name: Build Oppia beta AAB (with caching, non-fork only) + if: ${{ env.ENABLE_CACHING == 'true' && github.event.pull_request.head.repo.full_name == 'oppia/oppia-android' }} + env: + BAZEL_REMOTE_CACHE_URL: ${{ secrets.BAZEL_REMOTE_CACHE_URL }} + run: | + bazel build --compilation_mode=opt --remote_http_cache=$BAZEL_REMOTE_CACHE_URL --google_credentials=./config/oppia-dev-workflow-remote-cache-credentials.json -- //:oppia_beta + + - name: Build Oppia beta AAB (without caching, or on a fork) + if: ${{ env.ENABLE_CACHING == 'false' || github.event.pull_request.head.repo.full_name != 'oppia/oppia-android' }} + run: | + bazel build --compilation_mode=opt -- //:oppia_beta + + - name: Copy Oppia beta AAB for uploading + run: | + cp $GITHUB_WORKSPACE/bazel-bin/oppia_beta.aab /home/runner/work/oppia-android/oppia-android/ + + - uses: actions/upload-artifact@v2 + with: + name: oppia_beta.aab + path: /home/runner/work/oppia-android/oppia-android/oppia_beta.aab + + build_oppia_ga_aab: + name: Build Oppia AAB (GA flavor) + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-18.04] + env: + ENABLE_CACHING: false + CACHE_DIRECTORY: ~/.bazel_cache + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Set up JDK 9 + uses: actions/setup-java@v1 + with: + java-version: 9 + + - name: Set up Bazel + uses: abhinavsingh/setup-bazel@v3 + with: + version: 4.0.0 + + - name: Set up build environment + uses: ./.github/actions/set-up-android-bazel-build-environment + + # For reference on this & the later cache actions, see: + # https://github.com/actions/cache/issues/239#issuecomment-606950711 & + # https://github.com/actions/cache/issues/109#issuecomment-558771281. Note that these work + # with Bazel since Bazel can share the most recent cache from an unrelated build and still + # benefit from incremental build performance (assuming that actions/cache aggressively removes + # older caches due to the 5GB cache limit size & Bazel's large cache size). + - uses: actions/cache@v2 + id: cache + with: + path: ${{ env.CACHE_DIRECTORY }} + key: ${{ runner.os }}-${{ env.CACHE_DIRECTORY }}-bazel-binary-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-${{ env.CACHE_DIRECTORY }}-bazel-binary- + ${{ runner.os }}-${{ env.CACHE_DIRECTORY }}-bazel-tests- + ${{ runner.os }}-${{ env.CACHE_DIRECTORY }}-bazel- + + # This check is needed to ensure that Bazel's unbounded cache growth doesn't result in a + # situation where the cache never updates (e.g. due to exceeding GitHub's cache size limit) + # thereby only ever using the last successful cache version. This solution will result in a + # few slower CI actions around the time cache is detected to be too large, but it should + # incrementally improve thereafter. + - name: Ensure cache size + env: + BAZEL_CACHE_DIR: ${{ env.CACHE_DIRECTORY }} + run: | + # See https://stackoverflow.com/a/27485157 for reference. + EXPANDED_BAZEL_CACHE_PATH="${BAZEL_CACHE_DIR/#\~/$HOME}" + CACHE_SIZE_MB=$(du -smc $EXPANDED_BAZEL_CACHE_PATH | grep total | cut -f1) + echo "Total size of Bazel cache (rounded up to MBs): $CACHE_SIZE_MB" + # Use a 4.5GB threshold since actions/cache compresses the results, and Bazel caches seem + # to only increase by a few hundred megabytes across changes for unrelated branches. This + # is also a reasonable upper-bound (local tests as of 2021-03-31 suggest that a full build + # of the codebase (e.g. //...) from scratch only requires a ~2.1GB uncompressed/~900MB + # compressed cache). + if [[ "$CACHE_SIZE_MB" -gt 4500 ]]; then + echo "Cache exceeds cut-off; resetting it (will result in a slow build)" + rm -rf $EXPANDED_BAZEL_CACHE_PATH + fi + + - name: Configure Bazel to use a local cache + env: + BAZEL_CACHE_DIR: ${{ env.CACHE_DIRECTORY }} + run: | + EXPANDED_BAZEL_CACHE_PATH="${BAZEL_CACHE_DIR/#\~/$HOME}" + echo "Using $EXPANDED_BAZEL_CACHE_PATH as Bazel's cache path" + echo "build --disk_cache=$EXPANDED_BAZEL_CACHE_PATH" >> $HOME/.bazelrc + shell: bash + + - name: Check Bazel environment + run: bazel info + + # See https://git-secret.io/installation for details on installing git-secret. Note that the + # apt-get method isn't used since it's much slower to update & upgrade apt before installation + # versus just directly cloning & installing the project. Further, the specific version + # shouldn't matter since git-secret relies on a future-proof storage mechanism for secrets. + # This also uses a different directory to install git-secret to avoid requiring root access + # when running the git secret command. + - name: Install git-secret (non-fork only) + if: ${{ env.ENABLE_CACHING == 'true' && github.event.pull_request.head.repo.full_name == 'oppia/oppia-android' }} + shell: bash + run: | + cd $HOME + mkdir -p $HOME/gitsecret + git clone https://github.com/sobolevn/git-secret.git git-secret + cd git-secret && make build + PREFIX="$HOME/gitsecret" make install + echo "$HOME/gitsecret" >> $GITHUB_PATH + echo "$HOME/gitsecret/bin" >> $GITHUB_PATH + + - name: Decrypt secrets (non-fork only) + if: ${{ env.ENABLE_CACHING == 'true' && github.event.pull_request.head.repo.full_name == 'oppia/oppia-android' }} + env: + GIT_SECRET_GPG_PRIVATE_KEY: ${{ secrets.GIT_SECRET_GPG_PRIVATE_KEY }} + run: | + cd $HOME + # NOTE TO DEVELOPERS: Make sure to never print this key directly to stdout! + echo $GIT_SECRET_GPG_PRIVATE_KEY | base64 --decode > ./git_secret_private_key.gpg + gpg --import ./git_secret_private_key.gpg + cd $GITHUB_WORKSPACE + git secret reveal + + # Note that caching only works on non-forks. + - name: Build Oppia GA AAB (with caching, non-fork only) + if: ${{ env.ENABLE_CACHING == 'true' && github.event.pull_request.head.repo.full_name == 'oppia/oppia-android' }} + env: + BAZEL_REMOTE_CACHE_URL: ${{ secrets.BAZEL_REMOTE_CACHE_URL }} + run: | + bazel build --compilation_mode=opt --remote_http_cache=$BAZEL_REMOTE_CACHE_URL --google_credentials=./config/oppia-dev-workflow-remote-cache-credentials.json -- //:oppia_ga + + - name: Build Oppia GA AAB (without caching, or on a fork) + if: ${{ env.ENABLE_CACHING == 'false' || github.event.pull_request.head.repo.full_name != 'oppia/oppia-android' }} + run: | + bazel build --compilation_mode=opt -- //:oppia_ga + + - name: Copy Oppia GA AAB for uploading + run: | + cp $GITHUB_WORKSPACE/bazel-bin/oppia_ga.aab /home/runner/work/oppia-android/oppia-android/ + + - uses: actions/upload-artifact@v2 + with: + name: oppia_ga.aab + path: /home/runner/work/oppia-android/oppia-android/oppia_ga.aab diff --git a/app/BUILD.bazel b/app/BUILD.bazel index 749d0b4e908..d20fc5793ea 100644 --- a/app/BUILD.bazel +++ b/app/BUILD.bazel @@ -575,6 +575,7 @@ android_library( "//app/src/main/java/org/oppia/android/app/translation:app_language_resource_handler", "//model/src/main/proto:interaction_object_java_proto_lite", "//model/src/main/proto:thumbnail_java_proto_lite", + "//model/src/main/proto:version_java_proto_lite", "//third_party:androidx_annotation_annotation", "//third_party:androidx_constraintlayout_constraintlayout", "//third_party:androidx_core_core", @@ -824,6 +825,7 @@ TEST_DEPS = [ "//app/src/main/java/org/oppia/android/app/application:application_injector", "//app/src/main/java/org/oppia/android/app/application:application_injector_provider", "//app/src/main/java/org/oppia/android/app/application:common_application_modules", + "//app/src/main/java/org/oppia/android/app/application/testing:testing_build_flavor_module", "//app/src/main/java/org/oppia/android/app/testing/activity:test_activity", "//app/src/main/java/org/oppia/android/app/translation/testing:test_module", "//app/src/main/java/org/oppia/android/app/utility/math:math_expression_accessibility_util", diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 186e5830ae5..233b4595399 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -85,6 +85,14 @@ android:label="@string/my_downloads_activity_title" android:screenOrientation="portrait" android:theme="@style/OppiaThemeWithoutActionBar" /> + + (R.id.beta_notice_dialog_preference_checkbox) + return AlertDialog.Builder(activity) + .setTitle(R.string.beta_notice_dialog_title) + .setView(contentView) + .setPositiveButton(R.string.beta_notice_dialog_close_button_text) { _, _ -> + betaNoticeClosedListener.onBetaNoticeOkayButtonClicked(preferenceCheckbox.isChecked) + } + .setCancelable(false) + .create() + .also { it.setCanceledOnTouchOutside(false) } + } +} diff --git a/app/src/main/java/org/oppia/android/app/deprecation/DeprecationNoticeExitAppListener.kt b/app/src/main/java/org/oppia/android/app/notice/DeprecationNoticeExitAppListener.kt similarity index 82% rename from app/src/main/java/org/oppia/android/app/deprecation/DeprecationNoticeExitAppListener.kt rename to app/src/main/java/org/oppia/android/app/notice/DeprecationNoticeExitAppListener.kt index 020ac27ef8e..ff3d7dd1671 100644 --- a/app/src/main/java/org/oppia/android/app/deprecation/DeprecationNoticeExitAppListener.kt +++ b/app/src/main/java/org/oppia/android/app/notice/DeprecationNoticeExitAppListener.kt @@ -1,4 +1,4 @@ -package org.oppia.android.app.deprecation +package org.oppia.android.app.notice /** Listener for when the app deprecation dialog is closed. */ interface DeprecationNoticeExitAppListener { diff --git a/app/src/main/java/org/oppia/android/app/notice/GeneralAvailabilityUpgradeNoticeClosedListener.kt b/app/src/main/java/org/oppia/android/app/notice/GeneralAvailabilityUpgradeNoticeClosedListener.kt new file mode 100644 index 00000000000..715b7d06e6a --- /dev/null +++ b/app/src/main/java/org/oppia/android/app/notice/GeneralAvailabilityUpgradeNoticeClosedListener.kt @@ -0,0 +1,11 @@ +package org.oppia.android.app.notice + +/** Listener for when the general availability update dialog is closed. */ +interface GeneralAvailabilityUpgradeNoticeClosedListener { + /** + * Called when the notice dialog was closed. + * + * @param permanentlyDismiss whether the user never wants to see this notice again + */ + fun onGaUpgradeNoticeOkayButtonClicked(permanentlyDismiss: Boolean) +} diff --git a/app/src/main/java/org/oppia/android/app/notice/GeneralAvailabilityUpgradeNoticeDialogFragment.kt b/app/src/main/java/org/oppia/android/app/notice/GeneralAvailabilityUpgradeNoticeDialogFragment.kt new file mode 100644 index 00000000000..30ca45e7d1c --- /dev/null +++ b/app/src/main/java/org/oppia/android/app/notice/GeneralAvailabilityUpgradeNoticeDialogFragment.kt @@ -0,0 +1,31 @@ +package org.oppia.android.app.notice + +import android.app.Dialog +import android.content.Context +import android.os.Bundle +import org.oppia.android.app.fragment.FragmentComponentImpl +import org.oppia.android.app.fragment.InjectableDialogFragment +import javax.inject.Inject + +/** + * Dialog fragment to be shown when the user may be unaware that they've updated from a pre-release + * version of the app to general availability. + */ +class GeneralAvailabilityUpgradeNoticeDialogFragment : InjectableDialogFragment() { + companion object { + /** Returns a new instance of [GeneralAvailabilityUpgradeNoticeDialogFragment]. */ + fun newInstance(): GeneralAvailabilityUpgradeNoticeDialogFragment = + GeneralAvailabilityUpgradeNoticeDialogFragment() + } + + @Inject lateinit var presenter: GeneralAvailabilityUpgradeNoticeDialogFragmentPresenter + + override fun onAttach(context: Context) { + super.onAttach(context) + (fragmentComponent as FragmentComponentImpl).inject(this) + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return presenter.handleOnCreateDialog() + } +} diff --git a/app/src/main/java/org/oppia/android/app/notice/GeneralAvailabilityUpgradeNoticeDialogFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/notice/GeneralAvailabilityUpgradeNoticeDialogFragmentPresenter.kt new file mode 100644 index 00000000000..74cdca94c9d --- /dev/null +++ b/app/src/main/java/org/oppia/android/app/notice/GeneralAvailabilityUpgradeNoticeDialogFragmentPresenter.kt @@ -0,0 +1,42 @@ +package org.oppia.android.app.notice + +import android.app.Dialog +import android.view.View +import android.widget.CheckBox +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import org.oppia.android.R +import javax.inject.Inject + +/** + * Presenter for the dialog that shows when the user has updated to the general availability version + * app is being used. + */ +class GeneralAvailabilityUpgradeNoticeDialogFragmentPresenter @Inject constructor( + private val activity: AppCompatActivity +) { + private val gaUpgradeNoticeClosedListener by lazy { + activity as GeneralAvailabilityUpgradeNoticeClosedListener + } + + /** Handles dialog creation for the general availability update notice. */ + fun handleOnCreateDialog(): Dialog { + val contentView = + View.inflate( + activity, R.layout.general_availability_upgrade_notice_dialog_content, /* root= */ null + ) + val preferenceCheckbox = + contentView.findViewById(R.id.ga_update_notice_dialog_preference_checkbox) + return AlertDialog.Builder(activity) + .setTitle(R.string.general_availability_notice_dialog_title) + .setView(contentView) + .setPositiveButton(R.string.general_availability_notice_dialog_close_button_text) { _, _ -> + gaUpgradeNoticeClosedListener.onGaUpgradeNoticeOkayButtonClicked( + preferenceCheckbox.isChecked + ) + } + .setCancelable(false) + .create() + .also { it.setCanceledOnTouchOutside(false) } + } +} diff --git a/app/src/main/java/org/oppia/android/app/notice/testing/BUILD.bazel b/app/src/main/java/org/oppia/android/app/notice/testing/BUILD.bazel new file mode 100644 index 00000000000..92f1a0ae62b --- /dev/null +++ b/app/src/main/java/org/oppia/android/app/notice/testing/BUILD.bazel @@ -0,0 +1,34 @@ +""" +Test-only utilities corresponding to app notices. +""" + +load("@dagger//:workspace_defs.bzl", "dagger_rules") +load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_android_library") + +kt_android_library( + name = "beta_notice_dialog_frgment_test_activity", + testonly = True, + srcs = [ + "BetaNoticeDialogFragmentTestActivity.kt", + ], + visibility = ["//app:app_testing_visibility"], + deps = [ + "//app", + "//app/src/main/java/org/oppia/android/app/testing/activity:test_activity", + ], +) + +kt_android_library( + name = "general_availability_upgrade_notice_dialog_fragment_test_activity", + testonly = True, + srcs = [ + "GeneralAvailabilityUpgradeNoticeDialogFragmentTestActivity.kt", + ], + visibility = ["//app:app_testing_visibility"], + deps = [ + "//app", + "//app/src/main/java/org/oppia/android/app/testing/activity:test_activity", + ], +) + +dagger_rules() diff --git a/app/src/main/java/org/oppia/android/app/notice/testing/BetaNoticeDialogFragmentTestActivity.kt b/app/src/main/java/org/oppia/android/app/notice/testing/BetaNoticeDialogFragmentTestActivity.kt new file mode 100644 index 00000000000..7d0e612bb26 --- /dev/null +++ b/app/src/main/java/org/oppia/android/app/notice/testing/BetaNoticeDialogFragmentTestActivity.kt @@ -0,0 +1,26 @@ +package org.oppia.android.app.notice.testing + +import android.os.Bundle +import org.oppia.android.app.notice.BetaNoticeClosedListener +import org.oppia.android.app.notice.BetaNoticeDialogFragment +import org.oppia.android.app.testing.activity.TestActivity + +/** [TestActivity] for setting up a test environment for testing the beta notice dialog. */ +class BetaNoticeDialogFragmentTestActivity : TestActivity(), BetaNoticeClosedListener { + /** + * [BetaNoticeClosedListener] that must be initialized by the test, and is presumed to be a + * Mockito mock (though this is not, strictly speaking, required). + * + * This listener will be used as the callback for the dialog in response to UI operations. + */ + lateinit var mockCallbackListener: BetaNoticeClosedListener + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + BetaNoticeDialogFragment.newInstance().showNow(supportFragmentManager, "beta_notice_dialog") + } + + override fun onBetaNoticeOkayButtonClicked(permanentlyDismiss: Boolean) { + mockCallbackListener.onBetaNoticeOkayButtonClicked(permanentlyDismiss) + } +} diff --git a/app/src/main/java/org/oppia/android/app/notice/testing/GeneralAvailabilityUpgradeNoticeDialogFragmentTestActivity.kt b/app/src/main/java/org/oppia/android/app/notice/testing/GeneralAvailabilityUpgradeNoticeDialogFragmentTestActivity.kt new file mode 100644 index 00000000000..b378a876e41 --- /dev/null +++ b/app/src/main/java/org/oppia/android/app/notice/testing/GeneralAvailabilityUpgradeNoticeDialogFragmentTestActivity.kt @@ -0,0 +1,28 @@ +package org.oppia.android.app.notice.testing + +import android.os.Bundle +import org.oppia.android.app.notice.GeneralAvailabilityUpgradeNoticeClosedListener +import org.oppia.android.app.notice.GeneralAvailabilityUpgradeNoticeDialogFragment +import org.oppia.android.app.testing.activity.TestActivity + +/** [TestActivity] for setting up a test environment for testing the GA upgrade notice dialog. */ +class GeneralAvailabilityUpgradeNoticeDialogFragmentTestActivity : + TestActivity(), GeneralAvailabilityUpgradeNoticeClosedListener { + /** + * [GeneralAvailabilityUpgradeNoticeClosedListener] that must be initialized by the test, and is + * presumed to be a Mockito mock (though this is not, strictly speaking, required). + * + * This listener will be used as the callback for the dialog in response to UI operations. + */ + lateinit var mockCallbackListener: GeneralAvailabilityUpgradeNoticeClosedListener + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + GeneralAvailabilityUpgradeNoticeDialogFragment.newInstance() + .showNow(supportFragmentManager, "ga_upgrade_notice_dialog") + } + + override fun onGaUpgradeNoticeOkayButtonClicked(permanentlyDismiss: Boolean) { + mockCallbackListener.onGaUpgradeNoticeOkayButtonClicked(permanentlyDismiss) + } +} diff --git a/app/src/main/java/org/oppia/android/app/splash/SplashActivity.kt b/app/src/main/java/org/oppia/android/app/splash/SplashActivity.kt index 83859936edf..e0840ebdf83 100644 --- a/app/src/main/java/org/oppia/android/app/splash/SplashActivity.kt +++ b/app/src/main/java/org/oppia/android/app/splash/SplashActivity.kt @@ -6,10 +6,12 @@ import androidx.fragment.app.Fragment import org.oppia.android.app.activity.ActivityComponent import org.oppia.android.app.activity.ActivityComponentFactory import org.oppia.android.app.activity.ActivityComponentImpl -import org.oppia.android.app.deprecation.DeprecationNoticeExitAppListener import org.oppia.android.app.fragment.FragmentComponent import org.oppia.android.app.fragment.FragmentComponentBuilderInjector import org.oppia.android.app.fragment.FragmentComponentFactory +import org.oppia.android.app.notice.BetaNoticeClosedListener +import org.oppia.android.app.notice.DeprecationNoticeExitAppListener +import org.oppia.android.app.notice.GeneralAvailabilityUpgradeNoticeClosedListener import javax.inject.Inject /** @@ -21,7 +23,12 @@ import javax.inject.Inject * through their intents). */ class SplashActivity : - AppCompatActivity(), FragmentComponentFactory, DeprecationNoticeExitAppListener { + AppCompatActivity(), + FragmentComponentFactory, + DeprecationNoticeExitAppListener, + BetaNoticeClosedListener, + GeneralAvailabilityUpgradeNoticeClosedListener { + private lateinit var activityComponent: ActivityComponent @Inject @@ -35,10 +42,16 @@ class SplashActivity : splashActivityPresenter.handleOnCreate() } - override fun onCloseAppButtonClicked() = splashActivityPresenter.handleOnCloseAppButtonClicked() - override fun createFragmentComponent(fragment: Fragment): FragmentComponent { val builderInjector = activityComponent as FragmentComponentBuilderInjector return builderInjector.getFragmentComponentBuilderProvider().get().setFragment(fragment).build() } + + override fun onCloseAppButtonClicked() = splashActivityPresenter.handleOnCloseAppButtonClicked() + + override fun onBetaNoticeOkayButtonClicked(permanentlyDismiss: Boolean) = + splashActivityPresenter.handleOnBetaNoticeOkayButtonClicked(permanentlyDismiss) + + override fun onGaUpgradeNoticeOkayButtonClicked(permanentlyDismiss: Boolean) = + splashActivityPresenter.handleOnGaUpgradeNoticeOkayButtonClicked(permanentlyDismiss) } diff --git a/app/src/main/java/org/oppia/android/app/splash/SplashActivityPresenter.kt b/app/src/main/java/org/oppia/android/app/splash/SplashActivityPresenter.kt index fd02c1ea32b..5cbbc7df08c 100644 --- a/app/src/main/java/org/oppia/android/app/splash/SplashActivityPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/splash/SplashActivityPresenter.kt @@ -1,18 +1,23 @@ package org.oppia.android.app.splash -import android.view.WindowManager import androidx.appcompat.app.AppCompatActivity -import androidx.lifecycle.LiveData +import androidx.databinding.DataBindingUtil +import androidx.fragment.app.DialogFragment import androidx.lifecycle.Observer -import androidx.lifecycle.Transformations import org.oppia.android.R import org.oppia.android.app.activity.ActivityScope -import org.oppia.android.app.deprecation.AutomaticAppDeprecationNoticeDialogFragment import org.oppia.android.app.model.AppStartupState +import org.oppia.android.app.model.AppStartupState.BuildFlavorNoticeMode import org.oppia.android.app.model.AppStartupState.StartupMode +import org.oppia.android.app.model.BuildFlavor +import org.oppia.android.app.notice.AutomaticAppDeprecationNoticeDialogFragment +import org.oppia.android.app.notice.BetaNoticeDialogFragment +import org.oppia.android.app.notice.GeneralAvailabilityUpgradeNoticeDialogFragment import org.oppia.android.app.onboarding.OnboardingActivity import org.oppia.android.app.profile.ProfileChooserActivity import org.oppia.android.app.translation.AppLanguageLocaleHandler +import org.oppia.android.app.utility.LifecycleSafeTimerFactory +import org.oppia.android.databinding.SplashActivityBinding import org.oppia.android.domain.locale.LocaleController import org.oppia.android.domain.onboarding.AppStartupStateController import org.oppia.android.domain.oppialogger.OppiaLogger @@ -26,6 +31,8 @@ import org.oppia.android.util.locale.OppiaLocale import javax.inject.Inject private const val AUTO_DEPRECATION_NOTICE_DIALOG_FRAGMENT_TAG = "auto_deprecation_notice_dialog" +private const val BETA_NOTICE_DIALOG_FRAGMENT_TAG = "beta_notice_dialog" +private const val GA_UPDATE_NOTICE_DIALOG_FRAGMENT_TAG = "general_availability_update_notice_dialog" private const val SPLASH_INIT_STATE_DATA_PROVIDER_ID = "splash_init_state_data_provider" /** The presenter for [SplashActivity]. */ @@ -37,15 +44,21 @@ class SplashActivityPresenter @Inject constructor( private val primeTopicAssetsController: PrimeTopicAssetsController, private val translationController: TranslationController, private val localeController: LocaleController, - private val appLanguageLocaleHandler: AppLanguageLocaleHandler + private val appLanguageLocaleHandler: AppLanguageLocaleHandler, + private val lifecycleSafeTimerFactory: LifecycleSafeTimerFactory, + private val currentBuildFlavor: BuildFlavor ) { + lateinit var startupMode: StartupMode fun handleOnCreate() { - activity.setContentView(R.layout.splash_activity) - activity.window.setFlags( - WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN - ) + DataBindingUtil.setContentView( + activity, R.layout.splash_activity + ).apply { + isOnDeveloperFlavor = currentBuildFlavor == BuildFlavor.DEVELOPER + isOnAlphaFlavor = currentBuildFlavor == BuildFlavor.ALPHA + isOnBetaFlavor = currentBuildFlavor == BuildFlavor.BETA + } + // Initiate download support before any additional processing begins. primeTopicAssetsController.downloadAssets(R.style.OppiaAlertDialogTheme) subscribeToOnboardingFlow() @@ -57,44 +70,49 @@ class SplashActivityPresenter @Inject constructor( activity.finish() } + /** Handles cases when the user dismisses the beta notice dialog. */ + fun handleOnBetaNoticeOkayButtonClicked(permanentlyDismiss: Boolean) { + if (permanentlyDismiss) { + appStartupStateController.dismissBetaNoticesPermanently() + } + processStartupMode() + } + + /** Handles cases when the user dismisses the general availability update notice dialog. */ + fun handleOnGaUpgradeNoticeOkayButtonClicked(permanentlyDismiss: Boolean) { + if (permanentlyDismiss) { + appStartupStateController.dismissGaUpgradeNoticesPermanently() + } + processStartupMode() + } + private fun subscribeToOnboardingFlow() { - val liveData = computeInitStateLiveData() + val liveData = computeInitStateDataProvider().toLiveData() liveData.observe( activity, - object : Observer { - override fun onChanged(initState: SplashInitState) { - // It's possible for the observer to still be active & change due to the next activity - // causing a notification to be posted. That's always invalid to process here: the splash - // activity should never do anything after its initial state since it always finishes (or - // in the case of the deprecation dialog, blocks) the activity. - liveData.removeObserver(this) - - // First, initialize the app's initial locale. Note that since the activity can be - // reopened, it's possible for this to be initialized more than once. - if (!appLanguageLocaleHandler.isInitialized()) { - appLanguageLocaleHandler.initializeLocale(initState.displayLocale) - } - - // Second, route the user to the correct destination. - when (initState.startupMode) { - StartupMode.USER_IS_ONBOARDED -> { - activity.startActivity(ProfileChooserActivity.createProfileChooserActivity(activity)) - activity.finish() - } - StartupMode.APP_IS_DEPRECATED -> { - if (getDeprecationNoticeDialogFragment() == null) { - activity.supportFragmentManager.beginTransaction() - .add( - AutomaticAppDeprecationNoticeDialogFragment.newInstance(), - AUTO_DEPRECATION_NOTICE_DIALOG_FRAGMENT_TAG - ).commitNow() + object : Observer> { + override fun onChanged(initStateResult: AsyncResult) { + when (initStateResult) { + is AsyncResult.Pending -> { + // Ensure that pending states last no longer than 5 seconds. In cases where the app + // enters a bad state, this ensures that the user doesn't become stuck on the splash + // screen. + lifecycleSafeTimerFactory.createTimer(timeoutMillis = 5000).observe(activity) { + processInitState(SplashInitState.computeDefault(localeController)) } } - else -> { - // In all other cases (including errors when the startup state fails to load or is - // defaulted), assume the user needs to be onboarded. - activity.startActivity(OnboardingActivity.createOnboardingActivity(activity)) - activity.finish() + is AsyncResult.Failure -> { + oppiaLogger.e( + "SplashActivity", "Failed to compute initial state", initStateResult.error + ) + } + is AsyncResult.Success -> { + // It's possible for the observer to still be active & change due to the next activity + // causing a notification to be posted. That's always invalid to process here: the + // splash activity should never do anything after its initial state since it always + // finishes (or in the case of the deprecation dialog, blocks) the activity. + liveData.removeObserver(this) + processInitState(initStateResult.value) } } } @@ -102,47 +120,93 @@ class SplashActivityPresenter @Inject constructor( ) } + private fun processInitState(initState: SplashInitState) { + // First, initialize the app's initial locale. Note that since the activity can be + // reopened, it's possible for this to be initialized more than once. + if (!appLanguageLocaleHandler.isInitialized()) { + appLanguageLocaleHandler.initializeLocale(initState.displayLocale) + } + + // Second, prepare to route the user to the correct destination. + startupMode = initState.appStartupState.startupMode + + // Third, show any dismissible notices (if the app isn't deprecated). + if (startupMode != StartupMode.APP_IS_DEPRECATED) { + when (initState.appStartupState.buildFlavorNoticeMode) { + BuildFlavorNoticeMode.FLAVOR_NOTICE_MODE_UNSPECIFIED, BuildFlavorNoticeMode.NO_NOTICE, + BuildFlavorNoticeMode.UNRECOGNIZED, null -> { + // No notice should be shown. However, when a pre-release version of the app is active + // that changes the splash screen have it wait a bit longer so that the build flavor can + // be clearly seen. The developer build isn't part of the wait to ensure fast startup + // times (for development purposes). + when (currentBuildFlavor) { + BuildFlavor.BUILD_FLAVOR_UNSPECIFIED, BuildFlavor.UNRECOGNIZED, + BuildFlavor.TESTING, BuildFlavor.DEVELOPER, BuildFlavor.GENERAL_AVAILABILITY -> + processStartupMode() + BuildFlavor.ALPHA, BuildFlavor.BETA -> { + lifecycleSafeTimerFactory.createTimer(timeoutMillis = 2000).observe(activity) { + processStartupMode() + } + } + } + } + BuildFlavorNoticeMode.SHOW_BETA_NOTICE -> + showDialog(BETA_NOTICE_DIALOG_FRAGMENT_TAG, BetaNoticeDialogFragment::newInstance) + BuildFlavorNoticeMode.SHOW_UPGRADE_TO_GENERAL_AVAILABILITY_NOTICE -> { + showDialog( + GA_UPDATE_NOTICE_DIALOG_FRAGMENT_TAG, + GeneralAvailabilityUpgradeNoticeDialogFragment::newInstance + ) + } + } + } else processStartupMode() + } + + private fun processStartupMode() { + when (startupMode) { + StartupMode.USER_IS_ONBOARDED -> { + activity.startActivity(ProfileChooserActivity.createProfileChooserActivity(activity)) + activity.finish() + } + StartupMode.APP_IS_DEPRECATED -> { + showDialog( + AUTO_DEPRECATION_NOTICE_DIALOG_FRAGMENT_TAG, + AutomaticAppDeprecationNoticeDialogFragment::newInstance + ) + } + else -> { + // In all other cases (including errors when the startup state fails to load or is + // defaulted), assume the user needs to be onboarded. + activity.startActivity(OnboardingActivity.createOnboardingActivity(activity)) + activity.finish() + } + } + } + private fun computeInitStateDataProvider(): DataProvider { val startupStateDataProvider = appStartupStateController.getAppStartupState() val systemAppLanguageLocaleDataProvider = translationController.getSystemLanguageLocale() return startupStateDataProvider.combineWith( systemAppLanguageLocaleDataProvider, SPLASH_INIT_STATE_DATA_PROVIDER_ID ) { startupState, systemAppLanguageLocale -> - SplashInitState(startupState.startupMode, systemAppLanguageLocale) + SplashInitState(startupState, systemAppLanguageLocale) } } - private fun computeInitStateLiveData(): LiveData = - Transformations.map(computeInitStateDataProvider().toLiveData(), ::processInitState) - - private fun processInitState( - initStateResult: AsyncResult - ): SplashInitState { - // If there's an error loading the data, assume the default. - return when (initStateResult) { - is AsyncResult.Failure -> { - oppiaLogger.e("SplashActivity", "Failed to compute initial state", initStateResult.error) - SplashInitState.computeDefault(localeController) - } - is AsyncResult.Pending -> SplashInitState.computeDefault(localeController) - is AsyncResult.Success -> initStateResult.value + private inline fun showDialog(tag: String, createFragment: () -> T) { + if (activity.supportFragmentManager.findFragmentByTag(tag) as? T == null) { + activity.supportFragmentManager.beginTransaction().add(createFragment(), tag).commitNow() } } - private fun getDeprecationNoticeDialogFragment(): AutomaticAppDeprecationNoticeDialogFragment? { - return activity.supportFragmentManager.findFragmentByTag( - AUTO_DEPRECATION_NOTICE_DIALOG_FRAGMENT_TAG - ) as? AutomaticAppDeprecationNoticeDialogFragment - } - private data class SplashInitState( - val startupMode: StartupMode, + val appStartupState: AppStartupState, val displayLocale: OppiaLocale.DisplayLocale ) { companion object { fun computeDefault(localeController: LocaleController): SplashInitState { return SplashInitState( - startupMode = AppStartupState.getDefaultInstance().startupMode, + appStartupState = AppStartupState.getDefaultInstance(), displayLocale = localeController.reconstituteDisplayLocale( localeController.getLikelyDefaultAppStringLocaleContext() ) diff --git a/app/src/main/res/drawable/full_oppia_logo.xml b/app/src/main/res/drawable/full_oppia_logo.xml new file mode 100644 index 00000000000..f99bc2df503 --- /dev/null +++ b/app/src/main/res/drawable/full_oppia_logo.xml @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/app/src/main/res/layout/beta_notice_dialog_content.xml b/app/src/main/res/layout/beta_notice_dialog_content.xml new file mode 100755 index 00000000000..b456257a2b6 --- /dev/null +++ b/app/src/main/res/layout/beta_notice_dialog_content.xml @@ -0,0 +1,33 @@ + + + + + diff --git a/app/src/main/res/layout/general_availability_upgrade_notice_dialog_content.xml b/app/src/main/res/layout/general_availability_upgrade_notice_dialog_content.xml new file mode 100755 index 00000000000..f7ecc971fd7 --- /dev/null +++ b/app/src/main/res/layout/general_availability_upgrade_notice_dialog_content.xml @@ -0,0 +1,33 @@ + + + + + diff --git a/app/src/main/res/layout/splash_activity.xml b/app/src/main/res/layout/splash_activity.xml index c6a3978120e..f8d6bb317d5 100644 --- a/app/src/main/res/layout/splash_activity.xml +++ b/app/src/main/res/layout/splash_activity.xml @@ -1,7 +1,54 @@ - + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/untranslated_strings.xml b/app/src/main/res/values/untranslated_strings.xml index a0482e4ba72..ca0ce552a2c 100644 --- a/app/src/main/res/values/untranslated_strings.xml +++ b/app/src/main/res/values/untranslated_strings.xml @@ -79,4 +79,6 @@ Please connect to a WiFi or Cellular network in order to upload profile data. %s installation ID %s\'s learner ID + + Test-only activity diff --git a/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/AdministratorControlsActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/AdministratorControlsActivityTest.kt index 5918ad0614f..80fbbbbf0ec 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/AdministratorControlsActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/AdministratorControlsActivityTest.kt @@ -56,6 +56,7 @@ 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 @@ -835,7 +836,7 @@ class AdministratorControlsActivityTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/AdministratorControlsFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/AdministratorControlsFragmentTest.kt index ae58e53a313..95ee91fd9ef 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/AdministratorControlsFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/AdministratorControlsFragmentTest.kt @@ -47,6 +47,7 @@ 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 @@ -613,7 +614,7 @@ class AdministratorControlsFragmentTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/AppVersionActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/AppVersionActivityTest.kt index 4d3875e7a30..ea6982c549f 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/AppVersionActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/AppVersionActivityTest.kt @@ -37,6 +37,7 @@ 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 @@ -291,7 +292,7 @@ class AppVersionActivityTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/learneranalytics/BUILD.bazel b/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/learneranalytics/BUILD.bazel index 32688f31e7b..b64b7f60e82 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/learneranalytics/BUILD.bazel +++ b/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/learneranalytics/BUILD.bazel @@ -14,6 +14,7 @@ app_test( "//app/src/main/java/org/oppia/android/app/application:application_injector", "//app/src/main/java/org/oppia/android/app/application:application_injector_provider", "//app/src/main/java/org/oppia/android/app/application:common_application_modules", + "//app/src/main/java/org/oppia/android/app/application/testing:testing_build_flavor_module", "//app/src/main/java/org/oppia/android/app/translation/testing:test_module", "//testing", "//testing/src/main/java/org/oppia/android/testing/junit:initialize_default_locale_rule", @@ -45,6 +46,7 @@ app_test( "//app/src/main/java/org/oppia/android/app/application:application_injector", "//app/src/main/java/org/oppia/android/app/application:application_injector_provider", "//app/src/main/java/org/oppia/android/app/application:common_application_modules", + "//app/src/main/java/org/oppia/android/app/application/testing:testing_build_flavor_module", "//app/src/main/java/org/oppia/android/app/translation/testing:test_module", "//testing", "//testing/src/main/java/org/oppia/android/testing/junit:initialize_default_locale_rule", diff --git a/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdActivityTest.kt index e0b44a0a5fa..37fbe33d836 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdActivityTest.kt @@ -28,6 +28,7 @@ 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 @@ -199,7 +200,8 @@ class ProfileAndDeviceIdActivityTest { AssetModule::class, LocaleProdModule::class, ActivityRecreatorTestModule::class, SyncStatusModule::class, SplitScreenInteractionModule::class, NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, - MathEquationInputModule::class, MetricLogSchedulerModule::class + MathEquationInputModule::class, MetricLogSchedulerModule::class, + TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragmentTest.kt index 1c99c20e542..4f5b16a23e1 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdFragmentTest.kt @@ -42,6 +42,7 @@ 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 @@ -625,7 +626,8 @@ class ProfileAndDeviceIdFragmentTest { AssetModule::class, LocaleProdModule::class, ActivityRecreatorTestModule::class, SyncStatusModule::class, SplitScreenInteractionModule::class, NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, - MathEquationInputModule::class, MetricLogSchedulerModule::class + MathEquationInputModule::class, MetricLogSchedulerModule::class, + TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/completedstorylist/CompletedStoryListActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/completedstorylist/CompletedStoryListActivityTest.kt index dc1c7fd5a82..f50c687c987 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/completedstorylist/CompletedStoryListActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/completedstorylist/CompletedStoryListActivityTest.kt @@ -37,6 +37,7 @@ 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.ProfileId @@ -514,7 +515,7 @@ class CompletedStoryListActivityTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/customview/LessonThumbnailImageViewTest.kt b/app/src/sharedTest/java/org/oppia/android/app/customview/LessonThumbnailImageViewTest.kt index e9fd803c1e3..c4082c8733a 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/customview/LessonThumbnailImageViewTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/customview/LessonThumbnailImageViewTest.kt @@ -21,6 +21,7 @@ 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.LessonThumbnail @@ -172,7 +173,7 @@ class LessonThumbnailImageViewTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/customview/interaction/BUILD.bazel b/app/src/sharedTest/java/org/oppia/android/app/customview/interaction/BUILD.bazel index d7571e7f1ac..37a21163500 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/customview/interaction/BUILD.bazel +++ b/app/src/sharedTest/java/org/oppia/android/app/customview/interaction/BUILD.bazel @@ -17,6 +17,7 @@ app_test( "//app/src/main/java/org/oppia/android/app/application:application_injector", "//app/src/main/java/org/oppia/android/app/application:application_injector_provider", "//app/src/main/java/org/oppia/android/app/application:common_application_modules", + "//app/src/main/java/org/oppia/android/app/application/testing:testing_build_flavor_module", "//app/src/main/java/org/oppia/android/app/translation/testing:test_module", "//testing", "//testing/src/main/java/org/oppia/android/testing/espresso:edit_text_input_action", diff --git a/app/src/sharedTest/java/org/oppia/android/app/customview/interaction/MathExpressionInteractionsViewTest.kt b/app/src/sharedTest/java/org/oppia/android/app/customview/interaction/MathExpressionInteractionsViewTest.kt index 100c5a437f8..f5a54d6e7d1 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/customview/interaction/MathExpressionInteractionsViewTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/customview/interaction/MathExpressionInteractionsViewTest.kt @@ -26,6 +26,7 @@ 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.CustomSchemaValue @@ -1774,7 +1775,7 @@ class MathExpressionInteractionsViewTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/databinding/BUILD.bazel b/app/src/sharedTest/java/org/oppia/android/app/databinding/BUILD.bazel index e6c820172aa..019f1ff1983 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/databinding/BUILD.bazel +++ b/app/src/sharedTest/java/org/oppia/android/app/databinding/BUILD.bazel @@ -77,6 +77,7 @@ oppia_android_test( "//app/src/main/java/org/oppia/android/app/application:application_injector", "//app/src/main/java/org/oppia/android/app/application:application_injector_provider", "//app/src/main/java/org/oppia/android/app/application:common_application_modules", + "//app/src/main/java/org/oppia/android/app/application/testing:testing_build_flavor_module", "//app/src/main/java/org/oppia/android/app/translation/testing:test_module", "//domain", "//testing", @@ -113,6 +114,7 @@ oppia_android_test( "//app/src/main/java/org/oppia/android/app/application:application_injector", "//app/src/main/java/org/oppia/android/app/application:application_injector_provider", "//app/src/main/java/org/oppia/android/app/application:common_application_modules", + "//app/src/main/java/org/oppia/android/app/application/testing:testing_build_flavor_module", "//app/src/main/java/org/oppia/android/app/translation/testing:test_module", "//domain", "//testing", @@ -149,6 +151,7 @@ oppia_android_test( "//app/src/main/java/org/oppia/android/app/application:application_injector", "//app/src/main/java/org/oppia/android/app/application:application_injector_provider", "//app/src/main/java/org/oppia/android/app/application:common_application_modules", + "//app/src/main/java/org/oppia/android/app/application/testing:testing_build_flavor_module", "//app/src/main/java/org/oppia/android/app/translation/testing:test_module", "//domain", "//testing", @@ -185,6 +188,7 @@ oppia_android_test( "//app/src/main/java/org/oppia/android/app/application:application_injector", "//app/src/main/java/org/oppia/android/app/application:application_injector_provider", "//app/src/main/java/org/oppia/android/app/application:common_application_modules", + "//app/src/main/java/org/oppia/android/app/application/testing:testing_build_flavor_module", "//app/src/main/java/org/oppia/android/app/translation/testing:test_module", "//domain", "//testing", @@ -221,6 +225,7 @@ oppia_android_test( "//app/src/main/java/org/oppia/android/app/application:application_injector", "//app/src/main/java/org/oppia/android/app/application:application_injector_provider", "//app/src/main/java/org/oppia/android/app/application:common_application_modules", + "//app/src/main/java/org/oppia/android/app/application/testing:testing_build_flavor_module", "//app/src/main/java/org/oppia/android/app/translation/testing:test_module", "//domain", "//testing", @@ -257,6 +262,7 @@ oppia_android_test( "//app/src/main/java/org/oppia/android/app/application:application_injector", "//app/src/main/java/org/oppia/android/app/application:application_injector_provider", "//app/src/main/java/org/oppia/android/app/application:common_application_modules", + "//app/src/main/java/org/oppia/android/app/application/testing:testing_build_flavor_module", "//app/src/main/java/org/oppia/android/app/translation/testing:test_module", "//domain", "//testing", @@ -293,6 +299,7 @@ oppia_android_test( "//app/src/main/java/org/oppia/android/app/application:application_injector", "//app/src/main/java/org/oppia/android/app/application:application_injector_provider", "//app/src/main/java/org/oppia/android/app/application:common_application_modules", + "//app/src/main/java/org/oppia/android/app/application/testing:testing_build_flavor_module", "//app/src/main/java/org/oppia/android/app/translation/testing:test_module", "//domain", "//testing", diff --git a/app/src/sharedTest/java/org/oppia/android/app/databinding/DrawableBindingAdaptersTest.kt b/app/src/sharedTest/java/org/oppia/android/app/databinding/DrawableBindingAdaptersTest.kt index 1bee02bb647..da2853a4422 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/databinding/DrawableBindingAdaptersTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/databinding/DrawableBindingAdaptersTest.kt @@ -26,6 +26,7 @@ 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.databinding.DrawableBindingAdapters.setBackgroundColor import org.oppia.android.app.databinding.DrawableBindingAdapters.setBackgroundDrawable import org.oppia.android.app.databinding.DrawableBindingAdapters.setTopBackgroundDrawable @@ -183,7 +184,7 @@ class DrawableBindingAdaptersTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/databinding/ImageViewBindingAdaptersTest.kt b/app/src/sharedTest/java/org/oppia/android/app/databinding/ImageViewBindingAdaptersTest.kt index 6559b4e211b..fe003c940f8 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/databinding/ImageViewBindingAdaptersTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/databinding/ImageViewBindingAdaptersTest.kt @@ -30,6 +30,7 @@ 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.databinding.ImageViewBindingAdapters.setPlayStateDrawable import org.oppia.android.app.devoptions.DeveloperOptionsModule import org.oppia.android.app.devoptions.DeveloperOptionsStarterModule @@ -225,7 +226,7 @@ class ImageViewBindingAdaptersTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, SyncStatusModule::class, - MetricLogSchedulerModule::class + MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) /** Create a TestApplicationComponent. */ diff --git a/app/src/sharedTest/java/org/oppia/android/app/databinding/MarginBindingAdaptersTest.kt b/app/src/sharedTest/java/org/oppia/android/app/databinding/MarginBindingAdaptersTest.kt index 51af7e5e519..212323d5fdb 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/databinding/MarginBindingAdaptersTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/databinding/MarginBindingAdaptersTest.kt @@ -32,6 +32,7 @@ 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.databinding.MarginBindingAdapters.setLayoutMarginEnd import org.oppia.android.app.databinding.MarginBindingAdapters.setLayoutMarginStart import org.oppia.android.app.devoptions.DeveloperOptionsModule @@ -313,7 +314,7 @@ class MarginBindingAdaptersTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, SyncStatusModule::class, - MetricLogSchedulerModule::class + MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) /** Create a TestApplicationComponent. */ diff --git a/app/src/sharedTest/java/org/oppia/android/app/databinding/StateAssemblerMarginBindingAdaptersTest.kt b/app/src/sharedTest/java/org/oppia/android/app/databinding/StateAssemblerMarginBindingAdaptersTest.kt index 8f24ad52f76..e9603c8d4c0 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/databinding/StateAssemblerMarginBindingAdaptersTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/databinding/StateAssemblerMarginBindingAdaptersTest.kt @@ -32,6 +32,7 @@ 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.databinding.StateAssemblerMarginBindingAdapters.setExplorationSplitViewMargin import org.oppia.android.app.databinding.StateAssemblerMarginBindingAdapters.setExplorationViewMargin import org.oppia.android.app.databinding.StateAssemblerMarginBindingAdapters.setQuestionSplitViewMargin @@ -501,7 +502,7 @@ class StateAssemblerMarginBindingAdaptersTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, SyncStatusModule::class, - MetricLogSchedulerModule::class + MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) /** Create a TestApplicationComponent. */ diff --git a/app/src/sharedTest/java/org/oppia/android/app/databinding/StateAssemblerPaddingBindingAdaptersTest.kt b/app/src/sharedTest/java/org/oppia/android/app/databinding/StateAssemblerPaddingBindingAdaptersTest.kt index d4c2448a4db..ce12765a684 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/databinding/StateAssemblerPaddingBindingAdaptersTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/databinding/StateAssemblerPaddingBindingAdaptersTest.kt @@ -30,6 +30,7 @@ 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.databinding.StateAssemblerPaddingBindingAdapters.setExplorationSplitViewPadding import org.oppia.android.app.databinding.StateAssemblerPaddingBindingAdapters.setExplorationViewPadding import org.oppia.android.app.databinding.StateAssemblerPaddingBindingAdapters.setQuestionSplitViewPadding @@ -499,7 +500,7 @@ class StateAssemblerPaddingBindingAdaptersTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, SyncStatusModule::class, - MetricLogSchedulerModule::class + MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) diff --git a/app/src/sharedTest/java/org/oppia/android/app/databinding/TextViewBindingAdaptersTest.kt b/app/src/sharedTest/java/org/oppia/android/app/databinding/TextViewBindingAdaptersTest.kt index ebd73e65eda..aff64f8daa1 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/databinding/TextViewBindingAdaptersTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/databinding/TextViewBindingAdaptersTest.kt @@ -24,6 +24,7 @@ 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.databinding.TextViewBindingAdapters.setDrawableEndCompat import org.oppia.android.app.databinding.TextViewBindingAdapters.setProfileDataText import org.oppia.android.app.databinding.TextViewBindingAdapters.setProfileLastVisitedText @@ -332,7 +333,7 @@ class TextViewBindingAdaptersTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/databinding/ViewBindingAdaptersTest.kt b/app/src/sharedTest/java/org/oppia/android/app/databinding/ViewBindingAdaptersTest.kt index 5127d9aec73..2275048ed95 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/databinding/ViewBindingAdaptersTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/databinding/ViewBindingAdaptersTest.kt @@ -30,6 +30,7 @@ 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.databinding.ViewBindingAdapters.setRotationAnimation import org.oppia.android.app.devoptions.DeveloperOptionsModule import org.oppia.android.app.devoptions.DeveloperOptionsStarterModule @@ -233,7 +234,7 @@ class ViewBindingAdaptersTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, SyncStatusModule::class, - MetricLogSchedulerModule::class + MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) /** Create a TestApplicationComponent. */ diff --git a/app/src/sharedTest/java/org/oppia/android/app/devoptions/DeveloperOptionsActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/devoptions/DeveloperOptionsActivityTest.kt index f5be1badb63..78e2f796262 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/devoptions/DeveloperOptionsActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/devoptions/DeveloperOptionsActivityTest.kt @@ -49,6 +49,7 @@ 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.mathexpressionparser.MathExpressionParserActivity import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule import org.oppia.android.app.recyclerview.RecyclerViewMatcher.Companion.atPositionOnView @@ -325,7 +326,7 @@ class DeveloperOptionsActivityTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/devoptions/DeveloperOptionsFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/devoptions/DeveloperOptionsFragmentTest.kt index 98130794bda..b1e1c4215b4 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/devoptions/DeveloperOptionsFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/devoptions/DeveloperOptionsFragmentTest.kt @@ -38,6 +38,7 @@ 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.forcenetworktype.ForceNetworkTypeActivity import org.oppia.android.app.devoptions.markchapterscompleted.MarkChaptersCompletedActivity import org.oppia.android.app.devoptions.markstoriescompleted.MarkStoriesCompletedActivity @@ -656,7 +657,7 @@ class DeveloperOptionsFragmentTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/devoptions/MarkChaptersCompletedActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/devoptions/MarkChaptersCompletedActivityTest.kt index 1f3326161e7..932a971c13a 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/devoptions/MarkChaptersCompletedActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/devoptions/MarkChaptersCompletedActivityTest.kt @@ -27,6 +27,7 @@ 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.markchapterscompleted.MarkChaptersCompletedActivity import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule import org.oppia.android.app.shim.ViewBindingShimModule @@ -187,7 +188,7 @@ class MarkChaptersCompletedActivityTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/devoptions/MarkChaptersCompletedFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/devoptions/MarkChaptersCompletedFragmentTest.kt index 62272000673..b2ba91623d0 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/devoptions/MarkChaptersCompletedFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/devoptions/MarkChaptersCompletedFragmentTest.kt @@ -34,6 +34,7 @@ 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.markchapterscompleted.testing.MarkChaptersCompletedTestActivity import org.oppia.android.app.model.ProfileId import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule @@ -915,7 +916,7 @@ class MarkChaptersCompletedFragmentTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/devoptions/MarkStoriesCompletedActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/devoptions/MarkStoriesCompletedActivityTest.kt index b4b998b9f7c..5978a29ce69 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/devoptions/MarkStoriesCompletedActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/devoptions/MarkStoriesCompletedActivityTest.kt @@ -27,6 +27,7 @@ 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.markstoriescompleted.MarkStoriesCompletedActivity import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule import org.oppia.android.app.shim.ViewBindingShimModule @@ -187,7 +188,7 @@ class MarkStoriesCompletedActivityTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/devoptions/MarkStoriesCompletedFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/devoptions/MarkStoriesCompletedFragmentTest.kt index 9613d6d6729..e2ab546bb6b 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/devoptions/MarkStoriesCompletedFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/devoptions/MarkStoriesCompletedFragmentTest.kt @@ -34,6 +34,7 @@ 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.markstoriescompleted.testing.MarkStoriesCompletedTestActivity import org.oppia.android.app.model.ProfileId import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule @@ -592,7 +593,7 @@ class MarkStoriesCompletedFragmentTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/devoptions/MarkTopicsCompletedActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/devoptions/MarkTopicsCompletedActivityTest.kt index 5107bd10697..13a792f1c21 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/devoptions/MarkTopicsCompletedActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/devoptions/MarkTopicsCompletedActivityTest.kt @@ -27,6 +27,7 @@ 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.marktopicscompleted.MarkTopicsCompletedActivity import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule import org.oppia.android.app.shim.ViewBindingShimModule @@ -187,7 +188,7 @@ class MarkTopicsCompletedActivityTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/devoptions/MarkTopicsCompletedFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/devoptions/MarkTopicsCompletedFragmentTest.kt index 7ddd3a668ac..a13f699941d 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/devoptions/MarkTopicsCompletedFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/devoptions/MarkTopicsCompletedFragmentTest.kt @@ -34,6 +34,7 @@ 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.marktopicscompleted.testing.MarkTopicsCompletedTestActivity import org.oppia.android.app.model.ProfileId import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule @@ -562,7 +563,7 @@ class MarkTopicsCompletedFragmentTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/devoptions/ViewEventLogsActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/devoptions/ViewEventLogsActivityTest.kt index 2104922adec..d76f14aabf0 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/devoptions/ViewEventLogsActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/devoptions/ViewEventLogsActivityTest.kt @@ -28,6 +28,7 @@ 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.vieweventlogs.ViewEventLogsActivity import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule import org.oppia.android.app.shim.ViewBindingShimModule @@ -178,7 +179,8 @@ class ViewEventLogsActivityTest { MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, SyncStatusModule::class, MetricLogSchedulerModule::class, - PerformanceMetricsAssessorModule::class, PerformanceMetricsConfigurationsModule::class + PerformanceMetricsAssessorModule::class, PerformanceMetricsConfigurationsModule::class, + TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/devoptions/ViewEventLogsFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/devoptions/ViewEventLogsFragmentTest.kt index e10ba410943..907042b27ac 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/devoptions/ViewEventLogsFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/devoptions/ViewEventLogsFragmentTest.kt @@ -30,6 +30,7 @@ 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.vieweventlogs.testing.ViewEventLogsTestActivity import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule import org.oppia.android.app.recyclerview.RecyclerViewMatcher.Companion.atPositionOnView @@ -578,7 +579,7 @@ class ViewEventLogsFragmentTest { MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, SyncStatusModule::class, MetricLogSchedulerModule::class, PerformanceMetricsAssessorModule::class, - PerformanceMetricsConfigurationsModule::class + PerformanceMetricsConfigurationsModule::class, TestingBuildFlavorModule::class ] ) diff --git a/app/src/sharedTest/java/org/oppia/android/app/devoptions/forcenetworktype/ForceNetworkTypeActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/devoptions/forcenetworktype/ForceNetworkTypeActivityTest.kt index 1647b6b5dee..1acc5d39820 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/devoptions/forcenetworktype/ForceNetworkTypeActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/devoptions/forcenetworktype/ForceNetworkTypeActivityTest.kt @@ -27,6 +27,7 @@ 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 @@ -179,7 +180,7 @@ class ForceNetworkTypeActivityTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, SyncStatusModule::class, - MetricLogSchedulerModule::class + MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) /** [ApplicationComponent] for [ForceNetworkTypeActivityTest]. */ diff --git a/app/src/sharedTest/java/org/oppia/android/app/devoptions/forcenetworktype/ForceNetworkTypeFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/devoptions/forcenetworktype/ForceNetworkTypeFragmentTest.kt index d6c30067abe..0b63f3edacb 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/devoptions/forcenetworktype/ForceNetworkTypeFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/devoptions/forcenetworktype/ForceNetworkTypeFragmentTest.kt @@ -30,6 +30,7 @@ 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.devoptions.forcenetworktype.testing.ForceNetworkTypeTestActivity @@ -395,7 +396,7 @@ class ForceNetworkTypeFragmentTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, SyncStatusModule::class, - MetricLogSchedulerModule::class + MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) /** [ApplicationComponent] for [ForceNetworkTypeFragmentTest]. */ diff --git a/app/src/sharedTest/java/org/oppia/android/app/devoptions/mathexpressionparser/BUILD.bazel b/app/src/sharedTest/java/org/oppia/android/app/devoptions/mathexpressionparser/BUILD.bazel index b0d8810bf89..e20913f64aa 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/devoptions/mathexpressionparser/BUILD.bazel +++ b/app/src/sharedTest/java/org/oppia/android/app/devoptions/mathexpressionparser/BUILD.bazel @@ -17,6 +17,7 @@ app_test( "//app/src/main/java/org/oppia/android/app/application:application_injector", "//app/src/main/java/org/oppia/android/app/application:application_injector_provider", "//app/src/main/java/org/oppia/android/app/application:common_application_modules", + "//app/src/main/java/org/oppia/android/app/application/testing:testing_build_flavor_module", "//app/src/main/java/org/oppia/android/app/translation/testing:test_module", "//testing", "//testing/src/main/java/org/oppia/android/testing/espresso:edit_text_input_action", @@ -47,6 +48,7 @@ app_test( "//app/src/main/java/org/oppia/android/app/application:application_injector", "//app/src/main/java/org/oppia/android/app/application:application_injector_provider", "//app/src/main/java/org/oppia/android/app/application:common_application_modules", + "//app/src/main/java/org/oppia/android/app/application/testing:testing_build_flavor_module", "//app/src/main/java/org/oppia/android/app/testing/activity:test_activity", "//app/src/main/java/org/oppia/android/app/translation/testing:test_module", "//testing", diff --git a/app/src/sharedTest/java/org/oppia/android/app/devoptions/mathexpressionparser/MathExpressionParserActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/devoptions/mathexpressionparser/MathExpressionParserActivityTest.kt index e9a86da55ae..6a207e03bd8 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/devoptions/mathexpressionparser/MathExpressionParserActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/devoptions/mathexpressionparser/MathExpressionParserActivityTest.kt @@ -25,6 +25,7 @@ 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 @@ -163,7 +164,7 @@ class MathExpressionParserActivityTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/devoptions/mathexpressionparser/MathExpressionParserFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/devoptions/mathexpressionparser/MathExpressionParserFragmentTest.kt index 6bf88c29bf3..4f3a4f62600 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/devoptions/mathexpressionparser/MathExpressionParserFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/devoptions/mathexpressionparser/MathExpressionParserFragmentTest.kt @@ -34,6 +34,7 @@ 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 @@ -1413,7 +1414,7 @@ class MathExpressionParserFragmentTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/faq/FAQListFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/faq/FAQListFragmentTest.kt index e6f86c7cbbf..33fd5eeddb9 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/faq/FAQListFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/faq/FAQListFragmentTest.kt @@ -34,6 +34,7 @@ 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.help.faq.FAQListActivity @@ -247,7 +248,7 @@ class FAQListFragmentTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/faq/FAQSingleActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/faq/FAQSingleActivityTest.kt index 79c2ab467fb..1c354ef9c98 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/faq/FAQSingleActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/faq/FAQSingleActivityTest.kt @@ -32,6 +32,7 @@ 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.help.faq.faqsingle.FAQSingleActivity @@ -225,7 +226,7 @@ class FAQSingleActivityTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/faq/FaqListActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/faq/FaqListActivityTest.kt index 66b90ab902a..a9238755789 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/faq/FaqListActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/faq/FaqListActivityTest.kt @@ -21,6 +21,7 @@ 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.help.faq.FAQListActivity @@ -152,7 +153,7 @@ class FaqListActivityTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/help/HelpActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/help/HelpActivityTest.kt index 659279b6035..69c195e66e7 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/help/HelpActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/help/HelpActivityTest.kt @@ -21,6 +21,7 @@ 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 @@ -154,7 +155,7 @@ class HelpActivityTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/help/HelpFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/help/HelpFragmentTest.kt index 085d91bda44..1c7cd066ce9 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/help/HelpFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/help/HelpFragmentTest.kt @@ -44,6 +44,7 @@ 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.help.faq.FAQListActivity @@ -1442,7 +1443,7 @@ class HelpFragmentTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/home/HomeActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/home/HomeActivityTest.kt index 99e47450a8a..b229732f377 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/home/HomeActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/home/HomeActivityTest.kt @@ -50,6 +50,7 @@ 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.home.recentlyplayed.RecentlyPlayedActivity @@ -1811,7 +1812,7 @@ class HomeActivityTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/home/RecentlyPlayedFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/home/RecentlyPlayedFragmentTest.kt index 672069768fd..f086e9920d2 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/home/RecentlyPlayedFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/home/RecentlyPlayedFragmentTest.kt @@ -46,6 +46,7 @@ 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.home.recentlyplayed.RecentlyPlayedActivity @@ -1506,7 +1507,7 @@ class RecentlyPlayedFragmentTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/home/TopicSummaryViewModelTest.kt b/app/src/sharedTest/java/org/oppia/android/app/home/TopicSummaryViewModelTest.kt index 7e6cedb6445..f910210e0ac 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/home/TopicSummaryViewModelTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/home/TopicSummaryViewModelTest.kt @@ -19,6 +19,7 @@ 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.home.topiclist.TopicSummaryViewModel @@ -373,7 +374,7 @@ class TopicSummaryViewModelTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/home/WelcomeViewModelTest.kt b/app/src/sharedTest/java/org/oppia/android/app/home/WelcomeViewModelTest.kt index 7505e4d774c..95c188b9600 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/home/WelcomeViewModelTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/home/WelcomeViewModelTest.kt @@ -20,6 +20,7 @@ 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 @@ -357,7 +358,7 @@ class WelcomeViewModelTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/home/promotedlist/PromotedStoryListViewModelTest.kt b/app/src/sharedTest/java/org/oppia/android/app/home/promotedlist/PromotedStoryListViewModelTest.kt index aee3ce36441..15988155b7a 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/home/promotedlist/PromotedStoryListViewModelTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/home/promotedlist/PromotedStoryListViewModelTest.kt @@ -19,6 +19,7 @@ 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.PromotedActivityList @@ -369,7 +370,7 @@ class PromotedStoryListViewModelTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/home/promotedlist/PromotedStoryViewModelTest.kt b/app/src/sharedTest/java/org/oppia/android/app/home/promotedlist/PromotedStoryViewModelTest.kt index 6208ea3fbec..0e4f82c366e 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/home/promotedlist/PromotedStoryViewModelTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/home/promotedlist/PromotedStoryViewModelTest.kt @@ -19,6 +19,7 @@ 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.PromotedStory @@ -379,7 +380,7 @@ class PromotedStoryViewModelTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/mydownloads/MyDownloadsActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/mydownloads/MyDownloadsActivityTest.kt index a9b9c815a49..d1b682cbb0b 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/mydownloads/MyDownloadsActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/mydownloads/MyDownloadsActivityTest.kt @@ -20,6 +20,7 @@ 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 @@ -142,7 +143,7 @@ class MyDownloadsActivityTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/mydownloads/MyDownloadsFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/mydownloads/MyDownloadsFragmentTest.kt index a1aa44be743..330171bab0c 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/mydownloads/MyDownloadsFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/mydownloads/MyDownloadsFragmentTest.kt @@ -30,6 +30,7 @@ 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 @@ -233,7 +234,7 @@ class MyDownloadsFragmentTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/notice/BUILD.bazel b/app/src/sharedTest/java/org/oppia/android/app/notice/BUILD.bazel new file mode 100644 index 00000000000..a58c530be9f --- /dev/null +++ b/app/src/sharedTest/java/org/oppia/android/app/notice/BUILD.bazel @@ -0,0 +1,81 @@ +""" +Tests for notices shown in the app. +""" + +load("@dagger//:workspace_defs.bzl", "dagger_rules") +load("//app:app_test.bzl", "app_test") +load("//app:test_with_resources.bzl", "test_with_resources") + +app_test( + name = "BetaNoticeDialogFragmentTest", + processed_src = test_with_resources("BetaNoticeDialogFragmentTest.kt"), + test_class = "org.oppia.android.app.notice.BetaNoticeDialogFragmentTest", + deps = [ + ":dagger", + "//app", + "//app/src/main/java/org/oppia/android/app/application:application_component", + "//app/src/main/java/org/oppia/android/app/application:application_injector", + "//app/src/main/java/org/oppia/android/app/application:application_injector_provider", + "//app/src/main/java/org/oppia/android/app/application:common_application_modules", + "//app/src/main/java/org/oppia/android/app/application/testing:testing_build_flavor_module", + "//app/src/main/java/org/oppia/android/app/notice/testing:beta_notice_dialog_frgment_test_activity", + "//app/src/main/java/org/oppia/android/app/translation/testing:test_module", + "//data/src/main/java/org/oppia/android/data/backends/gae:prod_module", + "//domain/src/main/java/org/oppia/android/domain/onboarding/testing:retriever_test_module", + "//testing", + "//testing/src/main/java/org/oppia/android/testing/junit:initialize_default_locale_rule", + "//testing/src/main/java/org/oppia/android/testing/robolectric:test_module", + "//testing/src/main/java/org/oppia/android/testing/threading:test_module", + "//testing/src/main/java/org/oppia/android/testing/time:test_module", + "//third_party:androidx_test_espresso_espresso-core", + "//third_party:androidx_test_ext_junit", + "//third_party:org_robolectric_robolectric", + "//third_party:robolectric_android-all", + "//utility/src/main/java/org/oppia/android/util/accessibility:test_module", + "//utility/src/main/java/org/oppia/android/util/caching:asset_prod_module", + "//utility/src/main/java/org/oppia/android/util/caching/testing:caching_test_module", + "//utility/src/main/java/org/oppia/android/util/locale/testing:test_module", + "//utility/src/main/java/org/oppia/android/util/logging:prod_module", + "//utility/src/main/java/org/oppia/android/util/logging/firebase:debug_module", + "//utility/src/main/java/org/oppia/android/util/networking:debug_module", + "//utility/src/main/java/org/oppia/android/util/networking:debug_util_module", + ], +) + +app_test( + name = "GeneralAvailabilityUpgradeNoticeDialogFragmentTest", + processed_src = test_with_resources("GeneralAvailabilityUpgradeNoticeDialogFragmentTest.kt"), + test_class = "org.oppia.android.app.notice.GeneralAvailabilityUpgradeNoticeDialogFragmentTest", + deps = [ + ":dagger", + "//app", + "//app/src/main/java/org/oppia/android/app/application:application_component", + "//app/src/main/java/org/oppia/android/app/application:application_injector", + "//app/src/main/java/org/oppia/android/app/application:application_injector_provider", + "//app/src/main/java/org/oppia/android/app/application:common_application_modules", + "//app/src/main/java/org/oppia/android/app/application/testing:testing_build_flavor_module", + "//app/src/main/java/org/oppia/android/app/notice/testing:general_availability_upgrade_notice_dialog_fragment_test_activity", + "//app/src/main/java/org/oppia/android/app/translation/testing:test_module", + "//data/src/main/java/org/oppia/android/data/backends/gae:prod_module", + "//domain/src/main/java/org/oppia/android/domain/onboarding/testing:retriever_test_module", + "//testing", + "//testing/src/main/java/org/oppia/android/testing/junit:initialize_default_locale_rule", + "//testing/src/main/java/org/oppia/android/testing/robolectric:test_module", + "//testing/src/main/java/org/oppia/android/testing/threading:test_module", + "//testing/src/main/java/org/oppia/android/testing/time:test_module", + "//third_party:androidx_test_espresso_espresso-core", + "//third_party:androidx_test_ext_junit", + "//third_party:org_robolectric_robolectric", + "//third_party:robolectric_android-all", + "//utility/src/main/java/org/oppia/android/util/accessibility:test_module", + "//utility/src/main/java/org/oppia/android/util/caching:asset_prod_module", + "//utility/src/main/java/org/oppia/android/util/caching/testing:caching_test_module", + "//utility/src/main/java/org/oppia/android/util/locale/testing:test_module", + "//utility/src/main/java/org/oppia/android/util/logging:prod_module", + "//utility/src/main/java/org/oppia/android/util/logging/firebase:debug_module", + "//utility/src/main/java/org/oppia/android/util/networking:debug_module", + "//utility/src/main/java/org/oppia/android/util/networking:debug_util_module", + ], +) + +dagger_rules() diff --git a/app/src/sharedTest/java/org/oppia/android/app/notice/BetaNoticeDialogFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/notice/BetaNoticeDialogFragmentTest.kt new file mode 100644 index 00000000000..0a694c49b2b --- /dev/null +++ b/app/src/sharedTest/java/org/oppia/android/app/notice/BetaNoticeDialogFragmentTest.kt @@ -0,0 +1,278 @@ +package org.oppia.android.app.notice + +import android.app.Application +import android.view.View +import androidx.appcompat.app.AppCompatActivity +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.PositionAssertions.isCompletelyBelow +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.RootMatchers.isDialog +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.ext.junit.runners.AndroidJUnit4 +import dagger.BindsInstance +import dagger.Component +import org.hamcrest.Matcher +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule +import org.oppia.android.R +import org.oppia.android.app.activity.ActivityComponent +import org.oppia.android.app.activity.ActivityComponentFactory +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.notice.testing.BetaNoticeDialogFragmentTestActivity +import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule +import org.oppia.android.app.shim.ViewBindingShimModule +import org.oppia.android.app.topic.PracticeTabModule +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.lightweightcheckpointing.ExplorationStorageModule +import org.oppia.android.domain.hintsandsolution.HintsAndSolutionConfigFastShowTestModule +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.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.TestImageLoaderModule +import org.oppia.android.testing.TestLogReportingModule +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.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.ImageParsingModule +import org.robolectric.annotation.Config +import org.robolectric.annotation.LooperMode +import javax.inject.Inject +import javax.inject.Singleton + +/** Tests for [BetaNoticeDialogFragment]. */ +// FunctionName: test names are conventionally named with underscores. +@Suppress("FunctionName") +@RunWith(AndroidJUnit4::class) +@Config( + application = BetaNoticeDialogFragmentTest.TestApplication::class, qualifiers = "port-xxhdpi" +) +@LooperMode(LooperMode.Mode.PAUSED) +class BetaNoticeDialogFragmentTest { + @get:Rule + val initializeDefaultLocaleRule = InitializeDefaultLocaleRule() + + @get:Rule + val oppiaTestRule = OppiaTestRule() + + @field:[Rule JvmField] val mockitoRule: MockitoRule = MockitoJUnit.rule() + + @Inject + lateinit var testCoroutineDispatchers: TestCoroutineDispatchers + + @Mock + lateinit var mockBetaNoticeClosedListener: BetaNoticeClosedListener + + @Before + fun setUp() { + setUpTestApplicationComponent() + } + + @Test + fun testFragment_hasExpectedTitle() { + launchBetaNoticeDialogFragmentTestActivity { + onDialogView(withText(R.string.beta_notice_dialog_title)).check(matches(isDisplayed())) + } + } + + @Test + fun testFragment_hasExpectedContentMessageTextUnderTitle() { + launchBetaNoticeDialogFragmentTestActivity { + onDialogView(withId(R.id.beta_notice_dialog_message)).check(matches(isDisplayed())) + onDialogView(withId(R.id.beta_notice_dialog_message)) + .check(matches(withText(R.string.beta_notice_dialog_message))) + onDialogView(withId(R.id.beta_notice_dialog_message)) + .check(isCompletelyBelow(withText(R.string.beta_notice_dialog_title))) + } + } + + @Test + fun testFragment_hasDoNotShowAgainCheckboxUnderContentMessage() { + launchBetaNoticeDialogFragmentTestActivity { + onDialogView(withId(R.id.beta_notice_dialog_preference_checkbox)) + .check(matches(isDisplayed())) + onDialogView(withId(R.id.beta_notice_dialog_preference_checkbox)) + .check(matches(withText(R.string.beta_notice_dialog_do_not_show_again_text))) + onDialogView(withId(R.id.beta_notice_dialog_preference_checkbox)) + .check(isCompletelyBelow(withId(R.id.beta_notice_dialog_message))) + } + } + + @Test + fun testFragment_hasExpectedAcknowledgementButtonUnderDoNotShowAgainCheckbox() { + launchBetaNoticeDialogFragmentTestActivity { + onDialogView(withText(R.string.beta_notice_dialog_close_button_text)) + .check(matches(isDisplayed())) + onDialogView(withText(R.string.beta_notice_dialog_close_button_text)) + .check(isCompletelyBelow(withId(R.id.beta_notice_dialog_preference_checkbox))) + } + } + + @Test + fun testFragment_clickAcknowledgeButton_callsCallbackListenerWithFalse() { + launchBetaNoticeDialogFragmentTestActivity { + clickOnDialogView(withText(R.string.beta_notice_dialog_close_button_text)) + + verify(mockBetaNoticeClosedListener).onBetaNoticeOkayButtonClicked(false) + } + } + + @Test + fun testFragment_clickDoNotShowCheckbox_clickAcknowledgeButton_callsCallbackListenerWithTrue() { + launchBetaNoticeDialogFragmentTestActivity { + clickOnDialogView(withId(R.id.beta_notice_dialog_preference_checkbox)) + + clickOnDialogView(withText(R.string.beta_notice_dialog_close_button_text)) + + verify(mockBetaNoticeClosedListener).onBetaNoticeOkayButtonClicked(true) + } + } + + @Test + fun testFragment_toggleCheckbox_clickAcknowledgeButton_callsCallbackListenerWithFalse() { + launchBetaNoticeDialogFragmentTestActivity { + // Select, then deselect, the checkbox before closing the dialog. + clickOnDialogView(withId(R.id.beta_notice_dialog_preference_checkbox)) + clickOnDialogView(withId(R.id.beta_notice_dialog_preference_checkbox)) + + clickOnDialogView(withText(R.string.beta_notice_dialog_close_button_text)) + + verify(mockBetaNoticeClosedListener).onBetaNoticeOkayButtonClicked(false) + } + } + + private fun launchBetaNoticeDialogFragmentTestActivity(testBlock: () -> Unit) { + // Launch the test activity, but make sure that it's properly set up & time is given for it to + // initialize. + ActivityScenario.launch(BetaNoticeDialogFragmentTestActivity::class.java).use { scenario -> + scenario.onActivity { it.mockCallbackListener = mockBetaNoticeClosedListener } + testCoroutineDispatchers.runCurrent() + testBlock() + } + } + + private fun clickOnDialogView(matcher: Matcher) { + onDialogView(matcher).perform(click()) + testCoroutineDispatchers.runCurrent() + } + + private fun setUpTestApplicationComponent() { + ApplicationProvider.getApplicationContext().inject(this) + } + + private companion object { + private fun onDialogView(matcher: Matcher) = onView(matcher).inRoot(isDialog()) + } + + @Singleton + @Component( + modules = [ + RobolectricModule::class, PlatformParameterModule::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, + TestImageLoaderModule::class, ImageParsingModule::class, HtmlParserEntityTypeModule::class, + QuestionModule::class, TestLogReportingModule::class, AccessibilityTestModule::class, + LogStorageModule::class, PrimeTopicAssetsControllerModule::class, + ExpirationMetaDataRetrieverModule::class, ViewBindingShimModule::class, + RatioInputModule::class, ApplicationStartupListenerModule::class, + HintsAndSolutionConfigFastShowTestModule::class, HintsAndSolutionProdModule::class, + WorkManagerConfigurationModule::class, LogReportWorkerModule::class, + FirebaseLogUploaderModule::class, FakeOppiaClockModule::class, PracticeTabModule::class, + DeveloperOptionsStarterModule::class, DeveloperOptionsModule::class, + ExplorationStorageModule::class, NetworkConnectionUtilDebugModule::class, + NetworkConnectionDebugUtilModule::class, NetworkModule::class, NetworkConfigProdModule::class, + AssetModule::class, LocaleProdModule::class, ActivityRecreatorTestModule::class, + PlatformParameterSingletonModule::class, NumericExpressionInputModule::class, + AlgebraicExpressionInputModule::class, MathEquationInputModule::class, + SplitScreenInteractionModule::class, LoggingIdentifierModule::class, + ApplicationLifecycleModule::class, SyncStatusModule::class, TestingBuildFlavorModule::class, + CachingTestModule::class, MetricLogSchedulerModule::class + ] + ) + interface TestApplicationComponent : ApplicationComponent { + @Component.Builder + interface Builder { + @BindsInstance + fun setApplication(application: Application): Builder + + fun build(): TestApplicationComponent + } + + fun inject(test: BetaNoticeDialogFragmentTest) + } + + class TestApplication : Application(), ActivityComponentFactory, ApplicationInjectorProvider { + private val component: TestApplicationComponent by lazy { + DaggerBetaNoticeDialogFragmentTest_TestApplicationComponent.builder() + .setApplication(this) + .build() + } + + fun inject(test: BetaNoticeDialogFragmentTest) = component.inject(test) + + 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/notice/GeneralAvailabilityUpgradeNoticeDialogFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/notice/GeneralAvailabilityUpgradeNoticeDialogFragmentTest.kt new file mode 100644 index 00000000000..4207a1d5e78 --- /dev/null +++ b/app/src/sharedTest/java/org/oppia/android/app/notice/GeneralAvailabilityUpgradeNoticeDialogFragmentTest.kt @@ -0,0 +1,285 @@ +package org.oppia.android.app.notice + +import android.app.Application +import android.view.View +import androidx.appcompat.app.AppCompatActivity +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.PositionAssertions.isCompletelyBelow +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.RootMatchers.isDialog +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.ext.junit.runners.AndroidJUnit4 +import dagger.BindsInstance +import dagger.Component +import org.hamcrest.Matcher +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule +import org.oppia.android.R +import org.oppia.android.app.activity.ActivityComponent +import org.oppia.android.app.activity.ActivityComponentFactory +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.notice.testing.GeneralAvailabilityUpgradeNoticeDialogFragmentTestActivity +import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule +import org.oppia.android.app.shim.ViewBindingShimModule +import org.oppia.android.app.topic.PracticeTabModule +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.lightweightcheckpointing.ExplorationStorageModule +import org.oppia.android.domain.hintsandsolution.HintsAndSolutionConfigFastShowTestModule +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.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.TestImageLoaderModule +import org.oppia.android.testing.TestLogReportingModule +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.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.ImageParsingModule +import org.robolectric.annotation.Config +import org.robolectric.annotation.LooperMode +import javax.inject.Inject +import javax.inject.Singleton + +/** Tests for [GeneralAvailabilityUpgradeNoticeDialogFragment]. */ +// FunctionName: test names are conventionally named with underscores. +@Suppress("FunctionName") +@RunWith(AndroidJUnit4::class) +@Config( + application = GeneralAvailabilityUpgradeNoticeDialogFragmentTest.TestApplication::class, + qualifiers = "port-xxhdpi" +) +@LooperMode(LooperMode.Mode.PAUSED) +class GeneralAvailabilityUpgradeNoticeDialogFragmentTest { + @get:Rule + val initializeDefaultLocaleRule = InitializeDefaultLocaleRule() + + @get:Rule + val oppiaTestRule = OppiaTestRule() + + @field:[Rule JvmField] val mockitoRule: MockitoRule = MockitoJUnit.rule() + + @Inject + lateinit var testCoroutineDispatchers: TestCoroutineDispatchers + + @Mock + lateinit var mockNoticeClosedListener: GeneralAvailabilityUpgradeNoticeClosedListener + + @Before + fun setUp() { + setUpTestApplicationComponent() + } + + @Test + fun testFragment_hasExpectedTitle() { + launchGeneralAvailabilityUpgradeNoticeDialogFragmentTestActivity { + onDialogView(withText(R.string.general_availability_notice_dialog_title)) + .check(matches(isDisplayed())) + } + } + + @Test + fun testFragment_hasExpectedContentMessageTextUnderTitle() { + launchGeneralAvailabilityUpgradeNoticeDialogFragmentTestActivity { + onDialogView(withId(R.id.ga_update_notice_dialog_message)).check(matches(isDisplayed())) + onDialogView(withId(R.id.ga_update_notice_dialog_message)) + .check(matches(withText(R.string.general_availability_notice_dialog_message))) + onDialogView(withId(R.id.ga_update_notice_dialog_message)) + .check(isCompletelyBelow(withText(R.string.general_availability_notice_dialog_title))) + } + } + + @Test + fun testFragment_hasDoNotShowAgainCheckboxUnderContentMessage() { + launchGeneralAvailabilityUpgradeNoticeDialogFragmentTestActivity { + onDialogView(withId(R.id.ga_update_notice_dialog_preference_checkbox)) + .check(matches(isDisplayed())) + onDialogView(withId(R.id.ga_update_notice_dialog_preference_checkbox)).check( + matches(withText(R.string.general_availability_notice_dialog_do_not_show_again_text)) + ) + onDialogView(withId(R.id.ga_update_notice_dialog_preference_checkbox)) + .check(isCompletelyBelow(withId(R.id.ga_update_notice_dialog_message))) + } + } + + @Test + fun testFragment_hasExpectedAcknowledgementButtonUnderDoNotShowAgainCheckbox() { + launchGeneralAvailabilityUpgradeNoticeDialogFragmentTestActivity { + onDialogView(withText(R.string.general_availability_notice_dialog_close_button_text)) + .check(matches(isDisplayed())) + onDialogView(withText(R.string.general_availability_notice_dialog_close_button_text)) + .check(isCompletelyBelow(withId(R.id.ga_update_notice_dialog_preference_checkbox))) + } + } + + @Test + fun testFragment_clickAcknowledgeButton_callsCallbackListenerWithFalse() { + launchGeneralAvailabilityUpgradeNoticeDialogFragmentTestActivity { + clickOnDialogView(withText(R.string.general_availability_notice_dialog_close_button_text)) + + verify(mockNoticeClosedListener).onGaUpgradeNoticeOkayButtonClicked(false) + } + } + + @Test + fun testFragment_clickDoNotShowCheckbox_clickAcknowledgeButton_callsCallbackListenerWithTrue() { + launchGeneralAvailabilityUpgradeNoticeDialogFragmentTestActivity { + clickOnDialogView(withId(R.id.ga_update_notice_dialog_preference_checkbox)) + + clickOnDialogView(withText(R.string.general_availability_notice_dialog_close_button_text)) + + verify(mockNoticeClosedListener).onGaUpgradeNoticeOkayButtonClicked(true) + } + } + + @Test + fun testFragment_toggleCheckbox_clickAcknowledgeButton_callsCallbackListenerWithFalse() { + launchGeneralAvailabilityUpgradeNoticeDialogFragmentTestActivity { + // Select, then deselect, the checkbox before closing the dialog. + clickOnDialogView(withId(R.id.ga_update_notice_dialog_preference_checkbox)) + clickOnDialogView(withId(R.id.ga_update_notice_dialog_preference_checkbox)) + + clickOnDialogView(withText(R.string.general_availability_notice_dialog_close_button_text)) + + verify(mockNoticeClosedListener).onGaUpgradeNoticeOkayButtonClicked(false) + } + } + + private fun launchGeneralAvailabilityUpgradeNoticeDialogFragmentTestActivity( + testBlock: () -> Unit + ) { + // Launch the test activity, but make sure that it's properly set up & time is given for it to + // initialize. + ActivityScenario.launch( + GeneralAvailabilityUpgradeNoticeDialogFragmentTestActivity::class.java + ).use { scenario -> + scenario.onActivity { it.mockCallbackListener = mockNoticeClosedListener } + testCoroutineDispatchers.runCurrent() + testBlock() + } + } + + private fun clickOnDialogView(matcher: Matcher) { + onDialogView(matcher).perform(click()) + testCoroutineDispatchers.runCurrent() + } + + private fun setUpTestApplicationComponent() { + ApplicationProvider.getApplicationContext().inject(this) + } + + private companion object { + private fun onDialogView(matcher: Matcher) = onView(matcher).inRoot(isDialog()) + } + + @Singleton + @Component( + modules = [ + RobolectricModule::class, PlatformParameterModule::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, + TestImageLoaderModule::class, ImageParsingModule::class, HtmlParserEntityTypeModule::class, + QuestionModule::class, TestLogReportingModule::class, AccessibilityTestModule::class, + LogStorageModule::class, PrimeTopicAssetsControllerModule::class, + ExpirationMetaDataRetrieverModule::class, ViewBindingShimModule::class, + RatioInputModule::class, ApplicationStartupListenerModule::class, + HintsAndSolutionConfigFastShowTestModule::class, HintsAndSolutionProdModule::class, + WorkManagerConfigurationModule::class, LogReportWorkerModule::class, + FirebaseLogUploaderModule::class, FakeOppiaClockModule::class, PracticeTabModule::class, + DeveloperOptionsStarterModule::class, DeveloperOptionsModule::class, + ExplorationStorageModule::class, NetworkConnectionUtilDebugModule::class, + NetworkConnectionDebugUtilModule::class, NetworkModule::class, NetworkConfigProdModule::class, + AssetModule::class, LocaleProdModule::class, ActivityRecreatorTestModule::class, + PlatformParameterSingletonModule::class, NumericExpressionInputModule::class, + AlgebraicExpressionInputModule::class, MathEquationInputModule::class, + SplitScreenInteractionModule::class, LoggingIdentifierModule::class, + ApplicationLifecycleModule::class, SyncStatusModule::class, TestingBuildFlavorModule::class, + CachingTestModule::class, MetricLogSchedulerModule::class + ] + ) + interface TestApplicationComponent : ApplicationComponent { + @Component.Builder + interface Builder { + @BindsInstance + fun setApplication(application: Application): Builder + + fun build(): TestApplicationComponent + } + + fun inject(test: GeneralAvailabilityUpgradeNoticeDialogFragmentTest) + } + + class TestApplication : Application(), ActivityComponentFactory, ApplicationInjectorProvider { + private val component: TestApplicationComponent by lazy { + DaggerGeneralAvailabilityUpgradeNoticeDialogFragmentTest_TestApplicationComponent.builder() + .setApplication(this) + .build() + } + + fun inject(test: GeneralAvailabilityUpgradeNoticeDialogFragmentTest) = component.inject(test) + + 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/OnboardingActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/onboarding/OnboardingActivityTest.kt index 412a7674a49..3757c17d7cf 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/onboarding/OnboardingActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/onboarding/OnboardingActivityTest.kt @@ -21,6 +21,7 @@ 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 @@ -151,7 +152,7 @@ class OnboardingActivityTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { 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 5b3d03d8824..4fea9798161 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 @@ -46,6 +46,7 @@ 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 @@ -723,7 +724,7 @@ class OnboardingFragmentTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/ongoingtopiclist/OngoingTopicListActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/ongoingtopiclist/OngoingTopicListActivityTest.kt index c9d0f8ff1d1..44948180740 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/ongoingtopiclist/OngoingTopicListActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/ongoingtopiclist/OngoingTopicListActivityTest.kt @@ -36,6 +36,7 @@ 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.ProfileId @@ -458,7 +459,7 @@ class OngoingTopicListActivityTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/options/AppLanguageActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/options/AppLanguageActivityTest.kt index 62965d09a72..1bda24c4f48 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/options/AppLanguageActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/options/AppLanguageActivityTest.kt @@ -21,6 +21,7 @@ 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 @@ -161,7 +162,7 @@ class AppLanguageActivityTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/options/AppLanguageFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/options/AppLanguageFragmentTest.kt index 78966f2e217..3f4aa8f3a31 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/options/AppLanguageFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/options/AppLanguageFragmentTest.kt @@ -28,6 +28,7 @@ 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 @@ -256,7 +257,7 @@ class AppLanguageFragmentTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/options/AudioLanguageActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/options/AudioLanguageActivityTest.kt index eb69586b13c..2bc4922f9a4 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/options/AudioLanguageActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/options/AudioLanguageActivityTest.kt @@ -21,6 +21,7 @@ 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 @@ -161,7 +162,7 @@ class AudioLanguageActivityTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { 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 dbb338d76e2..69bf7e01e8a 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 @@ -27,6 +27,7 @@ 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 @@ -249,7 +250,7 @@ class AudioLanguageFragmentTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/options/OptionsActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/options/OptionsActivityTest.kt index b90e05fd338..8839bf88eb7 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/options/OptionsActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/options/OptionsActivityTest.kt @@ -21,6 +21,7 @@ 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 @@ -153,7 +154,7 @@ class OptionsActivityTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/options/OptionsFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/options/OptionsFragmentTest.kt index 50701da0b58..5e17d4b0877 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/options/OptionsFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/options/OptionsFragmentTest.kt @@ -39,6 +39,7 @@ 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 @@ -653,7 +654,7 @@ class OptionsFragmentTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/options/ReadingTextSizeActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/options/ReadingTextSizeActivityTest.kt index 40fd59f6fb8..2d11f2d65cc 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/options/ReadingTextSizeActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/options/ReadingTextSizeActivityTest.kt @@ -21,6 +21,7 @@ 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 @@ -161,7 +162,7 @@ class ReadingTextSizeActivityTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/options/ReadingTextSizeFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/options/ReadingTextSizeFragmentTest.kt index 46284607705..bb3b3d23a7b 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/options/ReadingTextSizeFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/options/ReadingTextSizeFragmentTest.kt @@ -34,6 +34,7 @@ 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 @@ -315,7 +316,7 @@ class ReadingTextSizeFragmentTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/parser/HtmlParserTest.kt b/app/src/sharedTest/java/org/oppia/android/app/parser/HtmlParserTest.kt index 6cc3bbcb4a0..83640c04d2f 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/parser/HtmlParserTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/parser/HtmlParserTest.kt @@ -54,6 +54,7 @@ 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.LanguageSupportDefinition @@ -907,7 +908,7 @@ class HtmlParserTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/player/audio/AudioFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/player/audio/AudioFragmentTest.kt index 30186b1cc96..ecafe75284e 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/player/audio/AudioFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/player/audio/AudioFragmentTest.kt @@ -41,6 +41,7 @@ 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.ProfileId @@ -478,7 +479,7 @@ class AudioFragmentTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/player/exploration/ExplorationActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/player/exploration/ExplorationActivityTest.kt index 2020e38b87b..053cddc373a 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/player/exploration/ExplorationActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/player/exploration/ExplorationActivityTest.kt @@ -65,6 +65,7 @@ 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.help.HelpActivity @@ -1958,7 +1959,7 @@ class ExplorationActivityTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, SyncStatusModule::class, - MetricLogSchedulerModule::class + MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) diff --git a/app/src/sharedTest/java/org/oppia/android/app/player/state/BUILD.bazel b/app/src/sharedTest/java/org/oppia/android/app/player/state/BUILD.bazel index 457225f4fe4..1430f73fa51 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/player/state/BUILD.bazel +++ b/app/src/sharedTest/java/org/oppia/android/app/player/state/BUILD.bazel @@ -18,6 +18,7 @@ app_test( "//app/src/main/java/org/oppia/android/app/application:application_injector", "//app/src/main/java/org/oppia/android/app/application:application_injector_provider", "//app/src/main/java/org/oppia/android/app/application:common_application_modules", + "//app/src/main/java/org/oppia/android/app/application/testing:testing_build_flavor_module", "//app/src/main/java/org/oppia/android/app/translation/testing:test_module", "//data/src/main/java/org/oppia/android/data/backends/gae:prod_module", "//domain/src/main/java/org/oppia/android/domain/onboarding/testing:retriever_test_module", diff --git a/app/src/sharedTest/java/org/oppia/android/app/player/state/StateFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/player/state/StateFragmentTest.kt index f1898d255d3..ed0a0125b0d 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/player/state/StateFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/player/state/StateFragmentTest.kt @@ -70,6 +70,7 @@ 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.OppiaLanguage @@ -4319,7 +4320,7 @@ class StateFragmentTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/policies/PoliciesActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/policies/PoliciesActivityTest.kt index 40789301bbd..26724b47f42 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/policies/PoliciesActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/policies/PoliciesActivityTest.kt @@ -21,6 +21,7 @@ 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.PolicyPage @@ -184,7 +185,7 @@ class PoliciesActivityTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/policies/PoliciesFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/policies/PoliciesFragmentTest.kt index f2ffe23dd5c..69b5e5ddbdf 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/policies/PoliciesFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/policies/PoliciesFragmentTest.kt @@ -43,6 +43,7 @@ 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.PolicyPage @@ -337,7 +338,7 @@ class PoliciesFragmentTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/profile/AddProfileActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/profile/AddProfileActivityTest.kt index 4cd82b31973..4595e72857e 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/profile/AddProfileActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/profile/AddProfileActivityTest.kt @@ -54,6 +54,7 @@ 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 @@ -1732,7 +1733,7 @@ class AddProfileActivityTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/profile/AdminAuthActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/profile/AdminAuthActivityTest.kt index c4934f2611c..23949bf6b7e 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/profile/AdminAuthActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/profile/AdminAuthActivityTest.kt @@ -40,6 +40,7 @@ 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 @@ -672,7 +673,7 @@ class AdminAuthActivityTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/profile/AdminPinActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/profile/AdminPinActivityTest.kt index 4f66269badb..5b92a040c25 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/profile/AdminPinActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/profile/AdminPinActivityTest.kt @@ -49,6 +49,7 @@ 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 @@ -1090,7 +1091,7 @@ class AdminPinActivityTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/profile/PinPasswordActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/profile/PinPasswordActivityTest.kt index e8770e57d44..c4b9a510724 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/profile/PinPasswordActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/profile/PinPasswordActivityTest.kt @@ -41,6 +41,7 @@ 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.home.HomeActivity @@ -1152,7 +1153,7 @@ class PinPasswordActivityTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/profile/ProfileChooserFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/profile/ProfileChooserFragmentTest.kt index cdcee36d429..cbb45e201cf 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/profile/ProfileChooserFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/profile/ProfileChooserFragmentTest.kt @@ -39,6 +39,7 @@ 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.drawer.NAVIGATION_PROFILE_ID_ARGUMENT_KEY @@ -529,7 +530,7 @@ class ProfileChooserFragmentTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/profileprogress/ProfilePictureActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/profileprogress/ProfilePictureActivityTest.kt index f1e9541bb3c..51a4440dc1f 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/profileprogress/ProfilePictureActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/profileprogress/ProfilePictureActivityTest.kt @@ -29,6 +29,7 @@ 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 @@ -204,7 +205,7 @@ class ProfilePictureActivityTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/profileprogress/ProfileProgressActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/profileprogress/ProfileProgressActivityTest.kt index 4716612c00e..a47d30bd5a0 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/profileprogress/ProfileProgressActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/profileprogress/ProfileProgressActivityTest.kt @@ -21,6 +21,7 @@ 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 @@ -155,7 +156,7 @@ class ProfileProgressActivityTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/profileprogress/ProfileProgressFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/profileprogress/ProfileProgressFragmentTest.kt index 3593c3a83b7..b01743cdfcb 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/profileprogress/ProfileProgressFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/profileprogress/ProfileProgressFragmentTest.kt @@ -54,6 +54,7 @@ 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.completedstorylist.CompletedStoryListActivity import org.oppia.android.app.devoptions.DeveloperOptionsModule import org.oppia.android.app.devoptions.DeveloperOptionsStarterModule @@ -856,7 +857,7 @@ class ProfileProgressFragmentTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/recyclerview/BindableAdapterTest.kt b/app/src/sharedTest/java/org/oppia/android/app/recyclerview/BindableAdapterTest.kt index e8a168ee1af..0a8321945bb 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/recyclerview/BindableAdapterTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/recyclerview/BindableAdapterTest.kt @@ -37,6 +37,7 @@ 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.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.fragment.FragmentComponent @@ -767,7 +768,7 @@ class BindableAdapterTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/resumelesson/ResumeLessonActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/resumelesson/ResumeLessonActivityTest.kt index e76a4ac0ab6..d3a0c8073bd 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/resumelesson/ResumeLessonActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/resumelesson/ResumeLessonActivityTest.kt @@ -34,6 +34,7 @@ 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.ExplorationCheckpoint @@ -225,7 +226,7 @@ class ResumeLessonActivityTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/resumelesson/ResumeLessonFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/resumelesson/ResumeLessonFragmentTest.kt index 1bf70dde862..4760439db99 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/resumelesson/ResumeLessonFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/resumelesson/ResumeLessonFragmentTest.kt @@ -33,6 +33,7 @@ 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.ExplorationCheckpoint @@ -314,7 +315,7 @@ class ResumeLessonFragmentTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileEditActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileEditActivityTest.kt index f950c6f152c..0e0450b08a2 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileEditActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileEditActivityTest.kt @@ -35,6 +35,7 @@ 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.ProfileId @@ -378,7 +379,7 @@ class ProfileEditActivityTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileEditFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileEditFragmentTest.kt index 84d523c5200..dcc7285bf51 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileEditFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileEditFragmentTest.kt @@ -35,6 +35,7 @@ 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 @@ -355,7 +356,7 @@ class ProfileEditFragmentTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) diff --git a/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileListActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileListActivityTest.kt index ea0e29aaac2..fc6b3cc972e 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileListActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileListActivityTest.kt @@ -21,6 +21,7 @@ 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 @@ -151,7 +152,7 @@ class ProfileListActivityTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileListFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileListFragmentTest.kt index 6e8ac36d94f..37feb72c90d 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileListFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileListFragmentTest.kt @@ -33,6 +33,7 @@ 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 @@ -389,7 +390,7 @@ class ProfileListFragmentTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileRenameActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileRenameActivityTest.kt index 55d6e85a511..026dede3753 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileRenameActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileRenameActivityTest.kt @@ -22,6 +22,7 @@ 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 @@ -174,7 +175,7 @@ class ProfileRenameActivityTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileRenameFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileRenameFragmentTest.kt index 9744a3daead..7efe91a973e 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileRenameFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileRenameFragmentTest.kt @@ -37,6 +37,7 @@ 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 @@ -463,7 +464,7 @@ class ProfileRenameFragmentTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileResetPinActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileResetPinActivityTest.kt index 38c6df5615d..766f0ffc3ec 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileResetPinActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileResetPinActivityTest.kt @@ -23,6 +23,7 @@ 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 @@ -185,7 +186,7 @@ class ProfileResetPinActivityTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileResetPinFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileResetPinFragmentTest.kt index dc210acaa36..c82448b3733 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileResetPinFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/settings/profile/ProfileResetPinFragmentTest.kt @@ -38,6 +38,7 @@ 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 @@ -1027,7 +1028,7 @@ class ProfileResetPinFragmentTest { AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/splash/BUILD.bazel b/app/src/sharedTest/java/org/oppia/android/app/splash/BUILD.bazel new file mode 100644 index 00000000000..8a15fa58892 --- /dev/null +++ b/app/src/sharedTest/java/org/oppia/android/app/splash/BUILD.bazel @@ -0,0 +1,53 @@ +""" +Tests for the splash/app UI initialization process. +""" + +load("@dagger//:workspace_defs.bzl", "dagger_rules") +load("//app:app_test.bzl", "app_test") +load("//app:test_with_resources.bzl", "test_with_resources") + +app_test( + name = "SplashActivityTest", + processed_src = test_with_resources("SplashActivityTest.kt"), + test_class = "org.oppia.android.app.splash.SplashActivityTest", + deps = [ + ":dagger", + "//app", + "//app:test_deps", + "//app/src/main/java/org/oppia/android/app/application:application_component", + "//app/src/main/java/org/oppia/android/app/application:application_injector", + "//app/src/main/java/org/oppia/android/app/application:application_injector_provider", + "//app/src/main/java/org/oppia/android/app/application:common_application_modules", + "//app/src/main/java/org/oppia/android/app/translation/testing:test_module", + "//data/src/main/java/org/oppia/android/data/backends/gae:prod_module", + "//domain/src/main/java/org/oppia/android/domain/onboarding/testing:retriever_test_module", + "//domain/src/main/java/org/oppia/android/domain/translation:translation_controller", + "//testing", + "//testing/src/main/java/org/oppia/android/testing/data:data_provider_test_monitor", + "//testing/src/main/java/org/oppia/android/testing/espresso:edit_text_input_action", + "//testing/src/main/java/org/oppia/android/testing/junit:initialize_default_locale_rule", + "//testing/src/main/java/org/oppia/android/testing/junit:oppia_parameterized_test_runner", + "//testing/src/main/java/org/oppia/android/testing/junit:parameterized_auto_android_test_runner", + "//testing/src/main/java/org/oppia/android/testing/junit:parameterized_robolectric_test_runner", + "//testing/src/main/java/org/oppia/android/testing/robolectric:test_module", + "//testing/src/main/java/org/oppia/android/testing/threading:coroutine_executor_service", + "//testing/src/main/java/org/oppia/android/testing/threading:test_module", + "//testing/src/main/java/org/oppia/android/testing/time:test_module", + "//third_party:com_github_bumptech_glide_mocks", + "//third_party:com_google_truth_truth", + "//third_party:junit_junit", + "//third_party:org_robolectric_robolectric", + "//third_party:robolectric_android-all", + "//utility/src/main/java/org/oppia/android/util/accessibility:test_module", + "//utility/src/main/java/org/oppia/android/util/caching:asset_prod_module", + "//utility/src/main/java/org/oppia/android/util/caching/testing:caching_test_module", + "//utility/src/main/java/org/oppia/android/util/data:data_providers", + "//utility/src/main/java/org/oppia/android/util/locale/testing:test_module", + "//utility/src/main/java/org/oppia/android/util/logging:prod_module", + "//utility/src/main/java/org/oppia/android/util/logging/firebase:debug_module", + "//utility/src/main/java/org/oppia/android/util/networking:debug_module", + "//utility/src/main/java/org/oppia/android/util/networking:debug_util_module", + ], +) + +dagger_rules() diff --git a/app/src/sharedTest/java/org/oppia/android/app/splash/SplashActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/splash/SplashActivityTest.kt index e554c9df2e1..9c1da667d90 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/splash/SplashActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/splash/SplashActivityTest.kt @@ -3,23 +3,30 @@ package org.oppia.android.app.splash import android.app.Application import android.app.Instrumentation import android.content.Context +import android.content.Intent +import android.view.View import androidx.appcompat.app.AppCompatActivity +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.doesNotExist 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.RootMatchers.isDialog import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withText -import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.rule.ActivityTestRule import com.google.common.truth.Truth.assertThat import dagger.BindsInstance import dagger.Component +import dagger.Module +import dagger.Provides +import org.hamcrest.Matcher +import org.hamcrest.Matchers.not import org.junit.After import org.junit.Before import org.junit.Rule @@ -35,6 +42,7 @@ import org.oppia.android.app.application.ApplicationModule import org.oppia.android.app.application.ApplicationStartupListenerModule import org.oppia.android.app.devoptions.DeveloperOptionsModule import org.oppia.android.app.devoptions.DeveloperOptionsStarterModule +import org.oppia.android.app.model.BuildFlavor import org.oppia.android.app.model.OppiaLanguage.ARABIC import org.oppia.android.app.model.OppiaLanguage.BRAZILIAN_PORTUGUESE import org.oppia.android.app.model.OppiaLanguage.ENGLISH @@ -85,6 +93,13 @@ import org.oppia.android.testing.OppiaTestRule import org.oppia.android.testing.RunOn import org.oppia.android.testing.TestLogReportingModule import org.oppia.android.testing.TestPlatform +import org.oppia.android.testing.data.DataProviderTestMonitor +import org.oppia.android.testing.junit.OppiaParameterizedTestRunner +import org.oppia.android.testing.junit.OppiaParameterizedTestRunner.Iteration +import org.oppia.android.testing.junit.OppiaParameterizedTestRunner.Parameter +import org.oppia.android.testing.junit.OppiaParameterizedTestRunner.RunParameterized +import org.oppia.android.testing.junit.OppiaParameterizedTestRunner.SelectRunnerPlatform +import org.oppia.android.testing.junit.ParameterizedAutoAndroidTestRunner import org.oppia.android.testing.robolectric.RobolectricModule import org.oppia.android.testing.threading.TestCoroutineDispatchers import org.oppia.android.testing.threading.TestDispatcherModule @@ -110,6 +125,7 @@ import java.time.Duration import java.time.Instant import java.util.Date import java.util.Locale +import java.util.concurrent.TimeUnit import javax.inject.Inject import javax.inject.Singleton @@ -117,29 +133,33 @@ import javax.inject.Singleton * Tests for [SplashActivity]. For context on the activity test rule setup see: * https://jabknowsnothing.wordpress.com/2015/11/05/activitytestrule-espressos-test-lifecycle/. */ -@RunWith(AndroidJUnit4::class) +// FunctionName: test names are conventionally named with underscores. +@Suppress("FunctionName") +@RunWith(OppiaParameterizedTestRunner::class) +@SelectRunnerPlatform(ParameterizedAutoAndroidTestRunner::class) @LooperMode(LooperMode.Mode.PAUSED) @Config(application = SplashActivityTest.TestApplication::class, qualifiers = "port-xxhdpi") class SplashActivityTest { @get:Rule val oppiaTestRule = OppiaTestRule() - @Inject - lateinit var context: Context + @Inject lateinit var context: Context + @Inject lateinit var testCoroutineDispatchers: TestCoroutineDispatchers + @Inject lateinit var fakeMetaDataRetriever: FakeExpirationMetaDataRetriever + @Inject lateinit var appLanguageLocaleHandler: AppLanguageLocaleHandler + @Inject lateinit var monitorFactory: DataProviderTestMonitor.Factory + @Inject lateinit var appStartupStateController: AppStartupStateController - @Inject - lateinit var testCoroutineDispatchers: TestCoroutineDispatchers - - @Inject - lateinit var fakeMetaDataRetriever: FakeExpirationMetaDataRetriever - - @Inject - lateinit var appLanguageLocaleHandler: AppLanguageLocaleHandler + @Parameter lateinit var firstOpen: String + @Parameter lateinit var secondOpen: String private val expirationDateFormat by lazy { SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) } + private val firstOpenFlavor by lazy { BuildFlavor.valueOf(firstOpen) } + private val secondOpenFlavor by lazy { BuildFlavor.valueOf(secondOpen) } @Before fun setUp() { + TestModule.buildFlavor = BuildFlavor.BUILD_FLAVOR_UNSPECIFIED Intents.init() } @@ -149,23 +169,13 @@ class SplashActivityTest { Intents.release() } - // The initialTouchMode enables the activity to be launched in touch mode. The launchActivity is - // disabled to launch Activity explicitly within each test case. - @get:Rule - var activityTestRule: ActivityTestRule = ActivityTestRule( - SplashActivity::class.java, - /* initialTouchMode= */ true, - /* launchActivity= */ false - ) - @Test fun testSplashActivity_initialOpen_routesToOnboardingActivity() { initializeTestApplication() - activityTestRule.launchActivity(null) - testCoroutineDispatchers.advanceUntilIdle() - - intended(hasComponent(OnboardingActivity::class.java.name)) + launchSplashActivityFully { + intended(hasComponent(OnboardingActivity::class.java.name)) + } } @Test @@ -173,10 +183,9 @@ class SplashActivityTest { simulateAppAlreadyOnboarded() initializeTestApplication() - activityTestRule.launchActivity(null) - testCoroutineDispatchers.advanceUntilIdle() - - intended(hasComponent(ProfileChooserActivity::class.java.name)) + launchSplashActivityFully { + intended(hasComponent(ProfileChooserActivity::class.java.name)) + } } @Test @@ -185,26 +194,24 @@ class SplashActivityTest { setAutoAppExpirationEnabled(enabled = true) setAutoAppExpirationDate(dateStringAfterToday()) - activityTestRule.launchActivity(null) - testCoroutineDispatchers.advanceUntilIdle() - - // App deprecation is enabled, but this app hasn't yet expired. - intended(hasComponent(OnboardingActivity::class.java.name)) + launchSplashActivityFully { + // App deprecation is enabled, but this app hasn't yet expired. + intended(hasComponent(OnboardingActivity::class.java.name)) + } } @Test - fun testOpenApp_initial_expirationEnabled_afterExpDate_intentsToDeprecationDialog() { + fun testOpenApp_initial_expirationEnabled_afterExpDate_showsDeprecationDialog() { initializeTestApplication() setAutoAppExpirationEnabled(enabled = true) setAutoAppExpirationDate(dateStringBeforeToday()) - activityTestRule.launchActivity(null) - testCoroutineDispatchers.advanceUntilIdle() - - // The current app is expired. - onView(withText(R.string.unsupported_app_version_dialog_title)) - .inRoot(isDialog()) - .check(matches(isDisplayed())) + launchSplashActivityFully { + // The current app is expired. + onView(withText(R.string.unsupported_app_version_dialog_title)) + .inRoot(isDialog()) + .check(matches(isDisplayed())) + } } @Test @@ -212,61 +219,60 @@ class SplashActivityTest { initializeTestApplication() setAutoAppExpirationEnabled(enabled = true) setAutoAppExpirationDate(dateStringBeforeToday()) - activityTestRule.launchActivity(null) - testCoroutineDispatchers.advanceUntilIdle() - onView(withText(R.string.unsupported_app_version_dialog_close_button_text)) - .inRoot(isDialog()) - .perform(click()) - testCoroutineDispatchers.advanceUntilIdle() + launchSplashActivityFully { scenario -> + onView(withText(R.string.unsupported_app_version_dialog_close_button_text)) + .inRoot(isDialog()) + .perform(click()) + testCoroutineDispatchers.advanceUntilIdle() - // Closing the dialog should close the activity (and thus, the app). - assertThat(activityTestRule.activity.isFinishing).isTrue() + scenario.onActivity { activity -> + // Closing the dialog should close the activity (and thus, the app). + assertThat(activity.isFinishing).isTrue() + } + } } @Test - fun testOpenApp_initial_expirationDisabled_afterExpDate_intentsToOnboardingFlow() { + fun testOpenApp_initial_expirationDisabled_afterExpDate_showsOnboardingFlow() { initializeTestApplication() setAutoAppExpirationEnabled(enabled = false) setAutoAppExpirationDate(dateStringBeforeToday()) - activityTestRule.launchActivity(null) - testCoroutineDispatchers.advanceUntilIdle() - - // The app is technically deprecated, but because the deprecation check is disabled the - // onboarding flow should be shown, instead. - intended(hasComponent(OnboardingActivity::class.java.name)) + launchSplashActivityFully { + // The app is technically deprecated, but because the deprecation check is disabled the + // onboarding flow should be shown, instead. + intended(hasComponent(OnboardingActivity::class.java.name)) + } } @Test - fun testOpenApp_reopen_onboarded_expirationEnabled_beforeExpDate_intentsToProfileChooser() { + fun testOpenApp_reopen_onboarded_expirationEnabled_beforeExpDate_routesToProfileChooser() { simulateAppAlreadyOnboarded() initializeTestApplication() setAutoAppExpirationEnabled(enabled = true) setAutoAppExpirationDate(dateStringAfterToday()) - activityTestRule.launchActivity(null) - testCoroutineDispatchers.advanceUntilIdle() - - // Reopening the app before it's expired should result in the profile activity showing since the - // user has already been onboarded. - intended(hasComponent(ProfileChooserActivity::class.java.name)) + launchSplashActivityFully { + // Reopening the app before it's expired should result in the profile activity showing since + // the user has already been onboarded. + intended(hasComponent(ProfileChooserActivity::class.java.name)) + } } @Test - fun testOpenApp_reopen_onboarded_expirationEnabled_afterExpDate_intentsToDeprecationDialog() { + fun testOpenApp_reopen_onboarded_expirationEnabled_afterExpDate_showsToDeprecationDialog() { simulateAppAlreadyOnboarded() initializeTestApplication() setAutoAppExpirationEnabled(enabled = true) setAutoAppExpirationDate(dateStringBeforeToday()) - activityTestRule.launchActivity(null) - testCoroutineDispatchers.advanceUntilIdle() - - // Reopening the app after it expires should prevent further access. - onView(withText(R.string.unsupported_app_version_dialog_title)) - .inRoot(isDialog()) - .check(matches(isDisplayed())) + launchSplashActivityFully { + // Reopening the app after it expires should prevent further access. + onView(withText(R.string.unsupported_app_version_dialog_title)) + .inRoot(isDialog()) + .check(matches(isDisplayed())) + } } @Test @@ -275,20 +281,19 @@ class SplashActivityTest { initializeTestApplication() forceDefaultLocale(Locale.ENGLISH) - activityTestRule.launchActivity(null) - testCoroutineDispatchers.advanceUntilIdle() - - // Verify that the locale is initialized (i.e. getDisplayLocale doesn't throw an exception) & - // that the correct display locale is defined per the system locale. - val displayLocale = appLanguageLocaleHandler.getDisplayLocale() - val context = displayLocale.localeContext - assertThat(context.languageDefinition.language).isEqualTo(ENGLISH) - assertThat(context.languageDefinition.minAndroidSdkVersion).isEqualTo(1) - assertThat(context.languageDefinition.appStringId.ietfBcp47Id.ietfLanguageTag).isEqualTo("en") - assertThat(context.hasFallbackLanguageDefinition()).isFalse() - assertThat(context.regionDefinition.region).isEqualTo(OppiaRegion.REGION_UNSPECIFIED) - assertThat(context.regionDefinition.regionId.ietfRegionTag).isEqualTo("") - assertThat(context.usageMode).isEqualTo(OppiaLocaleContext.LanguageUsageMode.APP_STRINGS) + launchSplashActivityFully { + // Verify that the locale is initialized (i.e. getDisplayLocale doesn't throw an exception) & + // that the correct display locale is defined per the system locale. + val displayLocale = appLanguageLocaleHandler.getDisplayLocale() + val context = displayLocale.localeContext + assertThat(context.languageDefinition.language).isEqualTo(ENGLISH) + assertThat(context.languageDefinition.minAndroidSdkVersion).isEqualTo(1) + assertThat(context.languageDefinition.appStringId.ietfBcp47Id.ietfLanguageTag).isEqualTo("en") + assertThat(context.hasFallbackLanguageDefinition()).isFalse() + assertThat(context.regionDefinition.region).isEqualTo(OppiaRegion.REGION_UNSPECIFIED) + assertThat(context.regionDefinition.regionId.ietfRegionTag).isEqualTo("") + assertThat(context.usageMode).isEqualTo(OppiaLocaleContext.LanguageUsageMode.APP_STRINGS) + } } @Test @@ -297,14 +302,13 @@ class SplashActivityTest { initializeTestApplication() forceDefaultLocale(EGYPT_ARABIC_LOCALE) - activityTestRule.launchActivity(null) - testCoroutineDispatchers.advanceUntilIdle() - - // Verify that the locale is initialized (i.e. getDisplayLocale doesn't throw an exception) & - // that the correct display locale is defined per the system locale. - val displayLocale = appLanguageLocaleHandler.getDisplayLocale() - val context = displayLocale.localeContext - assertThat(context.languageDefinition.language).isEqualTo(ARABIC) + launchSplashActivityFully { + // Verify that the locale is initialized (i.e. getDisplayLocale doesn't throw an exception) & + // that the correct display locale is defined per the system locale. + val displayLocale = appLanguageLocaleHandler.getDisplayLocale() + val context = displayLocale.localeContext + assertThat(context.languageDefinition.language).isEqualTo(ARABIC) + } } @Test @@ -313,14 +317,13 @@ class SplashActivityTest { initializeTestApplication() forceDefaultLocale(BRAZIL_PORTUGUESE_LOCALE) - activityTestRule.launchActivity(null) - testCoroutineDispatchers.advanceUntilIdle() - - // Verify that the locale is initialized (i.e. getDisplayLocale doesn't throw an exception) & - // that the correct display locale is defined per the system locale. - val displayLocale = appLanguageLocaleHandler.getDisplayLocale() - val context = displayLocale.localeContext - assertThat(context.languageDefinition.language).isEqualTo(BRAZILIAN_PORTUGUESE) + launchSplashActivityFully { + // Verify that the locale is initialized (i.e. getDisplayLocale doesn't throw an exception) & + // that the correct display locale is defined per the system locale. + val displayLocale = appLanguageLocaleHandler.getDisplayLocale() + val context = displayLocale.localeContext + assertThat(context.languageDefinition.language).isEqualTo(BRAZILIAN_PORTUGUESE) + } } @Test @@ -328,73 +331,701 @@ class SplashActivityTest { initializeTestApplication() forceDefaultLocale(TURKEY_TURKISH_LOCALE) - activityTestRule.launchActivity(null) - testCoroutineDispatchers.advanceUntilIdle() - - // Verify that the context is the default state (due to the unsupported locale). - val displayLocale = appLanguageLocaleHandler.getDisplayLocale() - val languageDefinition = displayLocale.localeContext.languageDefinition - assertThat(languageDefinition.language).isEqualTo(LANGUAGE_UNSPECIFIED) - assertThat(languageDefinition.minAndroidSdkVersion).isEqualTo(1) - assertThat(languageDefinition.appStringId.ietfBcp47Id.ietfLanguageTag).isEqualTo("tr-TR") + launchSplashActivityFully { + // Verify that the context is the default state (due to the unsupported locale). + val displayLocale = appLanguageLocaleHandler.getDisplayLocale() + val languageDefinition = displayLocale.localeContext.languageDefinition + assertThat(languageDefinition.language).isEqualTo(LANGUAGE_UNSPECIFIED) + assertThat(languageDefinition.minAndroidSdkVersion).isEqualTo(1) + assertThat(languageDefinition.appStringId.ietfBcp47Id.ietfLanguageTag).isEqualTo("tr-TR") + } } @Test - @RunOn(TestPlatform.ROBOLECTRIC) + @RunOn(TestPlatform.ROBOLECTRIC, buildEnvironments = [BuildEnvironment.BAZEL]) fun testSplashActivity_initializationFailure_initializesLocaleHandlerWithDefaultContext() { corruptCacheFile() initializeTestApplication() - activityTestRule.launchActivity(null) - testCoroutineDispatchers.advanceUntilIdle() - - // Verify that the context is the default state (due to the unsupported locale). - val displayLocale = appLanguageLocaleHandler.getDisplayLocale() - val context = displayLocale.localeContext - assertThat(context.languageDefinition.language).isEqualTo(ENGLISH) - assertThat(context.languageDefinition.minAndroidSdkVersion).isEqualTo(1) - assertThat(context.languageDefinition.appStringId.ietfBcp47Id.ietfLanguageTag).isEqualTo("en") - assertThat(context.hasFallbackLanguageDefinition()).isFalse() - assertThat(context.regionDefinition.region).isEqualTo(OppiaRegion.UNITED_STATES) - assertThat(context.regionDefinition.regionId.ietfRegionTag).isEqualTo("US") - assertThat(context.usageMode).isEqualTo(OppiaLocaleContext.LanguageUsageMode.APP_STRINGS) + launchSplashActivityFully { + // Verify that the context is the default state (due to the unsupported locale). + val displayLocale = appLanguageLocaleHandler.getDisplayLocale() + val context = displayLocale.localeContext + assertThat(context.languageDefinition.language).isEqualTo(ENGLISH) + assertThat(context.languageDefinition.minAndroidSdkVersion).isEqualTo(1) + assertThat(context.languageDefinition.appStringId.ietfBcp47Id.ietfLanguageTag).isEqualTo("en") + assertThat(context.hasFallbackLanguageDefinition()).isFalse() + assertThat(context.regionDefinition.region).isEqualTo(OppiaRegion.UNITED_STATES) + assertThat(context.regionDefinition.regionId.ietfRegionTag).isEqualTo("US") + assertThat(context.usageMode).isEqualTo(OppiaLocaleContext.LanguageUsageMode.APP_STRINGS) + } } @Test - @RunOn(TestPlatform.ROBOLECTRIC) - fun testSplashActivity_initializationFailure_logsError() { - // Simulate a corrupted cache file to trigger an initialization failure. + fun testSplashActivity_initializationFailure_routesToOnboardingActivity() { corruptCacheFile() initializeTestApplication() - activityTestRule.launchActivity(null) - testCoroutineDispatchers.advanceUntilIdle() - - val logs = getShadowLogsOnRobolectric() - assertThat(logs.any { it.contains("Failed to compute initial state") }).isTrue() + launchSplashActivityFully { + // Verify that an initialization failure leads to the onboarding activity by default. + intended(hasComponent(OnboardingActivity::class.java.name)) + } } @Test - fun testSplashActivity_initializationFailure_routesToOnboardingActivity() { - corruptCacheFile() + fun testSplashActivity_hasCorrectActivityLabel() { initializeTestApplication() - activityTestRule.launchActivity(null) - testCoroutineDispatchers.advanceUntilIdle() + launchSplashActivityFully { scenario -> + scenario.onActivity { activity -> + val title = activity.title - // Verify that an initialization failure leads to the onboarding activity by default. - intended(hasComponent(OnboardingActivity::class.java.name)) + assertThat(title).isEqualTo(context.getString(R.string.app_name)) + } + } } @Test - fun testSplashActivity_hasCorrectActivityLabel() { - initializeTestApplication() + fun testSplashActivity_newUser_firstTimeOpeningBetaFlavor_doesNotShowBetaNotice() { + initializeTestApplicationWithFlavor(BuildFlavor.BETA) - activityTestRule.launchActivity(null) - testCoroutineDispatchers.advanceUntilIdle() + launchSplashActivityFully { + // Verify that the beta notice does not open (since there wasn't a version change). + onView(withId(R.id.beta_notice_dialog_message)).check(doesNotExist()) + } + } + + @Test + @RunParameterized( + Iteration("testing_to_beta", "firstOpen=TESTING", "secondOpen=BETA"), + Iteration("dev_to_beta", "firstOpen=DEVELOPER", "secondOpen=BETA"), + Iteration("alpha_to_beta", "firstOpen=ALPHA", "secondOpen=BETA"), + Iteration("ga_to_beta", "firstOpen=GENERAL_AVAILABILITY", "secondOpen=BETA") + ) + fun testSplashActivity_newUser_betaFlavorTransitions_showsBetaNotice() { + simulateAppAlreadyOpenedWithFlavor(firstOpenFlavor) + + initializeTestApplicationWithFlavor(secondOpenFlavor) + + launchSplashActivityFully { + onDialogView(withText(R.string.beta_notice_dialog_title)).check(matches(isDisplayed())) + onDialogView(withId(R.id.beta_notice_dialog_message)).check(matches(isDisplayed())) + } + } + + @Test + @RunParameterized( + Iteration("testing_to_beta", "firstOpen=TESTING", "secondOpen=BETA"), + Iteration("dev_to_beta", "firstOpen=DEVELOPER", "secondOpen=BETA"), + Iteration("alpha_to_beta", "firstOpen=ALPHA", "secondOpen=BETA"), + Iteration("ga_to_beta", "firstOpen=GENERAL_AVAILABILITY", "secondOpen=BETA") + ) + fun testSplashActivity_newUser_betaFlavorTransitions_closeNotice_routesToOnboardingFlow() { + simulateAppAlreadyOpenedWithFlavor(firstOpenFlavor) + initializeTestApplicationWithFlavor(secondOpenFlavor) + + launchSplashActivityFully { + // Close the notice. + onDialogView(withText(R.string.beta_notice_dialog_close_button_text)).perform(click()) + testCoroutineDispatchers.runCurrent() + + // The user should be routed to the onboarding flow after seeing the beta notice. + intended(hasComponent(OnboardingActivity::class.java.name)) + } + } + + @Test + @RunParameterized( + Iteration("testing_to_beta", "firstOpen=TESTING", "secondOpen=BETA"), + Iteration("dev_to_beta", "firstOpen=DEVELOPER", "secondOpen=BETA"), + Iteration("alpha_to_beta", "firstOpen=ALPHA", "secondOpen=BETA"), + Iteration("ga_to_beta", "firstOpen=GENERAL_AVAILABILITY", "secondOpen=BETA") + ) + fun testSplashActivity_newUser_betaFlavorTransitions_doNotShowAgain_routesToOnboardingFlow() { + simulateAppAlreadyOpenedWithFlavor(firstOpenFlavor) + initializeTestApplicationWithFlavor(secondOpenFlavor) + + launchSplashActivityFully { + // Close the notice after selecting to never show it again. + onDialogView(withId(R.id.beta_notice_dialog_preference_checkbox)).perform(click()) + onDialogView(withText(R.string.beta_notice_dialog_close_button_text)).perform(click()) + testCoroutineDispatchers.runCurrent() + + // The user should be routed to the onboarding flow after seeing the beta notice. + intended(hasComponent(OnboardingActivity::class.java.name)) + } + } + + @Test + fun testSplashActivity_newUser_dismissBetaNotice_reopenApp_doesNotShowNotice() { + // Open the app in beta notice mode, then dismiss the notice. + simulateAppAlreadyOpenedWithFlavor(BuildFlavor.ALPHA) + initializeTestApplicationWithFlavor(BuildFlavor.BETA) + launchSplashActivityFully { + onDialogView(withText(R.string.beta_notice_dialog_close_button_text)).perform(click()) + testCoroutineDispatchers.runCurrent() + } + + // Note this is a different "recreation" than other tests since the same instrumentation + // process needs to be preserved for Espresso to work correctly. + recreateExistingApplication() + + launchSplashActivityFully { + // The user should be routed to the onboarding flow after seeing the beta notice. + onView(withId(R.id.beta_notice_dialog_message)).check(doesNotExist()) + intended(hasComponent(OnboardingActivity::class.java.name)) + } + } + + @Test + fun testSplashActivity_newUser_dismissBetaNotice_retriggerNotice_showsBetaNotice() { + // Open the app in beta notice mode, then dismiss the notice. + simulateAppAlreadyOpenedWithFlavor(BuildFlavor.ALPHA) + initializeTestApplicationWithFlavor(BuildFlavor.BETA) + launchSplashActivityFully { + onDialogView(withText(R.string.beta_notice_dialog_close_button_text)).perform(click()) + testCoroutineDispatchers.runCurrent() + } + + // "Retrigger" the notice by switching flavors again, then "recreate" the existing application + // so that new states can be observed. + simulateAppAlreadyOpenedWithFlavor(BuildFlavor.ALPHA) + recreateExistingApplicationWithFlavor(BuildFlavor.BETA) + + launchSplashActivityFully { + // The user should see the beta notice again despite dismissing it since the beta notice + // condition again occurred. + onDialogView(withText(R.string.beta_notice_dialog_title)).check(matches(isDisplayed())) + onDialogView(withId(R.id.beta_notice_dialog_message)).check(matches(isDisplayed())) + } + } + + @Test + fun testSplashActivity_newUser_dismissBetaNoticeForever_retriggerNotice_doesNotShowNotice() { + // Open the app in beta notice mode, then dismiss the notice permanently. + simulateAppAlreadyOpenedWithFlavor(BuildFlavor.ALPHA) + initializeTestApplicationWithFlavor(BuildFlavor.BETA) + launchSplashActivityFully { + onDialogView(withId(R.id.beta_notice_dialog_preference_checkbox)).perform(click()) + onDialogView(withText(R.string.beta_notice_dialog_close_button_text)).perform(click()) + testCoroutineDispatchers.runCurrent() + } + + // "Retrigger" the notice by switching flavors again, then "recreate" the existing application + // so that new states can be observed. + simulateAppAlreadyOpenedWithFlavor(BuildFlavor.ALPHA) + recreateExistingApplicationWithFlavor(BuildFlavor.BETA) + + launchSplashActivityFully { + // The user should not see the beta notice again even though they changed flavors since they + // opted to permanently disable the notice. + onView(withId(R.id.beta_notice_dialog_message)).check(doesNotExist()) + intended(hasComponent(OnboardingActivity::class.java.name)) + } + } + + @Test + fun testSplashActivity_newUser_firstTimeOpeningGaFlavor_doesNotShowGaUpgradeNotice() { + initializeTestApplicationWithFlavor(BuildFlavor.GENERAL_AVAILABILITY) + + launchSplashActivityFully { + // Verify that the GA notice does not open (since there wasn't an upgrade). + onView(withId(R.id.ga_update_notice_dialog_message)).check(doesNotExist()) + } + } + + @Test + @RunParameterized( + Iteration("alpha_to_ga", "firstOpen=ALPHA", "secondOpen=GENERAL_AVAILABILITY"), + Iteration("beta_to_ga", "firstOpen=BETA", "secondOpen=GENERAL_AVAILABILITY") + ) + fun testSplashActivity_onboarded_gaFlavorTransitions_showsGaUpgradeNotice() { + simulateAppAlreadyOnboardedWithFlavor(firstOpenFlavor) + + initializeTestApplicationWithFlavor(secondOpenFlavor) + + launchSplashActivityFully { + onDialogView(withText(R.string.general_availability_notice_dialog_title)) + .check(matches(isDisplayed())) + onDialogView(withId(R.id.ga_update_notice_dialog_message)).check(matches(isDisplayed())) + } + } + + @Test + @RunParameterized( + Iteration("alpha_to_ga", "firstOpen=ALPHA", "secondOpen=GENERAL_AVAILABILITY"), + Iteration("beta_to_ga", "firstOpen=BETA", "secondOpen=GENERAL_AVAILABILITY") + ) + fun testSplashActivity_onboarded_gaFlavorTransitions_closeNotice_routesToProfileChooser() { + simulateAppAlreadyOnboardedWithFlavor(firstOpenFlavor) + initializeTestApplicationWithFlavor(secondOpenFlavor) + + launchSplashActivityFully { + // Close the notice. + onDialogView(withText(R.string.general_availability_notice_dialog_close_button_text)) + .perform(click()) + testCoroutineDispatchers.runCurrent() + + // The user should be routed to the profile chooser after seeing the GA upgrade notice. + intended(hasComponent(ProfileChooserActivity::class.java.name)) + } + } + + @Test + @RunParameterized( + Iteration("alpha_to_ga", "firstOpen=ALPHA", "secondOpen=GENERAL_AVAILABILITY"), + Iteration("beta_to_ga", "firstOpen=BETA", "secondOpen=GENERAL_AVAILABILITY") + ) + fun testSplashActivity_onboarded_gaFlavorTransitions_doNotShowAgain_routesToProfileChooser() { + simulateAppAlreadyOnboardedWithFlavor(firstOpenFlavor) + initializeTestApplicationWithFlavor(secondOpenFlavor) + + launchSplashActivityFully { + // Close the notice after selecting to never show it again. + onDialogView(withId(R.id.ga_update_notice_dialog_preference_checkbox)).perform(click()) + onDialogView(withText(R.string.general_availability_notice_dialog_close_button_text)) + .perform(click()) + testCoroutineDispatchers.runCurrent() + + // The user should be routed to the profile chooser after seeing the GA upgrade notice. + intended(hasComponent(ProfileChooserActivity::class.java.name)) + } + } + + @Test + fun testSplashActivity_onboarded_dismissGaNotice_reopenApp_doesNotShowNotice() { + // Open the app in GA upgrade mode, then dismiss the notice. + simulateAppAlreadyOnboardedWithFlavor(BuildFlavor.BETA) + initializeTestApplicationWithFlavor(BuildFlavor.GENERAL_AVAILABILITY) + launchSplashActivityFully { + onDialogView(withText(R.string.general_availability_notice_dialog_close_button_text)) + .perform(click()) + testCoroutineDispatchers.runCurrent() + } + + // Note this is a different "recreation" than other tests since the same instrumentation + // process needs to be preserved for Espresso to work correctly. + recreateExistingApplication() + + launchSplashActivityFully { + // The user should be routed to the profile chooser after seeing the GA upgrade notice. + onView(withId(R.id.ga_update_notice_dialog_message)).check(doesNotExist()) + intended(hasComponent(ProfileChooserActivity::class.java.name)) + } + } + + @Test + fun testSplashActivity_onboarded_dismissGaNotice_retriggerNotice_showsGaNotice() { + // Open the app in GA upgrade mode, then dismiss the notice. + simulateAppAlreadyOpenedWithFlavor(BuildFlavor.ALPHA) + initializeTestApplicationWithFlavor(BuildFlavor.BETA) + launchSplashActivityFully { + onDialogView(withText(R.string.general_availability_notice_dialog_close_button_text)) + .perform(click()) + testCoroutineDispatchers.runCurrent() + } + + // "Retrigger" the notice by switching flavors again, then "recreate" the existing application + // so that new states can be observed. + simulateAppAlreadyOpenedWithFlavor(BuildFlavor.ALPHA) + recreateExistingApplicationWithFlavor(BuildFlavor.GENERAL_AVAILABILITY) + + launchSplashActivityFully { + // The user should see the GA upgrade notice again despite dismissing it since the notice + // condition again occurred. + onDialogView(withText(R.string.general_availability_notice_dialog_title)) + .check(matches(isDisplayed())) + onDialogView(withId(R.id.ga_update_notice_dialog_message)).check(matches(isDisplayed())) + } + } + + @Test + fun testSplashActivity_onboarded_dismissGaNoticeForever_retriggerNotice_doesNotShowNotice() { + // Open the app in GA upgrade mode, then dismiss the notice permanently. + simulateAppAlreadyOpenedWithFlavor(BuildFlavor.BETA) + initializeTestApplicationWithFlavor(BuildFlavor.GENERAL_AVAILABILITY) + launchSplashActivityFully { + onDialogView(withId(R.id.ga_update_notice_dialog_preference_checkbox)).perform(click()) + onDialogView(withText(R.string.general_availability_notice_dialog_close_button_text)) + .perform(click()) + testCoroutineDispatchers.runCurrent() + } + + // "Retrigger" the notice by switching flavors again, then "recreate" the existing application + // so that new states can be observed. + simulateAppAlreadyOpenedWithFlavor(BuildFlavor.ALPHA) + recreateExistingApplicationWithFlavor(BuildFlavor.GENERAL_AVAILABILITY) + + launchSplashActivityFully { + // The user should not see the GA upgrade notice again even though they changed flavors since + // they opted to permanently disable the notice. + onView(withId(R.id.ga_update_notice_dialog_message)).check(doesNotExist()) + intended(hasComponent(OnboardingActivity::class.java.name)) + } + } + + @Test + fun testSplashActivity_newUser_betaNoticeConditionsThenGa_showsGaNotice() { + // Simulate a beta notice first. + simulateAppAlreadyOpenedWithFlavor(BuildFlavor.ALPHA) + simulateAppAlreadyOpenedWithFlavor(BuildFlavor.BETA) + + // Then simulate the GA upgrade notice. + initializeTestApplicationWithFlavor(BuildFlavor.GENERAL_AVAILABILITY) + + // The GA upgrade notice should be the one to show since it's more recent. + launchSplashActivityFully { + onDialogView(withText(R.string.general_availability_notice_dialog_title)) + .check(matches(isDisplayed())) + onDialogView(withId(R.id.ga_update_notice_dialog_message)).check(matches(isDisplayed())) + } + } + + @Test + fun testSplashActivity_newUser_betaNoticeConditionsThenGa_gaDisabled_showsNoNotice() { + // First, disable the GA notice, then trigger a beta notice. + simulateAppAlreadyOpenedWithFlavor(BuildFlavor.ALPHA) + initializeTestApplicationWithFlavor(BuildFlavor.GENERAL_AVAILABILITY) + launchSplashActivityFully { + onDialogView(withId(R.id.ga_update_notice_dialog_preference_checkbox)).perform(click()) + onDialogView(withText(R.string.general_availability_notice_dialog_close_button_text)) + .perform(click()) + testCoroutineDispatchers.runCurrent() + } + reopenAppWithNewFlavor(BuildFlavor.BETA) + + // Then simulate the GA upgrade notice. + reopenAppWithNewFlavor(BuildFlavor.GENERAL_AVAILABILITY) - val title = activityTestRule.activity.title - assertThat(title).isEqualTo(context.getString(R.string.app_name)) + // No notice should show since the GA upgrade notice would normally show, but it's been + // permanently disabled. + launchSplashActivityFully { + onView(withId(R.id.beta_notice_dialog_message)).check(doesNotExist()) + onView(withId(R.id.ga_update_notice_dialog_message)).check(doesNotExist()) + } + } + + @Test + fun testSplashActivity_newUser_gaNoticeConditionsThenBeta_showsBetaNotice() { + // Simulate a GA upgrade notice first. + simulateAppAlreadyOpenedWithFlavor(BuildFlavor.BETA) + simulateAppAlreadyOpenedWithFlavor(BuildFlavor.GENERAL_AVAILABILITY) + + // Then simulate the beta notice. + initializeTestApplicationWithFlavor(BuildFlavor.BETA) + + // The beta notice should be the one to show since it's more recent. + launchSplashActivityFully { + onDialogView(withText(R.string.beta_notice_dialog_title)).check(matches(isDisplayed())) + onDialogView(withId(R.id.beta_notice_dialog_message)).check(matches(isDisplayed())) + } + } + + @Test + fun testSplashActivity_newUser_gaNoticeConditionsThenBeta_betaDisabled_showsNoNotice() { + // First, disable the beta notice, then trigger a GA upgrade notice. + simulateAppAlreadyOpenedWithFlavor(BuildFlavor.ALPHA) + initializeTestApplicationWithFlavor(BuildFlavor.BETA) + launchSplashActivityFully { + onDialogView(withId(R.id.beta_notice_dialog_preference_checkbox)).perform(click()) + onDialogView(withText(R.string.beta_notice_dialog_close_button_text)).perform(click()) + testCoroutineDispatchers.runCurrent() + } + reopenAppWithNewFlavor(BuildFlavor.GENERAL_AVAILABILITY) + + // Then simulate the beta notice. + reopenAppWithNewFlavor(BuildFlavor.BETA) + + // No notice should show since the beta notice would normally show, but it's been permanently + // disabled. + launchSplashActivityFully { + onView(withId(R.id.beta_notice_dialog_message)).check(doesNotExist()) + onView(withId(R.id.ga_update_notice_dialog_message)).check(doesNotExist()) + } + } + + @Test + @RunParameterized( + Iteration("testing_to_testing", "firstOpen=TESTING", "secondOpen=TESTING"), + Iteration("testing_to_dev", "firstOpen=TESTING", "secondOpen=DEVELOPER"), + Iteration("testing_to_alpha", "firstOpen=TESTING", "secondOpen=ALPHA"), + Iteration("testing_to_ga", "firstOpen=TESTING", "secondOpen=GENERAL_AVAILABILITY"), + Iteration("dev_to_testing", "firstOpen=DEVELOPER", "secondOpen=TESTING"), + Iteration("dev_to_dev", "firstOpen=DEVELOPER", "secondOpen=DEVELOPER"), + Iteration("dev_to_alpha", "firstOpen=DEVELOPER", "secondOpen=ALPHA"), + Iteration("dev_to_ga", "firstOpen=DEVELOPER", "secondOpen=GENERAL_AVAILABILITY"), + Iteration("alpha_to_testing", "firstOpen=ALPHA", "secondOpen=TESTING"), + Iteration("alpha_to_dev", "firstOpen=ALPHA", "secondOpen=DEVELOPER"), + Iteration("alpha_to_alpha", "firstOpen=ALPHA", "secondOpen=ALPHA"), + Iteration("beta_to_testing", "firstOpen=BETA", "secondOpen=TESTING"), + Iteration("beta_to_dev", "firstOpen=BETA", "secondOpen=DEVELOPER"), + Iteration("beta_to_alpha", "firstOpen=BETA", "secondOpen=ALPHA"), + Iteration("beta_to_beta", "firstOpen=BETA", "secondOpen=BETA"), + Iteration("ga_to_testing", "firstOpen=GENERAL_AVAILABILITY", "secondOpen=TESTING"), + Iteration("ga_to_dev", "firstOpen=GENERAL_AVAILABILITY", "secondOpen=DEVELOPER"), + Iteration("ga_to_alpha", "firstOpen=GENERAL_AVAILABILITY", "secondOpen=ALPHA"), + Iteration("ga_to_ga", "firstOpen=GENERAL_AVAILABILITY", "secondOpen=GENERAL_AVAILABILITY") + ) + fun testSplashActivity_newUser_ignoredFlavorTransitions_routesToOnboardingFlow() { + simulateAppAlreadyOpenedWithFlavor(firstOpenFlavor) + + initializeTestApplicationWithFlavor(secondOpenFlavor) + + launchSplashActivityFully { + // The user should be immediately routed to the onboarding flow since this flavor transition + // does not trigger a notice. + intended(hasComponent(OnboardingActivity::class.java.name)) + } + } + + @Test + @RunParameterized( + Iteration("testing_to_testing", "firstOpen=TESTING", "secondOpen=TESTING"), + Iteration("testing_to_dev", "firstOpen=TESTING", "secondOpen=DEVELOPER"), + Iteration("testing_to_alpha", "firstOpen=TESTING", "secondOpen=ALPHA"), + Iteration("testing_to_ga", "firstOpen=TESTING", "secondOpen=GENERAL_AVAILABILITY"), + Iteration("dev_to_testing", "firstOpen=DEVELOPER", "secondOpen=TESTING"), + Iteration("dev_to_dev", "firstOpen=DEVELOPER", "secondOpen=DEVELOPER"), + Iteration("dev_to_alpha", "firstOpen=DEVELOPER", "secondOpen=ALPHA"), + Iteration("dev_to_ga", "firstOpen=DEVELOPER", "secondOpen=GENERAL_AVAILABILITY"), + Iteration("alpha_to_testing", "firstOpen=ALPHA", "secondOpen=TESTING"), + Iteration("alpha_to_dev", "firstOpen=ALPHA", "secondOpen=DEVELOPER"), + Iteration("alpha_to_alpha", "firstOpen=ALPHA", "secondOpen=ALPHA"), + Iteration("beta_to_testing", "firstOpen=BETA", "secondOpen=TESTING"), + Iteration("beta_to_dev", "firstOpen=BETA", "secondOpen=DEVELOPER"), + Iteration("beta_to_alpha", "firstOpen=BETA", "secondOpen=ALPHA"), + Iteration("beta_to_beta", "firstOpen=BETA", "secondOpen=BETA"), + Iteration("ga_to_testing", "firstOpen=GENERAL_AVAILABILITY", "secondOpen=TESTING"), + Iteration("ga_to_dev", "firstOpen=GENERAL_AVAILABILITY", "secondOpen=DEVELOPER"), + Iteration("ga_to_alpha", "firstOpen=GENERAL_AVAILABILITY", "secondOpen=ALPHA"), + Iteration("ga_to_ga", "firstOpen=GENERAL_AVAILABILITY", "secondOpen=GENERAL_AVAILABILITY") + ) + fun testSplashActivity_onboarded_ignoredFlavorTransitions_routesToProfileChooser() { + simulateAppAlreadyOnboardedWithFlavor(firstOpenFlavor) + + initializeTestApplicationWithFlavor(secondOpenFlavor) + + launchSplashActivityFully { + // The user should be immediately routed to the profile chooser since this flavor transition + // does not trigger a notice. + intended(hasComponent(ProfileChooserActivity::class.java.name)) + } + } + + @Test + @RunParameterized( + Iteration("testing_to_testing", "firstOpen=TESTING", "secondOpen=TESTING"), + Iteration("testing_to_dev", "firstOpen=TESTING", "secondOpen=DEVELOPER"), + Iteration("testing_to_alpha", "firstOpen=TESTING", "secondOpen=ALPHA"), + Iteration("testing_to_beta", "firstOpen=TESTING", "secondOpen=BETA"), + Iteration("testing_to_ga", "firstOpen=TESTING", "secondOpen=GENERAL_AVAILABILITY"), + Iteration("dev_to_testing", "firstOpen=DEVELOPER", "secondOpen=TESTING"), + Iteration("dev_to_dev", "firstOpen=DEVELOPER", "secondOpen=DEVELOPER"), + Iteration("dev_to_alpha", "firstOpen=DEVELOPER", "secondOpen=ALPHA"), + Iteration("dev_to_beta", "firstOpen=DEVELOPER", "secondOpen=BETA"), + Iteration("dev_to_ga", "firstOpen=DEVELOPER", "secondOpen=GENERAL_AVAILABILITY"), + Iteration("alpha_to_testing", "firstOpen=ALPHA", "secondOpen=TESTING"), + Iteration("alpha_to_dev", "firstOpen=ALPHA", "secondOpen=DEVELOPER"), + Iteration("alpha_to_alpha", "firstOpen=ALPHA", "secondOpen=ALPHA"), + Iteration("alpha_to_beta", "firstOpen=ALPHA", "secondOpen=BETA"), + Iteration("alpha_to_ga", "firstOpen=ALPHA", "secondOpen=GENERAL_AVAILABILITY"), + Iteration("beta_to_testing", "firstOpen=BETA", "secondOpen=TESTING"), + Iteration("beta_to_dev", "firstOpen=BETA", "secondOpen=DEVELOPER"), + Iteration("beta_to_alpha", "firstOpen=BETA", "secondOpen=ALPHA"), + Iteration("beta_to_beta", "firstOpen=BETA", "secondOpen=BETA"), + Iteration("beta_to_ga", "firstOpen=BETA", "secondOpen=GENERAL_AVAILABILITY"), + Iteration("ga_to_testing", "firstOpen=GENERAL_AVAILABILITY", "secondOpen=TESTING"), + Iteration("ga_to_dev", "firstOpen=GENERAL_AVAILABILITY", "secondOpen=DEVELOPER"), + Iteration("ga_to_alpha", "firstOpen=GENERAL_AVAILABILITY", "secondOpen=ALPHA"), + Iteration("ga_to_beta", "firstOpen=GENERAL_AVAILABILITY", "secondOpen=BETA"), + Iteration("ga_to_ga", "firstOpen=GENERAL_AVAILABILITY", "secondOpen=GENERAL_AVAILABILITY") + ) + fun testSplashActivity_appDeprecated_allFlavorTransitions_showsDeprecationNotice() { + simulateAppAlreadyOnboardedWithFlavor(firstOpenFlavor) + + initializeTestApplicationWithFlavor(secondOpenFlavor) + setAutoAppExpirationEnabled(enabled = true) + setAutoAppExpirationDate(dateStringBeforeToday()) + + // The current app is expired, so the deprecation notice should show regardless of the build + // flavor notices that would normally show. + launchSplashActivityFully { + onView(withText(R.string.unsupported_app_version_dialog_title)) + .inRoot(isDialog()) + .check(matches(isDisplayed())) + } + } + + @Test + fun testSplashActivity_onboarded_testingFlavor_showsNoFlavorText() { + simulateAppAlreadyOnboardedWithFlavor(BuildFlavor.TESTING) + + initializeTestApplicationWithFlavor(BuildFlavor.TESTING) + + // No label should show for this version of the app since it's meant to simulate the GA flavor. + launchSplashActivityFully { + onView(withId(R.id.build_flavor_label)).check(matches(not(isDisplayed()))) + } + } + + @Test + fun testSplashActivity_onboarded_devFlavor_showsDevFlavorText() { + simulateAppAlreadyOnboardedWithFlavor(BuildFlavor.DEVELOPER) + + initializeTestApplicationWithFlavor(BuildFlavor.DEVELOPER) + + // The developer label should be showing. + launchSplashActivityFully { + onView(withId(R.id.build_flavor_label)).check(matches(isDisplayed())) + onView(withId(R.id.build_flavor_label)) + .check(matches(withText(R.string.splash_screen_developer_label))) + } + } + + @Test + fun testSplashActivity_onboarded_alphaFlavor_showsAlphaFlavorText() { + simulateAppAlreadyOnboardedWithFlavor(BuildFlavor.ALPHA) + + initializeTestApplicationWithFlavor(BuildFlavor.ALPHA) + + // The alpha label should be showing. + launchSplashActivityFully { + onView(withId(R.id.build_flavor_label)).check(matches(isDisplayed())) + onView(withId(R.id.build_flavor_label)) + .check(matches(withText(R.string.splash_screen_alpha_label))) + } + } + + @Test + fun testSplashActivity_onboarded_betaFlavor_showsBetaFlavorText() { + simulateAppAlreadyOnboardedWithFlavor(BuildFlavor.BETA) + + initializeTestApplicationWithFlavor(BuildFlavor.BETA) + + // The beta label should be showing. + launchSplashActivityFully { + onView(withId(R.id.build_flavor_label)).check(matches(isDisplayed())) + onView(withId(R.id.build_flavor_label)) + .check(matches(withText(R.string.splash_screen_beta_label))) + } + } + + @Test + fun testSplashActivity_onboarded_generalAvailabilityFlavor_showsNoFlavorText() { + simulateAppAlreadyOnboardedWithFlavor(BuildFlavor.GENERAL_AVAILABILITY) + + initializeTestApplicationWithFlavor(BuildFlavor.GENERAL_AVAILABILITY) + + // No label should show for this version of the app. + launchSplashActivityFully { + onView(withId(R.id.build_flavor_label)).check(matches(not(isDisplayed()))) + } + } + + @Test + @RunOn(TestPlatform.ROBOLECTRIC) + fun testSplashActivity_onboarded_testingFlavor_doesNotWaitToStart() { + simulateAppAlreadyOnboardedWithFlavor(BuildFlavor.TESTING) + initializeTestApplicationWithFlavor(BuildFlavor.TESTING) + + // The profile chooser opens immediately for the testing flavor since it has no delay. + launchSplashActivityPartially { + testCoroutineDispatchers.runCurrent() + + intended(hasComponent(ProfileChooserActivity::class.java.name)) + } + } + + @Test + @RunOn(TestPlatform.ROBOLECTRIC) + fun testSplashActivity_onboarded_devFlavor_doesNotWaitToStart() { + simulateAppAlreadyOnboardedWithFlavor(BuildFlavor.DEVELOPER) + initializeTestApplicationWithFlavor(BuildFlavor.DEVELOPER) + + // The profile chooser opens immediately for the developer flavor since it has no delay. + launchSplashActivityPartially { + testCoroutineDispatchers.runCurrent() + + intended(hasComponent(ProfileChooserActivity::class.java.name)) + } + } + + @Test + @RunOn(TestPlatform.ROBOLECTRIC) + fun testSplashActivity_onboarded_alphaFlavor_doNotWait_doesNotStart() { + simulateAppAlreadyOnboardedWithFlavor(BuildFlavor.ALPHA) + initializeTestApplicationWithFlavor(BuildFlavor.ALPHA) + + // Nothing opens without waiting for the alpha startup notice to finish. + launchSplashActivityPartially { + testCoroutineDispatchers.runCurrent() + + Intents.assertNoUnverifiedIntents() + } + } + + @Test + @RunOn(TestPlatform.ROBOLECTRIC) + fun testSplashActivity_onboarded_alphaFlavor_waitTwoSeconds_intentsToProfileChooser() { + simulateAppAlreadyOnboardedWithFlavor(BuildFlavor.ALPHA) + initializeTestApplicationWithFlavor(BuildFlavor.ALPHA) + + // The profile chooser should appear after the 2 seconds wait for the alpha splash screen. + launchSplashActivityPartially { + testCoroutineDispatchers.advanceTimeBy(TimeUnit.SECONDS.toMillis(2)) + + intended(hasComponent(ProfileChooserActivity::class.java.name)) + } + } + + @Test + @RunOn(TestPlatform.ROBOLECTRIC) + fun testSplashActivity_onboarded_betaFlavor_doNotWait_doesNotStart() { + simulateAppAlreadyOnboardedWithFlavor(BuildFlavor.BETA) + initializeTestApplicationWithFlavor(BuildFlavor.BETA) + + // Nothing opens without waiting for the beta startup notice to finish. + launchSplashActivityPartially { + testCoroutineDispatchers.runCurrent() + + Intents.assertNoUnverifiedIntents() + } + } + + @Test + @RunOn(TestPlatform.ROBOLECTRIC) + fun testSplashActivity_onboarded_betaFlavor_waitTwoSeconds_intentsToProfileChooser() { + simulateAppAlreadyOnboardedWithFlavor(BuildFlavor.BETA) + initializeTestApplicationWithFlavor(BuildFlavor.BETA) + + // The profile chooser should appear after the 2 seconds wait for the beta splash screen. + launchSplashActivityPartially { + testCoroutineDispatchers.advanceTimeBy(TimeUnit.SECONDS.toMillis(2)) + + intended(hasComponent(ProfileChooserActivity::class.java.name)) + } + } + + @Test + @RunOn(TestPlatform.ROBOLECTRIC) + fun testSplashActivity_onboarded_gaFlavor_doesNotWaitToStart() { + simulateAppAlreadyOnboardedWithFlavor(BuildFlavor.GENERAL_AVAILABILITY) + initializeTestApplicationWithFlavor(BuildFlavor.GENERAL_AVAILABILITY) + + // The profile chooser opens immediately for the GA flavor since it has no delay. + launchSplashActivityPartially { + testCoroutineDispatchers.runCurrent() + + intended(hasComponent(ProfileChooserActivity::class.java.name)) + } + } + + private fun simulateAppAlreadyOpened() { + runInNewTestApplication { + val monitor = monitorFactory.createMonitor(appStartupStateController.getAppStartupState()) + testCoroutineDispatchers.advanceUntilIdle() + monitor.ensureNextResultIsSuccess() + } } private fun simulateAppAlreadyOnboarded() { @@ -403,12 +1034,63 @@ class SplashActivityTest { // to be done in an isolated test application since the test application of this class shares // state with production code under test. The isolated test application must be created through // Instrumentation to ensure it's properly attached. - val testApplication = Instrumentation.newApplication( + runInNewTestApplication { + appStartupStateController.markOnboardingFlowCompleted() + testCoroutineDispatchers.advanceUntilIdle() + } + } + + private fun runInNewTestApplication(block: TestApplication.() -> Unit) { + val newApplication = Instrumentation.newApplication( TestApplication::class.java, InstrumentationRegistry.getInstrumentation().targetContext ) as TestApplication - testApplication.getAppStartupStateController().markOnboardingFlowCompleted() - testApplication.getTestCoroutineDispatchers().advanceUntilIdle() + newApplication.testCoroutineDispatchers.registerIdlingResource() + newApplication.block() + newApplication.testCoroutineDispatchers.unregisterIdlingResource() + } + + /** + * Allows the existing [TestApplication] to be treated as though it was recreated. + * + * This should only be used when Espresso needs to run operations in a "previous" application + * instance (since Espresso can only use the current instrumentation and not a new one). For all + * other cases, prefer [runInNewTestApplication] since it actually creates an entirely new + * application in isolation, and is closer to a separate app instance than what this method + * produces. + */ + private fun recreateExistingApplication() { + testCoroutineDispatchers.unregisterIdlingResource() + ApplicationProvider.getApplicationContext().recreateDaggerGraph() + initializeTestApplication() + + // Reset any intents previously recorded. + Intents.release() + Intents.init() + } + + private fun recreateExistingApplicationWithFlavor(buildFlavor: BuildFlavor) { + TestModule.buildFlavor = buildFlavor + recreateExistingApplication() + } + + /** See [recreateExistingApplication] for when to use this. */ + private fun reopenAppWithNewFlavor(buildFlavor: BuildFlavor) { + TestModule.buildFlavor = buildFlavor + recreateExistingApplication() + val monitor = monitorFactory.createMonitor(appStartupStateController.getAppStartupState()) + testCoroutineDispatchers.advanceUntilIdle() + monitor.ensureNextResultIsSuccess() + } + + private fun simulateAppAlreadyOpenedWithFlavor(buildFlavor: BuildFlavor) { + TestModule.buildFlavor = buildFlavor + simulateAppAlreadyOpened() + } + + private fun simulateAppAlreadyOnboardedWithFlavor(buildFlavor: BuildFlavor) { + TestModule.buildFlavor = buildFlavor + simulateAppAlreadyOnboarded() } private fun initializeTestApplication() { @@ -417,6 +1099,38 @@ class SplashActivityTest { setAutoAppExpirationEnabled(enabled = false) // Default to disabled. } + private fun initializeTestApplicationWithFlavor(buildFlavor: BuildFlavor) { + TestModule.buildFlavor = buildFlavor + initializeTestApplication() + } + + /** + * Launches [SplashActivity] and waits for all initial time-based operations to complete before + * executing [testBlock]. + */ + private fun launchSplashActivityFully(testBlock: (ActivityScenario) -> Unit) { + launchSplashActivity { + testCoroutineDispatchers.advanceUntilIdle() + testBlock(it) + } + } + + /** Launches [SplashActivity] and waits for initial time-based operations to start. */ + private fun launchSplashActivityPartially(testBlock: (ActivityScenario) -> Unit) { + launchSplashActivity { + testCoroutineDispatchers.runCurrent() + testBlock(it) + } + } + + private fun launchSplashActivity(testBlock: (ActivityScenario) -> Unit) { + val openFromLauncher = Intent(context, SplashActivity::class.java).also { + it.action = Intent.ACTION_MAIN + it.addCategory(Intent.CATEGORY_LAUNCHER) + } + ActivityScenario.launch(openFromLauncher).use(testBlock) + } + private fun setAutoAppExpirationEnabled(enabled: Boolean) { fakeMetaDataRetriever.putMetaDataBoolean("automatic_app_expiration_enabled", enabled) } @@ -448,26 +1162,26 @@ class SplashActivityTest { Locale.setDefault(locale) } - private fun getShadowLogsOnRobolectric(): List { - val shadowLogClass = Class.forName("org.robolectric.shadows.ShadowLog") - val shadowLogItem = Class.forName("org.robolectric.shadows.ShadowLog\$LogItem") - val msgField = shadowLogItem.getDeclaredField("msg") - val logItems = shadowLogClass.getDeclaredMethod("getLogs").invoke(/* obj= */ null) as? List<*> - return logItems?.map { logItem -> - msgField.get(logItem) as String - } ?: listOf() - } - private fun corruptCacheFile() { // Statically retrieve the application context since injection may not have yet occurred. val applicationContext = ApplicationProvider.getApplicationContext() File(applicationContext.filesDir, "on_boarding_flow.cache").writeText("broken") } + @Module + class TestModule { + companion object { + var buildFlavor = BuildFlavor.BUILD_FLAVOR_UNSPECIFIED + } + + @Provides + fun provideTestingBuildFlavor(): BuildFlavor = buildFlavor + } + @Singleton @Component( modules = [ - RobolectricModule::class, + TestModule::class, RobolectricModule::class, TestDispatcherModule::class, ApplicationModule::class, PlatformParameterModule::class, LoggerModule::class, ContinueModule::class, FractionInputModule::class, ItemSelectionInputModule::class, MultipleChoiceInputModule::class, @@ -505,34 +1219,47 @@ class SplashActivityTest { fun getTestCoroutineDispatchers(): TestCoroutineDispatchers + fun getMonitorFactory(): DataProviderTestMonitor.Factory + fun inject(splashActivityTest: SplashActivityTest) } class TestApplication : Application(), ActivityComponentFactory, ApplicationInjectorProvider { - private val component: TestApplicationComponent by lazy { - DaggerSplashActivityTest_TestApplicationComponent.builder() - .setApplication(this) - .build() - } + private var component: TestApplicationComponent = createTestApplicationComponent() + + val appStartupStateController: AppStartupStateController + get() = component.getAppStartupStateController() + val testCoroutineDispatchers: TestCoroutineDispatchers + get() = component.getTestCoroutineDispatchers() + val monitorFactory: DataProviderTestMonitor.Factory + get() = component.getMonitorFactory() fun inject(splashActivityTest: SplashActivityTest) { component.inject(splashActivityTest) } - fun getAppStartupStateController() = component.getAppStartupStateController() - - fun getTestCoroutineDispatchers() = component.getTestCoroutineDispatchers() - override fun createActivityComponent(activity: AppCompatActivity): ActivityComponent { return component.getActivityComponentBuilderProvider().get().setActivity(activity).build() } override fun getApplicationInjector(): ApplicationInjector = component + + fun recreateDaggerGraph() { + component = createTestApplicationComponent() + } + + private fun createTestApplicationComponent(): TestApplicationComponent { + return DaggerSplashActivityTest_TestApplicationComponent.builder() + .setApplication(this) + .build() + } } private companion object { private val EGYPT_ARABIC_LOCALE = Locale("ar", "EG") private val BRAZIL_PORTUGUESE_LOCALE = Locale("pt", "BR") private val TURKEY_TURKISH_LOCALE = Locale("tr", "TR") + + private fun onDialogView(matcher: Matcher) = onView(matcher).inRoot(isDialog()) } } diff --git a/app/src/sharedTest/java/org/oppia/android/app/story/StoryActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/story/StoryActivityTest.kt index 54df28345a1..2fa6bda9d46 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/story/StoryActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/story/StoryActivityTest.kt @@ -34,6 +34,7 @@ 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.exploration.ExplorationActivity @@ -235,7 +236,7 @@ class StoryActivityTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/story/StoryFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/story/StoryFragmentTest.kt index 74fae89ca56..e1b9859c69c 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/story/StoryFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/story/StoryFragmentTest.kt @@ -61,6 +61,7 @@ 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.customview.LessonThumbnailImageView import org.oppia.android.app.devoptions.DeveloperOptionsModule import org.oppia.android.app.devoptions.DeveloperOptionsStarterModule @@ -903,7 +904,7 @@ class StoryFragmentTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/testing/DragDropTestActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/testing/DragDropTestActivityTest.kt index fc866ddceb1..3e79cccd1b6 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/testing/DragDropTestActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/testing/DragDropTestActivityTest.kt @@ -24,6 +24,7 @@ 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 @@ -228,7 +229,7 @@ class DragDropTestActivityTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/testing/ImageRegionSelectionInteractionViewTest.kt b/app/src/sharedTest/java/org/oppia/android/app/testing/ImageRegionSelectionInteractionViewTest.kt index 20d315e0475..15ced77dc59 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/testing/ImageRegionSelectionInteractionViewTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/testing/ImageRegionSelectionInteractionViewTest.kt @@ -36,6 +36,7 @@ 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.ImageRegionSelectionInteractionView @@ -415,7 +416,7 @@ class ImageRegionSelectionInteractionViewTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/testing/InputInteractionViewTestActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/testing/InputInteractionViewTestActivityTest.kt index da4d3031acd..97d4326ceaf 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/testing/InputInteractionViewTestActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/testing/InputInteractionViewTestActivityTest.kt @@ -36,6 +36,7 @@ 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.customview.interaction.RatioInputInteractionView import org.oppia.android.app.devoptions.DeveloperOptionsModule import org.oppia.android.app.devoptions.DeveloperOptionsStarterModule @@ -1126,7 +1127,7 @@ class InputInteractionViewTestActivityTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/testing/NavigationDrawerActivityDebugTest.kt b/app/src/sharedTest/java/org/oppia/android/app/testing/NavigationDrawerActivityDebugTest.kt index 69a7cfcc0ac..6096a49752e 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/testing/NavigationDrawerActivityDebugTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/testing/NavigationDrawerActivityDebugTest.kt @@ -55,6 +55,7 @@ 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.DeveloperOptionsActivity import org.oppia.android.app.devoptions.DeveloperOptionsModule import org.oppia.android.app.devoptions.DeveloperOptionsStarterModule @@ -439,7 +440,7 @@ class NavigationDrawerActivityDebugTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/testing/NavigationDrawerActivityProdTest.kt b/app/src/sharedTest/java/org/oppia/android/app/testing/NavigationDrawerActivityProdTest.kt index c7a0fc12355..e9c6b2a98ac 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/testing/NavigationDrawerActivityProdTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/testing/NavigationDrawerActivityProdTest.kt @@ -60,6 +60,7 @@ 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.drawer.NavigationDrawerItem import org.oppia.android.app.help.HelpActivity @@ -988,7 +989,7 @@ class NavigationDrawerActivityProdTest { AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/testing/TestFontScaleConfigurationUtilActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/testing/TestFontScaleConfigurationUtilActivityTest.kt index 7488560d8d9..5cfe02d9bdc 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/testing/TestFontScaleConfigurationUtilActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/testing/TestFontScaleConfigurationUtilActivityTest.kt @@ -25,6 +25,7 @@ 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.ReadingTextSize @@ -206,7 +207,7 @@ class TestFontScaleConfigurationUtilActivityTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/testing/TopicTestActivityForStoryTest.kt b/app/src/sharedTest/java/org/oppia/android/app/testing/TopicTestActivityForStoryTest.kt index 5aebaae32fa..329648407b1 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/testing/TopicTestActivityForStoryTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/testing/TopicTestActivityForStoryTest.kt @@ -28,6 +28,7 @@ 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 @@ -203,7 +204,7 @@ class TopicTestActivityForStoryTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/thirdparty/LicenseListActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/thirdparty/LicenseListActivityTest.kt index efb85e232b1..32993ac8369 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/thirdparty/LicenseListActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/thirdparty/LicenseListActivityTest.kt @@ -21,6 +21,7 @@ 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.help.thirdparty.LicenseListActivity @@ -163,7 +164,7 @@ class LicenseListActivityTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, SyncStatusModule::class, - MetricLogSchedulerModule::class + MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) diff --git a/app/src/sharedTest/java/org/oppia/android/app/thirdparty/LicenseListFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/thirdparty/LicenseListFragmentTest.kt index 4fd305d3ce0..0b5f2eae1ab 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/thirdparty/LicenseListFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/thirdparty/LicenseListFragmentTest.kt @@ -34,6 +34,7 @@ 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.help.thirdparty.LicenseListActivity @@ -373,7 +374,7 @@ class LicenseListFragmentTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, SyncStatusModule::class, - MetricLogSchedulerModule::class + MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) diff --git a/app/src/sharedTest/java/org/oppia/android/app/thirdparty/LicenseTextViewerActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/thirdparty/LicenseTextViewerActivityTest.kt index a751f1b52de..198ef7c20f5 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/thirdparty/LicenseTextViewerActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/thirdparty/LicenseTextViewerActivityTest.kt @@ -21,6 +21,7 @@ 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.help.thirdparty.LicenseListActivity @@ -172,7 +173,7 @@ class LicenseTextViewerActivityTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, SyncStatusModule::class, - MetricLogSchedulerModule::class + MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) diff --git a/app/src/sharedTest/java/org/oppia/android/app/thirdparty/LicenseTextViewerFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/thirdparty/LicenseTextViewerFragmentTest.kt index bd5f229906f..b12abd0898a 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/thirdparty/LicenseTextViewerFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/thirdparty/LicenseTextViewerFragmentTest.kt @@ -26,6 +26,7 @@ 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.help.thirdparty.LicenseTextViewerActivity @@ -351,7 +352,7 @@ class LicenseTextViewerFragmentTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, SyncStatusModule::class, - MetricLogSchedulerModule::class + MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) diff --git a/app/src/sharedTest/java/org/oppia/android/app/thirdparty/ThirdPartyDependencyListActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/thirdparty/ThirdPartyDependencyListActivityTest.kt index 2298212d383..b6a7a2093b7 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/thirdparty/ThirdPartyDependencyListActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/thirdparty/ThirdPartyDependencyListActivityTest.kt @@ -21,6 +21,7 @@ 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.help.thirdparty.ThirdPartyDependencyListActivity @@ -160,7 +161,7 @@ class ThirdPartyDependencyListActivityTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, SyncStatusModule::class, - MetricLogSchedulerModule::class + MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) diff --git a/app/src/sharedTest/java/org/oppia/android/app/thirdparty/ThirdPartyDependencyListFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/thirdparty/ThirdPartyDependencyListFragmentTest.kt index f9dd5befd2e..cd7839f3fdb 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/thirdparty/ThirdPartyDependencyListFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/thirdparty/ThirdPartyDependencyListFragmentTest.kt @@ -33,6 +33,7 @@ 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.help.thirdparty.LicenseListActivity @@ -483,7 +484,7 @@ class ThirdPartyDependencyListFragmentTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, SyncStatusModule::class, - MetricLogSchedulerModule::class + MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) diff --git a/app/src/sharedTest/java/org/oppia/android/app/topic/TopicActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/topic/TopicActivityTest.kt index 15c80f80eca..9cbbb6dd857 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/topic/TopicActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/topic/TopicActivityTest.kt @@ -33,6 +33,7 @@ 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.ProfileId @@ -209,7 +210,7 @@ class TopicActivityTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/topic/TopicFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/topic/TopicFragmentTest.kt index 87fb560776c..a83ee19558f 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/topic/TopicFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/topic/TopicFragmentTest.kt @@ -43,6 +43,7 @@ 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 @@ -664,7 +665,7 @@ class TopicFragmentTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/topic/conceptcard/ConceptCardFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/topic/conceptcard/ConceptCardFragmentTest.kt index 6a3c9d7be97..3af01c379e0 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/topic/conceptcard/ConceptCardFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/topic/conceptcard/ConceptCardFragmentTest.kt @@ -38,6 +38,7 @@ 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.OppiaLanguage @@ -435,7 +436,7 @@ class ConceptCardFragmentTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/topic/info/TopicInfoFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/topic/info/TopicInfoFragmentTest.kt index e743d17c05d..ab908ad0315 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/topic/info/TopicInfoFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/topic/info/TopicInfoFragmentTest.kt @@ -43,6 +43,7 @@ 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 @@ -491,7 +492,7 @@ class TopicInfoFragmentTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentTest.kt index 2e12f85d07a..6cccfd1f84a 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentTest.kt @@ -43,6 +43,7 @@ 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.ProfileId @@ -1020,7 +1021,7 @@ class TopicLessonsFragmentTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/topic/practice/TopicPracticeFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/topic/practice/TopicPracticeFragmentTest.kt index 98ae842aa2d..be3f067135a 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/topic/practice/TopicPracticeFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/topic/practice/TopicPracticeFragmentTest.kt @@ -37,6 +37,7 @@ 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 @@ -436,7 +437,7 @@ class TopicPracticeFragmentTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/topic/questionplayer/QuestionPlayerActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/topic/questionplayer/QuestionPlayerActivityTest.kt index 8fbee49074d..13e515ebefc 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/topic/questionplayer/QuestionPlayerActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/topic/questionplayer/QuestionPlayerActivityTest.kt @@ -54,6 +54,7 @@ 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.OppiaLanguage @@ -725,7 +726,7 @@ class QuestionPlayerActivityTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/topic/revision/TopicRevisionFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/topic/revision/TopicRevisionFragmentTest.kt index 86da92fe690..01054765d12 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/topic/revision/TopicRevisionFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/topic/revision/TopicRevisionFragmentTest.kt @@ -37,6 +37,7 @@ 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 @@ -332,7 +333,7 @@ class TopicRevisionFragmentTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/topic/revisioncard/RevisionCardActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/topic/revisioncard/RevisionCardActivityTest.kt index 6d8fbf9be40..894dec9cb73 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/topic/revisioncard/RevisionCardActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/topic/revisioncard/RevisionCardActivityTest.kt @@ -32,6 +32,7 @@ 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.OppiaLanguage @@ -286,7 +287,7 @@ class RevisionCardActivityTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/topic/revisioncard/RevisionCardFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/topic/revisioncard/RevisionCardFragmentTest.kt index 84f04139d51..85c1e26ef3d 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/topic/revisioncard/RevisionCardFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/topic/revisioncard/RevisionCardFragmentTest.kt @@ -45,6 +45,7 @@ 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.help.HelpActivity @@ -602,7 +603,7 @@ class RevisionCardFragmentTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/utility/RatioExtensionsTest.kt b/app/src/sharedTest/java/org/oppia/android/app/utility/RatioExtensionsTest.kt index 3596c8533d1..8ea214e120a 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/utility/RatioExtensionsTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/utility/RatioExtensionsTest.kt @@ -18,6 +18,7 @@ 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.RatioExpression @@ -159,7 +160,7 @@ class RatioExtensionsTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/walkthrough/WalkthroughActivityTest.kt b/app/src/sharedTest/java/org/oppia/android/app/walkthrough/WalkthroughActivityTest.kt index cd49b90d400..9d977f37a02 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/walkthrough/WalkthroughActivityTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/walkthrough/WalkthroughActivityTest.kt @@ -31,6 +31,7 @@ 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 @@ -237,7 +238,7 @@ class WalkthroughActivityTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/walkthrough/WalkthroughFinalFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/walkthrough/WalkthroughFinalFragmentTest.kt index 7bdea47cd8c..d27197c2cad 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/walkthrough/WalkthroughFinalFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/walkthrough/WalkthroughFinalFragmentTest.kt @@ -33,6 +33,7 @@ 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 @@ -294,7 +295,7 @@ class WalkthroughFinalFragmentTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/walkthrough/WalkthroughTopicListFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/walkthrough/WalkthroughTopicListFragmentTest.kt index f334d5e7080..ec3ab607a23 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/walkthrough/WalkthroughTopicListFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/walkthrough/WalkthroughTopicListFragmentTest.kt @@ -34,6 +34,7 @@ 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 @@ -320,7 +321,7 @@ class WalkthroughTopicListFragmentTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/sharedTest/java/org/oppia/android/app/walkthrough/WalkthroughWelcomeFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/walkthrough/WalkthroughWelcomeFragmentTest.kt index 59b4f9f4ca8..d8b48d6dbdb 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/walkthrough/WalkthroughWelcomeFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/walkthrough/WalkthroughWelcomeFragmentTest.kt @@ -29,6 +29,7 @@ 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.ProfileId @@ -217,7 +218,7 @@ class WalkthroughWelcomeFragmentTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/test/java/org/oppia/android/app/activity/ActivityIntentFactoriesTest.kt b/app/src/test/java/org/oppia/android/app/activity/ActivityIntentFactoriesTest.kt index f0491f38b65..80145e188cc 100644 --- a/app/src/test/java/org/oppia/android/app/activity/ActivityIntentFactoriesTest.kt +++ b/app/src/test/java/org/oppia/android/app/activity/ActivityIntentFactoriesTest.kt @@ -19,6 +19,7 @@ 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.home.recentlyplayed.RecentlyPlayedActivity @@ -182,7 +183,7 @@ class ActivityIntentFactoriesTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/test/java/org/oppia/android/app/activity/BUILD.bazel b/app/src/test/java/org/oppia/android/app/activity/BUILD.bazel index a3c4e616851..ec052a7416a 100644 --- a/app/src/test/java/org/oppia/android/app/activity/BUILD.bazel +++ b/app/src/test/java/org/oppia/android/app/activity/BUILD.bazel @@ -19,6 +19,7 @@ oppia_android_test( "//app/src/main/java/org/oppia/android/app/application:application_injector", "//app/src/main/java/org/oppia/android/app/application:application_injector_provider", "//app/src/main/java/org/oppia/android/app/application:common_application_modules", + "//app/src/main/java/org/oppia/android/app/application/testing:testing_build_flavor_module", "//app/src/main/java/org/oppia/android/app/translation/testing:test_module", "//domain", "//domain/src/main/java/org/oppia/android/domain/classify:interactions_module", diff --git a/app/src/test/java/org/oppia/android/app/application/alpha/AlphaBuildFlavorModuleTest.kt b/app/src/test/java/org/oppia/android/app/application/alpha/AlphaBuildFlavorModuleTest.kt new file mode 100644 index 00000000000..f40c984f7b4 --- /dev/null +++ b/app/src/test/java/org/oppia/android/app/application/alpha/AlphaBuildFlavorModuleTest.kt @@ -0,0 +1,81 @@ +package org.oppia.android.app.application.alpha + +import android.app.Application +import android.content.Context +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import dagger.BindsInstance +import dagger.Component +import dagger.Module +import dagger.Provides +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.oppia.android.app.model.BuildFlavor +import org.robolectric.annotation.Config +import org.robolectric.annotation.LooperMode +import javax.inject.Inject +import javax.inject.Singleton + +/** Tests for [AlphaBuildFlavorModule]. */ +// FunctionName: test names are conventionally named with underscores. +@Suppress("FunctionName") +@RunWith(AndroidJUnit4::class) +@LooperMode(LooperMode.Mode.PAUSED) +@Config(application = AlphaBuildFlavorModuleTest.TestApplication::class) +class AlphaBuildFlavorModuleTest { + @Inject + lateinit var buildFlavor: BuildFlavor + + @Before + fun setUp() { + setUpTestApplicationComponent() + } + + @Test + fun testBuildFlavor_isAlphaBuildFlavor() { + assertThat(buildFlavor).isEqualTo(BuildFlavor.ALPHA) + } + + private fun setUpTestApplicationComponent() { + ApplicationProvider.getApplicationContext().inject(this) + } + + // TODO(#89): Move this to a common test application component. + @Module + class TestModule { + @Provides + @Singleton + fun provideContext(application: Application): Context { + return application + } + } + + // TODO(#89): Move this to a common test application component. + @Singleton + @Component(modules = [TestModule::class, AlphaBuildFlavorModule::class]) + interface TestApplicationComponent { + @Component.Builder + interface Builder { + @BindsInstance + fun setApplication(application: Application): Builder + + fun build(): TestApplicationComponent + } + + fun inject(test: AlphaBuildFlavorModuleTest) + } + + class TestApplication : Application() { + private val component: TestApplicationComponent by lazy { + DaggerAlphaBuildFlavorModuleTest_TestApplicationComponent.builder() + .setApplication(this) + .build() + } + + fun inject(test: AlphaBuildFlavorModuleTest) { + component.inject(test) + } + } +} diff --git a/app/src/test/java/org/oppia/android/app/application/alpha/BUILD.bazel b/app/src/test/java/org/oppia/android/app/application/alpha/BUILD.bazel new file mode 100644 index 00000000000..900715a50e6 --- /dev/null +++ b/app/src/test/java/org/oppia/android/app/application/alpha/BUILD.bazel @@ -0,0 +1,26 @@ +""" +Tests for alpha-specific top-level application configurations. +""" + +load("@dagger//:workspace_defs.bzl", "dagger_rules") +load("//:oppia_android_test.bzl", "oppia_android_test") + +oppia_android_test( + name = "AlphaBuildFlavorModuleTest", + srcs = ["AlphaBuildFlavorModuleTest.kt"], + custom_package = "org.oppia.android.app.application.alpha", + test_class = "org.oppia.android.app.application.alpha.AlphaBuildFlavorModuleTest", + test_manifest = "//app:test_manifest", + deps = [ + ":dagger", + "//app/src/main/java/org/oppia/android/app/application/alpha:alpha_build_flavor_module", + "//model/src/main/proto:version_java_proto_lite", + "//third_party:androidx_test_ext_junit", + "//third_party:com_google_truth_truth", + "//third_party:junit_junit", + "//third_party:org_robolectric_robolectric", + "//third_party:robolectric_android-all", + ], +) + +dagger_rules() diff --git a/app/src/test/java/org/oppia/android/app/application/beta/BUILD.bazel b/app/src/test/java/org/oppia/android/app/application/beta/BUILD.bazel new file mode 100644 index 00000000000..4706b193b26 --- /dev/null +++ b/app/src/test/java/org/oppia/android/app/application/beta/BUILD.bazel @@ -0,0 +1,26 @@ +""" +Tests for beta-specific top-level application configurations. +""" + +load("@dagger//:workspace_defs.bzl", "dagger_rules") +load("//:oppia_android_test.bzl", "oppia_android_test") + +oppia_android_test( + name = "BetaBuildFlavorModuleTest", + srcs = ["BetaBuildFlavorModuleTest.kt"], + custom_package = "org.oppia.android.app.application.beta", + test_class = "org.oppia.android.app.application.beta.BetaBuildFlavorModuleTest", + test_manifest = "//app:test_manifest", + deps = [ + ":dagger", + "//app/src/main/java/org/oppia/android/app/application/beta:beta_application", + "//model/src/main/proto:version_java_proto_lite", + "//third_party:androidx_test_ext_junit", + "//third_party:com_google_truth_truth", + "//third_party:junit_junit", + "//third_party:org_robolectric_robolectric", + "//third_party:robolectric_android-all", + ], +) + +dagger_rules() diff --git a/app/src/test/java/org/oppia/android/app/application/beta/BetaBuildFlavorModuleTest.kt b/app/src/test/java/org/oppia/android/app/application/beta/BetaBuildFlavorModuleTest.kt new file mode 100644 index 00000000000..efbe6eab3e7 --- /dev/null +++ b/app/src/test/java/org/oppia/android/app/application/beta/BetaBuildFlavorModuleTest.kt @@ -0,0 +1,81 @@ +package org.oppia.android.app.application.beta + +import android.app.Application +import android.content.Context +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import dagger.BindsInstance +import dagger.Component +import dagger.Module +import dagger.Provides +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.oppia.android.app.model.BuildFlavor +import org.robolectric.annotation.Config +import org.robolectric.annotation.LooperMode +import javax.inject.Inject +import javax.inject.Singleton + +/** Tests for [BetaBuildFlavorModule]. */ +// FunctionName: test names are conventionally named with underscores. +@Suppress("FunctionName") +@RunWith(AndroidJUnit4::class) +@LooperMode(LooperMode.Mode.PAUSED) +@Config(application = BetaBuildFlavorModuleTest.TestApplication::class) +class BetaBuildFlavorModuleTest { + @Inject + lateinit var buildFlavor: BuildFlavor + + @Before + fun setUp() { + setUpTestApplicationComponent() + } + + @Test + fun testBuildFlavor_isBetaBuildFlavor() { + assertThat(buildFlavor).isEqualTo(BuildFlavor.BETA) + } + + private fun setUpTestApplicationComponent() { + ApplicationProvider.getApplicationContext().inject(this) + } + + // TODO(#89): Move this to a common test application component. + @Module + class TestModule { + @Provides + @Singleton + fun provideContext(application: Application): Context { + return application + } + } + + // TODO(#89): Move this to a common test application component. + @Singleton + @Component(modules = [TestModule::class, BetaBuildFlavorModule::class]) + interface TestApplicationComponent { + @Component.Builder + interface Builder { + @BindsInstance + fun setApplication(application: Application): Builder + + fun build(): TestApplicationComponent + } + + fun inject(test: BetaBuildFlavorModuleTest) + } + + class TestApplication : Application() { + private val component: TestApplicationComponent by lazy { + DaggerBetaBuildFlavorModuleTest_TestApplicationComponent.builder() + .setApplication(this) + .build() + } + + fun inject(test: BetaBuildFlavorModuleTest) { + component.inject(test) + } + } +} diff --git a/app/src/test/java/org/oppia/android/app/application/dev/BUILD.bazel b/app/src/test/java/org/oppia/android/app/application/dev/BUILD.bazel new file mode 100644 index 00000000000..c6566c6f2a6 --- /dev/null +++ b/app/src/test/java/org/oppia/android/app/application/dev/BUILD.bazel @@ -0,0 +1,26 @@ +""" +Tests for developer-specific top-level application configurations. +""" + +load("@dagger//:workspace_defs.bzl", "dagger_rules") +load("//:oppia_android_test.bzl", "oppia_android_test") + +oppia_android_test( + name = "DeveloperBuildFlavorModuleTest", + srcs = ["DeveloperBuildFlavorModuleTest.kt"], + custom_package = "org.oppia.android.app.application.dev", + test_class = "org.oppia.android.app.application.dev.DeveloperBuildFlavorModuleTest", + test_manifest = "//app:test_manifest", + deps = [ + ":dagger", + "//app/src/main/java/org/oppia/android/app/application/dev:developer_application", + "//model/src/main/proto:version_java_proto_lite", + "//third_party:androidx_test_ext_junit", + "//third_party:com_google_truth_truth", + "//third_party:junit_junit", + "//third_party:org_robolectric_robolectric", + "//third_party:robolectric_android-all", + ], +) + +dagger_rules() diff --git a/app/src/test/java/org/oppia/android/app/application/dev/DeveloperBuildFlavorModuleTest.kt b/app/src/test/java/org/oppia/android/app/application/dev/DeveloperBuildFlavorModuleTest.kt new file mode 100644 index 00000000000..9f5a1c6e7ee --- /dev/null +++ b/app/src/test/java/org/oppia/android/app/application/dev/DeveloperBuildFlavorModuleTest.kt @@ -0,0 +1,81 @@ +package org.oppia.android.app.application.dev + +import android.app.Application +import android.content.Context +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import dagger.BindsInstance +import dagger.Component +import dagger.Module +import dagger.Provides +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.oppia.android.app.model.BuildFlavor +import org.robolectric.annotation.Config +import org.robolectric.annotation.LooperMode +import javax.inject.Inject +import javax.inject.Singleton + +/** Tests for [DeveloperBuildFlavorModule]. */ +// FunctionName: test names are conventionally named with underscores. +@Suppress("FunctionName") +@RunWith(AndroidJUnit4::class) +@LooperMode(LooperMode.Mode.PAUSED) +@Config(application = DeveloperBuildFlavorModuleTest.TestApplication::class) +class DeveloperBuildFlavorModuleTest { + @Inject + lateinit var buildFlavor: BuildFlavor + + @Before + fun setUp() { + setUpTestApplicationComponent() + } + + @Test + fun testBuildFlavor_isDeveloperBuildFlavor() { + assertThat(buildFlavor).isEqualTo(BuildFlavor.DEVELOPER) + } + + private fun setUpTestApplicationComponent() { + ApplicationProvider.getApplicationContext().inject(this) + } + + // TODO(#89): Move this to a common test application component. + @Module + class TestModule { + @Provides + @Singleton + fun provideContext(application: Application): Context { + return application + } + } + + // TODO(#89): Move this to a common test application component. + @Singleton + @Component(modules = [TestModule::class, DeveloperBuildFlavorModule::class]) + interface TestApplicationComponent { + @Component.Builder + interface Builder { + @BindsInstance + fun setApplication(application: Application): Builder + + fun build(): TestApplicationComponent + } + + fun inject(test: DeveloperBuildFlavorModuleTest) + } + + class TestApplication : Application() { + private val component: TestApplicationComponent by lazy { + DaggerDeveloperBuildFlavorModuleTest_TestApplicationComponent.builder() + .setApplication(this) + .build() + } + + fun inject(test: DeveloperBuildFlavorModuleTest) { + component.inject(test) + } + } +} diff --git a/app/src/test/java/org/oppia/android/app/application/ga/BUILD.bazel b/app/src/test/java/org/oppia/android/app/application/ga/BUILD.bazel new file mode 100644 index 00000000000..c3ce6168c22 --- /dev/null +++ b/app/src/test/java/org/oppia/android/app/application/ga/BUILD.bazel @@ -0,0 +1,26 @@ +""" +Tests for general availability-specific top-level application configurations. +""" + +load("@dagger//:workspace_defs.bzl", "dagger_rules") +load("//:oppia_android_test.bzl", "oppia_android_test") + +oppia_android_test( + name = "GaBuildFlavorModuleTest", + srcs = ["GaBuildFlavorModuleTest.kt"], + custom_package = "org.oppia.android.app.application.ga", + test_class = "org.oppia.android.app.application.ga.GaBuildFlavorModuleTest", + test_manifest = "//app:test_manifest", + deps = [ + ":dagger", + "//app/src/main/java/org/oppia/android/app/application/ga:general_availability_application", + "//model/src/main/proto:version_java_proto_lite", + "//third_party:androidx_test_ext_junit", + "//third_party:com_google_truth_truth", + "//third_party:junit_junit", + "//third_party:org_robolectric_robolectric", + "//third_party:robolectric_android-all", + ], +) + +dagger_rules() diff --git a/app/src/test/java/org/oppia/android/app/application/ga/GaBuildFlavorModuleTest.kt b/app/src/test/java/org/oppia/android/app/application/ga/GaBuildFlavorModuleTest.kt new file mode 100644 index 00000000000..b8a848a432e --- /dev/null +++ b/app/src/test/java/org/oppia/android/app/application/ga/GaBuildFlavorModuleTest.kt @@ -0,0 +1,81 @@ +package org.oppia.android.app.application.ga + +import android.app.Application +import android.content.Context +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import dagger.BindsInstance +import dagger.Component +import dagger.Module +import dagger.Provides +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.oppia.android.app.model.BuildFlavor +import org.robolectric.annotation.Config +import org.robolectric.annotation.LooperMode +import javax.inject.Inject +import javax.inject.Singleton + +/** Tests for [GaBuildFlavorModule]. */ +// FunctionName: test names are conventionally named with underscores. +@Suppress("FunctionName") +@RunWith(AndroidJUnit4::class) +@LooperMode(LooperMode.Mode.PAUSED) +@Config(application = GaBuildFlavorModuleTest.TestApplication::class) +class GaBuildFlavorModuleTest { + @Inject + lateinit var buildFlavor: BuildFlavor + + @Before + fun setUp() { + setUpTestApplicationComponent() + } + + @Test + fun testBuildFlavor_isGeneralAvailabilityBuildFlavor() { + assertThat(buildFlavor).isEqualTo(BuildFlavor.GENERAL_AVAILABILITY) + } + + private fun setUpTestApplicationComponent() { + ApplicationProvider.getApplicationContext().inject(this) + } + + // TODO(#89): Move this to a common test application component. + @Module + class TestModule { + @Provides + @Singleton + fun provideContext(application: Application): Context { + return application + } + } + + // TODO(#89): Move this to a common test application component. + @Singleton + @Component(modules = [TestModule::class, GaBuildFlavorModule::class]) + interface TestApplicationComponent { + @Component.Builder + interface Builder { + @BindsInstance + fun setApplication(application: Application): Builder + + fun build(): TestApplicationComponent + } + + fun inject(test: GaBuildFlavorModuleTest) + } + + class TestApplication : Application() { + private val component: TestApplicationComponent by lazy { + DaggerGaBuildFlavorModuleTest_TestApplicationComponent.builder() + .setApplication(this) + .build() + } + + fun inject(test: GaBuildFlavorModuleTest) { + component.inject(test) + } + } +} diff --git a/app/src/test/java/org/oppia/android/app/application/testing/BUILD.bazel b/app/src/test/java/org/oppia/android/app/application/testing/BUILD.bazel new file mode 100644 index 00000000000..2168366aeb4 --- /dev/null +++ b/app/src/test/java/org/oppia/android/app/application/testing/BUILD.bazel @@ -0,0 +1,26 @@ +""" +Tests for testing-specific top-level application configurations. +""" + +load("@dagger//:workspace_defs.bzl", "dagger_rules") +load("//:oppia_android_test.bzl", "oppia_android_test") + +oppia_android_test( + name = "TestingBuildFlavorModuleTest", + srcs = ["TestingBuildFlavorModuleTest.kt"], + custom_package = "org.oppia.android.app.application.testing", + test_class = "org.oppia.android.app.application.testing.TestingBuildFlavorModuleTest", + test_manifest = "//app:test_manifest", + deps = [ + ":dagger", + "//app/src/main/java/org/oppia/android/app/application/testing:testing_build_flavor_module", + "//model/src/main/proto:version_java_proto_lite", + "//third_party:androidx_test_ext_junit", + "//third_party:com_google_truth_truth", + "//third_party:junit_junit", + "//third_party:org_robolectric_robolectric", + "//third_party:robolectric_android-all", + ], +) + +dagger_rules() diff --git a/app/src/test/java/org/oppia/android/app/application/testing/TestingBuildFlavorModuleTest.kt b/app/src/test/java/org/oppia/android/app/application/testing/TestingBuildFlavorModuleTest.kt new file mode 100644 index 00000000000..f1b3136ccb0 --- /dev/null +++ b/app/src/test/java/org/oppia/android/app/application/testing/TestingBuildFlavorModuleTest.kt @@ -0,0 +1,81 @@ +package org.oppia.android.app.application.testing + +import android.app.Application +import android.content.Context +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import dagger.BindsInstance +import dagger.Component +import dagger.Module +import dagger.Provides +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.oppia.android.app.model.BuildFlavor +import org.robolectric.annotation.Config +import org.robolectric.annotation.LooperMode +import javax.inject.Inject +import javax.inject.Singleton + +/** Tests for [TestingBuildFlavorModule]. */ +// FunctionName: test names are conventionally named with underscores. +@Suppress("FunctionName") +@RunWith(AndroidJUnit4::class) +@LooperMode(LooperMode.Mode.PAUSED) +@Config(application = TestingBuildFlavorModuleTest.TestApplication::class) +class TestingBuildFlavorModuleTest { + @Inject + lateinit var buildFlavor: BuildFlavor + + @Before + fun setUp() { + setUpTestApplicationComponent() + } + + @Test + fun testBuildFlavor_isTestingBuildFlavor() { + assertThat(buildFlavor).isEqualTo(BuildFlavor.TESTING) + } + + private fun setUpTestApplicationComponent() { + ApplicationProvider.getApplicationContext().inject(this) + } + + // TODO(#89): Move this to a common test application component. + @Module + class TestModule { + @Provides + @Singleton + fun provideContext(application: Application): Context { + return application + } + } + + // TODO(#89): Move this to a common test application component. + @Singleton + @Component(modules = [TestModule::class, TestingBuildFlavorModule::class]) + interface TestApplicationComponent { + @Component.Builder + interface Builder { + @BindsInstance + fun setApplication(application: Application): Builder + + fun build(): TestApplicationComponent + } + + fun inject(test: TestingBuildFlavorModuleTest) + } + + class TestApplication : Application() { + private val component: TestApplicationComponent by lazy { + DaggerTestingBuildFlavorModuleTest_TestApplicationComponent.builder() + .setApplication(this) + .build() + } + + fun inject(test: TestingBuildFlavorModuleTest) { + component.inject(test) + } + } +} diff --git a/app/src/test/java/org/oppia/android/app/home/HomeActivityLocalTest.kt b/app/src/test/java/org/oppia/android/app/home/HomeActivityLocalTest.kt index 445649d393b..810f35b9475 100644 --- a/app/src/test/java/org/oppia/android/app/home/HomeActivityLocalTest.kt +++ b/app/src/test/java/org/oppia/android/app/home/HomeActivityLocalTest.kt @@ -21,6 +21,7 @@ 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.EventLog @@ -154,7 +155,7 @@ class HomeActivityLocalTest { AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/test/java/org/oppia/android/app/parser/FractionParsingUiErrorTest.kt b/app/src/test/java/org/oppia/android/app/parser/FractionParsingUiErrorTest.kt index d6cb209d2c1..c0216f6e8c6 100644 --- a/app/src/test/java/org/oppia/android/app/parser/FractionParsingUiErrorTest.kt +++ b/app/src/test/java/org/oppia/android/app/parser/FractionParsingUiErrorTest.kt @@ -18,6 +18,7 @@ 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 @@ -264,7 +265,7 @@ class FractionParsingUiErrorTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/test/java/org/oppia/android/app/parser/ListItemLeadingMarginSpanTest.kt b/app/src/test/java/org/oppia/android/app/parser/ListItemLeadingMarginSpanTest.kt index 1ccba698308..8c3fc424a40 100644 --- a/app/src/test/java/org/oppia/android/app/parser/ListItemLeadingMarginSpanTest.kt +++ b/app/src/test/java/org/oppia/android/app/parser/ListItemLeadingMarginSpanTest.kt @@ -36,6 +36,7 @@ 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.LanguageSupportDefinition @@ -1106,7 +1107,7 @@ class ListItemLeadingMarginSpanTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) diff --git a/app/src/test/java/org/oppia/android/app/parser/StringToRatioParserTest.kt b/app/src/test/java/org/oppia/android/app/parser/StringToRatioParserTest.kt index 73ab39d434f..0c038ea5d11 100644 --- a/app/src/test/java/org/oppia/android/app/parser/StringToRatioParserTest.kt +++ b/app/src/test/java/org/oppia/android/app/parser/StringToRatioParserTest.kt @@ -18,6 +18,7 @@ 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.RatioExpression @@ -268,7 +269,7 @@ class StringToRatioParserTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/test/java/org/oppia/android/app/player/exploration/ExplorationActivityLocalTest.kt b/app/src/test/java/org/oppia/android/app/player/exploration/ExplorationActivityLocalTest.kt index 1c875e82863..25d38fd6333 100644 --- a/app/src/test/java/org/oppia/android/app/player/exploration/ExplorationActivityLocalTest.kt +++ b/app/src/test/java/org/oppia/android/app/player/exploration/ExplorationActivityLocalTest.kt @@ -20,6 +20,7 @@ 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.EventLog @@ -215,7 +216,7 @@ class ExplorationActivityLocalTest { AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/test/java/org/oppia/android/app/player/state/StateFragmentLocalTest.kt b/app/src/test/java/org/oppia/android/app/player/state/StateFragmentLocalTest.kt index 30849532780..0a6b6a31e01 100644 --- a/app/src/test/java/org/oppia/android/app/player/state/StateFragmentLocalTest.kt +++ b/app/src/test/java/org/oppia/android/app/player/state/StateFragmentLocalTest.kt @@ -58,6 +58,7 @@ 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.hintsandsolution.TAG_REVEAL_SOLUTION_DIALOG @@ -2297,7 +2298,7 @@ class StateFragmentLocalTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/test/java/org/oppia/android/app/profile/ProfileChooserFragmentLocalTest.kt b/app/src/test/java/org/oppia/android/app/profile/ProfileChooserFragmentLocalTest.kt index df5ab8f58d0..db5a73a3add 100644 --- a/app/src/test/java/org/oppia/android/app/profile/ProfileChooserFragmentLocalTest.kt +++ b/app/src/test/java/org/oppia/android/app/profile/ProfileChooserFragmentLocalTest.kt @@ -19,6 +19,7 @@ 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.EventLog.Context.ActivityContextCase.OPEN_PROFILE_CHOOSER @@ -143,7 +144,7 @@ class ProfileChooserFragmentLocalTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/test/java/org/oppia/android/app/story/StoryActivityLocalTest.kt b/app/src/test/java/org/oppia/android/app/story/StoryActivityLocalTest.kt index 5d9cc5e3e75..dbe875db082 100644 --- a/app/src/test/java/org/oppia/android/app/story/StoryActivityLocalTest.kt +++ b/app/src/test/java/org/oppia/android/app/story/StoryActivityLocalTest.kt @@ -21,6 +21,7 @@ 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.EventLog @@ -168,7 +169,7 @@ class StoryActivityLocalTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/test/java/org/oppia/android/app/testing/CompletedStoryListSpanTest.kt b/app/src/test/java/org/oppia/android/app/testing/CompletedStoryListSpanTest.kt index 86a05a175dc..e2874312b64 100644 --- a/app/src/test/java/org/oppia/android/app/testing/CompletedStoryListSpanTest.kt +++ b/app/src/test/java/org/oppia/android/app/testing/CompletedStoryListSpanTest.kt @@ -22,6 +22,7 @@ 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.completedstorylist.CompletedStoryListActivity import org.oppia.android.app.completedstorylist.CompletedStoryListFragment.Companion.COMPLETED_STORY_LIST_FRAGMENT_TAG import org.oppia.android.app.devoptions.DeveloperOptionsModule @@ -177,7 +178,7 @@ class CompletedStoryListSpanTest { AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/test/java/org/oppia/android/app/testing/HomeSpanTest.kt b/app/src/test/java/org/oppia/android/app/testing/HomeSpanTest.kt index 83e920a77bd..d34e7fa7a19 100644 --- a/app/src/test/java/org/oppia/android/app/testing/HomeSpanTest.kt +++ b/app/src/test/java/org/oppia/android/app/testing/HomeSpanTest.kt @@ -22,6 +22,7 @@ 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.home.HomeActivity @@ -191,7 +192,7 @@ class HomeSpanTest { AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/test/java/org/oppia/android/app/testing/OngoingTopicListSpanTest.kt b/app/src/test/java/org/oppia/android/app/testing/OngoingTopicListSpanTest.kt index fc6c6aff74e..ae741ccd5b8 100644 --- a/app/src/test/java/org/oppia/android/app/testing/OngoingTopicListSpanTest.kt +++ b/app/src/test/java/org/oppia/android/app/testing/OngoingTopicListSpanTest.kt @@ -23,6 +23,7 @@ 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.ongoingtopiclist.OngoingTopicListActivity @@ -188,7 +189,7 @@ class OngoingTopicListSpanTest { AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/test/java/org/oppia/android/app/testing/PlatformParameterIntegrationTest.kt b/app/src/test/java/org/oppia/android/app/testing/PlatformParameterIntegrationTest.kt index 5d30677deb7..abb5758dc05 100644 --- a/app/src/test/java/org/oppia/android/app/testing/PlatformParameterIntegrationTest.kt +++ b/app/src/test/java/org/oppia/android/app/testing/PlatformParameterIntegrationTest.kt @@ -33,6 +33,7 @@ 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.PlatformParameter @@ -361,7 +362,7 @@ class PlatformParameterIntegrationTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/test/java/org/oppia/android/app/testing/ProfileChooserSpanTest.kt b/app/src/test/java/org/oppia/android/app/testing/ProfileChooserSpanTest.kt index ac18c7ff49f..07fa5fc3e8a 100644 --- a/app/src/test/java/org/oppia/android/app/testing/ProfileChooserSpanTest.kt +++ b/app/src/test/java/org/oppia/android/app/testing/ProfileChooserSpanTest.kt @@ -22,6 +22,7 @@ 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 @@ -390,7 +391,7 @@ class ProfileChooserSpanTest { AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/test/java/org/oppia/android/app/testing/ProfileProgressSpanCountTest.kt b/app/src/test/java/org/oppia/android/app/testing/ProfileProgressSpanCountTest.kt index 7b515278346..a51118c53c2 100644 --- a/app/src/test/java/org/oppia/android/app/testing/ProfileProgressSpanCountTest.kt +++ b/app/src/test/java/org/oppia/android/app/testing/ProfileProgressSpanCountTest.kt @@ -22,6 +22,7 @@ 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 @@ -174,7 +175,7 @@ class ProfileProgressSpanCountTest { AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/test/java/org/oppia/android/app/testing/RecentlyPlayedSpanTest.kt b/app/src/test/java/org/oppia/android/app/testing/RecentlyPlayedSpanTest.kt index 4571ab23451..d922623fe3f 100644 --- a/app/src/test/java/org/oppia/android/app/testing/RecentlyPlayedSpanTest.kt +++ b/app/src/test/java/org/oppia/android/app/testing/RecentlyPlayedSpanTest.kt @@ -23,6 +23,7 @@ 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.home.recentlyplayed.RecentlyPlayedActivity @@ -309,7 +310,7 @@ class RecentlyPlayedSpanTest { AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/test/java/org/oppia/android/app/testing/TopicRevisionSpanTest.kt b/app/src/test/java/org/oppia/android/app/testing/TopicRevisionSpanTest.kt index 9ea7e810383..24822b4c5f2 100644 --- a/app/src/test/java/org/oppia/android/app/testing/TopicRevisionSpanTest.kt +++ b/app/src/test/java/org/oppia/android/app/testing/TopicRevisionSpanTest.kt @@ -22,6 +22,7 @@ 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 @@ -174,7 +175,7 @@ class TopicRevisionSpanTest { AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/test/java/org/oppia/android/app/testing/activity/BUILD.bazel b/app/src/test/java/org/oppia/android/app/testing/activity/BUILD.bazel index e56a257b618..6b3c8185dd7 100644 --- a/app/src/test/java/org/oppia/android/app/testing/activity/BUILD.bazel +++ b/app/src/test/java/org/oppia/android/app/testing/activity/BUILD.bazel @@ -18,6 +18,7 @@ oppia_android_test( "//app/src/main/java/org/oppia/android/app/application:application_injector", "//app/src/main/java/org/oppia/android/app/application:application_injector_provider", "//app/src/main/java/org/oppia/android/app/application:common_application_modules", + "//app/src/main/java/org/oppia/android/app/application/testing:testing_build_flavor_module", "//app/src/main/java/org/oppia/android/app/testing/activity:test_activity", "//app/src/main/java/org/oppia/android/app/translation/testing:test_module", "//domain", diff --git a/app/src/test/java/org/oppia/android/app/testing/activity/TestActivityTest.kt b/app/src/test/java/org/oppia/android/app/testing/activity/TestActivityTest.kt index bb510bd60c1..a2b18b6500a 100644 --- a/app/src/test/java/org/oppia/android/app/testing/activity/TestActivityTest.kt +++ b/app/src/test/java/org/oppia/android/app/testing/activity/TestActivityTest.kt @@ -21,6 +21,7 @@ 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 @@ -199,7 +200,7 @@ class TestActivityTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/test/java/org/oppia/android/app/testing/administratorcontrols/AdministratorControlsFragmentTest.kt b/app/src/test/java/org/oppia/android/app/testing/administratorcontrols/AdministratorControlsFragmentTest.kt index 1f95d05540e..9f0d0a5e4a3 100644 --- a/app/src/test/java/org/oppia/android/app/testing/administratorcontrols/AdministratorControlsFragmentTest.kt +++ b/app/src/test/java/org/oppia/android/app/testing/administratorcontrols/AdministratorControlsFragmentTest.kt @@ -30,6 +30,7 @@ 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 @@ -198,7 +199,7 @@ class AdministratorControlsFragmentTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/test/java/org/oppia/android/app/testing/options/OptionsFragmentTest.kt b/app/src/test/java/org/oppia/android/app/testing/options/OptionsFragmentTest.kt index 3ebe6f52678..b0481e189ec 100644 --- a/app/src/test/java/org/oppia/android/app/testing/options/OptionsFragmentTest.kt +++ b/app/src/test/java/org/oppia/android/app/testing/options/OptionsFragmentTest.kt @@ -27,6 +27,7 @@ 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.AppLanguageFragment @@ -252,7 +253,7 @@ class OptionsFragmentTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/test/java/org/oppia/android/app/testing/player/split/PlayerSplitScreenTest.kt b/app/src/test/java/org/oppia/android/app/testing/player/split/PlayerSplitScreenTest.kt index a7d14d4a6c2..1b7ff73b2c3 100644 --- a/app/src/test/java/org/oppia/android/app/testing/player/split/PlayerSplitScreenTest.kt +++ b/app/src/test/java/org/oppia/android/app/testing/player/split/PlayerSplitScreenTest.kt @@ -20,6 +20,7 @@ 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 @@ -207,7 +208,7 @@ class PlayerSplitScreenTest { AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/test/java/org/oppia/android/app/testing/player/state/StateFragmentAccessibilityTest.kt b/app/src/test/java/org/oppia/android/app/testing/player/state/StateFragmentAccessibilityTest.kt index 11c88ca340c..369862684ef 100644 --- a/app/src/test/java/org/oppia/android/app/testing/player/state/StateFragmentAccessibilityTest.kt +++ b/app/src/test/java/org/oppia/android/app/testing/player/state/StateFragmentAccessibilityTest.kt @@ -24,6 +24,7 @@ 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.StateFragment @@ -214,7 +215,7 @@ class StateFragmentAccessibilityTest { AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/test/java/org/oppia/android/app/topic/info/TopicInfoFragmentLocalTest.kt b/app/src/test/java/org/oppia/android/app/topic/info/TopicInfoFragmentLocalTest.kt index 68b9da3603a..2ce810912e1 100644 --- a/app/src/test/java/org/oppia/android/app/topic/info/TopicInfoFragmentLocalTest.kt +++ b/app/src/test/java/org/oppia/android/app/topic/info/TopicInfoFragmentLocalTest.kt @@ -18,6 +18,7 @@ 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.EventLog @@ -156,7 +157,7 @@ class TopicInfoFragmentLocalTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/test/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentLocalTest.kt b/app/src/test/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentLocalTest.kt index cd09a71eb8b..34e74865716 100644 --- a/app/src/test/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentLocalTest.kt +++ b/app/src/test/java/org/oppia/android/app/topic/lessons/TopicLessonsFragmentLocalTest.kt @@ -18,6 +18,7 @@ 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.EventLog @@ -159,7 +160,7 @@ class TopicLessonsFragmentLocalTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/test/java/org/oppia/android/app/topic/questionplayer/QuestionPlayerActivityLocalTest.kt b/app/src/test/java/org/oppia/android/app/topic/questionplayer/QuestionPlayerActivityLocalTest.kt index aedeb8e43cc..f095af739b5 100644 --- a/app/src/test/java/org/oppia/android/app/topic/questionplayer/QuestionPlayerActivityLocalTest.kt +++ b/app/src/test/java/org/oppia/android/app/topic/questionplayer/QuestionPlayerActivityLocalTest.kt @@ -35,6 +35,7 @@ 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.ProfileId @@ -507,7 +508,7 @@ class QuestionPlayerActivityLocalTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/test/java/org/oppia/android/app/topic/revisioncard/RevisionCardActivityLocalTest.kt b/app/src/test/java/org/oppia/android/app/topic/revisioncard/RevisionCardActivityLocalTest.kt index 510f56bd197..df63ed67dbb 100644 --- a/app/src/test/java/org/oppia/android/app/topic/revisioncard/RevisionCardActivityLocalTest.kt +++ b/app/src/test/java/org/oppia/android/app/topic/revisioncard/RevisionCardActivityLocalTest.kt @@ -18,6 +18,7 @@ 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.EventLog @@ -148,7 +149,7 @@ class RevisionCardActivityLocalTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/test/java/org/oppia/android/app/translation/AppLanguageResourceHandlerTest.kt b/app/src/test/java/org/oppia/android/app/translation/AppLanguageResourceHandlerTest.kt index 3ef2af12404..12c7ed9d8c3 100644 --- a/app/src/test/java/org/oppia/android/app/translation/AppLanguageResourceHandlerTest.kt +++ b/app/src/test/java/org/oppia/android/app/translation/AppLanguageResourceHandlerTest.kt @@ -22,6 +22,7 @@ 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.AppLanguageSelection @@ -557,7 +558,7 @@ class AppLanguageResourceHandlerTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/test/java/org/oppia/android/app/translation/AppLanguageWatcherMixinTest.kt b/app/src/test/java/org/oppia/android/app/translation/AppLanguageWatcherMixinTest.kt index 21ac94faac3..5b4a6ede4fb 100644 --- a/app/src/test/java/org/oppia/android/app/translation/AppLanguageWatcherMixinTest.kt +++ b/app/src/test/java/org/oppia/android/app/translation/AppLanguageWatcherMixinTest.kt @@ -23,6 +23,7 @@ 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.AppLanguageSelection @@ -272,7 +273,7 @@ class AppLanguageWatcherMixinTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/test/java/org/oppia/android/app/translation/BUILD.bazel b/app/src/test/java/org/oppia/android/app/translation/BUILD.bazel index 54853ed376f..171d07f5602 100644 --- a/app/src/test/java/org/oppia/android/app/translation/BUILD.bazel +++ b/app/src/test/java/org/oppia/android/app/translation/BUILD.bazel @@ -53,6 +53,7 @@ oppia_android_test( "//app/src/main/java/org/oppia/android/app/application:application_injector", "//app/src/main/java/org/oppia/android/app/application:application_injector_provider", "//app/src/main/java/org/oppia/android/app/application:common_application_modules", + "//app/src/main/java/org/oppia/android/app/application/testing:testing_build_flavor_module", "//app/src/main/java/org/oppia/android/app/translation:app_language_locale_handler", "//app/src/main/java/org/oppia/android/app/translation/testing:test_module", "//domain", @@ -99,6 +100,7 @@ oppia_android_test( "//app/src/main/java/org/oppia/android/app/application:application_injector", "//app/src/main/java/org/oppia/android/app/application:application_injector_provider", "//app/src/main/java/org/oppia/android/app/application:common_application_modules", + "//app/src/main/java/org/oppia/android/app/application/testing:testing_build_flavor_module", "//app/src/main/java/org/oppia/android/app/translation/testing:test_module", "//data/src/main/java/org/oppia/android/data/backends/gae:prod_module", "//domain/src/main/java/org/oppia/android/domain/onboarding/testing:retriever_test_module", diff --git a/app/src/test/java/org/oppia/android/app/utility/datetime/DateTimeUtilTest.kt b/app/src/test/java/org/oppia/android/app/utility/datetime/DateTimeUtilTest.kt index 67d0b06add7..95ba0d375ff 100644 --- a/app/src/test/java/org/oppia/android/app/utility/datetime/DateTimeUtilTest.kt +++ b/app/src/test/java/org/oppia/android/app/utility/datetime/DateTimeUtilTest.kt @@ -22,6 +22,7 @@ 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 @@ -185,7 +186,7 @@ class DateTimeUtilTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class + SyncStatusModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/app/src/test/java/org/oppia/android/app/utility/math/BUILD.bazel b/app/src/test/java/org/oppia/android/app/utility/math/BUILD.bazel index b50532b834d..24a84596c68 100644 --- a/app/src/test/java/org/oppia/android/app/utility/math/BUILD.bazel +++ b/app/src/test/java/org/oppia/android/app/utility/math/BUILD.bazel @@ -18,6 +18,7 @@ oppia_android_test( "//app/src/main/java/org/oppia/android/app/application:application_injector", "//app/src/main/java/org/oppia/android/app/application:application_injector_provider", "//app/src/main/java/org/oppia/android/app/application:common_application_modules", + "//app/src/main/java/org/oppia/android/app/application/testing:testing_build_flavor_module", "//app/src/main/java/org/oppia/android/app/translation/testing:test_module", "//app/src/main/java/org/oppia/android/app/utility/math:math_expression_accessibility_util", "//domain/src/main/java/org/oppia/android/domain/onboarding/testing:retriever_test_module", diff --git a/app/src/test/java/org/oppia/android/app/utility/math/MathExpressionAccessibilityUtilTest.kt b/app/src/test/java/org/oppia/android/app/utility/math/MathExpressionAccessibilityUtilTest.kt index d742b20a586..293f8b0e94a 100644 --- a/app/src/test/java/org/oppia/android/app/utility/math/MathExpressionAccessibilityUtilTest.kt +++ b/app/src/test/java/org/oppia/android/app/utility/math/MathExpressionAccessibilityUtilTest.kt @@ -21,6 +21,7 @@ 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.MathBinaryOperation @@ -1331,7 +1332,7 @@ class MathExpressionAccessibilityUtilTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/build_flavors.bzl b/build_flavors.bzl index a5ca841a737..ec541fe8046 100644 --- a/build_flavors.bzl +++ b/build_flavors.bzl @@ -3,7 +3,7 @@ Macros & definitions corresponding to Oppia binary build flavors. """ load("//:oppia_android_application.bzl", "declare_deployable_application", "oppia_android_application") -load("//:version.bzl", "MAJOR_VERSION", "MINOR_VERSION", "OPPIA_ALPHA_KENYA_VERSION_CODE", "OPPIA_ALPHA_KITKAT_VERSION_CODE", "OPPIA_ALPHA_VERSION_CODE", "OPPIA_DEV_KITKAT_VERSION_CODE", "OPPIA_DEV_VERSION_CODE") +load("//:version.bzl", "MAJOR_VERSION", "MINOR_VERSION", "OPPIA_ALPHA_KENYA_VERSION_CODE", "OPPIA_ALPHA_KITKAT_VERSION_CODE", "OPPIA_ALPHA_VERSION_CODE", "OPPIA_BETA_VERSION_CODE", "OPPIA_DEV_KITKAT_VERSION_CODE", "OPPIA_DEV_VERSION_CODE", "OPPIA_GA_VERSION_CODE") # Defines the list of flavors available to build the Oppia app in. Note to developers: this list # should be ordered by the development pipeline (i.e. features go through dev first, then other @@ -14,6 +14,8 @@ AVAILABLE_FLAVORS = [ "alpha", "alpha_kitkat", "alpha_kenya", + "beta", + "ga", ] # This file contains the list of classes that must be in the main dex list for the legacy multidex @@ -108,6 +110,32 @@ _FLAVOR_METADATA = { "version_code": OPPIA_ALPHA_KENYA_VERSION_CODE, "application_class": ".app.application.alphakenya.AlphaKenyaOppiaApplication", }, + "beta": { + "manifest": "//app:src/main/AndroidManifest.xml", + "min_sdk_version": 21, + "target_sdk_version": 30, + "multidex": "native", + "proguard_specs": _PRODUCTION_PROGUARD_SPECS, + "production_release": True, + "deps": [ + "//app/src/main/java/org/oppia/android/app/application/beta:beta_application", + ], + "version_code": OPPIA_BETA_VERSION_CODE, + "application_class": ".app.application.beta.BetaOppiaApplication", + }, + "ga": { + "manifest": "//app:src/main/AndroidManifest.xml", + "min_sdk_version": 21, + "target_sdk_version": 30, + "multidex": "native", + "proguard_specs": _PRODUCTION_PROGUARD_SPECS, + "production_release": True, + "deps": [ + "//app/src/main/java/org/oppia/android/app/application/ga:general_availability_application", + ], + "version_code": OPPIA_GA_VERSION_CODE, + "application_class": ".app.application.ga.GaOppiaApplication", + }, } def _transform_android_manifest_impl(ctx): diff --git a/data/BUILD.bazel b/data/BUILD.bazel index aecd0697a62..8189af63a2f 100644 --- a/data/BUILD.bazel +++ b/data/BUILD.bazel @@ -6,6 +6,12 @@ This library provides data to the rest of the application. load("@dagger//:workspace_defs.bzl", "dagger_rules") load("//data:data_test.bzl", "data_test") +filegroup( + name = "test_manifest", + srcs = ["src/test/AndroidManifest.xml"], + visibility = ["//:oppia_testing_visibility"], +) + # keep sorted TEST_DEPS = [ ":dagger", diff --git a/data/src/main/java/org/oppia/android/data/persistence/PersistentCacheStore.kt b/data/src/main/java/org/oppia/android/data/persistence/PersistentCacheStore.kt index daacb9b8822..e7c081e6834 100644 --- a/data/src/main/java/org/oppia/android/data/persistence/PersistentCacheStore.kt +++ b/data/src/main/java/org/oppia/android/data/persistence/PersistentCacheStore.kt @@ -1,6 +1,6 @@ package org.oppia.android.data.persistence -import android.content.Context +import android.app.Application import androidx.annotation.GuardedBy import com.google.protobuf.MessageLite import kotlinx.coroutines.Deferred @@ -21,24 +21,27 @@ import kotlin.concurrent.withLock /** * An on-disk persistent cache for proto messages that ensures reads and writes happen in a - * well-defined order. Note that if this cache is used like a [DataProvider], there is a race - * condition between the initial store's data being retrieved and any early writes to the store - * (writes generally win). If this is not ideal, callers should use [primeInMemoryCacheAsync] to - * synchronously kick-off a read update to the store that is guaranteed to complete before any - * writes. This will be reflected in the first time the store's state is delivered to a subscriber - * to a LiveData version of this data provider. + * well-defined order. * - * Note that this is a fast-response data provider, meaning it will provide a pending [AsyncResult] - * to subscribers immediately until the actual store is retrieved from disk. + * Note that if this cache is used like a [DataProvider], there is a race condition between the + * initial store's data being retrieved and any early writes to the store (writes generally win). If + * this is not ideal, callers should use [primeInMemoryAndDiskCacheAsync] to synchronously kick-off + * a read update to the store that is guaranteed to complete before any writes. This will be + * reflected in the first time the store's state is delivered to a subscriber to a LiveData version + * of this data provider. Note that this priming will always complete before any updates if it's + * called before updates/reads. + * + * Note that this is a fast-response data provider, meaning it will provide a [AsyncResult.Pending] + * result to subscribers immediately until the actual store is retrieved from disk. */ class PersistentCacheStore private constructor( - context: Context, + application: Application, cacheFactory: InMemoryBlockingCache.Factory, private val asyncDataSubscriptionManager: AsyncDataSubscriptionManager, cacheName: String, private val initialValue: T, - directory: File = context.filesDir -) : DataProvider(context) { + directory: File = application.filesDir +) : DataProvider(application) { private val cacheFileName = "$cacheName.cache" private val providerId = PersistentCacheStoreId(cacheFileName) private val failureLock = ReentrantLock() @@ -50,14 +53,21 @@ class PersistentCacheStore private constructor( cacheFactory.create(CachePayload(state = CacheState.UNLOADED, value = initialValue)) init { - cache.observeChanges { - asyncDataSubscriptionManager.notifyChange(providerId) + cache.observeChanges { oldValue, newValue -> + // Only notice subscribers if the in-memory version of the cache actually changed (not just + // its load state). This extra check ensures that priming the cache does not unnecessarily + // trigger a notification which would result in an unnecessary retrieveData() call. The + // exception is that changing from an UNLOADED state always results in a notification (since + // UNLOADED is treated as 'pending' by default). + val wasPending = oldValue?.state == CacheState.UNLOADED + val nowPending = newValue?.state == CacheState.UNLOADED + if ((wasPending && !nowPending) || oldValue?.value != newValue?.value) { + asyncDataSubscriptionManager.notifyChange(providerId) + } } } - override fun getId(): Any { - return providerId - } + override fun getId(): Any = providerId override suspend fun retrieveData(): AsyncResult { cache.readIfPresentAsync().await().let { cachePayload -> @@ -85,69 +95,81 @@ class PersistentCacheStore private constructor( } /** - * Kicks off a read operation to update the in-memory cache. This operation blocks against calls - * to [storeDataAsync] and deferred calls to [retrieveData]. + * Primes the current cache such that certain guarantees can be assured for both the in-memory and + * on-disk version of this cache, depending on which policies are selected. * - * @param forceUpdate indicates whether to force a reset of the in-memory cache. Note that this - * only forces a load; if the load fails then the store will remain in its same state. If this - * value is false (the default), it will only perform file I/O if the cache is not already - * loaded into memory. - * @returns a [Deferred] that completes upon the completion of priming the cache, or failure to do - * so with the failed exception. Note that the failure reason will not be propagated to a - * LiveData-converted version of this data provider, so it must be handled at the callsite for - * this method. - */ - fun primeInMemoryCacheAsync(forceUpdate: Boolean = false): Deferred { - return cache.updateIfPresentAsync { cachePayload -> - if (forceUpdate || cachePayload.state == CacheState.UNLOADED) { - // Store the retrieved on-disk cache, if it's present (otherwise set up state such that - // retrieveData() does not attempt to load the file from disk again since the attempt was - // made here). - loadFileCache(cachePayload) - } else { - // Otherwise, keep the cache the same. - cachePayload - } - } - } - - /** - * Primes the current cache such that both the in-memory and on-disk versions of this cache are - * guaranteed to be in sync, returning a [Deferred] that completes only after the operation is - * finished. + * Note that the value of the returned [Deferred] is not useful. The state of the cache should + * monitored by treating this provider as a [DataProvider]. This method may result in an update + * notification to observers of this [DataProvider]. * - * The provided [initialize] initializer will only ever be called if the on-disk cache is not yet - * initialized, and it will be passed the initial value used to create this cache store. The value - * it returns will be used to initialize both the in-memory and on-disk copies of the cache. + * Note also that this method is particularly useful in two specific cases: + * 1. When an instance of this cache needs to be loaded from disk before an update operation + * occurs (otherwise update() will likely complete before a load, overwriting the current + * on-disk cache state). + * 2. When the cache needs to be initialized exactly once to a specific value (such as the case + * when an ID that cannot change after initialization needs to be generated and stored exactly + * once). * - * The value of the returned [Deferred] is not useful. The state of the cache should monitored by - * treating this provider as a [DataProvider]. This method may result in multiple update - * notifications to observers of this [DataProvider], but the latest value will be the source of - * truth. + * Each of the above states are possible using different combinations of the provided [UpdateMode] + * and [PublishMode] parameters. * - * Where [primeInMemoryCacheAsync] is useful to ensure any on-disk cache is properly loaded into - * memory prior to using a cache store, this method is useful when a disk cache has a - * contextually-sensitive initialization routine (such as an ID that cannot change after - * initialization) as it ensures a reliable, initial clean state for the cache store that will be - * consistent with future runs of the app. + * Finally, this method succeeding more or less guarantees that the cache store is now in a good + * state (i.e. it will even recover from a corrupted or invalid disk cache file). + * + * @param updateMode how the cache should be changed (depending on whether it's been loaded yet, + * and whether it has an on-disk cache) + * @param publishMode whether changes to the cache's in-memory copy during priming should be kept + * in-memory and sent to observers (otherwise, only store the results on-disk and do not + * notify changes). Note that the in-memory cache *will* be updated if it hasn't yet been + * initialized (which may mean saving a result from [update]). + * @param update an optional function to transform the cache's current (in-memory if loaded, or + * from-disk if not) state to a new state, and then update the on-disk cache (and potentially + * the in-memory cache based on [publishMode]). Note that if the cache has not been loaded yet + * and has no on-disk copy then the cache's [initialValue] will be passed, instead. Omitting + * this transformation will just ensure the in-memory and/or on-disk cache are appropriately + * initialized. + * @return a [Deferred] tracking the success/failure of priming this cache store */ - fun primeInMemoryAndDiskCacheAsync(initialize: (T) -> T): Deferred { + fun primeInMemoryAndDiskCacheAsync( + updateMode: UpdateMode, + publishMode: PublishMode, + update: (T) -> T = { it } + ): Deferred { return cache.updateIfPresentAsync { cachePayload -> - when (cachePayload.state) { + // It's expected 'oldState' to match 'cachePayload' unless the cache hasn't yet been read + // (since then 'cachePayload' will be based on the store's default value). + val (oldState, newState) = when (cachePayload.state) { CacheState.UNLOADED -> { val loadedPayload = loadFileCache(cachePayload) when (loadedPayload.state) { // The state should never stay as UNLOADED. CacheState.UNLOADED -> error("Something went wrong loading the cache during priming: $cacheFile") - CacheState.IN_MEMORY_ONLY -> storeFileCache(loadedPayload, initialize) // Needs saving. - CacheState.IN_MEMORY_AND_ON_DISK -> loadedPayload // Loaded from disk successfully. + CacheState.IN_MEMORY_ONLY -> { + // Needs saving. In this case, there is no "old" value since the cache was never + // initialized. + val storedPayload = storeFileCache(loadedPayload, update) + storedPayload to storedPayload + } + CacheState.IN_MEMORY_AND_ON_DISK -> // Loaded from disk successfully. + loadedPayload to loadedPayload.maybeReprimePayload(updateMode, update) } } - // This generally indicates that something went wrong reading the on-disk cache, so make - // sure it's properly initialized. - CacheState.IN_MEMORY_ONLY -> storeFileCache(cachePayload, initialize) - CacheState.IN_MEMORY_AND_ON_DISK -> cachePayload + // Generally indicates that the cache was loaded but never written. + CacheState.IN_MEMORY_ONLY -> cachePayload to storeFileCache(cachePayload, update) + CacheState.IN_MEMORY_AND_ON_DISK -> + cachePayload to cachePayload.maybeReprimePayload(updateMode, update) + } + + // The returned payload is always expected to be IN_MEMORY_AND_ON_DISK, but the in-memory copy + // may be intentionally kept out-of-date so that cache reads pick up the original version + // rather than the new one stored on-disk. Furthermore, this method guarantees by this point + // that the cache is in a good, non-error state (so the error is cleared in case one occurred + // during early priming). + failureLock.withLock { deferredLoadCacheFailure = null } + return@updateIfPresentAsync when (publishMode) { + PublishMode.PUBLISH_TO_IN_MEMORY_CACHE -> newState + PublishMode.DO_NOT_PUBLISH_TO_IN_MEMORY_CACHE -> newState.copy(value = oldState.value) } } } @@ -290,6 +312,16 @@ class PersistentCacheStore private constructor( ) } + private fun CachePayload.maybeReprimePayload( + updateMode: UpdateMode, + initialize: (T) -> T + ): CachePayload { + return when (updateMode) { + UpdateMode.UPDATE_IF_NEW_CACHE -> this // Nothing extra to do. + UpdateMode.UPDATE_ALWAYS -> storeFileCache(this, initialize) // Recompute the payload. + } + } + private data class PersistentCacheStoreId(private val id: String) /** Represents different states the cache store can be in. */ @@ -306,13 +338,46 @@ class PersistentCacheStore private constructor( private data class CachePayload(val state: CacheState, val value: T) + /** + * The mode of on-disk data updating that can be configured for specific operations like cache + * priming. + * + * This mode only configures on-disk data changes, not in-memory (see [PublishMode] for that). + */ + enum class UpdateMode { + /** Indicates that the on-disk cache should only be changed if it doesn't already exist. */ + UPDATE_IF_NEW_CACHE, + + /** + * Indicates that the on-disk cache should always be changed regardless of if it already exists. + */ + UPDATE_ALWAYS + } + + /** + * The mode of in-memory data updating that can be configured for specific operations like cache + * priming. + * + * This mode only configures in-memory data changes, not on-disk (see [UpdateMode] for that). + */ + enum class PublishMode { + /** + * Indicates that data changes should update the in-memory cache and be broadcast to + * subscribers. + */ + PUBLISH_TO_IN_MEMORY_CACHE, + + /** Indicates that data changes should not change the in-memory cache. */ + DO_NOT_PUBLISH_TO_IN_MEMORY_CACHE + } + /** * An injectable factory for [PersistentCacheStore]s. The stores themselves should be retrievable * from central controllers since they can't be placed directly in the Dagger graph. */ @Singleton class Factory @Inject constructor( - private val context: Context, + private val application: Application, private val cacheFactory: InMemoryBlockingCache.Factory, private val asyncDataSubscriptionManager: AsyncDataSubscriptionManager, private val directoryManagementUtil: DirectoryManagementUtil @@ -325,7 +390,7 @@ class PersistentCacheStore private constructor( */ fun create(cacheName: String, initialValue: T): PersistentCacheStore { return PersistentCacheStore( - context, + application, cacheFactory, asyncDataSubscriptionManager, cacheName, @@ -344,7 +409,7 @@ class PersistentCacheStore private constructor( ): PersistentCacheStore { val profileDirectory = directoryManagementUtil.getOrCreateDir(profileId.internalId.toString()) return PersistentCacheStore( - context, + application, cacheFactory, asyncDataSubscriptionManager, cacheName, diff --git a/data/src/test/java/org/oppia/android/data/persistence/BUILD.bazel b/data/src/test/java/org/oppia/android/data/persistence/BUILD.bazel new file mode 100644 index 00000000000..9a0686181ab --- /dev/null +++ b/data/src/test/java/org/oppia/android/data/persistence/BUILD.bazel @@ -0,0 +1,32 @@ +""" +Tests for infrastructure that provides data persistence support for the rest of the app. +""" + +load("@dagger//:workspace_defs.bzl", "dagger_rules") +load("//:oppia_android_test.bzl", "oppia_android_test") + +oppia_android_test( + name = "PersistentCacheStoreTest", + srcs = ["PersistentCacheStoreTest.kt"], + custom_package = "org.oppia.android.data.persistence", + test_class = "org.oppia.android.data.persistence.PersistentCacheStoreTest", + test_manifest = "//data:test_manifest", + deps = [ + ":dagger", + "//data/src/main/java/org/oppia/android/data/persistence:cache_store", + "//model/src/main/proto:test_models", + "//testing", + "//testing/src/main/java/org/oppia/android/testing/data:data_provider_test_monitor", + "//testing/src/main/java/org/oppia/android/testing/robolectric:test_module", + "//testing/src/main/java/org/oppia/android/testing/threading:test_module", + "//third_party:androidx_test_ext_junit", + "//third_party:com_google_truth_extensions_truth-liteproto-extension", + "//third_party:com_google_truth_truth", + "//third_party:junit_junit", + "//third_party:org_mockito_mockito-core", + "//third_party:org_robolectric_robolectric", + "//third_party:robolectric_android-all", + ], +) + +dagger_rules() diff --git a/data/src/test/java/org/oppia/android/data/persistence/PersistentCacheStoreTest.kt b/data/src/test/java/org/oppia/android/data/persistence/PersistentCacheStoreTest.kt index c14643c3baa..6143cf71b39 100644 --- a/data/src/test/java/org/oppia/android/data/persistence/PersistentCacheStoreTest.kt +++ b/data/src/test/java/org/oppia/android/data/persistence/PersistentCacheStoreTest.kt @@ -19,19 +19,32 @@ import kotlinx.coroutines.async import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule import org.oppia.android.app.model.TestMessage +import org.oppia.android.data.persistence.PersistentCacheStore.PublishMode.DO_NOT_PUBLISH_TO_IN_MEMORY_CACHE +import org.oppia.android.data.persistence.PersistentCacheStore.PublishMode.PUBLISH_TO_IN_MEMORY_CACHE +import org.oppia.android.data.persistence.PersistentCacheStore.UpdateMode.UPDATE_ALWAYS +import org.oppia.android.data.persistence.PersistentCacheStore.UpdateMode.UPDATE_IF_NEW_CACHE +import org.oppia.android.data.persistence.PersistentCacheStoreTest.SubscriptionCallback.Companion.toAsyncChange import org.oppia.android.testing.TestLogReportingModule import org.oppia.android.testing.data.AsyncResultSubject.Companion.assertThat import org.oppia.android.testing.data.DataProviderTestMonitor 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.util.data.AsyncDataSubscriptionManager import org.oppia.android.util.data.AsyncResult import org.oppia.android.util.data.DataProviders import org.oppia.android.util.data.DataProvidersInjector import org.oppia.android.util.data.DataProvidersInjectorProvider +import org.oppia.android.util.data.ObserveAsyncChange import org.oppia.android.util.threading.BackgroundDispatcher import org.robolectric.annotation.Config import org.robolectric.annotation.LooperMode @@ -42,6 +55,7 @@ import java.io.IOException import java.lang.IllegalStateException import javax.inject.Inject import javax.inject.Singleton +import kotlin.reflect.full.staticFunctions private const val CACHE_NAME_1 = "test_cache_1" private const val CACHE_NAME_2 = "test_cache_2" @@ -55,16 +69,38 @@ private const val CACHE_NAME_2 = "test_cache_2" @Config(application = PersistentCacheStoreTest.TestApplication::class) class PersistentCacheStoreTest { private companion object { - private val TEST_MESSAGE_V1 = TestMessage.newBuilder().setIntValue(1).build() - private val TEST_MESSAGE_V2 = TestMessage.newBuilder().setIntValue(2).build() + private const val TEST_INT_V1 = 1 + private const val TEST_INT_V2 = 2 + private const val TEST_STR_V1 = "test string" + private val DEFAULT_TEST_MESSAGE = TestMessage.getDefaultInstance() + private val TEST_INT_MESSAGE_V1 = createTestMessage(intValue = TEST_INT_V1) + private val TEST_INT_MESSAGE_V2 = createTestMessage(intValue = TEST_INT_V2) + + private fun TestMessage.addString(strValue: String) = toBuilder().apply { + this.strValue = strValue + }.build() + + private fun createTestMessage( + intValue: Int = DEFAULT_TEST_MESSAGE.intValue, + strValue: String = DEFAULT_TEST_MESSAGE.strValue + ) = TestMessage.newBuilder().apply { + this.intValue = intValue + this.strValue = strValue + }.build() } + @Rule + @JvmField + val mockitoRule: MockitoRule = MockitoJUnit.rule() + @Inject lateinit var context: Context @Inject lateinit var cacheFactory: PersistentCacheStore.Factory @Inject lateinit var dataProviders: DataProviders @Inject lateinit var testCoroutineDispatchers: TestCoroutineDispatchers @field:[Inject BackgroundDispatcher] lateinit var backgroundDispatcher: CoroutineDispatcher @Inject lateinit var monitorFactory: DataProviderTestMonitor.Factory + @Inject lateinit var asyncDataSubscriptionManager: AsyncDataSubscriptionManager + @Mock lateinit var mockSubscriptionCallback: SubscriptionCallback private val backgroundDispatcherScope by lazy { CoroutineScope(backgroundDispatcher) } @@ -107,12 +143,12 @@ class PersistentCacheStoreTest { @Test fun testCache_nonDefaultInitialState_loaded_providesCorrectInitialVal() { - val cacheStore = cacheFactory.create(CACHE_NAME_1, TEST_MESSAGE_V1) + val cacheStore = cacheFactory.create(CACHE_NAME_1, TEST_INT_MESSAGE_V1) val value = monitorFactory.waitForNextSuccessfulResult(cacheStore) // Caches can have non-default initial states. - assertThat(value).isEqualTo(TEST_MESSAGE_V1) + assertThat(value).isEqualTo(TEST_INT_MESSAGE_V1) } @Test @@ -120,27 +156,28 @@ class PersistentCacheStoreTest { val cacheStore = cacheFactory.create(CACHE_NAME_1, TestMessage.getDefaultInstance()) monitorFactory.waitForNextSuccessfulResult(cacheStore) - val storeOp = cacheStore.storeDataAsync { TEST_MESSAGE_V1 } + val storeOp = cacheStore.storeDataAsync { TEST_INT_MESSAGE_V1 } testCoroutineDispatchers.advanceUntilIdle() // The store operation should be completed, and the observer should be notified of the changed // value. assertThat(storeOp.isCompleted).isTrue() - assertThat(monitorFactory.waitForNextSuccessfulResult(cacheStore)).isEqualTo(TEST_MESSAGE_V1) + assertThat(monitorFactory.waitForNextSuccessfulResult(cacheStore)) + .isEqualTo(TEST_INT_MESSAGE_V1) } @Test fun testCache_registerObserver_updateBefore_observesUpdatedStateInitially() { val cacheStore = cacheFactory.create(CACHE_NAME_1, TestMessage.getDefaultInstance()) - val storeOp = cacheStore.storeDataAsync { TEST_MESSAGE_V1 } + val storeOp = cacheStore.storeDataAsync { TEST_INT_MESSAGE_V1 } testCoroutineDispatchers.advanceUntilIdle() // The store operation should be completed, and the observer's only call should be the updated // state. val value = monitorFactory.waitForNextSuccessfulResult(cacheStore) assertThat(storeOp.isCompleted).isTrue() - assertThat(value).isEqualTo(TEST_MESSAGE_V1) + assertThat(value).isEqualTo(TEST_INT_MESSAGE_V1) } @Test @@ -149,7 +186,7 @@ class PersistentCacheStoreTest { val monitor = monitorFactory.createMonitor(cacheStore) monitor.waitForNextSuccessResult() - val storeOp = cacheStore.storeDataAsync(updateInMemoryCache = false) { TEST_MESSAGE_V1 } + val storeOp = cacheStore.storeDataAsync(updateInMemoryCache = false) { TEST_INT_MESSAGE_V1 } testCoroutineDispatchers.advanceUntilIdle() // The store operation should be completed, but the observe will not be notified of changes @@ -162,7 +199,7 @@ class PersistentCacheStoreTest { fun testCache_noMemoryCacheUpdate_updateBeforeReg_observesUpdatedState() { val cacheStore = cacheFactory.create(CACHE_NAME_1, TestMessage.getDefaultInstance()) - val storeOp = cacheStore.storeDataAsync(updateInMemoryCache = false) { TEST_MESSAGE_V1 } + val storeOp = cacheStore.storeDataAsync(updateInMemoryCache = false) { TEST_INT_MESSAGE_V1 } testCoroutineDispatchers.advanceUntilIdle() // The store operation should be completed, but the observer will receive the updated state @@ -170,13 +207,14 @@ class PersistentCacheStoreTest { // NB: This may not be ideal behavior long-term; the store may need to be updated to be more // resilient to these types of scenarios. assertThat(storeOp.isCompleted).isTrue() - assertThat(monitorFactory.waitForNextSuccessfulResult(cacheStore)).isEqualTo(TEST_MESSAGE_V1) + assertThat(monitorFactory.waitForNextSuccessfulResult(cacheStore)) + .isEqualTo(TEST_INT_MESSAGE_V1) } @Test fun testCache_updated_newCache_newObserver_observesNewValue() { val cacheStore1 = cacheFactory.create(CACHE_NAME_1, TestMessage.getDefaultInstance()) - val storeOp = cacheStore1.storeDataAsync { TEST_MESSAGE_V1 } + val storeOp = cacheStore1.storeDataAsync { TEST_INT_MESSAGE_V1 } testCoroutineDispatchers.advanceUntilIdle() // Create a new cache with the same name. @@ -187,13 +225,14 @@ class PersistentCacheStoreTest { // refresh since UI components should share the same cache instance via an application-bound // controller object. assertThat(storeOp.isCompleted).isTrue() - assertThat(monitorFactory.waitForNextSuccessfulResult(cacheStore2)).isEqualTo(TEST_MESSAGE_V1) + assertThat(monitorFactory.waitForNextSuccessfulResult(cacheStore2)) + .isEqualTo(TEST_INT_MESSAGE_V1) } @Test fun testCache_updated_noInMemoryCacheUpdate_newCache_newObserver_observesNewVal() { val cacheStore1 = cacheFactory.create(CACHE_NAME_1, TestMessage.getDefaultInstance()) - val storeOp = cacheStore1.storeDataAsync(updateInMemoryCache = false) { TEST_MESSAGE_V1 } + val storeOp = cacheStore1.storeDataAsync(updateInMemoryCache = false) { TEST_INT_MESSAGE_V1 } testCoroutineDispatchers.advanceUntilIdle() // Create a new cache with the same name. @@ -205,108 +244,28 @@ class PersistentCacheStoreTest { // Dagger component refresh since UI components should share the same cache instance via an // application-bound controller object. assertThat(storeOp.isCompleted).isTrue() - assertThat(monitorFactory.waitForNextSuccessfulResult(cacheStore2)).isEqualTo(TEST_MESSAGE_V1) + assertThat(monitorFactory.waitForNextSuccessfulResult(cacheStore2)) + .isEqualTo(TEST_INT_MESSAGE_V1) } @Test fun testExistingDiskCache_newCacheObject_updateNoMemThenRead_receivesNewValue() { val cacheStore1 = cacheFactory.create(CACHE_NAME_1, TestMessage.getDefaultInstance()) val storeOp1 = - cacheStore1.storeDataAsync(updateInMemoryCache = false) { TEST_MESSAGE_V1 } + cacheStore1.storeDataAsync(updateInMemoryCache = false) { TEST_INT_MESSAGE_V1 } testCoroutineDispatchers.advanceUntilIdle() // Create a new cache with the same name and update it, then observe it. val cacheStore2 = cacheFactory.create(CACHE_NAME_1, TestMessage.getDefaultInstance()) - val storeOp2 = cacheStore2.storeDataAsync(updateInMemoryCache = false) { TEST_MESSAGE_V2 } + val storeOp2 = cacheStore2.storeDataAsync(updateInMemoryCache = false) { TEST_INT_MESSAGE_V2 } testCoroutineDispatchers.advanceUntilIdle() // Both operations should be complete, and the observer will receive the latest value since the // update was posted before the read occurred. assertThat(storeOp1.isCompleted).isTrue() assertThat(storeOp2.isCompleted).isTrue() - assertThat(monitorFactory.waitForNextSuccessfulResult(cacheStore2)).isEqualTo(TEST_MESSAGE_V2) - } - - @Test - fun testExistingDiskCache_newObject_updateNoMemThenRead_primed_receivesPrevVal() { - val cacheStore1 = cacheFactory.create(CACHE_NAME_1, TestMessage.getDefaultInstance()) - val storeOp1 = - cacheStore1.storeDataAsync(updateInMemoryCache = false) { TEST_MESSAGE_V1 } - testCoroutineDispatchers.advanceUntilIdle() - - // Create a new cache with the same name and update it, then observe it. However, first prime - // it. - val cacheStore2 = cacheFactory.create(CACHE_NAME_1, TestMessage.getDefaultInstance()) - val primeOp = cacheStore2.primeInMemoryCacheAsync() - testCoroutineDispatchers.advanceUntilIdle() - val storeOp2 = cacheStore2.storeDataAsync(updateInMemoryCache = false) { TEST_MESSAGE_V2 } - testCoroutineDispatchers.advanceUntilIdle() - - // All operations should be complete, but the observer will receive the previous update rather - // than the latest since it wasn't updated in memory and the cache was pre-primed. - assertThat(storeOp1.isCompleted).isTrue() - assertThat(storeOp2.isCompleted).isTrue() - assertThat(primeOp.isCompleted).isTrue() - assertThat(monitorFactory.waitForNextSuccessfulResult(cacheStore2)).isEqualTo(TEST_MESSAGE_V1) - } - - @Test - fun testExistingDiskCache_newObject_updateMemThenRead_primed_receivesNewVal() { - val cacheStore1 = cacheFactory.create(CACHE_NAME_1, TestMessage.getDefaultInstance()) - val storeOp1 = - cacheStore1.storeDataAsync(updateInMemoryCache = false) { TEST_MESSAGE_V1 } - testCoroutineDispatchers.advanceUntilIdle() - - // Create a new cache with the same name and update it, then observe it. However, first prime - // it. - val cacheStore2 = cacheFactory.create(CACHE_NAME_1, TestMessage.getDefaultInstance()) - val primeOp = cacheStore2.primeInMemoryCacheAsync() - testCoroutineDispatchers.advanceUntilIdle() - val storeOp2 = cacheStore2.storeDataAsync { TEST_MESSAGE_V2 } - testCoroutineDispatchers.advanceUntilIdle() - - // Similar to the previous test, except due to the in-memory update the observer will receive - // the latest result regardless of the cache priming. - assertThat(storeOp1.isCompleted).isTrue() - assertThat(storeOp2.isCompleted).isTrue() - assertThat(primeOp.isCompleted).isTrue() - assertThat(monitorFactory.waitForNextSuccessfulResult(cacheStore2)).isEqualTo(TEST_MESSAGE_V2) - } - - @Test - fun testCache_primed_afterStoreUpdateWithoutMemUpdate_notForced_observesOldValue() { - val cacheStore = cacheFactory.create(CACHE_NAME_1, TestMessage.getDefaultInstance()) - // Force initializing the store's in-memory cache - monitorFactory.waitForNextSuccessfulResult(cacheStore) - - val storeOp = cacheStore.storeDataAsync(updateInMemoryCache = false) { TEST_MESSAGE_V1 } - testCoroutineDispatchers.advanceUntilIdle() - val primeOp = cacheStore.primeInMemoryCacheAsync(forceUpdate = false) - testCoroutineDispatchers.advanceUntilIdle() - - // Both ops will succeed, and the observer will receive the old value due to the update not - // changing the in-memory cache, and the prime no-oping due to the cache already being - // initialized. - assertThat(storeOp.isCompleted).isTrue() - assertThat(primeOp.isCompleted).isTrue() - assertThat(monitorFactory.waitForNextSuccessfulResult(cacheStore)).isEqualToDefaultInstance() - } - - @Test - fun testCache_primed_afterStoreUpdateWithoutMemoryUpdate_forced_observesNewValue() { - val cacheStore = cacheFactory.create(CACHE_NAME_1, TestMessage.getDefaultInstance()) - monitorFactory.waitForNextSuccessfulResult(cacheStore) - - val storeOp = cacheStore.storeDataAsync(updateInMemoryCache = false) { TEST_MESSAGE_V1 } - testCoroutineDispatchers.advanceUntilIdle() - val primeOp = cacheStore.primeInMemoryCacheAsync(forceUpdate = true) - testCoroutineDispatchers.advanceUntilIdle() - - // The observer will receive the new value because the prime was forced. This ensures the - // store's in-memory cache is now up-to-date with the on-disk representation. - assertThat(storeOp.isCompleted).isTrue() - assertThat(primeOp.isCompleted).isTrue() - assertThat(monitorFactory.waitForNextSuccessfulResult(cacheStore)).isEqualTo(TEST_MESSAGE_V1) + assertThat(monitorFactory.waitForNextSuccessfulResult(cacheStore2)) + .isEqualTo(TEST_INT_MESSAGE_V2) } @Test @@ -325,7 +284,7 @@ class PersistentCacheStoreTest { @Test fun testCache_update_clear_resetsCacheToInitialState() { val cacheStore = cacheFactory.create(CACHE_NAME_1, TestMessage.getDefaultInstance()) - val storeOp = cacheStore.storeDataAsync { TEST_MESSAGE_V1 } + val storeOp = cacheStore.storeDataAsync { TEST_INT_MESSAGE_V1 } testCoroutineDispatchers.advanceUntilIdle() val clearOp = cacheStore.clearCacheAsync() @@ -340,7 +299,7 @@ class PersistentCacheStoreTest { @Test fun testCache_update_existingObserver_clear_isNotifiedOfClear() { val cacheStore = cacheFactory.create(CACHE_NAME_1, TestMessage.getDefaultInstance()) - val storeOp = cacheStore.storeDataAsync { TEST_MESSAGE_V1 } + val storeOp = cacheStore.storeDataAsync { TEST_INT_MESSAGE_V1 } testCoroutineDispatchers.advanceUntilIdle() val monitor = monitorFactory.createMonitor(cacheStore) @@ -357,34 +316,36 @@ class PersistentCacheStoreTest { @Test fun testCache_update_newCache_observesInitialState() { val cacheStore1 = cacheFactory.create(CACHE_NAME_1, TestMessage.getDefaultInstance()) - val storeOp = cacheStore1.storeDataAsync { TEST_MESSAGE_V1 } + val storeOp = cacheStore1.storeDataAsync { TEST_INT_MESSAGE_V1 } testCoroutineDispatchers.advanceUntilIdle() val clearOp = cacheStore1.clearCacheAsync() testCoroutineDispatchers.advanceUntilIdle() - val cacheStore2 = cacheFactory.create(CACHE_NAME_1, TEST_MESSAGE_V2) + val cacheStore2 = cacheFactory.create(CACHE_NAME_1, TEST_INT_MESSAGE_V2) // The new observer should observe that there's no persisted on-disk store since it has a // different default value that would only be used if there wasn't already on-disk storage. assertThat(storeOp.isCompleted).isTrue() assertThat(clearOp.isCompleted).isTrue() - assertThat(monitorFactory.waitForNextSuccessfulResult(cacheStore2)).isEqualTo(TEST_MESSAGE_V2) + assertThat(monitorFactory.waitForNextSuccessfulResult(cacheStore2)) + .isEqualTo(TEST_INT_MESSAGE_V2) } @Test fun testMultipleCaches_oneUpdates_newCacheSameNameDiffInit_observesUpdatedValue() { val cacheStore1 = cacheFactory.create(CACHE_NAME_1, TestMessage.getDefaultInstance()) - val storeOp = cacheStore1.storeDataAsync { TEST_MESSAGE_V1 } + val storeOp = cacheStore1.storeDataAsync { TEST_INT_MESSAGE_V1 } testCoroutineDispatchers.advanceUntilIdle() - val cacheStore2 = cacheFactory.create(CACHE_NAME_1, TEST_MESSAGE_V2) + val cacheStore2 = cacheFactory.create(CACHE_NAME_1, TEST_INT_MESSAGE_V2) // The new cache should observe the updated on-disk value rather than its new default since an // on-disk value exists. This isn't a very realistic test since all caches should use default // proto instances for initialization, but it's a possible edge case that should at least have // established behavior that can be adjusted later if it isn't desirable in some circumstances. assertThat(storeOp.isCompleted).isTrue() - assertThat(monitorFactory.waitForNextSuccessfulResult(cacheStore2)).isEqualTo(TEST_MESSAGE_V1) + assertThat(monitorFactory.waitForNextSuccessfulResult(cacheStore2)) + .isEqualTo(TEST_INT_MESSAGE_V1) } @Test @@ -394,7 +355,7 @@ class PersistentCacheStoreTest { val monitor1 = monitorFactory.createMonitor(cacheStore1) val monitor2 = monitorFactory.createMonitor(cacheStore2) - val storeOp = cacheStore1.storeDataAsync { TEST_MESSAGE_V1 } + val storeOp = cacheStore1.storeDataAsync { TEST_INT_MESSAGE_V1 } testCoroutineDispatchers.advanceUntilIdle() // The observer of the second store will be not notified of the change to the first store since @@ -408,7 +369,7 @@ class PersistentCacheStoreTest { fun testMultipleCaches_diffNames_oneUpdates_cachesRecreated_onlyOneObservesVal() { val cacheStore1a = cacheFactory.create(CACHE_NAME_1, TestMessage.getDefaultInstance()) cacheFactory.create(CACHE_NAME_2, TestMessage.getDefaultInstance()) - val storeOp = cacheStore1a.storeDataAsync { TEST_MESSAGE_V1 } + val storeOp = cacheStore1a.storeDataAsync { TEST_INT_MESSAGE_V1 } testCoroutineDispatchers.advanceUntilIdle() // Recreate the stores and observe them. @@ -418,14 +379,15 @@ class PersistentCacheStoreTest { // Only the observer of the first cache should notice the update since they are different // caches. assertThat(storeOp.isCompleted).isTrue() - assertThat(monitorFactory.waitForNextSuccessfulResult(cacheStore1b)).isEqualTo(TEST_MESSAGE_V1) + assertThat(monitorFactory.waitForNextSuccessfulResult(cacheStore1b)) + .isEqualTo(TEST_INT_MESSAGE_V1) assertThat(monitorFactory.waitForNextSuccessfulResult(cacheStore2b)).isEqualToDefaultInstance() } @Test fun testNewCache_fileCorrupted_providesError() { val cacheStore1 = cacheFactory.create(CACHE_NAME_1, TestMessage.getDefaultInstance()) - val storeOp = cacheStore1.storeDataAsync { TEST_MESSAGE_V1 } + val storeOp = cacheStore1.storeDataAsync { TEST_INT_MESSAGE_V1 } testCoroutineDispatchers.advanceUntilIdle() // Simulate the file being corrupted & reopen the file in a new store. @@ -470,17 +432,1000 @@ class PersistentCacheStoreTest { assertThat(monitorFactory.waitForNextSuccessfulResult(cacheStore).strValue).isEqualTo("initial") } + @Test + fun testNoPrime_noDiskCache_unloadedCache_noNotifyOrLoadedOrDisk() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + subscribeToCacheStoreChanges(cacheStore) + + // Verify that a default cache store performs no notifications, nor does it create a disk cache. + // This helps to establish the baseline assumed in later testPrime_* tests. + verifyCacheStoreDidNotSendDataProviderNotification() + assertThat(getCacheFile(CACHE_NAME_1).exists()).isFalse() + } + + @Test + fun testPrime_noDiskCache_unloadedCache_updateIfNew_publish_noXform_initsMemDiskNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync(UPDATE_IF_NEW_CACHE, PUBLISH_TO_IN_MEMORY_CACHE) + primeDeferred.waitForSuccessfulResult() + + verifyCacheStoreSentDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, DEFAULT_TEST_MESSAGE) + } + + @Test + fun testPrime_noDiskCache_unloadedCache_updateIfNew_publish_withXform_initsMemDiskNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync(UPDATE_IF_NEW_CACHE, PUBLISH_TO_IN_MEMORY_CACHE) { + it.addString(TEST_STR_V1) + } + primeDeferred.waitForSuccessfulResult() + + val expectedMessage = createTestMessage(strValue = TEST_STR_V1) + verifyCacheStoreSentDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, expectedMessage) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, expectedMessage) + } + + @Test + fun testPrime_noDiskCache_unloadedCache_updateIfNew_noPublish_noXform_initsMemDiskNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync( + UPDATE_IF_NEW_CACHE, DO_NOT_PUBLISH_TO_IN_MEMORY_CACHE + ) + primeDeferred.waitForSuccessfulResult() + + // The cache will notify in this case since the in-memory cache wasn't yet initialized. + verifyCacheStoreSentDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, DEFAULT_TEST_MESSAGE) + } + + @Test + fun testPrime_noDiskCache_unloadedCache_updateIfNew_noPublish_withXform_initsMemDiskNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync( + UPDATE_IF_NEW_CACHE, DO_NOT_PUBLISH_TO_IN_MEMORY_CACHE + ) { it.addString(TEST_STR_V1) } + primeDeferred.waitForSuccessfulResult() + + // The cache will notify in this case since the in-memory cache wasn't yet initialized. + val expectedMessage = createTestMessage(strValue = TEST_STR_V1) + verifyCacheStoreSentDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, expectedMessage) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, expectedMessage) + } + + @Test + fun testPrime_noDiskCache_unloadedCache_updateAlways_publish_noXform_initsMemDiskNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync(UPDATE_ALWAYS, PUBLISH_TO_IN_MEMORY_CACHE) + primeDeferred.waitForSuccessfulResult() + + verifyCacheStoreSentDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, DEFAULT_TEST_MESSAGE) + } + + @Test + fun testPrime_noDiskCache_unloadedCache_updateAlways_publish_withXform_initsMemDiskNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync(UPDATE_ALWAYS, PUBLISH_TO_IN_MEMORY_CACHE) { + it.addString(TEST_STR_V1) + } + primeDeferred.waitForSuccessfulResult() + + val expectedMessage = createTestMessage(strValue = TEST_STR_V1) + verifyCacheStoreSentDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, expectedMessage) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, expectedMessage) + } + + @Test + fun testPrime_noDiskCache_unloadedCache_updateAlways_noPublish_noXform_initsMemDiskNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync(UPDATE_ALWAYS, DO_NOT_PUBLISH_TO_IN_MEMORY_CACHE) + primeDeferred.waitForSuccessfulResult() + + // The cache will notify in this case since the in-memory cache wasn't yet initialized. + verifyCacheStoreSentDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, DEFAULT_TEST_MESSAGE) + } + + @Test + fun testPrime_noDiskCache_unloadedCache_updateAlways_noPublish_withXform_initsMemDiskNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync(UPDATE_ALWAYS, DO_NOT_PUBLISH_TO_IN_MEMORY_CACHE) { + it.addString(TEST_STR_V1) + } + primeDeferred.waitForSuccessfulResult() + + // The cache will notify in this case since the in-memory cache wasn't yet initialized. + val expectedMessage = createTestMessage(strValue = TEST_STR_V1) + verifyCacheStoreSentDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, expectedMessage) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, expectedMessage) + } + + @Test + fun testPrime_noDiskCache_cacheFromMem_updateIfNew_publish_noXform_initsDiskOnlyNoNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + loadCacheIntoMemory(cacheStore) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync(UPDATE_IF_NEW_CACHE, PUBLISH_TO_IN_MEMORY_CACHE) + primeDeferred.waitForSuccessfulResult() + + // No notification is sent since the in-memory cache is already initialized ahead of priming. + // The on-disk cache still needs to be initialized. + verifyCacheStoreDidNotSendDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, DEFAULT_TEST_MESSAGE) + } + + @Test + fun testPrime_noDiskCache_cacheFromMem_updateIfNew_publish_withXform_initsMemDiskNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + loadCacheIntoMemory(cacheStore) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync(UPDATE_IF_NEW_CACHE, PUBLISH_TO_IN_MEMORY_CACHE) { + it.addString(TEST_STR_V1) + } + primeDeferred.waitForSuccessfulResult() + + val expectedMessage = createTestMessage(strValue = TEST_STR_V1) + verifyCacheStoreSentDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, expectedMessage) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, expectedMessage) + } + + @Test + fun testPrime_noDiskCache_cacheFromMem_updateIfNew_noPublish_noXform_initsDiskOnlyNoNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + loadCacheIntoMemory(cacheStore) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync( + UPDATE_IF_NEW_CACHE, DO_NOT_PUBLISH_TO_IN_MEMORY_CACHE + ) + primeDeferred.waitForSuccessfulResult() + + verifyCacheStoreDidNotSendDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, DEFAULT_TEST_MESSAGE) + } + + @Test + fun testPrime_noDiskCache_cacheFromMem_updateIfNew_noPublish_withXform_initsDiskOnlyNoNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + loadCacheIntoMemory(cacheStore) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync( + UPDATE_IF_NEW_CACHE, DO_NOT_PUBLISH_TO_IN_MEMORY_CACHE + ) { it.addString(TEST_STR_V1) } + primeDeferred.waitForSuccessfulResult() + + // The in-memory cache will not be updated in this situation since it was already loaded ahead + // of priming the cache (and thus established). + val expectedDiskMessage = createTestMessage(strValue = TEST_STR_V1) + verifyCacheStoreDidNotSendDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, expectedDiskMessage) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, DEFAULT_TEST_MESSAGE) + } + + @Test + fun testPrime_noDiskCache_cacheFromMem_updateAlways_publish_noXform_initsDiskOnlyNoNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + loadCacheIntoMemory(cacheStore) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync(UPDATE_ALWAYS, PUBLISH_TO_IN_MEMORY_CACHE) + primeDeferred.waitForSuccessfulResult() + + // Despite the update & publish policies, no notification will be sent here since priming + // doesn't change the cache store and the store has already be initialized into memory. + verifyCacheStoreDidNotSendDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, DEFAULT_TEST_MESSAGE) + } + + @Test + fun testPrime_noDiskCache_cacheFromMem_updateAlways_publish_withXform_initsMemDiskNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + loadCacheIntoMemory(cacheStore) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync(UPDATE_ALWAYS, PUBLISH_TO_IN_MEMORY_CACHE) { + it.addString(TEST_STR_V1) + } + primeDeferred.waitForSuccessfulResult() + + val expectedMessage = createTestMessage(strValue = TEST_STR_V1) + verifyCacheStoreSentDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, expectedMessage) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, expectedMessage) + } + + @Test + fun testPrime_noDiskCache_cacheFromMem_updateAlways_noPublish_noXform_initsDiskOnlyNoNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + loadCacheIntoMemory(cacheStore) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync(UPDATE_ALWAYS, DO_NOT_PUBLISH_TO_IN_MEMORY_CACHE) + primeDeferred.waitForSuccessfulResult() + + verifyCacheStoreDidNotSendDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, DEFAULT_TEST_MESSAGE) + } + + @Test + fun testPrime_noDiskCache_cacheFromMem_updateAlways_noPublish_withXform_initsDiskOnlyNoNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + loadCacheIntoMemory(cacheStore) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync(UPDATE_ALWAYS, DO_NOT_PUBLISH_TO_IN_MEMORY_CACHE) { + it.addString(TEST_STR_V1) + } + primeDeferred.waitForSuccessfulResult() + + // The in-memory cache will not be updated in this situation since it was already loaded ahead + // of priming the cache (and thus established). + val expectedDiskMessage = createTestMessage(strValue = TEST_STR_V1) + verifyCacheStoreDidNotSendDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, expectedDiskMessage) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, DEFAULT_TEST_MESSAGE) + } + + @Test + fun testPrime_diskCache_unloadedCache_updateIfNew_publish_noXform_initsMemOnlyNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + writeFileCache(CACHE_NAME_1, TEST_INT_MESSAGE_V1) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync(UPDATE_IF_NEW_CACHE, PUBLISH_TO_IN_MEMORY_CACHE) + primeDeferred.waitForSuccessfulResult() + + // Effectively, only the in-memory version of the cache changes. + verifyCacheStoreSentDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, TEST_INT_MESSAGE_V1) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, TEST_INT_MESSAGE_V1) + } + + @Test + fun testPrime_diskCache_unloadedCache_updateIfNew_publish_withXform_initsMemOnlyNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + writeFileCache(CACHE_NAME_1, TEST_INT_MESSAGE_V1) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync(UPDATE_IF_NEW_CACHE, PUBLISH_TO_IN_MEMORY_CACHE) { + it.addString(TEST_STR_V1) + } + primeDeferred.waitForSuccessfulResult() + + // The in-memory version should be initialized, but not updated (since it already exists). Plus, + // the disk cache should not be read or updated. + val expectedMessage = createTestMessage(intValue = TEST_INT_V1) + verifyCacheStoreSentDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, expectedMessage) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, expectedMessage) + } + + @Test + fun testPrime_diskCache_unloadedCache_updateIfNew_noPublish_noXform_initsMemOnlyNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + writeFileCache(CACHE_NAME_1, TEST_INT_MESSAGE_V1) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync( + UPDATE_IF_NEW_CACHE, DO_NOT_PUBLISH_TO_IN_MEMORY_CACHE + ) + primeDeferred.waitForSuccessfulResult() + + // Effectively, only the in-memory version of the cache changes, which in turn still results in + // a notification being sent. + verifyCacheStoreSentDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, TEST_INT_MESSAGE_V1) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, TEST_INT_MESSAGE_V1) + } + + @Test + fun testPrime_diskCache_unloadedCache_updateIfNew_noPublish_withXform_initsMemOnlyNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + writeFileCache(CACHE_NAME_1, TEST_INT_MESSAGE_V1) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync( + UPDATE_IF_NEW_CACHE, DO_NOT_PUBLISH_TO_IN_MEMORY_CACHE + ) { it.addString(TEST_STR_V1) } + primeDeferred.waitForSuccessfulResult() + + // The in-memory version should be initialized, but not updated (since it already exists). Plus, + // the disk cache should not be read or updated. However, this will result in a notification + // since the in-memory cache was unloaded. + val expectedMessage = createTestMessage(intValue = TEST_INT_V1) + verifyCacheStoreSentDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, expectedMessage) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, expectedMessage) + } + + @Test + fun testPrime_diskCache_unloadedCache_updateAlways_publish_noXform_initsMemOnlyNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + writeFileCache(CACHE_NAME_1, TEST_INT_MESSAGE_V1) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync(UPDATE_ALWAYS, PUBLISH_TO_IN_MEMORY_CACHE) + primeDeferred.waitForSuccessfulResult() + + // Effectively, only the in-memory version of the cache changes. + verifyCacheStoreSentDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, TEST_INT_MESSAGE_V1) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, TEST_INT_MESSAGE_V1) + } + + @Test + fun testPrime_diskCache_unloadedCache_updateAlways_publish_withXform_initsMemDiskNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + writeFileCache(CACHE_NAME_1, TEST_INT_MESSAGE_V1) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync(UPDATE_ALWAYS, PUBLISH_TO_IN_MEMORY_CACHE) { + it.addString(TEST_STR_V1) + } + primeDeferred.waitForSuccessfulResult() + + // Both the in-memory and on-disk variants should change per the UPDATE_ALWAYS policy. + val expectedMessage = createTestMessage(intValue = TEST_INT_V1, strValue = TEST_STR_V1) + verifyCacheStoreSentDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, expectedMessage) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, expectedMessage) + } + + @Test + fun testPrime_diskCache_unloadedCache_updateAlways_noPublish_noXform_initsMemOnlyNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + writeFileCache(CACHE_NAME_1, TEST_INT_MESSAGE_V1) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync(UPDATE_ALWAYS, DO_NOT_PUBLISH_TO_IN_MEMORY_CACHE) + primeDeferred.waitForSuccessfulResult() + + // Effectively, only the in-memory version of the cache changes, which in turn still results in + // a notification being sent. + verifyCacheStoreSentDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, TEST_INT_MESSAGE_V1) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, TEST_INT_MESSAGE_V1) + } + + @Test + fun testPrime_diskCache_unloadedCache_updateAlways_noPublish_withXform_initsDiskOnlyNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + writeFileCache(CACHE_NAME_1, TEST_INT_MESSAGE_V1) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync(UPDATE_ALWAYS, DO_NOT_PUBLISH_TO_IN_MEMORY_CACHE) { + it.addString(TEST_STR_V1) + } + primeDeferred.waitForSuccessfulResult() + + // Only the on-disk variant should actually change (per UPDATE_ALWAYS), since the publish policy + // indicates that the in-memory cache shouldn't reflect the new value (only the existing disk + // cache). Furthermore, a notification will be sent anyway since the in-memory cache hasn't yet + // been initialized. + val expectedMemoryMessage = createTestMessage(intValue = TEST_INT_V1) + val expectedDiskMessage = createTestMessage(intValue = TEST_INT_V1, strValue = TEST_STR_V1) + verifyCacheStoreSentDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, expectedDiskMessage) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, expectedMemoryMessage) + } + + @Test + fun testPrime_diskCache_cacheFromDisk_updateIfNew_publish_noXform_noChangeOrNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + writeFileCache(CACHE_NAME_1, TEST_INT_MESSAGE_V1) + loadCacheIntoMemory(cacheStore) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync(UPDATE_IF_NEW_CACHE, PUBLISH_TO_IN_MEMORY_CACHE) + primeDeferred.waitForSuccessfulResult() + + // Nothing should be notified, or change on-disk or in-memory (other initing the cache). + verifyCacheStoreDidNotSendDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, TEST_INT_MESSAGE_V1) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, TEST_INT_MESSAGE_V1) + } + + @Test + fun testPrime_diskCache_cacheFromDisk_updateIfNew_publish_withXform_noChangeOrNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + writeFileCache(CACHE_NAME_1, TEST_INT_MESSAGE_V1) + loadCacheIntoMemory(cacheStore) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync(UPDATE_IF_NEW_CACHE, PUBLISH_TO_IN_MEMORY_CACHE) { + it.addString(TEST_STR_V1) + } + primeDeferred.waitForSuccessfulResult() + + // Nothing should be notified, or change on-disk or in-memory per UPDATE_IF_NEW_CACHE. + val expectedMessage = createTestMessage(intValue = TEST_INT_V1) + verifyCacheStoreDidNotSendDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, expectedMessage) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, expectedMessage) + } + + @Test + fun testPrime_diskCache_cacheFromDisk_updateIfNew_noPublish_noXform_noChangeOrNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + writeFileCache(CACHE_NAME_1, TEST_INT_MESSAGE_V1) + loadCacheIntoMemory(cacheStore) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync( + UPDATE_IF_NEW_CACHE, DO_NOT_PUBLISH_TO_IN_MEMORY_CACHE + ) + primeDeferred.waitForSuccessfulResult() + + // Nothing should be notified, or change on-disk or in-memory (other initing the cache). + verifyCacheStoreDidNotSendDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, TEST_INT_MESSAGE_V1) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, TEST_INT_MESSAGE_V1) + } + + @Test + fun testPrime_diskCache_cacheFromDisk_updateIfNew_noPublish_withXform_noChangeOrNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + writeFileCache(CACHE_NAME_1, TEST_INT_MESSAGE_V1) + loadCacheIntoMemory(cacheStore) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync( + UPDATE_IF_NEW_CACHE, DO_NOT_PUBLISH_TO_IN_MEMORY_CACHE + ) { it.addString(TEST_STR_V1) } + primeDeferred.waitForSuccessfulResult() + + // Nothing should be notified, or change on-disk or in-memory per UPDATE_IF_NEW_CACHE. + val expectedMessage = createTestMessage(intValue = TEST_INT_V1) + verifyCacheStoreDidNotSendDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, expectedMessage) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, expectedMessage) + } + + @Test + fun testPrime_diskCache_cacheFromDisk_updateAlways_publish_noXform_noChangeOrNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + writeFileCache(CACHE_NAME_1, TEST_INT_MESSAGE_V1) + loadCacheIntoMemory(cacheStore) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync(UPDATE_ALWAYS, PUBLISH_TO_IN_MEMORY_CACHE) + primeDeferred.waitForSuccessfulResult() + + // Nothing should be notified, or change on-disk or in-memory (other initing the cache). + verifyCacheStoreDidNotSendDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, TEST_INT_MESSAGE_V1) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, TEST_INT_MESSAGE_V1) + } + + @Test + fun testPrime_diskCache_cacheFromDisk_updateAlways_publish_withXform_initsMemDiskNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + writeFileCache(CACHE_NAME_1, TEST_INT_MESSAGE_V1) + loadCacheIntoMemory(cacheStore) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync(UPDATE_ALWAYS, PUBLISH_TO_IN_MEMORY_CACHE) { + it.addString(TEST_STR_V1) + } + primeDeferred.waitForSuccessfulResult() + + // Since the policy is to always update the cache, the in-memory & on-disk versions should be + // updated plus a notification sent. + val expectedMessage = createTestMessage(intValue = TEST_INT_V1, strValue = TEST_STR_V1) + verifyCacheStoreSentDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, expectedMessage) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, expectedMessage) + } + + @Test + fun testPrime_diskCache_cacheFromDisk_updateAlways_noPublish_noXform_noChangeOrNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + writeFileCache(CACHE_NAME_1, TEST_INT_MESSAGE_V1) + loadCacheIntoMemory(cacheStore) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync(UPDATE_ALWAYS, DO_NOT_PUBLISH_TO_IN_MEMORY_CACHE) + primeDeferred.waitForSuccessfulResult() + + // Nothing should be notified, or change on-disk or in-memory (other initing the cache). + verifyCacheStoreDidNotSendDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, TEST_INT_MESSAGE_V1) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, TEST_INT_MESSAGE_V1) + } + + @Test + fun testPrime_diskCache_cacheFromDisk_updateAlways_noPublish_withXform_initsMemDiskNoNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + writeFileCache(CACHE_NAME_1, TEST_INT_MESSAGE_V1) + loadCacheIntoMemory(cacheStore) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync(UPDATE_ALWAYS, DO_NOT_PUBLISH_TO_IN_MEMORY_CACHE) { + it.addString(TEST_STR_V1) + } + primeDeferred.waitForSuccessfulResult() + + // Since the policy is to always update the cache, the on-disk version should be updated. + // However, the in-memory cache will not be updated since it was already loaded ahead of priming + // (and hence established). Furthermore, no notification sent per the publish policy. + val expectedMemoryMessage = createTestMessage(intValue = TEST_INT_V1) + val expectedDiskMessage = createTestMessage(intValue = TEST_INT_V1, strValue = TEST_STR_V1) + verifyCacheStoreDidNotSendDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, expectedDiskMessage) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, expectedMemoryMessage) + } + + @Test + fun testPrime_badDiskCache_unloadedCache_updateIfNew_publish_noXform_initsMemDiskNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + corruptFileCache(CACHE_NAME_1) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync(UPDATE_IF_NEW_CACHE, PUBLISH_TO_IN_MEMORY_CACHE) + primeDeferred.waitForSuccessfulResult() + + // A corrupted disk cache is treated as a completely new cache. + verifyCacheStoreSentDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, DEFAULT_TEST_MESSAGE) + } + + @Test + fun testPrime_badDiskCache_unloadedCache_updateIfNew_publish_withXform_initsMemDiskNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + corruptFileCache(CACHE_NAME_1) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync(UPDATE_IF_NEW_CACHE, PUBLISH_TO_IN_MEMORY_CACHE) { + it.addString(TEST_STR_V1) + } + primeDeferred.waitForSuccessfulResult() + + // A corrupted disk cache is treated as a completely new cache. + val expectedMessage = createTestMessage(strValue = TEST_STR_V1) + verifyCacheStoreSentDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, expectedMessage) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, expectedMessage) + } + + @Test + fun testPrime_badDiskCache_unloadedCache_updateIfNew_noPublish_noXform_initsMemDiskNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + corruptFileCache(CACHE_NAME_1) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync( + UPDATE_IF_NEW_CACHE, DO_NOT_PUBLISH_TO_IN_MEMORY_CACHE + ) + primeDeferred.waitForSuccessfulResult() + + // A corrupted disk cache is treated as a completely new cache. Because of that, a notification + // is actually sent in this case. + verifyCacheStoreSentDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, DEFAULT_TEST_MESSAGE) + } + + @Test + fun testPrime_badDiskCache_unloadedCache_updateIfNew_noPublish_withXform_initsMemDiskNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + corruptFileCache(CACHE_NAME_1) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync( + UPDATE_IF_NEW_CACHE, DO_NOT_PUBLISH_TO_IN_MEMORY_CACHE + ) { it.addString(TEST_STR_V1) } + primeDeferred.waitForSuccessfulResult() + + // A corrupted disk cache is treated as a completely new cache. Because of that, a notification + // is actually sent in this case. + val expectedMessage = createTestMessage(strValue = TEST_STR_V1) + verifyCacheStoreSentDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, expectedMessage) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, expectedMessage) + } + + @Test + fun testPrime_badDiskCache_unloadedCache_updateAlways_publish_noXform_initsMemDiskNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + corruptFileCache(CACHE_NAME_1) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync(UPDATE_ALWAYS, PUBLISH_TO_IN_MEMORY_CACHE) + primeDeferred.waitForSuccessfulResult() + + verifyCacheStoreSentDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, DEFAULT_TEST_MESSAGE) + } + + @Test + fun testPrime_badDiskCache_unloadedCache_updateAlways_publish_withXform_initsMemDiskNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + corruptFileCache(CACHE_NAME_1) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync(UPDATE_ALWAYS, PUBLISH_TO_IN_MEMORY_CACHE) { + it.addString(TEST_STR_V1) + } + primeDeferred.waitForSuccessfulResult() + + val expectedMessage = createTestMessage(strValue = TEST_STR_V1) + verifyCacheStoreSentDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, expectedMessage) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, expectedMessage) + } + + @Test + fun testPrime_badDiskCache_unloadedCache_updateAlways_noPublish_noXform_initsMemDiskNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + corruptFileCache(CACHE_NAME_1) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync(UPDATE_ALWAYS, DO_NOT_PUBLISH_TO_IN_MEMORY_CACHE) + primeDeferred.waitForSuccessfulResult() + + // A corrupted disk cache is treated as a completely new cache. Because of that, a notification + // is actually sent in this case. + verifyCacheStoreSentDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, DEFAULT_TEST_MESSAGE) + } + + @Test + fun testPrime_badDiskCache_unloadedCache_updateAlways_noPublish_withXform_initsMemDiskNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + corruptFileCache(CACHE_NAME_1) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync(UPDATE_ALWAYS, DO_NOT_PUBLISH_TO_IN_MEMORY_CACHE) { + it.addString(TEST_STR_V1) + } + primeDeferred.waitForSuccessfulResult() + + // A corrupted disk cache is treated as a completely new cache. Because of that, a notification + // is actually sent in this case. + val expectedMessage = createTestMessage(strValue = TEST_STR_V1) + verifyCacheStoreSentDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, expectedMessage) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, expectedMessage) + } + + @Test + fun testPrime_badDiskCache_cacheFromMem_updateIfNew_publish_noXform_initsDiskOnlyNoNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + corruptFileCache(CACHE_NAME_1) + loadCacheIntoMemory(cacheStore) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync(UPDATE_IF_NEW_CACHE, PUBLISH_TO_IN_MEMORY_CACHE) + primeDeferred.waitForSuccessfulResult() + + // The corrupted cache should have failed to be read, so it will be treated as a new cache. + // However, only the disk cache actually needs to be updated since the in-memory one has already + // been established prior to priming (which is also why no notification is sent in this case). + verifyCacheStoreDidNotSendDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, DEFAULT_TEST_MESSAGE) + } + + @Test + fun testPrime_badDiskCache_cacheFromMem_updateIfNew_publish_withXform_initsMemDiskNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + corruptFileCache(CACHE_NAME_1) + loadCacheIntoMemory(cacheStore) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync(UPDATE_IF_NEW_CACHE, PUBLISH_TO_IN_MEMORY_CACHE) { + it.addString(TEST_STR_V1) + } + primeDeferred.waitForSuccessfulResult() + + // The corrupted cache should have failed to be read, so it will be treated as a new cache. + val expectedMessage = createTestMessage(strValue = TEST_STR_V1) + verifyCacheStoreSentDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, expectedMessage) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, expectedMessage) + } + + @Test + fun testPrime_badDiskCache_cacheFromMem_updateIfNew_noPublish_noXform_initsDiskOnlyNoNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + corruptFileCache(CACHE_NAME_1) + loadCacheIntoMemory(cacheStore) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync( + UPDATE_IF_NEW_CACHE, DO_NOT_PUBLISH_TO_IN_MEMORY_CACHE + ) + primeDeferred.waitForSuccessfulResult() + + // The corrupted cache should have failed to be read, so it will be treated as a new cache. + verifyCacheStoreDidNotSendDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, DEFAULT_TEST_MESSAGE) + } + + @Test + fun testPrime_badDiskCache_cacheFromMem_updateIfNew_noPublish_withXform_initsMemDiskNoNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + corruptFileCache(CACHE_NAME_1) + loadCacheIntoMemory(cacheStore) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync( + UPDATE_IF_NEW_CACHE, DO_NOT_PUBLISH_TO_IN_MEMORY_CACHE + ) { it.addString(TEST_STR_V1) } + primeDeferred.waitForSuccessfulResult() + + // The corrupted cache should have failed to be read, so it will be treated as a new cache. Note + // though that the initial load operation will result in the in-memory cache becoming the + // default cache value (and thus different from the on-disk version) per the publish policy. + val expectedOnDiskMessage = createTestMessage(strValue = TEST_STR_V1) + verifyCacheStoreDidNotSendDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, expectedOnDiskMessage) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, DEFAULT_TEST_MESSAGE) + } + + @Test + fun testPrime_badDiskCache_cacheFromMem_updateAlways_publish_noXform_initsDiskOnlyNoNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + corruptFileCache(CACHE_NAME_1) + loadCacheIntoMemory(cacheStore) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync(UPDATE_ALWAYS, PUBLISH_TO_IN_MEMORY_CACHE) + primeDeferred.waitForSuccessfulResult() + + // Despite the update & publish policies, no notification will be sent here since priming + // doesn't change the cache store and the store has already be initialized into memory. + verifyCacheStoreDidNotSendDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, DEFAULT_TEST_MESSAGE) + } + + @Test + fun testPrime_badDiskCache_cacheFromMem_updateAlways_publish_withXform_initsMemDiskNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + corruptFileCache(CACHE_NAME_1) + loadCacheIntoMemory(cacheStore) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync(UPDATE_ALWAYS, PUBLISH_TO_IN_MEMORY_CACHE) { + it.addString(TEST_STR_V1) + } + primeDeferred.waitForSuccessfulResult() + + val expectedMessage = createTestMessage(strValue = TEST_STR_V1) + verifyCacheStoreSentDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, expectedMessage) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, expectedMessage) + } + + @Test + fun testPrime_badDiskCache_cacheFromMem_updateAlways_noPublish_noXform_initsDiskOnlyNoNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + corruptFileCache(CACHE_NAME_1) + loadCacheIntoMemory(cacheStore) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync(UPDATE_ALWAYS, DO_NOT_PUBLISH_TO_IN_MEMORY_CACHE) + primeDeferred.waitForSuccessfulResult() + + verifyCacheStoreDidNotSendDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, DEFAULT_TEST_MESSAGE) + } + + @Test + fun testPrime_badDiskCache_cacheFromMem_updateAlways_noPublish_withXform_initsDiskOnlyNoNotify() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, DEFAULT_TEST_MESSAGE) + corruptFileCache(CACHE_NAME_1) + loadCacheIntoMemory(cacheStore) + subscribeToCacheStoreChanges(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync(UPDATE_ALWAYS, DO_NOT_PUBLISH_TO_IN_MEMORY_CACHE) { + it.addString(TEST_STR_V1) + } + primeDeferred.waitForSuccessfulResult() + + // The in-memory cache will not be updated in this situation since it was already loaded ahead + // of priming the cache (and thus established). + val expectedDiskMessage = createTestMessage(strValue = TEST_STR_V1) + verifyCacheStoreDidNotSendDataProviderNotification() + verifyDiskCacheHasValue(CACHE_NAME_1, expectedDiskMessage) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, DEFAULT_TEST_MESSAGE) + } + + @Test + fun testPrime_badDiskCache_cacheFromMem_passesDefaultMessageInToTransform() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, TEST_INT_MESSAGE_V1) + corruptFileCache(CACHE_NAME_1) + loadCacheIntoMemory(cacheStore) + + val primeDeferred = + cacheStore.primeInMemoryAndDiskCacheAsync(UPDATE_ALWAYS, DO_NOT_PUBLISH_TO_IN_MEMORY_CACHE) { + it + } + primeDeferred.waitForSuccessfulResult() + + // Verify that the proto passed into prime's update method is the default in this case (since + // the disk cache is corrupted). + val expectedMessage = createTestMessage(intValue = TEST_INT_V1) + verifyDiskCacheHasValue(CACHE_NAME_1, expectedMessage) + verifyCacheStoreHasInMemoryValue(CACHE_NAME_1, cacheStore, expectedMessage) + } + + @Test + fun testPrimeInMemoryAndOnDisk_newObject_updateNoMemThenRead_primed_receivesPrevVal() { + val cacheStore1 = cacheFactory.create(CACHE_NAME_1, TestMessage.getDefaultInstance()) + val storeOp1 = + cacheStore1.storeDataAsync(updateInMemoryCache = false) { TEST_INT_MESSAGE_V1 } + testCoroutineDispatchers.advanceUntilIdle() + + // Create a new cache with the same name and update it, then observe it. However, first prime + // it. + val cacheStore2 = cacheFactory.create(CACHE_NAME_1, TestMessage.getDefaultInstance()) + val primeOp = + cacheStore2.primeInMemoryAndDiskCacheAsync( + updateMode = UPDATE_IF_NEW_CACHE, publishMode = DO_NOT_PUBLISH_TO_IN_MEMORY_CACHE + ) + testCoroutineDispatchers.advanceUntilIdle() + val storeOp2 = cacheStore2.storeDataAsync(updateInMemoryCache = false) { TEST_INT_MESSAGE_V2 } + testCoroutineDispatchers.advanceUntilIdle() + + // All operations should be complete, but the observer will receive the previous update rather + // than the latest since it wasn't updated in memory and the cache was pre-primed. + assertThat(storeOp1.isCompleted).isTrue() + assertThat(storeOp2.isCompleted).isTrue() + assertThat(primeOp.isCompleted).isTrue() + assertThat(monitorFactory.waitForNextSuccessfulResult(cacheStore2)) + .isEqualTo(TEST_INT_MESSAGE_V1) + } + + @Test + fun testPrimeInMemoryAndOnDisk_onDisk_newObject_updateMemThenRead_primed_receivesNewVal() { + val cacheStore1 = cacheFactory.create(CACHE_NAME_1, TestMessage.getDefaultInstance()) + val storeOp1 = + cacheStore1.storeDataAsync(updateInMemoryCache = false) { TEST_INT_MESSAGE_V1 } + testCoroutineDispatchers.advanceUntilIdle() + + // Create a new cache with the same name and update it, then observe it. However, first prime + // it. + val cacheStore2 = cacheFactory.create(CACHE_NAME_1, TestMessage.getDefaultInstance()) + val primeOp = + cacheStore2.primeInMemoryAndDiskCacheAsync( + updateMode = UPDATE_IF_NEW_CACHE, publishMode = DO_NOT_PUBLISH_TO_IN_MEMORY_CACHE + ) + testCoroutineDispatchers.advanceUntilIdle() + val storeOp2 = cacheStore2.storeDataAsync { TEST_INT_MESSAGE_V2 } + testCoroutineDispatchers.advanceUntilIdle() + + // Similar to the previous test, except due to the in-memory update the observer will receive + // the latest result regardless of the cache priming. + assertThat(storeOp1.isCompleted).isTrue() + assertThat(storeOp2.isCompleted).isTrue() + assertThat(primeOp.isCompleted).isTrue() + assertThat(monitorFactory.waitForNextSuccessfulResult(cacheStore2)) + .isEqualTo(TEST_INT_MESSAGE_V2) + } + + @Test + fun testPrimeInMemoryAndOnDisk_afterStoreUpdateWithoutMemUpdate_observesOldValue() { + val cacheStore = cacheFactory.create(CACHE_NAME_1, TestMessage.getDefaultInstance()) + // Force initializing the store's in-memory cache + monitorFactory.waitForNextSuccessfulResult(cacheStore) + + val storeOp = cacheStore.storeDataAsync(updateInMemoryCache = false) { TEST_INT_MESSAGE_V1 } + testCoroutineDispatchers.advanceUntilIdle() + val primeOp = + cacheStore.primeInMemoryAndDiskCacheAsync( + updateMode = UPDATE_IF_NEW_CACHE, + publishMode = DO_NOT_PUBLISH_TO_IN_MEMORY_CACHE + ) + testCoroutineDispatchers.advanceUntilIdle() + + // Both ops will succeed, and the observer will receive the old value due to the update not + // changing the in-memory cache, and the prime no-oping due to the cache already being + // initialized. + assertThat(storeOp.isCompleted).isTrue() + assertThat(primeOp.isCompleted).isTrue() + assertThat(monitorFactory.waitForNextSuccessfulResult(cacheStore)).isEqualToDefaultInstance() + } + @Test fun testPrimeInMemoryAndOnDisk_newCache_notOnDisk_notInMem_writesFileAndRetsNewVal() { val cacheStore = cacheFactory.create(CACHE_NAME_1, TestMessage.getDefaultInstance()) - val deferred = cacheStore.primeInMemoryAndDiskCacheAsync { - it.toBuilder().apply { strValue += " first transform" }.build() - } + val deferred = + cacheStore.primeInMemoryAndDiskCacheAsync( + updateMode = UPDATE_IF_NEW_CACHE, publishMode = PUBLISH_TO_IN_MEMORY_CACHE + ) { it.toBuilder().apply { strValue += " first transform" }.build() } // The on-disk and in-memory values should change. deferred.waitForSuccessfulResult() - val onDiskValue = readFileCache(CACHE_NAME_1, TestMessage.getDefaultInstance()) + val onDiskValue = readFileCache(CACHE_NAME_1) val cacheValue = monitorFactory.waitForNextSuccessfulResult(cacheStore) assertThat(cacheValue).isEqualTo(onDiskValue) assertThat(cacheValue.strValue.trim()).isEqualTo("first transform") @@ -491,14 +1436,15 @@ class PersistentCacheStoreTest { writeFileCache(CACHE_NAME_1, TestMessage.newBuilder().apply { strValue = "initial" }.build()) val cacheStore = cacheFactory.create(CACHE_NAME_1, TestMessage.getDefaultInstance()) - val deferred = cacheStore.primeInMemoryAndDiskCacheAsync { - it.toBuilder().apply { strValue += " first transform" }.build() - } + val deferred = + cacheStore.primeInMemoryAndDiskCacheAsync( + updateMode = UPDATE_IF_NEW_CACHE, publishMode = PUBLISH_TO_IN_MEMORY_CACHE + ) { it.toBuilder().apply { strValue += " first transform" }.build() } // The on-disk value should be the same, and the in-memory value should become the on-disk // value. The initializer shouldn't be used since the value is already on disk. deferred.waitForSuccessfulResult() - val onDiskValue = readFileCache(CACHE_NAME_1, TestMessage.getDefaultInstance()) + val onDiskValue = readFileCache(CACHE_NAME_1) val cacheValue = monitorFactory.waitForNextSuccessfulResult(cacheStore) assertThat(cacheValue).isEqualTo(onDiskValue) assertThat(cacheValue.strValue).isEqualTo("initial") @@ -511,14 +1457,15 @@ class PersistentCacheStoreTest { CACHE_NAME_1, TestMessage.newBuilder().apply { strValue = "different initial" }.build() ) - val deferred = cacheStore.primeInMemoryAndDiskCacheAsync { - it.toBuilder().apply { strValue += " first transform" }.build() - } + val deferred = + cacheStore.primeInMemoryAndDiskCacheAsync( + updateMode = UPDATE_IF_NEW_CACHE, publishMode = PUBLISH_TO_IN_MEMORY_CACHE + ) { it.toBuilder().apply { strValue += " first transform" }.build() } // Priming should ignore both the on-disk and in-memory values of the cache store since only the // initial value matters. deferred.waitForSuccessfulResult() - val onDiskValue = readFileCache(CACHE_NAME_1, TestMessage.getDefaultInstance()) + val onDiskValue = readFileCache(CACHE_NAME_1) val cacheValue = monitorFactory.waitForNextSuccessfulResult(cacheStore) assertThat(cacheValue).isEqualTo(onDiskValue) assertThat(cacheValue.strValue).isEqualTo("initial") @@ -533,14 +1480,15 @@ class PersistentCacheStoreTest { it.toBuilder().apply { strValue = "different update" }.build() }.waitForResult() - val deferred = cacheStore.primeInMemoryAndDiskCacheAsync { - it.toBuilder().apply { strValue += " first transform" }.build() - } + val deferred = + cacheStore.primeInMemoryAndDiskCacheAsync( + updateMode = UPDATE_IF_NEW_CACHE, publishMode = PUBLISH_TO_IN_MEMORY_CACHE + ) { it.toBuilder().apply { strValue += " first transform" }.build() } // Priming shouldn't really change much since the recent change to the cache store takes // precedence. deferred.waitForSuccessfulResult() - val onDiskValue = readFileCache(CACHE_NAME_1, TestMessage.getDefaultInstance()) + val onDiskValue = readFileCache(CACHE_NAME_1) val cacheValue = monitorFactory.waitForNextSuccessfulResult(cacheStore) assertThat(cacheValue).isEqualTo(onDiskValue) assertThat(cacheValue.strValue).isEqualTo("different update") @@ -556,14 +1504,15 @@ class PersistentCacheStoreTest { it.toBuilder().apply { strValue = "different update" }.build() }.waitForResult() - val deferred = cacheStore.primeInMemoryAndDiskCacheAsync { - it.toBuilder().apply { strValue += " first transform" }.build() - } + val deferred = + cacheStore.primeInMemoryAndDiskCacheAsync( + updateMode = UPDATE_IF_NEW_CACHE, publishMode = PUBLISH_TO_IN_MEMORY_CACHE + ) { it.toBuilder().apply { strValue += " first transform" }.build() } // Priming shouldn't really change much since the recent change to the cache store takes // precedence. deferred.waitForSuccessfulResult() - val onDiskValue = readFileCache(CACHE_NAME_1, TestMessage.getDefaultInstance()) + val onDiskValue = readFileCache(CACHE_NAME_1) val cacheValue = monitorFactory.waitForNextSuccessfulResult(cacheStore) assertThat(cacheValue).isEqualTo(onDiskValue) assertThat(cacheValue.strValue).isEqualTo("different update") @@ -578,26 +1527,62 @@ class PersistentCacheStoreTest { val cacheStore2 = cacheFactory.create(CACHE_NAME_1, TestMessage.getDefaultInstance()) monitorFactory.ensureDataProviderExecutes(cacheStore1) - val deferred = cacheStore1.primeInMemoryAndDiskCacheAsync { - it.toBuilder().apply { strValue += " first transform" }.build() - } + val deferred = + cacheStore1.primeInMemoryAndDiskCacheAsync( + updateMode = UPDATE_IF_NEW_CACHE, publishMode = PUBLISH_TO_IN_MEMORY_CACHE + ) { it.toBuilder().apply { strValue += " first transform" }.build() } // The corrupted cache will trigger an in-memory only state that will lead to the cache being // overwritten (since it can't be determined whether the on-disk cache matches the expected - // value). Note that the cache will be in a bad state since it failed to read its original - // state, but the on-disk copy will still be updated. Note also that the second instance of the + // value). Note the on-disk cache will still be updated, and that the second instance of the // cache wasn't yet primed until this step, so it's being used to validate that the in-memory // copy is also correct after the on-disk value has been updated. deferred.waitForSuccessfulResult() - monitorFactory.waitForNextFailureResult(cacheStore1) - val onDiskValue = readFileCache(CACHE_NAME_1, TestMessage.getDefaultInstance()) + val onDiskValue = readFileCache(CACHE_NAME_1) val cacheValue = monitorFactory.waitForNextSuccessfulResult(cacheStore2) assertThat(cacheValue).isEqualTo(onDiskValue) assertThat(onDiskValue.strValue).isEqualTo("different initial first transform") } + private fun subscribeToCacheStoreChanges(cacheStore: PersistentCacheStore) { + asyncDataSubscriptionManager.subscribe( + cacheStore.getId(), mockSubscriptionCallback.toAsyncChange() + ) + } + + private fun loadCacheIntoMemory(cacheStore: PersistentCacheStore) { + // Attempt to load the cache store from disk--this ensures that the cache store is at least + // loaded into memory. + monitorFactory.ensureDataProviderExecutes(cacheStore) + } + + private fun verifyCacheStoreHasInMemoryValue( + cacheName: String, + cacheStore: PersistentCacheStore, + value: T + ) { + // Delete the cache before reading from the store to verify that the read value is actually + // in-memory and not being read from disk. + deleteCacheFile(cacheName) + assertThat(monitorFactory.waitForNextSuccessfulResult(cacheStore)).isEqualTo(value) + } + + private fun verifyDiskCacheHasValue(cacheName: String, value: T) { + assertThat(readFileCache(cacheName)).isEqualTo(value) + } + + private fun verifyCacheStoreSentDataProviderNotification() { + verify(mockSubscriptionCallback).onDataProviderChanged() + } + + private fun verifyCacheStoreDidNotSendDataProviderNotification() { + verify(mockSubscriptionCallback, never()).onDataProviderChanged() + } + private fun getCacheFile(cacheName: String) = File(context.filesDir, "$cacheName.cache") + private fun deleteCacheFile(cacheName: String) = getCacheFile(cacheName).delete() + private fun corruptFileCache(cacheName: String) { // NB: This is unfortunately tied to the implementation details of PersistentCacheStore. If this // ends up being an issue, the store should be updated to call into a file path provider that @@ -615,9 +1600,12 @@ class PersistentCacheStoreTest { FileOutputStream(this).use { it.write(data) } } - private inline fun readFileCache(cacheName: String, baseMessage: T): T { + private inline fun readFileCache(cacheName: String): T { + // Use reflection to simplify the test API. + val defaultInstance = + T::class.staticFunctions.find { it.name == "getDefaultInstance" }?.call() as? T? return FileInputStream(getCacheFile(cacheName)).use { - baseMessage.newBuilderForType().mergeFrom(it).build() + checkNotNull(defaultInstance).newBuilderForType().mergeFrom(it).build() } as T } @@ -694,4 +1682,12 @@ class PersistentCacheStoreTest { override fun getDataProvidersInjector(): DataProvidersInjector = component } + + interface SubscriptionCallback { + fun onDataProviderChanged() + + companion object { + fun SubscriptionCallback.toAsyncChange(): ObserveAsyncChange = { onDataProviderChanged() } + } + } } diff --git a/domain/BUILD.bazel b/domain/BUILD.bazel index e13cc36c9a6..db2919a4ff8 100755 --- a/domain/BUILD.bazel +++ b/domain/BUILD.bazel @@ -158,6 +158,7 @@ TEST_DEPS = [ ":interaction_object_test_builder", "//app:crashlytics", "//app:crashlytics_deps", + "//app/src/main/java/org/oppia/android/app/application/testing:testing_build_flavor_module", "//data/src/main/java/org/oppia/android/data/backends/gae:network_config_prod_module", "//data/src/main/java/org/oppia/android/data/backends/gae/model", "//data/src/main/java/org/oppia/android/data/persistence:cache_store", diff --git a/domain/src/main/java/org/oppia/android/domain/exploration/lightweightcheckpointing/ExplorationCheckpointController.kt b/domain/src/main/java/org/oppia/android/domain/exploration/lightweightcheckpointing/ExplorationCheckpointController.kt index b69e949a859..84b74a71211 100644 --- a/domain/src/main/java/org/oppia/android/domain/exploration/lightweightcheckpointing/ExplorationCheckpointController.kt +++ b/domain/src/main/java/org/oppia/android/domain/exploration/lightweightcheckpointing/ExplorationCheckpointController.kt @@ -8,6 +8,8 @@ import org.oppia.android.app.model.ExplorationCheckpointDatabase import org.oppia.android.app.model.ExplorationCheckpointDetails import org.oppia.android.app.model.ProfileId import org.oppia.android.data.persistence.PersistentCacheStore +import org.oppia.android.data.persistence.PersistentCacheStore.PublishMode +import org.oppia.android.data.persistence.PersistentCacheStore.UpdateMode import org.oppia.android.domain.exploration.ExplorationRetriever import org.oppia.android.domain.oppialogger.OppiaLogger import org.oppia.android.util.data.AsyncResult @@ -282,7 +284,10 @@ class ExplorationCheckpointController @Inject constructor( cacheStore } - cacheStore.primeInMemoryCacheAsync().invokeOnCompletion { throwable -> + cacheStore.primeInMemoryAndDiskCacheAsync( + updateMode = UpdateMode.UPDATE_IF_NEW_CACHE, + publishMode = PublishMode.PUBLISH_TO_IN_MEMORY_CACHE + ).invokeOnCompletion { throwable -> throwable?.let { oppiaLogger.e( "ExplorationCheckpointController", diff --git a/domain/src/main/java/org/oppia/android/domain/onboarding/AppStartupStateController.kt b/domain/src/main/java/org/oppia/android/domain/onboarding/AppStartupStateController.kt index e6196c6174e..31b6b203ba7 100644 --- a/domain/src/main/java/org/oppia/android/domain/onboarding/AppStartupStateController.kt +++ b/domain/src/main/java/org/oppia/android/domain/onboarding/AppStartupStateController.kt @@ -1,7 +1,9 @@ package org.oppia.android.domain.onboarding import org.oppia.android.app.model.AppStartupState +import org.oppia.android.app.model.AppStartupState.BuildFlavorNoticeMode import org.oppia.android.app.model.AppStartupState.StartupMode +import org.oppia.android.app.model.BuildFlavor import org.oppia.android.app.model.OnboardingState import org.oppia.android.data.persistence.PersistentCacheStore import org.oppia.android.domain.oppialogger.OppiaLogger @@ -12,7 +14,7 @@ import org.oppia.android.util.locale.OppiaLocale import javax.inject.Inject import javax.inject.Singleton -private const val APP_STARTUP_STATE_DATA_PROVIDER_ID = "app_startup_state_data_provider_id" +private const val APP_STARTUP_STATE_PROVIDER_ID = "app_startup_state_data_provider_id" /** Controller for persisting and retrieving the user's initial app state upon opening the app. */ @Singleton @@ -20,44 +22,65 @@ class AppStartupStateController @Inject constructor( cacheStoreFactory: PersistentCacheStore.Factory, private val oppiaLogger: OppiaLogger, private val expirationMetaDataRetriever: ExpirationMetaDataRetriever, - private val machineLocale: OppiaLocale.MachineLocale + private val machineLocale: OppiaLocale.MachineLocale, + private val currentBuildFlavor: BuildFlavor ) { - private val onboardingFlowStore = + private val onboardingFlowStore by lazy { cacheStoreFactory.create("on_boarding_flow", OnboardingState.getDefaultInstance()) - - private val appStartupStateDataProvider by lazy { - onboardingFlowStore.transform(APP_STARTUP_STATE_DATA_PROVIDER_ID) { - AppStartupState.newBuilder().setStartupMode(computeAppStartupMode(it)).build() - } } + private val appStartupStateDataProvider by lazy { computeAppStartupStateProvider() } + init { // Prime the cache ahead of time so that any existing history is read prior to any calls to - // markOnboardingFlowCompleted(). - onboardingFlowStore.primeInMemoryCacheAsync().invokeOnCompletion { - it?.let { + // markOnboardingFlowCompleted(). Note that this also ensures that the on-disk cache contains + // the last used build flavor (but it doesn't update the in-memory copy as it's the *last* used + // flavor, and thus requires an app restart in order to observe). + onboardingFlowStore.primeInMemoryAndDiskCacheAsync( + updateMode = PersistentCacheStore.UpdateMode.UPDATE_ALWAYS, + publishMode = PersistentCacheStore.PublishMode.DO_NOT_PUBLISH_TO_IN_MEMORY_CACHE + ) { state -> + state.toBuilder().apply { lastUsedBuildFlavor = currentBuildFlavor }.build() + }.invokeOnCompletion { primeFailure -> + if (primeFailure != null) { oppiaLogger.e( - "DOMAIN", "Failed to prime cache ahead of data retrieval for user onboarding data.", it + "StartupController", + "Failed to prime cache ahead of data retrieval for user onboarding data.", + primeFailure ) } } } /** - * Saves that the user has completed the app onboarding flow. Note that this does not notify - * existing subscribers of the changed state, nor can future subscribers observe this state until - * the app restarts. + * Saves that the user has completed the app onboarding flow. + * + * Note that this does not notify existing subscribers of the changed state, nor can future + * subscribers observe this state until the app restarts. */ fun markOnboardingFlowCompleted() { - onboardingFlowStore.storeDataAsync(updateInMemoryCache = false) { - it.toBuilder().setAlreadyOnboardedApp(true).build() - }.invokeOnCompletion { - it?.let { - oppiaLogger.e( - "DOMAIN", "Failed when storing that the user already onboarded the app.", it - ) - } - } + updateOnboardingState { alreadyOnboardedApp = true } + } + + /** + * Saves that the user never wants to see beta notices again. + * + * Note that this does not notify existing subscribers of the changed state, nor can future + * subscribers observe this state until the app restarts. + */ + fun dismissBetaNoticesPermanently() { + updateOnboardingState { permanentlyDismissedBetaNotice = true } + } + + /** + * Saves that the user never wants to notices for cases when their app has updated from a + * pre-release version of the app to the general availability version. + * + * Note that this does not notify existing subscribers of the changed state, nor can future + * subscribers observe this state until the app restarts. + */ + fun dismissGaUpgradeNoticesPermanently() { + updateOnboardingState { permanentlyDismissedGaUpgradeNotice = true } } /** @@ -66,6 +89,32 @@ class AppStartupStateController @Inject constructor( */ fun getAppStartupState(): DataProvider = appStartupStateDataProvider + private fun computeAppStartupStateProvider(): DataProvider { + return onboardingFlowStore.transform(APP_STARTUP_STATE_PROVIDER_ID) { onboardingState -> + AppStartupState.newBuilder().apply { + startupMode = computeAppStartupMode(onboardingState) + buildFlavorNoticeMode = computeBuildNoticeMode(onboardingState, startupMode) + }.build() + } + } + + private fun updateOnboardingState(updateState: OnboardingState.Builder.() -> Unit) { + // Note that the flavor must be written here since it only gets updated on-disk and never + // in-memory (which means it will be inadvertently overwritten when updating onboarding state + // here). + val deferred = onboardingFlowStore.storeDataAsync(updateInMemoryCache = false) { state -> + state.toBuilder().apply { + updateState() + lastUsedBuildFlavor = currentBuildFlavor + }.build() + } + deferred.invokeOnCompletion { failure -> + if (failure != null) { + oppiaLogger.e("StartupController", "Failed to update onboarding state.", failure) + } + } + } + private fun computeAppStartupMode(onboardingState: OnboardingState): StartupMode { return when { hasAppExpired() -> StartupMode.APP_IS_DEPRECATED @@ -74,6 +123,39 @@ class AppStartupStateController @Inject constructor( } } + private fun computeBuildNoticeMode( + onboardingState: OnboardingState, + startupMode: StartupMode + ): BuildFlavorNoticeMode { + return when (currentBuildFlavor) { + BuildFlavor.TESTING, BuildFlavor.BUILD_FLAVOR_UNSPECIFIED, BuildFlavor.UNRECOGNIZED -> + BuildFlavorNoticeMode.FLAVOR_NOTICE_MODE_UNSPECIFIED + // No notice is shown for developer & alpha builds. + BuildFlavor.DEVELOPER, BuildFlavor.ALPHA -> BuildFlavorNoticeMode.NO_NOTICE + BuildFlavor.BETA -> { + // Only show the beta notice if the user hasn't permanently dismissed it, and when it's + // appropriate to show (i.e. they've recently changed to the beta flavor, and their app is + // not force-deprecated). + if (!onboardingState.permanentlyDismissedBetaNotice && + onboardingState.lastUsedBuildFlavor != BuildFlavor.BETA && + startupMode != StartupMode.APP_IS_DEPRECATED + ) { + BuildFlavorNoticeMode.SHOW_BETA_NOTICE + } else BuildFlavorNoticeMode.NO_NOTICE + } + BuildFlavor.GENERAL_AVAILABILITY -> when (onboardingState.lastUsedBuildFlavor) { + BuildFlavor.ALPHA, BuildFlavor.BETA, null -> { + if (!onboardingState.permanentlyDismissedGaUpgradeNotice) { + BuildFlavorNoticeMode.SHOW_UPGRADE_TO_GENERAL_AVAILABILITY_NOTICE + } else BuildFlavorNoticeMode.NO_NOTICE // The user doesn't want to see the notice again. + } + // A brand new install should result in no notice, or an update from a developer build. + BuildFlavor.BUILD_FLAVOR_UNSPECIFIED, BuildFlavor.UNRECOGNIZED, BuildFlavor.TESTING, + BuildFlavor.DEVELOPER, BuildFlavor.GENERAL_AVAILABILITY -> BuildFlavorNoticeMode.NO_NOTICE + } + } + } + private fun hasAppExpired(): Boolean { val applicationMetadata = expirationMetaDataRetriever.getMetaData() val isAppExpirationEnabled = diff --git a/domain/src/main/java/org/oppia/android/domain/oppialogger/LoggingIdentifierController.kt b/domain/src/main/java/org/oppia/android/domain/oppialogger/LoggingIdentifierController.kt index 3440f62fe0f..ca4a741da2d 100644 --- a/domain/src/main/java/org/oppia/android/domain/oppialogger/LoggingIdentifierController.kt +++ b/domain/src/main/java/org/oppia/android/domain/oppialogger/LoggingIdentifierController.kt @@ -4,6 +4,8 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import org.oppia.android.app.model.DeviceContextDatabase import org.oppia.android.data.persistence.PersistentCacheStore +import org.oppia.android.data.persistence.PersistentCacheStore.PublishMode +import org.oppia.android.data.persistence.PersistentCacheStore.UpdateMode import org.oppia.android.util.data.DataProvider import org.oppia.android.util.data.DataProviders import org.oppia.android.util.data.DataProviders.Companion.transform @@ -36,10 +38,11 @@ class LoggingIdentifierController @Inject constructor( persistentCacheStoreFactory.create( cacheName = "device_context_database", DeviceContextDatabase.getDefaultInstance() ).also { - it.primeInMemoryAndDiskCacheAsync { database -> - database.toBuilder().apply { - installationId = computeInstallationId() - }.build() + it.primeInMemoryAndDiskCacheAsync( + updateMode = UpdateMode.UPDATE_IF_NEW_CACHE, + publishMode = PublishMode.PUBLISH_TO_IN_MEMORY_CACHE + ) { database -> + database.toBuilder().apply { installationId = computeInstallationId() }.build() }.invokeOnCompletion { failure -> if (failure != null) { oppiaLogger.e( diff --git a/domain/src/main/java/org/oppia/android/domain/profile/ProfileManagementController.kt b/domain/src/main/java/org/oppia/android/domain/profile/ProfileManagementController.kt index dac62b05666..e2c15df2253 100644 --- a/domain/src/main/java/org/oppia/android/domain/profile/ProfileManagementController.kt +++ b/domain/src/main/java/org/oppia/android/domain/profile/ProfileManagementController.kt @@ -17,6 +17,8 @@ import org.oppia.android.app.model.ProfileDatabase import org.oppia.android.app.model.ProfileId import org.oppia.android.app.model.ReadingTextSize import org.oppia.android.data.persistence.PersistentCacheStore +import org.oppia.android.data.persistence.PersistentCacheStore.PublishMode +import org.oppia.android.data.persistence.PersistentCacheStore.UpdateMode import org.oppia.android.domain.oppialogger.LoggingIdentifierController import org.oppia.android.domain.oppialogger.OppiaLogger import org.oppia.android.domain.oppialogger.analytics.LearnerAnalyticsLogger @@ -129,7 +131,10 @@ class ProfileManagementController @Inject constructor( // TODO(#272): Remove init block when storeDataAsync is fixed init { - profileDataStore.primeInMemoryCacheAsync().invokeOnCompletion { + profileDataStore.primeInMemoryAndDiskCacheAsync( + updateMode = UpdateMode.UPDATE_IF_NEW_CACHE, + publishMode = PublishMode.PUBLISH_TO_IN_MEMORY_CACHE + ).invokeOnCompletion { it?.let { oppiaLogger.e( "ProfileManagementController", diff --git a/domain/src/main/java/org/oppia/android/domain/topic/PrimeTopicAssetsControllerImpl.kt b/domain/src/main/java/org/oppia/android/domain/topic/PrimeTopicAssetsControllerImpl.kt index b1eddc72ddc..fa25ee6f163 100644 --- a/domain/src/main/java/org/oppia/android/domain/topic/PrimeTopicAssetsControllerImpl.kt +++ b/domain/src/main/java/org/oppia/android/domain/topic/PrimeTopicAssetsControllerImpl.kt @@ -3,7 +3,6 @@ package org.oppia.android.domain.topic import android.annotation.SuppressLint import android.app.Activity import android.app.Application -import android.content.Context import android.graphics.Typeface import android.os.Bundle import android.os.SystemClock @@ -73,7 +72,7 @@ private const val REPLACE_IMG_FILE_PATH_ATTRIBUTE = "src" */ @Singleton class PrimeTopicAssetsControllerImpl @Inject constructor( - private val context: Context, + private val application: Application, private val oppiaLogger: OppiaLogger, private val assetRepository: AssetRepository, private val topicController: TopicController, @@ -197,7 +196,6 @@ class PrimeTopicAssetsControllerImpl @Inject constructor( private fun prepareUiForDownloadStatusChanges(dialogStyleResId: Int) { // Reference: https://stackoverflow.com/a/37713320. - val application = context.applicationContext as Application application.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks { override fun onActivityPaused(activity: Activity) {} override fun onActivityResumed(activity: Activity) {} diff --git a/domain/src/main/java/org/oppia/android/domain/topic/StoryProgressController.kt b/domain/src/main/java/org/oppia/android/domain/topic/StoryProgressController.kt index bba4660a16f..81b9d28e8d8 100644 --- a/domain/src/main/java/org/oppia/android/domain/topic/StoryProgressController.kt +++ b/domain/src/main/java/org/oppia/android/domain/topic/StoryProgressController.kt @@ -8,6 +8,8 @@ import org.oppia.android.app.model.StoryProgress import org.oppia.android.app.model.TopicProgress import org.oppia.android.app.model.TopicProgressDatabase import org.oppia.android.data.persistence.PersistentCacheStore +import org.oppia.android.data.persistence.PersistentCacheStore.PublishMode +import org.oppia.android.data.persistence.PersistentCacheStore.UpdateMode import org.oppia.android.domain.oppialogger.OppiaLogger import org.oppia.android.util.data.AsyncResult import org.oppia.android.util.data.DataProvider @@ -371,8 +373,11 @@ class StoryProgressController @Inject constructor( cacheStore } - cacheStore.primeInMemoryCacheAsync().invokeOnCompletion { - it?.let { it -> + cacheStore.primeInMemoryAndDiskCacheAsync( + updateMode = UpdateMode.UPDATE_IF_NEW_CACHE, + publishMode = PublishMode.PUBLISH_TO_IN_MEMORY_CACHE + ).invokeOnCompletion { + if (it != null) { oppiaLogger.e( "StoryProgressController", "Failed to prime cache ahead of data retrieval for StoryProgressController.", diff --git a/domain/src/test/java/org/oppia/android/domain/onboarding/AppStartupStateControllerTest.kt b/domain/src/test/java/org/oppia/android/domain/onboarding/AppStartupStateControllerTest.kt index c77a04df305..9f924f394dd 100644 --- a/domain/src/test/java/org/oppia/android/domain/onboarding/AppStartupStateControllerTest.kt +++ b/domain/src/test/java/org/oppia/android/domain/onboarding/AppStartupStateControllerTest.kt @@ -6,17 +6,22 @@ import android.os.Bundle import androidx.test.core.app.ApplicationProvider import androidx.test.core.content.pm.ApplicationInfoBuilder import androidx.test.core.content.pm.PackageInfoBuilder -import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat import dagger.BindsInstance import dagger.Component import dagger.Module import dagger.Provides +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.oppia.android.app.model.AppStartupState.BuildFlavorNoticeMode.FLAVOR_NOTICE_MODE_UNSPECIFIED +import org.oppia.android.app.model.AppStartupState.BuildFlavorNoticeMode.NO_NOTICE +import org.oppia.android.app.model.AppStartupState.BuildFlavorNoticeMode.SHOW_BETA_NOTICE +import org.oppia.android.app.model.AppStartupState.BuildFlavorNoticeMode.SHOW_UPGRADE_TO_GENERAL_AVAILABILITY_NOTICE import org.oppia.android.app.model.AppStartupState.StartupMode.APP_IS_DEPRECATED import org.oppia.android.app.model.AppStartupState.StartupMode.USER_IS_ONBOARDED import org.oppia.android.app.model.AppStartupState.StartupMode.USER_NOT_YET_ONBOARDED +import org.oppia.android.app.model.BuildFlavor import org.oppia.android.app.model.OnboardingState import org.oppia.android.data.persistence.PersistentCacheStore import org.oppia.android.domain.oppialogger.LogStorageModule @@ -26,6 +31,12 @@ import org.oppia.android.domain.platformparameter.PlatformParameterModule import org.oppia.android.domain.platformparameter.PlatformParameterSingletonModule import org.oppia.android.testing.TestLogReportingModule import org.oppia.android.testing.data.DataProviderTestMonitor +import org.oppia.android.testing.junit.OppiaParameterizedTestRunner +import org.oppia.android.testing.junit.OppiaParameterizedTestRunner.Iteration +import org.oppia.android.testing.junit.OppiaParameterizedTestRunner.Parameter +import org.oppia.android.testing.junit.OppiaParameterizedTestRunner.RunParameterized +import org.oppia.android.testing.junit.OppiaParameterizedTestRunner.SelectRunnerPlatform +import org.oppia.android.testing.junit.ParameterizedRobolectricTestRunner import org.oppia.android.testing.robolectric.RobolectricModule import org.oppia.android.testing.threading.TestCoroutineDispatchers import org.oppia.android.testing.threading.TestDispatcherModule @@ -52,18 +63,25 @@ import javax.inject.Singleton /** Tests for [AppStartupStateController]. */ // FunctionName: test names are conventionally named with underscores. @Suppress("FunctionName") -@RunWith(AndroidJUnit4::class) +@RunWith(OppiaParameterizedTestRunner::class) +@SelectRunnerPlatform(ParameterizedRobolectricTestRunner::class) @Config(application = AppStartupStateControllerTest.TestApplication::class) class AppStartupStateControllerTest { @Inject lateinit var context: Context @Inject lateinit var appStartupStateController: AppStartupStateController @Inject lateinit var testCoroutineDispatchers: TestCoroutineDispatchers @Inject lateinit var monitorFactory: DataProviderTestMonitor.Factory + @Parameter lateinit var initialFlavorName: String // TODO(#3792): Remove this usage of Locale (probably by introducing a test utility in the locale // package to generate these strings). private val expirationDateFormat by lazy { SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) } + @Before + fun setUp() { + TestModule.buildFlavor = BuildFlavor.BUILD_FLAVOR_UNSPECIFIED + } + @Test fun testController_providesInitialState_indicatesUserHasNotOnboardedTheApp() { setUpDefaultTestApplicationComponent() @@ -91,7 +109,7 @@ class AppStartupStateControllerTest { @Test fun testController_settingAppOnboarded_observedNewController_userOnboardedApp() { // Simulate the previous app already having completed onboarding. - executeInPreviousApp { testComponent -> + executeInPreviousAppInstance { testComponent -> testComponent.getAppStartupStateController().markOnboardingFlowCompleted() testComponent.getTestCoroutineDispatchers().runCurrent() } @@ -100,8 +118,8 @@ class AppStartupStateControllerTest { setUpDefaultTestApplicationComponent() val appStartupState = appStartupStateController.getAppStartupState() - // The app should be considered onboarded since a new DataProvider instance was observed after - // marking the app as onboarded. + // The user should be considered onboarded since a new DataProvider instance was observed after + // marking the user as onboarded. val mode = monitorFactory.waitForNextSuccessfulResult(appStartupState) assertThat(mode.startupMode).isEqualTo(USER_IS_ONBOARDED) } @@ -110,7 +128,7 @@ class AppStartupStateControllerTest { @Suppress("DeferredResultUnused") fun testController_onboardedApp_cleared_observeNewController_userDidNotOnboardApp() { // Simulate the previous app already having completed onboarding, then cleared. - executeInPreviousApp { testComponent -> + executeInPreviousAppInstance { testComponent -> testComponent.getAppStartupStateController().markOnboardingFlowCompleted() testComponent.getTestCoroutineDispatchers().runCurrent() @@ -180,7 +198,7 @@ class AppStartupStateControllerTest { @Test fun testSecondAppOpen_onboardingFlowNotDone_deprecationEnabled_beforeDepDate_appNotDeprecated() { - executeInPreviousApp { testComponent -> + executeInPreviousAppInstance { testComponent -> setUpOppiaApplicationForContext( context = testComponent.getContext(), expirationEnabled = true, @@ -198,7 +216,7 @@ class AppStartupStateControllerTest { @Test fun testSecondAppOpen_onboardingFlowNotDone_deprecationEnabled_afterDepDate_appIsDeprecated() { - executeInPreviousApp { testComponent -> + executeInPreviousAppInstance { testComponent -> setUpOppiaApplicationForContext( context = testComponent.getContext(), expirationEnabled = true, @@ -216,7 +234,7 @@ class AppStartupStateControllerTest { @Test fun testSecondAppOpen_onboardingFlowCompleted_depEnabled_beforeDepDate_appNotDeprecated() { - executeInPreviousApp { testComponent -> + executeInPreviousAppInstance { testComponent -> setUpOppiaApplicationForContext( context = testComponent.getContext(), expirationEnabled = true, @@ -238,7 +256,7 @@ class AppStartupStateControllerTest { @Test fun testSecondAppOpen_onboardingFlowCompleted_deprecationEnabled_afterDepDate_appIsDeprecated() { - executeInPreviousApp { testComponent -> + executeInPreviousAppInstance { testComponent -> setUpOppiaApplicationForContext( context = testComponent.getContext(), expirationEnabled = true, @@ -258,6 +276,495 @@ class AppStartupStateControllerTest { assertThat(mode.startupMode).isEqualTo(APP_IS_DEPRECATED) } + /* Tests to verify that beta & no notices are shown at the expected times. */ + + @Test + fun testController_initialState_testingBuild_showsUnspecifiedNotice() { + TestModule.buildFlavor = BuildFlavor.TESTING + setUpDefaultTestApplicationComponent() + + val appStartupState = appStartupStateController.getAppStartupState() + + // Testing mode is specially handled as it's generally not expected to be encountered (but is + // still possible). + val mode = monitorFactory.waitForNextSuccessfulResult(appStartupState) + assertThat(mode.buildFlavorNoticeMode).isEqualTo(FLAVOR_NOTICE_MODE_UNSPECIFIED) + } + + @Test + fun testController_initialState_developerBuild_showNoNotice() { + TestModule.buildFlavor = BuildFlavor.DEVELOPER + setUpDefaultTestApplicationComponent() + + val appStartupState = appStartupStateController.getAppStartupState() + + val mode = monitorFactory.waitForNextSuccessfulResult(appStartupState) + assertThat(mode.buildFlavorNoticeMode).isEqualTo(NO_NOTICE) + } + + @Test + fun testController_initialState_alphaBuild_showNoNotice() { + TestModule.buildFlavor = BuildFlavor.ALPHA + setUpDefaultTestApplicationComponent() + + val appStartupState = appStartupStateController.getAppStartupState() + + val mode = monitorFactory.waitForNextSuccessfulResult(appStartupState) + assertThat(mode.buildFlavorNoticeMode).isEqualTo(NO_NOTICE) + } + + @Test + fun testController_initialState_betaBuild_showNoNotice() { + TestModule.buildFlavor = BuildFlavor.BETA + setUpDefaultTestApplicationComponent() + + val appStartupState = appStartupStateController.getAppStartupState() + + // No notice is shown here since the 'prior' version is beta, and the notice is only shown if + // the build flavor changes for the user. + val mode = monitorFactory.waitForNextSuccessfulResult(appStartupState) + assertThat(mode.buildFlavorNoticeMode).isEqualTo(NO_NOTICE) + } + + @Test + fun testController_initialState_gaBuild_showNoNotice() { + TestModule.buildFlavor = BuildFlavor.GENERAL_AVAILABILITY + setUpDefaultTestApplicationComponent() + + val appStartupState = appStartupStateController.getAppStartupState() + + val mode = monitorFactory.waitForNextSuccessfulResult(appStartupState) + assertThat(mode.buildFlavorNoticeMode).isEqualTo(NO_NOTICE) + } + + @Test + fun testController_appDeprecated_testingBuild_showsUnspecifiedNotice() { + TestModule.buildFlavor = BuildFlavor.TESTING + setUpDefaultTestApplicationComponent() + setUpOppiaApplication(expirationEnabled = true, expDate = dateStringForToday()) + + val appStartupState = appStartupStateController.getAppStartupState() + + // Testing mode is specially handled as it's generally not expected to be encountered (but is + // still possible). + val mode = monitorFactory.waitForNextSuccessfulResult(appStartupState) + assertThat(mode.buildFlavorNoticeMode).isEqualTo(FLAVOR_NOTICE_MODE_UNSPECIFIED) + } + + @Test + fun testController_appDeprecated_developerBuild_showNoNotice() { + TestModule.buildFlavor = BuildFlavor.DEVELOPER + setUpDefaultTestApplicationComponent() + setUpOppiaApplication(expirationEnabled = true, expDate = dateStringForToday()) + + val appStartupState = appStartupStateController.getAppStartupState() + + val mode = monitorFactory.waitForNextSuccessfulResult(appStartupState) + assertThat(mode.buildFlavorNoticeMode).isEqualTo(NO_NOTICE) + } + + @Test + fun testController_appDeprecated_alphaBuild_showNoNotice() { + TestModule.buildFlavor = BuildFlavor.ALPHA + setUpDefaultTestApplicationComponent() + setUpOppiaApplication(expirationEnabled = true, expDate = dateStringForToday()) + + val appStartupState = appStartupStateController.getAppStartupState() + + val mode = monitorFactory.waitForNextSuccessfulResult(appStartupState) + assertThat(mode.buildFlavorNoticeMode).isEqualTo(NO_NOTICE) + } + + @Test + fun testController_appDeprecated_betaBuild_showNoNotice() { + TestModule.buildFlavor = BuildFlavor.BETA + setUpDefaultTestApplicationComponent() + setUpOppiaApplication(expirationEnabled = true, expDate = dateStringForToday()) + + val appStartupState = appStartupStateController.getAppStartupState() + + // The beta notice is not shown in cases when the app is deprecated (since there's no point in + // showing it; the user can't actually use the app). + val mode = monitorFactory.waitForNextSuccessfulResult(appStartupState) + assertThat(mode.buildFlavorNoticeMode).isEqualTo(NO_NOTICE) + } + + @Test + fun testController_appDeprecated_gaBuild_showNoNotice() { + TestModule.buildFlavor = BuildFlavor.GENERAL_AVAILABILITY + setUpDefaultTestApplicationComponent() + setUpOppiaApplication(expirationEnabled = true, expDate = dateStringForToday()) + + val appStartupState = appStartupStateController.getAppStartupState() + + val mode = monitorFactory.waitForNextSuccessfulResult(appStartupState) + assertThat(mode.buildFlavorNoticeMode).isEqualTo(NO_NOTICE) + } + + @Test + fun testController_userOnboarded_testingBuild_showsUnspecifiedNotice() { + // Simulate the previous app already having completed onboarding. + executeInPreviousAppInstance { testComponent -> + testComponent.getAppStartupStateController().markOnboardingFlowCompleted() + testComponent.getTestCoroutineDispatchers().runCurrent() + } + TestModule.buildFlavor = BuildFlavor.TESTING + setUpDefaultTestApplicationComponent() + + val appStartupState = appStartupStateController.getAppStartupState() + + // Testing mode is specially handled as it's generally not expected to be encountered (but is + // still possible). + val mode = monitorFactory.waitForNextSuccessfulResult(appStartupState) + assertThat(mode.buildFlavorNoticeMode).isEqualTo(FLAVOR_NOTICE_MODE_UNSPECIFIED) + } + + @Test + fun testController_userOnboarded_developerBuild_showNoNotice() { + // Simulate the previous app already having completed onboarding. + executeInPreviousAppInstance { testComponent -> + testComponent.getAppStartupStateController().markOnboardingFlowCompleted() + testComponent.getTestCoroutineDispatchers().runCurrent() + } + TestModule.buildFlavor = BuildFlavor.DEVELOPER + setUpDefaultTestApplicationComponent() + + val appStartupState = appStartupStateController.getAppStartupState() + + val mode = monitorFactory.waitForNextSuccessfulResult(appStartupState) + assertThat(mode.buildFlavorNoticeMode).isEqualTo(NO_NOTICE) + } + + @Test + fun testController_userOnboarded_alphaBuild_showNoNotice() { + // Simulate the previous app already having completed onboarding. + executeInPreviousAppInstance { testComponent -> + testComponent.getAppStartupStateController().markOnboardingFlowCompleted() + testComponent.getTestCoroutineDispatchers().runCurrent() + } + TestModule.buildFlavor = BuildFlavor.ALPHA + setUpDefaultTestApplicationComponent() + + val appStartupState = appStartupStateController.getAppStartupState() + + val mode = monitorFactory.waitForNextSuccessfulResult(appStartupState) + assertThat(mode.buildFlavorNoticeMode).isEqualTo(NO_NOTICE) + } + + @Test + fun testController_userOnboarded_betaBuild_showBetaNotice() { + // Simulate the previous app already having completed onboarding. + executeInPreviousAppInstance { testComponent -> + testComponent.getAppStartupStateController().markOnboardingFlowCompleted() + testComponent.getTestCoroutineDispatchers().runCurrent() + } + TestModule.buildFlavor = BuildFlavor.BETA + setUpDefaultTestApplicationComponent() + + val appStartupState = appStartupStateController.getAppStartupState() + + // Beta is shown when using beta mode. + val mode = monitorFactory.waitForNextSuccessfulResult(appStartupState) + assertThat(mode.buildFlavorNoticeMode).isEqualTo(SHOW_BETA_NOTICE) + } + + @Test + fun testController_userOnboarded_gaBuild_showNoNotice() { + // Simulate the previous app already having completed onboarding. + executeInPreviousAppInstance { testComponent -> + testComponent.getAppStartupStateController().markOnboardingFlowCompleted() + testComponent.getTestCoroutineDispatchers().runCurrent() + } + TestModule.buildFlavor = BuildFlavor.GENERAL_AVAILABILITY + setUpDefaultTestApplicationComponent() + + val appStartupState = appStartupStateController.getAppStartupState() + + val mode = monitorFactory.waitForNextSuccessfulResult(appStartupState) + assertThat(mode.buildFlavorNoticeMode).isEqualTo(NO_NOTICE) + } + + /* Tests to verify that changing from one build flavor to another can cause notices to show. */ + + @Test + fun testController_userOnboarded_changeToTestingBuild_showsUnspecifiedNotice() { + // Simulate the previous app already having completed onboarding in a non-testing build. + executeInPreviousAppInstance { testComponent -> + TestModule.buildFlavor = BuildFlavor.DEVELOPER + testComponent.getAppStartupStateController().markOnboardingFlowCompleted() + testComponent.getTestCoroutineDispatchers().runCurrent() + } + TestModule.buildFlavor = BuildFlavor.TESTING + setUpDefaultTestApplicationComponent() + + val appStartupState = appStartupStateController.getAppStartupState() + + val mode = monitorFactory.waitForNextSuccessfulResult(appStartupState) + assertThat(mode.buildFlavorNoticeMode).isEqualTo(FLAVOR_NOTICE_MODE_UNSPECIFIED) + } + + @Test + fun testController_userOnboarded_changeToDevBuild_showNoNotice() { + // Simulate the previous app already having completed onboarding in a non-dev build. + executeInPreviousAppInstance { testComponent -> + TestModule.buildFlavor = BuildFlavor.TESTING + testComponent.getAppStartupStateController().markOnboardingFlowCompleted() + testComponent.getTestCoroutineDispatchers().runCurrent() + } + TestModule.buildFlavor = BuildFlavor.DEVELOPER + setUpDefaultTestApplicationComponent() + + val appStartupState = appStartupStateController.getAppStartupState() + + val mode = monitorFactory.waitForNextSuccessfulResult(appStartupState) + assertThat(mode.buildFlavorNoticeMode).isEqualTo(NO_NOTICE) + } + + @Test + fun testController_userOnboarded_changeToAlphaBuild_showNoNotice() { + // Simulate the previous app already having completed onboarding in a non-alpha build. + executeInPreviousAppInstance { testComponent -> + TestModule.buildFlavor = BuildFlavor.DEVELOPER + testComponent.getAppStartupStateController().markOnboardingFlowCompleted() + testComponent.getTestCoroutineDispatchers().runCurrent() + } + TestModule.buildFlavor = BuildFlavor.ALPHA + setUpDefaultTestApplicationComponent() + + val appStartupState = appStartupStateController.getAppStartupState() + + val mode = monitorFactory.waitForNextSuccessfulResult(appStartupState) + assertThat(mode.buildFlavorNoticeMode).isEqualTo(NO_NOTICE) + } + + @Test + fun testController_userOnboarded_changeToBetaBuild_fromAlpha_showBetaNotice() { + // Simulate the previous app already having completed onboarding in an alpha build. + executeInPreviousAppInstance { testComponent -> + TestModule.buildFlavor = BuildFlavor.ALPHA + testComponent.getAppStartupStateController().markOnboardingFlowCompleted() + testComponent.getTestCoroutineDispatchers().runCurrent() + } + TestModule.buildFlavor = BuildFlavor.BETA + setUpDefaultTestApplicationComponent() + + val appStartupState = appStartupStateController.getAppStartupState() + + // Changing from alpha to beta should result in the notice showing. + val mode = monitorFactory.waitForNextSuccessfulResult(appStartupState) + assertThat(mode.buildFlavorNoticeMode).isEqualTo(SHOW_BETA_NOTICE) + } + + @Test + fun testController_userOnboarded_changeToBetaBuild_fromBetaAgain_showNoNotice() { + // Simulate the previous app already having completed onboarding in a beta build. + executeInPreviousAppInstance { testComponent -> + TestModule.buildFlavor = BuildFlavor.BETA + testComponent.getAppStartupStateController().markOnboardingFlowCompleted() + testComponent.getTestCoroutineDispatchers().runCurrent() + } + TestModule.buildFlavor = BuildFlavor.BETA + setUpDefaultTestApplicationComponent() + + val appStartupState = appStartupStateController.getAppStartupState() + + // The beta notice was shown on the last run; don't show it again. + val mode = monitorFactory.waitForNextSuccessfulResult(appStartupState) + assertThat(mode.buildFlavorNoticeMode).isEqualTo(NO_NOTICE) + } + + @Test + fun testController_userOnboarded_changeToBetaBuild_fromGa_showBetaNotice() { + // Simulate the previous app already having completed onboarding in a generally available build. + executeInPreviousAppInstance { testComponent -> + TestModule.buildFlavor = BuildFlavor.GENERAL_AVAILABILITY + testComponent.getAppStartupStateController().markOnboardingFlowCompleted() + testComponent.getTestCoroutineDispatchers().runCurrent() + } + TestModule.buildFlavor = BuildFlavor.BETA + setUpDefaultTestApplicationComponent() + + val appStartupState = appStartupStateController.getAppStartupState() + + // Changing from GA to beta should result in the notice showing. + val mode = monitorFactory.waitForNextSuccessfulResult(appStartupState) + assertThat(mode.buildFlavorNoticeMode).isEqualTo(SHOW_BETA_NOTICE) + } + + @Test + fun testController_userOnboarded_changeToGaBuild_fromDeveloperBuild_showNoNotice() { + // Simulate the previous app already having completed onboarding in an developer-only build. + executeInPreviousAppInstance { testComponent -> + TestModule.buildFlavor = BuildFlavor.DEVELOPER + testComponent.getAppStartupStateController().markOnboardingFlowCompleted() + testComponent.getTestCoroutineDispatchers().runCurrent() + } + TestModule.buildFlavor = BuildFlavor.GENERAL_AVAILABILITY + setUpDefaultTestApplicationComponent() + + val appStartupState = appStartupStateController.getAppStartupState() + + // The GA upgrade notice is only shown when changing from alpha or beta. + val mode = monitorFactory.waitForNextSuccessfulResult(appStartupState) + assertThat(mode.buildFlavorNoticeMode).isEqualTo(NO_NOTICE) + } + + @Test + fun testController_userOnboarded_changeToGaBuild_fromAlpha_showGaNotice() { + // Simulate the previous app already having completed onboarding in an alpha build. + executeInPreviousAppInstance { testComponent -> + TestModule.buildFlavor = BuildFlavor.ALPHA + testComponent.getAppStartupStateController().markOnboardingFlowCompleted() + testComponent.getTestCoroutineDispatchers().runCurrent() + } + TestModule.buildFlavor = BuildFlavor.GENERAL_AVAILABILITY + setUpDefaultTestApplicationComponent() + + val appStartupState = appStartupStateController.getAppStartupState() + + // Changing from alpha to GA should result in the GA upgrade notice showing. + val mode = monitorFactory.waitForNextSuccessfulResult(appStartupState) + assertThat(mode.buildFlavorNoticeMode).isEqualTo(SHOW_UPGRADE_TO_GENERAL_AVAILABILITY_NOTICE) + } + + @Test + fun testController_userOnboarded_changeToGaBuild_fromBeta_showGaNotice() { + // Simulate the previous app already having completed onboarding in a beta build. + executeInPreviousAppInstance { testComponent -> + TestModule.buildFlavor = BuildFlavor.BETA + testComponent.getAppStartupStateController().markOnboardingFlowCompleted() + testComponent.getTestCoroutineDispatchers().runCurrent() + } + TestModule.buildFlavor = BuildFlavor.GENERAL_AVAILABILITY + setUpDefaultTestApplicationComponent() + + val appStartupState = appStartupStateController.getAppStartupState() + + // Changing from beta to GA should result in the GA upgrade notice showing. + val mode = monitorFactory.waitForNextSuccessfulResult(appStartupState) + assertThat(mode.buildFlavorNoticeMode).isEqualTo(SHOW_UPGRADE_TO_GENERAL_AVAILABILITY_NOTICE) + } + + @Test + fun testController_userOnboarded_changeToGaBuild_fromGaAgain_showNoNotice() { + // Simulate the previous app already having completed onboarding in a generally available build. + executeInPreviousAppInstance { testComponent -> + TestModule.buildFlavor = BuildFlavor.GENERAL_AVAILABILITY + testComponent.getAppStartupStateController().markOnboardingFlowCompleted() + testComponent.getTestCoroutineDispatchers().runCurrent() + } + TestModule.buildFlavor = BuildFlavor.GENERAL_AVAILABILITY + setUpDefaultTestApplicationComponent() + + val appStartupState = appStartupStateController.getAppStartupState() + + // The GA upgrade notice is only shown when changing from beta. + val mode = monitorFactory.waitForNextSuccessfulResult(appStartupState) + assertThat(mode.buildFlavorNoticeMode).isEqualTo(NO_NOTICE) + } + + /* Tests to verify that notices can be permanently dismissed. */ + + @Test + fun testController_dismissBetaNoticePermanently_scenariosWithoutBetaNotice_showNoNotice() { + executeInPreviousAppInstance { testComponent -> + TestModule.buildFlavor = BuildFlavor.BETA + testComponent.getAppStartupStateController().apply { + markOnboardingFlowCompleted() + // While this is technically impossible for this exact configuration, it could be done by + // permanently dismissing during an earlier time when the notice was shown before the user + // returned to the beta flavor of the app. + dismissBetaNoticesPermanently() + } + testComponent.getTestCoroutineDispatchers().runCurrent() + } + TestModule.buildFlavor = BuildFlavor.BETA + setUpDefaultTestApplicationComponent() + + val appStartupState = appStartupStateController.getAppStartupState() + + // No notice is shown in this case (beta -> beta). + val mode = monitorFactory.waitForNextSuccessfulResult(appStartupState) + assertThat(mode.buildFlavorNoticeMode).isEqualTo(NO_NOTICE) + } + + @Test + @RunParameterized( + Iteration("testing_to_beta", "initialFlavorName=TESTING"), + Iteration("dev_to_beta", "initialFlavorName=DEVELOPER"), + Iteration("alpha_to_beta", "initialFlavorName=ALPHA"), + Iteration("ga_to_beta", "initialFlavorName=GENERAL_AVAILABILITY") + ) + fun testController_dismissBetaNoticePermanently_scenariosWhenBetaNoticeDoesShow_showNoNotice() { + executeInPreviousAppInstance { testComponent -> + TestModule.buildFlavor = BuildFlavor.valueOf(initialFlavorName) + testComponent.getAppStartupStateController().apply { + markOnboardingFlowCompleted() + dismissBetaNoticesPermanently() + } + testComponent.getTestCoroutineDispatchers().runCurrent() + } + TestModule.buildFlavor = BuildFlavor.BETA + setUpDefaultTestApplicationComponent() + + val appStartupState = appStartupStateController.getAppStartupState() + + // Despite a notice normally showing in this circumstance, it doesn't here since the beta notice + // was permanently disabled. + val mode = monitorFactory.waitForNextSuccessfulResult(appStartupState) + assertThat(mode.buildFlavorNoticeMode).isEqualTo(NO_NOTICE) + } + + @Test + @RunParameterized( + Iteration("testing_to_ga", "initialFlavorName=TESTING"), + Iteration("dev_to_ga", "initialFlavorName=DEVELOPER") + ) + fun testController_dismissGaNoticePermanently_scenariosWhenGaNoticeDoesNotShow_showNoNotice() { + executeInPreviousAppInstance { testComponent -> + TestModule.buildFlavor = BuildFlavor.valueOf(initialFlavorName) + testComponent.getAppStartupStateController().apply { + markOnboardingFlowCompleted() + dismissGaUpgradeNoticesPermanently() + } + testComponent.getTestCoroutineDispatchers().runCurrent() + } + TestModule.buildFlavor = BuildFlavor.GENERAL_AVAILABILITY + setUpDefaultTestApplicationComponent() + + val appStartupState = appStartupStateController.getAppStartupState() + + // A notice does not show in these circumstances (though, it wouldn't be expected to regardless + // of whether the GA notice was permanently disabled). + val mode = monitorFactory.waitForNextSuccessfulResult(appStartupState) + assertThat(mode.buildFlavorNoticeMode).isEqualTo(NO_NOTICE) + } + + @Test + @RunParameterized( + Iteration("alpha_to_ga", "initialFlavorName=ALPHA"), + Iteration("beta_to_ga", "initialFlavorName=BETA") + ) + fun testController_dismissGaNoticePermanently_scenariosWhenGaNoticeDoesShow_showNoNotice() { + executeInPreviousAppInstance { testComponent -> + TestModule.buildFlavor = BuildFlavor.valueOf(initialFlavorName) + testComponent.getAppStartupStateController().apply { + markOnboardingFlowCompleted() + dismissGaUpgradeNoticesPermanently() + } + testComponent.getTestCoroutineDispatchers().runCurrent() + } + TestModule.buildFlavor = BuildFlavor.GENERAL_AVAILABILITY + setUpDefaultTestApplicationComponent() + + val appStartupState = appStartupStateController.getAppStartupState() + + // Despite a notice normally showing in this circumstance, it doesn't here since the GA upgrade + // notice was permanently disabled. + val mode = monitorFactory.waitForNextSuccessfulResult(appStartupState) + assertThat(mode.buildFlavorNoticeMode).isEqualTo(NO_NOTICE) + } + private fun setUpTestApplicationComponent() { ApplicationProvider.getApplicationContext().inject(this) } @@ -277,7 +784,7 @@ class AppStartupStateControllerTest { * Note that only dependencies fetched from the specified [TestApplicationComponent] should be * used, not any class-level injected dependencies. */ - private fun executeInPreviousApp(block: (TestApplicationComponent) -> Unit) { + private fun executeInPreviousAppInstance(block: (TestApplicationComponent) -> Unit) { val testApplication = TestApplication() // The true application is hooked as a base context. This is to make sure the new application // can behave like a real Android application class (per Robolectric) without having a shared @@ -341,6 +848,10 @@ class AppStartupStateControllerTest { // TODO(#89): Move this to a common test application component. @Module class TestModule { + companion object { + var buildFlavor = BuildFlavor.BUILD_FLAVOR_UNSPECIFIED + } + @Provides @Singleton fun provideContext(application: Application): Context { @@ -360,6 +871,9 @@ class AppStartupStateControllerTest { @GlobalLogLevel @Provides fun provideGlobalLogLevel(): LogLevel = LogLevel.VERBOSE + + @Provides + fun provideTestingBuildFlavor(): BuildFlavor = buildFlavor } // TODO(#89): Move this to a common test application component. diff --git a/domain/src/test/java/org/oppia/android/domain/onboarding/BUILD.bazel b/domain/src/test/java/org/oppia/android/domain/onboarding/BUILD.bazel new file mode 100644 index 00000000000..85124495fd0 --- /dev/null +++ b/domain/src/test/java/org/oppia/android/domain/onboarding/BUILD.bazel @@ -0,0 +1,39 @@ +""" +Tests for domain components pertaining to onboarding the user to the app. +""" + +load("@dagger//:workspace_defs.bzl", "dagger_rules") +load("//:oppia_android_test.bzl", "oppia_android_test") + +oppia_android_test( + name = "AppStartupStateControllerTest", + srcs = ["AppStartupStateControllerTest.kt"], + custom_package = "org.oppia.android.domain.onboarding", + test_class = "org.oppia.android.domain.onboarding.AppStartupStateControllerTest", + test_manifest = "//domain:test_manifest", + deps = [ + ":dagger", + "//domain", + "//domain/src/main/java/org/oppia/android/domain/onboarding:retriever_prod_module", + "//domain/src/main/java/org/oppia/android/domain/onboarding:state_controller", + "//domain/src/main/java/org/oppia/android/domain/oppialogger:prod_module", + "//domain/src/main/java/org/oppia/android/domain/oppialogger/analytics:prod_module", + "//testing", + "//testing/src/main/java/org/oppia/android/testing/data:data_provider_test_monitor", + "//testing/src/main/java/org/oppia/android/testing/junit:oppia_parameterized_test_runner", + "//testing/src/main/java/org/oppia/android/testing/junit:parameterized_robolectric_test_runner", + "//testing/src/main/java/org/oppia/android/testing/robolectric:test_module", + "//testing/src/main/java/org/oppia/android/testing/threading:test_module", + "//third_party:com_google_truth_truth", + "//third_party:junit_junit", + "//third_party:org_mockito_mockito-core", + "//third_party:org_robolectric_robolectric", + "//third_party:robolectric_android-all", + "//utility/src/main/java/org/oppia/android/util/locale:prod_module", + "//utility/src/main/java/org/oppia/android/util/logging:prod_module", + "//utility/src/main/java/org/oppia/android/util/networking:debug_module", + "//utility/src/main/java/org/oppia/android/util/system:prod_module", + ], +) + +dagger_rules() diff --git a/instrumentation/src/java/org/oppia/android/instrumentation/application/BUILD.bazel b/instrumentation/src/java/org/oppia/android/instrumentation/application/BUILD.bazel index 10e816007b1..78c15038969 100644 --- a/instrumentation/src/java/org/oppia/android/instrumentation/application/BUILD.bazel +++ b/instrumentation/src/java/org/oppia/android/instrumentation/application/BUILD.bazel @@ -22,6 +22,7 @@ kt_android_library( "//app/src/main/java/org/oppia/android/app/application:abstract_application", "//app/src/main/java/org/oppia/android/app/application:application_component", "//app/src/main/java/org/oppia/android/app/application:common_application_modules", + "//app/src/main/java/org/oppia/android/app/application/testing:testing_build_flavor_module", "//data/src/main/java/org/oppia/android/data/backends/gae:network_config_annotations", "//domain", "//utility", diff --git a/instrumentation/src/java/org/oppia/android/instrumentation/application/TestApplicationComponent.kt b/instrumentation/src/java/org/oppia/android/instrumentation/application/TestApplicationComponent.kt index b8f2ba6422a..7a95bcc6990 100644 --- a/instrumentation/src/java/org/oppia/android/instrumentation/application/TestApplicationComponent.kt +++ b/instrumentation/src/java/org/oppia/android/instrumentation/application/TestApplicationComponent.kt @@ -4,6 +4,7 @@ import dagger.Component import org.oppia.android.app.application.ApplicationComponent 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 @@ -96,7 +97,8 @@ import javax.inject.Singleton LoggingIdentifierModule::class, ApplicationLifecycleModule::class, SyncStatusModule::class, NetworkConnectionDebugUtilModule::class, MetricLogSchedulerModule::class, PerformanceMetricsLoggerModule::class, - PerformanceMetricsAssessorModule::class, PerformanceMetricsConfigurationsModule::class + PerformanceMetricsAssessorModule::class, PerformanceMetricsConfigurationsModule::class, + TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/model/src/main/proto/BUILD.bazel b/model/src/main/proto/BUILD.bazel index c6d19966e44..94372b448ff 100644 --- a/model/src/main/proto/BUILD.bazel +++ b/model/src/main/proto/BUILD.bazel @@ -112,6 +112,7 @@ java_lite_proto_library( oppia_proto_library( name = "onboarding_proto", srcs = ["onboarding.proto"], + deps = [":version_proto"], ) java_lite_proto_library( @@ -191,6 +192,18 @@ java_lite_proto_library( deps = [":translation_proto"], ) +oppia_proto_library( + name = "version_proto", + srcs = ["version.proto"], + visibility = ["//:oppia_api_visibility"], +) + +java_lite_proto_library( + name = "version_java_proto_lite", + visibility = ["//:oppia_api_visibility"], + deps = [":version_proto"], +) + oppia_proto_library( name = "voiceover_proto", srcs = ["voiceover.proto"], diff --git a/model/src/main/proto/onboarding.proto b/model/src/main/proto/onboarding.proto index ca2027922ec..3ed97a65062 100644 --- a/model/src/main/proto/onboarding.proto +++ b/model/src/main/proto/onboarding.proto @@ -2,6 +2,8 @@ syntax = "proto3"; package model; +import "version.proto"; + option java_package = "org.oppia.android.app.model"; option java_multiple_files = true; @@ -24,15 +26,51 @@ message AppStartupState { APP_IS_DEPRECATED = 3; } + // Describes different notices that may be shown to the user on startup depending on whether + // they're using or have used a pre-release version of the app. + enum BuildFlavorNoticeMode { + // Indicates that the current build flavor notice mode is unknown. + FLAVOR_NOTICE_MODE_UNSPECIFIED = 0; + + // Indicates that the user is not in a situation where a notice should be shown (either because + // they aren't or haven't recently used a pre-release version of the app, or they've asked to + // not see those notices again). + NO_NOTICE = 1; + + // Indicates that the user should be a shown a notice mentioning that they're currently using a + // beta version of the app. + SHOW_BETA_NOTICE = 2; + + // Indicates that the user should be shown a notice mentioning that they've recently upgraded to + // the general availability version of the app. + SHOW_UPGRADE_TO_GENERAL_AVAILABILITY_NOTICE = 3; + } + // Contains the user's startup state upon opening the app. This may change from app open to app // open, but should remain constant throughout the lifetime of a single process. StartupMode startup_mode = 1; + // Indicates if the app is starting up again immediately after a crash has occurred. bool is_from_crash = 2; + + // Indicates whether the user should be shown a startup notice based on their recent usage of + // different build flavors of the app. + BuildFlavorNoticeMode build_flavor_notice_mode = 3; } // Stores the completion state of the user's progress through the app onboarding flow. message OnboardingState { // Indicates whether user has fully completed the onboarding flow. bool already_onboarded_app = 1; + + // Represents the build flavor of the app the last time the user used it. + BuildFlavor last_used_build_flavor = 2; + + // Represents whether the user has seen, and asked to not see again, the notice that they're using + // a beta version of the app. + bool permanently_dismissed_beta_notice = 3; + + // Represents whether the user has seen, and asked to not see again, the notice that they're using + // the general availability version of the app after having previously used a pre-release version. + bool permanently_dismissed_ga_upgrade_notice = 4; } diff --git a/model/src/main/proto/version.proto b/model/src/main/proto/version.proto new file mode 100644 index 00000000000..a0878579e57 --- /dev/null +++ b/model/src/main/proto/version.proto @@ -0,0 +1,32 @@ +syntax = "proto3"; + +package model; + +option java_package = "org.oppia.android.app.model"; +option java_multiple_files = true; + +// Represents different compile-time build flavors that the app may be built under. +enum BuildFlavor { + // Indicates an unknown build flavor (in these situations, client code should fall back to a + // reasonable default). + BUILD_FLAVOR_UNSPECIFIED = 0; + + // Corresponds to a testing environment-specific build of the app. Note that this is different + // than a 'test version' of the app for the purpose of quality assurance testing. The QA team will + // always be testing one of the user-facing flavors of the app (generally alpha, beta, and GA, but + // potentially the developer flavor on occasion). This flavor is never expected to be used for a + // version of the app used directly by a human. + TESTING = 1; + + // Corresponds to a developer-only build of the app. + DEVELOPER = 2; + + // Corresponds to an alpha (closed testing track) pre-release build of the app. + ALPHA = 3; + + // Corresponds to a beta (open testing track) pre-release/early access build of the app. + BETA = 4; + + // Corresponds to a generally available production build of the app. + GENERAL_AVAILABILITY = 5; +} diff --git a/scripts/assets/file_content_validation_checks.textproto b/scripts/assets/file_content_validation_checks.textproto index ab76d9e3ddf..0c585bad36b 100644 --- a/scripts/assets/file_content_validation_checks.textproto +++ b/scripts/assets/file_content_validation_checks.textproto @@ -291,6 +291,7 @@ file_content_checks { prohibited_content_regex: "OppiaParameterizedTestRunner" failure_message: "To use OppiaParameterizedTestRunner, please add an exemption to file_content_validation_checks.textproto and add an explanation for your use case in your PR description. Note that parameterized tests should only be used in special circumstances where a single behavior can be tested across multiple inputs, or for especially large test suites that can be trivially reduced." exempted_file_name: "app/src/sharedTest/java/org/oppia/android/app/customview/interaction/MathExpressionInteractionsViewTest.kt" + exempted_file_name: "app/src/sharedTest/java/org/oppia/android/app/splash/SplashActivityTest.kt" exempted_file_name: "app/src/test/java/org/oppia/android/app/utility/math/MathExpressionAccessibilityUtilTest.kt" exempted_file_name: "domain/src/test/java/org/oppia/android/domain/classify/rules/algebraicexpressioninput/AlgebraicExpressionInputIsEquivalentToRuleClassifierProviderTest.kt" exempted_file_name: "domain/src/test/java/org/oppia/android/domain/classify/rules/algebraicexpressioninput/AlgebraicExpressionInputMatchesExactlyWithRuleClassifierProviderTest.kt" @@ -301,6 +302,7 @@ file_content_checks { exempted_file_name: "domain/src/test/java/org/oppia/android/domain/classify/rules/numericexpressioninput/NumericExpressionInputIsEquivalentToRuleClassifierProviderTest.kt" exempted_file_name: "domain/src/test/java/org/oppia/android/domain/classify/rules/numericexpressioninput/NumericExpressionInputMatchesExactlyWithRuleClassifierProviderTest.kt" exempted_file_name: "domain/src/test/java/org/oppia/android/domain/classify/rules/numericexpressioninput/NumericExpressionInputMatchesUpToTrivialManipulationsRuleClassifierProviderTest.kt" + exempted_file_name: "domain/src/test/java/org/oppia/android/domain/onboarding/AppStartupStateControllerTest.kt" exempted_file_name: "domain/src/test/java/org/oppia/android/domain/oppialogger/analytics/LearnerAnalyticsLoggerTest.kt" exempted_file_name: "scripts/src/javatests/org/oppia/android/scripts/regex/RegexPatternValidationCheckTest.kt" exempted_file_name: "utility/src/test/java/org/oppia/android/util/math/ExpressionToComparableOperationConverterTest.kt" diff --git a/scripts/assets/kdoc_validity_exemptions.textproto b/scripts/assets/kdoc_validity_exemptions.textproto index edae7eeecd5..eb430e59c77 100644 --- a/scripts/assets/kdoc_validity_exemptions.textproto +++ b/scripts/assets/kdoc_validity_exemptions.textproto @@ -23,7 +23,7 @@ exempted_file_path: "app/src/main/java/org/oppia/android/app/completedstorylist/ exempted_file_path: "app/src/main/java/org/oppia/android/app/completedstorylist/CompletedStoryListViewModel.kt" exempted_file_path: "app/src/main/java/org/oppia/android/app/customview/LessonThumbnailImageView.kt" exempted_file_path: "app/src/main/java/org/oppia/android/app/customview/SegmentedCircularProgressView.kt" -exempted_file_path: "app/src/main/java/org/oppia/android/app/deprecation/AutomaticAppDeprecationNoticeDialogFragmentPresenter.kt" +exempted_file_path: "app/src/main/java/org/oppia/android/app/notice/AutomaticAppDeprecationNoticeDialogFragmentPresenter.kt" exempted_file_path: "app/src/main/java/org/oppia/android/app/devoptions/DeveloperOptionsActivity.kt" exempted_file_path: "app/src/main/java/org/oppia/android/app/devoptions/DeveloperOptionsActivityPresenter.kt" exempted_file_path: "app/src/main/java/org/oppia/android/app/devoptions/DeveloperOptionsFragment.kt" diff --git a/scripts/assets/test_file_exemptions.textproto b/scripts/assets/test_file_exemptions.textproto index 24119196db3..8018118b950 100644 --- a/scripts/assets/test_file_exemptions.textproto +++ b/scripts/assets/test_file_exemptions.textproto @@ -51,8 +51,12 @@ exempted_file_path: "app/src/main/java/org/oppia/android/app/application/alpha/A exempted_file_path: "app/src/main/java/org/oppia/android/app/application/alphakenya/AlphaKenyaApplicationComponent.kt" exempted_file_path: "app/src/main/java/org/oppia/android/app/application/alphakenya/AlphaKenyaBuildFlavorModule.kt" exempted_file_path: "app/src/main/java/org/oppia/android/app/application/alphakenya/AlphaKenyaOppiaApplication.kt" +exempted_file_path: "app/src/main/java/org/oppia/android/app/application/beta/BetaOppiaApplication.kt" +exempted_file_path: "app/src/main/java/org/oppia/android/app/application/beta/BetaApplicationComponent.kt" exempted_file_path: "app/src/main/java/org/oppia/android/app/application/dev/DeveloperOppiaApplication.kt" exempted_file_path: "app/src/main/java/org/oppia/android/app/application/dev/DeveloperApplicationComponent.kt" +exempted_file_path: "app/src/main/java/org/oppia/android/app/application/ga/GaOppiaApplication.kt" +exempted_file_path: "app/src/main/java/org/oppia/android/app/application/ga/GaApplicationComponent.kt" exempted_file_path: "app/src/main/java/org/oppia/android/app/completedstorylist/CompletedStoryItemViewModel.kt" exempted_file_path: "app/src/main/java/org/oppia/android/app/completedstorylist/CompletedStoryListActivityPresenter.kt" exempted_file_path: "app/src/main/java/org/oppia/android/app/completedstorylist/CompletedStoryListFragment.kt" @@ -64,9 +68,13 @@ exempted_file_path: "app/src/main/java/org/oppia/android/app/customview/interact exempted_file_path: "app/src/main/java/org/oppia/android/app/customview/interaction/NumericInputInteractionView.kt" exempted_file_path: "app/src/main/java/org/oppia/android/app/customview/interaction/RatioInputInteractionView.kt" exempted_file_path: "app/src/main/java/org/oppia/android/app/customview/interaction/TextInputInteractionView.kt" -exempted_file_path: "app/src/main/java/org/oppia/android/app/deprecation/AutomaticAppDeprecationNoticeDialogFragment.kt" -exempted_file_path: "app/src/main/java/org/oppia/android/app/deprecation/AutomaticAppDeprecationNoticeDialogFragmentPresenter.kt" -exempted_file_path: "app/src/main/java/org/oppia/android/app/deprecation/DeprecationNoticeExitAppListener.kt" +exempted_file_path: "app/src/main/java/org/oppia/android/app/notice/AutomaticAppDeprecationNoticeDialogFragment.kt" +exempted_file_path: "app/src/main/java/org/oppia/android/app/notice/AutomaticAppDeprecationNoticeDialogFragmentPresenter.kt" +exempted_file_path: "app/src/main/java/org/oppia/android/app/notice/BetaNoticeClosedListener.kt" +exempted_file_path: "app/src/main/java/org/oppia/android/app/notice/BetaNoticeDialogFragmentPresenter.kt" +exempted_file_path: "app/src/main/java/org/oppia/android/app/notice/DeprecationNoticeExitAppListener.kt" +exempted_file_path: "app/src/main/java/org/oppia/android/app/notice/GeneralAvailabilityUpgradeNoticeClosedListener.kt" +exempted_file_path: "app/src/main/java/org/oppia/android/app/notice/GeneralAvailabilityUpgradeNoticeDialogFragmentPresenter.kt" exempted_file_path: "app/src/main/java/org/oppia/android/app/devoptions/DeveloperOptionsActivityPresenter.kt" exempted_file_path: "app/src/main/java/org/oppia/android/app/devoptions/DeveloperOptionsFragmentPresenter.kt" exempted_file_path: "app/src/main/java/org/oppia/android/app/devoptions/DeveloperOptionsModule.kt" @@ -215,6 +223,8 @@ exempted_file_path: "app/src/main/java/org/oppia/android/app/mydownloads/MyDownl exempted_file_path: "app/src/main/java/org/oppia/android/app/mydownloads/MyDownloadsViewPagerAdapter.kt" exempted_file_path: "app/src/main/java/org/oppia/android/app/mydownloads/UpdatesTabFragment.kt" exempted_file_path: "app/src/main/java/org/oppia/android/app/mydownloads/UpdatesTabFragmentPresenter.kt" +exempted_file_path: "app/src/main/java/org/oppia/android/app/notice/testing/BetaNoticeDialogFragmentTestActivity.kt" +exempted_file_path: "app/src/main/java/org/oppia/android/app/notice/testing/GeneralAvailabilityUpgradeNoticeDialogFragmentTestActivity.kt" exempted_file_path: "app/src/main/java/org/oppia/android/app/onboarding/OnboadingSlideViewModel.kt" exempted_file_path: "app/src/main/java/org/oppia/android/app/onboarding/OnboardingActivityPresenter.kt" exempted_file_path: "app/src/main/java/org/oppia/android/app/onboarding/OnboardingFragmentPresenter.kt" diff --git a/testing/src/main/java/org/oppia/android/testing/OppiaTestRunner.kt b/testing/src/main/java/org/oppia/android/testing/OppiaTestRunner.kt index 30a9a81d934..8ac79646368 100644 --- a/testing/src/main/java/org/oppia/android/testing/OppiaTestRunner.kt +++ b/testing/src/main/java/org/oppia/android/testing/OppiaTestRunner.kt @@ -27,7 +27,7 @@ class OppiaTestRunner : AndroidJUnitRunner() { // Load a new application if it's different than the original. val bindApplication = retrieveTestApplicationName(arguments?.getString("class"))?.let { newApplication(applicationClassLoader, it, targetContext) - } ?: targetContext.applicationContext as Application + } ?: targetContext as Application // Ensure the bound application is forcibly overwritten in the target context, and used // subsequently throughout the runner since it's replacing the previous application. diff --git a/testing/src/test/java/org/oppia/android/testing/junit/BUILD.bazel b/testing/src/test/java/org/oppia/android/testing/junit/BUILD.bazel index 6dcc99d23a8..cde8eeeb69a 100644 --- a/testing/src/test/java/org/oppia/android/testing/junit/BUILD.bazel +++ b/testing/src/test/java/org/oppia/android/testing/junit/BUILD.bazel @@ -18,6 +18,7 @@ oppia_android_test( "//app/src/main/java/org/oppia/android/app/application:application_injector", "//app/src/main/java/org/oppia/android/app/application:application_injector_provider", "//app/src/main/java/org/oppia/android/app/application:common_application_modules", + "//app/src/main/java/org/oppia/android/app/application/testing:testing_build_flavor_module", "//app/src/main/java/org/oppia/android/app/translation:app_language_locale_handler", "//app/src/main/java/org/oppia/android/app/translation/testing:test_module", "//domain", @@ -71,6 +72,7 @@ oppia_android_test( "//app/src/main/java/org/oppia/android/app/application:application_injector", "//app/src/main/java/org/oppia/android/app/application:application_injector_provider", "//app/src/main/java/org/oppia/android/app/application:common_application_modules", + "//app/src/main/java/org/oppia/android/app/application/testing:testing_build_flavor_module", "//app/src/main/java/org/oppia/android/app/translation:app_language_locale_handler", "//app/src/main/java/org/oppia/android/app/translation/testing:test_module", "//domain", @@ -123,6 +125,7 @@ oppia_android_test( "//app/src/main/java/org/oppia/android/app/application:application_injector", "//app/src/main/java/org/oppia/android/app/application:application_injector_provider", "//app/src/main/java/org/oppia/android/app/application:common_application_modules", + "//app/src/main/java/org/oppia/android/app/application/testing:testing_build_flavor_module", "//app/src/main/java/org/oppia/android/app/translation:app_language_locale_handler", "//app/src/main/java/org/oppia/android/app/translation/testing:test_module", "//domain", diff --git a/testing/src/test/java/org/oppia/android/testing/junit/InitializeDefaultLocaleRuleCustomContextTest.kt b/testing/src/test/java/org/oppia/android/testing/junit/InitializeDefaultLocaleRuleCustomContextTest.kt index 51e520fb94b..0064df9d8cc 100644 --- a/testing/src/test/java/org/oppia/android/testing/junit/InitializeDefaultLocaleRuleCustomContextTest.kt +++ b/testing/src/test/java/org/oppia/android/testing/junit/InitializeDefaultLocaleRuleCustomContextTest.kt @@ -18,6 +18,7 @@ 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.OppiaLanguage @@ -265,7 +266,7 @@ class InitializeDefaultLocaleRuleCustomContextTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/testing/src/test/java/org/oppia/android/testing/junit/InitializeDefaultLocaleRuleOmissionTest.kt b/testing/src/test/java/org/oppia/android/testing/junit/InitializeDefaultLocaleRuleOmissionTest.kt index 65b3bc82a08..4eeb5333f5c 100644 --- a/testing/src/test/java/org/oppia/android/testing/junit/InitializeDefaultLocaleRuleOmissionTest.kt +++ b/testing/src/test/java/org/oppia/android/testing/junit/InitializeDefaultLocaleRuleOmissionTest.kt @@ -17,6 +17,7 @@ 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 @@ -141,7 +142,7 @@ class InitializeDefaultLocaleRuleOmissionTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/testing/src/test/java/org/oppia/android/testing/junit/InitializeDefaultLocaleRuleTest.kt b/testing/src/test/java/org/oppia/android/testing/junit/InitializeDefaultLocaleRuleTest.kt index a53e4b716e0..4cb60a9367a 100644 --- a/testing/src/test/java/org/oppia/android/testing/junit/InitializeDefaultLocaleRuleTest.kt +++ b/testing/src/test/java/org/oppia/android/testing/junit/InitializeDefaultLocaleRuleTest.kt @@ -18,6 +18,7 @@ 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.OppiaLanguage @@ -145,7 +146,7 @@ class InitializeDefaultLocaleRuleTest { NumericExpressionInputModule::class, AlgebraicExpressionInputModule::class, MathEquationInputModule::class, SplitScreenInteractionModule::class, LoggingIdentifierModule::class, ApplicationLifecycleModule::class, - SyncStatusModule::class, MetricLogSchedulerModule::class + SyncStatusModule::class, MetricLogSchedulerModule::class, TestingBuildFlavorModule::class ] ) interface TestApplicationComponent : ApplicationComponent { diff --git a/utility/src/main/java/org/oppia/android/util/data/DataProvider.kt b/utility/src/main/java/org/oppia/android/util/data/DataProvider.kt index 5b940afa095..0cb2e1b97dd 100644 --- a/utility/src/main/java/org/oppia/android/util/data/DataProvider.kt +++ b/utility/src/main/java/org/oppia/android/util/data/DataProvider.kt @@ -1,14 +1,13 @@ package org.oppia.android.util.data -import android.content.Context +import android.app.Application /** * Represents a provider of data that can be delivered and changed asynchronously. * * @param The type of data being provided. */ -abstract class DataProvider(val context: Context) { - +abstract class DataProvider(val application: Application) { /** * Returns a unique identifier that corresponds to this data provider. This should be a trivially * copyable and immutable object. This ID is used to determine which data provider subscribers diff --git a/utility/src/main/java/org/oppia/android/util/data/DataProviders.kt b/utility/src/main/java/org/oppia/android/util/data/DataProviders.kt index e86c139c122..93049848c3c 100644 --- a/utility/src/main/java/org/oppia/android/util/data/DataProviders.kt +++ b/utility/src/main/java/org/oppia/android/util/data/DataProviders.kt @@ -1,6 +1,6 @@ package org.oppia.android.util.data -import android.content.Context +import android.app.Application import androidx.lifecycle.LiveData import dagger.Reusable import kotlinx.coroutines.CoroutineDispatcher @@ -26,7 +26,7 @@ import javax.inject.Inject */ @Reusable // Since otherwise a new provider will be created for each companion object call. class DataProviders @Inject constructor( - private val context: Context, + private val application: Application, @BackgroundDispatcher private val backgroundDispatcher: CoroutineDispatcher, private val asyncDataSubscriptionManager: AsyncDataSubscriptionManager, private val exceptionLogger: ExceptionLogger @@ -46,7 +46,7 @@ class DataProviders @Inject constructor( fun DataProvider.transform(newId: Any, function: (I) -> O): DataProvider { val dataProviders = getDataProviders() dataProviders.asyncDataSubscriptionManager.associateIds(newId, getId()) - return object : DataProvider(context) { + return object : DataProvider(application) { override fun getId(): Any = newId override suspend fun retrieveData(): AsyncResult { @@ -70,7 +70,7 @@ class DataProviders @Inject constructor( ): DataProvider { val dataProviders = getDataProviders() dataProviders.asyncDataSubscriptionManager.associateIds(newId, getId()) - return object : DataProvider(context) { + return object : DataProvider(application) { override fun getId(): Any = newId override suspend fun retrieveData(): AsyncResult { @@ -90,7 +90,7 @@ class DataProviders @Inject constructor( function: suspend (I) -> AsyncResult ): NestedTransformedDataProvider { return NestedTransformedDataProvider.createNestedTransformedDataProvider( - context, newId, this, function, getDataProviders().asyncDataSubscriptionManager + application, newId, this, function, getDataProviders().asyncDataSubscriptionManager ) } @@ -113,7 +113,7 @@ class DataProviders @Inject constructor( val dataProviders = getDataProviders() dataProviders.asyncDataSubscriptionManager.associateIds(newId, getId()) dataProviders.asyncDataSubscriptionManager.associateIds(newId, dataProvider.getId()) - return object : DataProvider(context) { + return object : DataProvider(application) { override fun getId(): Any { return newId } @@ -141,7 +141,7 @@ class DataProviders @Inject constructor( val dataProviders = getDataProviders() dataProviders.asyncDataSubscriptionManager.associateIds(newId, getId()) dataProviders.asyncDataSubscriptionManager.associateIds(newId, dataProvider.getId()) - return object : DataProvider(context) { + return object : DataProvider(application) { override fun getId(): Any { return newId } @@ -167,7 +167,7 @@ class DataProviders @Inject constructor( } private fun DataProvider.getDataProviders(): DataProviders { - val injectorProvider = context.applicationContext as DataProvidersInjectorProvider + val injectorProvider = application as DataProvidersInjectorProvider return injectorProvider.getDataProvidersInjector().getDataProviders() } } @@ -184,7 +184,7 @@ class DataProviders @Inject constructor( * [AsyncDataSubscriptionManager.notifyChange] with the in-memory provider's identifier. */ fun createInMemoryDataProvider(id: Any, loadFromMemory: () -> T): DataProvider { - return object : DataProvider(context) { + return object : DataProvider(application) { override fun getId(): Any { return id } @@ -208,7 +208,7 @@ class DataProviders @Inject constructor( id: Any, loadFromMemoryAsync: suspend () -> AsyncResult ): DataProvider { - return object : DataProvider(context) { + return object : DataProvider(application) { override fun getId(): Any { return id } @@ -277,12 +277,12 @@ class DataProviders @Inject constructor( * provider can change. */ class NestedTransformedDataProvider private constructor( - context: Context, + application: Application, private val id: Any, private var baseId: Any, private val asyncDataSubscriptionManager: AsyncDataSubscriptionManager, private var retrieveTransformedData: suspend () -> AsyncResult - ) : DataProvider(context) { + ) : DataProvider(application) { init { initializeTransformer() } @@ -320,14 +320,14 @@ class DataProviders @Inject constructor( companion object { /** Returns a new [NestedTransformedDataProvider]. */ internal fun createNestedTransformedDataProvider( - context: Context, + application: Application, id: Any, baseDataProvider: DataProvider, transform: suspend (I) -> AsyncResult, asyncDataSubscriptionManager: AsyncDataSubscriptionManager ): NestedTransformedDataProvider { return NestedTransformedDataProvider( - context, id, baseDataProvider.getId(), asyncDataSubscriptionManager + application, id, baseDataProvider.getId(), asyncDataSubscriptionManager ) { baseDataProvider.retrieveData().transformAsync(transform) } diff --git a/utility/src/main/java/org/oppia/android/util/data/InMemoryBlockingCache.kt b/utility/src/main/java/org/oppia/android/util/data/InMemoryBlockingCache.kt index 0229c013ea7..289a63b5324 100644 --- a/utility/src/main/java/org/oppia/android/util/data/InMemoryBlockingCache.kt +++ b/utility/src/main/java/org/oppia/android/util/data/InMemoryBlockingCache.kt @@ -28,10 +28,10 @@ class InMemoryBlockingCache private constructor( */ private var value: T? = initialValue - private var changeObserver: suspend () -> Unit = {} + private var changeObserver: suspend (T?, T?) -> Unit = { _, _ -> } /** Registers an observer that is called synchronously whenever this cache's contents are changed. */ - fun observeChanges(changeObserver: suspend () -> Unit) { + fun observeChanges(changeObserver: suspend (T?, T?) -> Unit) { this.changeObserver = changeObserver } @@ -168,14 +168,16 @@ class InMemoryBlockingCache private constructor( } private suspend fun setCache(newValue: T): T { + val oldValue = value value = newValue - changeObserver() + changeObserver(oldValue, newValue) return newValue } private suspend fun clearCache() { + val oldValue = value value = null - changeObserver() + changeObserver(oldValue, null) } /** An injectable factory for [InMemoryBlockingCache]es. */ diff --git a/utility/src/main/java/org/oppia/android/util/logging/firebase/FirebaseEventLogger.kt b/utility/src/main/java/org/oppia/android/util/logging/firebase/FirebaseEventLogger.kt index eb285ed8864..93704c12dd6 100644 --- a/utility/src/main/java/org/oppia/android/util/logging/firebase/FirebaseEventLogger.kt +++ b/utility/src/main/java/org/oppia/android/util/logging/firebase/FirebaseEventLogger.kt @@ -1,7 +1,7 @@ package org.oppia.android.util.logging.firebase import android.annotation.SuppressLint -import android.content.Context +import android.app.Application import android.os.Bundle import com.google.firebase.analytics.FirebaseAnalytics import org.oppia.android.app.model.EventLog @@ -65,13 +65,11 @@ class FirebaseEventLogger private constructor( /** Application-scoped injectable factory for creating new [FirebaseEventLogger]s. */ @SuppressLint("MissingPermission") // This is a false warning probably due to the IJwB plugin. class Factory @Inject constructor( - private val context: Context, + private val application: Application, private val networkConnectionUtil: NetworkConnectionUtil, private val eventBundleCreator: EventBundleCreator ) { - private val firebaseAnalytics by lazy { - FirebaseAnalytics.getInstance(context.applicationContext) - } + private val firebaseAnalytics by lazy { FirebaseAnalytics.getInstance(application) } /** * Returns a new [FirebaseEventLogger] for the current application context. diff --git a/utility/src/main/java/org/oppia/android/util/parser/image/RepositoryGlideModule.kt b/utility/src/main/java/org/oppia/android/util/parser/image/RepositoryGlideModule.kt index d2349ab3eec..bd6bcbb52e0 100644 --- a/utility/src/main/java/org/oppia/android/util/parser/image/RepositoryGlideModule.kt +++ b/utility/src/main/java/org/oppia/android/util/parser/image/RepositoryGlideModule.kt @@ -1,5 +1,6 @@ package org.oppia.android.util.parser.image +import android.app.Application import android.content.Context import com.bumptech.glide.Glide import com.bumptech.glide.Registry @@ -44,7 +45,9 @@ class RepositoryGlideModule : AppGlideModule() { registry.append( MathModel::class.java, ByteBuffer::class.java, - MathBitmapModelLoader.Factory(context.applicationContext) + MathBitmapModelLoader.Factory( + context as? Application ?: context.applicationContext as Application + ) ) } } diff --git a/utility/src/main/java/org/oppia/android/util/parser/math/MathBitmapModelLoader.kt b/utility/src/main/java/org/oppia/android/util/parser/math/MathBitmapModelLoader.kt index e53cd82e71b..73100116aea 100644 --- a/utility/src/main/java/org/oppia/android/util/parser/math/MathBitmapModelLoader.kt +++ b/utility/src/main/java/org/oppia/android/util/parser/math/MathBitmapModelLoader.kt @@ -1,6 +1,6 @@ package org.oppia.android.util.parser.math -import android.content.Context +import android.app.Application import android.graphics.Bitmap import android.graphics.Bitmap.Config.ARGB_8888 import android.graphics.Canvas @@ -46,24 +46,24 @@ import kotlin.math.roundToInt * and blocks the main thread). */ class MathBitmapModelLoader private constructor( - private val applicationContext: Context + private val application: Application ) : ModelLoader { // Ref: https://bumptech.github.io/glide/tut/custom-modelloader.html#writing-the-modelloader. private val backgroundDispatcher by lazy { - val injectorProvider = applicationContext.applicationContext as DispatcherInjectorProvider + val injectorProvider = application as DispatcherInjectorProvider val injector = injectorProvider.getDispatcherInjector() injector.getBackgroundDispatcher() } private val blockingDispatcher by lazy { - val injectorProvider = applicationContext.applicationContext as DispatcherInjectorProvider + val injectorProvider = application as DispatcherInjectorProvider val injector = injectorProvider.getDispatcherInjector() injector.getBlockingDispatcher() } private val consoleLogger by lazy { - val injectorProvider = applicationContext as ConsoleLoggerInjectorProvider + val injectorProvider = application as ConsoleLoggerInjectorProvider val injector = injectorProvider.getConsoleLoggerInjector() injector.getConsoleLogger() } @@ -77,7 +77,7 @@ class MathBitmapModelLoader private constructor( return ModelLoader.LoadData( model.toKeySignature(), LatexModelDataFetcher( - applicationContext, + application, model, width, height, @@ -91,7 +91,7 @@ class MathBitmapModelLoader private constructor( override fun handles(model: MathModel): Boolean = true private class LatexModelDataFetcher( - private val applicationContext: Context, + private val application: Application, private val model: MathModel, private val targetWidth: Int, private val targetHeight: Int, @@ -109,7 +109,7 @@ class MathBitmapModelLoader private constructor( // creation can still happen in parallel, and those are the more expensive steps. val span = withContext(CoroutineScope(blockingDispatcher).coroutineContext) { MathExpressionSpan( - model.rawLatex, model.lineHeight, applicationContext.assets, !model.useInlineRendering + model.rawLatex, model.lineHeight, application.assets, !model.useInlineRendering ).also { it.ensureDrawable() } } val renderableText = SpannableStringBuilder("\uFFFC").apply { @@ -385,10 +385,10 @@ class MathBitmapModelLoader private constructor( /** [ModelLoaderFactory] for creating new [MathBitmapModelLoader]s. */ class Factory( - private val applicationContext: Context + private val application: Application ) : ModelLoaderFactory { override fun build(factory: MultiModelLoaderFactory): ModelLoader { - return MathBitmapModelLoader(applicationContext) + return MathBitmapModelLoader(application) } override fun teardown() {} diff --git a/utility/src/test/java/org/oppia/android/util/data/DataProvidersTest.kt b/utility/src/test/java/org/oppia/android/util/data/DataProvidersTest.kt index b7b4832a180..d4697e891b7 100644 --- a/utility/src/test/java/org/oppia/android/util/data/DataProvidersTest.kt +++ b/utility/src/test/java/org/oppia/android/util/data/DataProvidersTest.kt @@ -74,7 +74,7 @@ private const val COMBINED_STR_VALUE_02 = "I used to be indecisive. At least I t class DataProvidersTest { @field:[Rule JvmField] val mockitoRule: MockitoRule = MockitoJUnit.rule() - @Inject lateinit var context: Context + @Inject lateinit var application: Application @Inject lateinit var dataProviders: DataProviders @Inject lateinit var asyncDataSubscriptionManager: AsyncDataSubscriptionManager @Inject lateinit var fakeExceptionLogger: FakeExceptionLogger @@ -111,7 +111,7 @@ class DataProvidersTest { @Test fun testConvertToLiveData_fakeDataProvider_noObserver_doesNotCallRetrieve() { - val fakeDataProvider = object : DataProvider(context) { + val fakeDataProvider = object : DataProvider(application) { var hasRetrieveBeenCalled = false override fun getId(): Any = "fake_data_provider" @@ -130,7 +130,7 @@ class DataProvidersTest { @Test fun testConvertToLiveData_fakeDataProvider_withObserver_callsRetrieve() { - val fakeDataProvider = object : DataProvider(context) { + val fakeDataProvider = object : DataProvider(application) { var hasRetrieveBeenCalled = false override fun getId(): Any = "fake_data_provider" @@ -149,7 +149,7 @@ class DataProvidersTest { @Test fun testConvertToLiveData_trivialDataProvider_withObserver_observerReceivesValue() { - val simpleDataProvider = object : DataProvider(context) { + val simpleDataProvider = object : DataProvider(application) { override fun getId(): Any = "simple_data_provider" override suspend fun retrieveData(): AsyncResult = AsyncResult.Success(123) @@ -165,7 +165,7 @@ class DataProvidersTest { @Test fun testConvertToLiveData_dataProviderChanges_withObserver_observerReceivesUpdatedValue() { var providerValue = 123 - val simpleDataProvider = object : DataProvider(context) { + val simpleDataProvider = object : DataProvider(application) { override fun getId(): Any = "simple_data_provider" override suspend fun retrieveData(): AsyncResult = AsyncResult.Success(providerValue) @@ -183,7 +183,7 @@ class DataProvidersTest { @Test fun testConvertToLiveData_providerChanges_withoutObserver_newObserver_newObserverReceivesValue() { var providerValue = 123 - val simpleDataProvider = object : DataProvider(context) { + val simpleDataProvider = object : DataProvider(application) { override fun getId(): Any = "simple_data_provider" override suspend fun retrieveData(): AsyncResult = AsyncResult.Success(providerValue) @@ -203,7 +203,7 @@ class DataProvidersTest { @Test fun testConvertToLiveData_dataProviderNotified_sameValue_withObserver_observerNotCalledAgain() { - val simpleDataProvider = object : DataProvider(context) { + val simpleDataProvider = object : DataProvider(application) { override fun getId(): Any = "simple_data_provider" override suspend fun retrieveData(): AsyncResult = AsyncResult.Success(123) @@ -225,7 +225,7 @@ class DataProvidersTest { val providerOldResult = AsyncResult.Success(123) testCoroutineDispatchers.advanceTimeBy(10) val providerNewResult = AsyncResult.Success(456) - val simpleDataProvider = object : DataProvider(context) { + val simpleDataProvider = object : DataProvider(application) { var callCount = 0 override fun getId(): Any = "simple_data_provider" @@ -257,7 +257,7 @@ class DataProvidersTest { @Test fun testConvertToLiveData_dataProvider_providesPendingResultTwice_doesNotRedeliver() { - val simpleDataProvider = object : DataProvider(context) { + val simpleDataProvider = object : DataProvider(application) { override fun getId(): Any = "simple_data_provider" // Return a new pending result for each call. diff --git a/version.bzl b/version.bzl index 51dba48bd38..06510b95a88 100644 --- a/version.bzl +++ b/version.bzl @@ -16,3 +16,5 @@ OPPIA_DEV_VERSION_CODE = 28 OPPIA_ALPHA_KITKAT_VERSION_CODE = 29 OPPIA_ALPHA_VERSION_CODE = 30 OPPIA_ALPHA_KENYA_VERSION_CODE = 31 +OPPIA_BETA_VERSION_CODE = 32 +OPPIA_GA_VERSION_CODE = 33