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