From e22cf3dfb878d5569f563b6de8eb44ef920d880d Mon Sep 17 00:00:00 2001 From: Krunoslav Zaher Date: Mon, 19 Oct 2015 00:52:11 +0200 Subject: [PATCH] Adds RxCocoa unit tests. --- .../Implementations/ControlTarget.swift | 10 +- RxCocoa/Common/RxTarget.swift | 15 ++ RxCocoa/OSX/NSControl+Rx.swift | 76 ++++++-- RxCocoa/OSX/NSTextField+Rx.swift | 11 +- RxCocoa/iOS/UIBarButtonItem+Rx.swift | 21 ++- RxCocoa/iOS/UICollectionView+Rx.swift | 9 +- RxCocoa/iOS/UIControl+Rx.swift | 25 ++- RxCocoa/iOS/UIGestureRecognizer+Rx.swift | 17 +- RxCocoa/iOS/UITableView+Rx.swift | 10 +- .../RxCocoaTests/Control+RxTests+Cocoa.swift | 44 +++++ .../RxCocoaTests/Control+RxTests+UIKit.swift | 174 ++++++++++++++++++ RxTests/RxCocoaTests/Control+RxTests.swift | 76 ++++++++ RxTests/RxCocoaTests/UIControl+RxTests.swift | 32 ---- RxTests/RxTests.xcodeproj/project.pbxproj | 30 ++- 14 files changed, 452 insertions(+), 98 deletions(-) create mode 100644 RxTests/RxCocoaTests/Control+RxTests+Cocoa.swift create mode 100644 RxTests/RxCocoaTests/Control+RxTests+UIKit.swift create mode 100644 RxTests/RxCocoaTests/Control+RxTests.swift delete mode 100644 RxTests/RxCocoaTests/UIControl+RxTests.swift diff --git a/RxCocoa/Common/Observables/Implementations/ControlTarget.swift b/RxCocoa/Common/Observables/Implementations/ControlTarget.swift index c74c5544..da2c7459 100644 --- a/RxCocoa/Common/Observables/Implementations/ControlTarget.swift +++ b/RxCocoa/Common/Observables/Implementations/ControlTarget.swift @@ -30,7 +30,7 @@ class ControlTarget: RxTarget { let selector: Selector = "eventHandler:" - unowned let control: Control + weak var control: Control? #if os(iOS) || os(tvOS) let controlEvents: UIControlEvents #endif @@ -72,7 +72,7 @@ class ControlTarget: RxTarget { #endif func eventHandler(sender: Control!) { - if let callback = self.callback { + if let callback = self.callback, control = self.control { callback(control) } } @@ -80,10 +80,10 @@ class ControlTarget: RxTarget { override func dispose() { super.dispose() #if os(iOS) || os(tvOS) - self.control.removeTarget(self, action: self.selector, forControlEvents: self.controlEvents) + self.control?.removeTarget(self, action: self.selector, forControlEvents: self.controlEvents) #elseif os(OSX) - self.control.target = nil - self.control.action = nil + self.control?.target = nil + self.control?.action = nil #endif self.callback = nil } diff --git a/RxCocoa/Common/RxTarget.swift b/RxCocoa/Common/RxTarget.swift index c59a1424..a32e6d87 100644 --- a/RxCocoa/Common/RxTarget.swift +++ b/RxCocoa/Common/RxTarget.swift @@ -19,11 +19,26 @@ class RxTarget : NSObject override init() { super.init() self.retainSelf = self + +#if TRACE_RESOURCES + OSAtomicIncrement32(&resourceCount) +#endif + +#if DEBUG MainScheduler.ensureExecutingOnScheduler() +#endif } func dispose() { +#if DEBUG MainScheduler.ensureExecutingOnScheduler() +#endif self.retainSelf = nil } + +#if TRACE_RESOURCES + deinit { + OSAtomicDecrement32(&resourceCount) + } +#endif } \ No newline at end of file diff --git a/RxCocoa/OSX/NSControl+Rx.swift b/RxCocoa/OSX/NSControl+Rx.swift index 6a267fb6..85b79764 100644 --- a/RxCocoa/OSX/NSControl+Rx.swift +++ b/RxCocoa/OSX/NSControl+Rx.swift @@ -12,36 +12,74 @@ import Cocoa import RxSwift #endif +var rx_value_key: UInt8 = 0 +var rx_control_events_key: UInt8 = 0 + extension NSControl { /** Reactive wrapper for control event. */ public var rx_controlEvents: ControlEvent { - let source: Observable = AnonymousObservable { observer in - MainScheduler.ensureExecutingOnScheduler() - - let observer = ControlTarget(control: self) { control in - observer.on(.Next()) - } - - return observer - }.takeUntil(rx_deallocated) + MainScheduler.ensureExecutingOnScheduler() + + let source = rx_lazyInstanceObservable(&rx_control_events_key) { () -> Observable in + AnonymousObservable { [weak self] observer in + MainScheduler.ensureExecutingOnScheduler() + + guard let control = self else { + observer.on(.Completed) + return NopDisposable.instance + } + + let observer = ControlTarget(control: control) { control in + observer.on(.Next()) + } + + return observer + }.takeUntil(self.rx_deallocated) + } return ControlEvent(source: source) } - + + /** + Helper to make sure that `Observable` returned from `createCachedObservable` is only created once. + This is important because on OSX there is only one `target` and `action` properties on `NSControl`. + */ + func rx_lazyInstanceObservable(key: UnsafePointer, createCachedObservable: () -> T) -> T { + if let value = objc_getAssociatedObject(self, key) { + return value as! T + } + + let observable = createCachedObservable() + + objc_setAssociatedObject(self, key, observable, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) + + return observable + } + func rx_value(getter getter: () -> T, setter: T -> Void) -> ControlProperty { - let source: Observable = AnonymousObservable { observer in - observer.on(.Next(getter())) - - let observer = ControlTarget(control: self) { control in + MainScheduler.ensureExecutingOnScheduler() + + let source = rx_lazyInstanceObservable(&rx_value_key) { () -> Observable in + return AnonymousObservable { [weak self] observer in + guard let control = self else { + observer.on(.Completed) + return NopDisposable.instance + } + observer.on(.Next(getter())) - } - - return observer - }.takeUntil(rx_deallocated) - + + let observer = ControlTarget(control: control) { control in + observer.on(.Next(getter())) + } + + return observer + }.takeUntil(self.rx_deallocated) + } + + return ControlProperty(source: source, observer: AnyObserver { event in switch event { case .Next(let value): diff --git a/RxCocoa/OSX/NSTextField+Rx.swift b/RxCocoa/OSX/NSTextField+Rx.swift index 99dc0d1d..af6646bb 100644 --- a/RxCocoa/OSX/NSTextField+Rx.swift +++ b/RxCocoa/OSX/NSTextField+Rx.swift @@ -15,13 +15,12 @@ import RxSwift class RxTextFieldDelegate : DelegateProxy , NSTextFieldDelegate , DelegateProxyType { - let textField: NSTextField - let textSubject = ReplaySubject.create(bufferSize: 1) + let textSubject = PublishSubject() required init(parentObject: AnyObject) { - self.textField = parentObject as! NSTextField + let textField = parentObject as! NSTextField super.init(parentObject: parentObject) - self.textSubject.on(.Next(self.textField.stringValue)) + self.textSubject.on(.Next(textField.stringValue)) } override func controlTextDidChange(notification: NSNotification) { @@ -59,7 +58,9 @@ extension NSTextField { public var rx_text: ControlProperty { let delegate = proxyForObject(self) as RxTextFieldDelegate - let source = delegate.textSubject + let source = deferred { [weak self] in + delegate.textSubject.startWith(self?.stringValue ?? "") + }.takeUntil(rx_deallocated) return ControlProperty(source: source, observer: AnyObserver { [weak self] event in MainScheduler.ensureExecutingOnScheduler() diff --git a/RxCocoa/iOS/UIBarButtonItem+Rx.swift b/RxCocoa/iOS/UIBarButtonItem+Rx.swift index c31bba0c..34952a29 100644 --- a/RxCocoa/iOS/UIBarButtonItem+Rx.swift +++ b/RxCocoa/iOS/UIBarButtonItem+Rx.swift @@ -38,8 +38,14 @@ extension UIBarButtonItem { Reactive wrapper for target action pattern on `self`. */ public var rx_tap: ControlEvent { - let source: Observable = AnonymousObservable { observer in - let target = BarButtonItemTarget(barButtonItem: self) { + let source: Observable = AnonymousObservable { [weak self] observer in + + guard let control = self else { + observer.on(.Completed) + return NopDisposable.instance + } + + let target = BarButtonItemTarget(barButtonItem: control) { observer.on(.Next()) } return target @@ -52,7 +58,7 @@ extension UIBarButtonItem { @objc -class BarButtonItemTarget: NSObject, Disposable { +class BarButtonItemTarget: RxTarget { typealias Callback = () -> Void weak var barButtonItem: UIBarButtonItem? @@ -66,12 +72,11 @@ class BarButtonItemTarget: NSObject, Disposable { barButtonItem.action = Selector("action:") } - deinit { - dispose() - } - - func dispose() { + override func dispose() { + super.dispose() +#if DEBUG MainScheduler.ensureExecutingOnScheduler() +#endif barButtonItem?.target = nil barButtonItem?.action = nil diff --git a/RxCocoa/iOS/UICollectionView+Rx.swift b/RxCocoa/iOS/UICollectionView+Rx.swift index 2bd20fd1..5a85c43f 100644 --- a/RxCocoa/iOS/UICollectionView+Rx.swift +++ b/RxCocoa/iOS/UICollectionView+Rx.swift @@ -143,10 +143,13 @@ extension UICollectionView { */ public func rx_modelSelected() -> ControlEvent { - let source: Observable = rx_itemSelected .map { indexPath in - let dataSource: RxCollectionViewReactiveArrayDataSource = castOrFatalError(self.rx_dataSource.forwardToDelegate(), message: "This method only works in case one of the `rx_itemsWith*` methods was used.") + let source: Observable = rx_itemSelected.flatMap { [weak self] indexPath -> Observable in + guard let view = self else { + return empty() + } + let dataSource: RxCollectionViewReactiveArrayDataSource = castOrFatalError(view.rx_dataSource.forwardToDelegate(), message: "This method only works in case one of the `rx_itemsWith*` methods was used.") - return dataSource.modelAtIndex(indexPath.item)! + return just(dataSource.modelAtIndex(indexPath.item)!) } return ControlEvent(source: source) diff --git a/RxCocoa/iOS/UIControl+Rx.swift b/RxCocoa/iOS/UIControl+Rx.swift index 63cb2245..c72465c8 100644 --- a/RxCocoa/iOS/UIControl+Rx.swift +++ b/RxCocoa/iOS/UIControl+Rx.swift @@ -41,10 +41,15 @@ extension UIControl { - parameter controlEvents: Filter for observed event types. */ public func rx_controlEvents(controlEvents: UIControlEvents) -> ControlEvent { - let source: Observable = AnonymousObservable { observer in + let source: Observable = AnonymousObservable { [weak self] observer in MainScheduler.ensureExecutingOnScheduler() - - let controlTarget = ControlTarget(control: self, controlEvents: controlEvents) { + + guard let control = self else { + observer.on(.Completed) + return NopDisposable.instance + } + + let controlTarget = ControlTarget(control: control, controlEvents: controlEvents) { control in observer.on(.Next()) } @@ -58,11 +63,15 @@ extension UIControl { } func rx_value(getter getter: () -> T, setter: T -> Void) -> ControlProperty { - let source: Observable = AnonymousObservable { observer in - + let source: Observable = AnonymousObservable { [weak self] observer in + guard let control = self else { + observer.on(.Completed) + return NopDisposable.instance + } + observer.on(.Next(getter())) - - let controlTarget = ControlTarget(control: self, controlEvents: [.EditingChanged, .ValueChanged]) { control in + + let controlTarget = ControlTarget(control: control, controlEvents: [.EditingChanged, .ValueChanged]) { control in observer.on(.Next(getter())) } @@ -73,7 +82,7 @@ extension UIControl { return ControlProperty(source: source, observer: AnyObserver { event in MainScheduler.ensureExecutingOnScheduler() - + switch event { case .Next(let value): setter(value) diff --git a/RxCocoa/iOS/UIGestureRecognizer+Rx.swift b/RxCocoa/iOS/UIGestureRecognizer+Rx.swift index 16685d1d..ec02538d 100644 --- a/RxCocoa/iOS/UIGestureRecognizer+Rx.swift +++ b/RxCocoa/iOS/UIGestureRecognizer+Rx.swift @@ -20,7 +20,7 @@ class GestureTarget: RxTarget { let selector = Selector("eventHandler:") - unowned let gestureRecognizer: UIGestureRecognizer + weak var gestureRecognizer: UIGestureRecognizer? var callback: Callback? init(_ gestureRecognizer: UIGestureRecognizer, callback: Callback) { @@ -38,15 +38,15 @@ class GestureTarget: RxTarget { } func eventHandler(sender: UIGestureRecognizer!) { - if let callback = self.callback { - callback(self.gestureRecognizer) + if let callback = self.callback, gestureRecognizer = self.gestureRecognizer { + callback(gestureRecognizer) } } override func dispose() { super.dispose() - self.gestureRecognizer.removeTarget(self, action: self.selector) + self.gestureRecognizer?.removeTarget(self, action: self.selector) self.callback = nil } } @@ -59,10 +59,15 @@ extension UIGestureRecognizer { public var rx_event: ControlEvent { let source: Observable = AnonymousObservable { [weak self] observer in MainScheduler.ensureExecutingOnScheduler() + + guard let control = self else { + observer.on(.Completed) + return NopDisposable.instance + } - let observer = GestureTarget(self!) { + let observer = GestureTarget(control) { control in - observer.on(.Next(self!)) + observer.on(.Next(control)) } return observer diff --git a/RxCocoa/iOS/UITableView+Rx.swift b/RxCocoa/iOS/UITableView+Rx.swift index 28d59935..911ea040 100644 --- a/RxCocoa/iOS/UITableView+Rx.swift +++ b/RxCocoa/iOS/UITableView+Rx.swift @@ -187,10 +187,14 @@ extension UITableView { */ public func rx_modelSelected() -> ControlEvent { - let source: Observable = rx_itemSelected.map { ip in - let dataSource: RxTableViewReactiveArrayDataSource = castOrFatalError(self.rx_dataSource.forwardToDelegate(), message: "This method only works in case one of the `rx_subscribeItemsTo` methods was used.") + let source: Observable = rx_itemSelected.flatMap { [weak self] ip -> Observable in + guard let view = self else { + return empty() + } + + let dataSource: RxTableViewReactiveArrayDataSource = castOrFatalError(view.rx_dataSource.forwardToDelegate(), message: "This method only works in case one of the `rx_subscribeItemsTo` methods was used.") - return dataSource.modelAtIndex(ip.item)! + return just(dataSource.modelAtIndex(ip.item)!) } return ControlEvent(source: source) diff --git a/RxTests/RxCocoaTests/Control+RxTests+Cocoa.swift b/RxTests/RxCocoaTests/Control+RxTests+Cocoa.swift new file mode 100644 index 00000000..9e69a780 --- /dev/null +++ b/RxTests/RxCocoaTests/Control+RxTests+Cocoa.swift @@ -0,0 +1,44 @@ +// +// Control+RxTests+Cocoa.swift +// RxTests +// +// Created by Krunoslav Zaher on 10/19/15. +// +// + +import Cocoa +import RxSwift +import RxCocoa +import XCTest + +// NSTextField +extension ControlTests { + func testTextField_TextCompletesOnDealloc() { + let createView: () -> NSTextField = { NSTextField(frame: CGRectMake(0, 0, 1, 1)) } + ensurePropertyDeallocated(createView, "a") { (view: NSTextField) in view.rx_text } + } +} + +// NSControl +extension ControlTests { + func testControl_DelegateEventCompletesOnDealloc() { + let createView: () -> NSControl = { NSControl(frame: CGRectMake(0, 0, 1, 1)) } + ensureEventDeallocated(createView) { (view: NSControl) in view.rx_controlEvents } + } +} + +// NSSlider +extension ControlTests { + func testCollectionView_DelegateEventCompletesOnDealloc() { + let createView: () -> NSSlider = { NSSlider(frame: CGRectMake(0, 0, 1, 1)) } + ensurePropertyDeallocated(createView, 0.3) { (view: NSSlider) in view.rx_value } + } +} + +// NSButton +extension ControlTests { + func testButton_DelegateEventCompletesOnDealloc() { + let createView: () -> NSButton = { NSButton(frame: CGRectMake(0, 0, 1, 1)) } + ensureEventDeallocated(createView) { (view: NSButton) in view.rx_tap } + } +} \ No newline at end of file diff --git a/RxTests/RxCocoaTests/Control+RxTests+UIKit.swift b/RxTests/RxCocoaTests/Control+RxTests+UIKit.swift new file mode 100644 index 00000000..63bba54e --- /dev/null +++ b/RxTests/RxCocoaTests/Control+RxTests+UIKit.swift @@ -0,0 +1,174 @@ +// +// Control+RxTests+UIKit.swift +// RxTests +// +// Created by Ash Furrow on 2015-07-04. +// +// + +import UIKit +import RxSwift +import RxCocoa +import XCTest + +extension ControlTests { + func testSubscribeEnabledToTrue() { + let subject = UIControl() + let enabledSequence = Variable(false) + enabledSequence.subscribe(subject.rx_enabled) + + enabledSequence.value = true + XCTAssert(subject.enabled == true, "Expected enabled set to true") + } + + func testSubscribeEnabledToFalse() { + let subject = UIControl() + let enabledSequence = Variable(true) + enabledSequence.subscribe(subject.rx_enabled) + + enabledSequence.value = false + XCTAssert(subject.enabled == false, "Expected enabled set to false") + } +} + +// UITextField +extension ControlTests { + func testTextField_TextCompletesOnDealloc() { + ensurePropertyDeallocated({ UITextField() }, "a") { (view: UITextField) in view.rx_text } + } +} + +// Attempting to load the view of a view controller while it is deallocating is not allowed and may result in undefined behavior () +// Don't know why can't use ActionSheet and AlertView inside unit tests + + +// UIActionSheet +extension ControlTests { + func testActionSheet_DelegateEventCompletesOnDealloc() { + let createActionSheet: () -> UIActionSheet = { UIActionSheet(title: "", delegate: nil, cancelButtonTitle: "", destructiveButtonTitle: "") } + ensureEventDeallocated(createActionSheet) { (view: UIActionSheet) in view.rx_clickedButtonAtIndex } + ensureEventDeallocated(createActionSheet) { (view: UIActionSheet) in view.rx_didDismissWithButtonIndex } + ensureEventDeallocated(createActionSheet) { (view: UIActionSheet) in view.rx_willDismissWithButtonIndex } + } +} + +// UIAlertView +extension ControlTests { + func testAlertView_DelegateEventCompletesOnDealloc() { + let createAlertView: () -> UIAlertView = { UIAlertView(title: "", message: "", delegate: nil, cancelButtonTitle: nil) } + ensureEventDeallocated(createAlertView) { (view: UIAlertView) in view.rx_clickedButtonAtIndex } + ensureEventDeallocated(createAlertView) { (view: UIAlertView) in view.rx_didDismissWithButtonIndex } + ensureEventDeallocated(createAlertView) { (view: UIAlertView) in view.rx_willDismissWithButtonIndex } + } +} + + +// UIBarButtonItem +extension ControlTests { + func testBarButtonItem_DelegateEventCompletesOnDealloc() { + ensureEventDeallocated({ UIBarButtonItem() }) { (view: UIBarButtonItem) in view.rx_tap } + } +} + +// UICollectionView +extension ControlTests { + func testCollectionView_DelegateEventCompletesOnDealloc() { + let layout = UICollectionViewFlowLayout() + let createView: () -> UICollectionView = { UICollectionView(frame: CGRectMake(0, 0, 1, 1), collectionViewLayout: layout) } + ensureEventDeallocated(createView) { (view: UICollectionView) in view.rx_itemSelected } + ensureEventDeallocated(createView) { (view: UICollectionView) in view.rx_modelSelected() as ControlEvent } + } +} + + +// UITableView +extension ControlTests { + func testTableView_DelegateEventCompletesOnDealloc() { + let createView: () -> UITableView = { UITableView(frame: CGRectMake(0, 0, 1, 1)) } + ensureEventDeallocated(createView) { (view: UITableView) in view.rx_itemSelected } + ensureEventDeallocated(createView) { (view: UITableView) in view.rx_modelSelected() as ControlEvent } + ensureEventDeallocated(createView) { (view: UITableView) in view.rx_itemDeleted } + ensureEventDeallocated(createView) { (view: UITableView) in view.rx_itemMoved } + ensureEventDeallocated(createView) { (view: UITableView) in view.rx_itemInserted } + } +} + +// UIControl +extension ControlTests { + func testControl_DelegateEventCompletesOnDealloc() { + let createView: () -> UIControl = { UIControl(frame: CGRectMake(0, 0, 1, 1)) } + ensureEventDeallocated(createView) { (view: UIControl) in view.rx_controlEvents(.AllEditingEvents) } + } +} + +// UIDatePicker +extension ControlTests { + func testDatePicker_DelegateEventCompletesOnDealloc() { + let createView: () -> UIDatePicker = { UIDatePicker(frame: CGRectMake(0, 0, 1, 1)) } + ensurePropertyDeallocated(createView, NSDate()) { (view: UIDatePicker) in view.rx_date } + } +} + +// UIGestureRecognizer +extension ControlTests { + func testGestureRecognizer_DelegateEventCompletesOnDealloc() { + let createView: () -> UIGestureRecognizer = { UIGestureRecognizer(target: nil, action: "s") } + ensureEventDeallocated(createView) { (view: UIGestureRecognizer) in view.rx_event } + } +} + +// UIScrollView +extension ControlTests { + func testScrollView_DelegateEventCompletesOnDealloc() { + let createView: () -> UIScrollView = { UIScrollView(frame: CGRectMake(0, 0, 1, 1)) } + ensurePropertyDeallocated(createView, CGPoint(x: 1, y: 1)) { (view: UIScrollView) in view.rx_contentOffset } + } +} + +// UISearchBar +extension ControlTests { + func testSearchBar_DelegateEventCompletesOnDealloc() { + let createView: () -> UISearchBar = { UISearchBar(frame: CGRectMake(0, 0, 1, 1)) } + ensurePropertyDeallocated(createView, "a") { (view: UISearchBar) in view.rx_text } + } +} + +// UISegmentedControl +extension ControlTests { + func testSegmentedControl_DelegateEventCompletesOnDealloc() { + let createView: () -> UISegmentedControl = { UISegmentedControl(items: ["a", "b", "c"]) } + ensurePropertyDeallocated(createView, 1) { (view: UISegmentedControl) in view.rx_value } + } +} + +// UISlider +extension ControlTests { + func testSlider_DelegateEventCompletesOnDealloc() { + let createView: () -> UISlider = { UISlider(frame: CGRectMake(0, 0, 1, 1)) } + ensurePropertyDeallocated(createView, 0.5) { (view: UISlider) in view.rx_value } + } +} + +// UIStepper +extension ControlTests { + func testStepper_DelegateEventCompletesOnDealloc() { + let createView: () -> UIStepper = { UIStepper(frame: CGRectMake(0, 0, 1, 1)) } + ensurePropertyDeallocated(createView, 1) { (view: UIStepper) in view.rx_value } + } +} + +// UISwitch +extension ControlTests { + func testSwitch_DelegateEventCompletesOnDealloc() { + let createView: () -> UISwitch = { UISwitch(frame: CGRectMake(0, 0, 1, 1)) } + ensurePropertyDeallocated(createView, true) { (view: UISwitch) in view.rx_value } + } +} + +// UITextView +extension ControlTests { + func testText_DelegateEventCompletesOnDealloc() { + let createView: () -> UITextView = { UITextView(frame: CGRectMake(0, 0, 1, 1)) } + ensurePropertyDeallocated(createView, "text") { (view: UITextView) in view.rx_text } + } +} \ No newline at end of file diff --git a/RxTests/RxCocoaTests/Control+RxTests.swift b/RxTests/RxCocoaTests/Control+RxTests.swift new file mode 100644 index 00000000..a409c687 --- /dev/null +++ b/RxTests/RxCocoaTests/Control+RxTests.swift @@ -0,0 +1,76 @@ +// +// Control+RxTests.swift +// RxTests +// +// Created by Krunoslav Zaher on 10/18/15. +// +// + +import Foundation +import RxCocoa +import RxSwift +import XCTest + +class ControlTests : RxTest { + + func ensurePropertyDeallocated(createControl: () -> C, _ initialValue: T, _ propertySelector: C -> ControlProperty) { + let variable = Variable(initialValue) + + + var completed = false + var deallocated = false + var lastReturnedPropertyValue: T! + + autoreleasepool { + var control: C! = createControl() + + let property = propertySelector(control) + + let disposable = variable.bindTo(property) + + _ = property.subscribe(onNext: { n in + lastReturnedPropertyValue = n + }, onCompleted: { + completed = true + disposable.dispose() + }) + + + _ = control.rx_deallocated.subscribeNext { _ in + deallocated = true + } + + control = nil + } + + XCTAssertTrue(deallocated) + XCTAssertTrue(completed) + XCTAssertEqual(initialValue, lastReturnedPropertyValue) + } + + func ensureEventDeallocated(createControl: () -> C, _ eventSelector: C -> ControlEvent) { + var completed = false + var deallocated = false + + autoreleasepool { + var control: C! = createControl() + let eventObservable = eventSelector(control) + + _ = eventObservable.subscribe(onNext: { n in + + }, onCompleted: { + completed = true + }) + + _ = control.rx_deallocated.subscribeNext { _ in + deallocated = true + } + + control = nil + } + + + XCTAssertTrue(deallocated) + XCTAssertTrue(completed) + } +} diff --git a/RxTests/RxCocoaTests/UIControl+RxTests.swift b/RxTests/RxCocoaTests/UIControl+RxTests.swift deleted file mode 100644 index 770ae194..00000000 --- a/RxTests/RxCocoaTests/UIControl+RxTests.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// UIControl+RxTests.swift -// RxTests -// -// Created by Ash Furrow on 2015-07-04. -// -// - -import UIKit -import RxSwift -import RxCocoa -import XCTest - -class UIControlRxTests : RxTest { - func testSubscribeEnabledToTrue() { - let subject = UIControl() - let enabledSequence = Variable(false) - enabledSequence.subscribe(subject.rx_enabled) - - enabledSequence.value = true - XCTAssert(subject.enabled == true, "Expected enabled set to true") - } - - func testSubscribeEnabledToFalse() { - let subject = UIControl() - let enabledSequence = Variable(true) - enabledSequence.subscribe(subject.rx_enabled) - - enabledSequence.value = false - XCTAssert(subject.enabled == false, "Expected enabled set to false") - } -} diff --git a/RxTests/RxTests.xcodeproj/project.pbxproj b/RxTests/RxTests.xcodeproj/project.pbxproj index 1ba1677b..8fe89feb 100644 --- a/RxTests/RxTests.xcodeproj/project.pbxproj +++ b/RxTests/RxTests.xcodeproj/project.pbxproj @@ -7,7 +7,7 @@ objects = { /* Begin PBXBuildFile section */ - 5E5D10BB1B48355200432B25 /* UIControl+RxTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E5D10BA1B48355200432B25 /* UIControl+RxTests.swift */; }; + 5E5D10BB1B48355200432B25 /* Control+RxTests+UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E5D10BA1B48355200432B25 /* Control+RxTests+UIKit.swift */; }; C69B65001BA88FAC00A7FA73 /* ObserverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C69B64FF1BA88FAC00A7FA73 /* ObserverTests.swift */; }; C69B65011BA8957C00A7FA73 /* ObserverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C69B64FF1BA88FAC00A7FA73 /* ObserverTests.swift */; }; C801EB5A1B97951100C4D8C4 /* Observable+CreationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C801EB591B97951100C4D8C4 /* Observable+CreationTest.swift */; }; @@ -112,6 +112,10 @@ C8E3812B1B2083C2008CDC33 /* PrimitiveMockObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8E3812A1B2083C2008CDC33 /* PrimitiveMockObserver.swift */; }; C8E3812C1B2083C2008CDC33 /* PrimitiveMockObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8E3812A1B2083C2008CDC33 /* PrimitiveMockObserver.swift */; }; C8E3813A1B21B77E008CDC33 /* Observable+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8E381221B2063CC008CDC33 /* Observable+Extensions.swift */; }; + C8E9D2BD1BD422D80079D0DB /* Control+RxTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8E9D2BC1BD422D80079D0DB /* Control+RxTests.swift */; settings = {ASSET_TAGS = (); }; }; + C8E9D2BE1BD422D80079D0DB /* Control+RxTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8E9D2BC1BD422D80079D0DB /* Control+RxTests.swift */; settings = {ASSET_TAGS = (); }; }; + C8E9D2BF1BD422D80079D0DB /* Control+RxTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8E9D2BC1BD422D80079D0DB /* Control+RxTests.swift */; settings = {ASSET_TAGS = (); }; }; + C8E9D2C41BD452650079D0DB /* Control+RxTests+Cocoa.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8E9D2C01BD4525B0079D0DB /* Control+RxTests+Cocoa.swift */; settings = {ASSET_TAGS = (); }; }; C8EA2D371BD02E1900FB22AC /* EquatableArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8EA2D361BD02E1900FB22AC /* EquatableArray.swift */; }; C8EA2D381BD02E1900FB22AC /* EquatableArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8EA2D361BD02E1900FB22AC /* EquatableArray.swift */; }; C8EA2D391BD02E1900FB22AC /* EquatableArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8EA2D361BD02E1900FB22AC /* EquatableArray.swift */; }; @@ -122,7 +126,7 @@ D203C4EE1BB9C22800D02D00 /* KVOObservableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8633AE41B0A9FF300375D60 /* KVOObservableTests.swift */; }; D203C4EF1BB9C22800D02D00 /* RxCocoaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B5BEA01B4A6A82000D732C /* RxCocoaTests.swift */; }; D203C4F01BB9C22800D02D00 /* NSObject+RxTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C81CC92A1B513FD400915606 /* NSObject+RxTests.swift */; }; - D203C5141BB9C54A00D02D00 /* UIControl+RxTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E5D10BA1B48355200432B25 /* UIControl+RxTests.swift */; }; + D203C5141BB9C54A00D02D00 /* Control+RxTests+UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E5D10BA1B48355200432B25 /* Control+RxTests+UIKit.swift */; }; D251ED291BB9BF90002D0E36 /* RxCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C8A468CC1B8A897800BF917B /* RxCocoa.framework */; }; D2AF91971BD2EBB900A008C1 /* MockDisposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2AF91961BD2EBB900A008C1 /* MockDisposable.swift */; }; D2AF91991BD3D9C600A008C1 /* MockDisposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2AF91961BD2EBB900A008C1 /* MockDisposable.swift */; }; @@ -172,7 +176,7 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - 5E5D10BA1B48355200432B25 /* UIControl+RxTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIControl+RxTests.swift"; sourceTree = ""; }; + 5E5D10BA1B48355200432B25 /* Control+RxTests+UIKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Control+RxTests+UIKit.swift"; sourceTree = ""; }; C69B64FF1BA88FAC00A7FA73 /* ObserverTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObserverTests.swift; sourceTree = ""; }; C801EB591B97951100C4D8C4 /* Observable+CreationTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Observable+CreationTest.swift"; sourceTree = ""; }; C80DDEDB1BCE9A03006A1832 /* Driver+Test.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Driver+Test.swift"; sourceTree = ""; }; @@ -231,6 +235,8 @@ C8E381221B2063CC008CDC33 /* Observable+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Observable+Extensions.swift"; sourceTree = ""; }; C8E381271B207D03008CDC33 /* PrimitiveHotObservable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrimitiveHotObservable.swift; sourceTree = ""; }; C8E3812A1B2083C2008CDC33 /* PrimitiveMockObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PrimitiveMockObserver.swift; sourceTree = ""; }; + C8E9D2BC1BD422D80079D0DB /* Control+RxTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Control+RxTests.swift"; sourceTree = ""; }; + C8E9D2C01BD4525B0079D0DB /* Control+RxTests+Cocoa.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Control+RxTests+Cocoa.swift"; sourceTree = ""; }; C8EA2D361BD02E1900FB22AC /* EquatableArray.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EquatableArray.swift; sourceTree = ""; }; C8FDC5F71B2B5B7E0065F8D9 /* ElementIndexPair.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ElementIndexPair.swift; sourceTree = ""; }; D2AF91961BD2EBB900A008C1 /* MockDisposable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockDisposable.swift; sourceTree = ""; }; @@ -324,12 +330,14 @@ C811082A1AF50E2A001C13E4 /* RxCocoaTests */ = { isa = PBXGroup; children = ( - C811082B1AF50E2A001C13E4 /* NSNotificationCenterTests.swift */, - C814CEA61AF642D600E98087 /* UI+RxTests.swift */, - 5E5D10BA1B48355200432B25 /* UIControl+RxTests.swift */, + C8E9D2BC1BD422D80079D0DB /* Control+RxTests.swift */, + 5E5D10BA1B48355200432B25 /* Control+RxTests+UIKit.swift */, + C8E9D2C01BD4525B0079D0DB /* Control+RxTests+Cocoa.swift */, C8633AE41B0A9FF300375D60 /* KVOObservableTests.swift */, - C8B5BEA01B4A6A82000D732C /* RxCocoaTests.swift */, + C811082B1AF50E2A001C13E4 /* NSNotificationCenterTests.swift */, C81CC92A1B513FD400915606 /* NSObject+RxTests.swift */, + C8B5BEA01B4A6A82000D732C /* RxCocoaTests.swift */, + C814CEA61AF642D600E98087 /* UI+RxTests.swift */, ); path = RxCocoaTests; sourceTree = SOURCE_ROOT; @@ -608,10 +616,11 @@ C84B8FC21B89D0D500C9CCCF /* BagTest.swift in Sources */, C81108511AF50E2A001C13E4 /* HotObservable.swift in Sources */, C81108541AF50E2A001C13E4 /* Observable.Extensions.swift in Sources */, + C8E9D2BD1BD422D80079D0DB /* Control+RxTests.swift in Sources */, C897EC3B1B10E000009C2CB0 /* BehaviorSubjectTest.swift in Sources */, C81108681AF50E2A001C13E4 /* QueueTests.swift in Sources */, C81108651AF50E2A001C13E4 /* Observable+SingleTest.swift in Sources */, - 5E5D10BB1B48355200432B25 /* UIControl+RxTests.swift in Sources */, + 5E5D10BB1B48355200432B25 /* Control+RxTests+UIKit.swift in Sources */, C897EC4A1B1123DA009C2CB0 /* Observable+MultipleTest+Zip.swift in Sources */, D2AF91971BD2EBB900A008C1 /* MockDisposable.swift in Sources */, C81108571AF50E2A001C13E4 /* Recorded.swift in Sources */, @@ -660,6 +669,7 @@ C80DDEDD1BCE9A03006A1832 /* Driver+Test.swift in Sources */, C88BB89F1B07E64B0064D411 /* DisposableTest.swift in Sources */, C88BB8A01B07E64B0064D411 /* Observable+StandardSequenceOperatorsTest.swift in Sources */, + C8E9D2C41BD452650079D0DB /* Control+RxTests+Cocoa.swift in Sources */, C88BB8A11B07E64B0064D411 /* Observable+MultipleTest.swift in Sources */, C81CC92C1B513FD400915606 /* NSObject+RxTests.swift in Sources */, D2AF91991BD3D9C600A008C1 /* MockDisposable.swift in Sources */, @@ -683,6 +693,7 @@ C88BB8A91B07E64B0064D411 /* Observable+AggregateTest.swift in Sources */, C88BB8AA1B07E64B0064D411 /* MockObserver.swift in Sources */, C80DDEE11BCEE898006A1832 /* MainThreadPrimitiveHotObservable.swift in Sources */, + C8E9D2BE1BD422D80079D0DB /* Control+RxTests.swift in Sources */, C8AF26F01B499E5C00131C03 /* DelegateProxyTest.swift in Sources */, C69B65011BA8957C00A7FA73 /* ObserverTests.swift in Sources */, C8633AE61B0AA0ED00375D60 /* KVOObservableTests.swift in Sources */, @@ -729,8 +740,9 @@ D2EBEB5F1BB9B7DB003A27DC /* Subscription.swift in Sources */, D2EBEB691BB9B7EF003A27DC /* DelegateProxyTest.swift in Sources */, D2EBEB701BB9B7EF003A27DC /* Observable+MultipleTest.swift in Sources */, - D203C5141BB9C54A00D02D00 /* UIControl+RxTests.swift in Sources */, + D203C5141BB9C54A00D02D00 /* Control+RxTests+UIKit.swift in Sources */, D2EBEB581BB9B7CC003A27DC /* TestObservable.swift in Sources */, + C8E9D2BF1BD422D80079D0DB /* Control+RxTests.swift in Sources */, D2EBEB661BB9B7EF003A27DC /* BehaviorSubjectTest.swift in Sources */, D2EBEB671BB9B7EF003A27DC /* CompositeObserverTest.swift in Sources */, D2EBEB7A1BB9B804003A27DC /* PerformanceTools.swift in Sources */,