From bba949c296eccb407e71bf2b9e738dca1845a8cb Mon Sep 17 00:00:00 2001 From: Patrick Stewart Date: Fri, 27 Dec 2024 00:01:18 -0700 Subject: [PATCH] refactor: array of concrete types support --- .../container/lib/src/container.dart | 13 +- .../container/test/array_concrete_test.dart | 317 ++++++++++++++++++ 2 files changed, 328 insertions(+), 2 deletions(-) create mode 100644 incubation/container/container/test/array_concrete_test.dart diff --git a/incubation/container/container/lib/src/container.dart b/incubation/container/container/lib/src/container.dart index 9a342be..8863874 100644 --- a/incubation/container/container/lib/src/container.dart +++ b/incubation/container/container/lib/src/container.dart @@ -597,8 +597,17 @@ class Container { /// Define a contextual binding. /// /// This allows you to define how abstract types should be resolved in specific contexts. - ContextualBindingBuilder when(Type concrete) { - return ContextualBindingBuilder(this, [concrete]); + /// + /// The [concrete] parameter can be either a single Type or a List. + /// When a List is provided, the same binding will be applied to all types in the list. + ContextualBindingBuilder when(dynamic concrete) { + if (concrete is Type) { + return ContextualBindingBuilder(this, [concrete]); + } else if (concrete is List) { + return ContextualBindingBuilder(this, concrete); + } + throw ArgumentError( + 'The concrete parameter must be either Type or List'); } /// Add a contextual binding to the container. diff --git a/incubation/container/container/test/array_concrete_test.dart b/incubation/container/container/test/array_concrete_test.dart new file mode 100644 index 0000000..0ab4e90 --- /dev/null +++ b/incubation/container/container/test/array_concrete_test.dart @@ -0,0 +1,317 @@ +import 'package:platformed_container/container.dart'; +import 'package:test/test.dart'; + +abstract class Logger { + void log(String message); +} + +class ConsoleLogger implements Logger { + @override + void log(String message) => print('Console: $message'); +} + +class FileLogger implements Logger { + @override + void log(String message) => print('File: $message'); +} + +class Service { + final Logger logger; + Service(this.logger); +} + +class AnotherService { + final Logger logger; + AnotherService(this.logger); +} + +class MockReflector extends Reflector { + @override + String? getName(Symbol symbol) => null; + + @override + ReflectedClass? reflectClass(Type clazz) { + if (clazz == Service) { + return MockReflectedClass( + 'Service', + [], + [], + [ + MockConstructor('', [ + MockParameter('logger', Logger, true, false), + ]) + ], + Service); + } + if (clazz == AnotherService) { + return MockReflectedClass( + 'AnotherService', + [], + [], + [ + MockConstructor('', [ + MockParameter('logger', Logger, true, false), + ]) + ], + AnotherService); + } + return null; + } + + @override + ReflectedType? reflectType(Type type) { + if (type == Service) { + return MockReflectedClass( + 'Service', + [], + [], + [ + MockConstructor('', [ + MockParameter('logger', Logger, true, false), + ]) + ], + Service); + } + if (type == AnotherService) { + return MockReflectedClass( + 'AnotherService', + [], + [], + [ + MockConstructor('', [ + MockParameter('logger', Logger, true, false), + ]) + ], + AnotherService); + } + if (type == ConsoleLogger) { + return MockReflectedClass( + 'ConsoleLogger', [], [], [MockConstructor('', [])], ConsoleLogger); + } + if (type == FileLogger) { + return MockReflectedClass( + 'FileLogger', [], [], [MockConstructor('', [])], FileLogger); + } + if (type == Logger) { + return MockReflectedClass( + 'Logger', [], [], [MockConstructor('', [])], Logger); + } + return 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) => null; + + @override + ReflectedFunction? findInstanceMethod(Object instance, String methodName) => + null; +} + +class MockReflectedClass extends ReflectedType implements ReflectedClass { + @override + final List annotations; + @override + final List constructors; + @override + final List declarations; + + MockReflectedClass( + String name, + List typeParameters, + this.annotations, + this.constructors, + Type reflectedType, + ) : declarations = [], + super(name, typeParameters, reflectedType); + + @override + ReflectedInstance newInstance( + String constructorName, List positionalArguments, + [Map namedArguments = const {}, + List typeArguments = const []]) { + if (reflectedType == Service) { + return MockReflectedInstance(Service(positionalArguments[0] as Logger)); + } + if (reflectedType == AnotherService) { + return MockReflectedInstance( + AnotherService(positionalArguments[0] as Logger)); + } + if (reflectedType == ConsoleLogger) { + return MockReflectedInstance(ConsoleLogger()); + } + if (reflectedType == FileLogger) { + return MockReflectedInstance(FileLogger()); + } + if (reflectedType == Logger) { + throw BindingResolutionException( + 'No implementation was provided for Logger'); + } + throw UnsupportedError('Unknown type: $reflectedType'); + } + + @override + bool isAssignableTo(ReflectedType? other) { + if (reflectedType == ConsoleLogger && other?.reflectedType == Logger) { + return true; + } + if (reflectedType == FileLogger && other?.reflectedType == Logger) { + return true; + } + return false; + } +} + +class MockConstructor implements ReflectedFunction { + final String constructorName; + final List constructorParameters; + + MockConstructor(this.constructorName, this.constructorParameters); + + @override + List get annotations => []; + + @override + bool get isGetter => false; + + @override + bool get isSetter => false; + + @override + String get name => constructorName; + + @override + List get parameters => constructorParameters; + + @override + ReflectedType? get returnType => null; + + @override + List get typeParameters => []; + + @override + ReflectedInstance invoke(Invocation invocation) => throw UnimplementedError(); +} + +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()); + }); + + group('Array of Concrete Types Tests', () { + test('can bind different implementations for different concrete types', () { + container.when([Service]).needs().give(); + container.when([AnotherService]).needs().give(); + + var service = container.make(); + var anotherService = container.make(); + + expect(service.logger, isA()); + expect(anotherService.logger, isA()); + }); + + test('can bind same implementation for multiple concrete types', () { + container + .when([Service, AnotherService]) + .needs() + .give(); + + var service = container.make(); + var anotherService = container.make(); + + expect(service.logger, isA()); + expect(anotherService.logger, isA()); + }); + + test('later bindings override earlier ones', () { + container + .when([Service, AnotherService]) + .needs() + .give(); + container.when([AnotherService]).needs().give(); + + var service = container.make(); + var anotherService = container.make(); + + expect(service.logger, isA()); + expect(anotherService.logger, isA()); + }); + + test('throws when no implementation is provided', () { + container.when([Service]).needs(); + + expect(() => container.make(), + throwsA(isA())); + }); + }); +}