| // Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| // Objective C support is only available on mac. |
| @TestOn('mac-os') |
| import 'dart:async'; |
| import 'dart:ffi'; |
| import 'dart:io'; |
| import 'dart:isolate'; |
| |
| import 'package:ffi/ffi.dart'; |
| import 'package:objective_c/objective_c.dart' hide ObjCProtocolImpl; |
| import 'package:objective_c/src/internal.dart' show getProtocol; |
| import 'package:path/path.dart' as path; |
| import 'package:test/test.dart'; |
| |
| import '../test_utils.dart'; |
| import 'protocol_bindings.dart'; |
| import 'util.dart'; |
| |
| typedef InstanceMethodBlock = ObjCBlock_NSString_ffiVoid_NSString_ffiDouble; |
| typedef OptionalMethodBlock = ObjCBlock_Int32_ffiVoid_SomeStruct; |
| typedef VoidMethodBlock = ObjCBlock_ffiVoid_ffiVoid_Int32; |
| typedef OtherMethodBlock = ObjCBlock_Int32_ffiVoid_Int32_Int32_Int32_Int32; |
| |
| void main() { |
| late ProtocolTestObjCLibrary lib; |
| |
| group('protocol', () { |
| setUpAll(() { |
| final dylib = File( |
| path.join( |
| packagePathForTests, |
| 'test', |
| 'native_objc_test', |
| 'objc_test.dylib', |
| ), |
| ); |
| verifySetupFile(dylib); |
| lib = ProtocolTestObjCLibrary(DynamicLibrary.open(dylib.absolute.path)); |
| generateBindingsForCoverage('protocol'); |
| }); |
| |
| group('ObjC implementation', () { |
| test('Method implementation', () { |
| final protocolImpl = ObjCProtocolImpl(); |
| final MyProtocol asMyProtocol = protocolImpl; |
| final SecondaryProtocol asSecondaryProtocol = protocolImpl; |
| |
| final consumer = ProtocolConsumer(); |
| |
| // Required instance method. |
| final result = consumer.callInstanceMethod(asMyProtocol); |
| expect( |
| result.toDartString(), |
| 'ObjCProtocolImpl: Hello from ObjC: 3.14', |
| ); |
| |
| // Optional instance method. |
| final intResult = consumer.callOptionalMethod(asMyProtocol); |
| expect(intResult, 579); |
| |
| // Required instance method from secondary protocol. |
| final otherIntResult = consumer.callOtherMethod(asSecondaryProtocol); |
| expect(otherIntResult, 10); |
| |
| // Method using two protocols. |
| final twoMethodResult = consumer.callTwoMethods(asMyProtocol); |
| expect(twoMethodResult, 579); |
| }); |
| |
| test('Method implementation, invoke from Dart', () { |
| final protocolImpl = ObjCProtocolImpl(); |
| |
| // Required instance method. |
| final result = protocolImpl.instanceMethod( |
| "abc".toNSString(), |
| withDouble: 123, |
| ); |
| expect(result.toDartString(), 'ObjCProtocolImpl: abc: 123.00'); |
| |
| // Optional instance method. |
| final structPtr = calloc<SomeStruct>(); |
| structPtr.ref.x = 12; |
| structPtr.ref.y = 34; |
| final intResult = protocolImpl.optionalMethod(structPtr.ref); |
| expect(intResult, 46); |
| calloc.free(structPtr); |
| |
| // Required instance method from secondary protocol. |
| final otherIntResult = protocolImpl.otherMethod(2, b: 4, c: 6, d: 8); |
| expect(otherIntResult, 20); |
| |
| // Method from a protocol that isn't included by the filters. |
| expect(protocolImpl.fooMethod(), 2468); |
| |
| // Class methods. |
| expect(ObjCProtocolImpl.requiredClassMethod(), 9876); |
| expect(ObjCProtocolImpl.optionalClassMethod(), 5432); |
| }); |
| |
| test('Unimplemented method', () { |
| final protocolImpl = ObjCProtocolImplMissingMethod(); |
| final consumer = ProtocolConsumer(); |
| |
| // Optional instance method, not implemented. |
| final intResult = consumer.callOptionalMethod(protocolImpl); |
| expect(intResult, -999); |
| }); |
| |
| test('Unimplemented method, invoke from Dart', () { |
| final protocolImpl = ObjCProtocolImplMissingMethod(); |
| |
| // Optional instance method, not implemented. |
| final structPtr = calloc<SomeStruct>(); |
| structPtr.ref.x = 12; |
| structPtr.ref.y = 34; |
| expect( |
| () => protocolImpl.optionalMethod(structPtr.ref), |
| throwsA(isA<UnimplementedOptionalMethodException>()), |
| ); |
| calloc.free(structPtr); |
| |
| expect( |
| () => ObjCProtocolImpl.unimplementedOtionalClassMethod(), |
| throwsA(isA<UnimplementedOptionalMethodException>()), |
| ); |
| }); |
| }); |
| |
| group('Dart implementation using helpers', () { |
| test('Method implementation', () { |
| final consumer = ProtocolConsumer(); |
| |
| final MyProtocol myProtocol = MyProtocol$Builder.implement( |
| instanceMethod_withDouble_: (NSString s, double x) { |
| return 'MyProtocol: ${s.toDartString()}: $x'.toNSString(); |
| }, |
| optionalMethod_: (SomeStruct s) { |
| return s.y - s.x; |
| }, |
| ); |
| |
| expect(MyProtocol.conformsTo(myProtocol), isTrue); |
| expect(SecondaryProtocol.conformsTo(myProtocol), isFalse); |
| |
| // Required instance method. |
| final result = consumer.callInstanceMethod(myProtocol); |
| expect(result.toDartString(), 'MyProtocol: Hello from ObjC: 3.14'); |
| |
| // Optional instance method. |
| final intResult = consumer.callOptionalMethod(myProtocol); |
| expect(intResult, 333); |
| }); |
| |
| test('Multiple protocol implementation', () { |
| final consumer = ProtocolConsumer(); |
| |
| final protocolBuilder = ObjCProtocolBuilder(); |
| MyProtocol$Builder.addToBuilder( |
| protocolBuilder, |
| instanceMethod_withDouble_: (NSString s, double x) { |
| return 'ProtocolBuilder: ${s.toDartString()}: $x'.toNSString(); |
| }, |
| optionalMethod_: (SomeStruct s) { |
| return s.y - s.x; |
| }, |
| ); |
| SecondaryProtocol$Builder.addToBuilder( |
| protocolBuilder, |
| otherMethod_b_c_d_: (int a, int b, int c, int d) { |
| return a * b * c * d; |
| }, |
| ); |
| final protocolImpl = protocolBuilder.build(); |
| final MyProtocol asMyProtocol = MyProtocol.as(protocolImpl); |
| final SecondaryProtocol asSecondaryProtocol = SecondaryProtocol.as( |
| protocolImpl, |
| ); |
| |
| expect(MyProtocol.conformsTo(protocolImpl), isTrue); |
| expect(SecondaryProtocol.conformsTo(protocolImpl), isTrue); |
| |
| // Required instance method. |
| final result = consumer.callInstanceMethod(asMyProtocol); |
| expect(result.toDartString(), 'ProtocolBuilder: Hello from ObjC: 3.14'); |
| |
| // Required instance method from secondary protocol. |
| final otherIntResult = consumer.callOtherMethod(asSecondaryProtocol); |
| expect(otherIntResult, 24); |
| |
| // Method using two protocols. |
| final twoMethodResult = consumer.callTwoMethods(asMyProtocol); |
| expect(twoMethodResult, 222000); |
| }); |
| |
| test('Multiple protocol implementation using method fields', () { |
| final consumer = ProtocolConsumer(); |
| |
| final protocolBuilder = ObjCProtocolBuilder(); |
| MyProtocol$Builder.instanceMethod_withDouble_.implement( |
| protocolBuilder, |
| (NSString s, double x) { |
| return 'ProtocolBuilder: ${s.toDartString()}: $x'.toNSString(); |
| }, |
| ); |
| SecondaryProtocol$Builder.otherMethod_b_c_d_.implement( |
| protocolBuilder, |
| (int a, int b, int c, int d) { |
| return a * b * c * d; |
| }, |
| ); |
| final protocolImpl = protocolBuilder.build(); |
| final MyProtocol asMyProtocol = MyProtocol.as(protocolImpl); |
| final SecondaryProtocol asSecondaryProtocol = SecondaryProtocol.as( |
| protocolImpl, |
| ); |
| |
| // Required instance method. |
| final result = consumer.callInstanceMethod(asMyProtocol); |
| expect(result.toDartString(), 'ProtocolBuilder: Hello from ObjC: 3.14'); |
| |
| // Required instance method from secondary protocol. |
| final otherIntResult = consumer.callOtherMethod(asSecondaryProtocol); |
| expect(otherIntResult, 24); |
| }); |
| |
| test('Unimplemented method', () { |
| final consumer = ProtocolConsumer(); |
| |
| final MyProtocol myProtocol = MyProtocol$Builder.implement( |
| instanceMethod_withDouble_: (NSString s, double x) { |
| throw UnimplementedError(); |
| }, |
| ); |
| |
| // Optional instance method, not implemented. |
| final intResult = consumer.callOptionalMethod(myProtocol); |
| expect(intResult, -999); |
| }); |
| |
| test('Method implementation as listener', () async { |
| final consumer = ProtocolConsumer(); |
| |
| final listenerCompleter = Completer<int>(); |
| final MyProtocol myProtocol = MyProtocol$Builder.implementAsListener( |
| instanceMethod_withDouble_: (NSString s, double x) { |
| return 'MyProtocol: ${s.toDartString()}: $x'.toNSString(); |
| }, |
| optionalMethod_: (SomeStruct s) { |
| return s.y - s.x; |
| }, |
| voidMethod_: (int x) { |
| listenerCompleter.complete(x); |
| }, |
| ); |
| |
| // Required instance method. |
| final result = consumer.callInstanceMethod(myProtocol); |
| expect(result.toDartString(), 'MyProtocol: Hello from ObjC: 3.14'); |
| |
| // Optional instance method. |
| final intResult = consumer.callOptionalMethod(myProtocol); |
| expect(intResult, 333); |
| |
| // Listener method. |
| consumer.callMethodOnRandomThread(myProtocol); |
| expect(await listenerCompleter.future, 123); |
| }); |
| |
| test('Multiple protocol implementation as listener', () async { |
| final consumer = ProtocolConsumer(); |
| |
| final listenerCompleter = Completer<int>(); |
| final protocolBuilder = ObjCProtocolBuilder(); |
| MyProtocol$Builder.addToBuilderAsListener( |
| protocolBuilder, |
| instanceMethod_withDouble_: (NSString s, double x) { |
| return 'ProtocolBuilder: ${s.toDartString()}: $x'.toNSString(); |
| }, |
| voidMethod_: (int x) { |
| listenerCompleter.complete(x); |
| }, |
| ); |
| SecondaryProtocol$Builder.addToBuilder( |
| protocolBuilder, |
| otherMethod_b_c_d_: (int a, int b, int c, int d) { |
| return a * b * c * d; |
| }, |
| ); |
| final protocolImpl = protocolBuilder.build(); |
| final MyProtocol asMyProtocol = MyProtocol.as(protocolImpl); |
| final SecondaryProtocol asSecondaryProtocol = SecondaryProtocol.as( |
| protocolImpl, |
| ); |
| |
| // Required instance method. |
| final result = consumer.callInstanceMethod(asMyProtocol); |
| expect(result.toDartString(), 'ProtocolBuilder: Hello from ObjC: 3.14'); |
| |
| // Required instance method from secondary protocol. |
| final otherIntResult = consumer.callOtherMethod(asSecondaryProtocol); |
| expect(otherIntResult, 24); |
| |
| // Listener method. |
| consumer.callMethodOnRandomThread(asMyProtocol); |
| expect(await listenerCompleter.future, 123); |
| }); |
| |
| void waitSync(Duration d) { |
| final t = Stopwatch(); |
| t.start(); |
| while (t.elapsed < d) { |
| // Waiting... |
| } |
| } |
| |
| test('Method implementation as blocking', () async { |
| final consumer = ProtocolConsumer(); |
| |
| final listenerCompleter = Completer<int>(); |
| final MyProtocol myProtocol = MyProtocol$Builder.implementAsBlocking( |
| instanceMethod_withDouble_: (NSString s, double x) { |
| throw UnimplementedError(); |
| }, |
| voidMethod_: (int x) { |
| listenerCompleter.complete(x); |
| }, |
| intPtrMethod_: (Pointer<Int32> ptr) { |
| waitSync(Duration(milliseconds: 100)); |
| ptr.value = 123456; |
| }, |
| ); |
| |
| // Blocking method. |
| consumer.callBlockingMethodOnRandomThread(myProtocol); |
| expect(await listenerCompleter.future, 123456); |
| }); |
| |
| test('Multiple protocol implementation as blocking', () async { |
| final consumer = ProtocolConsumer(); |
| |
| final listenerCompleter = Completer<int>(); |
| final protocolBuilder = ObjCProtocolBuilder(); |
| MyProtocol$Builder.addToBuilderAsBlocking( |
| protocolBuilder, |
| instanceMethod_withDouble_: (NSString s, double x) { |
| throw UnimplementedError(); |
| }, |
| voidMethod_: (int x) { |
| listenerCompleter.complete(x); |
| }, |
| intPtrMethod_: (Pointer<Int32> ptr) { |
| waitSync(Duration(milliseconds: 100)); |
| ptr.value = 98765; |
| }, |
| ); |
| SecondaryProtocol$Builder.addToBuilder( |
| protocolBuilder, |
| otherMethod_b_c_d_: (int a, int b, int c, int d) { |
| return a * b * c * d; |
| }, |
| ); |
| final protocolImpl = protocolBuilder.build(); |
| final MyProtocol asMyProtocol = MyProtocol.as(protocolImpl); |
| final SecondaryProtocol asSecondaryProtocol = SecondaryProtocol.as( |
| protocolImpl, |
| ); |
| |
| // Required instance method from secondary protocol. |
| final otherIntResult = consumer.callOtherMethod(asSecondaryProtocol); |
| expect(otherIntResult, 24); |
| |
| // Blocking method. |
| consumer.callBlockingMethodOnRandomThread(asMyProtocol); |
| expect(await listenerCompleter.future, 98765); |
| }); |
| |
| test('Direct method implementation using block', () async { |
| final consumer = ProtocolConsumer(); |
| |
| final builder = ObjCProtocolBuilder(); |
| MyProtocol$Builder.instanceMethod_withDouble_.implementWithBlock( |
| builder, |
| ObjCBlock_NSString_ffiVoid_NSString_ffiDouble.fromFunction( |
| (Pointer<Void> _, NSString s, double x) => |
| 'DirectImpl: ${s.toDartString()}: $x'.toNSString(), |
| ), |
| ); |
| final myProtocol = MyProtocol.as(builder.build()); |
| |
| // Required instance method. |
| final result = consumer.callInstanceMethod(myProtocol); |
| expect(result.toDartString(), 'DirectImpl: Hello from ObjC: 3.14'); |
| }); |
| }); |
| |
| test('Filters', () { |
| // SuperProtocol and FilteredProtocol's methods are included in the |
| // bindings, but there should only be stub bindings for the protocols |
| // themselves, because they're not included by the config. |
| // FilteredUnusedProtocol shouldn't appear at all. |
| final bindings = File( |
| path.join( |
| packagePathForTests, |
| 'test', |
| 'native_objc_test', |
| 'protocol_bindings.dart', |
| ), |
| ).readAsStringSync(); |
| |
| expect(bindings, contains('instanceMethod_withDouble_')); |
| expect(bindings, contains('fooMethod')); |
| |
| expect(bindings, contains('EmptyProtocol')); |
| expect(bindings, isNot(contains('EmptyProtocol is a stub'))); |
| |
| expect(bindings, contains('MyProtocol')); |
| expect(bindings, isNot(contains('MyProtocol is a stub'))); |
| |
| expect(bindings, contains('SecondaryProtocol')); |
| expect(bindings, isNot(contains('SecondaryProtocol is a stub'))); |
| |
| expect(bindings, contains('SuperProtocol is a stub')); |
| |
| expect(bindings, contains('FilteredProtocol is a stub')); |
| |
| expect(bindings, isNot(contains('FilteredUnusedProtocol'))); |
| expect(bindings, isNot(contains('filteredUnusedProtocolMethod'))); |
| }); |
| |
| test('Unused protocol', () { |
| // Regression test for https://github.com/dart-lang/native/issues/1672. |
| final proto = UnusedProtocol$Builder.implement(someMethod: () => 123); |
| expect(proto, isNotNull); |
| }); |
| |
| test('Disabled method', () { |
| // Regression test for https://github.com/dart-lang/native/issues/1702. |
| expect(MyProtocol$Builder.instanceMethod_withDouble_.isAvailable, isTrue); |
| expect(MyProtocol$Builder.optionalMethod_.isAvailable, isTrue); |
| expect(MyProtocol$Builder.disabledMethod.isAvailable, isFalse); |
| |
| expect( |
| () => MyProtocol$Builder.disabledMethod.implement( |
| ObjCProtocolBuilder(), |
| () => 123, |
| ), |
| throwsA(isA<FailedToLoadProtocolMethodException>()), |
| ); |
| }); |
| |
| test('conformsTo', () { |
| final inst = ObjCProtocolImpl(); |
| expect(NSObjectProtocol.conformsTo(inst), isTrue); |
| expect(MyProtocol.conformsTo(inst), isTrue); |
| expect(SecondaryProtocol.conformsTo(inst), isTrue); |
| expect(EmptyProtocol.conformsTo(inst), isFalse); |
| expect(UnusedProtocol.conformsTo(inst), isFalse); |
| }); |
| |
| test('Threading stress test', () async { |
| final consumer = ProtocolConsumer(); |
| final completer = Completer<void>(); |
| int count = 0; |
| |
| final protocolBuilder = ObjCProtocolBuilder(); |
| MyProtocol$Builder.voidMethod_.implementAsListener(protocolBuilder, ( |
| int x, |
| ) { |
| expect(x, 123); |
| ++count; |
| if (count == 1000) completer.complete(); |
| }); |
| |
| final protocol = protocolBuilder.build(); |
| final MyProtocol asMyProtocol = MyProtocol.as(protocol); |
| |
| for (int i = 0; i < 1000; ++i) { |
| consumer.callMethodOnRandomThread(asMyProtocol); |
| } |
| await completer.future; |
| expect(count, 1000); |
| }); |
| |
| (NSObject, Pointer<ObjCBlockImpl>) blockRefCountTestInner() { |
| final pool = lib.objc_autoreleasePoolPush(); |
| final protocolBuilder = ObjCProtocolBuilder(); |
| |
| final block = InstanceMethodBlock.fromFunction( |
| (Pointer<Void> p, NSString s, double x) => 'Hello'.toNSString(), |
| ); |
| MyProtocol$Builder.instanceMethod_withDouble_.implementWithBlock( |
| protocolBuilder, |
| block, |
| ); |
| final protocol = protocolBuilder.build(); |
| lib.objc_autoreleasePoolPop(pool); |
| |
| final blockPtr = block.ref.pointer; |
| |
| // There are 2 references to the block. One owned by the Dart wrapper |
| // object, and the other owned by the protocol. |
| doGC(); |
| expect(blockRetainCount(blockPtr), 2); |
| |
| return (protocol, blockPtr); |
| } |
| |
| Pointer<ObjCBlockImpl> blockRefCountTest() { |
| final (protocol, blockPtr) = blockRefCountTestInner(); |
| |
| // The Dart side block pointer has gone out of scope, but the protocol |
| // still owns a reference to it. |
| doGC(); |
| expect(blockRetainCount(blockPtr), 1); |
| |
| expect(protocol, isNotNull); // Force protocol to stay in scope. |
| |
| return blockPtr; |
| } |
| |
| test('Block ref counting', () { |
| final blockPtr = blockRefCountTest(); |
| |
| // The protocol object has gone out of scope, so it should be cleaned up. |
| // So should the block. |
| doGC(); |
| expect(blockRetainCount(blockPtr), 0); |
| }, skip: !canDoGC); |
| |
| test('keepIsolateAlive', () async { |
| final isolateSendPort = Completer<SendPort>(); |
| final protosCreated = Completer<void>(); |
| final protoKeepAliveDestroyed = Completer<void>(); |
| final receivePort = RawReceivePort((msg) { |
| if (msg is SendPort) { |
| isolateSendPort.complete(msg); |
| } else if (msg == 'Protocols created') { |
| protosCreated.complete(); |
| } else if (msg == 'protoKeepAlive destroyed') { |
| protoKeepAliveDestroyed.complete(); |
| } |
| }); |
| |
| final isExited = Completer<void>(); |
| late final RawReceivePort exitPort; |
| exitPort = RawReceivePort((_) { |
| isExited.complete(); |
| exitPort.close(); |
| }); |
| |
| final isolate = Isolate.spawn( |
| (sendPort) { |
| final protoKeepAlive = ObjCProtocolBuilder().build( |
| keepIsolateAlive: true, |
| ); |
| final protoDontKeepAlive = ObjCProtocolBuilder().build( |
| keepIsolateAlive: false, |
| ); |
| sendPort.send('Protocols created'); |
| |
| final isolatePort = RawReceivePort((msg) { |
| if (msg == 'Destroy protoKeepAlive') { |
| protoKeepAlive.ref.release(); |
| sendPort.send('protoKeepAlive destroyed'); |
| } |
| })..keepIsolateAlive = false; |
| |
| sendPort.send(isolatePort.sendPort); |
| }, |
| receivePort.sendPort, |
| onExit: exitPort.sendPort, |
| ); |
| |
| await protosCreated.future; |
| |
| doGC(); |
| await Future<void>.delayed(Duration.zero); // Let dispose message arrive. |
| doGC(); |
| await Future<void>.delayed(Duration.zero); // Let exit message arrive. |
| |
| // Both blocks are still alive. |
| expect(isExited.isCompleted, isFalse); |
| |
| (await isolateSendPort.future).send('Destroy protoKeepAlive'); |
| await protoKeepAliveDestroyed.future; |
| |
| doGC(); |
| await Future<void>.delayed(Duration.zero); // Let dispose message arrive. |
| doGC(); |
| await Future<void>.delayed(Duration.zero); // Let exit message arrive. |
| |
| // Only protoDontKeepAlive is alive. |
| await isExited; |
| |
| receivePort.close(); |
| }, skip: !canDoGC); |
| |
| test('class disposal, builder first', () { |
| final pool = lib.objc_autoreleasePoolPush(); |
| ObjCProtocolBuilder? protocolBuilder = ObjCProtocolBuilder( |
| debugName: 'Foo', |
| ); |
| |
| NSObject? protocol = protocolBuilder.build(); |
| final clazz = lib.getClass(protocol); |
| expect( |
| lib.getClassName(clazz).cast<Utf8>().toDartString(), |
| startsWith('Foo'), |
| ); |
| expect(isValidClass(clazz), isTrue); |
| lib.objc_autoreleasePoolPop(pool); |
| |
| protocolBuilder = null; |
| doGC(); |
| expect(isValidClass(clazz), isTrue); |
| |
| protocol = null; |
| doGC(); |
| expect(isValidClass(clazz), isFalse); |
| }, skip: !canDoGC); |
| |
| test('class disposal, instance first', () { |
| final pool = lib.objc_autoreleasePoolPush(); |
| ObjCProtocolBuilder? protocolBuilder = ObjCProtocolBuilder( |
| debugName: 'Foo', |
| ); |
| |
| NSObject? protocol = protocolBuilder.build(); |
| final clazz = lib.getClass(protocol); |
| expect( |
| lib.getClassName(clazz).cast<Utf8>().toDartString(), |
| startsWith('Foo'), |
| ); |
| expect(isValidClass(clazz), isTrue); |
| lib.objc_autoreleasePoolPop(pool); |
| |
| protocolBuilder = null; |
| doGC(); |
| expect(isValidClass(clazz), isTrue); |
| |
| protocol = null; |
| doGC(); |
| expect(isValidClass(clazz), isFalse); |
| }, skip: !canDoGC); |
| |
| test('adding more methods after build', () { |
| final protocolBuilder = ObjCProtocolBuilder(); |
| |
| MyProtocol$Builder.addToBuilder( |
| protocolBuilder, |
| instanceMethod_withDouble_: (NSString s, double x) { |
| return 'ProtocolBuilder: ${s.toDartString()}: $x'.toNSString(); |
| }, |
| optionalMethod_: (SomeStruct s) { |
| return s.y - s.x; |
| }, |
| ); |
| |
| final protocolImpl = protocolBuilder.build(); |
| |
| expect( |
| () => SecondaryProtocol$Builder.addToBuilder( |
| protocolBuilder, |
| otherMethod_b_c_d_: (int a, int b, int c, int d) { |
| return a * b * c * d; |
| }, |
| ), |
| throwsA(isA<StateError>()), |
| ); |
| }); |
| |
| test('calling methods on a protocol instance', () { |
| final protoImpl = ObjCProtocolImpl(); |
| |
| final MyProtocol myProto = protoImpl.returnsMyProtocol(); |
| final result = myProto.instanceMethod( |
| "abc".toNSString(), |
| withDouble: 123, |
| ); |
| expect(result.toDartString(), 'ObjCProtocolImpl: abc: 123.00'); |
| }); |
| }); |
| } |