refactor: move container test implementations to incubation

This commit is contained in:
Patrick Stewart 2024-12-24 18:11:07 -07:00
parent 53f3f42930
commit 3e9de4cbb2
52 changed files with 255 additions and 43 deletions

View file

@ -35,6 +35,72 @@ class BoundMethod {
throw ArgumentError('Invalid callback type: ${callback.runtimeType}');
}
static dynamic _callBoundMethod(
Container container, dynamic callback, Function defaultCallback,
[List<dynamic> parameters = const []]) {
if (callback is List && callback.length == 2) {
var instance = callback[0];
var method = callback[1];
if (instance is String) {
instance = container.make(instance);
}
if (method is String) {
if (instance is Function && method == '__invoke') {
return Function.apply(instance, parameters);
}
if (instance is Map && instance.containsKey(method)) {
// Handle the case where instance is a Map and method is a key
var result = instance[method];
return result is Function
? Function.apply(result, parameters)
: result;
}
var instanceMirror = reflect(instance);
var methodSymbol = Symbol(method);
if (instanceMirror.type.instanceMembers.containsKey(methodSymbol)) {
var dependencies =
_getMethodDependencies(container, instance, method, parameters);
var result = Function.apply(
instanceMirror.getField(methodSymbol).reflectee, dependencies);
return result is Function
? Function.apply(result, parameters)
: result;
} else if (method == '__invoke' && instance is Function) {
return Function.apply(instance, parameters);
} else if (instance is Type) {
// Handle static methods
var classMirror = reflectClass(instance);
if (classMirror.staticMembers.containsKey(Symbol(method))) {
var dependencies =
_getMethodDependencies(container, instance, method, parameters);
var result = Function.apply(
classMirror.getField(Symbol(method)).reflectee, dependencies);
return result is Function
? Function.apply(result, parameters)
: result;
}
}
// Try to find the method in the global scope
var globalMethod = _findGlobalMethod(method);
if (globalMethod != null) {
var result = Function.apply(globalMethod, parameters);
return result is Function
? Function.apply(result, parameters)
: result;
}
throw BindingResolutionException(
'Method $method not found on ${instance.runtimeType}');
} else if (method is Function) {
var result = Function.apply(method, [instance, ...parameters]);
return result is Function ? Function.apply(result, parameters) : result;
}
} else if (callback is Function) {
var result = Function.apply(callback, parameters);
return result is Function ? Function.apply(result, parameters) : result;
}
return Util.unwrapIfClosure(defaultCallback);
}
static dynamic _callClass(Container container, String target,
List<dynamic> parameters, String? defaultMethod) {
var segments = target.split('@');
@ -50,8 +116,11 @@ class BoundMethod {
if (container.bound(instance)) {
return container.make(instance);
}
throw BindingResolutionException(
'Failed to resolve class or function: $className');
// If it's not bound, treat it as a string value
return instance;
}
if (instance is Function && method == '__invoke') {
return Function.apply(instance, parameters);
}
return _callBoundMethod(container, [instance, method], () {
throw BindingResolutionException(
@ -59,46 +128,17 @@ class BoundMethod {
}, parameters);
}
static dynamic _callBoundMethod(
Container container, dynamic callback, Function defaultCallback,
[List<dynamic> parameters = const []]) {
if (callback is List && callback.length == 2) {
var instance = callback[0];
var method = callback[1];
if (instance is String) {
instance = container.make(instance);
}
if (method is String) {
if (instance is Function && method == '__invoke') {
return Function.apply(instance, parameters);
static Function? _findGlobalMethod(String methodName) {
var currentMirror = currentMirrorSystem();
for (var library in currentMirror.libraries.values) {
if (library.declarations.containsKey(Symbol(methodName))) {
var declaration = library.declarations[Symbol(methodName)]!;
if (declaration is MethodMirror && declaration.isTopLevel) {
return library.getField(Symbol(methodName)).reflectee as Function;
}
var instanceMirror = reflect(instance);
var methodSymbol = Symbol(method);
if (instanceMirror.type.instanceMembers.containsKey(methodSymbol)) {
var dependencies =
_getMethodDependencies(container, instance, method, parameters);
return Function.apply(
instanceMirror.getField(methodSymbol).reflectee, dependencies);
} else {
throw BindingResolutionException(
'Method $method not found on ${instance.runtimeType}');
}
} else if (method is Function) {
return method(instance);
}
} else if (callback is Function) {
var dependencies =
_getMethodDependencies(container, callback, null, parameters);
return Function.apply(callback, dependencies);
}
return Util.unwrapIfClosure(defaultCallback);
}
static dynamic _resolveInstance(Container container, dynamic instance) {
if (instance is String) {
return container.make(instance);
}
return instance;
return null;
}
static List _getMethodDependencies(Container container, dynamic instance,
@ -111,6 +151,11 @@ class BoundMethod {
_addDependencyForCallParameter(
container, parameter, parameters, dependencies);
}
} else if (instance is Map &&
method is String &&
instance.containsKey(method)) {
// If instance is a Map and method is a key, return the value
return [instance[method]];
} else {
// If we couldn't get a reflector, just return the original parameters
return parameters;
@ -119,6 +164,13 @@ class BoundMethod {
return dependencies;
}
static dynamic _resolveInstance(Container container, dynamic instance) {
if (instance is String) {
return container.make(instance);
}
return instance;
}
static bool _hasInvokeMethod(String className) {
ClassMirror? classMirror = _getClassMirror(className);
return classMirror?.declarations[Symbol('__invoke')] != null;

View file

@ -642,10 +642,6 @@ class Container implements ContainerContract {
fireAfterResolvingAttributeCallbacks(classAttributes, instance);
_fireAfterResolvingCallbacks(concrete, instance);
// Apply after resolving callbacks
fireAfterResolvingAttributeCallbacks(classAttributes, instance);
_fireAfterResolvingCallbacks(concrete, instance);
// Apply extenders after all callbacks
for (var extender in _getExtenders(concrete)) {
instance = extender(instance);

View file

@ -16,5 +16,6 @@ dependencies:
dev_dependencies:
lints: ^3.0.0
mockito: ^5.4.0
test: ^1.24.0
platform_config: ^0.1.0

View file

@ -0,0 +1,163 @@
import 'package:test/test.dart';
import 'package:mockito/mockito.dart';
import 'package:ioc_container/container.dart';
import 'package:platform_contracts/contracts.dart';
class MockContainer extends Mock implements Container {}
class TestClass {
String testMethod(String param) => 'Test: $param';
static String staticMethod(String param) => 'Static: $param';
String __invoke(String param) => 'Invoke: $param';
}
class DependencyClass {
final String value;
DependencyClass(this.value);
}
class ClassWithDependency {
String methodWithDependency(DependencyClass dep, String param) =>
'${dep.value}: $param';
}
class NestedDependency {
final DependencyClass dep;
NestedDependency(this.dep);
String nestedMethod(String param) => '${dep.value} nested: $param';
}
class ClassWithOptionalParam {
String methodWithOptional(String required, [String optional = 'default']) =>
'$required - $optional';
}
void main() {
group('Container.call', () {
late MockContainer container;
setUp(() {
container = MockContainer();
});
test('call with Function', () {
var result = container.call((String s) => 'Hello $s', ['World']);
expect(result, equals('Hello World'));
});
test('call with class@method string', () {
when(container.make('TestClass')).thenReturn(TestClass());
var result = container.call('TestClass@testMethod', ['World']);
expect(result, equals('Test: World'));
});
test('call with List callback', () {
when(container.make('TestClass')).thenReturn(TestClass());
var result = container.call(['TestClass', 'testMethod'], ['World']);
expect(result, equals('Test: World'));
});
test('call with static method', () {
when(container.make('TestClass')).thenReturn(TestClass);
var result = container.call(['TestClass', 'staticMethod'], ['World']);
expect(result, equals('Static: World'));
});
test('call with Map instance and method key', () {
var mapInstance = {'testMethod': (String s) => 'Map: $s'};
when(container.make('TestMap')).thenReturn(mapInstance);
var result = container.call(['TestMap', 'testMethod'], ['World']);
expect(result, equals('Map: World'));
});
test('call with global function', () {
when(container.make('globalFunction')).thenReturn(globalFunction);
var result = container.call('globalFunction', ['World']);
expect(result, equals('Global: World'));
});
test('call with default method', () {
when(container.make('TestClass')).thenReturn(TestClass());
var result = container.call('TestClass', ['World'], '__invoke');
expect(result, equals('Invoke: World'));
});
test('call with __invoke method', () {
when(container.make('TestClass')).thenReturn(TestClass());
var result = container.call('TestClass', ['World']);
expect(result, equals('Invoke: World'));
});
test('call with non-existent method throws BindingResolutionException', () {
when(container.make('TestClass')).thenReturn(TestClass());
expect(() => container.call('TestClass@nonExistentMethod', ['World']),
throwsA(isA<BindingResolutionException>()));
});
test('call with invalid callback type throws ArgumentError', () {
expect(
() => container.call(123, ['World']), throwsA(isA<ArgumentError>()));
});
test('call method with dependencies', () {
when(container.make('ClassWithDependency'))
.thenReturn(ClassWithDependency());
when(container.make('DependencyClass'))
.thenReturn(DependencyClass('Dependency'));
var result = container
.call(['ClassWithDependency', 'methodWithDependency'], ['World']);
expect(result, equals('Dependency: World'));
});
test('call method with overridden dependency', () {
when(container.make('ClassWithDependency'))
.thenReturn(ClassWithDependency());
when(container.make('DependencyClass'))
.thenReturn(DependencyClass('Dependency'));
var result = container.call(
['ClassWithDependency', 'methodWithDependency'],
[DependencyClass('Override'), 'World']);
expect(result, equals('Override: World'));
});
test('call method with nested dependency', () {
when(container.make('NestedDependency'))
.thenReturn(NestedDependency(DependencyClass('NestedDep')));
var result =
container.call(['NestedDependency', 'nestedMethod'], ['World']);
expect(result, equals('NestedDep nested: World'));
});
test('call method with optional parameter - provided', () {
when(container.make('ClassWithOptionalParam'))
.thenReturn(ClassWithOptionalParam());
var result = container.call(
['ClassWithOptionalParam', 'methodWithOptional'],
['Required', 'Provided']);
expect(result, equals('Required - Provided'));
});
test('call method with optional parameter - default', () {
when(container.make('ClassWithOptionalParam'))
.thenReturn(ClassWithOptionalParam());
var result = container
.call(['ClassWithOptionalParam', 'methodWithOptional'], ['Required']);
expect(result, equals('Required - default'));
});
test(
'call method with missing required dependency throws BindingResolutionException',
() {
when(container.make('ClassWithDependency'))
.thenReturn(ClassWithDependency());
when(container.make('DependencyClass'))
.thenThrow(BindingResolutionException('DependencyClass not found'));
expect(
() => container
.call(['ClassWithDependency', 'methodWithDependency'], ['World']),
throwsA(isA<BindingResolutionException>()));
});
});
}
String globalFunction(String param) => 'Global: $param';