diff --git a/incubation/container/container/lib/src/container.dart b/incubation/container/container/lib/src/container.dart index 8aad60a..8218c40 100644 --- a/incubation/container/container/lib/src/container.dart +++ b/incubation/container/container/lib/src/container.dart @@ -1157,6 +1157,30 @@ class Container { bind(abstract).to(concrete); } + /// Register a binding if it hasn't already been registered + void bindIf(dynamic concrete, {bool singleton = false}) { + if (!has()) { + if (singleton) { + if (concrete is Function) { + registerLazySingleton(concrete as T Function(Container)); + } else { + registerSingleton(concrete as T); + } + } else { + if (concrete is Function) { + registerFactory(concrete as T Function(Container)); + } else { + registerFactory((c) => concrete as T); + } + } + } + } + + /// Register a singleton if it hasn't already been registered + void singletonIf(dynamic concrete) { + bindIf(concrete, singleton: true); + } + /// Register all attribute-based bindings for a type void registerAttributeBindings(Type type) { var annotations = reflector.getAnnotations(type); diff --git a/incubation/container/container/test/conditional_binding_test.dart b/incubation/container/container/test/conditional_binding_test.dart new file mode 100644 index 0000000..f79fdfe --- /dev/null +++ b/incubation/container/container/test/conditional_binding_test.dart @@ -0,0 +1,212 @@ +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'); +} + +void main() { + late Container container; + + setUp(() { + container = Container(MockReflector()); + }); + + group('Conditional Binding Tests', () { + test('bindIf registers binding when not bound', () { + container.bindIf(ConsoleLogger()); + expect(container.make(), isA()); + }); + + test('bindIf skips registration when already bound', () { + container.bind(Logger).to(ConsoleLogger); + container.bindIf(FileLogger()); + expect(container.make(), isA()); + }); + + test('bindIf registers singleton when specified', () { + container.bindIf(ConsoleLogger(), singleton: true); + var first = container.make(); + var second = container.make(); + expect(identical(first, second), isTrue); + }); + + test('singletonIf registers singleton when not bound', () { + container.singletonIf(ConsoleLogger()); + var first = container.make(); + var second = container.make(); + expect(identical(first, second), isTrue); + }); + + test('singletonIf skips registration when already bound', () { + container.bind(Logger).to(ConsoleLogger); + container.singletonIf(FileLogger()); + expect(container.make(), isA()); + }); + + test('bindIf works with factory functions', () { + container.bindIf((c) => ConsoleLogger()); + expect(container.make(), isA()); + }); + + test('singletonIf works with factory functions', () { + container.singletonIf((c) => ConsoleLogger()); + var first = container.make(); + var second = container.make(); + expect(identical(first, second), isTrue); + }); + }); +} + +class MockReflector extends Reflector { + @override + String? getName(Symbol symbol) => null; + + @override + ReflectedClass? reflectClass(Type clazz) { + if (clazz == Logger) { + return MockReflectedClass('Logger', [], [], [], Logger); + } + if (clazz == ConsoleLogger) { + return MockReflectedClass( + 'ConsoleLogger', [], [], [MockConstructor('', [])], ConsoleLogger); + } + if (clazz == FileLogger) { + return MockReflectedClass( + 'FileLogger', [], [], [MockConstructor('', [])], FileLogger); + } + return null; + } + + @override + ReflectedType? reflectType(Type type) { + return reflectClass(type); + } + + @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; + + @override + List getAnnotations(Type type) => []; + + @override + List getParameterAnnotations( + Type type, String constructorName, String parameterName) => + []; +} + +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(reflectedType.toString(), typeParameters, reflectedType); + + @override + ReflectedInstance newInstance( + String constructorName, List positionalArguments, + [Map namedArguments = const {}, + List typeArguments = const []]) { + if (reflectedType == ConsoleLogger) { + return MockReflectedInstance(ConsoleLogger()); + } + if (reflectedType == FileLogger) { + return MockReflectedInstance(FileLogger()); + } + throw UnsupportedError('Unknown type: $reflectedType'); + } + + @override + bool isAssignableTo(ReflectedType? other) { + if (reflectedType == other?.reflectedType) { + return true; + } + if ((reflectedType == ConsoleLogger || 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 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(); +}