Skip to content

Commit

Permalink
gear: add a short syntax
Browse files Browse the repository at this point in the history
  • Loading branch information
twittemb committed Aug 24, 2020
1 parent 7729983 commit 73e99c4
Show file tree
Hide file tree
Showing 9 changed files with 219 additions and 18 deletions.
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -508,8 +508,12 @@ let feedback = Feedback<State, Event>(attachedTo: gear, propagating: { (event: G
return nil
})

// or with the short syntax

let feedback = Feedback<State, Event>(attachedTo: gear, catching: .authorizationIssueHappened, emitting: .checkAuthorization)

...
// create the Check Authorization Spin with this feedback
// then, create the Check Authorization Spin with this feedback
...
```

Expand All @@ -519,13 +523,17 @@ At last we have to tell a feedback from the feature Spin how it will push events
let feedback = Feedback<State, Event>(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<State, Event>(attachedTo: gear, catching: .unauthorized, propagating: .authorizationIssueHappened)

...
// create the Feature Spin with this feedback
// then, create the Feature Spin with this feedback
...
```

Expand Down
15 changes: 15 additions & 0 deletions Sources/Combine/Feedback.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,19 @@ where SchedulerTime: Strideable, SchedulerTime.Stride: SchedulerTimeIntervalConv

self.init(effect: effect, on: executer)
}

public init<Event>(attachedTo gear: Gear<Event>,
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)
}
}
50 changes: 35 additions & 15 deletions Sources/Common/FeedbackDefinition+Default.swift
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,26 @@ public extension FeedbackDefinition {
self.init(effect: effect, on: executer, applying: strategy)
}

init<GearType: GearDefinition>(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<Dep1>(effect: @escaping (Dep1, StateStream.Value) -> EventStream,
on executer: Executer? = nil,
applying strategy: ExecutionStrategy = Self.defaultExecutionStrategy,
Expand All @@ -220,31 +240,31 @@ public extension FeedbackDefinition {
}

init<Dep1, Dep2>(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<Dep1, Dep2, Dep3>(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<Dep1, Dep2, Dep3, Dep4>(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)
}
Expand Down
15 changes: 15 additions & 0 deletions Sources/ReactiveSwift/Feedback.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,19 @@ public struct Feedback<State, Event>: FeedbackDefinition {

self.init(effect: effect, on: executer)
}

public init<Event>(attachedTo gear: Gear<Event>,
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)
}
}
15 changes: 15 additions & 0 deletions Sources/RxSwift/Feedback.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,19 @@ public struct Feedback<State, Event>: FeedbackDefinition {

self.init(effect: effect, on: executer)
}

public init<Event>(attachedTo gear: Gear<Event>,
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)
}
}
28 changes: 28 additions & 0 deletions Tests/CombineTests/FeedbackTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<Int>()
var receivedElements = [String]()

// Given: a feedback attached to a Gear and triggering en event only of the gear event is 1
let sut = Feedback<Int, String>(attachedTo: gear, catching: 1, emitting: "event")

// When: executing the feedback
let inputStream = Just<Int>(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"])
}
}
39 changes: 39 additions & 0 deletions Tests/CommonTests/FeedbackDefinition+DefaultTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<MockState, MockEvent>(attachedTo: spyGear, catching: MockState(subState: 10), propagating: .event)

// Given: a feedback built with a gear attachment that return a MockGearEVent
_ = sut.effect(MockStream<MockState>(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<MockState, MockEvent>.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<MockState, MockEvent>(attachedTo: spyGear, catching: MockState(subState: 15), propagating: .event)

// Given: a feedback built with a gear attachment that return a MockGearEVent
_ = sut.effect(MockStream<MockState>(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<MockState, MockEvent>.defaultExecutionStrategy)
XCTAssertEqual(spyGear.receivedEvent, expectedMockGearEvent)
}

func test_initializer_transmit_one_dependency_to_effect() {
let expectedDep1 = "Dep1"
var receivedDep1: String?
Expand Down
30 changes: 30 additions & 0 deletions Tests/ReactiveSwiftTests/FeedbackTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<Int>()
var receivedEvents = [String]()

// Given: a feedback attached to a Gear and triggering en event only of the gear event is 1
let sut = Feedback<Int, String>(attachedTo: gear, catching: 1, emitting: "event")

// When: executing the feedback
let inputStream = SignalProducer<Int, Never>(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"])
}
}
31 changes: 31 additions & 0 deletions Tests/RxSwiftTests/FeedbackTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<Int>()
var receivedEvents = [String]()

// Given: a feedback attached to a Gear and triggering en event only of the gear event is 1
let sut = Feedback<Int, String>(attachedTo: gear, catching: 1, emitting: "event")

// When: executing the feedback
let inputStream = Observable<Int>.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"])
}
}

0 comments on commit 73e99c4

Please sign in to comment.