Skip to content

Commit 4ee527f

Browse files
committed
Added tests for bindTo and addressed a few formatting issues.
1 parent 2d50d51 commit 4ee527f

File tree

7 files changed

+102
-49
lines changed

7 files changed

+102
-49
lines changed

Action.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
/* Begin PBXBuildFile section */
1010
3D37A3961DB4A97C0028BC0E /* RxTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D9C98331DB4A87B004A9F7C /* RxTest.framework */; };
11+
5ED520241E1EA199007621B9 /* BindToTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ED520231E1EA199007621B9 /* BindToTests.swift */; };
1112
7BD1C7551E1D5562000D82DA /* UIControl+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BD1C7541E1D5562000D82DA /* UIControl+Rx.swift */; };
1213
7F0569E81DE28587007E1D0D /* Action+Internal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F0569E01DE28587007E1D0D /* Action+Internal.swift */; };
1314
7F0569E91DE28587007E1D0D /* Action.h in Headers */ = {isa = PBXBuildFile; fileRef = 7F0569E11DE28587007E1D0D /* Action.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -73,6 +74,7 @@
7374

7475
/* Begin PBXFileReference section */
7576
3D9C98331DB4A87B004A9F7C /* RxTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxTest.framework; path = Carthage/Checkouts/RxSwift/build/Debug/RxTest.framework; sourceTree = "<group>"; };
77+
5ED520231E1EA199007621B9 /* BindToTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BindToTests.swift; path = ActionTests/BindToTests.swift; sourceTree = "<group>"; };
7678
7BD1C7541E1D5562000D82DA /* UIControl+Rx.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIControl+Rx.swift"; sourceTree = "<group>"; };
7779
7F0569E01DE28587007E1D0D /* Action+Internal.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Action+Internal.swift"; sourceTree = "<group>"; };
7880
7F0569E11DE28587007E1D0D /* Action.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Action.h; sourceTree = "<group>"; };
@@ -197,6 +199,7 @@
197199
isa = PBXGroup;
198200
children = (
199201
7F0569F01DE288EB007E1D0D /* ActionTests.swift */,
202+
5ED520231E1EA199007621B9 /* BindToTests.swift */,
200203
7F0569F11DE288EB007E1D0D /* AlertActionTests.swift */,
201204
7F0569F21DE288EB007E1D0D /* BarButtonTests.swift */,
202205
7F0569F31DE288EB007E1D0D /* ButtonTests.swift */,
@@ -385,6 +388,7 @@
385388
buildActionMask = 2147483647;
386389
files = (
387390
7F0569F71DE288EB007E1D0D /* BarButtonTests.swift in Sources */,
391+
5ED520241E1EA199007621B9 /* BindToTests.swift in Sources */,
388392
7F0569F61DE288EB007E1D0D /* AlertActionTests.swift in Sources */,
389393
7F0569F51DE288EB007E1D0D /* ActionTests.swift in Sources */,
390394
7F0569F81DE288EB007E1D0D /* ButtonTests.swift in Sources */,

Demo/ViewController.swift

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,20 @@ class ViewController: UIViewController {
2525
@IBOutlet weak var button2: UIButton!
2626

2727
var disposableBag = DisposeBag()
28-
let sharedAction = Action <SharedInput, String> { input in
28+
let sharedAction = Action<SharedInput, String> { input in
2929
switch input {
3030
case .barButton: return Observable.just("UIBarButtonItem with 3 seconds delay").delaySubscription(3, scheduler: MainScheduler.instance)
31-
case .button (let title) : return .just("UIButton " + title)
31+
case .button(let title): return .just("UIButton " + title)
3232
}
3333
}
34+
3435
override func viewDidLoad() {
3536
super.viewDidLoad()
3637

3738
// Demo: add an action to a button in the view
3839
let action = CocoaAction {
3940
print("Button was pressed, showing an alert and keeping the activity indicator spinning while alert is displayed")
40-
return Observable.create {
41-
[weak self] observer -> Disposable in
42-
41+
return Observable.create { [weak self] observer -> Disposable in
4342
// Demo: show an alert and complete the view's button action once the alert's OK button is pressed
4443
let alertController = UIAlertController(title: "Hello world", message: "This alert was triggered by a button action", preferredStyle: .alert)
4544
var ok = UIAlertAction.Action("OK", style: .default)
@@ -49,7 +48,7 @@ class ViewController: UIViewController {
4948
return .empty()
5049
}
5150
alertController.addAction(ok)
52-
self!.present(alertController, animated: true, completion: nil)
51+
self?.present(alertController, animated: true, completion: nil)
5352

5453
return Disposables.create()
5554
}
@@ -58,7 +57,7 @@ class ViewController: UIViewController {
5857
button.rx.action = action
5958

6059
// Demo: add an action to a UIBarButtonItem in the navigation item
61-
self.navigationItem.rightBarButtonItem!.rx.action = CocoaAction {
60+
self.navigationItem.rightBarButtonItem?.rx.action = CocoaAction {
6261
print("Bar button item was pressed, simulating a 2 second action")
6362
return Observable.empty().delaySubscription(2, scheduler: MainScheduler.instance)
6463
}
@@ -67,15 +66,13 @@ class ViewController: UIViewController {
6766
// while performing the work
6867
Observable.combineLatest(
6968
button.rx.action!.executing,
70-
self.navigationItem.rightBarButtonItem!.rx.action!.executing) {
69+
self.navigationItem.rightBarButtonItem!.rx.action!.executing) { a,b in
7170
// we combine two boolean observable and output one boolean
72-
a,b in
7371
return a || b
7472
}
7573
.distinctUntilChanged()
76-
.subscribe(onNext: {
74+
.subscribe(onNext: { [weak self] executing in
7775
// every time the execution status changes, spin an activity indicator
78-
[weak self] executing in
7976
self?.workingLabel.isHidden = !executing
8077
if (executing) {
8178
self?.activityIndicator.startAnimating()
@@ -84,29 +81,26 @@ class ViewController: UIViewController {
8481
self?.activityIndicator.stopAnimating()
8582
}
8683
})
87-
8884
.addDisposableTo(self.disposableBag)
89-
90-
85+
9186
button1.rx.bindToAction(sharedAction, input: .button("Button 1"))
92-
87+
9388
button2.rx.bindToAction(sharedAction) { _ in
9489
return .button("Button 2")
9590
}
9691
self.navigationItem.leftBarButtonItem?.rx.bindToAction(sharedAction, input: .barButton)
97-
98-
sharedAction.executing.debounce(0, scheduler: MainScheduler.instance).subscribe(onNext:{[weak self] executing in
92+
93+
sharedAction.executing.debounce(0, scheduler: MainScheduler.instance).subscribe(onNext: { [weak self] executing in
9994
if (executing) {
10095
self?.activityIndicator.startAnimating()
10196
}
10297
else {
10398
self?.activityIndicator.stopAnimating()
10499
}
105100
}).addDisposableTo(self.disposableBag)
101+
106102
sharedAction.elements.subscribe(onNext: { string in
107-
print (string + " pressed")
103+
print(string + " pressed")
108104
}).addDisposableTo(self.disposableBag)
109-
110-
111105
}
112106
}

Sources/Action/UIKitExtensions/UIBarButtonItem+Action.swift

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,31 +33,30 @@ public extension Reactive where Base: UIBarButtonItem {
3333
self.tap.subscribe(onNext: { (_) in
3434
action.execute()
3535
})
36-
.addDisposableTo(self.base.actionDisposeBag)
36+
.addDisposableTo(self.base.actionDisposeBag)
3737
}
3838
}
3939
}
4040

41-
public func bindToAction <Input,Output>(_ action:Action<Input,Output>?, _ inputTransform: @escaping (Base) -> (Input)) {
41+
public func bindToAction<Input, Output>(_ action: Action<Input, Output>?, _ inputTransform: @escaping (Base) -> (Input)) {
4242
self.base.resetActionDisposeBag()
4343

4444
guard let action = action else {
4545
return
4646
}
47-
47+
4848
self.tap
49-
.map { return inputTransform(self.base) }
49+
.map { inputTransform(self.base) }
5050
.bindTo(action.inputs)
5151
.addDisposableTo(self.base.actionDisposeBag)
52-
52+
5353
action
5454
.enabled
5555
.bindTo(self.isEnabled)
5656
.addDisposableTo(self.base.actionDisposeBag)
57-
5857
}
5958

60-
public func bindToAction <Input,Output>(_ action:Action<Input,Output>?, input:Input) {
59+
public func bindToAction<Input, Output>(_ action: Action<Input,Output>?, input: Input) {
6160
self.bindToAction(action) { _ in input}
6261
}
6362

Sources/Action/UIKitExtensions/UIButton+Rx.swift

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public extension Reactive where Base: UIButton {
5555
/// Binds enabled state of action to button, and subscribes to rx_tap to execute action with given input transform.
5656
/// These subscriptions are managed in a private, inaccessible dispose bag. To cancel
5757
/// them, call bindToAction with another action or nil.
58-
public func bindToAction <Input,Output>(_ action:Action<Input,Output>?, _ inputTransform: @escaping (Base) -> (Input)) {
58+
public func bindToAction<Input, Output>(_ action:Action<Input,Output>?, _ inputTransform: @escaping (Base) -> (Input)) {
5959
// This effectively disposes of any existing subscriptions.
6060

6161
// Technically, this file is only included on tv/iOS platforms,
@@ -72,15 +72,13 @@ public extension Reactive where Base: UIButton {
7272
return
7373
}
7474
self.bindToAction(action, controlEvent: controlEvent, inputTransform)
75-
7675
}
7776

7877
/// Binds enabled state of action to button, and subscribes to rx_tap to execute action with given input value.
7978
/// These subscriptions are managed in a private, inaccessible dispose bag. To cancel
8079
/// them, call bindToAction with another action or nil.
81-
public func bindToAction <Input,Output>(_ action:Action<Input,Output>?, input:Input) {
82-
self.bindToAction(action) { _ in return input}
80+
public func bindToAction<Input, Output>(_ action:Action<Input,Output>?, input:Input) {
81+
self.bindToAction(action) { _ in return input }
8382
}
84-
8583
}
8684
#endif

Sources/Action/UIKitExtensions/UIControl+Rx.swift

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,36 +2,31 @@
22
import UIKit
33
import RxSwift
44
import RxCocoa
5-
import ObjectiveC
65

7-
public extension Reactive where Base : UIControl {
6+
public extension Reactive where Base: UIControl {
87
/// Binds enabled state of action to control, and subscribes action's execution to provided controlEvents.
98
/// These subscriptions are managed in a private, inaccessible dispose bag. To cancel
109
/// them, set the rx.action to nil or another action.
11-
public func bindToAction <Input,Output>(_ action:Action<Input,Output>?, controlEvent:ControlEvent<Void>, _ inputTransform: @escaping (Base) -> (Input)) {
10+
public func bindToAction <Input,Output>(_ action: Action<Input,Output>?, controlEvent: ControlEvent<Void>, _ inputTransform: @escaping (Base) -> (Input)) {
1211
// This effectively disposes of any existing subscriptions.
1312
self.base.resetActionDisposeBag()
14-
15-
13+
1614
// If no action is provided, there is nothing left to do. All previous subscriptions are disposed.
1715
guard let action = action else {
1816
return
1917
}
20-
// Technically, this file is only included on tv/iOS platforms,
21-
// so this optional will never be nil. But let's be safe 😉
22-
18+
2319
// For each tap event, use the inputTransform closure to provide an Input value to the action
2420
controlEvent
25-
.map { return inputTransform(self.base) }
21+
.map { inputTransform(self.base) }
2622
.bindTo(action.inputs)
2723
.addDisposableTo(self.base.actionDisposeBag)
28-
24+
2925
// Bind the enabled state of the control to the enabled state of the action
3026
action
3127
.enabled
3228
.bindTo(self.isEnabled)
3329
.addDisposableTo(self.base.actionDisposeBag)
34-
3530
}
3631
}
3732

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import Quick
2+
import Nimble
3+
import RxSwift
4+
import RxCocoa
5+
import RxBlocking
6+
import RxTest
7+
import Action
8+
9+
extension UIButton {
10+
// Normally I'd use subject.sendActionsForControlEvents(.TouchUpInside) but it's not working
11+
func test_executeTap() {
12+
for case let target as NSObject in allTargets {
13+
for action in actions(forTarget: target, forControlEvent: .touchUpInside) ?? [] {
14+
target.perform(Selector(action), with: self)
15+
}
16+
}
17+
}
18+
}
19+
20+
class BindToTests: QuickSpec {
21+
override func spec() {
22+
it("actives a UIButton") {
23+
var called = false
24+
let button = UIButton()
25+
let action = Action<String, String>(workFactory: { _ in
26+
called = true
27+
return .empty()
28+
})
29+
button.rx.bindToAction(action, input: "Hi there!")
30+
// Setting the action has an asynchronous effect of adding a target.
31+
expect(button.allTargets).toEventuallyNot( beEmpty() )
32+
33+
button.test_executeTap()
34+
35+
expect(called).toEventually( beTrue() )
36+
}
37+
38+
it("activates a generic control event") {
39+
var called = false
40+
let button = UIButton()
41+
let action = Action<String, String>(workFactory: { _ in
42+
called = true
43+
return .empty()
44+
})
45+
button.rx.bindToAction(action, controlEvent: button.rx.tap, { input in "\(input)" })
46+
// Setting the action has an asynchronous effect of adding a target.
47+
expect(button.allTargets).toEventuallyNot( beEmpty() )
48+
49+
button.test_executeTap()
50+
51+
expect(called).toEventually( beTrue() )
52+
}
53+
54+
it("actives a UIBarButtonItem") {
55+
var called = false
56+
let item = UIBarButtonItem()
57+
let action = Action<String, String>(workFactory: { _ in
58+
called = true
59+
return .empty()
60+
})
61+
item.rx.bindToAction(action, input: "Hi there!")
62+
63+
_ = item.target!.perform(item.action!, with: item)
64+
65+
expect(called).toEventually( beTrue() )
66+
}
67+
}
68+
}

Tests/ActionTests/ButtonTests.swift

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,12 +78,7 @@ class ButtonTests: QuickSpec {
7878
// Setting the action has an asynchronous effect of adding a target.
7979
expect(subject.allTargets).toEventuallyNot( beEmpty() )
8080

81-
// Normally I'd use subject.sendActionsForControlEvents(.TouchUpInside) but it's not working
82-
for case let target as NSObject in subject.allTargets {
83-
for action in subject.actions(forTarget: target, forControlEvent: .touchUpInside) ?? [] {
84-
target.perform(Selector(action), with: subject)
85-
}
86-
}
81+
subject.test_executeTap()
8782

8883
expect(executed).toEventually( beTrue() )
8984
}

0 commit comments

Comments
 (0)