diff --git a/incubation/container/container/lib/src/container.dart b/incubation/container/container/lib/src/container.dart index 059a85f..9a342be 100644 --- a/incubation/container/container/lib/src/container.dart +++ b/incubation/container/container/lib/src/container.dart @@ -1061,6 +1061,12 @@ class Container { // Find and call the method var method = reflector.findInstanceMethod(instance, methodName); + + // If method not found and it's __invoke, try 'call' instead + if (method == null && methodName == '__invoke') { + method = reflector.findInstanceMethod(instance, 'call'); + } + if (method == null) { throw ArgumentError('Method not found: $methodName on $className'); } diff --git a/incubation/container/container/test/invoke_test.dart b/incubation/container/container/test/invoke_test.dart new file mode 100644 index 0000000..20df9cb --- /dev/null +++ b/incubation/container/container/test/invoke_test.dart @@ -0,0 +1,185 @@ +import 'package:platformed_container/container.dart'; +import 'package:test/test.dart'; + +class InvokableClass { + String call(String message) => 'Invoked with: $message'; + String invoke(String message) => 'Called invoke with: $message'; +} + +class MockReflector extends Reflector { + @override + String? getName(Symbol symbol) => null; + + @override + ReflectedClass? reflectClass(Type clazz) => null; + + @override + ReflectedType? reflectType(Type type) => null; + + @override + ReflectedInstance? reflectInstance(Object? instance) => null; + + @override + ReflectedFunction? reflectFunction(Function function) => null; + + @override + ReflectedType reflectFutureOf(Type type) => throw UnimplementedError(); + + @override + Type? findTypeByName(String name) { + if (name == 'InvokableClass') return InvokableClass; + return null; + } + + @override + ReflectedFunction? findInstanceMethod(Object instance, String methodName) { + if (instance is InvokableClass) { + switch (methodName) { + case '__invoke': + case 'call': + return MockMethod('call', (invocation) { + var args = invocation.positionalArguments; + if (args.isEmpty) { + throw ArgumentError('Method call requires a message parameter'); + } + return MockReflectedInstance(instance.call(args[0] as String)); + }, [MockParameter('message', String, true, false)]); + case 'invoke': + return MockMethod('invoke', (invocation) { + var args = invocation.positionalArguments; + if (args.isEmpty) { + throw ArgumentError('Method invoke requires a message parameter'); + } + return MockReflectedInstance(instance.invoke(args[0] as String)); + }, [MockParameter('message', String, true, false)]); + } + } + return null; + } +} + +class MockMethod implements ReflectedFunction { + final String methodName; + final ReflectedInstance Function(Invocation) handler; + final List _parameters; + + MockMethod(this.methodName, this.handler, + [List? parameters]) + : _parameters = parameters ?? []; + + @override + List get annotations => []; + + @override + bool get isGetter => false; + + @override + bool get isSetter => false; + + @override + String get name => methodName; + + @override + List get parameters => _parameters; + + @override + ReflectedType? get returnType => null; + + @override + List get typeParameters => []; + + @override + ReflectedInstance invoke(Invocation invocation) => handler(invocation); +} + +class MockParameter implements ReflectedParameter { + @override + final String name; + @override + final bool isRequired; + @override + final bool isNamed; + final Type paramType; + final bool isVariadic; + + MockParameter(this.name, this.paramType, this.isRequired, this.isNamed, + {this.isVariadic = false}); + + @override + List get annotations => []; + + @override + ReflectedType get type => MockReflectedType(paramType); +} + +class MockReflectedType implements ReflectedType { + @override + final String name; + @override + final Type reflectedType; + + MockReflectedType(this.reflectedType) : name = reflectedType.toString(); + + @override + List get typeParameters => []; + + @override + bool isAssignableTo(ReflectedType? other) => false; + + @override + ReflectedInstance newInstance( + String constructorName, List positionalArguments, + [Map namedArguments = const {}, + List typeArguments = const []]) => + throw UnimplementedError(); +} + +class MockReflectedInstance implements ReflectedInstance { + final dynamic value; + + MockReflectedInstance(this.value); + + @override + ReflectedClass get clazz => throw UnimplementedError(); + + @override + ReflectedInstance getField(String name) => throw UnimplementedError(); + + @override + dynamic get reflectee => value; + + @override + ReflectedType get type => throw UnimplementedError(); +} + +void main() { + late Container container; + + setUp(() { + container = Container(MockReflector()); + container.registerSingleton(InvokableClass()); + }); + + group('Invoke Tests', () { + test('can call __invoke method', () { + var result = container.call('InvokableClass@__invoke', ['Hello world']); + expect(result, equals('Invoked with: Hello world')); + }); + + test('__invoke is alias for call method', () { + var invokeResult = container.call('InvokableClass@__invoke', ['Test']); + var callResult = container.call('InvokableClass@call', ['Test']); + expect(invokeResult, equals(callResult)); + }); + + test('can still call other methods', () { + var result = container.call('InvokableClass@invoke', ['Hello world']); + expect(result, equals('Called invoke with: Hello world')); + }); + + test('throws when required parameter is missing', () { + expect(() => container.call('InvokableClass@__invoke'), + throwsA(isA())); + }); + }); +}