From 0d2bfcf00a4c121d88d78c74078b4c3b25a8218c Mon Sep 17 00:00:00 2001 From: Vitor Conceicao Date: Sun, 8 Feb 2026 14:21:36 -0300 Subject: [PATCH 01/11] feat: add network package --- Projects/App/Project.swift | 4 ++- .../App/Sources/Application/AppDelegate.swift | 2 ++ .../Assemblies/NetworkAssembly.swift.swift | 27 +++++++++++++++++++ .../Tests/Application/AppDelegateTests.swift | 10 ++++++- Tuist/Package.resolved | 9 +++++++ Tuist/Package.swift | 3 ++- .../Project+Template.swift | 2 +- 7 files changed, 53 insertions(+), 4 deletions(-) create mode 100644 Projects/App/Sources/Assemblies/NetworkAssembly.swift.swift diff --git a/Projects/App/Project.swift b/Projects/App/Project.swift index 75fc026..e369e37 100644 --- a/Projects/App/Project.swift +++ b/Projects/App/Project.swift @@ -7,7 +7,9 @@ let dependecies: [TargetDependency] = [ .project(target: "DependencyInjection", path: "../DependencyInjection"), .project(target: "DependencyInjectionInterfaces", - path: "../DependencyInjection") + path: "../DependencyInjection"), + .external(name: "Networking"), + .external(name: "NetworkingInterfaces"), ] let testDependencies: [TargetDependency] = [ diff --git a/Projects/App/Sources/Application/AppDelegate.swift b/Projects/App/Sources/Application/AppDelegate.swift index 2f123d2..18af3e4 100644 --- a/Projects/App/Sources/Application/AppDelegate.swift +++ b/Projects/App/Sources/Application/AppDelegate.swift @@ -35,5 +35,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { private func setupDependencies() { let injector = Injector() SharedContainer.shared.setInjector(injector) + + NetworkAssembly(injector: injector).register() } } diff --git a/Projects/App/Sources/Assemblies/NetworkAssembly.swift.swift b/Projects/App/Sources/Assemblies/NetworkAssembly.swift.swift new file mode 100644 index 0000000..8c5ca5f --- /dev/null +++ b/Projects/App/Sources/Assemblies/NetworkAssembly.swift.swift @@ -0,0 +1,27 @@ +// +// +// Created by Vitor Conceicao. +// +// github.com/vitor-rc1 +// +// + +import DependencyInjectionInterfaces +import NetworkingInterfaces +import Networking + +import Foundation + +public struct NetworkAssembly { + private let injector: DependencyInjector + + public init(injector: DependencyInjector) { + self.injector = injector + } + + public func register() { + injector.register(NetworkServiceProtocol.self) { _ in + NetworkService() + } + } +} diff --git a/Projects/App/Tests/Application/AppDelegateTests.swift b/Projects/App/Tests/Application/AppDelegateTests.swift index bb6a59e..5ad68fd 100644 --- a/Projects/App/Tests/Application/AppDelegateTests.swift +++ b/Projects/App/Tests/Application/AppDelegateTests.swift @@ -8,15 +8,23 @@ import Testing import DependencyInjectionInterfaces +import NetworkingInterfaces @testable import App @Suite @MainActor struct AppDelegateTests { @Test("GIVEN AppDelegate initialize THEN it should set injector") - func example() async throws { + func setInjectot() async throws { #expect(throws: Never.self) { _ = SharedContainer.shared.resolver() } } + + @Test("GIVEN AppDelegate initialize THEN it SHOULD register DI Network") + func NetworkRegister() async throws { + #expect(throws: Never.self) { + let _: NetworkServiceProtocol = SharedContainer.shared.resolver().resolve() + } + } } diff --git a/Tuist/Package.resolved b/Tuist/Package.resolved index f640ad5..3cbbe40 100644 --- a/Tuist/Package.resolved +++ b/Tuist/Package.resolved @@ -1,5 +1,14 @@ { "pins" : [ + { + "identity" : "networking-package", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vitor-rc1/networking-package.git", + "state" : { + "revision" : "b5498ee2ab408592d4db9a9c95faec3a08de9ea2", + "version" : "1.1.0" + } + }, { "identity" : "swift-custom-dump", "kind" : "remoteSourceControl", diff --git a/Tuist/Package.swift b/Tuist/Package.swift index 9ca806e..f545677 100644 --- a/Tuist/Package.swift +++ b/Tuist/Package.swift @@ -13,6 +13,7 @@ let package = Package( name: "ExchangesApp", dependencies: [ .package(url: "https://github.com/Swinject/Swinject.git", from: "2.9.1"), - .package(url: "https://github.com/pointfreeco/swift-snapshot-testing.git", from: "1.18.9") + .package(url: "https://github.com/pointfreeco/swift-snapshot-testing.git", from: "1.18.9"), + .package(url: "https://github.com/vitor-rc1/networking-package.git", from: "1.1.0") ] ) diff --git a/Tuist/ProjectDescriptionHelpers/Project+Template.swift b/Tuist/ProjectDescriptionHelpers/Project+Template.swift index 241da1b..3f54a98 100644 --- a/Tuist/ProjectDescriptionHelpers/Project+Template.swift +++ b/Tuist/ProjectDescriptionHelpers/Project+Template.swift @@ -10,7 +10,7 @@ public let developmentEnv: [String: EnvironmentVariable] = [ "CM_API_BASE_URL": cmApiBaseURL, ] -public let iOSDeploymentTarget: DeploymentTargets = .iOS("14.0") +public let iOSDeploymentTarget: DeploymentTargets = .iOS("15.0") public let commonSettings: SettingsDictionary = [ "SWIFT_VERSION": "6.0" ] From 7174970602091e049612986bc1fbc44da6938f26 Mon Sep 17 00:00:00 2001 From: Vitor Conceicao Date: Sun, 8 Feb 2026 14:47:24 -0300 Subject: [PATCH 02/11] feat: add home module --- Projects/App/Project.swift | 2 ++ Projects/Home/Project.swift | 16 ++++++++++++++++ Workspace.swift | 3 ++- 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 Projects/Home/Project.swift diff --git a/Projects/App/Project.swift b/Projects/App/Project.swift index e369e37..df63160 100644 --- a/Projects/App/Project.swift +++ b/Projects/App/Project.swift @@ -8,6 +8,8 @@ let dependecies: [TargetDependency] = [ path: "../DependencyInjection"), .project(target: "DependencyInjectionInterfaces", path: "../DependencyInjection"), + .project(target: "HomeInterfaces", + path: "../Home"), .external(name: "Networking"), .external(name: "NetworkingInterfaces"), ] diff --git a/Projects/Home/Project.swift b/Projects/Home/Project.swift new file mode 100644 index 0000000..ef59de8 --- /dev/null +++ b/Projects/Home/Project.swift @@ -0,0 +1,16 @@ +import ProjectDescription +import ProjectDescriptionHelpers + +let moduleName = "Home" + +let dependecies: [TargetDependency] = [ + .project(target: "DependencyInjectionInterfaces", + path: "../DependencyInjection"), + .project(target: "DesignSystem", + path: "../DesignSystem"), + .external(name: "NetworkingInterfaces"), +] + +let project = Project.templateModule(named: moduleName, + targets: [.source,. interfaces, .test], + dependencies: dependecies) diff --git a/Workspace.swift b/Workspace.swift index 24febbc..de4324c 100644 --- a/Workspace.swift +++ b/Workspace.swift @@ -6,7 +6,8 @@ let workspace = Workspace( projects: [ "./Projects/App", "./Projects/DependencyInjection", - "./Projects/DesignSystem" + "./Projects/DesignSystem", + "./Projects/Home" ], schemes: [], generationOptions: .options(autogeneratedWorkspaceSchemes: .disabled) From d4e9786b9a97039a011107ac6712c2807a0d1ef4 Mon Sep 17 00:00:00 2001 From: Vitor Conceicao Date: Sun, 8 Feb 2026 14:48:01 -0300 Subject: [PATCH 03/11] feat: add home to run in pr status --- .github/workflows/pr_status.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/pr_status.yml b/.github/workflows/pr_status.yml index 15bf55b..20bcb29 100644 --- a/.github/workflows/pr_status.yml +++ b/.github/workflows/pr_status.yml @@ -24,6 +24,8 @@ jobs: - 'Projects/DependencyInjection/**' design_system: - 'Projects/DesignSystem/**' + home: + - 'Projects/Home/**' - name: Generate matrix id: matrix run: | @@ -39,6 +41,10 @@ jobs: if [ "${{ steps.filter.outputs.design_system }}" == "true" ]; then targets+=("DesignSystem") fi + + if [ "${{ steps.filter.outputs.home }}" == "true" ]; then + targets+=("Home") + fi if [ ${#targets[@]} -gt 0 ]; then matrix="{\"target\": [$(printf '"%s",' "${targets[@]}" | sed 's/,$//')]}" From 49fab9ce7acc2238d65de3a018c1c6f5ec3ae0b2 Mon Sep 17 00:00:00 2001 From: Vitor Conceicao Date: Sun, 8 Feb 2026 15:05:15 -0300 Subject: [PATCH 04/11] feat: add app coordinator and their assembly --- .../App/Sources/Application/AppDelegate.swift | 1 + .../Assemblies/CoordinatorAssembly.swift | 27 +++++++++++++++++ .../Sources/Coordinator/AppCoordinator.swift | 30 +++++++++++++++++++ .../Tests/Application/AppDelegateTests.swift | 15 +++++++++- Projects/Home/Interfaces/Coordinator.swift | 18 +++++++++++ 5 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 Projects/App/Sources/Assemblies/CoordinatorAssembly.swift create mode 100644 Projects/App/Sources/Coordinator/AppCoordinator.swift create mode 100644 Projects/Home/Interfaces/Coordinator.swift diff --git a/Projects/App/Sources/Application/AppDelegate.swift b/Projects/App/Sources/Application/AppDelegate.swift index 18af3e4..2818599 100644 --- a/Projects/App/Sources/Application/AppDelegate.swift +++ b/Projects/App/Sources/Application/AppDelegate.swift @@ -37,5 +37,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { SharedContainer.shared.setInjector(injector) NetworkAssembly(injector: injector).register() + CoordinatorAssembly(injector: injector).register() } } diff --git a/Projects/App/Sources/Assemblies/CoordinatorAssembly.swift b/Projects/App/Sources/Assemblies/CoordinatorAssembly.swift new file mode 100644 index 0000000..1dcf7f5 --- /dev/null +++ b/Projects/App/Sources/Assemblies/CoordinatorAssembly.swift @@ -0,0 +1,27 @@ +// +// +// Created by Vitor Conceicao. +// +// github.com/vitor-rc1 +// +// +import DependencyInjectionInterfaces +import NetworkingInterfaces +import Networking + +import UIKit + +public struct CoordinatorAssembly { + private let injector: DependencyInjector + + public init(injector: DependencyInjector) { + self.injector = injector + } + + @MainActor + public func register() { + injector.register(AppCoordinating.self) { (_, arg: UINavigationController) in + AppCoordinator(navigationController: arg) + } + } +} diff --git a/Projects/App/Sources/Coordinator/AppCoordinator.swift b/Projects/App/Sources/Coordinator/AppCoordinator.swift new file mode 100644 index 0000000..8586283 --- /dev/null +++ b/Projects/App/Sources/Coordinator/AppCoordinator.swift @@ -0,0 +1,30 @@ +// +// +// Created by Vitor Conceicao. +// +// github.com/vitor-rc1 +// +// + +import HomeInterfaces +import UIKit + +protocol AppCoordinating: Coordinator {} + +@MainActor +final class AppCoordinator: AppCoordinating { + var parentCoordinator: Coordinator? + var children: [Coordinator] = [] + var navigationController: UINavigationController + + init(navigationController: UINavigationController) { + self.navigationController = navigationController + } + + func start() { +// let viewModel = HomeViewModel() +// viewModel.coordinatorDelegate = self +// let homeVC = HomeViewController(viewModel: viewModel) +// navigationController.pushViewController(homeVC, animated: false) + } +} diff --git a/Projects/App/Tests/Application/AppDelegateTests.swift b/Projects/App/Tests/Application/AppDelegateTests.swift index 5ad68fd..6497d12 100644 --- a/Projects/App/Tests/Application/AppDelegateTests.swift +++ b/Projects/App/Tests/Application/AppDelegateTests.swift @@ -9,6 +9,8 @@ import Testing import DependencyInjectionInterfaces import NetworkingInterfaces +import UIKit + @testable import App @Suite @@ -22,9 +24,20 @@ struct AppDelegateTests { } @Test("GIVEN AppDelegate initialize THEN it SHOULD register DI Network") - func NetworkRegister() async throws { + func networkRegister() async throws { #expect(throws: Never.self) { let _: NetworkServiceProtocol = SharedContainer.shared.resolver().resolve() } } + + @Test("GIVEN AppDelegate initialize THEN it SHOULD register DI Network") + func appCoordinatorRegister() async throws { + #expect(throws: Never.self) { + let navigation = UINavigationController() + _ = SharedContainer + .shared + .resolver() + .resolve(AppCoordinating.self, argument: navigation) + } + } } diff --git a/Projects/Home/Interfaces/Coordinator.swift b/Projects/Home/Interfaces/Coordinator.swift new file mode 100644 index 0000000..532f4ca --- /dev/null +++ b/Projects/Home/Interfaces/Coordinator.swift @@ -0,0 +1,18 @@ +// +// +// Created by Vitor Conceicao. +// +// github.com/vitor-rc1 +// +// + +import UIKit + +@MainActor +public protocol Coordinator: AnyObject { + var parentCoordinator: Coordinator? { get set } + var children: [Coordinator] { get set } + var navigationController: UINavigationController { get set } + + func start() +} From abd84c4720196165f82d2abc8e03ae7293d8a796 Mon Sep 17 00:00:00 2001 From: Vitor Conceicao Date: Sun, 8 Feb 2026 16:26:00 -0300 Subject: [PATCH 05/11] tests: add dependency injector spy --- Projects/DependencyInjection/Project.swift | 2 +- .../Testing/DependencyInjectorSpy.swift | 76 +++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 Projects/DependencyInjection/Testing/DependencyInjectorSpy.swift diff --git a/Projects/DependencyInjection/Project.swift b/Projects/DependencyInjection/Project.swift index 134e69f..e4eb0aa 100644 --- a/Projects/DependencyInjection/Project.swift +++ b/Projects/DependencyInjection/Project.swift @@ -8,5 +8,5 @@ let dependecies: [TargetDependency] = [ ] let project = Project.templateModule(named: moduleName, - targets: [.source,. interfaces, .test], + targets: [.source,. interfaces, .test, .testing], dependencies: dependecies) diff --git a/Projects/DependencyInjection/Testing/DependencyInjectorSpy.swift b/Projects/DependencyInjection/Testing/DependencyInjectorSpy.swift new file mode 100644 index 0000000..83f8f42 --- /dev/null +++ b/Projects/DependencyInjection/Testing/DependencyInjectorSpy.swift @@ -0,0 +1,76 @@ +// +// Created by Vitor Conceicao. +// +// github.com/vitor-rc1 +// +// + +import DependencyInjectionInterfaces +import Foundation + +public final class DependencyInjectorSpy: DependencyInjector { + public init() {} + + enum Method { + case register + case registerWithArgument + case resolve + case resolveWithArgument + } + + // Container to hold registered services and their factories + private var registeredServices: [String: Any] = [:] + private var registeredServicesWithArgument: [String: Any] = [:] + + var calledMethods: [Method] = [] + + private func typeIdentifier(_ type: T.Type) -> String { + return String(reflecting: type) + } + + public func register(_ serviceType: Service.Type, + factory: @escaping (DependencyResolver) -> Service) { + calledMethods.append(.register) + let identifier = typeIdentifier(serviceType) + registeredServices[identifier] = factory + } + + public func register(_ serviceType: Service.Type, + factory: @escaping (DependencyResolver, Argument) -> Service) { + calledMethods.append(.registerWithArgument) + let identifier = typeIdentifier(serviceType) + registeredServicesWithArgument[identifier] = factory + } + + public func resolve() -> Service { + calledMethods.append(.resolve) + let identifier = typeIdentifier(Service.self) + + if let factory = registeredServices[identifier] as? (DependencyResolver) -> Service { + return factory(self) + } + + fatalError("No mock or registration found for \(identifier). Use stub(for:mock:) to configure a mock.") + } + + public func resolve(_ serviceType: Service.Type, + argument: Argument) -> Service { + calledMethods.append(.resolveWithArgument) + let identifier = typeIdentifier(serviceType) + + if let factory = registeredServicesWithArgument[identifier] as? (DependencyResolver, Argument) -> Service { + return factory(self, argument) + } + + fatalError("No mock or registration found for \(identifier). Use stub(for:mock:) to configure a mock.") + } + + // MARK: - Test Helpers + + /// Clear all registrations and mocks + public func reset() { + registeredServices.removeAll() + registeredServicesWithArgument.removeAll() + calledMethods.removeAll() + } +} From 0ad011d9af202369f7067009f1c58ca1573cdd61 Mon Sep 17 00:00:00 2001 From: Vitor Conceicao Date: Sun, 8 Feb 2026 16:26:33 -0300 Subject: [PATCH 06/11] refactor: prevent scene delegate to start app coordinator --- Projects/App/Project.swift | 4 +-- .../Sources/Application/SceneDelegate.swift | 30 ++++++++++++------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/Projects/App/Project.swift b/Projects/App/Project.swift index df63160..cdc5c12 100644 --- a/Projects/App/Project.swift +++ b/Projects/App/Project.swift @@ -63,8 +63,8 @@ let project = Project( "\(moduleName)Tests", // Disabled UITests for now, as they are not yet implemented // "\(moduleName)UITests" - ] - ), + ], + arguments: .arguments(environmentVariables: ["IS_TESTING": "true"])), runAction: .runAction(configuration: .debug, executable: .project(path: ".", target: moduleName), arguments: .arguments(environmentVariables: developmentEnv))) diff --git a/Projects/App/Sources/Application/SceneDelegate.swift b/Projects/App/Sources/Application/SceneDelegate.swift index 18a4ed2..532eb1c 100644 --- a/Projects/App/Sources/Application/SceneDelegate.swift +++ b/Projects/App/Sources/Application/SceneDelegate.swift @@ -6,30 +6,38 @@ // import UIKit +import DependencyInjectionInterfaces class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? + var appCoordinator: AppCoordinating? + + #if DEBUG + var isTesting: Bool { + return ProcessInfo.processInfo.environment["IS_TESTING"] == "true" + } + #endif func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let windowScene = (scene as? UIWindowScene) else { return } + #if DEBUG + if isTesting { return } + #endif + let window = UIWindow(windowScene: windowScene) - let nav = UINavigationController(rootViewController: ViewController()) + let nav = UINavigationController() + let resolver = SharedContainer.shared.resolver() + + appCoordinator = resolver.resolve(AppCoordinating.self, argument: nav) + appCoordinator?.start() + window.rootViewController = nav window.makeKeyAndVisible() + self.window = window } - - func sceneDidDisconnect(_ scene: UIScene) {} - - func sceneDidBecomeActive(_ scene: UIScene) {} - - func sceneWillResignActive(_ scene: UIScene) {} - - func sceneWillEnterForeground(_ scene: UIScene) {} - - func sceneDidEnterBackground(_ scene: UIScene) {} } From 5a7d7f201209ce73b0fc2eb3656261a2ed004ffb Mon Sep 17 00:00:00 2001 From: Vitor Conceicao Date: Sun, 8 Feb 2026 17:07:26 -0300 Subject: [PATCH 07/11] feat: add navigation module --- .../Navigation/Interfaces/Coordinator.swift | 18 ++++++++++ Projects/Navigation/Project.swift | 10 ++++++ .../Navigation/Testing/CoordinatorSpy.swift | 33 +++++++++++++++++++ .../Project+Template.swift | 4 ++- 4 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 Projects/Navigation/Interfaces/Coordinator.swift create mode 100644 Projects/Navigation/Project.swift create mode 100644 Projects/Navigation/Testing/CoordinatorSpy.swift diff --git a/Projects/Navigation/Interfaces/Coordinator.swift b/Projects/Navigation/Interfaces/Coordinator.swift new file mode 100644 index 0000000..27cfa90 --- /dev/null +++ b/Projects/Navigation/Interfaces/Coordinator.swift @@ -0,0 +1,18 @@ +// +// +// Created by Vitor Conceicao. +// +// github.com/vitor-rc1 +// +// + +import UIKit + +@MainActor +public protocol Coordinator: AnyObject { + var parentCoordinator: Coordinator? { get set } + var children: [Coordinator] { get set } + var navigationController: UINavigationController { get set } + + func start() +} diff --git a/Projects/Navigation/Project.swift b/Projects/Navigation/Project.swift new file mode 100644 index 0000000..e594ae8 --- /dev/null +++ b/Projects/Navigation/Project.swift @@ -0,0 +1,10 @@ +import ProjectDescription +import ProjectDescriptionHelpers + +let moduleName = "Navigation" + +let dependecies: [TargetDependency] = [] + +let project = Project.templateModule(named: moduleName, + targets: [.interfaces, .testing], + dependencies: dependecies) diff --git a/Projects/Navigation/Testing/CoordinatorSpy.swift b/Projects/Navigation/Testing/CoordinatorSpy.swift new file mode 100644 index 0000000..3f6ef91 --- /dev/null +++ b/Projects/Navigation/Testing/CoordinatorSpy.swift @@ -0,0 +1,33 @@ +// +// +// Created by Vitor Conceicao. +// +// github.com/vitor-rc1 +// +// + +import NavigationInterfaces +import UIKit + +final class CoordinatorSpy: Coordinator { + public enum Method { + case start + } + + public var calledMethods: [Method] = [] + var parentCoordinator: Coordinator? + var children: [Coordinator] = [] + var navigationController: UINavigationController + + public init(parentCoordinator: Coordinator? = nil, + children: [Coordinator], + navigationController: UINavigationController) { + self.parentCoordinator = parentCoordinator + self.children = children + self.navigationController = navigationController + } + + func start() { + calledMethods.append(.start) + } +} diff --git a/Tuist/ProjectDescriptionHelpers/Project+Template.swift b/Tuist/ProjectDescriptionHelpers/Project+Template.swift index 3f54a98..996e655 100644 --- a/Tuist/ProjectDescriptionHelpers/Project+Template.swift +++ b/Tuist/ProjectDescriptionHelpers/Project+Template.swift @@ -24,6 +24,7 @@ public extension Project { targets: [ModuleTargets] = [.source, .interfaces, .test], dependencies: [TargetDependency] = [], testDependencies: [TargetDependency] = [], + interfaceDependecies: [TargetDependency] = [], shouldSetEnvVars: Bool = false) -> Project { var selectedTargets: [Target] = [] @@ -38,7 +39,8 @@ public extension Project { buildableFolders: ["Interfaces"], scripts: [ swiftLintScript - ]) + ], + dependencies: interfaceDependecies) ) } From f6fb32ff8ac0d9e626141911dd242d6f1d100cda Mon Sep 17 00:00:00 2001 From: Vitor Conceicao Date: Sun, 8 Feb 2026 17:07:40 -0300 Subject: [PATCH 08/11] chore: remove dummy view controller --- Projects/App/Sources/ViewController.swift | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 Projects/App/Sources/ViewController.swift diff --git a/Projects/App/Sources/ViewController.swift b/Projects/App/Sources/ViewController.swift deleted file mode 100644 index 8ee1793..0000000 --- a/Projects/App/Sources/ViewController.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// ViewController.swift -// ExchangesApp -// -// Created by Vitor Conceicao on 06/02/26. -// - -import UIKit - -class ViewController: UIViewController { - override func viewDidLoad() { - super.viewDidLoad() - // Do any additional setup after loading the view. - view.backgroundColor = .blue - } -} From 1f5e3f022276ad5a03df79782c4097b1f71b339d Mon Sep 17 00:00:00 2001 From: Vitor Conceicao Date: Sun, 8 Feb 2026 17:08:12 -0300 Subject: [PATCH 09/11] refactor: remove coordinator from home interfaces --- Projects/Home/Interfaces/Coordinator.swift | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 Projects/Home/Interfaces/Coordinator.swift diff --git a/Projects/Home/Interfaces/Coordinator.swift b/Projects/Home/Interfaces/Coordinator.swift deleted file mode 100644 index 532f4ca..0000000 --- a/Projects/Home/Interfaces/Coordinator.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// -// Created by Vitor Conceicao. -// -// github.com/vitor-rc1 -// -// - -import UIKit - -@MainActor -public protocol Coordinator: AnyObject { - var parentCoordinator: Coordinator? { get set } - var children: [Coordinator] { get set } - var navigationController: UINavigationController { get set } - - func start() -} From 39019f78c62391883305467efa7ac7f15d10b04e Mon Sep 17 00:00:00 2001 From: Vitor Conceicao Date: Sun, 8 Feb 2026 17:51:26 -0300 Subject: [PATCH 10/11] feat: start app until home coordinator --- Projects/App/Project.swift | 10 ++++ .../App/Sources/Application/AppDelegate.swift | 2 +- .../Assemblies/CoordinatorAssembly.swift | 12 ++++- .../Sources/Coordinator/AppCoordinator.swift | 13 +++-- .../Tests/Application/AppDelegateTests.swift | 33 ++++++++++++ .../Coordinator/AppCoordinatorTests.swift | 50 +++++++++++++++++++ .../Testing/DependencyInjectorSpy.swift | 4 +- .../Home/Interfaces/HomeCoordinating.swift | 12 +++++ Projects/Home/Project.swift | 10 +++- .../Sources/Coordinator/HomeCoordinator.swift | 27 ++++++++++ .../Coordinator/HomeCoordinatingSpy.swift | 34 +++++++++++++ .../Navigation/Testing/CoordinatorSpy.swift | 10 ++-- 12 files changed, 201 insertions(+), 16 deletions(-) create mode 100644 Projects/App/Tests/Coordinator/AppCoordinatorTests.swift create mode 100644 Projects/Home/Interfaces/HomeCoordinating.swift create mode 100644 Projects/Home/Sources/Coordinator/HomeCoordinator.swift create mode 100644 Projects/Home/Testing/Coordinator/HomeCoordinatingSpy.swift diff --git a/Projects/App/Project.swift b/Projects/App/Project.swift index cdc5c12..bde3044 100644 --- a/Projects/App/Project.swift +++ b/Projects/App/Project.swift @@ -8,14 +8,24 @@ let dependecies: [TargetDependency] = [ path: "../DependencyInjection"), .project(target: "DependencyInjectionInterfaces", path: "../DependencyInjection"), + .project(target: "Home", + path: "../Home"), .project(target: "HomeInterfaces", path: "../Home"), + .project(target: "NavigationInterfaces", + path: "../Navigation"), .external(name: "Networking"), .external(name: "NetworkingInterfaces"), ] let testDependencies: [TargetDependency] = [ .target(name: moduleName), + .project(target: "DependencyInjectionTesting", + path: "../DependencyInjection"), + .project(target: "HomeTesting", + path: "../Home"), + .project(target: "NavigationTesting", + path: "../Navigation"), ] let project = Project( diff --git a/Projects/App/Sources/Application/AppDelegate.swift b/Projects/App/Sources/Application/AppDelegate.swift index 2818599..70912bf 100644 --- a/Projects/App/Sources/Application/AppDelegate.swift +++ b/Projects/App/Sources/Application/AppDelegate.swift @@ -32,7 +32,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {} @MainActor - private func setupDependencies() { + func setupDependencies() { let injector = Injector() SharedContainer.shared.setInjector(injector) diff --git a/Projects/App/Sources/Assemblies/CoordinatorAssembly.swift b/Projects/App/Sources/Assemblies/CoordinatorAssembly.swift index 1dcf7f5..6294188 100644 --- a/Projects/App/Sources/Assemblies/CoordinatorAssembly.swift +++ b/Projects/App/Sources/Assemblies/CoordinatorAssembly.swift @@ -6,8 +6,9 @@ // // import DependencyInjectionInterfaces -import NetworkingInterfaces -import Networking +import HomeInterfaces +import Home +import NavigationInterfaces import UIKit @@ -23,5 +24,12 @@ public struct CoordinatorAssembly { injector.register(AppCoordinating.self) { (_, arg: UINavigationController) in AppCoordinator(navigationController: arg) } + + injector.register(HomeCoordinating.self) { (_, arg: (UINavigationController, Coordinator?)) in + let (navigationController, parentCoordinator) = arg + + return HomeCoordinator(parentCoordinator: parentCoordinator, + navigationController: navigationController) + } } } diff --git a/Projects/App/Sources/Coordinator/AppCoordinator.swift b/Projects/App/Sources/Coordinator/AppCoordinator.swift index 8586283..124ee30 100644 --- a/Projects/App/Sources/Coordinator/AppCoordinator.swift +++ b/Projects/App/Sources/Coordinator/AppCoordinator.swift @@ -6,7 +6,9 @@ // // +import DependencyInjectionInterfaces import HomeInterfaces +import NavigationInterfaces import UIKit protocol AppCoordinating: Coordinator {} @@ -22,9 +24,12 @@ final class AppCoordinator: AppCoordinating { } func start() { -// let viewModel = HomeViewModel() -// viewModel.coordinatorDelegate = self -// let homeVC = HomeViewController(viewModel: viewModel) -// navigationController.pushViewController(homeVC, animated: false) + let resolver = SharedContainer.shared.resolver() + let arg: (UINavigationController, Coordinator?) = (navigationController, self) + + let homeCoordinator = resolver.resolve(HomeCoordinating.self, + argument: arg) + + homeCoordinator.start() } } diff --git a/Projects/App/Tests/Application/AppDelegateTests.swift b/Projects/App/Tests/Application/AppDelegateTests.swift index 6497d12..9f7cd17 100644 --- a/Projects/App/Tests/Application/AppDelegateTests.swift +++ b/Projects/App/Tests/Application/AppDelegateTests.swift @@ -9,6 +9,8 @@ import Testing import DependencyInjectionInterfaces import NetworkingInterfaces +import HomeInterfaces +import NavigationInterfaces import UIKit @testable import App @@ -16,6 +18,10 @@ import UIKit @Suite @MainActor struct AppDelegateTests { + init() { + _ = AppDelegate() + } + @Test("GIVEN AppDelegate initialize THEN it should set injector") func setInjectot() async throws { #expect(throws: Never.self) { @@ -40,4 +46,31 @@ struct AppDelegateTests { .resolve(AppCoordinating.self, argument: navigation) } } + + @Test("GIVEN AppDelegate initialize THEN it SHOULD register DI AppCoordinating") + func appCoordinatingRegister() async throws { + #expect(throws: Never.self) { + let navigation = UINavigationController() + _ = SharedContainer + .shared + .resolver() + .resolve(AppCoordinating.self, argument: navigation) + } + } + + @Test("GIVEN AppDelegate initialize THEN it SHOULD register DI HomeCoordinating") + func homeCoordinatorRegister() async throws { + #expect(throws: Never.self) { + let navigation = UINavigationController() + let parentCoordinator: Coordinator? = SharedContainer + .shared + .resolver() + .resolve(AppCoordinating.self, argument: navigation) + + _ = SharedContainer + .shared + .resolver() + .resolve(HomeCoordinating.self, argument: (navigation, parentCoordinator)) + } + } } diff --git a/Projects/App/Tests/Coordinator/AppCoordinatorTests.swift b/Projects/App/Tests/Coordinator/AppCoordinatorTests.swift new file mode 100644 index 0000000..eaa720a --- /dev/null +++ b/Projects/App/Tests/Coordinator/AppCoordinatorTests.swift @@ -0,0 +1,50 @@ +// +// +// Created by Vitor Conceicao. +// +// github.com/vitor-rc1 +// +// +// +import DependencyInjectionInterfaces +import DependencyInjectionTesting +import HomeInterfaces +import HomeTesting +import NavigationInterfaces +import NavigationTesting +import Testing +import UIKit + +@testable import App + +@MainActor +@Suite("AppCoordinator Tests") +struct AppCoordinatorTests { + + // MARK: - Start Tests + + @Test("Start resolves HomeCoordinating from container") + func startResolvesHomeCoordinator() { + // Given + let navigationController = UINavigationController() + let sut = AppCoordinator(navigationController: navigationController) + + let injectorSpy = DependencyInjectorSpy() + let homeCoordinatorSpy = HomeCoordinatingSpy( + children: [], + navigationController: navigationController + ) + + injectorSpy + .register(HomeCoordinating.self) { (_, arg: (UINavigationController, Coordinator?)) in + homeCoordinatorSpy + } + + SharedContainer.shared.setInjector(injectorSpy) + + sut.start() + + #expect(injectorSpy.calledMethods.contains(.resolveWithArgument)) + #expect(homeCoordinatorSpy.calledMethods.contains(.start)) + } +} diff --git a/Projects/DependencyInjection/Testing/DependencyInjectorSpy.swift b/Projects/DependencyInjection/Testing/DependencyInjectorSpy.swift index 83f8f42..fb1510c 100644 --- a/Projects/DependencyInjection/Testing/DependencyInjectorSpy.swift +++ b/Projects/DependencyInjection/Testing/DependencyInjectorSpy.swift @@ -11,7 +11,7 @@ import Foundation public final class DependencyInjectorSpy: DependencyInjector { public init() {} - enum Method { + public enum Method { case register case registerWithArgument case resolve @@ -22,7 +22,7 @@ public final class DependencyInjectorSpy: DependencyInjector { private var registeredServices: [String: Any] = [:] private var registeredServicesWithArgument: [String: Any] = [:] - var calledMethods: [Method] = [] + public var calledMethods: [Method] = [] private func typeIdentifier(_ type: T.Type) -> String { return String(reflecting: type) diff --git a/Projects/Home/Interfaces/HomeCoordinating.swift b/Projects/Home/Interfaces/HomeCoordinating.swift new file mode 100644 index 0000000..651ad83 --- /dev/null +++ b/Projects/Home/Interfaces/HomeCoordinating.swift @@ -0,0 +1,12 @@ +// +// +// Created by Vitor Conceicao. +// +// github.com/vitor-rc1 +// +// + +import NavigationInterfaces +import UIKit + +public protocol HomeCoordinating: Coordinator {} diff --git a/Projects/Home/Project.swift b/Projects/Home/Project.swift index ef59de8..b1e2751 100644 --- a/Projects/Home/Project.swift +++ b/Projects/Home/Project.swift @@ -11,6 +11,12 @@ let dependecies: [TargetDependency] = [ .external(name: "NetworkingInterfaces"), ] +let interfaceDependecies: [TargetDependency] = [ + .project(target: "NavigationInterfaces", + path: "../Navigation"), +] + let project = Project.templateModule(named: moduleName, - targets: [.source,. interfaces, .test], - dependencies: dependecies) + targets: [.source,. interfaces, .test, .testing], + dependencies: dependecies, + interfaceDependecies: interfaceDependecies) diff --git a/Projects/Home/Sources/Coordinator/HomeCoordinator.swift b/Projects/Home/Sources/Coordinator/HomeCoordinator.swift new file mode 100644 index 0000000..718b789 --- /dev/null +++ b/Projects/Home/Sources/Coordinator/HomeCoordinator.swift @@ -0,0 +1,27 @@ +// +// +// Created by Vitor Conceicao. +// +// github.com/vitor-rc1 +// +// + +import HomeInterfaces +import NavigationInterfaces +import UIKit + +public final class HomeCoordinator: HomeCoordinating { + public var parentCoordinator: (Coordinator)? + public var children: [Coordinator] = [] + public var navigationController: UINavigationController + + public init(parentCoordinator: Coordinator? = nil, + navigationController: UINavigationController) { + self.parentCoordinator = parentCoordinator + self.navigationController = navigationController + } + + public func start() { + + } +} diff --git a/Projects/Home/Testing/Coordinator/HomeCoordinatingSpy.swift b/Projects/Home/Testing/Coordinator/HomeCoordinatingSpy.swift new file mode 100644 index 0000000..218d4cf --- /dev/null +++ b/Projects/Home/Testing/Coordinator/HomeCoordinatingSpy.swift @@ -0,0 +1,34 @@ +// +// +// Created by Vitor Conceicao. +// +// github.com/vitor-rc1 +// +// + +import HomeInterfaces +import NavigationInterfaces +import UIKit + +public final class HomeCoordinatingSpy: HomeCoordinating { + public enum Method { + case start + } + + public var calledMethods: [Method] = [] + public var parentCoordinator: Coordinator? + public var children: [Coordinator] = [] + public var navigationController: UINavigationController + + public init(parentCoordinator: Coordinator? = nil, + children: [Coordinator], + navigationController: UINavigationController) { + self.parentCoordinator = parentCoordinator + self.children = children + self.navigationController = navigationController + } + + public func start() { + calledMethods.append(.start) + } +} diff --git a/Projects/Navigation/Testing/CoordinatorSpy.swift b/Projects/Navigation/Testing/CoordinatorSpy.swift index 3f6ef91..2c0ee6f 100644 --- a/Projects/Navigation/Testing/CoordinatorSpy.swift +++ b/Projects/Navigation/Testing/CoordinatorSpy.swift @@ -9,15 +9,15 @@ import NavigationInterfaces import UIKit -final class CoordinatorSpy: Coordinator { +public final class CoordinatorSpy: Coordinator { public enum Method { case start } public var calledMethods: [Method] = [] - var parentCoordinator: Coordinator? - var children: [Coordinator] = [] - var navigationController: UINavigationController + public var parentCoordinator: Coordinator? + public var children: [Coordinator] = [] + public var navigationController: UINavigationController public init(parentCoordinator: Coordinator? = nil, children: [Coordinator], @@ -27,7 +27,7 @@ final class CoordinatorSpy: Coordinator { self.navigationController = navigationController } - func start() { + public func start() { calledMethods.append(.start) } } From 425290c36a412f45655cd3e47f3a16f0c47a634b Mon Sep 17 00:00:00 2001 From: Vitor Conceicao Date: Sun, 8 Feb 2026 17:54:35 -0300 Subject: [PATCH 11/11] chore: add place holder to home tests --- Projects/Home/Tests/TestPlaceHolder.swift | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Projects/Home/Tests/TestPlaceHolder.swift diff --git a/Projects/Home/Tests/TestPlaceHolder.swift b/Projects/Home/Tests/TestPlaceHolder.swift new file mode 100644 index 0000000..e69de29