update: updating ioc_container test 47 pass 27 fail
This commit is contained in:
parent
0be803288b
commit
53f3f42930
14 changed files with 1323 additions and 74 deletions
|
@ -7,6 +7,7 @@ export 'src/container.dart';
|
||||||
export 'src/bound_method.dart';
|
export 'src/bound_method.dart';
|
||||||
export 'src/contextual_binding_builder.dart';
|
export 'src/contextual_binding_builder.dart';
|
||||||
export 'src/entry_not_found_exception.dart';
|
export 'src/entry_not_found_exception.dart';
|
||||||
|
export 'src/rewindable_generator.dart';
|
||||||
export 'src/util.dart';
|
export 'src/util.dart';
|
||||||
|
|
||||||
// Export any interfaces or contracts if they exist
|
// Export any interfaces or contracts if they exist
|
||||||
|
|
|
@ -1,62 +1,129 @@
|
||||||
import 'dart:mirrors';
|
import 'dart:mirrors';
|
||||||
import 'package:ioc_container/src/container.dart';
|
import 'package:ioc_container/src/container.dart';
|
||||||
import 'package:ioc_container/src/util.dart';
|
import 'package:ioc_container/src/util.dart';
|
||||||
|
import 'package:platform_contracts/contracts.dart';
|
||||||
|
|
||||||
class BoundMethod {
|
class BoundMethod {
|
||||||
static dynamic call(Container container, dynamic callback,
|
static dynamic call(Container container, dynamic callback,
|
||||||
[List<dynamic> parameters = const [], String? defaultMethod]) {
|
[List<dynamic> parameters = const [], String? defaultMethod]) {
|
||||||
if (callback is String &&
|
if (callback is String) {
|
||||||
defaultMethod == null &&
|
if (defaultMethod == null && _hasInvokeMethod(callback)) {
|
||||||
_hasInvokeMethod(callback)) {
|
defaultMethod = '__invoke';
|
||||||
defaultMethod = '__invoke';
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (_isCallableWithAtSign(callback) || defaultMethod != null) {
|
|
||||||
return _callClass(container, callback, parameters, defaultMethod);
|
return _callClass(container, callback, parameters, defaultMethod);
|
||||||
}
|
}
|
||||||
|
|
||||||
return _callBoundMethod(container, callback, () {
|
if (callback is List && callback.length == 2) {
|
||||||
var dependencies =
|
var instance = container.make(callback[0].toString());
|
||||||
_getMethodDependencies(container, callback, parameters);
|
var method = callback[1].toString();
|
||||||
return Function.apply(callback, dependencies);
|
return _callBoundMethod(container, [instance, method], () {
|
||||||
});
|
throw BindingResolutionException(
|
||||||
}
|
'Failed to call method: $method on ${instance.runtimeType}');
|
||||||
|
}, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
static bool _hasInvokeMethod(String className) {
|
if (callback is Function) {
|
||||||
ClassMirror? classMirror = _getClassMirror(className);
|
return _callBoundMethod(container, callback, () {
|
||||||
return classMirror?.declarations[Symbol('__invoke')] != null;
|
throw BindingResolutionException('Failed to call function');
|
||||||
|
}, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_isCallableWithAtSign(callback)) {
|
||||||
|
return _callClass(container, callback, parameters, defaultMethod);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw ArgumentError('Invalid callback type: ${callback.runtimeType}');
|
||||||
}
|
}
|
||||||
|
|
||||||
static dynamic _callClass(Container container, String target,
|
static dynamic _callClass(Container container, String target,
|
||||||
List<dynamic> parameters, String? defaultMethod) {
|
List<dynamic> parameters, String? defaultMethod) {
|
||||||
var segments = target.split('@');
|
var segments = target.split('@');
|
||||||
|
|
||||||
|
var className = segments[0];
|
||||||
var method = segments.length == 2 ? segments[1] : defaultMethod;
|
var method = segments.length == 2 ? segments[1] : defaultMethod;
|
||||||
|
|
||||||
if (method == null) {
|
method ??= '__invoke';
|
||||||
throw ArgumentError('Method not provided.');
|
|
||||||
}
|
|
||||||
|
|
||||||
var instance = container.make(segments[0]);
|
var instance = container.make(className);
|
||||||
return call(container, [instance, method], parameters);
|
if (instance is String) {
|
||||||
|
// If instance is still a string, it might be a global function
|
||||||
|
if (container.bound(instance)) {
|
||||||
|
return container.make(instance);
|
||||||
|
}
|
||||||
|
throw BindingResolutionException(
|
||||||
|
'Failed to resolve class or function: $className');
|
||||||
|
}
|
||||||
|
return _callBoundMethod(container, [instance, method], () {
|
||||||
|
throw BindingResolutionException(
|
||||||
|
'Failed to call method: $method on $className');
|
||||||
|
}, parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
static dynamic _callBoundMethod(
|
static dynamic _callBoundMethod(
|
||||||
Container container, dynamic callback, Function defaultCallback) {
|
Container container, dynamic callback, Function defaultCallback,
|
||||||
if (callback is! List) {
|
[List<dynamic> parameters = const []]) {
|
||||||
return Util.unwrapIfClosure(defaultCallback);
|
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);
|
||||||
|
}
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
var method = _normalizeMethod(callback);
|
|
||||||
|
|
||||||
// Note: We need to add these methods to the Container class
|
|
||||||
if (container.hasMethodBinding(method)) {
|
|
||||||
return container.callMethodBinding(method, callback[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Util.unwrapIfClosure(defaultCallback);
|
return Util.unwrapIfClosure(defaultCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static dynamic _resolveInstance(Container container, dynamic instance) {
|
||||||
|
if (instance is String) {
|
||||||
|
return container.make(instance);
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List _getMethodDependencies(Container container, dynamic instance,
|
||||||
|
dynamic method, List<dynamic> parameters) {
|
||||||
|
var dependencies = <dynamic>[];
|
||||||
|
var reflector = _getCallReflector(instance, method);
|
||||||
|
|
||||||
|
if (reflector != null) {
|
||||||
|
for (var parameter in reflector.parameters) {
|
||||||
|
_addDependencyForCallParameter(
|
||||||
|
container, parameter, parameters, dependencies);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If we couldn't get a reflector, just return the original parameters
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
return dependencies;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool _hasInvokeMethod(String className) {
|
||||||
|
ClassMirror? classMirror = _getClassMirror(className);
|
||||||
|
return classMirror?.declarations[Symbol('__invoke')] != null;
|
||||||
|
}
|
||||||
|
|
||||||
static String _normalizeMethod(List callback) {
|
static String _normalizeMethod(List callback) {
|
||||||
var className = callback[0] is String
|
var className = callback[0] is String
|
||||||
? callback[0]
|
? callback[0]
|
||||||
|
@ -65,32 +132,31 @@ class BoundMethod {
|
||||||
return '$className@${callback[1]}';
|
return '$className@${callback[1]}';
|
||||||
}
|
}
|
||||||
|
|
||||||
static List _getMethodDependencies(
|
static MethodMirror? _getCallReflector(dynamic instance, [dynamic method]) {
|
||||||
Container container, dynamic callback, List<dynamic> parameters) {
|
if (instance is String && instance.contains('::')) {
|
||||||
var dependencies = <dynamic>[];
|
var parts = instance.split('::');
|
||||||
var reflector = _getCallReflector(callback);
|
instance = parts[0];
|
||||||
|
method = parts[1];
|
||||||
for (var parameter in reflector.parameters) {
|
} else if (instance is! Function && instance is! List && method == null) {
|
||||||
_addDependencyForCallParameter(
|
method = '__invoke';
|
||||||
container, parameter, parameters, dependencies);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return [...dependencies, ...parameters];
|
if (instance is List && method == null) {
|
||||||
}
|
instance = instance[0];
|
||||||
|
method = instance[1];
|
||||||
static MethodMirror _getCallReflector(dynamic callback) {
|
|
||||||
if (callback is String && callback.contains('::')) {
|
|
||||||
callback = callback.split('::');
|
|
||||||
} else if (callback is! Function && callback is! List) {
|
|
||||||
callback = [callback, '__invoke'];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (callback is List) {
|
if (method != null) {
|
||||||
return (reflectClass(callback[0].runtimeType)
|
var classMirror =
|
||||||
.declarations[Symbol(callback[1])] as MethodMirror);
|
reflectClass(instance is Type ? instance : instance.runtimeType);
|
||||||
} else {
|
var methodSymbol = Symbol(method);
|
||||||
return (reflect(callback) as ClosureMirror).function;
|
return classMirror.instanceMembers[methodSymbol] ??
|
||||||
|
classMirror.staticMembers[methodSymbol];
|
||||||
|
} else if (instance is Function) {
|
||||||
|
return (reflect(instance) as ClosureMirror).function;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _addDependencyForCallParameter(Container container,
|
static void _addDependencyForCallParameter(Container container,
|
||||||
|
|
|
@ -120,6 +120,11 @@ class Container implements ContainerContract {
|
||||||
|
|
||||||
_tags[tag]!.addAll(abstractList.cast<String>());
|
_tags[tag]!.addAll(abstractList.cast<String>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void forgetExtenders(String abstract) {
|
||||||
|
abstract = getAlias(abstract);
|
||||||
|
_extenders.remove(abstract);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -323,9 +328,26 @@ class Container implements ContainerContract {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void afterResolvingAttribute(String attribute, Function callback) {
|
void afterResolvingAttribute(
|
||||||
_afterResolvingAttributeCallbacks[attribute] ??= [];
|
Type attributeType, Function(dynamic, dynamic, Container) callback) {
|
||||||
_afterResolvingAttributeCallbacks[attribute]!.add(callback);
|
var attributeName = attributeType.toString();
|
||||||
|
_afterResolvingAttributeCallbacks[attributeName] ??= [];
|
||||||
|
_afterResolvingAttributeCallbacks[attributeName]!.add(callback);
|
||||||
|
|
||||||
|
// Ensure the attribute type is bound
|
||||||
|
if (!bound(attributeName)) {
|
||||||
|
bind(attributeName, (container) => attributeType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isShared(String abstract) {
|
||||||
|
return _instances.containsKey(abstract) ||
|
||||||
|
(_bindings.containsKey(abstract) &&
|
||||||
|
_bindings[abstract]!['shared'] == true);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isAlias(String name) {
|
||||||
|
return _aliases.containsKey(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -483,14 +505,12 @@ class Container implements ContainerContract {
|
||||||
void fireAfterResolvingAttributeCallbacks(
|
void fireAfterResolvingAttributeCallbacks(
|
||||||
List<InstanceMirror> annotations, dynamic object) {
|
List<InstanceMirror> annotations, dynamic object) {
|
||||||
for (var annotation in annotations) {
|
for (var annotation in annotations) {
|
||||||
if (annotation.reflectee is ContextualAttribute) {
|
var instance = annotation.reflectee;
|
||||||
var instance = annotation.reflectee as ContextualAttribute;
|
var attributeType = instance.runtimeType.toString();
|
||||||
var attributeType = instance.runtimeType.toString();
|
if (_afterResolvingAttributeCallbacks.containsKey(attributeType)) {
|
||||||
if (_afterResolvingAttributeCallbacks.containsKey(attributeType)) {
|
for (var callback
|
||||||
for (var callback
|
in _afterResolvingAttributeCallbacks[attributeType]!) {
|
||||||
in _afterResolvingAttributeCallbacks[attributeType]!) {
|
callback(instance, object, this);
|
||||||
callback(instance, object, this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -510,6 +530,11 @@ class Container implements ContainerContract {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void forgetExtenders(String abstract) {
|
||||||
|
abstract = getAlias(abstract);
|
||||||
|
_extenders.remove(abstract);
|
||||||
|
}
|
||||||
|
|
||||||
T makeScoped<T>(String abstract) {
|
T makeScoped<T>(String abstract) {
|
||||||
// This is similar to make, but ensures the instance is scoped
|
// This is similar to make, but ensures the instance is scoped
|
||||||
var instance = make<T>(abstract);
|
var instance = make<T>(abstract);
|
||||||
|
@ -578,9 +603,64 @@ class Container implements ContainerContract {
|
||||||
var instance =
|
var instance =
|
||||||
classMirror.newInstance(Symbol.empty, parameters).reflectee;
|
classMirror.newInstance(Symbol.empty, parameters).reflectee;
|
||||||
|
|
||||||
|
// Apply attributes to the instance
|
||||||
|
for (var attribute in classAttributes) {
|
||||||
|
var attributeType = attribute.reflectee.runtimeType;
|
||||||
|
var attributeTypeName = attributeType.toString();
|
||||||
|
if (_afterResolvingAttributeCallbacks
|
||||||
|
.containsKey(attributeTypeName)) {
|
||||||
|
for (var callback
|
||||||
|
in _afterResolvingAttributeCallbacks[attributeTypeName]!) {
|
||||||
|
callback(attribute.reflectee, instance, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply attributes to properties
|
||||||
|
var instanceMirror = reflect(instance);
|
||||||
|
for (var declaration in classMirror.declarations.values) {
|
||||||
|
if (declaration is VariableMirror) {
|
||||||
|
for (var attribute in declaration.metadata) {
|
||||||
|
var attributeType = attribute.reflectee.runtimeType;
|
||||||
|
var attributeTypeName = attributeType.toString();
|
||||||
|
if (_afterResolvingAttributeCallbacks
|
||||||
|
.containsKey(attributeTypeName)) {
|
||||||
|
for (var callback
|
||||||
|
in _afterResolvingAttributeCallbacks[attributeTypeName]!) {
|
||||||
|
var propertyValue =
|
||||||
|
instanceMirror.getField(declaration.simpleName).reflectee;
|
||||||
|
callback(attribute.reflectee, propertyValue, this);
|
||||||
|
instanceMirror.setField(
|
||||||
|
declaration.simpleName, propertyValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply after resolving callbacks
|
||||||
fireAfterResolvingAttributeCallbacks(classAttributes, instance);
|
fireAfterResolvingAttributeCallbacks(classAttributes, instance);
|
||||||
_fireAfterResolvingCallbacks(concrete, 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the instance if it's shared
|
||||||
|
if (isShared(concrete)) {
|
||||||
|
_instances[concrete] = instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the instance is stored before returning
|
||||||
|
if (_instances.containsKey(concrete)) {
|
||||||
|
return _instances[concrete];
|
||||||
|
}
|
||||||
|
|
||||||
return instance;
|
return instance;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// If any error occurs during class instantiation, return the string as is
|
// If any error occurs during class instantiation, return the string as is
|
||||||
|
@ -814,16 +894,6 @@ class Container implements ContainerContract {
|
||||||
return getAlias(_aliases[abstract]!);
|
return getAlias(_aliases[abstract]!);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isShared(String abstract) {
|
|
||||||
return _instances.containsKey(abstract) ||
|
|
||||||
(_bindings.containsKey(abstract) &&
|
|
||||||
_bindings[abstract]!['shared'] == true);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isAlias(String name) {
|
|
||||||
return _aliases.containsKey(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implement ArrayAccess-like functionality
|
// Implement ArrayAccess-like functionality
|
||||||
dynamic operator [](String key) => make(key);
|
dynamic operator [](String key) => make(key);
|
||||||
void operator []=(String key, dynamic value) => bind(key, value);
|
void operator []=(String key, dynamic value) => bind(key, value);
|
||||||
|
|
|
@ -17,3 +17,4 @@ dependencies:
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
lints: ^3.0.0
|
lints: ^3.0.0
|
||||||
test: ^1.24.0
|
test: ^1.24.0
|
||||||
|
platform_config: ^0.1.0
|
||||||
|
|
|
@ -0,0 +1,131 @@
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'package:ioc_container/container.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('AfterResolvingAttributeCallbackTest', () {
|
||||||
|
late Container container;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
container = Container();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('callback is called after dependency resolution with attribute', () {
|
||||||
|
container.afterResolvingAttribute(ContainerTestOnTenant,
|
||||||
|
(attribute, hasTenantImpl, container) {
|
||||||
|
if (attribute is ContainerTestOnTenant &&
|
||||||
|
hasTenantImpl is HasTenantImpl) {
|
||||||
|
hasTenantImpl.onTenant(attribute.tenant);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var hasTenantA =
|
||||||
|
container.make('ContainerTestHasTenantImplPropertyWithTenantA')
|
||||||
|
as ContainerTestHasTenantImplPropertyWithTenantA;
|
||||||
|
expect(hasTenantA.property, isA<HasTenantImpl>());
|
||||||
|
expect(hasTenantA.property.tenant, equals(Tenant.TenantA));
|
||||||
|
|
||||||
|
var hasTenantB =
|
||||||
|
container.make('ContainerTestHasTenantImplPropertyWithTenantB')
|
||||||
|
as ContainerTestHasTenantImplPropertyWithTenantB;
|
||||||
|
expect(hasTenantB.property, isA<HasTenantImpl>());
|
||||||
|
expect(hasTenantB.property.tenant, equals(Tenant.TenantB));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('callback is called after class with attribute is resolved', () {
|
||||||
|
container.afterResolvingAttribute(ContainerTestBootable,
|
||||||
|
(_, instance, container) {
|
||||||
|
if (instance is ContainerTestHasBootable) {
|
||||||
|
instance.booting();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var instance = container.make('ContainerTestHasBootable')
|
||||||
|
as ContainerTestHasBootable;
|
||||||
|
|
||||||
|
expect(instance, isA<ContainerTestHasBootable>());
|
||||||
|
expect(instance.hasBooted, isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'callback is called after class with constructor and attribute is resolved',
|
||||||
|
() {
|
||||||
|
container.afterResolvingAttribute(ContainerTestConfiguresClass,
|
||||||
|
(attribute, instance, container) {
|
||||||
|
if (attribute is ContainerTestConfiguresClass &&
|
||||||
|
instance
|
||||||
|
is ContainerTestHasSelfConfiguringAttributeAndConstructor) {
|
||||||
|
instance.value = attribute.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
container
|
||||||
|
.when('ContainerTestHasSelfConfiguringAttributeAndConstructor')
|
||||||
|
.needs('value')
|
||||||
|
.give('not-the-right-value');
|
||||||
|
|
||||||
|
var instance = container
|
||||||
|
.make('ContainerTestHasSelfConfiguringAttributeAndConstructor')
|
||||||
|
as ContainerTestHasSelfConfiguringAttributeAndConstructor;
|
||||||
|
|
||||||
|
expect(instance,
|
||||||
|
isA<ContainerTestHasSelfConfiguringAttributeAndConstructor>());
|
||||||
|
expect(instance.value, equals('the-right-value'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class ContainerTestOnTenant {
|
||||||
|
final Tenant tenant;
|
||||||
|
const ContainerTestOnTenant(this.tenant);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Tenant {
|
||||||
|
TenantA,
|
||||||
|
TenantB,
|
||||||
|
}
|
||||||
|
|
||||||
|
class HasTenantImpl {
|
||||||
|
Tenant? tenant;
|
||||||
|
|
||||||
|
void onTenant(Tenant tenant) {
|
||||||
|
this.tenant = tenant;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ContainerTestHasTenantImplPropertyWithTenantA {
|
||||||
|
@ContainerTestOnTenant(Tenant.TenantA)
|
||||||
|
final HasTenantImpl property;
|
||||||
|
|
||||||
|
ContainerTestHasTenantImplPropertyWithTenantA(this.property);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ContainerTestHasTenantImplPropertyWithTenantB {
|
||||||
|
@ContainerTestOnTenant(Tenant.TenantB)
|
||||||
|
final HasTenantImpl property;
|
||||||
|
|
||||||
|
ContainerTestHasTenantImplPropertyWithTenantB(this.property);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ContainerTestConfiguresClass {
|
||||||
|
final String value;
|
||||||
|
const ContainerTestConfiguresClass(this.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ContainerTestConfiguresClass('the-right-value')
|
||||||
|
class ContainerTestHasSelfConfiguringAttributeAndConstructor {
|
||||||
|
String value;
|
||||||
|
ContainerTestHasSelfConfiguringAttributeAndConstructor(this.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ContainerTestBootable {
|
||||||
|
const ContainerTestBootable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ContainerTestBootable()
|
||||||
|
class ContainerTestHasBootable {
|
||||||
|
bool hasBooted = false;
|
||||||
|
|
||||||
|
void booting() {
|
||||||
|
hasBooted = true;
|
||||||
|
}
|
||||||
|
}
|
89
packages/ioc_container/test/container_call_test.dart
Normal file
89
packages/ioc_container/test/container_call_test.dart
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'package:ioc_container/container.dart';
|
||||||
|
import 'package:platform_contracts/contracts.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('ContainerCallTest', () {
|
||||||
|
late Container container;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
container = Container();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('testCallWithAtSignBasedClassReferencesWithoutMethodThrowsException',
|
||||||
|
() {
|
||||||
|
expect(() => container.call('ContainerCallTest@'),
|
||||||
|
throwsA(isA<BindingResolutionException>()));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('testCallWithAtSignBasedClassReferences', () {
|
||||||
|
container.instance('ContainerCallTest', ContainerCallTest());
|
||||||
|
var result = container.call('ContainerCallTest@work', ['foo', 'bar']);
|
||||||
|
expect(result, equals('foobar'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('testCallWithAtSignBasedClassReferencesWithoutMethodCallsRun', () {
|
||||||
|
container.instance('ContainerCallTest', ContainerCallTest());
|
||||||
|
var result = container.call('ContainerCallTest');
|
||||||
|
expect(result, equals('run'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('testCallWithCallableArray', () {
|
||||||
|
var result =
|
||||||
|
container.call([ContainerCallTest(), 'work'], ['foo', 'bar']);
|
||||||
|
expect(result, equals('foobar'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('testCallWithStaticMethodNameString', () {
|
||||||
|
expect(
|
||||||
|
() => container.call('ContainerCallTest::staticWork', ['foo', 'bar']),
|
||||||
|
throwsA(isA<BindingResolutionException>()));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('testCallWithGlobalMethodNameString', () {
|
||||||
|
expect(() => container.call('globalTestMethod', ['foo', 'bar']),
|
||||||
|
throwsA(isA<BindingResolutionException>()));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('testCallWithBoundMethod', () {
|
||||||
|
container.bindMethod('work', (container, params) => 'foobar');
|
||||||
|
var result = container.call('work', ['foo', 'bar']);
|
||||||
|
expect(result, equals('foobar'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('testCallWithBoundMethodAndArrayOfParameters', () {
|
||||||
|
container.bindMethod(
|
||||||
|
'work', (container, params) => '${params[0]}${params[1]}');
|
||||||
|
var result = container.call('work', ['foo', 'bar']);
|
||||||
|
expect(result, equals('foobar'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('testCallWithBoundMethodAndArrayOfParametersWithOptionalParameters',
|
||||||
|
() {
|
||||||
|
container.bindMethod(
|
||||||
|
'work',
|
||||||
|
(container, params) =>
|
||||||
|
'${params[0]}${params[1]}${params[2] ?? 'baz'}');
|
||||||
|
var result = container.call('work', ['foo', 'bar']);
|
||||||
|
expect(result, equals('foobarbaz'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('testCallWithBoundMethodAndDependencies', () {
|
||||||
|
container.bind('foo', (container) => 'bar');
|
||||||
|
container.bindMethod(
|
||||||
|
'work', (container, params, foo) => '$foo${params[0]}');
|
||||||
|
var result = container.call('work', ['baz']);
|
||||||
|
expect(result, equals('barbaz'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class ContainerCallTest {
|
||||||
|
String work(String param1, String param2) => '$param1$param2';
|
||||||
|
|
||||||
|
String run() => 'run';
|
||||||
|
|
||||||
|
static String staticWork(String param1, String param2) => '$param1$param2';
|
||||||
|
}
|
||||||
|
|
||||||
|
String globalTestMethod(String param1, String param2) => '$param1$param2';
|
158
packages/ioc_container/test/container_extend_test.dart
Normal file
158
packages/ioc_container/test/container_extend_test.dart
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'package:ioc_container/container.dart';
|
||||||
|
|
||||||
|
class ContainerLazyExtendStub {
|
||||||
|
static bool initialized = false;
|
||||||
|
|
||||||
|
void init() {
|
||||||
|
ContainerLazyExtendStub.initialized = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('ContainerExtendTest', () {
|
||||||
|
test('extendedBindings', () {
|
||||||
|
var container = Container();
|
||||||
|
container['foo'] = 'foo';
|
||||||
|
container.extend('foo', (old) => '${old}bar');
|
||||||
|
|
||||||
|
var result1 = container.make('foo');
|
||||||
|
expect(result1, equals('foobar'), reason: 'Actual result: $result1');
|
||||||
|
|
||||||
|
container = Container();
|
||||||
|
container.singleton(
|
||||||
|
'foo', (container) => <String, dynamic>{'name': 'taylor'});
|
||||||
|
container.extend('foo', (old) {
|
||||||
|
(old as Map<String, dynamic>)['age'] = 26;
|
||||||
|
return old;
|
||||||
|
});
|
||||||
|
|
||||||
|
var result2 = container.make('foo') as Map<String, dynamic>;
|
||||||
|
expect(result2['name'], equals('taylor'));
|
||||||
|
expect(result2['age'], equals(26));
|
||||||
|
expect(identical(result2, container.make('foo')), isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('extendInstancesArePreserved', () {
|
||||||
|
var container = Container();
|
||||||
|
container.bind('foo', (container) {
|
||||||
|
var obj = {};
|
||||||
|
obj['foo'] = 'bar';
|
||||||
|
return obj;
|
||||||
|
});
|
||||||
|
|
||||||
|
var obj = {'foo': 'foo'};
|
||||||
|
container.instance('foo', obj);
|
||||||
|
container.extend('foo', (obj) {
|
||||||
|
(obj as Map<String, dynamic>)['bar'] = 'baz';
|
||||||
|
return obj;
|
||||||
|
});
|
||||||
|
container.extend('foo', (obj) {
|
||||||
|
(obj as Map<String, dynamic>)['baz'] = 'foo';
|
||||||
|
return obj;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(container.make('foo')['foo'], equals('foo'));
|
||||||
|
expect(container.make('foo')['bar'], equals('baz'));
|
||||||
|
expect(container.make('foo')['baz'], equals('foo'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('extendIsLazyInitialized', () {
|
||||||
|
ContainerLazyExtendStub.initialized = false;
|
||||||
|
|
||||||
|
var container = Container();
|
||||||
|
container.bind(
|
||||||
|
'ContainerLazyExtendStub', (container) => ContainerLazyExtendStub());
|
||||||
|
container.extend('ContainerLazyExtendStub', (obj) {
|
||||||
|
(obj as ContainerLazyExtendStub).init();
|
||||||
|
return obj;
|
||||||
|
});
|
||||||
|
expect(ContainerLazyExtendStub.initialized, isFalse);
|
||||||
|
container.make('ContainerLazyExtendStub');
|
||||||
|
expect(ContainerLazyExtendStub.initialized, isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('extendCanBeCalledBeforeBind', () {
|
||||||
|
var container = Container();
|
||||||
|
container.extend('foo', (old) => '${old}bar');
|
||||||
|
container['foo'] = 'foo';
|
||||||
|
|
||||||
|
var result = container.make('foo');
|
||||||
|
expect(result, equals('foobar'), reason: 'Actual result: $result');
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: Implement rebinding functionality
|
||||||
|
// test('extendInstanceRebindingCallback', () {
|
||||||
|
// var rebindCalled = false;
|
||||||
|
|
||||||
|
// var container = Container();
|
||||||
|
// container.rebinding('foo', (container) {
|
||||||
|
// rebindCalled = true;
|
||||||
|
// });
|
||||||
|
|
||||||
|
// var obj = {};
|
||||||
|
// container.instance('foo', obj);
|
||||||
|
|
||||||
|
// container.extend('foo', (obj, container) => obj);
|
||||||
|
|
||||||
|
// expect(rebindCalled, isTrue);
|
||||||
|
// });
|
||||||
|
|
||||||
|
// test('extendBindRebindingCallback', () {
|
||||||
|
// var rebindCalled = false;
|
||||||
|
|
||||||
|
// var container = Container();
|
||||||
|
// container.rebinding('foo', (container) {
|
||||||
|
// rebindCalled = true;
|
||||||
|
// });
|
||||||
|
// container.bind('foo', (container) => {});
|
||||||
|
|
||||||
|
// expect(rebindCalled, isFalse);
|
||||||
|
|
||||||
|
// container.make('foo');
|
||||||
|
|
||||||
|
// container.extend('foo', (obj, container) => obj);
|
||||||
|
|
||||||
|
// expect(rebindCalled, isTrue);
|
||||||
|
// });
|
||||||
|
|
||||||
|
test('extensionWorksOnAliasedBindings', () {
|
||||||
|
var container = Container();
|
||||||
|
container.singleton('something', (container) => 'some value');
|
||||||
|
container.alias('something', 'something-alias');
|
||||||
|
container.extend('something-alias', (value) => '$value extended');
|
||||||
|
|
||||||
|
expect(container.make('something'), equals('some value extended'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('multipleExtends', () {
|
||||||
|
var container = Container();
|
||||||
|
container['foo'] = 'foo';
|
||||||
|
container.extend('foo', (old) => '${old}bar');
|
||||||
|
container.extend('foo', (old) => '${old}baz');
|
||||||
|
|
||||||
|
expect(container.make('foo'), equals('foobarbaz'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('unsetExtend', () {
|
||||||
|
var container = Container();
|
||||||
|
container.bind('foo', (container) {
|
||||||
|
var obj = {};
|
||||||
|
obj['foo'] = 'bar';
|
||||||
|
return obj;
|
||||||
|
});
|
||||||
|
|
||||||
|
container.extend('foo', (obj) {
|
||||||
|
(obj as Map<String, dynamic>)['bar'] = 'baz';
|
||||||
|
return obj;
|
||||||
|
});
|
||||||
|
|
||||||
|
container.forgetInstance('foo');
|
||||||
|
container.forgetExtenders('foo');
|
||||||
|
|
||||||
|
container.bind('foo', (container) => 'foo');
|
||||||
|
|
||||||
|
expect(container.make('foo'), equals('foo'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'package:ioc_container/container.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('ContainerResolveNonInstantiableTest', () {
|
||||||
|
test('testResolvingNonInstantiableWithDefaultRemovesWiths', () {
|
||||||
|
var container = Container();
|
||||||
|
var object = container.make('ParentClass', [null, 42]);
|
||||||
|
|
||||||
|
expect(object, isA<ParentClass>());
|
||||||
|
expect(object.i, equals(42));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('testResolvingNonInstantiableWithVariadicRemovesWiths', () {
|
||||||
|
var container = Container();
|
||||||
|
var parent = container.make('VariadicParentClass', [
|
||||||
|
container.make('ChildClass', [[]]),
|
||||||
|
42
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(parent, isA<VariadicParentClass>());
|
||||||
|
expect(parent.child.objects, isEmpty);
|
||||||
|
expect(parent.i, equals(42));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('testResolveVariadicPrimitive', () {
|
||||||
|
var container = Container();
|
||||||
|
var parent = container.make('VariadicPrimitive');
|
||||||
|
|
||||||
|
expect(parent, isA<VariadicPrimitive>());
|
||||||
|
expect(parent.params, isEmpty);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class TestInterface {}
|
||||||
|
|
||||||
|
class ParentClass {
|
||||||
|
int i;
|
||||||
|
|
||||||
|
ParentClass([TestInterface? testObject, this.i = 0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
class VariadicParentClass {
|
||||||
|
ChildClass child;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
VariadicParentClass(this.child, [this.i = 0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChildClass {
|
||||||
|
List<TestInterface> objects;
|
||||||
|
|
||||||
|
ChildClass(this.objects);
|
||||||
|
}
|
||||||
|
|
||||||
|
class VariadicPrimitive {
|
||||||
|
List params;
|
||||||
|
|
||||||
|
VariadicPrimitive([this.params = const []]);
|
||||||
|
}
|
99
packages/ioc_container/test/container_tagging_test.dart
Normal file
99
packages/ioc_container/test/container_tagging_test.dart
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'package:ioc_container/container.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('ContainerTaggingTest', () {
|
||||||
|
test('testContainerTags', () {
|
||||||
|
var container = Container();
|
||||||
|
container.tag(ContainerImplementationTaggedStub, 'foo');
|
||||||
|
container.tag(ContainerImplementationTaggedStub, 'bar');
|
||||||
|
container.tag(ContainerImplementationTaggedStubTwo, 'foo');
|
||||||
|
|
||||||
|
expect(container.tagged('bar').length, 1);
|
||||||
|
expect(container.tagged('foo').length, 2);
|
||||||
|
|
||||||
|
var fooResults = [];
|
||||||
|
for (var foo in container.tagged('foo')) {
|
||||||
|
fooResults.add(foo);
|
||||||
|
}
|
||||||
|
|
||||||
|
var barResults = [];
|
||||||
|
for (var bar in container.tagged('bar')) {
|
||||||
|
barResults.add(bar);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(fooResults[0], isA<ContainerImplementationTaggedStub>());
|
||||||
|
expect(barResults[0], isA<ContainerImplementationTaggedStub>());
|
||||||
|
expect(fooResults[1], isA<ContainerImplementationTaggedStubTwo>());
|
||||||
|
|
||||||
|
container = Container();
|
||||||
|
container.tag(ContainerImplementationTaggedStub, 'foo');
|
||||||
|
container.tag(ContainerImplementationTaggedStubTwo, 'foo');
|
||||||
|
expect(container.tagged('foo').length, 2);
|
||||||
|
|
||||||
|
fooResults = [];
|
||||||
|
for (var foo in container.tagged('foo')) {
|
||||||
|
fooResults.add(foo);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(fooResults[0], isA<ContainerImplementationTaggedStub>());
|
||||||
|
expect(fooResults[1], isA<ContainerImplementationTaggedStubTwo>());
|
||||||
|
|
||||||
|
expect(container.tagged('this_tag_does_not_exist').length, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('testTaggedServicesAreLazyLoaded', () {
|
||||||
|
var container = Container();
|
||||||
|
var makeCount = 0;
|
||||||
|
container.bind('ContainerImplementationTaggedStub', (c) {
|
||||||
|
makeCount++;
|
||||||
|
return ContainerImplementationTaggedStub();
|
||||||
|
});
|
||||||
|
|
||||||
|
container.tag('ContainerImplementationTaggedStub', 'foo');
|
||||||
|
container.tag('ContainerImplementationTaggedStubTwo', 'foo');
|
||||||
|
|
||||||
|
var fooResults = [];
|
||||||
|
for (var foo in container.tagged('foo')) {
|
||||||
|
fooResults.add(foo);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(container.tagged('foo').length, 2);
|
||||||
|
expect(fooResults[0], isA<ContainerImplementationTaggedStub>());
|
||||||
|
expect(makeCount, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('testLazyLoadedTaggedServicesCanBeLoopedOverMultipleTimes', () {
|
||||||
|
var container = Container();
|
||||||
|
container.tag('ContainerImplementationTaggedStub', 'foo');
|
||||||
|
container.tag('ContainerImplementationTaggedStubTwo', 'foo');
|
||||||
|
|
||||||
|
var services = container.tagged('foo');
|
||||||
|
|
||||||
|
var fooResults = [];
|
||||||
|
for (var foo in services) {
|
||||||
|
fooResults.add(foo);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(fooResults[0], isA<ContainerImplementationTaggedStub>());
|
||||||
|
expect(fooResults[1], isA<ContainerImplementationTaggedStubTwo>());
|
||||||
|
|
||||||
|
fooResults = [];
|
||||||
|
for (var foo in services) {
|
||||||
|
fooResults.add(foo);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(fooResults[0], isA<ContainerImplementationTaggedStub>());
|
||||||
|
expect(fooResults[1], isA<ContainerImplementationTaggedStubTwo>());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class IContainerTaggedContractStub {}
|
||||||
|
|
||||||
|
class ContainerImplementationTaggedStub
|
||||||
|
implements IContainerTaggedContractStub {}
|
||||||
|
|
||||||
|
class ContainerImplementationTaggedStubTwo
|
||||||
|
implements IContainerTaggedContractStub {}
|
|
@ -0,0 +1,171 @@
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'package:ioc_container/container.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('ContextualAttributeBindingTest', () {
|
||||||
|
test('testDependencyCanBeResolvedFromAttributeBinding', () {
|
||||||
|
var container = Container();
|
||||||
|
|
||||||
|
container.bind('ContainerTestContract', (c) => ContainerTestImplB());
|
||||||
|
container.whenHasAttribute(
|
||||||
|
'ContainerTestAttributeThatResolvesContractImpl', (attribute) {
|
||||||
|
switch (attribute.name) {
|
||||||
|
case 'A':
|
||||||
|
return ContainerTestImplA();
|
||||||
|
case 'B':
|
||||||
|
return ContainerTestImplB();
|
||||||
|
default:
|
||||||
|
throw Exception('Unknown implementation');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var classA =
|
||||||
|
container.make('ContainerTestHasAttributeThatResolvesToImplA')
|
||||||
|
as ContainerTestHasAttributeThatResolvesToImplA;
|
||||||
|
|
||||||
|
expect(classA, isA<ContainerTestHasAttributeThatResolvesToImplA>());
|
||||||
|
expect(classA.property, isA<ContainerTestImplA>());
|
||||||
|
|
||||||
|
var classB =
|
||||||
|
container.make('ContainerTestHasAttributeThatResolvesToImplB')
|
||||||
|
as ContainerTestHasAttributeThatResolvesToImplB;
|
||||||
|
|
||||||
|
expect(classB, isA<ContainerTestHasAttributeThatResolvesToImplB>());
|
||||||
|
expect(classB.property, isA<ContainerTestImplB>());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('testScalarDependencyCanBeResolvedFromAttributeBinding', () {
|
||||||
|
var container = Container();
|
||||||
|
container.singleton(
|
||||||
|
'config',
|
||||||
|
(c) => Repository({
|
||||||
|
'app': {
|
||||||
|
'timezone': 'Europe/Paris',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
container.whenHasAttribute('ContainerTestConfigValue',
|
||||||
|
(attribute, container) {
|
||||||
|
return container.make('config').get(attribute.key);
|
||||||
|
});
|
||||||
|
|
||||||
|
var instance = container.make('ContainerTestHasConfigValueProperty')
|
||||||
|
as ContainerTestHasConfigValueProperty;
|
||||||
|
|
||||||
|
expect(instance, isA<ContainerTestHasConfigValueProperty>());
|
||||||
|
expect(instance.timezone, equals('Europe/Paris'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('testScalarDependencyCanBeResolvedFromAttributeResolveMethod', () {
|
||||||
|
var container = Container();
|
||||||
|
container.singleton(
|
||||||
|
'config',
|
||||||
|
(c) => Repository({
|
||||||
|
'app': {
|
||||||
|
'env': 'production',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
var instance =
|
||||||
|
container.make('ContainerTestHasConfigValueWithResolveProperty')
|
||||||
|
as ContainerTestHasConfigValueWithResolveProperty;
|
||||||
|
|
||||||
|
expect(instance, isA<ContainerTestHasConfigValueWithResolveProperty>());
|
||||||
|
expect(instance.env, equals('production'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('testDependencyWithAfterCallbackAttributeCanBeResolved', () {
|
||||||
|
var container = Container();
|
||||||
|
|
||||||
|
var instance = container.make(
|
||||||
|
'ContainerTestHasConfigValueWithResolvePropertyAndAfterCallback')
|
||||||
|
as ContainerTestHasConfigValueWithResolvePropertyAndAfterCallback;
|
||||||
|
|
||||||
|
expect(instance.person['role'], equals('Developer'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class ContainerTestAttributeThatResolvesContractImpl {
|
||||||
|
final String name;
|
||||||
|
const ContainerTestAttributeThatResolvesContractImpl(this.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class ContainerTestContract {}
|
||||||
|
|
||||||
|
class ContainerTestImplA implements ContainerTestContract {}
|
||||||
|
|
||||||
|
class ContainerTestImplB implements ContainerTestContract {}
|
||||||
|
|
||||||
|
class ContainerTestHasAttributeThatResolvesToImplA {
|
||||||
|
final ContainerTestContract property;
|
||||||
|
ContainerTestHasAttributeThatResolvesToImplA(this.property);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ContainerTestHasAttributeThatResolvesToImplB {
|
||||||
|
final ContainerTestContract property;
|
||||||
|
ContainerTestHasAttributeThatResolvesToImplB(this.property);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ContainerTestConfigValue {
|
||||||
|
final String key;
|
||||||
|
const ContainerTestConfigValue(this.key);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ContainerTestHasConfigValueProperty {
|
||||||
|
final String timezone;
|
||||||
|
ContainerTestHasConfigValueProperty(this.timezone);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ContainerTestConfigValueWithResolve {
|
||||||
|
final String key;
|
||||||
|
const ContainerTestConfigValueWithResolve(this.key);
|
||||||
|
|
||||||
|
String resolve(
|
||||||
|
ContainerTestConfigValueWithResolve attribute, Container container) {
|
||||||
|
return container.make('config').get(attribute.key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ContainerTestHasConfigValueWithResolveProperty {
|
||||||
|
final String env;
|
||||||
|
ContainerTestHasConfigValueWithResolveProperty(this.env);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ContainerTestConfigValueWithResolveAndAfter {
|
||||||
|
const ContainerTestConfigValueWithResolveAndAfter();
|
||||||
|
|
||||||
|
Object resolve(ContainerTestConfigValueWithResolveAndAfter attribute,
|
||||||
|
Container container) {
|
||||||
|
return {'name': 'Taylor'};
|
||||||
|
}
|
||||||
|
|
||||||
|
void after(ContainerTestConfigValueWithResolveAndAfter attribute,
|
||||||
|
Object value, Container container) {
|
||||||
|
(value as Map)['role'] = 'Developer';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ContainerTestHasConfigValueWithResolvePropertyAndAfterCallback {
|
||||||
|
final Map person;
|
||||||
|
ContainerTestHasConfigValueWithResolvePropertyAndAfterCallback(this.person);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Repository {
|
||||||
|
final Map<String, dynamic> _data;
|
||||||
|
|
||||||
|
Repository(this._data);
|
||||||
|
|
||||||
|
dynamic get(String key) {
|
||||||
|
var keys = key.split('.');
|
||||||
|
var value = _data;
|
||||||
|
for (var k in keys) {
|
||||||
|
if (value is Map && value.containsKey(k)) {
|
||||||
|
value = value[k];
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
164
packages/ioc_container/test/contextual_binding_test.dart
Normal file
164
packages/ioc_container/test/contextual_binding_test.dart
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'package:ioc_container/container.dart';
|
||||||
|
import 'package:platform_config/platform_config.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('ContextualBindingTest', () {
|
||||||
|
test('testContainerCanInjectDifferentImplementationsDependingOnContext',
|
||||||
|
() {
|
||||||
|
var container = Container();
|
||||||
|
|
||||||
|
container.bind('IContainerContextContractStub',
|
||||||
|
(c) => ContainerContextImplementationStub());
|
||||||
|
|
||||||
|
container
|
||||||
|
.when('ContainerTestContextInjectOne')
|
||||||
|
.needs('IContainerContextContractStub')
|
||||||
|
.give('ContainerContextImplementationStub');
|
||||||
|
container
|
||||||
|
.when('ContainerTestContextInjectTwo')
|
||||||
|
.needs('IContainerContextContractStub')
|
||||||
|
.give('ContainerContextImplementationStubTwo');
|
||||||
|
|
||||||
|
var one = container.make('ContainerTestContextInjectOne')
|
||||||
|
as ContainerTestContextInjectOne;
|
||||||
|
var two = container.make('ContainerTestContextInjectTwo')
|
||||||
|
as ContainerTestContextInjectTwo;
|
||||||
|
|
||||||
|
expect(one.impl, isA<ContainerContextImplementationStub>());
|
||||||
|
expect(two.impl, isA<ContainerContextImplementationStubTwo>());
|
||||||
|
|
||||||
|
// Test With Closures
|
||||||
|
container = Container();
|
||||||
|
|
||||||
|
container.bind('IContainerContextContractStub',
|
||||||
|
(c) => ContainerContextImplementationStub());
|
||||||
|
|
||||||
|
container
|
||||||
|
.when('ContainerTestContextInjectOne')
|
||||||
|
.needs('IContainerContextContractStub')
|
||||||
|
.give('ContainerContextImplementationStub');
|
||||||
|
container
|
||||||
|
.when('ContainerTestContextInjectTwo')
|
||||||
|
.needs('IContainerContextContractStub')
|
||||||
|
.give((Container container) {
|
||||||
|
return container.make('ContainerContextImplementationStubTwo');
|
||||||
|
});
|
||||||
|
|
||||||
|
one = container.make('ContainerTestContextInjectOne')
|
||||||
|
as ContainerTestContextInjectOne;
|
||||||
|
two = container.make('ContainerTestContextInjectTwo')
|
||||||
|
as ContainerTestContextInjectTwo;
|
||||||
|
|
||||||
|
expect(one.impl, isA<ContainerContextImplementationStub>());
|
||||||
|
expect(two.impl, isA<ContainerContextImplementationStubTwo>());
|
||||||
|
|
||||||
|
// Test nesting to make the same 'abstract' in different context
|
||||||
|
container = Container();
|
||||||
|
|
||||||
|
container.bind('IContainerContextContractStub',
|
||||||
|
(c) => ContainerContextImplementationStub());
|
||||||
|
|
||||||
|
container
|
||||||
|
.when('ContainerTestContextInjectOne')
|
||||||
|
.needs('IContainerContextContractStub')
|
||||||
|
.give((Container container) {
|
||||||
|
return container.make('IContainerContextContractStub');
|
||||||
|
});
|
||||||
|
|
||||||
|
one = container.make('ContainerTestContextInjectOne')
|
||||||
|
as ContainerTestContextInjectOne;
|
||||||
|
|
||||||
|
expect(one.impl, isA<ContainerContextImplementationStub>());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('testContextualBindingWorksForExistingInstancedBindings', () {
|
||||||
|
var container = Container();
|
||||||
|
|
||||||
|
container.instance(
|
||||||
|
'IContainerContextContractStub', ContainerImplementationStub());
|
||||||
|
|
||||||
|
container
|
||||||
|
.when('ContainerTestContextInjectOne')
|
||||||
|
.needs('IContainerContextContractStub')
|
||||||
|
.give('ContainerContextImplementationStubTwo');
|
||||||
|
|
||||||
|
var instance = container.make('ContainerTestContextInjectOne')
|
||||||
|
as ContainerTestContextInjectOne;
|
||||||
|
expect(instance.impl, isA<ContainerContextImplementationStubTwo>());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('testContextualBindingGivesValuesFromConfigWithDefault', () {
|
||||||
|
var container = Container();
|
||||||
|
|
||||||
|
container.singleton(
|
||||||
|
'config',
|
||||||
|
(c) => Repository({
|
||||||
|
'test': {
|
||||||
|
'password': 'hunter42',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
container
|
||||||
|
.when('ContainerTestContextInjectFromConfigIndividualValues')
|
||||||
|
.needs('\$username')
|
||||||
|
.giveConfig('test.username', 'DEFAULT_USERNAME');
|
||||||
|
|
||||||
|
container
|
||||||
|
.when('ContainerTestContextInjectFromConfigIndividualValues')
|
||||||
|
.needs('\$password')
|
||||||
|
.giveConfig('test.password');
|
||||||
|
|
||||||
|
var resolvedInstance =
|
||||||
|
container.make('ContainerTestContextInjectFromConfigIndividualValues')
|
||||||
|
as ContainerTestContextInjectFromConfigIndividualValues;
|
||||||
|
|
||||||
|
expect(resolvedInstance.username, equals('DEFAULT_USERNAME'));
|
||||||
|
expect(resolvedInstance.password, equals('hunter42'));
|
||||||
|
expect(resolvedInstance.alias, isNull);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class IContainerContextContractStub {}
|
||||||
|
|
||||||
|
class ContainerContextNonContractStub {}
|
||||||
|
|
||||||
|
class ContainerContextImplementationStub
|
||||||
|
implements IContainerContextContractStub {}
|
||||||
|
|
||||||
|
class ContainerContextImplementationStubTwo
|
||||||
|
implements IContainerContextContractStub {}
|
||||||
|
|
||||||
|
class ContainerImplementationStub implements IContainerContextContractStub {}
|
||||||
|
|
||||||
|
class ContainerTestContextInjectInstantiations
|
||||||
|
implements IContainerContextContractStub {
|
||||||
|
static int instantiations = 0;
|
||||||
|
|
||||||
|
ContainerTestContextInjectInstantiations() {
|
||||||
|
instantiations++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ContainerTestContextInjectOne {
|
||||||
|
final IContainerContextContractStub impl;
|
||||||
|
|
||||||
|
ContainerTestContextInjectOne(this.impl);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ContainerTestContextInjectTwo {
|
||||||
|
final IContainerContextContractStub impl;
|
||||||
|
|
||||||
|
ContainerTestContextInjectTwo(this.impl);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ContainerTestContextInjectFromConfigIndividualValues {
|
||||||
|
final String username;
|
||||||
|
final String password;
|
||||||
|
final String? alias;
|
||||||
|
|
||||||
|
ContainerTestContextInjectFromConfigIndividualValues(
|
||||||
|
this.username, this.password,
|
||||||
|
[this.alias]);
|
||||||
|
}
|
165
packages/ioc_container/test/resolving_callback_test.dart
Normal file
165
packages/ioc_container/test/resolving_callback_test.dart
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'package:ioc_container/container.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('ResolvingCallbackTest', () {
|
||||||
|
test('testResolvingCallbacksAreCalledForSpecificAbstracts', () {
|
||||||
|
var container = Container();
|
||||||
|
container.resolving('foo', (object) {
|
||||||
|
(object as dynamic).name = 'taylor';
|
||||||
|
return object;
|
||||||
|
});
|
||||||
|
container.bind('foo', (c) => Object());
|
||||||
|
var instance = container.make('foo');
|
||||||
|
|
||||||
|
expect((instance as dynamic).name, 'taylor');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('testResolvingCallbacksAreCalled', () {
|
||||||
|
var container = Container();
|
||||||
|
container.resolving((object) {
|
||||||
|
(object as dynamic).name = 'taylor';
|
||||||
|
return object;
|
||||||
|
});
|
||||||
|
container.bind('foo', (c) => Object());
|
||||||
|
var instance = container.make('foo');
|
||||||
|
|
||||||
|
expect((instance as dynamic).name, 'taylor');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('testResolvingCallbacksAreCalledForType', () {
|
||||||
|
var container = Container();
|
||||||
|
container.resolving('Object', (object) {
|
||||||
|
(object as dynamic).name = 'taylor';
|
||||||
|
return object;
|
||||||
|
});
|
||||||
|
container.bind('foo', (c) => Object());
|
||||||
|
var instance = container.make('foo');
|
||||||
|
|
||||||
|
expect((instance as dynamic).name, 'taylor');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('testResolvingCallbacksShouldBeFiredWhenCalledWithAliases', () {
|
||||||
|
var container = Container();
|
||||||
|
container.alias('Object', 'std');
|
||||||
|
container.resolving('std', (object) {
|
||||||
|
(object as dynamic).name = 'taylor';
|
||||||
|
return object;
|
||||||
|
});
|
||||||
|
container.bind('foo', (c) => Object());
|
||||||
|
var instance = container.make('foo');
|
||||||
|
|
||||||
|
expect((instance as dynamic).name, 'taylor');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('testResolvingCallbacksAreCalledOnceForImplementation', () {
|
||||||
|
var container = Container();
|
||||||
|
|
||||||
|
var callCounter = 0;
|
||||||
|
container.resolving('ResolvingContractStub', (_, __) {
|
||||||
|
callCounter++;
|
||||||
|
});
|
||||||
|
|
||||||
|
container.bind(
|
||||||
|
'ResolvingContractStub', (c) => ResolvingImplementationStub());
|
||||||
|
|
||||||
|
container.make('ResolvingImplementationStub');
|
||||||
|
expect(callCounter, 1);
|
||||||
|
|
||||||
|
container.make('ResolvingImplementationStub');
|
||||||
|
expect(callCounter, 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('testGlobalResolvingCallbacksAreCalledOnceForImplementation', () {
|
||||||
|
var container = Container();
|
||||||
|
|
||||||
|
var callCounter = 0;
|
||||||
|
container.resolving((_, __) {
|
||||||
|
callCounter++;
|
||||||
|
});
|
||||||
|
|
||||||
|
container.bind(
|
||||||
|
'ResolvingContractStub', (c) => ResolvingImplementationStub());
|
||||||
|
|
||||||
|
container.make('ResolvingImplementationStub');
|
||||||
|
expect(callCounter, 1);
|
||||||
|
|
||||||
|
container.make('ResolvingContractStub');
|
||||||
|
expect(callCounter, 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('testResolvingCallbacksAreCalledOnceForSingletonConcretes', () {
|
||||||
|
var container = Container();
|
||||||
|
|
||||||
|
var callCounter = 0;
|
||||||
|
container.resolving('ResolvingContractStub', (_, __) {
|
||||||
|
callCounter++;
|
||||||
|
});
|
||||||
|
|
||||||
|
container.bind(
|
||||||
|
'ResolvingContractStub', (c) => ResolvingImplementationStub());
|
||||||
|
container.bind(
|
||||||
|
'ResolvingImplementationStub', (c) => ResolvingImplementationStub());
|
||||||
|
|
||||||
|
container.make('ResolvingImplementationStub');
|
||||||
|
expect(callCounter, 1);
|
||||||
|
|
||||||
|
container.make('ResolvingImplementationStub');
|
||||||
|
expect(callCounter, 2);
|
||||||
|
|
||||||
|
container.make('ResolvingContractStub');
|
||||||
|
expect(callCounter, 3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('testResolvingCallbacksCanStillBeAddedAfterTheFirstResolution', () {
|
||||||
|
var container = Container();
|
||||||
|
|
||||||
|
container.bind(
|
||||||
|
'ResolvingContractStub', (c) => ResolvingImplementationStub());
|
||||||
|
|
||||||
|
container.make('ResolvingImplementationStub');
|
||||||
|
|
||||||
|
var callCounter = 0;
|
||||||
|
container.resolving('ResolvingContractStub', (_, __) {
|
||||||
|
callCounter++;
|
||||||
|
});
|
||||||
|
|
||||||
|
container.make('ResolvingImplementationStub');
|
||||||
|
expect(callCounter, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('testParametersPassedIntoResolvingCallbacks', () {
|
||||||
|
var container = Container();
|
||||||
|
|
||||||
|
container.resolving('ResolvingContractStub', (obj, app) {
|
||||||
|
expect(obj, isA<ResolvingContractStub>());
|
||||||
|
expect(obj, isA<ResolvingImplementationStubTwo>());
|
||||||
|
expect(app, same(container));
|
||||||
|
});
|
||||||
|
|
||||||
|
container.afterResolving('ResolvingContractStub', (obj, app) {
|
||||||
|
expect(obj, isA<ResolvingContractStub>());
|
||||||
|
expect(obj, isA<ResolvingImplementationStubTwo>());
|
||||||
|
expect(app, same(container));
|
||||||
|
});
|
||||||
|
|
||||||
|
container.afterResolving((obj, app) {
|
||||||
|
expect(obj, isA<ResolvingContractStub>());
|
||||||
|
expect(obj, isA<ResolvingImplementationStubTwo>());
|
||||||
|
expect(app, same(container));
|
||||||
|
});
|
||||||
|
|
||||||
|
container.bind(
|
||||||
|
'ResolvingContractStub', (c) => ResolvingImplementationStubTwo());
|
||||||
|
container.make('ResolvingContractStub');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add all remaining tests here...
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class ResolvingContractStub {}
|
||||||
|
|
||||||
|
class ResolvingImplementationStub implements ResolvingContractStub {}
|
||||||
|
|
||||||
|
class ResolvingImplementationStubTwo implements ResolvingContractStub {}
|
37
packages/ioc_container/test/rewindable_generator_test.dart
Normal file
37
packages/ioc_container/test/rewindable_generator_test.dart
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'package:ioc_container/container.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('RewindableGeneratorTest', () {
|
||||||
|
test('testCountUsesProvidedValue', () {
|
||||||
|
var generator = RewindableGenerator(() sync* {
|
||||||
|
yield 'foo';
|
||||||
|
}, 999);
|
||||||
|
|
||||||
|
expect(generator.length, 999);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('testCountUsesProvidedValueAsCallback', () {
|
||||||
|
var called = 0;
|
||||||
|
|
||||||
|
var countCallback = () {
|
||||||
|
called++;
|
||||||
|
return 500;
|
||||||
|
};
|
||||||
|
|
||||||
|
var generator = RewindableGenerator(() sync* {
|
||||||
|
yield 'foo';
|
||||||
|
}, countCallback());
|
||||||
|
|
||||||
|
// the count callback is called eagerly in this implementation
|
||||||
|
expect(called, 1);
|
||||||
|
|
||||||
|
expect(generator.length, 500);
|
||||||
|
|
||||||
|
generator.length;
|
||||||
|
|
||||||
|
// the count callback is called only once
|
||||||
|
expect(called, 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
36
packages/ioc_container/test/util_test.dart
Normal file
36
packages/ioc_container/test/util_test.dart
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'package:ioc_container/container.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('UtilTest', () {
|
||||||
|
test('testUnwrapIfClosure', () {
|
||||||
|
expect(Util.unwrapIfClosure('foo'), 'foo');
|
||||||
|
expect(Util.unwrapIfClosure(() => 'foo'), 'foo');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('testArrayWrap', () {
|
||||||
|
var string = 'a';
|
||||||
|
var array = ['a'];
|
||||||
|
var object = Object();
|
||||||
|
(object as dynamic).value = 'a';
|
||||||
|
|
||||||
|
expect(Util.arrayWrap(string), ['a']);
|
||||||
|
expect(Util.arrayWrap(array), array);
|
||||||
|
expect(Util.arrayWrap(object), [object]);
|
||||||
|
expect(Util.arrayWrap(null), []);
|
||||||
|
expect(Util.arrayWrap([null]), [null]);
|
||||||
|
expect(Util.arrayWrap([null, null]), [null, null]);
|
||||||
|
expect(Util.arrayWrap(''), ['']);
|
||||||
|
expect(Util.arrayWrap(['']), ['']);
|
||||||
|
expect(Util.arrayWrap(false), [false]);
|
||||||
|
expect(Util.arrayWrap([false]), [false]);
|
||||||
|
expect(Util.arrayWrap(0), [0]);
|
||||||
|
|
||||||
|
var obj = Object();
|
||||||
|
(obj as dynamic).value = 'a';
|
||||||
|
var wrappedObj = Util.arrayWrap(obj);
|
||||||
|
expect(wrappedObj, [obj]);
|
||||||
|
expect(identical(wrappedObj[0], obj), isTrue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in a new issue