From 9a3793c478d164e1b028909658b018a5c495c755 Mon Sep 17 00:00:00 2001 From: Aaron Clarke Date: Fri, 8 May 2020 13:55:32 -0700 Subject: [PATCH] - cleaned up objc generation - added ability to generate mock handlers - added pigeon.dart --- packages/pigeon/CHANGELOG.md | 6 + packages/pigeon/README.md | 4 +- .../org.eclipse.buildship.core.prefs | 11 ++ .../test_objc/android/app/.classpath | 2 +- .../e2e_tests/test_objc/ios/Runner/dartle.h | 12 +- .../e2e_tests/test_objc/ios/Runner/dartle.m | 2 +- .../e2e_tests/test_objc/lib/dartle.dart | 19 ++- packages/pigeon/example/README.md | 2 +- packages/pigeon/lib/ast.dart | 5 +- packages/pigeon/lib/dart_generator.dart | 30 +++- packages/pigeon/lib/generator_tools.dart | 2 +- packages/pigeon/lib/objc_generator.dart | 10 +- packages/pigeon/lib/pigeon.dart | 1 + packages/pigeon/lib/pigeon_lib.dart | 25 +-- .../pigeon/mock_handler_tester/.gitignore | 44 ++++++ packages/pigeon/mock_handler_tester/.metadata | 10 ++ packages/pigeon/mock_handler_tester/README.md | 3 + .../pigeon/mock_handler_tester/lib/main.dart | 5 + .../pigeon/mock_handler_tester/pubspec.yaml | 19 +++ .../mock_handler_tester/test/message.dart | 147 ++++++++++++++++++ .../mock_handler_tester/test/widget_test.dart | 28 ++++ packages/pigeon/pigeons/host2flutter.dart | 2 +- packages/pigeon/pigeons/message.dart | 4 +- packages/pigeon/pigeons/void_arg_flutter.dart | 2 +- packages/pigeon/pigeons/void_arg_host.dart | 2 +- packages/pigeon/pigeons/voidflutter.dart | 2 +- packages/pigeon/pigeons/voidhost.dart | 2 +- packages/pigeon/pubspec.yaml | 2 +- packages/pigeon/run_tests.sh | 9 ++ packages/pigeon/test/dart_generator_test.dart | 28 ++++ packages/pigeon/test/objc_generator_test.dart | 4 +- packages/pigeon/test/pigeon_lib_test.dart | 14 ++ 32 files changed, 416 insertions(+), 42 deletions(-) create mode 100644 packages/pigeon/lib/pigeon.dart create mode 100644 packages/pigeon/mock_handler_tester/.gitignore create mode 100644 packages/pigeon/mock_handler_tester/.metadata create mode 100644 packages/pigeon/mock_handler_tester/README.md create mode 100644 packages/pigeon/mock_handler_tester/lib/main.dart create mode 100644 packages/pigeon/mock_handler_tester/pubspec.yaml create mode 100644 packages/pigeon/mock_handler_tester/test/message.dart create mode 100644 packages/pigeon/mock_handler_tester/test/widget_test.dart diff --git a/packages/pigeon/CHANGELOG.md b/packages/pigeon/CHANGELOG.md index 3ac6ce9452fd..c83d80a7db43 100644 --- a/packages/pigeon/CHANGELOG.md +++ b/packages/pigeon/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.1.0 + +* Added pigeon.dart. +* Fixed some Obj-C linter problems. +* Added the ability to generate a mock handler in Dart. + ## 0.1.0-experimental.11 * Fixed setting an api to null in Java. diff --git a/packages/pigeon/README.md b/packages/pigeon/README.md index a584f45720ce..18525a1c22cc 100644 --- a/packages/pigeon/README.md +++ b/packages/pigeon/README.md @@ -44,14 +44,14 @@ doesn't need to worry about conflicting versions of Pigeon. ### Rules for defining your communication interface -1) The file should contain no methods or function definitions. +1) The file should contain no method or function definitions. 1) Datatypes are defined as classes with fields of the supported datatypes (see the supported Datatypes section). 1) Api's should be defined as an `abstract class` with either `HostApi()` or `FlutterApi()` as metadata. The former being for procedures that are defined on the host platform and the latter for procedures that are defined in Dart. 1) Method declarations on the Api classes should have one argument and a return - value whose types are defined in the file. + value whose types are defined in the file or be `void`. ## Example diff --git a/packages/pigeon/e2e_tests/test_objc/android/.settings/org.eclipse.buildship.core.prefs b/packages/pigeon/e2e_tests/test_objc/android/.settings/org.eclipse.buildship.core.prefs index e8895216fd3c..861b6d866bcf 100644 --- a/packages/pigeon/e2e_tests/test_objc/android/.settings/org.eclipse.buildship.core.prefs +++ b/packages/pigeon/e2e_tests/test_objc/android/.settings/org.eclipse.buildship.core.prefs @@ -1,2 +1,13 @@ +arguments= +auto.sync=false +build.scans.enabled=false +connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) connection.project.dir= eclipse.preferences.version=1 +gradle.user.home= +java.home=/Library/Java/JavaVirtualMachines/jdk-11.0.4.jdk/Contents/Home +jvm.arguments= +offline.mode=false +override.workspace.settings=true +show.console.view=true +show.executions.view=true diff --git a/packages/pigeon/e2e_tests/test_objc/android/app/.classpath b/packages/pigeon/e2e_tests/test_objc/android/app/.classpath index 358909413920..4a04201ca283 100644 --- a/packages/pigeon/e2e_tests/test_objc/android/app/.classpath +++ b/packages/pigeon/e2e_tests/test_objc/android/app/.classpath @@ -1,6 +1,6 @@ - + diff --git a/packages/pigeon/e2e_tests/test_objc/ios/Runner/dartle.h b/packages/pigeon/e2e_tests/test_objc/ios/Runner/dartle.h index 4371da8ba016..8ef38d789b61 100644 --- a/packages/pigeon/e2e_tests/test_objc/ios/Runner/dartle.h +++ b/packages/pigeon/e2e_tests/test_objc/ios/Runner/dartle.h @@ -1,4 +1,4 @@ -// Autogenerated from Pigeon (v0.1.0-experimental.11), do not edit directly. +// Autogenerated from Pigeon (v0.1.0), do not edit directly. // See also: https://pub.dev/packages/pigeon #import @protocol FlutterBinaryMessenger; @@ -31,15 +31,17 @@ NS_ASSUME_NONNULL_BEGIN - (void)search:(ACSearchRequest *)input completion:(void (^)(ACSearchReply *, NSError *))completion; @end @protocol ACNestedApi -- (ACSearchReply *)search:(ACNested *)input error:(FlutterError *_Nullable *_Nonnull)error; +- (nullable ACSearchReply *)search:(ACNested *)input error:(FlutterError *_Nullable *_Nonnull)error; @end -extern void ACNestedApiSetup(id binaryMessenger, id api); +extern void ACNestedApiSetup(id binaryMessenger, + id _Nullable api); @protocol ACApi -- (ACSearchReply *)search:(ACSearchRequest *)input error:(FlutterError *_Nullable *_Nonnull)error; +- (nullable ACSearchReply *)search:(ACSearchRequest *)input + error:(FlutterError *_Nullable *_Nonnull)error; @end -extern void ACApiSetup(id binaryMessenger, id api); +extern void ACApiSetup(id binaryMessenger, id _Nullable api); NS_ASSUME_NONNULL_END diff --git a/packages/pigeon/e2e_tests/test_objc/ios/Runner/dartle.m b/packages/pigeon/e2e_tests/test_objc/ios/Runner/dartle.m index bf419cc90013..2865b26a3be7 100644 --- a/packages/pigeon/e2e_tests/test_objc/ios/Runner/dartle.m +++ b/packages/pigeon/e2e_tests/test_objc/ios/Runner/dartle.m @@ -1,4 +1,4 @@ -// Autogenerated from Pigeon (v0.1.0-experimental.11), do not edit directly. +// Autogenerated from Pigeon (v0.1.0), do not edit directly. // See also: https://pub.dev/packages/pigeon #import "dartle.h" #import diff --git a/packages/pigeon/e2e_tests/test_objc/lib/dartle.dart b/packages/pigeon/e2e_tests/test_objc/lib/dartle.dart index f31480722717..903c6b78cdff 100644 --- a/packages/pigeon/e2e_tests/test_objc/lib/dartle.dart +++ b/packages/pigeon/e2e_tests/test_objc/lib/dartle.dart @@ -1,4 +1,4 @@ -// Autogenerated from Pigeon (v0.1.0-experimental.11), do not edit directly. +// Autogenerated from Pigeon (v0.1.0), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import import 'dart:async'; @@ -128,3 +128,20 @@ class Api { } } } + +abstract class MockApi { + SearchReply search(SearchRequest arg); +} + +void MockApiSetup(MockApi api) { + { + const BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.Api.search', StandardMessageCodec()); + channel.setMockMessageHandler((dynamic message) async { + final Map mapMessage = message as Map; + final SearchRequest input = SearchRequest._fromMap(mapMessage); + final SearchReply output = api.search(input); + return {'result': output._toMap()}; + }); + } +} diff --git a/packages/pigeon/example/README.md b/packages/pigeon/example/README.md index 00f39ce7c84e..6c422ac43dd1 100644 --- a/packages/pigeon/example/README.md +++ b/packages/pigeon/example/README.md @@ -3,7 +3,7 @@ ## message.dart ```dart -import 'package:pigeon/pigeon_lib.dart'; +import 'package:pigeon/pigeon.dart'; class SearchRequest { String query; diff --git a/packages/pigeon/lib/ast.dart b/packages/pigeon/lib/ast.dart index 6c0e42d70ce9..cf57e2970d05 100644 --- a/packages/pigeon/lib/ast.dart +++ b/packages/pigeon/lib/ast.dart @@ -32,7 +32,7 @@ class Method extends Node { /// Represents a collection of [Method]s that are hosted ona given [location]. class Api extends Node { /// Parametric constructor for [Api]. - Api({this.name, this.location, this.methods}); + Api({this.name, this.location, this.methods, this.mockDartHandler}); /// The name of the API. String name; @@ -42,6 +42,9 @@ class Api extends Node { /// List of methods inside the API. List methods; + + /// The name of the Dart mock interface to generate to help with testing. + String mockDartHandler; } /// Represents a field on a [Class]. diff --git a/packages/pigeon/lib/dart_generator.dart b/packages/pigeon/lib/dart_generator.dart index f0120cc0ed67..dfdcdaca2e4d 100644 --- a/packages/pigeon/lib/dart_generator.dart +++ b/packages/pigeon/lib/dart_generator.dart @@ -60,7 +60,8 @@ if (replyMap == null) { indent.writeln(''); } -void _writeFlutterApi(Indent indent, Api api) { +void _writeFlutterApi(Indent indent, Api api, + {String Function(Method) channelNameFunc, bool isMockHandler = false}) { assert(api.location == ApiLocation.flutter); indent.write('abstract class ${api.name} '); indent.scoped('{', '}', () { @@ -79,11 +80,16 @@ void _writeFlutterApi(Indent indent, Api api) { indent.writeln('const BasicMessageChannel channel ='); indent.inc(); indent.inc(); + final String channelName = channelNameFunc == null + ? makeChannelName(api, func) + : channelNameFunc(func); indent.writeln( - 'BasicMessageChannel(\'${makeChannelName(api, func)}\', StandardMessageCodec());'); + 'BasicMessageChannel(\'$channelName\', StandardMessageCodec());'); indent.dec(); indent.dec(); - indent.write('channel.setMessageHandler((dynamic message) async '); + final String messageHandlerSetter = + isMockHandler ? 'setMockMessageHandler' : 'setMessageHandler'; + indent.write('channel.$messageHandlerSetter((dynamic message) async '); indent.scoped('{', '});', () { final String argType = func.argType; final String returnType = func.returnType; @@ -99,9 +105,16 @@ void _writeFlutterApi(Indent indent, Api api) { } if (returnType == 'void') { indent.writeln('$call;'); + if (isMockHandler) { + indent.writeln('return {};'); + } } else { indent.writeln('final $returnType output = $call;'); - indent.writeln('return output._toMap();'); + const String returnExpresion = 'output._toMap()'; + final String returnStatement = isMockHandler + ? 'return {\'${Keys.result}\': $returnExpresion};' + : 'return $returnExpresion;'; + indent.writeln(returnStatement); } }); }); @@ -166,6 +179,15 @@ void generateDart(Root root, StringSink sink) { for (Api api in root.apis) { if (api.location == ApiLocation.host) { _writeHostApi(indent, api); + if (api.mockDartHandler != null) { + final Api mockApi = Api( + name: api.mockDartHandler, + methods: api.methods, + location: ApiLocation.flutter); + _writeFlutterApi(indent, mockApi, + channelNameFunc: (Method func) => makeChannelName(api, func), + isMockHandler: true); + } } else if (api.location == ApiLocation.flutter) { _writeFlutterApi(indent, api); } diff --git a/packages/pigeon/lib/generator_tools.dart b/packages/pigeon/lib/generator_tools.dart index 57d8340368c7..5df2840ce140 100644 --- a/packages/pigeon/lib/generator_tools.dart +++ b/packages/pigeon/lib/generator_tools.dart @@ -8,7 +8,7 @@ import 'dart:mirrors'; import 'ast.dart'; /// The current version of pigeon. -const String pigeonVersion = '0.1.0-experimental.11'; +const String pigeonVersion = '0.1.0'; /// Read all the content from [stdin] to a String. String readStdin() { diff --git a/packages/pigeon/lib/objc_generator.dart b/packages/pigeon/lib/objc_generator.dart index 2f55ec697010..dd8dbb953e5f 100644 --- a/packages/pigeon/lib/objc_generator.dart +++ b/packages/pigeon/lib/objc_generator.dart @@ -90,7 +90,7 @@ void generateObjcHeader(ObjcOptions options, Root root, StringSink sink) { for (Class klass in root.classes) { indent.writeln( - '@interface ${_className(options.prefix, klass.name)} : NSObject '); + '@interface ${_className(options.prefix, klass.name)} : NSObject'); for (Field field in klass.fields) { final HostDatatype hostDatatype = getHostDatatype( field, root.classes, _objcTypeForDartType, @@ -115,20 +115,20 @@ void generateObjcHeader(ObjcOptions options, Root root, StringSink sink) { final String returnTypeName = _className(options.prefix, func.returnType); final String returnType = - func.returnType == 'void' ? 'void' : '$returnTypeName *'; + func.returnType == 'void' ? 'void' : 'nullable $returnTypeName *'; if (func.argType == 'void') { indent.writeln( - '-($returnType)${func.name}:(FlutterError * _Nullable * _Nonnull)error;'); + '-($returnType)${func.name}:(FlutterError *_Nullable *_Nonnull)error;'); } else { final String argType = _className(options.prefix, func.argType); indent.writeln( - '-($returnType)${func.name}:($argType*)input error:(FlutterError * _Nullable * _Nonnull)error;'); + '-($returnType)${func.name}:($argType*)input error:(FlutterError *_Nullable *_Nonnull)error;'); } } indent.writeln('@end'); indent.writeln(''); indent.writeln( - 'extern void ${apiName}Setup(id binaryMessenger, id<$apiName> api);'); + 'extern void ${apiName}Setup(id binaryMessenger, id<$apiName> _Nullable api);'); indent.writeln(''); } else if (api.location == ApiLocation.flutter) { indent.writeln('@interface $apiName : NSObject'); diff --git a/packages/pigeon/lib/pigeon.dart b/packages/pigeon/lib/pigeon.dart new file mode 100644 index 000000000000..7e4ba0057a17 --- /dev/null +++ b/packages/pigeon/lib/pigeon.dart @@ -0,0 +1 @@ +export 'pigeon_lib.dart'; diff --git a/packages/pigeon/lib/pigeon_lib.dart b/packages/pigeon/lib/pigeon_lib.dart index ff695ac193dd..13da9166c722 100644 --- a/packages/pigeon/lib/pigeon_lib.dart +++ b/packages/pigeon/lib/pigeon_lib.dart @@ -30,7 +30,11 @@ const List _validTypes = [ /// Metadata to mark an API which will be implemented on the host platform. class HostApi { /// Parametric constructor for [HostApi]. - const HostApi(); + const HostApi({this.mockDartHandler}); + + /// The name of the interface to generate to receive Pigeon messages locally + /// for testing. + final String mockDartHandler; } /// Metadata to mark an API which will be implemented in Flutter. @@ -56,16 +60,16 @@ class Error { bool _isApi(ClassMirror classMirror) { return classMirror.isAbstract && - (_isHostApi(classMirror) || _isFlutterApi(classMirror)); + (_getHostApi(classMirror) != null || _isFlutterApi(classMirror)); } -bool _isHostApi(ClassMirror apiMirror) { +HostApi _getHostApi(ClassMirror apiMirror) { for (InstanceMirror instance in apiMirror.metadata) { if (instance.reflectee is HostApi) { - return true; + return instance.reflectee; } } - return false; + return null; } bool _isFlutterApi(ClassMirror apiMirror) { @@ -180,11 +184,12 @@ class Pigeon { MirrorSystem.getName(declaration.returnType.simpleName)); } } - root.apis.add(Api() - ..name = MirrorSystem.getName(apiMirror.simpleName) - ..location = - _isHostApi(apiMirror) ? ApiLocation.host : ApiLocation.flutter - ..methods = functions); + final HostApi hostApi = _getHostApi(apiMirror); + root.apis.add(Api( + name: MirrorSystem.getName(apiMirror.simpleName), + location: hostApi != null ? ApiLocation.host : ApiLocation.flutter, + methods: functions, + mockDartHandler: hostApi?.mockDartHandler)); } final List validateErrors = _validateAst(root); diff --git a/packages/pigeon/mock_handler_tester/.gitignore b/packages/pigeon/mock_handler_tester/.gitignore new file mode 100644 index 000000000000..f3c205341e7d --- /dev/null +++ b/packages/pigeon/mock_handler_tester/.gitignore @@ -0,0 +1,44 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Exceptions to above rules. +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages diff --git a/packages/pigeon/mock_handler_tester/.metadata b/packages/pigeon/mock_handler_tester/.metadata new file mode 100644 index 000000000000..bb4f09ae7510 --- /dev/null +++ b/packages/pigeon/mock_handler_tester/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: e6b697a9df5e9ce933024be3334e86b599c60e71 + channel: unknown + +project_type: app diff --git a/packages/pigeon/mock_handler_tester/README.md b/packages/pigeon/mock_handler_tester/README.md new file mode 100644 index 000000000000..fd24e4f90cd7 --- /dev/null +++ b/packages/pigeon/mock_handler_tester/README.md @@ -0,0 +1,3 @@ +# mock_handler_tester + +A test bed for testing the code generated by `mockDartHandler`. diff --git a/packages/pigeon/mock_handler_tester/lib/main.dart b/packages/pigeon/mock_handler_tester/lib/main.dart new file mode 100644 index 000000000000..e4b5c93dede1 --- /dev/null +++ b/packages/pigeon/mock_handler_tester/lib/main.dart @@ -0,0 +1,5 @@ +import 'package:flutter/material.dart'; + +void main() { + runApp(Container()); +} diff --git a/packages/pigeon/mock_handler_tester/pubspec.yaml b/packages/pigeon/mock_handler_tester/pubspec.yaml new file mode 100644 index 000000000000..6abb15122cce --- /dev/null +++ b/packages/pigeon/mock_handler_tester/pubspec.yaml @@ -0,0 +1,19 @@ +name: mock_handler_tester +description: A testbed for testing mockDartHandler. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev +version: 1.0.0+1 + +environment: + sdk: ">=2.7.0 <3.0.0" + +dependencies: + flutter: + sdk: flutter + cupertino_icons: ^0.1.3 + +dev_dependencies: + flutter_test: + sdk: flutter + +flutter: + uses-material-design: true diff --git a/packages/pigeon/mock_handler_tester/test/message.dart b/packages/pigeon/mock_handler_tester/test/message.dart new file mode 100644 index 000000000000..903c6b78cdff --- /dev/null +++ b/packages/pigeon/mock_handler_tester/test/message.dart @@ -0,0 +1,147 @@ +// Autogenerated from Pigeon (v0.1.0), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import +import 'dart:async'; +import 'package:flutter/services.dart'; + +class SearchReply { + String result; + String error; + // ignore: unused_element + Map _toMap() { + final Map pigeonMap = {}; + pigeonMap['result'] = result; + pigeonMap['error'] = error; + return pigeonMap; + } + + // ignore: unused_element + static SearchReply _fromMap(Map pigeonMap) { + final SearchReply result = SearchReply(); + result.result = pigeonMap['result']; + result.error = pigeonMap['error']; + return result; + } +} + +class SearchRequest { + String query; + int anInt; + bool aBool; + // ignore: unused_element + Map _toMap() { + final Map pigeonMap = {}; + pigeonMap['query'] = query; + pigeonMap['anInt'] = anInt; + pigeonMap['aBool'] = aBool; + return pigeonMap; + } + + // ignore: unused_element + static SearchRequest _fromMap(Map pigeonMap) { + final SearchRequest result = SearchRequest(); + result.query = pigeonMap['query']; + result.anInt = pigeonMap['anInt']; + result.aBool = pigeonMap['aBool']; + return result; + } +} + +class Nested { + SearchRequest request; + // ignore: unused_element + Map _toMap() { + final Map pigeonMap = {}; + pigeonMap['request'] = request._toMap(); + return pigeonMap; + } + + // ignore: unused_element + static Nested _fromMap(Map pigeonMap) { + final Nested result = Nested(); + result.request = SearchRequest._fromMap(pigeonMap['request']); + return result; + } +} + +abstract class FlutterSearchApi { + SearchReply search(SearchRequest arg); +} + +void FlutterSearchApiSetup(FlutterSearchApi api) { + { + const BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.FlutterSearchApi.search', StandardMessageCodec()); + channel.setMessageHandler((dynamic message) async { + final Map mapMessage = message as Map; + final SearchRequest input = SearchRequest._fromMap(mapMessage); + final SearchReply output = api.search(input); + return output._toMap(); + }); + } +} + +class NestedApi { + Future search(Nested arg) async { + final Map requestMap = arg._toMap(); + const BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.NestedApi.search', StandardMessageCodec()); + + final Map replyMap = await channel.send(requestMap); + if (replyMap == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + details: null); + } else if (replyMap['error'] != null) { + final Map error = replyMap['error']; + throw PlatformException( + code: error['code'], + message: error['message'], + details: error['details']); + } else { + return SearchReply._fromMap(replyMap['result']); + } + } +} + +class Api { + Future search(SearchRequest arg) async { + final Map requestMap = arg._toMap(); + const BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.Api.search', StandardMessageCodec()); + + final Map replyMap = await channel.send(requestMap); + if (replyMap == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + details: null); + } else if (replyMap['error'] != null) { + final Map error = replyMap['error']; + throw PlatformException( + code: error['code'], + message: error['message'], + details: error['details']); + } else { + return SearchReply._fromMap(replyMap['result']); + } + } +} + +abstract class MockApi { + SearchReply search(SearchRequest arg); +} + +void MockApiSetup(MockApi api) { + { + const BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.Api.search', StandardMessageCodec()); + channel.setMockMessageHandler((dynamic message) async { + final Map mapMessage = message as Map; + final SearchRequest input = SearchRequest._fromMap(mapMessage); + final SearchReply output = api.search(input); + return {'result': output._toMap()}; + }); + } +} diff --git a/packages/pigeon/mock_handler_tester/test/widget_test.dart b/packages/pigeon/mock_handler_tester/test/widget_test.dart new file mode 100644 index 000000000000..9a20fc9364a0 --- /dev/null +++ b/packages/pigeon/mock_handler_tester/test/widget_test.dart @@ -0,0 +1,28 @@ +// Copyright 2020 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_test/flutter_test.dart'; +import 'message.dart'; + +class Mock implements MockApi { + bool didCall = false; + @override + SearchReply search(SearchRequest arg) { + didCall = true; + return SearchReply()..result = arg.query; + } +} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + test('description', () async { + final Api api = Api(); + final Mock mock = Mock(); + MockApiSetup(mock); + final SearchReply reply = await api.search(SearchRequest()..query = 'foo'); + expect(mock.didCall, true); + expect(reply.result, 'foo'); + }); +} diff --git a/packages/pigeon/pigeons/host2flutter.dart b/packages/pigeon/pigeons/host2flutter.dart index 6d13569d0562..673ff56334f9 100644 --- a/packages/pigeon/pigeons/host2flutter.dart +++ b/packages/pigeon/pigeons/host2flutter.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:pigeon/pigeon_lib.dart'; +import 'package:pigeon/pigeon.dart'; class SearchRequest { String query; diff --git a/packages/pigeon/pigeons/message.dart b/packages/pigeon/pigeons/message.dart index 955836642ba7..395ce429ec85 100644 --- a/packages/pigeon/pigeons/message.dart +++ b/packages/pigeon/pigeons/message.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:pigeon/pigeon_lib.dart'; +import 'package:pigeon/pigeon.dart'; class SearchRequest { String query; @@ -15,7 +15,7 @@ class SearchReply { String error; } -@HostApi() +@HostApi(mockDartHandler: 'MockApi') abstract class Api { SearchReply search(SearchRequest request); } diff --git a/packages/pigeon/pigeons/void_arg_flutter.dart b/packages/pigeon/pigeons/void_arg_flutter.dart index 29accee79fd8..9886e0e10c0b 100644 --- a/packages/pigeon/pigeons/void_arg_flutter.dart +++ b/packages/pigeon/pigeons/void_arg_flutter.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:pigeon/pigeon_lib.dart'; +import 'package:pigeon/pigeon.dart'; class Result { int code; diff --git a/packages/pigeon/pigeons/void_arg_host.dart b/packages/pigeon/pigeons/void_arg_host.dart index 5935dd2aa8f6..38229a08fa70 100644 --- a/packages/pigeon/pigeons/void_arg_host.dart +++ b/packages/pigeon/pigeons/void_arg_host.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:pigeon/pigeon_lib.dart'; +import 'package:pigeon/pigeon.dart'; class Result { int code; diff --git a/packages/pigeon/pigeons/voidflutter.dart b/packages/pigeon/pigeons/voidflutter.dart index 59c77e0f658a..9606c210e8af 100644 --- a/packages/pigeon/pigeons/voidflutter.dart +++ b/packages/pigeon/pigeons/voidflutter.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:pigeon/pigeon_lib.dart'; +import 'package:pigeon/pigeon.dart'; class SetRequest { int value; diff --git a/packages/pigeon/pigeons/voidhost.dart b/packages/pigeon/pigeons/voidhost.dart index 9769da5dca49..ffd7f01c4db0 100644 --- a/packages/pigeon/pigeons/voidhost.dart +++ b/packages/pigeon/pigeons/voidhost.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:pigeon/pigeon_lib.dart'; +import 'package:pigeon/pigeon.dart'; class SetRequest { int value; diff --git a/packages/pigeon/pubspec.yaml b/packages/pigeon/pubspec.yaml index f3c11e9a3727..b10abb959dbe 100644 --- a/packages/pigeon/pubspec.yaml +++ b/packages/pigeon/pubspec.yaml @@ -1,5 +1,5 @@ name: pigeon -version: 0.1.0-experimental.11 +version: 0.1.0 description: Code generator tool to make communication between Flutter and the host platform type-safe and easier. homepage: https://github.com/flutter/packages/tree/master/packages/pigeon dependencies: diff --git a/packages/pigeon/run_tests.sh b/packages/pigeon/run_tests.sh index c04296a8d279..9a81105489cf 100755 --- a/packages/pigeon/run_tests.sh +++ b/packages/pigeon/run_tests.sh @@ -62,6 +62,15 @@ test_pigeon_ios ./pigeons/voidflutter.dart test_pigeon_ios ./pigeons/void_arg_host.dart test_pigeon_ios ./pigeons/void_arg_flutter.dart +pushd $PWD +pub run pigeon \ + --input pigeons/message.dart \ + --dart_out mock_handler_tester/test/message.dart +dartfmt -w mock_handler_tester/test/message.dart +cd mock_handler_tester +flutter test +popd + DARTLE_H="e2e_tests/test_objc/ios/Runner/dartle.h" DARTLE_M="e2e_tests/test_objc/ios/Runner/dartle.m" DARTLE_DART="e2e_tests/test_objc/lib/dartle.dart" diff --git a/packages/pigeon/test/dart_generator_test.dart b/packages/pigeon/test/dart_generator_test.dart index 189744d76317..a3a6da992a25 100644 --- a/packages/pigeon/test/dart_generator_test.dart +++ b/packages/pigeon/test/dart_generator_test.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:pigeon/generator_tools.dart'; import 'package:test/test.dart'; import 'package:pigeon/dart_generator.dart'; import 'package:pigeon/ast.dart'; @@ -149,4 +150,31 @@ void main() { final String code = sink.toString(); expect(code, matches('channel\.send[(]null[)]')); }); + + test('mock dart handler', () { + final Root root = Root(apis: [ + Api( + name: 'Api', + location: ApiLocation.host, + mockDartHandler: 'ApiMock', + methods: [ + Method(name: 'doSomething', argType: 'Input', returnType: 'Output'), + Method(name: 'voidReturner', argType: 'Input', returnType: 'void') + ]) + ], classes: [ + Class( + name: 'Input', + fields: [Field(name: 'input', dataType: 'String')]), + Class( + name: 'Output', + fields: [Field(name: 'output', dataType: 'String')]) + ]); + final StringBuffer sink = StringBuffer(); + generateDart(root, sink); + final String code = sink.toString(); + expect(code, matches('abstract class ApiMock')); + expect(code, isNot(matches('\.ApiMock\.doSomething'))); + expect(code, matches('\'${Keys.result}\': output._toMap()')); + expect(code, contains('return {};')); + }); } diff --git a/packages/pigeon/test/objc_generator_test.dart b/packages/pigeon/test/objc_generator_test.dart index aba0ed624eda..939bed1f530a 100644 --- a/packages/pigeon/test/objc_generator_test.dart +++ b/packages/pigeon/test/objc_generator_test.dart @@ -52,8 +52,8 @@ void main() { expect(code, contains('@interface Input')); expect(code, contains('@interface Output')); expect(code, contains('@protocol Api')); - expect(code, matches('Output.*doSomething.*Input.*FlutterError')); - expect(code, contains('ApiSetup(')); + expect(code, matches('nullable Output.*doSomething.*Input.*FlutterError')); + expect(code, matches('ApiSetup.*\.*_Nullable')); }); test('gen one api source', () { diff --git a/packages/pigeon/test/pigeon_lib_test.dart b/packages/pigeon/test/pigeon_lib_test.dart index 48f84e50cf82..e72f7d8eed53 100644 --- a/packages/pigeon/test/pigeon_lib_test.dart +++ b/packages/pigeon/test/pigeon_lib_test.dart @@ -48,6 +48,11 @@ abstract class VoidArgApi { Output1 doit(); } +@HostApi(mockDartHandler: 'ApiWithMockDartClassMock') +abstract class ApiWithMockDartClass { + Output1 doit(); +} + void main() { test('parse args - input', () { final PigeonOptions opts = @@ -176,4 +181,13 @@ void main() { expect(results.root.apis[0].methods[0].returnType, equals('Output1')); expect(results.root.apis[0].methods[0].argType, equals('void')); }); + + test('mockDartClass', () { + final Pigeon pigeon = Pigeon.setup(); + final ParseResults results = pigeon.parse([ApiWithMockDartClass]); + expect(results.errors.length, equals(0)); + expect(results.root.apis.length, equals(1)); + expect(results.root.apis[0].mockDartHandler, + equals('ApiWithMockDartClassMock')); + }); }