Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature(scoped-parent): implemented scoped container that inherit a parent container's registrations (final implementation). #101

Merged
merged 2 commits into from
Jan 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions kiwi/lib/src/kiwi_container.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,19 @@ typedef T Factory<T>(KiwiContainer container);
/// A simple service container.
class KiwiContainer {
/// Creates a scoped container.
KiwiContainer.scoped()
: _namedProviders = Map<String?, Map<Type, _Provider<Object>>>();
///
/// If [parent] is set, the new scoped instance will include its providers.
KiwiContainer.scoped({
KiwiContainer? parent,
}) : _namedProviders = <String?, Map<Type, _Provider<Object>>>{
if (parent != null)
...parent._namedProviders.map(
// [Map.from] is needed to create a copy of value and not use its reference,
// because if only value is passed, everything included in the parent will be
// added to the new instance at any time, even after this scoped instance has been created.
(key, value) => MapEntry(key, Map.from(value)),
),
};

static final KiwiContainer _instance = KiwiContainer.scoped();

Expand Down
158 changes: 151 additions & 7 deletions kiwi/test/kiwi_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ void main() {
});

test('instances should be resolveAs', () {
final sith = Sith('Anakin', 'Skywalker', 'DartVader');
final sith = Sith('Anakin', 'Skywalker', 'DarthVader');
container.registerSingleton<Character>((c) => sith);

expect(container.resolveAs<Character, Sith>(), sith);
Expand Down Expand Up @@ -95,19 +95,19 @@ void main() {
test('builders should be resolved', () {
container.registerSingleton((c) => 5);
container.registerFactory(
(c) => const Sith('Anakin', 'Skywalker', 'DartVader'));
(c) => const Sith('Anakin', 'Skywalker', 'DarthVader'));
container.registerFactory((c) => const Character('Anakin', 'Skywalker'));
container.registerFactory<Character>(
(c) => const Sith('Anakin', 'Skywalker', 'DartVader'),
(c) => const Sith('Anakin', 'Skywalker', 'DarthVader'),
name: 'named');

expect(container.resolve<int>(), 5);
expect(container.resolve<Sith>(),
const Sith('Anakin', 'Skywalker', 'DartVader'));
const Sith('Anakin', 'Skywalker', 'DarthVader'));
expect(container.resolve<Character>(),
const Character('Anakin', 'Skywalker'));
expect(container.resolve<Character>('named'),
const Sith('Anakin', 'Skywalker', 'DartVader'));
const Sith('Anakin', 'Skywalker', 'DarthVader'));
});

test('builders should always be created', () {
Expand All @@ -120,13 +120,13 @@ void main() {
test('one time builders should be resolved', () {
container.registerSingleton((c) => 5);
container.registerSingleton(
(c) => const Sith('Anakin', 'Skywalker', 'DartVader'));
(c) => const Sith('Anakin', 'Skywalker', 'DarthVader'));
container.registerSingleton<Character>(
(c) => const Character('Anakin', 'Skywalker'));

expect(container.resolve<int>(), 5);
expect(container.resolve<Sith>(),
const Sith('Anakin', 'Skywalker', 'DartVader'));
const Sith('Anakin', 'Skywalker', 'DarthVader'));
expect(container.resolve<Character>(),
const Character('Anakin', 'Skywalker'));
});
Expand Down Expand Up @@ -249,6 +249,150 @@ void main() {
'KiwiError:\n\n\nFailed to resolve `Character` as `Sith`:\n\nThe type `Character` as `Sith` was not registered for the name `named`\n\nMake sure `Sith` is added to your KiwiContainer and rerun build_runner build\n(If you are using the kiwi_generator)\n\nWhen using Flutter, most of the time a hot restart is required to setup the KiwiContainer again.\n\n\n',
)));
});

test('Parented [KiwiContainer.scoped] should inherit global registrations',
() {
container.registerInstance(5);
container.registerInstance(6, name: 'named');
container.registerInstance<num>(7);

final character = Character('Gabriel', 'Kiwilied');
container.registerFactory<Character>((c) => character);

final scoped = KiwiContainer.scoped(parent: container);

// The scoped instance and global container must be different.
expect(scoped, isNot(container));

expect(scoped.resolve<int>(), 5);
expect(scoped.resolve<int>('named'), 6);
expect(scoped.resolve<num>(), 7);
expect(scoped.resolve<Character>(), character);
});

test('Parented [KiwiContainer.scoped] should inherit registrations', () {
final firstScoped = KiwiContainer.scoped();

firstScoped.registerInstance<int>(5);
firstScoped.registerInstance<int>(6, name: 'named');
firstScoped.registerInstance<num>(7);

final character = Character('Gabriel', 'Kiwilied');
firstScoped.registerFactory<Character>((c) => character);

final secondScoped = KiwiContainer.scoped(parent: firstScoped);

// The scoped instances must be different.
expect(secondScoped, isNot(firstScoped));

expect(secondScoped.resolve<int>(), 5);
expect(secondScoped.resolve<int>('named'), 6);
expect(secondScoped.resolve<num>(), 7);
expect(secondScoped.resolve<Character>(), character);
});

