From 1cf56fe829284c7304cf2f39810afa52f223c683 Mon Sep 17 00:00:00 2001 From: Anton Efimenko Date: Sun, 12 Mar 2017 14:47:37 +0300 Subject: [PATCH] Fix setBy when CompositeAction used Now if used Composite action in state.setBy will be displayed actual action, not CompositeAction --- RxDataFlow/RxDataFlowController.swift | 23 +++++++------- RxDataFlowTests/CompositeActionsTests.swift | 35 +++++++++++++++++++++ 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/RxDataFlow/RxDataFlowController.swift b/RxDataFlow/RxDataFlowController.swift index 4dc5248..7e57a44 100644 --- a/RxDataFlow/RxDataFlowController.swift +++ b/RxDataFlow/RxDataFlowController.swift @@ -89,31 +89,32 @@ public final class RxDataFlowController : RxDataFlowControll actionsQueue.currentItemSubject.observeOn(scheduler) .flatMap { [weak self] action -> Observable in guard let object = self else { return Observable.empty() } - - return object.observe(action: action) + + return object.observe(action: action) }.subscribe().addDisposableTo(bag) } - + private func observe(action: RxActionType) -> Observable { let object = self - let handle: Observable = { + let handle: Observable<(setBy: RxActionType, state: RxStateType)> = { guard let compositeAction = action as? RxCompositeAction else { - return Observable.from([action], scheduler: action.scheduler ?? object.scheduler) - .flatMap { a -> Observable in object.reducer.handle(action, flowController: object).subscribeOn(action.scheduler ?? object.scheduler) } + return Observable.from([action], scheduler: action.scheduler ?? object.scheduler) + .flatMap { act in object.reducer.handle(act, flowController: object).subscribeOn(act.scheduler ?? object.scheduler) } + .flatMap { result -> Observable<(setBy: RxActionType, state: RxStateType)> in return .just((setBy: action, state: result)) } } return object.observe(compositeAction: compositeAction) }() return handle - .do(onNext: { object.currentStateSubject.onNext((setBy: action, state: $0 as! State)) }, + .do(onNext: { object.currentStateSubject.onNext((setBy: $0.setBy, state: $0.state as! State)) }, onError: { object.errorsSubject.onNext((state: object.currentState.state, action: action, error: $0)) }, onDispose: { _ in _ = object.actionsQueue.dequeue() }) - .flatMap { result -> Observable in .just(result) } + .flatMap { result -> Observable in .just(result.state) } .catchErrorJustReturn(nil) .flatMap { _ in return Observable.just() } } - func observe(compositeAction action: RxCompositeAction) -> Observable { + func observe(compositeAction action: RxCompositeAction) -> Observable<(setBy: RxActionType, state: RxStateType)> { return Observable.create { [weak self] observer in guard let object = self else { return Disposables.create() } @@ -122,8 +123,8 @@ public final class RxDataFlowController : RxDataFlowControll let disposable = compositeQueue.currentItemSubject.observeOn(object.scheduler).flatMap { action -> Observable in return Observable.create { _ in let subscribsion = Observable.from([action], scheduler: action.scheduler ?? object.scheduler) - .flatMap { a -> Observable in object.reducer.handle(action, flowController: object).subscribeOn(action.scheduler ?? object.scheduler) } - .do(onNext: { observer.onNext($0) }, + .flatMap { act -> Observable in object.reducer.handle(act, flowController: object).subscribeOn(act.scheduler ?? object.scheduler) } + .do(onNext: { observer.onNext((setBy: action, state: $0)) }, onError: { observer.onError($0) }, onCompleted: { _ = compositeQueue.dequeue() }, onDispose: { if compositeQueue.count == 0 { observer.onCompleted() } }) diff --git a/RxDataFlowTests/CompositeActionsTests.swift b/RxDataFlowTests/CompositeActionsTests.swift index c45da10..fec5228 100644 --- a/RxDataFlowTests/CompositeActionsTests.swift +++ b/RxDataFlowTests/CompositeActionsTests.swift @@ -39,6 +39,41 @@ class CompositeActions: XCTestCase { XCTAssertEqual(expectedStateHistoryTextValues, store.stateStack.array.flatMap { $0 }.map { $0.state.text }) } + func testCorrectSetByAction() { + let store = RxDataFlowController(reducer: TestStoreReducer(), + initialState: TestState(text: "Initial value")) + + let completeExpectation = expectation(description: "Should perform all non-error actions") + _ = store.state.filter { $0.setBy is CompletionAction }.subscribe(onNext: { next in + completeExpectation.fulfill() + }) + + let changeTextValueActionExpectation = expectation(description: "Should perform ChangeTextValueAction with correct setBy") + let customDescriptorActionExpectation = expectation(description: "Should perform CustomDescriptorAction with correct setBy") + _ = store.state.subscribe(onNext: { next in + if next.setBy is ChangeTextValueAction { + changeTextValueActionExpectation.fulfill() + } else if next.setBy is CustomDescriptorAction { + customDescriptorActionExpectation.fulfill() + } + }) + + let action = RxCompositeAction(actions: [ChangeTextValueAction(newText: "Action 1 executed"), + CustomDescriptorAction(scheduler: nil, descriptor: .just((TestState(text: "Action 2 executed"))))]) + store.dispatch(action) + store.dispatch(CompletionAction()) + + waitForExpectations(timeout: 1, handler: nil) + + let expectedStateHistoryTextValues = ["Initial value", + "Action 1 executed", + "Action 2 executed", + "Completed"] + + XCTAssertEqual(expectedStateHistoryTextValues, store.stateStack.array.flatMap { $0 }.map { $0.state.text }) + XCTAssertTrue(store.currentState.setBy is CompletionAction) + } + func testCompositeActionStopIfErrorOccurred() { let store = RxDataFlowController(reducer: TestStoreReducer(), initialState: TestState(text: "Initial value"))