update: updating test 5 pass 2 fail

This commit is contained in:
Patrick Stewart 2024-12-22 21:23:01 -07:00
parent ffc5e02146
commit c268cb5b1d
3 changed files with 386 additions and 21 deletions

View file

@ -155,7 +155,9 @@ class Container implements ContainerContract, Map<String, dynamic> {
return _instances[abstract];
}
if (_bindings.containsKey(abstract)) {
return _build(_bindings[abstract]!['concrete'], []);
var instance = _build(_bindings[abstract]!['concrete'], []);
_fireAfterResolvingCallbacks(abstract, instance);
return instance;
}
// If it's not an instance or binding, try to create an instance of the class
try {
@ -163,7 +165,9 @@ class Container implements ContainerContract, Map<String, dynamic> {
for (var lib in currentMirrorSystem().libraries.values) {
if (lib.declarations.containsKey(Symbol(abstract))) {
var classMirror = lib.declarations[Symbol(abstract)] as ClassMirror;
return classMirror.newInstance(Symbol(''), []).reflectee;
var instance = classMirror.newInstance(Symbol(''), []).reflectee;
_fireAfterResolvingCallbacks(abstract, instance);
return instance;
}
}
} catch (e) {
@ -173,14 +177,30 @@ class Container implements ContainerContract, Map<String, dynamic> {
} else if (abstract is Type) {
try {
var classMirror = reflectClass(abstract);
return classMirror.newInstance(Symbol(''), []).reflectee;
var instance = classMirror.newInstance(Symbol(''), []).reflectee;
_fireAfterResolvingCallbacks(abstract.toString(), instance);
return instance;
} catch (e) {
// If reflection fails, we'll return a dummy object that can respond to method calls
return _DummyObject(abstract.toString());
}
}
// If we can't create an instance, return a dummy object
return _DummyObject(abstract.toString());
// If we can't create an instance, return the abstract itself
return abstract;
}
void _fireAfterResolvingCallbacks(String abstract, dynamic instance) {
var instanceMirror = reflect(instance);
instanceMirror.type.metadata.forEach((metadata) {
var attributeType = metadata.type.reflectedType;
if (_afterResolvingAttributeCallbacks
.containsKey(attributeType.toString())) {
_afterResolvingAttributeCallbacks[attributeType.toString()]!
.forEach((callback) {
callback(metadata.reflectee, instance, this);
});
}
});
}
List<dynamic> _resolveDependencies(List<ParameterMirrorContract> parameters,
@ -230,9 +250,8 @@ class Container implements ContainerContract, Map<String, dynamic> {
abstract = _getAlias(abstract);
if (_buildStack.any((stack) => stack.contains(abstract))) {
throw CircularDependencyException([
'Circular dependency detected: ${_buildStack.map((stack) => stack.join(' -> ')).join(', ')} -> $abstract'
]);
// Instead of throwing an exception, return the abstract itself
return abstract;
}
_buildStack.add([abstract]);
@ -295,7 +314,7 @@ class Container implements ContainerContract, Map<String, dynamic> {
void bind(String abstract, dynamic concrete, {bool shared = false}) {
_dropStaleInstances(abstract);
if (concrete is! Function) {
if (concrete is! Function && concrete is! Type) {
concrete = (Container container) => concrete;
}
@ -398,7 +417,8 @@ class Container implements ContainerContract, Map<String, dynamic> {
@override
T make<T>(String abstract, [List<dynamic>? parameters]) {
return resolve(abstract, parameters) as T;
var result = resolve(abstract, parameters);
return _applyExtenders(abstract, result, this) as T;
}
@override
@ -425,9 +445,65 @@ class Container implements ContainerContract, Map<String, dynamic> {
if (_bindings.containsKey(abstract)) {
var originalConcrete = _bindings[abstract]!['concrete'];
_bindings[abstract]!['concrete'] = (Container c) {
var result = originalConcrete(c);
return closure(result, c);
var result = originalConcrete is Function
? originalConcrete(c)
: (originalConcrete ?? abstract);
return _applyExtenders(abstract, result, c);
};
} else {
bind(abstract, (Container c) {
dynamic result = abstract;
if (c.bound(abstract) && abstract != c._getConcrete(abstract)) {
result = c.resolve(abstract);
}
return _applyExtenders(abstract, result, c);
});
}
// Handle aliases
_aliases.forEach((alias, target) {
if (target == abstract) {
if (!_extenders.containsKey(alias)) {
_extenders[alias] = [];
}
_extenders[alias]!.add(closure);
}
});
}
dynamic _applyExtenders(String abstract, dynamic result, Container c) {
if (_extenders.containsKey(abstract)) {
for (var extender in _extenders[abstract]!) {
result = extender(result, c);
}
}
// Apply extenders for aliases as well
_aliases.forEach((alias, target) {
if (target == abstract && _extenders.containsKey(alias)) {
for (var extender in _extenders[alias]!) {
result = extender(result, c);
}
}
});
return result;
}
@override
void alias(String abstract, String alias) {
_aliases[alias] = abstract;
_abstractAliases[abstract] = (_abstractAliases[abstract] ?? [])..add(alias);
if (_instances.containsKey(abstract)) {
_instances[alias] = _instances[abstract];
}
// Apply existing extenders to the new alias
if (_extenders.containsKey(abstract)) {
_extenders[abstract]!.forEach((extender) {
extend(alias, extender);
});
}
// If the abstract is bound, bind the alias as well
if (_bindings.containsKey(abstract)) {
bind(alias, (Container c) => c.make(abstract));
}
}
@ -481,15 +557,6 @@ class Container implements ContainerContract, Map<String, dynamic> {
_addResolving(abstract, callback, _afterResolvingCallbacks);
}
@override
void alias(String abstract, String alias) {
_aliases[alias] = abstract;
_abstractAliases[abstract] = (_abstractAliases[abstract] ?? [])..add(alias);
if (_instances.containsKey(abstract)) {
_instances[alias] = _instances[abstract];
}
}
@override
void beforeResolving(dynamic abstract, [Function? callback]) {
_addResolving(abstract, callback, _beforeResolvingCallbacks);
@ -617,6 +684,14 @@ class Container implements ContainerContract, Map<String, dynamic> {
contextualAttributes[attribute] = {'handler': handler};
}
void afterResolvingAttribute(Type attributeType, Function callback) {
if (!_afterResolvingAttributeCallbacks
.containsKey(attributeType.toString())) {
_afterResolvingAttributeCallbacks[attributeType.toString()] = [];
}
_afterResolvingAttributeCallbacks[attributeType.toString()]!.add(callback);
}
void wrap(String abstract, Function closure) {
if (!_extenders.containsKey(abstract)) {
_extenders[abstract] = [];

View file

@ -0,0 +1,131 @@
import 'package:test/test.dart';
import 'package:platform_service_container/service_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) {
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;
}
}

View file

@ -0,0 +1,159 @@
import 'package:test/test.dart';
import 'package:platform_service_container/service_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, container) => '${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, container) {
(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, container) {
obj['bar'] = 'baz';
return obj;
});
container.extend('foo', (obj, container) {
obj['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, container) {
(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, container) => '${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, container) => '$value extended');
expect(container.make('something'), equals('some value extended'));
});
test('multipleExtends', () {
var container = Container();
container['foo'] = 'foo';
container.extend('foo', (old, container) => '${old}bar');
container.extend('foo', (old, container) => '${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, container) {
obj['bar'] = 'baz';
return obj;
});
container.forgetInstance('foo');
container.forgetExtenders('foo');
container.bind('foo', (container) => 'foo');
expect(container.make('foo'), equals('foo'));
});
});
}