diff --git a/.github/.cspell/words_dictionary.txt b/.github/.cspell/words_dictionary.txt index 5ea562a6..2ed338ad 100644 --- a/.github/.cspell/words_dictionary.txt +++ b/.github/.cspell/words_dictionary.txt @@ -9,6 +9,7 @@ dependabot # Automated dependency updates built into GitHub. deserializes # The process of reconstructing a data structure or object from a series of bytes or a string in order to instantiate the object for consumption. docusaurs # An optimized site generator in React. genhtml # Tool to generate an HTML view from LCOV coverage data files. +gobject # Base system for the GTK library used in Linux. gradlew # Wrapper tool that uses Gradle. lcov # Graphical tool for displaying coverage reports. localizable # Capable of being localized. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 04c8a012..5296713a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,32 +5,30 @@ We use [`release-please-action`][release-please-action-link] to automate version ### How it works: - ๐Ÿ“Œ **On Every Commit to main:** - - Commits are analyzed using [Conventional Commits][conventional_commits_link]. - - If a version bump is needed, a **release PR** is automatically created or updated by [`release-please-action`][release-please-action-link]. - - The **release PR** includes: - - An updated `CHANGELOG.md` - - A version bump in `brick.yaml` - - ##### ๐Ÿ’ก Notes - - - Release PR's are created **per template**. - - The GitHub Action workflow that automates the release process is configured in `.github/workflows/release_please.yaml` - - release-please settings are defined in `.release-please-config.json` and `.release-please-manifest.json` - - The release PR can be manually edited before merging. - - The release PR should be merged **ONLY** when a new release is needed. + - Commits are analyzed using [Conventional Commits][conventional_commits_link]. + - If a version bump is needed, a **release PR** is automatically created or updated by [`release-please-action`][release-please-action-link]. + - The **release PR** includes: + - An updated `CHANGELOG.md` + - A version bump in `brick.yaml` + + ##### ๐Ÿ’ก Notes + - Release PR's are created **per template**. + - The GitHub Action workflow that automates the release process is configured in `.github/workflows/release_please.yaml` + - release-please settings are defined in `.release-please-config.json` and `.release-please-manifest.json` + - The release PR can be manually edited before merging. + - The release PR should be merged **ONLY** when a new release is needed.
- โœ… **When the Release PR Is Merged:** - - A new Git tag is created. - - A GitHub Release is published with the changelog. - - A new version of the brick is automatically published in [brickhub.dev][brickhub_link]. + - A new Git tag is created. + - A GitHub Release is published with the changelog. + - A new version of the brick is automatically published in [brickhub.dev][brickhub_link]. - #### ๐Ÿ’ก Notes - - - GitHub Releases are created **per template**. - - The publishing process is automatically triggered when a version tag is created. - - The automated publishing workflows to [brickhub.dev][brickhub_link] are defined in `.github/workflows/publish_.yaml` + #### ๐Ÿ’ก Notes + - GitHub Releases are created **per template**. + - The publishing process is automatically triggered when a version tag is created. + - The automated publishing workflows to [brickhub.dev][brickhub_link] are defined in `.github/workflows/publish_.yaml`
@@ -40,4 +38,4 @@ This document provides a good summary of how it works and how we use it, but we [brickhub_publishing_link]: https://docs.brickhub.dev/mason-publish [sem_ver_link]: https://semver.org/ [release-please-action-link]: https://github.com/googleapis/release-please-action -[conventional_commits_link]: https://www.conventionalcommits.org/en/v1.0.0 \ No newline at end of file +[conventional_commits_link]: https://www.conventionalcommits.org/en/v1.0.0 diff --git a/README.md b/README.md index 7b55531a..0a5283bc 100644 --- a/README.md +++ b/README.md @@ -22,9 +22,9 @@ Collection of open source templates created and maintained by the [Very Good Ven ### Deprecated Templates -| Template | Description | -| -------------------------------------------- | ------------------------------------------------------------------ | -| [very_good_wear_app](very_good_wear_app/) | A Very Good WearOS Flutter app. **Deprecated** -- no longer maintained. | +| Template | Description | +| ----------------------------------------- | ----------------------------------------------------------------------- | +| [very_good_wear_app](very_good_wear_app/) | A Very Good WearOS Flutter app. **Deprecated** -- no longer maintained. | @@ -94,5 +94,3 @@ Collection of open source templates created and maintained by the [Very Good Ven [very_good_flutter_plugin_workflow_link]: https://github.com/VeryGoodOpenSource/very_good_templates/actions/workflows/very_good_flutter_plugin.yaml?query=branch%3Amain [very_good_flutter_plugin_code_link]: https://github.com/VeryGoodOpenSource/very_good_templates/tree/main/very_good_flutter_plugin [very_good_flutter_plugin_docs_link]: https://cli.vgv.dev/docs/templates/federated_plugin - - diff --git a/very_good_dart_package/.gitignore b/very_good_dart_package/.gitignore index e2f4add9..7991ea7e 100644 --- a/very_good_dart_package/.gitignore +++ b/very_good_dart_package/.gitignore @@ -6,6 +6,7 @@ .dart_tool/ .packages pubspec.lock +build/ # Files and directories created by mason .mason/ diff --git a/very_good_flutter_plugin/__brick__/README.md b/very_good_flutter_plugin/__brick__/README.md index 10170792..d05a0918 100644 --- a/very_good_flutter_plugin/__brick__/README.md +++ b/very_good_flutter_plugin/__brick__/README.md @@ -13,11 +13,10 @@ A Very Good Flutter Federated Plugin created by the [Very Good Ventures Team][ve Generated by the [Very Good CLI][very_good_cli_link]. ๐Ÿค– +## Integration tests ๐Ÿงช -### Integration tests ๐Ÿงช - -Very Good Flutter Plugin uses [fluttium][fluttium_link] for integration tests. Those tests are located -in the front facing package `{{project_name.snakeCase()}}` example. +Very Good Flutter Plugin uses [fluttium][fluttium_link] for integration tests. Those tests are located +in the front facing package `{{project_name.snakeCase()}}` example. **โ— In order to run the integration tests, you need to have the `fluttium_cli` installed. [See how][fluttium_install].** @@ -40,4 +39,4 @@ fluttium test flows/test_platform_name.yaml [very_good_ventures_link_dark]: https://verygood.ventures/?utm_source=github&utm_medium=banner&utm_campaign=core#gh-dark-mode-only [very_good_ventures_link_light]: https://verygood.ventures/?utm_source=github&utm_medium=banner&utm_campaign=core#gh-light-mode-only [fluttium_link]: https://fluttium.dev/ -[fluttium_install]: https://fluttium.dev/docs/getting-started/installing-cli \ No newline at end of file +[fluttium_install]: https://fluttium.dev/docs/getting-started/installing-cli diff --git a/very_good_flutter_plugin/__brick__/{{#android}}{{project_name.snakeCase()}}_android{{/android}}/analysis_options.yaml b/very_good_flutter_plugin/__brick__/{{#android}}{{project_name.snakeCase()}}_android{{/android}}/analysis_options.yaml index 9df80aa4..c5dcf888 100644 --- a/very_good_flutter_plugin/__brick__/{{#android}}{{project_name.snakeCase()}}_android{{/android}}/analysis_options.yaml +++ b/very_good_flutter_plugin/__brick__/{{#android}}{{project_name.snakeCase()}}_android{{/android}}/analysis_options.yaml @@ -1 +1,4 @@ include: package:very_good_analysis/analysis_options.yaml +analyzer: + exclude: + - "**/*.g.dart" diff --git a/very_good_flutter_plugin/__brick__/{{#android}}{{project_name.snakeCase()}}_android{{/android}}/android/src/main/kotlin/{{org_name.pathCase()}}/{{project_name.pascalCase()}}Plugin.kt b/very_good_flutter_plugin/__brick__/{{#android}}{{project_name.snakeCase()}}_android{{/android}}/android/src/main/kotlin/{{org_name.pathCase()}}/{{project_name.pascalCase()}}Plugin.kt index 6ff70458..32bf8bff 100644 --- a/very_good_flutter_plugin/__brick__/{{#android}}{{project_name.snakeCase()}}_android{{/android}}/android/src/main/kotlin/{{org_name.pathCase()}}/{{project_name.pascalCase()}}Plugin.kt +++ b/very_good_flutter_plugin/__brick__/{{#android}}{{project_name.snakeCase()}}_android{{/android}}/android/src/main/kotlin/{{org_name.pathCase()}}/{{project_name.pascalCase()}}Plugin.kt @@ -1,30 +1,22 @@ package {{org_name.dotCase()}} -import androidx.annotation.NonNull - +import {{project_name.pascalCase()}}Api import io.flutter.embedding.engine.plugins.FlutterPlugin -import io.flutter.plugin.common.MethodCall -import io.flutter.plugin.common.MethodChannel -import io.flutter.plugin.common.MethodChannel.MethodCallHandler -import io.flutter.plugin.common.MethodChannel.Result -class {{project_name.pascalCase()}}Plugin : FlutterPlugin, MethodCallHandler { - private lateinit var channel: MethodChannel +class {{project_name.pascalCase()}}Plugin : FlutterPlugin, {{project_name.pascalCase()}}Api { + companion object { + private const val TAG = "{{project_name.pascalCase()}}Plugin" + } - override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { - channel = MethodChannel(flutterPluginBinding.binaryMessenger, "{{project_name.snakeCase()}}_android") - channel.setMethodCallHandler(this) + override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { + {{project_name.pascalCase()}}Api.setUp(binding.binaryMessenger, this) } - override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { - if (call.method == "getPlatformName") { - result.success("Android ${android.os.Build.VERSION.RELEASE}") - } else { - result.notImplemented() - } + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { + {{project_name.pascalCase()}}Api.setUp(binding.binaryMessenger, null) } - override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { - channel.setMethodCallHandler(null) + override fun getPlatformName(callback: (Result) -> Unit) { + callback(Result.success("Android ${android.os.Build.VERSION.RELEASE}")) } } \ No newline at end of file diff --git a/very_good_flutter_plugin/__brick__/{{#android}}{{project_name.snakeCase()}}_android{{/android}}/lib/{{project_name.snakeCase()}}_android.dart b/very_good_flutter_plugin/__brick__/{{#android}}{{project_name.snakeCase()}}_android{{/android}}/lib/{{project_name.snakeCase()}}_android.dart index 14ea9fc0..13cc7615 100644 --- a/very_good_flutter_plugin/__brick__/{{#android}}{{project_name.snakeCase()}}_android{{/android}}/lib/{{project_name.snakeCase()}}_android.dart +++ b/very_good_flutter_plugin/__brick__/{{#android}}{{project_name.snakeCase()}}_android{{/android}}/lib/{{project_name.snakeCase()}}_android.dart @@ -1,15 +1,18 @@ import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart'; +import 'package:{{project_name.snakeCase()}}_android/src/messages.g.dart'; import 'package:{{project_name.snakeCase()}}_platform_interface/{{project_name.snakeCase()}}_platform_interface.dart'; +/// {@template {{project_name.snakeCase()}}_android} /// The Android implementation of [{{project_name.pascalCase()}}Platform]. -class {{project_name.pascalCase()}}Android - extends {{project_name.pascalCase()}}Platform { - /// The method channel used to interact with the native platform. - @visibleForTesting - final methodChannel = const MethodChannel( - '{{project_name.snakeCase()}}_android', - ); +/// {@endtemplate} +class {{project_name.pascalCase()}}Android extends {{project_name.pascalCase()}}Platform { + /// {@macro {{project_name.snakeCase()}}_android} + {{project_name.pascalCase()}}Android({ + @visibleForTesting {{project_name.pascalCase()}}Api? api, + }) : api = api ?? {{project_name.pascalCase()}}Api(); + + /// The API used to interact with the native platform. + final {{project_name.pascalCase()}}Api api; /// Registers this class as the default instance of /// [{{project_name.pascalCase()}}Platform]. @@ -20,6 +23,6 @@ class {{project_name.pascalCase()}}Android @override Future getPlatformName() { - return methodChannel.invokeMethod('getPlatformName'); + return api.getPlatformName(); } } diff --git a/very_good_flutter_plugin/__brick__/{{#android}}{{project_name.snakeCase()}}_android{{/android}}/pigeons/copyright.txt b/very_good_flutter_plugin/__brick__/{{#android}}{{project_name.snakeCase()}}_android{{/android}}/pigeons/copyright.txt new file mode 100644 index 00000000..edc1c28e --- /dev/null +++ b/very_good_flutter_plugin/__brick__/{{#android}}{{project_name.snakeCase()}}_android{{/android}}/pigeons/copyright.txt @@ -0,0 +1 @@ +Copyright (c) {{currentYear}}, {{org_name.titleCase()}} diff --git a/very_good_flutter_plugin/__brick__/{{#android}}{{project_name.snakeCase()}}_android{{/android}}/pigeons/messages.dart b/very_good_flutter_plugin/__brick__/{{#android}}{{project_name.snakeCase()}}_android{{/android}}/pigeons/messages.dart new file mode 100644 index 00000000..9dfaf653 --- /dev/null +++ b/very_good_flutter_plugin/__brick__/{{#android}}{{project_name.snakeCase()}}_android{{/android}}/pigeons/messages.dart @@ -0,0 +1,19 @@ +// {{project_name.pascalCase()}}Api must be abstract. +// ignore_for_file: one_member_abstracts + +import 'package:pigeon/pigeon.dart'; + +@ConfigurePigeon( + PigeonOptions( + dartOut: 'lib/src/messages.g.dart', + dartPackageName: '{{project_name.snakeCase()}}', + kotlinOut: 'android/src/main/kotlin/{{org_name.pathCase()}}/Messages.g.kt', + kotlinOptions: KotlinOptions(), + copyrightHeader: 'pigeons/copyright.txt', + ), +) +@HostApi() +abstract class {{project_name.pascalCase()}}Api { + @async + String? getPlatformName(); +} diff --git a/very_good_flutter_plugin/__brick__/{{#android}}{{project_name.snakeCase()}}_android{{/android}}/pubspec.yaml b/very_good_flutter_plugin/__brick__/{{#android}}{{project_name.snakeCase()}}_android{{/android}}/pubspec.yaml index d0dd6078..5752451e 100644 --- a/very_good_flutter_plugin/__brick__/{{#android}}{{project_name.snakeCase()}}_android{{/android}}/pubspec.yaml +++ b/very_good_flutter_plugin/__brick__/{{#android}}{{project_name.snakeCase()}}_android{{/android}}/pubspec.yaml @@ -24,5 +24,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + mocktail: ^1.0.4 + pigeon: ^26.0.2 plugin_platform_interface: ^2.1.8 very_good_analysis: ^10.2.0 diff --git a/very_good_flutter_plugin/__brick__/{{#android}}{{project_name.snakeCase()}}_android{{/android}}/test/{{project_name.snakeCase()}}_android_test.dart b/very_good_flutter_plugin/__brick__/{{#android}}{{project_name.snakeCase()}}_android{{/android}}/test/{{project_name.snakeCase()}}_android_test.dart index 5f74c50c..237619a0 100644 --- a/very_good_flutter_plugin/__brick__/{{#android}}{{project_name.snakeCase()}}_android{{/android}}/test/{{project_name.snakeCase()}}_android_test.dart +++ b/very_good_flutter_plugin/__brick__/{{#android}}{{project_name.snakeCase()}}_android{{/android}}/test/{{project_name.snakeCase()}}_android_test.dart @@ -1,32 +1,23 @@ -import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:{{project_name.snakeCase()}}_android/src/messages.g.dart'; import 'package:{{project_name.snakeCase()}}_android/{{project_name.snakeCase()}}_android.dart'; import 'package:{{project_name.snakeCase()}}_platform_interface/{{project_name.snakeCase()}}_platform_interface.dart'; +class _Mock{{project_name.pascalCase()}}Api extends Mock + implements {{project_name.pascalCase()}}Api {} + void main() { TestWidgetsFlutterBinding.ensureInitialized(); - group('{{project_name.pascalCase()}}Android', () { + group({{project_name.pascalCase()}}Android, () { const kPlatformName = 'Android'; late {{project_name.pascalCase()}}Android {{project_name.camelCase()}}; - late List log; - - setUp(() async { - {{project_name.camelCase()}} = {{project_name.pascalCase()}}Android(); - - log = []; - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler({{project_name.camelCase()}}.methodChannel, ( - methodCall, - ) async { - log.add(methodCall); - switch (methodCall.method) { - case 'getPlatformName': - return kPlatformName; - default: - return null; - } - }); + late {{project_name.pascalCase()}}Api api; + + setUp(() { + api = _Mock{{project_name.pascalCase()}}Api(); + {{project_name.camelCase()}} = {{project_name.pascalCase()}}Android(api: api); }); test('can be registered', () { @@ -38,12 +29,14 @@ void main() { }); test('getPlatformName returns correct name', () async { - final name = await {{project_name.camelCase()}}.getPlatformName(); - expect( - log, - [isMethodCall('getPlatformName', arguments: null)], + when(api.getPlatformName).thenAnswer((_) async => kPlatformName); + + await expectLater( + {{project_name.camelCase()}}.getPlatformName(), + completion(equals(kPlatformName)), ); - expect(name, equals(kPlatformName)); + + verify(api.getPlatformName).called(1); }); }); } diff --git a/very_good_flutter_plugin/__brick__/{{#ios}}{{project_name.snakeCase()}}_ios{{/ios}}/analysis_options.yaml b/very_good_flutter_plugin/__brick__/{{#ios}}{{project_name.snakeCase()}}_ios{{/ios}}/analysis_options.yaml index 9df80aa4..c5dcf888 100644 --- a/very_good_flutter_plugin/__brick__/{{#ios}}{{project_name.snakeCase()}}_ios{{/ios}}/analysis_options.yaml +++ b/very_good_flutter_plugin/__brick__/{{#ios}}{{project_name.snakeCase()}}_ios{{/ios}}/analysis_options.yaml @@ -1 +1,4 @@ include: package:very_good_analysis/analysis_options.yaml +analyzer: + exclude: + - "**/*.g.dart" diff --git a/very_good_flutter_plugin/__brick__/{{#ios}}{{project_name.snakeCase()}}_ios{{/ios}}/ios/{{project_name.snakeCase()}}_ios/Sources/{{project_name.snakeCase()}}_ios/{{project_name.pascalCase()}}Plugin.swift b/very_good_flutter_plugin/__brick__/{{#ios}}{{project_name.snakeCase()}}_ios{{/ios}}/ios/{{project_name.snakeCase()}}_ios/Sources/{{project_name.snakeCase()}}_ios/{{project_name.pascalCase()}}Plugin.swift index 40605ee1..7834b0a7 100644 --- a/very_good_flutter_plugin/__brick__/{{#ios}}{{project_name.snakeCase()}}_ios{{/ios}}/ios/{{project_name.snakeCase()}}_ios/Sources/{{project_name.snakeCase()}}_ios/{{project_name.pascalCase()}}Plugin.swift +++ b/very_good_flutter_plugin/__brick__/{{#ios}}{{project_name.snakeCase()}}_ios{{/ios}}/ios/{{project_name.snakeCase()}}_ios/Sources/{{project_name.snakeCase()}}_ios/{{project_name.pascalCase()}}Plugin.swift @@ -1,14 +1,14 @@ import Flutter -import UIKit -public class {{project_name.pascalCase()}}Plugin: NSObject, FlutterPlugin { +public class {{project_name.pascalCase()}}Plugin: NSObject, FlutterPlugin, {{project_name.pascalCase()}}Api { public static func register(with registrar: FlutterPluginRegistrar) { - let channel = FlutterMethodChannel(name: "{{project_name.snakeCase()}}_ios", binaryMessenger: registrar.messenger()) + let binaryMessenger = registrar.messenger() let instance = {{project_name.pascalCase()}}Plugin() - registrar.addMethodCallDelegate(instance, channel: channel) + {{project_name.pascalCase()}}ApiSetup.setUp(binaryMessenger: binaryMessenger, api: instance) + registrar.publish(instance) } - public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - result("iOS") + func getPlatformName(completion: @escaping (Result) -> Void) { + completion(.success("iOS")) } } diff --git a/very_good_flutter_plugin/__brick__/{{#ios}}{{project_name.snakeCase()}}_ios{{/ios}}/lib/{{project_name.snakeCase()}}_ios.dart b/very_good_flutter_plugin/__brick__/{{#ios}}{{project_name.snakeCase()}}_ios{{/ios}}/lib/{{project_name.snakeCase()}}_ios.dart index 0fe4255c..2d0dd172 100644 --- a/very_good_flutter_plugin/__brick__/{{#ios}}{{project_name.snakeCase()}}_ios{{/ios}}/lib/{{project_name.snakeCase()}}_ios.dart +++ b/very_good_flutter_plugin/__brick__/{{#ios}}{{project_name.snakeCase()}}_ios{{/ios}}/lib/{{project_name.snakeCase()}}_ios.dart @@ -1,15 +1,18 @@ import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart'; +import 'package:{{project_name.snakeCase()}}_ios/src/messages.g.dart'; import 'package:{{project_name.snakeCase()}}_platform_interface/{{project_name.snakeCase()}}_platform_interface.dart'; +/// {@template {{project_name.snakeCase()}}_ios} /// The iOS implementation of [{{project_name.pascalCase()}}Platform]. -class {{project_name.pascalCase()}}IOS - extends {{project_name.pascalCase()}}Platform { - /// The method channel used to interact with the native platform. - @visibleForTesting - final methodChannel = const MethodChannel( - '{{project_name.snakeCase()}}_ios', - ); +/// {@endtemplate} +class {{project_name.pascalCase()}}IOS extends {{project_name.pascalCase()}}Platform { + /// {@macro {{project_name.snakeCase()}}_ios} + {{project_name.pascalCase()}}IOS({ + @visibleForTesting {{project_name.pascalCase()}}Api? api, + }) : api = api ?? {{project_name.pascalCase()}}Api(); + + /// The API used to interact with the native platform. + final {{project_name.pascalCase()}}Api api; /// Registers this class as the default instance of /// [{{project_name.pascalCase()}}Platform]. @@ -20,6 +23,6 @@ class {{project_name.pascalCase()}}IOS @override Future getPlatformName() { - return methodChannel.invokeMethod('getPlatformName'); + return api.getPlatformName(); } } diff --git a/very_good_flutter_plugin/__brick__/{{#ios}}{{project_name.snakeCase()}}_ios{{/ios}}/pigeons/copyright.txt b/very_good_flutter_plugin/__brick__/{{#ios}}{{project_name.snakeCase()}}_ios{{/ios}}/pigeons/copyright.txt new file mode 100644 index 00000000..00a4ecbb --- /dev/null +++ b/very_good_flutter_plugin/__brick__/{{#ios}}{{project_name.snakeCase()}}_ios{{/ios}}/pigeons/copyright.txt @@ -0,0 +1 @@ +Copyright (c) {{currentYear}}, {{org_name.sentenceCase()}} diff --git a/very_good_flutter_plugin/__brick__/{{#ios}}{{project_name.snakeCase()}}_ios{{/ios}}/pigeons/messages.dart b/very_good_flutter_plugin/__brick__/{{#ios}}{{project_name.snakeCase()}}_ios{{/ios}}/pigeons/messages.dart new file mode 100644 index 00000000..f2ad49f3 --- /dev/null +++ b/very_good_flutter_plugin/__brick__/{{#ios}}{{project_name.snakeCase()}}_ios{{/ios}}/pigeons/messages.dart @@ -0,0 +1,19 @@ +// {{project_name.pascalCase()}}Api must be abstract. +// ignore_for_file: one_member_abstracts + +import 'package:pigeon/pigeon.dart'; + +@ConfigurePigeon( + PigeonOptions( + dartOut: 'lib/src/messages.g.dart', + dartPackageName: '{{project_name.snakeCase()}}', + swiftOut: 'ios/{{project_name.snakeCase()}}_ios/Sources/{{project_name.snakeCase()}}_ios/Messages.g.swift', + swiftOptions: SwiftOptions(), + copyrightHeader: 'pigeons/copyright.txt', + ), +) +@HostApi() +abstract class {{project_name.pascalCase()}}Api { + @async + String? getPlatformName(); +} diff --git a/very_good_flutter_plugin/__brick__/{{#ios}}{{project_name.snakeCase()}}_ios{{/ios}}/pubspec.yaml b/very_good_flutter_plugin/__brick__/{{#ios}}{{project_name.snakeCase()}}_ios{{/ios}}/pubspec.yaml index 40855fe5..7a7ffbe5 100644 --- a/very_good_flutter_plugin/__brick__/{{#ios}}{{project_name.snakeCase()}}_ios{{/ios}}/pubspec.yaml +++ b/very_good_flutter_plugin/__brick__/{{#ios}}{{project_name.snakeCase()}}_ios{{/ios}}/pubspec.yaml @@ -23,5 +23,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + mocktail: ^1.0.4 + pigeon: ^26.0.2 plugin_platform_interface: ^2.1.8 very_good_analysis: ^10.2.0 diff --git a/very_good_flutter_plugin/__brick__/{{#ios}}{{project_name.snakeCase()}}_ios{{/ios}}/test/{{project_name.snakeCase()}}_ios_test.dart b/very_good_flutter_plugin/__brick__/{{#ios}}{{project_name.snakeCase()}}_ios{{/ios}}/test/{{project_name.snakeCase()}}_ios_test.dart index c3b74995..5ff23cfa 100644 --- a/very_good_flutter_plugin/__brick__/{{#ios}}{{project_name.snakeCase()}}_ios{{/ios}}/test/{{project_name.snakeCase()}}_ios_test.dart +++ b/very_good_flutter_plugin/__brick__/{{#ios}}{{project_name.snakeCase()}}_ios{{/ios}}/test/{{project_name.snakeCase()}}_ios_test.dart @@ -1,32 +1,23 @@ -import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:{{project_name.snakeCase()}}_ios/src/messages.g.dart'; import 'package:{{project_name.snakeCase()}}_ios/{{project_name.snakeCase()}}_ios.dart'; import 'package:{{project_name.snakeCase()}}_platform_interface/{{project_name.snakeCase()}}_platform_interface.dart'; +class _Mock{{project_name.pascalCase()}}Api extends Mock + implements {{project_name.pascalCase()}}Api {} + void main() { TestWidgetsFlutterBinding.ensureInitialized(); - group('{{project_name.pascalCase()}}IOS', () { + group({{project_name.pascalCase()}}IOS, () { const kPlatformName = 'iOS'; late {{project_name.pascalCase()}}IOS {{project_name.camelCase()}}; - late List log; - - setUp(() async { - {{project_name.camelCase()}} = {{project_name.pascalCase()}}IOS(); - - log = []; - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler({{project_name.camelCase()}}.methodChannel, ( - methodCall, - ) async { - log.add(methodCall); - switch (methodCall.method) { - case 'getPlatformName': - return kPlatformName; - default: - return null; - } - }); + late {{project_name.pascalCase()}}Api api; + + setUp(() { + api = _Mock{{project_name.pascalCase()}}Api(); + {{project_name.camelCase()}} = {{project_name.pascalCase()}}IOS(api: api); }); test('can be registered', () { @@ -38,12 +29,14 @@ void main() { }); test('getPlatformName returns correct name', () async { - final name = await {{project_name.camelCase()}}.getPlatformName(); - expect( - log, - [isMethodCall('getPlatformName', arguments: null)], + when(api.getPlatformName).thenAnswer((_) async => kPlatformName); + + await expectLater( + {{project_name.camelCase()}}.getPlatformName(), + completion(equals(kPlatformName)), ); - expect(name, equals(kPlatformName)); + + verify(api.getPlatformName).called(1); }); }); } diff --git a/very_good_flutter_plugin/__brick__/{{#linux}}{{project_name.snakeCase()}}_linux{{/linux}}/analysis_options.yaml b/very_good_flutter_plugin/__brick__/{{#linux}}{{project_name.snakeCase()}}_linux{{/linux}}/analysis_options.yaml index 9df80aa4..c5dcf888 100644 --- a/very_good_flutter_plugin/__brick__/{{#linux}}{{project_name.snakeCase()}}_linux{{/linux}}/analysis_options.yaml +++ b/very_good_flutter_plugin/__brick__/{{#linux}}{{project_name.snakeCase()}}_linux{{/linux}}/analysis_options.yaml @@ -1 +1,4 @@ include: package:very_good_analysis/analysis_options.yaml +analyzer: + exclude: + - "**/*.g.dart" diff --git a/very_good_flutter_plugin/__brick__/{{#linux}}{{project_name.snakeCase()}}_linux{{/linux}}/lib/src/{{project_name.snakeCase()}}_linux.dart b/very_good_flutter_plugin/__brick__/{{#linux}}{{project_name.snakeCase()}}_linux{{/linux}}/lib/src/{{project_name.snakeCase()}}_linux.dart index f00bee73..3079b2bf 100644 --- a/very_good_flutter_plugin/__brick__/{{#linux}}{{project_name.snakeCase()}}_linux{{/linux}}/lib/src/{{project_name.snakeCase()}}_linux.dart +++ b/very_good_flutter_plugin/__brick__/{{#linux}}{{project_name.snakeCase()}}_linux{{/linux}}/lib/src/{{project_name.snakeCase()}}_linux.dart @@ -1,15 +1,18 @@ import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart'; +import 'package:{{project_name.snakeCase()}}_linux/src/messages.g.dart'; import 'package:{{project_name.snakeCase()}}_platform_interface/{{project_name.snakeCase()}}_platform_interface.dart'; +/// {@template {{project_name.snakeCase()}}_linux} /// The Linux implementation of [{{project_name.pascalCase()}}Platform]. -class {{project_name.pascalCase()}}Linux - extends {{project_name.pascalCase()}}Platform { - /// The method channel used to interact with the native platform. - @visibleForTesting - final methodChannel = const MethodChannel( - '{{project_name.snakeCase()}}_linux', - ); +/// {@endtemplate} +class {{project_name.pascalCase()}}Linux extends {{project_name.pascalCase()}}Platform { + /// {@macro {{project_name.snakeCase()}}_linux} + {{project_name.pascalCase()}}Linux({ + @visibleForTesting {{project_name.pascalCase()}}Api? api, + }) : api = api ?? {{project_name.pascalCase()}}Api(); + + /// The API used to interact with the native platform. + final {{project_name.pascalCase()}}Api api; /// Registers this class as the default instance of /// [{{project_name.pascalCase()}}Platform]. @@ -20,6 +23,6 @@ class {{project_name.pascalCase()}}Linux @override Future getPlatformName() { - return methodChannel.invokeMethod('getPlatformName'); + return api.getPlatformName(); } } diff --git a/very_good_flutter_plugin/__brick__/{{#linux}}{{project_name.snakeCase()}}_linux{{/linux}}/linux/{{project_name.snakeCase()}}_linux_plugin.cc b/very_good_flutter_plugin/__brick__/{{#linux}}{{project_name.snakeCase()}}_linux{{/linux}}/linux/{{project_name.snakeCase()}}_linux_plugin.cc index 968995c7..9ec247f1 100644 --- a/very_good_flutter_plugin/__brick__/{{#linux}}{{project_name.snakeCase()}}_linux{{/linux}}/linux/{{project_name.snakeCase()}}_linux_plugin.cc +++ b/very_good_flutter_plugin/__brick__/{{#linux}}{{project_name.snakeCase()}}_linux{{/linux}}/linux/{{project_name.snakeCase()}}_linux_plugin.cc @@ -1,42 +1,32 @@ #include "include/{{project_name.snakeCase()}}_linux/{{project_name.snakeCase()}}_plugin.h" - #include #include #include - #include - -const char kChannelName[] = "{{project_name.snakeCase()}}_linux"; -const char kGetPlatformName[] = "getPlatformName"; +#include "messages.g.h" struct _Fl{{project_name.pascalCase()}}Plugin { GObject parent_instance; FlPluginRegistrar* registrar; - - // Connection to Flutter engine. - FlMethodChannel* channel; }; G_DEFINE_TYPE(Fl{{project_name.pascalCase()}}Plugin, fl_{{project_name.snakeCase()}}_plugin, g_object_get_type()) -// Called when a method call is received from Flutter. -static void method_call_cb(FlMethodChannel* channel, FlMethodCall* method_call, - gpointer user_data) { - const gchar* method = fl_method_call_get_name(method_call); - - g_autoptr(FlMethodResponse) response = nullptr; - if (strcmp(method, kGetPlatformName) == 0) - response = FL_METHOD_RESPONSE(fl_method_success_response_new(fl_value_new_string("Linux"))); - else - response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new()); - - g_autoptr(GError) error = nullptr; - if (!fl_method_call_respond(method_call, response, &error)) - g_warning("Failed to send method call response: %s", error->message); +static void handle_get_platform_name( + {{project_name.pascalCase()}}{{project_name.pascalCase()}}ApiResponseHandle* response_handle, + gpointer user_data) { + {{project_name.snakeCase()}}_{{project_name.snakeCase()}}_api_respond_get_platform_name(response_handle, "Linux"); } +static {{project_name.pascalCase()}}{{project_name.pascalCase()}}ApiVTable api_vtable = { + handle_get_platform_name, +}; + static void fl_{{project_name.snakeCase()}}_plugin_dispose(GObject* object) { + Fl{{project_name.pascalCase()}}Plugin* self = FL_{{project_name.constantCase()}}_PLUGIN(object); + {{project_name.snakeCase()}}_{{project_name.snakeCase()}}_api_clear_method_handlers( + fl_plugin_registrar_get_messenger(self->registrar), nullptr); G_OBJECT_CLASS(fl_{{project_name.snakeCase()}}_plugin_parent_class)->dispose(object); } @@ -50,12 +40,9 @@ Fl{{project_name.pascalCase()}}Plugin* fl_{{project_name.snakeCase()}}_plugin_ne self->registrar = FL_PLUGIN_REGISTRAR(g_object_ref(registrar)); - g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); - self->channel = - fl_method_channel_new(fl_plugin_registrar_get_messenger(registrar), - kChannelName, FL_METHOD_CODEC(codec)); - fl_method_channel_set_method_call_handler(self->channel, method_call_cb, - g_object_ref(self), g_object_unref); + {{project_name.snakeCase()}}_{{project_name.snakeCase()}}_api_set_method_handlers( + fl_plugin_registrar_get_messenger(registrar), nullptr, + &api_vtable, g_object_ref(self), g_object_unref); return self; } diff --git a/very_good_flutter_plugin/__brick__/{{#linux}}{{project_name.snakeCase()}}_linux{{/linux}}/pigeons/copyright.txt b/very_good_flutter_plugin/__brick__/{{#linux}}{{project_name.snakeCase()}}_linux{{/linux}}/pigeons/copyright.txt new file mode 100644 index 00000000..00a4ecbb --- /dev/null +++ b/very_good_flutter_plugin/__brick__/{{#linux}}{{project_name.snakeCase()}}_linux{{/linux}}/pigeons/copyright.txt @@ -0,0 +1 @@ +Copyright (c) {{currentYear}}, {{org_name.sentenceCase()}} diff --git a/very_good_flutter_plugin/__brick__/{{#linux}}{{project_name.snakeCase()}}_linux{{/linux}}/pigeons/messages.dart b/very_good_flutter_plugin/__brick__/{{#linux}}{{project_name.snakeCase()}}_linux{{/linux}}/pigeons/messages.dart new file mode 100644 index 00000000..949296c8 --- /dev/null +++ b/very_good_flutter_plugin/__brick__/{{#linux}}{{project_name.snakeCase()}}_linux{{/linux}}/pigeons/messages.dart @@ -0,0 +1,19 @@ +// {{project_name.pascalCase()}}Api must be abstract. +// ignore_for_file: one_member_abstracts + +import 'package:pigeon/pigeon.dart'; + +@ConfigurePigeon( + PigeonOptions( + dartOut: 'lib/src/messages.g.dart', + dartPackageName: '{{project_name.snakeCase()}}', + gobjectHeaderOut: 'linux/messages.g.h', + gobjectSourceOut: 'linux/messages.g.cc', + copyrightHeader: 'pigeons/copyright.txt', + ), +) +@HostApi() +abstract class {{project_name.pascalCase()}}Api { + @async + String? getPlatformName(); +} diff --git a/very_good_flutter_plugin/__brick__/{{#linux}}{{project_name.snakeCase()}}_linux{{/linux}}/pubspec.yaml b/very_good_flutter_plugin/__brick__/{{#linux}}{{project_name.snakeCase()}}_linux{{/linux}}/pubspec.yaml index e2a1024c..f8e62602 100644 --- a/very_good_flutter_plugin/__brick__/{{#linux}}{{project_name.snakeCase()}}_linux{{/linux}}/pubspec.yaml +++ b/very_good_flutter_plugin/__brick__/{{#linux}}{{project_name.snakeCase()}}_linux{{/linux}}/pubspec.yaml @@ -23,4 +23,6 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + mocktail: ^1.0.4 + pigeon: ^26.0.2 very_good_analysis: ^10.2.0 diff --git a/very_good_flutter_plugin/__brick__/{{#linux}}{{project_name.snakeCase()}}_linux{{/linux}}/test/{{project_name.snakeCase()}}_linux_test.dart b/very_good_flutter_plugin/__brick__/{{#linux}}{{project_name.snakeCase()}}_linux{{/linux}}/test/{{project_name.snakeCase()}}_linux_test.dart index b7b3f573..6f00fa79 100644 --- a/very_good_flutter_plugin/__brick__/{{#linux}}{{project_name.snakeCase()}}_linux{{/linux}}/test/{{project_name.snakeCase()}}_linux_test.dart +++ b/very_good_flutter_plugin/__brick__/{{#linux}}{{project_name.snakeCase()}}_linux{{/linux}}/test/{{project_name.snakeCase()}}_linux_test.dart @@ -1,32 +1,23 @@ -import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:{{project_name.snakeCase()}}_linux/src/messages.g.dart'; import 'package:{{project_name.snakeCase()}}_linux/{{project_name.snakeCase()}}_linux.dart'; import 'package:{{project_name.snakeCase()}}_platform_interface/{{project_name.snakeCase()}}_platform_interface.dart'; +class _Mock{{project_name.pascalCase()}}Api extends Mock + implements {{project_name.pascalCase()}}Api {} + void main() { TestWidgetsFlutterBinding.ensureInitialized(); - group('{{project_name.pascalCase()}}Linux', () { + group({{project_name.pascalCase()}}Linux, () { const kPlatformName = 'Linux'; late {{project_name.pascalCase()}}Linux {{project_name.camelCase()}}; - late List log; - - setUp(() async { - {{project_name.camelCase()}} = {{project_name.pascalCase()}}Linux(); - - log = []; - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler({{project_name.camelCase()}}.methodChannel, ( - methodCall, - ) async { - log.add(methodCall); - switch (methodCall.method) { - case 'getPlatformName': - return kPlatformName; - default: - return null; - } - }); + late {{project_name.pascalCase()}}Api api; + + setUp(() { + api = _Mock{{project_name.pascalCase()}}Api(); + {{project_name.camelCase()}} = {{project_name.pascalCase()}}Linux(api: api); }); test('can be registered', () { @@ -38,12 +29,14 @@ void main() { }); test('getPlatformName returns correct name', () async { - final name = await {{project_name.camelCase()}}.getPlatformName(); - expect( - log, - [isMethodCall('getPlatformName', arguments: null)], + when(api.getPlatformName).thenAnswer((_) async => kPlatformName); + + await expectLater( + {{project_name.camelCase()}}.getPlatformName(), + completion(equals(kPlatformName)), ); - expect(name, equals(kPlatformName)); + + verify(api.getPlatformName).called(1); }); }); } diff --git a/very_good_flutter_plugin/__brick__/{{#macos}}{{project_name.snakeCase()}}_macos{{/macos}}/analysis_options.yaml b/very_good_flutter_plugin/__brick__/{{#macos}}{{project_name.snakeCase()}}_macos{{/macos}}/analysis_options.yaml index 9df80aa4..c5dcf888 100644 --- a/very_good_flutter_plugin/__brick__/{{#macos}}{{project_name.snakeCase()}}_macos{{/macos}}/analysis_options.yaml +++ b/very_good_flutter_plugin/__brick__/{{#macos}}{{project_name.snakeCase()}}_macos{{/macos}}/analysis_options.yaml @@ -1 +1,4 @@ include: package:very_good_analysis/analysis_options.yaml +analyzer: + exclude: + - "**/*.g.dart" diff --git a/very_good_flutter_plugin/__brick__/{{#macos}}{{project_name.snakeCase()}}_macos{{/macos}}/lib/{{project_name.snakeCase()}}_macos.dart b/very_good_flutter_plugin/__brick__/{{#macos}}{{project_name.snakeCase()}}_macos{{/macos}}/lib/{{project_name.snakeCase()}}_macos.dart index c0e814a9..cc1e8353 100644 --- a/very_good_flutter_plugin/__brick__/{{#macos}}{{project_name.snakeCase()}}_macos{{/macos}}/lib/{{project_name.snakeCase()}}_macos.dart +++ b/very_good_flutter_plugin/__brick__/{{#macos}}{{project_name.snakeCase()}}_macos{{/macos}}/lib/{{project_name.snakeCase()}}_macos.dart @@ -1,15 +1,18 @@ import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart'; +import 'package:{{project_name.snakeCase()}}_macos/src/messages.g.dart'; import 'package:{{project_name.snakeCase()}}_platform_interface/{{project_name.snakeCase()}}_platform_interface.dart'; +/// {@template {{project_name.snakeCase()}}_macos} /// The MacOS implementation of [{{project_name.pascalCase()}}Platform]. -class {{project_name.pascalCase()}}MacOS - extends {{project_name.pascalCase()}}Platform { - /// The method channel used to interact with the native platform. - @visibleForTesting - final methodChannel = const MethodChannel( - '{{project_name.snakeCase()}}_macos', - ); +/// {@endtemplate} +class {{project_name.pascalCase()}}MacOS extends {{project_name.pascalCase()}}Platform { + /// {@macro {{project_name.snakeCase()}}_macos} + {{project_name.pascalCase()}}MacOS({ + @visibleForTesting {{project_name.pascalCase()}}Api? api, + }) : api = api ?? {{project_name.pascalCase()}}Api(); + + /// The API used to interact with the native platform. + final {{project_name.pascalCase()}}Api api; /// Registers this class as the default instance of /// [{{project_name.pascalCase()}}Platform]. @@ -20,6 +23,6 @@ class {{project_name.pascalCase()}}MacOS @override Future getPlatformName() { - return methodChannel.invokeMethod('getPlatformName'); + return api.getPlatformName(); } } diff --git a/very_good_flutter_plugin/__brick__/{{#macos}}{{project_name.snakeCase()}}_macos{{/macos}}/macos/{{project_name.snakeCase()}}_macos/Sources/{{project_name.snakeCase()}}_macos/{{project_name.pascalCase()}}Plugin.swift b/very_good_flutter_plugin/__brick__/{{#macos}}{{project_name.snakeCase()}}_macos{{/macos}}/macos/{{project_name.snakeCase()}}_macos/Sources/{{project_name.snakeCase()}}_macos/{{project_name.pascalCase()}}Plugin.swift index 81ad8c16..b9381994 100644 --- a/very_good_flutter_plugin/__brick__/{{#macos}}{{project_name.snakeCase()}}_macos{{/macos}}/macos/{{project_name.snakeCase()}}_macos/Sources/{{project_name.snakeCase()}}_macos/{{project_name.pascalCase()}}Plugin.swift +++ b/very_good_flutter_plugin/__brick__/{{#macos}}{{project_name.snakeCase()}}_macos{{/macos}}/macos/{{project_name.snakeCase()}}_macos/Sources/{{project_name.snakeCase()}}_macos/{{project_name.pascalCase()}}Plugin.swift @@ -1,14 +1,15 @@ import FlutterMacOS import Foundation -public class {{project_name.pascalCase()}}Plugin: NSObject, FlutterPlugin { - public static func register(with registrar: FlutterPluginRegistrar) { - let channel = FlutterMethodChannel(name: "{{project_name.snakeCase()}}_macos", binaryMessenger: registrar.messenger) +public class {{project_name.pascalCase()}}Plugin: NSObject, FlutterPlugin, {{project_name.pascalCase()}}Api { + public static func register(with registrar: FlutterPluginRegistrar) { + let binaryMessenger = registrar.messenger let instance = {{project_name.pascalCase()}}Plugin() - registrar.addMethodCallDelegate(instance, channel: channel) + {{project_name.pascalCase()}}ApiSetup.setUp(binaryMessenger: binaryMessenger, api: instance) + registrar.publish(instance) } - public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - result("macOS") + func getPlatformName(completion: @escaping (Result) -> Void) { + completion(.success("macOS")) } } diff --git a/very_good_flutter_plugin/__brick__/{{#macos}}{{project_name.snakeCase()}}_macos{{/macos}}/pigeons/copyright.txt b/very_good_flutter_plugin/__brick__/{{#macos}}{{project_name.snakeCase()}}_macos{{/macos}}/pigeons/copyright.txt new file mode 100644 index 00000000..00a4ecbb --- /dev/null +++ b/very_good_flutter_plugin/__brick__/{{#macos}}{{project_name.snakeCase()}}_macos{{/macos}}/pigeons/copyright.txt @@ -0,0 +1 @@ +Copyright (c) {{currentYear}}, {{org_name.sentenceCase()}} diff --git a/very_good_flutter_plugin/__brick__/{{#macos}}{{project_name.snakeCase()}}_macos{{/macos}}/pigeons/messages.dart b/very_good_flutter_plugin/__brick__/{{#macos}}{{project_name.snakeCase()}}_macos{{/macos}}/pigeons/messages.dart new file mode 100644 index 00000000..3a9a53df --- /dev/null +++ b/very_good_flutter_plugin/__brick__/{{#macos}}{{project_name.snakeCase()}}_macos{{/macos}}/pigeons/messages.dart @@ -0,0 +1,19 @@ +// {{project_name.pascalCase()}}Api must be abstract. +// ignore_for_file: one_member_abstracts + +import 'package:pigeon/pigeon.dart'; + +@ConfigurePigeon( + PigeonOptions( + dartOut: 'lib/src/messages.g.dart', + dartPackageName: '{{project_name.snakeCase()}}', + swiftOut: 'macos/{{project_name.snakeCase()}}_macos/Sources/{{project_name.snakeCase()}}_macos/Messages.g.swift', + swiftOptions: SwiftOptions(), + copyrightHeader: 'pigeons/copyright.txt', + ), +) +@HostApi() +abstract class {{project_name.pascalCase()}}Api { + @async + String? getPlatformName(); +} diff --git a/very_good_flutter_plugin/__brick__/{{#macos}}{{project_name.snakeCase()}}_macos{{/macos}}/pubspec.yaml b/very_good_flutter_plugin/__brick__/{{#macos}}{{project_name.snakeCase()}}_macos{{/macos}}/pubspec.yaml index d470ff5f..52d5bf4f 100644 --- a/very_good_flutter_plugin/__brick__/{{#macos}}{{project_name.snakeCase()}}_macos{{/macos}}/pubspec.yaml +++ b/very_good_flutter_plugin/__brick__/{{#macos}}{{project_name.snakeCase()}}_macos{{/macos}}/pubspec.yaml @@ -23,4 +23,6 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + mocktail: ^1.0.4 + pigeon: ^26.0.2 very_good_analysis: ^10.2.0 diff --git a/very_good_flutter_plugin/__brick__/{{#macos}}{{project_name.snakeCase()}}_macos{{/macos}}/test/{{project_name.snakeCase()}}_macos_test.dart b/very_good_flutter_plugin/__brick__/{{#macos}}{{project_name.snakeCase()}}_macos{{/macos}}/test/{{project_name.snakeCase()}}_macos_test.dart index ea675ef0..19cfaccd 100644 --- a/very_good_flutter_plugin/__brick__/{{#macos}}{{project_name.snakeCase()}}_macos{{/macos}}/test/{{project_name.snakeCase()}}_macos_test.dart +++ b/very_good_flutter_plugin/__brick__/{{#macos}}{{project_name.snakeCase()}}_macos{{/macos}}/test/{{project_name.snakeCase()}}_macos_test.dart @@ -1,32 +1,23 @@ -import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:{{project_name.snakeCase()}}_macos/src/messages.g.dart'; import 'package:{{project_name.snakeCase()}}_macos/{{project_name.snakeCase()}}_macos.dart'; import 'package:{{project_name.snakeCase()}}_platform_interface/{{project_name.snakeCase()}}_platform_interface.dart'; +class _Mock{{project_name.pascalCase()}}Api extends Mock + implements {{project_name.pascalCase()}}Api {} + void main() { TestWidgetsFlutterBinding.ensureInitialized(); - group('{{project_name.pascalCase()}}MacOS', () { + group({{project_name.pascalCase()}}MacOS, () { const kPlatformName = 'MacOS'; late {{project_name.pascalCase()}}MacOS {{project_name.camelCase()}}; - late List log; - - setUp(() async { - {{project_name.camelCase()}} = {{project_name.pascalCase()}}MacOS(); - - log = []; - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler({{project_name.camelCase()}}.methodChannel, ( - methodCall, - ) async { - log.add(methodCall); - switch (methodCall.method) { - case 'getPlatformName': - return kPlatformName; - default: - return null; - } - }); + late {{project_name.pascalCase()}}Api api; + + setUp(() { + api = _Mock{{project_name.pascalCase()}}Api(); + {{project_name.camelCase()}} = {{project_name.pascalCase()}}MacOS(api: api); }); test('can be registered', () { @@ -38,12 +29,14 @@ void main() { }); test('getPlatformName returns correct name', () async { - final name = await {{project_name.camelCase()}}.getPlatformName(); - expect( - log, - [isMethodCall('getPlatformName', arguments: null)], + when(api.getPlatformName).thenAnswer((_) async => kPlatformName); + + await expectLater( + {{project_name.camelCase()}}.getPlatformName(), + completion(equals(kPlatformName)), ); - expect(name, equals(kPlatformName)); + + verify(api.getPlatformName).called(1); }); }); } diff --git a/very_good_flutter_plugin/__brick__/{{#web}}{{project_name.snakeCase()}}_web{{/web}}/lib/{{project_name.snakeCase()}}_web.dart b/very_good_flutter_plugin/__brick__/{{#web}}{{project_name.snakeCase()}}_web{{/web}}/lib/{{project_name.snakeCase()}}_web.dart index 61f08d8f..518443e2 100644 --- a/very_good_flutter_plugin/__brick__/{{#web}}{{project_name.snakeCase()}}_web{{/web}}/lib/{{project_name.snakeCase()}}_web.dart +++ b/very_good_flutter_plugin/__brick__/{{#web}}{{project_name.snakeCase()}}_web{{/web}}/lib/{{project_name.snakeCase()}}_web.dart @@ -1,8 +1,7 @@ import 'package:{{project_name.snakeCase()}}_platform_interface/{{project_name.snakeCase()}}_platform_interface.dart'; /// The Web implementation of [{{project_name.pascalCase()}}Platform]. -class {{project_name.pascalCase()}}Web - extends {{project_name.pascalCase()}}Platform { +class {{project_name.pascalCase()}}Web extends {{project_name.pascalCase()}}Platform { /// Registers this class as the default instance of /// [{{project_name.pascalCase()}}Platform]. static void registerWith([Object? registrar]) { diff --git a/very_good_flutter_plugin/__brick__/{{#web}}{{project_name.snakeCase()}}_web{{/web}}/test/{{project_name.snakeCase()}}_web_test.dart b/very_good_flutter_plugin/__brick__/{{#web}}{{project_name.snakeCase()}}_web{{/web}}/test/{{project_name.snakeCase()}}_web_test.dart index 3b5affb5..f3991e01 100644 --- a/very_good_flutter_plugin/__brick__/{{#web}}{{project_name.snakeCase()}}_web{{/web}}/test/{{project_name.snakeCase()}}_web_test.dart +++ b/very_good_flutter_plugin/__brick__/{{#web}}{{project_name.snakeCase()}}_web{{/web}}/test/{{project_name.snakeCase()}}_web_test.dart @@ -9,7 +9,7 @@ void main() { const kPlatformName = 'Web'; late {{project_name.pascalCase()}}Web {{project_name.camelCase()}}; - setUp(() async { + setUp(() { {{project_name.camelCase()}} = {{project_name.pascalCase()}}Web(); }); diff --git a/very_good_flutter_plugin/__brick__/{{#windows}}{{project_name.snakeCase()}}_windows{{/windows}}/analysis_options.yaml b/very_good_flutter_plugin/__brick__/{{#windows}}{{project_name.snakeCase()}}_windows{{/windows}}/analysis_options.yaml index 9df80aa4..c5dcf888 100644 --- a/very_good_flutter_plugin/__brick__/{{#windows}}{{project_name.snakeCase()}}_windows{{/windows}}/analysis_options.yaml +++ b/very_good_flutter_plugin/__brick__/{{#windows}}{{project_name.snakeCase()}}_windows{{/windows}}/analysis_options.yaml @@ -1 +1,4 @@ include: package:very_good_analysis/analysis_options.yaml +analyzer: + exclude: + - "**/*.g.dart" diff --git a/very_good_flutter_plugin/__brick__/{{#windows}}{{project_name.snakeCase()}}_windows{{/windows}}/lib/{{project_name.snakeCase()}}_windows.dart b/very_good_flutter_plugin/__brick__/{{#windows}}{{project_name.snakeCase()}}_windows{{/windows}}/lib/{{project_name.snakeCase()}}_windows.dart index 46a5cc82..87a69847 100644 --- a/very_good_flutter_plugin/__brick__/{{#windows}}{{project_name.snakeCase()}}_windows{{/windows}}/lib/{{project_name.snakeCase()}}_windows.dart +++ b/very_good_flutter_plugin/__brick__/{{#windows}}{{project_name.snakeCase()}}_windows{{/windows}}/lib/{{project_name.snakeCase()}}_windows.dart @@ -1,15 +1,18 @@ import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart'; +import 'package:{{project_name.snakeCase()}}_windows/src/messages.g.dart'; import 'package:{{project_name.snakeCase()}}_platform_interface/{{project_name.snakeCase()}}_platform_interface.dart'; +/// {@template {{project_name.snakeCase()}}_windows} /// The Windows implementation of [{{project_name.pascalCase()}}Platform]. -class {{project_name.pascalCase()}}Windows - extends {{project_name.pascalCase()}}Platform { - /// The method channel used to interact with the native platform. - @visibleForTesting - final methodChannel = const MethodChannel( - '{{project_name.snakeCase()}}_windows', - ); +/// {@endtemplate} +class {{project_name.pascalCase()}}Windows extends {{project_name.pascalCase()}}Platform { + /// {@macro {{project_name.snakeCase()}}_windows} + {{project_name.pascalCase()}}Windows({ + @visibleForTesting {{project_name.pascalCase()}}Api? api, + }) : api = api ?? {{project_name.pascalCase()}}Api(); + + /// The API used to interact with the native platform. + final {{project_name.pascalCase()}}Api api; /// Registers this class as the default instance of /// [{{project_name.pascalCase()}}Platform]. @@ -20,6 +23,6 @@ class {{project_name.pascalCase()}}Windows @override Future getPlatformName() { - return methodChannel.invokeMethod('getPlatformName'); + return api.getPlatformName(); } } diff --git a/very_good_flutter_plugin/__brick__/{{#windows}}{{project_name.snakeCase()}}_windows{{/windows}}/pigeons/copyright.txt b/very_good_flutter_plugin/__brick__/{{#windows}}{{project_name.snakeCase()}}_windows{{/windows}}/pigeons/copyright.txt new file mode 100644 index 00000000..00a4ecbb --- /dev/null +++ b/very_good_flutter_plugin/__brick__/{{#windows}}{{project_name.snakeCase()}}_windows{{/windows}}/pigeons/copyright.txt @@ -0,0 +1 @@ +Copyright (c) {{currentYear}}, {{org_name.sentenceCase()}} diff --git a/very_good_flutter_plugin/__brick__/{{#windows}}{{project_name.snakeCase()}}_windows{{/windows}}/pigeons/messages.dart b/very_good_flutter_plugin/__brick__/{{#windows}}{{project_name.snakeCase()}}_windows{{/windows}}/pigeons/messages.dart new file mode 100644 index 00000000..f14c738d --- /dev/null +++ b/very_good_flutter_plugin/__brick__/{{#windows}}{{project_name.snakeCase()}}_windows{{/windows}}/pigeons/messages.dart @@ -0,0 +1,20 @@ +// {{project_name.pascalCase()}}Api must be abstract. +// ignore_for_file: one_member_abstracts + +import 'package:pigeon/pigeon.dart'; + +@ConfigurePigeon( + PigeonOptions( + dartOut: 'lib/src/messages.g.dart', + dartPackageName: '{{project_name.snakeCase()}}', + cppOptions: CppOptions(namespace: '{{project_name.snakeCase()}}'), + cppHeaderOut: 'windows/include/{{project_name.snakeCase()}}_windows/messages.g.h', + cppSourceOut: 'windows/messages.g.cpp', + copyrightHeader: 'pigeons/copyright.txt', + ), +) +@HostApi() +abstract class {{project_name.pascalCase()}}Api { + @async + String? getPlatformName(); +} diff --git a/very_good_flutter_plugin/__brick__/{{#windows}}{{project_name.snakeCase()}}_windows{{/windows}}/pubspec.yaml b/very_good_flutter_plugin/__brick__/{{#windows}}{{project_name.snakeCase()}}_windows{{/windows}}/pubspec.yaml index c98854ec..4c4d6e20 100644 --- a/very_good_flutter_plugin/__brick__/{{#windows}}{{project_name.snakeCase()}}_windows{{/windows}}/pubspec.yaml +++ b/very_good_flutter_plugin/__brick__/{{#windows}}{{project_name.snakeCase()}}_windows{{/windows}}/pubspec.yaml @@ -23,4 +23,6 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + mocktail: ^1.0.4 + pigeon: ^26.0.2 very_good_analysis: ^10.2.0 diff --git a/very_good_flutter_plugin/__brick__/{{#windows}}{{project_name.snakeCase()}}_windows{{/windows}}/test/{{project_name.snakeCase()}}_windows_test.dart b/very_good_flutter_plugin/__brick__/{{#windows}}{{project_name.snakeCase()}}_windows{{/windows}}/test/{{project_name.snakeCase()}}_windows_test.dart index 7a324c8b..2ddb96e1 100644 --- a/very_good_flutter_plugin/__brick__/{{#windows}}{{project_name.snakeCase()}}_windows{{/windows}}/test/{{project_name.snakeCase()}}_windows_test.dart +++ b/very_good_flutter_plugin/__brick__/{{#windows}}{{project_name.snakeCase()}}_windows{{/windows}}/test/{{project_name.snakeCase()}}_windows_test.dart @@ -1,32 +1,23 @@ -import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:{{project_name.snakeCase()}}_windows/src/messages.g.dart'; import 'package:{{project_name.snakeCase()}}_platform_interface/{{project_name.snakeCase()}}_platform_interface.dart'; import 'package:{{project_name.snakeCase()}}_windows/{{project_name.snakeCase()}}_windows.dart'; +class _Mock{{project_name.pascalCase()}}Api extends Mock + implements {{project_name.pascalCase()}}Api {} + void main() { TestWidgetsFlutterBinding.ensureInitialized(); - group('{{project_name.pascalCase()}}Windows', () { + group({{project_name.pascalCase()}}Windows, () { const kPlatformName = 'Windows'; late {{project_name.pascalCase()}}Windows {{project_name.camelCase()}}; - late List log; - - setUp(() async { - {{project_name.camelCase()}} = {{project_name.pascalCase()}}Windows(); - - log = []; - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .setMockMethodCallHandler({{project_name.camelCase()}}.methodChannel, ( - methodCall, - ) async { - log.add(methodCall); - switch (methodCall.method) { - case 'getPlatformName': - return kPlatformName; - default: - return null; - } - }); + late {{project_name.pascalCase()}}Api api; + + setUp(() { + api = _Mock{{project_name.pascalCase()}}Api(); + {{project_name.camelCase()}} = {{project_name.pascalCase()}}Windows(api: api); }); test('can be registered', () { @@ -38,12 +29,14 @@ void main() { }); test('getPlatformName returns correct name', () async { - final name = await {{project_name.camelCase()}}.getPlatformName(); - expect( - log, - [isMethodCall('getPlatformName', arguments: null)], + when(api.getPlatformName).thenAnswer((_) async => kPlatformName); + + await expectLater( + {{project_name.camelCase()}}.getPlatformName(), + completion(equals(kPlatformName)), ); - expect(name, equals(kPlatformName)); + + verify(api.getPlatformName).called(1); }); }); } diff --git a/very_good_flutter_plugin/__brick__/{{project_name.snakeCase()}}_platform_interface/test/src/method_channel_{{project_name.snakeCase()}}_test.dart b/very_good_flutter_plugin/__brick__/{{project_name.snakeCase()}}_platform_interface/test/src/method_channel_{{project_name.snakeCase()}}_test.dart index a49c4ac3..90df9ad8 100644 --- a/very_good_flutter_plugin/__brick__/{{project_name.snakeCase()}}_platform_interface/test/src/method_channel_{{project_name.snakeCase()}}_test.dart +++ b/very_good_flutter_plugin/__brick__/{{project_name.snakeCase()}}_platform_interface/test/src/method_channel_{{project_name.snakeCase()}}_test.dart @@ -11,9 +11,8 @@ void main() { methodChannel{{project_name.pascalCase()}}; final log = []; - setUp(() async { - methodChannel{{project_name.pascalCase()}} = - MethodChannel{{project_name.pascalCase()}}(); + setUp(() { + methodChannel{{project_name.pascalCase()}} = MethodChannel{{project_name.pascalCase()}}(); TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger .setMockMethodCallHandler( methodChannel{{project_name.pascalCase()}}.methodChannel, diff --git a/very_good_flutter_plugin/hooks/lib/src/cli/dart_cli.dart b/very_good_flutter_plugin/hooks/lib/src/cli/dart_cli.dart index 06e69af4..f0cc1f66 100644 --- a/very_good_flutter_plugin/hooks/lib/src/cli/dart_cli.dart +++ b/very_good_flutter_plugin/hooks/lib/src/cli/dart_cli.dart @@ -28,12 +28,16 @@ class DartCli { } /// Idiomatically format Dart source code. - Future format({required Logger logger, String cwd = '.'}) async { + Future format({ + required Logger logger, + String cwd = '.', + String path = '.', + }) async { await CommandLine.run( _executableName, - ['format'], - workingDirectory: cwd, + ['format', path], logger: logger, + workingDirectory: cwd, ); } @@ -48,8 +52,23 @@ class DartCli { await CommandLine.run( _executableName, ['fix', if (apply) '--apply'], + logger: logger, workingDirectory: cwd, + ); + } + + /// Run a command. + Future run({ + required Logger logger, + required String command, + List args = const [], + String cwd = '.', + }) async { + await CommandLine.run( + _executableName, + ['run', command, ...args], logger: logger, + workingDirectory: cwd, ); } } diff --git a/very_good_flutter_plugin/hooks/lib/version.dart b/very_good_flutter_plugin/hooks/lib/version.dart index 2e8d8cf9..740c5fe1 100644 --- a/very_good_flutter_plugin/hooks/lib/version.dart +++ b/very_good_flutter_plugin/hooks/lib/version.dart @@ -20,3 +20,25 @@ const $flutterVersion = '3.41.1'; /// /// * [Flutter SDK archive](https://docs.flutter.dev/release/archive) const $minDartVersion = '3.11.0'; + +/// The available platforms that can be generated by the template. +const $availablePlatforms = [ + 'android', + 'ios', + 'macos', + 'linux', + 'web', + 'windows', +]; + +/// The platforms that use Pigeon for native bindings. +/// +/// Web is excluded because it does not use native code and therefore does not +/// require Pigeon code generation. +const $pigeonPlatforms = [ + 'android', + 'ios', + 'macos', + 'linux', + 'windows', +]; diff --git a/very_good_flutter_plugin/hooks/post_gen.dart b/very_good_flutter_plugin/hooks/post_gen.dart index 017a7de1..094c399e 100644 --- a/very_good_flutter_plugin/hooks/post_gen.dart +++ b/very_good_flutter_plugin/hooks/post_gen.dart @@ -3,29 +3,16 @@ import 'dart:io'; import 'package:mason/mason.dart'; import 'package:meta/meta.dart'; import 'package:very_good_flutter_plugin_hooks/src/cli/cli.dart'; +import 'package:very_good_flutter_plugin_hooks/version.dart'; + +/// The key for the `projectName` context variable. +@visibleForTesting +const projectNameVariableKey = 'project_name'; /// The key for the `dartFixOutput` context variable. @visibleForTesting const dartFixOutputVariableKey = 'dart_fix_output'; -Future run( - HookContext context, { - @visibleForTesting VeryGoodCli veryGoodCli = VeryGoodCli.instance, - @visibleForTesting DartCli dartCli = DartCli.instance, -}) async { - final dartFixOutput = - context.vars.containsKey(dartFixOutputVariableKey) && - context.vars[dartFixOutputVariableKey] as bool; - if (dartFixOutput) { - await _dartFixOutput( - logger: context.logger, - workingDirectory: Directory.current.path, - veryGoodCli: veryGoodCli, - dartCli: dartCli, - ); - } -} - /// Attempts to `dart` fix and format the output. /// /// Since the template includes Dart files with templated variables, generating @@ -41,37 +28,88 @@ Future run( /// /// If the [DartCli] or [VeryGoodCli] is not installed, this function will log /// a warning and return immediately. -Future _dartFixOutput({ - required Logger logger, - required String workingDirectory, - required VeryGoodCli veryGoodCli, - required DartCli dartCli, +Future run( + HookContext context, { + @visibleForTesting DartCli dartCli = DartCli.instance, + @visibleForTesting VeryGoodCli veryGoodCli = VeryGoodCli.instance, }) async { + final logger = context.logger; + final cwd = Directory.current.path; + if (!await dartCli.isInstalled(logger: logger)) { - logger.warn('''Could not fix output because Dart CLI is not installed.'''); - return; + return logger.warn( + '''Could not fix output because Dart CLI is not installed.''', + ); } + if (!await veryGoodCli.isInstalled(logger: logger)) { - logger.warn( + return logger.warn( '''Could not fix output because Very Good CLI is not installed.''', ); - return; } - try { - await veryGoodCli.packagesGet( + final progress = logger.progress('Getting dependencies ๐Ÿ“ฆ'); + + await _wrapWithProcessError( + () => veryGoodCli.packagesGet( + cwd: cwd, logger: logger, recursive: true, - cwd: workingDirectory, + ), + logger: logger, + ); + + final projectName = context.vars[projectNameVariableKey] as String; + final directories = $pigeonPlatforms + .where((platform) => context.vars[platform] as bool) + .map((platform) => '$cwd/${projectName}_$platform'); + + progress.update('Generating Pigeon bindings ๐Ÿฆพ'); + + await Future.wait( + directories.map( + (directory) => _wrapWithProcessError( + () => dartCli.run( + cwd: directory, + logger: logger, + command: 'pigeon', + args: ['--input', 'pigeons/messages.dart'], + ), + logger: logger, + ), + ), + ); + + final dartFixOutput = + context.vars.containsKey(dartFixOutputVariableKey) && + context.vars[dartFixOutputVariableKey] as bool; + + if (dartFixOutput) { + progress.update('Fixing Dart imports ordering ๐Ÿ”จ'); + await _wrapWithProcessError( + () async { + await dartCli.fix(logger: logger, cwd: cwd, apply: true); + await dartCli.format(logger: logger, cwd: cwd); + }, + logger: logger, ); - await dartCli.fix(logger: logger, apply: true, cwd: workingDirectory); - await dartCli.format(logger: logger, cwd: workingDirectory); + } + + progress.complete('Completed post generation โœ…'); +} + +/// Wraps a process in a try-catch block and logs any errors. +Future _wrapWithProcessError( + Future Function() process, { + required Logger logger, +}) async { + try { + await process(); } on ProcessException catch (e) { - logger.err(''' -Running process ${e.executable} with ${e.arguments} failed: -${e.message} -'''); + logger.err( + '''\n\nRunning process ${e.executable} with ${e.arguments} failed: ${e.message}''', + ); } on Exception catch (e) { - logger.err('Unknown error occurred when fixing output: $e'); + logger.err('Unknown error occurred: $e'); } } diff --git a/very_good_flutter_plugin/hooks/pre_gen.dart b/very_good_flutter_plugin/hooks/pre_gen.dart index d6555eb6..01df3725 100644 --- a/very_good_flutter_plugin/hooks/pre_gen.dart +++ b/very_good_flutter_plugin/hooks/pre_gen.dart @@ -3,15 +3,6 @@ import 'package:mason/mason.dart'; import 'package:very_good_flutter_plugin_hooks/version.dart'; void run(HookContext context) { - const availablePlatforms = [ - 'android', - 'ios', - 'macos', - 'linux', - 'web', - 'windows', - ]; - final selectedPlatformsVar = context.vars['platforms'] as Object?; final selectedPlatforms = switch (selectedPlatformsVar) { @@ -28,7 +19,8 @@ void run(HookContext context) { 'current_year': clock.now().year.toString(), 'flutterVersion': $flutterVersion, 'dartSdkVersionBounds': '^${$minDartVersion}', - for (final platform in availablePlatforms) + 'currentYear': DateTime.now().year.toString(), + for (final platform in $availablePlatforms) platform: selectedPlatforms.contains(platform), }); } diff --git a/very_good_flutter_plugin/hooks/test/cli/dart_cli_test.dart b/very_good_flutter_plugin/hooks/test/cli/dart_cli_test.dart index b5c8697b..d9eb4059 100644 --- a/very_good_flutter_plugin/hooks/test/cli/dart_cli_test.dart +++ b/very_good_flutter_plugin/hooks/test/cli/dart_cli_test.dart @@ -21,7 +21,7 @@ class _MockProcess extends Mock implements _TestProcess {} class _MockLogger extends Mock implements Logger {} void main() { - group('$DartCli', () { + group(DartCli, () { final processResult = ProcessResult(42, ExitCode.success.code, '', ''); late _TestProcess process; late Logger logger; @@ -78,14 +78,14 @@ void main() { }); test('calls with given working directory', () async { - await DartCli.instance.format(logger: logger, cwd: 'foo'); + await DartCli.instance.format(logger: logger, path: 'foo'); verify( () => process.run( 'dart', - ['format'], + ['format', 'foo'], runInShell: true, - workingDirectory: 'foo', + workingDirectory: '.', ), ).called(1); }); @@ -96,7 +96,7 @@ void main() { verify( () => process.run( 'dart', - ['format'], + ['format', '.'], runInShell: true, workingDirectory: '.', ), diff --git a/very_good_flutter_plugin/hooks/test/post_gen_test.dart b/very_good_flutter_plugin/hooks/test/post_gen_test.dart index 0e08e95c..ab54bd1f 100644 --- a/very_good_flutter_plugin/hooks/test/post_gen_test.dart +++ b/very_good_flutter_plugin/hooks/test/post_gen_test.dart @@ -4,6 +4,7 @@ import 'package:mason/mason.dart'; import 'package:mocktail/mocktail.dart'; import 'package:test/test.dart'; import 'package:very_good_flutter_plugin_hooks/src/cli/cli.dart'; +import 'package:very_good_flutter_plugin_hooks/version.dart'; import '../post_gen.dart' as post_gen; @@ -11,6 +12,8 @@ class _MockHookContext extends Mock implements HookContext {} class _MockLogger extends Mock implements Logger {} +class _MockProgress extends Mock implements Progress {} + class _MockDartCli extends Mock implements DartCli {} class _MockVeryGoodCli extends Mock implements VeryGoodCli {} @@ -19,200 +22,154 @@ void main() { group('post gen', () { late HookContext context; late Logger logger; + late Progress progress; + late DartCli dartCli; + late VeryGoodCli veryGoodCli; + + setUpAll(() { + registerFallbackValue(''); + }); setUp(() { context = _MockHookContext(); logger = _MockLogger(); + progress = _MockProgress(); + dartCli = _MockDartCli(); + veryGoodCli = _MockVeryGoodCli(); when(() => context.vars).thenReturn({}); when(() => context.logger).thenReturn(logger); - }); - - group('dart fix output', () { - late DartCli dartCli; - late VeryGoodCli veryGoodCli; - - setUpAll(() { - registerFallbackValue(''); + when( + () => logger.progress( + any(), + options: any(named: 'options'), + ), + ).thenReturn(progress); + + when(() => context.vars).thenReturn({ + post_gen.dartFixOutputVariableKey: true, + post_gen.projectNameVariableKey: 'project_name', + for (final platform in $availablePlatforms) platform: true, }); - setUp(() { - dartCli = _MockDartCli(); - veryGoodCli = _MockVeryGoodCli(); - - when( - () => dartCli.isInstalled(logger: logger), - ).thenAnswer((_) => Future.value(true)); - - when( - () => veryGoodCli.isInstalled(logger: logger), - ).thenAnswer((_) => Future.value(true)); + when( + () => dartCli.isInstalled(logger: logger), + ).thenAnswer((_) => Future.value(true)); + + when( + () => veryGoodCli.isInstalled(logger: logger), + ).thenAnswer((_) => Future.value(true)); + + when( + () => veryGoodCli.packagesGet( + logger: logger, + recursive: true, + cwd: any(named: 'cwd'), + ), + ).thenAnswer((_) async {}); + + when( + () => dartCli.fix( + logger: logger, + apply: true, + cwd: any(named: 'cwd'), + ), + ).thenAnswer((_) async {}); + + when( + () => dartCli.format( + logger: logger, + cwd: any(named: 'cwd'), + path: any(named: 'path'), + ), + ).thenAnswer((_) async {}); + + when( + () => dartCli.run( + logger: logger, + cwd: any(named: 'cwd'), + args: any(named: 'args'), + command: any(named: 'command'), + ), + ).thenAnswer((_) async {}); + }); - when( - () => context.vars, - ).thenReturn({post_gen.dartFixOutputVariableKey: true}); + test('runs when enabled', () async { + await post_gen.run(context, dartCli: dartCli, veryGoodCli: veryGoodCli); + + verify( + () => veryGoodCli.packagesGet( + logger: logger, + recursive: true, + cwd: any(named: 'cwd'), + ), + ).called(1); + verify( + () => dartCli.fix( + logger: logger, + apply: true, + cwd: any(named: 'cwd'), + ), + ).called(1); + verify( + () => dartCli.format( + logger: logger, + cwd: any(named: 'cwd'), + ), + ).called(1); + verify( + () => dartCli.run( + logger: logger, + cwd: any(named: 'cwd'), + args: any(named: 'args'), + command: any(named: 'command'), + ), + ).called($pigeonPlatforms.length); + }); + group('warns', () { + test('if Dart CLI is not installed', () async { when( - () => veryGoodCli.packagesGet( - logger: logger, - recursive: true, - cwd: any(named: 'cwd'), - ), - ).thenAnswer((_) async {}); - when( - () => dartCli.fix( - logger: logger, - apply: true, - cwd: any(named: 'cwd'), - ), - ).thenAnswer((_) async {}); - when( - () => dartCli.format( - logger: logger, - cwd: any(named: 'cwd'), - ), - ).thenAnswer((_) async {}); - }); + () => dartCli.isInstalled(logger: logger), + ).thenAnswer((_) => Future.value(false)); - test('runs when enabled', () async { - await post_gen.run(context, dartCli: dartCli, veryGoodCli: veryGoodCli); + await post_gen.run(context, dartCli: dartCli); verify( - () => veryGoodCli.packagesGet( - logger: logger, - recursive: true, - cwd: any(named: 'cwd'), - ), - ).called(1); - verify( - () => dartCli.fix( - logger: logger, - apply: true, - cwd: any(named: 'cwd'), - ), - ).called(1); - verify( - () => dartCli.format( - logger: logger, - cwd: any(named: 'cwd'), + () => logger.warn( + 'Could not fix output because Dart CLI is not installed.', ), ).called(1); }); - test('does not run when disabled', () async { + test('if Very Good CLI is not installed', () async { when( - () => context.vars, - ).thenReturn({post_gen.dartFixOutputVariableKey: false}); - - await post_gen.run(context, veryGoodCli: veryGoodCli, dartCli: dartCli); - - verifyZeroInteractions(dartCli); - verifyZeroInteractions(veryGoodCli); - }); - - test('does not run when no key exists', () async { - when(() => context.vars).thenReturn({}); - - await post_gen.run(context, veryGoodCli: veryGoodCli, dartCli: dartCli); - - verifyZeroInteractions(dartCli); - verifyZeroInteractions(veryGoodCli); - }); - - group('warns', () { - test('if Dart CLI is not installed', () async { - when( - () => dartCli.isInstalled(logger: logger), - ).thenAnswer((_) => Future.value(false)); - - await post_gen.run(context, dartCli: dartCli); - - verify( - () => logger.warn( - 'Could not fix output because Dart CLI is not installed.', - ), - ).called(1); - }); - - test('if Very Good CLI is not installed', () async { - when( - () => veryGoodCli.isInstalled(logger: logger), - ).thenAnswer((_) => Future.value(false)); - - await post_gen.run( - context, - dartCli: dartCli, - veryGoodCli: veryGoodCli, - ); - - verify( - () => logger.warn( - 'Could not fix output because Very Good CLI is not installed.', - ), - ).called(1); - }); - }); + () => veryGoodCli.isInstalled(logger: logger), + ).thenAnswer((_) => Future.value(false)); - group('errors', () { - test( - 'if a $ProcessException is thrown by VeryGoodCLI.packagesGet', - () async { - const exception = ProcessException('executable', ['arguments']); - when( - () => veryGoodCli.packagesGet( - logger: logger, - recursive: true, - cwd: any(named: 'cwd'), - ), - ).thenAnswer((_) => Future.error(exception)); - - await post_gen.run( - context, - dartCli: dartCli, - veryGoodCli: veryGoodCli, - ); - - verify( - () => logger.err(''' -Running process ${exception.executable} with ${exception.arguments} failed: -${exception.message} -'''), - ).called(1); - }, + await post_gen.run( + context, + dartCli: dartCli, + veryGoodCli: veryGoodCli, ); - test( - 'if an unknown error is thrown by VeryGoodCLI.packagesGet', - () async { - final exception = Exception('error'); - when( - () => veryGoodCli.packagesGet( - logger: logger, - recursive: true, - cwd: any(named: 'cwd'), - ), - ).thenAnswer((_) => Future.error(exception)); - - await post_gen.run( - context, - dartCli: dartCli, - veryGoodCli: veryGoodCli, - ); - - verify( - () => logger.err( - '''Unknown error occurred when fixing output: $exception''', - ), - ).called(1); - }, - ); + verify( + () => logger.warn( + 'Could not fix output because Very Good CLI is not installed.', + ), + ).called(1); + }); + }); - test('if a $ProcessException is thrown by DartCli.fix', () async { + group('errors', () { + test( + 'if a $ProcessException is thrown by VeryGoodCLI.packagesGet', + () async { const exception = ProcessException('executable', ['arguments']); when( - () => dartCli.fix( + () => veryGoodCli.packagesGet( logger: logger, - apply: true, + recursive: true, cwd: any(named: 'cwd'), ), ).thenAnswer((_) => Future.error(exception)); @@ -224,19 +181,21 @@ ${exception.message} ); verify( - () => logger.err(''' -Running process ${exception.executable} with ${exception.arguments} failed: -${exception.message} -'''), + () => logger.err( + '''\n\nRunning process ${exception.executable} with ${exception.arguments} failed: ${exception.message}''', + ), ).called(1); - }); + }, + ); - test('if an unknown error is thrown by DartCli.fix', () async { + test( + 'if an unknown error is thrown by VeryGoodCLI.packagesGet', + () async { final exception = Exception('error'); when( - () => dartCli.fix( + () => veryGoodCli.packagesGet( logger: logger, - apply: true, + recursive: true, cwd: any(named: 'cwd'), ), ).thenAnswer((_) => Future.error(exception)); @@ -248,56 +207,148 @@ ${exception.message} ); verify( - () => logger.err( - '''Unknown error occurred when fixing output: $exception''', - ), + () => logger.err('Unknown error occurred: $exception'), ).called(1); + }, + ); + }); + + group('dart fix output', () { + test('does not run when disabled', () async { + when(() => context.vars).thenReturn({ + post_gen.dartFixOutputVariableKey: false, + post_gen.projectNameVariableKey: 'project_name', + for (final platform in $availablePlatforms) platform: true, }); - test('if a $ProcessException is thrown by DartCli.format', () async { - const exception = ProcessException('executable', ['arguments']); - when( - () => dartCli.format( - logger: logger, - cwd: any(named: 'cwd'), - ), - ).thenAnswer((_) => Future.error(exception)); + await post_gen.run(context, veryGoodCli: veryGoodCli, dartCli: dartCli); - await post_gen.run( - context, - dartCli: dartCli, - veryGoodCli: veryGoodCli, - ); + verifyNever( + () => dartCli.fix( + logger: logger, + apply: true, + cwd: any(named: 'cwd'), + ), + ); + verifyNever( + () => dartCli.format( + logger: logger, + cwd: any(named: 'cwd'), + path: any(named: 'path'), + ), + ); + }); - verify( - () => logger.err(''' -Running process ${exception.executable} with ${exception.arguments} failed: -${exception.message} -'''), - ).called(1); + test('does not run when no key exists', () async { + when(() => context.vars).thenReturn({ + post_gen.projectNameVariableKey: 'project_name', + for (final platform in $availablePlatforms) platform: true, }); - test('if an unknown error is thrown by DartCli.format', () async { - final exception = Exception('error'); - when( - () => dartCli.format( - logger: logger, - cwd: any(named: 'cwd'), - ), - ).thenAnswer((_) => Future.error(exception)); + await post_gen.run(context, veryGoodCli: veryGoodCli, dartCli: dartCli); - await post_gen.run( - context, - dartCli: dartCli, - veryGoodCli: veryGoodCli, - ); + verifyNever( + () => dartCli.fix( + logger: logger, + apply: true, + cwd: any(named: 'cwd'), + ), + ); + verifyNever( + () => dartCli.format( + logger: logger, + cwd: any(named: 'cwd'), + path: any(named: 'path'), + ), + ); + }); - verify( - () => logger.err( - '''Unknown error occurred when fixing output: $exception''', - ), - ).called(1); - }); + test('if a $ProcessException is thrown by DartCli.fix', () async { + const exception = ProcessException('executable', ['arguments']); + when( + () => dartCli.fix( + logger: logger, + apply: true, + cwd: any(named: 'cwd'), + ), + ).thenAnswer((_) => Future.error(exception)); + + await post_gen.run( + context, + dartCli: dartCli, + veryGoodCli: veryGoodCli, + ); + + verify( + () => logger.err( + '''\n\nRunning process ${exception.executable} with ${exception.arguments} failed: ${exception.message}''', + ), + ).called(1); + }); + + test('if an unknown error is thrown by DartCli.fix', () async { + final exception = Exception('error'); + when( + () => dartCli.fix( + logger: logger, + apply: true, + cwd: any(named: 'cwd'), + ), + ).thenAnswer((_) => Future.error(exception)); + + await post_gen.run( + context, + dartCli: dartCli, + veryGoodCli: veryGoodCli, + ); + + verify( + () => logger.err('Unknown error occurred: $exception'), + ).called(1); + }); + + test('if a $ProcessException is thrown by DartCli.format', () async { + const exception = ProcessException('executable', ['arguments']); + when( + () => dartCli.format( + logger: logger, + cwd: any(named: 'cwd'), + path: any(named: 'path'), + ), + ).thenAnswer((_) => Future.error(exception)); + + await post_gen.run( + context, + dartCli: dartCli, + veryGoodCli: veryGoodCli, + ); + + verify( + () => logger.err( + '''\n\nRunning process ${exception.executable} with ${exception.arguments} failed: ${exception.message}''', + ), + ).called(1); + }); + + test('if an unknown error is thrown by DartCli.format', () async { + final exception = Exception('error'); + when( + () => dartCli.format( + logger: logger, + cwd: any(named: 'cwd'), + path: any(named: 'path'), + ), + ).thenAnswer((_) => Future.error(exception)); + + await post_gen.run( + context, + dartCli: dartCli, + veryGoodCli: veryGoodCli, + ); + + verify( + () => logger.err('Unknown error occurred: $exception'), + ).called(1); }); }); });