mirror of
https://github.com/ReactiveX/RxSwift.git
synced 2024-10-04 22:17:41 +03:00
Makes rx.text
type consistent with UIKit String?
type.
This commit is contained in:
parent
b85d57fe8b
commit
2e520bf264
15
CHANGELOG.md
15
CHANGELOG.md
@ -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
|
||||
|
@ -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 */,
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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: (
|
||||
|
@ -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: (
|
||||
|
@ -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 }
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
1
Sources/AllTestz/Event+Test.swift
Symbolic link
1
Sources/AllTestz/Event+Test.swift
Symbolic link
@ -0,0 +1 @@
|
||||
../../Tests/RxSwiftTests/Event+Test.swift
|
@ -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),
|
||||
|
@ -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], ==)
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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 }
|
||||
}
|
||||
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -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() {
|
||||
|
43
Tests/RxSwiftTests/Event+Test.swift
Normal file
43
Tests/RxSwiftTests/Event+Test.swift
Normal 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 }
|
||||
}
|
||||
}
|
||||
|
@ -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)])
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user