diff --git a/README.md b/README.md index 8842dfb..3f4054c 100644 --- a/README.md +++ b/README.md @@ -508,8 +508,12 @@ let feedback = Feedback(attachedTo: gear, propagating: { (event: G return nil }) +// or with the short syntax + +let feedback = Feedback(attachedTo: gear, catching: .authorizationIssueHappened, emitting: .checkAuthorization) + ... -// create the Check Authorization Spin with this feedback +// then, create the Check Authorization Spin with this feedback ... ``` @@ -519,13 +523,17 @@ At last we have to tell a feedback from the feature Spin how it will push events let feedback = Feedback(attachedTo: gear, propagating: { (state: State) in if state == .unauthorized { // only the .unauthorized state should trigger en event in the Gear - return . authorizationIssueHappened + return .authorizationIssueHappened } return nil }) +// or with the short syntax + +let feedback = Feedback(attachedTo: gear, catching: .unauthorized, propagating: .authorizationIssueHappened) + ... -// create the Feature Spin with this feedback +// then, create the Feature Spin with this feedback ... ``` diff --git a/Sources/Combine/Feedback.swift b/Sources/Combine/Feedback.swift index f0ddf22..0e16ce5 100644 --- a/Sources/Combine/Feedback.swift +++ b/Sources/Combine/Feedback.swift @@ -84,4 +84,19 @@ where SchedulerTime: Strideable, SchedulerTime.Stride: SchedulerTimeIntervalConv self.init(effect: effect, on: executer) } + + public init(attachedTo gear: Gear, + catching event: Event, + emitting loopEvent: EventStream.Value, + on executer: Executer? = nil) where Event: Equatable { + let emitFunction: (Event) -> EventStream .Value? = { gearEvent in + if event == gearEvent { + return loopEvent + } + + return nil + } + + self.init(attachedTo: gear, propagating: emitFunction, on: executer) + } } diff --git a/Sources/Common/FeedbackDefinition+Default.swift b/Sources/Common/FeedbackDefinition+Default.swift index 9906f80..3e2e919 100644 --- a/Sources/Common/FeedbackDefinition+Default.swift +++ b/Sources/Common/FeedbackDefinition+Default.swift @@ -211,6 +211,26 @@ public extension FeedbackDefinition { self.init(effect: effect, on: executer, applying: strategy) } + init(attachedTo gear: GearType, + catching state: StateStream.Value, + propagating event: GearType.Event, + on executer: Executer? = nil, + applying strategy: ExecutionStrategy = Self.defaultExecutionStrategy) where StateStream.Value: Equatable { + + let propagationFunction: (StateStream.Value) -> GearType.Event? = { feedbackState in + if feedbackState == state { + return event + } + + return nil + } + + self.init(attachedTo: gear, + propagating: propagationFunction, + on: executer, + applying: strategy) + } + init(effect: @escaping (Dep1, StateStream.Value) -> EventStream, on executer: Executer? = nil, applying strategy: ExecutionStrategy = Self.defaultExecutionStrategy, @@ -220,31 +240,31 @@ public extension FeedbackDefinition { } init(effect: @escaping (Dep1, Dep2, StateStream.Value) -> EventStream, - on executer: Executer? = nil, - applying strategy: ExecutionStrategy = Self.defaultExecutionStrategy, - dep1: Dep1, - dep2: Dep2) { + on executer: Executer? = nil, + applying strategy: ExecutionStrategy = Self.defaultExecutionStrategy, + dep1: Dep1, + dep2: Dep2) { let partializedEffect = partial(effect, arg1: dep1, arg2: dep2, arg3: .undefined) self.init(effect: partializedEffect, on: executer, applying: strategy) } init(effect: @escaping (Dep1, Dep2, Dep3, StateStream.Value) -> EventStream, - on executer: Executer? = nil, - applying strategy: ExecutionStrategy = Self.defaultExecutionStrategy, - dep1: Dep1, - dep2: Dep2, - dep3: Dep3) { + on executer: Executer? = nil, + applying strategy: ExecutionStrategy = Self.defaultExecutionStrategy, + dep1: Dep1, + dep2: Dep2, + dep3: Dep3) { let partializedEffect = partial(effect, arg1: dep1, arg2: dep2, arg3: dep3, arg4: .undefined) self.init(effect: partializedEffect, on: executer, applying: strategy) } init(effect: @escaping (Dep1, Dep2, Dep3, Dep4, StateStream.Value) -> EventStream, - on executer: Executer? = nil, - applying strategy: ExecutionStrategy = Self.defaultExecutionStrategy, - dep1: Dep1, - dep2: Dep2, - dep3: Dep3, - dep4: Dep4) { + on executer: Executer? = nil, + applying strategy: ExecutionStrategy = Self.defaultExecutionStrategy, + dep1: Dep1, + dep2: Dep2, + dep3: Dep3, + dep4: Dep4) { let partializedEffect = partial(effect, arg1: dep1, arg2: dep2, arg3: dep3, arg4: dep4, arg5: .undefined) self.init(effect: partializedEffect, on: executer, applying: strategy) } diff --git a/Sources/ReactiveSwift/Feedback.swift b/Sources/ReactiveSwift/Feedback.swift index 2857290..ef21cc9 100644 --- a/Sources/ReactiveSwift/Feedback.swift +++ b/Sources/ReactiveSwift/Feedback.swift @@ -72,4 +72,19 @@ public struct Feedback: FeedbackDefinition { self.init(effect: effect, on: executer) } + + public init(attachedTo gear: Gear, + catching event: Event, + emitting loopEvent: EventStream.Value, + on executer: Executer? = nil) where Event: Equatable { + let emitFunction: (Event) -> EventStream .Value? = { gearEvent in + if event == gearEvent { + return loopEvent + } + + return nil + } + + self.init(attachedTo: gear, propagating: emitFunction, on: executer) + } } diff --git a/Sources/RxSwift/Feedback.swift b/Sources/RxSwift/Feedback.swift index 7cceef2..359b16d 100644 --- a/Sources/RxSwift/Feedback.swift +++ b/Sources/RxSwift/Feedback.swift @@ -77,4 +77,19 @@ public struct Feedback: FeedbackDefinition { self.init(effect: effect, on: executer) } + + public init(attachedTo gear: Gear, + catching event: Event, + emitting loopEvent: EventStream.Value, + on executer: Executer? = nil) where Event: Equatable { + let emitFunction: (Event) -> EventStream .Value? = { gearEvent in + if event == gearEvent { + return loopEvent + } + + return nil + } + + self.init(attachedTo: gear, propagating: emitFunction, on: executer) + } } diff --git a/Tests/CombineTests/FeedbackTests.swift b/Tests/CombineTests/FeedbackTests.swift index ae6f5fe..13c39e0 100644 --- a/Tests/CombineTests/FeedbackTests.swift +++ b/Tests/CombineTests/FeedbackTests.swift @@ -256,4 +256,32 @@ final class FeedbackTests: XCTestCase { XCTAssertEqual(numberOfCallsGearSideEffect, 2) XCTAssertEqual(receivedElements, ["event"]) } + + func testFeedback_call_gearSideEffect_and_does_only_trigger_a_feedbackEvent_when_catching_expected_event() throws { + let exp = expectation(description: "Gear") + + let gear = Gear() + var receivedElements = [String]() + + // Given: a feedback attached to a Gear and triggering en event only of the gear event is 1 + let sut = Feedback(attachedTo: gear, catching: 1, emitting: "event") + + // When: executing the feedback + let inputStream = Just(1701).eraseToAnyPublisher() + sut + .effect(inputStream) + .sink(receiveCompletion: { _ in exp.fulfill() }, receiveValue: { element in receivedElements.append(element) }) + .store(in: &self.subscriptions) + + // When: sending 0 and then 1 as gear event + gear.eventSubject.send(0) + gear.eventSubject.send(1) + gear.eventSubject.send(completion: .finished) + + waitForExpectations(timeout: 0.5) + + // Then: the gear dedicated side effect is called twice + // Then: the only event triggered by the feedback is the one when attachment is not nil + XCTAssertEqual(receivedElements, ["event"]) + } } diff --git a/Tests/CommonTests/FeedbackDefinition+DefaultTests.swift b/Tests/CommonTests/FeedbackDefinition+DefaultTests.swift index 5f877aa..eecf063 100644 --- a/Tests/CommonTests/FeedbackDefinition+DefaultTests.swift +++ b/Tests/CommonTests/FeedbackDefinition+DefaultTests.swift @@ -398,6 +398,45 @@ final class FeedbackDefinition_DefaultTests: XCTestCase { XCTAssertEqual(spyGear.receivedEvent, expectedMockGearEvent) } + func testGear_do_not_propagate_event_if_catched_state_in_the_not_exepcted_one() { + let spyGear = MockGear() + + // Given: a feedback built with a gear attachment + let sut = SpyFeedback(attachedTo: spyGear, catching: MockState(subState: 10), propagating: .event) + + // Given: a feedback built with a gear attachment that return a MockGearEVent + _ = sut.effect(MockStream(value: MockState(subState: 15))) + + // Then: the default initializer of the feedback is called + // Then: the Executer inside the feedback is nil + // Then: the ExecutionStrategy is the default one + // Then: the event is propagated to the gear + XCTAssertTrue(sut.initIsCalled) + XCTAssertNil(sut.feedbackExecuter) + XCTAssertEqual(spyExecutionStrategy, MockFeedback.defaultExecutionStrategy) + XCTAssertNil(spyGear.receivedEvent) + } + + func testGear_propagate_event_if_catched_state_in_the_exepcted_one() { + let spyGear = MockGear() + let expectedMockGearEvent = MockGearEvent.event + + // Given: a feedback built with a gear attachment + let sut = SpyFeedback(attachedTo: spyGear, catching: MockState(subState: 15), propagating: .event) + + // Given: a feedback built with a gear attachment that return a MockGearEVent + _ = sut.effect(MockStream(value: MockState(subState: 15))) + + // Then: the default initializer of the feedback is called + // Then: the Executer inside the feedback is nil + // Then: the ExecutionStrategy is the default one + // Then: the event is propagated to the gear + XCTAssertTrue(sut.initIsCalled) + XCTAssertNil(sut.feedbackExecuter) + XCTAssertEqual(spyExecutionStrategy, MockFeedback.defaultExecutionStrategy) + XCTAssertEqual(spyGear.receivedEvent, expectedMockGearEvent) + } + func test_initializer_transmit_one_dependency_to_effect() { let expectedDep1 = "Dep1" var receivedDep1: String? diff --git a/Tests/ReactiveSwiftTests/FeedbackTests.swift b/Tests/ReactiveSwiftTests/FeedbackTests.swift index 7dc253c..93b2a25 100644 --- a/Tests/ReactiveSwiftTests/FeedbackTests.swift +++ b/Tests/ReactiveSwiftTests/FeedbackTests.swift @@ -213,4 +213,34 @@ final class FeedbackTests: XCTestCase { XCTAssertEqual(numberOfCallsGearSideEffect, 2) XCTAssertEqual(receivedEvents, ["event"]) } + + func testFeedback_call_gearSideEffect_and_does_only_trigger_a_feedbackEvent_when_catching_expected_event() { + let exp = expectation(description: "attach") + let gear = Gear() + var receivedEvents = [String]() + + // Given: a feedback attached to a Gear and triggering en event only of the gear event is 1 + let sut = Feedback(attachedTo: gear, catching: 1, emitting: "event") + + // When: executing the feedback + let inputStream = SignalProducer(value: 1701) + sut.effect(inputStream) + .collect() + .startWithValues({ events in + receivedEvents = events + exp.fulfill() + }) + .add(to: self.disposables) + + // When: sending 0 and then 1 as gear event + gear.eventsObserver.send(value: 0) + gear.eventsObserver.send(value: 1) + gear.eventsObserver.sendCompleted() + + waitForExpectations(timeout: 0.5) + + // Then: the gear dedicated side effect is called twice + // Then: the only event triggered by the feedback is the one when attachment is not nil + XCTAssertEqual(receivedEvents, ["event"]) + } } diff --git a/Tests/RxSwiftTests/FeedbackTests.swift b/Tests/RxSwiftTests/FeedbackTests.swift index 022bc7a..b8859b2 100644 --- a/Tests/RxSwiftTests/FeedbackTests.swift +++ b/Tests/RxSwiftTests/FeedbackTests.swift @@ -264,4 +264,35 @@ final class FeedbackTests: XCTestCase { XCTAssertEqual(numberOfCallsGearSideEffect, 2) XCTAssertEqual(receivedEvents, ["event"]) } + + func testFeedback_call_gearSideEffect_and_does_only_trigger_a_feedbackEvent_when_catching_expected_event() { + let exp = expectation(description: "attach") + let gear = Gear() + var receivedEvents = [String]() + + // Given: a feedback attached to a Gear and triggering en event only of the gear event is 1 + let sut = Feedback(attachedTo: gear, catching: 1, emitting: "event") + + // When: executing the feedback + let inputStream = Observable.just(1701) + sut.effect(inputStream) + .do(onNext: { event in + receivedEvents.append(event) + if receivedEvents.count == 1 { + exp.fulfill() + } + }) + .subscribe() + .disposed(by: self.disposeBag) + + // When: sending 0 and then 1 as gear event + gear.eventSubject.accept(0) + gear.eventSubject.accept(1) + + waitForExpectations(timeout: 0.5) + + // Then: the gear dedicated side effect is called twice + // Then: the only event triggered by the feedback is the one when attachment is not nil + XCTAssertEqual(receivedEvents, ["event"]) + } }