diff --git a/Sources/Store/SerializableStore.swift b/Sources/Store/SerializableStore.swift index 2ba0ae8..847cfbc 100644 --- a/Sources/Store/SerializableStore.swift +++ b/Sources/Store/SerializableStore.swift @@ -46,6 +46,7 @@ open class SerializableStore: Store { } override open func didUpdateModel(transaction: TransactionProtocol?, old: M, new: M) { + super.didUpdateModel(transaction: transaction, old: old, new: new) guard let transaction = transaction else { return } @@ -87,6 +88,15 @@ open class SerializableStore: Store { } } + /// Creates a store for a subtree of the wrapped model. + /// Similar to Redux `combineStores`. + public func makeChildSerializableStore( + keyPath: WritableKeyPath, + create: (M_1) -> SerializableStore = { SerializableStore(model: $0) } + ) -> SerializableStore { + super.makeChildStore(keyPath: keyPath, create: create) as! SerializableStore + } + // MARK: - Model Encode/Decode /// Encodes the model into a dictionary. diff --git a/Tests/StoreTests/CombinedStores.swift b/Tests/StoreTests/CombinedStores.swift index ba3512a..4bc1a93 100644 --- a/Tests/StoreTests/CombinedStores.swift +++ b/Tests/StoreTests/CombinedStores.swift @@ -2,13 +2,13 @@ import XCTest import Combine @testable import Store -struct Root { - struct Todo { +struct Root: Codable { + struct Todo: Codable { var name: String = "Untitled" var description: String = "N/A" var done: Bool = false } - struct Note { + struct Note: Codable { var author: String = "Nobody" var text: String = "" var upvotes: Int = 0 @@ -19,19 +19,20 @@ struct Root { var list: [Todo] = [] } -class RootStore: Store { +class RootStore: SerializableStore { // Children test. - lazy var todoStore = makeChildStore(keyPath: \.todo) - lazy var noteStore = makeChildStore(keyPath: \.note) + lazy var todoStore = makeChildSerializableStore(keyPath: \.todo) + lazy var noteStore = makeChildSerializableStore(keyPath: \.note) // List and transient store tests. - lazy var listStore = makeChildStore(keyPath: \.list) + lazy var listStore = makeChildSerializableStore(keyPath: \.list) } // MARK: Combined Stores extension Root.Todo { struct Action_MarkAsDone: ActionProtocol { + let id = "mark_as_done" func reduce(context: TransactionContext, Self>) { defer { context.fulfill() } context.reduceModel { $0.done = true } @@ -41,6 +42,7 @@ extension Root.Todo { extension Root.Note { struct Action_IncreaseUpvotes: ActionProtocol { + let id = "increase_upvotes" func reduce(context: TransactionContext, Self>) { defer { context.fulfill() } context.reduceModel { $0.upvotes += 1 } @@ -52,6 +54,7 @@ extension Root.Note { extension Root.Todo { struct Action_ListCreateNew: ActionProtocol { + let id = "list_create_new" let name: String let description: String func reduce(context: TransactionContext>, Self>) { @@ -73,6 +76,11 @@ final class CombinedStoreTests: XCTestCase { func testChildStoreChangesRootStoreValue() { let rootStore = RootStore(model: Root()) + rootStore.register(middleware: LoggerMiddleware()) + rootStore.todoStore.register(middleware: LoggerMiddleware()) + rootStore.noteStore.register(middleware: LoggerMiddleware()) + rootStore.listStore.register(middleware: LoggerMiddleware()) + XCTAssertFalse(rootStore.model.todo.done) XCTAssertFalse(rootStore.todoStore.model.done) rootStore.todoStore.run(action: Root.Todo.Action_MarkAsDone(), mode: .sync) @@ -83,6 +91,11 @@ final class CombinedStoreTests: XCTestCase { func testChildStoreChangesTriggersRootObserver() { let observerExpectation = expectation(description: "Observer called.") let rootStore = RootStore(model: Root()) + rootStore.register(middleware: LoggerMiddleware()) + rootStore.todoStore.register(middleware: LoggerMiddleware()) + rootStore.noteStore.register(middleware: LoggerMiddleware()) + rootStore.listStore.register(middleware: LoggerMiddleware()) + sink = rootStore.objectWillChange.sink { XCTAssertTrue(rootStore.model.todo.done) XCTAssertTrue(rootStore.todoStore.model.done) @@ -99,6 +112,9 @@ final class CombinedStoreTests: XCTestCase { rootStore.listStore.run( action: Root.Todo.Action_ListCreateNew(name: "New", description: "New"), mode: .sync) + rootStore.register(middleware: LoggerMiddleware()) + rootStore.listStore.register(middleware: LoggerMiddleware()) + XCTAssertTrue(rootStore.listStore.model.count == 1) XCTAssertTrue(rootStore.listStore.model[0].name == "New") XCTAssertTrue(rootStore.listStore.model[0].description == "New") @@ -108,7 +124,9 @@ final class CombinedStoreTests: XCTestCase { XCTAssertTrue(rootStore.model.list[0].description == "New") XCTAssertTrue(rootStore.model.list[0].done == false) - let todoStore: Store = rootStore.listStore.makeChildStore(keyPath: \.[0]) + let todoStore: Store = rootStore.listStore.makeChildSerializableStore(keyPath: \.[0]) + todoStore.register(middleware: LoggerMiddleware()) + todoStore.run(action: Root.Todo.Action_MarkAsDone(), mode: .sync) XCTAssertTrue(todoStore.model.name == "New") XCTAssertTrue(todoStore.model.description == "New") diff --git a/Tests/StoreTests/Support/TestModel.swift b/Tests/StoreTests/Support/TestModel.swift index 47c13b9..f0e1743 100644 --- a/Tests/StoreTests/Support/TestModel.swift +++ b/Tests/StoreTests/Support/TestModel.swift @@ -27,11 +27,11 @@ enum Action: ActionProtocol { var id: String { switch self { - case .increase(_): return "INCREASE" - case .throttleIncrease(_): return "THROTTLE_INCREASE" - case .decrease(_): return "DECREASE" - case .updateLabel(_): return "UPDATE_LABEL" - case .setArray(_): return "SET_ARRAY" + case .increase(_): return "increase" + case .throttleIncrease(_): return "throttle_increase" + case .decrease(_): return "decrease" + case .updateLabel(_): return "update_label" + case .setArray(_): return "set_array" } }