├── .fvmrc
├── example
├── linux
│ ├── .gitignore
│ ├── main.cc
│ ├── flutter
│ │ ├── generated_plugin_registrant.cc
│ │ ├── generated_plugin_registrant.h
│ │ ├── generated_plugins.cmake
│ │ └── CMakeLists.txt
│ ├── my_application.h
│ └── my_application.cc
├── lib
│ ├── home
│ │ └── home.dart
│ ├── widgets
│ │ ├── widgets.dart
│ │ ├── red_button.dart
│ │ └── contact_list_tile.dart
│ ├── main.dart
│ └── app
│ │ └── app.dart
├── ios
│ ├── Flutter
│ │ ├── Debug.xcconfig
│ │ ├── Release.xcconfig
│ │ └── AppFrameworkInfo.plist
│ ├── Runner
│ │ ├── Runner-Bridging-Header.h
│ │ ├── Assets.xcassets
│ │ │ ├── LaunchImage.imageset
│ │ │ │ ├── LaunchImage.png
│ │ │ │ ├── LaunchImage@2x.png
│ │ │ │ ├── LaunchImage@3x.png
│ │ │ │ ├── README.md
│ │ │ │ └── Contents.json
│ │ │ └── AppIcon.appiconset
│ │ │ │ ├── Icon-App-20x20@1x.png
│ │ │ │ ├── Icon-App-20x20@2x.png
│ │ │ │ ├── Icon-App-20x20@3x.png
│ │ │ │ ├── Icon-App-29x29@1x.png
│ │ │ │ ├── Icon-App-29x29@2x.png
│ │ │ │ ├── Icon-App-29x29@3x.png
│ │ │ │ ├── Icon-App-40x40@1x.png
│ │ │ │ ├── Icon-App-40x40@2x.png
│ │ │ │ ├── Icon-App-40x40@3x.png
│ │ │ │ ├── Icon-App-60x60@2x.png
│ │ │ │ ├── Icon-App-60x60@3x.png
│ │ │ │ ├── Icon-App-76x76@1x.png
│ │ │ │ ├── Icon-App-76x76@2x.png
│ │ │ │ ├── Icon-App-1024x1024@1x.png
│ │ │ │ ├── Icon-App-83.5x83.5@2x.png
│ │ │ │ └── Contents.json
│ │ ├── AppDelegate.swift
│ │ ├── Base.lproj
│ │ │ ├── Main.storyboard
│ │ │ └── LaunchScreen.storyboard
│ │ └── Info.plist
│ ├── Runner.xcodeproj
│ │ ├── project.xcworkspace
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata
│ │ │ │ ├── WorkspaceSettings.xcsettings
│ │ │ │ └── IDEWorkspaceChecks.plist
│ │ └── xcshareddata
│ │ │ └── xcschemes
│ │ │ └── Runner.xcscheme
│ ├── Runner.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── WorkspaceSettings.xcsettings
│ │ │ └── IDEWorkspaceChecks.plist
│ └── .gitignore
├── macos
│ ├── Flutter
│ │ ├── Flutter-Debug.xcconfig
│ │ ├── Flutter-Release.xcconfig
│ │ └── GeneratedPluginRegistrant.swift
│ ├── Runner
│ │ ├── Configs
│ │ │ ├── Debug.xcconfig
│ │ │ ├── Release.xcconfig
│ │ │ ├── Warnings.xcconfig
│ │ │ └── AppInfo.xcconfig
│ │ ├── Assets.xcassets
│ │ │ └── AppIcon.appiconset
│ │ │ │ ├── app_icon_16.png
│ │ │ │ ├── app_icon_32.png
│ │ │ │ ├── app_icon_64.png
│ │ │ │ ├── app_icon_1024.png
│ │ │ │ ├── app_icon_128.png
│ │ │ │ ├── app_icon_256.png
│ │ │ │ ├── app_icon_512.png
│ │ │ │ └── Contents.json
│ │ ├── AppDelegate.swift
│ │ ├── Release.entitlements
│ │ ├── DebugProfile.entitlements
│ │ ├── MainFlutterWindow.swift
│ │ └── Info.plist
│ ├── .gitignore
│ ├── Runner.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ └── Runner.xcodeproj
│ │ ├── project.xcworkspace
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ │ └── xcshareddata
│ │ └── xcschemes
│ │ └── Runner.xcscheme
├── web
│ ├── favicon.png
│ ├── icons
│ │ ├── Icon-192.png
│ │ ├── Icon-512.png
│ │ ├── Icon-maskable-192.png
│ │ └── Icon-maskable-512.png
│ ├── manifest.json
│ └── index.html
├── android
│ ├── gradle.properties
│ ├── app
│ │ ├── src
│ │ │ ├── main
│ │ │ │ ├── res
│ │ │ │ │ ├── mipmap-hdpi
│ │ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── mipmap-mdpi
│ │ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── mipmap-xhdpi
│ │ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── drawable
│ │ │ │ │ │ └── launch_background.xml
│ │ │ │ │ ├── drawable-v21
│ │ │ │ │ │ └── launch_background.xml
│ │ │ │ │ ├── values
│ │ │ │ │ │ └── styles.xml
│ │ │ │ │ └── values-night
│ │ │ │ │ │ └── styles.xml
│ │ │ │ ├── kotlin
│ │ │ │ │ └── com
│ │ │ │ │ │ └── betterment
│ │ │ │ │ │ └── example
│ │ │ │ │ │ └── MainActivity.kt
│ │ │ │ └── AndroidManifest.xml
│ │ │ ├── debug
│ │ │ │ └── AndroidManifest.xml
│ │ │ └── profile
│ │ │ │ └── AndroidManifest.xml
│ │ └── build.gradle
│ ├── gradle
│ │ └── wrapper
│ │ │ └── gradle-wrapper.properties
│ ├── .gitignore
│ ├── settings.gradle
│ └── build.gradle
├── test
│ ├── widgets
│ │ ├── goldens
│ │ │ └── ci
│ │ │ │ ├── red_button.png
│ │ │ │ └── contact_list_tile.png
│ │ ├── contact_list_tile_golden_test.dart
│ │ ├── red_button_golden_test.dart
│ │ └── multiple_icons_golden_test.dart
│ └── flutter_test_config.dart
├── windows
│ ├── runner
│ │ ├── resources
│ │ │ └── app_icon.ico
│ │ ├── resource.h
│ │ ├── utils.h
│ │ ├── runner.exe.manifest
│ │ ├── flutter_window.h
│ │ ├── CMakeLists.txt
│ │ ├── main.cpp
│ │ ├── utils.cpp
│ │ ├── flutter_window.cpp
│ │ ├── Runner.rc
│ │ └── win32_window.h
│ ├── flutter
│ │ ├── generated_plugin_registrant.cc
│ │ ├── generated_plugin_registrant.h
│ │ ├── generated_plugins.cmake
│ │ └── CMakeLists.txt
│ ├── .gitignore
│ └── CMakeLists.txt
├── analysis_options.yaml
├── pubspec.yaml
├── .gitignore
├── README.md
├── .metadata
└── example.md
├── .github
├── ISSUE_TEMPLATE
│ ├── config.yaml
│ ├── feature_request.yaml
│ └── bug_report.yaml
├── CODEOWNERS
├── workflows
│ ├── semantic-pull-request.yaml
│ ├── post_merge.yaml
│ ├── update_goldens.yaml
│ ├── main.yaml
│ └── check_compat.yaml
└── pull_request_template.md
├── test
├── helpers
│ ├── helpers.dart
│ └── fake_test_asset_bundle.dart
├── src
│ ├── blocked_text_image_reference.png
│ ├── golden_test_scenario_constraints_test.dart
│ ├── blocked_text_painting_context_golden_test.dart
│ ├── alchemist_test_variant_test.dart
│ ├── interactions_test.dart
│ ├── host_platform_test.dart
│ └── golden_test_scenario_test.dart
├── smoke_tests
│ ├── goldens
│ │ ├── 3.32.0
│ │ │ └── ci
│ │ │ │ ├── dropdown_smoke_test.png
│ │ │ │ ├── asset_image_smoke_test.png
│ │ │ │ ├── back_button_smoke_test.png
│ │ │ │ ├── error_message_smoke_test.png
│ │ │ │ ├── network_image_smoke_test.png
│ │ │ │ ├── timer_button_smoke_test.png
│ │ │ │ ├── unconstrained_smoke_test.png
│ │ │ │ ├── constrained_big_smoke_test.png
│ │ │ │ ├── render_object_text_smoke_test.png
│ │ │ │ ├── composited_transform_smoke_test.png
│ │ │ │ ├── interactions_smoke_test_pressed.png
│ │ │ │ ├── interactions_smoke_test_regular.png
│ │ │ │ └── interactions_smoke_test_long_pressed.png
│ │ └── 3.35.0
│ │ │ └── ci
│ │ │ ├── dropdown_smoke_test.png
│ │ │ ├── asset_image_smoke_test.png
│ │ │ ├── back_button_smoke_test.png
│ │ │ ├── error_message_smoke_test.png
│ │ │ ├── network_image_smoke_test.png
│ │ │ ├── timer_button_smoke_test.png
│ │ │ ├── unconstrained_smoke_test.png
│ │ │ ├── constrained_big_smoke_test.png
│ │ │ ├── render_object_text_smoke_test.png
│ │ │ ├── composited_transform_smoke_test.png
│ │ │ ├── interactions_smoke_test_pressed.png
│ │ │ ├── interactions_smoke_test_regular.png
│ │ │ └── interactions_smoke_test_long_pressed.png
│ ├── back_button_smoke_test.dart
│ ├── error_message_smoke_test.dart
│ ├── asset_image_smoke_test.dart
│ ├── network_image_smoke_test.dart
│ ├── unconstrained_smoke_test.dart
│ ├── composited_transform_smoke_test.dart
│ ├── timer_button_smoke_test.dart
│ ├── dropdown_smoke_test.dart
│ ├── render_object_smoke_test.dart
│ └── interactions_smoke_test.dart
└── flutter_test_config.dart
├── .vscode
├── settings.json
└── launch.json
├── assets
├── fonts
│ └── Roboto
│ │ ├── Roboto-Black.ttf
│ │ ├── Roboto-Bold.ttf
│ │ ├── Roboto-Light.ttf
│ │ ├── Roboto-Thin.ttf
│ │ ├── Roboto-Medium.ttf
│ │ └── Roboto-Regular.ttf
└── readme
│ ├── ci_list_tile_golden_file.png
│ └── macos_list_tile_golden_file.png
├── codecov.yml
├── analysis_options.yaml
├── dart_test.yaml
├── cspell.json
├── .metadata
├── lib
├── alchemist.dart
└── src
│ ├── golden_test_scenario_constraints.dart
│ ├── interactions.dart
│ ├── alchemist_test_variant.dart
│ ├── pumps.dart
│ ├── golden_test_theme.dart
│ ├── host_platform.dart
│ ├── golden_test_scenario.dart
│ └── golden_test_runner.dart
├── tool
└── verify_pub_score.sh
├── coverage_badge.svg
├── LICENSE
├── pubspec.yaml
├── .gitignore
├── CONTRIBUTING.md
└── RECOMMENDED_SETUP_GUIDE.md
/.fvmrc:
--------------------------------------------------------------------------------
1 | {
2 | "flutter": "3.32.8"
3 | }
--------------------------------------------------------------------------------
/example/linux/.gitignore:
--------------------------------------------------------------------------------
1 | flutter/ephemeral
2 |
--------------------------------------------------------------------------------
/example/lib/home/home.dart:
--------------------------------------------------------------------------------
1 | export 'home_page.dart';
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yaml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: true
2 |
--------------------------------------------------------------------------------
/example/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/test/helpers/helpers.dart:
--------------------------------------------------------------------------------
1 | export 'fake_test_asset_bundle.dart';
2 |
--------------------------------------------------------------------------------
/example/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "dart.flutterSdkPath": ".fvm/versions/3.32.8"
3 | }
--------------------------------------------------------------------------------
/example/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/example/macos/Flutter/Flutter-Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "ephemeral/Flutter-Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/example/macos/Flutter/Flutter-Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "ephemeral/Flutter-Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/example/lib/widgets/widgets.dart:
--------------------------------------------------------------------------------
1 | export 'contact_list_tile.dart';
2 | export 'red_button.dart';
3 |
--------------------------------------------------------------------------------
/example/web/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/example/web/favicon.png
--------------------------------------------------------------------------------
/example/web/icons/Icon-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/example/web/icons/Icon-192.png
--------------------------------------------------------------------------------
/example/web/icons/Icon-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/example/web/icons/Icon-512.png
--------------------------------------------------------------------------------
/assets/fonts/Roboto/Roboto-Black.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/assets/fonts/Roboto/Roboto-Black.ttf
--------------------------------------------------------------------------------
/assets/fonts/Roboto/Roboto-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/assets/fonts/Roboto/Roboto-Bold.ttf
--------------------------------------------------------------------------------
/assets/fonts/Roboto/Roboto-Light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/assets/fonts/Roboto/Roboto-Light.ttf
--------------------------------------------------------------------------------
/assets/fonts/Roboto/Roboto-Thin.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/assets/fonts/Roboto/Roboto-Thin.ttf
--------------------------------------------------------------------------------
/codecov.yml:
--------------------------------------------------------------------------------
1 | coverage:
2 | range: "100...100"
3 | status:
4 | project:
5 | default:
6 | target: 100
7 |
--------------------------------------------------------------------------------
/example/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 |
--------------------------------------------------------------------------------
/example/macos/Runner/Configs/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "../../Flutter/Flutter-Debug.xcconfig"
2 | #include "Warnings.xcconfig"
3 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | include: package:very_good_analysis/analysis_options.5.1.0.yaml
2 |
3 | formatter:
4 | page_width: 80
5 |
--------------------------------------------------------------------------------
/assets/fonts/Roboto/Roboto-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/assets/fonts/Roboto/Roboto-Medium.ttf
--------------------------------------------------------------------------------
/assets/fonts/Roboto/Roboto-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/assets/fonts/Roboto/Roboto-Regular.ttf
--------------------------------------------------------------------------------
/example/macos/Runner/Configs/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "../../Flutter/Flutter-Release.xcconfig"
2 | #include "Warnings.xcconfig"
3 |
--------------------------------------------------------------------------------
/example/web/icons/Icon-maskable-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/example/web/icons/Icon-maskable-192.png
--------------------------------------------------------------------------------
/example/web/icons/Icon-maskable-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/example/web/icons/Icon-maskable-512.png
--------------------------------------------------------------------------------
/test/src/blocked_text_image_reference.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/test/src/blocked_text_image_reference.png
--------------------------------------------------------------------------------
/assets/readme/ci_list_tile_golden_file.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/assets/readme/ci_list_tile_golden_file.png
--------------------------------------------------------------------------------
/example/macos/.gitignore:
--------------------------------------------------------------------------------
1 | # Flutter-related
2 | **/Flutter/ephemeral/
3 | **/Pods/
4 |
5 | # Xcode-related
6 | **/dgph
7 | **/xcuserdata/
8 |
--------------------------------------------------------------------------------
/assets/readme/macos_list_tile_golden_file.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/assets/readme/macos_list_tile_golden_file.png
--------------------------------------------------------------------------------
/example/test/widgets/goldens/ci/red_button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/example/test/widgets/goldens/ci/red_button.png
--------------------------------------------------------------------------------
/example/windows/runner/resources/app_icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/example/windows/runner/resources/app_icon.ico
--------------------------------------------------------------------------------
/example/test/widgets/goldens/ci/contact_list_tile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/example/test/widgets/goldens/ci/contact_list_tile.png
--------------------------------------------------------------------------------
/example/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | include: package:very_good_analysis/analysis_options.3.1.0.yaml
2 |
3 | linter:
4 | rules:
5 | public_member_api_docs: false
6 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # Every request must be reviewed and accepted by:
2 |
3 | * @Kirpal @jeroen-meijer @btrautmann @jolexxa @marcossevilla @Betterment/mobile-platform
4 |
--------------------------------------------------------------------------------
/test/smoke_tests/goldens/3.32.0/ci/dropdown_smoke_test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/test/smoke_tests/goldens/3.32.0/ci/dropdown_smoke_test.png
--------------------------------------------------------------------------------
/test/smoke_tests/goldens/3.35.0/ci/dropdown_smoke_test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/test/smoke_tests/goldens/3.35.0/ci/dropdown_smoke_test.png
--------------------------------------------------------------------------------
/dart_test.yaml:
--------------------------------------------------------------------------------
1 | # The existence of this file prevents warnings about unrecognized tags when
2 | # running Alchemist tests.
3 |
4 | tags:
5 | golden:
6 | timeout: 15s
7 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/test/smoke_tests/goldens/3.32.0/ci/asset_image_smoke_test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/test/smoke_tests/goldens/3.32.0/ci/asset_image_smoke_test.png
--------------------------------------------------------------------------------
/test/smoke_tests/goldens/3.32.0/ci/back_button_smoke_test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/test/smoke_tests/goldens/3.32.0/ci/back_button_smoke_test.png
--------------------------------------------------------------------------------
/test/smoke_tests/goldens/3.35.0/ci/asset_image_smoke_test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/test/smoke_tests/goldens/3.35.0/ci/asset_image_smoke_test.png
--------------------------------------------------------------------------------
/test/smoke_tests/goldens/3.35.0/ci/back_button_smoke_test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/test/smoke_tests/goldens/3.35.0/ci/back_button_smoke_test.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:example/app/app.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | void main() {
5 | runApp(const AlchemistExampleApp());
6 | }
7 |
--------------------------------------------------------------------------------
/test/smoke_tests/goldens/3.32.0/ci/error_message_smoke_test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/test/smoke_tests/goldens/3.32.0/ci/error_message_smoke_test.png
--------------------------------------------------------------------------------
/test/smoke_tests/goldens/3.32.0/ci/network_image_smoke_test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/test/smoke_tests/goldens/3.32.0/ci/network_image_smoke_test.png
--------------------------------------------------------------------------------
/test/smoke_tests/goldens/3.32.0/ci/timer_button_smoke_test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/test/smoke_tests/goldens/3.32.0/ci/timer_button_smoke_test.png
--------------------------------------------------------------------------------
/test/smoke_tests/goldens/3.32.0/ci/unconstrained_smoke_test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/test/smoke_tests/goldens/3.32.0/ci/unconstrained_smoke_test.png
--------------------------------------------------------------------------------
/test/smoke_tests/goldens/3.35.0/ci/error_message_smoke_test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/test/smoke_tests/goldens/3.35.0/ci/error_message_smoke_test.png
--------------------------------------------------------------------------------
/test/smoke_tests/goldens/3.35.0/ci/network_image_smoke_test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/test/smoke_tests/goldens/3.35.0/ci/network_image_smoke_test.png
--------------------------------------------------------------------------------
/test/smoke_tests/goldens/3.35.0/ci/timer_button_smoke_test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/test/smoke_tests/goldens/3.35.0/ci/timer_button_smoke_test.png
--------------------------------------------------------------------------------
/test/smoke_tests/goldens/3.35.0/ci/unconstrained_smoke_test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/test/smoke_tests/goldens/3.35.0/ci/unconstrained_smoke_test.png
--------------------------------------------------------------------------------
/test/smoke_tests/goldens/3.32.0/ci/constrained_big_smoke_test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/test/smoke_tests/goldens/3.32.0/ci/constrained_big_smoke_test.png
--------------------------------------------------------------------------------
/test/smoke_tests/goldens/3.35.0/ci/constrained_big_smoke_test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/test/smoke_tests/goldens/3.35.0/ci/constrained_big_smoke_test.png
--------------------------------------------------------------------------------
/test/smoke_tests/goldens/3.32.0/ci/render_object_text_smoke_test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/test/smoke_tests/goldens/3.32.0/ci/render_object_text_smoke_test.png
--------------------------------------------------------------------------------
/test/smoke_tests/goldens/3.35.0/ci/render_object_text_smoke_test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/test/smoke_tests/goldens/3.35.0/ci/render_object_text_smoke_test.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png
--------------------------------------------------------------------------------
/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png
--------------------------------------------------------------------------------
/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png
--------------------------------------------------------------------------------
/test/smoke_tests/goldens/3.32.0/ci/composited_transform_smoke_test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/test/smoke_tests/goldens/3.32.0/ci/composited_transform_smoke_test.png
--------------------------------------------------------------------------------
/test/smoke_tests/goldens/3.32.0/ci/interactions_smoke_test_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/test/smoke_tests/goldens/3.32.0/ci/interactions_smoke_test_pressed.png
--------------------------------------------------------------------------------
/test/smoke_tests/goldens/3.32.0/ci/interactions_smoke_test_regular.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/test/smoke_tests/goldens/3.32.0/ci/interactions_smoke_test_regular.png
--------------------------------------------------------------------------------
/test/smoke_tests/goldens/3.35.0/ci/composited_transform_smoke_test.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/test/smoke_tests/goldens/3.35.0/ci/composited_transform_smoke_test.png
--------------------------------------------------------------------------------
/test/smoke_tests/goldens/3.35.0/ci/interactions_smoke_test_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/test/smoke_tests/goldens/3.35.0/ci/interactions_smoke_test_pressed.png
--------------------------------------------------------------------------------
/test/smoke_tests/goldens/3.35.0/ci/interactions_smoke_test_regular.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/test/smoke_tests/goldens/3.35.0/ci/interactions_smoke_test_regular.png
--------------------------------------------------------------------------------
/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png
--------------------------------------------------------------------------------
/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png
--------------------------------------------------------------------------------
/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png
--------------------------------------------------------------------------------
/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/test/smoke_tests/goldens/3.32.0/ci/interactions_smoke_test_long_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/test/smoke_tests/goldens/3.32.0/ci/interactions_smoke_test_long_pressed.png
--------------------------------------------------------------------------------
/test/smoke_tests/goldens/3.35.0/ci/interactions_smoke_test_long_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/test/smoke_tests/goldens/3.35.0/ci/interactions_smoke_test_long_pressed.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Betterment/alchemist/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/example/linux/main.cc:
--------------------------------------------------------------------------------
1 | #include "my_application.h"
2 |
3 | int main(int argc, char** argv) {
4 | g_autoptr(MyApplication) app = my_application_new();
5 | return g_application_run(G_APPLICATION(app), argc, argv);
6 | }
7 |
--------------------------------------------------------------------------------
/example/android/app/src/main/kotlin/com/betterment/example/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.betterment.alchemist-example
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity: FlutterActivity() {
6 | }
7 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/macos/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/macos/Flutter/GeneratedPluginRegistrant.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | import FlutterMacOS
6 | import Foundation
7 |
8 |
9 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
10 | }
11 |
--------------------------------------------------------------------------------
/example/linux/flutter/generated_plugin_registrant.cc:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | // clang-format off
6 |
7 | #include "generated_plugin_registrant.h"
8 |
9 |
10 | void fl_register_plugins(FlPluginRegistry* registry) {
11 | }
12 |
--------------------------------------------------------------------------------
/example/windows/flutter/generated_plugin_registrant.cc:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | // clang-format off
6 |
7 | #include "generated_plugin_registrant.h"
8 |
9 |
10 | void RegisterPlugins(flutter::PluginRegistry* registry) {
11 | }
12 |
--------------------------------------------------------------------------------
/example/test/flutter_test_config.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
4 |
5 | Future testExecutable(FutureOr Function() testMain) async {
6 | LeakTesting.enable();
7 | await testMain();
8 | }
9 |
--------------------------------------------------------------------------------
/example/macos/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import Cocoa
2 | import FlutterMacOS
3 |
4 | @NSApplicationMain
5 | class AppDelegate: FlutterAppDelegate {
6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
7 | return true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/cspell.json:
--------------------------------------------------------------------------------
1 | {
2 | "words": [
3 | "alchemistconfig",
4 | "automaticcustom",
5 | "endtemplate",
6 | "goldens",
7 | "LTRBXY",
8 | "LTWH",
9 | "Microtask",
10 | "Occluder",
11 | "rects",
12 | "RGBO",
13 | "Roboto",
14 | "rrect"
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/example/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Jun 23 08:50:38 CEST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
7 |
--------------------------------------------------------------------------------
/example/macos/Runner/Release.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: d79295af24c3ed621c33713ecda14ad196fd9c31
8 | channel: stable
9 |
10 | project_type: package
11 |
--------------------------------------------------------------------------------
/example/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
9 | # Remember to never publicly share your keystore.
10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
11 | key.properties
12 | **/*.keystore
13 | **/*.jks
14 |
--------------------------------------------------------------------------------
/example/windows/.gitignore:
--------------------------------------------------------------------------------
1 | flutter/ephemeral/
2 |
3 | # Visual Studio user-specific files.
4 | *.suo
5 | *.user
6 | *.userosscache
7 | *.sln.docstates
8 |
9 | # Visual Studio build-related files.
10 | x64/
11 | x86/
12 |
13 | # Visual Studio cache files
14 | # files ending in .cache can be ignored
15 | *.[Cc]ache
16 | # but keep track of directories ending in .cache
17 | !*.[Cc]ache/
18 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 |
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 |
5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
--------------------------------------------------------------------------------
/lib/alchemist.dart:
--------------------------------------------------------------------------------
1 | export 'src/alchemist_config.dart';
2 | export 'src/blocked_text_image.dart';
3 | export 'src/golden_test.dart';
4 | export 'src/golden_test_group.dart';
5 | export 'src/golden_test_scenario.dart';
6 | export 'src/golden_test_theme.dart';
7 | export 'src/host_platform.dart';
8 | export 'src/interactions.dart';
9 | export 'src/pumps.dart';
10 | export 'src/utilities.dart' show TestAssetBundle;
11 |
--------------------------------------------------------------------------------
/example/linux/flutter/generated_plugin_registrant.h:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | // clang-format off
6 |
7 | #ifndef GENERATED_PLUGIN_REGISTRANT_
8 | #define GENERATED_PLUGIN_REGISTRANT_
9 |
10 | #include
11 |
12 | // Registers Flutter plugins.
13 | void fl_register_plugins(FlPluginRegistry* registry);
14 |
15 | #endif // GENERATED_PLUGIN_REGISTRANT_
16 |
--------------------------------------------------------------------------------
/example/windows/flutter/generated_plugin_registrant.h:
--------------------------------------------------------------------------------
1 | //
2 | // Generated file. Do not edit.
3 | //
4 |
5 | // clang-format off
6 |
7 | #ifndef GENERATED_PLUGIN_REGISTRANT_
8 | #define GENERATED_PLUGIN_REGISTRANT_
9 |
10 | #include
11 |
12 | // Registers Flutter plugins.
13 | void RegisterPlugins(flutter::PluginRegistry* registry);
14 |
15 | #endif // GENERATED_PLUGIN_REGISTRANT_
16 |
--------------------------------------------------------------------------------
/.github/workflows/semantic-pull-request.yaml:
--------------------------------------------------------------------------------
1 | name: Semantic PR
2 |
3 | # ignore: RiskyTriggers
4 | on: [pull_request_target]
5 |
6 | jobs:
7 | validate_title:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - name: Validate PR title
11 | uses: amannn/action-semantic-pull-request@0723387faaf9b38adef4775cd42cfd5155ed6017 # v5.5.3
12 | env:
13 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
14 |
--------------------------------------------------------------------------------
/test/smoke_tests/back_button_smoke_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:alchemist/src/golden_test.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_test/flutter_test.dart';
4 |
5 | void main() {
6 | group('smoke test', () {
7 | goldenTest(
8 | 'succeeds with a BackButton widget',
9 | fileName: 'back_button_smoke_test',
10 | builder: () => const BackButton(),
11 | );
12 | });
13 | }
14 |
--------------------------------------------------------------------------------
/example/macos/Runner/DebugProfile.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.cs.allow-jit
8 |
9 | com.apple.security.network.server
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/example/lib/app/app.dart:
--------------------------------------------------------------------------------
1 | import 'package:example/home/home.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | class AlchemistExampleApp extends StatelessWidget {
5 | const AlchemistExampleApp({super.key});
6 |
7 | @override
8 | Widget build(BuildContext context) {
9 | return MaterialApp(
10 | title: 'Flutter Demo',
11 | theme: ThemeData(
12 | primarySwatch: Colors.blue,
13 | ),
14 | home: const HomePage(),
15 | );
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/example/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Flutter
3 |
4 | @UIApplicationMain
5 | @objc class AppDelegate: FlutterAppDelegate {
6 | override func application(
7 | _ application: UIApplication,
8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
9 | ) -> Bool {
10 | GeneratedPluginRegistrant.register(with: self)
11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/example/linux/my_application.h:
--------------------------------------------------------------------------------
1 | #ifndef FLUTTER_MY_APPLICATION_H_
2 | #define FLUTTER_MY_APPLICATION_H_
3 |
4 | #include
5 |
6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION,
7 | GtkApplication)
8 |
9 | /**
10 | * my_application_new:
11 | *
12 | * Creates a new Flutter-based application.
13 | *
14 | * Returns: a new #MyApplication.
15 | */
16 | MyApplication* my_application_new();
17 |
18 | #endif // FLUTTER_MY_APPLICATION_H_
19 |
--------------------------------------------------------------------------------
/example/macos/Runner/MainFlutterWindow.swift:
--------------------------------------------------------------------------------
1 | import Cocoa
2 | import FlutterMacOS
3 |
4 | class MainFlutterWindow: NSWindow {
5 | override func awakeFromNib() {
6 | let flutterViewController = FlutterViewController.init()
7 | let windowFrame = self.frame
8 | self.contentViewController = flutterViewController
9 | self.setFrame(windowFrame, display: true)
10 |
11 | RegisterGeneratedPlugins(registry: flutterViewController)
12 |
13 | super.awakeFromNib()
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "Update Goldens",
6 | "request": "launch",
7 | "type": "dart",
8 | "args": [
9 | "--update-goldens"
10 | ],
11 | "program": "${relativeFile}",
12 | "flutterMode": "debug",
13 | "console": "debugConsole",
14 | "codeLens": {
15 | "for": [
16 | "debug-test",
17 | "debug-test-file"
18 | ]
19 | }
20 | },
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/example/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: example
2 | description: An example project showcasing Alchemist's testing functionality.
3 |
4 | publish_to: "none"
5 |
6 | version: 1.0.0+1
7 |
8 | environment:
9 | sdk: ">=2.17.0 <3.0.0"
10 |
11 | dependencies:
12 | flutter:
13 | sdk: flutter
14 |
15 | dev_dependencies:
16 | alchemist:
17 | path: ../
18 | flutter_test:
19 | sdk: flutter
20 | leak_tracker_flutter_testing: any
21 | very_good_analysis: ^3.1.0
22 |
23 | flutter:
24 | uses-material-design: true
25 |
--------------------------------------------------------------------------------
/example/windows/runner/resource.h:
--------------------------------------------------------------------------------
1 | //{{NO_DEPENDENCIES}}
2 | // Microsoft Visual C++ generated include file.
3 | // Used by Runner.rc
4 | //
5 | #define IDI_APP_ICON 101
6 |
7 | // Next default values for new objects
8 | //
9 | #ifdef APSTUDIO_INVOKED
10 | #ifndef APSTUDIO_READONLY_SYMBOLS
11 | #define _APS_NEXT_RESOURCE_VALUE 102
12 | #define _APS_NEXT_COMMAND_VALUE 40001
13 | #define _APS_NEXT_CONTROL_VALUE 1001
14 | #define _APS_NEXT_SYMED_VALUE 101
15 | #endif
16 | #endif
17 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ## Description
2 |
3 |
4 |
5 | ## Type of Change
6 |
7 |
8 |
9 | - [ ] ✨ New feature (non-breaking change which adds functionality)
10 | - [ ] 🛠️ Bug fix (non-breaking change which fixes an issue)
11 | - [ ] ❌ Breaking change (fix or feature that would cause existing functionality to change)
12 | - [ ] 🧹 Code refactor
13 | - [ ] ✅ Build configuration change
14 | - [ ] 📝 Documentation
15 | - [ ] 🗑️ Chore
16 |
--------------------------------------------------------------------------------
/example/android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
4 | def properties = new Properties()
5 |
6 | assert localPropertiesFile.exists()
7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
8 |
9 | def flutterSdkPath = properties.getProperty("flutter.sdk")
10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
12 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LaunchImage.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "LaunchImage@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "LaunchImage@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/test/smoke_tests/error_message_smoke_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:alchemist/src/golden_test.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_test/flutter_test.dart';
4 |
5 | void main() {
6 | group('smoke test', () {
7 | goldenTest(
8 | 'succeeds with an error message',
9 | fileName: 'error_message_smoke_test',
10 | builder: () => SizedBox(
11 | width: 200,
12 | height: 200,
13 | child: ErrorWidget(FlutterError('This is an error message.')),
14 | ),
15 | );
16 | });
17 | }
18 |
--------------------------------------------------------------------------------
/test/smoke_tests/asset_image_smoke_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:alchemist/src/golden_test.dart';
2 | import 'package:alchemist/src/pumps.dart';
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_test/flutter_test.dart';
5 |
6 | import '../helpers/helpers.dart';
7 |
8 | void main() {
9 | group('smoke test', () {
10 | goldenTest(
11 | 'succeeds with an asset image',
12 | fileName: 'asset_image_smoke_test',
13 | pumpBeforeTest: precacheImages,
14 | builder: () => DefaultAssetBundle(
15 | bundle: FakeTestAssetBundle(),
16 | child: Image.asset('test.png'),
17 | ),
18 | );
19 | });
20 | }
21 |
--------------------------------------------------------------------------------
/example/macos/Runner/Configs/Warnings.xcconfig:
--------------------------------------------------------------------------------
1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings
2 | GCC_WARN_UNDECLARED_SELECTOR = YES
3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES
4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE
5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES
6 | CLANG_WARN_PRAGMA_PACK = YES
7 | CLANG_WARN_STRICT_PROTOTYPES = YES
8 | CLANG_WARN_COMMA = YES
9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES
10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES
11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES
12 | GCC_WARN_SHADOW = YES
13 | CLANG_WARN_UNREACHABLE_CODE = YES
14 |
--------------------------------------------------------------------------------
/example/macos/Runner/Configs/AppInfo.xcconfig:
--------------------------------------------------------------------------------
1 | // Application-level settings for the Runner target.
2 | //
3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the
4 | // future. If not, the values below would default to using the project name when this becomes a
5 | // 'flutter create' template.
6 |
7 | // The application's name. By default this is also the title of the Flutter window.
8 | PRODUCT_NAME = example
9 |
10 | // The application's bundle identifier
11 | PRODUCT_BUNDLE_IDENTIFIER = com.betterment.alchemist-example
12 |
13 | // The copyright displayed in application information
14 | PRODUCT_COPYRIGHT = Copyright © 2022 com.betterment. All rights reserved.
15 |
--------------------------------------------------------------------------------
/example/ios/.gitignore:
--------------------------------------------------------------------------------
1 | **/dgph
2 | *.mode1v3
3 | *.mode2v3
4 | *.moved-aside
5 | *.pbxuser
6 | *.perspectivev3
7 | **/*sync/
8 | .sconsign.dblite
9 | .tags*
10 | **/.vagrant/
11 | **/DerivedData/
12 | Icon?
13 | **/Pods/
14 | **/.symlinks/
15 | profile
16 | xcuserdata
17 | **/.generated/
18 | Flutter/App.framework
19 | Flutter/Flutter.framework
20 | Flutter/Flutter.podspec
21 | Flutter/Generated.xcconfig
22 | Flutter/ephemeral/
23 | Flutter/app.flx
24 | Flutter/app.zip
25 | Flutter/flutter_assets/
26 | Flutter/flutter_export_environment.sh
27 | ServiceDefinitions.json
28 | Runner/GeneratedPluginRegistrant.*
29 |
30 | # Exceptions to above rules.
31 | !default.mode1v3
32 | !default.mode2v3
33 | !default.pbxuser
34 | !default.perspectivev3
35 |
--------------------------------------------------------------------------------
/test/smoke_tests/network_image_smoke_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:alchemist/alchemist.dart';
2 | import 'package:flutter/widgets.dart';
3 | import 'package:flutter_test/flutter_test.dart';
4 | import 'package:mocktail_image_network/mocktail_image_network.dart';
5 |
6 | void main() {
7 | group('smoke test', () {
8 | goldenTest(
9 | 'succeeds with a network image',
10 | fileName: 'network_image_smoke_test',
11 | pumpWidget: (tester, widget) async {
12 | await mockNetworkImages(() => tester.pumpWidget(widget));
13 | },
14 | builder: () => Padding(
15 | padding: const EdgeInsets.all(8),
16 | child: Image.network('https://fakeurl.com/image.png'),
17 | ),
18 | );
19 | });
20 | }
21 |
--------------------------------------------------------------------------------
/example/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.6.10'
3 | repositories {
4 | google()
5 | mavenCentral()
6 | }
7 |
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:7.1.2'
10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 | }
12 | }
13 |
14 | allprojects {
15 | repositories {
16 | google()
17 | mavenCentral()
18 | }
19 | }
20 |
21 | rootProject.buildDir = '../build'
22 | subprojects {
23 | project.buildDir = "${rootProject.buildDir}/${project.name}"
24 | }
25 | subprojects {
26 | project.evaluationDependsOn(':app')
27 | }
28 |
29 | task clean(type: Delete) {
30 | delete rootProject.buildDir
31 | }
32 |
--------------------------------------------------------------------------------
/example/windows/runner/utils.h:
--------------------------------------------------------------------------------
1 | #ifndef RUNNER_UTILS_H_
2 | #define RUNNER_UTILS_H_
3 |
4 | #include
5 | #include
6 |
7 | // Creates a console for the process, and redirects stdout and stderr to
8 | // it for both the runner and the Flutter library.
9 | void CreateAndAttachConsole();
10 |
11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string
12 | // encoded in UTF-8. Returns an empty std::string on failure.
13 | std::string Utf8FromUtf16(const wchar_t* utf16_string);
14 |
15 | // Gets the command line arguments passed in as a std::vector,
16 | // encoded in UTF-8. Returns an empty std::vector on failure.
17 | std::vector GetCommandLineArguments();
18 |
19 | #endif // RUNNER_UTILS_H_
20 |
--------------------------------------------------------------------------------
/test/helpers/fake_test_asset_bundle.dart:
--------------------------------------------------------------------------------
1 | import 'dart:convert';
2 | import 'dart:typed_data';
3 |
4 | import 'package:alchemist/alchemist.dart';
5 |
6 | final redPixelImage = base64Decode(
7 | 'iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAApElEQVR42u3RAQ0AAAjD'
8 | 'MO5fNCCDkC5z0HTVrisFCBABASIgQAQEiIAAAQJEQIAICBABASIgQAREQIAICBABASIg'
9 | 'QAREQIAICBABASIgQAREQIAICBABASIgQAREQIAICBABASIgQAREQIAICBABASIgQARE'
10 | 'QIAICBABASIgQAREQIAICBABASIgQAREQIAICBABASIgQAQECBAgAgJEQIAIyPcGFY7H'
11 | 'nV2aPXoAAAAASUVORK5CYII=',
12 | );
13 |
14 | class FakeTestAssetBundle extends TestAssetBundle {
15 | @override
16 | Future load(String key) async {
17 | if (key.endsWith('png')) {
18 | return ByteData.view(redPixelImage.buffer);
19 | }
20 | return super.load(key);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/tool/verify_pub_score.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Runs `pana . --no-warning` and verifies that the package score
3 | # is greater or equal to the desired score. By default the desired score is
4 | # a perfect score but it can be overridden by passing the desired score as an argument.
5 | #
6 | # Ensure the package has a score of at least a 100
7 | # `./verify_pub_score.sh 100`
8 | #
9 | # Ensure the package has a perfect score
10 | # `./verify_pub_score.sh`
11 |
12 | PANA=$(pana . --no-warning); PANA_SCORE=$(echo $PANA | sed -n "s/.*Points: \([0-9]*\)\/\([0-9]*\)./\1\/\2/p")
13 | echo "score: $PANA_SCORE"
14 | IFS='/'; read -a SCORE_ARR <<< "$PANA_SCORE"; SCORE=SCORE_ARR[0]; TOTAL=SCORE_ARR[1]
15 | if [ -z "$1" ]; then MINIMUM_SCORE=TOTAL; else MINIMUM_SCORE=$1; fi
16 | if (( $SCORE < $MINIMUM_SCORE )); then echo "minimum score $MINIMUM_SCORE was not met!"; exit 1; fi
17 |
--------------------------------------------------------------------------------
/test/smoke_tests/unconstrained_smoke_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:alchemist/src/golden_test.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_test/flutter_test.dart';
4 |
5 | void main() {
6 | group('smoke test', () {
7 | goldenTest(
8 | 'succeeds with an unconstrained widget',
9 | fileName: 'unconstrained_smoke_test',
10 | constraints: const BoxConstraints(maxWidth: 3000, maxHeight: 3000),
11 | builder: () =>
12 | const SizedBox.expand(child: ColoredBox(color: Colors.red)),
13 | );
14 |
15 | goldenTest(
16 | 'succeeds with a big constrained widget',
17 | fileName: 'constrained_big_smoke_test',
18 | builder: () => const SizedBox(
19 | width: 3000,
20 | height: 3000,
21 | child: ColoredBox(color: Colors.red),
22 | ),
23 | );
24 | });
25 | }
26 |
--------------------------------------------------------------------------------
/example/linux/flutter/generated_plugins.cmake:
--------------------------------------------------------------------------------
1 | #
2 | # Generated file, do not edit.
3 | #
4 |
5 | list(APPEND FLUTTER_PLUGIN_LIST
6 | )
7 |
8 | list(APPEND FLUTTER_FFI_PLUGIN_LIST
9 | )
10 |
11 | set(PLUGIN_BUNDLED_LIBRARIES)
12 |
13 | foreach(plugin ${FLUTTER_PLUGIN_LIST})
14 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
15 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
16 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $)
17 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
18 | endforeach(plugin)
19 |
20 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
21 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin})
22 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
23 | endforeach(ffi_plugin)
24 |
--------------------------------------------------------------------------------
/example/windows/flutter/generated_plugins.cmake:
--------------------------------------------------------------------------------
1 | #
2 | # Generated file, do not edit.
3 | #
4 |
5 | list(APPEND FLUTTER_PLUGIN_LIST
6 | )
7 |
8 | list(APPEND FLUTTER_FFI_PLUGIN_LIST
9 | )
10 |
11 | set(PLUGIN_BUNDLED_LIBRARIES)
12 |
13 | foreach(plugin ${FLUTTER_PLUGIN_LIST})
14 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
15 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
16 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $)
17 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
18 | endforeach(plugin)
19 |
20 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
21 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
22 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
23 | endforeach(ffi_plugin)
24 |
--------------------------------------------------------------------------------
/test/smoke_tests/composited_transform_smoke_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:alchemist/src/golden_test.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_test/flutter_test.dart';
4 |
5 | class _SmokeTest extends StatelessWidget {
6 | _SmokeTest({super.key}) : _link = LayerLink();
7 |
8 | final LayerLink _link;
9 |
10 | @override
11 | Widget build(BuildContext context) {
12 | return Stack(
13 | children: [
14 | CompositedTransformTarget(link: _link, child: const FlutterLogo()),
15 | CompositedTransformFollower(link: _link, child: const Text('label')),
16 | ],
17 | );
18 | }
19 | }
20 |
21 | void main() {
22 | group('smoke test', () {
23 | goldenTest(
24 | 'succeeds with CompositedTransformFollower',
25 | fileName: 'composited_transform_smoke_test',
26 | builder: _SmokeTest.new,
27 | );
28 | });
29 | }
30 |
--------------------------------------------------------------------------------
/example/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 9.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/lib/widgets/red_button.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class RedButton extends StatelessWidget {
4 | const RedButton({
5 | super.key,
6 | required this.onPressed,
7 | this.icon,
8 | required this.child,
9 | });
10 |
11 | final VoidCallback? onPressed;
12 | final Widget? icon;
13 | final Widget child;
14 |
15 | @override
16 | Widget build(BuildContext context) {
17 | final style = ElevatedButton.styleFrom(
18 | backgroundColor: Colors.red,
19 | foregroundColor: Colors.white,
20 | );
21 | if (icon != null) {
22 | return ElevatedButton.icon(
23 | style: style,
24 | onPressed: onPressed,
25 | icon: icon,
26 | label: child,
27 | );
28 | } else {
29 | return ElevatedButton(
30 | style: style,
31 | onPressed: onPressed,
32 | child: child,
33 | );
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/test/smoke_tests/timer_button_smoke_test.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 |
3 | import 'package:alchemist/src/golden_test.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter_test/flutter_test.dart';
6 |
7 | class TimerButton extends StatelessWidget {
8 | const TimerButton({super.key});
9 |
10 | @override
11 | Widget build(BuildContext context) {
12 | return ElevatedButton(
13 | child: const Text('Button'),
14 | onPressed: () {
15 | Timer(Duration.zero, () {});
16 | },
17 | );
18 | }
19 | }
20 |
21 | void main() {
22 | group('smoke test', () {
23 | goldenTest(
24 | 'succeeds after tapping button with timer',
25 | fileName: 'timer_button_smoke_test',
26 | pumpBeforeTest: (tester) async {
27 | await tester.tap(find.byType(TimerButton));
28 | await tester.pumpAndSettle();
29 | },
30 | builder: () => const TimerButton(),
31 | );
32 | });
33 | }
34 |
--------------------------------------------------------------------------------
/example/windows/runner/runner.exe.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PerMonitorV2
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/example/web/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "short_name": "example",
4 | "start_url": ".",
5 | "display": "standalone",
6 | "background_color": "#0175C2",
7 | "theme_color": "#0175C2",
8 | "description": "A new Flutter project.",
9 | "orientation": "portrait-primary",
10 | "prefer_related_applications": false,
11 | "icons": [
12 | {
13 | "src": "icons/Icon-192.png",
14 | "sizes": "192x192",
15 | "type": "image/png"
16 | },
17 | {
18 | "src": "icons/Icon-512.png",
19 | "sizes": "512x512",
20 | "type": "image/png"
21 | },
22 | {
23 | "src": "icons/Icon-maskable-192.png",
24 | "sizes": "192x192",
25 | "type": "image/png",
26 | "purpose": "maskable"
27 | },
28 | {
29 | "src": "icons/Icon-maskable-512.png",
30 | "sizes": "512x512",
31 | "type": "image/png",
32 | "purpose": "maskable"
33 | }
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 | migrate_working_dir/
12 |
13 | # IntelliJ related
14 | *.iml
15 | *.ipr
16 | *.iws
17 | .idea/
18 |
19 | # The .vscode folder contains launch configuration and tasks you configure in
20 | # VS Code which you may wish to be included in version control, so this line
21 | # is commented out by default.
22 | #.vscode/
23 |
24 | # Flutter/Dart/Pub related
25 | **/doc/api/
26 | **/ios/Flutter/.last_build_id
27 | .dart_tool/
28 | .flutter-plugins
29 | .flutter-plugins-dependencies
30 | .packages
31 | .pub-cache/
32 | .pub/
33 | /build/
34 |
35 | # Web related
36 | lib/generated_plugin_registrant.dart
37 |
38 | # Symbolication related
39 | app.*.symbols
40 |
41 | # Obfuscation related
42 | app.*.map.json
43 |
44 | # Android Studio will place build artifacts here
45 | /android/app/debug
46 | /android/app/profile
47 | /android/app/release
48 |
49 | # Ignore platform-specific goldens
50 | **/goldens/macos
51 | **/goldens/linux
52 | **/goldens/windows
53 |
--------------------------------------------------------------------------------
/example/windows/runner/flutter_window.h:
--------------------------------------------------------------------------------
1 | #ifndef RUNNER_FLUTTER_WINDOW_H_
2 | #define RUNNER_FLUTTER_WINDOW_H_
3 |
4 | #include
5 | #include
6 |
7 | #include
8 |
9 | #include "win32_window.h"
10 |
11 | // A window that does nothing but host a Flutter view.
12 | class FlutterWindow : public Win32Window {
13 | public:
14 | // Creates a new FlutterWindow hosting a Flutter view running |project|.
15 | explicit FlutterWindow(const flutter::DartProject& project);
16 | virtual ~FlutterWindow();
17 |
18 | protected:
19 | // Win32Window:
20 | bool OnCreate() override;
21 | void OnDestroy() override;
22 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
23 | LPARAM const lparam) noexcept override;
24 |
25 | private:
26 | // The project to run.
27 | flutter::DartProject project_;
28 |
29 | // The Flutter instance hosted by this window.
30 | std::unique_ptr flutter_controller_;
31 | };
32 |
33 | #endif // RUNNER_FLUTTER_WINDOW_H_
34 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/coverage_badge.svg:
--------------------------------------------------------------------------------
1 |
21 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Betterment LLC
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
19 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
20 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
21 | OR OTHER DEALINGS IN THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/lib/src/golden_test_scenario_constraints.dart:
--------------------------------------------------------------------------------
1 | import 'package:alchemist/src/golden_test_scenario.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | /// {@template golden_test_scenario_constraints}
5 | /// Applies constraints to the children of [GoldenTestScenario] widgets. This is
6 | /// intended for internal use only.
7 | /// {@endtemplate}
8 | class GoldenTestScenarioConstraints extends InheritedWidget {
9 | /// {@macro golden_test_scenario_constraints}
10 | const GoldenTestScenarioConstraints({
11 | required super.child,
12 | required this.constraints,
13 | super.key,
14 | });
15 |
16 | /// The constraints to apply to the scenario's child.
17 | final BoxConstraints? constraints;
18 |
19 | @override
20 | bool updateShouldNotify(covariant GoldenTestScenarioConstraints oldWidget) {
21 | return oldWidget.constraints != constraints;
22 | }
23 |
24 | /// Obtains the constraints from the nearest instance of this widget from the
25 | /// given context.
26 | static BoxConstraints? maybeOf(BuildContext context) => context
27 | .dependOnInheritedWidgetOfExactType()
28 | ?.constraints;
29 | }
30 |
--------------------------------------------------------------------------------
/example/macos/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(FLUTTER_BUILD_NAME)
21 | CFBundleVersion
22 | $(FLUTTER_BUILD_NUMBER)
23 | LSMinimumSystemVersion
24 | $(MACOSX_DEPLOYMENT_TARGET)
25 | NSHumanReadableCopyright
26 | $(PRODUCT_COPYRIGHT)
27 | NSMainNibFile
28 | MainMenu
29 | NSPrincipalClass
30 | NSApplication
31 |
32 |
33 |
--------------------------------------------------------------------------------
/test/smoke_tests/dropdown_smoke_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:alchemist/src/golden_test.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_test/flutter_test.dart';
4 |
5 | class TestDropdownButton extends StatelessWidget {
6 | const TestDropdownButton({super.key});
7 |
8 | @override
9 | Widget build(BuildContext context) => Center(
10 | child: DropdownButton(
11 | value: '0',
12 | items: const [
13 | DropdownMenuItem(value: '0', child: Text('0')),
14 | DropdownMenuItem(value: '1', child: Text('1')),
15 | DropdownMenuItem(value: '2', child: Text('2')),
16 | ],
17 | onChanged: (_) {},
18 | ),
19 | );
20 | }
21 |
22 | void main() {
23 | group('smoke test', () {
24 | goldenTest(
25 | 'succeeds after tapping dropdown',
26 | fileName: 'dropdown_smoke_test',
27 | constraints: const BoxConstraints(maxWidth: 200, maxHeight: 250),
28 | pumpBeforeTest: (tester) async {
29 | await tester.pumpAndSettle();
30 | await tester.tap(find.byType(DropdownButton));
31 | await tester.pumpAndSettle();
32 | },
33 | builder: () {
34 | return const TestDropdownButton();
35 | },
36 | );
37 | });
38 | }
39 |
--------------------------------------------------------------------------------
/.github/workflows/post_merge.yaml:
--------------------------------------------------------------------------------
1 | name: post-merge
2 |
3 | on:
4 | push:
5 | branches:
6 | - 'main'
7 |
8 | jobs:
9 | upload_coverage:
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
14 |
15 | - name: Determine Flutter version
16 | id: flutter_version
17 | run: |
18 | # Grab the Flutter version from the .fvmrc file
19 | FLUTTER_VERSION=$(grep '"flutter"' .fvmrc | grep -o '[0-9][0-9.]*')
20 | echo "flutter_version=$FLUTTER_VERSION" >> "$GITHUB_OUTPUT"
21 |
22 | - uses: subosito/flutter-action@e938fdf56512cc96ef2f93601a5a40bde3801046 # v2.19.0
23 | with:
24 | flutter-version: ${{ steps.flutter_version.outputs.flutter_version }}
25 | cache: true
26 |
27 | - name: Install Dependencies
28 | run: flutter packages get
29 |
30 | - name: Disable animations
31 | run: flutter config --no-cli-animations
32 |
33 | - name: Run tests
34 | run: |
35 | flutter test --no-pub --coverage --test-randomize-ordering-seed=random
36 |
37 | - uses: codecov/codecov-action@c2fcb216de2b0348de0100baa3ea2cad9f100a01 # v5.1.0
38 | with:
39 | files: coverage/lcov.info
40 |
--------------------------------------------------------------------------------
/example/test/widgets/contact_list_tile_golden_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:alchemist/alchemist.dart';
2 | import 'package:example/widgets/widgets.dart';
3 | import 'package:flutter_test/flutter_test.dart';
4 |
5 | void main() {
6 | group('Contact List Tile Golden Tests', () {
7 | goldenTest(
8 | 'renders correctly',
9 | fileName: 'contact_list_tile',
10 | builder: () => GoldenTestGroup(
11 | children: [
12 | GoldenTestScenario(
13 | name: 'enabled, one name',
14 | child: ContactListTile(
15 | onPressed: () {},
16 | name: 'Contact',
17 | email: 'contact@example.com',
18 | ),
19 | ),
20 | GoldenTestScenario(
21 | name: 'enabled, two names',
22 | child: ContactListTile(
23 | onPressed: () {},
24 | name: 'Contact List',
25 | email: 'contactlist@example.com',
26 | ),
27 | ),
28 | GoldenTestScenario(
29 | name: 'enabled, three names',
30 | child: ContactListTile(
31 | onPressed: () {},
32 | name: 'Contact List Tile',
33 | email: 'contactlisttile@example.com',
34 | ),
35 | ),
36 | ],
37 | ),
38 | );
39 | });
40 | }
41 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: alchemist
2 | description: A support package that aims to make golden testing in Flutter
3 | easier and more streamlined.
4 | version: 0.13.0
5 | homepage: https://github.com/Betterment/alchemist
6 | repository: https://github.com/Betterment/alchemist
7 |
8 | environment:
9 | sdk: ">=3.8.0 <4.0.0"
10 | flutter: ">=3.32.0"
11 |
12 | dependencies:
13 | equatable: ^2.0.3
14 | flutter:
15 | sdk: flutter
16 | flutter_test:
17 | sdk: flutter
18 | meta: ^1.7.0
19 |
20 | dev_dependencies:
21 | mocktail: ^0.3.0
22 | mocktail_image_network: ^0.3.1
23 | path: ^1.8.3
24 | test: ^1.21.1
25 | version: ^3.0.2
26 | very_good_analysis: ^5.1.0
27 |
28 | flutter:
29 | uses-material-design: true
30 | # Used for readable platform golden tests.
31 | fonts:
32 | - family: Roboto
33 | fonts:
34 | - asset: assets/fonts/Roboto/Roboto-Thin.ttf
35 | weight: 100
36 | - asset: assets/fonts/Roboto/Roboto-Light.ttf
37 | weight: 300
38 | - asset: assets/fonts/Roboto/Roboto-Regular.ttf
39 | weight: 400
40 | - asset: assets/fonts/Roboto/Roboto-Medium.ttf
41 | weight: 500
42 | - asset: assets/fonts/Roboto/Roboto-Bold.ttf
43 | weight: 700
44 | - asset: assets/fonts/Roboto/Roboto-Black.ttf
45 | weight: 900
46 |
--------------------------------------------------------------------------------
/example/windows/runner/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.14)
2 | project(runner LANGUAGES CXX)
3 |
4 | # Define the application target. To change its name, change BINARY_NAME in the
5 | # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
6 | # work.
7 | #
8 | # Any new source files that you add to the application should be added here.
9 | add_executable(${BINARY_NAME} WIN32
10 | "flutter_window.cpp"
11 | "main.cpp"
12 | "utils.cpp"
13 | "win32_window.cpp"
14 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
15 | "Runner.rc"
16 | "runner.exe.manifest"
17 | )
18 |
19 | # Apply the standard set of build settings. This can be removed for applications
20 | # that need different build settings.
21 | apply_standard_settings(${BINARY_NAME})
22 |
23 | # Disable Windows macros that collide with C++ standard library functions.
24 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
25 |
26 | # Add dependency libraries and include directories. Add any application-specific
27 | # dependencies here.
28 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
29 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
30 |
31 | # Run the Flutter tool portions of the build. This must not be removed.
32 | add_dependencies(${BINARY_NAME} flutter_assemble)
33 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # Alchemist Example Project
2 |
3 | > For a quick explanation on how to get started with Alchemist, read the [example.md][example_markdown] file or the [Recommended Setup Guide][setup_guide].
4 |
5 | This is an example app that showcases the Alchemist golden testing package.
6 |
7 | It contains two custom widgets that each have their own golden tests; a [RedButton](./lib/widgets/red_button.dart) and a [ContactListTile](./lib/widgets/contact_list_tile.dart). Both have several variations, all of which are showcased in the app.
8 |
9 | The primary goal of this app is to show how to use the Alchemist golden testing package to test widgets. Please see Alchemist's [README](../README.md) for more information, and visit the [`test/`](./test/) directory in this app to see how these widgets are tested.
10 |
11 | ## Getting Started
12 |
13 | This project is a starting point for a Flutter application.
14 |
15 | A few resources to get you started if this is your first Flutter project:
16 |
17 | - [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
18 | - [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
19 |
20 | For help getting started with Flutter development, view the
21 | [online documentation](https://docs.flutter.dev/), which offers tutorials,
22 | samples, guidance on mobile development, and a full API reference.
23 |
24 | [example_markdown]: ./example.md
25 | [setup_guide]: ../RECOMMENDED_SETUP_GUIDE.md
26 |
--------------------------------------------------------------------------------
/example/windows/runner/main.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | #include "flutter_window.h"
6 | #include "utils.h"
7 |
8 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
9 | _In_ wchar_t *command_line, _In_ int show_command) {
10 | // Attach to console when present (e.g., 'flutter run') or create a
11 | // new console when running with a debugger.
12 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
13 | CreateAndAttachConsole();
14 | }
15 |
16 | // Initialize COM, so that it is available for use in the library and/or
17 | // plugins.
18 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
19 |
20 | flutter::DartProject project(L"data");
21 |
22 | std::vector command_line_arguments =
23 | GetCommandLineArguments();
24 |
25 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
26 |
27 | FlutterWindow window(project);
28 | Win32Window::Point origin(10, 10);
29 | Win32Window::Size size(1280, 720);
30 | if (!window.CreateAndShow(L"example", origin, size)) {
31 | return EXIT_FAILURE;
32 | }
33 | window.SetQuitOnClose(true);
34 |
35 | ::MSG msg;
36 | while (::GetMessage(&msg, nullptr, 0, 0)) {
37 | ::TranslateMessage(&msg);
38 | ::DispatchMessage(&msg);
39 | }
40 |
41 | ::CoUninitialize();
42 | return EXIT_SUCCESS;
43 | }
44 |
--------------------------------------------------------------------------------
/lib/src/interactions.dart:
--------------------------------------------------------------------------------
1 | import 'package:alchemist/src/utilities.dart';
2 | import 'package:flutter/foundation.dart';
3 | import 'package:flutter/gestures.dart';
4 | import 'package:flutter_test/flutter_test.dart';
5 |
6 | /// An interaction to perform while rendering a golden test. Returns
7 | /// an asynchronous callback that should be called to cleanup when
8 | /// the golden test completes.
9 | typedef Interaction = Future Function(WidgetTester);
10 |
11 | /// Presses all widgets matching `finder`.
12 | Interaction press(
13 | Finder finder, {
14 | Duration? holdFor = const Duration(milliseconds: 300),
15 | }) => (WidgetTester tester) async {
16 | final gestures = await tester.pressAll(finder);
17 | await tester.pump(kPressTimeout);
18 | await tester.pump(holdFor);
19 | return gestures.releaseAll;
20 | };
21 |
22 | /// Long-presses all widgets matching [finder].
23 | Interaction longPress(Finder finder) => (WidgetTester tester) async {
24 | final gestures = await tester.pressAll(finder);
25 | await tester.pump(kLongPressTimeout);
26 | return gestures.releaseAll;
27 | };
28 |
29 | /// Scrolls all widgets matching `finder`.
30 | Interaction scroll(
31 | Finder finder, {
32 | required Offset offset,
33 | double speed = kMinFlingVelocity,
34 | }) => (WidgetTester tester) async {
35 | final elements = finder.evaluate();
36 | for (final element in elements) {
37 | await tester.fling(find.byWidget(element.widget), offset, speed);
38 | }
39 | return;
40 | };
41 |
--------------------------------------------------------------------------------
/example/test/widgets/red_button_golden_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:alchemist/alchemist.dart';
2 | import 'package:example/widgets/widgets.dart';
3 | import 'package:flutter/material.dart';
4 | import 'package:flutter_test/flutter_test.dart';
5 |
6 | void main() {
7 | group('Red Button Golden Tests', () {
8 | goldenTest(
9 | 'renders correctly',
10 | fileName: 'red_button',
11 | builder: () => GoldenTestGroup(
12 | children: [
13 | GoldenTestScenario(
14 | name: 'enabled',
15 | child: RedButton(
16 | onPressed: () {},
17 | child: const Text('Red Button'),
18 | ),
19 | ),
20 | GoldenTestScenario(
21 | name: 'disabled',
22 | child: const RedButton(
23 | onPressed: null,
24 | child: Text('Red Button'),
25 | ),
26 | ),
27 | GoldenTestScenario(
28 | name: 'with icon',
29 | child: RedButton(
30 | onPressed: () {},
31 | icon: const Icon(Icons.add),
32 | child: const Text('Red Button'),
33 | ),
34 | ),
35 | GoldenTestScenario(
36 | name: 'disabled with icon',
37 | child: const RedButton(
38 | onPressed: null,
39 | icon: Icon(Icons.add),
40 | child: Text('Red Button'),
41 | ),
42 | ),
43 | ],
44 | ),
45 | );
46 | });
47 | }
48 |
--------------------------------------------------------------------------------
/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "16x16",
5 | "idiom" : "mac",
6 | "filename" : "app_icon_16.png",
7 | "scale" : "1x"
8 | },
9 | {
10 | "size" : "16x16",
11 | "idiom" : "mac",
12 | "filename" : "app_icon_32.png",
13 | "scale" : "2x"
14 | },
15 | {
16 | "size" : "32x32",
17 | "idiom" : "mac",
18 | "filename" : "app_icon_32.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "32x32",
23 | "idiom" : "mac",
24 | "filename" : "app_icon_64.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "128x128",
29 | "idiom" : "mac",
30 | "filename" : "app_icon_128.png",
31 | "scale" : "1x"
32 | },
33 | {
34 | "size" : "128x128",
35 | "idiom" : "mac",
36 | "filename" : "app_icon_256.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "256x256",
41 | "idiom" : "mac",
42 | "filename" : "app_icon_256.png",
43 | "scale" : "1x"
44 | },
45 | {
46 | "size" : "256x256",
47 | "idiom" : "mac",
48 | "filename" : "app_icon_512.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "512x512",
53 | "idiom" : "mac",
54 | "filename" : "app_icon_512.png",
55 | "scale" : "1x"
56 | },
57 | {
58 | "size" : "512x512",
59 | "idiom" : "mac",
60 | "filename" : "app_icon_1024.png",
61 | "scale" : "2x"
62 | }
63 | ],
64 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/test/smoke_tests/render_object_smoke_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:alchemist/alchemist.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_test/flutter_test.dart';
4 |
5 | void main() {
6 | group('smoke test', () {
7 | goldenTest(
8 | 'succeeds for custom render object drawing text',
9 | fileName: 'render_object_text_smoke_test',
10 | builder: () => const SizedBox(
11 | height: 400,
12 | width: 400,
13 | child: _CustomExampleRenderObject(),
14 | ),
15 | );
16 | });
17 | }
18 |
19 | class _CustomExampleRenderObject extends LeafRenderObjectWidget {
20 | const _CustomExampleRenderObject();
21 |
22 | @override
23 | _CustomExampleRenderBox createRenderObject(BuildContext context) {
24 | return _CustomExampleRenderBox();
25 | }
26 | }
27 |
28 | class _CustomExampleRenderBox extends RenderBox {
29 | @override
30 | void paint(PaintingContext context, Offset offset) {
31 | final textPainter = TextPainter(
32 | text: const TextSpan(
33 | text: 'text',
34 | style: TextStyle(
35 | fontFamily: 'Roboto',
36 | color: Colors.black,
37 | fontSize: 24,
38 | ),
39 | ),
40 | textDirection: TextDirection.ltr,
41 | )..layout();
42 | textPainter.paint(
43 | context.canvas,
44 | const Offset(200, 200) - textPainter.size.center(Offset.zero),
45 | );
46 | }
47 |
48 | @override
49 | void performLayout() {
50 | size = computeDryLayout(constraints);
51 | }
52 |
53 | @override
54 | Size computeDryLayout(BoxConstraints constraints) {
55 | return constraints.constrain(Size(200, constraints.maxHeight));
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/example/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.yaml:
--------------------------------------------------------------------------------
1 | name: Feature Request
2 | description: Request a new feature.
3 | title: "request: "
4 | labels: ["feature request", "triage"]
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: |
9 | Thanks for taking the time to fill out this bug report!
10 |
11 | - type: checkboxes
12 | attributes:
13 | label: Is there an existing feature request for this?
14 | description: |
15 | Please search to see if an issue already exists for the feature you would like to see.
16 | options:
17 | - label: I have searched the existing issues.
18 | required: true
19 |
20 | - type: input
21 | validations:
22 | required: true
23 | id: command
24 | attributes:
25 | label: Command
26 | description: |
27 | What feature would you like to see?
28 | placeholder: "I would love if we could do ..."
29 |
30 | - type: textarea
31 | validations:
32 | required: true
33 | id: description
34 | attributes:
35 | label: Description
36 | description: |
37 | Give us a clear and concise description of what the feature is and what it would do.
38 | placeholder: As a developer, I would like to be able to ...
39 |
40 | - type: textarea
41 | validations:
42 | required: true
43 | id: reasoning
44 | attributes:
45 | label: Reasoning
46 | description: |
47 | What is the reason for your request?
48 | Why do you think this feature would be useful?
49 | placeholder: |
50 | I think this feature would be useful because...
51 |
52 | - type: textarea
53 | id: comments
54 | attributes:
55 | label: Additional context and comments
56 | description: |
57 | Anything else you want to say?
58 |
--------------------------------------------------------------------------------
/example/lib/widgets/contact_list_tile.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class ContactListTile extends StatelessWidget {
4 | const ContactListTile({
5 | super.key,
6 | required this.onPressed,
7 | required this.name,
8 | required this.email,
9 | }) : assert(name.length > 0, 'name must be non-empty'),
10 | assert(email.length > 0, 'email must be non-empty');
11 |
12 | final VoidCallback? onPressed;
13 | final String name;
14 | final String email;
15 |
16 | bool get _isEnabled => onPressed != null;
17 |
18 | /// Combines the first character of the [name]'s first and last names.
19 | String get _initials {
20 | final charsByPart = name.split(' ').map((part) => part.split('')).toList();
21 | final initialsBuffer = StringBuffer()..write(charsByPart[0][0]);
22 | if (charsByPart.length > 1) {
23 | initialsBuffer.write(charsByPart.last[0]);
24 | }
25 | return initialsBuffer.toString().toUpperCase();
26 | }
27 |
28 | @override
29 | Widget build(BuildContext context) {
30 | return Padding(
31 | padding: const EdgeInsets.symmetric(horizontal: 8),
32 | child: InkWell(
33 | onTap: onPressed,
34 | customBorder: const RoundedRectangleBorder(
35 | borderRadius: BorderRadius.all(Radius.circular(8)),
36 | ),
37 | child: ListTile(
38 | contentPadding: const EdgeInsets.symmetric(horizontal: 8),
39 | enabled: _isEnabled,
40 | leading: CircleAvatar(
41 | backgroundColor: _isEnabled ? Colors.blue.shade900 : Colors.grey,
42 | foregroundColor: Colors.white,
43 | child: Text(_initials),
44 | ),
45 | title: Text(name),
46 | subtitle: Text(email),
47 | ),
48 | ),
49 | );
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/example/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
7 |
15 |
19 |
23 |
24 |
25 |
26 |
27 |
28 |
30 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/test/smoke_tests/interactions_smoke_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:alchemist/alchemist.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_test/flutter_test.dart';
4 |
5 | void main() {
6 | group('smoke test', () {
7 | GoldenTestGroup buildSmokeTestGroup() {
8 | return GoldenTestGroup(
9 | children: [
10 | GoldenTestScenario(name: 'scenario_text', child: const Text('text')),
11 | GoldenTestScenario(
12 | name: 'scenario_button',
13 | child: TextButton(
14 | style: const ButtonStyle(
15 | backgroundColor: WidgetStatePropertyAll(Color(0xFF2196F3)),
16 | foregroundColor: WidgetStatePropertyAll(Color(0xFFFFFFFF)),
17 | shadowColor: WidgetStatePropertyAll(Color(0xFFFF0000)),
18 | surfaceTintColor: WidgetStatePropertyAll(Color(0xFF00FF00)),
19 | overlayColor: WidgetStatePropertyAll(Color(0xFF0000FF)),
20 | ),
21 | onPressed: () {},
22 | onLongPress: () {},
23 | child: const Text('button'),
24 | ),
25 | ),
26 | ],
27 | );
28 | }
29 |
30 | goldenTest(
31 | 'succeeds in regular state',
32 | fileName: 'interactions_smoke_test_regular',
33 | builder: buildSmokeTestGroup,
34 | );
35 |
36 | goldenTest(
37 | 'succeeds while pressed',
38 | fileName: 'interactions_smoke_test_pressed',
39 | whilePerforming: press(find.byType(TextButton)),
40 | builder: buildSmokeTestGroup,
41 | );
42 |
43 | goldenTest(
44 | 'succeeds while long pressed',
45 | fileName: 'interactions_smoke_test_long_pressed',
46 | whilePerforming: longPress(find.byType(TextButton)),
47 | builder: buildSmokeTestGroup,
48 | );
49 | });
50 | }
51 |
--------------------------------------------------------------------------------
/lib/src/alchemist_test_variant.dart:
--------------------------------------------------------------------------------
1 | import 'package:alchemist/src/alchemist_config.dart';
2 | import 'package:alchemist/src/host_platform.dart';
3 | import 'package:flutter/widgets.dart';
4 | import 'package:flutter_test/flutter_test.dart';
5 |
6 | /// {@template alchemist_test_variant}
7 | /// A [TestVariant] used to run both CI and platform golden tests with one
8 | /// [testWidgets] function.
9 | /// {@endtemplate}
10 | @visibleForTesting
11 | class AlchemistTestVariant extends TestVariant {
12 | /// {@macro alchemist_test_variant}
13 | AlchemistTestVariant({
14 | required AlchemistConfig config,
15 | required HostPlatform currentPlatform,
16 | }) : _config = config,
17 | _currentPlatform = currentPlatform;
18 |
19 | final AlchemistConfig _config;
20 | final HostPlatform _currentPlatform;
21 |
22 | /// The [GoldensConfig] to use for the current variant
23 | GoldensConfig get currentConfig => _currentConfig;
24 | late GoldensConfig _currentConfig;
25 |
26 | @override
27 | String describeValue(GoldensConfig value) => value.environmentName;
28 |
29 | @override
30 | Future setUp(GoldensConfig value) async {
31 | _currentConfig = value;
32 | }
33 |
34 | @override
35 | Future tearDown(
36 | GoldensConfig value,
37 | covariant AlchemistTestVariant? memento,
38 | ) async {
39 | imageCache.clear();
40 | }
41 |
42 | @override
43 | Iterable get values {
44 | final platformConfig = _config.platformGoldensConfig;
45 | final runPlatformTest =
46 | platformConfig.enabled &&
47 | platformConfig.platforms.contains(_currentPlatform);
48 |
49 | final ciConfig = _config.ciGoldensConfig;
50 | final runCiTest = ciConfig.enabled;
51 |
52 | return {if (runPlatformTest) platformConfig, if (runCiTest) ciConfig};
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/example/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled.
5 |
6 | version:
7 | revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
8 | channel: stable
9 |
10 | project_type: app
11 |
12 | # Tracks metadata for the flutter migrate command
13 | migration:
14 | platforms:
15 | - platform: root
16 | create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
17 | base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
18 | - platform: android
19 | create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
20 | base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
21 | - platform: ios
22 | create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
23 | base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
24 | - platform: linux
25 | create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
26 | base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
27 | - platform: macos
28 | create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
29 | base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
30 | - platform: web
31 | create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
32 | base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
33 | - platform: windows
34 | create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
35 | base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
36 |
37 | # User provided section
38 |
39 | # List of Local paths (relative to this file) that should be
40 | # ignored by the migrate tool.
41 | #
42 | # Files that are not part of the templates will be ignored by default.
43 | unmanaged_files:
44 | - 'lib/main.dart'
45 | - 'ios/Runner.xcodeproj/project.pbxproj'
46 |
--------------------------------------------------------------------------------
/example/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CADisableMinimumFrameDurationOnPhone
6 |
7 | CFBundleDevelopmentRegion
8 | $(DEVELOPMENT_LANGUAGE)
9 | CFBundleDisplayName
10 | Example
11 | CFBundleExecutable
12 | $(EXECUTABLE_NAME)
13 | CFBundleIdentifier
14 | $(PRODUCT_BUNDLE_IDENTIFIER)
15 | CFBundleInfoDictionaryVersion
16 | 6.0
17 | CFBundleName
18 | example
19 | CFBundlePackageType
20 | APPL
21 | CFBundleShortVersionString
22 | $(FLUTTER_BUILD_NAME)
23 | CFBundleSignature
24 | ????
25 | CFBundleVersion
26 | $(FLUTTER_BUILD_NUMBER)
27 | LSRequiresIPhoneOS
28 |
29 | UILaunchStoryboardName
30 | LaunchScreen
31 | UIMainStoryboardFile
32 | Main
33 | UISupportedInterfaceOrientations
34 |
35 | UIInterfaceOrientationPortrait
36 | UIInterfaceOrientationLandscapeLeft
37 | UIInterfaceOrientationLandscapeRight
38 |
39 | UISupportedInterfaceOrientations~ipad
40 |
41 | UIInterfaceOrientationPortrait
42 | UIInterfaceOrientationPortraitUpsideDown
43 | UIInterfaceOrientationLandscapeLeft
44 | UIInterfaceOrientationLandscapeRight
45 |
46 | UIViewControllerBasedStatusBarAppearance
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/example/windows/runner/utils.cpp:
--------------------------------------------------------------------------------
1 | #include "utils.h"
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | #include
9 |
10 | void CreateAndAttachConsole() {
11 | if (::AllocConsole()) {
12 | FILE *unused;
13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) {
14 | _dup2(_fileno(stdout), 1);
15 | }
16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) {
17 | _dup2(_fileno(stdout), 2);
18 | }
19 | std::ios::sync_with_stdio();
20 | FlutterDesktopResyncOutputStreams();
21 | }
22 | }
23 |
24 | std::vector GetCommandLineArguments() {
25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use.
26 | int argc;
27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
28 | if (argv == nullptr) {
29 | return std::vector();
30 | }
31 |
32 | std::vector command_line_arguments;
33 |
34 | // Skip the first argument as it's the binary name.
35 | for (int i = 1; i < argc; i++) {
36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i]));
37 | }
38 |
39 | ::LocalFree(argv);
40 |
41 | return command_line_arguments;
42 | }
43 |
44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) {
45 | if (utf16_string == nullptr) {
46 | return std::string();
47 | }
48 | int target_length = ::WideCharToMultiByte(
49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
50 | -1, nullptr, 0, nullptr, nullptr);
51 | std::string utf8_string;
52 | if (target_length == 0 || target_length > utf8_string.max_size()) {
53 | return utf8_string;
54 | }
55 | utf8_string.resize(target_length);
56 | int converted_length = ::WideCharToMultiByte(
57 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,
58 | -1, utf8_string.data(),
59 | target_length, nullptr, nullptr);
60 | if (converted_length == 0) {
61 | return std::string();
62 | }
63 | return utf8_string;
64 | }
65 |
--------------------------------------------------------------------------------
/example/windows/runner/flutter_window.cpp:
--------------------------------------------------------------------------------
1 | #include "flutter_window.h"
2 |
3 | #include
4 |
5 | #include "flutter/generated_plugin_registrant.h"
6 |
7 | FlutterWindow::FlutterWindow(const flutter::DartProject& project)
8 | : project_(project) {}
9 |
10 | FlutterWindow::~FlutterWindow() {}
11 |
12 | bool FlutterWindow::OnCreate() {
13 | if (!Win32Window::OnCreate()) {
14 | return false;
15 | }
16 |
17 | RECT frame = GetClientArea();
18 |
19 | // The size here must match the window dimensions to avoid unnecessary surface
20 | // creation / destruction in the startup path.
21 | flutter_controller_ = std::make_unique(
22 | frame.right - frame.left, frame.bottom - frame.top, project_);
23 | // Ensure that basic setup of the controller was successful.
24 | if (!flutter_controller_->engine() || !flutter_controller_->view()) {
25 | return false;
26 | }
27 | RegisterPlugins(flutter_controller_->engine());
28 | SetChildContent(flutter_controller_->view()->GetNativeWindow());
29 | return true;
30 | }
31 |
32 | void FlutterWindow::OnDestroy() {
33 | if (flutter_controller_) {
34 | flutter_controller_ = nullptr;
35 | }
36 |
37 | Win32Window::OnDestroy();
38 | }
39 |
40 | LRESULT
41 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
42 | WPARAM const wparam,
43 | LPARAM const lparam) noexcept {
44 | // Give Flutter, including plugins, an opportunity to handle window messages.
45 | if (flutter_controller_) {
46 | std::optional result =
47 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
48 | lparam);
49 | if (result) {
50 | return *result;
51 | }
52 | }
53 |
54 | switch (message) {
55 | case WM_FONTCHANGE:
56 | flutter_controller_->engine()->ReloadSystemFonts();
57 | break;
58 | }
59 |
60 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
61 | }
62 |
--------------------------------------------------------------------------------
/example/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | example
33 |
34 |
35 |
39 |
40 |
41 |
42 |
43 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Alchemist
2 |
3 | **/failures/**/*.png
4 |
5 | # Miscellaneous
6 | *.class
7 | *.log
8 | *.pyc
9 | *.swp
10 | .DS_Store
11 | .atom/
12 | .buildlog/
13 | .history
14 | .svn/
15 |
16 | # IntelliJ related
17 | *.iml
18 | *.ipr
19 | *.iws
20 | .idea/
21 |
22 | # The .vscode folder contains launch configuration and tasks you configure in
23 | # VS Code which you may wish to be included in version control, so this line
24 | # is commented out by default.
25 | #.vscode/
26 |
27 | # Flutter/Dart/Pub related
28 | **/doc/api/
29 | .dart_tool/
30 | .flutter-plugins
31 | .flutter-plugins-dependencies
32 | .packages
33 | .pub-cache/
34 | .pub/
35 | build/
36 | coverage/
37 | pubspec.lock
38 | config/
39 |
40 | # Android related
41 | **/android/**/gradle-wrapper.jar
42 | **/android/.gradle
43 | **/android/captures/
44 | **/android/gradlew
45 | **/android/gradlew.bat
46 | **/android/local.properties
47 | **/android/**/GeneratedPluginRegistrant.java
48 |
49 | # iOS/XCode related
50 | **/ios/**/*.mode1v3
51 | **/ios/**/*.mode2v3
52 | **/ios/**/*.moved-aside
53 | **/ios/**/*.pbxuser
54 | **/ios/**/*.perspectivev3
55 | **/ios/**/*sync/
56 | **/ios/**/.sconsign.dblite
57 | **/ios/**/.tags*
58 | **/ios/**/.vagrant/
59 | **/ios/**/DerivedData/
60 | **/ios/**/Icon?
61 | **/ios/**/Pods/
62 | **/ios/**/.symlinks/
63 | **/ios/**/profile
64 | **/ios/**/xcuserdata
65 | **/ios/.generated/
66 | **/ios/Flutter/App.framework
67 | **/ios/Flutter/Flutter.framework
68 | **/ios/Flutter/Flutter.podspec
69 | **/ios/Flutter/Generated.xcconfig
70 | **/ios/Flutter/ephemeral
71 | **/ios/Flutter/app.flx
72 | **/ios/Flutter/app.zip
73 | **/ios/Flutter/flutter_assets/
74 | **/ios/Flutter/flutter_export_environment.sh
75 | **/ios/ServiceDefinitions.json
76 | **/ios/Runner/GeneratedPluginRegistrant.*
77 |
78 | # Exceptions to above rules.
79 | !**/ios/**/default.mode1v3
80 | !**/ios/**/default.mode2v3
81 | !**/ios/**/default.pbxuser
82 | !**/ios/**/default.perspectivev3
83 |
84 | # Ignore platform-specific goldens
85 | **/goldens/macos
86 | **/goldens/linux
87 | **/goldens/windows
88 |
89 | # FVM Version Cache
90 | .fvm/
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to Alchemist
2 |
3 | Thanks for checking out `alchemist`! Your contributions are greatly appreciated 🎉.
4 | The following guidelines should get you started out on your path towards contribution.
5 |
6 | ## Creating a Bug Report
7 |
8 | If you've found a bug, [create an issue using the bug report template][bug_report_template] rather than immediately opening a pull request. This allows us to triage the issue as necessary and discuss potential solutions. Please try to provide as much information as possible, including detailed reproduction steps. Once one of the package maintainers has reviewed the issue and an agreement is reached regarding the fix, a pull request can be created.
9 |
10 | ## Creating a Feature Request
11 |
12 | Use the built-in [Feature Request template][feature_request_template] to add in any relevant details with your request. Once one of the package maintainers has reviewed the issue and triaged it, a pull request can be created.
13 |
14 | ## Creating a Pull Request
15 |
16 | Before creating a pull request please:
17 |
18 | 1. Fork the repository and create your branch from `main`.
19 | 2. Install all dependencies (`flutter pub get`).
20 | 3. Make your changes.
21 | 4. Add tests — only PR's with 100% test coverage are accepted!
22 | 5. Ensure the existing test suite passes locally.
23 | 6. Format your code (`dart format .`).
24 | 7. Analyze your code (`dart analyze --fatal-infos --fatal-warnings .`).
25 | 8. Create the Pull Request with [semantic title](https://github.com/zeke/semantic-pull-requests).
26 | 9. Verify that all status checks are passing.
27 |
28 | If you are fixing an issue in Alchemist, add a smoke test in `test/smoke_tests` that tests a widget which replicates the issue you are fixing to prevent future regressions.
29 |
30 | ## License
31 |
32 | This packages uses the [MIT license](https://github.com/Betterment/alchemist/blob/main/LICENSE)
33 |
34 | [bug_report_template]: https://github.com/Betterment/alchemist/issues/new?assignees=&labels=&template=bug_report.yaml
35 | [feature_request_template]: https://github.com/Betterment/alchemist/issues/new?assignees=&labels=&template=feature_request.yaml
36 |
--------------------------------------------------------------------------------
/test/src/golden_test_scenario_constraints_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:alchemist/src/golden_test_scenario_constraints.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:flutter_test/flutter_test.dart';
4 |
5 | void main() {
6 | group('GoldenTestScenarioConstraints', () {
7 | const child = SizedBox();
8 | group('updateShouldNotify', () {
9 | test('returns false when constraints are the same', () {
10 | const newWidget = GoldenTestScenarioConstraints(
11 | constraints: BoxConstraints(maxHeight: 400),
12 | child: child,
13 | );
14 | const oldWidget = GoldenTestScenarioConstraints(
15 | constraints: BoxConstraints(maxHeight: 400),
16 | child: child,
17 | );
18 |
19 | expect(newWidget.updateShouldNotify(oldWidget), isFalse);
20 | });
21 |
22 | test('returns true when constraints are different', () {
23 | const newWidget = GoldenTestScenarioConstraints(
24 | constraints: BoxConstraints(maxHeight: 400),
25 | child: child,
26 | );
27 | const oldWidget = GoldenTestScenarioConstraints(
28 | constraints: BoxConstraints(maxWidth: 400),
29 | child: child,
30 | );
31 |
32 | expect(newWidget.updateShouldNotify(oldWidget), isTrue);
33 | });
34 | });
35 |
36 | group('maybeOf', () {
37 | testWidgets('returns the constraints from the nearest widget', (
38 | tester,
39 | ) async {
40 | late BoxConstraints? constraints;
41 | await tester.pumpWidget(
42 | GoldenTestScenarioConstraints(
43 | constraints: const BoxConstraints(maxHeight: 200),
44 | child: GoldenTestScenarioConstraints(
45 | constraints: const BoxConstraints(minWidth: 200),
46 | child: Builder(
47 | builder: (context) {
48 | constraints = GoldenTestScenarioConstraints.maybeOf(context);
49 | return const SizedBox();
50 | },
51 | ),
52 | ),
53 | ),
54 | );
55 |
56 | expect(constraints, equals(const BoxConstraints(minWidth: 200)));
57 | });
58 | });
59 | });
60 | }
61 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yaml:
--------------------------------------------------------------------------------
1 | name: Bug Report
2 | description: Create a report to help us improve.
3 | title: "fix: "
4 | labels: ["bug", "triage"]
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: |
9 | Thanks for taking the time to fill out this bug report!
10 |
11 | - type: checkboxes
12 | attributes:
13 | label: Is there an existing issue for this?
14 | description: |
15 | Please search to see if an issue already exists for the bug you encountered.
16 | options:
17 | - label: I have searched the existing issues.
18 | required: true
19 |
20 | - type: input
21 | id: version
22 | validations:
23 | required: true
24 | attributes:
25 | label: Version
26 | description: |
27 | What version are you running?
28 | placeholder: "1.2.0"
29 |
30 | - type: textarea
31 | id: description
32 | validations:
33 | required: true
34 | attributes:
35 | label: Description
36 | description: |
37 | Give us a clear and concise description of what the bug is and what happened.
38 | placeholder: It throws an error if I ...
39 |
40 | - type: textarea
41 | id: reproduction
42 | validations:
43 | required: true
44 | attributes:
45 | label: Steps to reproduce
46 | description: |
47 | What steps can we take to reproduce the bug?
48 | placeholder: |
49 | 1. When golden tests are setup with ...
50 | 2. It does [this] instead of [that] ...
51 | 3. I think it should do [that] because of [this].
52 |
53 | - type: textarea
54 | id: expected
55 | validations:
56 | required: true
57 | attributes:
58 | label: Expected behavior
59 | description: |
60 | What did you expect to happen?
61 | placeholder: |
62 | When running ..., it should ...
63 |
64 | - type: textarea
65 | id: screenshots
66 | validations:
67 | required: false
68 | attributes:
69 | label: Screenshots
70 | description: |
71 | If you have any screenshots, please attach them here.
72 |
73 | - type: textarea
74 | id: comments
75 | attributes:
76 | label: Additional context and comments
77 | description: |
78 | Anything else you want to say?
79 |
--------------------------------------------------------------------------------
/example/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | def localProperties = new Properties()
2 | def localPropertiesFile = rootProject.file('local.properties')
3 | if (localPropertiesFile.exists()) {
4 | localPropertiesFile.withReader('UTF-8') { reader ->
5 | localProperties.load(reader)
6 | }
7 | }
8 |
9 | def flutterRoot = localProperties.getProperty('flutter.sdk')
10 | if (flutterRoot == null) {
11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
12 | }
13 |
14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
15 | if (flutterVersionCode == null) {
16 | flutterVersionCode = '1'
17 | }
18 |
19 | def flutterVersionName = localProperties.getProperty('flutter.versionName')
20 | if (flutterVersionName == null) {
21 | flutterVersionName = '1.0'
22 | }
23 |
24 | apply plugin: 'com.android.application'
25 | apply plugin: 'kotlin-android'
26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
27 |
28 | android {
29 | compileSdkVersion flutter.compileSdkVersion
30 | ndkVersion flutter.ndkVersion
31 |
32 | compileOptions {
33 | sourceCompatibility JavaVersion.VERSION_1_8
34 | targetCompatibility JavaVersion.VERSION_1_8
35 | }
36 |
37 | kotlinOptions {
38 | jvmTarget = '1.8'
39 | }
40 |
41 | sourceSets {
42 | main.java.srcDirs += 'src/main/kotlin'
43 | }
44 |
45 | defaultConfig {
46 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
47 | applicationId "com.betterment.alchemist-example"
48 | // You can update the following values to match your application needs.
49 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
50 | minSdkVersion flutter.minSdkVersion
51 | targetSdkVersion flutter.targetSdkVersion
52 | versionCode flutterVersionCode.toInteger()
53 | versionName flutterVersionName
54 | }
55 |
56 | buildTypes {
57 | release {
58 | // TODO: Add your own signing config for the release build.
59 | // Signing with the debug keys for now, so `flutter run --release` works.
60 | signingConfig signingConfigs.debug
61 | }
62 | }
63 | }
64 |
65 | flutter {
66 | source '../..'
67 | }
68 |
69 | dependencies {
70 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
71 | }
72 |
--------------------------------------------------------------------------------
/example/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/test/src/blocked_text_painting_context_golden_test.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ui' as ui;
2 |
3 | import 'package:alchemist/src/golden_test_runner.dart';
4 | import 'package:flutter/material.dart';
5 | import 'package:flutter_test/flutter_test.dart';
6 |
7 | class _TextCustomPainter extends CustomPainter {
8 | const _TextCustomPainter();
9 |
10 | @override
11 | void paint(Canvas canvas, Size size) {
12 | final paragraphBuilder = ui.ParagraphBuilder(ui.ParagraphStyle())
13 | ..pushStyle(ui.TextStyle(color: const Color(0xFF0000FF)))
14 | ..addText('blue text');
15 | final paragraph = paragraphBuilder.build()
16 | ..layout(ui.ParagraphConstraints(width: size.width));
17 | canvas.drawParagraph(paragraph, Offset.zero);
18 | }
19 |
20 | @override
21 | bool shouldRepaint(covariant _TextCustomPainter oldDelegate) {
22 | return true;
23 | }
24 | }
25 |
26 | void main() {
27 | group('BlockedTextPaintingContext', () {
28 | const goldenFilePath = 'blocked_text_image_reference.png';
29 |
30 | Future setUpSurface(WidgetTester tester) async {
31 | final originalSize = tester.view.physicalSize;
32 | const adjustedSize = Size(250, 100);
33 |
34 | tester.view.physicalSize = adjustedSize;
35 | await tester.binding.setSurfaceSize(adjustedSize);
36 | tester.view.devicePixelRatio = 1.0;
37 |
38 | addTearDown(tester.view.resetPhysicalSize);
39 | addTearDown(() => tester.binding.setSurfaceSize(originalSize));
40 | addTearDown(tester.view.resetDevicePixelRatio);
41 | }
42 |
43 | Widget buildSubject({Key? key}) {
44 | return MaterialApp(
45 | key: key,
46 | debugShowCheckedModeBanner: false,
47 | home: const Scaffold(
48 | backgroundColor: Color(0x0f000000),
49 | body: Padding(
50 | padding: EdgeInsets.all(8),
51 | child: Column(
52 | crossAxisAlignment: CrossAxisAlignment.start,
53 | children: [
54 | Text('black text', style: TextStyle(color: Color(0xFF000000))),
55 | SizedBox(height: 3),
56 | Text('red text', style: TextStyle(color: Color(0xFFFF0000))),
57 | SizedBox(height: 3),
58 | CustomPaint(painter: _TextCustomPainter(), size: Size(250, 20)),
59 | ],
60 | ),
61 | ),
62 | ),
63 | );
64 | }
65 |
66 | testWidgets('paints paragraphs in colored blocks', (tester) async {
67 | await setUpSurface(tester);
68 |
69 | const rootKey = Key('root');
70 | await tester.pumpWidget(buildSubject(key: rootKey));
71 |
72 | final image = await goldenTestAdapter.getBlockedTextImage(
73 | finder: find.byKey(rootKey),
74 | tester: tester,
75 | );
76 |
77 | await expectLater(image, matchesGoldenFile(goldenFilePath));
78 | });
79 | });
80 | }
81 |
--------------------------------------------------------------------------------
/example/example.md:
--------------------------------------------------------------------------------
1 | # Alchemist Example
2 |
3 | ## Recommended Setup Guide
4 |
5 | For a more detailed explanation on how Betterment uses Alchemist, read the included [Recommended Setup Guide][setup-guide].
6 |
7 | ## Full Example Project
8 |
9 | A full project containing an application containing exemplary widgets and golden tests is included in the [example][example_dir] folder.
10 |
11 | ## Basic usage
12 |
13 | In your project's `test/` directory, add a file for your widget's tests. Then, write and run golden tests by using the `goldenTest` function.
14 |
15 | We recommend putting all golden tests related to the same component into a test `group`.
16 |
17 | Every `goldenTest` commonly contains a group of scenarios related to each other (for example, all scenarios that test the same constructor or widget in a particular context).
18 |
19 | This example shows a basic golden test for `ListTile`s that makes use of some of the more advanced features of the `goldenTest` API to control the output of the test.
20 |
21 | ```dart
22 | import 'package:alchemist/alchemist.dart';
23 | import 'package:flutter/material.dart';
24 | import 'package:flutter_test/flutter_test.dart';
25 |
26 | void main() {
27 | group('ListTile Golden Tests', () {
28 | goldenTest(
29 | 'renders correctly',
30 | fileName: 'list_tile',
31 | builder: () => GoldenTestGroup(
32 | scenarioConstraints: const BoxConstraints(maxWidth: 600),
33 | children: [
34 | GoldenTestScenario(
35 | name: 'with title',
36 | child: const ListTile(
37 | title: Text('ListTile.title'),
38 | ),
39 | ),
40 | GoldenTestScenario(
41 | name: 'with title and subtitle',
42 | child: const ListTile(
43 | title: Text('ListTile.title'),
44 | subtitle: Text('ListTile.subtitle'),
45 | ),
46 | ),
47 | GoldenTestScenario(
48 | name: 'with trailing icon',
49 | child: const ListTile(
50 | title: Text('ListTile.title'),
51 | trailing: Icon(Icons.chevron_right_rounded),
52 | ),
53 | ),
54 | ],
55 | ),
56 | );
57 | });
58 | }
59 | ```
60 |
61 | Then, simply run Flutter test and pass the `--update-goldens` flag to generate the golden files.
62 |
63 | ```shell
64 | flutter test --update-goldens
65 | ```
66 |
67 | ## gitignore
68 |
69 | We recommend adding the following lines to your project's `.gitignore` file to prevent platform-specific artifacts from being included in your git repository.
70 |
71 | ```gitignore
72 | # Ignore platform-specific goldens
73 | **/goldens/macos
74 | **/goldens/linux
75 | **/goldens/windows
76 | ```
77 |
78 | [setup-guide]: https://github.com/Betterment/alchemist/blob/main/RECOMMENDED_SETUP_GUIDE.md
79 | [example_dir]: https://github.com/Betterment/alchemist/tree/main/example
80 |
--------------------------------------------------------------------------------
/test/src/alchemist_test_variant_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:alchemist/src/alchemist_config.dart';
2 | import 'package:alchemist/src/alchemist_test_variant.dart';
3 | import 'package:alchemist/src/host_platform.dart';
4 | import 'package:flutter/widgets.dart';
5 | import 'package:flutter_test/flutter_test.dart';
6 | import 'package:mocktail/mocktail.dart';
7 |
8 | class MockAlchemistConfig extends Mock implements AlchemistConfig {}
9 |
10 | class MockPlatformGoldensConfig extends Mock implements PlatformGoldensConfig {}
11 |
12 | class MockCiGoldensConfig extends Mock implements CiGoldensConfig {}
13 |
14 | class FakeImageStreamCompleter extends ImageStreamCompleter {}
15 |
16 | void main() {
17 | group('AlchemistTestVariant', () {
18 | test('returns values', () {
19 | const platform = HostPlatform.linux;
20 | final ciConfig = MockCiGoldensConfig();
21 | when(() => ciConfig.enabled).thenReturn(true);
22 | final platformConfig = MockPlatformGoldensConfig();
23 | when(() => platformConfig.enabled).thenReturn(true);
24 | when(() => platformConfig.platforms).thenReturn({platform});
25 | final config = MockAlchemistConfig();
26 | when(() => config.platformGoldensConfig).thenReturn(platformConfig);
27 | when(() => config.ciGoldensConfig).thenReturn(ciConfig);
28 | final variant = AlchemistTestVariant(
29 | config: config,
30 | currentPlatform: platform,
31 | );
32 | expect(variant.values, {platformConfig, ciConfig});
33 | });
34 |
35 | group('Lifecycle', () {
36 | late AlchemistTestVariant variant;
37 | late MockAlchemistConfig config;
38 |
39 | setUp(() {
40 | config = MockAlchemistConfig();
41 | variant = AlchemistTestVariant(
42 | config: config,
43 | currentPlatform: HostPlatform.linux,
44 | );
45 | });
46 |
47 | test('instantiates', () {
48 | expect(variant, isA());
49 | });
50 |
51 | test('tearDown clears the image cache', () async {
52 | TestWidgetsFlutterBinding.ensureInitialized();
53 | imageCache.putIfAbsent('key', FakeImageStreamCompleter.new);
54 | expect(imageCache.containsKey('key'), isTrue);
55 | await variant.tearDown(MockCiGoldensConfig(), null);
56 | expect(imageCache.containsKey('key'), isFalse);
57 | });
58 |
59 | test('setUp sets current value', () async {
60 | final value = MockCiGoldensConfig();
61 | await expectLater(variant.setUp(value), completes);
62 | expect(variant.currentConfig, value);
63 | });
64 |
65 | test('describeValue returns environment name', () async {
66 | final value = MockCiGoldensConfig();
67 | const environmentName = 'TEST';
68 | when(() => value.environmentName).thenReturn(environmentName);
69 | expect(variant.describeValue(value), environmentName);
70 | });
71 | });
72 | });
73 | }
74 |
--------------------------------------------------------------------------------
/lib/src/pumps.dart:
--------------------------------------------------------------------------------
1 | import 'package:alchemist/alchemist.dart';
2 | import 'package:flutter/widgets.dart';
3 | import 'package:flutter_test/flutter_test.dart';
4 |
5 | /// A function that may perform pumping actions to prime a golden test.
6 | ///
7 | /// Used in the [goldenTest] function to perform any actions necessary to prime
8 | /// the widget tree before the golden test is compared or generated.
9 | typedef PumpAction = Future Function(WidgetTester tester);
10 |
11 | /// A function used to render a given [Widget].
12 | typedef PumpWidget = Future Function(WidgetTester tester, Widget widget);
13 |
14 | /// Returns a custom [PumpAction] that pumps the widget tree [n] times before
15 | /// golden evaluation.
16 | ///
17 | /// See [PumpAction] for more details.
18 | PumpAction pumpNTimes(int n, [Duration? duration]) {
19 | return (tester) async {
20 | for (var i = 0; i < n; i++) {
21 | await tester.pump(duration);
22 | }
23 | };
24 | }
25 |
26 | /// A custom [PumpAction] that pumps the widget tree once before golden
27 | /// evaluation.
28 | ///
29 | /// See [PumpAction] for more details.
30 | final pumpOnce = pumpNTimes(1);
31 |
32 | /// A custom [PumpAction] that pumps and settles the widget tree before golden
33 | /// evaluation.
34 | ///
35 | /// See [PumpAction] for more details.
36 | Future onlyPumpAndSettle(WidgetTester tester) => tester.pumpAndSettle();
37 |
38 | /// A custom [PumpAction] to ensure that the images for all [Image],
39 | /// [FadeInImage], and [DecoratedBox] widgets are loaded before the golden file
40 | /// is generated.
41 | ///
42 | /// See [PumpAction] for more details.
43 | Future precacheImages(WidgetTester tester) async {
44 | await tester.runAsync(() async {
45 | final images = >[];
46 | for (final element in find.byType(Image).evaluate()) {
47 | final widget = element.widget as Image;
48 | final image = widget.image;
49 | images.add(precacheImage(image, element));
50 | }
51 | for (final element in find.byType(FadeInImage).evaluate()) {
52 | final widget = element.widget as FadeInImage;
53 | final image = widget.image;
54 | images.add(precacheImage(image, element));
55 | }
56 | for (final element in find.byType(DecoratedBox).evaluate()) {
57 | final widget = element.widget as DecoratedBox;
58 | final decoration = widget.decoration;
59 | if (decoration is BoxDecoration && decoration.image != null) {
60 | final image = decoration.image!.image;
61 | images.add(precacheImage(image, element));
62 | }
63 | }
64 | await Future.wait(images);
65 | });
66 | await tester.pumpAndSettle();
67 | }
68 |
69 | /// A custom [PumpWidget] that pumps the widget tree before golden
70 | /// evaluation, analogous to [WidgetTester.pumpWidget].
71 | ///
72 | /// See [PumpWidget] for more details.
73 | Future onlyPumpWidget(WidgetTester tester, Widget widget) {
74 | return tester.pumpWidget(widget);
75 | }
76 |
--------------------------------------------------------------------------------
/lib/src/golden_test_theme.dart:
--------------------------------------------------------------------------------
1 | import 'package:alchemist/src/golden_test_group.dart';
2 | import 'package:alchemist/src/golden_test_scenario.dart';
3 | import 'package:flutter/material.dart';
4 |
5 | /// {@template golden_test_theme}
6 | /// A theme that dictates the behavior and appearance of elements created
7 | /// by Alchemist during golden testing. This theme is used to ensure that
8 | /// parts of golden tests controlled by Alchemist are consistent across
9 | /// Flutter SDK versions.
10 | /// {@endtemplate}
11 | class GoldenTestTheme extends ThemeExtension {
12 | /// {@macro golden_test_theme}
13 | GoldenTestTheme({
14 | required this.backgroundColor,
15 | required this.borderColor,
16 | required this.nameTextStyle,
17 | this.padding = EdgeInsets.zero,
18 | });
19 |
20 | /// The standard theme for golden tests, used when no other theme is provided.
21 | factory GoldenTestTheme.standard() {
22 | return GoldenTestTheme(
23 | // These colors are not tied to any implementation so they won't
24 | // change out from under us, which would cause golden tests to fail.
25 | backgroundColor: const Color(0xFF2b54a1),
26 | borderColor: const Color(0xFF3d394a),
27 | nameTextStyle: const TextStyle(fontSize: 18),
28 | );
29 | }
30 |
31 | /// The background color of the golden test.
32 | final Color backgroundColor;
33 |
34 | /// The border color used to separate scenarios in a [GoldenTestGroup].
35 | final Color borderColor;
36 |
37 | /// The padding that is used to wrap around:
38 | /// - the whole image
39 | /// - each individual [GoldenTestScenario]
40 | final EdgeInsetsGeometry padding;
41 |
42 | /// The text style that is used to show the name in a [GoldenTestScenario]
43 | final TextStyle nameTextStyle;
44 |
45 | @override
46 | ThemeExtension copyWith({
47 | Color? backgroundColor,
48 | Color? borderColor,
49 | EdgeInsetsGeometry? padding,
50 | TextStyle? nameTextStyle,
51 | }) {
52 | return GoldenTestTheme(
53 | backgroundColor: backgroundColor ?? this.backgroundColor,
54 | borderColor: borderColor ?? this.borderColor,
55 | padding: padding ?? this.padding,
56 | nameTextStyle: nameTextStyle ?? this.nameTextStyle,
57 | );
58 | }
59 |
60 | @override
61 | ThemeExtension lerp(
62 | covariant ThemeExtension? other,
63 | double t,
64 | ) {
65 | if (other is! GoldenTestTheme) {
66 | return this;
67 | }
68 | return GoldenTestTheme(
69 | backgroundColor:
70 | Color.lerp(backgroundColor, other.backgroundColor, t) ??
71 | backgroundColor,
72 | borderColor: Color.lerp(borderColor, other.borderColor, t) ?? borderColor,
73 | padding: EdgeInsetsGeometry.lerp(padding, other.padding, t) ?? padding,
74 | nameTextStyle: nameTextStyle.copyWith(
75 | color:
76 | Color.lerp(nameTextStyle.color, other.nameTextStyle.color, t) ??
77 | nameTextStyle.color,
78 | ),
79 | );
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/.github/workflows/update_goldens.yaml:
--------------------------------------------------------------------------------
1 | name: Update Goldens
2 | # ignore: RiskyTriggers
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | branch:
7 | description: "Branch to generate goldens on"
8 | required: true
9 | flutter_version:
10 | description: "Flutter version to use"
11 | required: true
12 |
13 | permissions:
14 | contents: write
15 |
16 | jobs:
17 | update_goldens:
18 | runs-on: ubuntu-latest
19 | steps:
20 | - name: Checkout repo
21 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
22 |
23 | - name: Validate branch input
24 | run: |
25 | BRANCH_PATTERN="^[a-zA-Z0-9/_.-]+$"
26 |
27 | echo "Checking branch name: $BRANCH_NAME"
28 |
29 | # Validate branch name against the regex pattern
30 | if [[ $BRANCH_NAME =~ $BRANCH_PATTERN ]]; then
31 | echo "Branch name is valid."
32 | exit 0
33 | else
34 | echo "Branch name is invalid."
35 | exit 1
36 | fi
37 | env:
38 | BRANCH_NAME: ${{ inputs.branch }}
39 |
40 | - name: Validate Flutter version input
41 | run: |
42 | VERSION_PATTERN="^[0-9]+\.[0-9]+\.[0-9]+$"
43 |
44 | echo "Checking version input: $VERSION"
45 |
46 | # Validate branch name against the regex pattern
47 | if [[ $VERSION =~ $VERSION_PATTERN ]]; then
48 | echo "Version input is valid."
49 | exit 0
50 | else
51 | echo "Version input is invalid."
52 | exit 1
53 | fi
54 | env:
55 | VERSION: ${{ inputs.flutter_version }}
56 |
57 | - name: Ensure branch is not main
58 | if: ${{ github.event.inputs.branch == 'main' || github.event.inputs.branch == 'origin/main'}}
59 | run: exit 1
60 |
61 | - name: Checkout branch
62 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
63 | with:
64 | # Branch validated above
65 | # ignore: UnsafeCheckout
66 | ref: ${{ github.event.inputs.branch }}
67 |
68 | - uses: subosito/flutter-action@e938fdf56512cc96ef2f93601a5a40bde3801046 # v2.19.0
69 | with:
70 | flutter-version: ${{ github.event.inputs.flutter_version }}
71 | channel: any
72 | cache: true
73 |
74 | - name: Disable animations
75 | run: flutter config --no-cli-animations
76 |
77 | - name: Update Goldens
78 | run: |
79 | flutter test --update-goldens
80 | continue-on-error: true
81 |
82 | - name: Commit Changes
83 | id: commit_changes
84 | env:
85 | BRANCH_NAME: ${{ github.event.inputs.branch }}
86 | run: |
87 | git config --local user.name "github-actions[bot]"
88 | git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
89 | git add .
90 | git diff-index --quiet HEAD || git commit -m "chore: Updating Goldens"
91 | git push origin HEAD:"$BRANCH_NAME"
92 |
--------------------------------------------------------------------------------
/example/linux/flutter/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # This file controls Flutter-level build steps. It should not be edited.
2 | cmake_minimum_required(VERSION 3.10)
3 |
4 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
5 |
6 | # Configuration provided via flutter tool.
7 | include(${EPHEMERAL_DIR}/generated_config.cmake)
8 |
9 | # TODO: Move the rest of this into files in ephemeral. See
10 | # https://github.com/flutter/flutter/issues/57146.
11 |
12 | # Serves the same purpose as list(TRANSFORM ... PREPEND ...),
13 | # which isn't available in 3.10.
14 | function(list_prepend LIST_NAME PREFIX)
15 | set(NEW_LIST "")
16 | foreach(element ${${LIST_NAME}})
17 | list(APPEND NEW_LIST "${PREFIX}${element}")
18 | endforeach(element)
19 | set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
20 | endfunction()
21 |
22 | # === Flutter Library ===
23 | # System-level dependencies.
24 | find_package(PkgConfig REQUIRED)
25 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
26 | pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
27 | pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
28 |
29 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
30 |
31 | # Published to parent scope for install step.
32 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
33 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
34 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
35 | set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
36 |
37 | list(APPEND FLUTTER_LIBRARY_HEADERS
38 | "fl_basic_message_channel.h"
39 | "fl_binary_codec.h"
40 | "fl_binary_messenger.h"
41 | "fl_dart_project.h"
42 | "fl_engine.h"
43 | "fl_json_message_codec.h"
44 | "fl_json_method_codec.h"
45 | "fl_message_codec.h"
46 | "fl_method_call.h"
47 | "fl_method_channel.h"
48 | "fl_method_codec.h"
49 | "fl_method_response.h"
50 | "fl_plugin_registrar.h"
51 | "fl_plugin_registry.h"
52 | "fl_standard_message_codec.h"
53 | "fl_standard_method_codec.h"
54 | "fl_string_codec.h"
55 | "fl_value.h"
56 | "fl_view.h"
57 | "flutter_linux.h"
58 | )
59 | list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
60 | add_library(flutter INTERFACE)
61 | target_include_directories(flutter INTERFACE
62 | "${EPHEMERAL_DIR}"
63 | )
64 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
65 | target_link_libraries(flutter INTERFACE
66 | PkgConfig::GTK
67 | PkgConfig::GLIB
68 | PkgConfig::GIO
69 | )
70 | add_dependencies(flutter flutter_assemble)
71 |
72 | # === Flutter tool backend ===
73 | # _phony_ is a non-existent file to force this command to run every time,
74 | # since currently there's no way to get a full input/output list from the
75 | # flutter tool.
76 | add_custom_command(
77 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
78 | ${CMAKE_CURRENT_BINARY_DIR}/_phony_
79 | COMMAND ${CMAKE_COMMAND} -E env
80 | ${FLUTTER_TOOL_ENVIRONMENT}
81 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
82 | ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
83 | VERBATIM
84 | )
85 | add_custom_target(flutter_assemble DEPENDS
86 | "${FLUTTER_LIBRARY}"
87 | ${FLUTTER_LIBRARY_HEADERS}
88 | )
89 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-App-20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-App-20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-App-29x29@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-App-29x29@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-App-40x40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-App-40x40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-App-60x60@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-App-60x60@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-App-20x20@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-App-20x20@2x.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-App-29x29@1x.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-App-29x29@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-App-40x40@1x.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-App-40x40@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-App-76x76@1x.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-App-76x76@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "Icon-App-83.5x83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "Icon-App-1024x1024@1x.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/lib/src/host_platform.dart:
--------------------------------------------------------------------------------
1 | import 'dart:io';
2 |
3 | import 'package:equatable/equatable.dart';
4 |
5 | /// Default host platform (the current host machine platform).
6 | final defaultHostPlatform = HostPlatform._realPlatform();
7 | HostPlatform _hostPlatform = defaultHostPlatform;
8 |
9 | /// Indicates the current host platform used by Alchemist.
10 | /// Can be overridden for testing. This value is utilized by
11 | /// [HostPlatform.current].
12 | HostPlatform get hostPlatform => _hostPlatform;
13 | set hostPlatform(HostPlatform value) => _hostPlatform = value;
14 |
15 | /// A class that represents a host platform that can run golden tests.
16 | ///
17 | /// The current platform can be retrieved using [HostPlatform.current], and
18 | /// checks against this value are available using [isMacOS], [isLinux] and so
19 | /// on.
20 | class HostPlatform extends Equatable implements Comparable {
21 | const HostPlatform._(this._value);
22 |
23 | /// An internal factory used to retrieve a [HostPlatform] based on the current
24 | /// real platform this program is running on.
25 | factory HostPlatform._realPlatform() {
26 | final hostByPlatform = {
27 | Platform.isMacOS: HostPlatform.macOS,
28 | Platform.isLinux: HostPlatform.linux,
29 | Platform.isWindows: HostPlatform.windows,
30 | };
31 |
32 | return hostByPlatform[true]!;
33 | }
34 |
35 | /// The current host platform.
36 | factory HostPlatform.current() {
37 | return hostPlatform;
38 | }
39 |
40 | /// The internal value used to represent the current platform.
41 | ///
42 | /// This value is also returned by [operatingSystem].
43 | final String _value;
44 |
45 | /// Returns all values [HostPlatform] can represent.
46 | static final values = {macOS, linux, windows};
47 |
48 | /// The Apple macOS platform (`"macOS"`).
49 | ///
50 | /// See [HostPlatform] for more information.
51 | static const macOS = HostPlatform._('macOS');
52 |
53 | /// The Linux platform (`"Linux"`).
54 | ///
55 | /// See [HostPlatform] for more information.
56 | static const linux = HostPlatform._('Linux');
57 |
58 | /// The Microsoft Windows platform (`"Windows"`).
59 | ///
60 | /// See [HostPlatform] for more information.
61 | static const windows = HostPlatform._('Windows');
62 |
63 | /// The name of the current platform.
64 | ///
65 | /// This will return the name of this [HostPlatform].
66 | ///
67 | /// ```dart
68 | /// HostPlatform.macOS.operatingSystem; // "macOS"
69 | /// HostPlatform.linux.operatingSystem; // "Linux"
70 | /// HostPlatform.windows.operatingSystem; // "Windows"
71 | /// ```
72 | String get operatingSystem => _value;
73 |
74 | /// Indicates whether this platform is Apple's macOS.
75 | bool get isMacOS => this == HostPlatform.macOS;
76 |
77 | /// Indicates whether this platform is Linux.
78 | bool get isLinux => this == HostPlatform.linux;
79 |
80 | /// Indicates whether this platform is Microsoft's Windows.
81 | bool get isWindows => this == HostPlatform.windows;
82 |
83 | @override
84 | String toString() {
85 | return 'HostPlatform($_value)';
86 | }
87 |
88 | @override
89 | List