diff --git a/deltachat-ios-uitests/ChatTests.swift b/deltachat-ios-uitests/ChatTests.swift new file mode 100644 index 000000000..888389a66 --- /dev/null +++ b/deltachat-ios-uitests/ChatTests.swift @@ -0,0 +1,239 @@ +import XCTest +import SnapshotTesting + +// TODO: Should create a test that generates screenshots for README and App Store +// TODO: Maybe split up the test into multiple tests + +final class ChatTests: XCTestCase { + var bundleIdentifier: String = "chat.delta.amzd" + + override func setUp() { + continueAfterFailure = false + } + + lazy var app = XCUIApplication(bundleIdentifier: bundleIdentifier) + lazy var springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard") + + func testChatViewController() { + switch (UIDevice.current.systemVersion, UIDevice.current.name) { + case ("16.4", "iPhone X"): break + case ("17.5", "iPhone SE (3rd generation)"): break + // Note: 18.1 changes the space bar to include locale which breaks + // the screenshots if ran on a mac with a different locale. + // So even if the keyboard is in English it will show "EN NL" on a mac with Dutch locale. + case ("18.0", "iPhone 16"): break + default: XCTFail("Not a tested device") + } + + XCTAssertNotEqual(String.localized("write_message_desktop"), "write_message_desktop", + "Make sure localized strings work") + + app.resetAuthorizationStatus(for: .microphone) + app.resetAuthorizationStatus(for: .camera) + app.resetAuthorizationStatus(for: .photos) + app.resetAuthorizationStatus(for: .contacts) + + // AppStateRestorer.Tab.chatTab = 12 + app.launchArguments += ["-last_active_tab2", "12"] + app.launchArguments += ["-last_active_chat_id", "0"] + app.launchArguments += ["-AppleLanguages", "(en)"] + app.launchArguments += ["-AppleLocale", "en_US"] + app.launchArguments += ["--UITests"] + app.launch() + app.staticTexts[.localized("saved_messages")].tap() + + // There should be no messages. If this fails check why TestUtil.selectUITestAccount() did not + // clear the self-chat. + XCTAssert(app.staticTexts[.localized("saved_messages_explain")].waitForExistence(timeout: 2)) + + // Send message + app.textViews[.localized("write_message_desktop")].tap() + XCTAssert(app.keyboards.firstMatch.exists) + app.dismissKeyboardTutorialIfNeeded() + app.textViews[.localized("write_message_desktop")].typeText("Hey!") + app.buttons[.localized("menu_send")].tap() + XCTAssert(app.cells[containing: .localized("a11y_delivery_status_delivered")].waitForExistence(timeout: 5)) + XCTAssert(app.keyboards.firstMatch.exists) + screenshot(app, named: "Sent Message") + + // React with emoji + app.cells[containing: "Hey!"].press(forDuration: 1) + if #available(iOS 18.0, *) { + // TODO: Figure out why iOS 18 can't find the button by localized string + // maybe a localization issue that needs to be fixed in the app + app.buttons["•••"].tap() + } else { + app.buttons[.localized("pref_other")].tap() + } + XCTAssertFalse(app.keyboards.firstMatch.exists) + app.staticTexts["😀"].firstMatch.tap() + XCTAssert(app.keyboards.firstMatch.exists) + screenshot(app, named: "Reacted with emoji") + + // Send Contact + app.buttons[.localized("menu_add_attachment")].tap() + app.buttons[.localized("contact")].tap() + XCTAssert(app.navigationBars[.localized("contacts_title")].waitForExistence(timeout: 3)) + XCTAssertFalse(app.keyboards.firstMatch.exists) + screenshot(app, named: "Selecting Contact") + app.staticTexts[.localized("self")].tap() + XCTAssert(app.keyboards.firstMatch.exists) + // on iOS 16 the keyboard tutorial is shown the second time the keyboard is shown + app.dismissKeyboardTutorialIfNeeded() + screenshot(app, named: "Sending Contact") + app.buttons[.localized("menu_send")].tap() + screenshot(app, named: "Sent Contact") + + // Send audio message + app.buttons[.localized("menu_add_attachment")].tap() + app.buttons[.localized("voice_message")].tap() + if springboard.staticTexts["“Delta Chat” Would Like to Access the Microphone"].exists { + if #available(iOS 17, *) { + springboard.buttons["Allow"].tap() + } else { + springboard.buttons["OK"].tap() + } + } + sleep(3) // Wait for recording + app.buttons[.localized("menu_send")].tap() + XCTAssert(app.keyboards.firstMatch.exists) + screenshot(app, named: "Sent Voice message") + + // Test Share Sheet + app.cells[containing: .localized("voice_message")].press(forDuration: 1) + // Note: The context menu here sometimes causes a long "waiting for idle" time + app.buttons[.localized("menu_more_options")].tap() + app.buttons[.localized("menu_more_options")].tap() + app.buttons[.localized("menu_share")].tap() + XCTAssert(app.textViews[.localized("write_message_desktop")].waitForNonExistence(timeout: 2)) + app.buttons[.localized("close")].tap() + // keyboard is dismissed rn, but maybe it shouldn't be? + XCTAssert(app.textViews[.localized("write_message_desktop")].waitForExistence(timeout: 2)) + + // Check More Options menu and copy text + app.cells[containing: "Hey!"].press(forDuration: 1) + app.buttons[.localized("menu_more_options")].tap() + app.buttons[.localized("menu_copy_text_to_clipboard")].tap() + // keyboard is dismissed rn, but maybe it shouldn't be? + XCTAssertFalse(app.keyboards.firstMatch.exists) + app.textViews[.localized("write_message_desktop")].press(forDuration: 2) + app.menuItems["Paste"].tap() + XCTAssertEqual(app.textViews[.localized("write_message_desktop")].value as? String, "Hey!") + app.buttons[.localized("menu_send")].tap() + + // Test File Picker Search Field + // Note: File Picker is broken in iOS 18 simulators using Rosetta + if #unavailable(iOS 18) { + app.buttons[.localized("menu_add_attachment")].tap() + app.buttons[.localized("files")].tap() + // Focus the search field in the picker to test if the first responder is returned after dismiss + app.searchFields["Search"].tap() + XCTAssert(app.keyboards.firstMatch.waitForExistence(timeout: 2)) + app.buttons["Cancel"].tap() + XCTAssert(app.keyboards.firstMatch.exists) + screenshot(app, named: "After File Picker") + } + + // Send Photo + // Note: Image Picker is broken in iOS 18 simulators using Rosetta + // Note: Sadly simulators pre-iOS 18 do not have the search field so we should test this on a real device + // wether the first responder is returned after using search field in the image picker. + if #unavailable(iOS 18) { + app.buttons[.localized("menu_add_attachment")].tap() + app.buttons[.localized("gallery")].tap() + if #available(iOS 17.0, *) { + app.images["Photo, 30 March 2018, 21:14"].tap() + } else { + app.images["Photo, March 30, 2018, 21:14"].tap() + } + XCTAssert(app.keyboards.firstMatch.waitForExistence(timeout: 2)) + screenshot(app, named: "Selected Photo") + app.buttons[.localized("menu_send")].tap() + screenshot(app, named: "Sent Photo") + } + } + + override func tearDown() { + // TODO: This is not working, the app is not terminated because of a bug with Rosetta simulators, but Rosetta is required for the snapshot dependency.... uhg + // that means the app does not get terminated and does not clean up the test account + // which is not that big of a deal because it is only on simulators but would like to fix it + // app.terminate() + } + + // MARK: - Helpers + + /// The number of screenshots taken in this test. Used in the name of the screenshots to sort them chronologically. + var numberOfScreenshots = 0 + lazy var navigationBarY = app.navigationBars.firstMatch.frame.minY + lazy var bottomSafeAreaInset = app.frame.maxY - app.otherElements["safeAreaProvider"].frame.maxY + + func screenshot( + _ app: XCUIApplication, + named name: String, + crop: Bool = true, + record recording: Bool? = nil, + timeout: TimeInterval = 5, + fileID: StaticString = #fileID, + file filePath: StaticString = #filePath, + testName: String = #function, + line: UInt = #line, + column: UInt = #column + ) { + let previousContinueAfterFailure = continueAfterFailure + continueAfterFailure = true + + // Wait for animations to finish + sleep(1) + + // Crop out the status bar and the home indicator + // Needed because the home indicator color is not deterministic and can change between runs (on iOS 18) + // and the status bar can have different content depending on the state of the device (`xcrun simctl status_bar` does not work on rosetta simulators) + let image: UIImage = crop ? { + let cgImage = app.screenshot().image.cgImage! + // Using navigationBarY instead of safeAreaProvider.minY because the safe area is + // bigger than the navigation bar on iPhone 16 (iOS 18) + let cropTopPercentage = navigationBarY / app.frame.height + let cropTop = CGFloat(cgImage.height) * cropTopPercentage + let cropBottomPercentage = bottomSafeAreaInset / app.frame.height + let cropBottom = CGFloat(cgImage.height) * cropBottomPercentage + let cropFrame = CGRect(x: 0.0, y: cropTop, width: CGFloat(cgImage.width), height: CGFloat(cgImage.height)-cropTop-cropBottom) + let croppedCGImage = cgImage.cropping(to: cropFrame)! + return UIImage(cgImage: croppedCGImage) + }() : app.screenshot().image + + assertSnapshot( + of: image, + as: .image, + named: "\(UIDevice.current.name) \(UIDevice.current.systemVersion) \(numberOfScreenshots) \(name)", + record: recording, + timeout: timeout, + fileID: fileID, + file: filePath, + testName: testName, + line: line, + column: column + ) + numberOfScreenshots += 1 + continueAfterFailure = previousContinueAfterFailure + } +} + +extension XCUIApplication { + /// Dissmisses the "swipe to type" keyboard tutorial if it is shown + func dismissKeyboardTutorialIfNeeded() { + let predicate = NSPredicate { (evaluatedObject, _) in + (evaluatedObject as? XCUIElementAttributes)?.identifier == "UIContinuousPathIntroductionView" + } + let keyboardTutorialView = windows.otherElements.element(matching: predicate) + if keyboardTutorialView.exists { + keyboardTutorialView.buttons["Continue"].tap() + } + } +} + +extension XCUIElementQuery { + subscript(with part: String = "label", containing string: String) -> XCUIElement { + precondition(!string.contains("'")) + return self.element(matching: .init(format: "\(part) CONTAINS '\(string)'")) + } +} diff --git a/deltachat-ios-uitests/Util/String+localized.swift b/deltachat-ios-uitests/Util/String+localized.swift new file mode 100644 index 000000000..927d4dfe5 --- /dev/null +++ b/deltachat-ios-uitests/Util/String+localized.swift @@ -0,0 +1,19 @@ +import Foundation + +extension Bundle { + static var uitest: Bundle { + Bundle(for: ChatTests.self) + } +} + +public extension String { + static func localized(_ stringID: String) -> String { + return NSLocalizedString(stringID, bundle: .uitest, comment: "") + } + + static func localized(stringID: String, parameter: CVarArg...) -> String { + let formatString = localized(stringID) + let resultString = String.localizedStringWithFormat(formatString, parameter) + return resultString + } +} diff --git a/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-16-18-0-0-Sent-Message.png b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-16-18-0-0-Sent-Message.png new file mode 100644 index 000000000..92f764a72 Binary files /dev/null and b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-16-18-0-0-Sent-Message.png differ diff --git a/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-16-18-0-1-Reacted-with-emoji.png b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-16-18-0-1-Reacted-with-emoji.png new file mode 100644 index 000000000..a8ff019f5 Binary files /dev/null and b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-16-18-0-1-Reacted-with-emoji.png differ diff --git a/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-16-18-0-2-Selecting-Contact.png b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-16-18-0-2-Selecting-Contact.png new file mode 100644 index 000000000..aa624e592 Binary files /dev/null and b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-16-18-0-2-Selecting-Contact.png differ diff --git a/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-16-18-0-3-Sending-Contact.png b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-16-18-0-3-Sending-Contact.png new file mode 100644 index 000000000..dfb99dc49 Binary files /dev/null and b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-16-18-0-3-Sending-Contact.png differ diff --git a/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-16-18-0-4-Sent-Contact.png b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-16-18-0-4-Sent-Contact.png new file mode 100644 index 000000000..d1100b0cd Binary files /dev/null and b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-16-18-0-4-Sent-Contact.png differ diff --git a/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-16-18-0-5-Sent-Voice-message.png b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-16-18-0-5-Sent-Voice-message.png new file mode 100644 index 000000000..8a7a0fd36 Binary files /dev/null and b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-16-18-0-5-Sent-Voice-message.png differ diff --git a/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-16-18-0-6-After-Share-Sheet.png b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-16-18-0-6-After-Share-Sheet.png new file mode 100644 index 000000000..3e83fdc75 Binary files /dev/null and b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-16-18-0-6-After-Share-Sheet.png differ diff --git a/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-SE-3rd-generation-17-5-0-Sent-Message.png b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-SE-3rd-generation-17-5-0-Sent-Message.png new file mode 100644 index 000000000..8722096fb Binary files /dev/null and b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-SE-3rd-generation-17-5-0-Sent-Message.png differ diff --git a/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-SE-3rd-generation-17-5-1-Reacted-with-emoji.png b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-SE-3rd-generation-17-5-1-Reacted-with-emoji.png new file mode 100644 index 000000000..af597e2d8 Binary files /dev/null and b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-SE-3rd-generation-17-5-1-Reacted-with-emoji.png differ diff --git a/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-SE-3rd-generation-17-5-2-Selecting-Contact.png b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-SE-3rd-generation-17-5-2-Selecting-Contact.png new file mode 100644 index 000000000..5836bab51 Binary files /dev/null and b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-SE-3rd-generation-17-5-2-Selecting-Contact.png differ diff --git a/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-SE-3rd-generation-17-5-3-Sending-Contact.png b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-SE-3rd-generation-17-5-3-Sending-Contact.png new file mode 100644 index 000000000..bdce468d2 Binary files /dev/null and b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-SE-3rd-generation-17-5-3-Sending-Contact.png differ diff --git a/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-SE-3rd-generation-17-5-4-Sent-Contact.png b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-SE-3rd-generation-17-5-4-Sent-Contact.png new file mode 100644 index 000000000..1c5589fce Binary files /dev/null and b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-SE-3rd-generation-17-5-4-Sent-Contact.png differ diff --git a/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-SE-3rd-generation-17-5-5-Sent-Voice-message.png b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-SE-3rd-generation-17-5-5-Sent-Voice-message.png new file mode 100644 index 000000000..d1fc4825a Binary files /dev/null and b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-SE-3rd-generation-17-5-5-Sent-Voice-message.png differ diff --git a/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-SE-3rd-generation-17-5-6-After-File-Picker.png b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-SE-3rd-generation-17-5-6-After-File-Picker.png new file mode 100644 index 000000000..92d59ce64 Binary files /dev/null and b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-SE-3rd-generation-17-5-6-After-File-Picker.png differ diff --git a/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-SE-3rd-generation-17-5-7-Selected-Photo.png b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-SE-3rd-generation-17-5-7-Selected-Photo.png new file mode 100644 index 000000000..5d565fc39 Binary files /dev/null and b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-SE-3rd-generation-17-5-7-Selected-Photo.png differ diff --git a/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-SE-3rd-generation-17-5-8-Sent-Photo.png b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-SE-3rd-generation-17-5-8-Sent-Photo.png new file mode 100644 index 000000000..54447b3c8 Binary files /dev/null and b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-SE-3rd-generation-17-5-8-Sent-Photo.png differ diff --git a/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-X-16-4-0-Sent-Message.png b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-X-16-4-0-Sent-Message.png new file mode 100644 index 000000000..edff9ebd9 Binary files /dev/null and b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-X-16-4-0-Sent-Message.png differ diff --git a/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-X-16-4-1-Reacted-with-emoji.png b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-X-16-4-1-Reacted-with-emoji.png new file mode 100644 index 000000000..cb31a59ef Binary files /dev/null and b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-X-16-4-1-Reacted-with-emoji.png differ diff --git a/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-X-16-4-2-Selecting-Contact.png b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-X-16-4-2-Selecting-Contact.png new file mode 100644 index 000000000..eb1e338e1 Binary files /dev/null and b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-X-16-4-2-Selecting-Contact.png differ diff --git a/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-X-16-4-3-Sending-Contact.png b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-X-16-4-3-Sending-Contact.png new file mode 100644 index 000000000..7b3d3e6e5 Binary files /dev/null and b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-X-16-4-3-Sending-Contact.png differ diff --git a/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-X-16-4-4-Sent-Contact.png b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-X-16-4-4-Sent-Contact.png new file mode 100644 index 000000000..7d73b2e03 Binary files /dev/null and b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-X-16-4-4-Sent-Contact.png differ diff --git a/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-X-16-4-5-Sent-Voice-message.png b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-X-16-4-5-Sent-Voice-message.png new file mode 100644 index 000000000..00f6a573d Binary files /dev/null and b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-X-16-4-5-Sent-Voice-message.png differ diff --git a/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-X-16-4-6-After-File-Picker.png b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-X-16-4-6-After-File-Picker.png new file mode 100644 index 000000000..8a94deb12 Binary files /dev/null and b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-X-16-4-6-After-File-Picker.png differ diff --git a/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-X-16-4-7-Selected-Photo.png b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-X-16-4-7-Selected-Photo.png new file mode 100644 index 000000000..8aa027b34 Binary files /dev/null and b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-X-16-4-7-Selected-Photo.png differ diff --git a/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-X-16-4-8-Sent-Photo.png b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-X-16-4-8-Sent-Photo.png new file mode 100644 index 000000000..30bca1c72 Binary files /dev/null and b/deltachat-ios-uitests/__Snapshots__/ChatTests/testChatViewController.iPhone-X-16-4-8-Sent-Photo.png differ diff --git a/deltachat-ios.xcodeproj/project.pbxproj b/deltachat-ios.xcodeproj/project.pbxproj index 2e233e752..7421d0a02 100644 --- a/deltachat-ios.xcodeproj/project.pbxproj +++ b/deltachat-ios.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 70; objects = { /* Begin PBXBuildFile section */ @@ -132,6 +132,9 @@ 30FDB71F24D8170E0066C48D /* TextMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30FDB71E24D8170E0066C48D /* TextMessageCell.swift */; }; 30FDB72124D838240066C48D /* BaseMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30FDB72024D838240066C48D /* BaseMessageCell.swift */; }; 5F153E9A2CC38BAA00871ABE /* GiveBackMyFirstResponder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F153E992CC38BAA00871ABE /* GiveBackMyFirstResponder.swift */; }; + 5F2E3FDC2CBD59EF002208F0 /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = 5F2E3FDB2CBD59EF002208F0 /* SnapshotTesting */; }; + 5F7105222CD3953D00B38E34 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 3060119E22DDE24000C1CE6F /* Localizable.strings */; }; + 5F7105232CD3953D00B38E34 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 306011B422E5E7FB00C1CE6F /* Localizable.stringsdict */; }; 5F785F6E2CB9344F003FFFB9 /* ReusableCellProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F785F6D2CB9344F003FFFB9 /* ReusableCellProtocol.swift */; }; 7070FB9B2101ECBB000DC258 /* NewGroupController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7070FB9A2101ECBB000DC258 /* NewGroupController.swift */; }; 7092474120B3869500AF8799 /* ContactDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7092474020B3869500AF8799 /* ContactDetailViewController.swift */; }; @@ -236,6 +239,13 @@ remoteGlobalIDString = 30E8F20F2447285600CE2C90; remoteInfo = DcShare; }; + 5FAA92E42CBC0FB400FA8F68 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 7A9FB1381FB061E2001FEA36 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 7A9FB13F1FB061E2001FEA36; + remoteInfo = "deltachat-ios"; + }; B2D0E4972B93A4B200791949 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 7A9FB1381FB061E2001FEA36 /* Project object */; @@ -437,6 +447,8 @@ 30FDB72024D838240066C48D /* BaseMessageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseMessageCell.swift; sourceTree = ""; }; 5F153E992CC38BAA00871ABE /* GiveBackMyFirstResponder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GiveBackMyFirstResponder.swift; sourceTree = ""; }; 5F785F6D2CB9344F003FFFB9 /* ReusableCellProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReusableCellProtocol.swift; sourceTree = ""; }; + 5FAA92DE2CBC0FB300FA8F68 /* deltachat-ios-uitests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "deltachat-ios-uitests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 5FAA92DE2CBC0FB300FA8F68 /* deltachat-ios-uitests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "deltachat-ios-uitests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 7070FB9A2101ECBB000DC258 /* NewGroupController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewGroupController.swift; sourceTree = ""; }; 7092474020B3869500AF8799 /* ContactDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactDetailViewController.swift; sourceTree = ""; }; 70B8882D2091B8550074812E /* ContactCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactCell.swift; sourceTree = ""; }; @@ -615,6 +627,11 @@ E23C80FA36453692862D4A90 /* Pods_deltachat_iosTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_deltachat_iosTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 5FA2860A2CD62F2500712725 /* TestUtil */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = TestUtil; sourceTree = ""; }; + 5FAA92DF2CBC0FB400FA8F68 /* deltachat-ios-uitests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = "deltachat-ios-uitests"; sourceTree = ""; }; +/* End PBXFileSystemSynchronizedRootGroup section */ + /* Begin PBXFrameworksBuildPhase section */ 30E8F20D2447285600CE2C90 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; @@ -625,6 +642,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 5FAA92DB2CBC0FB300FA8F68 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 5F2E3FDC2CBD59EF002208F0 /* SnapshotTesting in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 7A9FB13D1FB061E2001FEA36 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -858,6 +883,7 @@ 7A9FB1421FB061E2001FEA36 /* deltachat-ios */, 30E8F2112447285600CE2C90 /* DcShare */, B2D0E4932B93A4B200791949 /* DcNotificationService */, + 5FAA92DF2CBC0FB400FA8F68 /* deltachat-ios-uitests */, 7A9FB1411FB061E2001FEA36 /* Products */, 7A9FB4F81FB084E6001FEA36 /* Frameworks */, AFE4D4B4B038293E63BC1537 /* Pods */, @@ -873,6 +899,7 @@ 7A9FB1401FB061E2001FEA36 /* deltachat-ios.app */, 30E8F2102447285600CE2C90 /* Delta Chat.appex */, B2D0E4922B93A4B200791949 /* DcNotificationService.appex */, + 5FAA92DE2CBC0FB300FA8F68 /* deltachat-ios-uitests.xctest */, ); name = Products; sourceTree = ""; @@ -894,6 +921,7 @@ AE851AC0227C693B00ED86F0 /* Controller */, AE851ACB227C7A5000ED86F0 /* Handler */, 7A9FB15B1FB07364001FEA36 /* libraries */, + 5FA2860A2CD62F2500712725 /* TestUtil */, 78C7036A21D46752005D4525 /* deltachat-ios.entitlements */, 7A9FB14A1FB061E2001FEA36 /* Assets.xcassets */, 7A9FB14C1FB061E2001FEA36 /* LaunchScreen.storyboard */, @@ -1195,6 +1223,30 @@ productReference = 30E8F2102447285600CE2C90 /* Delta Chat.appex */; productType = "com.apple.product-type.app-extension"; }; + 5FAA92DD2CBC0FB300FA8F68 /* deltachat-ios-uitests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 5FAA92E82CBC0FB400FA8F68 /* Build configuration list for PBXNativeTarget "deltachat-ios-uitests" */; + buildPhases = ( + 5FAA92DA2CBC0FB300FA8F68 /* Sources */, + 5FAA92DB2CBC0FB300FA8F68 /* Frameworks */, + 5FAA92DC2CBC0FB300FA8F68 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 5FAA92E52CBC0FB400FA8F68 /* PBXTargetDependency */, + ); + fileSystemSynchronizedGroups = ( + 5FAA92DF2CBC0FB400FA8F68 /* deltachat-ios-uitests */, + ); + name = "deltachat-ios-uitests"; + packageProductDependencies = ( + 5F2E3FDB2CBD59EF002208F0 /* SnapshotTesting */, + ); + productName = "deltachat-ios-uitests"; + productReference = 5FAA92DE2CBC0FB300FA8F68 /* deltachat-ios-uitests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; 7A9FB13F1FB061E2001FEA36 /* deltachat-ios */ = { isa = PBXNativeTarget; buildConfigurationList = 7A9FB1521FB061E2001FEA36 /* Build configuration list for PBXNativeTarget "deltachat-ios" */; @@ -1215,6 +1267,9 @@ 30E8F2192447285600CE2C90 /* PBXTargetDependency */, B2D0E4982B93A4B200791949 /* PBXTargetDependency */, ); + fileSystemSynchronizedGroups = ( + 5FA2860A2CD62F2500712725 /* TestUtil */, + ); name = "deltachat-ios"; productName = "deltachat-ios"; productReference = 7A9FB1401FB061E2001FEA36 /* deltachat-ios.app */; @@ -1244,7 +1299,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; - LastSwiftUpdateCheck = 1520; + LastSwiftUpdateCheck = 1600; LastUpgradeCheck = 1500; ORGANIZATIONNAME = "merlinux GmbH"; TargetAttributes = { @@ -1253,10 +1308,13 @@ LastSwiftMigration = 1510; ProvisioningStyle = Automatic; }; + 5FAA92DD2CBC0FB300FA8F68 = { + CreatedOnToolsVersion = 16.0; + TestTargetID = 7A9FB13F1FB061E2001FEA36; + }; 7A9FB13F1FB061E2001FEA36 = { CreatedOnToolsVersion = 9.1; LastSwiftMigration = 1510; - ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.ApplicationGroups.iOS = { enabled = 1; @@ -1330,6 +1388,9 @@ vi, ); mainGroup = 7A9FB1371FB061E2001FEA36; + packageReferences = ( + 5F2E3FDA2CBD59EF002208F0 /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */, + ); productRefGroup = 7A9FB1411FB061E2001FEA36 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -1337,6 +1398,7 @@ 7A9FB13F1FB061E2001FEA36 /* deltachat-ios */, 30E8F20F2447285600CE2C90 /* DcShare */, B2D0E4912B93A4B200791949 /* DcNotificationService */, + 5FAA92DD2CBC0FB300FA8F68 /* deltachat-ios-uitests */, ); }; /* End PBXProject section */ @@ -1354,6 +1416,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 5FAA92DC2CBC0FB300FA8F68 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 5F7105222CD3953D00B38E34 /* Localizable.strings in Resources */, + 5F7105232CD3953D00B38E34 /* Localizable.stringsdict in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 7A9FB13E1FB061E2001FEA36 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -1531,6 +1602,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 5FAA92DA2CBC0FB300FA8F68 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 7A9FB13C1FB061E2001FEA36 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -1727,6 +1805,11 @@ target = 30E8F20F2447285600CE2C90 /* DcShare */; targetProxy = 30E8F2182447285600CE2C90 /* PBXContainerItemProxy */; }; + 5FAA92E52CBC0FB400FA8F68 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 7A9FB13F1FB061E2001FEA36 /* deltachat-ios */; + targetProxy = 5FAA92E42CBC0FB400FA8F68 /* PBXContainerItemProxy */; + }; B2D0E4982B93A4B200791949 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = B2D0E4912B93A4B200791949 /* DcNotificationService */; @@ -1998,6 +2081,58 @@ }; name = Release; }; + 5FAA92E62CBC0FB400FA8F68 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = C9U4548TV4; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "me.amzd.redstone.deltachat-ios-uitests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = "deltachat-ios"; + }; + name = Debug; + }; + 5FAA92E72CBC0FB400FA8F68 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_OBJC_WEAK = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = C9U4548TV4; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "me.amzd.redstone.deltachat-ios-uitests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = "deltachat-ios"; + }; + name = Release; + }; 7A9FB1501FB061E2001FEA36 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -2341,6 +2476,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 5FAA92E82CBC0FB400FA8F68 /* Build configuration list for PBXNativeTarget "deltachat-ios-uitests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 5FAA92E62CBC0FB400FA8F68 /* Debug */, + 5FAA92E72CBC0FB400FA8F68 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 7A9FB13B1FB061E2001FEA36 /* Build configuration list for PBXProject "deltachat-ios" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -2369,6 +2513,25 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 5F2E3FDA2CBD59EF002208F0 /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/pointfreeco/swift-snapshot-testing"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.17.5; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 5F2E3FDB2CBD59EF002208F0 /* SnapshotTesting */ = { + isa = XCSwiftPackageProductDependency; + package = 5F2E3FDA2CBD59EF002208F0 /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */; + productName = SnapshotTesting; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 7A9FB1381FB061E2001FEA36 /* Project object */; } diff --git a/deltachat-ios.xcodeproj/xcshareddata/xcschemes/deltachat-ios.xcscheme b/deltachat-ios.xcodeproj/xcshareddata/xcschemes/deltachat-ios.xcscheme index 0ff2ad8be..4befad67e 100644 --- a/deltachat-ios.xcodeproj/xcshareddata/xcschemes/deltachat-ios.xcscheme +++ b/deltachat-ios.xcodeproj/xcshareddata/xcschemes/deltachat-ios.xcscheme @@ -20,6 +20,20 @@ ReferencedContainer = "container:deltachat-ios.xcodeproj"> + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/deltachat-ios.xctestplan b/deltachat-ios.xctestplan new file mode 100644 index 000000000..ef07354c3 --- /dev/null +++ b/deltachat-ios.xctestplan @@ -0,0 +1,32 @@ +{ + "configurations" : [ + { + "id" : "25A440E8-B4D6-45EB-A536-7CCD973EECAC", + "name" : "Configuration 1", + "options" : { + + } + } + ], + "defaultOptions" : { + "codeCoverage" : false, + "targetForVariableExpansion" : { + "containerPath" : "container:deltachat-ios.xcodeproj", + "identifier" : "7A9FB13F1FB061E2001FEA36", + "name" : "deltachat-ios" + } + }, + "testTargets" : [ + { + "skippedTests" : [ + "ChatTests\/testCreateAccount()" + ], + "target" : { + "containerPath" : "container:deltachat-ios.xcodeproj", + "identifier" : "5FAA92DD2CBC0FB300FA8F68", + "name" : "deltachat-ios-uitests" + } + } + ], + "version" : 1 +} diff --git a/deltachat-ios.xcworkspace/contents.xcworkspacedata b/deltachat-ios.xcworkspace/contents.xcworkspacedata index cb2b779fd..a87c6ba9b 100644 --- a/deltachat-ios.xcworkspace/contents.xcworkspacedata +++ b/deltachat-ios.xcworkspace/contents.xcworkspacedata @@ -25,4 +25,7 @@ + + diff --git a/deltachat-ios.xcworkspace/xcshareddata/swiftpm/Package.resolved b/deltachat-ios.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 000000000..eabb35edf --- /dev/null +++ b/deltachat-ios.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,24 @@ +{ + "originHash" : "09b08963887ce6eac26952e8acb25256a8ec102f2b5528ae68a97fa296723712", + "pins" : [ + { + "identity" : "swift-snapshot-testing", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-snapshot-testing", + "state" : { + "revision" : "7b0bbbae90c41f848f90ac7b4df6c4f50068256d", + "version" : "1.17.5" + } + }, + { + "identity" : "swift-syntax", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swiftlang/swift-syntax", + "state" : { + "revision" : "0687f71944021d616d34d922343dcef086855920", + "version" : "600.0.1" + } + } + ], + "version" : 3 +} diff --git a/deltachat-ios/AppDelegate.swift b/deltachat-ios/AppDelegate.swift index b85117637..f13a8df80 100644 --- a/deltachat-ios/AppDelegate.swift +++ b/deltachat-ios/AppDelegate.swift @@ -85,6 +85,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD // /migrating global notifications self.launchOptions = launchOptions + TestUtil.didFinishLaunching(with: launchOptions) continueDidFinishLaunchingWithOptions() return true } @@ -189,7 +190,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD launchOptions = nil appFullyInitialized = true } - + func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { diff --git a/deltachat-ios/DC/DcContext.swift b/deltachat-ios/DC/DcContext.swift index 7efe6548d..3c0fd11c9 100644 --- a/deltachat-ios/DC/DcContext.swift +++ b/deltachat-ios/DC/DcContext.swift @@ -61,6 +61,14 @@ public class DcContext { public func sendMessage(chatId: Int, message: DcMsg) { dc_send_msg(contextPointer, UInt32(chatId), message.messagePointer) + if CommandLine.arguments.contains("--UITests") { + // TODO: FIX + // FIXME: I think this is a bug but sendMessage does not publish the DC_EVENT_MSGS_CHANGED event in UITests. Probably because the message is not actually sent due to it not being a real, functioning account? + NotificationCenter.default.post(name: Event.messagesChanged, object: nil, userInfo: [ + "message_id": message.id, + "chat_id": chatId, + ]) + } } public func downloadFullMessage(id: Int) { diff --git a/deltachat-ios/TestUtil/TestUtil.swift b/deltachat-ios/TestUtil/TestUtil.swift new file mode 100644 index 000000000..c246675d2 --- /dev/null +++ b/deltachat-ios/TestUtil/TestUtil.swift @@ -0,0 +1,22 @@ +import UIKit + +struct TestUtil { + static func didFinishLaunching(with options: [UIApplication.LaunchOptionsKey: Any]?) { + #if DEBUG + if isRunningUITests { + selectUITestAccount() + setCursorTintColor() + // Wait for window creation + DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: addSafeAreaProvider) + } + #endif + } + + static var isRunningUITests: Bool { + #if DEBUG + return CommandLine.arguments.contains("--UITests") + #else + return false + #endif + } +} diff --git a/deltachat-ios/TestUtil/UITestAccount.swift b/deltachat-ios/TestUtil/UITestAccount.swift new file mode 100644 index 000000000..f7904bd30 --- /dev/null +++ b/deltachat-ios/TestUtil/UITestAccount.swift @@ -0,0 +1,73 @@ +#if DEBUG +import DcCore +import UIKit + +extension TestUtil { + static let uitestMail = "uitest@delta.chat" + + /// Selects the account used for UI tests. + /// + /// If any of these asserts fail, reset the simulator with Device > Erase All Content and Settings + static func selectUITestAccount() { + // TODO: Maybe this logic should be in core because it is partly stolen from Android + + let dcAccounts = DcAccounts.shared + + // Try to select the test account + if dcAccounts.getSelected().addr != uitestMail { + for accountId in dcAccounts.getAll() { + if dcAccounts.get(id: accountId).addr == uitestMail { + assert(dcAccounts.select(id: accountId)) + } + } + } + + // Create the test account if it doesn't exist + if dcAccounts.getSelected().addr != uitestMail { + // create new account + let newAccountId = dcAccounts.add() + let newAccount = dcAccounts.get(id: newAccountId) + newAccount.setConfig("displayname", "Me") + newAccount.setConfig("addr", uitestMail) + newAccount.setConfig("configured_addr", uitestMail) + newAccount.setConfig("configured_mail_pw", "abcd") + newAccount.setConfigBool("configured", true) + newAccount.setConfigBool("bcc_self", false) + assert(dcAccounts.select(id: newAccountId)) + } + + // Clear the self-chat + let account = dcAccounts.getSelected() + let selfChat = account.createChatByContactId(contactId: Int(DC_CONTACT_ID_SELF)) + let oldMessages = account.getChatMsgs(chatId: selfChat, flags: 0) + account.deleteMessages(msgIds: oldMessages) + account.setDraft(chatId: Int(DC_CONTACT_ID_SELF), message: nil) + + // TODO: Create a contact request for the test account so we can test the contact request UI + // This is currently not possible because core doesn't expose the necessary functions + + // Delete the test account when the app terminates + deleteTestAccount = AppTerminationListener { + if dcAccounts.getAll().count > 1, dcAccounts.getSelected().addr == uitestMail { + // user had multiple accounts, delete the test account + assert(dcAccounts.remove(id: dcAccounts.getSelected().id)) + } + } + } + + static var deleteTestAccount: AppTerminationListener? +} + +extension TestUtil { + class AppTerminationListener { + let willTerminate: () -> Void + init(willTerminate: @escaping () -> Void) { + self.willTerminate = willTerminate + NotificationCenter.default.addObserver(self, selector: #selector(willTerminateFunc), name: UIApplication.willTerminateNotification, object: nil) + } + @objc func willTerminateFunc(_ handler: @escaping () -> Void) { + willTerminate() + } + } +} +#endif diff --git a/deltachat-ios/TestUtil/UITestCursor.swift b/deltachat-ios/TestUtil/UITestCursor.swift new file mode 100644 index 000000000..9095076a2 --- /dev/null +++ b/deltachat-ios/TestUtil/UITestCursor.swift @@ -0,0 +1,27 @@ +#if DEBUG +import UIKit + +extension TestUtil { + /// Set the tintColor of UITextView and UITextField to prevent the cursor from being visible in UITests. + static func setCursorTintColor() { + UITextField.appearance().tintColor = .clear + UITextView.appearance().tintColor = .clear + } +} + +// MARK: Prevent cursor tintColor changes when testing + +extension UITextView { + open override var tintColor: UIColor! { + get { return super.tintColor } + set { super.tintColor = TestUtil.isRunningUITests ? .clear : newValue } + } +} + +extension UITextField { + open override var tintColor: UIColor! { + get { return super.tintColor } + set { super.tintColor = TestUtil.isRunningUITests ? .clear : newValue } + } +} +#endif diff --git a/deltachat-ios/TestUtil/UITestKeyboard.swift b/deltachat-ios/TestUtil/UITestKeyboard.swift new file mode 100644 index 000000000..21bc54c4b --- /dev/null +++ b/deltachat-ios/TestUtil/UITestKeyboard.swift @@ -0,0 +1,29 @@ +#if DEBUG +import UIKit + +// MARK: Set keyboard language to English and disable autocorrection in UITests + +extension UIView { + open override var textInputMode: UITextInputMode? { + if TestUtil.isRunningUITests { + // Hide the autocorrection options above the keyboard + if let textView = self as? UITextView { + textView.autocorrectionType = .no + textView.spellCheckingType = .no + } + if let textField = self as? UITextField { + textField.autocorrectionType = .no + textField.spellCheckingType = .no + } + + // Make sure the keyboard is always in English in UITests for consistent screenshots + return .activeInputModes.first(where: { + $0.primaryLanguage == "en-US" + }) ?? { fatalError("UITest: Missing en-US keyboard in simulator") }() + } else { + return super.textInputMode + } + } +} + +#endif diff --git a/deltachat-ios/TestUtil/UITestSafeArea.swift b/deltachat-ios/TestUtil/UITestSafeArea.swift new file mode 100644 index 000000000..5e386bdc9 --- /dev/null +++ b/deltachat-ios/TestUtil/UITestSafeArea.swift @@ -0,0 +1,19 @@ +import UIKit + +extension TestUtil { + /// Adds a view to the root view controller that represents the safe area layout guide. + /// You can then use this view to retreive the safe area in your UITests using `app.otherElements["safeAreaProvider"].frame`. + static func addSafeAreaProvider() { + let uitestSafeAreaProvider = UIView() + uitestSafeAreaProvider.backgroundColor = .clear + uitestSafeAreaProvider.isUserInteractionEnabled = false + uitestSafeAreaProvider.accessibilityIdentifier = "safeAreaProvider" + UIApplication.shared.delegate?.window??.rootViewController?.view.addSubview(uitestSafeAreaProvider) + guard let superview = uitestSafeAreaProvider.superview else { fatalError("UITest: Safe area provider was not added") } + uitestSafeAreaProvider.translatesAutoresizingMaskIntoConstraints = false + uitestSafeAreaProvider.leftAnchor.constraint(equalTo: superview.safeAreaLayoutGuide.leftAnchor).isActive = true + uitestSafeAreaProvider.rightAnchor.constraint(equalTo: superview.safeAreaLayoutGuide.rightAnchor).isActive = true + uitestSafeAreaProvider.topAnchor.constraint(equalTo: superview.safeAreaLayoutGuide.topAnchor).isActive = true + uitestSafeAreaProvider.bottomAnchor.constraint(equalTo: superview.safeAreaLayoutGuide.bottomAnchor).isActive = true + } +}