Adds UITests.

This commit is contained in:
Krunoslav Zaher 2016-08-20 22:59:34 +02:00
parent c9742e51b6
commit b68fc4260e
17 changed files with 825 additions and 437 deletions

View File

@ -92,7 +92,7 @@ extension Reactive where Base: NSObject {
#endif
// Dealloc
extension Reactive where Base: NSObject {
extension Reactive where Base: AnyObject {
/**
Observable sequence of object deallocated events.
@ -211,7 +211,7 @@ let deallocSelector = NSSelectorFromString("dealloc")
let rxDeallocatingSelector = RX_selector(deallocSelector)
let rxDeallocatingSelectorReference = RX_reference_from_selector(rxDeallocatingSelector)
extension Reactive where Base: NSObject {
extension Reactive where Base: AnyObject {
func synchronized<T>( _ action: () -> T) -> T {
objc_sync_enter(self.base)
let result = action()
@ -220,7 +220,7 @@ extension Reactive where Base: NSObject {
}
}
extension Reactive where Base: NSObject {
extension Reactive where Base: AnyObject {
/**
Helper to make sure that `Observable` returned from `createCachedObservable` is only created once.
This is important because there is only one `target` and `action` properties on `NSControl` or `UIBarButtonItem`.

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: AnyObject, T: Equatable>(_ control: C, getter: @escaping (C) -> T, setter: @escaping (C, T) -> Void) -> ControlProperty<T> {
static func value<C: NSObject, T: Equatable>(_ 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)
@ -80,7 +80,7 @@ extension Reactive where Base: UIControl {
return Disposables.create(with: controlTarget.dispose)
}
.takeUntil((control as! NSObject).rx.deallocated)
.takeUntil((control as NSObject).rx.deallocated)
let bindingObserver = UIBindingObserver(UIElement: control, binding: setter)

View File

@ -18,6 +18,10 @@ extension Reactive where Base: UISwitch {
/**
Reactive wrapper for `on` property.
**Unlike other controls, Apple is reusing instances of UISwitch or a there is a leak,
so underlying observable sequence won't complete when nothing holds a strong reference
to UISwitch.**
*/
public var value: ControlProperty<Bool> {
return Reactive<UIControl>.value(

View File

@ -0,0 +1,266 @@
//
// FlowTests.swift
// RxExample-iOSUITests
//
// Created by Krunoslav Zaher on 8/20/16.
// Copyright © 2016 Krunoslav Zaher. All rights reserved.
//
import XCTest
class FlowTests : XCTestCase {
var app: XCUIApplication!
override func setUp() {
super.setUp()
continueAfterFailure = false
self.app = XCUIApplication()
self.app.launchEnvironment = ["isUITest": ""]
self.app.launch()
}
}
extension FlowTests {
func testAll() {
for test in [
_testSearchWikipedia,
_testMasterDetail,
_testGitHubSignUp,
_testAnimatedPartialUpdates,
_testVisitEveryScreen
] {
test()
wait(interval: 1.0)
}
}
func _testGitHubSignUp() {
app.tables.allElementsBoundByIndex[0].cells.allElementsBoundByIndex[3].tap()
let username = app.textFields.allElementsBoundByIndex[0]
let password = app.secureTextFields.allElementsBoundByIndex[0]
let repeatedPassword = app.secureTextFields.allElementsBoundByIndex[1]
username.tap()
username.typeText("rxrevolution")
password.tap()
password.typeText("mypassword")
repeatedPassword.tap()
repeatedPassword.typeText("mypassword")
app.windows.allElementsBoundByIndex[0].coordinate(withNormalizedOffset: CGVector(dx: 14.50, dy: 80.00)).tap()
app.buttons["Sign up"].tap()
waitForElementToAppear(app.alerts.element(boundBy: 0))
app.alerts.allElementsBoundByIndex[0].buttons.allElementsBoundByIndex[0].tap()
goBack()
}
func _testSearchWikipedia() {
app.tables.allElementsBoundByIndex[0].cells.allElementsBoundByIndex[12].tap()
let searchField = app.tables.children(matching: .searchField).element
searchField.tap()
searchField.typeSlow(text: "banana")
searchField.clearText()
searchField.typeSlow(text: "Yosemite")
searchField.clearText()
goBack()
}
func _testMasterDetail() {
app.tables.allElementsBoundByIndex[0].cells.allElementsBoundByIndex[10].tap()
waitForElementToAppear(app.tables.allElementsBoundByIndex[0].cells.element(boundBy: 5))
let editButton = app.navigationBars.buttons["Edit"]
editButton.tap()
func reorderButtonForIndex(_ index: Int) -> XCUIElement {
return app.tables.cells.allElementsBoundByIndex[index].buttons.allElementsBoundByIndex.filter { element in
element.label.hasPrefix("Reorder ")
}.first!
}
reorderButtonForIndex(5).press(forDuration: 1.5, thenDragTo: reorderButtonForIndex(2))
reorderButtonForIndex(7).press(forDuration: 1.5, thenDragTo: reorderButtonForIndex(4))
reorderButtonForIndex(1).press(forDuration: 1.5, thenDragTo: reorderButtonForIndex(3))
let doneButton = app.navigationBars.buttons["Done"]
doneButton.tap()
app.tables.allElementsBoundByIndex[0].cells.allElementsBoundByIndex[6].tap()
goBack()
goBack()
}
func _testAnimatedPartialUpdates() {
app.tables.allElementsBoundByIndex[0].cells.allElementsBoundByIndex[11].tap()
let randomize = app.navigationBars.buttons["Randomize"]
waitForElementToAppear(randomize)
randomize.tap()
randomize.tap()
randomize.tap()
randomize.tap()
randomize.tap()
randomize.tap()
randomize.tap()
randomize.tap()
randomize.tap()
goBack()
}
func _testVisitEveryScreen() {
let count = Int(app.tables.allElementsBoundByIndex[0].cells.count)
XCTAssertTrue(count > 0)
for i in 0 ..< count {
app.tables.allElementsBoundByIndex[0].cells.allElementsBoundByIndex[i].tap()
goBack()
}
}
}
extension FlowTests {
func testControls() {
for test in [
_testDatePicker,
_testBarButtonItemTap,
_testButtonTap,
_testSegmentedControl,
//_testUISwitch,
_testUITextField,
_testUITextView,
_testSlider
] {
goToControlsView()
test()
goBack()
}
}
func goToControlsView() {
let tableView = app.tables.element(boundBy: 0)
waitForElementToAppear(tableView)
tableView.cells.allElementsBoundByIndex[5].tap()
}
func checkDebugLabelValue(_ expected: String) {
let textValue = app.staticTexts["debugLabel"].value as? String
XCTAssertEqual(textValue, expected)
}
func _testDatePicker() {
let picker = app.datePickers.allElementsBoundByIndex[0]
picker.pickerWheels.element(boundBy: 0).coordinate(withNormalizedOffset: CGVector(dx: 0.49, dy: 0.65)).tap()
picker.pickerWheels.element(boundBy: 1).coordinate(withNormalizedOffset: CGVector(dx: 0.35, dy: 0.64)).tap()
picker.pickerWheels.element(boundBy: 2).coordinate(withNormalizedOffset: CGVector(dx: 0.46, dy: 0.64)).tap()
wait(interval: 1.0)
checkDebugLabelValue("UIDatePicker date 1970-01-02 01:01:00 +0000")
}
func _testBarButtonItemTap() {
app.navigationBars.buttons["TapMe"].tap()
checkDebugLabelValue("UIBarButtonItem Tapped")
}
func _testButtonTap() {
app.scrollViews.buttons["TapMe"].tap()
checkDebugLabelValue("UIButton Tapped")
}
func _testSegmentedControl() {
let segmentedControl = app.scrollViews.segmentedControls.allElementsBoundByIndex[0]
segmentedControl.buttons["Second"].tap()
checkDebugLabelValue("UISegmentedControl value 1")
segmentedControl.buttons["First"].tap()
checkDebugLabelValue("UISegmentedControl value 0")
}
func _testUISwitch() {
let switchControl = app.switches.allElementsBoundByIndex[0]
switchControl.tap()
checkDebugLabelValue("UISwitch value false")
switchControl.tap()
checkDebugLabelValue("UISwitch value true")
}
func _testUITextField() {
let textField = app.textFields.allElementsBoundByIndex[0]
textField.tap()
textField.typeText("f")
checkDebugLabelValue("UITextField text f")
}
func _testUITextView() {
let textView = app.textViews.allElementsBoundByIndex[0]
textView.tap()
textView.typeText("f")
checkDebugLabelValue("UITextView text f")
}
func _testSlider() {
let slider = app.sliders.allElementsBoundByIndex[0]
slider.adjust(toNormalizedSliderPosition: 0)
checkDebugLabelValue("UISlider value 0.0")
}
}
extension FlowTests {
func goBack() {
let navigationBar = app.navigationBars.allElementsBoundByIndex[0]
navigationBar.coordinate(withNormalizedOffset: .zero).withOffset(CGVector(dx: 20, dy: 30)).tap()
wait(interval: 1.5)
}
func waitForElementToAppear(_ element: XCUIElement, timeout: TimeInterval = 2, file: String = #file, line: UInt = #line) {
let existsPredicate = NSPredicate(format: "exists == true")
expectation(for: existsPredicate,
evaluatedWith: element,
handler: nil)
waitForExpectations(timeout: timeout) { (error) -> Void in
if (error != nil) {
let message = "Failed to find \(element) after \(timeout) seconds."
self.recordFailure(withDescription: message, inFile: file, atLine: line, expected: true)
}
}
}
func wait(interval: TimeInterval) {
RunLoop.current.run(until: Date().addingTimeInterval(interval))
}
}
extension XCUIElement {
func clearText() {
let backspace = "\u{8}"
let backspaces = Array(((self.value as? String) ?? "").characters).map { _ in backspace }
self.typeText(backspaces.joined(separator: ""))
}
func typeSlow(text: String) {
for i in text.characters {
self.typeText(String(i))
}
}
}

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>

View File

@ -96,6 +96,7 @@
C88BB8C71B07E6C90064D411 /* Dependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07E3C2321B03605B0010338D /* Dependencies.swift */; };
C88BB8CA1B07E6C90064D411 /* WikipediaAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C86E2F3B1AE5A0CA00C31024 /* WikipediaAPI.swift */; };
C88BB8CC1B07E6C90064D411 /* WikipediaPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C86E2F3C1AE5A0CA00C31024 /* WikipediaPage.swift */; };
C88C2B2A1D67EC5200B01A98 /* FlowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C88C2B291D67EC5200B01A98 /* FlowTests.swift */; };
C890A65D1AEC084100AFF7E6 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C890A65C1AEC084100AFF7E6 /* ViewController.swift */; };
C89634081B95BE50002AE38C /* RxBlocking.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C8A468EF1B8A8BD000BF917B /* RxBlocking.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
C89634091B95BE50002AE38C /* RxCocoa.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C8A468ED1B8A8BCC00BF917B /* RxCocoa.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
@ -125,6 +126,8 @@
C8C46DAA1B47F7110020D71E /* WikipediaSearchCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8C46DA51B47F7110020D71E /* WikipediaSearchCell.swift */; };
C8C46DAB1B47F7110020D71E /* WikipediaSearchCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = C8C46DA61B47F7110020D71E /* WikipediaSearchCell.xib */; };
C8C46DAC1B47F7110020D71E /* WikipediaSearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8C46DA71B47F7110020D71E /* WikipediaSearchViewController.swift */; };
C8CDF0AB1D67F8FC00C18F99 /* UIApplication+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8CDF0AA1D67F8FC00C18F99 /* UIApplication+Extensions.swift */; };
C8CDF0C11D688DF700C18F99 /* UITableView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8CDF0C01D688DF700C18F99 /* UITableView+Extensions.swift */; };
C8D132151C42B54B00B59FFF /* UIImagePickerController+RxCreate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8D132141C42B54B00B59FFF /* UIImagePickerController+RxCreate.swift */; };
C8DF92CD1B0B2F84009BCF9A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8DF92C81B0B2F84009BCF9A /* AppDelegate.swift */; };
C8DF92DF1B0B328B009BCF9A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8DF92DE1B0B328B009BCF9A /* AppDelegate.swift */; };
@ -288,6 +291,13 @@
remoteGlobalIDString = C88FA53F1C25C4CC00CCFEA4;
remoteInfo = "RxTests-watchOS";
};
C88C2B2C1D67EC5200B01A98 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = C83366D51AD0293800C668A7 /* Project object */;
proxyType = 1;
remoteGlobalIDString = C83366DC1AD0293800C668A7;
remoteInfo = "RxExample-iOS";
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
@ -386,6 +396,9 @@
C86E2F3C1AE5A0CA00C31024 /* WikipediaPage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = WikipediaPage.swift; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
C86E2F3D1AE5A0CA00C31024 /* WikipediaSearchResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = WikipediaSearchResult.swift; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
C88BB8DC1B07E6C90064D411 /* RxExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RxExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
C88C2B271D67EC5200B01A98 /* RxExample-iOSUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "RxExample-iOSUITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
C88C2B291D67EC5200B01A98 /* FlowTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlowTests.swift; sourceTree = "<group>"; };
C88C2B2B1D67EC5200B01A98 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
C890A65C1AEC084100AFF7E6 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
C8984CCE1C36BC3E001E4272 /* NumberCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NumberCell.swift; sourceTree = "<group>"; };
C8984CCF1C36BC3E001E4272 /* NumberSectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NumberSectionView.swift; sourceTree = "<group>"; };
@ -410,6 +423,8 @@
C8C46DA51B47F7110020D71E /* WikipediaSearchCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WikipediaSearchCell.swift; sourceTree = "<group>"; };
C8C46DA61B47F7110020D71E /* WikipediaSearchCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = WikipediaSearchCell.xib; sourceTree = "<group>"; };
C8C46DA71B47F7110020D71E /* WikipediaSearchViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WikipediaSearchViewController.swift; sourceTree = "<group>"; };
C8CDF0AA1D67F8FC00C18F99 /* UIApplication+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIApplication+Extensions.swift"; sourceTree = "<group>"; };
C8CDF0C01D688DF700C18F99 /* UITableView+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITableView+Extensions.swift"; sourceTree = "<group>"; };
C8D132141C42B54B00B59FFF /* UIImagePickerController+RxCreate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImagePickerController+RxCreate.swift"; sourceTree = "<group>"; };
C8DF92C81B0B2F84009BCF9A /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
C8DF92DE1B0B328B009BCF9A /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
@ -456,6 +471,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
C88C2B241D67EC5200B01A98 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
@ -548,6 +570,7 @@
C8B290A51C959D2900E923D0 /* RxDataSources */,
C83366DF1AD0293800C668A7 /* RxExample */,
C849EF621C3190360048AC4A /* RxExample-iOSTests */,
C88C2B281D67EC5200B01A98 /* RxExample-iOSUITests */,
C83366DE1AD0293800C668A7 /* Products */,
);
sourceTree = "<group>";
@ -558,6 +581,7 @@
C83366DD1AD0293800C668A7 /* RxExample-iOS.app */,
C88BB8DC1B07E6C90064D411 /* RxExample.app */,
C849EF611C3190360048AC4A /* RxExample-iOSTests.xctest */,
C88C2B271D67EC5200B01A98 /* RxExample-iOSUITests.xctest */,
);
name = Products;
sourceTree = "<group>";
@ -773,6 +797,15 @@
path = GitHubSignup;
sourceTree = "<group>";
};
C88C2B281D67EC5200B01A98 /* RxExample-iOSUITests */ = {
isa = PBXGroup;
children = (
C88C2B291D67EC5200B01A98 /* FlowTests.swift */,
C88C2B2B1D67EC5200B01A98 /* Info.plist */,
);
path = "RxExample-iOSUITests";
sourceTree = "<group>";
};
C8984CCD1C36BC3E001E4272 /* TableViewPartialUpdates */ = {
isa = PBXGroup;
children = (
@ -835,6 +868,8 @@
C8DF92E11B0B32DA009BCF9A /* Main.storyboard */,
C8DF92E21B0B32DA009BCF9A /* RootViewController.swift */,
C8DF92C81B0B2F84009BCF9A /* AppDelegate.swift */,
C8CDF0AA1D67F8FC00C18F99 /* UIApplication+Extensions.swift */,
C8CDF0C01D688DF700C18F99 /* UITableView+Extensions.swift */,
);
path = iOS;
sourceTree = "<group>";
@ -905,13 +940,31 @@
productReference = C88BB8DC1B07E6C90064D411 /* RxExample.app */;
productType = "com.apple.product-type.application";
};
C88C2B261D67EC5200B01A98 /* RxExample-iOSUITests */ = {
isa = PBXNativeTarget;
buildConfigurationList = C88C2B2E1D67EC5200B01A98 /* Build configuration list for PBXNativeTarget "RxExample-iOSUITests" */;
buildPhases = (
C88C2B231D67EC5200B01A98 /* Sources */,
C88C2B241D67EC5200B01A98 /* Frameworks */,
C88C2B251D67EC5200B01A98 /* Resources */,
);
buildRules = (
);
dependencies = (
C88C2B2D1D67EC5200B01A98 /* PBXTargetDependency */,
);
name = "RxExample-iOSUITests";
productName = "RxExample-iOSUITests";
productReference = C88C2B271D67EC5200B01A98 /* RxExample-iOSUITests.xctest */;
productType = "com.apple.product-type.bundle.ui-testing";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
C83366D51AD0293800C668A7 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0720;
LastSwiftUpdateCheck = 0800;
LastUpgradeCheck = 0800;
ORGANIZATIONNAME = "Krunoslav Zaher";
TargetAttributes = {
@ -922,12 +975,19 @@
};
C849EF601C3190360048AC4A = {
CreatedOnToolsVersion = 7.2;
DevelopmentTeam = 783T66X79Y;
LastSwiftMigration = 0800;
TestTargetID = C83366DC1AD0293800C668A7;
};
C88BB8B91B07E6C90064D411 = {
LastSwiftMigration = 0800;
};
C88C2B261D67EC5200B01A98 = {
CreatedOnToolsVersion = 8.0;
DevelopmentTeam = 783T66X79Y;
ProvisioningStyle = Automatic;
TestTargetID = C83366DC1AD0293800C668A7;
};
};
};
buildConfigurationList = C83366D81AD0293800C668A7 /* Build configuration list for PBXProject "RxExample" */;
@ -952,6 +1012,7 @@
C83366DC1AD0293800C668A7 /* RxExample-iOS */,
C88BB8B91B07E6C90064D411 /* RxExample-OSX */,
C849EF601C3190360048AC4A /* RxExample-iOSTests */,
C88C2B261D67EC5200B01A98 /* RxExample-iOSUITests */,
);
};
/* End PBXProject section */
@ -1128,6 +1189,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
C88C2B251D67EC5200B01A98 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@ -1172,6 +1240,7 @@
C84780061D29DE8C0074454A /* RxTableViewSectionedReloadDataSource.swift in Sources */,
C843A0901C1CE39900CBA4BD /* GitHubSearchRepositoriesViewController.swift in Sources */,
C803973A1BD3E17D009D8B26 /* ActivityIndicator.swift in Sources */,
C8CDF0C11D688DF700C18F99 /* UITableView+Extensions.swift in Sources */,
C849EF821C3193B10048AC4A /* GithubSignupViewModel1.swift in Sources */,
C864BADD1C3332F10083833C /* TableViewWithEditingCommandsViewController.swift in Sources */,
C8F8C4A01C277F5A0047640B /* Operation.swift in Sources */,
@ -1193,6 +1262,7 @@
C8984CD51C36BC3E001E4272 /* PartialUpdatesViewController.swift in Sources */,
8479BC721C3BDAD400FB8B54 /* ImagePickerController.swift in Sources */,
C8477FFF1D29DE8C0074454A /* SectionModelType.swift in Sources */,
C8CDF0AB1D67F8FC00C18F99 /* UIApplication+Extensions.swift in Sources */,
C864BAE11C3332F10083833C /* User.swift in Sources */,
0744CDED1C4DB78600720FD2 /* GeolocationViewController.swift in Sources */,
C83367231AD029AE00C668A7 /* Example.swift in Sources */,
@ -1264,6 +1334,14 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
C88C2B231D67EC5200B01A98 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
C88C2B2A1D67EC5200B01A98 /* FlowTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
@ -1272,6 +1350,11 @@
target = C83366DC1AD0293800C668A7 /* RxExample-iOS */;
targetProxy = C849EF661C3190360048AC4A /* PBXContainerItemProxy */;
};
C88C2B2D1D67EC5200B01A98 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = C83366DC1AD0293800C668A7 /* RxExample-iOS */;
targetProxy = C88C2B2C1D67EC5200B01A98 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
@ -1407,6 +1490,7 @@
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 783T66X79Y;
GCC_NO_COMMON_BLOCKS = YES;
INFOPLIST_FILE = "RxExample-iOSTests/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 9.2;
@ -1423,6 +1507,7 @@
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 783T66X79Y;
GCC_NO_COMMON_BLOCKS = YES;
INFOPLIST_FILE = "RxExample-iOSTests/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 9.2;
@ -1440,6 +1525,7 @@
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 783T66X79Y;
GCC_NO_COMMON_BLOCKS = YES;
INFOPLIST_FILE = "RxExample-iOSTests/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 9.2;
@ -1477,6 +1563,69 @@
};
name = Release;
};
C88C2B2F1D67EC5200B01A98 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ANALYZER_NONNULL = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_SUSPICIOUS_MOVES = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 783T66X79Y;
INFOPLIST_FILE = "RxExample-iOSUITests/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "rx.RxExample-iOSUITests";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_VERSION = 3.0;
TEST_TARGET_NAME = "RxExample-iOS";
};
name = Debug;
};
C88C2B301D67EC5200B01A98 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ANALYZER_NONNULL = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_SUSPICIOUS_MOVES = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 783T66X79Y;
INFOPLIST_FILE = "RxExample-iOSUITests/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "rx.RxExample-iOSUITests";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_VERSION = 3.0;
TEST_TARGET_NAME = "RxExample-iOS";
};
name = Release;
};
C88C2B311D67EC5200B01A98 /* Release-Tests */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ANALYZER_NONNULL = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_SUSPICIOUS_MOVES = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 783T66X79Y;
INFOPLIST_FILE = "RxExample-iOSUITests/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "rx.RxExample-iOSUITests";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_VERSION = 3.0;
TEST_TARGET_NAME = "RxExample-iOS";
};
name = "Release-Tests";
};
C8DF92ED1B0B3DFA009BCF9A /* Release-Tests */ = {
isa = XCBuildConfiguration;
buildSettings = {
@ -1594,6 +1743,16 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
C88C2B2E1D67EC5200B01A98 /* Build configuration list for PBXNativeTarget "RxExample-iOSUITests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
C88C2B2F1D67EC5200B01A98 /* Debug */,
C88C2B301D67EC5200B01A98 /* Release */,
C88C2B311D67EC5200B01A98 /* Release-Tests */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = C83366D51AD0293800C668A7 /* Project object */;

View File

@ -38,6 +38,16 @@
ReferencedContainer = "container:RxExample.xcodeproj">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C88C2B261D67EC5200B01A98"
BuildableName = "RxExample-iOSUITests.xctest"
BlueprintName = "RxExample-iOSUITests"
ReferencedContainer = "container:RxExample.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
@ -52,7 +62,7 @@
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Release"
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"

View File

@ -83,9 +83,13 @@ class APIWrappersViewController: ViewController {
// MARK: UISwitch
/*
// also test two way binding
let switchValue = Variable(true)
_ = switcher.rx.value <-> switchValue
/***Unlike other controls, Apple is reusing instances of UISwitch or a there is a leak,
so underlying observable sequence won't complete when nothing holds a strong reference
to UISwitch.***/
(switcher.rx.value <-> switchValue).addDisposableTo(disposeBag)
switchValue.asObservable()
.subscribe(onNext: { [weak self] x in
@ -98,7 +102,7 @@ class APIWrappersViewController: ViewController {
switcher.rx.value
.bindTo(activityIndicator.rx.animating)
.addDisposableTo(disposeBag)
*/
// MARK: UIButton

View File

@ -54,14 +54,16 @@ class PartialUpdatesViewController : ViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.rightBarButtonItem?.accessibilityLabel = "Randomize"
// For UICollectionView, if another animation starts before previous one is finished, it will sometimes crash :(
// It's not deterministic (because Randomizer generates deterministic updates), and if you click fast
// It sometimes will and sometimes wont crash, depending on tapping speed.
// I guess you can maybe try some tricks with timeout, hard to tell :( That's on Apple side.
if generateCustomSize {
let nSections = 10
let nItems = 100
let nSections = UIApplication.isInUITest ? 10 : 10
let nItems = UIApplication.isInUITest ? 20 : 100
var sections = [AnimatableSectionModel<String, Int>]()

View File

@ -23,7 +23,7 @@ class DetailViewController: ViewController {
override func viewDidLoad() {
super.viewDidLoad()
imageView.makeRoundedCorners(5)
imageView.makeRoundedCorners(40)
let url = URL(string: user.imageURL)!
let request = URLRequest(url: url)

View File

@ -11,10 +11,11 @@ import UIKit
extension UIImageView {
func makeRoundedCorners(_ radius: CGFloat) {
self.layer.cornerRadius = self.frame.size.width / 2
self.layer.borderColor = UIColor.darkGray.cgColor
self.layer.borderWidth = radius
self.layer.cornerRadius = radius
self.layer.masksToBounds = true
}
func makeRoundedCorners() {
self.makeRoundedCorners(self.frame.size.width / 2)
}
}

View File

@ -13,21 +13,9 @@ import RxCocoa
#endif
class WikipediaSearchViewController: ViewController {
@IBOutlet var searchBarContainer: UIView!
private let searchController = UISearchController(searchResultsController: UITableViewController())
private var resultsViewController: UITableViewController {
return (self.searchController.searchResultsController as? UITableViewController)!
}
private var resultsTableView: UITableView {
return self.resultsViewController.tableView!
}
private var searchBar: UISearchBar {
return self.searchController.searchBar
}
@IBOutlet var searchBar: UISearchBar!
@IBOutlet var resultsTableView: UITableView!
@IBOutlet var emptyView: UIView!
override func awakeFromNib() {
super.awakeFromNib()
@ -37,15 +25,8 @@ class WikipediaSearchViewController: ViewController {
override func viewDidLoad() {
super.viewDidLoad()
let searchBar = self.searchBar
let searchBarContainer = self.searchBarContainer
searchBarContainer?.addSubview(searchBar)
searchBar.frame = (searchBarContainer?.bounds)!
searchBar.autoresizingMask = .flexibleWidth
resultsViewController.edgesForExtendedLayout = UIRectEdge()
self.edgesForExtendedLayout = .all
configureTableDataSource()
configureKeyboardDismissesOnScroll()
@ -57,14 +38,12 @@ class WikipediaSearchViewController: ViewController {
resultsTableView.register(UINib(nibName: "WikipediaSearchCell", bundle: nil), forCellReuseIdentifier: "WikipediaSearchCell")
resultsTableView.rowHeight = 194
resultsTableView.hideEmptyCells()
// This is for clarity only, don't use static dependencies
let API = DefaultWikipediaAPI.sharedAPI
resultsTableView.delegate = nil
resultsTableView.dataSource = nil
searchBar.rx.text
let results = searchBar.rx.text
.asDriver()
.throttle(0.3)
.distinctUntilChanged()
@ -78,24 +57,27 @@ class WikipediaSearchViewController: ViewController {
.map { results in
results.map(SearchResultViewModel.init)
}
results
.drive(resultsTableView.rx.items(cellIdentifier: "WikipediaSearchCell", cellType: WikipediaSearchCell.self)) { (_, viewModel, cell) in
cell.viewModel = viewModel
}
.addDisposableTo(disposeBag)
results
.map { $0.count != 0 }
.drive(self.emptyView.rx.hidden)
.addDisposableTo(disposeBag)
}
func configureKeyboardDismissesOnScroll() {
let searchBar = self.searchBar
let searchController = self.searchController
resultsTableView.rx.contentOffset
.asDriver()
.filter { _ -> Bool in
return !searchController.isBeingPresented
}
.drive(onNext: { _ in
if searchBar.isFirstResponder {
_ = searchBar.resignFirstResponder()
if searchBar?.isFirstResponder ?? false {
_ = searchBar?.resignFirstResponder()
}
})
.addDisposableTo(disposeBag)

View File

@ -79,7 +79,7 @@ class ViewController: OSViewController {
If somebody knows more about why this delay happens, you can make a PR with explanation here.
*/
let when = DispatchTime.now() + DispatchTimeInterval.milliseconds(20)
let when = DispatchTime.now() + DispatchTimeInterval.milliseconds(UIApplication.isInUITest ? 1000 : 10)
mainQueue.asyncAfter (deadline: when) {
@ -92,7 +92,7 @@ class ViewController: OSViewController {
//
// If this crashes when you've been clicking slowly, then it would be interesting to find out why.
// ¯\_()_/¯
assert(resourceCount <= numberOfResourcesThatShouldRemain, "Resources weren't cleaned properly")
assert(resourceCount <= numberOfResourcesThatShouldRemain, "Resources weren't cleaned properly, \(resourceCount) remaned, \(numberOfResourcesThatShouldRemain) expected")
}
#endif

View File

@ -12,6 +12,11 @@ import UIKit
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func applicationDidFinishLaunching(_ application: UIApplication) {
if UIApplication.isInUITest {
UIView.setAnimationsEnabled(false)
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,15 @@
//
// UIApplication+Extensions.swift
// RxExample
//
// Created by Krunoslav Zaher on 8/20/16.
// Copyright © 2016 Krunoslav Zaher. All rights reserved.
//
import UIKit
extension UIApplication {
static var isInUITest: Bool {
return ProcessInfo.processInfo.environment["isUITest"] != nil;
}
}

View File

@ -0,0 +1,15 @@
//
// UITableView+Extensions.swift
// RxExample
//
// Created by Krunoslav Zaher on 8/20/16.
// Copyright © 2016 Krunoslav Zaher. All rights reserved.
//
import UIKit
extension UITableView {
func hideEmptyCells() {
self.tableFooterView = UIView(frame: .zero)
}
}