diff --git a/Apps/Locations/Libraries/Package.swift b/Apps/Locations/Libraries/Package.swift index 396d480..6c5bf79 100644 --- a/Apps/Locations/Libraries/Package.swift +++ b/Apps/Locations/Libraries/Package.swift @@ -11,7 +11,9 @@ let package = Package( .library( name: "Libraries", targets: [ - "Locations" + "Dependency", + "Locations", + "Persistence" ] ), ], @@ -21,6 +23,10 @@ let package = Package( name: "APICore", dependencies: [] ), + .target( + name: "Dependency", + dependencies: [] + ), .target( name: "Locations", dependencies: [ @@ -37,6 +43,12 @@ let package = Package( "APICore" ] ), + .testTarget( + name: "DependencyTests", + dependencies: [ + "Dependency" + ] + ), .testTarget( name: "LocationsTests", dependencies: [ diff --git a/Apps/Locations/Libraries/Sources/Dependency/Property Wrappers/Dependency.swift b/Apps/Locations/Libraries/Sources/Dependency/Property Wrappers/Dependency.swift new file mode 100644 index 0000000..83b9eda --- /dev/null +++ b/Apps/Locations/Libraries/Sources/Dependency/Property Wrappers/Dependency.swift @@ -0,0 +1,31 @@ +// +// Dependency.swift +// Dependency +// +// Created by Javier Cicchelli on 11/04/2023. +// Copyright © 2023 Röck+Cöde. All rights reserved. +// + +/// This property wrapper provides a direct connection to the `DependencyService` service. +@propertyWrapper +public struct Dependency { + + // MARK: Properties + + private let keyPath: WritableKeyPath + + /// This property allows direct read/write access to a defined dependency attached to a selected key path. + public var wrappedValue: D { + get { DependencyService[keyPath] } + set { DependencyService[keyPath] = newValue } + } + + // MARK: Initialisers + + /// Initialise the property wrapper by setting a key path to a defined dependency. + /// - Parameter keyPath: A key path to a defined dependency in the `DependencyService` service. + public init(_ keyPath: WritableKeyPath) { + self.keyPath = keyPath + } + +} diff --git a/Apps/Locations/Libraries/Sources/Dependency/Protocols/DependencyKey.swift b/Apps/Locations/Libraries/Sources/Dependency/Protocols/DependencyKey.swift new file mode 100644 index 0000000..8f5fb08 --- /dev/null +++ b/Apps/Locations/Libraries/Sources/Dependency/Protocols/DependencyKey.swift @@ -0,0 +1,22 @@ +// +// DependencyKey.swift +// Dependency +// +// Created by Javier Cicchelli on 11/04/2023. +// Copyright © 2023 Röck+Cöde. All rights reserved. +// + +/// This protocol defines a key to use in the dependency service. +public protocol DependencyKey { + + // MARK: Associated types + + /// The associated type representing the type of the dependency key's value. + associatedtype Value + + // MARK: Properties + + /// The default value for the dependency key. + static var currentValue: Value { get set } + +} diff --git a/Apps/Locations/Libraries/Sources/Dependency/Services/DependencyService.swift b/Apps/Locations/Libraries/Sources/Dependency/Services/DependencyService.swift new file mode 100644 index 0000000..3716c42 --- /dev/null +++ b/Apps/Locations/Libraries/Sources/Dependency/Services/DependencyService.swift @@ -0,0 +1,28 @@ +// +// DependencyService.swift +// Dependency +// +// Created by Javier Cicchelli on 11/04/2023. +// Copyright © 2023 Röck+Cöde. All rights reserved. +// + +/// This service provide write/read access to the injected dependencies. +public struct DependencyService { + + // MARK: Properties + + private static var current = DependencyService() + + // MARK: Subscripts + + public static subscript(key: DK.Type) -> DK.Value { + get { key.currentValue } + set { key.currentValue = newValue } + } + + public static subscript(_ keyPath: WritableKeyPath) -> D { + get { current[keyPath: keyPath] } + set { current[keyPath: keyPath] = newValue } + } + +} diff --git a/Apps/Locations/Libraries/Tests/DependencyTests/Helpers/TestServices.swift b/Apps/Locations/Libraries/Tests/DependencyTests/Helpers/TestServices.swift new file mode 100644 index 0000000..2f19465 --- /dev/null +++ b/Apps/Locations/Libraries/Tests/DependencyTests/Helpers/TestServices.swift @@ -0,0 +1,34 @@ +// +// TestServices.swift +// DependencyTests +// +// Created by Javier Cicchelli on 11/04/2023. +// Copyright © 2023 Röck+Cöde. All rights reserved. +// + +import Dependency + +// MARK: - Protocols + +protocol TestService {} + +// MARK: - Services + +struct SomeService: TestService, Equatable {} +struct SomeOtherService: TestService, Equatable {} + +// MARK: - DependencyKey + +struct TestServiceKey: DependencyKey { + static var currentValue: TestService = SomeService() +} + +// MARK: - DependencyService+Keys + +extension DependencyService { + var testService: TestService { + get { Self[TestServiceKey.self] } + set { Self[TestServiceKey.self] = newValue } + } +} + diff --git a/Apps/Locations/Libraries/Tests/DependencyTests/Property Wrappers/DependencyTests.swift b/Apps/Locations/Libraries/Tests/DependencyTests/Property Wrappers/DependencyTests.swift new file mode 100644 index 0000000..7daf5ab --- /dev/null +++ b/Apps/Locations/Libraries/Tests/DependencyTests/Property Wrappers/DependencyTests.swift @@ -0,0 +1,74 @@ +// +// DependencyTests.swift +// DependencyTests +// +// Created by Javier Cicchelli on 11/04/2023. +// Copyright © 2023 Röck+Cöde. All rights reserved. +// + +import XCTest + +@testable import Dependency + +final class DependencyTests: XCTestCase { + + // MARK: Properties + + private var subject: TestSubject! + + // MARK: Setup + + override func setUp() { + DependencyService[\.testService] = SomeService() + } + + // MARK: Tests + + func test_readTestService() { + // GIVEN + subject = .init() + + // WHEN + let service = subject.testService + + // THEN + XCTAssertNotNil(service) + XCTAssert(service is SomeService) + } + + func test_writeDependencyKey() async throws { + // GIVEN + subject = .init() + + subject.testService = SomeOtherService() + + // WHEN + let service = DependencyService[\.testService] + + // THEN + XCTAssertNotNil(service) + XCTAssert(service is SomeOtherService) + } + + func test_writeDependencyKeyTwice() async throws { + // GIVEN + subject = .init() + + subject.testService = SomeOtherService() + subject.testService = SomeService() + + // WHEN + let service = DependencyService[\.testService] + + // THEN + XCTAssertNotNil(service) + XCTAssert(service is SomeService) + } + +} + +// MARK: - TestSubject + +private struct TestSubject { + @Dependency(\.testService) var testService +} diff --git a/Apps/Locations/Libraries/Tests/DependencyTests/Services/DependencyServiceTests.swift b/Apps/Locations/Libraries/Tests/DependencyTests/Services/DependencyServiceTests.swift new file mode 100644 index 0000000..186931a --- /dev/null +++ b/Apps/Locations/Libraries/Tests/DependencyTests/Services/DependencyServiceTests.swift @@ -0,0 +1,58 @@ +// +// DependencyServiceTests.swift +// DependencyTests +// +// Created by Javier Cicchelli on 11/04/2023. +// Copyright © 2023 Röck+Cöde. All rights reserved. +// + +import XCTest + +@testable import Dependency + +final class DependencyServiceTests: XCTestCase { + + // MARK: Setup + + override func setUp() { + DependencyService[\.testService] = SomeService() + } + + // MARK: Tests + + func test_readDependencyKey() async throws { + // GIVEN + // WHEN + let service = DependencyService[\.testService] + + // THEN + XCTAssertNotNil(service) + XCTAssert(service is SomeService) + } + + func test_writeDependencyKey() async throws { + // GIVEN + DependencyService[\.testService] = SomeOtherService() + + // WHEN + let service = DependencyService[\.testService] + + // THEN + XCTAssertNotNil(service) + XCTAssert(service is SomeOtherService) + } + + func test_writeDependencyKeyTwice() async throws { + // GIVEN + DependencyService[\.testService] = SomeOtherService() + DependencyService[\.testService] = SomeService() + + // WHEN + let service = DependencyService[\.testService] + + // THEN + XCTAssertNotNil(service) + XCTAssert(service is SomeService) + } + +}