diff --git a/.github/workflows/integration_tests.yaml b/.github/workflows/integration_tests.yaml new file mode 100644 index 00000000..275c9f1b --- /dev/null +++ b/.github/workflows/integration_tests.yaml @@ -0,0 +1,153 @@ +name: Build and Run Integration Tests + +run-name: ${{ github.actor }} is building and testing OpenHIIT + +on: + workflow_dispatch: + pull_request: + types: + - opened + - reopened + - synchronize + - ready_for_review + branches: + - 'main' + - 'pre-release' + paths: + - 'lib/**' + - 'test_driver/**' + - 'integration_test/**' + - 'pubspec.yaml' + - 'pubspec.lock' + - 'android/**' + - 'ios/**' + +jobs: + + Workout: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + + - uses: subosito/flutter-action@v2 + with: + channel: 'stable' + - name: Check Version + run: flutter --version + + - name: Flutter Doctor + run: flutter doctor -v + + - name: Enable KVM + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + + - name: run tests + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 35 + arch: x86_64 + profile: pixel_6_pro + script: | + flutter build apk --target integration_test/workout_test.dart --debug + adb -s emulator-5554 install build/app/outputs/flutter-apk/app-debug.apk + adb -s emulator-5554 root + adb -s emulator-5554 shell appops set com.codepup.workout_timer SCHEDULE_EXACT_ALARM allow + flutter drive -d emulator-5554 --driver=test_driver/integration_test.dart --use-application-binary=build/app/outputs/flutter-apk/app-debug.apk + + Timer: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + + - uses: subosito/flutter-action@v2 + with: + channel: 'stable' + + - name: Check Version + run: flutter --version + + - name: Flutter Doctor + run: flutter doctor -v + + - name: Enable KVM + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + + - name: run tests + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 35 + arch: x86_64 + profile: pixel_6_pro + script: | + flutter build apk --target integration_test/timer_test.dart --debug + adb -s emulator-5554 install build/app/outputs/flutter-apk/app-debug.apk + adb -s emulator-5554 root + adb -s emulator-5554 shell appops set com.codepup.workout_timer SCHEDULE_EXACT_ALARM allow + flutter drive -d emulator-5554 --driver=test_driver/integration_test.dart --use-application-binary=build/app/outputs/flutter-apk/app-debug.apk + + General: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + + - uses: subosito/flutter-action@v2 + with: + channel: 'stable' + + - name: Check Version + run: flutter --version + + - name: Flutter Doctor + run: flutter doctor -v + + - name: Enable KVM + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + + - name: run tests + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 35 + arch: x86_64 + profile: pixel_6_pro + script: | + flutter build apk --target integration_test/general_test.dart --debug + adb -s emulator-5554 install build/app/outputs/flutter-apk/app-debug.apk + adb -s emulator-5554 root + adb -s emulator-5554 shell appops set com.codepup.workout_timer SCHEDULE_EXACT_ALARM allow + flutter drive -d emulator-5554 --driver=test_driver/integration_test.dart --use-application-binary=build/app/outputs/flutter-apk/app-debug.apk \ No newline at end of file diff --git a/.github/workflows/nightly_build.yaml b/.github/workflows/nightly_build.yaml new file mode 100644 index 00000000..21e0b0d9 --- /dev/null +++ b/.github/workflows/nightly_build.yaml @@ -0,0 +1,130 @@ +name: Nightly Build + +run-name: Building the Android and iOS app bundles 🚀 + +on: + workflow_dispatch: + schedule: + - cron: '0 0 * * *' # Runs every day at midnight UTC + +jobs: + + BuildAndroid: + runs-on: ubuntu-latest + environment: build + permissions: + contents: write + + steps: + - uses: actions/checkout@v3 + + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: '17' + cache: 'gradle' + check-latest: true + + - name: Checkout app code + uses: actions/checkout@v3 + + - uses: subosito/flutter-action@v2 + with: + channel: "stable" + + - run: flutter doctor -v + + - run: flutter pub get + + - name: Download Android keystore + id: android_keystore + uses: timheuer/base64-to-file@v1.0.3 + with: + fileName: upload-keystore.jks + encodedString: ${{ secrets.KEYSTORE }} + + - name: Create key.properties + run: | + echo "storeFile=${{ steps.android_keystore.outputs.filePath }}" > android/key.properties + echo "storePassword=${{ secrets.SIGNING_STORE_PASSWORD }}" >> android/key.properties + echo "keyPassword=${{ secrets.SIGNING_KEY_PASSWORD }}" >> android/key.properties + echo "keyAlias=${{ secrets.SIGNING_KEY_ALIAS }}" >> android/key.properties + + - name: Build Android Debug APK + run: flutter build apk --debug + + - name: Build Android Debug AppBundle + run: flutter build appbundle --debug + + - name: Upload APK Artifact + uses: actions/upload-artifact@v3 + with: + name: debug-apk + path: build/app/outputs/flutter-apk/app-debug.apk + + - name: Upload Android AppBundle Artifact + uses: actions/upload-artifact@v3 + with: + name: debug-aab + path: build/app/outputs/flutter-apk/app-debug.aab + + BuildiOS: + runs-on: macos-latest + environment: build + permissions: + contents: write + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - uses: subosito/flutter-action@v2 + with: + channel: "stable" + + - run: flutter doctor -v + + - run: flutter pub get + + - name: Install the Certificate and Provisioning Profile + env: + BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }} + P12_PASSWORD: ${{ secrets.P12_PASSWORD }} + BUILD_PROVISION_PROFILE_BASE64: ${{ secrets.BUILD_PROVISION_PROFILE_BASE64 }} + KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} + EXPORT_OPTIONS_PLIST: ${{ secrets.EXPORT_OPTIONS_PLIST }} + run: | + # create variables + CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12 + PP_PATH=$RUNNER_TEMP/build_pp.mobileprovision + KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db + # import certificate and provisioning profile from secrets + echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode --output $CERTIFICATE_PATH + echo -n "$BUILD_PROVISION_PROFILE_BASE64" | base64 --decode --output $PP_PATH + # create temporary keychain + security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + security set-keychain-settings -lut 21600 $KEYCHAIN_PATH + security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + # import certificate to keychain + security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH + security list-keychain -d user -s $KEYCHAIN_PATH + # apply provisioning profile + mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles + cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles + EXPORT_OPTS_PATH=$RUNNER_TEMP/ExportOptions.plist + echo -n "$EXPORT_OPTIONS_PLIST" | base64 --decode -o $EXPORT_OPTS_PATH + + - name: Build and Sign IPA + run: flutter build ipa --debug --export-options-plist=$RUNNER_TEMP/ExportOptions.plist + + - name: Upload IPA Artifact + uses: actions/upload-artifact@v3 + with: + name: debug-ipa + path: build/ios/ipa/*.ipa + + - name: Clean up keychain and provisioning profile + if: ${{ always() }} + run: | + security delete-keychain $RUNNER_TEMP/app-signing.keychain-db + rm ~/Library/MobileDevice/Provisioning\ Profiles/build_pp.mobileprovision diff --git a/.github/workflows/build_and_release.yaml b/.github/workflows/pre_release.yaml similarity index 81% rename from .github/workflows/build_and_release.yaml rename to .github/workflows/pre_release.yaml index 18717ac2..fd0a5e40 100644 --- a/.github/workflows/build_and_release.yaml +++ b/.github/workflows/pre_release.yaml @@ -1,45 +1,21 @@ -name: Build and Release Flutter App +name: Pre-Release OpenHIIT -run-name: ${{ github.actor }} is building the Android app bundle 🚀 +run-name: Creating pre-release 🚀 on: workflow_dispatch: push: - branches: - - main - paths: - - VERSION - tags-ignore: - - "*" + tags: + - '*-beta' jobs: - GetVersionNumber: - runs-on: ubuntu-latest - permissions: - contents: read - outputs: - version: ${{ steps.version.outputs.content }} - steps: - - uses: actions/checkout@v3 - - - name: Read file - id: version - uses: juliangruber/read-file-action@v1.1.6 - with: - path: VERSION - - - name: Print version - run: echo ${{ steps.version.outputs.content }} - BuildAndroidRelease: runs-on: ubuntu-latest environment: production - needs: GetVersionNumber permissions: contents: write - # Steps represent a sequence of tasks that will be executed as part of the job steps: - uses: actions/checkout@v3 @@ -51,11 +27,9 @@ jobs: cache: 'gradle' check-latest: true - # Checkout the repository code and get packages. - name: Checkout app code uses: actions/checkout@v3 - # Set up Flutter. - uses: subosito/flutter-action@v1 with: channel: "stable" @@ -103,11 +77,10 @@ jobs: permissions: contents: write steps: - # Checks-out our repository under $GITHUB_WORKSPACE, so our job can access it + - name: Checkout repository uses: actions/checkout@v3 - # Set up Flutter. - uses: subosito/flutter-action@v1 with: channel: "stable" @@ -116,7 +89,7 @@ jobs: - run: flutter pub get - # Install the Apple certificate and provisioning profile + # Install the Apple certificate and provisioning profile - name: Install the Apple certificate and provisioning profile env: BUILD_CERTIFICATE_BASE64: ${{ secrets.APPSTORE_CERT_BASE64 }} @@ -164,7 +137,7 @@ jobs: CreateDraftRelease: runs-on: ubuntu-latest - needs: [BuildAndroidRelease, BuildiOSRelease, GetVersionNumber] + needs: [BuildAndroidRelease, BuildiOSRelease] permissions: contents: write steps: @@ -188,6 +161,8 @@ jobs: uses: ncipollo/release-action@v1.13.0 with: artifacts: "app-release.aab, app-release.apk, workout_timer.ipa" - draft: "true" - tag: ${{needs.GetVersionNumber.outputs.version}} - generateReleaseNotes: "true" + draft: true + tag: ${{ github.ref_name }} + generateReleaseNotes: true + skipIfReleaseExists: true + prerelease: true diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 00000000..9be3a22b --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,165 @@ +name: Release OpenHIIT + +run-name: ${{ github.actor }} is creating a new release 🚀 + +on: + workflow_dispatch: + push: + tags: + - 'v*.*.*' + - '!*-beta' + +jobs: + + BuildAndroidRelease: + runs-on: ubuntu-latest + environment: production + permissions: + contents: write + + steps: + - uses: actions/checkout@v3 + + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: '17' + cache: 'gradle' + check-latest: true + + - name: Checkout app code + uses: actions/checkout@v3 + + - uses: subosito/flutter-action@v1 + with: + channel: "stable" + + - run: flutter doctor -v + + - run: flutter pub get + + - name: Download Android keystore + id: android_keystore + uses: timheuer/base64-to-file@v1.0.3 + with: + fileName: upload-keystore.jks + encodedString: ${{ secrets.KEYSTORE_FILE_BASE64 }} + + - name: Create key.properties + run: | + echo "storeFile=${{ steps.android_keystore.outputs.filePath }}" > android/key.properties + echo "storePassword=${{ secrets.KEYSTORE_PASSWORD }}" >> android/key.properties + echo "keyPassword=${{ secrets.KEYSTORE_KEY_PASSWORD }}" >> android/key.properties + echo "keyAlias=${{ secrets.KEYSTORE_KEY_ALIAS }}" >> android/key.properties + + - name: Build Android Release APK + run: flutter build apk --release + + - name: Build Android Release AppBundle + run: flutter build appbundle + + - name: Upload APK Artifact + uses: actions/upload-artifact@v3 + with: + name: release-apk + path: build/app/outputs/apk/release/app-release.apk + + - name: Upload Android AppBundle Artifact + uses: actions/upload-artifact@v3 + with: + name: release-aab + path: build/app/outputs/bundle/release/app-release.aab + + BuildiOSRelease: + runs-on: macos-latest + environment: production + needs: GetVersionNumber + permissions: + contents: write + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - uses: subosito/flutter-action@v1 + with: + channel: "stable" + + - run: flutter doctor -v + + - run: flutter pub get + + # Install the Apple certificate and provisioning profile + - name: Install the Apple certificate and provisioning profile + env: + BUILD_CERTIFICATE_BASE64: ${{ secrets.APPSTORE_CERT_BASE64 }} + P12_PASSWORD: ${{ secrets.APPSTORE_CERT_PASSWORD }} + BUILD_PROVISION_PROFILE_BASE64: ${{ secrets.MOBILEPROVISION_BASE64 }} + KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} + EXPORT_OPTIONS_PLIST: ${{ secrets.EXPORT_OPTIONS_PLIST }} + run: | + # create variables + CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12 + PP_PATH=$RUNNER_TEMP/build_pp.mobileprovision + KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db + # import certificate and provisioning profile from secrets + echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode --output $CERTIFICATE_PATH + echo -n "$BUILD_PROVISION_PROFILE_BASE64" | base64 --decode --output $PP_PATH + # create temporary keychain + security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + security set-keychain-settings -lut 21600 $KEYCHAIN_PATH + security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + # import certificate to keychain + security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH + security list-keychain -d user -s $KEYCHAIN_PATH + # apply provisioning profile + mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles + cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles + EXPORT_OPTS_PATH=$RUNNER_TEMP/ExportOptions.plist + echo -n "$EXPORT_OPTIONS_PLIST" | base64 --decode -o $EXPORT_OPTS_PATH + + - name: Building IPA + run: flutter build ipa --release --export-options-plist=$RUNNER_TEMP/ExportOptions.plist + + - name: Upload IPA Artifact + uses: actions/upload-artifact@v3 + with: + name: release-ipa + path: build/ios/ipa/*.ipa + + - name: Clean up keychain and provisioning profile + if: ${{ always() }} + run: | + security delete-keychain $RUNNER_TEMP/app-signing.keychain-db + rm ~/Library/MobileDevice/Provisioning\ Profiles/build_pp.mobileprovision + + CreateDraftRelease: + runs-on: ubuntu-latest + needs: [BuildAndroidRelease, BuildiOSRelease] + permissions: + contents: write + steps: + + - name: Download Android APK Artifact + uses: actions/download-artifact@v3 + with: + name: release-apk + + - name: Download Android AAB Artifact + uses: actions/download-artifact@v3 + with: + name: release-aab + + - name: Download iOS IPA Artifact + uses: actions/download-artifact@v3 + with: + name: release-ipa + + - name: Create Draft Release + uses: ncipollo/release-action@v1.13.0 + with: + artifacts: "app-release.aab, app-release.apk, workout_timer.ipa" + draft: true + tag: ${{ github.ref_name }} + generateReleaseNotes: true + skipIfReleaseExists: true diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml deleted file mode 100644 index cb9d4fc8..00000000 --- a/.github/workflows/test.yaml +++ /dev/null @@ -1,34 +0,0 @@ -name: Test - -run-name: ${{ github.actor }} is testing the app - -on: - pull_request: - types: - - opened - - reopened - - synchronize - - ready_for_review - branches: - - 'main' - paths-ignore: - - '**.md' - - '**.yaml' - - 'doc/**' - - '.git/' - - '.vscode/' - -jobs: - Build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-java@v2 - with: - distribution: 'zulu' - java-version: '11' - - uses: subosito/flutter-action@v2 - with: - channel: 'stable' - - run: flutter pub get - - run: flutter test \ No newline at end of file diff --git a/docs/test-cases.md b/docs/test-cases.md deleted file mode 100644 index 650e5dcb..00000000 --- a/docs/test-cases.md +++ /dev/null @@ -1,16 +0,0 @@ -# Test Cases - -1. [Create and save a new interval timer](https://github.com/a-mabe/OpenHIIT/blob/main/test/interval_timer_test.dart). -1. [Create and save a new workout](https://github.com/a-mabe/OpenHIIT/blob/main/test/workout_test.dart). -1. Edit an interval timer. -1. Edit a workout. -1. Play interval timer with default sound effects. -1. Play interval timer with custom sounds. -1. Play interval timer with no sounds. -1. Pause/play an interval timer. -1. Restart an interval timer. -1. Play workout with default sounds. -1. Play workout with custom sounds. -1. Play workout with no sounds. -1. Pause/play a workout. -1. Restart a workout. \ No newline at end of file diff --git a/integration_test/functions/functions.dart b/integration_test/functions/functions.dart new file mode 100644 index 00000000..65cfe792 --- /dev/null +++ b/integration_test/functions/functions.dart @@ -0,0 +1,338 @@ +import 'dart:io'; +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:openhiit/main.dart'; + +Future loadApp(WidgetTester tester) async { + await tester.pumpWidget(const WorkoutTimer()); + await tester.pumpAndSettle(); +} + +Future selectSound(WidgetTester tester, Key key, String soundName) async { + print("LOG --- tapping dropdown"); + await tester.tap(find.byKey(key)); + await tester.pumpAndSettle(); + print("LOG --- dropdown opened, selecting sound"); + await tester.tap(find + .descendant(of: find.byKey(key), matching: find.text(soundName)) + .last); + await tester.pumpAndSettle(); + print("LOG --- sound selected"); +} + +Future selectColor(WidgetTester tester) async { + print("LOG --- opening color picker"); + await tester.tap(find.byKey(const Key('color-picker'))); + await tester.pumpAndSettle(); + print("LOG --- hitting select"); + await tester.tap(find.text('Select')); + await tester.pumpAndSettle(); +} + +Future setExercises(WidgetTester tester) async { + const exercises = ['Push-ups', 'Sit-ups', 'Jumping Jacks']; + for (int i = 0; i < exercises.length; i++) { + print("LOG --- entering text ${exercises[i]}"); + await tester.enterText(find.byKey(Key('exercise-$i')), exercises[i]); + } +} + +Future setTimings(WidgetTester tester, String workTime, String restTime, + bool fullWorkout) async { + await tester.enterText(find.byKey(const Key('work-seconds')), workTime); + await tester.enterText(find.byKey(const Key('rest-seconds')), restTime); + if (fullWorkout) { + await tester.tap(find.byType(ExpansionTile).first); + await tester.pumpAndSettle(); + const timings = { + 'get-ready-seconds': '40', + 'cooldown-seconds': '30', + 'warmup-seconds': '20', + 'iterations': '2', + 'break-seconds': '90' + }; + for (var key in timings.keys) { + if (key != 'break-seconds') { + await tester.enterText(find.byKey(Key(key)), timings[key]!); + } else { + await tester.pump(Duration(seconds: 3)); + await tester.enterText(find.byKey(Key(key)), timings[key]!); + } + } + } +} + +Future createOrEditWorkoutOrTimer( + WidgetTester tester, + String workoutName, + int numIntervals, + bool addExercises, + bool isWorkout, + bool fullWorkout, + String workSound, + String restSound, + String halfwaySound, + String countdownSound, + String endSound, + String workTime, + String restTime) async { + await selectColor(tester); + await tester.enterText( + find.byKey(const Key('interval-input')), numIntervals.toString()); + await tester.tap(find.text('Submit')); + await tester.pumpAndSettle(); + + if (addExercises) await setExercises(tester); + if (isWorkout) { + await tester.tap(find.text('Submit')); + await tester.pumpAndSettle(); + } + + await setTimings(tester, workTime, restTime, fullWorkout); + await tester.tap(find.text('Submit')); + await tester.pumpAndSettle(); + + expect(find.text('Work Sound'), findsOneWidget); + await selectSound(tester, const Key('work-sound'), workSound); + await selectSound(tester, const Key('rest-sound'), restSound); + await selectSound(tester, const Key('halfway-sound'), halfwaySound); + await selectSound(tester, const Key('countdown-sound'), countdownSound); + await selectSound(tester, const Key('end-sound'), endSound); + + await tester.tap(find.text('Submit')); + await tester.pump(Duration(seconds: 1)); + await tester.pumpAndSettle(); + expect(find.text(workoutName), findsOneWidget); +} + +Future checkWorkoutOrTimer( + WidgetTester tester, + String name, + int numIntervals, + bool exercises, + Map times, + Map sounds) async { + await tester.tap(find.byIcon(Icons.edit)); + await tester.pumpAndSettle(); + + expect(find.text(name), findsOneWidget); + expect(find.text(numIntervals.toString()), findsOneWidget); + await tester.tap(find.text('Submit')); + await tester.pumpAndSettle(); + + if (exercises) { + expect(find.text('Push-ups'), findsOneWidget); + expect(find.text('Sit-ups'), findsOneWidget); + expect(find.text('Jumping Jacks'), findsOneWidget); + + await tester.tap(find.text('Submit')); + await tester.pumpAndSettle(); + } + + await tester.tap(find.byType(ExpansionTile).first); + await tester.pumpAndSettle(); + for (var key in times.keys) { + expect(find.text(key.toString()), findsExactly(times[key]!)); + } + await tester.tap(find.text('Submit')); + await tester.pumpAndSettle(); + + for (var key in sounds.keys) { + final workDropdown = find.byKey(Key(key)); + expect(find.descendant(of: workDropdown, matching: find.text(sounds[key]!)), + findsExactly(2)); + } + + await tester.tap(find.text('Submit')); + await tester.pump(Duration(seconds: 1)); + await tester.pumpAndSettle(); + expect(find.text(name), findsOneWidget); +} + +Future editWorkoutOne( + WidgetTester tester, + String workoutName, + int numIntervals, + bool addExercises, + bool isWorkout, + bool fullWorkout, + String workSound, + String restSound, + String halfwaySound, + String countdownSound, + String endSound, + String workTime, + String restTime) async { + await selectColor(tester); + await tester.enterText( + find.byKey(const Key('interval-input')), numIntervals.toString()); + await tester.tap(find.text('Submit')); + await tester.pumpAndSettle(); + + if (isWorkout) { + await tester.tap(find.text('Submit')); + await tester.pumpAndSettle(); + } + + await setTimings(tester, workTime, restTime, fullWorkout); + await tester.tap(find.text('Submit')); + await tester.pumpAndSettle(); + + expect(find.text('Work Sound'), findsOneWidget); + await selectSound(tester, const Key('work-sound'), workSound); + await selectSound(tester, const Key('rest-sound'), restSound); + await selectSound(tester, const Key('halfway-sound'), halfwaySound); + await selectSound(tester, const Key('countdown-sound'), countdownSound); + await selectSound(tester, const Key('end-sound'), endSound); + + await tester.tap(find.text('Submit')); + await tester.pumpAndSettle(); + expect(find.text(workoutName), findsOneWidget); +} + +Future navigateToAddWorkoutOrTimer( + WidgetTester tester, bool isWorkout) async { + await tester.tap(find.byIcon(Icons.add)); + await tester.pumpAndSettle(); + await tester.tap(find.byIcon(isWorkout ? Icons.fitness_center : Icons.timer)); + await tester.pumpAndSettle(); +} + +Future createWorkout(WidgetTester tester, String name) async { + await tester.enterText(find.byKey(const Key('timer-name')), name); + await createOrEditWorkoutOrTimer( + tester, + name, + 3, + true, + true, + false, + "Harsh beep sequence", + "Ding", + "Quick beep sequence", + "Beep", + "Horn", + "10", + "10"); +} + +Future editWorkout(WidgetTester tester, String name) async { + await tester.tap(find.byIcon(Icons.edit)); + await tester.pumpAndSettle(); + await tester.enterText(find.byKey(const Key('timer-name')), name); + await editWorkoutOne(tester, name, 2, true, true, false, + "Harsh beep sequence", "Ding", "None", "Beep", "Horn", "20", "10"); +} + +Future createTimer(WidgetTester tester, String name) async { + await tester.enterText(find.byKey(const Key('timer-name')), name); + await createOrEditWorkoutOrTimer(tester, name, 1, false, false, true, "None", + "None", "None", "None", "None", "10", "10"); +} + +Future verifyWorkoutOrTimerOpens( + WidgetTester tester, String workoutName) async { + await tester.tap(find.text(workoutName)); + await tester.pump(Duration(seconds: 1)); + expect(find.text("Start"), findsOneWidget); +} + +Future takeScreenshot(WidgetTester tester, String fileName) async { + final screenshotFile = File(fileName); + final bytes = await tester.runAsync(() async { + final renderRepaintBoundary = tester + .firstRenderObject(find.byType(RepaintBoundary)); + final image = await renderRepaintBoundary.toImage(); + final byteData = await image.toByteData(format: ImageByteFormat.png); + return byteData?.buffer.asUint8List(); + }); + if (bytes != null) { + await screenshotFile.writeAsBytes(bytes); + } +} + +Future runWorkoutOne( + WidgetTester tester, IntegrationTestWidgetsFlutterBinding binding) async { + await tester.tap(find.text('Start')); + await tester.pumpAndSettle(); + expect(find.textContaining("Get Ready"), findsOneWidget); + + await Future.delayed(const Duration(seconds: 12)); + expect(find.textContaining("1 of 3"), findsOneWidget); + expect(find.textContaining("Push-ups"), findsOneWidget); + + await Future.delayed(const Duration(seconds: 10)); + expect(find.textContaining("1 of 3"), findsNothing); + + await Future.delayed(const Duration(seconds: 10)); + expect(find.textContaining("2 of 3"), findsOneWidget); + expect(find.textContaining("Sit-ups"), findsOneWidget); + + await Future.delayed(const Duration(seconds: 20)); + expect(find.textContaining("3 of 3"), findsOneWidget); + expect(find.textContaining("Jumping Jacks"), findsOneWidget); + + await Future.delayed(const Duration(seconds: 10)); + expect(find.textContaining("Nice"), findsOneWidget); + await tester.tap(find.text("Restart")); + await tester.pumpAndSettle(); + expect(find.textContaining("Get Ready"), findsOneWidget); +} + +Future runWorkoutTwo(WidgetTester tester) async { + await tester.tap(find.text('Start')); + await tester.pumpAndSettle(); + expect(find.textContaining("Get Ready"), findsOneWidget); + + await tester.pump(const Duration(seconds: 10)); + expect(find.textContaining("1 of 2"), findsOneWidget); + expect(find.textContaining("Push-ups"), findsOneWidget); + + await tester.pump(const Duration(seconds: 20)); + expect(find.textContaining("1 of 2"), findsNothing); + + await tester.pump(const Duration(seconds: 12)); + expect(find.textContaining("2 of 2"), findsOneWidget); + expect(find.textContaining("Sit-ups"), findsOneWidget); + + await tester.pump(const Duration(seconds: 20)); + expect(find.textContaining("Nice"), findsOneWidget); + await tester.tap(find.text("Restart")); + await tester.pumpAndSettle(); + expect(find.textContaining("Get Ready"), findsOneWidget); +} + +Future runTimerOne(WidgetTester tester) async { + await tester.tap(find.text('Start')); + await tester.pumpAndSettle(); + expect(find.textContaining("Get Ready"), findsOneWidget); + expect(find.textContaining("Warmup"), findsOneWidget); + + await tester.pump(const Duration(seconds: 50)); + await tester.pump(const Duration(seconds: 12)); + expect(find.textContaining("1 of 1"), findsOneWidget); + expect(find.textContaining("Work"), findsAtLeast(1)); + + await tester.pump(const Duration(seconds: 10)); + expect(find.textContaining("1 of 1"), findsNothing); + expect(find.textContaining("Break"), findsAtLeast(1)); + + await tester.tap(find.byIcon(Icons.arrow_back)); + await tester.pump(Duration(seconds: 1)); + expect(find.text("Start"), findsOneWidget); +} + +Future deleteWorkoutOrTimer(WidgetTester tester, String name) async { + await verifyWorkoutOrTimerOpens(tester, name); + await tester.tap(find.byKey(const Key('Menu'))); + await tester.pump(Duration(seconds: 1)); + await tester.tap(find.byIcon(Icons.delete)); + await tester.pump(Duration(seconds: 1)); + await tester.tap(find.text("Delete")); + await tester.pump(Duration(seconds: 1)); + expect(find.text(name), findsNothing); +} diff --git a/integration_test/functions/general_functions.dart b/integration_test/functions/general_functions.dart new file mode 100644 index 00000000..6777b154 --- /dev/null +++ b/integration_test/functions/general_functions.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +Future tapInfo(WidgetTester tester) async { + await tester.tap(find.byIcon(Icons.info_outline)); + await tester.pumpAndSettle(); + expect(find.text("About OpenHIIT"), findsOneWidget); +} + +Future closeDialog(WidgetTester tester) async { + await tester.tap(find.text("Close")); + await tester.pumpAndSettle(); +} diff --git a/integration_test/general_test.dart b/integration_test/general_test.dart new file mode 100644 index 00000000..19e08be8 --- /dev/null +++ b/integration_test/general_test.dart @@ -0,0 +1,20 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import 'functions/functions.dart'; +import 'functions/general_functions.dart'; + +void main() { + final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive; + + group('end-to-end test', () { + testWidgets('verify app load', (tester) async { + await loadApp(tester); + expect(find.text('No saved timers'), findsOneWidget); + await tapInfo(tester); + await closeDialog(tester); + expect(find.text('About OpenHIIT'), findsNothing); + }); + }); +} diff --git a/integration_test/timer_test.dart b/integration_test/timer_test.dart new file mode 100644 index 00000000..65e4fb95 --- /dev/null +++ b/integration_test/timer_test.dart @@ -0,0 +1,46 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import 'functions/functions.dart'; + +void main() { + final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive; + + const String timerName = 'Test Timer'; + + group('end-to-end test', () { + testWidgets('create a timer', (tester) async { + await loadApp(tester); + await navigateToAddWorkoutOrTimer(tester, false); + await createTimer(tester, timerName); + }); + testWidgets('check timer settings', (tester) async { + await loadApp(tester); + await verifyWorkoutOrTimerOpens(tester, timerName); + await checkWorkoutOrTimer(tester, timerName, 1, false, { + "10": 2, + "40": 1, + "30": 1, + "90": 1, + "20": 1, + "2": 1 + }, { + "work-sound": "None", + "rest-sound": "None", + "halfway-sound": "None", + "countdown-sound": "None", + "end-sound": "None", + }); + }); + testWidgets('run timer and cancel timer', (tester) async { + await loadApp(tester); + await verifyWorkoutOrTimerOpens(tester, timerName); + await runTimerOne(tester); + }); + testWidgets('delete timer', (tester) async { + await loadApp(tester); + await deleteWorkoutOrTimer(tester, timerName); + }); + }); +} diff --git a/integration_test/workout_test.dart b/integration_test/workout_test.dart new file mode 100644 index 00000000..bee22423 --- /dev/null +++ b/integration_test/workout_test.dart @@ -0,0 +1,48 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import 'functions/functions.dart'; + +void main() { + final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive; + + const String workoutName = 'Test Workout'; + const String workoutName2 = 'Test Workout Edited'; + + group('end-to-end test', () { + testWidgets('create a workout', (tester) async { + await loadApp(tester); + await navigateToAddWorkoutOrTimer(tester, true); + await createWorkout(tester, workoutName); + }); + testWidgets('check workout settings', (tester) async { + await loadApp(tester); + await verifyWorkoutOrTimerOpens(tester, workoutName); + await checkWorkoutOrTimer(tester, workoutName, 3, true, { + "10": 3, + }, { + "work-sound": "Harsh beep sequence", + "rest-sound": "Ding", + "halfway-sound": "Quick beep sequence", + "countdown-sound": "Beep", + "end-sound": "Horn", + }); + }); + testWidgets('run a workout and restart', (tester) async { + await loadApp(tester); + await verifyWorkoutOrTimerOpens(tester, workoutName); + await runWorkoutOne(tester, binding); + }); + testWidgets('edit workout', (tester) async { + await loadApp(tester); + await verifyWorkoutOrTimerOpens(tester, workoutName); + await editWorkout(tester, workoutName2); + }); + testWidgets('run an edited workout and restart', (tester) async { + await loadApp(tester); + await verifyWorkoutOrTimerOpens(tester, workoutName2); + await runWorkoutTwo(tester); + }); + }); +} diff --git a/ios/Podfile.lock b/ios/Podfile.lock index d279396c..4599da93 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -47,7 +47,7 @@ PODS: - fluttertoast (0.0.2): - Flutter - Toast - - just_audio (0.0.1): + - integration_test (0.0.1): - Flutter - package_info_plus (0.4.5): - Flutter @@ -85,7 +85,7 @@ DEPENDENCIES: - flutter_background_service_ios (from `.symlinks/plugins/flutter_background_service_ios/ios`) - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) - - just_audio (from `.symlinks/plugins/just_audio/ios`) + - integration_test (from `.symlinks/plugins/integration_test/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) @@ -121,8 +121,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/flutter_local_notifications/ios" fluttertoast: :path: ".symlinks/plugins/fluttertoast/ios" - just_audio: - :path: ".symlinks/plugins/just_audio/ios" + integration_test: + :path: ".symlinks/plugins/integration_test/ios" package_info_plus: :path: ".symlinks/plugins/package_info_plus/ios" path_provider_foundation: @@ -153,8 +153,8 @@ SPEC CHECKSUMS: flutter_background_service_ios: e30e0d3ee69e4cee66272d0c78eacd48c2e94aac flutter_local_notifications: df98d66e515e1ca797af436137b4459b160ad8c9 fluttertoast: e9a18c7be5413da53898f660530c56f35edfba9c - just_audio: baa7252489dbcf47a4c7cc9ca663e9661c99aafa - package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 + integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573 + package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 SDWebImage: dfe95b2466a9823cf9f0c6d01217c06550d7b29a @@ -165,7 +165,7 @@ SPEC CHECKSUMS: SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 Toast: 1f5ea13423a1e6674c4abdac5be53587ae481c4e url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe - wakelock_plus: 78ec7c5b202cab7761af8e2b2b3d0671be6c4ae1 + wakelock_plus: 373cfe59b235a6dd5837d0fb88791d2f13a90d56 PODFILE CHECKSUM: a74b8704f768957a23e2d804b55390ecc9fffc9d diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index ea612e71..d7501b55 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -379,7 +379,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Distribution"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = ""; @@ -393,7 +393,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.codepup.workoutTimer; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "OpenHIIT December 2024"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "GitHub AppStore"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; @@ -515,7 +515,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Distribution"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = ""; @@ -529,7 +529,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.codepup.workoutTimer; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "OpenHIIT December 2024"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "GitHub AppStore"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -545,7 +545,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Distribution"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = ""; @@ -592,4 +592,4 @@ /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; -} \ No newline at end of file +} diff --git a/lib/pages/active_timer/widgets/control_bar.dart b/lib/pages/active_timer/widgets/control_bar.dart index 661ade6c..3b0f7ad3 100644 --- a/lib/pages/active_timer/widgets/control_bar.dart +++ b/lib/pages/active_timer/widgets/control_bar.dart @@ -64,6 +64,7 @@ class ControlBarState extends State { children: [ IconButton( icon: Icon( + key: const Key('volume'), size: MediaQuery.of(context).size.width > 600 ? 50 : 35, widget.changeVolume ? Icons.close : Icons.volume_up, color: Colors.white, @@ -71,6 +72,7 @@ class ControlBarState extends State { onPressed: widget.onAdjustVolume, ), IconButton( + key: const Key('skip_previous'), tooltip: 'Skip Previous', icon: Icon(Icons.skip_previous, size: MediaQuery.of(context).size.width > 600 ? 45 : 30, @@ -78,6 +80,7 @@ class ControlBarState extends State { onPressed: widget.onSkipPrevious, ), IconButton( + key: const Key('play_pause'), tooltip: 'Pause', icon: Icon( size: MediaQuery.of(context).size.width > 600 ? 65 : 55, @@ -87,6 +90,7 @@ class ControlBarState extends State { onPressed: widget.onTogglePlayPause, ), IconButton( + key: const Key('skip_next'), tooltip: 'Skip Next', icon: Icon(Icons.skip_next, size: MediaQuery.of(context).size.width > 600 ? 45 : 30, @@ -94,6 +98,7 @@ class ControlBarState extends State { onPressed: widget.onSkipNext, ), IconButton( + key: const Key('restart'), tooltip: 'Restart', icon: Icon(Icons.restart_alt, size: MediaQuery.of(context).size.width > 600 ? 50 : 35, diff --git a/lib/pages/active_timer/widgets/landscape_control_bar.dart b/lib/pages/active_timer/widgets/landscape_control_bar.dart index 45bd9a3f..6eaf80c8 100644 --- a/lib/pages/active_timer/widgets/landscape_control_bar.dart +++ b/lib/pages/active_timer/widgets/landscape_control_bar.dart @@ -64,6 +64,7 @@ class LandscapeControlBarState extends State { children: [ IconButton( icon: Icon( + key: const Key('volume'), size: MediaQuery.of(context).size.width > 600 ? 50 : 35, widget.changeVolume ? Icons.close : Icons.volume_up, color: Colors.white, @@ -71,6 +72,7 @@ class LandscapeControlBarState extends State { onPressed: widget.onAdjustVolume, ), IconButton( + key: const Key('skip_previous'), tooltip: 'Skip Previous', icon: Icon(Icons.skip_previous, size: MediaQuery.of(context).size.width > 600 ? 45 : 30, @@ -78,6 +80,7 @@ class LandscapeControlBarState extends State { onPressed: widget.onSkipPrevious, ), IconButton( + key: const Key('play_pause'), tooltip: 'Pause', icon: Icon( size: MediaQuery.of(context).size.width > 600 ? 60 : 45, @@ -87,6 +90,7 @@ class LandscapeControlBarState extends State { onPressed: widget.onTogglePlayPause, ), IconButton( + key: const Key('skip_next'), tooltip: 'Skip Next', icon: Icon(Icons.skip_next, size: MediaQuery.of(context).size.width > 600 ? 45 : 30, @@ -94,6 +98,7 @@ class LandscapeControlBarState extends State { onPressed: widget.onSkipNext, ), IconButton( + key: const Key('restart'), tooltip: 'Restart', icon: Icon(Icons.restart_alt, size: MediaQuery.of(context).size.width > 600 ? 50 : 35, diff --git a/lib/pages/active_timer/widgets/timer_complete.dart b/lib/pages/active_timer/widgets/timer_complete.dart index 79ca6f1a..67d2ec6d 100644 --- a/lib/pages/active_timer/widgets/timer_complete.dart +++ b/lib/pages/active_timer/widgets/timer_complete.dart @@ -99,6 +99,7 @@ class TimerCompleteState extends State { height: 40, ), FilledButton.icon( + key: const Key('back'), onPressed: () { Navigator.pop(context); }, @@ -113,6 +114,7 @@ class TimerCompleteState extends State { height: 10, ), FilledButton.icon( + key: const Key('restart'), onPressed: widget.onRestart, style: ButtonStyle( backgroundColor: WidgetStatePropertyAll( diff --git a/lib/pages/create_timer/create_timer.dart b/lib/pages/create_timer/create_timer.dart index 9d07c059..7bf99494 100644 --- a/lib/pages/create_timer/create_timer.dart +++ b/lib/pages/create_timer/create_timer.dart @@ -20,19 +20,6 @@ class CreateTimerState extends State { Widget build(BuildContext context) { final formKey = GlobalKey(); - /// Push to the SetTimings page. - /// - // void pushTimings(TimerType timer) { - // setState(() { - // Navigator.push( - // context, - // MaterialPageRoute( - // builder: (context) => const SetTimings(timer: timer), - // ), - // ); - // }); - // } - /// Submit and form, save the workout values, and move /// to the next view. /// @@ -65,19 +52,5 @@ class CreateTimerState extends State { }, ), body: CreateForm(timer: widget.timer, formKey: formKey)); - - // return Scaffold( - // appBar: AppBar( - // title: const Text("New Interval Timer"), - // ), - // bottomSheet: SubmitButton( - // text: "Submit", - // color: const Color.fromARGB(255, 58, 165, 255), - // onTap: () { - // submitForm(widget.timer, snapshot.data); - // }, - // ), - // body: CreateForm(timer: widget.timer, formKey: formKey)); - // body: CreateForm(timer: widget.timer, formKey: formKey)); } } diff --git a/lib/pages/create_workout/create_workout.dart b/lib/pages/create_workout/create_workout.dart deleted file mode 100644 index 631a4c5d..00000000 --- a/lib/pages/create_workout/create_workout.dart +++ /dev/null @@ -1,77 +0,0 @@ -// import 'package:flutter/material.dart'; -// import 'package:openhiit/utils/log/log.dart'; -// import 'package:openhiit/widgets/form_widgets/create_form.dart'; -// import '../../data/workout_type.dart'; -// import '../../widgets/form_widgets/submit_button.dart'; -// import '../set_exercises/set_exercises.dart'; - -// class CreateWorkout extends StatefulWidget { -// const CreateWorkout({super.key}); - -// @override -// CreateWorkoutState createState() => CreateWorkoutState(); -// } - -// class CreateWorkoutState extends State { -// @override -// Widget build(BuildContext context) { -// /// Grab the [workout] that was passed to this view -// /// from the previous view. -// /// -// Workout workout = ModalRoute.of(context)!.settings.arguments as Workout; - -// /// Create a global key that uniquely identifies the Form widget -// /// and allows validation of the form. -// /// -// /// Note: This is a `GlobalKey`, -// /// not a GlobalKey. -// /// -// final formKey = GlobalKey(); - -// /// Push to the SetExercises page. -// /// -// void pushExercises(workout) { -// Navigator.push( -// context, -// MaterialPageRoute( -// builder: (context) => const SetExercises(), -// settings: RouteSettings( -// arguments: workout, -// ), -// ), -// ); -// } - -// /// Submit and form, save the workout values, and move -// /// to the next view. -// /// -// void submitForm(Workout workout) { -// // Validate returns true if the form is valid, or false otherwise. -// final form = formKey.currentState!; -// if (form.validate()) { -// form.save(); - -// logger.i( -// "Title: ${workout.title}, Color: ${workout.colorInt}, Intervals: ${workout.numExercises}"); - -// pushExercises(workout); -// } -// } -// // --- - -// return Container(); - -// // return Scaffold( -// // appBar: AppBar( -// // title: const Text("New Workout"), -// // ), -// // bottomSheet: SubmitButton( -// // text: "Submit", -// // color: Colors.blue, -// // onTap: () { -// // submitForm(workout); -// // }, -// // ), -// // body: CreateForm(timer: timer, formKey: formKey)); -// } -// } diff --git a/lib/pages/home/home.dart b/lib/pages/home/home.dart index 2699b36b..9b0dc241 100644 --- a/lib/pages/home/home.dart +++ b/lib/pages/home/home.dart @@ -5,7 +5,7 @@ import 'package:flutter/services.dart'; import 'package:openhiit/constants/snackbars.dart'; import 'package:openhiit/data/timer_type.dart'; import 'package:openhiit/pages/select_timer/select_timer.dart'; -import 'package:openhiit/pages/view_workout/view_timer.dart'; +import 'package:openhiit/pages/view_timer/view_timer.dart'; import 'package:openhiit/pages/home/widgets/fab_column.dart'; import 'package:openhiit/providers/workout_provider.dart'; import 'package:openhiit/utils/database/database_manager.dart'; diff --git a/lib/pages/set_sounds/widgets/sound_dropdown.dart b/lib/pages/set_sounds/widgets/sound_dropdown.dart index e3deca98..410d2189 100644 --- a/lib/pages/set_sounds/widgets/sound_dropdown.dart +++ b/lib/pages/set_sounds/widgets/sound_dropdown.dart @@ -55,6 +55,11 @@ class SoundDropdownState extends State @override Widget build(BuildContext context) { + String initialSelection; + widget.initialSelection == "" + ? initialSelection = "none" + : initialSelection = widget.initialSelection; + return SizedBox( height: 110, child: Column( @@ -73,7 +78,7 @@ class SoundDropdownState extends State DropdownMenu( key: widget.dropdownKey, width: 240, - initialSelection: widget.initialSelection, + initialSelection: initialSelection, onSelected: (String? value) { // This is called when the user selects an item. widget.onFinished!(value); diff --git a/lib/pages/view_workout/view_timer.dart b/lib/pages/view_timer/view_timer.dart similarity index 98% rename from lib/pages/view_workout/view_timer.dart rename to lib/pages/view_timer/view_timer.dart index f2054e94..edf46513 100644 --- a/lib/pages/view_workout/view_timer.dart +++ b/lib/pages/view_timer/view_timer.dart @@ -7,8 +7,8 @@ import 'package:openhiit/models/lists/timer_list_tile_model.dart'; import 'package:openhiit/pages/active_timer/workout.dart'; import 'package:openhiit/pages/create_timer/create_timer.dart'; import 'package:openhiit/pages/home/home.dart'; -import 'package:openhiit/pages/view_workout/widgets/start_button.dart'; -import 'package:openhiit/pages/view_workout/widgets/view_workout_appbar.dart'; +import 'package:openhiit/pages/view_timer/widgets/start_button.dart'; +import 'package:openhiit/pages/view_timer/widgets/view_timer_appbar.dart'; import 'package:openhiit/providers/workout_provider.dart'; import 'package:openhiit/utils/database/database_manager.dart'; import 'package:openhiit/utils/functions.dart'; @@ -92,7 +92,7 @@ class ViewTimerState extends State { } }, )), - appBar: ViewWorkoutAppBar( + appBar: ViewTimerAppbar( timer: widget.timer, height: MediaQuery.of(context).orientation == Orientation.portrait diff --git a/lib/pages/view_workout/widgets/start_button.dart b/lib/pages/view_timer/widgets/start_button.dart similarity index 100% rename from lib/pages/view_workout/widgets/start_button.dart rename to lib/pages/view_timer/widgets/start_button.dart diff --git a/lib/pages/view_workout/widgets/view_workout_appbar.dart b/lib/pages/view_timer/widgets/view_timer_appbar.dart similarity index 95% rename from lib/pages/view_workout/widgets/view_workout_appbar.dart rename to lib/pages/view_timer/widgets/view_timer_appbar.dart index 6ba46bce..2395683c 100644 --- a/lib/pages/view_workout/widgets/view_workout_appbar.dart +++ b/lib/pages/view_timer/widgets/view_timer_appbar.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:openhiit/data/timer_type.dart'; import 'package:openhiit/widgets/home/export_bottom_sheet.dart'; -class ViewWorkoutAppBar extends StatelessWidget implements PreferredSizeWidget { +class ViewTimerAppbar extends StatelessWidget implements PreferredSizeWidget { /// Called on delete button tap. /// final VoidCallback? onDelete; @@ -24,7 +24,7 @@ class ViewWorkoutAppBar extends StatelessWidget implements PreferredSizeWidget { /// final double height; - const ViewWorkoutAppBar( + const ViewTimerAppbar( {super.key, required this.onDelete, required this.onEdit, @@ -113,12 +113,12 @@ class ViewWorkoutAppBar extends StatelessWidget implements PreferredSizeWidget { actions: [ // Defines the edit button. IconButton( - key: const Key("edit-workout"), + key: const Key("Edit"), icon: const Icon(Icons.edit, color: Colors.white), onPressed: onEdit, ), PopupMenuButton( - key: const Key("popup-menu"), + key: const Key("Menu"), onSelected: (String item) { handleMenuSelection(item, context); }, diff --git a/pubspec.lock b/pubspec.lock index e61a55b3..1a20ab50 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,18 +5,18 @@ packages: dependency: transitive description: name: archive - sha256: "6199c74e3db4fbfbd04f66d739e72fe11c8a8957d5f219f1f4482dbde6420b5a" + sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "3.6.1" args: dependency: transitive description: name: args - sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" url: "https://pub.dev" source: hosted - version: "2.6.0" + version: "2.5.0" async: dependency: transitive description: @@ -29,10 +29,10 @@ packages: dependency: "direct main" description: name: audio_session - sha256: b2a26ba8b7efa1790d6460e82971fde3e398cfbe2295df9dea22f3499d2c12a7 + sha256: "343e83bc7809fbda2591a49e525d6b63213ade10c76f15813be9aed6657b3261" url: "https://pub.dev" source: hosted - version: "0.1.23" + version: "0.1.21" auto_size_text: dependency: "direct main" description: @@ -77,10 +77,10 @@ packages: dependency: transitive description: name: cli_util - sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c + sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19 url: "https://pub.dev" source: hosted - version: "0.4.2" + version: "0.4.1" clock: dependency: transitive description: @@ -105,14 +105,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.8.0" - coverage: - dependency: "direct main" - description: - name: coverage - sha256: e3493833ea012784c740e341952298f1cc77f1f01b1bbc3eb4eecf6984fb7f43 - url: "https://pub.dev" - source: hosted - version: "1.11.1" cross_file: dependency: transitive description: @@ -125,10 +117,10 @@ packages: dependency: transitive description: name: crypto - sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab url: "https://pub.dev" source: hosted - version: "3.0.6" + version: "3.0.3" cupertino_icons: dependency: "direct main" description: @@ -173,12 +165,12 @@ packages: dependency: transitive description: name: dio_web_adapter - sha256: "33259a9276d6cea88774a0000cfae0d861003497755969c92faa223108620dc8" + sha256: "36c5b2d79eb17cdae41e974b7a8284fec631651d2a6f39a8a2ff22327e90aeac" url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "1.0.1" fake_async: - dependency: transitive + dependency: "direct main" description: name: fake_async sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" @@ -189,18 +181,18 @@ packages: dependency: transitive description: name: ffi - sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.2" file: dependency: transitive description: name: file - sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" url: "https://pub.dev" source: hosted - version: "7.0.1" + version: "7.0.0" file_picker: dependency: "direct main" description: @@ -221,10 +213,10 @@ packages: dependency: transitive description: name: fixnum - sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.0" flutter: dependency: "direct main" description: flutter @@ -262,6 +254,11 @@ packages: url: "https://pub.dev" source: hosted version: "5.1.2" + flutter_driver: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" flutter_launcher_icons: dependency: "direct main" description: @@ -314,10 +311,10 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "615a505aef59b151b46bbeef55b36ce2b6ed299d160c51d84281946f0aa0ce0e" + sha256: "9d98bd47ef9d34e803d438f17fd32b116d31009f534a6fa5ce3a1167f189a6de" url: "https://pub.dev" source: hosted - version: "2.0.24" + version: "2.0.21" flutter_test: dependency: "direct dev" description: flutter @@ -336,14 +333,11 @@ packages: url: "https://pub.dev" source: hosted version: "8.2.10" - glob: + fuchsia_remote_debug_protocol: dependency: transitive - description: - name: glob - sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" - url: "https://pub.dev" - source: hosted - version: "2.1.2" + description: flutter + source: sdk + version: "0.0.0" google_fonts: dependency: "direct main" description: @@ -364,26 +358,23 @@ packages: dependency: transitive description: name: http_parser - sha256: "76d306a1c3afb33fe82e2bbacad62a61f409b5634c915fceb0d799de1a913360" + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" url: "https://pub.dev" source: hosted - version: "4.1.1" + version: "4.0.2" image: dependency: transitive description: name: image - sha256: "8346ad4b5173924b5ddddab782fc7d8a6300178c8b1dc427775405a01701c4a6" - url: "https://pub.dev" - source: hosted - version: "4.5.2" - infinite_listview: - dependency: transitive - description: - name: infinite_listview - sha256: f6062c1720eb59be553dfa6b89813d3e8dd2f054538445aaa5edaddfa5195ce6 + sha256: "2237616a36c0d69aef7549ab439b833fb7f9fb9fc861af2cc9ac3eedddd69ca8" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "4.2.0" + integration_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" json_annotation: dependency: transitive description: @@ -392,30 +383,6 @@ packages: url: "https://pub.dev" source: hosted version: "4.9.0" - just_audio: - dependency: "direct main" - description: - name: just_audio - sha256: a49e7120b95600bd357f37a2bb04cd1e88252f7cdea8f3368803779b925b1049 - url: "https://pub.dev" - source: hosted - version: "0.9.42" - just_audio_platform_interface: - dependency: transitive - description: - name: just_audio_platform_interface - sha256: "0243828cce503c8366cc2090cefb2b3c871aa8ed2f520670d76fd47aa1ab2790" - url: "https://pub.dev" - source: hosted - version: "4.3.0" - just_audio_web: - dependency: transitive - description: - name: just_audio_web - sha256: "9a98035b8b24b40749507687520ec5ab404e291d2b0937823ff45d92cb18d448" - url: "https://pub.dev" - source: hosted - version: "0.4.13" leak_tracker: dependency: transitive description: @@ -452,18 +419,10 @@ packages: dependency: "direct main" description: name: logger - sha256: be4b23575aac7ebf01f225a241eb7f6b5641eeaf43c6a8613510fc2f8cf187d1 + sha256: "697d067c60c20999686a0add96cf6aba723b3aa1f83ecf806a8097231529ec32" url: "https://pub.dev" source: hosted - version: "2.5.0" - logging: - dependency: transitive - description: - name: logging - sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 - url: "https://pub.dev" - source: hosted - version: "1.3.0" + version: "2.4.0" matcher: dependency: transitive description: @@ -492,10 +451,10 @@ packages: dependency: transitive description: name: mime - sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "1.0.5" nested: dependency: transitive description: @@ -504,38 +463,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" - numberpicker: - dependency: "direct main" - description: - name: numberpicker - sha256: "4c129154944b0f6b133e693f8749c3f8bfb67c4d07ef9dcab48b595c22d1f156" - url: "https://pub.dev" - source: hosted - version: "2.1.2" - package_config: - dependency: transitive - description: - name: package_config - sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67" - url: "https://pub.dev" - source: hosted - version: "2.1.1" package_info_plus: dependency: transitive description: name: package_info_plus - sha256: "70c421fe9d9cc1a9a7f3b05ae56befd469fe4f8daa3b484823141a55442d858d" + sha256: a75164ade98cb7d24cfd0a13c6408927c6b217fa60dee5a7ff5c116a58f28918 url: "https://pub.dev" source: hosted - version: "8.1.2" + version: "8.0.2" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: a5ef9986efc7bf772f2696183a3992615baa76c1ffb1189318dd8803778fb05b + sha256: ac1f4a4847f1ade8e6a87d1f39f5d7c67490738642e2542f559ec38c37489a66 url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.1" path: dependency: "direct main" description: @@ -548,26 +491,26 @@ packages: dependency: "direct main" description: name: path_provider - sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378 url: "https://pub.dev" source: hosted - version: "2.1.5" + version: "2.1.4" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" + sha256: "490539678396d4c3c0b06efdaab75ae60675c3e0c66f72bc04c2e2c1e0e2abeb" url: "https://pub.dev" source: hosted - version: "2.2.15" + version: "2.2.9" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.0" path_provider_linux: dependency: transitive description: @@ -604,10 +547,10 @@ packages: dependency: transitive description: name: permission_handler_android - sha256: "71bbecfee799e65aff7c744761a57e817e73b738fedf62ab7afd5593da21f9f1" + sha256: eaf2a1ec4472775451e88ca6a7b86559ef2f1d1ed903942ed135e38ea0097dca url: "https://pub.dev" source: hosted - version: "12.0.13" + version: "12.0.8" permission_handler_apple: dependency: transitive description: @@ -628,10 +571,10 @@ packages: dependency: transitive description: name: permission_handler_platform_interface - sha256: e9c8eadee926c4532d0305dff94b85bf961f16759c3af791486613152af4b4f9 + sha256: fe0ffe274d665be8e34f9c59705441a7d248edebbe5d9e3ec2665f88b79358ea url: "https://pub.dev" source: hosted - version: "4.2.3" + version: "4.2.2" permission_handler_windows: dependency: transitive description: @@ -652,10 +595,10 @@ packages: dependency: transitive description: name: platform - sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" url: "https://pub.dev" source: hosted - version: "3.1.6" + version: "3.1.5" plugin_platform_interface: dependency: transitive description: @@ -664,14 +607,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" - posix: + process: dependency: transitive description: - name: posix - sha256: a0117dc2167805aa9125b82eee515cc891819bac2f538c83646d355b16f58b9a + name: process + sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32" url: "https://pub.dev" source: hosted - version: "6.0.1" + version: "5.0.2" provider: dependency: "direct main" description: @@ -708,26 +651,26 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: "3c7e73920c694a436afaf65ab60ce3453d91f84208d761fbd83fc21182134d93" + sha256: c272f9cabca5a81adc9b0894381e9c1def363e980f960fa903c604c471b22f68 url: "https://pub.dev" source: hosted - version: "2.3.4" + version: "2.3.1" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: "02a7d8a9ef346c9af715811b01fbd8e27845ad2c41148eefd31321471b41863d" + sha256: a7e8467e9181cef109f601e3f65765685786c1a738a83d7fbbde377589c0d974 url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.3.1" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" + sha256: "776786cff96324851b656777648f36ac772d88bc4c669acff97b7fce5de3c849" url: "https://pub.dev" source: hosted - version: "2.5.4" + version: "2.5.1" shared_preferences_linux: dependency: transitive description: @@ -805,14 +748,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.0" - source_maps: - dependency: transitive - description: - name: source_maps - sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" - url: "https://pub.dev" - source: hosted - version: "0.10.13" source_span: dependency: transitive description: @@ -909,14 +844,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" + sync_http: + dependency: transitive + description: + name: sync_http + sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961" + url: "https://pub.dev" + source: hosted + version: "0.3.1" synchronized: dependency: transitive description: name: synchronized - sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225" + sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558" url: "https://pub.dev" source: hosted - version: "3.3.0+3" + version: "3.1.0+1" term_glyph: dependency: transitive description: @@ -945,58 +888,58 @@ packages: dependency: transitive description: name: timezone - sha256: ffc9d5f4d1193534ef051f9254063fa53d588609418c84299956c3db9383587d + sha256: "2236ec079a174ce07434e89fcd3fcda430025eb7692244139a9cf54fdcf1fc7d" url: "https://pub.dev" source: hosted - version: "0.10.0" + version: "0.9.4" typed_data: dependency: transitive description: name: typed_data - sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.3.2" url_launcher: dependency: "direct main" description: name: url_launcher - sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" + sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3" url: "https://pub.dev" source: hosted - version: "6.3.1" + version: "6.3.0" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: "6fc2f56536ee873eeb867ad176ae15f304ccccc357848b351f6f0d8d4a40d193" + sha256: e35a698ac302dd68e41f73250bd9517fe3ab5fa4f18fe4647a0872db61bacbab url: "https://pub.dev" source: hosted - version: "6.3.14" + version: "6.3.10" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626" + sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e url: "https://pub.dev" source: hosted - version: "6.3.2" + version: "6.3.1" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" + sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.2.0" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" + sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de" url: "https://pub.dev" source: hosted - version: "3.2.2" + version: "3.2.0" url_launcher_platform_interface: dependency: transitive description: @@ -1017,18 +960,18 @@ packages: dependency: transitive description: name: url_launcher_windows - sha256: "44cf3aabcedde30f2dba119a9dea3b0f2672fbe6fa96e85536251d678216b3c4" + sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185" url: "https://pub.dev" source: hosted - version: "3.1.3" + version: "3.1.2" uuid: dependency: "direct main" description: name: uuid - sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff + sha256: "83d37c7ad7aaf9aa8e275490669535c8080377cfa7a7004c24dfac53afffaa90" url: "https://pub.dev" source: hosted - version: "4.5.1" + version: "4.4.2" vector_math: dependency: transitive description: @@ -1069,30 +1012,38 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + webdriver: + dependency: transitive + description: + name: webdriver + sha256: "3d773670966f02a646319410766d3b5e1037efb7f07cc68f844d5e06cd4d61c8" + url: "https://pub.dev" + source: hosted + version: "3.0.4" win32: dependency: transitive description: name: win32 - sha256: "8b338d4486ab3fbc0ba0db9f9b4f5239b6697fcee427939a40e720cbb9ee0a69" + sha256: "015002c060f1ae9f41a818f2d5640389cc05283e368be19dc8d77cecb43c40c9" url: "https://pub.dev" source: hosted - version: "5.9.0" + version: "5.5.3" win32_registry: dependency: transitive description: name: win32_registry - sha256: "21ec76dfc731550fd3e2ce7a33a9ea90b828fdf19a5c3bcf556fa992cfa99852" + sha256: "723b7f851e5724c55409bb3d5a32b203b3afe8587eaf5dafb93a5fed8ecda0d6" url: "https://pub.dev" source: hosted - version: "1.1.5" + version: "1.1.4" xdg_directories: dependency: transitive description: name: xdg_directories - sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.0.4" xml: dependency: transitive description: @@ -1105,10 +1056,10 @@ packages: dependency: transitive description: name: yaml - sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" url: "https://pub.dev" source: hosted - version: "3.1.3" + version: "3.1.2" sdks: dart: ">=3.6.0 <4.0.0" flutter: ">=3.24.0" diff --git a/pubspec.yaml b/pubspec.yaml index 6706748a..430a11b7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -37,7 +37,6 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 - numberpicker: ^2.1.2 sqflite: ^2.2.8+2 uuid: ^4.2.2 timer_count_down: ^2.2.2 @@ -45,11 +44,8 @@ dependencies: sqflite_common_ffi: ^2.2.5 wakelock_plus: ^1.0.0 flutter_launcher_icons: ^0.14.1 - coverage: ^1.6.3 flutter_material_color_picker: ^1.2.0 - background_hiit_timer: 1.0.4 - just_audio: ^0.9.35 soundpool: ^2.4.1 auto_size_text: ^3.0.0 google_fonts: ^6.2.1 @@ -65,6 +61,7 @@ dependencies: url_launcher: ^6.3.0 provider: ^6.1.2 audio_session: ^0.1.21 + fake_async: ^1.3.1 flutter_launcher_icons: android: "launcher_icon" @@ -90,6 +87,8 @@ dev_dependencies: # package. See that file for information about deactivating specific lint # rules and activating additional ones. flutter_lints: ^5.0.0 + integration_test: + sdk: flutter # background_timer: # git: @@ -110,34 +109,3 @@ flutter: # included with your application, so that you can use the icons in # the material Icons class. uses-material-design: true - - # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware - - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/assets-and-images/#from-packages - - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/custom-fonts/#from-packages diff --git a/test/functions.dart b/test/functions.dart deleted file mode 100644 index f5c06c48..00000000 --- a/test/functions.dart +++ /dev/null @@ -1,178 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -Future selectSound(WidgetTester tester, Key key, String soundName) async { - await tester.tap(find.byKey(key)); - await tester.pump(const Duration(seconds: 1)); - await tester.tap(find - .descendant( - of: find.byKey(key), - matching: find.text(soundName), - ) - .last); - await tester.pumpAndSettle(); -} - -Future createOrEditWorkout( - WidgetTester tester, - String workoutName, - int numIntervals, - bool addExercises, - bool isWorkout, - bool fullWorkout, - String workSound, - String restSound, - String halfwaySound, - String countdownSound, - String endSound, - String workTime, - String restTime) async { - // Tap the color picker - await tester.tap(find.byKey(const Key('color-picker'))); - await tester.pumpAndSettle(); // Wait for the dialog to appear - - // In the color picker dialog, select a color - await tester.tap(find.text('Select')); - await tester.pumpAndSettle(); // Wait for the dialog to close - - // Enter the number of intervals - await tester.enterText( - find.byKey(const Key('interval-input')), numIntervals.toString()); - - // Tap the Timer Display option (assuming there are two options) - // await tester.tap(find.text('Timer display:').last); - // await tester.pumpAndSettle(); - - // Submit the form - await tester.tap(find.text( - 'Submit')); // Replace 'Submit' with the actual text of your submit button - await tester.pump(const Duration(seconds: 1)); - await tester.pumpAndSettle(); - - /// - /// SET EXERCISES - /// - - if (addExercises) { - // Enter exercise names - await tester.enterText(find.byKey(const Key('exercise-0')), 'Push-ups'); - await tester.enterText(find.byKey(const Key('exercise-1')), 'Sit-ups'); - await tester.enterText( - find.byKey(const Key('exercise-2')), 'Jumping Jacks'); - } - - if (isWorkout) { - // Tap the Submit button - await tester.tap(find.text('Submit')); - - // Wait for the navigation to complete - await tester.pump(const Duration(seconds: 1)); - await tester.pumpAndSettle(); - } - - /// - /// SET TIMINGS - /// - - // Verify that the setTimings view has loaded - expect(find.byKey(const Key('work-seconds')), findsOneWidget); - - // Enter work time - await tester.enterText(find.byKey(const Key('work-seconds')), workTime); - - // Enter rest time - await tester.enterText(find.byKey(const Key('rest-seconds')), restTime); - - if (fullWorkout) { - // Set additional timings - String getReadyTime = '40'; - String coolDownTime = '30'; - String warmupTime = '20'; - String breakTime = '90'; - String restart = '2'; - - // Tap the Open Expansion Tile button - await tester.tap(find.byType(ExpansionTile).first); - - for (int i = 0; i < 5; i++) { - await tester.pump(const Duration(seconds: 1)); - } - - await tester.pumpAndSettle(); - - // Enter get ready time - await tester.enterText( - find.byKey(const Key('get-ready-seconds')), - getReadyTime, - ); - - // Enter cool down time - await tester.enterText( - find.byKey(const Key('cooldown-seconds')), - coolDownTime, - ); - - // Enter warmup time - await tester.enterText( - find.byKey(const Key('warmup-seconds')), - warmupTime, - ); - - // Enter restart amount - await tester.enterText( - find.byKey(const Key('iterations')), - restart, - ); - - for (int i = 0; i < 5; i++) { - await tester.pump(const Duration(seconds: 1)); - } - - // Enter break time - await tester.enterText( - find.byKey(const Key('break-seconds')), - breakTime, - ); - } - - // Tap the Submit button - await tester.tap(find.text('Submit')); - - // Wait for the navigation to complete - await tester.pumpAndSettle(); - - /// - /// SET SOUNDS - /// - - // Verify that the SetSounds screen is navigated to - expect(find.text('Work Sound'), findsOneWidget); - - // Select work sound - await selectSound(tester, const Key('work-sound'), workSound); - - // Select rest sound - await selectSound(tester, const Key('rest-sound'), restSound); - - // Select halfway sound - await selectSound(tester, const Key('halfway-sound'), halfwaySound); - - // Select countdown sound - await selectSound(tester, const Key('countdown-sound'), countdownSound); - - // Select end sound - await selectSound(tester, const Key('end-sound'), endSound); - - // Tap the Submit button - await tester.tap(find.text('Submit')); - - // Wait for the navigation to complete - await tester.pumpAndSettle(); - - /// - /// MAIN PAGE - /// - - // Verify that the workout was submitted successfully - expect(find.text(workoutName), findsOneWidget); -} diff --git a/test/interval_timer_all_settings_test.dart b/test/interval_timer_all_settings_test.dart deleted file mode 100644 index 8578d39f..00000000 --- a/test/interval_timer_all_settings_test.dart +++ /dev/null @@ -1,89 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:openhiit/main.dart'; - -import 'functions.dart'; - -const double portraitWidth = 1242.0; -const double portraitHeight = 2208.0; -const double landscapeWidth = portraitHeight; -const double landscapeHeight = portraitWidth; - -void main() { - testWidgets('Test CreateInterval', (WidgetTester tester) async { - // Set additional timings - String getReadyTime = '40'; - String coolDownTime = '30'; - String warmupTime = '20'; - String breakTime = '90'; - - final TestWidgetsFlutterBinding binding = - TestWidgetsFlutterBinding.ensureInitialized(); - - await binding.setSurfaceSize(const Size(portraitWidth, portraitHeight)); - - String timerName = "Test interval timer 1"; - - // Build our app and trigger a frame. - await tester.pumpWidget(const WorkoutTimer()); - - // Tap the '+' icon and trigger the add Workout or Timer page. - await tester.tap(find.byIcon(Icons.add)); - await tester.pumpAndSettle(); - - // Verify that the next page has loaded. - expect(find.text('Interval Timer'), findsOneWidget); - expect(find.text('Workout'), findsOneWidget); - - /// - /// CREATE FORM - /// - - // Tap to add a Workout. - await tester.tap(find.byIcon(Icons.timer)); - await tester.pumpAndSettle(); - - // Verify that the next page has loaded. - expect(find.text('Enter a name:'), findsOneWidget); - - // Enter a name - await tester.enterText(find.byKey(const Key('timer-name')), timerName); - - await createOrEditWorkout( - tester, - timerName, - 3, - false, - false, - true, - "Harsh beep sequence", - "Ding", - "Quick beep sequence", - "Beep", - "Horn", - "60", - "30"); - - // Tap the workout to view details - await tester.tap(find.text(timerName)); - - await tester.pump(); // allow the application to handle - - await tester.pump(const Duration(seconds: 1)); // skip past the animation - - // Verify the ViewWorkout page has loaded - expect(find.text("Start"), findsOneWidget); - - // Verify the get ready time is correct - expect(find.textContaining(getReadyTime), findsOneWidget); - - // Verify the warm down time is correct - expect(find.textContaining(warmupTime), findsAtLeast(1)); - - // Verify the cool down time is correct - expect(find.textContaining(coolDownTime), findsAtLeast(1)); - - // Verify the break time is correct - expect(find.textContaining(breakTime), findsAtLeast(1)); - }); -} diff --git a/test/interval_timer_test.dart b/test/interval_timer_test.dart deleted file mode 100644 index 02c189cb..00000000 --- a/test/interval_timer_test.dart +++ /dev/null @@ -1,118 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:openhiit/main.dart'; - -import 'functions.dart'; - -const double portraitWidth = 1242.0; -const double portraitHeight = 2208.0; -const double landscapeWidth = portraitHeight; -const double landscapeHeight = portraitWidth; - -void main() { - testWidgets('Test CreateInterval', (WidgetTester tester) async { - final TestWidgetsFlutterBinding binding = - TestWidgetsFlutterBinding.ensureInitialized(); - - await binding.setSurfaceSize(const Size(portraitWidth, portraitHeight)); - - String timerName = "Test interval timer 1"; - - // Build our app and trigger a frame. - await tester.pumpWidget(const WorkoutTimer()); - - // Tap the '+' icon and trigger the add Workout or Timer page. - await tester.tap(find.byIcon(Icons.add)); - await tester.pumpAndSettle(); - - // Verify that the next page has loaded. - expect(find.text('Interval Timer'), findsOneWidget); - expect(find.text('Workout'), findsOneWidget); - - /// - /// CREATE FORM - /// - - // Tap to add a Workout. - await tester.tap(find.byIcon(Icons.timer)); - await tester.pumpAndSettle(); - - // Verify that the next page has loaded. - expect(find.text('Enter a name:'), findsOneWidget); - - // Enter a name - await tester.enterText(find.byKey(const Key('timer-name')), timerName); - - await createOrEditWorkout( - tester, - timerName, - 3, - false, - false, - false, - "Harsh beep sequence", - "Ding", - "Quick beep sequence", - "Beep", - "Horn", - "60", - "30"); - - // Tap the workout to view details - await tester.tap(find.text(timerName)); - - await tester.pump(); // allow the application to handle - - await tester.pump(const Duration(seconds: 1)); // skip past the animation - - // Verify the ViewWorkout page has loaded - expect(find.text("Start"), findsOneWidget); - - // Find and tap the edit button - await tester.tap(find.byKey(const Key('edit-workout'))); - - await tester.pump(); // allow the application to handle - - await tester.pump(const Duration(seconds: 1)); // skip past the animation - - await createOrEditWorkout(tester, timerName, 2, false, false, false, "Ding", - "Thunk", "Horn", "None", "Quick beep sequence", "90", "20"); - - // Tap the workout to view details - await tester.tap(find.text(timerName)); - - await tester.pump(); // allow the application to handle - - await tester.pump(const Duration(seconds: 1)); // skip past the animation - - // Verify the ViewWorkout page has loaded - expect(find.text("Start"), findsOneWidget); - - // Find and tap the three dots - await tester.tap(find.byKey(const Key('popup-menu'))); - - // Wait for the menu to appear - for (int i = 0; i < 5; i++) { - await tester.pump(const Duration(seconds: 1)); - } - - // Find and tap the delete button - await tester.tap(find.text('Delete')); - - // Wait for the dialog to appear - for (int i = 0; i < 5; i++) { - await tester.pump(const Duration(seconds: 1)); - } - - expect(find.text('Delete $timerName'), findsOneWidget); - - // Tap the Delete button in the dialog - await tester.tap(find.text('Delete')); - - // Wait for the dialog to close - await tester.pumpAndSettle(); - - // Verify that the workout is no longer displayed - expect(find.text(timerName), findsNothing); - }); -} diff --git a/test/run_timer.dart b/test/run_timer.dart deleted file mode 100644 index 6fc0d5d3..00000000 --- a/test/run_timer.dart +++ /dev/null @@ -1,149 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:openhiit/main.dart'; - -import 'functions.dart'; - -const double portraitWidth = 1242.0; -const double portraitHeight = 2208.0; -const double landscapeWidth = portraitHeight; -const double landscapeHeight = portraitWidth; - -void main() { - testWidgets('Test CreateWorkout', (WidgetTester tester) async { - final TestWidgetsFlutterBinding binding = - TestWidgetsFlutterBinding.ensureInitialized(); - - await binding.setSurfaceSize(const Size(portraitWidth, portraitHeight)); - - String workoutName = "Test workout 1"; - - // Build our app and trigger a frame. - await tester.pumpWidget(const WorkoutTimer()); - - // Tap the '+' icon and trigger the add Workout or Timer page. - await tester.tap(find.byIcon(Icons.add)); - await tester.pumpAndSettle(); - - // Verify that the next page has loaded. - expect(find.text('Interval Timer'), findsOneWidget); - expect(find.text('Workout'), findsOneWidget); - - /// - /// CREATE FORM - /// - - // Tap to add a Workout. - await tester.tap(find.byIcon(Icons.fitness_center)); - await tester.pumpAndSettle(); - - // Verify that the next page has loaded. - expect(find.text('Enter a name:'), findsOneWidget); - - // Enter a name - await tester.enterText(find.byKey(const Key('timer-name')), workoutName); - - await createOrEditWorkout( - tester, - workoutName, - 3, - true, - true, - false, - "Long whistle", - "Ding", - "Quick beep sequence", - "Beep", - "Horn", - "10", - "5"); - - // Tap the workout to view details - await tester.tap(find.text(workoutName)); - - await tester.pump(); // allow the application to handle - - await tester.pump(const Duration(seconds: 1)); // skip past the animation - - // Verify the ViewWorkout page has loaded - expect(find.text("Start"), findsOneWidget); - - // Find and tap the edit button - await tester.tap(find.text("Start")); - - // await tester.pump(); // allow the application to handle - - // await tester.pump(const Duration(seconds: 1)); // skip past the animation - - // Wait for the dialog to appear - for (int i = 0; i < 2; i++) { - await tester.pump(const Duration(seconds: 1)); - } - - // Verify the timer has started - expect(find.text("Get ready"), findsOneWidget); - - await tester.pump(const Duration( - seconds: 11)); // skip past the first portion of the timer - - // Should see text "1 of 3" - expect(find.text("1 of 3"), findsOneWidget); - // Verify the exercise name - expect(find.textContaining("Push-ups"), findsAtLeast(2)); - - await tester.pump(const Duration( - seconds: 11)); // skip past the first work portion of the timer - - // Should no longer see text "1 of 3" - expect(find.text("1 of 3"), findsNothing); - - await tester.pump(const Duration( - seconds: 6)); // skip past the first rest portion of the timer - - // Should no longer see text "1 of 3" - expect(find.text("2 of 3"), findsOne); - // Verify the exercise name - expect(find.textContaining("Sit-ups"), findsAtLeast(2)); - - await tester.pump(const Duration( - seconds: 11)); // skip past the second work portion of the timer - - // Should no longer see text "1 of 3" - expect(find.text("2 of 3"), findsNothing); - - await tester.pump(const Duration( - seconds: 6)); // skip past the second rest portion of the timer - - // Should no longer see text "1 of 3" - expect(find.text("3 of 3"), findsOne); - // Verify the exercise name - expect(find.textContaining("Jumping Jacks"), findsAtLeast(2)); - - await tester.pump(const Duration( - seconds: 11)); // skip past the third work portion of the timer - - await tester.pump(const Duration(seconds: 2)); - - // Find the Nice job screen - expect(find.text("Nice job!"), findsOne); - - // Find and tap the restart button - await tester.tap(find.text("Restart")); - - for (int i = 0; i < 5; i++) { - await tester.pump(const Duration(seconds: 1)); - } - - // await tester.pumpAndSettle(); - - // await tester.pump(); - - // await tester.pump(const Duration(seconds: 2)); - - // Verify the timer has started - expect(find.text("Get ready"), findsOneWidget); - expect(find.textContaining("Push-ups"), findsOneWidget); - expect(find.textContaining("Sit-ups"), findsOneWidget); - expect(find.textContaining("Jumping Jacks"), findsOneWidget); - }); -} diff --git a/test/workout_test.dart b/test/workout_test.dart deleted file mode 100644 index 9126e171..00000000 --- a/test/workout_test.dart +++ /dev/null @@ -1,116 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:openhiit/main.dart'; - -import 'functions.dart'; - -const double portraitWidth = 1242.0; -const double portraitHeight = 2208.0; -const double landscapeWidth = portraitHeight; -const double landscapeHeight = portraitWidth; - -void main() { - testWidgets('Test CreateWorkout', (WidgetTester tester) async { - final TestWidgetsFlutterBinding binding = - TestWidgetsFlutterBinding.ensureInitialized(); - - await binding.setSurfaceSize(const Size(portraitWidth, portraitHeight)); - - String workoutName = "Test workout 1"; - - // Build our app and trigger a frame. - await tester.pumpWidget(const WorkoutTimer()); - - // Tap the '+' icon and trigger the add Workout or Timer page. - await tester.tap(find.byIcon(Icons.add)); - await tester.pumpAndSettle(); - - // Verify that the next page has loaded. - expect(find.text('Interval Timer'), findsOneWidget); - expect(find.text('Workout'), findsOneWidget); - - /// - /// CREATE FORM - /// - - // Tap to add a Workout. - await tester.tap(find.byIcon(Icons.fitness_center)); - await tester.pumpAndSettle(); - - // Verify that the next page has loaded. - expect(find.text('Enter a name:'), findsOneWidget); - - // Enter a name - await tester.enterText(find.byKey(const Key('timer-name')), workoutName); - - await createOrEditWorkout( - tester, - workoutName, - 3, - true, - true, - false, - "Harsh beep sequence", - "Ding", - "Quick beep sequence", - "Beep", - "Horn", - "60", - "30"); - - // Tap the workout to view details - await tester.tap(find.text(workoutName)); - - await tester.pump(); // allow the application to handle - - await tester.pump(const Duration(seconds: 1)); // skip past the animation - - // Verify the ViewWorkout page has loaded - expect(find.text("Start"), findsOneWidget); - - // Find and tap the edit button - await tester.tap(find.byKey(const Key('edit-workout'))); - - await tester.pump(); // allow the application to handle - - await tester.pump(const Duration(seconds: 1)); // skip past the animation - - await createOrEditWorkout(tester, workoutName, 2, false, true, false, - "Ding", "Thunk", "Horn", "None", "Quick beep sequence", "90", "20"); - - // Tap the workout to view details - await tester.tap(find.text(workoutName)); - - await tester.pump(); // allow the application to handle - - await tester.pump(const Duration(seconds: 1)); // skip past the animation - - // Find and tap the three dots - await tester.tap(find.byKey(const Key('popup-menu'))); - - // Wait for the menu to appear - for (int i = 0; i < 5; i++) { - await tester.pump(const Duration(seconds: 1)); - } - - // Find and tap the delete button - await tester.tap(find.text('Delete')); - - // Wait for the dialog to appear - for (int i = 0; i < 5; i++) { - await tester.pump(const Duration(seconds: 1)); - } - - // Verify that the dialog is displayed - expect(find.text('Delete $workoutName'), findsOneWidget); - - // Tap the Delete button in the dialog - await tester.tap(find.text('Delete')); - - // Wait for the dialog to close - await tester.pumpAndSettle(); - - // Verify that the workout is no longer displayed - expect(find.text(workoutName), findsNothing); - }); -} diff --git a/test_driver/integration_test.dart b/test_driver/integration_test.dart new file mode 100644 index 00000000..5cd83000 --- /dev/null +++ b/test_driver/integration_test.dart @@ -0,0 +1,5 @@ +import 'package:integration_test/integration_test_driver.dart'; + +Future main() async { + await integrationDriver(); +}