diff --git a/packages/container/example/README.md b/packages/container/example/README.md new file mode 100644 index 0000000..936b06f --- /dev/null +++ b/packages/container/example/README.md @@ -0,0 +1,126 @@ +# Laravel Container Examples + +These examples demonstrate how to use the Laravel-style dependency injection container in Dart. + +## Basic Container Features (container_usage.dart) + +Shows core container features using a payment processing example: + +```dart +// Basic binding +container.bind((c) => StripeGateway(c.make())); + +// Singleton registration +container.singleton((c) => PaymentService(c.make())); + +// Contextual binding +container.when(PaymentService) + .needs() + .give(PayPalGateway()); + +// Tagged services +container.tag([StripeGateway, PayPalGateway], 'gateways'); +final gateways = container.tagged('gateways'); + +// Instance extending +container.extend(PaymentGateway, (gateway) { + print('Payment gateway was created'); +}); +``` + +Run with: +```bash +dart run container_usage.dart +``` + +## Advanced Container Features (advanced_usage.dart) + +Demonstrates advanced container capabilities: + +```dart +// Child containers +final rootContainer = IlluminateContainer(reflector); +rootContainer.singleton((c) => ProductionLogger()); + +final testContainer = rootContainer.createChild(); +testContainer.singleton((c) => TestLogger()); + +// Scoped bindings +container.scoped((c) => RequestScope()); +container.bind((c) => UserRepository( + c.make(), + c.make() +)); + +// Scope lifecycle +final repo1 = container.make(); +final repo2 = container.make(); +assert(identical(repo1.scope, repo2.scope)); // Same scope + +container.forgetScopedInstances(); // Clear scope + +final repo3 = container.make(); +assert(!identical(repo1.scope, repo3.scope)); // New scope +``` + +Run with: +```bash +dart run advanced_usage.dart +``` + +## Application Architecture (app_example.dart) + +Shows how to use the container in a real application: + +```dart +// Register repositories +container.singleton((c) => DatabaseUserRepository()); + +// Register services +container.singleton((c) => EmailService()); +container.singleton((c) => UserService( + c.make(), + c.make() +)); + +// Register controllers +container.singleton((c) => UserController( + c.make() +)); + +// Use the application +final controller = container.make(); +controller.createUser('John Doe', 'john@example.com'); +``` + +Run with: +```bash +dart run app_example.dart +``` + +## Key Features Demonstrated + +1. Dependency Registration + - Basic bindings + - Singleton registration + - Factory bindings + - Instance registration + - Scoped bindings + +2. Service Resolution + - Automatic dependency injection + - Contextual binding + - Tagged services + - Instance extending + +3. Container Features + - Child containers + - Scope management + - Service location + - Interface binding + +4. Architecture Patterns + - Repository pattern + - Service layer + - Dependency injection + - Interface segregation diff --git a/packages/container/example/advanced_usage.dart b/packages/container/example/advanced_usage.dart new file mode 100644 index 0000000..7219db5 --- /dev/null +++ b/packages/container/example/advanced_usage.dart @@ -0,0 +1,111 @@ +import 'package:platform_contracts/contracts.dart'; +import 'package:platform_container/platform_container.dart'; + +// Example services +abstract class Logger { + void log(String message); +} + +class ProductionLogger implements Logger { + @override + void log(String message) => print('PROD: $message'); +} + +class TestLogger implements Logger { + @override + void log(String message) => print('TEST: $message'); +} + +class RequestScope { + final String id = DateTime.now().toIso8601String(); + + @override + String toString() => 'RequestScope($id)'; +} + +class UserRepository { + final Logger logger; + final RequestScope scope; + + UserRepository(this.logger, this.scope); + + void save(String userId) { + logger.log('[${scope.id}] Saving user: $userId'); + } +} + +void main() { + // Create root container + final container = IlluminateContainer(ExampleReflector()); + + print('1. Child Containers'); + print('------------------'); + // Register production logger in root + container.singleton((c) => ProductionLogger()); + + // Create child container with test logger + final testContainer = container.createChild(); + testContainer.singleton((c) => TestLogger()); + + // Use both containers + container.make().log('Using production logger'); + testContainer.make().log('Using test logger'); + container.make().log('Still using production logger'); + + print('\n2. Scoped Bindings'); + print('-----------------'); + // Register scoped request + container.scoped((c) { + final scope = RequestScope(); + print('Created new scope: $scope'); + return scope; + }); + + // Register repository that uses scope + container.bind( + (c) => UserRepository(c.make(), c.make())); + + print('\nFirst request:'); + // Same scope within request + final repo1 = container.make(); + final repo2 = container.make(); + repo1.save('user1'); + repo2.save('user2'); + + // Verify same scope + print('Using same scope: ${identical(repo1.scope, repo2.scope)}'); + + print('\nNew request:'); + // Clear scope + container.forgetScopedInstances(); + + // New scope for new request + final repo3 = container.make(); + repo3.save('user3'); + + // Verify different scope + print('Using different scope: ${!identical(repo1.scope, repo3.scope)}'); +} + +/// Example reflector implementation +class ExampleReflector implements ReflectorContract { + @override + String? getName(Symbol symbol) => null; + + @override + ReflectedClassContract? reflectClass(Type clazz) => null; + + @override + ReflectedFunctionContract? reflectFunction(Function function) => null; + + @override + ReflectedInstanceContract? reflectInstance(Object object) => null; + + @override + ReflectedTypeContract reflectFutureOf(Type type) { + throw UnsupportedError('Future reflection not needed for example'); + } + + @override + ReflectedTypeContract? reflectType(Type type) => null; +} diff --git a/packages/container/example/app_example.dart b/packages/container/example/app_example.dart new file mode 100644 index 0000000..0fa5ea1 --- /dev/null +++ b/packages/container/example/app_example.dart @@ -0,0 +1,134 @@ +import 'package:platform_contracts/contracts.dart'; +import 'package:platform_container/platform_container.dart'; + +// Domain Models +class User { + final String id; + final String name; + final String email; + + User(this.id, this.name, this.email); +} + +// Repository Layer +abstract class UserRepository { + User? findById(String id); + void save(User user); +} + +class DatabaseUserRepository implements UserRepository { + final Map _users = {}; + + @override + User? findById(String id) => _users[id]; + + @override + void save(User user) { + print('DB: Saving user ${user.id}'); + _users[user.id] = user; + } +} + +// Service Layer +class UserService { + final UserRepository repository; + final EmailService emailService; + + UserService(this.repository, this.emailService); + + void registerUser(String name, String email) { + // Create user + final user = + User(DateTime.now().millisecondsSinceEpoch.toString(), name, email); + + // Save user + repository.save(user); + + // Send welcome email + emailService.sendWelcomeEmail(user); + } + + User? getUser(String id) => repository.findById(id); +} + +class EmailService { + void sendWelcomeEmail(User user) { + print('Sending welcome email to ${user.email}'); + } +} + +// Controller Layer +class UserController { + final UserService userService; + + UserController(this.userService); + + void createUser(String name, String email) { + try { + userService.registerUser(name, email); + print('User created successfully'); + } catch (e) { + print('Error creating user: $e'); + } + } + + void getUser(String id) { + final user = userService.getUser(id); + if (user != null) { + print('Found user: ${user.name} (${user.email})'); + } else { + print('User not found'); + } + } +} + +void main() { + // Create container + final container = IlluminateContainer(ExampleReflector()); + + // Register repositories + container.singleton((c) => DatabaseUserRepository()); + + // Register services + container.singleton((c) => EmailService()); + container.singleton( + (c) => UserService(c.make(), c.make())); + + // Register controllers + container + .singleton((c) => UserController(c.make())); + + // Use the application + final controller = container.make(); + + // Create a user + print('Creating user...'); + controller.createUser('John Doe', 'john@example.com'); + + // Try to find the user + print('\nLooking up user...'); + controller.getUser('123'); // Not found +} + +/// Example reflector implementation +class ExampleReflector implements ReflectorContract { + @override + String? getName(Symbol symbol) => null; + + @override + ReflectedClassContract? reflectClass(Type clazz) => null; + + @override + ReflectedFunctionContract? reflectFunction(Function function) => null; + + @override + ReflectedInstanceContract? reflectInstance(Object object) => null; + + @override + ReflectedTypeContract reflectFutureOf(Type type) { + throw UnsupportedError('Future reflection not needed for example'); + } + + @override + ReflectedTypeContract? reflectType(Type type) => null; +} diff --git a/packages/container/example/container_usage.dart b/packages/container/example/container_usage.dart new file mode 100644 index 0000000..2448c4c --- /dev/null +++ b/packages/container/example/container_usage.dart @@ -0,0 +1,120 @@ +import 'package:platform_contracts/contracts.dart'; +import 'package:platform_container/platform_container.dart'; + +// Example services +abstract class PaymentGateway { + void processPayment(double amount); +} + +class StripeGateway implements PaymentGateway { + final String apiKey; + + StripeGateway(this.apiKey); + + @override + void processPayment(double amount) { + print('Stripe: Processing \$$amount'); + } +} + +class PayPalGateway implements PaymentGateway { + @override + void processPayment(double amount) { + print('PayPal: Processing \$$amount'); + } +} + +class PaymentService { + final PaymentGateway gateway; + + PaymentService(this.gateway); + + void pay(double amount) { + print('Payment requested'); + gateway.processPayment(amount); + } +} + +void main() { + // Create container + final container = IlluminateContainer(ExampleReflector()); + + print('1. Basic Binding'); + // Register API key + container.instance('stripe-key-123'); + + // Register payment gateway + container.bind((c) => StripeGateway(c.make())); + + // Use the gateway + final gateway = container.make(); + gateway.processPayment(99.99); + + print('\n2. Singleton'); + // Register payment service as singleton + container.singleton( + (c) => PaymentService(c.make())); + + // Get same instance twice + final service1 = container.make(); + final service2 = container.make(); + print('Same instance: ${identical(service1, service2)}'); + + print('\n3. Contextual Binding'); + // Use PayPal for specific service + container.when(PaymentService).needs().give(PayPalGateway()); + + // New service uses PayPal + final paypalService = container.make(); + paypalService.pay(49.99); + + print('\n4. Tagged Services'); + // Register concrete implementations + container.bind((c) => StripeGateway(c.make())); + container.bind((c) => PayPalGateway()); + + // Tag payment gateways + container.tag([StripeGateway, PayPalGateway], 'gateways'); + + // Get all gateways + final gateways = container.tagged('gateways'); + print('Found ${gateways.length} payment gateways'); + + // Use each gateway + for (final gateway in gateways) { + gateway.processPayment(29.99); + } + + print('\n5. Extending Instances'); + // Add logging to payment gateway + container.extend(PaymentGateway, (gateway) { + print('Payment gateway was created'); + }); + + // Extension runs when making instance + final extendedGateway = container.make(); + extendedGateway.processPayment(199.99); +} + +/// Example reflector implementation +class ExampleReflector implements ReflectorContract { + @override + String? getName(Symbol symbol) => null; + + @override + ReflectedClassContract? reflectClass(Type clazz) => null; + + @override + ReflectedFunctionContract? reflectFunction(Function function) => null; + + @override + ReflectedInstanceContract? reflectInstance(Object object) => null; + + @override + ReflectedTypeContract reflectFutureOf(Type type) { + throw UnsupportedError('Future reflection not needed for example'); + } + + @override + ReflectedTypeContract? reflectType(Type type) => null; +} diff --git a/packages/container/lib/platform_container.dart b/packages/container/lib/platform_container.dart new file mode 100644 index 0000000..fd9a336 --- /dev/null +++ b/packages/container/lib/platform_container.dart @@ -0,0 +1,3 @@ +library platform_container; + +export 'src/container.dart'; diff --git a/packages/container/lib/src/container.dart b/packages/container/lib/src/container.dart new file mode 100644 index 0000000..31448ee --- /dev/null +++ b/packages/container/lib/src/container.dart @@ -0,0 +1,461 @@ +import 'package:platform_contracts/contracts.dart'; + +/// Laravel-style container implementation. +class IlluminateContainer implements ContainerContract { + final ReflectorContract _reflector; + final Map _bindings = {}; + final Map _instances = {}; + final Map _namedInstances = {}; + final Map> _tags = {}; + final Map> _contextualBindings = {}; + final Map _methodBindings = {}; + final Map _aliases = {}; + final Map> _extenders = {}; + final Map> _reboundCallbacks = {}; + final Map _scopedInstances = {}; + final IlluminateContainer? _parent; + + final List _beforeResolvingCallbacks = []; + final List _resolvingCallbacks = []; + final List _afterResolvingCallbacks = []; + + IlluminateContainer(this._reflector) : _parent = null; + + IlluminateContainer._child(this._parent, this._reflector); + + @override + ReflectorContract get reflector => _reflector; + + @override + bool get isRoot => _parent == null; + + @override + ContainerContract createChild() { + return IlluminateContainer._child(this, _reflector); + } + + @override + bool bound(String abstract) { + return has(Type); + } + + @override + void bind(T Function(ContainerContract) concrete, {bool shared = false}) { + if (shared) { + singleton(concrete); + } else { + registerFactory(concrete); + } + } + + @override + void bindIf(T Function(ContainerContract) concrete, + {bool shared = false}) { + if (!has()) { + bind(concrete, shared: shared); + } + } + + @override + void singleton(T Function(ContainerContract) concrete) { + registerLazySingleton(concrete); + } + + @override + void singletonIf(T Function(ContainerContract) concrete) { + if (!has()) { + singleton(concrete); + } + } + + @override + void scoped(T Function(ContainerContract) concrete) { + final type = T; + T Function(ContainerContract) wrapper = (container) { + if (!_scopedInstances.containsKey(type)) { + _scopedInstances[type] = concrete(container); + } + return _scopedInstances[type] as T; + }; + _bindings[type] = wrapper; + } + + @override + void scopedIf(T Function(ContainerContract) concrete) { + if (!has()) { + scoped(concrete); + } + } + + @override + T instance(T instance) { + return registerSingleton(instance); + } + + @override + void bindMethod(String method, Function callback) { + _methodBindings[method] = callback; + } + + @override + dynamic call(Function callback, [List parameters = const []]) { + final reflected = reflector.reflectFunction(callback); + if (reflected == null) { + Function.apply(callback, parameters); + return null; + } + + final injectedParams = reflected.parameters.map((param) { + if (parameters.isNotEmpty) { + return parameters.removeAt(0); + } + return make(param.type.reflectedType); + }).toList(); + + final result = Function.apply(callback, injectedParams); + return result ?? null; + } + + @override + Function wrap(Function callback, [List parameters = const []]) { + return () => call(callback, parameters); + } + + @override + ContextualBindingBuilder when(Type concrete) { + return _ContextualBindingBuilder(this, concrete); + } + + @override + void addContextualBinding( + Type concrete, Type abstract, dynamic implementation) { + _contextualBindings.putIfAbsent(concrete, () => {})[abstract] = + implementation; + } + + @override + void beforeResolving( + void Function(ContainerContract, T instance) callback) { + _beforeResolvingCallbacks.add(callback); + } + + @override + void resolving(void Function(ContainerContract, T instance) callback) { + _resolvingCallbacks.add(callback); + } + + @override + void afterResolving( + void Function(ContainerContract, T instance) callback) { + _afterResolvingCallbacks.add(callback); + } + + @override + bool has([Type? t]) { + final type = t ?? T; + return _bindings.containsKey(type) || + _instances.containsKey(type) || + (_parent?.has(t) ?? false); + } + + @override + bool hasNamed(String name) { + return _namedInstances.containsKey(name) || + (_parent?.hasNamed(name) ?? false); + } + + @override + T make([Type? type]) { + final resolveType = type ?? T; + final concrete = _aliases[resolveType] ?? resolveType; + + // Fire before resolving callbacks + _fireBeforeResolvingCallbacks(null); + + // Check for singleton instance + if (_instances.containsKey(concrete)) { + final instance = _instances[concrete]; + // Apply extenders to singleton instance + final extenders = getExtenders(concrete); + for (var extender in extenders) { + extender(instance); + } + _fireResolvingCallbacks(instance as T); + return instance as T; + } + + // Check for contextual binding + if (_contextualBindings.containsKey(concrete)) { + final bindings = _contextualBindings[concrete]!; + if (bindings.containsKey(T)) { + final binding = bindings[T]; + if (binding is Function) { + final instance = binding(this) as T; + // Apply extenders to contextual binding instance + final extenders = getExtenders(concrete); + for (var extender in extenders) { + extender(instance); + } + _fireResolvingCallbacks(instance); + return instance; + } + _fireResolvingCallbacks(binding as T); + return binding as T; + } + } + + // Check for binding + if (_bindings.containsKey(concrete)) { + final instance = _bindings[concrete]!(this); + // Apply extenders to factory instance + final extenders = getExtenders(concrete); + for (var extender in extenders) { + extender(instance); + } + _fireResolvingCallbacks(instance as T); + return instance as T; + } + + // Check parent container + if (_parent != null && _parent!.has(type)) { + return _parent!.make(type); + } + + // Try to create instance via reflection + final reflected = reflector.reflectClass(concrete); + if (reflected == null) { + throw StateError('Could not reflect type $concrete'); + } + + try { + final instance = reflected.newInstance('', []).reflectee as T; + // Apply extenders to reflected instance + final extenders = getExtenders(concrete); + for (var extender in extenders) { + extender(instance); + } + _fireResolvingCallbacks(instance); + return instance; + } catch (e) { + throw StateError( + 'Failed to create instance of $concrete: ${e.toString()}'); + } + } + + void _fireBeforeResolvingCallbacks(T? instance) { + for (var callback in _beforeResolvingCallbacks) { + callback(this, instance); + } + } + + void _fireResolvingCallbacks(T instance) { + for (var callback in _resolvingCallbacks) { + callback(this, instance); + } + for (var callback in _afterResolvingCallbacks) { + callback(this, instance); + } + } + + @override + Future makeAsync([Type? type]) async { + try { + final instance = make(type); + if (instance is Future) { + return instance; + } + return Future.value(instance); + } catch (e) { + final futureType = reflector.reflectFutureOf(type ?? T); + final instance = futureType.newInstance('', []); + if (instance.reflectee is Future) { + return instance.reflectee as Future; + } + throw StateError('Failed to create Future<$T>: Invalid instance type'); + } + } + + @override + T registerSingleton(T object, {Type? as}) { + final type = as ?? T; + if (_instances.containsKey(type)) { + throw StateError('Singleton already registered for type $type'); + } + _instances[type] = object; + return object; + } + + @override + T Function(ContainerContract) registerFactory( + T Function(ContainerContract) factory, + {Type? as}) { + final type = as ?? T; + _bindings[type] = factory; + return factory; + } + + @override + T Function(ContainerContract) registerLazySingleton( + T Function(ContainerContract) factory, + {Type? as}) { + final type = as ?? T; + T Function(ContainerContract) wrapper = (container) { + if (!_instances.containsKey(type)) { + _instances[type] = factory(container); + } + return _instances[type] as T; + }; + _bindings[type] = wrapper; + return wrapper; + } + + @override + T findByName(String name) { + if (_namedInstances.containsKey(name)) { + return _namedInstances[name] as T; + } + if (_parent != null) { + return _parent!.findByName(name); + } + throw StateError('No singleton registered with name "$name"'); + } + + @override + T registerNamedSingleton(String name, T object) { + if (_namedInstances.containsKey(name)) { + throw StateError('Singleton already registered with name "$name"'); + } + _namedInstances[name] = object; + return object; + } + + @override + void tag(List abstracts, String tag) { + _tags.putIfAbsent(tag, () => []).addAll(abstracts); + } + + @override + List tagged(String tag) { + final types = _tags[tag] ?? []; + if (types.isEmpty) { + throw StateError('No types registered for tag "$tag"'); + } + + final instances = []; + for (final type in types) { + if (_bindings.containsKey(type)) { + final instance = _bindings[type]!(this) as T; + instances.add(instance); + } else if (_parent != null && _parent!.has(type)) { + final instance = _parent!.make(type) as T; + instances.add(instance); + } else { + throw StateError('Could not resolve type $type'); + } + } + return instances; + } + + @override + void alias(Type abstract, Type alias) { + if (abstract == alias) { + throw StateError('$abstract is aliased to itself.'); + } + _aliases[alias] = abstract; + } + + @override + bool isAlias(String name) { + return _aliases.keys.any((type) => type.toString() == name); + } + + @override + Type getAlias(Type abstract) { + return _aliases[abstract] ?? abstract; + } + + @override + void extend(Type abstract, Function(dynamic instance) extension) { + _extenders.putIfAbsent(abstract, () => []).add(extension); + } + + @override + List getExtenders(Type abstract) { + return _extenders[abstract] ?? []; + } + + @override + void forgetExtenders(Type abstract) { + _extenders.remove(abstract); + } + + @override + void forgetInstance(Type abstract) { + _instances.remove(abstract); + } + + @override + void forgetInstances() { + _instances.clear(); + } + + @override + void forgetScopedInstances() { + _scopedInstances.clear(); + } + + @override + void flush() { + _bindings.clear(); + _instances.clear(); + _namedInstances.clear(); + _tags.clear(); + _contextualBindings.clear(); + _methodBindings.clear(); + _aliases.clear(); + _extenders.clear(); + _reboundCallbacks.clear(); + _scopedInstances.clear(); + _beforeResolvingCallbacks.clear(); + _resolvingCallbacks.clear(); + _afterResolvingCallbacks.clear(); + } + + @override + void rebinding(Type abstract, Function(ContainerContract, dynamic) callback) { + _reboundCallbacks.putIfAbsent(abstract, () => []).add(callback); + } + + @override + void refresh(Type abstract, dynamic target, String method) { + final instance = make(abstract); + + for (var callback in _reboundCallbacks[abstract] ?? []) { + callback(this, instance); + } + + Function.apply(target[method], [instance]); + } +} + +class _ContextualBindingBuilder implements ContextualBindingBuilder { + final IlluminateContainer _container; + final Type _concrete; + Type? _needsType; + + _ContextualBindingBuilder(this._container, this._concrete); + + @override + ContextualBindingBuilder needs() { + _needsType = T; + return this; + } + + @override + void give(dynamic implementation) { + if (_needsType == null) { + throw StateError('Must call needs() before give()'); + } + _container.addContextualBinding(_concrete, _needsType!, implementation); + } +} diff --git a/packages/container/pubspec.lock b/packages/container/pubspec.lock new file mode 100644 index 0000000..b6928fc --- /dev/null +++ b/packages/container/pubspec.lock @@ -0,0 +1,561 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: "45cfa8471b89fb6643fe9bf51bd7931a76b8f5ec2d65de4fb176dba8d4f22c77" + url: "https://pub.dev" + source: hosted + version: "73.0.0" + _macros: + dependency: transitive + description: dart + source: sdk + version: "0.3.2" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "4959fec185fe70cce007c57e9ab6983101dbe593d2bf8bbfb4453aaec0cf470a" + url: "https://pub.dev" + source: hosted + version: "6.8.0" + args: + dependency: transitive + description: + name: args + sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 + url: "https://pub.dev" + source: hosted + version: "2.6.0" + async: + dependency: transitive + description: + name: async + sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 + url: "https://pub.dev" + source: hosted + version: "2.12.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + build: + dependency: transitive + description: + name: build + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + build_config: + dependency: transitive + description: + name: build_config + sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + url: "https://pub.dev" + source: hosted + version: "1.1.1" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d" + url: "https://pub.dev" + source: hosted + version: "2.4.13" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0 + url: "https://pub.dev" + source: hosted + version: "7.3.2" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb + url: "https://pub.dev" + source: hosted + version: "8.9.2" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + url: "https://pub.dev" + source: hosted + version: "2.0.3" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" + url: "https://pub.dev" + source: hosted + version: "4.10.1" + collection: + dependency: "direct main" + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + coverage: + dependency: transitive + description: + name: coverage + sha256: "4b03e11f6d5b8f6e5bb5e9f7889a56fe6c5cbe942da5378ea4d4d7f73ef9dfe5" + url: "https://pub.dev" + source: hosted + version: "1.11.0" + crypto: + dependency: transitive + description: + name: crypto + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab" + url: "https://pub.dev" + source: hosted + version: "2.3.7" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + graphs: + dependency: transitive + description: + name: graphs + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "76d306a1c3afb33fe82e2bbacad62a61f409b5634c915fceb0d799de1a913360" + url: "https://pub.dev" + source: hosted + version: "4.1.1" + io: + dependency: transitive + description: + name: io + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + js: + dependency: transitive + description: + name: js + sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf + url: "https://pub.dev" + source: hosted + version: "0.7.1" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" + lints: + dependency: "direct dev" + description: + name: lints + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + macros: + dependency: transitive + description: + name: macros + sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" + url: "https://pub.dev" + source: hosted + version: "0.1.2-main.4" + matcher: + dependency: transitive + description: + name: matcher + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + url: "https://pub.dev" + source: hosted + version: "0.12.16+1" + meta: + dependency: "direct main" + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" + source: hosted + version: "1.16.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + mockito: + dependency: "direct dev" + description: + name: mockito + sha256: "6841eed20a7befac0ce07df8116c8b8233ed1f4486a7647c7fc5a02ae6163917" + url: "https://pub.dev" + source: hosted + version: "5.4.4" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + package_config: + dependency: transitive + description: + name: package_config + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + platform_contracts: + dependency: "direct main" + description: + path: "../contracts" + relative: true + source: path + version: "1.0.0" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.dev" + source: hosted + version: "1.4.2" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 + url: "https://pub.dev" + source: hosted + version: "1.1.3" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b + url: "https://pub.dev" + source: hosted + version: "2.1.2" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703" + url: "https://pub.dev" + source: hosted + version: "0.10.12" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" + url: "https://pub.dev" + source: hosted + version: "1.12.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "0bd04f5bb74fcd6ff0606a888a30e917af9bd52820b178eaa464beb11dca84b6" + url: "https://pub.dev" + source: hosted + version: "1.4.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test: + dependency: "direct dev" + description: + name: test + sha256: "713a8789d62f3233c46b4a90b174737b2c04cb6ae4500f2aa8b1be8f03f5e67f" + url: "https://pub.dev" + source: hosted + version: "1.25.8" + test_api: + dependency: transitive + description: + name: test_api + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" + url: "https://pub.dev" + source: hosted + version: "0.7.3" + test_core: + dependency: transitive + description: + name: test_core + sha256: "12391302411737c176b0b5d6491f466b0dd56d4763e347b6714efbaa74d7953d" + url: "https://pub.dev" + source: hosted + version: "0.6.5" + timing: + dependency: transitive + description: + name: timing + sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" + url: "https://pub.dev" + source: hosted + version: "14.3.1" + watcher: + dependency: transitive + description: + name: watcher + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb + url: "https://pub.dev" + source: hosted + version: "1.1.0" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" + url: "https://pub.dev" + source: hosted + version: "0.1.6" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + yaml: + dependency: transitive + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" + source: hosted + version: "3.1.2" +sdks: + dart: ">=3.5.0 <4.0.0" diff --git a/packages/container/pubspec.yaml b/packages/container/pubspec.yaml new file mode 100644 index 0000000..7caa2c6 --- /dev/null +++ b/packages/container/pubspec.yaml @@ -0,0 +1,19 @@ +name: platform_container +description: Laravel-style container implementation for the Protevus Platform +version: 0.1.0 +publish_to: none + +environment: + sdk: '>=3.0.0 <4.0.0' + +dependencies: + platform_contracts: + path: ../contracts + meta: ^1.9.0 + collection: ^1.17.0 + +dev_dependencies: + test: ^1.24.0 + mockito: ^5.4.0 + build_runner: ^2.4.0 + lints: ^2.1.0 diff --git a/packages/container/test/Container/container_test.dart b/packages/container/test/Container/container_test.dart new file mode 100644 index 0000000..e405cd2 --- /dev/null +++ b/packages/container/test/Container/container_test.dart @@ -0,0 +1,257 @@ +import 'package:platform_contracts/contracts.dart'; +import 'package:platform_container/platform_container.dart'; +import 'package:test/test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:mockito/annotations.dart'; + +import '../laravel_container_test.mocks.dart'; +import '../stubs.dart'; + +@GenerateMocks([ + ReflectorContract, + ReflectedClassContract, + ReflectedInstanceContract, + ReflectedFunctionContract, + ReflectedParameterContract, + ReflectedTypeContract +]) +void main() { + late IlluminateContainer container; + late MockReflectorContract reflector; + late MockReflectedClassContract reflectedClass; + late MockReflectedInstanceContract reflectedInstance; + late MockReflectedFunctionContract reflectedFunction; + late MockReflectedParameterContract reflectedParameter; + late MockReflectedTypeContract reflectedType; + + setUp(() { + reflector = MockReflectorContract(); + reflectedClass = MockReflectedClassContract(); + reflectedInstance = MockReflectedInstanceContract(); + reflectedFunction = MockReflectedFunctionContract(); + reflectedParameter = MockReflectedParameterContract(); + reflectedType = MockReflectedTypeContract(); + container = IlluminateContainer(reflector); + + // Setup default reflection behavior + when(reflector.reflectClass(any)).thenReturn(reflectedClass); + when(reflectedClass.newInstance('', [])).thenReturn(reflectedInstance); + }); + + group('Container Basic Resolution', () { + test('testClosureResolution', () { + container.bind((c) => 'Taylor'); + expect(container.make(), equals('Taylor')); + }); + + test('testBindIfDoesntRegisterIfServiceAlreadyRegistered', () { + container.bind((c) => 'Taylor'); + container.bindIf((c) => 'Dayle'); + expect(container.make(), equals('Taylor')); + }); + + test('testBindIfDoesRegisterIfServiceNotRegisteredYet', () { + container.bindIf((c) => 'Dayle'); + expect(container.make(), equals('Dayle')); + }); + + test('testAutoConcreteResolution', () { + final instance = ContainerConcreteStub(); + when(reflectedInstance.reflectee).thenReturn(instance); + expect(container.make(), + isA()); + }); + }); + + group('Container Singleton', () { + test('testSharedClosureResolution', () { + container + .singleton((c) => ContainerConcreteStub()); + final first = container.make(); + final second = container.make(); + expect(identical(first, second), isTrue); + }); + + test('testSingletonIfDoesntRegisterIfBindingAlreadyRegistered', () { + container + .singleton((c) => ContainerConcreteStub()); + final first = container.make(); + container + .singletonIf((c) => ContainerConcreteStub()); + final second = container.make(); + expect(identical(first, second), isTrue); + }); + + test('testSingletonIfDoesRegisterIfBindingNotRegisteredYet', () { + container + .singletonIf((c) => ContainerConcreteStub()); + final first = container.make(); + final second = container.make(); + expect(identical(first, second), isTrue); + }); + + test('testBindingAnInstanceAsShared', () { + final instance = ContainerConcreteStub(); + container.instance(instance); + expect( + identical(container.make(), instance), isTrue); + }); + }); + + group('Container Scoped', () { + test('testScopedClosureResolution', () { + container.scoped((c) => ContainerConcreteStub()); + final first = container.make(); + final second = container.make(); + expect(identical(first, second), isTrue); + + container.forgetScopedInstances(); + final third = container.make(); + expect(identical(second, third), isFalse); + }); + + test('testScopedIfDoesntRegisterIfBindingAlreadyRegistered', () { + container.scoped((c) => ContainerConcreteStub()); + final first = container.make(); + container.scopedIf((c) => ContainerConcreteStub()); + final second = container.make(); + expect(identical(first, second), isTrue); + }); + + test('testScopedIfDoesRegisterIfBindingNotRegisteredYet', () { + container.scopedIf((c) => ContainerConcreteStub()); + final first = container.make(); + final second = container.make(); + expect(identical(first, second), isTrue); + }); + }); + + group('Container Dependencies', () { + test('testAbstractToConcreteResolution', () { + final impl = ContainerImplementationStub(); + final dependent = ContainerDependentStub(impl); + when(reflectedInstance.reflectee).thenReturn(dependent); + + container + .bind((c) => ContainerImplementationStub()); + final instance = container.make(); + expect(instance.impl, isA()); + }); + + test('testNestedDependencyResolution', () { + final impl = ContainerImplementationStub(); + final dependent = ContainerDependentStub(impl); + final nested = ContainerNestedDependentStub(dependent); + when(reflectedInstance.reflectee).thenReturn(nested); + + container + .bind((c) => ContainerImplementationStub()); + final instance = container.make(); + expect(instance.inner, isA()); + expect(instance.inner.impl, isA()); + }); + + test('testContainerIsPassedToResolvers', () { + container.bind((c) => c); + final resolved = container.make(); + expect(identical(resolved, container), isTrue); + }); + }); + + group('Container Aliases', () { + test('testAliases', () { + final impl = ContainerImplementationStub('bar'); + when(reflectedInstance.reflectee).thenReturn(impl); + + container.bind((c) => impl); + container.alias(IContainerContractStub, ContainerImplementationStub); + + expect( + container.make().getValue(), equals('bar')); + expect(container.make().getValue(), + equals('bar')); + expect(container.isAlias('ContainerImplementationStub'), isTrue); + }); + + test('testAliasesWithArrayOfParameters', () { + final impl = ContainerImplementationStub('foo'); + when(reflectedInstance.reflectee).thenReturn(impl); + + container.bind((c) => impl); + container.alias(IContainerContractStub, ContainerImplementationStub); + + expect( + container.make().getValue(), equals('foo')); + expect(container.make().getValue(), + equals('foo')); + }); + + test('testGetAliasRecursive', () { + final impl = ContainerImplementationStub('foo'); + when(reflectedInstance.reflectee).thenReturn(impl); + + // Bind the implementation to the interface + container.bind((c) => impl); + + // Create alias chain in reverse order + container.alias(ContainerImplementationStub, StubAlias); + container.alias(IContainerContractStub, ContainerImplementationStub); + + // Verify immediate aliases + expect( + container.getAlias(StubAlias), equals(ContainerImplementationStub)); + expect(container.getAlias(ContainerImplementationStub), + equals(IContainerContractStub)); + + // Verify instance resolution through alias chain + final instance = container.make(); + expect(instance.getValue(), equals('foo')); + }); + }); + + group('Container State', () { + test('testBindingsCanBeOverridden', () { + container.bind((c) => 'bar'); + container.bind((c) => 'baz'); + expect(container.make(), equals('baz')); + }); + + test('testContainerFlushFlushesAllBindingsAliasesAndResolvedInstances', () { + container + .bind((c) => ContainerImplementationStub()); + container.bind((c) => ContainerConcreteStub()); + container.tag([ContainerConcreteStub], 'services'); + container.alias(IContainerContractStub, ContainerImplementationStub); + + expect(container.has(), isTrue); + expect(container.has(), isTrue); + expect(container.isAlias('ContainerImplementationStub'), isTrue); + + container.flush(); + + expect(container.has(), isFalse); + expect(container.has(), isFalse); + expect(container.isAlias('ContainerImplementationStub'), isFalse); + }); + + test('testForgetInstanceForgetsInstance', () { + final instance = ContainerConcreteStub(); + container.instance(instance); + expect(container.has(), isTrue); + container.forgetInstance(ContainerConcreteStub); + expect(container.has(), isFalse); + }); + + test('testForgetInstancesForgetsAllInstances', () { + final instance1 = ContainerConcreteStub(); + final instance2 = ContainerImplementationStub(); + container.instance(instance1); + container.instance(instance2); + expect(container.has(), isTrue); + expect(container.has(), isTrue); + container.forgetInstances(); + expect(container.has(), isFalse); + expect(container.has(), isFalse); + }); + }); +} diff --git a/packages/container/test/container_test.dart b/packages/container/test/container_test.dart new file mode 100644 index 0000000..6d42b23 --- /dev/null +++ b/packages/container/test/container_test.dart @@ -0,0 +1,126 @@ +import 'package:platform_contracts/contracts.dart'; +import 'package:platform_container/platform_container.dart'; +import 'package:test/test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:mockito/annotations.dart'; + +import 'container_test.mocks.dart'; + +@GenerateMocks( + [ReflectorContract, ReflectedTypeContract, ReflectedInstanceContract]) +void main() { + late IlluminateContainer container; + late MockReflectorContract reflector; + late MockReflectedTypeContract reflectedType; + late MockReflectedInstanceContract reflectedInstance; + + setUp(() { + reflector = MockReflectorContract(); + reflectedType = MockReflectedTypeContract(); + reflectedInstance = MockReflectedInstanceContract(); + container = IlluminateContainer(reflector); + + // Setup default reflection behavior + when(reflector.reflectClass(any)).thenReturn(null); + }); + + group('Container', () { + test('isRoot returns true for root container', () { + expect(container.isRoot, isTrue); + }); + + test('isRoot returns false for child container', () { + final child = container.createChild(); + expect(child.isRoot, isFalse); + }); + + test('has returns true for registered singleton', () { + container.registerSingleton('test'); + expect(container.has(), isTrue); + }); + + test('has returns true for registered factory', () { + container.registerFactory((c) => 'test'); + expect(container.has(), isTrue); + }); + + test('has returns true for registered lazy singleton', () { + container.registerLazySingleton((c) => 'test'); + expect(container.has(), isTrue); + }); + + test('hasNamed returns true for registered named singleton', () { + container.registerNamedSingleton('test.name', 'test'); + expect(container.hasNamed('test.name'), isTrue); + }); + + test('make returns singleton instance', () { + container.registerSingleton('test'); + expect(container.make(), equals('test')); + }); + + test('make creates new instance for factory', () { + var count = 0; + container.registerFactory((c) => 'test${count++}'); + expect(container.make(), equals('test0')); + expect(container.make(), equals('test1')); + }); + + test('make returns same instance for lazy singleton', () { + var count = 0; + container.registerLazySingleton((c) => 'test${count++}'); + expect(container.make(), equals('test0')); + expect(container.make(), equals('test0')); + }); + + test('makeAsync returns Future for async dependency', () async { + // Setup mock behavior + when(reflector.reflectFutureOf(String)).thenReturn(reflectedType); + when(reflectedType.newInstance('', [])).thenReturn(reflectedInstance); + when(reflectedInstance.reflectee).thenAnswer((_) => Future.value('test')); + + final result = await container.makeAsync(); + expect(result, equals('test')); + }); + + test('findByName returns named singleton', () { + container.registerNamedSingleton('test.name', 'test'); + expect(container.findByName('test.name'), equals('test')); + }); + + test('child container inherits parent bindings', () { + container.registerSingleton('test'); + final child = container.createChild(); + expect(child.make(), equals('test')); + }); + + test('child container can override parent bindings', () { + container.registerSingleton('parent'); + final child = container.createChild() as IlluminateContainer; + child.registerSingleton('child'); + expect(child.make(), equals('child')); + expect(container.make(), equals('parent')); + }); + + test('throws when making unregistered type without reflection', () { + expect(() => container.make(), throwsStateError); + }); + + test('throws when making named singleton that does not exist', () { + expect(() => container.findByName('missing'), throwsStateError); + }); + + test('throws when registering duplicate singleton', () { + container.registerSingleton('test'); + expect( + () => container.registerSingleton('test2'), throwsStateError); + }); + + test('throws when registering duplicate named singleton', () { + container.registerNamedSingleton('test.name', 'test'); + expect( + () => container.registerNamedSingleton('test.name', 'test2'), + throwsStateError); + }); + }); +} diff --git a/packages/container/test/container_test.mocks.dart b/packages/container/test/container_test.mocks.dart new file mode 100644 index 0000000..adf5ea8 --- /dev/null +++ b/packages/container/test/container_test.mocks.dart @@ -0,0 +1,241 @@ +// Mocks generated by Mockito 5.4.4 from annotations +// in platform_illuminate_container/test/container_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'package:mockito/mockito.dart' as _i1; +import 'package:mockito/src/dummies.dart' as _i3; +import 'package:platform_contracts/src/reflection/reflector_contract.dart' + as _i2; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeReflectedTypeContract_0 extends _i1.SmartFake + implements _i2.ReflectedTypeContract { + _FakeReflectedTypeContract_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeType_1 extends _i1.SmartFake implements Type { + _FakeType_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeReflectedInstanceContract_2 extends _i1.SmartFake + implements _i2.ReflectedInstanceContract { + _FakeReflectedInstanceContract_2( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeReflectedClassContract_3 extends _i1.SmartFake + implements _i2.ReflectedClassContract { + _FakeReflectedClassContract_3( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [ReflectorContract]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockReflectorContract extends _i1.Mock implements _i2.ReflectorContract { + MockReflectorContract() { + _i1.throwOnMissingStub(this); + } + + @override + String? getName(Symbol? symbol) => (super.noSuchMethod(Invocation.method( + #getName, + [symbol], + )) as String?); + + @override + _i2.ReflectedClassContract? reflectClass(Type? clazz) => + (super.noSuchMethod(Invocation.method( + #reflectClass, + [clazz], + )) as _i2.ReflectedClassContract?); + + @override + _i2.ReflectedFunctionContract? reflectFunction(Function? function) => + (super.noSuchMethod(Invocation.method( + #reflectFunction, + [function], + )) as _i2.ReflectedFunctionContract?); + + @override + _i2.ReflectedTypeContract? reflectType(Type? type) => + (super.noSuchMethod(Invocation.method( + #reflectType, + [type], + )) as _i2.ReflectedTypeContract?); + + @override + _i2.ReflectedInstanceContract? reflectInstance(Object? object) => + (super.noSuchMethod(Invocation.method( + #reflectInstance, + [object], + )) as _i2.ReflectedInstanceContract?); + + @override + _i2.ReflectedTypeContract reflectFutureOf(Type? type) => (super.noSuchMethod( + Invocation.method( + #reflectFutureOf, + [type], + ), + returnValue: _FakeReflectedTypeContract_0( + this, + Invocation.method( + #reflectFutureOf, + [type], + ), + ), + ) as _i2.ReflectedTypeContract); +} + +/// A class which mocks [ReflectedTypeContract]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockReflectedTypeContract extends _i1.Mock + implements _i2.ReflectedTypeContract { + MockReflectedTypeContract() { + _i1.throwOnMissingStub(this); + } + + @override + String get name => (super.noSuchMethod( + Invocation.getter(#name), + returnValue: _i3.dummyValue( + this, + Invocation.getter(#name), + ), + ) as String); + + @override + List<_i2.ReflectedTypeParameterContract> get typeParameters => + (super.noSuchMethod( + Invocation.getter(#typeParameters), + returnValue: <_i2.ReflectedTypeParameterContract>[], + ) as List<_i2.ReflectedTypeParameterContract>); + + @override + Type get reflectedType => (super.noSuchMethod( + Invocation.getter(#reflectedType), + returnValue: _FakeType_1( + this, + Invocation.getter(#reflectedType), + ), + ) as Type); + + @override + bool isAssignableTo(_i2.ReflectedTypeContract? other) => (super.noSuchMethod( + Invocation.method( + #isAssignableTo, + [other], + ), + returnValue: false, + ) as bool); + + @override + _i2.ReflectedInstanceContract newInstance( + String? constructorName, + List? positionalArguments, [ + Map? namedArguments = const {}, + List? typeArguments = const [], + ]) => + (super.noSuchMethod( + Invocation.method( + #newInstance, + [ + constructorName, + positionalArguments, + namedArguments, + typeArguments, + ], + ), + returnValue: _FakeReflectedInstanceContract_2( + this, + Invocation.method( + #newInstance, + [ + constructorName, + positionalArguments, + namedArguments, + typeArguments, + ], + ), + ), + ) as _i2.ReflectedInstanceContract); +} + +/// A class which mocks [ReflectedInstanceContract]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockReflectedInstanceContract extends _i1.Mock + implements _i2.ReflectedInstanceContract { + MockReflectedInstanceContract() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.ReflectedTypeContract get type => (super.noSuchMethod( + Invocation.getter(#type), + returnValue: _FakeReflectedTypeContract_0( + this, + Invocation.getter(#type), + ), + ) as _i2.ReflectedTypeContract); + + @override + _i2.ReflectedClassContract get clazz => (super.noSuchMethod( + Invocation.getter(#clazz), + returnValue: _FakeReflectedClassContract_3( + this, + Invocation.getter(#clazz), + ), + ) as _i2.ReflectedClassContract); + + @override + _i2.ReflectedInstanceContract getField(String? name) => (super.noSuchMethod( + Invocation.method( + #getField, + [name], + ), + returnValue: _FakeReflectedInstanceContract_2( + this, + Invocation.method( + #getField, + [name], + ), + ), + ) as _i2.ReflectedInstanceContract); +} diff --git a/packages/container/test/laravel_container_test.dart b/packages/container/test/laravel_container_test.dart new file mode 100644 index 0000000..301c508 --- /dev/null +++ b/packages/container/test/laravel_container_test.dart @@ -0,0 +1,293 @@ +import 'package:platform_contracts/contracts.dart'; +import 'package:platform_container/platform_container.dart'; +import 'package:test/test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:mockito/annotations.dart'; + +import 'laravel_container_test.mocks.dart'; + +// Test classes +abstract class PaymentGateway {} + +class StripeGateway implements PaymentGateway {} + +class PayPalGateway implements PaymentGateway {} + +class Service {} + +class SpecialService extends Service {} + +class Client { + final Service service; + Client(this.service); +} + +@GenerateMocks([ + ReflectorContract, + ReflectedClassContract, + ReflectedInstanceContract, + ReflectedFunctionContract, + ReflectedParameterContract, + ReflectedTypeContract +]) +void main() { + late IlluminateContainer container; + late MockReflectorContract reflector; + late MockReflectedClassContract reflectedClass; + late MockReflectedInstanceContract reflectedInstance; + late MockReflectedFunctionContract reflectedFunction; + late MockReflectedParameterContract reflectedParameter; + late MockReflectedTypeContract reflectedType; + + setUp(() { + reflector = MockReflectorContract(); + reflectedClass = MockReflectedClassContract(); + reflectedInstance = MockReflectedInstanceContract(); + reflectedFunction = MockReflectedFunctionContract(); + reflectedParameter = MockReflectedParameterContract(); + reflectedType = MockReflectedTypeContract(); + container = IlluminateContainer(reflector); + + // Setup default reflection behavior + when(reflector.reflectClass(any)).thenReturn(null); + when(reflector.reflectFunction(any)).thenReturn(null); + }); + + group('Laravel Container', () { + test('contextual binding with when/needs/give', () { + // When we set up a contextual binding + container.when(Client).needs().give(SpecialService()); + + // Then the contextual binding should be used + when(reflector.reflectClass(Client)).thenReturn(reflectedClass); + when(reflectedClass.newInstance('', [])).thenReturn(reflectedInstance); + when(reflectedInstance.reflectee).thenReturn(Client(SpecialService())); + + final client = container.make(); + expect(client.service, isA()); + }); + + test('method injection with call', () { + void method(Service service) {} + + // Setup reflection mocks + when(reflector.reflectFunction(method)).thenReturn(reflectedFunction); + when(reflectedFunction.parameters).thenReturn([reflectedParameter]); + when(reflectedParameter.type).thenReturn(reflectedType); + when(reflectedType.reflectedType).thenReturn(Service); + + // Register service + final service = Service(); + container.registerSingleton(service); + + // When we call the method through the container + container.call(method); + + // Then dependencies should be injected + verify(reflector.reflectFunction(method)).called(1); + }); + + test('resolution hooks', () { + // Given some resolution hooks + final resolving = []; + final testValue = 'test'; + + // Register hooks with explicit type parameters + void beforeCallback(ContainerContract c, String? i) => + resolving.add('before'); + void resolvingCallback(ContainerContract c, String i) => + resolving.add('resolving'); + void afterCallback(ContainerContract c, String i) => + resolving.add('after'); + + container.beforeResolving(beforeCallback); + container.resolving(resolvingCallback); + container.afterResolving(afterCallback); + + // Register a value to resolve + container.registerSingleton(testValue); + + // When we resolve a type + container.make(); + + // Then hooks should be called in order + expect(resolving, equals(['before', 'resolving', 'after'])); + }); + + test('method binding', () { + // Setup reflection mocks + when(reflector.reflectFunction(any)).thenReturn(reflectedFunction); + when(reflectedFunction.parameters).thenReturn([]); + + // Register a method + String boundMethod() => 'bound'; + container.bindMethod('test', boundMethod); + + // Call a test method + String testMethod() => 'test'; + final result = container.call(testMethod); + + // Verify the method was called + verify(reflector.reflectFunction(any)).called(1); + expect(result, equals('test')); + }); + + test('tagged bindings', () { + // Create gateway instances + final stripeGateway = StripeGateway(); + final paypalGateway = PayPalGateway(); + + // Register factories for concrete types + container.registerFactory((c) => stripeGateway); + container.registerFactory((c) => paypalGateway); + + // Register types in order + container.tag([StripeGateway, PayPalGateway], 'gateways'); + + // When we resolve tagged bindings + final gateways = container.tagged('gateways'); + + // Then all tagged instances should be returned in order + expect(gateways.length, equals(2)); + expect(gateways[0], same(stripeGateway)); + expect(gateways[1], same(paypalGateway)); + }); + + test('shared bindings', () { + // Given a shared binding + container.bind((c) => 'test', shared: true); + + // When we resolve multiple times + final first = container.make(); + final second = container.make(); + + // Then the same instance should be returned + expect(identical(first, second), isTrue); + }); + + test('child container inherits bindings', () { + // Given a parent binding + container.bind((c) => 'parent'); + + // When we create a child container + final child = container.createChild(); + + // Then the child should inherit bindings + expect(child.make(), equals('parent')); + }); + + test('child container can override bindings', () { + // Given a parent binding + container.registerFactory((c) => 'parent'); + + // When we override in child + final child = container.createChild(); + child.registerFactory((c) => 'child'); + + // Then child should use its own binding + expect(child.make(), equals('child')); + expect(container.make(), equals('parent')); + }); + + test('scoped bindings', () { + // Given a scoped binding + var counter = 0; + container.scoped((c) => 'scoped${counter++}'); + + // When we resolve multiple times + final first = container.make(); + final second = container.make(); + + // Then the same instance should be returned + expect(first, equals(second)); + + // When we forget scoped instances + container.forgetScopedInstances(); + + // Then a new instance should be created + final third = container.make(); + expect(third, isNot(equals(second))); + }); + + test('type aliases', () { + // Given a binding and an alias + container.bind((c) => StripeGateway()); + container.alias(PaymentGateway, StripeGateway); + + // When we check if it's an alias + final isAlias = container.isAlias('StripeGateway'); + + // Then it should be true + expect(isAlias, isTrue); + + // And the alias should resolve to the original type + expect(container.getAlias(StripeGateway), equals(PaymentGateway)); + }); + + test('extending instances', () { + print('Starting extending instances test'); + + // Given a binding and an extender + var extended = false; + print('Setting up binding and extender'); + container.bind((c) => Service()); + container.extend(Service, (instance) { + print('Extender called'); + extended = true; + }); + + print('Making instance'); + // When we make an instance + container.make(); + + print('Extended value: $extended'); + // Then the extender should be called + expect(extended, isTrue); + + // And we can get the extenders + expect(container.getExtenders(Service).length, equals(1)); + + // And we can forget them + container.forgetExtenders(Service); + expect(container.getExtenders(Service).length, equals(0)); + }); + + test('rebinding callbacks', () { + // Given a binding and a rebind callback + var rebound = false; + container.bind((c) => Service()); + container.rebinding(Service, (c, i) => rebound = true); + + // When we refresh an instance + final target = {'method': (Service s) {}}; + container.refresh(Service, target, 'method'); + + // Then the callback should be called + expect(rebound, isTrue); + }); + + test('container flushing', () { + // Given some bindings and instances + container.bind((c) => Service()); + container.registerSingleton(PayPalGateway()); + container.registerNamedSingleton('test', 'value'); + container.tag([Service], 'services'); + container.when(Client).needs().give(SpecialService()); + container.bindMethod('test', () {}); + container.alias(PaymentGateway, PayPalGateway); + container.extend(Service, (i) {}); + container.rebinding(Service, (c, i) {}); + container.scoped((c) => 'scoped'); + container.beforeResolving((c, i) {}); + + // When we flush the container + container.flush(); + + // Then everything should be cleared + expect(container.has(), isFalse); + expect(container.hasNamed('test'), isFalse); + expect(() => container.tagged('services'), throwsStateError); + expect(container.getExtenders(Service).length, equals(0)); + }); + }); +} diff --git a/packages/container/test/laravel_container_test.mocks.dart b/packages/container/test/laravel_container_test.mocks.dart new file mode 100644 index 0000000..1cfd9b3 --- /dev/null +++ b/packages/container/test/laravel_container_test.mocks.dart @@ -0,0 +1,447 @@ +// Mocks generated by Mockito 5.4.4 from annotations +// in platform_illuminate_container/test/laravel_container_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'package:mockito/mockito.dart' as _i1; +import 'package:mockito/src/dummies.dart' as _i3; +import 'package:platform_contracts/src/reflection/reflector_contract.dart' + as _i2; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeReflectedTypeContract_0 extends _i1.SmartFake + implements _i2.ReflectedTypeContract { + _FakeReflectedTypeContract_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeType_1 extends _i1.SmartFake implements Type { + _FakeType_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeReflectedInstanceContract_2 extends _i1.SmartFake + implements _i2.ReflectedInstanceContract { + _FakeReflectedInstanceContract_2( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeReflectedClassContract_3 extends _i1.SmartFake + implements _i2.ReflectedClassContract { + _FakeReflectedClassContract_3( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [ReflectorContract]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockReflectorContract extends _i1.Mock implements _i2.ReflectorContract { + MockReflectorContract() { + _i1.throwOnMissingStub(this); + } + + @override + String? getName(Symbol? symbol) => (super.noSuchMethod(Invocation.method( + #getName, + [symbol], + )) as String?); + + @override + _i2.ReflectedClassContract? reflectClass(Type? clazz) => + (super.noSuchMethod(Invocation.method( + #reflectClass, + [clazz], + )) as _i2.ReflectedClassContract?); + + @override + _i2.ReflectedFunctionContract? reflectFunction(Function? function) => + (super.noSuchMethod(Invocation.method( + #reflectFunction, + [function], + )) as _i2.ReflectedFunctionContract?); + + @override + _i2.ReflectedTypeContract? reflectType(Type? type) => + (super.noSuchMethod(Invocation.method( + #reflectType, + [type], + )) as _i2.ReflectedTypeContract?); + + @override + _i2.ReflectedInstanceContract? reflectInstance(Object? object) => + (super.noSuchMethod(Invocation.method( + #reflectInstance, + [object], + )) as _i2.ReflectedInstanceContract?); + + @override + _i2.ReflectedTypeContract reflectFutureOf(Type? type) => (super.noSuchMethod( + Invocation.method( + #reflectFutureOf, + [type], + ), + returnValue: _FakeReflectedTypeContract_0( + this, + Invocation.method( + #reflectFutureOf, + [type], + ), + ), + ) as _i2.ReflectedTypeContract); +} + +/// A class which mocks [ReflectedClassContract]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockReflectedClassContract extends _i1.Mock + implements _i2.ReflectedClassContract { + MockReflectedClassContract() { + _i1.throwOnMissingStub(this); + } + + @override + List<_i2.ReflectedInstanceContract> get annotations => (super.noSuchMethod( + Invocation.getter(#annotations), + returnValue: <_i2.ReflectedInstanceContract>[], + ) as List<_i2.ReflectedInstanceContract>); + + @override + List<_i2.ReflectedFunctionContract> get constructors => (super.noSuchMethod( + Invocation.getter(#constructors), + returnValue: <_i2.ReflectedFunctionContract>[], + ) as List<_i2.ReflectedFunctionContract>); + + @override + List<_i2.ReflectedDeclarationContract> get declarations => + (super.noSuchMethod( + Invocation.getter(#declarations), + returnValue: <_i2.ReflectedDeclarationContract>[], + ) as List<_i2.ReflectedDeclarationContract>); + + @override + String get name => (super.noSuchMethod( + Invocation.getter(#name), + returnValue: _i3.dummyValue( + this, + Invocation.getter(#name), + ), + ) as String); + + @override + List<_i2.ReflectedTypeParameterContract> get typeParameters => + (super.noSuchMethod( + Invocation.getter(#typeParameters), + returnValue: <_i2.ReflectedTypeParameterContract>[], + ) as List<_i2.ReflectedTypeParameterContract>); + + @override + Type get reflectedType => (super.noSuchMethod( + Invocation.getter(#reflectedType), + returnValue: _FakeType_1( + this, + Invocation.getter(#reflectedType), + ), + ) as Type); + + @override + bool isAssignableTo(_i2.ReflectedTypeContract? other) => (super.noSuchMethod( + Invocation.method( + #isAssignableTo, + [other], + ), + returnValue: false, + ) as bool); + + @override + _i2.ReflectedInstanceContract newInstance( + String? constructorName, + List? positionalArguments, [ + Map? namedArguments = const {}, + List? typeArguments = const [], + ]) => + (super.noSuchMethod( + Invocation.method( + #newInstance, + [ + constructorName, + positionalArguments, + namedArguments, + typeArguments, + ], + ), + returnValue: _FakeReflectedInstanceContract_2( + this, + Invocation.method( + #newInstance, + [ + constructorName, + positionalArguments, + namedArguments, + typeArguments, + ], + ), + ), + ) as _i2.ReflectedInstanceContract); +} + +/// A class which mocks [ReflectedInstanceContract]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockReflectedInstanceContract extends _i1.Mock + implements _i2.ReflectedInstanceContract { + MockReflectedInstanceContract() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.ReflectedTypeContract get type => (super.noSuchMethod( + Invocation.getter(#type), + returnValue: _FakeReflectedTypeContract_0( + this, + Invocation.getter(#type), + ), + ) as _i2.ReflectedTypeContract); + + @override + _i2.ReflectedClassContract get clazz => (super.noSuchMethod( + Invocation.getter(#clazz), + returnValue: _FakeReflectedClassContract_3( + this, + Invocation.getter(#clazz), + ), + ) as _i2.ReflectedClassContract); + + @override + _i2.ReflectedInstanceContract getField(String? name) => (super.noSuchMethod( + Invocation.method( + #getField, + [name], + ), + returnValue: _FakeReflectedInstanceContract_2( + this, + Invocation.method( + #getField, + [name], + ), + ), + ) as _i2.ReflectedInstanceContract); +} + +/// A class which mocks [ReflectedFunctionContract]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockReflectedFunctionContract extends _i1.Mock + implements _i2.ReflectedFunctionContract { + MockReflectedFunctionContract() { + _i1.throwOnMissingStub(this); + } + + @override + String get name => (super.noSuchMethod( + Invocation.getter(#name), + returnValue: _i3.dummyValue( + this, + Invocation.getter(#name), + ), + ) as String); + + @override + List<_i2.ReflectedTypeParameterContract> get typeParameters => + (super.noSuchMethod( + Invocation.getter(#typeParameters), + returnValue: <_i2.ReflectedTypeParameterContract>[], + ) as List<_i2.ReflectedTypeParameterContract>); + + @override + List<_i2.ReflectedInstanceContract> get annotations => (super.noSuchMethod( + Invocation.getter(#annotations), + returnValue: <_i2.ReflectedInstanceContract>[], + ) as List<_i2.ReflectedInstanceContract>); + + @override + List<_i2.ReflectedParameterContract> get parameters => (super.noSuchMethod( + Invocation.getter(#parameters), + returnValue: <_i2.ReflectedParameterContract>[], + ) as List<_i2.ReflectedParameterContract>); + + @override + bool get isGetter => (super.noSuchMethod( + Invocation.getter(#isGetter), + returnValue: false, + ) as bool); + + @override + bool get isSetter => (super.noSuchMethod( + Invocation.getter(#isSetter), + returnValue: false, + ) as bool); + + @override + _i2.ReflectedInstanceContract invoke(Invocation? invocation) => + (super.noSuchMethod( + Invocation.method( + #invoke, + [invocation], + ), + returnValue: _FakeReflectedInstanceContract_2( + this, + Invocation.method( + #invoke, + [invocation], + ), + ), + ) as _i2.ReflectedInstanceContract); +} + +/// A class which mocks [ReflectedParameterContract]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockReflectedParameterContract extends _i1.Mock + implements _i2.ReflectedParameterContract { + MockReflectedParameterContract() { + _i1.throwOnMissingStub(this); + } + + @override + String get name => (super.noSuchMethod( + Invocation.getter(#name), + returnValue: _i3.dummyValue( + this, + Invocation.getter(#name), + ), + ) as String); + + @override + List<_i2.ReflectedInstanceContract> get annotations => (super.noSuchMethod( + Invocation.getter(#annotations), + returnValue: <_i2.ReflectedInstanceContract>[], + ) as List<_i2.ReflectedInstanceContract>); + + @override + _i2.ReflectedTypeContract get type => (super.noSuchMethod( + Invocation.getter(#type), + returnValue: _FakeReflectedTypeContract_0( + this, + Invocation.getter(#type), + ), + ) as _i2.ReflectedTypeContract); + + @override + bool get isRequired => (super.noSuchMethod( + Invocation.getter(#isRequired), + returnValue: false, + ) as bool); + + @override + bool get isNamed => (super.noSuchMethod( + Invocation.getter(#isNamed), + returnValue: false, + ) as bool); +} + +/// A class which mocks [ReflectedTypeContract]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockReflectedTypeContract extends _i1.Mock + implements _i2.ReflectedTypeContract { + MockReflectedTypeContract() { + _i1.throwOnMissingStub(this); + } + + @override + String get name => (super.noSuchMethod( + Invocation.getter(#name), + returnValue: _i3.dummyValue( + this, + Invocation.getter(#name), + ), + ) as String); + + @override + List<_i2.ReflectedTypeParameterContract> get typeParameters => + (super.noSuchMethod( + Invocation.getter(#typeParameters), + returnValue: <_i2.ReflectedTypeParameterContract>[], + ) as List<_i2.ReflectedTypeParameterContract>); + + @override + Type get reflectedType => (super.noSuchMethod( + Invocation.getter(#reflectedType), + returnValue: _FakeType_1( + this, + Invocation.getter(#reflectedType), + ), + ) as Type); + + @override + bool isAssignableTo(_i2.ReflectedTypeContract? other) => (super.noSuchMethod( + Invocation.method( + #isAssignableTo, + [other], + ), + returnValue: false, + ) as bool); + + @override + _i2.ReflectedInstanceContract newInstance( + String? constructorName, + List? positionalArguments, [ + Map? namedArguments = const {}, + List? typeArguments = const [], + ]) => + (super.noSuchMethod( + Invocation.method( + #newInstance, + [ + constructorName, + positionalArguments, + namedArguments, + typeArguments, + ], + ), + returnValue: _FakeReflectedInstanceContract_2( + this, + Invocation.method( + #newInstance, + [ + constructorName, + positionalArguments, + namedArguments, + typeArguments, + ], + ), + ), + ) as _i2.ReflectedInstanceContract); +} diff --git a/packages/container/test/stubs.dart b/packages/container/test/stubs.dart new file mode 100644 index 0000000..04f16d2 --- /dev/null +++ b/packages/container/test/stubs.dart @@ -0,0 +1,86 @@ +// Test classes ported from Laravel's ContainerTest.php + +// Basic concrete class +class ContainerConcreteStub {} + +// Interface and implementations +abstract class IContainerContractStub { + String getValue(); +} + +class ContainerImplementationStub implements IContainerContractStub { + final String value; + ContainerImplementationStub([this.value = 'implementation']); + @override + String getValue() => value; +} + +class ContainerImplementationStubTwo implements IContainerContractStub { + @override + String getValue() => 'implementation2'; +} + +// Classes with dependencies +class ContainerDependentStub { + final IContainerContractStub impl; + ContainerDependentStub(this.impl); +} + +class ContainerNestedDependentStub { + final ContainerDependentStub inner; + ContainerNestedDependentStub(this.inner); +} + +// Classes with default values +class ContainerDefaultValueStub { + final ContainerConcreteStub stub; + final String default_; + ContainerDefaultValueStub(this.stub, [this.default_ = 'taylor']); +} + +// Classes with mixed parameters +class ContainerMixedPrimitiveStub { + final int first; + final ContainerConcreteStub stub; + final int last; + ContainerMixedPrimitiveStub(this.first, this.stub, this.last); +} + +// Classes for variable injection +class ContainerInjectVariableStub { + final String something; + ContainerInjectVariableStub(ContainerConcreteStub concrete, this.something); +} + +class ContainerInjectVariableStubWithInterface + implements IContainerContractStub { + final String something; + ContainerInjectVariableStubWithInterface( + ContainerConcreteStub concrete, this.something); + @override + String getValue() => something; +} + +// Class for contextual binding +class ContainerContextualBindingCallTarget { + IContainerContractStub work(IContainerContractStub stub) => stub; +} + +// Classes for circular dependency testing +class CircularAStub { + CircularAStub(CircularBStub b); +} + +class CircularBStub { + CircularBStub(CircularCStub c); +} + +class CircularCStub { + CircularCStub(CircularAStub a); +} + +// Additional test types +class StubAlias implements IContainerContractStub { + @override + String getValue() => 'alias'; +}