test('Parented [KiwiContainer.scoped] should be impacted by parent', () {
final firstScoped = KiwiContainer.scoped();

firstScoped.registerInstance<int>(5);
firstScoped.registerInstance<int>(6, name: 'named');
firstScoped.registerInstance<num>(7);

final character = Character('Gabriel', 'Kiwilied');
firstScoped.registerFactory<Character>((c) => character);

final secondScoped = KiwiContainer.scoped(parent: firstScoped);

firstScoped.registerInstance<int>(26, name: 'exclusive_to_parent');
firstScoped.registerInstance<String>('random_string');

expect(firstScoped.resolve<int>(), 5);
expect(firstScoped.resolve<int>('named'), 6);
expect(firstScoped.resolve<num>(), 7);
expect(firstScoped.resolve<Character>(), character);
// The instances registered in [firstScoped] after creation of [secondScoped].
expect(firstScoped.resolve<int>('exclusive_to_parent'), 26);
expect(firstScoped.resolve<String>(), 'random_string');

expect(secondScoped.resolve<int>(), 5);
expect(secondScoped.resolve<int>('named'), 6);
expect(secondScoped.resolve<Character>(), character);

// The [secondScoped] must not have the [firstScoped] instances registered after [secondScoped] creation.
expect(
() => secondScoped.resolve<int>('exclusive_to_parent'),
throwsA(TypeMatcher<KiwiError>().having(
(f) => f.toString(),
'toString()',
'Not Registered KiwiError:\n\n\nFailed to resolve `int`:\n\nThe type `int` was not registered for the name `exclusive_to_parent`\n\nMake sure `int` is added to your KiwiContainer and rerun build_runner build\n(If you are using the kiwi_generator)\n\nWhen using Flutter, most of the time a hot restart is required to setup the KiwiContainer again.\n\n\n',
)));

expect(
() => secondScoped.resolve<String>(),
throwsA(TypeMatcher<KiwiError>().having(
(f) => f.toString(),
'toString()',
'Not Registered KiwiError:\n\n\nFailed to resolve `String`:\n\nThe type `String` was not registered\n\nMake sure `String` is added to your KiwiContainer and rerun build_runner build\n(If you are using the kiwi_generator)\n\nWhen using Flutter, most of the time a hot restart is required to setup the KiwiContainer again.\n\n\n',
)));
});

test('Parented [KiwiContainer.scoped] should not impact parent', () {
final firstScoped = KiwiContainer.scoped();

firstScoped.registerInstance<int>(5);
firstScoped.registerInstance<int>(6, name: 'named');
firstScoped.registerInstance<num>(7);

final character = Character('Gabriel', 'Kiwilied');
firstScoped.registerFactory<Character>((c) => character);

final secondScoped = KiwiContainer.scoped(parent: firstScoped);

secondScoped.registerInstance<int>(27, name: 'exclusive_to_scoped');
secondScoped.registerInstance<String>('random_string');

expect(firstScoped.resolve<int>(), 5);
expect(firstScoped.resolve<int>('named'), 6);
expect(firstScoped.resolve<num>(), 7);
expect(firstScoped.resolve<Character>(), character);

expect(secondScoped.resolve<int>(), 5);
expect(secondScoped.resolve<int>('named'), 6);
expect(secondScoped.resolve<num>(), 7);
expect(secondScoped.resolve<Character>(), character);
// The instances registered in [secondScoped] after your creation.
expect(secondScoped.resolve<int>('exclusive_to_scoped'), 27);
expect(secondScoped.resolve<String>(), 'random_string');

// The [firstScoped] must not have the [secondScoped] instances registered only in [secondScoped].
expect(
() => firstScoped.resolve<int>('exclusive_to_scoped'),
throwsA(TypeMatcher<KiwiError>().having(
(f) => f.toString(),
'toString()',
'Not Registered KiwiError:\n\n\nFailed to resolve `int`:\n\nThe type `int` was not registered for the name `exclusive_to_scoped`\n\nMake sure `int` is added to your KiwiContainer and rerun build_runner build\n(If you are using the kiwi_generator)\n\nWhen using Flutter, most of the time a hot restart is required to setup the KiwiContainer again.\n\n\n',
)));

expect(
() => firstScoped.resolve<String>(),
throwsA(TypeMatcher<KiwiError>().having(
(f) => f.toString(),
'toString()',
'Not Registered KiwiError:\n\n\nFailed to resolve `String`:\n\nThe type `String` was not registered\n\nMake sure `String` is added to your KiwiContainer and rerun build_runner build\n(If you are using the kiwi_generator)\n\nWhen using Flutter, most of the time a hot restart is required to setup the KiwiContainer again.\n\n\n',
)));
});

test('Unparented [KiwiContainer.scoped] should not be resolved', () {
final scoped = KiwiContainer.scoped(parent: container);

expect(
() => scoped.resolve<int>(),
throwsA(TypeMatcher<KiwiError>().having(
(f) => f.toString(),
'toString()',
'Not Registered KiwiError:\n\n\nFailed to resolve `int`:\n\nThe type `int` was not registered\n\nMake sure `int` is added to your KiwiContainer and rerun build_runner build\n(If you are using the kiwi_generator)\n\nWhen using Flutter, most of the time a hot restart is required to setup the KiwiContainer again.\n\n\n',
)));
});
});
}

Expand Down