From 0ef7a460ce1e41546f2ba1510ff12bbf88ad8177 Mon Sep 17 00:00:00 2001 From: RD Rama Devi <122200035+Rd4dev@users.noreply.github.com> Date: Wed, 28 Aug 2024 11:09:13 +0530 Subject: [PATCH 01/10] Fix part of #5343: Update Incorrect Link for the Oppia Android Code Coverage Wiki Page (#5511) ## Explanation Fixes Part of #5343 ### This PR includes - Updated the incorrect link reference in the #5483 for the Oppia Android Code Coverage Page - Current Broken link: https://github.com/oppia/oppia-android-workflow/wiki/Oppia-Android-Code-Coverage - Correct link to wiki: https://github.com/oppia/oppia-android/wiki/Oppia-Android-Code-Coverage - Fixed an incorrect SKIP status that triggered even after 'Unit Tests - Bazel' failure causing it to miscalculate the pb file list as zero thereby posting a skip comment. - Solved by adding an additional condition for the evaluation job. ``` if: ${{ !cancelled() && needs.check_unit_tests_completed.result == 'success'}} ``` - The code coverage comment got triggered even after assignment changes due to the presence of 'assigned' in the on `pull_request_target` triggered, fixed it by removing the 'assigned' trigger. - Utilized 'HtmlEscapers' to escape HTML characters from source code lines in CoverageReporter.kt as strings those already included html tags overlapped / conflicted with the report html templates. - Added limitations section to the 'Oppia-Android-Code-Coverage' wiki page - Filed all possible coverage gaps that exist right now based on the available pass cases. ## Essential Checklist - [x] The PR title and explanation each start with "Fix #bugnum: " (If this PR fixes part of an issue, prefix the title with "Fix part of #bugnum: ...".) - [x] Any changes to [scripts/assets](https://github.com/oppia/oppia-android/tree/develop/scripts/assets) files have their rationale included in the PR explanation. - [x] The PR follows the [style guide](https://github.com/oppia/oppia-android/wiki/Coding-style-guide). - [x] The PR does not contain any unnecessary code changes from Android Studio ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#undo-unnecessary-changes)). - [x] The PR is made from a branch that's **not** called "develop" and is up-to-date with "develop". - [x] The PR is **assigned** to the appropriate reviewers ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#clarification-regarding-assignees-and-reviewers-section)). ## For UI-specific PRs only If your PR includes UI-related changes, then: - Add screenshots for portrait/landscape for both a tablet & phone of the before & after UI changes - For the screenshots above, include both English and pseudo-localized (RTL) screenshots (see [RTL guide](https://github.com/oppia/oppia-android/wiki/RTL-Guidelines)) - Add a video showing the full UX flow with a screen reader enabled (see [accessibility guide](https://github.com/oppia/oppia-android/wiki/Accessibility-A11y-Guide)) - For PRs introducing new UI elements or color changes, both light and dark mode screenshots must be included - Add a screenshot demonstrating that you ran affected Espresso tests locally & that they're passing --- .github/workflows/code_coverage.yml | 10 ++- .github/workflows/comment_coverage_report.yml | 4 +- .../scripts/coverage/reporter/BUILD.bazel | 1 + .../coverage/reporter/CoverageReporter.kt | 3 +- .../scripts/coverage/RunCoverageTest.kt | 86 ++++++++++++++++--- .../scripts/coverage/reporter/BUILD.bazel | 1 + .../coverage/reporter/CoverageReporterTest.kt | 8 +- wiki/Oppia-Android-Code-Coverage.md | 28 +++++- ...ing-tests-with-good-behavioral-coverage.md | 2 +- wiki/_Sidebar.md | 2 +- 10 files changed, 121 insertions(+), 24 deletions(-) diff --git a/.github/workflows/code_coverage.yml b/.github/workflows/code_coverage.yml index 992a7e86cb1..276f2841312 100644 --- a/.github/workflows/code_coverage.yml +++ b/.github/workflows/code_coverage.yml @@ -258,10 +258,10 @@ jobs: evaluate_code_coverage_reports: name: Evaluate Code Coverage Reports runs-on: ubuntu-20.04 - needs: code_coverage_run + needs: [ check_unit_tests_completed, code_coverage_run ] # The expression if: ${{ !cancelled() }} runs a job or step regardless of its success or failure while responding to cancellations, # serving as a cancellation-compliant alternative to if: ${{ always() }} in concurrent workflows. - if: ${{ !cancelled() }} + if: ${{ !cancelled() && needs.check_unit_tests_completed.result == 'success'}} env: CACHE_DIRECTORY: ~/.bazel_cache steps: @@ -311,12 +311,16 @@ jobs: # Reference: https://github.community/t/127354/7. check_coverage_results: name: Check Code Coverage Results - needs: [ compute_changed_files, code_coverage_run, evaluate_code_coverage_reports ] + needs: [ check_unit_tests_completed, compute_changed_files, code_coverage_run, evaluate_code_coverage_reports ] # The expression if: ${{ !cancelled() }} runs a job or step regardless of its success or failure while responding to cancellations, # serving as a cancellation-compliant alternative to if: ${{ always() }} in concurrent workflows. if: ${{ !cancelled() }} runs-on: ubuntu-20.04 steps: + - name: Check unit tests passed + if: ${{ needs.check_unit_tests_completed.result != 'success' }} + run: exit 1 + - name: Check coverages passed if: ${{ needs.compute_changed_files.outputs.can_skip_files != 'true' && needs.code_coverage_run.result != 'success' }} run: exit 1 diff --git a/.github/workflows/comment_coverage_report.yml b/.github/workflows/comment_coverage_report.yml index 9ecb4e2e95c..16c1ae0da63 100644 --- a/.github/workflows/comment_coverage_report.yml +++ b/.github/workflows/comment_coverage_report.yml @@ -3,11 +3,11 @@ name: Comment Coverage Report # Controls when the action will run. Triggers the workflow on pull request events -# (assigned, opened, synchronize, reopened) +# (opened, synchronize, reopened) on: pull_request_target: - types: [assigned, opened, synchronize, reopened] + types: [opened, synchronize, reopened] concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} diff --git a/scripts/src/java/org/oppia/android/scripts/coverage/reporter/BUILD.bazel b/scripts/src/java/org/oppia/android/scripts/coverage/reporter/BUILD.bazel index 85b59087cc2..217bc961aa1 100644 --- a/scripts/src/java/org/oppia/android/scripts/coverage/reporter/BUILD.bazel +++ b/scripts/src/java/org/oppia/android/scripts/coverage/reporter/BUILD.bazel @@ -15,5 +15,6 @@ kt_jvm_library( "//scripts/src/java/org/oppia/android/scripts/common:bazel_client", "//scripts/src/java/org/oppia/android/scripts/proto:coverage_java_proto", "//scripts/src/java/org/oppia/android/scripts/proto:script_exemptions_java_proto", + "//third_party:com_google_guava_guava", ], ) diff --git a/scripts/src/java/org/oppia/android/scripts/coverage/reporter/CoverageReporter.kt b/scripts/src/java/org/oppia/android/scripts/coverage/reporter/CoverageReporter.kt index 31763392945..f447a58c256 100644 --- a/scripts/src/java/org/oppia/android/scripts/coverage/reporter/CoverageReporter.kt +++ b/scripts/src/java/org/oppia/android/scripts/coverage/reporter/CoverageReporter.kt @@ -1,5 +1,6 @@ package org.oppia.android.scripts.coverage.reporter +import com.google.common.html.HtmlEscapers import org.oppia.android.scripts.proto.Coverage import org.oppia.android.scripts.proto.CoverageReport import org.oppia.android.scripts.proto.CoverageReportContainer @@ -277,7 +278,7 @@ class CoverageReporter( """ ${lineNumber.toString().padStart(4, ' ')} - $line + ${HtmlEscapers.htmlEscaper().escape(line)} """.trimIndent() ) diff --git a/scripts/src/javatests/org/oppia/android/scripts/coverage/RunCoverageTest.kt b/scripts/src/javatests/org/oppia/android/scripts/coverage/RunCoverageTest.kt index 7ef0183f00d..3d28a816c7d 100644 --- a/scripts/src/javatests/org/oppia/android/scripts/coverage/RunCoverageTest.kt +++ b/scripts/src/javatests/org/oppia/android/scripts/coverage/RunCoverageTest.kt @@ -317,7 +317,13 @@ class RunCoverageTest { val expectedResult = getExpectedHtmlText(kotlinFilePath) - assertThat(readHtmlReport(kotlinFilePath)).isEqualTo(expectedResult) + val unescapedHtmlReport = readHtmlReport(kotlinFilePath) + .replace(""", "\"") + .replace("&", "&") + .replace("<", "<") + .replace(">", ">") + + assertThat(unescapedHtmlReport).isEqualTo(expectedResult) } @Test @@ -342,7 +348,13 @@ class RunCoverageTest { val expectedResult = getExpectedHtmlText(sourceFilePath) - assertThat(readHtmlReport(sourceFilePath)).isEqualTo(expectedResult) + val unescapedHtmlReport = readHtmlReport(sourceFilePath) + .replace(""", "\"") + .replace("&", "&") + .replace("<", "<") + .replace(">", ">") + + assertThat(unescapedHtmlReport).isEqualTo(expectedResult) } @Test @@ -541,7 +553,13 @@ class RunCoverageTest { val expectedResult = getExpectedHtmlText(filePath) - assertThat(readHtmlReport(filePath)).isEqualTo(expectedResult) + val unescapedHtmlReport = readHtmlReport(filePath) + .replace(""", "\"") + .replace("&", "&") + .replace("<", "<") + .replace(">", ">") + + assertThat(unescapedHtmlReport).isEqualTo(expectedResult) } @Test @@ -1880,10 +1898,20 @@ class RunCoverageTest { ).execute() val expectedResult1 = getExpectedHtmlText(filePathList.get(0)) - assertThat(readHtmlReport(filePathList.get(0))).isEqualTo(expectedResult1) + val unescapedHtmlReport1 = readHtmlReport(filePathList.get(0)) + .replace(""", "\"") + .replace("&", "&") + .replace("<", "<") + .replace(">", ">") + assertThat(unescapedHtmlReport1).isEqualTo(expectedResult1) val expectedResult2 = getExpectedHtmlText(filePathList.get(1)) - assertThat(readHtmlReport(filePathList.get(1))).isEqualTo(expectedResult2) + val unescapedHtmlReport2 = readHtmlReport(filePathList.get(1)) + .replace(""", "\"") + .replace("&", "&") + .replace("<", "<") + .replace(">", ">") + assertThat(unescapedHtmlReport2).isEqualTo(expectedResult2) } @Test @@ -1910,7 +1938,13 @@ class RunCoverageTest { val expectedResult = getExpectedHtmlText(filePathList.get(0)) - assertThat(readHtmlReport(filePathList.get(0))).isEqualTo(expectedResult) + val unescapedHtmlReport = readHtmlReport(filePathList.get(0)) + .replace(""", "\"") + .replace("&", "&") + .replace("<", "<") + .replace(">", ">") + + assertThat(unescapedHtmlReport).isEqualTo(expectedResult) } @Test @@ -1937,7 +1971,13 @@ class RunCoverageTest { val expectedResult = getExpectedHtmlText(filePathList.get(0)) - assertThat(readHtmlReport(filePathList.get(0))).isEqualTo(expectedResult) + val unescapedHtmlReport = readHtmlReport(filePathList.get(0)) + .replace(""", "\"") + .replace("&", "&") + .replace("<", "<") + .replace(">", ">") + + assertThat(unescapedHtmlReport).isEqualTo(expectedResult) } @Test @@ -1964,7 +2004,13 @@ class RunCoverageTest { val expectedResult = getExpectedHtmlText(filePathList.get(0)) - assertThat(readHtmlReport(filePathList.get(0))).isEqualTo(expectedResult) + val unescapedHtmlReport = readHtmlReport(filePathList.get(0)) + .replace(""", "\"") + .replace("&", "&") + .replace("<", "<") + .replace(">", ">") + + assertThat(unescapedHtmlReport).isEqualTo(expectedResult) } @Test @@ -2009,7 +2055,13 @@ class RunCoverageTest { val expectedResult = getExpectedHtmlText(filePathList.get(0)) - assertThat(readHtmlReport(filePathList.get(0))).isEqualTo(expectedResult) + val unescapedHtmlReport = readHtmlReport(filePathList.get(0)) + .replace(""", "\"") + .replace("&", "&") + .replace("<", "<") + .replace(">", ">") + + assertThat(unescapedHtmlReport).isEqualTo(expectedResult) } @Test @@ -2036,7 +2088,13 @@ class RunCoverageTest { val expectedResult = getExpectedHtmlText(filePathList.get(0)) - assertThat(readHtmlReport(filePathList.get(0))).isEqualTo(expectedResult) + val unescapedHtmlReport = readHtmlReport(filePathList.get(0)) + .replace(""", "\"") + .replace("&", "&") + .replace("<", "<") + .replace(">", ">") + + assertThat(unescapedHtmlReport).isEqualTo(expectedResult) } @Test @@ -2262,7 +2320,13 @@ class RunCoverageTest { """.trimIndent() - assertThat(readHtmlReport(filePathList.get(0))).isEqualTo(expectedResult) + val unescapedHtmlReport = readHtmlReport(filePathList.get(0)) + .replace(""", "\"") + .replace("&", "&") + .replace("<", "<") + .replace(">", ">") + + assertThat(unescapedHtmlReport).isEqualTo(expectedResult) } @Test diff --git a/scripts/src/javatests/org/oppia/android/scripts/coverage/reporter/BUILD.bazel b/scripts/src/javatests/org/oppia/android/scripts/coverage/reporter/BUILD.bazel index 6571e76119e..0747162663f 100644 --- a/scripts/src/javatests/org/oppia/android/scripts/coverage/reporter/BUILD.bazel +++ b/scripts/src/javatests/org/oppia/android/scripts/coverage/reporter/BUILD.bazel @@ -13,6 +13,7 @@ kt_jvm_test( "//scripts/src/java/org/oppia/android/scripts/coverage/reporter:coverage_reporter_lib", "//scripts/src/java/org/oppia/android/scripts/proto:script_exemptions_java_proto", "//testing:assertion_helpers", + "//third_party:com_google_guava_guava", "//third_party:com_google_truth_truth", "//third_party:org_jetbrains_kotlin_kotlin-test-junit", ], diff --git a/scripts/src/javatests/org/oppia/android/scripts/coverage/reporter/CoverageReporterTest.kt b/scripts/src/javatests/org/oppia/android/scripts/coverage/reporter/CoverageReporterTest.kt index b0af56a1115..ae06e6a693e 100644 --- a/scripts/src/javatests/org/oppia/android/scripts/coverage/reporter/CoverageReporterTest.kt +++ b/scripts/src/javatests/org/oppia/android/scripts/coverage/reporter/CoverageReporterTest.kt @@ -697,7 +697,13 @@ class CoverageReporterTest { """.trimIndent() - assertThat(outputReportText).isEqualTo(expectedHtml) + val unescapedOutputReportText = outputReportText + .replace(""", "\"") + .replace("&", "&") + .replace("<", "<") + .replace(">", ">") + + assertThat(unescapedOutputReportText).isEqualTo(expectedHtml) } @Test diff --git a/wiki/Oppia-Android-Code-Coverage.md b/wiki/Oppia-Android-Code-Coverage.md index e5ed0479619..499b3591642 100644 --- a/wiki/Oppia-Android-Code-Coverage.md +++ b/wiki/Oppia-Android-Code-Coverage.md @@ -139,7 +139,7 @@ Coverage Analysis: **FAIL** :x:
| File | Coverage | Lines Hit | Status | Min Required | |------|:--------:|----------:|:------:|:------------:| -|
MathTokenizer.ktutility/src/main/java/org/oppia/android/util/math/MathTokenizer.kt
| 94.26% | 197 / 209 | :white_check_mark: | 70% | +|
Pass.ktutility/src/main/java/org/oppia/android/util/math/Pass.kt
| 94.26% | 197 / 209 | :white_check_mark: | 70% | ### Exempted coverage @@ -282,7 +282,7 @@ Certain files are exempt from coverage checks. These exemptions include: 1. **Test File Exemptions:** Files that are exempted from having corresponding test files are also exempted from coverage checks. Since no test files are available for these sources, coverage analysis cannot be performed, and these files are therefore skipped. -2. **Source File Incompatibility Exemptions:** Some files are currently incompatible with Bazel coverage execution ([see tracking issue #5481](https://github.com/oppia/oppia-android/issues/5481)) and are temporarily excluded from coverage checks. +2. **Source File Incompatibility Exemptions:** Some files are currently incompatible with Bazel coverage execution (see tracking issue [#5481](https://github.com/oppia/oppia-android/issues/5481)) and are temporarily excluded from coverage checks. You can find the complete list of exemptions in this file: [test_file_exemptions.textproto](https://github.com/oppia/oppia-android/blob/develop/scripts/assets/test_file_exemptions.textproto) @@ -311,7 +311,19 @@ bazel run //scripts:run_coverage -- : Your root directory. - : Files you want to generate coverage reports for. -For example, to analyze coverage for the file MathTokenizer.kt, use the relative path: +To get the relative path of a file: + +1. Navigate to the Project view on the left-hand side in Android Studio. +2. Locate the file to analyze Code Coverage for. +3. Right click the file and select Copy Path. To get the path relative to the root. + +Alternatively, the coverage report itself provides the relative paths. You can reveal this information by clicking on the drop-down that precedes the file name in the report. + +| File | Coverage | Lines Hit | Status | Min Required | +|------|:--------:|----------:|:------:|:------------:| +|
MathTokenizer.ktutility/src/main/java/org/oppia/android/util/math/MathTokenizer.kt
| 94.26% | 197 / 209 | :white_check_mark: | 70% | + +To analyze coverage for the file MathTokenizer.kt, use the relative path: ```sh bazel run //scripts:run_coverage -- $(pwd) utility/src/main/java/org/oppia/android/util/math/MathTokenizer.kt @@ -456,6 +468,14 @@ It’s essential to ensure that each source file is directly tested by its own c ## Limitations of the code coverage tool -1. **Incompatibility with Code Coverage Analysis:** Certain test targets in the Oppia-Android codebase fail to execute and collect coverage using the Bazel coverage command. The underlying issues are still being investigated ([see tracking issue #5481](https://github.com/oppia/oppia-android/issues/5481)), and these files are currently exempt from coverage checks. However, it's expected that all new test files should work without needing this exemption. +1. **Incompatibility with Code Coverage Analysis:** Certain test targets in the Oppia-Android codebase fail to execute and collect coverage using the Bazel coverage command. The underlying issues are still being investigated (see tracking issue [#5481](https://github.com/oppia/oppia-android/issues/5481)), and these files are currently exempt from coverage checks. However, it's expected that all new test files should work without needing this exemption. 2. **Function and Branch Coverage:** The Oppia-Android code coverage tool currently provides only line coverage data. It does not include information on function or branch coverage. + +3. **Kotlin inline functions:** With JaCoCo coverage gaps, Kotlin inline functions may be inaccurately reported as uncovered in coverage reports. (See tracking issue [#5501](https://github.com/oppia/oppia-android/issues/5501)) + +4. **Line and Partial Coverages:** The current line coverage analysis in Oppia Android is limited and may not accurately reflect the execution of complex or multi-branch code within a single line, reporting lines as fully covered even if only part of the logic within those lines is executed, leading to potentially misleading coverage data. (See tracking issue [#5503](https://github.com/oppia/oppia-android/issues/5503)) + +5. **Flow Interrupting Statements:** The coverage reports may inaccurately reflect the coverage of flow-interrupting statements (e.g., exitProcess(1), assertion failures, break). These lines may be marked as uncovered even when executed, due to JaCoCo's limitations in tracking code execution after abrupt control flow interruptions. (See tracking issue [#5506](https://github.com/oppia/oppia-android/issues/5506)) + +6. **Uncovered Last Curly Brace in Kotlin:** The last curly brace of some Kotlin functions may be reported as uncovered, even when the function is fully executed during tests. This issue requires further investigation to determine if it's due to incomplete test execution or dead code generated by the Kotlin compiler. (See tracking issue [#5523](https://github.com/oppia/oppia-android/issues/5523)) \ No newline at end of file diff --git a/wiki/Writing-tests-with-good-behavioral-coverage.md b/wiki/Writing-tests-with-good-behavioral-coverage.md index 7a0aa5caebc..db50cfa83a6 100644 --- a/wiki/Writing-tests-with-good-behavioral-coverage.md +++ b/wiki/Writing-tests-with-good-behavioral-coverage.md @@ -1017,7 +1017,7 @@ Note: For more information on how to utilize the code coverage analysis tool, pl ## Testing a Single Outcome in Multiple Ways -When testing a single outcome like a successful withdrawal, you can use multiple approaches to verify the if the balance is updated correctly. Here are different ways to ensure the single outcome of withdrawal was processed correctly, each following a distinct approach. +When testing a single outcome, such as a successful withdrawal, you can use multiple approaches to verify if the balance is updated correctly. Here are different ways to ensure the single outcome of withdrawal was processed correctly, each following a distinct approach. **a. To verify correctness of output:** diff --git a/wiki/_Sidebar.md b/wiki/_Sidebar.md index ea5a254f2c6..c1f9834bc49 100644 --- a/wiki/_Sidebar.md +++ b/wiki/_Sidebar.md @@ -25,7 +25,7 @@ * Testing * [Oppia Android Testing](https://github.com/oppia/oppia-android/wiki/Oppia-Android-Testing) * [End to End Testing Guide](https://github.com/oppia/oppia-android/wiki/End-to-End-Testing-Guide) - * [Oppia Android Code Coverage](https://github.com/oppia/oppia-android-workflow/wiki/Oppia-Android-Code-Coverage) + * [Oppia Android Code Coverage](https://github.com/oppia/oppia-android/wiki/Oppia-Android-Code-Coverage) * [Writing Tests with Good Behavioral Coverage](https://github.com/oppia/oppia-android/wiki/Writing-Tests-With-Good-Behavioral-Coverage) * [Developing Skills](https://github.com/oppia/oppia-android/wiki/Developing-skills) * [Frequent Errors and Solutions](https://github.com/oppia/oppia-android/wiki/Frequent-Errors-and-Solutions) From a1fbe0c98b512eec45bb94804b50095606a5e64b Mon Sep 17 00:00:00 2001 From: Vishwajith Shettigar <76042077+Vishwajith-Shettigar@users.noreply.github.com> Date: Fri, 30 Aug 2024 18:16:33 +0530 Subject: [PATCH 02/10] Fix #5395: Fixed concept card not closing when opened from hint (#5509) ## Explanation Fixes #5395. `(fragment.requireActivity() as? ConceptCardListener)?.dismissConceptCard()` in `ConceptCardFragmentPresenter` calls the `dismissConceptCard()` method in` ExplorationActivity` since `ExplorationActivity `implements `ConceptCardListener` and overrides the `dismissConceptCard()` method. This makes call `dismissAll()` in `ConceptCardFragment` through `StateFragment` there it passes its `childFragmentManager`. Even though it managed to call the `dismissAll()` method in `ConceptCardFragment`, but the wrong fragment manager was passed. We passed `StateFragment's childFragmentManager`, but `ConceptCardFragment `is nested within `HintAndSolutionFragment`, so it should be passed `HintAndSolutionFragment's childFragmentManager`. [concept_card.webm](https://github.com/user-attachments/assets/d1959296-79bf-4e4f-b2d9-04acff089c37) ## Essential Checklist - [x] The PR title and explanation each start with "Fix #bugnum: " (If this PR fixes part of an issue, prefix the title with "Fix part of #bugnum: ...".) - [x] Any changes to [scripts/assets](https://github.com/oppia/oppia-android/tree/develop/scripts/assets) files have their rationale included in the PR explanation. - [x] The PR follows the [style guide](https://github.com/oppia/oppia-android/wiki/Coding-style-guide). - [x] The PR does not contain any unnecessary code changes from Android Studio ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#undo-unnecessary-changes)). - [x] The PR is made from a branch that's **not** called "develop" and is up-to-date with "develop". - [x] The PR is **assigned** to the appropriate reviewers ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#clarification-regarding-assignees-and-reviewers-section)). ## For UI-specific PRs only If your PR includes UI-related changes, then: - Add screenshots for portrait/landscape for both a tablet & phone of the before & after UI changes - For the screenshots above, include both English and pseudo-localized (RTL) screenshots (see [RTL guide](https://github.com/oppia/oppia-android/wiki/RTL-Guidelines)) - Add a video showing the full UX flow with a screen reader enabled (see [accessibility guide](https://github.com/oppia/oppia-android/wiki/Accessibility-A11y-Guide)) - For PRs introducing new UI elements or color changes, both light and dark mode screenshots must be included - Add a screenshot demonstrating that you ran affected Espresso tests locally & that they're passing --- .../HintsAndSolutionDialogFragment.kt | 9 ++ ...HintsAndSolutionDialogFragmentPresenter.kt | 5 ++ .../player/exploration/ExplorationActivity.kt | 4 +- .../ExplorationActivityPresenter.kt | 4 - .../player/exploration/ExplorationFragment.kt | 2 - .../ExplorationFragmentPresenter.kt | 2 - .../android/app/player/state/StateFragment.kt | 2 - .../player/state/StateFragmentPresenter.kt | 5 -- .../ConceptCardFragmentTestActivity.kt | 2 +- .../QuestionPlayerActivityPresenter.kt | 4 +- .../questionplayer/QuestionPlayerFragment.kt | 2 - .../QuestionPlayerFragmentPresenter.kt | 5 -- .../exploration/ExplorationActivityTest.kt | 89 +++++++++++++++++++ 13 files changed, 110 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/org/oppia/android/app/hintsandsolution/HintsAndSolutionDialogFragment.kt b/app/src/main/java/org/oppia/android/app/hintsandsolution/HintsAndSolutionDialogFragment.kt index c36cafe9ecc..e81aca55b3f 100644 --- a/app/src/main/java/org/oppia/android/app/hintsandsolution/HintsAndSolutionDialogFragment.kt +++ b/app/src/main/java/org/oppia/android/app/hintsandsolution/HintsAndSolutionDialogFragment.kt @@ -14,6 +14,7 @@ import org.oppia.android.app.model.HintsAndSolutionDialogFragmentStateBundle import org.oppia.android.app.model.ProfileId import org.oppia.android.app.model.State import org.oppia.android.app.model.WrittenTranslationContext +import org.oppia.android.app.topic.conceptcard.ConceptCardFragment import org.oppia.android.util.extensions.getProto import org.oppia.android.util.extensions.putProto import javax.inject.Inject @@ -192,4 +193,12 @@ class HintsAndSolutionDialogFragment : isSolutionRevealed ) } + + /** + * Delegates the removal of all [ConceptCardFragment] instances + * to the [hintsAndSolutionDialogFragmentPresenter]. + */ + fun dismissConceptCard() { + hintsAndSolutionDialogFragmentPresenter.dismissConceptCard() + } } diff --git a/app/src/main/java/org/oppia/android/app/hintsandsolution/HintsAndSolutionDialogFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/hintsandsolution/HintsAndSolutionDialogFragmentPresenter.kt index 16f9dec7b19..824c777ca78 100644 --- a/app/src/main/java/org/oppia/android/app/hintsandsolution/HintsAndSolutionDialogFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/hintsandsolution/HintsAndSolutionDialogFragmentPresenter.kt @@ -331,4 +331,9 @@ class HintsAndSolutionDialogFragmentPresenter @Inject constructor( override fun onConceptCardLinkClicked(view: View, skillId: String) { ConceptCardFragment.bringToFrontOrCreateIfNew(skillId, profileId, fragment.childFragmentManager) } + + /** Removes all [ConceptCardFragment] in the given FragmentManager. */ + fun dismissConceptCard() { + ConceptCardFragment.dismissAll(fragment.childFragmentManager) + } } diff --git a/app/src/main/java/org/oppia/android/app/player/exploration/ExplorationActivity.kt b/app/src/main/java/org/oppia/android/app/player/exploration/ExplorationActivity.kt index b95785bf2d0..6912d639ed9 100755 --- a/app/src/main/java/org/oppia/android/app/player/exploration/ExplorationActivity.kt +++ b/app/src/main/java/org/oppia/android/app/player/exploration/ExplorationActivity.kt @@ -190,7 +190,9 @@ class ExplorationActivity : this.writtenTranslationContext = writtenTranslationContext } - override fun dismissConceptCard() = explorationActivityPresenter.dismissConceptCard() + override fun dismissConceptCard() { + getHintsAndSolution()?.dismissConceptCard() + } override fun requestVoiceOverIconSpotlight(numberOfLogins: Int) { explorationActivityPresenter.requestVoiceOverIconSpotlight(numberOfLogins) diff --git a/app/src/main/java/org/oppia/android/app/player/exploration/ExplorationActivityPresenter.kt b/app/src/main/java/org/oppia/android/app/player/exploration/ExplorationActivityPresenter.kt index 9d1c50ec2ea..1c493c19bfa 100644 --- a/app/src/main/java/org/oppia/android/app/player/exploration/ExplorationActivityPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/player/exploration/ExplorationActivityPresenter.kt @@ -334,10 +334,6 @@ class ExplorationActivityPresenter @Inject constructor( showDialogFragmentBasedOnCurrentCheckpointState() } - fun dismissConceptCard() { - getExplorationFragment()?.dismissConceptCard() - } - private fun updateToolbarTitle(explorationId: String) { subscribeToExploration( explorationDataController.getExplorationById(profileId, explorationId).toLiveData() diff --git a/app/src/main/java/org/oppia/android/app/player/exploration/ExplorationFragment.kt b/app/src/main/java/org/oppia/android/app/player/exploration/ExplorationFragment.kt index fdffb73b32d..6d4cb2a6330 100755 --- a/app/src/main/java/org/oppia/android/app/player/exploration/ExplorationFragment.kt +++ b/app/src/main/java/org/oppia/android/app/player/exploration/ExplorationFragment.kt @@ -84,7 +84,5 @@ class ExplorationFragment : InjectableFragment() { explorationFragmentPresenter.viewSolution() } - fun dismissConceptCard() = explorationFragmentPresenter.dismissConceptCard() - fun getExplorationCheckpointState() = explorationFragmentPresenter.getExplorationCheckpointState() } diff --git a/app/src/main/java/org/oppia/android/app/player/exploration/ExplorationFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/player/exploration/ExplorationFragmentPresenter.kt index 151f2456f53..207f5bf9e7f 100755 --- a/app/src/main/java/org/oppia/android/app/player/exploration/ExplorationFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/player/exploration/ExplorationFragmentPresenter.kt @@ -141,8 +141,6 @@ class ExplorationFragmentPresenter @Inject constructor( getStateFragment()?.viewSolution() } - fun dismissConceptCard() = getStateFragment()?.dismissConceptCard() - fun getExplorationCheckpointState() = getStateFragment()?.getExplorationCheckpointState() private fun getStateFragment(): StateFragment? { diff --git a/app/src/main/java/org/oppia/android/app/player/state/StateFragment.kt b/app/src/main/java/org/oppia/android/app/player/state/StateFragment.kt index 50f05d60082..df5e8a07dde 100755 --- a/app/src/main/java/org/oppia/android/app/player/state/StateFragment.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/StateFragment.kt @@ -162,8 +162,6 @@ class StateFragment : stateFragmentPresenter.viewSolution() } - fun dismissConceptCard() = stateFragmentPresenter.dismissConceptCard() - fun getExplorationCheckpointState() = stateFragmentPresenter.getExplorationCheckpointState() override fun onSaveInstanceState(outState: Bundle) { diff --git a/app/src/main/java/org/oppia/android/app/player/state/StateFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/player/state/StateFragmentPresenter.kt index 672595d81ef..0147fb7e82c 100755 --- a/app/src/main/java/org/oppia/android/app/player/state/StateFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/StateFragmentPresenter.kt @@ -38,7 +38,6 @@ import org.oppia.android.app.player.state.listener.RouteToHintsAndSolutionListen import org.oppia.android.app.player.stopplaying.StopStatePlayingSessionWithSavedProgressListener import org.oppia.android.app.survey.SurveyWelcomeDialogFragment import org.oppia.android.app.survey.TAG_SURVEY_WELCOME_DIALOG -import org.oppia.android.app.topic.conceptcard.ConceptCardFragment import org.oppia.android.app.translation.AppLanguageResourceHandler import org.oppia.android.app.utility.SplitScreenManager import org.oppia.android.app.utility.lifecycle.LifecycleSafeTimerFactory @@ -428,10 +427,6 @@ class StateFragmentPresenter @Inject constructor( subscribeToAnswerOutcome(explorationProgressController.submitAnswer(answer).toLiveData()) } - fun dismissConceptCard() { - ConceptCardFragment.dismissAll(fragment.childFragmentManager) - } - private fun moveToNextState() { stateViewModel.setCanSubmitAnswer(canSubmitAnswer = false) explorationProgressController.moveToNextState().toLiveData().observe( diff --git a/app/src/main/java/org/oppia/android/app/testing/ConceptCardFragmentTestActivity.kt b/app/src/main/java/org/oppia/android/app/testing/ConceptCardFragmentTestActivity.kt index b87c9c8a431..c428093696f 100644 --- a/app/src/main/java/org/oppia/android/app/testing/ConceptCardFragmentTestActivity.kt +++ b/app/src/main/java/org/oppia/android/app/testing/ConceptCardFragmentTestActivity.kt @@ -29,7 +29,7 @@ class ConceptCardFragmentTestActivity : } override fun dismissConceptCard() { - getConceptCardFragment()?.dismiss() + ConceptCardFragment.dismissAll(supportFragmentManager) } private fun getConceptCardFragment(): ConceptCardFragment? { diff --git a/app/src/main/java/org/oppia/android/app/topic/questionplayer/QuestionPlayerActivityPresenter.kt b/app/src/main/java/org/oppia/android/app/topic/questionplayer/QuestionPlayerActivityPresenter.kt index 5c1f7484766..523ebf2cc4d 100644 --- a/app/src/main/java/org/oppia/android/app/topic/questionplayer/QuestionPlayerActivityPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/topic/questionplayer/QuestionPlayerActivityPresenter.kt @@ -274,7 +274,9 @@ class QuestionPlayerActivityPresenter @Inject constructor( getHintsAndSolutionDialogFragment()?.dismiss() } - fun dismissConceptCard() = getQuestionPlayerFragment()?.dismissConceptCard() + fun dismissConceptCard() { + getHintsAndSolutionDialogFragment()?.dismissConceptCard() + } private fun getHintsAndSolutionDialogFragment(): HintsAndSolutionDialogFragment? { return activity.supportFragmentManager.findFragmentByTag( diff --git a/app/src/main/java/org/oppia/android/app/topic/questionplayer/QuestionPlayerFragment.kt b/app/src/main/java/org/oppia/android/app/topic/questionplayer/QuestionPlayerFragment.kt index b5e67ec1318..c825f53e5cf 100644 --- a/app/src/main/java/org/oppia/android/app/topic/questionplayer/QuestionPlayerFragment.kt +++ b/app/src/main/java/org/oppia/android/app/topic/questionplayer/QuestionPlayerFragment.kt @@ -106,8 +106,6 @@ class QuestionPlayerFragment : questionPlayerFragmentPresenter.revealSolution() } - fun dismissConceptCard() = questionPlayerFragmentPresenter.dismissConceptCard() - companion object { /** Arguments key for [QuestionPlayerFragment]. */ diff --git a/app/src/main/java/org/oppia/android/app/topic/questionplayer/QuestionPlayerFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/topic/questionplayer/QuestionPlayerFragmentPresenter.kt index 7b4861580ab..6319e930125 100644 --- a/app/src/main/java/org/oppia/android/app/topic/questionplayer/QuestionPlayerFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/topic/questionplayer/QuestionPlayerFragmentPresenter.kt @@ -27,7 +27,6 @@ import org.oppia.android.app.player.state.StatePlayerRecyclerViewAssembler import org.oppia.android.app.player.state.listener.RouteToHintsAndSolutionListener import org.oppia.android.app.player.stopplaying.RestartPlayingSessionListener import org.oppia.android.app.player.stopplaying.StopStatePlayingSessionListener -import org.oppia.android.app.topic.conceptcard.ConceptCardFragment import org.oppia.android.app.utility.FontScaleConfigurationUtil import org.oppia.android.app.utility.SplitScreenManager import org.oppia.android.databinding.QuestionPlayerFragmentBinding @@ -124,10 +123,6 @@ class QuestionPlayerFragmentPresenter @Inject constructor( subscribeToHintSolution(questionAssessmentProgressController.submitSolutionIsRevealed()) } - fun dismissConceptCard() { - ConceptCardFragment.dismissAll(fragment.childFragmentManager) - } - private fun retrieveArguments(): QuestionPlayerFragmentArguments { return fragment.requireArguments().getProto( QuestionPlayerFragment.ARGUMENTS_KEY, QuestionPlayerFragmentArguments.getDefaultInstance() 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 ec53953d954..32d29315737 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 @@ -3,7 +3,9 @@ package org.oppia.android.app.player.exploration import android.app.Application import android.content.Context import android.content.Intent +import android.text.Spannable import android.text.TextUtils +import android.text.style.ClickableSpan import android.view.View import android.widget.TextView import androidx.appcompat.app.AppCompatActivity @@ -48,6 +50,7 @@ import org.hamcrest.CoreMatchers.containsString import org.hamcrest.Description import org.hamcrest.Matcher import org.hamcrest.Matchers.not +import org.hamcrest.TypeSafeMatcher import org.junit.After import org.junit.Before import org.junit.Ignore @@ -2559,6 +2562,92 @@ class ExplorationActivityTest { } } + @Test + @RunOn(TestPlatform.ROBOLECTRIC) // TODO(#3858): Enable for Espresso. + fun testExpActivity_openConceptCard_selectNavigationUp_conceptCardCloses() { + markAllSpotlightsSeen() + launch( + createExplorationActivityIntent( + internalProfileId, + TEST_CLASSROOM_ID_0, + TEST_TOPIC_ID_0, + TEST_STORY_ID_0, + TEST_EXPLORATION_ID_2, + shouldSavePartialProgress = false + ) + ).use { + explorationDataController.startPlayingNewExploration( + internalProfileId, + TEST_CLASSROOM_ID_0, + TEST_TOPIC_ID_0, + TEST_STORY_ID_0, + TEST_EXPLORATION_ID_2 + ) + testCoroutineDispatchers.runCurrent() + clickContinueButton() + // Submit two incorrect answers. + submitFractionAnswer(answerText = "1/3") + submitFractionAnswer(answerText = "1/4") + + // Reveal the hint. + openHintsAndSolutionsDialog() + pressRevealHintButton(hintPosition = 0) + + onView(withId(R.id.hints_and_solution_summary)) + .inRoot(isDialog()) + .perform(openClickableSpan("test_skill_id_1 concept card")) + + testCoroutineDispatchers.runCurrent() + + onView(withText("Concept Card")).inRoot(isDialog()).check(matches(isDisplayed())) + onView(withText("Another important skill")).inRoot(isDialog()).check(matches(isDisplayed())) + onView(withId(R.id.concept_card_toolbar)).check(matches(isDisplayed())) + + onView(withContentDescription(R.string.navigate_up)).perform(click()) + + testCoroutineDispatchers.runCurrent() + onView(withId(R.id.concept_card_toolbar)).check(doesNotExist()) + } + explorationDataController.stopPlayingExploration(isCompletion = false) + } + + private fun openClickableSpan(text: String): ViewAction { + return object : ViewAction { + override fun getDescription(): String = "openClickableSpan" + + override fun getConstraints(): Matcher = hasClickableSpanWithText(text) + + override fun perform(uiController: UiController?, view: View?) { + // The view shouldn't be null if the constraints are being met. + (view as? TextView)?.getClickableSpans()?.findMatchingTextOrNull(text)?.onClick(view) + } + } + } + + private fun List>.findMatchingTextOrNull(text: String) = + find { text in it.first }?.second + + private fun TextView.getClickableSpans(): List> { + val viewText = text + return (viewText as Spannable).getSpans( + /* start= */ 0, /* end= */ text.length, ClickableSpan::class.java + ).map { + viewText.subSequence(viewText.getSpanStart(it), viewText.getSpanEnd(it)).toString() to it + } + } + + private fun hasClickableSpanWithText(text: String): Matcher { + return object : TypeSafeMatcher(TextView::class.java) { + override fun describeTo(description: Description?) { + description?.appendText("has ClickableSpan with text")?.appendValue(text) + } + + override fun matchesSafely(item: View?): Boolean { + return (item as? TextView)?.getClickableSpans()?.findMatchingTextOrNull(text) != null + } + } + } + private fun markSpotlightSeen(feature: Spotlight.FeatureCase) { val profileId = ProfileId.newBuilder().setInternalId(internalProfileId).build() spotlightStateController.markSpotlightViewed(profileId, feature) From 16082aac839fc3e0a4c245bbc16e4b5f1a1dfe57 Mon Sep 17 00:00:00 2001 From: Subhajit Mallick <153619690+subhajitxyz@users.noreply.github.com> Date: Thu, 5 Sep 2024 13:02:41 +0530 Subject: [PATCH 03/10] Fix #5485 Create means for verifying Fragment Arguments (#5522) ## Explanation Feature Request part1 #5485 Added test for 10 fragments arguments and saveInstanceState. ## Essential Checklist - [x] The PR title and explanation each start with "Fix #bugnum: " (If this PR fixes part of an issue, prefix the title with "Fix part of #bugnum: ...".) - [x] Any changes to [scripts/assets](https://github.com/oppia/oppia-android/tree/develop/scripts/assets) files have their rationale included in the PR explanation. - [x] The PR follows the [style guide](https://github.com/oppia/oppia-android/wiki/Coding-style-guide). - [x] The PR does not contain any unnecessary code changes from Android Studio ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#undo-unnecessary-changes)). - [x] The PR is made from a branch that's **not** called "develop" and is up-to-date with "develop". - [x] The PR is **assigned** to the appropriate reviewers ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#clarification-regarding-assignees-and-reviewers-section)). ## For UI-specific PRs only If your PR includes UI-related changes, then: - Add screenshots for portrait/landscape for both a tablet & phone of the before & after UI changes - For the screenshots above, include both English and pseudo-localized (RTL) screenshots (see [RTL guide](https://github.com/oppia/oppia-android/wiki/RTL-Guidelines)) - Add a video showing the full UX flow with a screen reader enabled (see [accessibility guide](https://github.com/oppia/oppia-android/wiki/Accessibility-A11y-Guide)) - For PRs introducing new UI elements or color changes, both light and dark mode screenshots must be included - Add a screenshot demonstrating that you ran affected Espresso tests locally & that they're passing --- .../app/options/AppLanguageFragment.kt | 3 +- .../app/options/AudioLanguageFragment.kt | 3 +- .../MarkChaptersCompletedFragmentTest.kt | 67 +++++++++++++++++++ .../MarkStoriesCompletedFragmentTest.kt | 54 +++++++++++++++ .../MarkTopicsCompletedFragmentTest.kt | 52 ++++++++++++++ .../android/app/help/HelpFragmentTest.kt | 32 +++++++++ .../app/options/AppLanguageFragmentTest.kt | 46 +++++++++++++ .../app/options/AudioLanguageFragmentTest.kt | 62 +++++++++++++++-- .../app/player/audio/AudioFragmentTest.kt | 20 ++++++ .../profile/ProfileEditFragmentTest.kt | 34 ++++++++++ .../app/thirdparty/LicenseListFragmentTest.kt | 30 +++++++++ .../conceptcard/ConceptCardFragmentTest.kt | 35 ++++++++++ 12 files changed, 431 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/oppia/android/app/options/AppLanguageFragment.kt b/app/src/main/java/org/oppia/android/app/options/AppLanguageFragment.kt index b946c93555b..477b78c49fd 100644 --- a/app/src/main/java/org/oppia/android/app/options/AppLanguageFragment.kt +++ b/app/src/main/java/org/oppia/android/app/options/AppLanguageFragment.kt @@ -42,7 +42,8 @@ class AppLanguageFragment : InjectableFragment(), AppLanguageRadioButtonListener } } - private fun Bundle.retrieveLanguageFromArguments(): OppiaLanguage { + /** Returns the [OppiaLanguage] stored in the fragment's arguments. */ + fun Bundle.retrieveLanguageFromArguments(): OppiaLanguage { return getProto( FRAGMENT_ARGUMENTS_KEY, AppLanguageFragmentArguments.getDefaultInstance() ).oppiaLanguage diff --git a/app/src/main/java/org/oppia/android/app/options/AudioLanguageFragment.kt b/app/src/main/java/org/oppia/android/app/options/AudioLanguageFragment.kt index 71ea48ca09e..4cb067f8cc7 100644 --- a/app/src/main/java/org/oppia/android/app/options/AudioLanguageFragment.kt +++ b/app/src/main/java/org/oppia/android/app/options/AudioLanguageFragment.kt @@ -84,7 +84,8 @@ class AudioLanguageFragment : InjectableFragment(), AudioLanguageRadioButtonList } } - private fun Bundle.retrieveLanguageFromArguments(): AudioLanguage { + /** Returns the [AudioLanguage] stored in the fragment's arguments. */ + fun Bundle.retrieveLanguageFromArguments(): AudioLanguage { return getProto( FRAGMENT_ARGUMENTS_KEY, AudioLanguageFragmentArguments.getDefaultInstance() ).audioLanguage 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 8c79da699c3..2ce4a9323ab 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 @@ -40,8 +40,10 @@ import org.oppia.android.app.application.ApplicationInjectorProvider import org.oppia.android.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.MarkChaptersCompletedFragment import org.oppia.android.app.devoptions.markchapterscompleted.testing.MarkChaptersCompletedTestActivity import org.oppia.android.app.model.ChapterPlayState +import org.oppia.android.app.model.MarkChaptersCompletedFragmentArguments import org.oppia.android.app.model.ProfileId import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule import org.oppia.android.app.recyclerview.RecyclerViewMatcher.Companion.atPositionOnView @@ -94,6 +96,7 @@ 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.extensions.getProto import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.EventLoggingConfigurationModule @@ -901,6 +904,70 @@ class MarkChaptersCompletedFragmentTest { } } + @Test + fun testFragment_fragmentLoaded_verifyCorrectArgumentsPassed() { + launchMarkChaptersCompletedFragmentTestActivity( + internalProfileId, showConfirmationNotice = true + ).use { scenario -> + testCoroutineDispatchers.runCurrent() + scenario.onActivity { activity -> + + val fragment = activity.supportFragmentManager + .findFragmentById(R.id.mark_chapters_completed_container) as MarkChaptersCompletedFragment + val arguments = + checkNotNull(fragment.arguments) { + "Expected arguments to be passed to MarkChaptersCompletedFragment" + } + val args = arguments.getProto( + "MarkChaptersCompletedFragment.arguments", + MarkChaptersCompletedFragmentArguments.getDefaultInstance() + ) + val receivedInternalProfileId = args?.internalProfileId + val receivedShowConfirmationNotice = args?.showConfirmationNotice + + assertThat(receivedInternalProfileId).isEqualTo(internalProfileId) + assertThat(receivedShowConfirmationNotice).isEqualTo(true) + } + } + } + + @Test + fun testFragment_saveInstanceState_verifyCorrectStateRestored() { + launchMarkChaptersCompletedFragmentTestActivity( + internalProfileId, showConfirmationNotice = true + ).use { scenario -> + testCoroutineDispatchers.runCurrent() + onView(withId(R.id.mark_chapters_completed_all_check_box_container)).perform(click()) + var actualSelectedExplorationIds = ArrayList() + var actualSelectedExplorationTitles = ArrayList() + + scenario.onActivity { activity -> + var fragment = activity.supportFragmentManager + .findFragmentById(R.id.mark_chapters_completed_container) as MarkChaptersCompletedFragment + + actualSelectedExplorationIds = + fragment.markChaptersCompletedFragmentPresenter.serializableSelectedExplorationIds + actualSelectedExplorationTitles = + fragment.markChaptersCompletedFragmentPresenter.serializableSelectedExplorationTitles + } + + scenario.recreate() + + scenario.onActivity { activity -> + val newFragment = activity.supportFragmentManager + .findFragmentById(R.id.mark_chapters_completed_container) as MarkChaptersCompletedFragment + + val receivedSelectedExplorationIds = + newFragment.markChaptersCompletedFragmentPresenter.serializableSelectedExplorationIds + val receivedSelectedExplorationTitles = + newFragment.markChaptersCompletedFragmentPresenter.serializableSelectedExplorationTitles + + assertThat(receivedSelectedExplorationIds).isEqualTo(actualSelectedExplorationIds) + assertThat(receivedSelectedExplorationTitles).isEqualTo(actualSelectedExplorationTitles) + } + } + } + private fun launchMarkChaptersCompletedFragmentTestActivity( internalProfileId: Int, showConfirmationNotice: Boolean = false 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 19836a42087..f8ab0bbacef 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 @@ -37,6 +37,7 @@ import org.oppia.android.app.application.ApplicationInjectorProvider import org.oppia.android.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.MarkStoriesCompletedFragment 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 @@ -99,6 +100,7 @@ import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule import org.oppia.android.util.parser.image.GlideImageLoaderModule import org.oppia.android.util.parser.image.ImageParsingModule +import org.oppia.android.util.profile.CurrentUserProfileIdIntentDecorator.extractCurrentUserProfileId import org.robolectric.annotation.Config import org.robolectric.annotation.LooperMode import javax.inject.Inject @@ -469,6 +471,58 @@ class MarkStoriesCompletedFragmentTest { } } + @Test + fun testFragment_fragmentLoaded_verifyCorrectArgumentsPassed() { + launch( + createMarkStoriesCompletedTestActivityIntent(internalProfileId) + ).use { scenario -> + testCoroutineDispatchers.runCurrent() + scenario.onActivity { activity -> + + val fragment = activity.supportFragmentManager + .findFragmentById(R.id.mark_stories_completed_container) as MarkStoriesCompletedFragment + + val arguments = + checkNotNull(fragment.arguments) { + "Expected arguments to be passed to MarkStoriesCompletedFragment" + } + val profileId = arguments.extractCurrentUserProfileId() + val receivedInternalProfileId = profileId.internalId + + assertThat(receivedInternalProfileId).isEqualTo(internalProfileId) + } + } + } + + @Test + fun testFragment_saveInstanceState_verifyCorrectStateRestored() { + launch( + createMarkStoriesCompletedTestActivityIntent(internalProfileId) + ).use { scenario -> + testCoroutineDispatchers.runCurrent() + onView(withId(R.id.mark_stories_completed_all_check_box_container)).perform(click()) + var actualSelectedStoryIdList = ArrayList() + + scenario.onActivity { activity -> + var fragment = activity.supportFragmentManager + .findFragmentById(R.id.mark_stories_completed_container) as MarkStoriesCompletedFragment + actualSelectedStoryIdList = + fragment.markStoriesCompletedFragmentPresenter.selectedStoryIdList + } + + scenario.recreate() + + scenario.onActivity { activity -> + val newFragment = activity.supportFragmentManager + .findFragmentById(R.id.mark_stories_completed_container) as MarkStoriesCompletedFragment + val receivedSelectedStoryIdList = + newFragment.markStoriesCompletedFragmentPresenter.selectedStoryIdList + + assertThat(receivedSelectedStoryIdList).isEqualTo(actualSelectedStoryIdList) + } + } + } + private fun createMarkStoriesCompletedTestActivityIntent(internalProfileId: Int): Intent { return MarkStoriesCompletedTestActivity.createMarkStoriesCompletedTestIntent( context, internalProfileId 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 dd17bbe74ba..91cc3ee71f2 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 @@ -37,6 +37,7 @@ import org.oppia.android.app.application.ApplicationInjectorProvider import org.oppia.android.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.MarkTopicsCompletedFragment 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 @@ -99,6 +100,7 @@ import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule import org.oppia.android.util.parser.image.GlideImageLoaderModule import org.oppia.android.util.parser.image.ImageParsingModule +import org.oppia.android.util.profile.CurrentUserProfileIdIntentDecorator.extractCurrentUserProfileId import org.robolectric.annotation.Config import org.robolectric.annotation.LooperMode import javax.inject.Inject @@ -450,6 +452,56 @@ class MarkTopicsCompletedFragmentTest { } } + @Test + fun testFragment_fragmentLoaded_verifyCorrectArgumentsPassed() { + launch( + createMarkTopicsCompletedTestActivityIntent(internalProfileId) + ).use { scenario -> + testCoroutineDispatchers.runCurrent() + scenario.onActivity { activity -> + + var fragment = activity.supportFragmentManager + .findFragmentById(R.id.mark_topics_completed_container) as MarkTopicsCompletedFragment + val arguments = + checkNotNull(fragment.arguments) { + "Expected arguments to be passed to MarkTopicsCompletedFragment" + } + val receivedProfileId = arguments.extractCurrentUserProfileId() + + assertThat(receivedProfileId).isEqualTo(profileId) + } + } + } + + @Test + fun testFragment_saveInstanceState_verifyCorrectStateRestored() { + launch( + createMarkTopicsCompletedTestActivityIntent(internalProfileId) + ).use { scenario -> + testCoroutineDispatchers.runCurrent() + onView(withId(R.id.mark_topics_completed_all_check_box_container)).perform(click()) + var actualSelectedTopicsList = ArrayList() + + scenario.onActivity { activity -> + var fragment = activity.supportFragmentManager + .findFragmentById(R.id.mark_topics_completed_container) as MarkTopicsCompletedFragment + actualSelectedTopicsList = + fragment.markTopicsCompletedFragmentPresenter.selectedTopicIdList + } + + scenario.recreate() + + scenario.onActivity { activity -> + val newFragment = activity.supportFragmentManager + .findFragmentById(R.id.mark_topics_completed_container) as MarkTopicsCompletedFragment + val restoredTopicIdList = + newFragment.markTopicsCompletedFragmentPresenter.selectedTopicIdList + + assertThat(restoredTopicIdList).isEqualTo(actualSelectedTopicsList) + } + } + } + private fun createMarkTopicsCompletedTestActivityIntent(internalProfileId: Int): Intent { return MarkTopicsCompletedTestActivity.createMarkTopicsCompletedTestIntent( context, internalProfileId 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 508d72bdb2a..e378a684dd9 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 @@ -2,6 +2,7 @@ package org.oppia.android.app.help import android.app.Application import android.content.Intent +import android.widget.FrameLayout import androidx.appcompat.app.AppCompatActivity import androidx.drawerlayout.widget.DrawerLayout import androidx.recyclerview.widget.RecyclerView @@ -29,6 +30,7 @@ import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat import dagger.Component import org.hamcrest.Matchers.equalTo import org.junit.After @@ -50,6 +52,7 @@ import org.oppia.android.app.devoptions.DeveloperOptionsModule import org.oppia.android.app.devoptions.DeveloperOptionsStarterModule import org.oppia.android.app.help.faq.FAQListActivity import org.oppia.android.app.help.thirdparty.ThirdPartyDependencyListActivity +import org.oppia.android.app.model.HelpFragmentArguments import org.oppia.android.app.model.PoliciesActivityParams import org.oppia.android.app.model.PolicyPage import org.oppia.android.app.model.ProfileId @@ -104,6 +107,7 @@ 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.extensions.getProto import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.EventLoggingConfigurationModule @@ -1374,6 +1378,34 @@ class HelpFragmentTest { } } + @Test + fun testFragment_fragmentLoaded_verifyCorrectArgumentsPassed() { + launch( + createHelpActivityIntent( + internalProfileId = 0, + isFromNavigationDrawer = true + ) + ).use { scenario -> + testCoroutineDispatchers.runCurrent() + scenario.onActivity { activity -> + + var fragment = activity.supportFragmentManager + .findFragmentById(R.id.help_fragment_placeholder) as HelpFragment + val isMultipane = + activity.findViewById(R.id.multipane_options_container) != null + + val arguments = checkNotNull(fragment.arguments) { + "Expected arguments to be passed to HelpFragment" + } + val args = + arguments.getProto("HelpFragment.arguments", HelpFragmentArguments.getDefaultInstance()) + val receivedIsMultipane = args.isMultipane + + assertThat(receivedIsMultipane).isEqualTo(isMultipane) + } + } + } + private fun ActivityScenario.openNavigationDrawer() { onView(withContentDescription(R.string.drawer_open_content_description)) .check(matches(isCompletelyDisplayed())) 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 62749ba3863..074985b485b 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 @@ -33,6 +33,7 @@ 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 +import org.oppia.android.app.options.AppLanguageFragment.Companion.retrieveLanguageFromArguments import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule import org.oppia.android.app.recyclerview.RecyclerViewMatcher.Companion.atPositionOnView import org.oppia.android.app.shim.ViewBindingShimModule @@ -95,6 +96,7 @@ import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule import org.oppia.android.util.parser.image.GlideImageLoaderModule import org.oppia.android.util.parser.image.ImageParsingModule +import org.oppia.android.util.profile.CurrentUserProfileIdIntentDecorator.extractCurrentUserProfileId import org.robolectric.annotation.Config import org.robolectric.annotation.LooperMode import javax.inject.Inject @@ -215,6 +217,50 @@ class AppLanguageFragmentTest { } } + @Test + fun testFragment_fragmentLoaded_verifyCorrectArgumentsPassed() { + launch(createAppLanguageActivityIntent(OppiaLanguage.ENGLISH)) + .use { scenario -> + testCoroutineDispatchers.runCurrent() + scenario.onActivity { activity -> + + val appLanguageFragment = activity.supportFragmentManager + .findFragmentById(R.id.app_language_fragment_container) as AppLanguageFragment + val recievedLanguage = appLanguageFragment.arguments?.retrieveLanguageFromArguments() + val receivedProfileId = + appLanguageFragment.arguments?.extractCurrentUserProfileId()?.internalId + + assertThat(recievedLanguage).isEqualTo(OppiaLanguage.ENGLISH) + assertThat(receivedProfileId).isEqualTo(internalProfileId) + } + } + } + + @Test + fun testFragment_saveInstanceState_verifyCorrectStateRestored() { + launch(createAppLanguageActivityIntent(OppiaLanguage.ENGLISH)) + .use { scenario -> + testCoroutineDispatchers.runCurrent() + + scenario.onActivity { activity -> + var appLanguageFragment = activity.supportFragmentManager + .findFragmentById(R.id.app_language_fragment_container) as AppLanguageFragment + appLanguageFragment.appLanguageFragmentPresenter.onLanguageSelected(OppiaLanguage.ARABIC) + } + + scenario.recreate() + + scenario.onActivity { activity -> + val newAppLanguageFragment = activity.supportFragmentManager + .findFragmentById(R.id.app_language_fragment_container) as AppLanguageFragment + val restoredLanguage = + newAppLanguageFragment.appLanguageFragmentPresenter.getLanguageSelected() + + assertThat(restoredLanguage).isEqualTo(OppiaLanguage.ARABIC) + } + } + } + private fun verifyKiswahiliIsSelected(appLanguageActivity: AppLanguageActivity?) { checkSelectedLanguage(index = KISWAHILI_BUTTON_INDEX, expectedLanguageName = "Kiswahili") assertThat(appLanguageActivity?.appLanguageActivityPresenter?.getLanguageSelected()?.name) 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 7ad7462af04..b8aed771aaa 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 @@ -39,6 +39,7 @@ import org.oppia.android.app.model.AudioLanguage import org.oppia.android.app.model.AudioLanguage.BRAZILIAN_PORTUGUESE_LANGUAGE import org.oppia.android.app.model.AudioLanguage.ENGLISH_AUDIO_LANGUAGE import org.oppia.android.app.model.AudioLanguage.NIGERIAN_PIDGIN_LANGUAGE +import org.oppia.android.app.options.AudioLanguageFragment.Companion.retrieveLanguageFromArguments import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule import org.oppia.android.app.recyclerview.RecyclerViewMatcher.Companion.atPositionOnView import org.oppia.android.app.shim.ViewBindingShimModule @@ -117,12 +118,17 @@ class AudioLanguageFragmentTest { private const val NIGERIAN_PIDGIN_BUTTON_INDEX = 4 } - @get:Rule val initializeDefaultLocaleRule = InitializeDefaultLocaleRule() - @get:Rule val oppiaTestRule = OppiaTestRule() + @get:Rule + val initializeDefaultLocaleRule = InitializeDefaultLocaleRule() + @get:Rule + val oppiaTestRule = OppiaTestRule() - @Inject lateinit var context: Context - @Inject lateinit var profileTestHelper: ProfileTestHelper - @Inject lateinit var testCoroutineDispatchers: TestCoroutineDispatchers + @Inject + lateinit var context: Context + @Inject + lateinit var profileTestHelper: ProfileTestHelper + @Inject + lateinit var testCoroutineDispatchers: TestCoroutineDispatchers @After fun tearDown() { @@ -351,6 +357,52 @@ class AudioLanguageFragmentTest { } } + @Test + fun testFragment_fragmentLoaded_verifyCorrectArgumentsPassed() { + initializeTestApplicationComponent(enableOnboardingFlowV2 = true) + launch( + createDefaultAudioActivityIntent(ENGLISH_AUDIO_LANGUAGE) + ).use { scenario -> + testCoroutineDispatchers.runCurrent() + scenario.onActivity { activity -> + + val fragment = activity.supportFragmentManager + .findFragmentById(R.id.audio_language_fragment_container) as AudioLanguageFragment + val receivedAudioLanguage = fragment.arguments?.retrieveLanguageFromArguments() + + assertThat(ENGLISH_AUDIO_LANGUAGE).isEqualTo(receivedAudioLanguage) + } + } + } + + @Test + fun testFragment_saveInstanceState_verifyCorrectStateRestored() { + initializeTestApplicationComponent(enableOnboardingFlowV2 = true) + launch( + createDefaultAudioActivityIntent(ENGLISH_AUDIO_LANGUAGE) + ).use { scenario -> + testCoroutineDispatchers.runCurrent() + var language: AudioLanguage? = null + + scenario.onActivity { activity -> + var fragment = activity.supportFragmentManager + .findFragmentById(R.id.audio_language_fragment_container) as AudioLanguageFragment + language = fragment.audioLanguageFragmentPresenterV1.getLanguageSelected() + } + + scenario.recreate() + + scenario.onActivity { activity -> + val newfragment = activity.supportFragmentManager + .findFragmentById(R.id.audio_language_fragment_container) as AudioLanguageFragment + val restoredAudioLanguage = + newfragment.audioLanguageFragmentPresenterV1.getLanguageSelected() + + assertThat(restoredAudioLanguage).isEqualTo(language) + } + } + } + private fun launchActivityWithLanguage( audioLanguage: AudioLanguage ): ActivityScenario { 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 ed37473683f..382327eb863 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 @@ -23,6 +23,7 @@ import androidx.test.espresso.matcher.ViewMatchers.withContentDescription import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat import dagger.Component import org.hamcrest.Description import org.hamcrest.Matcher @@ -110,6 +111,7 @@ import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule import org.oppia.android.util.parser.image.GlideImageLoaderModule import org.oppia.android.util.parser.image.ImageParsingModule +import org.oppia.android.util.profile.CurrentUserProfileIdIntentDecorator.extractCurrentUserProfileId import org.robolectric.annotation.Config import org.robolectric.annotation.LooperMode import javax.inject.Inject @@ -343,6 +345,24 @@ class AudioFragmentTest { } } + @Test + fun testFragment_fragmentLoaded_verifyCorrectArgumentsPassed() { + addMediaInfo() + launch( + createAudioFragmentTestIntent(internalProfileId) + ).use { scenario -> + testCoroutineDispatchers.runCurrent() + scenario.onActivity { activity -> + + val audioFragment = activity.supportFragmentManager + .findFragmentById(R.id.audio_fragment_placeholder) as AudioFragment + val receivedProfileId = audioFragment.arguments?.extractCurrentUserProfileId() + + assertThat(receivedProfileId).isEqualTo(profileId) + } + } + } + private fun withSeekBarPosition(position: Int) = object : TypeSafeMatcher() { override fun describeTo(description: Description) { description.appendText("SeekBar with progress same as $position") 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 37992371629..811072493a6 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 @@ -45,6 +45,8 @@ import org.oppia.android.app.devoptions.DeveloperOptionsStarterModule import org.oppia.android.app.devoptions.markchapterscompleted.MarkChaptersCompletedActivity import org.oppia.android.app.devoptions.markchapterscompleted.MarkChaptersCompletedActivity.Companion.MARK_CHAPTERS_COMPLETED_ACTIVITY_PARAMS import org.oppia.android.app.model.MarkChaptersCompletedActivityParams +import org.oppia.android.app.model.ProfileEditActivityParams +import org.oppia.android.app.model.ProfileEditFragmentArguments import org.oppia.android.app.model.ProfileId import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule import org.oppia.android.app.shim.ViewBindingShimModule @@ -100,6 +102,8 @@ 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.data.DataProviders.Companion.toLiveData +import org.oppia.android.util.extensions.getProto +import org.oppia.android.util.extensions.getProtoExtra import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.EventLoggingConfigurationModule @@ -473,6 +477,36 @@ class ProfileEditFragmentTest { assertThat(profile.allowInLessonQuickLanguageSwitching).isTrue() } + @Test + fun testFragment_fragmentLoaded_verifyCorrectArgumentsPassed() { + launchFragmentTestActivity(internalProfileId = 1).use { scenario -> + scenario.onActivity { activity -> + + val activityArgs = activity.intent.getProtoExtra( + ProfileEditActivity.PROFILE_EDIT_ACTIVITY_PARAMS_KEY, + ProfileEditActivityParams.getDefaultInstance() + ) + val isMultipane = activityArgs?.isMultipane ?: false + + val fragment = activity.supportFragmentManager + .findFragmentById(R.id.profile_edit_fragment_placeholder) as ProfileEditFragment + + val arguments = checkNotNull(fragment.arguments) { + "Expected variables to be passed to ProfileEditFragment" + } + val args = arguments.getProto( + ProfileEditFragment.PROFILE_EDIT_FRAGMENT_ARGUMENTS_KEY, + ProfileEditFragmentArguments.getDefaultInstance() + ) + val receivedInternalProfileId = args.internalProfileId + val receivedIsMultipane = args.isMultipane + + assertThat(receivedInternalProfileId).isEqualTo(1) + assertThat(receivedIsMultipane).isEqualTo(isMultipane) + } + } + } + private fun launchFragmentTestActivity(internalProfileId: Int) = launch( createProfileEditFragmentTestActivity(context, internalProfileId) 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 1e2bc4791a7..5e6a627b086 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 @@ -19,6 +19,7 @@ import androidx.test.espresso.matcher.ViewMatchers.isRoot import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat import dagger.Component import org.hamcrest.Matchers.allOf import org.junit.After @@ -39,7 +40,9 @@ 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 +import org.oppia.android.app.help.thirdparty.LicenseListFragment import org.oppia.android.app.help.thirdparty.LicenseTextViewerActivity +import org.oppia.android.app.model.LicenseListFragmentArguments import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule import org.oppia.android.app.recyclerview.RecyclerViewMatcher.Companion.atPosition import org.oppia.android.app.shim.ViewBindingShimModule @@ -87,6 +90,7 @@ 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.extensions.getProto import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.EventLoggingConfigurationModule @@ -340,6 +344,32 @@ class LicenseListFragmentTest { } } + @Test + fun testFragment_fragmentLoaded_verifyCorrectArgumentsPassed() { + launch(createLicenseListActivity(2)).use { scenario -> + + testCoroutineDispatchers.runCurrent() + scenario.onActivity { activity -> + + var fragment = activity.supportFragmentManager + .findFragmentById(R.id.license_list_fragment_placeholder) as LicenseListFragment + + val arguments = checkNotNull(fragment.arguments) { + "Expected arguments to be passed to LicenseListFragment" + } + val args = arguments.getProto( + "LicenseListFragment.arguments", + LicenseListFragmentArguments.getDefaultInstance() + ) + val receivedDependencyIndex = args.dependencyIndex + val receivedIsMultipane = args.isMultipane + + assertThat(receivedDependencyIndex).isEqualTo(2) + assertThat(receivedIsMultipane).isEqualTo(false) + } + } + } + private fun createLicenseListActivity(dependencyIndex: Int): Intent { return LicenseListActivity.createLicenseListActivityIntent( ApplicationProvider.getApplicationContext(), 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 c8ec2292371..ac21622d80e 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 @@ -56,6 +56,7 @@ 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.InjectableDialogFragment +import org.oppia.android.app.model.ConceptCardFragmentArguments import org.oppia.android.app.model.OppiaLanguage import org.oppia.android.app.model.ProfileId import org.oppia.android.app.model.WrittenTranslationLanguageSelection @@ -116,6 +117,7 @@ import org.oppia.android.util.accessibility.AccessibilityTestModule import org.oppia.android.util.caching.AssetModule import org.oppia.android.util.caching.LoadImagesFromAssets import org.oppia.android.util.caching.LoadLessonProtosFromAssets +import org.oppia.android.util.extensions.getProto import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.EventLoggingConfigurationModule @@ -127,6 +129,7 @@ import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule import org.oppia.android.util.parser.image.GlideImageLoaderModule import org.oppia.android.util.parser.image.ImageParsingModule +import org.oppia.android.util.profile.CurrentUserProfileIdIntentDecorator.extractCurrentUserProfileId import org.robolectric.annotation.Config import org.robolectric.annotation.LooperMode import javax.inject.Inject @@ -492,6 +495,38 @@ class ConceptCardFragmentTest { } } + @Test + fun testFragment_fragmentLoaded_verifyCorrectArgumentsPassed() { + launchTestActivity().use { scenario -> + scenario.onActivity { activity -> + + ConceptCardFragment.bringToFrontOrCreateIfNew( + TEST_SKILL_ID_0, + profileId, + activity.supportFragmentManager + ) + val fragmentSkill0 = + activity.supportFragmentManager.fragments.filterIsInstance().single() + + val arguments = checkNotNull(fragmentSkill0.arguments) { + "Expected arguments to be passed to ConceptCardFragment" + } + val args = arguments.getProto( + ConceptCardFragment.CONCEPT_CARD_FRAGMENT_ARGUMENTS_KEY, + ConceptCardFragmentArguments.getDefaultInstance() + ) + val skillId = + checkNotNull(args.skillId) { + "Expected skillId to be passed to ConceptCardFragment" + } + val receivedProfileId = arguments.extractCurrentUserProfileId() + + assertThat(skillId).isEqualTo(TEST_SKILL_ID_0) + assertThat(receivedProfileId).isEqualTo(profileId) + } + } + } + private fun launchTestActivity(): ActivityScenario { val scenario = ActivityScenario.launch( ConceptCardFragmentTestActivity.createIntent(context, profileId) From 4730edb0263135e88dab08ab0f6dc724a1585a67 Mon Sep 17 00:00:00 2001 From: RD Rama Devi <122200035+Rd4dev@users.noreply.github.com> Date: Thu, 5 Sep 2024 22:52:56 +0530 Subject: [PATCH 04/10] Fix #1730 : Prevent binary files from being checked in using pre-commit hook (#5525) ## Explanation Fixes #1730 ### The PR includes - A pre-commit hook that identifies binary files in both - staged changes (for local checks before committing) - committed files (for CI checks after pushing to a PR) - If binary files are found, the commit/check is blocked, and the offending files are listed for removal. - The pre-commit hook is integrated via a setup script. - The same script is utilized for the CI pipeline. - The 'Pass' statement is only included in the CI checks to keep the local commit process cleaner. ### Local block as pre-commit hook when a binary file is detected ![image](https://github.com/user-attachments/assets/b953b2d7-55ec-46e6-a0b9-00967200509c) ### CI re-check if the PR includes a binary - [ Fail - [stack trace](https://github.com/Rd4dev/oppia-android/actions/runs/10715972847/job/29712474511#step:7:15) ] [ Pass - [stack trace](https://github.com/Rd4dev/oppia-android/actions/runs/10716040065/job/29712695824?pr=11#step:7:10) ] ![image](https://github.com/user-attachments/assets/64352473-86cb-49e0-b888-d3247286e9e5) ## Essential Checklist - [x] The PR title and explanation each start with "Fix #bugnum: " (If this PR fixes part of an issue, prefix the title with "Fix part of #bugnum: ...".) - [x] Any changes to [scripts/assets](https://github.com/oppia/oppia-android/tree/develop/scripts/assets) files have their rationale included in the PR explanation. - [x] The PR follows the [style guide](https://github.com/oppia/oppia-android/wiki/Coding-style-guide). - [x] The PR does not contain any unnecessary code changes from Android Studio ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#undo-unnecessary-changes)). - [x] The PR is made from a branch that's **not** called "develop" and is up-to-date with "develop". - [x] The PR is **assigned** to the appropriate reviewers ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#clarification-regarding-assignees-and-reviewers-section)). --- .github/workflows/static_checks.yml | 11 +++++++++ scripts/pre-commit.sh | 35 +++++++++++++++++++++++++++++ scripts/setup.sh | 3 +++ 3 files changed, 49 insertions(+) create mode 100644 scripts/pre-commit.sh diff --git a/.github/workflows/static_checks.yml b/.github/workflows/static_checks.yml index 15dd3e28e76..ae04da9c0f4 100644 --- a/.github/workflows/static_checks.yml +++ b/.github/workflows/static_checks.yml @@ -107,6 +107,8 @@ jobs: CACHE_DIRECTORY: ~/.bazel_cache steps: - uses: actions/checkout@v2 + with: + fetch-depth: 0 - name: Set up Bazel uses: abhinavsingh/setup-bazel@v3 @@ -205,6 +207,15 @@ jobs: run: | bazel run //scripts:string_resource_validation_check -- $(pwd) + - name: Binary files check + # The expression if: ${{ !cancelled() }} runs a job or step regardless of its success or failure while responding to cancellations, + # serving as a cancellation-compliant alternative to if: ${{ always() }} in concurrent workflows. + if: ${{ !cancelled() }} + run: | + bash /home/runner/work/oppia-android/oppia-android/scripts/pre-commit.sh + echo "No binary files found in commit" + echo "BINARY FILES CHECK PASSED" + # Note that caching is intentionally not enabled for this check since licenses should always be # verified without any potential influence from earlier builds (i.e. always from a clean build to # ensure the results exactly match the current state of the repository). diff --git a/scripts/pre-commit.sh b/scripts/pre-commit.sh new file mode 100644 index 00000000000..26061ef5c09 --- /dev/null +++ b/scripts/pre-commit.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +# Pre-commit hook to check for binary files. + +# Find the common ancestor between develop and the current branch +base_commit=$(git merge-base 'origin/develop' HEAD) + +# Get the list of staged changes (files ready to be committed) +staged_files=$(git diff --cached --name-only) + +# Get the list of changed files compared to the base commit +changed_files=$(git diff --name-only "$base_commit" HEAD) + +# Combine both lists of files, ensuring no duplicates +all_files=$(echo -e "$staged_files\n$changed_files" | sort -u) + +function checkForBinaries() { + binaryFilesCount=0 + + # Iterate over all files (both staged and changed) + for file in $all_files; do + if [ -f "$file" ] && file --mime "$file" | grep -q 'binary'; then + ((binaryFilesCount++)) + printf "\n\033[33m%s\033[0m" "$file" + fi + done + + if [[ "${binaryFilesCount}" -gt 0 ]]; then + printf "\n\nPlease remove the %d detected binary file(s)." "$binaryFilesCount" + printf "\nBINARY FILES CHECK FAILED" + exit 1 + fi +} + +checkForBinaries diff --git a/scripts/setup.sh b/scripts/setup.sh index 8c3ef595e1d..9f822095f32 100644 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -13,6 +13,9 @@ # Move file from script folder to .git/hooks folder cp scripts/pre-push.sh .git/hooks/pre-push +# Copy the pre-commit hook from script to .git/hooks folder +cp scripts/pre-commit.sh .git/hooks/pre-commit + # Create a folder where all the set up files will be downloaded mkdir -p ../oppia-android-tools cd ../oppia-android-tools From 4f4831ac9d251930568369471efabea3175e9001 Mon Sep 17 00:00:00 2001 From: Adhiambo Peres <59600948+adhiamboperes@users.noreply.github.com> Date: Wed, 11 Sep 2024 02:17:46 +0300 Subject: [PATCH 05/10] Fix #5357: Remove CDATA from translatable strings (#5524) ## Explanation Fixes https://github.com/oppia/oppia-android/issues/5357 This PR includes the changes previously included in #5361, with an additional fix for missing closing html tags described in #5403: ``` faq_answer_app_language: Missing tag after the email address. faq_answer_bug_reporting: Missing closing tag. faq_answer_exploration_player: Missing closing tag. faq_answer_update_app: Missing closing tag and closing

