Makes rx.text type consistent with UIKit String? type.

This commit is contained in:
Krunoslav Zaher 2016-10-16 21:41:28 +02:00
parent b85d57fe8b
commit 2e520bf264
26 changed files with 264 additions and 55 deletions

View File

@ -11,6 +11,13 @@ All notable changes to this project will be documented in this file.
* Adds Linux support
* Replaces `AnyObserver` with `UIBindingObserver` in public interface.
* Renames `resourceCount` to `Resources.total`.
* Makes `rx.text` type consistent with UIKit `String?` type.
```swift
textField.rx.text // <- now has type `ControlProperty<String?>`
textField.rx.text.orEmpty // <- now has type `ControlProperty<String>`
```
* Adds optional overloads for `bindTo` and `drive`. Now the following works:
```swift
@ -19,7 +26,13 @@ let text: Observable<String> = Observable.just("")
// Previously `map { $0 }` was needed because of mismatch betweeen sequence `String` type and `String?` type
// on binding `rx.text` observer.
text.bindTo(label.rx.text)
addDisposableTo(disposeBag)
.addDisposableTo(disposeBag)
...
let text = Driver.just("")
text.drive(label.rx.text)
.addDisposableTo(disposeBag)
```
* Adds trim output parameter to `debug` operator. #930

View File

@ -282,6 +282,9 @@
C81B6AAF1DB2C15C0047CF86 /* Platform.Linux.swift in Sources */ = {isa = PBXBuildFile; fileRef = C81B6AA91DB2C15C0047CF86 /* Platform.Linux.swift */; };
C821DBA21BA4DCAB008F3809 /* Buffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C821DBA11BA4DCAB008F3809 /* Buffer.swift */; };
C821DBA31BA4DCAB008F3809 /* Buffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C821DBA11BA4DCAB008F3809 /* Buffer.swift */; };
C822BACA1DB4058000F98810 /* Event+Test.swift in Sources */ = {isa = PBXBuildFile; fileRef = C822BAC51DB4048F00F98810 /* Event+Test.swift */; };
C822BACB1DB4058000F98810 /* Event+Test.swift in Sources */ = {isa = PBXBuildFile; fileRef = C822BAC51DB4048F00F98810 /* Event+Test.swift */; };
C822BACC1DB4058100F98810 /* Event+Test.swift in Sources */ = {isa = PBXBuildFile; fileRef = C822BAC51DB4048F00F98810 /* Event+Test.swift */; };
C83100641BF7D51600AAE3CD /* Sequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = C83100631BF7D51600AAE3CD /* Sequence.swift */; };
C83100651BF7D51600AAE3CD /* Sequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = C83100631BF7D51600AAE3CD /* Sequence.swift */; };
C83100661BF7D51600AAE3CD /* Sequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = C83100631BF7D51600AAE3CD /* Sequence.swift */; };
@ -1545,6 +1548,7 @@
C81B6AA81DB2C15C0047CF86 /* Platform.Darwin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Platform.Darwin.swift; sourceTree = "<group>"; };
C81B6AA91DB2C15C0047CF86 /* Platform.Linux.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Platform.Linux.swift; sourceTree = "<group>"; };
C821DBA11BA4DCAB008F3809 /* Buffer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Buffer.swift; sourceTree = "<group>"; };
C822BAC51DB4048F00F98810 /* Event+Test.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Event+Test.swift"; sourceTree = "<group>"; };
C83100631BF7D51600AAE3CD /* Sequence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Sequence.swift; sourceTree = "<group>"; };
C834F6C11DB394E100C29244 /* Observable+BlockingTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Observable+BlockingTest.swift"; sourceTree = "<group>"; };
C834F6C51DB3950600C29244 /* NSControl+RxTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSControl+RxTests.swift"; sourceTree = "<group>"; };
@ -2336,6 +2340,7 @@
C835091B1C38706D0027C24C /* VariableTest.swift */,
C835091C1C38706D0027C24C /* VirtualSchedulerTest.swift */,
C86B1E211D42BF5200130546 /* SchedulerTests.swift */,
C822BAC51DB4048F00F98810 /* Event+Test.swift */,
);
path = RxSwiftTests;
sourceTree = "<group>";
@ -3618,6 +3623,7 @@
C83509621C38706E0027C24C /* QueueTests.swift in Sources */,
914FCD671CCDB82E0058B304 /* UIPageControl+RxTest.swift in Sources */,
C83509351C38706E0027C24C /* KVOObservableTests.swift in Sources */,
C822BACA1DB4058000F98810 /* Event+Test.swift in Sources */,
C83509421C38706E0027C24C /* MainThreadPrimitiveHotObservable.swift in Sources */,
C835093A1C38706E0027C24C /* RuntimeStateSnapshot.swift in Sources */,
C86B1E221D42BF5200130546 /* SchedulerTests.swift in Sources */,
@ -3732,6 +3738,7 @@
C83509EB1C3875580027C24C /* MainThreadPrimitiveHotObservable.swift in Sources */,
C83509C21C3875220027C24C /* Driver+Test.swift in Sources */,
C83509FE1C38755D0027C24C /* Observable+CreationTest.swift in Sources */,
C822BACB1DB4058000F98810 /* Event+Test.swift in Sources */,
C8353CE71DA19BC500BE3F5C /* Recorded+Timeless.swift in Sources */,
C8350A161C38756A0027C24C /* QueueTests.swift in Sources */,
C89CFA0D1DAAB4670079D23B /* RxTest.swift in Sources */,
@ -3794,6 +3801,7 @@
C8E9E42D1D43B26C0049644E /* Observable+DebugTest.swift in Sources */,
C83509C81C3875230027C24C /* DelegateProxyTest.swift in Sources */,
C8350A0D1C38755E0027C24C /* Observable+MultipleTest+CombineLatest.swift in Sources */,
C822BACC1DB4058100F98810 /* Event+Test.swift in Sources */,
C8350A0B1C38755E0027C24C /* Observable+ConcurrencyTest.swift in Sources */,
C83509E21C3875580027C24C /* BackgroundThreadPrimitiveHotObservable.swift in Sources */,
C8350A0E1C3875630027C24C /* Observable+MultipleTest+Zip.swift in Sources */,

