diff --git a/.github/ISSUE_TEMPLATE/Post_RC_Code_Change.md b/.github/ISSUE_TEMPLATE/Post_RC_Code_Change.md index 0bb1da5594c..e97bbdd76d1 100644 --- a/.github/ISSUE_TEMPLATE/Post_RC_Code_Change.md +++ b/.github/ISSUE_TEMPLATE/Post_RC_Code_Change.md @@ -3,7 +3,7 @@ name: Post RC Code Change about: Template for Adding Code After RC is Cut requiring a new RC build title: Post RC Code Change Template labels: release -assignees: TKDickson, bischoffa +assignees: TKDickson, SarahHuber_AdHoc --- diff --git a/.github/ISSUE_TEMPLATE/release_ticket.md b/.github/ISSUE_TEMPLATE/release_ticket.md index faa9246b6d8..8a467d4dbda 100644 --- a/.github/ISSUE_TEMPLATE/release_ticket.md +++ b/.github/ISSUE_TEMPLATE/release_ticket.md @@ -3,7 +3,7 @@ name: Release Review Template about: Template for requesting a production release for VA mobile app title: "{{ env.releaseDate }} Release Sign-Off: {{ env.versionNumber }}" labels: release -assignees: timwright12, chrisj-usds, dumathane, rachelhanster, kellylein, DonMcCaugheyUSDS, TKDickson +assignees: timwright12, chrisj-usds, dumathane, rachelhanster, SarahHuber_AdHoc, DonMcCaugheyUSDS, TKDickson --- diff --git a/.github/workflows/e2e_android.yml b/.github/workflows/e2e_android.yml index 8171cab4e40..d5121a34e41 100644 --- a/.github/workflows/e2e_android.yml +++ b/.github/workflows/e2e_android.yml @@ -30,10 +30,6 @@ on: description: 'Add results to testRail?' type: boolean required: false - device_specific: - description: 'Is your testRail run Android only? (only check if adding results to testRail)' - type: boolean - required: false schedule: - cron: '0 4 * * 1,2,3,4,5' workflow_run: @@ -86,7 +82,6 @@ jobs: message: 'Starting E2E Android tests. Please see :thread: for results. This process may take a while.' find_detox_tests_to_run: - if: github.event_name == 'pull_request' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' uses: ./.github/workflows/e2e_detox_mapping.yml output_detox_tests_to_run: @@ -103,8 +98,8 @@ jobs: - name: 'Get Matrix Value' id: matrix_value run: | - if [[ "${{ inputs.run_full_test }}" == "true" ]] || [[ ${{ github.event_name }} == 'schedule' ]] || [[ "${{ inputs.tests_to_run}}" != "" ]]; then - if [[ "${{ inputs.run_full_test }}" == "true" ]] || [[ ${{ github.event_name }} == 'schedule' ]]; then + if [[ "${{ inputs.run_full_test }}" == "true" ]] || [[ ${{ github.event_name }} == 'schedule' ]] || [[ "${{ inputs.tests_to_run}}" != "" ]] || [[ "${{ github.event.pull_request.user.login }}" == "dependabot[bot]" ]]; then + if [[ "${{ inputs.run_full_test }}" == "true" ]] || [[ ${{ github.event_name }} == 'schedule' ]] || [[ "${{ github.event.pull_request.user.login }}" == "dependabot[bot]" ]]; then e2eNames=$(gh api repos/department-of-veterans-affairs/va-mobile-app/contents/VAMobile/e2e/tests | jq --compact-output 'del(.[] | select(.name == "utils.ts")) | [.[].name]') echo "matrix=$e2eNames" >> "$GITHUB_OUTPUT" echo "individual_matrix=" >> "$GITHUB_OUTPUT" @@ -120,7 +115,7 @@ jobs: if [[ "${{ needs.find_detox_tests_to_run.outputs.test_run }}" != "" ]]; then echo "${{needs.find_detox_tests_to_run.outputs.test_matrix}}" if [[ "${{needs.find_detox_tests_to_run.outputs.test_matrix}}" == "[]" ]]; then - if [[ "${{ inputs.run_full_test }}" == "true" ]] || [[ ${{ github.event_name }} == 'schedule' ]]; then + if [[ "${{ inputs.run_full_test }}" == "true" ]] || [[ ${{ github.event_name }} == 'schedule' ]] || [[ "${{ github.event.pull_request.user.login }}" == "dependabot[bot]" ]]; then e2eNames=$(gh api repos/department-of-veterans-affairs/va-mobile-app/contents/VAMobile/e2e/tests | jq --compact-output 'del(.[] | select(.name == "utils.ts")) | [.[].name]') echo "matrix=$e2eNames" >> "$GITHUB_OUTPUT" echo "individual_matrix=" >> "$GITHUB_OUTPUT" @@ -246,7 +241,7 @@ jobs: if: failure() || success() uses: actions/upload-artifact@v4 with: - name: ${{matrix.testsuite}}-e2e-junit + name: e2e-junit-${{matrix.testsuite}} path: VAMobile/e2e/test_reports/e2e-junit.xml - name: Upload artifacts on failure @@ -353,18 +348,11 @@ jobs: matrix_send_test_results_to_testrail: if: (!cancelled()) && github.event.inputs.run_testRail == 'true' - needs: [matrix-e2e-android, output_detox_tests_to_run] - strategy: - fail-fast: false - max-parallel: 1 - matrix: - testsuite: ${{ fromJSON(needs.output_detox_tests_to_run.outputs.output1) }} + needs: [matrix-e2e-android, output_detox_tests_to_run] name: Update testRail Results uses: ./.github/workflows/update_testrail_results.yml with: - test_names: "${{matrix.testsuite}}" testRail_name: ${{ inputs.testRail_name }} - test_specific_OS_needed: ${{ inputs.device_specific}} test_OS_name: "Android" secrets: inherit diff --git a/.github/workflows/e2e_detox_mapping.yml b/.github/workflows/e2e_detox_mapping.yml index 000c07e8344..2c86b02b9af 100644 --- a/.github/workflows/e2e_detox_mapping.yml +++ b/.github/workflows/e2e_detox_mapping.yml @@ -93,7 +93,7 @@ jobs: select(contains(["auth"])) += ["SignIn"] | select(contains(["authorizedServices"])) += ["Appeals", "AppealsExpanded", "Appointments", "AppointmentsExpanded", "Claims", "DirectDeposit", "DisabilityRatings", "PersonalInformationScreen", "VALetters", "MilitaryInformation", "Payments", "Prescriptions", "Messages", "VeteranStatusCard"] | select(contains(["contactInformation"]) or contains(["ContactInformationScreen"])) += ["ContactInformation", "VALetters"] | - select(contains(["NotificationManager"])) += ["SettingsScreen", "PushNotifications"] | + select(contains(["NotificationManager"])) += ["SettingsScreen", "PushNotifications", "Onboarding"] | select(contains(["Types"]) or contains(["VAImage"])) += ["AvailabilityFramework", "Cerner", "ContactInformation", "VALetters", "LoginScreen", "Onboarding", "ProfileScreen", "PushNotifications", "SettingsScreen", "SignIn", "VaccineRecords", "Claims", "Appeals", "AppealsExpanded", "DisabilityRatings", "Appointments", "AppointmentsExpanded", "Prescriptions", "Messages", "MilitaryInformation", "HomeScreen", "VeteransCrisisLine", "VeteranStatusCard", "DirectDeposit", "Payments", "PersonalInformationScreen"] | unique') diff --git a/.github/workflows/e2e_ios.yml b/.github/workflows/e2e_ios.yml index ca507211298..7197fe38d91 100644 --- a/.github/workflows/e2e_ios.yml +++ b/.github/workflows/e2e_ios.yml @@ -30,10 +30,6 @@ on: description: 'Add results to testRail?' type: boolean required: false - device_specific: - description: 'Is your testRail run iOS only? (only check if adding results to testRail)' - type: boolean - required: false schedule: - cron: '0 4 * * 1,2,3,4,5' workflow_run: @@ -77,7 +73,6 @@ jobs: message: 'Starting E2E iOS tests. Please see :thread: for results. This process may take a while.' find_detox_tests_to_run: - if: github.event_name == 'pull_request' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' uses: ./.github/workflows/e2e_detox_mapping.yml output_detox_tests_to_run: @@ -94,8 +89,8 @@ jobs: - name: 'Get Matrix Value' id: matrix_value run: | - if [[ "${{ inputs.run_full_test }}" == "true" ]] || [[ ${{ github.event_name }} == 'schedule' ]] || [[ "${{ inputs.tests_to_run}}" != "" ]]; then - if [[ "${{ inputs.run_full_test }}" == "true" ]] || [[ ${{ github.event_name }} == 'schedule' ]]; then + if [[ "${{ inputs.run_full_test }}" == "true" ]] || [[ ${{ github.event_name }} == 'schedule' ]] || [[ "${{ inputs.tests_to_run}}" != "" ]] || [[ "${{ github.event.pull_request.user.login }}" == "dependabot[bot]" ]]; then + if [[ "${{ inputs.run_full_test }}" == "true" ]] || [[ ${{ github.event_name }} == 'schedule' ]] || [[ "${{ github.event.pull_request.user.login }}" == "dependabot[bot]" ]]; then e2eNames=$(gh api repos/department-of-veterans-affairs/va-mobile-app/contents/VAMobile/e2e/tests | jq --compact-output 'del(.[] | select(.name == "utils.ts")) | [.[].name]') echo "matrix=$e2eNames" >> "$GITHUB_OUTPUT" echo "individual_matrix=" >> "$GITHUB_OUTPUT" @@ -111,7 +106,7 @@ jobs: if [[ "${{ needs.find_detox_tests_to_run.outputs.test_run }}" != "" ]]; then echo "${{needs.find_detox_tests_to_run.outputs.test_matrix}}" if [[ "${{needs.find_detox_tests_to_run.outputs.test_matrix}}" == "[]" ]]; then - if [[ "${{ inputs.run_full_test }}" == "true" ]] || [[ ${{ github.event_name }} == 'schedule' ]]; then + if [[ "${{ inputs.run_full_test }}" == "true" ]] || [[ ${{ github.event_name }} == 'schedule' ]] || [[ "${{ github.event.pull_request.user.login }}" == "dependabot[bot]" ]]; then e2eNames=$(gh api repos/department-of-veterans-affairs/va-mobile-app/contents/VAMobile/e2e/tests | jq --compact-output 'del(.[] | select(.name == "utils.ts")) | [.[].name]') echo "matrix=$e2eNames" >> "$GITHUB_OUTPUT" echo "individual_matrix=" >> "$GITHUB_OUTPUT" @@ -143,7 +138,6 @@ jobs: IOS_PROJ_FILE: 'VAMobile.xcodeproj' # Xcode scheme to build IOS_SCHEME: 'VAMobileRelease' - strategy: fail-fast: false matrix: @@ -224,8 +218,8 @@ jobs: if: failure() || success() uses: actions/upload-artifact@v4 with: - name: ${{matrix.testsuite}}-e2e-junit - path: VAMobile/e2e/test_reports/e2e-junit.xml + name: e2e-junit-${{matrix.testsuite}} + path: VAMobile/e2e/test_reports/e2e-junit.xml - name: Upload artifacts on failure if: failure() || steps.run_e2e_tests.outcome == 'failure' @@ -332,18 +326,11 @@ jobs: matrix_send_test_results_to_testrail: if: (!cancelled()) && github.event.inputs.run_testRail == 'true' - needs: [matrix-e2e-ios, output_detox_tests_to_run] - strategy: - fail-fast: false - max-parallel: 1 - matrix: - testsuite: ${{ fromJSON(needs.output_detox_tests_to_run.outputs.output1) }} + needs: [matrix-e2e-ios, output_detox_tests_to_run] name: Update testRail Results uses: ./.github/workflows/update_testrail_results.yml with: - test_names: "${{matrix.testsuite}}" testRail_name: ${{ inputs.testRail_name }} - test_specific_OS_needed: ${{ inputs.device_specific}} test_OS_name: "iOS" secrets: inherit diff --git a/.github/workflows/release_branch_issue.yml b/.github/workflows/release_branch_issue.yml index 40db9d7f871..75d64ec5994 100644 --- a/.github/workflows/release_branch_issue.yml +++ b/.github/workflows/release_branch_issue.yml @@ -94,7 +94,7 @@ jobs: run: | declare -A GITHUB_TO_SLACK_MAP GITHUB_TO_SLACK_MAP["TKDickson"]="U02PJLJ0H6H" - GITHUB_TO_SLACK_MAP["kellylein"]="UJHA49K6X" + GITHUB_TO_SLACK_MAP["SarahHuber_AdHoc"]="U07U9EDGAFP" GITHUB_TO_SLACK_MAP["dumathane"]="U02RC1BRZBP" GITHUB_TO_SLACK_MAP["timwright12"]="U01DBDAJZ18" GITHUB_TO_SLACK_MAP["ala_yna"]="UQC180926" @@ -111,7 +111,7 @@ jobs: env: SLACK_API_TOKEN: ${{ secrets.SLACK_API_TOKEN }} SLACK_TKDickson: $TKDickson - SLACK_kellylein: $kellylein + SLACK_SarahHuberAdHoc: $SarahHuber_AdHoc SLACK_dumathane: $dumathane SLACK_timwright12: $timwright12 SLACK_alayna: $ala_yna @@ -133,7 +133,7 @@ jobs: - *Tickets Tagged Appropriately:* ${{ needs.release_ticket.outputs.qaDueDate }}\n\n\ *Contacts:*\n\ - *Release Testing:* <@${SLACK_TKDickson}>\n\ - - *Release Manager:* <@${SLACK_kellylein}>\n\ + - *Release Manager:* <@${SLACK_SarahHuberAdHoc}>\n\ - *Release Ticket Validation:* <@${SLACK_dumathane}>\n\ - *Engineering:* <@${SLACK_dumathane}>, <@${SLACK_timwright12}>\n\ - *Mobile Product Approvers:* H&B and Global - <@${SLACK_alayna}> Global - <@${SLACK_ajsarkar28}>\n\ @@ -148,4 +148,4 @@ jobs: -H 'Authorization: Bearer '"$SLACK_API_TOKEN" \ -H 'Content-type: application/json' \ -d @- \ - https://slack.com/api/chat.postMessage + https://slack.com/api/chat.postMessage \ No newline at end of file diff --git a/.github/workflows/update_testrail_results.yml b/.github/workflows/update_testrail_results.yml index 064d93e054d..e2b4ebf3cd6 100644 --- a/.github/workflows/update_testrail_results.yml +++ b/.github/workflows/update_testrail_results.yml @@ -10,15 +10,9 @@ on: description: "TestRail api key" required: true inputs: - test_names: - type: string - default: '' testRail_name: type: string default: '' - test_specific_OS_needed: - type: boolean - default: false test_OS_name: type: string default: '' @@ -42,7 +36,7 @@ jobs: - name: Download junit file uses: actions/download-artifact@v4 with: - name: ${{inputs.test_names}}-e2e-junit + pattern: e2e-junit-* - name: 'Find run ID in testRail' id: run-id-selection run: | @@ -53,18 +47,13 @@ jobs: echo "TEST_RUN_ID=$resp" >> "$GITHUB_OUTPUT" if [ "$resp" == '' ]; then if [[ "${{ inputs.testRail_name }}" == "" ]]; then - if [[ ${{ inputs.test_specific_OS_needed}} == true ]]; then - OS_name="${{inputs.test_OS_name}}: " - else - OS_name='' - fi resp=$(curl -X POST -H 'Content-Type: application/json' \ -u "${{secrets.TEST_RAIL_USER}}:${{secrets.TEST_RAIL_KEY}}" \ - -d '{"suite_id": 92, "include_all": false, "name": "'"${OS_name}$(date +'%Y-%m-%d')"'"}' \ + -d '{"suite_id": 92, "include_all": false, "name": "'"$(date +'%Y-%m-%d')"'"}' \ "https://dsvavsp.testrail.io//index.php?/api/v2/add_run/29" ) resp=$(curl -X GET -H 'Content-Type: application/json' \ -u "${{secrets.TEST_RAIL_USER}}:${{secrets.TEST_RAIL_KEY}}" \ - "https://dsvavsp.testrail.io//index.php?/api/v2/get_runs/29&is_completed=0" | jq '.runs[] | select(.name =="'"${OS_name}$(date +'%Y-%m-%d')"'") | .id') + "https://dsvavsp.testrail.io//index.php?/api/v2/get_runs/29&is_completed=0" | jq '.runs[] | select(.name =="'"$(date +'%Y-%m-%d')"'") | .id') echo "$resp" echo "TEST_RUN_ID=$resp" >> "$GITHUB_OUTPUT" else @@ -94,15 +83,19 @@ jobs: python-version: '3.x' - name: TestRail CLI upload results if: always() - run: | + run: | pip install trcli - trcli -y \ - -h https://dsvavsp.testrail.io/ \ - --project "VA Mobile App" \ - --project-id 29 \ - -u ${{secrets.TEST_RAIL_USER}} \ - -k ${{secrets.TEST_RAIL_KEY}} \ - parse_junit \ - --run-id ${{steps.run-id-selection.outputs.TEST_RUN_ID}} \ - --section-id ${{steps.section-id-selection.outputs.SECTION_RUN_ID}} \ - -f "/home/runner/work/va-mobile-app/va-mobile-app/e2e-junit.xml" + for dir in /home/runner/work/va-mobile-app/va-mobile-app/e2e-junit-*/; do + echo "$dir" + trcli -y \ + -h https://dsvavsp.testrail.io/ \ + --project "VA Mobile App" \ + --project-id 29 \ + -u ${{secrets.TEST_RAIL_USER}} \ + -k ${{secrets.TEST_RAIL_KEY}} \ + parse_junit \ + --run-id ${{steps.run-id-selection.outputs.TEST_RUN_ID}} \ + --section-id ${{steps.section-id-selection.outputs.SECTION_RUN_ID}} \ + -f "${dir}e2e-junit.xml" + done + diff --git a/VAMobile/android/Gemfile.lock b/VAMobile/android/Gemfile.lock index 3cd8acc8321..f715530f744 100644 --- a/VAMobile/android/Gemfile.lock +++ b/VAMobile/android/Gemfile.lock @@ -10,7 +10,7 @@ GEM artifactory (3.0.17) atomos (0.1.3) aws-eventstream (1.3.0) - aws-partitions (1.993.0) + aws-partitions (1.1001.0) aws-sdk-core (3.211.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) @@ -113,7 +113,7 @@ GEM google-apis-firebaseappdistribution_v1alpha (~> 0.2.0) fastlane-plugin-slack_bot (1.4.0) gh_inspector (1.1.3) - google-apis-androidpublisher_v3 (0.73.0) + google-apis-androidpublisher_v3 (0.74.0) google-apis-core (>= 0.15.0, < 2.a) google-apis-core (0.15.1) addressable (~> 2.5, >= 2.5.1) @@ -127,7 +127,7 @@ GEM google-apis-core (>= 0.11.0, < 2.a) google-apis-firebaseappdistribution_v1alpha (0.2.0) google-apis-core (>= 0.11.0, < 2.a) - google-apis-iamcredentials_v1 (0.21.0) + google-apis-iamcredentials_v1 (0.22.0) google-apis-core (>= 0.15.0, < 2.a) google-apis-playcustomapp_v1 (0.16.0) google-apis-core (>= 0.15.0, < 2.a) @@ -148,7 +148,7 @@ GEM google-cloud-core (~> 1.6) googleauth (~> 1.9) mini_mime (~> 1.0) - googleauth (1.11.1) + googleauth (1.11.2) faraday (>= 1.0, < 3.a) google-cloud-env (~> 2.1) jwt (>= 1.4, < 3.0) @@ -160,7 +160,7 @@ GEM domain_name (~> 0.5) httpclient (2.8.3) jmespath (1.6.2) - json (2.7.2) + json (2.7.6) jwt (2.9.3) base64 mini_magick (4.13.2) @@ -168,7 +168,7 @@ GEM multi_json (1.15.0) multipart-post (2.4.1) mutex_m (0.2.0) - nanaimo (0.3.0) + nanaimo (0.4.0) naturally (2.2.1) nkf (0.2.0) optparse (0.5.0) @@ -181,7 +181,7 @@ GEM trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) retriable (3.1.2) - rexml (3.3.8) + rexml (3.3.9) rouge (2.0.7) ruby2_keywords (0.0.5) rubyzip (2.3.2) @@ -205,12 +205,12 @@ GEM uber (0.1.0) unicode-display_width (2.6.0) word_wrap (1.0.0) - xcodeproj (1.25.1) + xcodeproj (1.27.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) - nanaimo (~> 0.3.0) + nanaimo (~> 0.4.0) rexml (>= 3.3.6, < 4.0) xcpretty (0.3.0) rouge (~> 2.0.7) diff --git a/VAMobile/documentation/design/About/designers.md b/VAMobile/documentation/design/About/designers.md index 0c0adf3737f..8f8db755633 100644 --- a/VAMobile/documentation/design/About/designers.md +++ b/VAMobile/documentation/design/About/designers.md @@ -48,12 +48,12 @@ Once you’ve loaded the library, you should be able to access everything in it ## Figma -VA mobile design teams at Ad Hoc use Figma to view, share, and collaborate on our work. Only designers actively working on products at VA can be added to Figma. Once you have been added, you may access the libraries in the cloud. +VA mobile app design teams use Figma to view, share, and collaborate on our work. Currently, only designers at Ad Hoc can be added as Editors to the Mobile App team's Figma account. If you're working on an external Experience Team and need access to our files, you can follow the steps below to be added as a Viewer. ### Get added to Figma 1. Go to [figma.com](https://www.figma.com/) and [create a Figma account](https://help.figma.com/hc/en-us/articles/360039811114-Create-a-Figma-account) -2. In the [#va-mobile-app-shared-systems](https://dsva.slack.com/archives/C05HF9ULKJ4) channel in Slack, ping a Figma admin (currently Jen Ecker and Jessica Woodin) requesting to be added. +2. In the [#va-mobile-app-shared-systems](https://dsva.slack.com/archives/C05HF9ULKJ4) channel in Slack, ping a Figma admin (currently Kelly Lein, Jessica Woodin, and Holly Collier) requesting to be added. 3. Receive the invite via email and accept the invitation. 4. Boom, you’re in! @@ -65,4 +65,15 @@ Figma contains a few important features that help teams work together: * You receive library updates automatically (a big advantage of using Figma) * You can see what everyone else is working on in the VA workspace * Developers can inspect any element on a page -* You can [create a branch](https://department-of-veterans-affairs.github.io/va-mobile-app/docs/UX/How-We-Work/figma-branching) of your file at any time \ No newline at end of file +* You can [create a branch](https://department-of-veterans-affairs.github.io/va-mobile-app/docs/UX/How-We-Work/figma-branching) of your file at any time + +### Designers on Experience Teams +Currently, designers on Experience Teams can only be added as Viewers in the **VA Mobile App's Figma account**. In order to use the Mobile App libraries in the **VA.gov Platform team's Figma account**, designers should follow the steps below. + +1. In the [VA Mobile App team's Figma account](https://www.figma.com/files/827597988283174959/team/1114266503868297401), open the [Component Library](https://www.figma.com/design/Zzt8z60hCtdEzXx2GFWghH/%F0%9F%93%90-Component-Library---Design-System---VA-Mobile?m=auto&t=h1T1ozCx1hqbFSDa-7) and/or [Flagship Library](https://www.figma.com/design/QVLPB3eOunmKrgQOuOt0SU/Flagship-Library---%F0%9F%93%90-Resource---VA-Mobile?m=auto&t=h1T1ozCx1hqbFSDa-7). +2. Find the component you need. +3. Copy the component. +4. In the [VA.gov Platform team's Figma account](https://www.figma.com/files/team/1278375444205744118/all-projects), open your working file. +5. Paste the component into your working file. + +If you have questions or need assistance, reach out in the [#va-mobile-app-shared-systems](https://dsva.slack.com/archives/C05HF9ULKJ4) channel. \ No newline at end of file diff --git a/VAMobile/documentation/docs/App Features/Availability Framework/Availability Framework.md b/VAMobile/documentation/docs/App Features/Availability Framework/Availability Framework.md index 878d9191bc2..0c987bc0993 100644 --- a/VAMobile/documentation/docs/App Features/Availability Framework/Availability Framework.md +++ b/VAMobile/documentation/docs/App Features/Availability Framework/Availability Framework.md @@ -1,4 +1,6 @@ -# Availability Framework +--- +title: Availability Framework +--- ## Feature Summary @@ -6,36 +8,46 @@ Given the limitations of app store release processes between iOS and Android, we ## Use Cases -* Use Case 1: A screen is broken (for all users) and it can’t be rendered without crashing the app/red screen of death. -* Use Case 2: A screen element, feature, or part of a feature is broken (for all users) and the feature entry point can still be accessed and screen rendered (with missing or bad data), and we want to prevent everyone from accessing the broken feature. -* Use Case 3: A screen element, feature, or part of feature is broken (for SOME users. not all) and the feature entry point can still be accessed screen rendered, but some folks will have critically broken data/feature. +* **Use Case 1**: A screen is broken (for all users) and it can’t be rendered without crashing the app/red screen of death. +* **Use Case 2**: A screen element, feature, or part of a feature is broken (for all users) and the feature entry point can still be accessed and screen rendered (with missing or bad data), and we want to prevent everyone from accessing the broken feature. +* **Use Case 3**: A screen element, feature, or part of feature is broken (for SOME users. not all) and the feature entry point can still be accessed screen rendered, but some folks will have critically broken data/feature. -## Example Screenshots +## Use Case Screenshots -Use Case 1: ![](../../../static/img/availabilityFramework/AF-UseCase1.png) +### Use Case 1 -Use Case 2: ![](../../../static/img/availabilityFramework/AF-UseCase2.png) +![The VA Mobile app with a dismissable alert that says: We are sorry! There's something wrong and we are working on it!](../../../static/img/availabilityFramework/AF-UseCase1.png) -Use Case 3: ![](../../../static/img/availabilityFramework/AF-UseCase3.png) +### Use Case 2 + +![The VA Mobile app with a non-dismissable alert replacing page content that asks the user to update the app](../../../static/img/availabilityFramework/AF-UseCase2.png) + +### Use Case 3 + +![The VA Mobile app with a non-dismissable warning message letting the user know that something might be wrong for some users and we're working to fix the issue](../../../static/img/availabilityFramework/AF-UseCase3.png) ## How to Enable Here you'll see existing examples of previously enabled availability framework setups: -Firebase definitions: ![](../../../static/img/availabilityFramework/AF_in_Firebase.png) +### Firebase definitions + +![The firebase UI showing a created waygate](../../../static/img/availabilityFramework/AF_in_Firebase.png) [JSON documentation and the parameter setup](https://github.com/department-of-veterans-affairs/va.gov-team/blob/master/products/va-mobile-app/Teams/QA%20and%20Release/Policies/Process%20-%20Availability%20Framework.md#json-disclaimer). ## Developer Notes * If we run into an issue that requires ClaimsHistoryScreen to have an AF toggle, we will need to investigate doing the same for 'ClaimsHistory' for releases that happened in january if it's relevant to the scenario. +* We should endeavor to avoid screen name changes at all costs to avoid duplicative AF requirements. * All 'Else' clauses should be set to the default waygate configuration to avoid issues with the remote config developer setup. -Defaults: - enabled: true, - type: undefined, - errorMsgTitle: undefined, - errorMsgBody: undefined, - appUpdateButton: false +### Default waygate configuration -* We should endeavor to avoid screen name changes at all costs to avoid duplicative AF requirements. +```js +enabled: true, +type: undefined, +errorMsgTitle: undefined, +errorMsgBody: undefined, +appUpdateButton: false +``` diff --git a/VAMobile/documentation/docs/App Features/EncouragedUpdate/EncouragedUpdate.md b/VAMobile/documentation/docs/App Features/EncouragedUpdate/EncouragedUpdate.md index 2843e72fc32..1104b2eee18 100644 --- a/VAMobile/documentation/docs/App Features/EncouragedUpdate/EncouragedUpdate.md +++ b/VAMobile/documentation/docs/App Features/EncouragedUpdate/EncouragedUpdate.md @@ -1,5 +1,7 @@ -# Encouraged Update - +--- +title: Encouraged Update +--- + ## Feature Summary Encouraged update displays to the veteran that there is a newer version in the app store to encourage them to update with a button to click to either download in-app updates (Android) or to go to the app store to download (iOS). In doing so, they will download the latest version of the app and have access to all of the benefits that come with it. We also give them the option of skipping an update for a particular version, which hides this alert until the next version is released. This alert takes priority over the What's New alert. @@ -13,10 +15,11 @@ This is potentially a precursor to forced upgrading in the future after so many * Use Case 3: The version on the device is the same version in the store or newer (updates from the app store roll out to devices periodically, so it is possible that the app was updated but the store's API is returning an older version) so it displays the [What's New](../WhatsNew/WhatsNew.md) alert if applicable ## How to force this to appear in Demo Mode -Step 1: Go to the developer screen in the settings part of the app and scroll to the bottom where it has Encouraged Update and What's New versions -Step 2: Set the Encouraged Update version override to a version that is lower than the store version -Step 3: Logout of the app and log back into demo mode -## Screenshot +1. Go to the developer screen in the settings part of the app and scroll to the bottom where it has Encouraged Update and What's New versions +2. Set the Encouraged Update version override to a version that is lower than the store version +3. Logout of the app and log back into demo mode + +## Screenshot of an encouraged update -![](../../../static/img/encouragedUpdate/EncouragedUpdate.png) \ No newline at end of file +![The VA mobile app showing an example of an encouraged update for a user](../../../static/img/encouragedUpdate/EncouragedUpdate.png) \ No newline at end of file diff --git a/VAMobile/documentation/docs/App Features/WhatsNew/WhatsNew.md b/VAMobile/documentation/docs/App Features/WhatsNew/WhatsNew.md index 3b4707620c0..6117bd0368b 100644 --- a/VAMobile/documentation/docs/App Features/WhatsNew/WhatsNew.md +++ b/VAMobile/documentation/docs/App Features/WhatsNew/WhatsNew.md @@ -1,4 +1,6 @@ -# What's New +--- +title: What's New +--- ## Feature Summary @@ -12,13 +14,18 @@ What's new displays to the veteran what is new in the version that they upgraded * Use Case 4: The user is on the most recent version, but there are no notes to be displayed, so there is no alert ## How to force this to appear in Demo Mode -Step 1: Go to the developer screen in the settings part of the app and scroll to the bottom where it has Encouraged Update and What's New versions -Step 2: Set the Encouraged Update version override to a version that is equal to or greater than the store version -Step 3: Set the What's New version override to a version that is a valid version in the `common.json` file in the translations folder (Valid versions at the time of writing this are 2.0, 2.2, 2.3, 2.13, and 2.23) + +1. Go to the developer screen in the settings part of the app and scroll to the bottom where it has Encouraged Update and What's New versions +2. Set the Encouraged Update version override to a version that is equal to or greater than the store version +3. Set the What's New version override to a version that is a valid version in the `common.json` file in the translations folder (Valid versions at the time of writing this are 2.0, 2.2, 2.3, 2.13, and 2.23) Step 4: Logout of the app and log back into demo mode -## Example Screenshot +## Example Screenshots of the What's New feature + +### Expanded + +![The expanded state of the what's new component](../../../static/img/whatsNew/WhatsNewExpanded.png) -Expanded: ![](../../../static/img/whatsNew/WhatsNewExpanded.png) +### Not Expanded -Not Expanded: ![](../../../static/img/whatsNew/WhatsNewNotExpanded.png) +![The collapsed state of the what's new component](../../../static/img/whatsNew/WhatsNewNotExpanded.png) diff --git a/VAMobile/documentation/docs/Flagship design library/Components/Buttons and Links/Menus.md b/VAMobile/documentation/docs/Flagship design library/Components/Buttons and Links/Menus.md index ae7c5be6ed4..8bb4c5cf9c6 100644 --- a/VAMobile/documentation/docs/Flagship design library/Components/Buttons and Links/Menus.md +++ b/VAMobile/documentation/docs/Flagship design library/Components/Buttons and Links/Menus.md @@ -7,10 +7,10 @@ Menus provide temporary access to functionality that's directly related to the o ## Examples ### Master component - + ### Examples - + ## Usage diff --git a/VAMobile/documentation/docs/Flagship design library/Components/Layout and Organization/List/ListHeader.md b/VAMobile/documentation/docs/Flagship design library/Components/Layout and Organization/List/ListHeader.md index 36f121ec641..34fe89097e9 100644 --- a/VAMobile/documentation/docs/Flagship design library/Components/Layout and Organization/List/ListHeader.md +++ b/VAMobile/documentation/docs/Flagship design library/Components/Layout and Organization/List/ListHeader.md @@ -7,10 +7,10 @@ The list header provides an overview of what the list is about and allows you to ## Examples ### Default - + ### Variations - + ## Usage diff --git a/VAMobile/documentation/docs/Flagship design library/Components/Layout and Organization/MultiTouchCard.mdx b/VAMobile/documentation/docs/Flagship design library/Components/Layout and Organization/MultiTouchCard.mdx index d0706312915..d4be9f27257 100644 --- a/VAMobile/documentation/docs/Flagship design library/Components/Layout and Organization/MultiTouchCard.mdx +++ b/VAMobile/documentation/docs/Flagship design library/Components/Layout and Organization/MultiTouchCard.mdx @@ -12,10 +12,10 @@ The multi-touch card provides multiple touch targets on a list item. ## Examples ### Default - + ### Variations - + ## Usage diff --git a/VAMobile/documentation/docs/Flagship design library/Components/Layout and Organization/Nametag.md b/VAMobile/documentation/docs/Flagship design library/Components/Layout and Organization/Nametag.md index da3381fc6f6..63b0dfef7a5 100644 --- a/VAMobile/documentation/docs/Flagship design library/Components/Layout and Organization/Nametag.md +++ b/VAMobile/documentation/docs/Flagship design library/Components/Layout and Organization/Nametag.md @@ -7,10 +7,10 @@ The Nametag provides an official identity to an authenticated user's experience ## Examples ### Default - + ### Variations - + ## Usage diff --git a/VAMobile/documentation/docs/Flagship design library/Components/Navigation/Secondary/Pagination.mdx b/VAMobile/documentation/docs/Flagship design library/Components/Navigation/Secondary/Pagination.mdx index 2d854451c6c..9c51dc72162 100644 --- a/VAMobile/documentation/docs/Flagship design library/Components/Navigation/Secondary/Pagination.mdx +++ b/VAMobile/documentation/docs/Flagship design library/Components/Navigation/Secondary/Pagination.mdx @@ -10,10 +10,10 @@ Pagination is navigation for paginated content. ## Examples ### Master component - + ### Examples - + ## Usage * Refer to the [VA Design System for usage guidance](https://design.va.gov/components/pagination#usage) diff --git a/VAMobile/documentation/docs/Flagship design library/Components/Selection and Input/Form Elements/RadioButton.md b/VAMobile/documentation/docs/Flagship design library/Components/Selection and Input/Form Elements/RadioButton.md index 5a7cdebe4e5..e8bcbdd9cc0 100644 --- a/VAMobile/documentation/docs/Flagship design library/Components/Selection and Input/Form Elements/RadioButton.md +++ b/VAMobile/documentation/docs/Flagship design library/Components/Selection and Input/Form Elements/RadioButton.md @@ -7,10 +7,10 @@ Radio buttons allow users to select exactly one choice from a group. ## Examples ### Master component - + ### Examples - + ## Usage diff --git a/VAMobile/documentation/docs/Flagship design library/Components/Selection and Input/Form Elements/VATextInput.mdx b/VAMobile/documentation/docs/Flagship design library/Components/Selection and Input/Form Elements/VATextInput.mdx index 2b726f3b3ac..c07116de939 100644 --- a/VAMobile/documentation/docs/Flagship design library/Components/Selection and Input/Form Elements/VATextInput.mdx +++ b/VAMobile/documentation/docs/Flagship design library/Components/Selection and Input/Form Elements/VATextInput.mdx @@ -11,10 +11,10 @@ Text input allows people to enter any type of text unless otherwise restricted. ## Examples ### Default - + ### Examples - + ## Usage diff --git a/VAMobile/documentation/docs/Flagship design library/Components/Tags/Tag.mdx b/VAMobile/documentation/docs/Flagship design library/Components/Tags/Tag.mdx index 4c78f9f73cb..e2cb4bb0896 100644 --- a/VAMobile/documentation/docs/Flagship design library/Components/Tags/Tag.mdx +++ b/VAMobile/documentation/docs/Flagship design library/Components/Tags/Tag.mdx @@ -11,10 +11,10 @@ Tags provide quick contextual keywords to inform status of a row or item. ## Examples ### Default - + ### Variations - + ## Usage ### When to use Tags diff --git a/VAMobile/documentation/docs/Flagship design library/Foundation/Icons/index.md b/VAMobile/documentation/docs/Flagship design library/Foundation/Icons/index.md index 4e68802feab..61943e252b1 100644 --- a/VAMobile/documentation/docs/Flagship design library/Foundation/Icons/index.md +++ b/VAMobile/documentation/docs/Flagship design library/Foundation/Icons/index.md @@ -4,6 +4,6 @@ title: Icons Icons are used sparingly throughout the VA mobile app as a way to enhance user understanding and wayfinding. - + * [Icons in Figma](https://www.figma.com/file/QVLPB3eOunmKrgQOuOt0SU/%F0%9F%93%90-DesignLibrary2.0---VAMobile?type=design&node-id=4156%3A7676&t=LWuS4oyNuplsuZBa-1) \ No newline at end of file diff --git a/VAMobile/documentation/docs/Flagship design library/Foundation/color-palette.md b/VAMobile/documentation/docs/Flagship design library/Foundation/color-palette.md index 84701326e35..2432de774a3 100644 --- a/VAMobile/documentation/docs/Flagship design library/Foundation/color-palette.md +++ b/VAMobile/documentation/docs/Flagship design library/Foundation/color-palette.md @@ -5,7 +5,7 @@ sidebar_position: 2 The VA mobile app has two color themes: Light and Dark. [The VA mobile app themes](https://www.figma.com/file/bGO6g5cCvWycrNjoK66PXc/VA-Mobile-Design-Tokens?node-id=151%3A76) use colors from the [VA style guide](https://design.va.gov/foundation/color-palette), and only deviate if necessary for accessibility purposes. - + **Themes** - [Light theme in Figma](https://www.figma.com/file/yXL0MkEKyAPGXPZqRH0VFZ/VA-Mobile-light-theme?node-id=183%3A441) diff --git a/VAMobile/documentation/docs/Flagship design library/Foundation/layout.md b/VAMobile/documentation/docs/Flagship design library/Foundation/layout.md index 0ac4691dc90..c45ed11022f 100644 --- a/VAMobile/documentation/docs/Flagship design library/Foundation/layout.md +++ b/VAMobile/documentation/docs/Flagship design library/Foundation/layout.md @@ -3,6 +3,6 @@ title: Layout sidebar_position: 4 --- - + * [Grid in Figma](https://www.figma.com/file/QVLPB3eOunmKrgQOuOt0SU/%F0%9F%93%90-DesignLibrary2.0---VAMobile?type=design&node-id=3859%3A7737&t=EuJmlpcIxjibtKua-1) \ No newline at end of file diff --git a/VAMobile/documentation/docs/Flagship design library/Foundation/typography.md b/VAMobile/documentation/docs/Flagship design library/Foundation/typography.md index 595b23c7996..2451440b90e 100644 --- a/VAMobile/documentation/docs/Flagship design library/Foundation/typography.md +++ b/VAMobile/documentation/docs/Flagship design library/Foundation/typography.md @@ -64,7 +64,7 @@ Typography generally follows [VA.gov’s guidance](https://design.va.gov/foundat --> - + ## Front-end To support the common usage of colors for each font style, defaults colors have been set for each type of font under `src/styles/themes/standardTheme.ts`(See `buildTypography` function). Colors for each font style are based on the [Design Tokens - Design Library](https://www.figma.com/file/bGO6g5cCvWycrNjoK66PXc/%F0%9F%93%90-DesignTokens1.0---Library---VAMobile?node-id=115%3A157&t=RpifEcByzqSp4on7-1) file. \ No newline at end of file diff --git a/VAMobile/documentation/docs/Flagship design library/Templates/NavigationModel.md b/VAMobile/documentation/docs/Flagship design library/Templates/NavigationModel.md index 4af7947b0cd..9663272d435 100644 --- a/VAMobile/documentation/docs/Flagship design library/Templates/NavigationModel.md +++ b/VAMobile/documentation/docs/Flagship design library/Templates/NavigationModel.md @@ -4,7 +4,7 @@ sidebar_position: 2 --- The VA Health & Benefits mobile app's navigation model is the same on both iOS and Android. - + * **Primary navigation:** The app uses a standard tab navigation for its primary navigation. * The tab bar/bottom navigation bar is located at the bottom of the screen and is persistent—it appears on every screen in the hierarchy (except those that are modally presented) so that the user has a clear sense of all the main sections of the app and quick access to switch between them. diff --git a/VAMobile/documentation/docs/Flagship design library/Templates/ScreenTypes.md b/VAMobile/documentation/docs/Flagship design library/Templates/ScreenTypes.md index 0b50b46062e..6095c74956e 100644 --- a/VAMobile/documentation/docs/Flagship design library/Templates/ScreenTypes.md +++ b/VAMobile/documentation/docs/Flagship design library/Templates/ScreenTypes.md @@ -17,7 +17,7 @@ The VA mobile app has 5 main screen types that fall into two categories: ## Hierarchical screens ### Category landing screen​ - + - **Definition:** The Category landing screen template is used by the navigation categories at the top of the app's hierarchy: Health, Benefits, and Payments. These navigation category landing screens group features of a similar type, providing permanent entry points to features and variable description text for each feature when applicable. The app's Home screen also uses this template, but has a special set of rules that distinguish it from the other top-level categories ([see Home Screen for a more detailed description](https://department-of-veterans-affairs.github.io/va-mobile-app/docs/Flagship%20design%20library/Templates/Home)). The category landing screen template displays the tab bar and a link to the Veteran Crisis line. It does not display a back button. - **Screen transition:** Screen transition between categories is top-level peer (fade through). @@ -31,7 +31,7 @@ The VA mobile app has 5 main screen types that fall into two categories: - [Github ticket: Category landing screen template](https://github.com/department-of-veterans-affairs/va-mobile-app/issues/3996) ### Feature landing screen​ - + - **Definition:** The “home” screen of a feature. Features are parent sections with multiple children that generally live within a category. A complex feature (like Prescriptions or Messages) can also have subsections. Displays the tab bar and a descriptive back button. - **Screen transition:** Horizontal (pushing on & off from right). @@ -46,7 +46,7 @@ The VA mobile app has 5 main screen types that fall into two categories: - [Github ticket: Feature landing/child screen template](https://app.zenhub.com/workspaces/va-mobile-frontendqa-60f1a34998bc75000f2a489f/issues/gh/department-of-veterans-affairs/va-mobile-app/3977) ### Child screen​ - + * **Definition:** Child screens live within a feature, generally an item in a list. It’s often the end point of a hierarchy. Displays the tab bar and a descriptive back button. * **Screen transition:** Horizontal (pushing on & off from right). @@ -62,7 +62,7 @@ The VA mobile app has 5 main screen types that fall into two categories: ## Modal screens ### Fullscreen task/subtask​ - + * **Definition:** A contained, linear flow that is presented modally at any level of the app’s hierarchy, opening on a layer over the current screen and taking up the whole screen. A fullscreen task/subtask can be one or multiple steps, and it requires an explicit close or cancel button to exit. Use a task/subtask to enable something complex in order to lock in focus. Because it covers the entire screen, it is the only modally appearing screen over which other dialogs or panels can appear. It does not display the tab bar. * **Behaviors & Logic:** @@ -81,7 +81,7 @@ The VA mobile app has 5 main screen types that fall into two categories: * [Github ticket: Fullscreen subtask template](https://app.zenhub.com/workspaces/va-mobile-frontendqa-60f1a34998bc75000f2a489f/issues/gh/department-of-veterans-affairs/va-mobile-app/3978) ### Large panel​ - + * **Definition:** A contained (single step) task that is presented modally at any level of the app’s hierarchy and appears as a card that covers most of the underlying content. It displays a close button to exit, but can also swipe down or tap on background to close. Use a large panel to display more in depth detail (multiple paragraphs) or a small (quick) task, when you need to lock in focus and limit the possibility of abandoning. Cannot appear over another panel. Does not display the tab bar. * **Behaviors & Logic:** @@ -104,7 +104,7 @@ The VA mobile app has 5 main screen types that fall into two categories: * [Github ticket: Large panel template](https://app.zenhub.com/workspaces/va-mobile-frontendqa-60f1a34998bc75000f2a489f/issues/gh/department-of-veterans-affairs/va-mobile-app/3979) ### Web view - + * **Definition:** A screen that displays content from an outside website without requiring the user to leave the app. Requires an explicit close or cancel button to exit. Does not display tab bar due to required web toolbar. diff --git a/VAMobile/documentation/docs/Operations/App metrics/Dashboard.md b/VAMobile/documentation/docs/Operations/App metrics/Dashboard.md index 9b7362ed764..a2b976a816c 100644 --- a/VAMobile/documentation/docs/Operations/App metrics/Dashboard.md +++ b/VAMobile/documentation/docs/Operations/App metrics/Dashboard.md @@ -8,7 +8,7 @@ sidebar_position: 1 [Monthly overview dashboard](https://lookerstudio.google.com/embed/reporting/e28cd59a-b2e5-4f29-8ae4-a4eea6d23f9c/page/p_6ji09oht5c) - + ## Feature specific boards diff --git a/VAMobile/documentation/docs/Operations/Releases/release-process.md b/VAMobile/documentation/docs/Operations/Releases/release-process.md index ffbfc0ab5de..27f57861ca3 100644 --- a/VAMobile/documentation/docs/Operations/Releases/release-process.md +++ b/VAMobile/documentation/docs/Operations/Releases/release-process.md @@ -2,4 +2,4 @@ title: Release process --- - \ No newline at end of file + \ No newline at end of file diff --git a/VAMobile/documentation/docs/QA/QualityAssuranceProcess/Accessibility/a11y-research-session-help.md b/VAMobile/documentation/docs/QA/QualityAssuranceProcess/Accessibility/a11y-research-session-help.md new file mode 100644 index 00000000000..f8aee15ce93 --- /dev/null +++ b/VAMobile/documentation/docs/QA/QualityAssuranceProcess/Accessibility/a11y-research-session-help.md @@ -0,0 +1,61 @@ +--- +title: Accessible Research Session Help +--- + +# Helpful tips for running an accessible research session + +*Last update: November 4, 2024* + +## Locating the VA: Health and Benefits app +With the recent update to iOS 18, users can now customize the appearance of their app icons without an additional shortcut or appearance customization app. There are now options available for light and dark mode for the app icons or to completely customize the color of the icons themselves. Prior to the update, we would have asked a sighted participant to locate the app icon with the white "VA" and blue background, however, a participant's app icon may no longer visually appear this way. On Android devices, it is also common for a user to customize the appearance of their app icons, font styles, etc. using shortcuts or apperance customization apps. + +Instead of relying on the app icon's appearance to describe and verify that the participant has the VA: Health and Benefits app installed, you should consider building a few extra minutes into your research study plan to verify that the participant has the correct app by asking them to open the app and visually look at the splash / login screen in their screen share to verify. + +If Perigean will be verifying this for you, notify your recruiter that they will need to request that the Veteran open the app and visually verify that they have the correct app installed. You should also consider supplying screenshots of the login screen to the recruiter in both dark and light modes, so that they have it to make the visual comparison themselves. + +For screen reader participants who are using VoiceOver or TalkBack, no matter which color option or icon style they may have chosen to use on their device, both screen readers will still announce the app as "VA". + + +## iOS + +### How to enable screen sharing in Zoom when using VoiceOver +1. In Zoom, navigate/swipe to the "share" (button) and double-tap +2. Double-tap on the first option: "screen" (button) +3. Double-tap on "start broadcast" (button) + - The participant will then hear a countdown (3, 2, 1) and the screen will begin broadcasting. + +### Screen Curtain +When VoiceOver is active and the participant has begun sharing their screen with you, you may notice that the screen is black and that you cannot see anything that is being shared. This is called **screen curtain**. If a participant has screen curtain enabled, you will not be able to see anything on the participants' screen unless they disable it. + +To disable screen curtain, instruct the participant to tap the screen four times using three fingers. This is called a three-finger quadruple tap. +- **Note:** If you instruct a user to turn off screen curtain during a session, offer to help them re-enable it before ending the session. Provide these instructions (the same instructions used to disable it) while they are still sharing their screen so that you can verify that screen curtain is re-enabled. + +## Android + +### How to enable screen sharing in Zoom when using TalkBack +1. By default, Zoom will typically default to "active speaker" mode. To display the toolbar, the participant can double-tap the screen with one finger. +2. Once the toolbar is displayed in Zoom, the particpant should navigate/swipe to the "share" (button) and double-tap + - _Share should be option 6 of 11 in the Zoom toolbar._ +3. The participant should navigate through the list until they get to "screen" (this is usually the 6th option in the list) and double-tap. +4. The participant will then read out the disclaimer for sharing their screen. Instruct the participant to navigate to "start now" (button) and double-tap. +5. After sharing the screen, Zoom will usually take the user out of Zoom and back to their home screen (or the last screen they were on prior to joining the meeting). Instruct them to return to / open the Zoom app to be able to access the chat area and access the link to the build. + - **Note:** After the participant opens the Zoom app again, it will typically default them back to the share option (although it will now announce as "stop share"). + - _Chat should be option 4 of 11 in the Zoom toolbar._ + +### Screen Curtain / Screen Shade +Some Android devices do have the ability to enable screen curtain on their device. If a participant has shared their screen with you during a session and their screen is black, dimmed, or is not changing, it is possible that their device support screen curtain and that it is enabled. Unfortunately, there is not a simple gesture availablet to disable screen curtain. + +To disable screen curtain on an Android device, the participant should: +1. Triple-tap the screen once to open the TalkBack menu. + - **Note:** On some devices, the gesture to open the TalkBack menu may not be available or may not work. In this case, you should instruct the user to navigate to their TalkBack settings within their device settings (Settings > Accessibility > TalkBack). +2. Instruct the participant to locate an option for "screen curtain", "screen shade", "show screen", etc. + - **Note:** Depending on the participant's Android device manufacturer (Google, Samsung, etc.), this feature may have a different name or a different way of announcing / listing the feature. The participant should listen for an option that might impact the visibility of the device screen. + - Alternatively, they might also be ale to activate their voice assistant (Google Assistant, Bixby, etc.) and instruct the voice assistant to show their screen, disable screen shade/curtain, etc. +3. This step will depend on the participant's Android device, but after they have located the option for the screen curtain / shade, they should follow any necessary steps to show their screen / disable screen curtain. This could be a simple double-tap gesture to show the screen or could take them to their TalkBack settings where they may need to toggle off the screen curtain. + - **Note:** It is recommended that you ask the participant to talk you through the steps that they are taking to disable the screen shade / curtain and that you (or an observer) make a quick note so that you can help them reactivate it at the end of the session. + +## Additional Resources +- [VoiceOver Gestures (iOS)](https://support.apple.com/guide/iphone/use-voiceover-gestures-iph3e2e2281/ios) +- [TalkBack Gestures (Android)](https://support.google.com/accessibility/android/answer/6151827?hl=en) +- [Inclusive Research for Screen Reader Users by Angela Fowler and Jamie Klenetsky Fay (Google Doc)](https://docs.google.com/document/d/1KvXZqzTm_Go1ZjzCmo8lNqe6Y2QOB9-9bt-RWsojGI0/edit?usp=sharing) + - **Note:** This guide is based on running inclusive research sessions with a computer and not on mobile devices. Not all of the information in the guide will apply to mobile-based research sessions. \ No newline at end of file diff --git a/VAMobile/documentation/docs/QA/QualityAssuranceProcess/Accessibility/index.md b/VAMobile/documentation/docs/QA/QualityAssuranceProcess/Accessibility/index.md index 0eab83a2e11..f09b022f204 100644 --- a/VAMobile/documentation/docs/QA/QualityAssuranceProcess/Accessibility/index.md +++ b/VAMobile/documentation/docs/QA/QualityAssuranceProcess/Accessibility/index.md @@ -8,27 +8,23 @@ We take a proactive, accessibility-first approach to everything we build. Access We also believe in accessibility beyond compliance. We don’t just meet the bare minimum of accessibility recommendations. We go above and beyond these recommendations to ensure we’re creating a product that is truly accessible to all of our users. - ## Accessibility principles - ### Based on VA standards The [VA design system](https://design.va.gov/) does not have comprehensive accessibility guidelines. However, it does [provide accessibility samples and guidelines](https://department-of-veterans-affairs.github.io/va-mobile-app/docs/QA/QualityAssuranceProcess/Accessibility#:~:text=provide%20accessibility%20samples%20and%20guidelines) when it comes to design, content, and components. We base our design system on this existing system with modifications for native mobile apps. - ### Content takes center stage All content throughout the app is clear and direct to ensure that users understand the information presented to them. A more thorough explanation of our best practices for accessible content can be found on the [content documentation page](/docs/Flagship%20design%20library/Content/content-style-guide). - ### Go beyond guideline standards -The VA Mobile app targets WCAG 2.2 Level AA and Level AAA success criteria and section 508 guidelines. For more information, see the following resources: +The VA Mobile app targets WCAG 2.2 Level AA and Level AAA success criteria and section 508 guidelines. We also reference MCAG (Mobile Content Accessiblity Guidelines), when applicable. For more information, see the following resources: - [WCAG 2.2](https://www.w3.org/TR/WCAG22/) - [508 guidelines](https://www.access-board.gov/ict/#508-chapter-1-application-and-administration) - +- [MCAG (Mobile Content Accessibility Guidelines)](https://getevinced.github.io/mcag/) ### Comprehensive disability support @@ -40,7 +36,6 @@ We aim to provide a usable experience for all Veterans and include disability su - Individuals with hearing impairments - Individuals with mental or cognitive disabilities - ### Assistive technology support Our app and components are tested for accessibility with automated and manual techniques. Users should expect to be able to access our app using modern assistive technologies. These include native and third-party tools like: @@ -51,21 +46,18 @@ Our app and components are tested for accessibility with automated and manual te - Bluetooth mouse and keyboard - Tools for readability - ### Beyond disabilities We believe that accessibility also goes beyond disabilities and we strive to provide access to our app for users with limited connectivity and device performance. - ### The Section 508 law Section 508 is a federal law that requires all U.S. government agencies to make their electronic and information technology and data accessible to everyone. By law, agencies must provide people with disabilities the same level of access as those without disabilities. - ## Resources -* [Accessibility checklist for UX designers](/docs/QA/QualityAssuranceProcess/Accessibility/a11y-checklist-ux-designers) -* [Accessibility checklist for content designers](/docs/QA/QualityAssuranceProcess/Accessibility/a11y-checklist-content-designers) -* [Haptic feedback](/docs/Flagship%20design%20library/Patterns/haptics) -* [VA.gov mobile accessibility testing plan](/docs/QA/QualityAssuranceProcess/Accessibility/testing-plan) -* [Native mobile app accessibility resources & articles](/docs/QA/QualityAssuranceProcess/Accessibility/resources) +- [Accessibility checklist for UX designers](/docs/QA/QualityAssuranceProcess/Accessibility/a11y-checklist-ux-designers) +- [Accessibility checklist for content designers](/docs/QA/QualityAssuranceProcess/Accessibility/a11y-checklist-content-designers) +- [Haptic feedback](/docs/Flagship%20design%20library/Patterns/haptics) +- [VA.gov mobile accessibility testing plan](/docs/QA/QualityAssuranceProcess/Accessibility/testing-plan) +- [Native mobile app accessibility resources & articles](/docs/QA/QualityAssuranceProcess/Accessibility/resources) diff --git a/VAMobile/documentation/docs/QA/QualityAssuranceProcess/a11y-research-session-help.md b/VAMobile/documentation/docs/QA/QualityAssuranceProcess/a11y-research-session-help.md new file mode 100644 index 00000000000..91fe358801c --- /dev/null +++ b/VAMobile/documentation/docs/QA/QualityAssuranceProcess/a11y-research-session-help.md @@ -0,0 +1,68 @@ +--- +title: Accessible Research Session Help +description: Helpful tips for running an accessible research session +--- + +## Locating the VA: Health and Benefits app + +With the recent update to iOS 18, users can now customize the appearance of their app icons without an additional shortcut or appearance customization app. There are options for light and dark mode for the app icons or to completely customize the color of the icons themselves. Prior to the update, we would have asked a sighted participant to locate the app icon with the white "VA" and blue background, however, a participant's app icon may no longer visually appear this way. On Android devices, it is also common for a user to customize their icons, font styles, etc. using shortcuts or apperance customization apps. + +Instead of relying on the app icon's appearance to describe and verify that the participant has the VA: Health and Benefits app installed, you should consider building a few extra minutes into your research study plan to verify that the participant has the app by asking them to open the app and visually looking at the splash / login screen in their screen share. + +If a third party company will be verifying this for you, notify your recruiter that they will need to request that the Veteran open the app and visually verify that they have the correct app installed. You should also consider supplying screenshots of the login screen to the recruiter in both dark and light modes, so that they have it to make the visual comparison themselves. + +For screen reader participants who are using VoiceOver or TalkBack, no matter what color options or icon style they may have chosen to use on their device, both screen readers will still announce the app as "VA". + +## iOS + +### How to enable screen sharing in Zoom when using VoiceOver + +1. In Zoom, navigate/swipe to the "share" (button) and double-tap +2. Double-tap on the first option: "screen" (button) +3. Double-tap on "start broadcast" (button) +4. The participant will then hear a countdown (3, 2, 1) and the screen will begin broadcasting. + +### Screen Curtain + +When VoiceOver is active and the participant has begun sharing their screen with you, you may notice that the screen is black and that you cannot see anything that is being shared. This is called [screen curtain](https://support.apple.com/en-us/111797). If a participant has screen curtain enabled, you will not be able to see anything on the participants' screen unless they disable it. + +[Read instructions on how to disable screen curtain](https://support.apple.com/guide/iphone/keep-the-screen-off-iph756788a12/ios) + +:::note +If you instruct a user to turn off screen curtain during a session, offer to help them re-enable it before ending the session. Provide these instructions (the same instructions used to disable it) while they are still sharing their screen so that you can verify that screen curtain is re-enabled. +::: + +## Android + +### How to enable screen sharing in Zoom when using TalkBack + +1. By default, Zoom will typically default to "active speaker" mode. To display the toolbar, the participant can double-tap the screen with one finger. +2. Once the toolbar is displayed in Zoom, the particpant should navigate/swipe to the "share" (button) and double-tap ("share" should be option 6 of 11 in the Zoom toolbar). +3. The participant should navigate through the list until they get to "screen" (this is usually the 6th option in the list), then double-tap. +4. The participant will then read out the disclaimer for sharing their screen. Instruct the participant to navigate to "start now" (button) and double-tap. +5. After sharing the screen, Zoom will usually take the user out of Zoom and back to their home screen (or the last screen they were on prior to joining the meeting). Instruct them to return to the Zoom app to be able to access the chat area and access the link to the build. + +:::note +After the participant opens the Zoom app again, it will typically default them back to the share option, although it will now announce as "stop share" (Chat should be option 4 of 11 in the Zoom toolbar). +::: + +### Screen Curtain / Screen Shade + +Some Android devices do have the ability to enable screen curtain on their device. If a participant has shared their screen with you during a session and their screen is black, dimmed, or is not changing, it is possible that their device support screen curtain and that it is enabled. Unfortunately, there is not a simple gesture available to disable screen curtain. + +To disable screen curtain on an Android device, the participant should: + +1. Triple-tap the screen once or navigate to TalkBack settings within device settings (Settings > Accessibility > TalkBack) to open the TalkBack menu. +2. Instruct the participant to locate an option for "screen curtain", "screen shade", or "show screen". Depending on the participant's Android device manufacturer (Google, Samsung, etc.), this feature may have a different name or a different way of announcing / listing the feature. The participant should listen for an option that might impact the visibility of the device screen.Alternatively, they might also be ale to activate their voice assistant (Google Assistant, Bixby, etc.) and instruct the voice assistant to show their screen, disable screen shade/curtain, etc. +3. (This step will depend on the participant's Android device) After they have located the option for the screen curtain / shade, they should follow any necessary steps to show their screen and disable screen curtain. This could be a simple double-tap gesture to show the screen or could take them to their TalkBack settings where they may need to toggle off the screen curtain. + +:::note +It is recommended that you ask the participant to talk you through the steps that they are taking to disable the screen shade / curtain and that you (or an observer) make a quick note so that you can help them reactivate it at the end of the session. +::: + +## Additional Resources + +- [VoiceOver Gestures (iOS)](https://support.apple.com/guide/iphone/use-voiceover-gestures-iph3e2e2281/ios) +- [TalkBack Gestures (Android)](https://support.google.com/accessibility/android/answer/6151827?hl=en) +- [Inclusive Research for Screen Reader Users by Angela Fowler and Jamie Klenetsky Fay (Google Doc)](https://docs.google.com/document/d/1KvXZqzTm_Go1ZjzCmo8lNqe6Y2QOB9-9bt-RWsojGI0/edit?usp=sharing) + - **Note:** This guide is based on running inclusive research sessions with a computer and not on mobile devices. Not all of the information in the guide will apply to mobile-based research sessions. diff --git a/VAMobile/documentation/docs/QA/QualityAssuranceProcess/index.md b/VAMobile/documentation/docs/QA/QualityAssuranceProcess/index.md index dae3addd2cd..d73a766cef1 100644 --- a/VAMobile/documentation/docs/QA/QualityAssuranceProcess/index.md +++ b/VAMobile/documentation/docs/QA/QualityAssuranceProcess/index.md @@ -69,8 +69,9 @@ All issue tickets should be written using the [issue ticket template](https://gi | **Frequency** | **Definition** | **Examples** | | --- | --- | --- | -| High frequency | Affects a component, screen, or action used by 15% or more of monthly users | Authentication, home page (including What's New and Encouraged Update), category landing screens, feature landing or child screens for most-used features (Claims, Upcoming Appointments, and Decision Letters), feature landing or child screens for next-most-used features (Prescriptions, Payments, Messaging, Letters, Disability Rating, and Military Info), and downloading a letter (decision or any other kind) | -| Low frequency | Affects a component, screen, or action used by 14% or fewer of monthly users | All other app actions (send message, upload file, etc); and all screens not listed in high frequency examples | +| High frequency | Affects a component, screen, or action used by 15% or more of monthly users | - Authentication/login
- Home page (including What's New and Encouraged Update)
- Category landing screens (Health, Benefits, Payments and Profile)
- Feature landing and child screens for most-used features (Prescriptions, Upcoming Appointments, and Secure Messaging)
- Feature landing and child screens for next-most-used features (Claims, Disability Rating, Letters, Payment History)
- Key actions from the features already listed: med refill, sending a message (new or reply), and downloading a letter (decision or any other kind) | +| Low frequency | Affects a component, screen, or action used by 14% or fewer of monthly users | All other all screens and app actions (upload file, change demographic info, etc) not listed in high frequency examples | +_Frequency examples informed by the [Flagship Mobile Monthly Report](https://lookerstudio.google.com/reporting/e28cd59a-b2e5-4f29-8ae4-a4eea6d23f9c/page/p_xatxe90k9c)_ ### User-submitted Bug Reports diff --git a/VAMobile/documentation/docs/UX/Foundations/Information-Architecture.md b/VAMobile/documentation/docs/UX/Foundations/Information-Architecture.md index ff717f20798..4f3fd64b994 100644 --- a/VAMobile/documentation/docs/UX/Foundations/Information-Architecture.md +++ b/VAMobile/documentation/docs/UX/Foundations/Information-Architecture.md @@ -37,7 +37,7 @@ Understanding what’s guiding the app’s current information architecture and ### Sitemap/flow diagram A sitemap is a planning tool that visually shows how information will be grouped and labeled, where content will be located, and how a user will move through the app. This adaptation of a standard sitemap includes the system display logic for screens that have variants, key actions (buttons, links), common processes and points where it makes use of native mobile integrations. **This is the source of truth for the app’s IA.** - + [VA Mobile App - Detailed sitemap 2.0 (FigJam)](https://www.figma.com/file/TEEgHdlibzCilCj4LviHVF/VA-Mobile-app---Detailed-Sitemap-2.0?type=whiteboard&node-id=0%3A1&t=NOXEk15mCNO0XQ5Q-1) diff --git a/VAMobile/e2e/tests/AppointmentsExpanded.e2e.ts b/VAMobile/e2e/tests/AppointmentsExpanded.e2e.ts index 630d17cf08e..46d00f5bd0f 100644 --- a/VAMobile/e2e/tests/AppointmentsExpanded.e2e.ts +++ b/VAMobile/e2e/tests/AppointmentsExpanded.e2e.ts @@ -22,14 +22,25 @@ const checkMedicationWording = async ({ appointmentType === 'VA' || appointmentType === 'ATLAS' || appointmentType === 'GFE' || - appointmentType === 'Home' + appointmentType === 'Home' || + appointmentType === 'Claim' ) { if ( appointmentStatus === 'Canceled' || (!pastAppointment && (appointmentStatus === 'Upcoming' || appointmentStatus === 'Confirmed')) ) { await expect(element(by.text('Prepare for your appointment'))).toExist() - await expect(element(by.text('Find a full list of things to bring to your appointment'))).toExist() + if ( + appointmentType === 'Phone' || + appointmentType === 'CC' || + appointmentType === 'Onsite' || + appointmentType === 'VA' || + appointmentType === 'ATLAS' || + appointmentType === 'GFE' || + appointmentType === 'Home' + ) { + await expect(element(by.text('Find a full list of things to bring to your appointment'))).toExist() + } if (appointmentType === 'ATLAS' || appointmentType === 'Home' || appointmentType === 'GFE') { await expect(element(by.text('Get your device ready to join.'))).toExist() @@ -41,7 +52,26 @@ const checkMedicationWording = async ({ await element(by.id('prepareForVideoVisitTestID')).tap() await expect(element(by.text('Appointments help'))).toExist() await element(by.text('Close')).tap() + } else if (appointmentType === 'Claim') { + await expect(element(by.text('You don’t need to bring anything to your exam.'))).toExist() + await expect( + element( + by.text( + 'If you have any new non-VA medication records (like records from a recent surgery or illness), be sure to submit them before your appointment.', + ), + ), + ).toExist() + await expect(element(by.text('Learn more about claim exam appointments'))).toExist() } else { + await expect(element(by.text('You don’t need to bring anything to your exam.'))).not.toExist() + await expect( + element( + by.text( + 'If you have any new non-VA medication records (like records from a recent surgery or illness), be sure to submit them before your appointment.', + ), + ), + ).not.toExist() + await expect(element(by.text('Learn more about claim exam appointments'))).not.toExist() await expect(element(by.text('Get your device ready to join.'))).not.toExist() await expect(element(by.id('prepareForVideoVisitTestID'))).not.toExist() } diff --git a/VAMobile/e2e/tests/AvailabilityFramework.e2e.ts b/VAMobile/e2e/tests/AvailabilityFramework.e2e.ts index 064495a41be..c838ec428fa 100644 --- a/VAMobile/e2e/tests/AvailabilityFramework.e2e.ts +++ b/VAMobile/e2e/tests/AvailabilityFramework.e2e.ts @@ -131,13 +131,13 @@ const AFNavigationForIndividual = [ ['BenefitLetters.e2e', 'WG_ClaimLettersScreen', 'Benefits', 'Claims', 'Claim letters'], ['Claims.e2e', 'WG_ClaimDetailsScreen', 'Benefits', 'Claims', 'Claims history', 'Received December 05, 2021'], // [ - // 'Claims.e2e', - // 'WG_SubmitEvidence', - // 'Benefits', - // 'Claims', - // 'Claims history', - // 'Received December 05, 2021', - // 'Submit evidence', + // 'Claims.e2e', + // 'WG_SubmitEvidence', + // 'Benefits', + // 'Claims', + // 'Claims history', + // 'Received December 05, 2021', + // 'Submit evidence', // ], ['Appeals.e2e', 'WG_AppealDetailsScreen', 'Benefits', 'Claims', 'Claims history', 'Received July 17, 2008'], [ diff --git a/VAMobile/e2e/tests/Messages.e2e.ts b/VAMobile/e2e/tests/Messages.e2e.ts index ea5110971ad..fa91c9bc234 100644 --- a/VAMobile/e2e/tests/Messages.e2e.ts +++ b/VAMobile/e2e/tests/Messages.e2e.ts @@ -11,16 +11,17 @@ import { } from './utils' export const MessagesE2eIdConstants = { - MESSAGE_1_ID: 'Unread: Martha Kaplan, Md October 26, 2021 Medication: Naproxen side effects', - MESSAGE_1_READ_ID: 'Martha Kaplan, Md October 26, 2021 Medication: Naproxen side effects', - MESSAGE_2_ID: 'Unread: Diana Persson, Md October 26, 2021 Has attachment COVID: Prepping for your visit', - MESSAGE_2_READ_ID: 'Diana Persson, Md October 26, 2021 Has attachment COVID: Prepping for your visit', - MESSAGE_3_ID: 'Unread: Sarah Kotagal, Md October 26, 2021 General: Your requested info', - MESSAGE_4_ID: 'Cheryl Rodger, Md October 26, 2021 Appointment: Please read and prepare appropriately', - MESSAGE_5_ID: 'Vija A. Ravi, Md October 21, 2021 General: Summary of visit', - MESSAGE_6_ID: 'Ratana, Narin October 21, 2021 Test: Preparing for your visit', - MESSAGE_7_ID: 'Ratana, Narin September 17, 2021 Education: Good morning to you', - MESSAGE_10_ID: 'Ratana, Narin September 17, 2021 COVID: Test', + MESSAGE_1_ID: 'Unread: Martha Kaplan, Md October 26, 2024 Medication: Naproxen side effects', + MESSAGE_1_READ_ID: 'Martha Kaplan, Md October 26, 2024 Medication: Naproxen side effects', + MESSAGE_2_ID: 'Unread: Diana Persson, Md October 19, 2024 Has attachment COVID: Prepping for your visit', + MESSAGE_2_READ_ID: 'Diana Persson, Md October 19, 2024 Has attachment COVID: Prepping for your visit', + MESSAGE_3_ID: 'Unread: Sarah Kotagal, Md August 26, 2024 General: Your requested info', + MESSAGE_3_READ_ID: 'Sarah Kotagal, Md August 26, 2024 General: Your requested info', + MESSAGE_4_ID: 'Cheryl Rodger, Md August 26, 2024 Appointment: Please read and prepare appropriately', + MESSAGE_5_ID: 'Vija A. Ravi, Md July 21, 2024 General: Summary of visit', + MESSAGE_6_ID: 'Ratana, Narin July 21, 2024 Test: Preparing for your visit', + MESSAGE_7_ID: 'Ratana, Narin June 17, 2024 Education: Good morning to you', + MESSAGE_10_ID: 'Ratana, Narin February 17, 2024 COVID: Test', FOLDERS_ID: 'foldersID', MESSAGES_ID: 'messagesTestID', REVIEW_MESSAGE_REPLY_ID: 'replyTestID', @@ -116,22 +117,17 @@ describe('Messages Screen', () => { it('verify message OLDER than 45 days information', async () => { await element(by.id(MessagesE2eIdConstants.MESSAGES_ID)).scrollTo('top') - await element(by.id(MessagesE2eIdConstants.MESSAGE_2_ID)).tap() + await expect(element(by.id(MessagesE2eIdConstants.MESSAGE_3_ID))).toBeVisible() + await element(by.id(MessagesE2eIdConstants.MESSAGE_3_ID)).tap() await expect(element(by.id('secureMessagingOlderThan45DaysAlertID'))).toExist() await expect(element(by.text(MessagesE2eIdConstants.ONLY_USE_MESSAGES_TEXT))).toExist() await expect(element(by.id(MessagesE2eIdConstants.REVIEW_MESSAGE_REPLY_ID))).not.toExist() await expect(element(by.id(CommonE2eIdConstants.START_NEW_MESSAGE_BUTTON_ID))) }) - it('verify the message just opened is displayed as read', async () => { - await element(by.id(MessagesE2eIdConstants.BACK_TO_MESSAGES_ID)).tap() - await expect( - element(by.id('Diana Persson, Md October 26, 2021 Has attachment COVID: Prepping for your visit')), - ).toExist() - await expect(element(by.text('Inbox (2)'))).toExist() - }) - it('verify message NEWER than 45 days information', async () => { + await element(by.id(MessagesE2eIdConstants.BACK_TO_MESSAGES_ID)).tap() + await element(by.id(MessagesE2eIdConstants.MESSAGES_ID)).scrollTo('top') await element(by.id(MessagesE2eIdConstants.MESSAGE_1_ID)).tap() await expect(element(by.text(MessagesE2eIdConstants.ONLY_USE_MESSAGES_TEXT))).toExist() await expect(element(by.id(MessagesE2eIdConstants.REVIEW_MESSAGE_REPLY_ID))).toExist() @@ -176,21 +172,26 @@ describe('Messages Screen', () => { } }) - it('verify medication message details', async () => { + it('verify the messages just opened are displayed as read', async () => { await element(by.id(MessagesE2eIdConstants.BACK_TO_MESSAGES_ID)).tap() - await element(by.id('Martha Kaplan, Md October 26, 2021 Medication: Naproxen side effects')).tap() + await expect(element(by.id(MessagesE2eIdConstants.MESSAGE_1_READ_ID))).toExist() + await expect(element(by.text('Inbox (1)'))).toExist() + }) + + it('verify medication message details', async () => { + await element(by.id(MessagesE2eIdConstants.MESSAGE_1_READ_ID)).tap() await expect(element(by.text('Medication: Naproxen side effects'))).toExist() await element(by.id(MessagesE2eIdConstants.BACK_TO_MESSAGES_ID)).tap() }) it('verify COVID message details', async () => { - await element(by.id('Diana Persson, Md October 26, 2021 Has attachment COVID: Prepping for your visit')).tap() + await element(by.id(MessagesE2eIdConstants.MESSAGE_2_ID)).tap() await expect(element(by.text('COVID: Your requested info'))).toExist() await element(by.id(MessagesE2eIdConstants.BACK_TO_MESSAGES_ID)).tap() }) it('verify general message details', async () => { - await element(by.id(MessagesE2eIdConstants.MESSAGE_3_ID)).tap() + await element(by.id(MessagesE2eIdConstants.MESSAGE_3_READ_ID)).tap() await expect(element(by.text('General: Vaccine Booster'))).toExist() await element(by.id(MessagesE2eIdConstants.BACK_TO_MESSAGES_ID)).tap() }) @@ -447,13 +448,11 @@ describe('Messages Screen', () => { await element(by.text(MessagesE2eIdConstants.MESSAGE_CANCEL_DELETE_TEXT)).tap() await expect(element(by.id(CommonE2eIdConstants.START_NEW_MESSAGE_BUTTON_ID))).toExist() await expect(element(by.id(MessagesE2eIdConstants.FOLDERS_ID))).toExist() - await expect( - element(by.id('Diana Persson, Md October 26, 2021 Has attachment COVID: Prepping for your visit')), - ).toExist() + await expect(element(by.id(MessagesE2eIdConstants.MESSAGE_2_READ_ID))).toExist() }) it('verify the attachment is on message with attachment', async () => { - await element(by.id('Diana Persson, Md October 26, 2021 Has attachment COVID: Prepping for your visit')).tap() + await element(by.id(MessagesE2eIdConstants.MESSAGE_2_READ_ID)).tap() await expect(element(by.text('COVID-19-mRNA-infographic_G_508.pdf (0.17 MB)'))).toExist() }) @@ -467,7 +466,7 @@ describe('Messages Screen', () => { it('verify a message threads', async () => { await element( by.id( - 'Va Flagship Mobile Applications Interface 2_dayt29 November 16, 2021 Appointment: Preparing for your visit', + 'Va Flagship Mobile Applications Interface 2_dayt29 November 16, 2024 Appointment: Preparing for your visit', ), ).tap() await element(by.id(MessagesE2eIdConstants.VIEW_MESSAGE_ID)).scrollTo('bottom') @@ -566,7 +565,7 @@ describe('Messages Screen', () => { await expect( element( by.id( - 'Va Flagship Mobile Applications Interface 2_dayt29 November 16, 2021 Appointment: Preparing for your visit', + 'Va Flagship Mobile Applications Interface 2_dayt29 November 16, 2024 Appointment: Preparing for your visit', ), ), ).toExist() @@ -575,7 +574,7 @@ describe('Messages Screen', () => { it('verify a sent messages can display attachments', async () => { await element( by.id( - 'Va Flagship Mobile Applications Interface 2_dayt29 November 3, 2021 Has attachment Education: Education Inquiry', + 'Va Flagship Mobile Applications Interface 2_dayt29 November 3, 2024 Has attachment Education: Education Inquiry', ), ).tap() await expect(element(by.text('rn_image_picker_lib_temp_52383988-331b-4acc-baaf-9ae21c8a508e.jpg (0.92 MB)'))) diff --git a/VAMobile/e2e/tests/Onboarding.e2e.ts b/VAMobile/e2e/tests/Onboarding.e2e.ts index e0bada940ec..7a6c41c3f69 100644 --- a/VAMobile/e2e/tests/Onboarding.e2e.ts +++ b/VAMobile/e2e/tests/Onboarding.e2e.ts @@ -5,7 +5,7 @@ import { CommonE2eIdConstants, checkImages, loginToDemoMode } from './utils' export const OnboardingE2eIdConstants = { VA_ICON_ID: 'VAIconOnboardingLogo', DONE_NEXT_BUTTON_ID: 'onboardingDoneNextButtonID', - SKIP_BACK_BUTTON_ID: 'onboardingSkipBackButtonID', + NOTIFICATIONS_PAGE_HEADER: 'Stay updated with app notifications', } beforeAll(async () => { @@ -26,7 +26,7 @@ describe('Onboarding Screen', () => { ), ), ).toExist() - await expect(element(by.id(OnboardingE2eIdConstants.SKIP_BACK_BUTTON_ID))).toExist() + await expect(element(by.id(CommonE2eIdConstants.SKIP_BACK_BUTTON_ID))).toExist() await expect(element(by.id(OnboardingE2eIdConstants.DONE_NEXT_BUTTON_ID))).toExist() }) @@ -37,7 +37,7 @@ describe('Onboarding Screen', () => { await expect(element(by.text('Refill your prescriptions'))).toExist() await expect(element(by.text('Communicate with your health care team'))).toExist() await expect(element(by.text('Review your appointments'))).toExist() - await expect(element(by.id(OnboardingE2eIdConstants.SKIP_BACK_BUTTON_ID))).toExist() + await expect(element(by.id(CommonE2eIdConstants.SKIP_BACK_BUTTON_ID))).toExist() await expect(element(by.id(OnboardingE2eIdConstants.DONE_NEXT_BUTTON_ID))).toExist() }) @@ -48,7 +48,7 @@ describe('Onboarding Screen', () => { await expect(element(by.text('Review your disability rating'))).toExist() await expect(element(by.text('Check the status of your claims and appeals'))).toExist() await expect(element(by.label('Download common V-A letters'))).toExist() - await expect(element(by.id(OnboardingE2eIdConstants.SKIP_BACK_BUTTON_ID))).toExist() + await expect(element(by.id(CommonE2eIdConstants.SKIP_BACK_BUTTON_ID))).toExist() await expect(element(by.id(OnboardingE2eIdConstants.DONE_NEXT_BUTTON_ID))).toExist() }) @@ -58,33 +58,38 @@ describe('Onboarding Screen', () => { await expect(element(by.text('Use our payments tools to manage tasks like these:'))).toExist() await expect(element(by.text('Update your direct deposit information'))).toExist() await expect(element(by.text('Review the history of payments we’ve sent to you'))).toExist() - await expect(element(by.id(OnboardingE2eIdConstants.SKIP_BACK_BUTTON_ID))).toExist() + await expect(element(by.id(CommonE2eIdConstants.SKIP_BACK_BUTTON_ID))).toExist() await expect(element(by.id(OnboardingE2eIdConstants.DONE_NEXT_BUTTON_ID))).toExist() }) it('should tap back and verify the previous page is displayed', async () => { - await element(by.id(OnboardingE2eIdConstants.SKIP_BACK_BUTTON_ID)).tap() + await element(by.id(CommonE2eIdConstants.SKIP_BACK_BUTTON_ID)).tap() await expect(element(by.text('Manage your benefits'))).toExist() - await element(by.id(OnboardingE2eIdConstants.SKIP_BACK_BUTTON_ID)).tap() + await element(by.id(CommonE2eIdConstants.SKIP_BACK_BUTTON_ID)).tap() await expect(element(by.text('Manage your health care'))).toExist() - await element(by.id(OnboardingE2eIdConstants.SKIP_BACK_BUTTON_ID)).tap() + await element(by.id(CommonE2eIdConstants.SKIP_BACK_BUTTON_ID)).tap() await expect(element(by.text('Welcome, Kimberly'))).toExist() }) - it('verify the home page is displayed after tapping done', async () => { + it('verify the notifications page is displayed after tapping done', async () => { await element(by.id(OnboardingE2eIdConstants.DONE_NEXT_BUTTON_ID)).tap() await element(by.id(OnboardingE2eIdConstants.DONE_NEXT_BUTTON_ID)).tap() await element(by.id(OnboardingE2eIdConstants.DONE_NEXT_BUTTON_ID)).tap() await element(by.id(OnboardingE2eIdConstants.DONE_NEXT_BUTTON_ID)).tap() - await expect(element(by.text(CommonE2eIdConstants.HOME_ACTIVITY_HEADER_TEXT))).toExist() + await expect(element(by.text(OnboardingE2eIdConstants.NOTIFICATIONS_PAGE_HEADER))).toExist() }) - it('verify the home page is displayed after skipping', async () => { + it('verify the notifications page is displayed after skipping', async () => { await device.uninstallApp() await device.installApp() await device.launchApp({ newInstance: true, permissions: { notifications: 'YES' } }) await loginToDemoMode(false) - await element(by.id(OnboardingE2eIdConstants.SKIP_BACK_BUTTON_ID)).tap() + await element(by.id(CommonE2eIdConstants.SKIP_BACK_BUTTON_ID)).tap() + await expect(element(by.text(OnboardingE2eIdConstants.NOTIFICATIONS_PAGE_HEADER))).toExist() + }) + + it('verify the home page is displayed after tapping turn on notifications', async () => { + await element(by.text('Turn on notifications')).tap() await expect(element(by.text(CommonE2eIdConstants.HOME_ACTIVITY_HEADER_TEXT))).toExist() }) }) diff --git a/VAMobile/e2e/tests/Payments.e2e.ts b/VAMobile/e2e/tests/Payments.e2e.ts index 8d36aba215a..04c750c24b2 100644 --- a/VAMobile/e2e/tests/Payments.e2e.ts +++ b/VAMobile/e2e/tests/Payments.e2e.ts @@ -50,7 +50,7 @@ describe('Payments Screen', () => { it('payment details: verify the payment details for paper check', async () => { await element(by.id(PaymentsE2eIDConstants.PAYMENT_HISTORY_1_ID)).atIndex(0).tap() - await expect(element(by.text('June 1, 2017'))).toExist() + await expect(element(by.text('June 1, 2024'))).toExist() await expect(element(by.text('Regular Chapter 31'))).toExist() await expect(element(by.text('$603.33'))).toExist() await expect(element(by.text('Paper Check'))).toExist() @@ -90,14 +90,14 @@ describe('Payments Screen', () => { await element(by.id(PaymentsE2eIDConstants.PAYMENTS_YEAR_PICKER_ID)).tap() await expect(element(by.text('Select a year'))).toExist() await element(by.id(PaymentsE2eIDConstants.SELECT_A_YEAR_CANCEL_ID)).tap() - await expect(element(by.text('2017')).atIndex(0)).toExist() + await expect(element(by.text('2024')).atIndex(0)).toExist() }) - it('should tap on and select 2016 from the select a year picker', async () => { + it('should tap on and select 2023 from the select a year picker', async () => { await element(by.id(PaymentsE2eIDConstants.PAYMENTS_YEAR_PICKER_ID)).tap() - await element(by.text('2016')).tap() + await element(by.text('2023')).tap() await element(by.id(PaymentsE2eIDConstants.SELECT_A_YEAR_CONFIRM_ID)).tap() - await expect(element(by.text('2016')).atIndex(0)).toExist() + await expect(element(by.text('2023')).atIndex(0)).toExist() }) it('should verify the next and back page arrows work', async () => { diff --git a/VAMobile/e2e/tests/utils.ts b/VAMobile/e2e/tests/utils.ts index 2b0baf4b7d5..a54dbc36ad6 100644 --- a/VAMobile/e2e/tests/utils.ts +++ b/VAMobile/e2e/tests/utils.ts @@ -27,7 +27,7 @@ export const CommonE2eIdConstants = { DEMO_MODE_INPUT_ID: 'demo-mode-password', DEMO_BTN_ID: 'demo-btn', SIGN_IN_BTN_ID: 'Sign in', - SKIP_BTN_TEXT: 'Skip', + TURN_ON_NOTIFICATIONS_TEXT: 'Turn on notifications', VETERAN_CRISIS_LINE_BTN_TEXT: 'Talk to the Veterans Crisis Line now', VETERAN_CRISIS_LINE_BTN_ID: 'veteransCrisisLineID', VETERAN_CRISIS_LINE_BACK_ID: 'veteranCrisisLineBackID', @@ -87,6 +87,13 @@ export const CommonE2eIdConstants = { CLAIMS_DETAILS_BACK_ID: 'claimsDetailsBackTestID', CLAIMS_HISTORY_BACK_ID: 'claimsHistoryBackTestID', CLAIMS_HISTORY_CLOSED_TAB_ID: 'claimsHistoryClosedID', + SKIP_BACK_BUTTON_ID: 'onboardingSkipBackButtonID', + HEALTH_TAB_BUTTON_ID: 'Health', + PAYMENTS_TAB_BUTTON_ID: 'Payments', + BENEFITS_TAB_BUTTON_ID: 'Benefits', + HOME_TAB_BUTTON_ID: 'Home', + AF_APP_UPDATE_BUTTON_TOGGLE_ID: 'remoteConfigAppUpdateTestID', + AF_ENABLE_TOGGLE_ID: 'remoteConfigEnableTestID', } /** Log the automation into demo mode @@ -122,15 +129,22 @@ export async function loginToDemoMode(skipOnboarding = true, pushNotifications?: await element(by.id(CommonE2eIdConstants.DEMO_MODE_INPUT_ID)).tapReturnKey() await element(by.id(CommonE2eIdConstants.DEMO_BTN_ID)).multiTap(2) - await element(by.text(CommonE2eIdConstants.SIGN_IN_BTN_ID)).tap() + await element(by.id(CommonE2eIdConstants.SIGN_IN_BTN_ID)).tap() if (skipOnboarding === true) { - const ifCarouselSkipBtnExist = await checkIfElementIsPresent(CommonE2eIdConstants.SKIP_BTN_TEXT, true) + const ifCarouselSkipBtnExist = await checkIfElementIsPresent(CommonE2eIdConstants.SKIP_BACK_BUTTON_ID) if (ifCarouselSkipBtnExist) { - await element(by.text(CommonE2eIdConstants.SKIP_BTN_TEXT)).tap() + await element(by.id(CommonE2eIdConstants.SKIP_BACK_BUTTON_ID)).tap() } } + const turnOnNotificationsBtnExist = await checkIfElementIsPresent( + CommonE2eIdConstants.TURN_ON_NOTIFICATIONS_TEXT, + true, + ) + if (turnOnNotificationsBtnExist) { + await element(by.text(CommonE2eIdConstants.TURN_ON_NOTIFICATIONS_TEXT)).tap() + } } /** this function is to see if a element is present that could sometime not be like the carousel for example @@ -316,7 +330,7 @@ export async function openMilitaryInformation() { } export async function openHealth() { - await element(by.text(CommonE2eIdConstants.HEALTH_TAB_BUTTON_TEXT)).tap() + await element(by.id(CommonE2eIdConstants.HEALTH_TAB_BUTTON_ID)).tap() } export async function openAppointments() { @@ -324,7 +338,7 @@ export async function openAppointments() { } export async function openPayments() { - await element(by.text(CommonE2eIdConstants.PAYMENTS_TAB_BUTTON_TEXT)).tap() + await element(by.id(CommonE2eIdConstants.PAYMENTS_TAB_BUTTON_ID)).tap() } export async function openDirectDeposit() { @@ -344,7 +358,7 @@ export async function openVAPaymentHistory() { } export async function openBenefits() { - await element(by.text(CommonE2eIdConstants.BENEFITS_TAB_BUTTON_TEXT)).tap() + await element(by.id(CommonE2eIdConstants.BENEFITS_TAB_BUTTON_ID)).tap() } export async function openLetters() { @@ -392,11 +406,11 @@ export async function enableAF(AFFeature, AFUseCase, AFAppUpdate = false) { await openProfile() await openSettings() await openDeveloperScreen() - await waitFor(element(by.text('Remote Config'))) + await waitFor(element(by.text(CommonE2eIdConstants.REMOTE_CONFIG_BUTTON_TEXT))) .toBeVisible() .whileElement(by.id('developerScreenTestID')) .scroll(200, 'down') - await element(by.text('Remote Config')).tap() + await element(by.text(CommonE2eIdConstants.REMOTE_CONFIG_BUTTON_TEXT)).tap() if (AFUseCase === 'DenyAccess') { await waitFor(element(by.text(CommonE2eIdConstants.IN_APP_REVIEW_TOGGLE_TEXT))) .toBeVisible() @@ -412,30 +426,30 @@ export async function enableAF(AFFeature, AFUseCase, AFAppUpdate = false) { if (AFAppUpdate) { try { - await expect(element(by.id('remoteConfigAppUpdateTestID'))).toHaveToggleValue(true) + await expect(element(by.id(CommonE2eIdConstants.AF_APP_UPDATE_BUTTON_TOGGLE_ID))).toHaveToggleValue(true) } catch (ex) { - await element(by.text('appUpdateButton')).tap() + await element(by.id(CommonE2eIdConstants.AF_APP_UPDATE_BUTTON_TOGGLE_ID)).tap() } } else if (AFFeature === 'WG_Health') { try { - await expect(element(by.id('remoteConfigEnableTestID'))).toHaveToggleValue(false) + await expect(element(by.id(CommonE2eIdConstants.AF_ENABLE_TOGGLE_ID))).toHaveToggleValue(false) } catch (ex) { - await element(by.text('Enabled')).tap() + await element(by.id(CommonE2eIdConstants.AF_ENABLE_TOGGLE_ID)).tap() } } if (!AFAppUpdate) { if (AFUseCase === 'AllowFunction') { try { - await expect(element(by.id('remoteConfigEnableTestID'))).toHaveToggleValue(false) + await expect(element(by.id(CommonE2eIdConstants.AF_ENABLE_TOGGLE_ID))).toHaveToggleValue(false) } catch (ex) { - await element(by.text('Enabled')).tap() + await element(by.id(CommonE2eIdConstants.AF_ENABLE_TOGGLE_ID)).tap() } } else if (AFUseCase === 'DenyAccess') { try { - await expect(element(by.id('remoteConfigEnableTestID'))).toHaveToggleValue(false) + await expect(element(by.id(CommonE2eIdConstants.AF_ENABLE_TOGGLE_ID))).toHaveToggleValue(false) } catch (ex) { - await element(by.text('Enabled')).tap() + await element(by.id(CommonE2eIdConstants.AF_ENABLE_TOGGLE_ID)).tap() } } } @@ -457,7 +471,7 @@ export async function enableAF(AFFeature, AFUseCase, AFAppUpdate = false) { await loginToDemoMode() } } else { - await element(by.text('Home')).tap() + await element(by.id(CommonE2eIdConstants.HOME_TAB_BUTTON_ID)).tap() } } @@ -471,11 +485,11 @@ export async function disableAF(featureNavigationArray, AFFeature, AFFeatureName await openProfile() await openSettings() await openDeveloperScreen() - await waitFor(element(by.text('Remote Config'))) + await waitFor(element(by.text(CommonE2eIdConstants.REMOTE_CONFIG_BUTTON_TEXT))) .toBeVisible() .whileElement(by.id('developerScreenTestID')) .scroll(200, 'down') - await element(by.text('Remote Config')).tap() + await element(by.text(CommonE2eIdConstants.REMOTE_CONFIG_BUTTON_TEXT)).tap() await waitFor(element(by.text(AFFeature))) .toBeVisible() .whileElement(by.id(CommonE2eIdConstants.REMOTE_CONFIG_TEST_ID)) @@ -484,7 +498,7 @@ export async function disableAF(featureNavigationArray, AFFeature, AFFeatureName await element(by.text('Enabled')).tap() await element(by.text('Save')).tap() - await element(by.text('Home')).tap() + await element(by.id(CommonE2eIdConstants.HOME_TAB_BUTTON_ID)).tap() if (featureNavigationArray !== undefined) { await navigateToFeature(featureNavigationArray) @@ -584,17 +598,19 @@ export async function verifyAF(featureNavigationArray, AFUseCase, AFUseCaseUpgra if (device.getPlatform() === 'android') { await device.disableSynchronization() try { - await element(by.text('800-698-2411')).atIndex(0).tap() + await element(by.id(CommonE2eIdConstants.CALL_VA_PHONE_NUMBER_ID)).atIndex(0).tap() } catch (ex) { - await element(by.text('800-698-2411').withAncestor(by.id('AFUseCase2TestID'))).tap() + await element(by.id(CommonE2eIdConstants.CALL_VA_PHONE_NUMBER_ID).withAncestor(by.id('AFUseCase2TestID'))).tap() } await setTimeout(5000) await device.takeScreenshot(featureName + 'AFUseCase2PhoneNumber') await device.launchApp({ newInstance: false }) try { - await element(by.text('TTY: 711')).atIndex(0).tap() + await element(by.id(CommonE2eIdConstants.CALL_VA_TTY_PHONE_NUMBER_ID)).atIndex(0).tap() } catch (ex) { - await element(by.text('TTY: 711').withAncestor(by.id('AFUseCase2TestID'))).tap() + await element( + by.id(CommonE2eIdConstants.CALL_VA_TTY_PHONE_NUMBER_ID).withAncestor(by.id('AFUseCase2TestID')), + ).tap() } await setTimeout(5000) await device.takeScreenshot(featureName + 'AFUseCase2TTY') diff --git a/VAMobile/env/constant.env b/VAMobile/env/constant.env index d912e23ce81..1c69bafef5e 100644 --- a/VAMobile/env/constant.env +++ b/VAMobile/env/constant.env @@ -1,3 +1,4 @@ +WEBVIEW_URL_APPOINTMENTS_CLAIM_EXAM_LEARN_MORE=https://www.va.gov/disability/va-claim-exam/ WEBVIEW_URL_CHANGE_LEGAL_NAME=https://www.va.gov/resources/how-to-change-your-legal-name-on-file-with-va/ WEBVIEW_URL_CORONA_FAQ=https://www.va.gov/coronavirus-veteran-frequently-asked-questions WEBVIEW_URL_FACILITY_LOCATOR=https://www.va.gov/find-locations/ diff --git a/VAMobile/ios/Gemfile.lock b/VAMobile/ios/Gemfile.lock index f2ba1c7c2d3..4ce56818fa0 100644 --- a/VAMobile/ios/Gemfile.lock +++ b/VAMobile/ios/Gemfile.lock @@ -5,8 +5,9 @@ GEM base64 nkf rexml - activesupport (7.2.1.1) + activesupport (7.2.2) base64 + benchmark (>= 0.3) bigdecimal concurrent-ruby (~> 1.0, >= 1.3.1) connection_pool (>= 2.2.5) @@ -24,7 +25,7 @@ GEM artifactory (3.0.17) atomos (0.1.3) aws-eventstream (1.3.0) - aws-partitions (1.993.0) + aws-partitions (1.1001.0) aws-sdk-core (3.211.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) @@ -41,12 +42,13 @@ GEM aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) base64 (0.2.0) + benchmark (0.3.0) bigdecimal (3.1.8) claide (1.1.0) - cocoapods (1.15.2) + cocoapods (1.16.2) addressable (~> 2.8) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.15.2) + cocoapods-core (= 1.16.2) cocoapods-deintegrate (>= 1.0.3, < 2.0) cocoapods-downloader (>= 2.1, < 3.0) cocoapods-plugins (>= 1.0.0, < 2.0) @@ -60,8 +62,8 @@ GEM molinillo (~> 0.8.0) nap (~> 1.0) ruby-macho (>= 2.3.0, < 3.0) - xcodeproj (>= 1.23.0, < 2.0) - cocoapods-core (1.15.2) + xcodeproj (>= 1.27.0, < 2.0) + cocoapods-core (1.16.2) activesupport (>= 5.0, < 8) addressable (~> 2.8) algoliasearch (~> 1.0) @@ -126,7 +128,7 @@ GEM faraday_middleware (1.2.1) faraday (~> 1.0) fastimage (2.3.1) - fastlane (2.224.0) + fastlane (2.225.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) @@ -219,7 +221,7 @@ GEM i18n (1.14.6) concurrent-ruby (~> 1.0) jmespath (1.6.2) - json (2.7.2) + json (2.7.6) jwt (2.9.3) base64 logger (1.6.1) @@ -229,7 +231,7 @@ GEM molinillo (0.8.0) multi_json (1.15.0) multipart-post (2.4.1) - nanaimo (0.3.0) + nanaimo (0.4.0) nap (1.1.0) naturally (2.2.1) netrc (0.11.0) @@ -244,7 +246,7 @@ GEM trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) retriable (3.1.2) - rexml (3.3.8) + rexml (3.3.9) rouge (2.0.7) ruby-macho (2.5.1) ruby2_keywords (0.0.5) @@ -278,12 +280,12 @@ GEM xcode-install (2.8.1) claide (>= 0.9.1) fastlane (>= 2.1.0, < 3.0.0) - xcodeproj (1.25.1) + xcodeproj (1.27.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) - nanaimo (~> 0.3.0) + nanaimo (~> 0.4.0) rexml (>= 3.3.6, < 4.0) xcpretty (0.3.0) rouge (~> 2.0.7) diff --git a/VAMobile/ios/Podfile.lock b/VAMobile/ios/Podfile.lock index 6fc01d9a335..822cbb93c65 100644 --- a/VAMobile/ios/Podfile.lock +++ b/VAMobile/ios/Podfile.lock @@ -1713,7 +1713,7 @@ PODS: - ReactNativeWebPFormat (1.2.0): - React-Core - SDWebImageWebPCoder - - RNCAsyncStorage (1.23.1): + - RNCAsyncStorage (1.24.0): - React-Core - RNFBAnalytics (18.9.0): - Firebase/AnalyticsWithoutAdIdSupport (= 10.20.0) @@ -1765,10 +1765,29 @@ PODS: - Yoga - RNKeychain (8.2.0): - React-Core - - RNLocalize (3.1.0): + - RNLocalize (3.2.1): - React-Core - - RNReactNativeHapticFeedback (2.2.0): + - RNReactNativeHapticFeedback (2.3.3): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga - RNScreens (3.34.0): - DoubleConversion - glog @@ -2194,7 +2213,7 @@ SPEC CHECKSUMS: ReactCommon: 289214026502e6a93484f4a46bcc0efa4f3f2864 ReactNativeKeyboardManager: 60292dbac467c49841a7d69588208ec4a4f7b2a2 ReactNativeWebPFormat: f5f043cc9ded052e535c3869ec341f91ba5a67c5 - RNCAsyncStorage: 826b603ae9c0f88b5ac4e956801f755109fa4d5c + RNCAsyncStorage: ec53e44dc3e75b44aa2a9f37618a49c3bc080a7a RNFBAnalytics: 60ffe0c9c80f404246e384d0d2076e35dd129b16 RNFBApp: a3e139715386fe79a09c387f2dbeb6890eb05b39 RNFBCrashlytics: e6d595ed2619e5e8ee3cdfd12c6a62e470280a03 @@ -2204,8 +2223,8 @@ SPEC CHECKSUMS: RNFileViewer: ce7ca3ac370e18554d35d6355cffd7c30437c592 RNGestureHandler: 939f21fabf5d45a725c0bf175eb819dd25cf2e70 RNKeychain: bfe3d12bf4620fe488771c414530bf16e88f3678 - RNLocalize: e8694475db034bf601e17bd3dfa8986565e769eb - RNReactNativeHapticFeedback: ec56a5f81c3941206fd85625fa669ffc7b4545f9 + RNLocalize: 4f22418187ecd5ca693231093ff1d912d1b3c9bc + RNReactNativeHapticFeedback: 0d591ea1e150f36cb96d868d4e8d77272243d78a RNScreens: 19719a9c326e925498ac3b2d35c4e50fe87afc06 RNSVG: 963a95f1f5d512a13d11ffd50d351c87fb5c6890 SDWebImage: 8a6b7b160b4d710e2a22b6900e25301075c34cb3 diff --git a/VAMobile/jest/testSetup.ts b/VAMobile/jest/testSetup.ts index 89fa935594f..adaa5e09744 100644 --- a/VAMobile/jest/testSetup.ts +++ b/VAMobile/jest/testSetup.ts @@ -315,3 +315,7 @@ jest.mock('react-native-webview', () => { WebView: View, } }) + +jest.mock('react-native-haptic-feedback', () => ({ + HapticFeedbackTypes: {}, +})) diff --git a/VAMobile/package.json b/VAMobile/package.json index 724b8533d5e..5d0c6b47bed 100644 --- a/VAMobile/package.json +++ b/VAMobile/package.json @@ -70,7 +70,7 @@ "react-native-dotenv": "^3.4.11", "react-native-file-viewer": "^2.1.4", "react-native-gesture-handler": "~2.18.1", - "react-native-haptic-feedback": "^2.2.0", + "react-native-haptic-feedback": "^2.3.3", "react-native-image-picker": "^7.1.2", "react-native-keyboard-manager": "^6.5.11-2", "react-native-keychain": "^8.2.0", @@ -83,25 +83,25 @@ "react-native-svg-transformer": "^1.5.0", "react-native-toast-notifications": "^3.4.0", "react-native-webp-format": "^1.2.0", - "react-native-webview": "^13.10.4", + "react-native-webview": "^13.12.3", "react-redux": "^9.1.2", "styled-components": "^5.3.10", "underscore": "^1.13.7" }, "devDependencies": { - "@babel/core": "^7.24.9", + "@babel/core": "^7.26.0", "@babel/plugin-proposal-private-methods": "^7.18.6", "@babel/plugin-transform-runtime": "^7.24.7", - "@babel/preset-env": "^7.24.8", - "@babel/preset-typescript": "^7.25.7", - "@babel/runtime": "^7.24.8", + "@babel/preset-env": "^7.26.0", + "@babel/preset-typescript": "^7.26.0", + "@babel/runtime": "^7.26.0", "@department-of-veterans-affairs/eslint-config-mobile": "0.15.0", "@react-native/babel-preset": "0.75.4", "@react-native/eslint-config": "^0.75.3", "@react-native/metro-config": "^0.75.3", "@react-native/typescript-config": "0.76.0", - "@testing-library/jest-dom": "^6.6.2", - "@testing-library/react-native": "^12.7.2", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react-native": "^12.8.1", "@trivago/prettier-plugin-sort-imports": "^4.3.0", "@tsconfig/react-native": "^3.0.5", "@types/detox": "^18.1.0", @@ -124,7 +124,7 @@ "eslint-config-prettier": "^9.1.0", "eslint-plugin-jest": "^27.9.0", "eslint-plugin-prettier": "^5.2.1", - "eslint-plugin-react": "^7.37.1", + "eslint-plugin-react": "^7.37.2", "eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-react-native": "^4.1.0", "husky": "^9.1.6", diff --git a/VAMobile/src/App.tsx b/VAMobile/src/App.tsx index 5f0ba1d06f3..29a66624dbe 100644 --- a/VAMobile/src/App.tsx +++ b/VAMobile/src/App.tsx @@ -42,9 +42,10 @@ import { getHomeScreens, getPaymentsScreens, } from 'screens' -import BiometricsPreferenceScreen from 'screens/BiometricsPreferenceScreen' import { profileAddressType } from 'screens/HomeScreen/ProfileScreen/ContactInformationScreen/AddressSummary' import EditAddressScreen from 'screens/HomeScreen/ProfileScreen/ContactInformationScreen/EditAddressScreen' +import BiometricsPreferenceScreen from 'screens/auth/BiometricsPreferenceScreen' +import RequestNotificationsScreen from 'screens/auth/RequestNotifications/RequestNotificationsScreen' import store, { RootState } from 'store' import { injectStore } from 'store/api/api' import { AnalyticsState, AuthState, handleTokenCallbackUrl, initializeAuth } from 'store/slices' @@ -103,6 +104,7 @@ export type RootNavStackParamList = WebviewStackParams & { type StackNavParamList = WebviewStackParams & { Splash: undefined BiometricsPreference: undefined + RequestNotifications: undefined Sync: undefined Login: undefined LoaGate: undefined @@ -187,8 +189,15 @@ function MainApp() { export function AuthGuard() { const dispatch = useAppDispatch() - const { initializing, loggedIn, syncing, firstTimeLogin, canStoreWithBiometric, displayBiometricsPreferenceScreen } = - useSelector((state) => state.auth) + const { + initializing, + loggedIn, + syncing, + firstTimeLogin, + canStoreWithBiometric, + displayBiometricsPreferenceScreen, + requestNotificationsPreferenceScreen, + } = useSelector((state) => state.auth) const { tappedForegroundNotification, setTappedForegroundNotification } = useNotificationContext() const { loadingRemoteConfig, remoteConfigActivated } = useSelector( (state) => state.settings, @@ -348,6 +357,16 @@ export function AuthGuard() { ) } else if (firstTimeLogin && loggedIn) { content = + } else if (!firstTimeLogin && loggedIn && requestNotificationsPreferenceScreen) { + content = ( + + + + ) } else if (loggedIn) { content = ( <> diff --git a/VAMobile/src/api/claimsAndAppeals/downloadEFolderDocument.tsx b/VAMobile/src/api/claimsAndAppeals/downloadEFolderDocument.tsx new file mode 100644 index 00000000000..13a87ff846c --- /dev/null +++ b/VAMobile/src/api/claimsAndAppeals/downloadEFolderDocument.tsx @@ -0,0 +1,42 @@ +import FileViewer from 'react-native-file-viewer' + +import { useQuery } from '@tanstack/react-query' + +import store from 'store' +import { DEMO_MODE_LETTER_ENDPOINT } from 'store/api/demo/letters' +import getEnv from 'utils/env' +import { downloadDemoFile, downloadFile } from 'utils/filesystem' +import { registerReviewEvent } from 'utils/inAppReviews' + +import { claimsAndAppealsKeys } from './queryKeys' + +const { API_ROOT } = getEnv() + +/** + * Fetch user E Folder Document + */ +const downloadEFolderDocument = async (id: string, fileName: string): Promise => { + const eFolderDocumentAPI = `${API_ROOT}/v0/efolder/documents/${id}/download?file_name=${fileName}}` + + const filePath = store.getState().demo.demoMode + ? await downloadDemoFile(DEMO_MODE_LETTER_ENDPOINT, fileName) + : await downloadFile('POST', eFolderDocumentAPI, fileName, undefined, 1) + if (filePath) { + await FileViewer.open(filePath, { onDismiss: () => registerReviewEvent() }) + return true + } +} + +/** + * Returns a query for a user E Folder Document + */ +export const useDownloadEFolderDocument = (id: string, fileName: string) => { + return useQuery({ + enabled: false, + queryKey: [claimsAndAppealsKeys.eFolderDownloadDoc, id, fileName], + queryFn: () => downloadEFolderDocument(id, fileName), + meta: { + errorName: 'downloadEFolderDocument: Service error', + }, + }) +} diff --git a/VAMobile/src/api/claimsAndAppeals/getAppeal.tsx b/VAMobile/src/api/claimsAndAppeals/getAppeal.tsx index 89e4d48bb22..d747048113d 100644 --- a/VAMobile/src/api/claimsAndAppeals/getAppeal.tsx +++ b/VAMobile/src/api/claimsAndAppeals/getAppeal.tsx @@ -8,19 +8,19 @@ import { claimsAndAppealsKeys } from './queryKeys' /** * Fetch user Appeal */ -const getAppeal = async (id: string, abortSignal: AbortSignal): Promise => { - const response = await get(`/v0/appeal/${id}`, {}, abortSignal) +const getAppeal = async (id: string): Promise => { + const response = await get(`/v0/appeal/${id}`, {}) return response?.data } /** * Returns a query for user Appeal */ -export const useAppeal = (id: string, abortSignal: AbortSignal, options?: { enabled?: boolean }) => { +export const useAppeal = (id: string, options?: { enabled?: boolean }) => { return useQuery({ ...options, queryKey: [claimsAndAppealsKeys.appeal, id], - queryFn: () => getAppeal(id, abortSignal), + queryFn: () => getAppeal(id), meta: { errorName: 'getAppeal: Service error', }, diff --git a/VAMobile/src/api/claimsAndAppeals/getClaim.tsx b/VAMobile/src/api/claimsAndAppeals/getClaim.tsx index 6e22392688d..4d61137875d 100644 --- a/VAMobile/src/api/claimsAndAppeals/getClaim.tsx +++ b/VAMobile/src/api/claimsAndAppeals/getClaim.tsx @@ -8,19 +8,19 @@ import { claimsAndAppealsKeys } from './queryKeys' /** * Fetch user Claim */ -const getClaim = async (id: string, abortSignal?: AbortSignal): Promise => { - const response = await get(`/v0/claim/${id}`, {}, abortSignal) +const getClaim = async (id: string): Promise => { + const response = await get(`/v0/claim/${id}`, {}) return response?.data } /** * Returns a query for user Claim */ -export const useClaim = (id: string, abortSignal?: AbortSignal, options?: { enabled?: boolean }) => { +export const useClaim = (id: string, options?: { enabled?: boolean }) => { return useQuery({ ...options, queryKey: [claimsAndAppealsKeys.claim, id], - queryFn: () => getClaim(id, abortSignal), + queryFn: () => getClaim(id), meta: { errorName: 'getClaim: Service error', }, diff --git a/VAMobile/src/api/claimsAndAppeals/getEFolderDocuments.tsx b/VAMobile/src/api/claimsAndAppeals/getEFolderDocuments.tsx new file mode 100644 index 00000000000..892c9ee1b10 --- /dev/null +++ b/VAMobile/src/api/claimsAndAppeals/getEFolderDocuments.tsx @@ -0,0 +1,28 @@ +import { useQuery } from '@tanstack/react-query' + +import { ClaimEFolderData, ClaimEFolderDocuments } from 'api/types' +import { get } from 'store/api' + +import { claimsAndAppealsKeys } from './queryKeys' + +/** + * Fetch user E Folder Documents + */ +const getEfolderDocuments = async (): Promise | undefined> => { + const response = await get(`/v0/efolder/documents`, {}) + return response?.data +} + +/** + * Returns a query for user E Folder Documents + */ +export const useEFolderDocuments = (options?: { enabled?: boolean }) => { + return useQuery({ + ...options, + queryKey: [claimsAndAppealsKeys.eFolderDocs], + queryFn: () => getEfolderDocuments(), + meta: { + errorName: 'get E Folder Documents: Service error', + }, + }) +} diff --git a/VAMobile/src/api/claimsAndAppeals/index.ts b/VAMobile/src/api/claimsAndAppeals/index.ts index e1b10870ffc..82c26abab14 100644 --- a/VAMobile/src/api/claimsAndAppeals/index.ts +++ b/VAMobile/src/api/claimsAndAppeals/index.ts @@ -1,6 +1,8 @@ +export * from './downloadEFolderDocument' export * from './getAppeal' export * from './getClaim' export * from './getClaimsAndAppeals' +export * from './getEFolderDocuments' export * from './queryKeys' export * from './submitClaimDecision' export * from './uploadFileToClaim' diff --git a/VAMobile/src/api/claimsAndAppeals/queryKeys.ts b/VAMobile/src/api/claimsAndAppeals/queryKeys.ts index ef0537ab4e5..9a8ca14816d 100644 --- a/VAMobile/src/api/claimsAndAppeals/queryKeys.ts +++ b/VAMobile/src/api/claimsAndAppeals/queryKeys.ts @@ -2,4 +2,6 @@ export const claimsAndAppealsKeys = { appeal: ['appeal'] as const, claim: ['claim'] as const, claimsAndAppeals: ['claimsAndAppeals'] as const, + eFolderDocs: ['EFolderDocs'] as const, + eFolderDownloadDoc: ['EFolderDownloadDoc'] as const, } diff --git a/VAMobile/src/api/types/ClaimsAndAppealsData.ts b/VAMobile/src/api/types/ClaimsAndAppealsData.ts index 8f1f9c90fea..47b082e3a4d 100644 --- a/VAMobile/src/api/types/ClaimsAndAppealsData.ts +++ b/VAMobile/src/api/types/ClaimsAndAppealsData.ts @@ -459,6 +459,21 @@ export type ClaimEventData = { suspenseDate?: string | null documents?: Array phase?: number + documentId?: string +} + +export type ClaimEFolderData = { + data: Array +} + +export type ClaimEFolderDocuments = { + id: string + type: string + attributes: { + doc_type: string + type_description: string + received_at: string + } } export type ClaimAttributesData = { @@ -535,6 +550,7 @@ export type ClaimEventDocumentData = { documentType?: string filename?: string uploadDate: string + documentId?: string } export type ClaimPhaseData = { diff --git a/VAMobile/src/components/LabelTag.tsx b/VAMobile/src/components/LabelTag.tsx index fca4f8cc893..a1a1fdbb2ae 100644 --- a/VAMobile/src/components/LabelTag.tsx +++ b/VAMobile/src/components/LabelTag.tsx @@ -1,5 +1,5 @@ import React, { FC } from 'react' -import { Pressable, PressableProps, useWindowDimensions } from 'react-native' +import { AccessibilityRole, Pressable, PressableProps, useWindowDimensions } from 'react-native' import { useTheme } from 'utils/hooks' @@ -36,10 +36,13 @@ export type LabelTagProps = { /** Optional accessibility hint if there is an on press */ a11yHint?: string + + /** Optional role to override the default role of button */ + a11yRole?: AccessibilityRole } /**Common component to show a text inside a tag*/ -const LabelTag: FC = ({ text, labelType, onPress, a11yHint, a11yLabel }) => { +const LabelTag: FC = ({ text, labelType, onPress, a11yHint, a11yLabel, a11yRole }) => { const theme = useTheme() const fontScale = useWindowDimensions().fontScale const adjustSize = fontScale >= 2 @@ -84,7 +87,7 @@ const LabelTag: FC = ({ text, labelType, onPress, a11yHint, a11yL let pressableProps: PressableProps = { onPress: onPress, accessible: true, - accessibilityRole: 'button', + accessibilityRole: a11yRole || 'button', } if (a11yHint) { diff --git a/VAMobile/src/components/Menu/MenuView.tsx b/VAMobile/src/components/Menu/MenuView.tsx index 6a81f08b4a9..7748b7d5285 100644 --- a/VAMobile/src/components/Menu/MenuView.tsx +++ b/VAMobile/src/components/Menu/MenuView.tsx @@ -136,7 +136,7 @@ const MenuView: FC = ({ actions }) => { return ( <> - + diff --git a/VAMobile/src/components/MultiTouchCard.test.tsx b/VAMobile/src/components/MultiTouchCard.test.tsx index 5d94120601e..aae71520e0b 100644 --- a/VAMobile/src/components/MultiTouchCard.test.tsx +++ b/VAMobile/src/components/MultiTouchCard.test.tsx @@ -61,7 +61,7 @@ context('MultiTouchCard', () => { }) it('calls onPress function on bottomContent click', () => { - fireEvent.press(screen.getByRole('button', { name: 'bottom line 1' })) + fireEvent.press(screen.getByRole('link', { name: 'bottom line 1' })) expect(onPressSpy).toBeCalled() }) }) diff --git a/VAMobile/src/components/MultiTouchCard.tsx b/VAMobile/src/components/MultiTouchCard.tsx index df5e1a4bf9a..a22e85fe752 100644 --- a/VAMobile/src/components/MultiTouchCard.tsx +++ b/VAMobile/src/components/MultiTouchCard.tsx @@ -58,7 +58,7 @@ const MultiTouchCard: FC = ({ let bottomPressableProps: PressableProps = { onPress: bottomOnPress, accessible: true, - accessibilityRole: 'button', + accessibilityRole: 'link', accessibilityHint: bottomA11yHint, } diff --git a/VAMobile/src/components/NotificationManager/NotificationManager.tsx b/VAMobile/src/components/NotificationManager/NotificationManager.tsx index 487303b7b47..ad9d1f5121d 100644 --- a/VAMobile/src/components/NotificationManager/NotificationManager.tsx +++ b/VAMobile/src/components/NotificationManager/NotificationManager.tsx @@ -30,7 +30,7 @@ const NotificationContext = createContext({ * notification manager component to handle all push logic */ const NotificationManager: FC = ({ children }) => { - const { loggedIn } = useSelector((state) => state.auth) + const { loggedIn, firstTimeLogin, requestNotifications } = useSelector((state) => state.auth) const { data: personalInformation } = usePersonalInformation({ enabled: loggedIn }) const { mutate: registerDevice } = useRegisterDevice() const [tappedForegroundNotification, setTappedForegroundNotification] = useState(false) @@ -57,13 +57,15 @@ const NotificationManager: FC = ({ children }) => { registeredNotifications.remove() failedNotifications.remove() }) - Notifications.registerRemoteNotifications() + if (firstTimeLogin === false && requestNotifications === true) { + Notifications.registerRemoteNotifications() + } } if (loggedIn && personalInformation?.id) { register() } - }, [loggedIn, personalInformation?.id, registerDevice]) + }, [loggedIn, firstTimeLogin, requestNotifications, personalInformation?.id, registerDevice]) const registerNotificationEvents = () => { // Register callbacks for notifications that happen when the app is in the foreground diff --git a/VAMobile/src/screens/BenefitsScreen/ClaimsScreen/AppealDetailsScreen/AppealDetailsScreen.test.tsx b/VAMobile/src/screens/BenefitsScreen/ClaimsScreen/AppealDetailsScreen/AppealDetailsScreen.test.tsx index 7f1871e24a2..e8b767b1478 100644 --- a/VAMobile/src/screens/BenefitsScreen/ClaimsScreen/AppealDetailsScreen/AppealDetailsScreen.test.tsx +++ b/VAMobile/src/screens/BenefitsScreen/ClaimsScreen/AppealDetailsScreen/AppealDetailsScreen.test.tsx @@ -49,7 +49,7 @@ context('AppealDetailsScreen', () => { const date = DateTime.fromISO('2018-01-19T10:20:42-05:00') const dateTime = date.toLocaleString(Object.assign(DateTime.DATETIME_FULL, { day: '2-digit' })) when(api.get as jest.Mock) - .calledWith(`/v0/appeal/0`, {}, expect.anything()) + .calledWith(`/v0/appeal/0`, {}) .mockResolvedValue({ data: { ...appealData, @@ -115,7 +115,7 @@ context('AppealDetailsScreen', () => { describe('when the selected tab is issues', () => { it('should display the AppealStatus component', async () => { when(api.get as jest.Mock) - .calledWith(`/v0/appeal/0`, {}, expect.anything()) + .calledWith(`/v0/appeal/0`, {}) .mockResolvedValue({ data: { ...appealData, @@ -157,7 +157,7 @@ context('AppealDetailsScreen', () => { describe('when the type is higherLevelReview', () => { it('should display "Higher level review appeal for {{ programArea }}" as the title', async () => { when(api.get as jest.Mock) - .calledWith(`/v0/appeal/0`, {}, expect.anything()) + .calledWith(`/v0/appeal/0`, {}) .mockResolvedValue({ data: { ...appealData, @@ -173,7 +173,7 @@ context('AppealDetailsScreen', () => { it('should display the Received date as the event date where the type is "hlr_request"', async () => { when(api.get as jest.Mock) - .calledWith(`/v0/appeal/0`, {}, expect.anything()) + .calledWith(`/v0/appeal/0`, {}) .mockResolvedValue({ data: { ...appealData, @@ -205,7 +205,7 @@ context('AppealDetailsScreen', () => { describe('when the type is legacyAppeal', () => { it('should display "Appeal for {{ programArea }}" as the title', async () => { when(api.get as jest.Mock) - .calledWith(`/v0/appeal/0`, {}, expect.anything()) + .calledWith(`/v0/appeal/0`, {}) .mockResolvedValue({ data: { ...appealData, @@ -221,7 +221,7 @@ context('AppealDetailsScreen', () => { it('should display the Received date as the event date where the type is "nod"', async () => { when(api.get as jest.Mock) - .calledWith(`/v0/appeal/0`, {}, expect.anything()) + .calledWith(`/v0/appeal/0`, {}) .mockResolvedValue({ data: { ...appealData, @@ -253,7 +253,7 @@ context('AppealDetailsScreen', () => { describe('when the type is appeal', () => { it('should display "Appeal for {{ programArea }}" as the title', async () => { when(api.get as jest.Mock) - .calledWith(`/v0/appeal/0`, {}, expect.anything()) + .calledWith(`/v0/appeal/0`, {}) .mockResolvedValue({ data: { ...appealData, @@ -269,7 +269,7 @@ context('AppealDetailsScreen', () => { it('should display the Received date as the event date where the type is "nod"', async () => { when(api.get as jest.Mock) - .calledWith(`/v0/appeal/0`, {}, expect.anything()) + .calledWith(`/v0/appeal/0`, {}) .mockResolvedValue({ data: { ...appealData, @@ -301,7 +301,7 @@ context('AppealDetailsScreen', () => { describe('when the type is supplementalClaim', () => { it('should display "Supplemental claim appeal for {{ programArea }}" as the title', async () => { when(api.get as jest.Mock) - .calledWith(`/v0/appeal/0`, {}, expect.anything()) + .calledWith(`/v0/appeal/0`, {}) .mockResolvedValue({ data: { ...appealData, @@ -319,7 +319,7 @@ context('AppealDetailsScreen', () => { it('should display the Received date as the event date where the type is "sc_request"', async () => { when(api.get as jest.Mock) - .calledWith(`/v0/appeal/0`, {}, expect.anything()) + .calledWith(`/v0/appeal/0`, {}) .mockResolvedValue({ data: { ...appealData, @@ -351,7 +351,7 @@ context('AppealDetailsScreen', () => { describe('when common error occurs', () => { it('should render error component when the stores screenID matches the components screenID', async () => { when(api.get as jest.Mock) - .calledWith(`/v0/appeal/0`, {}, expect.anything()) + .calledWith(`/v0/appeal/0`, {}) .mockRejectedValue({ networkError: true } as api.APIError) renderWithData() await waitFor(() => expect(screen.getByRole('header', { name: "The app can't be loaded." })).toBeTruthy()) diff --git a/VAMobile/src/screens/BenefitsScreen/ClaimsScreen/AppealDetailsScreen/AppealDetailsScreen.tsx b/VAMobile/src/screens/BenefitsScreen/ClaimsScreen/AppealDetailsScreen/AppealDetailsScreen.tsx index ff7e6e4efa4..d3cc73f0a15 100644 --- a/VAMobile/src/screens/BenefitsScreen/ClaimsScreen/AppealDetailsScreen/AppealDetailsScreen.tsx +++ b/VAMobile/src/screens/BenefitsScreen/ClaimsScreen/AppealDetailsScreen/AppealDetailsScreen.tsx @@ -15,7 +15,7 @@ import { BenefitsStackParamList } from 'screens/BenefitsScreen/BenefitsStackScre import { ScreenIDTypesConstants } from 'store/api/types/Screens' import { logAnalyticsEvent } from 'utils/analytics' import { formatDateMMMMDDYYYY, getFormattedTimeForTimeZone, getTranslation } from 'utils/formattingUtils' -import { useBeforeNavBackListener, useTheme } from 'utils/hooks' +import { useTheme } from 'utils/hooks' import { registerReviewEvent } from 'utils/inAppReviews' import { screenContentAllowed } from 'utils/waygateConfig' @@ -37,26 +37,17 @@ function AppealDetailsScreen({ navigation, route }: AppealDetailsScreenProps) { t('appealDetails.viewYourAppeal', { tabName: t('claimDetails.status') }), t('appealDetails.viewYourAppeal', { tabName: t('appealDetails.issuesTab') }), ] - const abortController = new AbortController() - const abortSignal = abortController.signal const { appealID } = route.params const { data: appeal, error: appealError, refetch: refetchAppeals, isFetching: loadingAppeal, - } = useAppeal(appealID, abortSignal, { enabled: screenContentAllowed('WG_AppealDetailsScreen') }) + } = useAppeal(appealID, { enabled: screenContentAllowed('WG_AppealDetailsScreen') }) const { attributes, type } = appeal || ({} as AppealData) const { updated, programArea, events, status, aoj, docket, issues, active } = attributes || ({} as AppealAttributesData) - useBeforeNavBackListener(navigation, () => { - // if appeals is still loading cancel it - if (loadingAppeal) { - abortController.abort() - } - }) - useEffect(() => { if (appeal && !loadingAppeal && !appealError) { registerReviewEvent(true) diff --git a/VAMobile/src/screens/BenefitsScreen/ClaimsScreen/ClaimDetailsScreen/ClaimDetailsScreen.test.tsx b/VAMobile/src/screens/BenefitsScreen/ClaimsScreen/ClaimDetailsScreen/ClaimDetailsScreen.test.tsx index a55b634cb3d..8a231052be3 100644 --- a/VAMobile/src/screens/BenefitsScreen/ClaimsScreen/ClaimDetailsScreen/ClaimDetailsScreen.test.tsx +++ b/VAMobile/src/screens/BenefitsScreen/ClaimsScreen/ClaimDetailsScreen/ClaimDetailsScreen.test.tsx @@ -59,7 +59,7 @@ context('ClaimDetailsScreen', () => { beforeEach(() => { when(api.get as jest.Mock) - .calledWith(`/v0/claim/600156928`, {}, expect.anything()) + .calledWith(`/v0/claim/600156928`, {}) .mockResolvedValue({ data: { ...claimData, @@ -183,7 +183,7 @@ context('ClaimDetailsScreen', () => { describe('when common error occurs', () => { it('should render error component when the stores screenID matches the components screenID', async () => { when(api.get as jest.Mock) - .calledWith(`/v0/claim/600156928`, {}, expect.anything()) + .calledWith(`/v0/claim/600156928`, {}) .mockRejectedValue({ networkError: true } as api.APIError) renderWithData() diff --git a/VAMobile/src/screens/BenefitsScreen/ClaimsScreen/ClaimDetailsScreen/ClaimDetailsScreen.tsx b/VAMobile/src/screens/BenefitsScreen/ClaimsScreen/ClaimDetailsScreen/ClaimDetailsScreen.tsx index b474df6172b..125b9683a1a 100644 --- a/VAMobile/src/screens/BenefitsScreen/ClaimsScreen/ClaimDetailsScreen/ClaimDetailsScreen.tsx +++ b/VAMobile/src/screens/BenefitsScreen/ClaimsScreen/ClaimDetailsScreen/ClaimDetailsScreen.tsx @@ -62,14 +62,12 @@ function ClaimDetailsScreen({ navigation, route }: ClaimDetailsScreenProps) { const { claimID, claimType } = route.params const queryClient = useQueryClient() - const abortController = new AbortController() - const abortSignal = abortController.signal const { data: claim, isFetching: loadingClaim, error: claimError, refetch: refetchClaim, - } = useClaim(claimID, abortSignal, { enabled: screenContentAllowed('WG_ClaimDetailsScreen') }) + } = useClaim(claimID, { enabled: screenContentAllowed('WG_ClaimDetailsScreen') }) const { data: decisionLetterData } = useDecisionLetters() const { data: userAuthorizedServices } = useAuthorizedServices() const { attributes } = claim || ({} as ClaimData) @@ -93,7 +91,6 @@ function ClaimDetailsScreen({ navigation, route }: ClaimDetailsScreenProps) { // if claim is still loading cancel it if (loadingClaim) { queryClient.invalidateQueries({ queryKey: claimsAndAppealsKeys.claim }) - abortController.abort() } }) diff --git a/VAMobile/src/screens/BenefitsScreen/ClaimsScreen/ClaimDetailsScreen/ClaimStatus/ClaimFileUpload/AskForClaimDecision/AskForClaimDecision.test.tsx b/VAMobile/src/screens/BenefitsScreen/ClaimsScreen/ClaimDetailsScreen/ClaimStatus/ClaimFileUpload/AskForClaimDecision/AskForClaimDecision.test.tsx index 2dd152ad4cd..cf9594f38f1 100644 --- a/VAMobile/src/screens/BenefitsScreen/ClaimsScreen/ClaimDetailsScreen/ClaimStatus/ClaimFileUpload/AskForClaimDecision/AskForClaimDecision.test.tsx +++ b/VAMobile/src/screens/BenefitsScreen/ClaimsScreen/ClaimDetailsScreen/ClaimStatus/ClaimFileUpload/AskForClaimDecision/AskForClaimDecision.test.tsx @@ -19,7 +19,7 @@ jest.mock('utils/hooks', () => { }) when(api.get as jest.Mock) - .calledWith(`/v0/claim/600156928/request-decision`, {}, expect.anything()) + .calledWith(`/v0/claim/600156928/request-decision`, {}) .mockResolvedValue({}) context('AskForClaimDecision', () => { @@ -53,7 +53,7 @@ context('AskForClaimDecision', () => { it('should initialize', async () => { when(api.get as jest.Mock) - .calledWith(`/v0/claim/600156928`, {}, undefined) + .calledWith(`/v0/claim/600156928`, {}) .mockResolvedValue({ data: { ...claim, @@ -95,7 +95,7 @@ context('AskForClaimDecision', () => { describe('when submitted decision is false or there is an erroror check box is not checked', () => { it('should not call navigation go back and display a field error when not checked', async () => { when(api.get as jest.Mock) - .calledWith(`/v0/claim/600156928`, {}, undefined) + .calledWith(`/v0/claim/600156928`, {}) .mockResolvedValue({ data: { ...claim, @@ -131,7 +131,7 @@ context('AskForClaimDecision', () => { describe('when common error occurs', () => { it('should render error component when the stores screenID matches the components screenID', async () => { when(api.get as jest.Mock) - .calledWith(`/v0/claim/600156928`, {}, undefined) + .calledWith(`/v0/claim/600156928`, {}) .mockRejectedValue({ networkError: true } as api.APIError) initializeTestInstance() await waitFor(() => expect(screen.getByRole('header', { name: "The app can't be loaded." })).toBeTruthy()) diff --git a/VAMobile/src/screens/BenefitsScreen/ClaimsScreen/ClaimDetailsScreen/ClaimStatus/ClaimFileUpload/FileRequest.test.tsx b/VAMobile/src/screens/BenefitsScreen/ClaimsScreen/ClaimDetailsScreen/ClaimStatus/ClaimFileUpload/FileRequest.test.tsx index 831a5f6a732..a51b0634adc 100644 --- a/VAMobile/src/screens/BenefitsScreen/ClaimsScreen/ClaimDetailsScreen/ClaimStatus/ClaimFileUpload/FileRequest.test.tsx +++ b/VAMobile/src/screens/BenefitsScreen/ClaimsScreen/ClaimDetailsScreen/ClaimStatus/ClaimFileUpload/FileRequest.test.tsx @@ -69,7 +69,7 @@ context('FileRequest', () => { ] when(api.get as jest.Mock) - .calledWith(`/v0/claim/600156928`, {}, undefined) + .calledWith(`/v0/claim/600156928`, {}) .mockResolvedValue({ data: { ...Claim, @@ -90,7 +90,7 @@ context('FileRequest', () => { describe('when number of requests is equal to 1', () => { it('display correctly', async () => { when(api.get as jest.Mock) - .calledWith(`/v0/claim/600156928`, {}, undefined) + .calledWith(`/v0/claim/600156928`, {}) .mockResolvedValue({ data: { ...Claim, @@ -132,7 +132,7 @@ context('FileRequest', () => { describe('when common error occurs', () => { it('should render error component when the stores screenID matches the components screenID', async () => { when(api.get as jest.Mock) - .calledWith(`/v0/claim/600156928`, {}, undefined) + .calledWith(`/v0/claim/600156928`, {}) .mockRejectedValue({ networkError: true } as api.APIError) renderWithData(request) @@ -162,7 +162,7 @@ context('FileRequest', () => { }, ] when(api.get as jest.Mock) - .calledWith(`/v0/claim/600156928`, {}, undefined) + .calledWith(`/v0/claim/600156928`, {}) .mockResolvedValue({ data: { ...Claim, diff --git a/VAMobile/src/screens/BenefitsScreen/ClaimsScreen/ClaimDetailsScreen/ClaimStatus/ClaimFileUpload/FileRequest.tsx b/VAMobile/src/screens/BenefitsScreen/ClaimsScreen/ClaimDetailsScreen/ClaimStatus/ClaimFileUpload/FileRequest.tsx index 7b0d8afcded..20ee6112943 100644 --- a/VAMobile/src/screens/BenefitsScreen/ClaimsScreen/ClaimDetailsScreen/ClaimStatus/ClaimFileUpload/FileRequest.tsx +++ b/VAMobile/src/screens/BenefitsScreen/ClaimsScreen/ClaimDetailsScreen/ClaimStatus/ClaimFileUpload/FileRequest.tsx @@ -39,7 +39,7 @@ function FileRequest({ navigation, route }: FileRequestProps) { error: claimError, refetch: refetchClaim, isFetching: loadingClaim, - } = useClaim(claimID, undefined, { enabled: !claim }) + } = useClaim(claimID, { enabled: !claim }) const requests = currentRequestsForVet( claim?.attributes.eventsTimeline || claimFallBack?.attributes.eventsTimeline || [], ) diff --git a/VAMobile/src/screens/BiometricsPreferenceScreen/BiometricsPreferenceScreen.test.tsx b/VAMobile/src/screens/BiometricsPreferenceScreen/BiometricsPreferenceScreen.test.tsx deleted file mode 100644 index a49b172f2b2..00000000000 --- a/VAMobile/src/screens/BiometricsPreferenceScreen/BiometricsPreferenceScreen.test.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import React from 'react' -import { BIOMETRY_TYPE } from 'react-native-keychain' - -import { fireEvent, screen } from '@testing-library/react-native' - -import { InitialState, setBiometricsPreference, setDisplayBiometricsPreferenceScreen } from 'store/slices' -import { context, render } from 'testUtils' - -import BiometricsPreferenceScreen from './BiometricsPreferenceScreen' - -jest.mock('store/slices', () => { - const actual = jest.requireActual('store/slices') - return { - ...actual, - setBiometricsPreference: jest.fn(() => { - return { - type: '', - payload: '', - } - }), - setDisplayBiometricsPreferenceScreen: jest.fn(() => { - return { - type: '', - payload: '', - } - }), - } -}) - -context('BiometricsPreferenceScreen', () => { - const initializeTestInstance = (biometric = BIOMETRY_TYPE.TOUCH_ID) => { - render(, { - preloadedState: { - auth: { - ...InitialState.auth, - supportedBiometric: biometric, - }, - }, - }) - } - - beforeEach(() => { - initializeTestInstance() - }) - - it('initializes correctly', () => { - expect(screen.getByRole('header', { name: 'Do you want to allow us to use Touch ID for sign in?' })).toBeTruthy() - expect( - screen.getByText( - 'Touch ID lets you use your fingerprint to sign in to this app.\nYou can always change this later in your app settings.', - ), - ).toBeTruthy() - initializeTestInstance(BIOMETRY_TYPE.FACE_ID) - expect(screen.getByRole('header', { name: 'Do you want to allow us to use Face ID for sign in?' })).toBeTruthy() - expect( - screen.getByText( - 'Face ID lets us recognize an image of your face to sign you in to this app.\nYou can always change this later in your app settings.', - ), - ).toBeTruthy() - initializeTestInstance(BIOMETRY_TYPE.FACE) - expect( - screen.getByRole('header', { name: 'Do you want to allow us to use Face Recognition for sign in?' }), - ).toBeTruthy() - expect( - screen.getByText( - 'Face recognition lets you use facial recognition to sign into this app.\nYou can always change this later in your app settings.', - ), - ).toBeTruthy() - initializeTestInstance(BIOMETRY_TYPE.FINGERPRINT) - expect(screen.getByRole('header', { name: 'Do you want to allow us to use Fingerprint for sign in?' })).toBeTruthy() - expect( - screen.getByText( - 'Fingerprint lets you use your fingerprint to sign into this app.\nYou can always change this later in your app settings.', - ), - ).toBeTruthy() - initializeTestInstance(BIOMETRY_TYPE.IRIS) - expect(screen.getByRole('header', { name: 'Do you want to allow us to use Iris for sign in?' })).toBeTruthy() - expect( - screen.getByText( - 'Iris lets us recognize a video image of your eyes to sign you in to this app.\nYou can always change this later in your app settings.', - ), - ).toBeTruthy() - }) - - describe('on click of the use biometric button', () => { - it('should call setBiometricsPreference and setDisplayBiometricsPreferenceScreen', () => { - fireEvent.press(screen.getByRole('button', { name: 'Turn on Touch ID' })) - expect(setBiometricsPreference).toHaveBeenCalledWith(true) - expect(setDisplayBiometricsPreferenceScreen).toHaveBeenCalledWith(false) - }) - }) - - describe('on click of the skip button button', () => { - it('should call setDisplayBiometricsPreferenceScreen', () => { - fireEvent.press(screen.getByRole('button', { name: 'Skip' })) - expect(setDisplayBiometricsPreferenceScreen).toHaveBeenCalledWith(false) - }) - }) -}) diff --git a/VAMobile/src/screens/HealthScreen/Appointments/AppointmentTypeComponents/ClaimExamAppointment.test.tsx b/VAMobile/src/screens/HealthScreen/Appointments/AppointmentTypeComponents/ClaimExamAppointment.test.tsx index a9c6960fd7d..2b8926588bd 100644 --- a/VAMobile/src/screens/HealthScreen/Appointments/AppointmentTypeComponents/ClaimExamAppointment.test.tsx +++ b/VAMobile/src/screens/HealthScreen/Appointments/AppointmentTypeComponents/ClaimExamAppointment.test.tsx @@ -1,6 +1,7 @@ import React from 'react' import { screen } from '@testing-library/react-native' +import { t } from 'i18next' import { AppointmentAttributes, @@ -107,6 +108,11 @@ context('ClaimExamAppointment', () => { expect(screen.getByText('Clinic: Johnson Clinic suite 100')).toBeTruthy() expect(screen.getByText('Location: 123 San Jacinto Ave, San Jacinto, CA 92583')).toBeTruthy() + expect(screen.getByRole('header', { name: t('appointmentsTab.medicationWording.title') })).toBeTruthy() + expect(screen.getByText(t('appointmentsTab.medicationWording.claimExam.bullet1'))).toBeTruthy() + expect(screen.getByText(t('appointmentsTab.medicationWording.claimExam.bullet2'))).toBeTruthy() + expect(screen.getByRole('link', { name: t('appointmentsTab.medicationWording.claimExam.webLink') })).toBeTruthy() + expect(screen.getByRole('header', { name: 'Need to reschedule or cancel?' })).toBeTruthy() expect( screen.getByText('Call the compensation and pension office at VA Long Beach Healthcare System.'), @@ -162,6 +168,11 @@ context('ClaimExamAppointment', () => { expect(screen.getByText('Clinic: Not available')).toBeTruthy() expect(screen.getByText('Location: Not available')).toBeTruthy() + expect(screen.getByRole('header', { name: t('appointmentsTab.medicationWording.title') })).toBeTruthy() + expect(screen.getByText(t('appointmentsTab.medicationWording.claimExam.bullet1'))).toBeTruthy() + expect(screen.getByText(t('appointmentsTab.medicationWording.claimExam.bullet2'))).toBeTruthy() + expect(screen.getByRole('link', { name: t('appointmentsTab.medicationWording.claimExam.webLink') })).toBeTruthy() + expect(screen.getByRole('header', { name: 'Need to reschedule or cancel?' })).toBeTruthy() expect(screen.getByText('Call the compensation and pension office at VA facility.')).toBeTruthy() }) @@ -268,6 +279,11 @@ context('ClaimExamAppointment', () => { expect(screen.getByText('Clinic: Johnson Clinic suite 100')).toBeTruthy() expect(screen.getByText('Location: 123 San Jacinto Ave, San Jacinto, CA 92583')).toBeTruthy() + expect(screen.getByRole('header', { name: t('appointmentsTab.medicationWording.title') })).toBeTruthy() + expect(screen.getByText(t('appointmentsTab.medicationWording.claimExam.bullet1'))).toBeTruthy() + expect(screen.getByText(t('appointmentsTab.medicationWording.claimExam.bullet2'))).toBeTruthy() + expect(screen.getByRole('link', { name: t('appointmentsTab.medicationWording.claimExam.webLink') })).toBeTruthy() + expect(screen.getByRole('header', { name: 'Need to reschedule?' })).toBeTruthy() expect( screen.getByLabelText('Call the compensation and pension office at V-A Long Beach Healthcare System.'), @@ -318,6 +334,11 @@ context('ClaimExamAppointment', () => { expect(screen.getByText('Clinic: Not available')).toBeTruthy() expect(screen.getByText('Location: Not available')).toBeTruthy() + expect(screen.getByRole('header', { name: t('appointmentsTab.medicationWording.title') })).toBeTruthy() + expect(screen.getByText(t('appointmentsTab.medicationWording.claimExam.bullet1'))).toBeTruthy() + expect(screen.getByText(t('appointmentsTab.medicationWording.claimExam.bullet2'))).toBeTruthy() + expect(screen.getByRole('link', { name: t('appointmentsTab.medicationWording.claimExam.webLink') })).toBeTruthy() + expect(screen.getByRole('header', { name: 'Need to reschedule?' })).toBeTruthy() expect(screen.getByText('Call the compensation and pension office at VA facility.')).toBeTruthy() }) diff --git a/VAMobile/src/screens/HealthScreen/Appointments/AppointmentTypeComponents/ClaimExamAppointment.tsx b/VAMobile/src/screens/HealthScreen/Appointments/AppointmentTypeComponents/ClaimExamAppointment.tsx index 8cb9103cd63..372534221d2 100644 --- a/VAMobile/src/screens/HealthScreen/Appointments/AppointmentTypeComponents/ClaimExamAppointment.tsx +++ b/VAMobile/src/screens/HealthScreen/Appointments/AppointmentTypeComponents/ClaimExamAppointment.tsx @@ -13,6 +13,7 @@ import { AppointmentDateAndTime, AppointmentDetailsModality, AppointmentLocation, + AppointmentMedicationWording, AppointmentPersonalContactInfo, AppointmentPreferredModality, AppointmentProvider, @@ -62,6 +63,7 @@ function ClaimExamAppointment({ + , + t: TFunction, +) => { + let text = t('appointmentsTab.medicationWording.whatToBringLink') + let url = WEBVIEW_URL_WHAT_TO_BRING_TO_APPOINTMENTS + + if (type === AppointmentDetailsTypeConstants.ClaimExam) { + text = t('appointmentsTab.medicationWording.claimExam.webLink') + url = WEBVIEW_URL_APPOINTMENTS_CLAIM_EXAM_LEARN_MORE + } -const getWebViewLink = (onPress: () => void, t: TFunction) => ( - -) + return ( + { + navigateTo('Webview', { + url, + displayTitle: t('webview.vagov'), + loadingMessage: t('loading.vaWebsite'), + }) + }} + text={text} + /> + ) +} type AppointmentMedicationWordingProps = { subType: AppointmentDetailsSubType type: AppointmentDetailsScreenType } -const { WEBVIEW_URL_WHAT_TO_BRING_TO_APPOINTMENTS } = getEnv() - function AppointmentMedicationWording({ subType, type }: AppointmentMedicationWordingProps) { const { t } = useTranslation(NAMESPACE.COMMON) const navigateTo = useRouteNavigation() const body = t('appointmentsTab.medicationWording.default.body') const theme = useTheme() - const openWebviewLink = () => { - navigateTo('Webview', { - url: WEBVIEW_URL_WHAT_TO_BRING_TO_APPOINTMENTS, - displayTitle: t('webview.vagov'), - loadingMessage: t('loading.vaWebsite'), - }) - } - - const webViewLink = getWebViewLink(openWebviewLink, t) + const webViewLink = getWebViewLink(type, navigateTo, t) const getContent = () => { switch (type) { @@ -76,6 +91,21 @@ function AppointmentMedicationWording({ subType, type }: AppointmentMedicationWo /> ) + case AppointmentDetailsTypeConstants.ClaimExam: + return ( + <> + + {webViewLink} + + ) default: return null } diff --git a/VAMobile/src/screens/HealthScreen/Pharmacy/PrescriptionCommon/RefillTag.tsx b/VAMobile/src/screens/HealthScreen/Pharmacy/PrescriptionCommon/RefillTag.tsx index bfb654b4bdd..1c946697735 100644 --- a/VAMobile/src/screens/HealthScreen/Pharmacy/PrescriptionCommon/RefillTag.tsx +++ b/VAMobile/src/screens/HealthScreen/Pharmacy/PrescriptionCommon/RefillTag.tsx @@ -28,6 +28,7 @@ function RefillTag({ status }: RefillTagProps) { labelType: getTagTypeForStatus(status), onPress: () => navigateTo('StatusDefinition', { display: statusText, value: status }), a11yHint: t('prescription.history.a11yHint.status'), + a11yRole: 'link', } return ( diff --git a/VAMobile/src/screens/HealthScreen/Pharmacy/PrescriptionHistory/PrescriptionHistory.tsx b/VAMobile/src/screens/HealthScreen/Pharmacy/PrescriptionHistory/PrescriptionHistory.tsx index 56e50ce8038..e08faf29405 100644 --- a/VAMobile/src/screens/HealthScreen/Pharmacy/PrescriptionHistory/PrescriptionHistory.tsx +++ b/VAMobile/src/screens/HealthScreen/Pharmacy/PrescriptionHistory/PrescriptionHistory.tsx @@ -216,7 +216,7 @@ function PrescriptionHistory({ navigation, route }: PrescriptionHistoryProps) { const detailsPressableProps: PressableProps = { onPress: () => prescriptionDetailsClicked(prescription), accessible: true, - accessibilityRole: 'button', + accessibilityRole: 'link', accessibilityLabel: t('prescription.history.getDetails'), } diff --git a/VAMobile/src/screens/HealthScreen/SecureMessaging/ViewMessage/ViewMessageScreen.test.tsx b/VAMobile/src/screens/HealthScreen/SecureMessaging/ViewMessage/ViewMessageScreen.test.tsx index 1b796a4178b..946a6c7097c 100644 --- a/VAMobile/src/screens/HealthScreen/SecureMessaging/ViewMessage/ViewMessageScreen.test.tsx +++ b/VAMobile/src/screens/HealthScreen/SecureMessaging/ViewMessage/ViewMessageScreen.test.tsx @@ -4,10 +4,13 @@ import { screen } from '@testing-library/react-native' import { CategoryTypeFields, + SecureMessagingFolderMessagesGetData, SecureMessagingFoldersGetData, SecureMessagingMessageGetData, + SecureMessagingSystemFolderIdConstants, SecureMessagingThreadGetData, } from 'api/types' +import { LARGE_PAGE_SIZE } from 'constants/common' import * as api from 'store/api' import { context, mockNavProps, render, waitFor, when } from 'testUtils' @@ -195,6 +198,47 @@ context('ViewMessageScreen', () => { inboxUnreadCount: 0, } + const messages: SecureMessagingFolderMessagesGetData = { + data: [ + { + type: 'test', + id: 1, + attributes: { + messageId: 1, + category: CategoryTypeFields.other, + subject: 'test', + body: 'test', + hasAttachments: false, + attachment: false, + sentDate: '1-1-21', + senderId: 2, + senderName: 'mock sender', + recipientId: 3, + recipientName: 'mock recipient name', + readReceipt: 'mock read receipt', + }, + }, + ], + links: { + self: '', + first: '', + prev: '', + next: '', + last: '', + }, + meta: { + sort: { + sentDate: 'DESC', + }, + pagination: { + currentPage: 1, + perPage: 1, + totalPages: 3, + totalEntries: 5, + }, + }, + } + const initializeTestInstance = (messageID: number = 3) => { render( { .mockResolvedValue(oldMessage) .calledWith('/v0/messaging/health/folders') .mockResolvedValue(listOfFolders) + .calledWith(`/v0/messaging/health/folders/${SecureMessagingSystemFolderIdConstants.INBOX}/messages`, { + page: '1', + per_page: LARGE_PAGE_SIZE.toString(), + useCache: 'false', + } as api.Params) + .mockResolvedValue(messages) initializeTestInstance(45) await waitFor(() => expect(screen.getByText('mock sender 45')).toBeTruthy()) await waitFor(() => expect(screen.getByText('Start new message')).toBeTruthy()) @@ -240,6 +290,12 @@ context('ViewMessageScreen', () => { .mockResolvedValue(message) .calledWith('/v0/messaging/health/folders') .mockResolvedValue(listOfFolders) + .calledWith(`/v0/messaging/health/folders/${SecureMessagingSystemFolderIdConstants.INBOX}/messages`, { + page: '1', + per_page: LARGE_PAGE_SIZE.toString(), + useCache: 'false', + } as api.Params) + .mockResolvedValue(messages) initializeTestInstance() expect(screen.getByText('Loading your message...')).toBeTruthy() await waitFor(() => expect(screen.queryByRole('link', { name: '1-800-698-2411.Thank' })).toBeFalsy()) diff --git a/VAMobile/src/screens/HealthScreen/SecureMessaging/ViewMessage/ViewMessageScreen.tsx b/VAMobile/src/screens/HealthScreen/SecureMessaging/ViewMessage/ViewMessageScreen.tsx index bcb6ec070e1..f4b215db10a 100644 --- a/VAMobile/src/screens/HealthScreen/SecureMessaging/ViewMessage/ViewMessageScreen.tsx +++ b/VAMobile/src/screens/HealthScreen/SecureMessaging/ViewMessage/ViewMessageScreen.tsx @@ -9,7 +9,14 @@ import { useQueryClient } from '@tanstack/react-query' import { DateTime } from 'luxon' import _ from 'underscore' -import { secureMessagingKeys, useFolders, useMessage, useMoveMessage, useThread } from 'api/secureMessaging' +import { + secureMessagingKeys, + useFolderMessages, + useFolders, + useMessage, + useMoveMessage, + useThread, +} from 'api/secureMessaging' import { MoveMessageParameters, SecureMessagingAttachment, @@ -134,6 +141,15 @@ function ViewMessageScreen({ route, navigation }: ViewMessageScreenProps) { enabled: isScreenContentAllowed && smNotInDowntime, }) + const { + data: inboxMessagesData, + isFetching: loadingFolderMessages, + error: folderMessagesError, + refetch: refetchFolderMessages, + } = useFolderMessages(currentFolderIdParam, { + enabled: isScreenContentAllowed && smNotInDowntime, + }) + const folders = foldersData?.data || ([] as SecureMessagingFolderList) const message = messageData?.data.attributes || ({} as SecureMessagingMessageAttributes) const includedAttachments = messageData?.included?.filter((included) => included.type === 'attachments') @@ -159,11 +175,7 @@ function ViewMessageScreen({ route, navigation }: ViewMessageScreenProps) { useEffect(() => { if (messageFetched && currentFolderIdParam === SecureMessagingSystemFolderIdConstants.INBOX && currentPage) { let updateQueries = false - const inboxMessagesData = queryClient.getQueryData([ - secureMessagingKeys.folderMessages, - currentFolderIdParam, - ]) as SecureMessagingFolderMessagesGetData - const newInboxMessages = inboxMessagesData.data.map((m) => { + const newInboxMessages = inboxMessagesData?.data.map((m) => { if (m.attributes.messageId === message.messageId && m.attributes.readReceipt !== READ) { updateQueries = true m.attributes.readReceipt = READ @@ -209,6 +221,7 @@ function ViewMessageScreen({ route, navigation }: ViewMessageScreenProps) { messageData?.included, foldersData, messageData, + inboxMessagesData, ]) const getFolders = (): PickerItem[] => { @@ -354,8 +367,8 @@ function ViewMessageScreen({ route, navigation }: ViewMessageScreenProps) { // If error is caused by an individual message, we want the error alert to be // contained to that message, not to take over the entire screen - const hasError = foldersError || messageError || threadError || !smNotInDowntime - const isLoading = loadingFolder || loadingThread || loadingMessage || loadingMoveMessage + const hasError = folderMessagesError || foldersError || messageError || threadError || !smNotInDowntime + const isLoading = loadingFolder || loadingThread || loadingMessage || loadingMoveMessage || loadingFolderMessages const isEmpty = !message || !thread const loadingText = loadingMoveMessage ? t('secureMessaging.movingMessage') : t('secureMessaging.viewMessage.loading') @@ -385,9 +398,17 @@ function ViewMessageScreen({ route, navigation }: ViewMessageScreenProps) { ) : hasError ? ( ) : isEmpty ? ( diff --git a/VAMobile/src/screens/HomeScreen/ContactVAScreen/ContactVAScreen.test.tsx b/VAMobile/src/screens/HomeScreen/ContactVAScreen/ContactVAScreen.test.tsx index 71eab71b885..101314e4ffc 100644 --- a/VAMobile/src/screens/HomeScreen/ContactVAScreen/ContactVAScreen.test.tsx +++ b/VAMobile/src/screens/HomeScreen/ContactVAScreen/ContactVAScreen.test.tsx @@ -1,8 +1,10 @@ import React from 'react' import { screen } from '@testing-library/react-native' +import { t } from 'i18next' import { context, mockNavProps, render } from 'testUtils' +import { displayedTextPhoneNumber } from 'utils/formattingUtils' import ContactVAScreen from './ContactVAScreen' @@ -18,14 +20,10 @@ context('ContactVAScreen', () => { }) it('initializes correctly', () => { - expect(screen.getByRole('link', { name: 'Talk to the Veterans Crisis Line now' })).toBeTruthy() - expect(screen.getByRole('header', { name: 'Call MyVA411' })).toBeTruthy() - expect( - screen.getByText( - 'MyVA411 is our main VA information line. We can help connect you to any of our VA contact centers.', - ), - ).toBeTruthy() - expect(screen.getByRole('link', { name: '800-698-2411' })).toBeTruthy() - expect(screen.getByRole('link', { name: 'TTY: 711' })).toBeTruthy() + expect(screen.getByRole('link', { name: t('crisisLineButton.label') })).toBeTruthy() + expect(screen.getByRole('header', { name: t('contactVA.va411.callMy') })).toBeTruthy() + expect(screen.getByText(t('contactVA.va411.body'))).toBeTruthy() + expect(screen.getByRole('link', { name: displayedTextPhoneNumber(t('8006982411')) })).toBeTruthy() + expect(screen.getByRole('link', { name: t('contactVA.tty.displayText') })).toBeTruthy() }) }) diff --git a/VAMobile/src/screens/HomeScreen/HomeScreen.test.tsx b/VAMobile/src/screens/HomeScreen/HomeScreen.test.tsx index 6d1d16f03b8..b7971babf71 100644 --- a/VAMobile/src/screens/HomeScreen/HomeScreen.test.tsx +++ b/VAMobile/src/screens/HomeScreen/HomeScreen.test.tsx @@ -2,6 +2,7 @@ import React from 'react' import { Linking } from 'react-native' import { fireEvent, screen, waitFor } from '@testing-library/react-native' +import { t } from 'i18next' import { DateTime } from 'luxon' import { @@ -15,6 +16,7 @@ import { DEFAULT_UPCOMING_DAYS_LIMIT } from 'constants/appointments' import { get } from 'store/api' import { ErrorsState } from 'store/slices' import { RenderParams, context, mockNavProps, render, when } from 'testUtils' +import { roundToHundredthsPlace } from 'utils/formattingUtils' import { getAppointmentsPayload, getClaimsAndAppealsPayload, @@ -107,9 +109,7 @@ context('HomeScreen', () => { .mockRejectedValue('failure') initializeTestInstance() - await waitFor(() => - expect(screen.queryByText('We can’t show all activity right now. Check back later.')).toBeTruthy(), - ) + await waitFor(() => expect(screen.queryByText(t('activity.error.cantShowAllActivity'))).toBeTruthy()) }) it('displays error message when one of the features are in downtime', async () => { @@ -135,10 +135,8 @@ context('HomeScreen', () => { } as ErrorsState, }, }) - await waitFor(() => expect(screen.queryByText('Loading mobile app activity...')).toBeFalsy()) - await waitFor(() => - expect(screen.getByText('We can’t show all activity right now. Check back later.')).toBeTruthy(), - ) + await waitFor(() => expect(screen.queryByText(t('activity.loading'))).toBeFalsy()) + await waitFor(() => expect(screen.getByText(t('activity.error.cantShowAllActivity'))).toBeTruthy()) }) it('displays error message when all the features are in downtime', async () => { @@ -169,10 +167,8 @@ context('HomeScreen', () => { } as ErrorsState, }, }) - await waitFor(() => expect(screen.queryByText('Loading mobile app activity...')).toBeFalsy()) - await waitFor(() => - expect(screen.getByText('We can’t show all activity right now. Check back later.')).toBeTruthy(), - ) + await waitFor(() => expect(screen.queryByText(t('activity.loading'))).toBeFalsy()) + await waitFor(() => expect(screen.getByText(t('activity.error.cantShowAllActivity'))).toBeTruthy()) }) it('does not display an error message when all API calls succeed', async () => { @@ -187,10 +183,8 @@ context('HomeScreen', () => { .mockResolvedValue(getPrescriptionsPayload(3)) initializeTestInstance() - await waitFor(() => expect(screen.queryByText('Loading mobile app activity...')).toBeFalsy()) - await waitFor(() => - expect(screen.queryByText('We can’t show all activity right now. Check back later.')).toBeFalsy(), - ) + await waitFor(() => expect(screen.queryByText(t('activity.loading'))).toBeFalsy()) + await waitFor(() => expect(screen.queryByText(t('activity.error.cantShowAllActivity'))).toBeFalsy()) }) it('displays cerner related message if veteran has a cerner facility', async () => { @@ -198,7 +192,7 @@ context('HomeScreen', () => { .calledWith('/v0/facilities-info') .mockResolvedValue(getFacilitiesPayload(true)) initializeTestInstance() - await waitFor(() => expect(screen.getByText('Information from My VA Health portal not included.')).toBeTruthy()) + await waitFor(() => expect(screen.getByText(t('activity.informationNotIncluded'))).toBeTruthy()) }) it('does not display cerner related message if veteran does not have a cerner facility', async () => { @@ -207,7 +201,7 @@ context('HomeScreen', () => { .mockResolvedValue(getFacilitiesPayload(false)) initializeTestInstance() await waitFor(() => expect(get).toBeCalledWith('/v0/facilities-info')) - await waitFor(() => expect(screen.queryByText('Information from My VA Health portal not included.')).toBeFalsy()) + await waitFor(() => expect(screen.queryByText(t('activity.informationNotIncluded'))).toBeFalsy()) }) }) @@ -218,11 +212,14 @@ context('HomeScreen', () => { .calledWith('/v0/appointments', expect.anything()) .mockResolvedValue(getAppointmentsPayload(upcomingAppointmentsCount)) initializeTestInstance() - await waitFor(() => expect(screen.getByRole('link', { name: 'Appointments' })).toBeTruthy()) + await waitFor(() => expect(screen.getByRole('link', { name: t('appointments') })).toBeTruthy()) await waitFor(() => expect( screen.getByRole('link', { - name: `${upcomingAppointmentsCount} in the next ${DEFAULT_UPCOMING_DAYS_LIMIT} days`, + name: t('appointments.activityButton.subText', { + count: upcomingAppointmentsCount, + dayCount: DEFAULT_UPCOMING_DAYS_LIMIT, + }), }), ).toBeTruthy(), ) @@ -233,7 +230,7 @@ context('HomeScreen', () => { .calledWith('/v0/appointments', expect.anything()) .mockResolvedValue(getAppointmentsPayload(3)) initializeTestInstance() - await waitFor(() => fireEvent.press(screen.getByRole('link', { name: 'Appointments' }))) + await waitFor(() => fireEvent.press(screen.getByRole('link', { name: t('appointments') }))) await waitFor(() => expect(Linking.openURL).toBeCalledWith('vamobile://appointments')) }) @@ -242,8 +239,8 @@ context('HomeScreen', () => { .calledWith('/v0/appointments', expect.anything()) .mockResolvedValue(getAppointmentsPayload(0)) initializeTestInstance() - await waitFor(() => expect(screen.queryByText('Loading mobile app activity...')).toBeFalsy()) - await waitFor(() => expect(screen.queryByRole('link', { name: 'Appointments' })).toBeFalsy()) + await waitFor(() => expect(screen.queryByText(t('activity.loading'))).toBeFalsy()) + await waitFor(() => expect(screen.queryByRole('link', { name: t('appointments') })).toBeFalsy()) }) it('is not displayed when the API call throws an error', async () => { @@ -251,8 +248,8 @@ context('HomeScreen', () => { .calledWith('/v0/appointments', expect.anything()) .mockRejectedValue('fail') initializeTestInstance() - await waitFor(() => expect(screen.queryByText('Loading mobile app activity...')).toBeFalsy()) - await waitFor(() => expect(screen.queryByRole('link', { name: 'Appointments' })).toBeFalsy()) + await waitFor(() => expect(screen.queryByText(t('activity.loading'))).toBeFalsy()) + await waitFor(() => expect(screen.queryByRole('link', { name: t('appointments') })).toBeFalsy()) }) it('is not displayed when appointments is in downtime', async () => { @@ -271,8 +268,8 @@ context('HomeScreen', () => { } as ErrorsState, }, }) - await waitFor(() => expect(screen.queryByText('Loading mobile app activity...')).toBeFalsy()) - await waitFor(() => expect(screen.queryByRole('link', { name: 'Appointments' })).toBeFalsy()) + await waitFor(() => expect(screen.queryByText(t('activity.loading'))).toBeFalsy()) + await waitFor(() => expect(screen.queryByRole('link', { name: t('appointments') })).toBeFalsy()) }) }) @@ -283,8 +280,16 @@ context('HomeScreen', () => { .calledWith('/v0/claims-and-appeals-overview', expect.anything()) .mockResolvedValue(getClaimsAndAppealsPayload(activeClaimsCount)) initializeTestInstance() - await waitFor(() => expect(screen.getByRole('link', { name: 'Claims' })).toBeTruthy()) - await waitFor(() => expect(screen.getByRole('link', { name: `${activeClaimsCount} active` })).toBeTruthy()) + await waitFor(() => expect(screen.getByRole('link', { name: t('claims.title') })).toBeTruthy()) + await waitFor(() => + expect( + screen.getByRole('link', { + name: t('claims.activityButton.subText', { + count: activeClaimsCount, + }), + }), + ).toBeTruthy(), + ) }) it('navigates to Claims history screen when pressed', async () => { @@ -292,7 +297,7 @@ context('HomeScreen', () => { .calledWith('/v0/claims-and-appeals-overview', expect.anything()) .mockResolvedValue(getClaimsAndAppealsPayload(2)) initializeTestInstance() - await waitFor(() => fireEvent.press(screen.getByRole('link', { name: 'Claims' }))) + await waitFor(() => fireEvent.press(screen.getByRole('link', { name: t('claims.title') }))) await waitFor(() => expect(Linking.openURL).toBeCalledWith('vamobile://claims')) }) @@ -301,8 +306,8 @@ context('HomeScreen', () => { .calledWith('/v0/claims-and-appeals-overview', expect.anything()) .mockResolvedValue(getClaimsAndAppealsPayload(0)) initializeTestInstance() - await waitFor(() => expect(screen.queryByText('Loading mobile app activity...')).toBeFalsy()) - await waitFor(() => expect(screen.queryByRole('link', { name: 'Claims' })).toBeFalsy()) + await waitFor(() => expect(screen.queryByText(t('activity.loading'))).toBeFalsy()) + await waitFor(() => expect(screen.queryByRole('link', { name: t('claims.title') })).toBeFalsy()) }) it('is not displayed when the API call throws an error', async () => { @@ -310,8 +315,8 @@ context('HomeScreen', () => { .calledWith('/v0/claims-and-appeals-overview', expect.anything()) .mockRejectedValue('fail') initializeTestInstance() - await waitFor(() => expect(screen.queryByText('Loading mobile app activity...')).toBeFalsy()) - await waitFor(() => expect(screen.queryByRole('link', { name: 'Claims' })).toBeFalsy()) + await waitFor(() => expect(screen.queryByText(t('activity.loading'))).toBeFalsy()) + await waitFor(() => expect(screen.queryByRole('link', { name: t('claims.title') })).toBeFalsy()) }) it('is not displayed when there is a service error', async () => { @@ -326,8 +331,8 @@ context('HomeScreen', () => { .calledWith('/v0/claims-and-appeals-overview', expect.anything()) .mockResolvedValue(getClaimsAndAppealsPayload(2, serviceErrors)) initializeTestInstance() - await waitFor(() => expect(screen.queryByText('Loading mobile app activity...')).toBeFalsy()) - await waitFor(() => expect(screen.queryByRole('link', { name: 'Claims' })).toBeFalsy()) + await waitFor(() => expect(screen.queryByText(t('activity.loading'))).toBeFalsy()) + await waitFor(() => expect(screen.queryByRole('link', { name: t('claims.title') })).toBeFalsy()) }) it('is not displayed when claims is in downtime', async () => { @@ -349,8 +354,8 @@ context('HomeScreen', () => { } as ErrorsState, }, }) - await waitFor(() => expect(screen.queryByText('Loading mobile app activity...')).toBeFalsy()) - await waitFor(() => expect(screen.queryByRole('link', { name: 'Claims' })).toBeFalsy()) + await waitFor(() => expect(screen.queryByText(t('activity.loading'))).toBeFalsy()) + await waitFor(() => expect(screen.queryByRole('link', { name: t('claims.title') })).toBeFalsy()) }) }) @@ -361,8 +366,14 @@ context('HomeScreen', () => { .calledWith('/v0/messaging/health/folders') .mockResolvedValue(getFoldersPayload(unreadMessageCount)) initializeTestInstance() - await waitFor(() => expect(screen.getByRole('link', { name: 'Messages' })).toBeTruthy()) - await waitFor(() => expect(screen.getByRole('link', { name: `${unreadMessageCount} unread` })).toBeTruthy()) + await waitFor(() => expect(screen.getByRole('link', { name: t('messages') })).toBeTruthy()) + await waitFor(() => + expect( + screen.getByRole('link', { + name: t('secureMessaging.activityButton.subText', { count: unreadMessageCount }), + }), + ).toBeTruthy(), + ) }) it('navigates to Messages screen when pressed', async () => { @@ -370,7 +381,7 @@ context('HomeScreen', () => { .calledWith('/v0/messaging/health/folders') .mockResolvedValue(getFoldersPayload(3)) initializeTestInstance() - await waitFor(() => fireEvent.press(screen.getByRole('link', { name: 'Messages' }))) + await waitFor(() => fireEvent.press(screen.getByRole('link', { name: t('messages') }))) await waitFor(() => expect(Linking.openURL).toBeCalledWith('vamobile://messages')) }) @@ -379,8 +390,8 @@ context('HomeScreen', () => { .calledWith('/v0/messaging/health/folders') .mockResolvedValue(getFoldersPayload(0)) initializeTestInstance() - await waitFor(() => expect(screen.queryByText('Loading mobile app activity...')).toBeFalsy()) - await waitFor(() => expect(screen.queryByRole('link', { name: 'Messages' })).toBeFalsy()) + await waitFor(() => expect(screen.queryByText(t('activity.loading'))).toBeFalsy()) + await waitFor(() => expect(screen.queryByRole('link', { name: t('messages') })).toBeFalsy()) }) it('is not displayed when the API call throws an error', async () => { @@ -388,8 +399,8 @@ context('HomeScreen', () => { .calledWith('/v0/messaging/health/folders') .mockRejectedValue('fail') initializeTestInstance() - await waitFor(() => expect(screen.queryByText('Loading mobile app activity...')).toBeFalsy()) - await waitFor(() => expect(screen.queryByRole('link', { name: 'Messages' })).toBeFalsy()) + await waitFor(() => expect(screen.queryByText(t('activity.loading'))).toBeFalsy()) + await waitFor(() => expect(screen.queryByRole('link', { name: t('messages') })).toBeFalsy()) }) it('is not displayed when secure messaging is in downtime', async () => { @@ -408,8 +419,8 @@ context('HomeScreen', () => { } as ErrorsState, }, }) - await waitFor(() => expect(screen.queryByText('Loading mobile app activity...')).toBeFalsy()) - await waitFor(() => expect(screen.queryByRole('link', { name: 'Messages' })).toBeFalsy()) + await waitFor(() => expect(screen.queryByText(t('activity.loading'))).toBeFalsy()) + await waitFor(() => expect(screen.queryByRole('link', { name: t('messages') })).toBeFalsy()) }) }) @@ -420,9 +431,15 @@ context('HomeScreen', () => { .calledWith('/v0/health/rx/prescriptions', expect.anything()) .mockResolvedValue(getPrescriptionsPayload(refillablePrescriptionsCount)) initializeTestInstance() - await waitFor(() => expect(screen.getByRole('link', { name: 'Prescriptions' })).toBeTruthy()) + await waitFor(() => expect(screen.getByRole('link', { name: t('prescription.title') })).toBeTruthy()) await waitFor(() => - expect(screen.getByRole('link', { name: `${refillablePrescriptionsCount} ready to refill` })).toBeTruthy(), + expect( + screen.getByRole('link', { + name: t('prescriptions.activityButton.subText', { + count: refillablePrescriptionsCount, + }), + }), + ).toBeTruthy(), ) }) @@ -431,7 +448,7 @@ context('HomeScreen', () => { .calledWith('/v0/health/rx/prescriptions', expect.anything()) .mockResolvedValue(getPrescriptionsPayload(3)) initializeTestInstance() - await waitFor(() => fireEvent.press(screen.getByRole('link', { name: 'Prescriptions' }))) + await waitFor(() => fireEvent.press(screen.getByRole('link', { name: t('prescription.title') }))) await waitFor(() => expect(Linking.openURL).toBeCalledWith('vamobile://prescriptions')) }) @@ -440,8 +457,8 @@ context('HomeScreen', () => { .calledWith('/v0/health/rx/prescriptions', expect.anything()) .mockResolvedValue(getPrescriptionsPayload(0)) initializeTestInstance() - await waitFor(() => expect(screen.queryByText('Loading mobile app activity...')).toBeFalsy()) - await waitFor(() => expect(screen.queryByRole('link', { name: 'Prescriptions' })).toBeFalsy()) + await waitFor(() => expect(screen.queryByText(t('activity.loading'))).toBeFalsy()) + await waitFor(() => expect(screen.queryByRole('link', { name: t('prescription.title') })).toBeFalsy()) }) it('is not displayed when the API call throws an error', async () => { @@ -449,8 +466,8 @@ context('HomeScreen', () => { .calledWith('/v0/health/rx/prescriptions', expect.anything()) .mockRejectedValue('fail') initializeTestInstance() - await waitFor(() => expect(screen.queryByText('Loading mobile app activity...')).toBeFalsy()) - await waitFor(() => expect(screen.queryByRole('link', { name: 'Prescriptions' })).toBeFalsy()) + await waitFor(() => expect(screen.queryByText(t('activity.loading'))).toBeFalsy()) + await waitFor(() => expect(screen.queryByRole('link', { name: t('prescription.title') })).toBeFalsy()) }) it('is not displayed when prescriptions is in downtime', async () => { @@ -469,18 +486,25 @@ context('HomeScreen', () => { } as ErrorsState, }, }) - await waitFor(() => expect(screen.queryByText('Loading mobile app activity...')).toBeFalsy()) - await waitFor(() => expect(screen.queryByRole('link', { name: 'Prescriptions' })).toBeFalsy()) + await waitFor(() => expect(screen.queryByText(t('activity.loading'))).toBeFalsy()) + await waitFor(() => expect(screen.queryByRole('link', { name: t('prescription.title') })).toBeFalsy()) }) }) describe('About you section', () => { it('displays disability rating percentage when veteran has disability rating', async () => { + const disabilityRating = 100 when(get as jest.Mock) .calledWith('/v0/disability-rating') - .mockResolvedValue(getDisabilityRatingPayload(100)) + .mockResolvedValue(getDisabilityRatingPayload(disabilityRating)) initializeTestInstance() - await waitFor(() => expect(screen.getByLabelText('Disability rating 100% service connected')).toBeTruthy()) + await waitFor(() => + expect( + screen.getByLabelText( + `${t('disabilityRating.title')} ${t('disabilityRatingDetails.percentage', { rate: disabilityRating })} ${t('disabilityRating.serviceConnected')}`, + ), + ).toBeTruthy(), + ) }) it('does not display disability rating percentage when veteran does not have disability rating', async () => { @@ -488,8 +512,8 @@ context('HomeScreen', () => { .calledWith('/v0/disability-rating') .mockResolvedValue(getDisabilityRatingPayload(0)) initializeTestInstance() - await waitFor(() => expect(screen.queryByText('Loading your information...')).toBeFalsy()) - await waitFor(() => expect(screen.queryByText('Disability rating')).toBeFalsy()) + await waitFor(() => expect(screen.queryByText(t('aboutYou.loading'))).toBeFalsy()) + await waitFor(() => expect(screen.queryByText(t('disabilityRating.title'))).toBeFalsy()) }) it('does not display disability rating percentage and show error message when disability ratings API call fails', async () => { @@ -501,19 +525,22 @@ context('HomeScreen', () => { .calledWith('/v0/military-service-history') .mockResolvedValue(getMilitaryServiceHistoryPayload({} as ServiceHistoryAttributes)) initializeTestInstance() - await waitFor(() => expect(screen.queryByText('Loading your information...')).toBeFalsy()) - await waitFor(() => expect(screen.queryByText('Disability rating')).toBeFalsy()) - await waitFor(() => - expect(screen.queryByText('We can’t show all your information right now. Check back later.')).toBeTruthy(), - ) + await waitFor(() => expect(screen.queryByText(t('aboutYou.loading'))).toBeFalsy()) + await waitFor(() => expect(screen.queryByText(t('disabilityRating.title'))).toBeFalsy()) + await waitFor(() => expect(screen.queryByText(t('aboutYou.error.cantShowAllInfo'))).toBeTruthy()) }) it('displays monthly payment amount when veteran has monthly compensation payment', async () => { + const monthlyAwardAmount = 3084.75 when(get as jest.Mock) .calledWith('/v0/letters/beneficiary') - .mockResolvedValue(getLetterBeneficiaryPayload(3084.75)) + .mockResolvedValue(getLetterBeneficiaryPayload(monthlyAwardAmount)) initializeTestInstance() - await waitFor(() => expect(screen.getByLabelText('Monthly compensation payment $3,084.75')).toBeTruthy()) + await waitFor(() => + expect( + screen.getByLabelText(`${t('monthlyCompensationPayment')} $${roundToHundredthsPlace(monthlyAwardAmount)}`), + ).toBeTruthy(), + ) }) it('does not display monthly payment amount when veteran does not have monthly compensation payment', async () => { @@ -521,8 +548,8 @@ context('HomeScreen', () => { .calledWith('/v0/letters/beneficiary') .mockResolvedValue(getLetterBeneficiaryPayload(0)) initializeTestInstance() - await waitFor(() => expect(screen.queryByText('Loading your information...')).toBeFalsy()) - await waitFor(() => expect(screen.queryByText('Monthly compensation payment')).toBeFalsy()) + await waitFor(() => expect(screen.queryByText(t('aboutYou.loading'))).toBeFalsy()) + await waitFor(() => expect(screen.queryByText(t('monthlyCompensationPayment'))).toBeFalsy()) }) it('does not display monthly payment and show error message when the beneficiary API call fails', async () => { @@ -534,11 +561,9 @@ context('HomeScreen', () => { .calledWith('/v0/military-service-history') .mockResolvedValue(getMilitaryServiceHistoryPayload({} as ServiceHistoryAttributes)) initializeTestInstance() - await waitFor(() => expect(screen.queryByText('Loading your information...')).toBeFalsy()) - await waitFor(() => expect(screen.queryByText('Monthly compensation payment')).toBeFalsy()) - await waitFor(() => - expect(screen.queryByText('We can’t show all your information right now. Check back later.')).toBeTruthy(), - ) + await waitFor(() => expect(screen.queryByText(t('aboutYou.loading'))).toBeFalsy()) + await waitFor(() => expect(screen.queryByText(t('monthlyCompensationPayment'))).toBeFalsy()) + await waitFor(() => expect(screen.queryByText(t('aboutYou.error.cantShowAllInfo'))).toBeTruthy()) }) it("displays message when no 'About you' info exists", async () => { @@ -551,10 +576,8 @@ context('HomeScreen', () => { .mockResolvedValue(getMilitaryServiceHistoryPayload({} as ServiceHistoryAttributes)) initializeTestInstance() - await waitFor(() => expect(screen.queryByText('We can’t show your information right now.')).toBeTruthy()) - await waitFor(() => - expect(screen.queryByText('We can’t show all your information right now. Check back later.')).toBeFalsy(), - ) + await waitFor(() => expect(screen.queryByText(t('aboutYou.noInformation'))).toBeTruthy()) + await waitFor(() => expect(screen.queryByText(t('aboutYou.error.cantShowAllInfo'))).toBeFalsy()) }) it('displays error message when one of the features are in downtime', async () => { @@ -577,10 +600,8 @@ context('HomeScreen', () => { } as ErrorsState, }, }) - await waitFor(() => expect(screen.queryByText('Loading your information...')).toBeFalsy()) - await waitFor(() => - expect(screen.queryByText('We can’t show all your information right now. Check back later.')).toBeTruthy(), - ) + await waitFor(() => expect(screen.queryByText(t('aboutYou.loading'))).toBeFalsy()) + await waitFor(() => expect(screen.queryByText(t('aboutYou.error.cantShowAllInfo'))).toBeTruthy()) }) it("displays error message when some 'About you' info doesn't exist and rest of info has errors", async () => { @@ -593,27 +614,25 @@ context('HomeScreen', () => { .mockRejectedValue('fail') initializeTestInstance() - await waitFor(() => expect(screen.queryByText('We can’t show your information right now.')).toBeFalsy()) - await waitFor(() => - expect(screen.queryByText('We can’t show all your information right now. Check back later.')).toBeTruthy(), - ) + await waitFor(() => expect(screen.queryByText(t('aboutYou.noInformation'))).toBeFalsy()) + await waitFor(() => expect(screen.queryByText(t('aboutYou.error.cantShowAllInfo'))).toBeTruthy()) }) }) describe('VA resources section', () => { it('navigates to the "Contact VA" screen when the "Contact us" link is pressed', () => { initializeTestInstance() - fireEvent.press(screen.getByRole('link', { name: 'Contact us' })) + fireEvent.press(screen.getByRole('link', { name: t('contactUs') })) expect(mockNavigationSpy).toBeCalledWith('ContactVA') }) it('launches WebView when the "Find a VA location" link is pressed', () => { initializeTestInstance() - fireEvent.press(screen.getByRole('link', { name: 'Find a VA location' })) + fireEvent.press(screen.getByRole('link', { name: t('findLocation.title') })) expect(mockNavigationSpy).toBeCalledWith('Webview', { - displayTitle: 'va.gov', + displayTitle: t('webview.vagov'), url: 'https://www.va.gov/find-locations/', - loadingMessage: 'Loading VA location finder...', + loadingMessage: t('webview.valocation.loading'), }) }) }) diff --git a/VAMobile/src/screens/HomeScreen/HomeScreen.tsx b/VAMobile/src/screens/HomeScreen/HomeScreen.tsx index cab27ac6447..75f7819ae02 100644 --- a/VAMobile/src/screens/HomeScreen/HomeScreen.tsx +++ b/VAMobile/src/screens/HomeScreen/HomeScreen.tsx @@ -65,6 +65,7 @@ import ProfileScreen from './ProfileScreen/ProfileScreen' import SettingsScreen from './ProfileScreen/SettingsScreen' import AccountSecurity from './ProfileScreen/SettingsScreen/AccountSecurity/AccountSecurity' import DeveloperScreen from './ProfileScreen/SettingsScreen/DeveloperScreen' +import OverrideAPIScreen from './ProfileScreen/SettingsScreen/DeveloperScreen/OverrideApiScreen' import RemoteConfigScreen from './ProfileScreen/SettingsScreen/DeveloperScreen/RemoteConfigScreen' import NotificationsSettingsScreen from './ProfileScreen/SettingsScreen/NotificationsSettingsScreen/NotificationsSettingsScreen' @@ -583,6 +584,11 @@ function HomeStackScreen({}: HomeStackScreenProps) { options={FEATURE_LANDING_TEMPLATE_OPTIONS} /> + { .calledWith('/v0/military-service-history') .mockResolvedValue(militaryServiceHistoryData) initializeTestInstance() - await waitFor(() => expect(screen.queryByText('Personal information')).toBeFalsy()) - await waitFor(() => expect(screen.queryByText('Contact information')).toBeFalsy()) - await waitFor(() => expect(screen.getByText('Military information')).toBeTruthy()) - await waitFor(() => expect(screen.getByText('Settings')).toBeTruthy()) + await waitFor(() => expect(screen.queryByText(t('personalInformation.title'))).toBeFalsy()) + await waitFor(() => expect(screen.queryByText(t('contactInformation.title'))).toBeFalsy()) + await waitFor(() => expect(screen.getByText(t('militaryInformation.title'))).toBeTruthy()) + await waitFor(() => expect(screen.getByText(t('settings.title'))).toBeTruthy()) }) }) @@ -89,9 +90,7 @@ context('ProfileScreen', () => { .mockRejectedValue({ networkError: true } as api.APIError) initializeTestInstance() - await waitFor(() => - expect(screen.getByText('We can’t show all your information right now. Check back later.')).toBeTruthy(), - ) + await waitFor(() => expect(screen.getByText(t('aboutYou.error.cantShowAllInfo'))).toBeTruthy()) }) }) }) diff --git a/VAMobile/src/screens/HomeScreen/ProfileScreen/SettingsScreen/DeveloperScreen/DeveloperScreen.tsx b/VAMobile/src/screens/HomeScreen/ProfileScreen/SettingsScreen/DeveloperScreen/DeveloperScreen.tsx index da98fba138a..3f4657520a1 100644 --- a/VAMobile/src/screens/HomeScreen/ProfileScreen/SettingsScreen/DeveloperScreen/DeveloperScreen.tsx +++ b/VAMobile/src/screens/HomeScreen/ProfileScreen/SettingsScreen/DeveloperScreen/DeveloperScreen.tsx @@ -213,6 +213,11 @@ function DeveloperScreen({ navigation }: DeveloperScreenSettingsScreenProps) { + + + + ) + }) + + return ( + + +