tag. faq_answer_update_os: Missing closing tag and closing

tag. ``` This PR removes all CDATA declarations in the translatable strings.xml file and instead escapes all necessary characters: < and > (& didn't need to be escaped since no strings use that character at the moment). This is needed because Translatewiki doesn't seem to extract the HTML within the CDATA declaration correctly, so it may not be translated (some existing strings were never translated, and per https://github.com/oppia/oppia-android/pull/5274 the latest FAQ changes aren't being processed correctly (leading to empty translation strings being submitted). This PR also introduces a regex check + test to ensure that CDATA isn't used anymore in strings. ## Essential Checklist - [x] The PR title and explanation each start with "Fix #bugnum: " (If this PR fixes part of an issue, prefix the title with "Fix part of #bugnum: ...".) - [x] Any changes to [scripts/assets](https://github.com/oppia/oppia-android/tree/develop/scripts/assets) files have their rationale included in the PR explanation. - [x] The PR follows the [style guide](https://github.com/oppia/oppia-android/wiki/Coding-style-guide). - [x] The PR does not contain any unnecessary code changes from Android Studio ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#undo-unnecessary-changes)). - [x] The PR is made from a branch that's **not** called "develop" and is up-to-date with "develop". - [x] The PR is **assigned** to the appropriate reviewers ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#clarification-regarding-assignees-and-reviewers-section)). ## For UI-specific PRs only If your PR includes UI-related changes, then: - Add screenshots for portrait/landscape for both a tablet & phone of the before & after UI changes - For the screenshots above, include both English and pseudo-localized (RTL) screenshots (see [RTL guide](https://github.com/oppia/oppia-android/wiki/RTL-Guidelines)) - Add a video showing the full UX flow with a screen reader enabled (see [accessibility guide](https://github.com/oppia/oppia-android/wiki/Accessibility-A11y-Guide)) - For PRs introducing new UI elements or color changes, both light and dark mode screenshots must be included - Add a screenshot demonstrating that you ran affected Espresso tests locally & that they're passing --- app/src/main/res/values-sw/strings.xml | 2 +- app/src/main/res/values/strings.xml | 44 +++++++++---------- .../file_content_validation_checks.textproto | 5 +++ .../regex/RegexPatternValidationCheckTest.kt | 24 ++++++++++ 4 files changed, 52 insertions(+), 23 deletions(-) diff --git a/app/src/main/res/values-sw/strings.xml b/app/src/main/res/values-sw/strings.xml index 049ebac057a..17d2fd54cec 100644 --- a/app/src/main/res/values-sw/strings.xml +++ b/app/src/main/res/values-sw/strings.xml @@ -152,7 +152,7 @@ Tafadhali anza jibu lako kwa nambari (k.m.,”0” katika 0.5). Tafadhali weka nambari halali. Jibu linaweza kuwa na tarakimu zisizozidi 15 (0–9) au alama (. au -). - Tafadhali andika uwiano unaojumuisha tarakimu zilizotenganishwa na koloni(k.m. 1:2 au 1:2:3). + Tafadhali andika uwiano unaojumuisha tarakimu zilizotenganishwa na koloni (k.m. 1:2 au 1:2:3). Tafadhali weka uwiano sahihi (k.m. 1:2 au 1:2:3). Jibu lako lina koloni mbili (:) karibu na kila moja. Idadi ya masharti si sawa na masharti yanayohitajika. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1395b1d24a3..319d70ff93a 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -175,12 +175,12 @@ Please enter a valid number. The answer can contain at most 15 digits (0–9) or symbols (. or -). Enter a number to continue. - Please write a ratio that consists of digits separated by colons (e.g. 1:2 or 1:2:3). - Please enter a valid ratio (e.g. 1:2 or 1:2:3). - Your answer has two colons (:) next to each other. - Number of terms is not equal to the required terms. - Ratios cannot have 0 as an element. - Enter a ratio to continue. + Please write a ratio that consists of digits separated by colons (e.g. 1:2 or 1:2:3). + Please enter a valid ratio (e.g. 1:2 or 1:2:3). + Your answer has two colons (:) next to each other. + Number of terms is not equal to the required terms. + Ratios cannot have 0 as an element. + Enter a ratio to continue. Enter text to continue. Choose an answer to continue. Unknown size @@ -549,11 +549,11 @@ Policy Page Privacy Policy - this page for the latest version of this privacy policy.]]> + Please visit <a href="https://www.oppia.org/privacy-policy">this page</a> for the latest version of this privacy policy. Terms of Service - Terms of Service and Privacy Policy.]]> - this page for the latest version of these terms.]]> + By using %s, you agree to our <br> <oppia-noninteractive-policy link="tos">Terms of Service</oppia-noninteractive-policy> and <oppia-noninteractive-policy link="privacy">Privacy Policy</oppia-noninteractive-policy>. + Please visit <a href="https://www.oppia.org/terms">this page</a> for the latest version of these terms. What is %s? Who is an Administrator? @@ -569,19 +569,19 @@ How do I update my Android OS? I can\'t find my question here. What now? - %1$s "O-pee-yah" (Finnish) - "to learn"


%1$s\'s mission is to help anyone learn anything they want in an effective and enjoyable way.


By creating a set of free, high-quality, demonstrably effective lessons with the help of educators from around the world, %1$s aims to provide students with quality education — regardless of where they are or what traditional resources they have access to.


As a student, you can begin your learning adventure by browsing the topics listed on the Home Page!

]]>
- An Administrator is the main user that manages profiles and settings for every profile on their account. They are most likely your parent, teacher, or guardian that created this profile for you.


Administrators have the ability to manage profiles, assign PINs, and change other settings under their account. Depending on your profile, Administrator permissions may be required for certain features such as changing your PIN, and more.


To see who your Administrator is, go to the Profile Chooser. The first profile listed and has "Administrator" written under their name is the Administrator.

]]>
- If it is your first time creating a profile and you do not have a PIN:
  1. From the Profile Chooser, tap on Set up Multiple Profiles.
  2. Create a PIN and Save.
  3. Fill in all fields for the profile.
    1. (Optional) Upload a photo.
    2. Enter a name.
    3. (Optional) Assign a 3-digit PIN.
  4. Tap Create. This profile is added to your Profile Chooser!

If you have created a profile before and have a PIN:

  1. From the Profile Chooser, tap on Add Profile.
  2. Enter your PIN and tap Submit.
  3. Fill in all fields for the profile.
    1. (Optional) Upload a photo.
    2. Enter a name.
    3. (Optional) Assign a 3-digit PIN.
  4. Tap Create. This profile is added to your Profile Chooser!


Note: Only the Administrator is able to manage profiles.

]]>
- The %s app currently supports English, Brazilian Portuguese, Arabic, Swahili and Nigerian Pidgin. Choose one of these languages in the menu, under Options. To request the app in your language, please contact us at admin@oppia.org.

]]>
-
  1. From your %s app home screen, tap the menu in the top left corner.
  2. Tap Share feedback.
  3. Follow the instructions to report the bug or share feedback.
  4. ]]> - %1$s’s mission is to help learners gain necessary life skills. Math is an essential skill in everyday life. %1$s will be offering new lessons on science and other subjects soon!

    ]]>
    - Yes, %s will be offering new lessons on science and other subjects soon. Please check back for updates!

    ]]>
    - If the Exploration Player is not loading


    Check to see if the app is up to date:

    • Go to the Play Store and make sure the app is updated to its latest version


    Check your internet connection:

    • If your internet connection is slow, try re-connecting to your Wi-Fi network or connecting to a different network.

    Ask the Administrator to check their device and internet connection:

    • Get the Administrator to troubleshoot using the steps above

    Let us know if you still have issues with loading:

    • Report a problem by contacting us at admin@oppia.org.
    ]]>
    - If your audio is not playing


    Check to see if the app is up to date:

    • Go to the Play Store and make sure the app is updated to its latest version


    Check your internet connection:

    • If your internet connection is slow, try re-connecting to your Wi-Fi network or connecting to a different network. Slow internet may cause the audio to load irregularly, making it difficult to play.


    Ask the Administrator to check their device and internet connection:

    • Get the Administrator to troubleshoot using the steps above


    Let us know if you still have issues with loading:

    • Report a problem by contacting us at admin@oppia.org.
    ]]>
    - Once a profile is deleted:

    1. The profile cannot be recovered.
    2. Profile information such as name, photos, and progress will be permanently deleted.

    To delete a profile (excluding the Administrator\'s):

    1. From the Administrator\'s Home Page, tap on the menu button on the top left.
    2. Tap on Administrator Controls.
    3. Tap on Edit Profiles.
    4. Tap on the Profile you would like to delete.
    5. At the bottom of the screen, tap Profile Deletion.
    6. Tap Delete to confirm deletion.


    Note: Only the Administrator is able to manage profiles.

    ]]>
    -
    1. Open the Google Play Store app.
    2. Search for the %s app.
    3. Tap Update.

      ]]> -
      1. Tap your phone\'s Settings app.
      2. Tap System updates.
      3. Tap System updates and follow the instructions to update your Android operating system.

        ]]> - If you cannot find your question or would like to report a bug, contact us at admin@oppia.org.

        ]]>
        + <p>%1$s <i>"O-pee-yah"</i> (Finnish) - "to learn"</p><p><br></p><p>%1$s\'s mission is to help anyone learn anything they want in an effective and enjoyable way.</p><p><br></p><p>By creating a set of free, high-quality, demonstrably effective lessons with the help of educators from around the world, %1$s aims to provide students with quality education — regardless of where they are or what traditional resources they have access to.</p><p><br></p><p>As a student, you can begin your learning adventure by browsing the topics listed on the Home Page!</p> + <p>An Administrator is the main user that manages profiles and settings for every profile on their account. They are most likely your parent, teacher, or guardian that created this profile for you.</p><p><br></p><p>Administrators have the ability to manage profiles, assign PINs, and change other settings under their account. Depending on your profile, Administrator permissions may be required for certain features such as changing your PIN, and more.</p><p><br></p><p>To see who your Administrator is, go to the Profile Chooser. The first profile listed and has "Administrator" written under their name is the Administrator.</p> + <p>If it is your first time creating a profile and you do not have a PIN:<ol><li>From the Profile Chooser, tap on <strong>Set up Multiple Profiles</strong>.</li><li>Create a PIN and <strong>Save</strong>.</li><li>Fill in all fields for the profile.<ol><li>(Optional) Upload a photo.</li><li>Enter a name.</li><li>(Optional) Assign a 3-digit PIN.</li></ol></li><li>Tap <strong>Create</strong>. This profile is added to your Profile Chooser!</li></ol></p><p>If you have created a profile before and have a PIN:<ol><li>From the Profile Chooser, tap on <strong>Add Profile</strong>.</li><li>Enter your PIN and tap <strong>Submit</strong>.</li><li>Fill in all fields for the profile.<ol><li>(Optional) Upload a photo.</li><li>Enter a name.</li><li>(Optional) Assign a 3-digit PIN.</li></ol></li><li>Tap <strong>Create</strong>. This profile is added to your Profile Chooser!</li></ol></p><p><br></p><p>Note: Only the <u>Administrator</u> is able to manage profiles.</p> + <p>The %s app currently supports English, Brazilian Portuguese, Arabic, Swahili, and Nigerian Pidgin. Choose one of these languages in the menu, under Options. To request the app in your language, please contact us at <strong>admin@oppia.org</strong>.</p> + <p><ol><li>From your %s app home screen, tap the menu in the top left corner.</li><li>Tap <strong>Share feedback</strong>.</li><li>Follow the instructions to report the bug or share feedback.</li></ol></p> + <p>%1$s\'s mission is to help learners gain necessary life skills. Math is an essential skill in everyday life. %1$s will be offering new lessons on science and other subjects soon!</p> + <p>Yes, %s will be offering new lessons on science and other subjects soon. Please check back for updates!</p> + <p>If the Exploration Player is not loading</p><p><br></p><p>Check to see if the app is up to date:</p><ul><li>Go to the Play Store and make sure the app is updated to its latest version</li></ul><p><br></p><p>Check your internet connection:</p><ul><li>If your internet connection is slow, try re-connecting to your Wi-Fi network or connecting to a different network.</li></ul><p>Ask the Administrator to check their device and internet connection:</p><ul><li>Get the Administrator to troubleshoot using the steps above</li></ul><p><br></p><p>Let us know if you still have issues with loading:</p><ul><li>Report a problem by contacting us at admin@oppia.org.</li></ul> + <p>If your audio is not playing</p><p><br></p><p>Check to see if the app is up to date:</p><ul><li>Go to the Play Store and make sure the app is updated to its latest version</li></ul><p><br></p><p>Check your internet connection:</p><ul><li>If your internet connection is slow, try re-connecting to your Wi-Fi network or connecting to a different network. Slow internet may cause the audio to load irregularly, making it difficult to play.</li></ul><p><br></p><p>Ask the Administrator to check their device and internet connection:</p><ul><li>Get the Administrator to troubleshoot using the steps above</li></ul><p><br></p><p>Let us know if you still have issues with loading:</p><ul><li>Report a problem by contacting us at admin@oppia.org.</li></ul> + <p>Once a profile is deleted:</p><ol><li>The profile cannot be recovered.</li><li>Profile information such as name, photos, and progress will be permanently deleted.</li></ol><p>To delete a profile (excluding the <u>Administrator</u>):</p><ol><li>From the Administrator\'s Home Page, tap on the menu button on the top left.</li><li>Tap on <strong>Administrator Controls</strong>.</li><li>Tap on <strong>Edit Profiles</strong>.</li><li>Tap on the Profile you would like to delete.</li><li>At the bottom of the screen, tap <strong>Profile Deletion</strong>.</li><li>Tap <strong>Delete</strong> to confirm deletion.</li></ol><p><br></p><p>Note: Only the <u>Administrator</u> is able to manage profiles.</p> + <p><ol><li>Open the Google Play Store app.</li><li>Search for the %s app.</li><li>Tap Update.</li></ol></p> + <p><ol><li>Tap your phone\'s Settings app.</li><li>Tap System updates.</li><li>Tap System updates and follow the instructions to update your Android operating system.</li></ol></p> + <p>If you cannot find your question or would like to report a bug, contact us at <strong>admin@oppia.org.</strong></p> Profile Edit Fragment Test Activity Administrator Controls Fragment Test Activity diff --git a/scripts/assets/file_content_validation_checks.textproto b/scripts/assets/file_content_validation_checks.textproto index 9a9918f2f64..d4e3597a664 100644 --- a/scripts/assets/file_content_validation_checks.textproto +++ b/scripts/assets/file_content_validation_checks.textproto @@ -116,6 +116,11 @@ file_content_checks { failure_message: "All plurals outside strings.xml must be marked as not translatable, or moved to strings.xml." exempted_file_patterns: "app/src/main/res/values.*?/strings\\.xml" } +file_content_checks { + file_path_regex: "app/src/main/res/values/strings\\.xml" + prohibited_content_regex: "CDATA" + failure_message: "CDATA isn't handled by Translatewiki correctly. Use escaped HTML, instead." +} file_content_checks { file_path_regex: ".+?\\.kt" prohibited_content_regex: "android.text.BidiFormatter" diff --git a/scripts/src/javatests/org/oppia/android/scripts/regex/RegexPatternValidationCheckTest.kt b/scripts/src/javatests/org/oppia/android/scripts/regex/RegexPatternValidationCheckTest.kt index 4e96123650e..5f47d4b81e1 100644 --- a/scripts/src/javatests/org/oppia/android/scripts/regex/RegexPatternValidationCheckTest.kt +++ b/scripts/src/javatests/org/oppia/android/scripts/regex/RegexPatternValidationCheckTest.kt @@ -222,6 +222,8 @@ class RegexPatternValidationCheckTest { private val referenceComputeIfAbsent = "computeIfAbsent won't desugar and requires Java 8 support (SDK 24+). Suggest using an atomic" + " Kotlin-specific solution, instead." + private val cdataShouldNotBeUsed = + "CDATA isn't handled by Translatewiki correctly. Use escaped HTML, instead." private val wikiReferenceNote = "Refer to https://github.com/oppia/oppia-android/wiki/Static-Analysis-Checks" + "#regexpatternvalidation-check for more details on how to fix this." @@ -2752,6 +2754,28 @@ class RegexPatternValidationCheckTest { ) } + @Test + fun testFileContent_includesCdataContentInStringsXml_fileContentIsNotCorrect() { + val prohibitedContent = + """ + Some nested HTML.

        ]]>
        + """.trimIndent() + tempFolder.newFolder("testfiles", "app", "src", "main", "res", "values") + val stringFilePath = "app/src/main/res/values/strings.xml" + tempFolder.newFile("testfiles/$stringFilePath").writeText(prohibitedContent) + + val exception = assertThrows() { runScript() } + + assertThat(exception).hasMessageThat().contains(REGEX_CHECK_FAILED_OUTPUT_INDICATOR) + assertThat(outContent.toString().trim()) + .isEqualTo( + """ + $stringFilePath:1: $cdataShouldNotBeUsed + $wikiReferenceNote + """.trimIndent() + ) + } + /** Runs the regex_pattern_validation_check. */ private fun runScript() { main(File(tempFolder.root, "testfiles").absolutePath) From c82b71b82717e3c998fbdb7bfb052bd580d6d78e Mon Sep 17 00:00:00 2001 From: RD Rama Devi <122200035+Rd4dev@users.noreply.github.com> Date: Thu, 12 Sep 2024 15:05:07 +0530 Subject: [PATCH 06/10] Fix part of #5508: Limit APK/AAB Difference analysis reports in the PR Comment Thread (#5532) ## Explanation Fixes part of #5508 ### This PR includes Steps to locate the previous `stats.yml` workflow run, download its build artifact, and compare it with the current build log. If changes are detected, a comment will be uploaded to help minimize comment thread overload. **The implementation:** - Download the previous build log artifact (if available). - Run the script. - Compare the current generated build log with the previous build log artifact: - if no differences are found -> skip commenting. - if differences are found -> comment the current generated build log - if no previous artifact is found -> comment the current generated build log - This occurs in 2 instances: - 1. It's the first run of the PR. - 2. An error occurred during the previous stat check (since the previous build is from the second-to-last run ID). - Upload the current build log as an artifact (for the next stat run). - Comment/skip the stat report based on the comparison result. # ### Tested with a cloned PR _(with stats.yml implementation on develop)_ Tested PR: https://github.com/Rd4dev/Oppia-Android-Fork-from-Fork/pull/40 Reference for proof of implementation: - [x] should comment on initial run | [comment](https://github.com/Rd4dev/Oppia-Android-Fork-from-Fork/pull/40#issuecomment-2336828924) | [stack trace](https://github.com/Rd4dev/Oppia-Android-Fork-from-Fork/actions/runs/10762876260/job/29843752198#step:19:26) - [x] shouldn't comment when no change | [reference1](https://github.com/Rd4dev/Oppia-Android-Fork-from-Fork/pull/40#issuecomment-2337237045) | [reference2](https://github.com/Rd4dev/Oppia-Android-Fork-from-Fork/pull/40#issuecomment-2337278404) - [x] should comment on change | [comment](https://github.com/Rd4dev/Oppia-Android-Fork-from-Fork/pull/40#issuecomment-2337251971) | [reference](https://github.com/Rd4dev/Oppia-Android-Fork-from-Fork/pull/40#issuecomment-2337260315) - [x] comment on previous build fail (replicated!) | [comment](https://github.com/Rd4dev/Oppia-Android-Fork-from-Fork/pull/40#issuecomment-2337297618) | [reference](https://github.com/Rd4dev/Oppia-Android-Fork-from-Fork/pull/40#issuecomment-2337310429) ## Essential Checklist - [x] The PR title and explanation each start with "Fix #bugnum: " (If this PR fixes part of an issue, prefix the title with "Fix part of #bugnum: ...".) - [x] Any changes to [scripts/assets](https://github.com/oppia/oppia-android/tree/develop/scripts/assets) files have their rationale included in the PR explanation. - [x] The PR follows the [style guide](https://github.com/oppia/oppia-android/wiki/Coding-style-guide). - [x] The PR does not contain any unnecessary code changes from Android Studio ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#undo-unnecessary-changes)). - [x] The PR is made from a branch that's **not** called "develop" and is up-to-date with "develop". - [x] The PR is **assigned** to the appropriate reviewers ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#clarification-regarding-assignees-and-reviewers-section)). --------- Co-authored-by: Ben Henning --- .github/workflows/build_tests.yml | 2 +- .github/workflows/main.yml | 10 +++--- .github/workflows/stats.yml | 60 ++++++++++++++++++++++++++++++- 3 files changed, 65 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build_tests.yml b/.github/workflows/build_tests.yml index 7dde621a72f..7a024172368 100644 --- a/.github/workflows/build_tests.yml +++ b/.github/workflows/build_tests.yml @@ -153,7 +153,7 @@ jobs: run: | cp $GITHUB_WORKSPACE/bazel-bin/oppia.apk /home/runner/work/oppia-android/oppia-android/ - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 with: name: oppia-bazel.apk path: /home/runner/work/oppia-android/oppia-android/oppia.apk diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 84cc12006a8..f5ba874970e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -64,7 +64,7 @@ jobs: # We require 'sudo' to avoid an error of the existing android sdk. See https://github.com/actions/starter-workflows/issues/58 run: sudo ./gradlew --full-stacktrace :utility:testDebugUnitTest -Dorg.gradle.java.home=$JAVA_HOME - name: Upload Utility Test Reports - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} # IMPORTANT: Upload reports regardless of success or failure status with: name: utility reports @@ -77,7 +77,7 @@ jobs: # We require 'sudo' to avoid an error of the existing android sdk. See https://github.com/actions/starter-workflows/issues/58 run: sudo ./gradlew --full-stacktrace :data:testDebugUnitTest -Dorg.gradle.java.home=$JAVA_HOME - name: Upload Data Test Reports - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} # IMPORTANT: Upload reports regardless of success or failure status with: name: data reports @@ -90,7 +90,7 @@ jobs: # We require 'sudo' to avoid an error of the existing android sdk. See https://github.com/actions/starter-workflows/issues/58 run: sudo ./gradlew --full-stacktrace :domain:testDebugUnitTest -Dorg.gradle.java.home=$JAVA_HOME - name: Upload Domain Test Reports - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} # IMPORTANT: Upload reports regardless of success or failure status with: name: domain reports @@ -103,7 +103,7 @@ jobs: # We require 'sudo' to avoid an error of the existing android sdk. See https://github.com/actions/starter-workflows/issues/58 run: sudo ./gradlew --full-stacktrace :testing:testDebugUnitTest -Dorg.gradle.java.home=$JAVA_HOME - name: Upload Testing Test Reports - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} # IMPORTANT: Upload reports regardless of success or failure status with: name: testing reports @@ -144,7 +144,7 @@ jobs: run: | sudo ./gradlew --full-stacktrace :app:testDebugUnitTest --${{ matrix.shard }} -Dorg.gradle.java.home=$JAVA_HOME - name: Upload App Test Reports - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} # IMPORTANT: Upload reports regardless of success or failure status with: name: app reports ${{ matrix.shard }} diff --git a/.github/workflows/stats.yml b/.github/workflows/stats.yml index de3ce5fadd5..74cbbeabbed 100644 --- a/.github/workflows/stats.yml +++ b/.github/workflows/stats.yml @@ -184,6 +184,63 @@ jobs: beta $(pwd)/oppia_beta_without_changes.aab $(pwd)/oppia_beta_with_changes.aab \ ga $(pwd)/oppia_ga_without_changes.aab $(pwd)/oppia_ga_with_changes.aab + - name: Find CI workflow run for PR + id: find-workflow-run + uses: actions/github-script@v7 + continue-on-error: true + with: + script: | + const { owner, repo } = context.repo; + const runsResponse = await github.rest.actions.listWorkflowRuns({ + owner, + repo, + workflow_id: 'stats.yml', + }); + + const runs = runsResponse.data.workflow_runs; + runs.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()); + + const run = runs[1]; + if(!run) { + core.setFailed('Could not find a succesful workflow run'); + return; + } + console.log(run.id); + + core.setOutput('run-id', run.id); + + - name: Download previous build summary + uses: actions/download-artifact@v4 + with: + name: brief_build_summary_${{ matrix.prInfo.number }} + path: ./previous_build_logs + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ steps.find-workflow-run.outputs.run-id }} + continue-on-error: true # Ignore errors if the file doesn't exist (first run) + + - name: Compare current build summary with the previous one + id: build-comparison + run: | + if [ -f ./develop/brief_build_summary.log ]; then + echo "Comparing current and previous build summaries..." + if diff ./develop/brief_build_summary.log ./previous_build_logs/brief_build_summary.log > /dev/null; then + echo "No changes detected; skipping comment." + echo "skip_comment=true" >> $GITHUB_ENV + else + echo "Changes detected; proceeding with the comment." + echo "skip_comment=false" >> $GITHUB_ENV + fi + else + echo "No previous summary found; proceeding with the comment." + echo "skip_comment=false" >> $GITHUB_ENV + fi + + - name: Upload current build summary for future comparison + uses: actions/upload-artifact@v4 + with: + name: brief_build_summary_${{ matrix.prInfo.number }} + path: ./develop/brief_build_summary.log + # Reference: https://github.com/peter-evans/create-or-update-comment#setting-the-comment-body-from-a-file. # Also, for multi-line env values, see: https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#multiline-strings. - name: Extract reports for uploading & commenting @@ -203,6 +260,7 @@ jobs: cp "$GITHUB_WORKSPACE/develop/full_build_summary.log" "$FULL_BUILD_SUMMARY_FILE_PATH" - name: Add build stats summary comment + if: ${{ env.skip_comment == 'false' }} env: PR_NUMBER: ${{ matrix.prInfo.number }} uses: peter-evans/create-or-update-comment@v1 @@ -210,7 +268,7 @@ jobs: issue-number: ${{ env.PR_NUMBER }} body: ${{ steps.compute-comment-body.outputs.comment_body }} - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 with: name: ${{ env.FULL_BUILD_SUMMARY_FILE_NAME }} path: ${{ env.FULL_BUILD_SUMMARY_FILE_PATH }} From 65197bfad9a11db12838222b4050f6818fe87b8c Mon Sep 17 00:00:00 2001 From: Subhajit Mallick <153619690+subhajitxyz@users.noreply.github.com> Date: Mon, 16 Sep 2024 16:04:19 +0530 Subject: [PATCH 07/10] Fix #5485 Create means for verifying Fragment Arguments (#5527) ## Explanation Fixes #5485 Added test for 10 fragments arguments and saveInstanceState. ## Essential Checklist - [x] The PR title and explanation each start with "Fix #bugnum: " (If this PR fixes part of an issue, prefix the title with "Fix part of #bugnum: ...".) - [ ] Any changes to [scripts/assets](https://github.com/oppia/oppia-android/tree/develop/scripts/assets) files have their rationale included in the PR explanation. - [x] The PR follows the [style guide](https://github.com/oppia/oppia-android/wiki/Coding-style-guide). - [x] The PR does not contain any unnecessary code changes from Android Studio ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#undo-unnecessary-changes)). - [x] The PR is made from a branch that's **not** called "develop" and is up-to-date with "develop". - [x] The PR is **assigned** to the appropriate reviewers ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#clarification-regarding-assignees-and-reviewers-section)). ## For UI-specific PRs only If your PR includes UI-related changes, then: - Add screenshots for portrait/landscape for both a tablet & phone of the before & after UI changes - For the screenshots above, include both English and pseudo-localized (RTL) screenshots (see [RTL guide](https://github.com/oppia/oppia-android/wiki/RTL-Guidelines)) - Add a video showing the full UX flow with a screen reader enabled (see [accessibility guide](https://github.com/oppia/oppia-android/wiki/Accessibility-A11y-Guide)) - For PRs introducing new UI elements or color changes, both light and dark mode screenshots must be included - Add a screenshot demonstrating that you ran affected Espresso tests locally & that they're passing --------- Co-authored-by: Mr. 17 --- .../app/options/ReadingTextSizeFragment.kt | 3 +- .../options/ReadingTextSizeFragmentTest.kt | 43 ++++++++++++ .../resumelesson/ResumeLessonFragmentTest.kt | 37 ++++++++++ .../profile/ProfileListFragmentTest.kt | 27 ++++++++ .../profile/ProfileRenameFragmentTest.kt | 26 +++++++ .../profile/ProfileResetPinFragmentTest.kt | 67 +++++++++++++++++++ .../app/spotlight/SpotlightFragmentTest.kt | 21 ++++++ .../android/app/story/StoryFragmentTest.kt | 42 ++++++++++++ .../android/app/survey/SurveyFragmentTest.kt | 29 ++++++++ .../ThirdPartyDependencyListFragmentTest.kt | 28 ++++++++ .../revisioncard/RevisionCardFragmentTest.kt | 43 ++++++++++++ 11 files changed, 365 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/oppia/android/app/options/ReadingTextSizeFragment.kt b/app/src/main/java/org/oppia/android/app/options/ReadingTextSizeFragment.kt index 1cdae8c8578..02c9a456e74 100644 --- a/app/src/main/java/org/oppia/android/app/options/ReadingTextSizeFragment.kt +++ b/app/src/main/java/org/oppia/android/app/options/ReadingTextSizeFragment.kt @@ -63,7 +63,8 @@ class ReadingTextSizeFragment : InjectableFragment(), TextSizeRadioButtonListene readingTextSizeFragmentPresenter.onTextSizeSelected(selectedTextSize) } - private fun retrieveFragmentArguments(): ReadingTextSizeFragmentArguments { + /** Returns the [ReadingTextSizeFragmentArguments] stored in the fragment's arguments. */ + fun retrieveFragmentArguments(): ReadingTextSizeFragmentArguments { return checkNotNull(arguments) { "Expected arguments to be passed to ReadingTextSizeFragment" }.getProto(FRAGMENT_ARGUMENTS_KEY, ReadingTextSizeFragmentArguments.getDefaultInstance()) 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 5e4b14be4aa..1883d2459bf 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 @@ -16,6 +16,7 @@ import androidx.test.espresso.matcher.ViewMatchers.isChecked import androidx.test.espresso.matcher.ViewMatchers.isRoot import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat import dagger.Component import org.hamcrest.Description import org.hamcrest.TypeSafeMatcher @@ -37,6 +38,7 @@ 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 +import org.oppia.android.app.model.ReadingTextSize.MEDIUM_TEXT_SIZE import org.oppia.android.app.model.ReadingTextSize.SMALL_TEXT_SIZE import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule import org.oppia.android.app.recyclerview.RecyclerViewMatcher.Companion.atPositionOnView @@ -194,6 +196,47 @@ class ReadingTextSizeFragmentTest { } } + @Test + fun testFragment_fragmentLoaded_verifyCorrectArgumentsPassed() { + launch(createReadingTextSizeActivityIntent()).use { scenario -> + testCoroutineDispatchers.runCurrent() + scenario.onActivity { activity -> + + val readingTextSizeFragment = activity.supportFragmentManager + .findFragmentById(R.id.reading_text_size_container) as ReadingTextSizeFragment + val receivedReadingTextSize = readingTextSizeFragment.retrieveFragmentArguments() + .readingTextSize + + assertThat(receivedReadingTextSize).isEqualTo(SMALL_TEXT_SIZE) + } + } + } + + @Test + fun testFragment_saveInstanceState_verifyCorrectStateRestored() { + launch(createReadingTextSizeActivityIntent()).use { scenario -> + testCoroutineDispatchers.runCurrent() + + scenario.onActivity { activity -> + val readingTextSizeFragment = activity.supportFragmentManager + .findFragmentById(R.id.reading_text_size_container) as ReadingTextSizeFragment + readingTextSizeFragment.readingTextSizeFragmentPresenter + .onTextSizeSelected(MEDIUM_TEXT_SIZE) + } + + scenario.recreate() + + scenario.onActivity { activity -> + val newReadingTextSizeFragment = activity.supportFragmentManager + .findFragmentById(R.id.reading_text_size_container) as ReadingTextSizeFragment + val restoredTopicIdList = + newReadingTextSizeFragment.readingTextSizeFragmentPresenter.getTextSizeSelected() + + assertThat(restoredTopicIdList).isEqualTo(MEDIUM_TEXT_SIZE) + } + } + } + private fun createReadingTextSizeActivityIntent() = ReadingTextSizeActivity.createReadingTextSizeActivityIntent(context, SMALL_TEXT_SIZE) 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 1a28e628594..33da1eeed36 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 @@ -41,6 +41,7 @@ import org.oppia.android.app.model.ExplorationActivityParams import org.oppia.android.app.model.ExplorationCheckpoint import org.oppia.android.app.model.ProfileId import org.oppia.android.app.model.ReadingTextSize +import org.oppia.android.app.model.ResumeLessonFragmentArguments import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule import org.oppia.android.app.shim.ViewBindingShimModule import org.oppia.android.app.translation.testing.ActivityRecreatorTestModule @@ -99,6 +100,7 @@ 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.extensions.getProto import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.EventLoggingConfigurationModule @@ -273,6 +275,41 @@ class ResumeLessonFragmentTest { } } + @Test + fun testFragment_fragmentLoaded_verifyCorrectArgumentsPassed() { + launch(createResumeLessonActivityIntent()).use { scenario -> + testCoroutineDispatchers.runCurrent() + scenario.onActivity { activity -> + + val resumeLessonFragment = activity.supportFragmentManager + .findFragmentById(R.id.resume_lesson_fragment_placeholder) as ResumeLessonFragment + val args = checkNotNull(resumeLessonFragment.arguments) { + "Expected arguments to be provided for fragment." + }.getProto( + ResumeLessonFragment.RESUME_LESSON_FRAGMENT_ARGUMENTS_KEY, + ResumeLessonFragmentArguments.getDefaultInstance() + ) + val receivedProfileId = args.profileId + val receivedClassroomId = args.classroomId + val receivedTopicId = args.topicId + val receivedStoryId = args.storyId + val receivedExplorationId = args.explorationId + val receivedParentScreen = args.parentScreen + val receivedCheckpoint = args.checkpoint + + assertThat(receivedProfileId) + .isEqualTo(ProfileId.newBuilder().apply { internalId = 1 }.build()) + assertThat(receivedClassroomId).isEqualTo(TEST_CLASSROOM_ID_1) + assertThat(receivedTopicId).isEqualTo(FRACTIONS_TOPIC_ID) + assertThat(receivedStoryId).isEqualTo(FRACTIONS_STORY_ID_0) + assertThat(receivedExplorationId).isEqualTo(FRACTIONS_EXPLORATION_ID_0) + assertThat(receivedParentScreen) + .isEqualTo(ExplorationActivityParams.ParentScreen.PARENT_SCREEN_UNSPECIFIED) + assertThat(receivedCheckpoint).isEqualTo(ExplorationCheckpoint.getDefaultInstance()) + } + } + } + private fun createResumeLessonActivityIntent(): Intent { return ResumeLessonActivity.createResumeLessonActivityIntent( context, 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 5b7bd78bb3e..742c20bd0f5 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 @@ -18,6 +18,7 @@ import androidx.test.espresso.matcher.ViewMatchers.isRoot import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat import dagger.Component import org.hamcrest.Matchers.not import org.junit.After @@ -37,6 +38,7 @@ import org.oppia.android.app.application.ApplicationStartupListenerModule import org.oppia.android.app.application.testing.TestingBuildFlavorModule import org.oppia.android.app.devoptions.DeveloperOptionsModule import org.oppia.android.app.devoptions.DeveloperOptionsStarterModule +import org.oppia.android.app.model.ProfileListFragmentArguments import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule import org.oppia.android.app.recyclerview.RecyclerViewMatcher.Companion.atPosition import org.oppia.android.app.recyclerview.RecyclerViewMatcher.Companion.atPositionOnView @@ -86,6 +88,7 @@ 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.extensions.getProto import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.EventLoggingConfigurationModule @@ -367,6 +370,30 @@ class ProfileListFragmentTest { } } + @Test + fun testFragment_fragmentLoaded_verifyCorrectArgumentsPassed() { + profileTestHelper.initializeProfiles() + launch(ProfileListActivity::class.java).use { scenario -> + testCoroutineDispatchers.runCurrent() + scenario.onActivity { activity -> + + val profileListFragment = activity.supportFragmentManager + .findFragmentById(R.id.profile_list_container) as ProfileListFragment + + val arguments = checkNotNull(profileListFragment.arguments) { + "Expected variables to be passed to ProfileListFragment" + } + val args = arguments.getProto( + ProfileListFragment.PROFILE_LIST_FRAGMENT_ARGUMENTS_KEY, + ProfileListFragmentArguments.getDefaultInstance() + ) + val receivedIsMultipane = args.isMultipane + + assertThat(receivedIsMultipane).isEqualTo(false) + } + } + } + // TODO(#59): Figure out a way to reuse modules instead of needing to re-declare them. @Singleton @Component( 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 fd4bf50712b..1fc5d9316d3 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 @@ -21,6 +21,7 @@ import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.rule.ActivityTestRule +import com.google.common.truth.Truth.assertThat import dagger.Component import org.hamcrest.CoreMatchers.not import org.hamcrest.core.AllOf.allOf @@ -102,6 +103,7 @@ import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule import org.oppia.android.util.parser.image.GlideImageLoaderModule import org.oppia.android.util.parser.image.ImageParsingModule +import org.oppia.android.util.profile.CurrentUserProfileIdIntentDecorator.extractCurrentUserProfileId import org.robolectric.annotation.Config import org.robolectric.annotation.LooperMode import javax.inject.Inject @@ -441,6 +443,30 @@ class ProfileRenameFragmentTest { } } + @Test + fun testFragment_fragmentLoaded_verifyCorrectArgumentsPassed() { + ActivityScenario.launch( + ProfileRenameActivity.createProfileRenameActivity( + context = context, + internalProfileId = 1 + ) + ).use { scenario -> + testCoroutineDispatchers.runCurrent() + scenario.onActivity { activity -> + + val profileRenameFragment = activity.supportFragmentManager + .findFragmentById(R.id.profile_rename_fragment_placeholder) as ProfileRenameFragment + val args = + checkNotNull(profileRenameFragment.arguments) { + "Expected arguments to be passed to ProfileRenameFragment" + } + val receivedProfileId = args.extractCurrentUserProfileId().internalId + + assertThat(receivedProfileId).isEqualTo(1) + } + } + } + // TODO(#59): Figure out a way to reuse modules instead of needing to re-declare them. @Singleton @Component( 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 c306e9a6206..c0e1541d8a3 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 @@ -22,6 +22,7 @@ import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.rule.ActivityTestRule +import com.google.common.truth.Truth.assertThat import dagger.Component import org.hamcrest.CoreMatchers.allOf import org.hamcrest.CoreMatchers.not @@ -42,6 +43,7 @@ import org.oppia.android.app.application.ApplicationStartupListenerModule import org.oppia.android.app.application.testing.TestingBuildFlavorModule import org.oppia.android.app.devoptions.DeveloperOptionsModule import org.oppia.android.app.devoptions.DeveloperOptionsStarterModule +import org.oppia.android.app.model.ProfileResetPinFragmentArguments import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule import org.oppia.android.app.shim.ViewBindingShimModule import org.oppia.android.app.translation.testing.ActivityRecreatorTestModule @@ -92,6 +94,7 @@ 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.extensions.getProto import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.EventLoggingConfigurationModule @@ -1006,6 +1009,70 @@ class ProfileResetPinFragmentTest { } } + @Test + fun testFragment_fragmentLoaded_verifyCorrectArgumentsPassed() { + ActivityScenario.launch( + ProfileResetPinActivity.createProfileResetPinActivity( + context = context, + profileId = 0, + isAdmin = true + ) + ).use { scenario -> + testCoroutineDispatchers.runCurrent() + scenario.onActivity { activity -> + + val profileResetPinFragment = activity.supportFragmentManager + .findFragmentById(R.id.profile_reset_pin_fragment_placeholder) as ProfileResetPinFragment + + val arguments = checkNotNull(profileResetPinFragment.arguments) { + "Expected arguments to be passed to ProfileResetPinFragment" + } + val args = + arguments.getProto( + ProfileResetPinFragment.PROFILE_RESET_PIN_FRAGMENT_ARGUMENTS_KEY, + ProfileResetPinFragmentArguments.getDefaultInstance() + ) + val receivedProfileResetPinProfileId = args.internalProfileId + val receivedProfileResetPinIsAdmin = args.isAdmin + + assertThat(receivedProfileResetPinProfileId).isEqualTo(0) + assertThat(receivedProfileResetPinIsAdmin).isEqualTo(true) + } + } + } + + @Test + fun testFragment_fragmentLoaded_whenIsAdminFalse_verifyCorrectArgumentsPassed() { + ActivityScenario.launch( + ProfileResetPinActivity.createProfileResetPinActivity( + context = context, + profileId = 0, + isAdmin = false + ) + ).use { scenario -> + testCoroutineDispatchers.runCurrent() + scenario.onActivity { activity -> + + val profileResetPinFragment = activity.supportFragmentManager + .findFragmentById(R.id.profile_reset_pin_fragment_placeholder) as ProfileResetPinFragment + + val arguments = checkNotNull(profileResetPinFragment.arguments) { + "Expected arguments to be passed to ProfileResetPinFragment" + } + val args = + arguments.getProto( + ProfileResetPinFragment.PROFILE_RESET_PIN_FRAGMENT_ARGUMENTS_KEY, + ProfileResetPinFragmentArguments.getDefaultInstance() + ) + val receivedProfileResetPinProfileId = args.internalProfileId + val receivedProfileResetPinIsAdmin = args.isAdmin + + assertThat(receivedProfileResetPinProfileId).isEqualTo(0) + assertThat(receivedProfileResetPinIsAdmin).isEqualTo(false) + } + } + } + // TODO(#59): Figure out a way to reuse modules instead of needing to re-declare them. @Singleton @Component( diff --git a/app/src/sharedTest/java/org/oppia/android/app/spotlight/SpotlightFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/spotlight/SpotlightFragmentTest.kt index 1e37adc5e68..9fb017111e3 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/spotlight/SpotlightFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/spotlight/SpotlightFragmentTest.kt @@ -14,6 +14,7 @@ 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 com.google.common.truth.Truth.assertThat import dagger.Component import org.junit.After import org.junit.Before @@ -93,6 +94,7 @@ 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.oppia.android.util.profile.CurrentUserProfileIdIntentDecorator.extractCurrentUserProfileId import org.robolectric.annotation.Config import org.robolectric.annotation.LooperMode import javax.inject.Inject @@ -339,6 +341,25 @@ class SpotlightFragmentTest { } } + @Test + fun testFragment_fragmentLoaded_verifyCorrectArgumentsPassed() { + TestPlatformParameterModule.forceEnableSpotlightUi(true) + launch( + createSpotlightFragmentTestActivity(context) + ).use { scenario -> + testCoroutineDispatchers.runCurrent() + + scenario.onActivity { activity -> + val spotlightFragment = activity.supportFragmentManager + .findFragmentByTag(SpotlightManager.SPOTLIGHT_FRAGMENT_TAG) as SpotlightFragment + val receivedInternalProfileId = spotlightFragment + .arguments?.extractCurrentUserProfileId()?.internalId ?: -1 + + assertThat(receivedInternalProfileId).isEqualTo(0) + } + } + } + private fun setUpTestApplicationComponent() { ApplicationProvider.getApplicationContext().inject(this) } 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 c93e0f8460a..c8013552937 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 @@ -67,6 +67,7 @@ import org.oppia.android.app.customview.LessonThumbnailImageView import org.oppia.android.app.devoptions.DeveloperOptionsModule import org.oppia.android.app.devoptions.DeveloperOptionsStarterModule import org.oppia.android.app.model.ProfileId +import org.oppia.android.app.model.StoryFragmentArguments import org.oppia.android.app.player.exploration.ExplorationActivity import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule import org.oppia.android.app.recyclerview.RecyclerViewMatcher.Companion.atPosition @@ -132,6 +133,7 @@ import org.oppia.android.util.accessibility.AccessibilityTestModule import org.oppia.android.util.accessibility.FakeAccessibilityService import org.oppia.android.util.caching.AssetModule import org.oppia.android.util.caching.testing.CachingTestModule +import org.oppia.android.util.extensions.getProto import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.EventLoggingConfigurationModule @@ -144,6 +146,7 @@ import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule import org.oppia.android.util.parser.image.ImageLoader import org.oppia.android.util.parser.image.ImageParsingModule import org.oppia.android.util.parser.image.ImageTransformation +import org.oppia.android.util.profile.CurrentUserProfileIdIntentDecorator.extractCurrentUserProfileId import org.robolectric.annotation.Config import org.robolectric.annotation.LooperMode import javax.inject.Inject @@ -772,6 +775,45 @@ class StoryFragmentTest { } } + @Test + fun testFragment_fragmentLoaded_verifyCorrectArgumentsPassed() { + launch(createFractionsStoryActivityIntent()).use { scenario -> + testCoroutineDispatchers.runCurrent() + + scenario.onActivity { activity -> + val storyFragment = activity.supportFragmentManager + .findFragmentById(R.id.story_fragment_placeholder) as StoryFragment + + val arguments = checkNotNull(storyFragment.arguments) { + "Expected arguments to be passed to StoryFragment." + } + val args = arguments.getProto( + StoryFragment.STORY_FRAGMENT_ARGUMENTS_KEY, + StoryFragmentArguments.getDefaultInstance() + ) + + val receivedInternalProfileId = arguments.extractCurrentUserProfileId().internalId + val receivedClassroomId = + checkNotNull(args.classroomId) { + "Expected classroomId to be passed to StoryFragment." + } + val receivedTopicId = + checkNotNull(args.topicId) { + "Expected topicId to be passed to StoryFragment." + } + val receivedStoryId = + checkNotNull(args.storyId) { + "Expected storyId to be passed to StoryFragment." + } + + assertThat(receivedInternalProfileId).isEqualTo(internalProfileId) + assertThat(receivedClassroomId).isEqualTo(TEST_CLASSROOM_ID_1) + assertThat(receivedTopicId).isEqualTo(FRACTIONS_TOPIC_ID) + assertThat(receivedStoryId).isEqualTo(FRACTIONS_STORY_ID_0) + } + } + } + @Config(qualifiers = "+sw600dp") @Test // TODO(#4212): Error -> No views in hierarchy found matching fun testStoryFragment_completedChapter_checkProgressDrawableIsCorrect() { diff --git a/app/src/sharedTest/java/org/oppia/android/app/survey/SurveyFragmentTest.kt b/app/src/sharedTest/java/org/oppia/android/app/survey/SurveyFragmentTest.kt index b600558c0da..332914c0c7c 100644 --- a/app/src/sharedTest/java/org/oppia/android/app/survey/SurveyFragmentTest.kt +++ b/app/src/sharedTest/java/org/oppia/android/app/survey/SurveyFragmentTest.kt @@ -48,6 +48,7 @@ import org.oppia.android.app.devoptions.DeveloperOptionsModule import org.oppia.android.app.devoptions.DeveloperOptionsStarterModule import org.oppia.android.app.model.ProfileId import org.oppia.android.app.model.ScreenName +import org.oppia.android.app.model.SurveyFragmentArguments import org.oppia.android.app.model.SurveyQuestionName import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule import org.oppia.android.app.recyclerview.RecyclerViewMatcher.Companion.atPositionOnView @@ -101,6 +102,7 @@ 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.extensions.getProto import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.CurrentAppScreenNameIntentDecorator.extractCurrentAppScreenName @@ -113,6 +115,7 @@ import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule import org.oppia.android.util.parser.image.GlideImageLoaderModule import org.oppia.android.util.parser.image.ImageParsingModule +import org.oppia.android.util.profile.CurrentUserProfileIdIntentDecorator.extractCurrentUserProfileId import org.robolectric.annotation.Config import org.robolectric.annotation.LooperMode import javax.inject.Inject @@ -535,6 +538,32 @@ class SurveyFragmentTest { } } + @Test + fun testFragment_fragmentLoaded_verifyCorrectArgumentsPassed() { + launch( + createSurveyActivityIntent() + ).use { scenario -> + testCoroutineDispatchers.runCurrent() + scenario.onActivity { activity -> + + val surveyFragment = activity.supportFragmentManager + .findFragmentById(R.id.survey_fragment_placeholder) as SurveyFragment + val args = surveyFragment.arguments!!.getProto( + SurveyFragment.SURVEY_FRAGMENT_ARGUMENTS_KEY, + SurveyFragmentArguments.getDefaultInstance() + ) + val receivedInternalProfileId = surveyFragment.arguments!! + .extractCurrentUserProfileId().internalId + val receivedTopicId = args.topicId!! + val receivedExplorationId = args.explorationId!! + + assertThat(receivedInternalProfileId).isEqualTo(0) + assertThat(receivedTopicId).isEqualTo(TEST_TOPIC_ID_0) + assertThat(receivedExplorationId).isEqualTo(TEST_EXPLORATION_ID_2) + } + } + } + private fun selectNpsAnswerAndMoveToNextQuestion(npsScore: Int) { onView( allOf( 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 89403f4efe2..08c49683c59 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 @@ -18,6 +18,7 @@ import androidx.test.espresso.matcher.ViewMatchers.isRoot import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat import dagger.Component import org.hamcrest.Matchers.allOf import org.junit.After @@ -39,6 +40,8 @@ import org.oppia.android.app.devoptions.DeveloperOptionsModule import org.oppia.android.app.devoptions.DeveloperOptionsStarterModule import org.oppia.android.app.help.thirdparty.LicenseListActivity import org.oppia.android.app.help.thirdparty.ThirdPartyDependencyListActivity +import org.oppia.android.app.help.thirdparty.ThirdPartyDependencyListFragment +import org.oppia.android.app.model.ThirdPartyDependencyListFragmentArguments import org.oppia.android.app.player.state.itemviewmodel.SplitScreenInteractionModule import org.oppia.android.app.recyclerview.RecyclerViewMatcher.Companion.atPosition import org.oppia.android.app.shim.ViewBindingShimModule @@ -86,6 +89,7 @@ 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.extensions.getProto import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.EventLoggingConfigurationModule @@ -447,6 +451,30 @@ class ThirdPartyDependencyListFragmentTest { } } + @Test + fun testFragment_fragmentLoaded_verifyCorrectArgumentsPassed() { + launch(ThirdPartyDependencyListActivity::class.java).use { scenario -> + testCoroutineDispatchers.runCurrent() + scenario.onActivity { activity -> + + val thirdPartyDependencyListFragment = activity.supportFragmentManager + .findFragmentById(R.id.third_party_dependency_list_fragment_placeholder) + as ThirdPartyDependencyListFragment + + val arguments = checkNotNull(thirdPartyDependencyListFragment.arguments) { + "Expected arguments to be passed to ThirdPartyDependencyListFragment" + } + val args = arguments.getProto( + "ThirdPartyDependencyListFragment.arguments", + ThirdPartyDependencyListFragmentArguments.getDefaultInstance() + ) + val receivedIsMultipane = args?.isMultipane ?: false + + assertThat(receivedIsMultipane).isEqualTo(false) + } + } + } + private fun retrieveDependencyName(id: Int): String { return ApplicationProvider.getApplicationContext() .resources.getString(id) 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 29dde65b725..8be20c6dc55 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 @@ -26,6 +26,7 @@ import androidx.test.espresso.matcher.ViewMatchers.isRoot import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat import dagger.Component import dagger.Module import dagger.Provides @@ -58,6 +59,7 @@ import org.oppia.android.app.model.OptionsActivityParams import org.oppia.android.app.model.ProfileId import org.oppia.android.app.model.ReadingTextSize import org.oppia.android.app.model.RevisionCardActivityParams +import org.oppia.android.app.model.RevisionCardFragmentArguments import org.oppia.android.app.model.WrittenTranslationLanguageSelection import org.oppia.android.app.options.OptionsActivity import org.oppia.android.app.player.exploration.ExplorationActivity @@ -122,6 +124,7 @@ import org.oppia.android.util.accessibility.AccessibilityTestModule import org.oppia.android.util.caching.AssetModule import org.oppia.android.util.caching.LoadImagesFromAssets import org.oppia.android.util.caching.LoadLessonProtosFromAssets +import org.oppia.android.util.extensions.getProto import org.oppia.android.util.gcsresource.GcsResourceModule import org.oppia.android.util.locale.LocaleProdModule import org.oppia.android.util.logging.EventLoggingConfigurationModule @@ -133,6 +136,7 @@ import org.oppia.android.util.networking.NetworkConnectionUtilDebugModule import org.oppia.android.util.parser.html.HtmlParserEntityTypeModule import org.oppia.android.util.parser.image.GlideImageLoaderModule import org.oppia.android.util.parser.image.ImageParsingModule +import org.oppia.android.util.profile.CurrentUserProfileIdIntentDecorator.extractCurrentUserProfileId import org.robolectric.annotation.Config import org.robolectric.annotation.LooperMode import javax.inject.Inject @@ -810,6 +814,45 @@ class RevisionCardFragmentTest { } } + @Test + fun testFragment_fragmentLoaded_verifyCorrectArgumentsPassed() { + launch( + createRevisionCardActivityIntent( + context, + profileId.internalId, + FRACTIONS_TOPIC_ID, + subtopicId = 2, + FRACTIONS_SUBTOPIC_LIST_SIZE + ) + ).use { scenario -> + testCoroutineDispatchers.runCurrent() + scenario.onActivity { activity -> + + val revisionCardFragment = activity.supportFragmentManager + .findFragmentById(R.id.revision_card_fragment_placeholder) as RevisionCardFragment + val arguments = checkNotNull(revisionCardFragment.arguments) { + "Expected arguments to be passed to StoryFragment" + } + val args = arguments.getProto( + RevisionCardFragment.REVISION_CARD_FRAGMENT_ARGUMENTS_KEY, + RevisionCardFragmentArguments.getDefaultInstance() + ) + val receivedTopicId = + checkNotNull(args?.topicId) { + "Expected topicId to be passed to RevisionCardFragment" + } + val receivedSubtopicId = args?.subtopicId ?: -1 + val receivedProfileId = arguments.extractCurrentUserProfileId() + val receivedSubtopicListSize = args?.subtopicListSize ?: -1 + + assertThat(receivedTopicId).isEqualTo(FRACTIONS_TOPIC_ID) + assertThat(receivedSubtopicId).isEqualTo(2) + assertThat(receivedProfileId).isEqualTo(profileId) + assertThat(receivedSubtopicListSize).isEqualTo(FRACTIONS_SUBTOPIC_LIST_SIZE) + } + } + } + /** See the version in StateFragmentTest for documentation details. */ @Suppress("SameParameterValue") private fun openClickableSpan(text: String): ViewAction { From aadc1a1d944d92beed0c7532d2fc0d60b5644d37 Mon Sep 17 00:00:00 2001 From: "Mr. 17" Date: Mon, 16 Sep 2024 17:40:16 +0530 Subject: [PATCH 08/10] Fix #5344: Remove temporary functions from TopicListController (#5528) ## Explanation Fixes #5344 This PR surfaces the `getClassrooms` and `getClassroomById` functions from the `ClassroomController`, adds tests to ensure their correctness, and refactors the `TopicListController` to replace temporary functions with these new implementations. ## Essential Checklist - [x] The PR title and explanation each start with "Fix #bugnum: " (If this PR fixes part of an issue, prefix the title with "Fix part of #bugnum: ...".) - [x] Any changes to [scripts/assets](https://github.com/oppia/oppia-android/tree/develop/scripts/assets) files have their rationale included in the PR explanation. - [x] The PR follows the [style guide](https://github.com/oppia/oppia-android/wiki/Coding-style-guide). - [x] The PR does not contain any unnecessary code changes from Android Studio ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#undo-unnecessary-changes)). - [x] The PR is made from a branch that's **not** called "develop" and is up-to-date with "develop". - [x] The PR is **assigned** to the appropriate reviewers ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#clarification-regarding-assignees-and-reviewers-section)). ## For UI-specific PRs only If your PR includes UI-related changes, then: - Add screenshots for portrait/landscape for both a tablet & phone of the before & after UI changes - For the screenshots above, include both English and pseudo-localized (RTL) screenshots (see [RTL guide](https://github.com/oppia/oppia-android/wiki/RTL-Guidelines)) - Add a video showing the full UX flow with a screen reader enabled (see [accessibility guide](https://github.com/oppia/oppia-android/wiki/Accessibility-A11y-Guide)) - For PRs introducing new UI elements or color changes, both light and dark mode screenshots must be included - Add a screenshot demonstrating that you ran affected Espresso tests locally & that they're passing --- .../domain/classroom/ClassroomController.kt | 56 +++++--- .../domain/topic/TopicListController.kt | 133 +++--------------- .../classroom/ClassroomControllerTest.kt | 17 +++ 3 files changed, 69 insertions(+), 137 deletions(-) diff --git a/domain/src/main/java/org/oppia/android/domain/classroom/ClassroomController.kt b/domain/src/main/java/org/oppia/android/domain/classroom/ClassroomController.kt index 20cc3ee832e..dc99f75c5ce 100644 --- a/domain/src/main/java/org/oppia/android/domain/classroom/ClassroomController.kt +++ b/domain/src/main/java/org/oppia/android/domain/classroom/ClassroomController.kt @@ -72,6 +72,32 @@ class ClassroomController @Inject constructor( ) } + /** + * Returns the list of [ClassroomRecord]s currently available in the app. + */ + fun getClassrooms(): List { + return if (loadLessonProtosFromAssets) { + assetRepository.loadProtoFromLocalAssets( + assetName = "classrooms", + baseMessage = ClassroomIdList.getDefaultInstance() + ).classroomIdsList.map { classroomId -> + getClassroomById(classroomId) + } + } else loadClassroomsFromJson() + } + + /** + * Returns the [ClassroomRecord] associated with the given [classroomId]. + */ + fun getClassroomById(classroomId: String): ClassroomRecord { + return if (loadLessonProtosFromAssets) { + assetRepository.tryLoadProtoFromLocalAssets( + assetName = classroomId, + defaultMessage = ClassroomRecord.getDefaultInstance() + ) ?: ClassroomRecord.getDefaultInstance() + } else loadClassroomByIdFromJson(classroomId) + } + /** * Returns the list of [TopicSummary]s currently tracked by the app, possibly up to * [EVICTION_TIME_MILLIS] old. @@ -90,7 +116,7 @@ class ClassroomController @Inject constructor( */ fun getClassroomIdByTopicId(topicId: String): String { var classroomId = "" - loadClassrooms().forEach { + getClassrooms().forEach { if (it.topicPrerequisitesMap.keys.contains(topicId)) { classroomId = it.id } @@ -333,17 +359,6 @@ class ClassroomController @Inject constructor( .build() } - private fun loadClassrooms(): List { - return if (loadLessonProtosFromAssets) { - assetRepository.loadProtoFromLocalAssets( - assetName = "classrooms", - baseMessage = ClassroomIdList.getDefaultInstance() - ).classroomIdsList.map { classroomId -> - loadClassroomById(classroomId) - } - } else loadClassroomsFromJson() - } - private fun loadClassroomsFromJson(): List { // Load the classrooms.json file. val classroomIdsObj = jsonAssetRetriever.loadJsonFromAsset("classrooms.json") @@ -359,27 +374,20 @@ class ClassroomController @Inject constructor( val classroomId = checkNotNull(classroomIds.optString(i)) { "Expected non-null classroom ID at index $i." } - val classroomRecord = loadClassroomById(classroomId) + val classroomRecord = getClassroomById(classroomId) classroomRecords.add(classroomRecord) } return classroomRecords } - private fun loadClassroomById(classroomId: String): ClassroomRecord { - return if (loadLessonProtosFromAssets) { - assetRepository.tryLoadProtoFromLocalAssets( - assetName = classroomId, - defaultMessage = ClassroomRecord.getDefaultInstance() - ) ?: ClassroomRecord.getDefaultInstance() - } else loadClassroomByIdFromJson(classroomId) - } - private fun loadClassroomByIdFromJson(classroomId: String): ClassroomRecord { // Load the classroom obj. val classroomObj = jsonAssetRetriever.loadJsonFromAsset("$classroomId.json") checkNotNull(classroomObj) { "Failed to load $classroomId.json." } + val classroomTitle = classroomObj.getJSONObject("classroom_title") + // Load the topic prerequisite map. val topicPrereqsObj = checkNotNull(classroomObj.optJSONObject("topic_prerequisites")) { "Expected classroom to have non-null topic_prerequisites." @@ -398,6 +406,10 @@ class ClassroomController @Inject constructor( id = checkNotNull(classroomObj.optString("classroom_id")) { "Expected classroom to have ID." } + translatableTitle = SubtitledHtml.newBuilder().apply { + contentId = classroomTitle.getStringFromObject("content_id") + html = classroomTitle.getStringFromObject("html") + }.build() putAllTopicPrerequisites( topicPrereqs.mapValues { (_, topicIds) -> ClassroomRecord.TopicIdList.newBuilder().apply { diff --git a/domain/src/main/java/org/oppia/android/domain/topic/TopicListController.kt b/domain/src/main/java/org/oppia/android/domain/topic/TopicListController.kt index 4fa2ed9edef..b82f8f56f0f 100644 --- a/domain/src/main/java/org/oppia/android/domain/topic/TopicListController.kt +++ b/domain/src/main/java/org/oppia/android/domain/topic/TopicListController.kt @@ -5,9 +5,7 @@ import org.json.JSONObject import org.oppia.android.app.model.ChapterPlayState import org.oppia.android.app.model.ChapterProgress import org.oppia.android.app.model.ChapterSummary -import org.oppia.android.app.model.ClassroomIdList import org.oppia.android.app.model.ClassroomRecord -import org.oppia.android.app.model.ClassroomRecord.TopicIdList import org.oppia.android.app.model.ComingSoonTopicList import org.oppia.android.app.model.EphemeralTopicSummary import org.oppia.android.app.model.LessonThumbnail @@ -29,7 +27,7 @@ import org.oppia.android.app.model.TopicProgress import org.oppia.android.app.model.TopicRecord import org.oppia.android.app.model.TopicSummary import org.oppia.android.app.model.UpcomingTopic -import org.oppia.android.domain.classroom.TEST_CLASSROOM_ID_0 +import org.oppia.android.domain.classroom.ClassroomController import org.oppia.android.domain.translation.TranslationController import org.oppia.android.domain.util.JsonAssetRetriever import org.oppia.android.domain.util.getStringFromObject @@ -101,6 +99,7 @@ class TopicListController @Inject constructor( private val oppiaClock: OppiaClock, private val assetRepository: AssetRepository, private val translationController: TranslationController, + private val classroomController: ClassroomController, @LoadLessonProtosFromAssets private val loadLessonProtosFromAssets: Boolean ) { @@ -137,7 +136,7 @@ class TopicListController @Inject constructor( private fun createTopicList(contentLocale: OppiaLocale.ContentLocale): TopicList { return if (loadLessonProtosFromAssets) { - val topicIdList = loadCombinedClassroomsTopicIdList() + val topicIdList = loadCombinedTopicIdList() return TopicList.newBuilder().apply { // Only include topics currently playable in the topic list. addAllTopicSummary( @@ -152,7 +151,7 @@ class TopicListController @Inject constructor( } private fun loadTopicListFromJson(contentLocale: OppiaLocale.ContentLocale): TopicList { - val topicIdList = loadCombinedClassroomsTopicIdList() + val topicIdList = loadCombinedTopicIdList() val topicListBuilder = TopicList.newBuilder() for (topicId in topicIdList) { val ephemeralSummary = createEphemeralTopicSummary(topicId, contentLocale) @@ -166,7 +165,7 @@ class TopicListController @Inject constructor( } private fun computeComingSoonTopicList(): ComingSoonTopicList { - val topicIdList = loadCombinedClassroomsTopicIdList() + val topicIdList = loadCombinedTopicIdList() val comingSoonTopicListBuilder = ComingSoonTopicList.newBuilder() for (topicId in topicIdList) { val upcomingTopicSummary = createUpcomingTopicSummary(topicId) @@ -185,7 +184,7 @@ class TopicListController @Inject constructor( contentLocale: OppiaLocale.ContentLocale ): EphemeralTopicSummary { val topicSummary = createTopicSummary(topicId) - val classroomRecord = loadClassroomById(topicSummary.classroomId) + val classroomRecord = classroomController.getClassroomById(topicSummary.classroomId) return EphemeralTopicSummary.newBuilder().apply { this.topicSummary = topicSummary writtenTranslationContext = @@ -217,7 +216,7 @@ class TopicListController @Inject constructor( this.topicId = topicId putAllWrittenTranslations(topicRecord.writtenTranslationsMap) title = topicRecord.translatableTitle - classroomId = getClassroomIdByTopicId(topicId) + classroomId = classroomController.getClassroomIdByTopicId(topicId) totalChapterCount = storyRecords.map { it.chaptersList.size }.sum() topicThumbnail = topicRecord.topicThumbnail topicPlayAvailability = if (topicRecord.isPublished) { @@ -259,7 +258,7 @@ class TopicListController @Inject constructor( contentId = "title" html = jsonObject.getStringFromObject("topic_name") }.build() - val classroomId = getClassroomIdByTopicId(topicId) + val classroomId = classroomController.getClassroomIdByTopicId(topicId) // No written translations are included since none are retrieved from JSON. return TopicSummary.newBuilder() .setTopicId(topicId) @@ -296,7 +295,7 @@ class TopicListController @Inject constructor( html = jsonObject.getStringFromObject("topic_name") }.build() - val classroomId = getClassroomIdByTopicId(topicId) + val classroomId = classroomController.getClassroomIdByTopicId(topicId) val classroomJsonObject = jsonAssetRetriever.loadJsonFromAsset("$classroomId.json")!! val classroomTitle = classroomJsonObject.getJSONObject("classroom_title").let { @@ -369,8 +368,8 @@ class TopicListController @Inject constructor( sortedTopicProgressList.forEach { topicProgress -> val topic = topicController.retrieveTopic(topicProgress.topicId) val classroom = topic?.topicId?.let { topicId -> - val classroomId = getClassroomIdByTopicId(topicId) - loadClassroomById(classroomId) + val classroomId = classroomController.getClassroomIdByTopicId(topicId) + classroomController.getClassroomById(classroomId) } ?: ClassroomRecord.getDefaultInstance() // Ignore topics that are no longer on the device, or that have been unpublished. if (topic?.topicPlayAvailability?.availabilityCase == AVAILABLE_TO_PLAY_NOW) { @@ -556,7 +555,7 @@ class TopicListController @Inject constructor( * being suggested. */ private fun retrieveTopicDependencies(topicId: String): List { - val classrooms = loadClassrooms() + val classrooms = classroomController.getClassrooms() for (classroom in classrooms) { if (classroom.topicPrerequisitesMap.containsKey(topicId)) { return classroom.topicPrerequisitesMap.getValue(topicId).topicIdsList @@ -589,7 +588,7 @@ class TopicListController @Inject constructor( contentLocale: OppiaLocale.ContentLocale ): List { return if (loadLessonProtosFromAssets) { - val topicIdList = loadCombinedClassroomsTopicIdList() + val topicIdList = loadCombinedTopicIdList() return computeSuggestedStoriesForTopicIds(topicProgressList, topicIdList, contentLocale) } else computeSuggestedStoriesFromJson(topicProgressList, contentLocale) } @@ -599,7 +598,7 @@ class TopicListController @Inject constructor( contentLocale: OppiaLocale.ContentLocale ): List { // All topics that could potentially be recommended. - val topicIdList = loadCombinedClassroomsTopicIdList() + val topicIdList = loadCombinedTopicIdList() return computeSuggestedStoriesForTopicIds(topicProgressList, topicIdList, contentLocale) } @@ -713,7 +712,7 @@ class TopicListController @Inject constructor( ) val classroomRecord = assetRepository.loadProtoFromLocalAssets( - assetName = getClassroomIdByTopicId(topicId), + assetName = classroomController.getClassroomIdByTopicId(topicId), baseMessage = ClassroomRecord.getDefaultInstance() ) return PromotedStory.newBuilder().apply { @@ -783,7 +782,7 @@ class TopicListController @Inject constructor( }.build() } ?: SubtitledHtml.getDefaultInstance() - val classroomId = getClassroomIdByTopicId(topicId) + val classroomId = classroomController.getClassroomIdByTopicId(topicId) val classroomJson = jsonAssetRetriever.loadJsonFromAsset("$classroomId.json") if (classroomJson!!.optString("classroom_title").isNullOrEmpty()) return null @@ -867,104 +866,8 @@ class TopicListController @Inject constructor( .build() } - private fun getClassroomIdByTopicId(topicId: String): String { - var classroomId = TEST_CLASSROOM_ID_0 - loadClassrooms().forEach { - if (it.topicPrerequisitesMap.keys.contains(topicId)) { - classroomId = it.id - } - } - return classroomId - } - - // TODO(#5344): Remove this in favor of per-classroom data handling. - private fun loadClassrooms(): List { - return if (loadLessonProtosFromAssets) { - assetRepository.loadProtoFromLocalAssets( - assetName = "classrooms", - baseMessage = ClassroomIdList.getDefaultInstance() - ).classroomIdsList.map { classroomId -> - loadClassroomById(classroomId) - } - } else loadClassroomsFromJson() - } - - // TODO(#5344): Remove this in favor of per-classroom data handling. - private fun loadClassroomsFromJson(): List { - // Load the classrooms.json file. - val classroomIdsObj = jsonAssetRetriever.loadJsonFromAsset("classrooms.json") - checkNotNull(classroomIdsObj) { "Failed to load classrooms.json." } - val classroomIds = classroomIdsObj.optJSONArray("classroom_id_list") - checkNotNull(classroomIds) { "classrooms.json is missing classroom IDs." } - - // Initialize a list to store the [ClassroomRecord]s. - val classroomRecords = mutableListOf() - - // Iterate over all classroomIds and load each classroom's JSON. - for (i in 0 until classroomIds.length()) { - val classroomId = checkNotNull(classroomIds.optString(i)) { - "Expected non-null classroom ID at index $i." - } - val classroomRecord = loadClassroomById(classroomId) - classroomRecords.add(classroomRecord) - } - - return classroomRecords - } - - // TODO(#5344): Move this to classroom controller. - private fun loadClassroomById(classroomId: String): ClassroomRecord { - return if (loadLessonProtosFromAssets) { - assetRepository.tryLoadProtoFromLocalAssets( - assetName = classroomId, - defaultMessage = ClassroomRecord.getDefaultInstance() - ) ?: ClassroomRecord.getDefaultInstance() - } else loadClassroomByIdFromJson(classroomId) - } - - // TODO(#5344): Remove this in favor of per-classroom data handling. - private fun loadClassroomByIdFromJson(classroomId: String): ClassroomRecord { - // Load the classroom obj. - val classroomObj = jsonAssetRetriever.loadJsonFromAsset("$classroomId.json") - checkNotNull(classroomObj) { "Failed to load $classroomId.json." } - - val classroomTitle = classroomObj.getJSONObject("classroom_title") - - // Load the topic prerequisite map. - val topicPrereqsObj = checkNotNull(classroomObj.optJSONObject("topic_prerequisites")) { - "Expected classroom to have non-null topic_prerequisites." - } - val topicPrereqs = topicPrereqsObj.keys().asSequence().associateWith { topicId -> - val topicIdArray = checkNotNull(topicPrereqsObj.optJSONArray(topicId)) { - "Expected topic $topicId to have a non-null string list." - } - return@associateWith List(topicIdArray.length()) { index -> - checkNotNull(topicIdArray.optString(index)) { - "Expected topic $topicId to have non-null string at index $index." - } - } - } - return ClassroomRecord.newBuilder().apply { - id = checkNotNull(classroomObj.optString("classroom_id")) { - "Expected classroom to have ID." - } - translatableTitle = SubtitledHtml.newBuilder().apply { - contentId = classroomTitle.getStringFromObject("content_id") - html = classroomTitle.getStringFromObject("html") - }.build() - putAllTopicPrerequisites( - topicPrereqs.mapValues { (_, topicIds) -> - TopicIdList.newBuilder().apply { - addAllTopicIds(topicIds) - }.build() - } - ) - }.build() - } - - // TODO(#5344): Remove this in favor of per-classroom data handling. - private fun loadCombinedClassroomsTopicIdList(): List = - loadClassrooms().flatMap { it.topicPrerequisitesMap.keys.toList() } + private fun loadCombinedTopicIdList(): List = + classroomController.getClassrooms().flatMap { it.topicPrerequisitesMap.keys.toList() } } internal fun createTopicThumbnailFromJson(topicJsonObject: JSONObject): LessonThumbnail { diff --git a/domain/src/test/java/org/oppia/android/domain/classroom/ClassroomControllerTest.kt b/domain/src/test/java/org/oppia/android/domain/classroom/ClassroomControllerTest.kt index 24129abc307..6c1cee650bf 100644 --- a/domain/src/test/java/org/oppia/android/domain/classroom/ClassroomControllerTest.kt +++ b/domain/src/test/java/org/oppia/android/domain/classroom/ClassroomControllerTest.kt @@ -131,6 +131,23 @@ class ClassroomControllerTest { assertThat(classroomList.classroomSummaryList.size).isEqualTo(2) } + @Test + fun testGetClassrooms_returnsAllClassrooms() { + val classrooms = classroomController.getClassrooms() + + assertThat(classrooms[0].id).isEqualTo(TEST_CLASSROOM_ID_0) + assertThat(classrooms[1].id).isEqualTo(TEST_CLASSROOM_ID_1) + assertThat(classrooms[2].id).isEqualTo(TEST_CLASSROOM_ID_2) + } + + @Test + fun testGetClassroomById_hasCorrectClassroomInfo() { + val classroom = classroomController.getClassroomById(TEST_CLASSROOM_ID_0) + + assertThat(classroom.id).isEqualTo(TEST_CLASSROOM_ID_0) + assertThat(classroom.translatableTitle.html).isEqualTo("Science") + } + @Test fun testRetrieveTopicList_isSuccessful() { val topicListProvider = classroomController.getTopicList(profileId0, TEST_CLASSROOM_ID_0) From dfb9a301280b9a46526cb2f5ca6329532fec6bf0 Mon Sep 17 00:00:00 2001 From: Sneha Datta Date: Tue, 17 Sep 2024 21:18:11 +0530 Subject: [PATCH 09/10] Fix #3842: Remove second/millisecond conversion check for greeting timestamp (#5536) ## Explanation Fixes #3842 This PR removes the `ensureTimestampIsInMilliseconds` function from `TextViewBindingAdapters`, eliminating the need for the second-to-millisecond conversion check for greeting timestamps. ### Sreenshots |Before|After| |--|--| |![WhatsApp Image 2024-09-17 at 11 36 52 AM](https://github.com/user-attachments/assets/0978ffe5-9457-4071-8828-a2791e7e4664)|![WhatsApp Image 2024-09-17 at 11 39 41 AM](https://github.com/user-attachments/assets/5c41cdc1-5287-4979-8969-4057362434c9)| ## Essential Checklist - [x] The PR title and explanation each start with "Fix #bugnum: " (If this PR fixes part of an issue, prefix the title with "Fix part of #bugnum: ...".) - [x] Any changes to [scripts/assets](https://github.com/oppia/oppia-android/tree/develop/scripts/assets) files have their rationale included in the PR explanation. - [x] The PR follows the [style guide](https://github.com/oppia/oppia-android/wiki/Coding-style-guide). - [x] The PR does not contain any unnecessary code changes from Android Studio ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#undo-unnecessary-changes)). - [x] The PR is made from a branch that's **not** called "develop" and is up-to-date with "develop". - [x] The PR is **assigned** to the appropriate reviewers ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#clarification-regarding-assignees-and-reviewers-section)). ## For UI-specific PRs only If your PR includes UI-related changes, then: - Add screenshots for portrait/landscape for both a tablet & phone of the before & after UI changes - For the screenshots above, include both English and pseudo-localized (RTL) screenshots (see [RTL guide](https://github.com/oppia/oppia-android/wiki/RTL-Guidelines)) - Add a video showing the full UX flow with a screen reader enabled (see [accessibility guide](https://github.com/oppia/oppia-android/wiki/Accessibility-A11y-Guide)) - For PRs introducing new UI elements or color changes, both light and dark mode screenshots must be included - Add a screenshot demonstrating that you ran affected Espresso tests locally & that they're passing --- .../app/databinding/TextViewBindingAdapters.java | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/org/oppia/android/app/databinding/TextViewBindingAdapters.java b/app/src/main/java/org/oppia/android/app/databinding/TextViewBindingAdapters.java index e4584d7cad1..284e1332b2d 100644 --- a/app/src/main/java/org/oppia/android/app/databinding/TextViewBindingAdapters.java +++ b/app/src/main/java/org/oppia/android/app/databinding/TextViewBindingAdapters.java @@ -68,15 +68,14 @@ public static void setDrawableEndCompat( } private static String getTimeAgo(View view, long lastVisitedTimestamp) { - long timeStampMillis = ensureTimestampIsInMilliseconds(lastVisitedTimestamp); long currentTimeMillis = getOppiaClock(view).getCurrentTimeMs(); AppLanguageResourceHandler resourceHandler = getResourceHandler(view); - if (timeStampMillis > currentTimeMillis || timeStampMillis <= 0) { + if (lastVisitedTimestamp > currentTimeMillis || lastVisitedTimestamp <= 0) { return resourceHandler.getStringInLocale(R.string.last_logged_in_recently); } - long timeDifferenceMillis = currentTimeMillis - timeStampMillis; + long timeDifferenceMillis = currentTimeMillis - lastVisitedTimestamp; if (timeDifferenceMillis < (int) TimeUnit.MINUTES.toMillis(1)) { return resourceHandler.getStringInLocale(R.string.just_now); @@ -112,15 +111,6 @@ private static String getPluralString( ); } - private static long ensureTimestampIsInMilliseconds(long lastVisitedTimestamp) { - // TODO(#3842): Investigate & remove this check. - if (lastVisitedTimestamp < 1000000000000L) { - // If timestamp is given in seconds, convert that to milliseconds. - return TimeUnit.SECONDS.toMillis(lastVisitedTimestamp); - } - return lastVisitedTimestamp; - } - private static AppLanguageResourceHandler getResourceHandler(View view) { AppLanguageActivityInjectorProvider provider = (AppLanguageActivityInjectorProvider) getAttachedActivity(view); From 8d44bf7b006aae8fbd0bab5db609ec76cd481151 Mon Sep 17 00:00:00 2001 From: Sneha Datta Date: Thu, 19 Sep 2024 14:05:17 +0530 Subject: [PATCH 10/10] Fix part of #5404: Migrate away from onBackPressed (#5521) ## Explanation Fixes part of #5404 This PR migrates deprecated `onBackPressed` usage to `OnBackPressedDispatcher` callback in the following activities and presenters. - AdministratorControlsActivity - AppVersionActivity - ProfileAndDeviceIdActivity - MarkChaptersCompletedActivity - MarkStoriesCompletedActivity - MarkTopicsCompletedActivity - ReadingTextSizeActivityPresenter - ExplorationActivityPresenter - ResumeLessonActivityPresenter ## Essential Checklist - [x] The PR title and explanation each start with "Fix #bugnum: " (If this PR fixes part of an issue, prefix the title with "Fix part of #bugnum: ...".) - [x] Any changes to [scripts/assets](https://github.com/oppia/oppia-android/tree/develop/scripts/assets) files have their rationale included in the PR explanation. - [x] The PR follows the [style guide](https://github.com/oppia/oppia-android/wiki/Coding-style-guide). - [x] The PR does not contain any unnecessary code changes from Android Studio ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#undo-unnecessary-changes)). - [x] The PR is made from a branch that's **not** called "develop" and is up-to-date with "develop". - [x] The PR is **assigned** to the appropriate reviewers ([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#clarification-regarding-assignees-and-reviewers-section)). ## For UI-specific PRs only If your PR includes UI-related changes, then: - Add screenshots for portrait/landscape for both a tablet & phone of the before & after UI changes - For the screenshots above, include both English and pseudo-localized (RTL) screenshots (see [RTL guide](https://github.com/oppia/oppia-android/wiki/RTL-Guidelines)) - Add a video showing the full UX flow with a screen reader enabled (see [accessibility guide](https://github.com/oppia/oppia-android/wiki/Accessibility-A11y-Guide)) - For PRs introducing new UI elements or color changes, both light and dark mode screenshots must be included - Add a screenshot demonstrating that you ran affected Espresso tests locally & that they're passing --------- Co-authored-by: Adhiambo Peres <59600948+adhiamboperes@users.noreply.github.com> --- .../AdministratorControlsActivity.kt | 15 ++++++++-- .../appversion/AppVersionActivity.kt | 13 +++++++-- .../ProfileAndDeviceIdActivity.kt | 16 +++++++++-- .../MarkChaptersCompletedActivity.kt | 13 +++++++-- .../MarkStoriesCompletedActivity.kt | 13 +++++++-- .../MarkTopicsCompletedActivity.kt | 13 +++++++-- .../app/options/ReadingTextSizeActivity.kt | 28 +++++++++++-------- .../ReadingTextSizeActivityPresenter.kt | 3 +- .../player/exploration/ExplorationActivity.kt | 16 +++++++---- .../ExplorationActivityPresenter.kt | 3 +- .../app/resumelesson/ResumeLessonActivity.kt | 15 ++++++---- .../ResumeLessonActivityPresenter.kt | 3 +- 12 files changed, 110 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/org/oppia/android/app/administratorcontrols/AdministratorControlsActivity.kt b/app/src/main/java/org/oppia/android/app/administratorcontrols/AdministratorControlsActivity.kt index 2cda2286d02..64beda78f53 100644 --- a/app/src/main/java/org/oppia/android/app/administratorcontrols/AdministratorControlsActivity.kt +++ b/app/src/main/java/org/oppia/android/app/administratorcontrols/AdministratorControlsActivity.kt @@ -3,6 +3,7 @@ package org.oppia.android.app.administratorcontrols import android.content.Context import android.content.Intent import android.os.Bundle +import androidx.activity.OnBackPressedCallback import org.oppia.android.R import org.oppia.android.app.activity.ActivityComponentImpl import org.oppia.android.app.activity.InjectableAutoLocalizedAppCompatActivity @@ -83,6 +84,15 @@ class AdministratorControlsActivity : isProfileDeletionDialogVisible ) title = resourceHandler.getStringInLocale(R.string.administrator_controls) + + onBackPressedDispatcher.addCallback( + this, + object : OnBackPressedCallback(/* enabled = */ true) { + override fun handleOnBackPressed() { + this@AdministratorControlsActivity.handleBackPress() + } + } + ) } override fun routeToAppVersion() { @@ -121,7 +131,7 @@ class AdministratorControlsActivity : } } - override fun onBackPressed() { + private fun handleBackPress() { val fragment = supportFragmentManager.findFragmentById( R.id.administrator_controls_fragment_multipane_placeholder @@ -134,8 +144,7 @@ class AdministratorControlsActivity : if (fragment is ProfileEditFragment) { administratorControlsActivityPresenter.handleOnBackPressed() } else { - @Suppress("DEPRECATION") // TODO(#5404): Migrate to a back pressed dispatcher. - super.onBackPressed() + finish() } } diff --git a/app/src/main/java/org/oppia/android/app/administratorcontrols/appversion/AppVersionActivity.kt b/app/src/main/java/org/oppia/android/app/administratorcontrols/appversion/AppVersionActivity.kt index e41600bb929..f78d770df1d 100644 --- a/app/src/main/java/org/oppia/android/app/administratorcontrols/appversion/AppVersionActivity.kt +++ b/app/src/main/java/org/oppia/android/app/administratorcontrols/appversion/AppVersionActivity.kt @@ -4,6 +4,7 @@ import android.content.Context import android.content.Intent import android.os.Bundle import android.view.MenuItem +import androidx.activity.OnBackPressedCallback import org.oppia.android.app.activity.ActivityComponentImpl import org.oppia.android.app.activity.InjectableAutoLocalizedAppCompatActivity import org.oppia.android.app.model.ScreenName.APP_VERSION_ACTIVITY @@ -19,12 +20,20 @@ class AppVersionActivity : InjectableAutoLocalizedAppCompatActivity() { super.onCreate(savedInstanceState) (activityComponent as ActivityComponentImpl).inject(this) appVersionActivityPresenter.handleOnCreate() + + onBackPressedDispatcher.addCallback( + this, + object : OnBackPressedCallback(/* enabled = */ true) { + override fun handleOnBackPressed() { + finish() + } + } + ) } override fun onOptionsItemSelected(item: MenuItem): Boolean { if (item.itemId == android.R.id.home) { - @Suppress("DEPRECATION") // TODO(#5404): Migrate to a back pressed dispatcher. - onBackPressed() + onBackPressedDispatcher.onBackPressed() } return super.onOptionsItemSelected(item) } diff --git a/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdActivity.kt b/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdActivity.kt index cf6fb025db6..45a27fac5b8 100644 --- a/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdActivity.kt +++ b/app/src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileAndDeviceIdActivity.kt @@ -4,6 +4,7 @@ import android.content.Context import android.content.Intent import android.os.Bundle import android.view.MenuItem +import androidx.activity.OnBackPressedCallback import org.oppia.android.app.activity.ActivityComponentImpl import org.oppia.android.app.activity.InjectableAutoLocalizedAppCompatActivity import org.oppia.android.app.model.ScreenName.PROFILE_AND_DEVICE_ID_ACTIVITY @@ -18,18 +19,27 @@ import javax.inject.Inject * a particular user or group. */ class ProfileAndDeviceIdActivity : InjectableAutoLocalizedAppCompatActivity() { - @Inject lateinit var profileAndDeviceIdActivityPresenter: ProfileAndDeviceIdActivityPresenter + @Inject + lateinit var profileAndDeviceIdActivityPresenter: ProfileAndDeviceIdActivityPresenter override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) (activityComponent as ActivityComponentImpl).inject(this) profileAndDeviceIdActivityPresenter.handleOnCreate() + + onBackPressedDispatcher.addCallback( + this, + object : OnBackPressedCallback(/* enabled = */ true) { + override fun handleOnBackPressed() { + finish() + } + } + ) } override fun onOptionsItemSelected(item: MenuItem): Boolean { if (item.itemId == android.R.id.home) { - @Suppress("DEPRECATION") // TODO(#5404): Migrate to a back pressed dispatcher. - onBackPressed() + onBackPressedDispatcher.onBackPressed() } return super.onOptionsItemSelected(item) } diff --git a/app/src/main/java/org/oppia/android/app/devoptions/markchapterscompleted/MarkChaptersCompletedActivity.kt b/app/src/main/java/org/oppia/android/app/devoptions/markchapterscompleted/MarkChaptersCompletedActivity.kt index ec8979dad41..cc1a7897150 100644 --- a/app/src/main/java/org/oppia/android/app/devoptions/markchapterscompleted/MarkChaptersCompletedActivity.kt +++ b/app/src/main/java/org/oppia/android/app/devoptions/markchapterscompleted/MarkChaptersCompletedActivity.kt @@ -4,6 +4,7 @@ import android.content.Context import android.content.Intent import android.os.Bundle import android.view.MenuItem +import androidx.activity.OnBackPressedCallback import org.oppia.android.R import org.oppia.android.app.activity.ActivityComponentImpl import org.oppia.android.app.activity.InjectableAutoLocalizedAppCompatActivity @@ -37,12 +38,20 @@ class MarkChaptersCompletedActivity : InjectableAutoLocalizedAppCompatActivity() val showConfirmationNotice = args?.showConfirmationNotice ?: false markChaptersCompletedActivityPresenter.handleOnCreate(internalProfileId, showConfirmationNotice) title = resourceHandler.getStringInLocale(R.string.mark_chapters_completed_activity_title) + + onBackPressedDispatcher.addCallback( + this, + object : OnBackPressedCallback(/* enabled = */ true) { + override fun handleOnBackPressed() { + finish() + } + } + ) } override fun onOptionsItemSelected(item: MenuItem): Boolean { if (item.itemId == android.R.id.home) { - @Suppress("DEPRECATION") // TODO(#5404): Migrate to a back pressed dispatcher. - onBackPressed() + onBackPressedDispatcher.onBackPressed() } return super.onOptionsItemSelected(item) } diff --git a/app/src/main/java/org/oppia/android/app/devoptions/markstoriescompleted/MarkStoriesCompletedActivity.kt b/app/src/main/java/org/oppia/android/app/devoptions/markstoriescompleted/MarkStoriesCompletedActivity.kt index e3373fc7df6..86bb96b2f8d 100644 --- a/app/src/main/java/org/oppia/android/app/devoptions/markstoriescompleted/MarkStoriesCompletedActivity.kt +++ b/app/src/main/java/org/oppia/android/app/devoptions/markstoriescompleted/MarkStoriesCompletedActivity.kt @@ -4,6 +4,7 @@ import android.content.Context import android.content.Intent import android.os.Bundle import android.view.MenuItem +import androidx.activity.OnBackPressedCallback import org.oppia.android.R import org.oppia.android.app.activity.ActivityComponentImpl import org.oppia.android.app.activity.InjectableAutoLocalizedAppCompatActivity @@ -34,12 +35,20 @@ class MarkStoriesCompletedActivity : InjectableAutoLocalizedAppCompatActivity() internalProfileId = profileId?.internalId ?: -1 markStoriesCompletedActivityPresenter.handleOnCreate(internalProfileId) title = resourceHandler.getStringInLocale(R.string.mark_stories_completed_activity_title) + + onBackPressedDispatcher.addCallback( + this, + object : OnBackPressedCallback(/* enabled = */ true) { + override fun handleOnBackPressed() { + finish() + } + } + ) } override fun onOptionsItemSelected(item: MenuItem): Boolean { if (item.itemId == android.R.id.home) { - @Suppress("DEPRECATION") // TODO(#5404): Migrate to a back pressed dispatcher. - onBackPressed() + onBackPressedDispatcher.onBackPressed() } return super.onOptionsItemSelected(item) } diff --git a/app/src/main/java/org/oppia/android/app/devoptions/marktopicscompleted/MarkTopicsCompletedActivity.kt b/app/src/main/java/org/oppia/android/app/devoptions/marktopicscompleted/MarkTopicsCompletedActivity.kt index a891de3a059..606c4f70bd0 100644 --- a/app/src/main/java/org/oppia/android/app/devoptions/marktopicscompleted/MarkTopicsCompletedActivity.kt +++ b/app/src/main/java/org/oppia/android/app/devoptions/marktopicscompleted/MarkTopicsCompletedActivity.kt @@ -4,6 +4,7 @@ import android.content.Context import android.content.Intent import android.os.Bundle import android.view.MenuItem +import androidx.activity.OnBackPressedCallback import org.oppia.android.R import org.oppia.android.app.activity.ActivityComponentImpl import org.oppia.android.app.activity.InjectableAutoLocalizedAppCompatActivity @@ -34,12 +35,20 @@ class MarkTopicsCompletedActivity : InjectableAutoLocalizedAppCompatActivity() { internalProfileId = profileId?.internalId ?: -1 markTopicsCompletedActivityPresenter.handleOnCreate(internalProfileId) title = resourceHandler.getStringInLocale(R.string.mark_topics_completed_activity_title) + + onBackPressedDispatcher.addCallback( + this, + object : OnBackPressedCallback(/* enabled = */ true) { + override fun handleOnBackPressed() { + finish() + } + } + ) } override fun onOptionsItemSelected(item: MenuItem): Boolean { if (item.itemId == android.R.id.home) { - @Suppress("DEPRECATION") // TODO(#5404): Migrate to a back pressed dispatcher. - onBackPressed() + onBackPressedDispatcher.onBackPressed() } return super.onOptionsItemSelected(item) } diff --git a/app/src/main/java/org/oppia/android/app/options/ReadingTextSizeActivity.kt b/app/src/main/java/org/oppia/android/app/options/ReadingTextSizeActivity.kt index e88525841b5..054a4d8e54b 100644 --- a/app/src/main/java/org/oppia/android/app/options/ReadingTextSizeActivity.kt +++ b/app/src/main/java/org/oppia/android/app/options/ReadingTextSizeActivity.kt @@ -3,6 +3,7 @@ package org.oppia.android.app.options import android.content.Context import android.content.Intent import android.os.Bundle +import androidx.activity.OnBackPressedCallback import org.oppia.android.app.activity.ActivityComponentImpl import org.oppia.android.app.activity.InjectableAutoLocalizedAppCompatActivity import org.oppia.android.app.model.ReadingTextSize @@ -34,6 +35,22 @@ class ReadingTextSizeActivity : InjectableAutoLocalizedAppCompatActivity() { savedInstanceState?.retrieveStateBundle()?.selectedReadingTextSize ?: retrieveActivityParams().readingTextSize readingTextSizeActivityPresenter.handleOnCreate(readingTextSize) + + onBackPressedDispatcher.addCallback( + this, + object : OnBackPressedCallback(/* enabled = */ true) { + override fun handleOnBackPressed() { + val resultBundle = ReadingTextSizeActivityResultBundle.newBuilder().apply { + selectedReadingTextSize = readingTextSizeActivityPresenter.getSelectedReadingTextSize() + }.build() + val intent = Intent().apply { + putProtoExtra(MESSAGE_READING_TEXT_SIZE_RESULTS_KEY, resultBundle) + } + setResult(RESULT_OK, intent) + finish() + } + } + ) } companion object { @@ -60,17 +77,6 @@ class ReadingTextSizeActivity : InjectableAutoLocalizedAppCompatActivity() { outState.putProto(ACTIVITY_SAVED_STATE_KEY, stateBundle) } - override fun onBackPressed() { - val resultBundle = ReadingTextSizeActivityResultBundle.newBuilder().apply { - selectedReadingTextSize = readingTextSizeActivityPresenter.getSelectedReadingTextSize() - }.build() - val intent = Intent().apply { - putProtoExtra(MESSAGE_READING_TEXT_SIZE_RESULTS_KEY, resultBundle) - } - setResult(RESULT_OK, intent) - finish() - } - private fun retrieveActivityParams() = intent.getProtoExtra(ACTIVITY_PARAMS_KEY, ReadingTextSizeActivityParams.getDefaultInstance()) diff --git a/app/src/main/java/org/oppia/android/app/options/ReadingTextSizeActivityPresenter.kt b/app/src/main/java/org/oppia/android/app/options/ReadingTextSizeActivityPresenter.kt index 85eedd9d379..491ad80fb11 100644 --- a/app/src/main/java/org/oppia/android/app/options/ReadingTextSizeActivityPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/options/ReadingTextSizeActivityPresenter.kt @@ -28,8 +28,7 @@ class ReadingTextSizeActivityPresenter @Inject constructor( private fun setToolbar() { val readingTextSizeToolbar: Toolbar = activity.findViewById(R.id.reading_text_size_toolbar) readingTextSizeToolbar.setNavigationOnClickListener { - @Suppress("DEPRECATION") // TODO(#5404): Migrate to a back pressed dispatcher. - activity.onBackPressed() + activity.onBackPressedDispatcher.onBackPressed() } } diff --git a/app/src/main/java/org/oppia/android/app/player/exploration/ExplorationActivity.kt b/app/src/main/java/org/oppia/android/app/player/exploration/ExplorationActivity.kt index 6912d639ed9..d9519c01e2a 100755 --- a/app/src/main/java/org/oppia/android/app/player/exploration/ExplorationActivity.kt +++ b/app/src/main/java/org/oppia/android/app/player/exploration/ExplorationActivity.kt @@ -3,6 +3,7 @@ package org.oppia.android.app.player.exploration import android.content.Context import android.content.Intent import android.os.Bundle +import androidx.activity.OnBackPressedCallback import org.oppia.android.app.activity.ActivityComponentImpl import org.oppia.android.app.activity.InjectableAutoLocalizedAppCompatActivity import org.oppia.android.app.hintsandsolution.HintsAndSolutionDialogFragment @@ -48,7 +49,8 @@ class ExplorationActivity : BottomSheetOptionsMenuItemClickListener, RequestVoiceOverIconSpotlightListener { - @Inject lateinit var explorationActivityPresenter: ExplorationActivityPresenter + @Inject + lateinit var explorationActivityPresenter: ExplorationActivityPresenter private lateinit var state: State private lateinit var writtenTranslationContext: WrittenTranslationContext @@ -67,6 +69,14 @@ class ExplorationActivity : params.parentScreen, params.isCheckpointingEnabled ) + onBackPressedDispatcher.addCallback( + this, + object : OnBackPressedCallback(/* enabled = */ true) { + override fun handleOnBackPressed() { + explorationActivityPresenter.backButtonPressed() + } + } + ) } // TODO(#1655): Re-restrict access to fields in tests post-Gradle. @@ -114,10 +124,6 @@ class ExplorationActivity : getProtoExtra(PARAMS_KEY, ExplorationActivityParams.getDefaultInstance()) } - override fun onBackPressed() { - explorationActivityPresenter.backButtonPressed() - } - override fun deleteCurrentProgressAndStopSession(isCompletion: Boolean) { explorationActivityPresenter.deleteCurrentProgressAndStopExploration(isCompletion) } diff --git a/app/src/main/java/org/oppia/android/app/player/exploration/ExplorationActivityPresenter.kt b/app/src/main/java/org/oppia/android/app/player/exploration/ExplorationActivityPresenter.kt index 1c493c19bfa..4049f5dc4a2 100644 --- a/app/src/main/java/org/oppia/android/app/player/exploration/ExplorationActivityPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/player/exploration/ExplorationActivityPresenter.kt @@ -113,8 +113,7 @@ class ExplorationActivityPresenter @Inject constructor( } binding.explorationToolbar.setNavigationOnClickListener { - @Suppress("DEPRECATION") // TODO(#5404): Migrate to a back pressed dispatcher. - activity.onBackPressed() + activity.onBackPressedDispatcher.onBackPressed() } binding.actionAudioPlayer.setOnClickListener { diff --git a/app/src/main/java/org/oppia/android/app/resumelesson/ResumeLessonActivity.kt b/app/src/main/java/org/oppia/android/app/resumelesson/ResumeLessonActivity.kt index fc63898ac40..4e268b486c2 100644 --- a/app/src/main/java/org/oppia/android/app/resumelesson/ResumeLessonActivity.kt +++ b/app/src/main/java/org/oppia/android/app/resumelesson/ResumeLessonActivity.kt @@ -3,6 +3,7 @@ package org.oppia.android.app.resumelesson import android.content.Context import android.content.Intent import android.os.Bundle +import androidx.activity.OnBackPressedCallback import org.oppia.android.app.activity.ActivityComponentImpl import org.oppia.android.app.activity.InjectableAutoLocalizedAppCompatActivity import org.oppia.android.app.home.RouteToExplorationListener @@ -41,6 +42,15 @@ class ResumeLessonActivity : params.parentScreen, params.checkpoint ) + onBackPressedDispatcher.addCallback( + this, + object : OnBackPressedCallback(/* enabled = */ true) { + override fun handleOnBackPressed() { + resumeLessonActivityPresenter.setReadingTextSizeNormal() + finish() + } + } + ) } // TODO(#1655): Re-restrict access to fields in tests post-Gradle. @@ -113,9 +123,4 @@ class ResumeLessonActivity : override fun onDefaultFontSizeLoaded(readingTextSize: ReadingTextSize) { resumeLessonActivityPresenter.loadResumeLessonFragment(readingTextSize) } - - override fun onBackPressed() { - resumeLessonActivityPresenter.setReadingTextSizeNormal() - finish() - } } diff --git a/app/src/main/java/org/oppia/android/app/resumelesson/ResumeLessonActivityPresenter.kt b/app/src/main/java/org/oppia/android/app/resumelesson/ResumeLessonActivityPresenter.kt index 210d49c887c..b09e77fdfce 100644 --- a/app/src/main/java/org/oppia/android/app/resumelesson/ResumeLessonActivityPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/resumelesson/ResumeLessonActivityPresenter.kt @@ -73,8 +73,7 @@ class ResumeLessonActivityPresenter @Inject constructor( context = activity, ReadingTextSize.MEDIUM_TEXT_SIZE ) - @Suppress("DEPRECATION") // TODO(#5404): Migrate to a back pressed dispatcher. - activity.onBackPressed() + activity.onBackPressedDispatcher.onBackPressed() } }