From 4f9fa0e9affc80b0a00c3dbb221d4d3c69c6c1ef Mon Sep 17 00:00:00 2001 From: vessaas Date: Thu, 1 Feb 2024 21:46:08 +0700 Subject: [PATCH 1/7] chore: Add CI configuration --- .github/dependabot.yml | 51 +++++- .github/workflows/analyze.yaml | 83 ++++++++++ .github/workflows/build.yaml | 149 +++++++++++++++++ .github/workflows/integration_tests.yaml | 151 ++++++++++++++++++ .github/workflows/licence-check.yaml | 47 ++++++ .../workflows/scripts/validate-formatting.sh | 45 ++++++ CHANGELOG.md | 14 ++ CONTRIBUTING.md | 13 +- melos.yaml | 71 +++++--- pubspec.yaml | 2 +- tools/test-ios.sh | 26 +++ 11 files changed, 624 insertions(+), 28 deletions(-) create mode 100644 .github/workflows/analyze.yaml create mode 100644 .github/workflows/build.yaml create mode 100644 .github/workflows/integration_tests.yaml create mode 100644 .github/workflows/licence-check.yaml create mode 100755 .github/workflows/scripts/validate-formatting.sh create mode 100755 tools/test-ios.sh diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 539ba93..ad68211 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,6 +1,51 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + version: 2 updates: - - package-ecosystem: "npm" - directory: "/" + - package-ecosystem: "pub" + directory: "/" # Location of package manifests schedule: - interval: "weekly" \ No newline at end of file + interval: "weekly" + open-pull-requests-limit: 10 + commit-message: + prefix: chore(deps) + - package-ecosystem: "pub" + directory: "/example" # Location of package manifests + schedule: + interval: "weekly" + open-pull-requests-limit: 10 + commit-message: + prefix: chore(deps) + - package-ecosystem: "gradle" + directory: "/android" # Location of package manifests + schedule: + interval: "weekly" + open-pull-requests-limit: 10 + commit-message: + prefix: chore(deps) + - package-ecosystem: "gradle" + directory: "/example/android" # Location of package manifests + schedule: + interval: "weekly" + open-pull-requests-limit: 10 + commit-message: + prefix: chore(deps) + - package-ecosystem: "github-actions" + directory: "/" # Location of package manifests + schedule: + interval: "weekly" + open-pull-requests-limit: 10 + commit-message: + prefix: chore(deps) diff --git a/.github/workflows/analyze.yaml b/.github/workflows/analyze.yaml new file mode 100644 index 0000000..520704b --- /dev/null +++ b/.github/workflows/analyze.yaml @@ -0,0 +1,83 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: Run code analysis +on: + pull_request: + push: + branches: + - main + +jobs: + flutter-analyze: + timeout-minutes: 45 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 + - uses: subosito/flutter-action@48cafc24713cca54bbe03cdc3a423187d413aafa + with: + channel: 'stable' + cache: true + - uses: bluefireteam/melos-action@dd3c344d731938d2ab2567a261f54a19a68b5f6a + with: + melos-version: '3.0.1' + - name: 'Run Flutter Analyze' + run: melos run flutter-analyze + + format: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 + - uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '17' + - uses: subosito/flutter-action@48cafc24713cca54bbe03cdc3a423187d413aafa + with: + channel: 'stable' + cache: true + - uses: bluefireteam/melos-action@dd3c344d731938d2ab2567a261f54a19a68b5f6a + with: + melos-version: '3.0.1' + - name: Restore Cache + uses: actions/cache/restore@v3 + id: cache + with: + path: /home/linuxbrew/.linuxbrew + key: ${{ runner.os }}-linuxbrew- + - uses: Homebrew/actions/setup-homebrew@master + - name: 'Install swiftformat v0.53' + run: | + export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 + curl -O https://raw.githubusercontent.com/Homebrew/homebrew-core/86f85aaa82beba49f8a5aabf3a22508c9249f188/Formula/s/swiftformat.rb + echo 'brew "swiftformat.rb"' > Brewfile + brew bundle + - name: Save Cache + uses: actions/cache/save@v3 + if: endsWith(steps.cache.outputs.cache-matched-key,hashFiles('Brewfile.lock.json')) == false + with: + path: /home/linuxbrew/.linuxbrew + key: ${{ runner.os }}-linuxbrew-${{ hashFiles('Brewfile.lock.json') }} + - name: 'Formatter version' + run: | + swiftformat --version + - name: 'Run format' + if: ${{ success() || failure() }} + run: | + melos run format + - name: 'Validate formatting' + if: ${{ success() || failure() }} + run: | + ./.github/workflows/scripts/validate-formatting.sh diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..3049cd4 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,149 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: Run test and build +on: + pull_request: + push: + branches: + - main + +jobs: + test-dart: + timeout-minutes: 45 + runs-on: + labels: ubuntu-latest-8core + steps: + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 + - uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '17' + cache: 'gradle' + - uses: subosito/flutter-action@48cafc24713cca54bbe03cdc3a423187d413aafa + with: + channel: 'stable' + cache: true + - uses: bluefireteam/melos-action@dd3c344d731938d2ab2567a261f54a19a68b5f6a + with: + melos-version: '3.0.1' + - name: 'Run flutter test' + run: melos run test:dart + + test-android: + timeout-minutes: 45 + runs-on: + labels: ubuntu-latest-8core + steps: + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 + - uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '17' + cache: 'gradle' + - uses: subosito/flutter-action@48cafc24713cca54bbe03cdc3a423187d413aafa + with: + flutter-version: '3.16.x' + channel: 'stable' + cache: true + - uses: bluefireteam/melos-action@dd3c344d731938d2ab2567a261f54a19a68b5f6a + with: + melos-version: '3.0.1' + - name: 'Run Android native unit tests' + run: melos run test:android + + test-ios: + timeout-minutes: 45 + runs-on: + labels: macos-latest-xlarge + strategy: + matrix: + working_directory: + ['example'] + steps: + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 + - uses: actions/cache@v3 + with: + path: '**/Pods' + key: ${{ runner.os }}-pods-${{ hashFiles('**/*.podspec') }} + restore-keys: | + ${{ runner.os }}-pods- + - uses: subosito/flutter-action@48cafc24713cca54bbe03cdc3a423187d413aafa + with: + flutter-version: '3.16.x' + channel: 'stable' + cache: true + - uses: bluefireteam/melos-action@dd3c344d731938d2ab2567a261f54a19a68b5f6a + with: + melos-version: '3.0.1' + - name: Generate necessary files with flutter build + working-directory: ${{ matrix.working_directory }} + run: flutter build ios --config-only + - name: 'Run iOS native unit tests' + run: DEVICE='iPhone 14 Pro' melos run test:ios + + build-android: + needs: [test-dart,test-android,test-ios] + if: contains(github.base_ref, 'main') + timeout-minutes: 45 + runs-on: + labels: ubuntu-latest-8core + environment: + name: MAPS + env: + MAPS_API_KEY: ${{ secrets.ACTIONS_API_KEY }} + steps: + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 + - uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '17' + cache: 'gradle' + - uses: subosito/flutter-action@48cafc24713cca54bbe03cdc3a423187d413aafa + with: + channel: 'stable' + cache: true + - uses: bluefireteam/melos-action@dd3c344d731938d2ab2567a261f54a19a68b5f6a + with: + melos-version: '3.0.1' + - name: 'Run build for Android' + run: melos run flutter-build-android + + build-ios: + needs: [test-dart,test-android,test-ios] + if: contains(github.base_ref, 'main') + timeout-minutes: 90 + runs-on: macos-latest-xlarge + environment: + name: MAPS + env: + MAPS_API_KEY: ${{ secrets.ACTIONS_API_KEY }} + steps: + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 + - uses: subosito/flutter-action@48cafc24713cca54bbe03cdc3a423187d413aafa + with: + channel: 'stable' + cache: true + architecture: x64 + - uses: bluefireteam/melos-action@dd3c344d731938d2ab2567a261f54a19a68b5f6a + with: + melos-version: '3.0.1' + - uses: actions/cache@v3 + with: + path: '**/Pods' + key: ${{ runner.os }}-pods-${{ hashFiles('**/*.podspec') }} + restore-keys: | + ${{ runner.os }}-pods- + - name: 'Run build for iOS' + run: melos run flutter-build-ios diff --git a/.github/workflows/integration_tests.yaml b/.github/workflows/integration_tests.yaml new file mode 100644 index 0000000..f3262d0 --- /dev/null +++ b/.github/workflows/integration_tests.yaml @@ -0,0 +1,151 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: Run integration tests +on: + pull_request: + push: + branches: + - main + +jobs: + android: + if: contains(github.base_ref, 'main') + timeout-minutes: 45 + strategy: + matrix: + patrol_cli_version: [2.5.0] + working_directory: + ['example'] + runs-on: + labels: ubuntu-latest-8core + environment: + name: MAPS + env: + MAPS_API_KEY: ${{ secrets.ACTIONS_API_KEY }} + steps: + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 + - uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '17' + cache: 'gradle' + - name: Enable KVM group perms + 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 + - uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 + name: AVD Cache + id: avd-cache + with: + path: | + ~/.android/avd/* + ~/.android/adb* + key: avd + - uses: subosito/flutter-action@48cafc24713cca54bbe03cdc3a423187d413aafa + with: + flutter-version: '3.16.x' + channel: 'stable' + cache: true + - name: Install patrol_cli + run: flutter pub global activate patrol_cli ${{ matrix.patrol_cli_version }} + - name: Run flutter pub get + run: flutter pub get + - name: Create and start emulator + run: | + echo "List installed packages" + $ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager --list_installed + + echo "Installing system image" + echo "y" | $ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager "system-images;android-33;google_apis;x86_64" + + echo "Creating AVD" + echo "no" | $ANDROID_SDK_ROOT/cmdline-tools/latest/bin/avdmanager create avd -n test_emulator -k "system-images;android-33;google_apis;x86_64" --force + + echo "Starting emulator" + $ANDROID_SDK_ROOT/emulator/emulator -avd test_emulator -no-audio -no-boot-anim -no-window & + adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done; input keyevent 82' + - name: Generate gradlew file with flutter build + working-directory: ${{ matrix.working_directory }} + run: flutter build apk --config-only + - name: Run integration tests + working-directory: ${{ matrix.working_directory }} + run: patrol test --verbose + - name: Upload test report + uses: actions/upload-artifact@v3 + if: ${{ always() }} + with: + name: android_integration_test_report + path: example/build/app/reports/androidTests/connected/ + retention-days: 5 + + ios: + if: contains(github.base_ref, 'main') + timeout-minutes: 90 + strategy: + matrix: + patrol_cli_version: [2.5.0] + working_directory: + ['example'] + runs-on: + labels: macos-13-large + environment: + name: MAPS + env: + MAPS_API_KEY: ${{ secrets.ACTIONS_API_KEY }} + steps: + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 + - uses: subosito/flutter-action@48cafc24713cca54bbe03cdc3a423187d413aafa + with: + flutter-version: '3.16.x' + channel: 'stable' + cache: true + - uses: actions/cache@v3 + with: + path: '**/Pods' + key: ${{ runner.os }}-pods-${{ hashFiles('**/*.podspec') }} + restore-keys: | + ${{ runner.os }}-pods- + - name: Select XCode 15.1 + run: sudo xcode-select -s '/Applications/Xcode_15.1.app/Contents/Developer' + - name: Start iOS simulator + run: | + RESULT=0 + while [[ $RESULT == 0 ]]; do + xcrun simctl delete "Flutter-iPhone" || RESULT=1 + if [ $RESULT == 0 ]; then + echo -e "Deleted Flutter-iPhone" + fi + done + xcrun simctl create "Flutter-iPhone" "com.apple.CoreSimulator.SimDeviceType.iPhone-14-Pro" "com.apple.CoreSimulator.SimRuntime.iOS-17-2" | xargs xcrun simctl boot + xcrun simctl list + - name: Install patrol_cli + run: flutter pub global activate patrol_cli ${{ matrix.patrol_cli_version }} + - name: Run flutter pub get + run: flutter pub get + - name: Run Integration tests + id: tests_step + working-directory: ${{ matrix.working_directory }} + run: | + patrol test --dart-define=MAPS_API_KEY="$MAPS_API_KEY" --verbose -d 'Flutter-iPhone' + - name: Upload test report + uses: actions/upload-artifact@v3 + if: ${{ always() }} + with: + name: ios_integration_test_report + path: example/build/ios_results_*.xcresult + retention-days: 5 diff --git a/.github/workflows/licence-check.yaml b/.github/workflows/licence-check.yaml new file mode 100644 index 0000000..674bc1a --- /dev/null +++ b/.github/workflows/licence-check.yaml @@ -0,0 +1,47 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: Check license headers +on: + pull_request: + push: + branches: + - main + +jobs: + check-files-license-headers: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 + - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 + with: + go-version: '^1.13.1' + # Go is used by addlicense command (addlicense is used in melos run + # check-license-header) + - run: go install github.com/google/addlicense@latest + - name: Install Flutter + uses: subosito/flutter-action@48cafc24713cca54bbe03cdc3a423187d413aafa + with: + channel: 'stable' + cache: true + - name: Install Melos + uses: bluefireteam/melos-action@dd3c344d731938d2ab2567a261f54a19a68b5f6a + with: + # Running `melos bootstrap` is not needed because we use Melos just + # for the `check-license-header` script. + run-bootstrap: false + melos-version: '3.0.1' + - name: Check license header + run: melos run check-license-header diff --git a/.github/workflows/scripts/validate-formatting.sh b/.github/workflows/scripts/validate-formatting.sh new file mode 100755 index 0000000..757dcfd --- /dev/null +++ b/.github/workflows/scripts/validate-formatting.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +set -e + +if [[ $(git ls-files --modified) ]]; then + echo "" + echo "" + echo "These files are not formatted correctly:" + for f in $(git ls-files --modified); do + echo "" + echo "" + echo "-----------------------------------------------------------------" + echo "$f" + echo "-----------------------------------------------------------------" + echo "" + git --no-pager diff --unified=0 --minimal $f + echo "" + echo "-----------------------------------------------------------------" + echo "" + echo "" + done + if [[ $GITHUB_WORKFLOW ]]; then + git checkout . > /dev/null 2>&1 + fi + echo "" + echo "❌ Some files are incorrectly formatted, see above output." + echo "" + echo "To fix these locally, run: 'melos run format'." + exit 1 +else + echo "" + echo "✅ All files are formatted correctly." +fi diff --git a/CHANGELOG.md b/CHANGELOG.md index 5497cf2..7665358 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +## 0.2.1-beta + +This is the beta release of the Google Maps Navigation package for Flutter. It is an early look at the package and is intended for testing and feedback collection. The functionalities and APIs in this version are subject to change. + +**Key Features:** +- Added a CI configuration for these jobs: + - Flutter analyze + - Format + - Unit tests for Dart, Android and iOS + - Build Android and iOS + - Integration tests for Android and iOS + - License header check +- Added a dependabot configuration + ## 0.2.0-beta This is the beta release of the Google Maps Navigation package for Flutter. It is an early look at the package and is intended for testing and feedback collection. The functionalities and APIs in this version are subject to change. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8998cac..09feb0c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -104,14 +104,13 @@ Google Maps Flutter Navigation package has integration and unit tests. To run unit tests for the Google Maps Flutter Navigation plugin, navigate to the plugin's root directory and execute the `flutter test` command. Use the following command: ```bash -flutter test +melos run test:dart ``` To run unit tests on Android call ```bash -cd example/android -./gradlew test +melos run test:android ``` To run unit tests on iOS, follow these steps: @@ -120,6 +119,14 @@ To run unit tests on iOS, follow these steps: 3. Find and select the "RunnerTests" target. 4. Click on the play icon button to run the tests. +Or to run the iOS unit tests from command line, call + +```bash +DEVICE='iPhone 15' melos run ios:test +``` + +Specify the device you want to run the tests on with the DEVICE env variable. + ### Integration tests Integration tests are responsible for ensuring that the plugin works against the native Navigation SDK for both Android and iOS platforms. Patrol is used for the integration tests to simplify interactions with native elements. To use patrol, you first need to activate the patrol_cli. diff --git a/melos.yaml b/melos.yaml index d72a0e5..2255543 100644 --- a/melos.yaml +++ b/melos.yaml @@ -39,9 +39,9 @@ scripts: flutter-analyze: # We are setting the concurrency to 1 because a higher concurrency can crash # the analysis server on low performance machines (like GitHub Actions). - run: | - melos exec -c 1 -- \ - flutter analyze . --fatal-infos + run: flutter analyze . --fatal-infos + exec: + concurrency: 1 description: | Run `flutter analyze` in all packages. packageFilters: @@ -69,9 +69,11 @@ scripts: format:android: run: | - melos exec -c 1 --fail-fast -- \ - "if [ ! -f android/gradlew ]; then flutter build apk --config-only; fi \ - && cd android && ./gradlew ktfmtFormat" + if [ ! -f android/gradlew ]; then flutter build apk --config-only; fi && + cd android && ./gradlew ktfmtFormat + exec: + concurrency: 1 + failFast: true description: | Formats the code of Android package with ktfmt. packageFilters: @@ -79,18 +81,44 @@ scripts: - android scope: '*example*' - flutter-test: - run: | - melos exec -c 1 --fail-fast -- \ - "flutter pub get && flutter test" + test:dart: + run: flutter pub get && flutter test + exec: + concurrency: 1 + failFast: true description: Flutter test packageFilters: fileExists: 'pigeons/messages.dart' - flutter-build-android: + test:android: run: | - melos exec -c 1 --fail-fast -- \ - "flutter pub get && flutter build apk && flutter build appbundle" + if [ ! -f android/gradlew ]; then flutter build apk --config-only; fi \ + && cd android && ./gradlew test + exec: + concurrency: 1 + failFast: true + description: Android native unit tests + packageFilters: + dirExists: + - android + scope: '*example*' + + test:ios: + run: ../tools/test-ios.sh + exec: + concurrency: 1 + failFast: true + description: iOS native unit tests + packageFilters: + dirExists: + - ios + scope: '*example*' + + flutter-build-android: + run: flutter pub get && flutter build apk && flutter build appbundle + exec: + concurrency: 1 + failFast: true description: Build a specific example app for Android. packageFilters: dirExists: @@ -98,9 +126,10 @@ scripts: scope: '*example*' flutter-build-ios: - run: | - melos exec -c 1 --fail-fast -- \ - "flutter pub get && flutter build ios --release --no-codesign" + run: flutter pub get && flutter build ios --release --no-codesign + exec: + concurrency: 1 + failFast: true description: Build a specific example app for iOS. packageFilters: dirExists: @@ -108,16 +137,16 @@ scripts: scope: '*example*' generate:mocks: - run: | - melos exec -- "dart run build_runner build --delete-conflicting-outputs" && + exec: | + dart run build_runner build --delete-conflicting-outputs && melos run format --no-select && melos run add-license-header description: Generate the pigeon messages for all the supported packages. packageFilters: fileExists: 'pigeons/messages.dart' generate:pigeon: - run: | - melos exec -- "dart run pigeon --input ./pigeons/messages.dart" && + exec: | + dart run pigeon --input ./pigeons/messages.dart && melos run format --no-select description: Generate the pigeon messages for all the supported packages. packageFilters: @@ -148,4 +177,4 @@ scripts: --ignore "**/GeneratedPluginRegistrant.*" \ --ignore "**/flutter_export_environment.sh" \ . - description: Add a license header to all necessary files. \ No newline at end of file + description: Add a license header to all necessary files. diff --git a/pubspec.yaml b/pubspec.yaml index ef3a88a..a0c93fa 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ name: google_maps_navigation description: A Google Maps Navigation plugin repository: https://github.com/googlemaps/flutter-navigation-sdk issue_tracker: https://github.com/googlemaps/flutter-navigation-sdk/issues -version: 0.2.0-beta +version: 0.2.1-beta publish_to: none environment: diff --git a/tools/test-ios.sh b/tools/test-ios.sh new file mode 100755 index 0000000..e7601d3 --- /dev/null +++ b/tools/test-ios.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +set -e + +DEVICE_NAME=${DEVICE:-'iPhone 14 Pro'} # Default to 'iPhone 14 Pro' if no argument is provided + +# Navigate to the ios directory and run xcodebuild with the provided device name +cd ios && xcodebuild test \ + -workspace Runner.xcworkspace \ + -scheme Runner \ + -only-testing RunnerTests \ + -configuration Debug \ + -sdk iphoneos -destination "platform=iOS Simulator,name=$DEVICE_NAME" \ + -derivedDataPath ../build/ios_unit From d82a3a32513165c1b7723bd20407ae97f8774b43 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Thu, 1 Feb 2024 18:24:58 +0200 Subject: [PATCH 2/7] fix: remove environments from github action workflows --- .github/workflows/build.yaml | 4 ---- .github/workflows/integration_tests.yaml | 4 ---- 2 files changed, 8 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 3049cd4..4ed8116 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -99,8 +99,6 @@ jobs: timeout-minutes: 45 runs-on: labels: ubuntu-latest-8core - environment: - name: MAPS env: MAPS_API_KEY: ${{ secrets.ACTIONS_API_KEY }} steps: @@ -125,8 +123,6 @@ jobs: if: contains(github.base_ref, 'main') timeout-minutes: 90 runs-on: macos-latest-xlarge - environment: - name: MAPS env: MAPS_API_KEY: ${{ secrets.ACTIONS_API_KEY }} steps: diff --git a/.github/workflows/integration_tests.yaml b/.github/workflows/integration_tests.yaml index f3262d0..a2b35cd 100644 --- a/.github/workflows/integration_tests.yaml +++ b/.github/workflows/integration_tests.yaml @@ -30,8 +30,6 @@ jobs: ['example'] runs-on: labels: ubuntu-latest-8core - environment: - name: MAPS env: MAPS_API_KEY: ${{ secrets.ACTIONS_API_KEY }} steps: @@ -103,8 +101,6 @@ jobs: ['example'] runs-on: labels: macos-13-large - environment: - name: MAPS env: MAPS_API_KEY: ${{ secrets.ACTIONS_API_KEY }} steps: From 3672c0550a1dfdacaaf66fba9ff3939a71889aa2 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Thu, 1 Feb 2024 18:25:16 +0200 Subject: [PATCH 3/7] fix: add missing license header --- .github/blunderbuss.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/blunderbuss.yml b/.github/blunderbuss.yml index cb4f4af..250e6af 100644 --- a/.github/blunderbuss.yml +++ b/.github/blunderbuss.yml @@ -1,3 +1,17 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + assign_issues: - ArturoSalazarB16 - caio1985 From 90a60fd1b7144b42cd06d856eef9c6389557f05a Mon Sep 17 00:00:00 2001 From: vessaas Date: Fri, 2 Feb 2024 18:02:36 +0700 Subject: [PATCH 4/7] feature: Add listener integration tests and make some integration tests more reliable --- example/integration_test/shared.dart | 49 ++++++- .../integration_test/t03_navigation_test.dart | 83 +++++++---- .../t04_navigation_ui_test.dart | 7 + example/integration_test/t05_camera_test.dart | 33 +++-- example/integration_test/t06_map_test.dart | 41 +++++- .../t07_event_listener_test.dart | 129 +++++++++++++++--- ...8_marker_polygon_polyline_circle_test.dart | 57 ++++++-- 7 files changed, 324 insertions(+), 75 deletions(-) diff --git a/example/integration_test/shared.dart b/example/integration_test/shared.dart index 57cc053..ae1dc56 100644 --- a/example/integration_test/shared.dart +++ b/example/integration_test/shared.dart @@ -35,7 +35,7 @@ export 'package:patrol/patrol.dart'; /// Location coordinates for starting position simulation in Finland - Näkkäläntie. const double startLocationLat = 68.5938196099399; -const double startLocationLon = 23.510696979963722; +const double startLocationLng = 23.510696979963722; const NativeAutomatorConfig _nativeAutomatorConfig = NativeAutomatorConfig( findTimeout: Duration(seconds: 20), @@ -162,7 +162,7 @@ Future startNavigation( await GoogleMapsNavigator.simulator.setUserLocation(const LatLng( latitude: startLocationLat, - longitude: startLocationLon, + longitude: startLocationLng, )); /// Set Destination. @@ -192,13 +192,32 @@ Future startNavigation( return controller; } -/// Start navigation without setting the destination and optionally -/// simulating starting location with [simulateLocation] and skipping -/// initialization with [initializeNavigation]. +/// Start navigation without setting the destination. +/// +/// Optionally simulate the starting location with [simulateLocation], +/// skip the initialization with [initializeNavigation] and set various +/// event callback listener functions. Future startNavigationWithoutDestination( PatrolIntegrationTester $, { bool initializeNavigation = true, bool simulateLocation = false, + void Function(String)? onMarkerClicked, + void Function(String)? onCircleClicked, + void Function(LatLng)? onMapClicked, + void Function(LatLng)? onMapLongClicked, + void Function(String, LatLng)? onMarkerDrag, + void Function(String, LatLng)? onMarkerDragEnd, + void Function(String, LatLng)? onMarkerDragStart, + void Function(String)? onMarkerInfoWindowClicked, + void Function(String)? onMarkerInfoWindowClosed, + void Function(String)? onMarkerInfoWindowLongClicked, + void Function(MyLocationButtonClickedEvent)? onMyLocationButtonClicked, + void Function(MyLocationClickedEvent)? onMyLocationClicked, + void Function(bool)? onNavigationUIEnabledChanged, + void Function(String)? onPolygonClicked, + void Function(String)? onPolylineClicked, + void Function(NavigationViewRecenterButtonClickedEvent)? + onRecenterButtonClicked, void Function(CameraPosition)? onCameraIdle, }) async { final Completer controllerCompleter = @@ -214,12 +233,28 @@ Future startNavigationWithoutDestination( onViewCreated: (GoogleNavigationViewController viewController) { controllerCompleter.complete(viewController); }, - onCameraIdle: onCameraIdle, + onMarkerClicked: onMarkerClicked, + onCircleClicked: onCircleClicked, + onMapClicked: onMapClicked, + onMapLongClicked: onMapLongClicked, + onMarkerDrag: onMarkerDrag, + onMarkerDragEnd: onMarkerDragEnd, + onMarkerDragStart: onMarkerDragStart, + onMarkerInfoWindowClicked: onMarkerInfoWindowClicked, + onMarkerInfoWindowClosed: onMarkerInfoWindowClosed, + onMarkerInfoWindowLongClicked: onMarkerInfoWindowLongClicked, + onMyLocationButtonClicked: onMyLocationButtonClicked, + onMyLocationClicked: onMyLocationClicked, + onNavigationUIEnabledChanged: onNavigationUIEnabledChanged, + onPolygonClicked: onPolygonClicked, + onPolylineClicked: onPolygonClicked, + onRecenterButtonClicked: onRecenterButtonClicked, ), ); final GoogleNavigationViewController controller = await controllerCompleter.future; + await $.pumpAndSettle(); if (initializeNavigation) { await GoogleMapsNavigator.initializeNavigationSession(); @@ -229,7 +264,7 @@ Future startNavigationWithoutDestination( if (simulateLocation) { await GoogleMapsNavigator.simulator.setUserLocation(const LatLng( latitude: startLocationLat, - longitude: startLocationLon, + longitude: startLocationLng, )); } diff --git a/example/integration_test/t03_navigation_test.dart b/example/integration_test/t03_navigation_test.dart index f33f1b3..ec9ebd6 100644 --- a/example/integration_test/t03_navigation_test.dart +++ b/example/integration_test/t03_navigation_test.dart @@ -36,7 +36,7 @@ void main() { /// Start location coordinates in Finland (Näkkäläntie). const double startLat = startLocationLat; - const double startLon = startLocationLon; + const double startLng = startLocationLng; bool isPhysicalDevice = false; final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); @@ -55,9 +55,20 @@ void main() { final GoogleNavigationViewController viewController = await startNavigationWithoutDestination($); + /// Set audio guidance settings. + /// Cannot be verified, because native SDK lacks getter methods, + /// but exercise the API for basic sanity testing + final NavigationAudioGuidanceSettings settings = + NavigationAudioGuidanceSettings( + isBluetoothAudioEnabled: true, + isVibrationEnabled: true, + guidanceType: NavigationAudioGuidanceType.alertsAndGuidance, + ); + await GoogleMapsNavigator.setAudioGuidance(settings); + /// Specify tolerance and navigation end coordinates. const double tolerance = 0.0005; - const double endLat = 68.59451829688189, endLon = 23.512277951523007; + const double endLat = 68.59451829688189, endLng = 23.512277951523007; /// Finish executing the tests once onArrival event comes in /// and test that the guidance stops. @@ -71,12 +82,12 @@ void main() { /// Simulate location and test it. await GoogleMapsNavigator.simulator.setUserLocation(const LatLng( latitude: startLat, - longitude: startLon, + longitude: startLng, )); await $.pumpAndSettle(); final LatLng? currentLocation = await viewController.getMyLocation(); expect(currentLocation?.latitude, closeTo(startLat, tolerance)); - expect(currentLocation?.longitude, closeTo(startLon, tolerance)); + expect(currentLocation?.longitude, closeTo(startLng, tolerance)); /// Set Destination. final Destinations destinations = Destinations( @@ -85,7 +96,7 @@ void main() { title: 'Näkkäläntie', target: const LatLng( latitude: endLat, - longitude: endLon, + longitude: endLng, ), ), ], @@ -116,11 +127,11 @@ void main() { ); expectSync( msg.location.longitude, - greaterThanOrEqualTo(startLon - tolerance), + greaterThanOrEqualTo(startLng - tolerance), ); expectSync( msg.location.longitude, - lessThanOrEqualTo(endLon + tolerance), + lessThanOrEqualTo(endLng + tolerance), ); } @@ -146,12 +157,23 @@ void main() { final GoogleNavigationViewController viewController = await startNavigationWithoutDestination($); + /// Set audio guidance settings. + /// Cannot be verified, because native SDK lacks getter methods, + /// but exercise the API for basic sanity testing + final NavigationAudioGuidanceSettings settings = + NavigationAudioGuidanceSettings( + isBluetoothAudioEnabled: false, + isVibrationEnabled: false, + guidanceType: NavigationAudioGuidanceType.alertsOnly, + ); + await GoogleMapsNavigator.setAudioGuidance(settings); + /// Specify tolerance and navigation destination coordinates. const double tolerance = 0.0005; const double midLat = 68.59781164189049, midLon = 23.520303427087182, endLat = 68.60079240808535, - endLon = 23.527946512754752; + endLng = 23.527946512754752; Future onArrivalEvent(OnArrivalEvent msg) async { arrivalEventCount += 1; @@ -169,12 +191,12 @@ void main() { /// Simulate location and test it. await GoogleMapsNavigator.simulator.setUserLocation(const LatLng( latitude: startLat, - longitude: startLon, + longitude: startLng, )); await $.pumpAndSettle(timeout: const Duration(seconds: 1)); final LatLng? currentLocation = await viewController.getMyLocation(); expect(currentLocation!.latitude, closeTo(startLat, tolerance)); - expect(currentLocation.longitude, closeTo(startLon, tolerance)); + expect(currentLocation.longitude, closeTo(startLng, tolerance)); /// Set Destination. final Destinations destinations = Destinations( @@ -190,7 +212,7 @@ void main() { title: 'Näkkäläntie 2nd stop', target: const LatLng( latitude: endLat, - longitude: endLon, + longitude: endLng, ), ), ], @@ -222,11 +244,11 @@ void main() { ); expectSync( msg.location.longitude, - greaterThanOrEqualTo(startLon - tolerance), + greaterThanOrEqualTo(startLng - tolerance), ); expectSync( msg.location.longitude, - lessThanOrEqualTo(endLon + tolerance), + lessThanOrEqualTo(endLng + tolerance), ); } } @@ -255,7 +277,7 @@ void main() { /// Specify tolerance and navigation end coordinates. const double tolerance = 0.0005; - const double endLat = 68.59451829688189, endLon = 23.512277951523007; + const double endLat = 68.59451829688189, endLng = 23.512277951523007; /// Create a waypoint. final List waypoint = [ @@ -263,7 +285,7 @@ void main() { title: 'Näkkäläntie', target: const LatLng( latitude: endLat, - longitude: endLon, + longitude: endLng, ), ), ]; @@ -360,7 +382,7 @@ void main() { /// Simulate location. await GoogleMapsNavigator.simulator.setUserLocation(const LatLng( latitude: startLat, - longitude: startLon, + longitude: startLng, )); debugPrint('Starting location set.'); await $.pumpAndSettle(); @@ -372,7 +394,7 @@ void main() { 'LatLngSimulator: ${msg.location.latitude}, ${msg.location.longitude}.'); if ((!hasArrived) && (endLat - msg.location.latitude <= tolerance) && - (endLon - msg.location.longitude <= tolerance)) { + (endLng - msg.location.longitude <= tolerance)) { hasArrived = true; finishTest.complete(); } else { @@ -386,11 +408,11 @@ void main() { ); expectSync( msg.location.longitude, - greaterThanOrEqualTo(startLon - tolerance), + greaterThanOrEqualTo(startLng - tolerance), ); expectSync( msg.location.longitude, - lessThanOrEqualTo(endLon + tolerance), + lessThanOrEqualTo(endLng + tolerance), ); } } @@ -420,19 +442,19 @@ void main() { /// Specify tolerance and navigation end coordinates. const double tolerance = 0.0005; - const double endLat = 68.60338455021943, endLon = 23.548804200724454; + const double endLat = 68.60338455021943, endLng = 23.548804200724454; /// Simulate location. await GoogleMapsNavigator.simulator.setUserLocation(const LatLng( latitude: startLat, - longitude: startLon, + longitude: startLng, )); await $.pumpAndSettle(); /// Test the location simulation. LatLng? currentLocation = await viewController.getMyLocation(); expect(currentLocation!.latitude, closeTo(startLat, tolerance)); - expect(currentLocation.longitude, closeTo(startLon, tolerance)); + expect(currentLocation.longitude, closeTo(startLng, tolerance)); /// Test the simulated location was removed when using Android. /// iOS Xcode simulators location is flaky making the test fail sometimes @@ -443,13 +465,13 @@ void main() { currentLocation = await viewController.getMyLocation(); expect(currentLocation!.latitude, isNot(closeTo(startLat, tolerance))); - expect(currentLocation.longitude, isNot(closeTo(startLon, tolerance))); + expect(currentLocation.longitude, isNot(closeTo(startLng, tolerance))); } /// Simulate location. await GoogleMapsNavigator.simulator.setUserLocation(const LatLng( latitude: startLat, - longitude: startLon, + longitude: startLng, )); await $.pumpAndSettle(duration: const Duration(seconds: 1)); @@ -460,7 +482,7 @@ void main() { title: 'Näkkäläntie', target: const LatLng( latitude: endLat, - longitude: endLon, + longitude: endLng, ), ), ], @@ -498,6 +520,17 @@ void main() { /// Set up navigation. await startNavigationWithoutDestination($); + /// Set audio guidance settings. + /// Cannot be verified, because native SDK lacks getter methods, + /// but exercise the API for basic sanity testing + final NavigationAudioGuidanceSettings settings = + NavigationAudioGuidanceSettings( + isBluetoothAudioEnabled: false, + isVibrationEnabled: true, + guidanceType: NavigationAudioGuidanceType.silent, + ); + await GoogleMapsNavigator.setAudioGuidance(settings); + /// Simulate location (1298 California St) await GoogleMapsNavigator.simulator.setUserLocation(const LatLng( latitude: 37.79136614772824, diff --git a/example/integration_test/t04_navigation_ui_test.dart b/example/integration_test/t04_navigation_ui_test.dart index 83d4f0b..f6f7ce1 100644 --- a/example/integration_test/t04_navigation_ui_test.dart +++ b/example/integration_test/t04_navigation_ui_test.dart @@ -36,6 +36,12 @@ void main() { await checkLocationDialogAndTosAcceptance($); + /// The events are not tested because there's no reliable way to trigger them currently. + void onRecenterButtonClicked( + NavigationViewRecenterButtonClickedEvent event) { + debugPrint('Re-center button clicked event: $event.'); + } + /// Display navigation view. final Key key = GlobalKey(); await pumpNavigationView( @@ -49,6 +55,7 @@ void main() { onNavigationUIEnabledChanged: (bool isEnabled) { navigationUIisEnabled = isEnabled; }, + onRecenterButtonClicked: onRecenterButtonClicked, ), ); diff --git a/example/integration_test/t05_camera_test.dart b/example/integration_test/t05_camera_test.dart index e35f85e..73921c9 100644 --- a/example/integration_test/t05_camera_test.dart +++ b/example/integration_test/t05_camera_test.dart @@ -143,11 +143,6 @@ void main() { expect(received.target.longitude, closeTo(expected.target.longitude, 0.02)); } - // onTimeout fail function for futures. - void onTimeout() { - fail('Future timed out'); - } - patrol('Test camera modes', (PatrolIntegrationTester $) async { // Test that followMyLocation is not active and no stop or start events have come in. if (Platform.isAndroid) { @@ -391,8 +386,9 @@ void main() { // Wait for cameraIdleEvent before doing doing the tests. await cameraIdleCompleter.future.timeout( const Duration(seconds: 10), - onTimeout: onTimeout, - ); + onTimeout: () { + fail('Future timed out'); + }); camera = await controller.getCameraPosition(); // Test stoppedFollowingMyLocation event is received on Android. @@ -460,18 +456,21 @@ void main() { (PatrolIntegrationTester $) async { /// Initialize navigation with the event listener functions. final GoogleNavigationViewController controller = - await startNavigationWithoutDestination( + await startNavigation( $, - simulateLocation: true, onCameraIdle: onCameraIdle, ); + await GoogleMapsNavigator.stopGuidance(); // Create a wrapper for moveCamera() that waits until the move is finished. Future moveCamera(CameraUpdate update) async { resetCameraEventCompleters(); await controller.moveCamera(update); await cameraIdleCompleter.future - .timeout(const Duration(seconds: 10), onTimeout: onTimeout); + .timeout(const Duration(seconds: 10), onTimeout: () { + fail('Future timed out'); + } + ); } // Create a wrapper for animateCamera() that waits until move is finished @@ -495,7 +494,9 @@ void main() { // Wait until the cameraIdle event comes in. await cameraIdleCompleter.future.timeout( const Duration(seconds: 10), - onTimeout: onTimeout, + onTimeout: () { + fail('Future timed out'); + } ); } @@ -503,15 +504,15 @@ void main() { Function(CameraUpdate update)>[moveCamera, animateCamera]; const double startLat = startLocationLat + 1; - const double startLon = startLocationLon + 1; + const double startLng = startLocationLng + 1; const LatLng target = - LatLng(latitude: startLat + 1, longitude: startLon + 1); + LatLng(latitude: startLat + 1, longitude: startLng + 1); final CameraUpdate start = CameraUpdate.newCameraPosition(const CameraPosition( target: LatLng( latitude: startLat, - longitude: startLon, + longitude: startLng, ), zoom: 9, )); @@ -522,7 +523,9 @@ void main() { await controller.moveCamera(start); await cameraIdleCompleter.future.timeout( const Duration(seconds: 10), - onTimeout: onTimeout, + onTimeout: () { + fail('Future timed out'); + } ); } diff --git a/example/integration_test/t06_map_test.dart b/example/integration_test/t06_map_test.dart index 86cf955..0796f3f 100644 --- a/example/integration_test/t06_map_test.dart +++ b/example/integration_test/t06_map_test.dart @@ -93,10 +93,39 @@ void main() { }); patrol('Test map UI settings', (PatrolIntegrationTester $) async { + /// The events are not tested because there's currently no reliable way to trigger them. + void onMyLocationButtonClicked(MyLocationButtonClickedEvent event) { + debugPrint('My location button clicked event: currently $event'); + } + + /// The evot tested because there's no reliable way to trigger them currently. + void onMyLocationClicked(MyLocationClickedEvent event) { + debugPrint('My location clicked event: currently $event'); + } + + /// The evot tested because there's no reliable way to trigger them currently. + void onMapLongClicked(LatLng coordinates) { + debugPrint( + 'Map clicked event lat: ${coordinates.latitude}, lng: ${coordinates.longitude}.'); + } + /// Set up navigation without initialization to test isMyLocationEnabled - /// is false before initialization is done. + /// is false before initialization is done. Test the onMapClicked event + /// and setting the other callback functions. final GoogleNavigationViewController controller = - await startNavigationWithoutDestination($, initializeNavigation: false); + await startNavigationWithoutDestination( + $, + initializeNavigation: false, + onMapClicked: expectAsync1((LatLng msg) { + expectSync(msg, isA()); + }, count: 1, max: 1), + onMapLongClicked: onMapLongClicked, + onMyLocationButtonClicked: onMyLocationButtonClicked, + onMyLocationClicked: onMyLocationClicked, + ); + + /// Test that the onMapClicked event comes in. + await $.native.tap(Selector(enabled: true)); /// Test the default values match with what has been documented in the /// API documentation in google_maps_navigation.dart file. @@ -110,6 +139,9 @@ void main() { await controller.settings.isScrollGesturesEnabledDuringRotateOrZoom(), true); expect(await controller.settings.isTiltGesturesEnabled(), true); + if (Platform.isAndroid) { + expect(await controller.settings.isMapToolbarEnabled(), true); + } final List results = [true, false, true]; for (final bool result in results) { @@ -146,6 +178,9 @@ void main() { if (Platform.isAndroid) { await controller.settings.setZoomControlsEnabled(result); expect(await controller.settings.isZoomControlsEnabled(), result); + + await controller.settings.setMapToolbarEnabled(result); + expect(await controller.settings.isMapToolbarEnabled(), result); } } @@ -187,7 +222,7 @@ void main() { await viewController.setMapStyle( '[{"elementType":"geometry","stylers":[{"color":"#ffffff"}]}]'); - // Test that null value doens't throw exception. + // Test that null value doesn't throw exception. await viewController.setMapStyle(null); // Test that invalid json throws exception. diff --git a/example/integration_test/t07_event_listener_test.dart b/example/integration_test/t07_event_listener_test.dart index 0acab6c..917378a 100644 --- a/example/integration_test/t07_event_listener_test.dart +++ b/example/integration_test/t07_event_listener_test.dart @@ -20,19 +20,31 @@ // For more information about Flutter integration tests, please see // https://docs.flutter.dev/cookbook/testing/integration/introduction +import 'dart:async'; import 'dart:io'; +import 'package:flutter/material.dart'; import 'shared.dart'; void main() { patrol('Test navigation OnRemainingTimeOrDistanceChanged event listener', (PatrolIntegrationTester $) async { + final Completer eventReceived = Completer(); + /// Set up navigation. await startNavigationWithoutDestination($); /// Set up the listener and the test. - GoogleMapsNavigator.setOnRemainingTimeOrDistanceChangedListener( - expectAsync1( - (RemainingTimeOrDistanceChangedEvent event) {}, + final StreamSubscription subscription = + GoogleMapsNavigator.setOnRemainingTimeOrDistanceChangedListener( + expectAsync1( + (RemainingTimeOrDistanceChangedEvent event) { + expectSync(event.remainingDistance, isA()); + expectSync(event.remainingTime, isA()); + /// Complete the eventReceived completer only once. + if (!eventReceived.isCompleted) { + eventReceived.complete(); + } + }, count: 1, max: -1, )); @@ -68,16 +80,28 @@ void main() { /// Start simulation. await GoogleMapsNavigator.simulator.simulateLocationsAlongExistingRoute(); await $.pumpAndSettle(); + + /// Wait until the event is received and then test cancelling the subscription. + await eventReceived.future; + await subscription.cancel(); }); patrol('Test navigation OnRouteChanged event listener', (PatrolIntegrationTester $) async { + final Completer eventReceived = Completer(); + /// Set up navigation. await startNavigationWithoutDestination($); /// Set up the listener and the test. - GoogleMapsNavigator.setOnRouteChangedListener(expectAsync0( - () {}, + final StreamSubscription subscription = + GoogleMapsNavigator.setOnRouteChangedListener(expectAsync0( + () { + /// Complete the eventReceived completer only once. + if (!eventReceived.isCompleted) { + eventReceived.complete(); + } + }, count: 1, max: -1, )); @@ -114,10 +138,16 @@ void main() { /// Start simulation. await GoogleMapsNavigator.simulator.simulateLocationsAlongExistingRoute(); await $.pumpAndSettle(); + + /// Wait until the event is received and then test cancelling the subscription. + await eventReceived.future; + await subscription.cancel(); }); patrol('Test navigation RoadSnappedLocationUpdated event listener', (PatrolIntegrationTester $) async { + final Completer eventReceived = Completer(); + /// Sert up navigation. await startNavigationWithoutDestination($); @@ -150,9 +180,16 @@ void main() { await $.pumpAndSettle(); /// Set up the listener and the test. - await GoogleMapsNavigator.setRoadSnappedLocationUpdatedListener( - expectAsync1( - (RoadSnappedLocationUpdatedEvent event) {}, + final StreamSubscription subscription = + await GoogleMapsNavigator.setRoadSnappedLocationUpdatedListener( + expectAsync1( + (RoadSnappedLocationUpdatedEvent event) { + expectSync(event.location, isA()); + /// Complete the eventReceived completer only once. + if (!eventReceived.isCompleted) { + eventReceived.complete(); + } + }, count: 1, max: -1, )); @@ -161,7 +198,12 @@ void main() { await GoogleMapsNavigator.simulator.simulateLocationsAlongExistingRoute(); await $.pumpAndSettle(); - if (Platform.isAndroid) { + /// Test setting the background location updates. + if (Platform.isIOS) { + await GoogleMapsNavigator.allowBackgroundLocationUpdates(true); + await GoogleMapsNavigator.allowBackgroundLocationUpdates(false); + } + else if (Platform.isAndroid) { try { await GoogleMapsNavigator.allowBackgroundLocationUpdates(true); fail('Expected to get UnsupportedError'); @@ -169,19 +211,43 @@ void main() { expect(e, const TypeMatcher()); } } + + /// Wait until the event is received and then test cancelling the subscription. + await eventReceived.future; + await subscription.cancel(); }); - patrol('Test navigation onArrival event listener', + patrol('Test navigation onArrival and onSpeedingUpdated event listeners', (PatrolIntegrationTester $) async { - /// Set uo navigation. + final Completer eventReceived = Completer(); + + /// Set up navigation. await startNavigationWithoutDestination($); - /// Set up the listener and the test. - GoogleMapsNavigator.setOnArrivalListener(expectAsync1( - (OnArrivalEvent event) {}, + /// Set up the listeners and the tests. + final StreamSubscription onArrivalSubscription = + GoogleMapsNavigator.setOnArrivalListener(expectAsync1( + (OnArrivalEvent event) { + expectSync(event.waypoint, isA()); + /// Comoplete the eventReceived completer only once. + if (!eventReceived.isCompleted) { + eventReceived.complete(); + } + }, count: 1, max: -1, )); + + /// The events are not tested because there's currently no reliable way to trigger them. + void speedingUpdated(SpeedingUpdatedEvent event) { + debugPrint('SpeedingUpdated: $event'); + } + + /// This event isn't reveived with iOS in this test scenario so skipping + /// the test that checks the event is received. + final StreamSubscription + onSpeedingUpdatedSubscription = + GoogleMapsNavigator.setSpeedingUpdatedListener(speedingUpdated); await $.pumpAndSettle(); await GoogleMapsNavigator.simulator.setUserLocation(const LatLng( @@ -217,23 +283,47 @@ void main() { speedMultiplier: 100, )); await $.pumpAndSettle(); + + /// Wait until the event is received and then test cancelling the subscriptions. + await eventReceived.future; + await onArrivalSubscription.cancel(); + await onSpeedingUpdatedSubscription.cancel(); }); /// Rerouting listener is Android only. if (Platform.isAndroid) { - patrol('Test navigation onRerouting event listener', + patrol('Test navigation onRerouting and onGpsAvailability event listeners', (PatrolIntegrationTester $) async { + final Completer eventReceived = Completer(); + /// Set up navigation. await startNavigationWithoutDestination($); /// Set up the rerouting listener with the test. - GoogleMapsNavigator.setOnReroutingListener(expectAsync1( - (OnArrivalEvent event) {}, + final StreamSubscription onReroutingSubscription = + GoogleMapsNavigator.setOnReroutingListener(expectAsync0( + () { + /// Complete the eventReceived completer only once. + if (!eventReceived.isCompleted) { + eventReceived.complete(); + } + }, count: 1, max: -1, )); await $.pumpAndSettle(); + /// The events are not tested because there's currently no reliable way to trigger them. + void onGpsAvailability(GpsAvailabilityUpdatedEvent event) { + debugPrint('GpsAvailabilityEvent: $event'); + } + + /// Set up the gpsAvailability listener with the test. + final StreamSubscription + onGpsAvailabilitySubscription = + await GoogleMapsNavigator.setOnGpsAvailabilityListener( + onGpsAvailability); + /// Simulate location. await GoogleMapsNavigator.simulator.setUserLocation(const LatLng( latitude: 37.790693, @@ -280,6 +370,11 @@ void main() { RoutingOptions(), SimulationOptions(speedMultiplier: 100)); await $.pumpAndSettle(); + + /// Wait until the event is received and then test cancelling the subscriptions. + await eventReceived.future; + await onReroutingSubscription.cancel(); + await onGpsAvailabilitySubscription.cancel(); }); } } diff --git a/example/integration_test/t08_marker_polygon_polyline_circle_test.dart b/example/integration_test/t08_marker_polygon_polyline_circle_test.dart index 4a363e0..86dde19 100644 --- a/example/integration_test/t08_marker_polygon_polyline_circle_test.dart +++ b/example/integration_test/t08_marker_polygon_polyline_circle_test.dart @@ -29,9 +29,32 @@ import 'shared.dart'; void main() { patrol('Marker tests', (PatrolIntegrationTester $) async { - /// Set up navigation. + void onMarkerClicked(String event) { + debugPrint('Marker clicked event: $event.'); + } + + /// The events are not tested because there's currently no reliable way to trigger them. + void onMarkerDrag(String event, LatLng coordinates) { + debugPrint('Marker dragged event: $event. Coorinates: $coordinates.'); + } + + /// The events are not tested because there's currently no reliable way to trigger them. + void onMarkerInfoWindowAction(String event) { + debugPrint('Marker dragged event: $event.'); + } + + /// Set up navigation and test setting the callback functions. final GoogleNavigationViewController viewController = - await startNavigationWithoutDestination($); + await startNavigationWithoutDestination( + $, + onMarkerClicked: onMarkerClicked, + onMarkerDrag: onMarkerDrag, + onMarkerDragEnd: onMarkerDrag, + onMarkerDragStart: onMarkerDrag, + onMarkerInfoWindowClicked: onMarkerInfoWindowAction, + onMarkerInfoWindowClosed: onMarkerInfoWindowAction, + onMarkerInfoWindowLongClicked: onMarkerInfoWindowAction, + ); // markerOne options. const MarkerOptions markerOneOptions = MarkerOptions( @@ -244,9 +267,15 @@ void main() { }); patrol('Test polylines', (PatrolIntegrationTester $) async { - /// Set up navigation. + /// The events are not tested because there's currently no reliable way to trigger them. + void onPolylineClicked(String event) { + debugPrint('Polyline clicked event: $event.'); + } + + /// Set up navigation and test setting the callback function. final GoogleNavigationViewController viewController = - await startNavigationWithoutDestination($); + await startNavigationWithoutDestination($, + onPolylineClicked: onPolylineClicked); await viewController.addPolylines( [ @@ -456,9 +485,15 @@ void main() { }); patrol('Polygon tests', (PatrolIntegrationTester $) async { - /// Set up navigation. + void onPolygonClicked(String event) { + /// The events are not tested because there's currently no reliable way to trigger them. + debugPrint('Polygon clicked event: $event.'); + } + + /// Set up navigation and test setting up the callback listener. final GoogleNavigationViewController viewController = - await startNavigationWithoutDestination($); + await startNavigationWithoutDestination($, + onPolygonClicked: onPolygonClicked); /// Creates square, 4 coordinates, from top left and bottom right coordinates. List createSquare(LatLng topLeft, LatLng bottomRight) { @@ -742,9 +777,15 @@ void main() { }); patrol('Circle tests', (PatrolIntegrationTester $) async { - /// Set up navigation. + void onCircleClicked(String event) { + /// The events are not tested because there's currently no reliable way to trigger them. + debugPrint('Circle clicked event: $event.'); + } + + /// Set up navigation and test setting up the callback functions. final GoogleNavigationViewController viewController = - await startNavigationWithoutDestination($); + await startNavigationWithoutDestination($, + onCircleClicked: onCircleClicked); // Add circle on the current camera position. final CameraPosition position = await viewController.getCameraPosition(); From c2fa088e38a62a1820a67f2654edcb16f1f1dcc0 Mon Sep 17 00:00:00 2001 From: vessaas Date: Fri, 2 Feb 2024 18:13:29 +0700 Subject: [PATCH 5/7] fix: re-format comments --- .../t04_navigation_ui_test.dart | 2 +- example/integration_test/t05_camera_test.dart | 39 ++++++++----------- example/integration_test/t06_map_test.dart | 6 +-- .../t07_event_listener_test.dart | 12 +++--- ...8_marker_polygon_polyline_circle_test.dart | 10 ++--- 5 files changed, 32 insertions(+), 37 deletions(-) diff --git a/example/integration_test/t04_navigation_ui_test.dart b/example/integration_test/t04_navigation_ui_test.dart index f6f7ce1..c40b66c 100644 --- a/example/integration_test/t04_navigation_ui_test.dart +++ b/example/integration_test/t04_navigation_ui_test.dart @@ -36,7 +36,7 @@ void main() { await checkLocationDialogAndTosAcceptance($); - /// The events are not tested because there's no reliable way to trigger them currently. + /// The events are not tested because there's no reliable way to trigger them currently. void onRecenterButtonClicked( NavigationViewRecenterButtonClickedEvent event) { debugPrint('Re-center button clicked event: $event.'); diff --git a/example/integration_test/t05_camera_test.dart b/example/integration_test/t05_camera_test.dart index 73921c9..d7914a8 100644 --- a/example/integration_test/t05_camera_test.dart +++ b/example/integration_test/t05_camera_test.dart @@ -384,11 +384,10 @@ void main() { await controller.moveCamera(positionUpdate); // Wait for cameraIdleEvent before doing doing the tests. - await cameraIdleCompleter.future.timeout( - const Duration(seconds: 10), - onTimeout: () { - fail('Future timed out'); - }); + await cameraIdleCompleter.future.timeout(const Duration(seconds: 10), + onTimeout: () { + fail('Future timed out'); + }); camera = await controller.getCameraPosition(); // Test stoppedFollowingMyLocation event is received on Android. @@ -455,8 +454,7 @@ void main() { patrol('Test moveCamera() and animateCamera() with various options', (PatrolIntegrationTester $) async { /// Initialize navigation with the event listener functions. - final GoogleNavigationViewController controller = - await startNavigation( + final GoogleNavigationViewController controller = await startNavigation( $, onCameraIdle: onCameraIdle, ); @@ -466,11 +464,10 @@ void main() { Future moveCamera(CameraUpdate update) async { resetCameraEventCompleters(); await controller.moveCamera(update); - await cameraIdleCompleter.future - .timeout(const Duration(seconds: 10), onTimeout: () { + await cameraIdleCompleter.future.timeout(const Duration(seconds: 10), + onTimeout: () { fail('Future timed out'); - } - ); + }); } // Create a wrapper for animateCamera() that waits until move is finished @@ -492,12 +489,10 @@ void main() { ); // Wait until the cameraIdle event comes in. - await cameraIdleCompleter.future.timeout( - const Duration(seconds: 10), - onTimeout: () { - fail('Future timed out'); - } - ); + await cameraIdleCompleter.future.timeout(const Duration(seconds: 10), + onTimeout: () { + fail('Future timed out'); + }); } final List Function(CameraUpdate update)> cameraMethods = @@ -521,12 +516,10 @@ void main() { Future moveCameraToStart() async { resetCameraEventCompleters(); await controller.moveCamera(start); - await cameraIdleCompleter.future.timeout( - const Duration(seconds: 10), - onTimeout: () { - fail('Future timed out'); - } - ); + await cameraIdleCompleter.future.timeout(const Duration(seconds: 10), + onTimeout: () { + fail('Future timed out'); + }); } final CameraUpdate updateCameraPosition = diff --git a/example/integration_test/t06_map_test.dart b/example/integration_test/t06_map_test.dart index 0796f3f..6a76bb5 100644 --- a/example/integration_test/t06_map_test.dart +++ b/example/integration_test/t06_map_test.dart @@ -93,17 +93,17 @@ void main() { }); patrol('Test map UI settings', (PatrolIntegrationTester $) async { - /// The events are not tested because there's currently no reliable way to trigger them. + /// The events are not tested because there's currently no reliable way to trigger them. void onMyLocationButtonClicked(MyLocationButtonClickedEvent event) { debugPrint('My location button clicked event: currently $event'); } - /// The evot tested because there's no reliable way to trigger them currently. + /// The evot tested because there's no reliable way to trigger them currently. void onMyLocationClicked(MyLocationClickedEvent event) { debugPrint('My location clicked event: currently $event'); } - /// The evot tested because there's no reliable way to trigger them currently. + /// The evot tested because there's no reliable way to trigger them currently. void onMapLongClicked(LatLng coordinates) { debugPrint( 'Map clicked event lat: ${coordinates.latitude}, lng: ${coordinates.longitude}.'); diff --git a/example/integration_test/t07_event_listener_test.dart b/example/integration_test/t07_event_listener_test.dart index 917378a..f211c4f 100644 --- a/example/integration_test/t07_event_listener_test.dart +++ b/example/integration_test/t07_event_listener_test.dart @@ -40,6 +40,7 @@ void main() { (RemainingTimeOrDistanceChangedEvent event) { expectSync(event.remainingDistance, isA()); expectSync(event.remainingTime, isA()); + /// Complete the eventReceived completer only once. if (!eventReceived.isCompleted) { eventReceived.complete(); @@ -185,6 +186,7 @@ void main() { expectAsync1( (RoadSnappedLocationUpdatedEvent event) { expectSync(event.location, isA()); + /// Complete the eventReceived completer only once. if (!eventReceived.isCompleted) { eventReceived.complete(); @@ -198,12 +200,11 @@ void main() { await GoogleMapsNavigator.simulator.simulateLocationsAlongExistingRoute(); await $.pumpAndSettle(); - /// Test setting the background location updates. + /// Test setting the background location updates. if (Platform.isIOS) { await GoogleMapsNavigator.allowBackgroundLocationUpdates(true); await GoogleMapsNavigator.allowBackgroundLocationUpdates(false); - } - else if (Platform.isAndroid) { + } else if (Platform.isAndroid) { try { await GoogleMapsNavigator.allowBackgroundLocationUpdates(true); fail('Expected to get UnsupportedError'); @@ -229,6 +230,7 @@ void main() { GoogleMapsNavigator.setOnArrivalListener(expectAsync1( (OnArrivalEvent event) { expectSync(event.waypoint, isA()); + /// Comoplete the eventReceived completer only once. if (!eventReceived.isCompleted) { eventReceived.complete(); @@ -238,7 +240,7 @@ void main() { max: -1, )); - /// The events are not tested because there's currently no reliable way to trigger them. + /// The events are not tested because there's currently no reliable way to trigger them. void speedingUpdated(SpeedingUpdatedEvent event) { debugPrint('SpeedingUpdated: $event'); } @@ -313,7 +315,7 @@ void main() { )); await $.pumpAndSettle(); - /// The events are not tested because there's currently no reliable way to trigger them. + /// The events are not tested because there's currently no reliable way to trigger them. void onGpsAvailability(GpsAvailabilityUpdatedEvent event) { debugPrint('GpsAvailabilityEvent: $event'); } diff --git a/example/integration_test/t08_marker_polygon_polyline_circle_test.dart b/example/integration_test/t08_marker_polygon_polyline_circle_test.dart index 86dde19..065c4df 100644 --- a/example/integration_test/t08_marker_polygon_polyline_circle_test.dart +++ b/example/integration_test/t08_marker_polygon_polyline_circle_test.dart @@ -33,12 +33,12 @@ void main() { debugPrint('Marker clicked event: $event.'); } - /// The events are not tested because there's currently no reliable way to trigger them. + /// The events are not tested because there's currently no reliable way to trigger them. void onMarkerDrag(String event, LatLng coordinates) { debugPrint('Marker dragged event: $event. Coorinates: $coordinates.'); } - /// The events are not tested because there's currently no reliable way to trigger them. + /// The events are not tested because there's currently no reliable way to trigger them. void onMarkerInfoWindowAction(String event) { debugPrint('Marker dragged event: $event.'); } @@ -267,7 +267,7 @@ void main() { }); patrol('Test polylines', (PatrolIntegrationTester $) async { - /// The events are not tested because there's currently no reliable way to trigger them. + /// The events are not tested because there's currently no reliable way to trigger them. void onPolylineClicked(String event) { debugPrint('Polyline clicked event: $event.'); } @@ -486,7 +486,7 @@ void main() { patrol('Polygon tests', (PatrolIntegrationTester $) async { void onPolygonClicked(String event) { - /// The events are not tested because there's currently no reliable way to trigger them. + /// The events are not tested because there's currently no reliable way to trigger them. debugPrint('Polygon clicked event: $event.'); } @@ -778,7 +778,7 @@ void main() { patrol('Circle tests', (PatrolIntegrationTester $) async { void onCircleClicked(String event) { - /// The events are not tested because there's currently no reliable way to trigger them. + /// The events are not tested because there's currently no reliable way to trigger them. debugPrint('Circle clicked event: $event.'); } From 1aa99fce2e1aa00c9460e68c28044cf5f8d4ee3f Mon Sep 17 00:00:00 2001 From: vessaas Date: Fri, 2 Feb 2024 19:04:29 +0700 Subject: [PATCH 6/7] chore: Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7665358..6fd8b77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ This is the beta release of the Google Maps Navigation package for Flutter. It i - Integration tests for Android and iOS - License header check - Added a dependabot configuration +- Added more integration tests, mostly for event listeners +- Improved the reliability of some flaky integration tests ## 0.2.0-beta From 0b28a38be7565af663248c04ddec92f1f7b0cca7 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Fri, 9 Feb 2024 12:21:12 +0200 Subject: [PATCH 7/7] chore: Enable metal renderer for the iOS example app --- README.md | 6 ++++++ example/ios/Runner/AppDelegate.swift | 1 + 2 files changed, 7 insertions(+) diff --git a/README.md b/README.md index 8152166..0b6acbd 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,12 @@ import GoogleNavigation } ``` +> [!NOTE] +> Above code snipped also enables Metal rendering for Google Maps SDK. If you are not using Metal rendering, you can remove the following line: +> ```swift +> GMSServices.setMetalRendererEnabled(true) +> ``` + ## Usage You can now add a `GoogleMapsNavigationView` widget to your widget tree. diff --git a/example/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift index 61079a6..a8e1cb3 100644 --- a/example/ios/Runner/AppDelegate.swift +++ b/example/ios/Runner/AppDelegate.swift @@ -31,6 +31,7 @@ import GoogleNavigation mapsApiKey = "YOUR_API_KEY" } GMSServices.provideAPIKey(mapsApiKey) + GMSServices.setMetalRendererEnabled(true) GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) }