diff --git a/Sources/CacheStore/Stores/CacheStore.swift b/Sources/CacheStore/Stores/CacheStore/CacheStore.swift similarity index 99% rename from Sources/CacheStore/Stores/CacheStore.swift rename to Sources/CacheStore/Stores/CacheStore/CacheStore.swift index c62efd0..243401c 100644 --- a/Sources/CacheStore/Stores/CacheStore.swift +++ b/Sources/CacheStore/Stores/CacheStore/CacheStore.swift @@ -168,7 +168,7 @@ public extension CacheStore { /// Creates a `ScopedCacheStore` with the given key transformation and default cache func scope( - keyTransformation: c.BiDirectionalTransformation, + keyTransformation: BiDirectionalTransformation, defaultCache: [ScopedKey: Any] = [:] ) -> CacheStore { let scopedCacheStore = ScopedCacheStore(keyTransformation: keyTransformation) diff --git a/Sources/CacheStore/Stores/ScopedStores/ScopedCacheStore.swift b/Sources/CacheStore/Stores/CacheStore/ScopedCacheStore.swift similarity index 80% rename from Sources/CacheStore/Stores/ScopedStores/ScopedCacheStore.swift rename to Sources/CacheStore/Stores/CacheStore/ScopedCacheStore.swift index af68dae..492ccb1 100644 --- a/Sources/CacheStore/Stores/ScopedStores/ScopedCacheStore.swift +++ b/Sources/CacheStore/Stores/CacheStore/ScopedCacheStore.swift @@ -1,11 +1,13 @@ import c +public typealias BiDirectionalTransformation = c.BiDirectionalTransformation + class ScopedCacheStore: CacheStore { weak var parentCacheStore: CacheStore? - private var keyTransformation: c.BiDirectionalTransformation + private var keyTransformation: BiDirectionalTransformation init( - keyTransformation: c.BiDirectionalTransformation + keyTransformation: BiDirectionalTransformation ) { self.keyTransformation = keyTransformation diff --git a/Sources/CacheStore/Stores/Store/Content/Store+StoreContent.swift b/Sources/CacheStore/Stores/Store/Content/Store+StoreContent.swift new file mode 100644 index 0000000..372d9b2 --- /dev/null +++ b/Sources/CacheStore/Stores/Store/Content/Store+StoreContent.swift @@ -0,0 +1,15 @@ +import c + +public extension Store { + /// Create a StoreContent for the provided content type + func content( + using contentType: Content.Type = Content.self + ) -> Content where Content.Key == Key { + contentType.init( + store: actionlessScope( + keyTransformation: (from: { $0 }, to: { $0 }), + dependencyTransformation: { _ in () } + ) + ) + } +} diff --git a/Sources/CacheStore/Stores/Store/Content/StoreContent.swift b/Sources/CacheStore/Stores/Store/Content/StoreContent.swift new file mode 100644 index 0000000..b9d56b9 --- /dev/null +++ b/Sources/CacheStore/Stores/Store/Content/StoreContent.swift @@ -0,0 +1,7 @@ +/// The content a StoreView uses when creating SwiftUI views +public protocol StoreContent { + associatedtype Key: Hashable + + /// Creates the content from an actionless store that has a Void dependency + init(store: Store) +} diff --git a/Sources/CacheStore/Stores/Store/Content/StoreView.swift b/Sources/CacheStore/Stores/Store/Content/StoreView.swift new file mode 100644 index 0000000..d17363a --- /dev/null +++ b/Sources/CacheStore/Stores/Store/Content/StoreView.swift @@ -0,0 +1,24 @@ +import SwiftUI + +/// SwiftUI View that uses a Store and StoreContent +public protocol StoreView: View { + /// Key for the Store + associatedtype Key: Hashable + /// Action for the Store + associatedtype Action + /// Dependency for the Store + associatedtype Dependency + /// The content the View cares about and uses + associatedtype Content: StoreContent + + /// An `ObservableObject` that uses actions to modify the state which is a `CacheStore` + var store: Store { get set } + /// The content a StoreView uses when creating SwiftUI views + var content: Content { get } + + init(store: Store) +} + +public extension StoreView where Content.Key == Key { + var content: Content { store.content() } +} diff --git a/Sources/CacheStore/Stores/ScopedStores/ScopedStore.swift b/Sources/CacheStore/Stores/Store/ScopedStore.swift similarity index 100% rename from Sources/CacheStore/Stores/ScopedStores/ScopedStore.swift rename to Sources/CacheStore/Stores/Store/ScopedStore.swift diff --git a/Sources/CacheStore/Stores/Store.swift b/Sources/CacheStore/Stores/Store/Store.swift similarity index 97% rename from Sources/CacheStore/Stores/Store.swift rename to Sources/CacheStore/Stores/Store/Store.swift index 462f76c..ea72ee6 100644 --- a/Sources/CacheStore/Stores/Store.swift +++ b/Sources/CacheStore/Stores/Store/Store.swift @@ -139,7 +139,7 @@ public class Store: ObservableObject, ActionH /// Creates a `ScopedStore` public func scope( - keyTransformation: c.BiDirectionalTransformation, + keyTransformation: BiDirectionalTransformation, actionHandler: StoreActionHandler, dependencyTransformation: (Dependency) -> ScopedDependency, defaultCache: [ScopedKey: Any] = [:], @@ -182,7 +182,7 @@ public class Store: ObservableObject, ActionH /// Creates an Actionless `ScopedStore` public func actionlessScope( - keyTransformation: c.BiDirectionalTransformation, + keyTransformation: BiDirectionalTransformation, dependencyTransformation: (Dependency) -> ScopedDependency, defaultCache: [ScopedKey: Any] = [:] ) -> Store { @@ -225,7 +225,7 @@ public class Store: ObservableObject, ActionH public extension Store where Dependency == Void { /// Creates a `ScopedStore` func scope( - keyTransformation: c.BiDirectionalTransformation, + keyTransformation: BiDirectionalTransformation, actionHandler: StoreActionHandler, defaultCache: [ScopedKey: Any] = [:], actionTransformation: @escaping (ScopedAction?) -> Action? = { _ in nil } @@ -241,7 +241,7 @@ public extension Store where Dependency == Void { /// Creates a `ScopedStore` func actionlessScope( - keyTransformation: c.BiDirectionalTransformation, + keyTransformation: BiDirectionalTransformation, defaultCache: [ScopedKey: Any] = [:] ) -> Store { actionlessScope( diff --git a/Sources/CacheStore/Stores/TestStore.swift b/Sources/CacheStore/Stores/Store/TestStore.swift similarity index 86% rename from Sources/CacheStore/Stores/TestStore.swift rename to Sources/CacheStore/Stores/Store/TestStore.swift index 46765e8..d4fe7d7 100644 --- a/Sources/CacheStore/Stores/TestStore.swift +++ b/Sources/CacheStore/Stores/Store/TestStore.swift @@ -136,6 +136,28 @@ public class TestStore { send(nextAction, file: file, line: line, expecting: expecting) } + /// Create a StoreContent for the provided content type and make assertions in the expecting closure about the content + public func content( + using contentType: Content.Type = Content.self, + expecting: @escaping (Content) throws -> Void, + file: StaticString = #filePath, + line: UInt = #line + ) where Content.Key == Key { + do { + try expecting(content(using: contentType)) + } catch { + TestStoreFailure.handler("❌ Expectation failed: \(error)", file, line) + return + } + } + + /// Create a StoreContent for the provided content type + public func content( + using contentType: Content.Type = Content.self + ) -> Content where Content.Key == Key { + store.content(using: contentType) + } + /// Checks to make sure the cache has the required keys, otherwise it will fail func require( keys: Set, diff --git a/Tests/CacheStoreTests/CacheStoreTests.swift b/Tests/CacheStoreTests/CacheStoreTests.swift index 1bba498..83a369b 100644 --- a/Tests/CacheStoreTests/CacheStoreTests.swift +++ b/Tests/CacheStoreTests/CacheStoreTests.swift @@ -141,7 +141,7 @@ final class CacheStoreTests: XCTestCase { } let scopedCacheStore: CacheStore = store.scope( - keyTransformation: c.transformer( + keyTransformation: ( from: { global in switch global { case .b: return .b @@ -190,7 +190,7 @@ final class CacheStoreTests: XCTestCase { let storeOldScopeValue: String = scopedCacheStore.resolve(.b) let newlyScopedCacheStore: CacheStore = store.scope( - keyTransformation: c.transformer( + keyTransformation: ( from: { global in switch global { case .b: return .b