View File

@ -108,3 +108,16 @@ public struct ControlProperty<PropertyType> : ControlPropertyType {
}
}
}
extension ControlPropertyType where E == String? {
/**
Transforms control property of type `String?` into control property of type `String`.
*/
public var orEmpty: ControlProperty<String> {
let original: ControlProperty<String?> = self.asControlProperty()
let values: Observable<String> = original._values.map { $0 ?? "" }
let valueSink: AnyObserver<String> = original._valueSink.map { $0 }
return ControlProperty<String>(values: values, valueSink: valueSink)
}
}

View File

@ -27,7 +27,7 @@ import Foundation
/**
Reactive wrapper for `text` property.
*/
public let text: ControlProperty<String>
public let text: ControlProperty<String?>
/**
Initializes new text input.
@ -35,7 +35,7 @@ import Foundation
- parameter base: Base object.
- parameter text: Textual control property.
*/
public init(base: Base, text: ControlProperty<String>) {
public init(base: Base, text: ControlProperty<String?>) {
self.base = base
self.text = text
}
@ -65,19 +65,19 @@ import Foundation
@available(*, deprecated, renamed: "TextInput")
public protocol RxTextInput : UITextInput {
@available(*, deprecated, renamed: "rx.textInput.text")
var rx_text: ControlProperty<String> { get }
var rx_text: ControlProperty<String?> { get }
}
extension UITextField : RxTextInput {
@available(*, deprecated, renamed: "rx.textInput.text")
public var rx_text: ControlProperty<String> {
public var rx_text: ControlProperty<String?> {
return self.rx.text
}
}
extension UITextView : RxTextInput {
@available(*, deprecated, renamed: "rx.textInput.text")
public var rx_text: ControlProperty<String> {
public var rx_text: ControlProperty<String?> {
return self.rx.text
}
}

View File

@ -63,7 +63,7 @@ extension Reactive where Base: UIControl {
You might be wondering why the ugly `as!` casts etc, well, for some reason if
Swift compiler knows C is UIControl type and optimizations are turned on, it will crash.
*/
static func value<C: NSObject, T: Equatable>(_ control: C, getter: @escaping (C) -> T, setter: @escaping (C, T) -> Void) -> ControlProperty<T> {
static func value<C: NSObject, T>(_ control: C, getter: @escaping (C) -> T, setter: @escaping (C, T) -> Void) -> ControlProperty<T> {
let source: Observable<T> = Observable.create { [weak weakControl = control] observer in
guard let control = weakControl else {
observer.on(.completed)

View File

@ -19,11 +19,11 @@ extension Reactive where Base: UITextField {
/**
Reactive wrapper for `text` property.
*/
public var text: ControlProperty<String> {
public var text: ControlProperty<String?> {
return UIControl.rx.value(
base,
getter: { textField in
textField.text ?? ""
textField.text
}, setter: { textField, value in
// This check is important because setting text value always clears control state
// including marked text selection which is imporant for proper input

View File

@ -32,9 +32,9 @@ extension Reactive where Base: UITextView {
/**
Reactive wrapper for `text` property.
*/
public var text: ControlProperty<String> {
let source: Observable<String> = Observable.deferred { [weak textView = self.base] in
let text = textView?.text ?? ""
public var text: ControlProperty<String?> {
let source: Observable<String?> = Observable.deferred { [weak textView = self.base] in
let text = textView?.text
let textChanged = textView?.textStorage
// This project uses text storage notifications because
@ -46,7 +46,7 @@ extension Reactive where Base: UITextView {
// so rebinding a value will cause an exception to be thrown.
.observeOn(MainScheduler.asyncInstance)
.map { _ in
return textView?.textStorage.string ?? ""
return textView?.textStorage.string
}
?? Observable.empty()
@ -54,7 +54,7 @@ extension Reactive where Base: UITextView {
.startWith(text)
}
let bindingObserver = UIBindingObserver(UIElement: self.base) { (textView, text: String) in
let bindingObserver = UIBindingObserver(UIElement: self.base) { (textView, text: String?) in
// This check is important because setting text value always clears control state
// including marked text selection which is imporant for proper input
// when IME input method is used.

View File

@ -31,9 +31,9 @@ class GitHubSignupViewController2 : ViewController {
let viewModel = GithubSignupViewModel2(
input: (
username: usernameOutlet.rx.text.asDriver(),
password: passwordOutlet.rx.text.asDriver(),
repeatedPassword: repeatedPasswordOutlet.rx.text.asDriver(),
username: usernameOutlet.rx.text.orEmpty.asDriver(),
password: passwordOutlet.rx.text.orEmpty.asDriver(),
repeatedPassword: repeatedPasswordOutlet.rx.text.orEmpty.asDriver(),
loginTaps: signupOutlet.rx.tap.asDriver()
),
dependency: (

View File

@ -31,9 +31,9 @@ class GitHubSignupViewController1 : ViewController {
let viewModel = GithubSignupViewModel1(
input: (
username: usernameOutlet.rx.text.asObservable(),
password: passwordOutlet.rx.text.asObservable(),
repeatedPassword: repeatedPasswordOutlet.rx.text.asObservable(),
username: usernameOutlet.rx.text.orEmpty.asObservable(),
password: passwordOutlet.rx.text.orEmpty.asObservable(),
repeatedPassword: repeatedPasswordOutlet.rx.text.orEmpty.asObservable(),
loginTaps: signupOutlet.rx.tap.asObservable()
),
dependency: (

View File

@ -23,7 +23,7 @@ class NumbersViewController: ViewController {
override func viewDidLoad() {
super.viewDidLoad()
Observable.combineLatest(number1.rx.text, number2.rx.text, number3.rx.text) { textValue1, textValue2, textValue3 -> Int in
Observable.combineLatest(number1.rx.text.orEmpty, number2.rx.text.orEmpty, number3.rx.text.orEmpty) { textValue1, textValue2, textValue3 -> Int in
return (Int(textValue1) ?? 0) + (Int(textValue2) ?? 0) + (Int(textValue3) ?? 0)
}
.map { $0.description }

View File

@ -32,11 +32,11 @@ class SimpleValidationViewController : ViewController {
usernameValidOutlet.text = "Username has to be at least \(minimalUsernameLength) characters"
passwordValidOutlet.text = "Password has to be at least \(minimalPasswordLength) characters"
let usernameValid = usernameOutlet.rx.text
let usernameValid = usernameOutlet.rx.text.orEmpty
.map { $0.characters.count >= minimalUsernameLength }
.shareReplay(1) // without this map would be executed once for each binding, rx is stateless by default
let passwordValid = passwordOutlet.rx.text
let passwordValid = passwordOutlet.rx.text.orEmpty
.map { $0.characters.count >= minimalPasswordLength }
.shareReplay(1)

View File

@ -72,4 +72,16 @@ extension ObserverType {
public func asObserver() -> AnyObserver<E> {
return AnyObserver(self)
}
/**
Transforms observer of type R to type E using custom transform method.
Each event sent to result observer is transformed and sent to `self`.
- returns: observer that transforms events.
*/
public func map<R>(_ transform: @escaping (R) throws -> E) -> AnyObserver<R> {
return AnyObserver { e in
self.on(e.map(transform))
}
}
}

View File

@ -64,3 +64,23 @@ extension Event {
return nil
}
}
extension Event {
/// Maps sequence elements using transform. If error happens during the transform .error
/// will be returned as value
public func map<Result>(_ transform: (Element) throws -> Result) -> Event<Result> {
do {
switch self {
case let .next(element):
return .next(try transform(element))
case let .error(error):
return .error(error)
case .completed:
return .completed
}
}
catch let e {
return .error(e)
}
}
}

View File

@ -46,7 +46,7 @@ public class Observable<Element> : ObservableType {
Optimizations for map operator
*/
internal func composeMap<R>(_ selector: @escaping (Element) throws -> R) -> Observable<R> {
return Map(source: self, selector: selector)
return Map(source: self, transform: selector)
}
}

View File

@ -9,15 +9,15 @@
import Foundation
class MapSink<SourceType, O : ObserverType> : Sink<O>, ObserverType {
typealias Selector = (SourceType) throws -> ResultType
typealias Transform = (SourceType) throws -> ResultType
typealias ResultType = O.E
typealias Element = SourceType
private let _selector: Selector
private let _transform: Transform
init(selector: @escaping Selector, observer: O, cancel: Cancelable) {
_selector = selector
init(transform: @escaping Transform, observer: O, cancel: Cancelable) {
_transform = transform
super.init(observer: observer, cancel: cancel)
}
@ -25,7 +25,7 @@ class MapSink<SourceType, O : ObserverType> : Sink<O>, ObserverType {
switch event {
case .next(let element):
do {
let mappedElement = try _selector(element)
let mappedElement = try _transform(element)
forwardOn(.next(mappedElement))
}
catch let e {
@ -108,15 +108,15 @@ class MapWithIndex<SourceType, ResultType> : Producer<ResultType> {
#endif
class Map<SourceType, ResultType>: Producer<ResultType> {
typealias Selector = (SourceType) throws -> ResultType
typealias Transform = (SourceType) throws -> ResultType
private let _source: Observable<SourceType>
private let _selector: Selector
private let _transform: Transform
init(source: Observable<SourceType>, selector: @escaping Selector) {
init(source: Observable<SourceType>, transform: @escaping Transform) {
_source = source
_selector = selector
_transform = transform
#if TRACE_RESOURCES
let _ = AtomicIncrement(&_numberOfMapOperators)
@ -124,15 +124,15 @@ class Map<SourceType, ResultType>: Producer<ResultType> {
}
override func composeMap<R>(_ selector: @escaping (ResultType) throws -> R) -> Observable<R> {
let originalSelector = _selector
return Map<SourceType, R>(source: _source, selector: { (s: SourceType) throws -> R in
let originalSelector = _transform
return Map<SourceType, R>(source: _source, transform: { (s: SourceType) throws -> R in
let r: ResultType = try originalSelector(s)
return try selector(r)
})
}
override func run<O: ObserverType>(_ observer: O, cancel: Cancelable) -> (sink: Disposable, subscription: Disposable) where O.E == ResultType {
let sink = MapSink(selector: _selector, observer: observer, cancel: cancel)
let sink = MapSink(transform: _transform, observer: observer, cancel: cancel)
let subscription = _source.subscribe(sink)
return (sink: sink, subscription: subscription)
}

View File

@ -168,14 +168,14 @@ extension ObservableType {
- seealso: [map operator on reactivex.io](http://reactivex.io/documentation/operators/map.html)
- parameter selector: A transform function to apply to each source element.
- parameter transform: A transform function to apply to each source element.
- returns: An observable sequence whose elements are the result of invoking the transform function on each element of source.
*/
// @warn_unused_result(message:"http://git.io/rxs.uo")
public func map<R>(_ selector: @escaping (E) throws -> R)
public func map<R>(_ transform: @escaping (E) throws -> R)
-> Observable<R> {
return self.asObservable().composeMap(selector)
return self.asObservable().composeMap(transform)
}
/**

View File

@ -0,0 +1 @@
../../Tests/RxSwiftTests/Event+Test.swift

View File

@ -12,6 +12,21 @@ protocol RxTestCase {
}
final class EventTests_ : EventTests, RxTestCase {
#if os(OSX)
required override init() {
super.init()
}
#endif
static var allTests: [(String, (EventTests_) -> () -> ())] { return [
("testMapTransformNext", EventTests.testMapTransformNext),
("testMapTransformNextThrow", EventTests.testMapTransformNextThrow),
("testMapTransformError", EventTests.testMapTransformError),
("testMapTransformCompleted", EventTests.testMapTransformCompleted),
] }
}
final class PublishSubjectTest_ : PublishSubjectTest, RxTestCase {
#if os(OSX)
required override init() {
@ -599,6 +614,10 @@ final class ObserverTests_ : ObserverTests, RxTestCase {
("testConvenienceOn_Next", ObserverTests.testConvenienceOn_Next),
("testConvenienceOn_Error", ObserverTests.testConvenienceOn_Error),
("testConvenienceOn_Complete", ObserverTests.testConvenienceOn_Complete),
("testMapElement", ObserverTests.testMapElement),
("testMapElementCompleted", ObserverTests.testMapElementCompleted),
("testMapElementError", ObserverTests.testMapElementError),
("testMapElementThrow", ObserverTests.testMapElementThrow),
] }
}
@ -1046,6 +1065,7 @@ func XCTMain(_ tests: [() -> ()]) {
#endif
XCTMain([
testCase(EventTests_.allTests),
testCase(PublishSubjectTest_.allTests),
testCase(VirtualSchedulerTest_.allTests),
testCase(ObservableBlockingTest_.allTests),

View File

@ -10,8 +10,12 @@ import Foundation
import XCTest
import RxCocoa
import RxSwift
import RxTest
class ControlPropertyTests : RxTest {
}
extension ControlPropertyTests {
func testObservingIsAlwaysHappeningOnMainThread() {
let hotObservable = MainThreadPrimitiveHotObservable<Int>()
@ -48,3 +52,21 @@ class ControlPropertyTests : RxTest {
XCTAssertTrue(observedOnMainThread)
}
}
extension ControlPropertyTests {
func testOrEmpty() {
let bindingObserver = PrimitiveMockObserver<String?>()
let controlProperty = ControlProperty<String?>(values: Observable.just(nil), valueSink: bindingObserver.asObserver())
let orEmpty = controlProperty.orEmpty
let finalObserver = PrimitiveMockObserver<String>()
_ = orEmpty.subscribe(finalObserver)
orEmpty.on(.next("a"))
let bindingEvents: [Event<String>] = bindingObserver.events.map { $0.value.map { $0 ?? "" } }
let observingEvents: [Event<String>] = finalObserver.events.map { $0.value.map { $0 } }
XCTAssertArraysEqual(bindingEvents, [Event<String>.next("a")], ==)
XCTAssertArraysEqual(observingEvents, [Event<String>.next(""), Event<String>.completed], ==)
}
}

View File

@ -13,8 +13,13 @@ import XCTest
extension RxTest {
func ensurePropertyDeallocated<C, T: Equatable>(_ createControl: () -> C, _ initialValue: T, _ propertySelector: (C) -> ControlProperty<T>) where C: NSObject {
let variable = Variable(initialValue)
ensurePropertyDeallocated(createControl, initialValue, comparer: ==, propertySelector)
}
func ensurePropertyDeallocated<C, T>(_ createControl: () -> C, _ initialValue: T, comparer: (T, T) -> Bool, _ propertySelector: (C) -> ControlProperty<T>) where C: NSObject {
let variable = Variable(initialValue)
var completed = false
var deallocated = false
@ -55,7 +60,7 @@ extension RxTest {
XCTAssertTrue(deallocated)
XCTAssertTrue(completed)
XCTAssertEqual(initialValue, lastReturnedPropertyValue)
XCTAssertTrue(comparer(initialValue, lastReturnedPropertyValue))
}
func ensureEventDeallocated<C, T>(_ createControl: @escaping () -> C, _ eventSelector: (C) -> ControlEvent<T>) where C: NSObject {

View File

@ -449,7 +449,7 @@ extension UITableViewTests {
let commitedEvents = tableView.rx.dataSource.sentMessage(#selector(UITableViewDataSource.tableView(_:commit:forRowAt:)))
XCTAssertFalse(tableView.dataSource!.responds(to: #selector(UITableViewDataSource.tableView(_:commit:forRowAt:))))
XCTAssertEqual(setDataSources, [tableView.dataSource!] as [UITableViewDataSource?]) { $0 === $1 }
XCTAssertArraysEqual(setDataSources, [tableView.dataSource!] as [UITableViewDataSource?]) { $0 === $1 }
var firstEvents: [Arguments] = []
var secondEvents: [Arguments] = []
@ -459,14 +459,14 @@ extension UITableViewTests {
})
XCTAssertTrue(tableView.dataSource!.responds(to: #selector(UITableViewDataSource.tableView(_:commit:forRowAt:))))
XCTAssertEqual(setDataSources, [tableView.dataSource, nil, tableView.dataSource] as [UITableViewDataSource?]) { $0 === $1 }
XCTAssertArraysEqual(setDataSources, [tableView.dataSource, nil, tableView.dataSource] as [UITableViewDataSource?]) { $0 === $1 }
let subscription2 = commitedEvents.subscribe(onNext: { event in
secondEvents.append(Arguments(values: event))
})
XCTAssertTrue(tableView.dataSource!.responds(to: #selector(UITableViewDataSource.tableView(_:commit:forRowAt:))))
XCTAssertEqual(setDataSources, [tableView.dataSource, nil, tableView.dataSource] as [UITableViewDataSource?]) { $0 === $1 }
XCTAssertArraysEqual(setDataSources, [tableView.dataSource, nil, tableView.dataSource] as [UITableViewDataSource?]) { $0 === $1 }
let deleteEditingStyle: NSNumber = NSNumber(value: UITableViewCellEditingStyle.delete.rawValue)
let indexPath: NSIndexPath = NSIndexPath(item: 0, section: 0)
@ -479,12 +479,12 @@ extension UITableViewTests {
subscription1.dispose()
XCTAssertTrue(tableView.dataSource!.responds(to: #selector(UITableViewDataSource.tableView(_:commit:forRowAt:))))
XCTAssertEqual(setDataSources, [tableView.dataSource, nil, tableView.dataSource] as [UITableViewDataSource?]) { $0 === $1 }
XCTAssertArraysEqual(setDataSources, [tableView.dataSource, nil, tableView.dataSource] as [UITableViewDataSource?]) { $0 === $1 }
subscription2.dispose()
XCTAssertFalse(tableView.dataSource!.responds(to: #selector(UITableViewDataSource.tableView(_:commit:forRowAt:))))
XCTAssertEqual(setDataSources, [tableView.dataSource, nil, tableView.dataSource, nil, tableView.dataSource]) { $0 === $1 }
XCTAssertArraysEqual(setDataSources, [tableView.dataSource, nil, tableView.dataSource, nil, tableView.dataSource]) { $0 === $1 }
}
func testDataSource_commitForRowAt_methodInvoked() {
@ -506,7 +506,7 @@ extension UITableViewTests {
let commitedEvents = tableView.rx.dataSource.methodInvoked(#selector(UITableViewDataSource.tableView(_:commit:forRowAt:)))
XCTAssertFalse(tableView.dataSource!.responds(to: #selector(UITableViewDataSource.tableView(_:commit:forRowAt:))))
XCTAssertEqual(setDataSources, [tableView.dataSource!] as [UITableViewDataSource?]) { $0 === $1 }
XCTAssertArraysEqual(setDataSources, [tableView.dataSource!] as [UITableViewDataSource?]) { $0 === $1 }
var firstEvents: [Arguments] = []
var secondEvents: [Arguments] = []
@ -516,14 +516,14 @@ extension UITableViewTests {
})
XCTAssertTrue(tableView.dataSource!.responds(to: #selector(UITableViewDataSource.tableView(_:commit:forRowAt:))))
XCTAssertEqual(setDataSources, [tableView.dataSource, nil, tableView.dataSource] as [UITableViewDataSource?]) { $0 === $1 }
XCTAssertArraysEqual(setDataSources, [tableView.dataSource, nil, tableView.dataSource] as [UITableViewDataSource?]) { $0 === $1 }
let subscription2 = commitedEvents.subscribe(onNext: { event in
secondEvents.append(Arguments(values: event))
})
XCTAssertTrue(tableView.dataSource!.responds(to: #selector(UITableViewDataSource.tableView(_:commit:forRowAt:))))
XCTAssertEqual(setDataSources, [tableView.dataSource, nil, tableView.dataSource] as [UITableViewDataSource?]) { $0 === $1 }
XCTAssertArraysEqual(setDataSources, [tableView.dataSource, nil, tableView.dataSource] as [UITableViewDataSource?]) { $0 === $1 }
let deleteEditingStyle: NSNumber = NSNumber(value: UITableViewCellEditingStyle.delete.rawValue)
let indexPath: NSIndexPath = NSIndexPath(item: 0, section: 0)
@ -536,12 +536,12 @@ extension UITableViewTests {
subscription1.dispose()
XCTAssertTrue(tableView.dataSource!.responds(to: #selector(UITableViewDataSource.tableView(_:commit:forRowAt:))))
XCTAssertEqual(setDataSources, [tableView.dataSource, nil, tableView.dataSource] as [UITableViewDataSource?]) { $0 === $1 }
XCTAssertArraysEqual(setDataSources, [tableView.dataSource, nil, tableView.dataSource] as [UITableViewDataSource?]) { $0 === $1 }
subscription2.dispose()
XCTAssertFalse(tableView.dataSource!.responds(to: #selector(UITableViewDataSource.tableView(_:commit:forRowAt:))))
XCTAssertEqual(setDataSources, [tableView.dataSource, nil, tableView.dataSource, nil, tableView.dataSource]) { $0 === $1 }
XCTAssertArraysEqual(setDataSources, [tableView.dataSource, nil, tableView.dataSource, nil, tableView.dataSource]) { $0 === $1 }
}

View File

@ -14,7 +14,7 @@ import XCTest
// UITextField
class UITextFieldTests : RxTest {
func testTextCompletesOnDealloc() {
ensurePropertyDeallocated({ UITextField() }, "a") { (view: UITextField) in view.rx.text }
ensurePropertyDeallocated({ UITextField() }, "a", comparer: { $0 == $1 }) { (view: UITextField) in view.rx.text }
}
func testSettingTextDoesntClearMarkedText() {

View File

@ -15,7 +15,7 @@ import XCTest
class UITextViewTests : RxTest {
func testText_DelegateEventCompletesOnDealloc() {
let createView: () -> UITextView = { UITextView(frame: CGRect(x: 0, y: 0, width: 1, height: 1)) }
ensurePropertyDeallocated(createView, "text") { (view: UITextView) in view.rx.text }
ensurePropertyDeallocated(createView, "text", comparer: { $0 == $1 }) { (view: UITextView) in view.rx.text }
}
func testSettingTextDoesntClearMarkedText() {

View File

@ -0,0 +1,43 @@
//
// Event+Test.swift
// Tests
//
// Created by Krunoslav Zaher on 10/16/16.
// Copyright © 2016 Krunoslav Zaher. All rights reserved.
//
import Foundation
import RxSwift
import XCTest
import RxTest
class EventTests: RxTest {
}
extension EventTests {
func testMapTransformNext() {
let original = Event.next(1)
XCTAssertEqual(Event.next(2), original.map { x -> Int in x + 1 }) { $0 == $1 }
}
func testMapTransformNextThrow() {
let original = Event.next(1)
XCTAssertEqual(Event.error(testError), original.map { _ -> Int in throw testError }) { $0 == $1 }
}
func testMapTransformError() {
let original = Event<Int>.error(testError2)
XCTAssertEqual(Event.error(testError2), original.map { _ -> Int in throw testError }) { $0 == $1 }
}
func testMapTransformCompleted() {
let original = Event<Int>.completed
XCTAssertEqual(Event.completed, original.map { _ -> Int in throw testError }) { $0 == $1 }
}
}

View File

@ -9,6 +9,7 @@
import Foundation
import XCTest
import RxSwift
import RxTest
class ObserverTests: RxTest { }
@ -89,3 +90,45 @@ extension ObserverTests {
XCTAssertEqual(elements, [0])
}
}
extension ObserverTests {
func testMapElement() {
let observer = PrimitiveMockObserver<Int>()
observer.map { (x: Int) -> Int in
return x / 2
}.on(.next(2))
XCTAssertEqual(observer.events, [next(1)])
}
func testMapElementCompleted() {
let observer = PrimitiveMockObserver<Int>()
observer.map { (x: Int) -> Int in
return x / 2
}.on(.completed)
XCTAssertEqual(observer.events, [completed()])
}
func testMapElementError() {
let observer = PrimitiveMockObserver<Int>()
observer.map { (x: Int) -> Int in
return x / 2
}.on(.error(testError))
XCTAssertEqual(observer.events, [error(testError)])
}
func testMapElementThrow() {
let observer = PrimitiveMockObserver<Int>()
observer.map { (x: Int) -> Int in
throw testError
}.on(.next(2))
XCTAssertEqual(observer.events, [error(testError)])
}
}

View File

@ -42,7 +42,7 @@ func XCTAssertEqualNSValues(_ lhs: AnyObject, rhs: AnyObject) {
}
func XCTAssertEqualAnyObjectArrayOfArrays(_ lhs: [[Any]], _ rhs: [[Any]]) {
XCTAssertEqual(lhs, rhs) { lhs, rhs in
XCTAssertArraysEqual(lhs, rhs) { (lhs: [Any], rhs: [Any]) in
if lhs.count != rhs.count {
return false
}
@ -54,7 +54,16 @@ func XCTAssertEqualAnyObjectArrayOfArrays(_ lhs: [[Any]], _ rhs: [[Any]]) {
}
}
func XCTAssertEqual<T>(_ lhs: [T], _ rhs: [T], _ comparison: (T, T) -> Bool) {
func XCTAssertEqual<T>(_ lhs: T, _ rhs: T, _ comparison: (T, T) -> Bool) {
let areEqual = comparison(lhs, rhs)
XCTAssertTrue(areEqual)
if (!areEqual) {
print(lhs)
print(rhs)
}
}
func XCTAssertArraysEqual<T>(_ lhs: [T], _ rhs: [T], _ comparison: (T, T) -> Bool) {
XCTAssertEqual(lhs.count, rhs.count)
let areEqual = zip(lhs, rhs).reduce(true) { (a: Bool, z: (T, T)) in a && comparison(z.0, z.1) }
XCTAssertTrue(areEqual)