From a81a1cf1b232c8a057b1a674f166fb148105c993 Mon Sep 17 00:00:00 2001 From: Alex Usbergo Date: Mon, 23 Mar 2020 08:26:51 +0100 Subject: [PATCH] Adds tests for child store --- Sources/Store/Store.swift | 23 ++++++++++++--------- Tests/StoreTests/CombinedStores.swift | 29 +++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/Sources/Store/Store.swift b/Sources/Store/Store.swift index c239369..c36f0ee 100644 --- a/Sources/Store/Store.swift +++ b/Sources/Store/Store.swift @@ -37,8 +37,11 @@ public protocol StoreProtocol: AnyStoreProtocol { } open class Store: StoreProtocol, ObservableObject { + /// A publisher that emits before the object has changed. + public let objectWillChange = ObservableObjectPublisher() + /// The current state of this store. - @Published public private(set) var model: M + public private(set) var model: M /// Opaque reference to the model wrapped by this store. public var opaqueModelRef: Any { model } @@ -49,11 +52,9 @@ open class Store: StoreProtocol, ObservableObject { /// The parent store. public var parent: AnyStoreProtocol? - /// Synchronizes the access to the state object. private var _stateLock = SpinLock() - - /// All of the children observers. private var _childrenBag = Array() + private var _reduceParent: ((M) -> Void)? public init(model: M) { self.model = model @@ -69,12 +70,15 @@ open class Store: StoreProtocol, ObservableObject { _onMainThread { self.model = new } - didUpdateModel(transaction: transaction, old: old, new: new) self._stateLock.unlock() + didUpdateModel(transaction: transaction, old: old, new: new) } + /// Emits the `objectWillChange` event and propage the changes to its parent. + /// - note: Call `super` implementation if you override this function. open func didUpdateModel(transaction: TransactionProtocol?, old: M, new: M) { - // Subclasses to override this. + _reduceParent?(new) + notifyObservers() } /// Notify the store observers for the change of this store. @@ -118,10 +122,11 @@ open class Store: StoreProtocol, ObservableObject { ) -> Store { let childStore = create(model[keyPath: keyPath]); childStore.parent = self - let cancellable = childStore.objectWillChange.sink { - self.reduceModel { $0[keyPath: keyPath] = childStore.model } + childStore._reduceParent = { child in + self.reduceModel { parent in + parent[keyPath: keyPath] = child + } } - _childrenBag.append(cancellable) return childStore } diff --git a/Tests/StoreTests/CombinedStores.swift b/Tests/StoreTests/CombinedStores.swift index 8b05f7c..4da5308 100644 --- a/Tests/StoreTests/CombinedStores.swift +++ b/Tests/StoreTests/CombinedStores.swift @@ -39,3 +39,32 @@ extension Root.Note { } } } + +@available(iOS 13.0, macOS 10.15, watchOS 6.0, tvOS 13.0, *) +final class CombinedStoreTests: XCTestCase { + + var sink: AnyCancellable? + + func testChildStoreChangesRootStoreValue() { + let rootStore = RootStore(model: Root()) + XCTAssertFalse(rootStore.model.todo.done) + XCTAssertFalse(rootStore.todoStore.model.done) + rootStore.todoStore.run(action: Root.Todo.Action_MarkAsDone(), mode: .sync) + XCTAssertTrue(rootStore.todoStore.model.done) + XCTAssertTrue(rootStore.model.todo.done) + } + + func testChildStoreChangesTriggersRootObserver() { + let observerExpectation = expectation(description: "Observer called.") + let rootStore = RootStore(model: Root()) + sink = rootStore.objectWillChange.sink { + XCTAssertTrue(rootStore.model.todo.done) + XCTAssertTrue(rootStore.todoStore.model.done) + observerExpectation.fulfill() + } + rootStore.todoStore.run(action: Root.Todo.Action_MarkAsDone(), mode: .sync) + XCTAssertTrue(rootStore.todoStore.model.done) + XCTAssertTrue(rootStore.model.todo.done) + waitForExpectations(timeout: 1) + } +}