diff --git a/AuthenticatorShared/UI/Platform/Application/Utilities/Alert/Alert+Error.swift b/AuthenticatorShared/UI/Platform/Application/Utilities/Alert/Alert+Error.swift deleted file mode 100644 index 009cf7e7dd..0000000000 --- a/AuthenticatorShared/UI/Platform/Application/Utilities/Alert/Alert+Error.swift +++ /dev/null @@ -1,42 +0,0 @@ -import BitwardenKit -import BitwardenResources -import Foundation - -// MARK: - Alert+Error - -extension Alert { - // MARK: Methods - - /// The default alert style with a standard ok button to dismiss. - /// - /// - Parameters: - /// - title: The alert's title. - /// - message: The alert's message. - /// - /// - Returns a default styled alert. - /// - static func defaultAlert( - title: String? = nil, - message: String? = nil, - alertActions: [AlertAction]? = nil, - ) -> Alert { - Alert( - title: title, - message: message, - alertActions: alertActions ?? [AlertAction(title: Localizations.ok, style: .cancel)], - ) - } - - /// Creates an alert for an `InputValidationError`. - /// - /// - Parameter error: The error to create the alert for. - /// - Returns: An alert that can be displayed to the user for an `InputValidationError`. - /// - static func inputValidationAlert(error: InputValidationError) -> Alert { - Alert( - title: Localizations.anErrorHasOccurred, - message: error.message, - alertActions: [AlertAction(title: Localizations.ok, style: .default)], - ) - } -} diff --git a/AuthenticatorShared/UI/Platform/Application/Utilities/Alert/Alert.swift b/AuthenticatorShared/UI/Platform/Application/Utilities/Alert/Alert.swift deleted file mode 100644 index a467dc1b82..0000000000 --- a/AuthenticatorShared/UI/Platform/Application/Utilities/Alert/Alert.swift +++ /dev/null @@ -1,168 +0,0 @@ -import UIKit - -// MARK: - Alert - -/// A helper class that can create a `UIAlertController`. -/// This allows for easier testing of alert controllers and actions. -/// -public class Alert { - // MARK: Properties - - /// A list of actions that the user can tap on in the alert. - var alertActions: [AlertAction] = [] - - /// A list of text fields that the user can use to enter text. - var alertTextFields: [AlertTextField] = [] - - /// The message that is displayed in the alert. - let message: String? - - /// The preferred action for the user to take in the alert, which emphasis is given. - var preferredAction: AlertAction? - - /// The alert's style. - let preferredStyle: UIAlertController.Style - - /// The title of the message that is displayed at the top of the alert. - let title: String? - - // MARK: Initialization - - /// Initializes an `Alert`. - /// - /// - Parameters: - /// - title: The title of the message that is displayed at the top of the alert. - /// - message: The message that is displayed in the alert. - /// - preferredStyle: The alert's style. - /// - alertActions: A list of actions that the user can tap on in the alert. - /// - alertTextFields: A list of text fields that the user can enter text into. - /// - public init( - title: String?, - message: String?, - preferredStyle: UIAlertController.Style = .alert, - alertActions: [AlertAction] = [], - alertTextFields: [AlertTextField] = [], - ) { - self.title = title - self.message = message - self.preferredStyle = preferredStyle - self.alertActions = alertActions - self.alertTextFields = alertTextFields - } - - // MARK: Methods - - /// Adds an `AlertAction` to the `Alert`. - /// - /// - Parameter action: The `AlertAction` to add to the `Alert`. - /// - /// - Returns: `self` to allow `add(_:)` methods to be chained. - /// - @discardableResult - func add(_ action: AlertAction) -> Self { - alertActions.append(action) - return self - } - - /// Adds an `AlertTextField` to the `Alert`. - /// - /// - Parameter textField: The `AlertTextField` to add to the `Alert`. - /// - /// - Returns: `self` to allow `add(_:)` methods to be chained. - /// - @discardableResult - func add(_ textField: AlertTextField) -> Self { - alertTextFields.append(textField) - return self - } - - /// Adds a preferred `AlertAction` to the `Alert`. The preferred action is the action that the - /// user should take and is given emphasis. This replaces an existing preferred action, if one - /// exists. - /// - /// - Parameter action: The preferred `AlertAction` to add to the `Alert`. - /// - /// - Returns: `self` to allow `add(_:)` methods to be chained. - /// - @discardableResult - func addPreferred(_ action: AlertAction) -> Self { - alertActions.append(action) - preferredAction = action - return self - } - - /// Creates a `UIAlertController` from the `Alert` that can be presented in the view. - /// - /// - Returns An initialized `UIAlertController` that has the `AlertAction`s added. - /// - @MainActor - func createAlertController() -> UIAlertController { - let alert = UIAlertController(title: title, message: message, preferredStyle: preferredStyle) - alertTextFields.forEach { alertTextField in - alert.addTextField { textField in - textField.placeholder = alertTextField.placeholder - textField.tintColor = Asset.Colors.primaryBitwarden.color - textField.keyboardType = alertTextField.keyboardType - textField.isSecureTextEntry = alertTextField.isSecureTextEntry - textField.autocapitalizationType = alertTextField.autocapitalizationType - textField.autocorrectionType = alertTextField.autocorrectionType - textField.text = alertTextField.text - textField.addTarget( - alertTextField, - action: #selector(AlertTextField.textChanged(in:)), - for: .editingChanged, - ) - } - } - - alertActions.forEach { alertAction in - let action = UIAlertAction(title: alertAction.title, style: alertAction.style) { _ in - Task { - await alertAction.handler?(alertAction, self.alertTextFields) - } - } - - alert.addAction(action) - - if let preferredAction, preferredAction === alertAction { - alert.preferredAction = action - } - } - alert.view.tintColor = Asset.Colors.primaryBitwarden.color - - return alert - } -} - -// swiftlint:disable line_length - -extension Alert: CustomDebugStringConvertible { - public var debugDescription: String { - """ - Alert(title: \(title ?? "nil"), message: \(message ?? "nil"), alertActions: \(alertActions), alertTextFields: \(alertTextFields)) - """ - } -} - -// swiftlint:enable line_length - -extension Alert: Equatable { - public static func == (lhs: Alert, rhs: Alert) -> Bool { - lhs.alertActions == rhs.alertActions - && lhs.message == rhs.message - && lhs.preferredAction == rhs.preferredAction - && lhs.preferredStyle == rhs.preferredStyle - && lhs.title == rhs.title - } -} - -extension Alert: Hashable { - public func hash(into hasher: inout Hasher) { - hasher.combine(alertActions) - hasher.combine(message) - hasher.combine(preferredAction) - hasher.combine(preferredStyle) - hasher.combine(title) - } -} diff --git a/AuthenticatorShared/UI/Platform/Application/Utilities/Alert/AlertAction.swift b/AuthenticatorShared/UI/Platform/Application/Utilities/Alert/AlertAction.swift deleted file mode 100644 index 41d910d9b1..0000000000 --- a/AuthenticatorShared/UI/Platform/Application/Utilities/Alert/AlertAction.swift +++ /dev/null @@ -1,78 +0,0 @@ -import UIKit - -// MARK: - AlertAction - -/// An action that can be added to an `Alert`. This is modeled after `UIAlertAction` -/// and allows the handler to be invoked from tests. -/// -public class AlertAction { - // MARK: Properties - - /// An optional handler that is called when the user taps on the action from the alert. - let handler: ((AlertAction, [AlertTextField]) async -> Void)? - - /// The style of the action. - let style: UIAlertAction.Style - - /// The title of the alert action to display in the alert. - let title: String - - // MARK: Initialization - - /// Initializes an `AlertAction` with a title, style and optional handler. - /// - /// - Parameters: - /// - title: The title of the alert action. - /// - style: The style of the alert action to use when creating a `UIAlertAction`. - /// - handler: The handler that is called when the user taps on the action in the alert. - /// - public init( - title: String, - style: UIAlertAction.Style, - handler: ((AlertAction, [AlertTextField]) async -> Void)? = nil, - ) { - self.title = title - self.style = style - self.handler = handler - } - - /// Initializes an `AlertAction` with a title, style and optional handler. - /// - /// - Parameters: - /// - title: The title of the alert action. - /// - style: The style of the alert action to use when creating a `UIAlertAction`. - /// - handler: The handler that is called when the user taps on the action in the alert. - /// - public init( - title: String, - style: UIAlertAction.Style, - handler: @escaping (AlertAction) async -> Void, - ) { - self.title = title - self.style = style - self.handler = { action, _ in - await handler(action) - } - } -} - -extension AlertAction: Equatable { - public static func == (lhs: AlertAction, rhs: AlertAction) -> Bool { - guard lhs.style == rhs.style, lhs.title == rhs.title else { return false } - switch (lhs.handler, rhs.handler) { - case (.none, .none), - (.some, .some): - return true - case (_, .some), - (.some, _): - return false - } - } -} - -extension AlertAction: Hashable { - public func hash(into hasher: inout Hasher) { - hasher.combine(title) - hasher.combine(style) - } -} diff --git a/AuthenticatorShared/UI/Platform/Application/Utilities/Alert/AlertErrorTests.swift b/AuthenticatorShared/UI/Platform/Application/Utilities/Alert/AlertErrorTests.swift deleted file mode 100644 index 826b6218a1..0000000000 --- a/AuthenticatorShared/UI/Platform/Application/Utilities/Alert/AlertErrorTests.swift +++ /dev/null @@ -1,29 +0,0 @@ -import BitwardenKit -import BitwardenResources -import XCTest - -@testable import AuthenticatorShared - -class AlertErrorTests: BitwardenTestCase { - /// `defaultAlert(title:message:)` constructs an `Alert` with the title, message, and an OK button. - func test_defaultAlert() { - let subject = Alert.defaultAlert(title: "title", message: "message") - - XCTAssertEqual(subject.title, "title") - XCTAssertEqual(subject.message, "message") - XCTAssertEqual(subject.alertActions, [AlertAction(title: Localizations.ok, style: .cancel)]) - } - - /// `inputValidationAlert(error:)` creates an `Alert` for an input validation error. - func test_inputValidationAlert() { - let subject = Alert.inputValidationAlert( - error: InputValidationError( - message: Localizations.validationFieldRequired(Localizations.accountName), - ), - ) - - XCTAssertEqual(subject.title, Localizations.anErrorHasOccurred) - XCTAssertEqual(subject.message, Localizations.validationFieldRequired(Localizations.accountName)) - XCTAssertEqual(subject.alertActions, [AlertAction(title: Localizations.ok, style: .default)]) - } -} diff --git a/AuthenticatorShared/UI/Platform/Application/Utilities/Alert/AlertPresentable.swift b/AuthenticatorShared/UI/Platform/Application/Utilities/Alert/AlertPresentable.swift deleted file mode 100644 index 09e1e64a31..0000000000 --- a/AuthenticatorShared/UI/Platform/Application/Utilities/Alert/AlertPresentable.swift +++ /dev/null @@ -1,46 +0,0 @@ -import BitwardenKit -import UIKit - -// MARK: - AlertPresentable - -/// Protocol for creating and presenting a `UIAlertController` from an `Alert`. -/// -@MainActor -public protocol AlertPresentable { - /// The root view controller that the alert should be presented on. - var rootViewController: UIViewController? { get } - - /// Presents a `UIAlertController` created from the `Alert` on the provided `rootViewController`. - /// - /// - Parameter alert: The `Alert` used to create a `UIAlertController` to present. - /// - func present(_ alert: Alert) -} - -public extension AlertPresentable { - /// Presents a `UIAlertController` created from the `Alert` on the provided `rootViewController`. - /// - /// - Parameter alert: The `Alert` used to create a `UIAlertController` to present. - /// - func present(_ alert: Alert) { - let alertController = alert.createAlertController() - guard let parent = rootViewController?.topmostViewController() else { return } - - if alert.preferredStyle == .actionSheet { - // iPadOS requires an anchor for action sheets. This solution keeps the iPad app from crashing, and centers - // the presentation of the action sheet. - alertController.popoverPresentationController?.sourceView = parent.view - alertController.popoverPresentationController?.sourceRect = CGRect( - x: parent.view.bounds.midX, - y: parent.view.bounds.midY, - width: 0, - height: 0, - ) - alertController.popoverPresentationController?.permittedArrowDirections = [] - } - - parent.present(alertController, animated: UI.animated) - } -} - -extension UIWindow: AlertPresentable {} diff --git a/AuthenticatorShared/UI/Platform/Application/Utilities/Alert/AlertPresentableTests.swift b/AuthenticatorShared/UI/Platform/Application/Utilities/Alert/AlertPresentableTests.swift deleted file mode 100644 index 0eeac6d46e..0000000000 --- a/AuthenticatorShared/UI/Platform/Application/Utilities/Alert/AlertPresentableTests.swift +++ /dev/null @@ -1,41 +0,0 @@ -import XCTest - -@testable import AuthenticatorShared - -// MARK: - AlertPresentableTests - -class AlertPresentableTests: BitwardenTestCase { - // MARK: Properties - - var rootViewController: UIViewController! - var subject: AlertPresentableSubject! - - // MARK: Setup and Teardown - - override func setUp() { - super.setUp() - rootViewController = UIViewController() - subject = AlertPresentableSubject() - subject.rootViewController = rootViewController - setKeyWindowRoot(viewController: rootViewController) - } - - override func tearDown() { - super.tearDown() - rootViewController = nil - subject = nil - } - - // MARK: Tests - - /// `present(_:)` presents a `UIAlertController` on the root view controller. - @MainActor - func test_present() { - subject.present(Alert(title: "🍎", message: "🥝", preferredStyle: .alert)) - XCTAssertNotNil(rootViewController.presentedViewController as? UIAlertController) - } -} - -class AlertPresentableSubject: AlertPresentable { - var rootViewController: UIViewController? -} diff --git a/AuthenticatorShared/UI/Platform/Application/Utilities/Alert/AlertTests.swift b/AuthenticatorShared/UI/Platform/Application/Utilities/Alert/AlertTests.swift deleted file mode 100644 index 239f69e5b8..0000000000 --- a/AuthenticatorShared/UI/Platform/Application/Utilities/Alert/AlertTests.swift +++ /dev/null @@ -1,117 +0,0 @@ -import BitwardenSdk -import XCTest - -@testable import AuthenticatorShared - -// MARK: - AlertTests - -class AlertTests: BitwardenTestCase { - // MARK: Properties - - var subject: Alert! - - // MARK: Setup and Teardown - - override func setUp() { - super.setUp() - - subject = Alert(title: "🍎", message: "🥝", preferredStyle: .alert) - .add(AlertAction(title: "Cancel", style: .cancel)) - .addPreferred(AlertAction(title: "OK", style: .default)) - .add(AlertTextField( - id: "field", - autocapitalizationType: .allCharacters, - autocorrectionType: .no, - isSecureTextEntry: true, - keyboardType: .numberPad, - placeholder: "placeholder", - text: "value", - )) - } - - override func tearDown() { - super.tearDown() - subject = nil - } - - // MARK: Tests - - /// `createAlertController` returns a `UIAlertController` based on the alert details. - @MainActor - func test_createAlertController() throws { - let alertController = subject.createAlertController() - - XCTAssertEqual(alertController.title, "🍎") - XCTAssertEqual(alertController.message, "🥝") - XCTAssertEqual(alertController.preferredStyle, .alert) - XCTAssertEqual(alertController.actions.count, 2) - XCTAssertEqual(alertController.actions[0].title, "Cancel") - XCTAssertEqual(alertController.actions[0].style, .cancel) - XCTAssertEqual(alertController.actions[1].title, "OK") - XCTAssertEqual(alertController.actions[1].style, .default) - XCTAssertEqual(alertController.textFields?.count, 1) - - let textField = try XCTUnwrap(alertController.textFields?.first) - XCTAssertEqual(textField.text, "value") - XCTAssertEqual(textField.placeholder, "placeholder") - XCTAssertEqual(textField.autocapitalizationType, .allCharacters) - XCTAssertEqual(textField.autocorrectionType, .no) - XCTAssertEqual(textField.isSecureTextEntry, true) - XCTAssertEqual(textField.keyboardType, .numberPad) - XCTAssertEqual(alertController.preferredAction?.title, "OK") - } - - /// `debugDescription` contains the alert's properties - func test_debugDescription() { - XCTAssertEqual( - subject!.debugDescription, - // swiftlint:disable:next line_length - "Alert(title: 🍎, message: 🥝, alertActions: [AuthenticatorShared.AlertAction, AuthenticatorShared.AlertAction]," - + " alertTextFields: [AuthenticatorShared.AlertTextField])", - ) - } - - /// Alert conforms to `Equatable`. - func test_equatable() { - XCTAssertEqual(subject, Alert(title: "🍎", message: "🥝", preferredStyle: .alert) - .add(AlertAction(title: "Cancel", style: .cancel)) - .addPreferred(AlertAction(title: "OK", style: .default)) - .add(AlertTextField( - id: "field", - autocapitalizationType: .allCharacters, - autocorrectionType: .yes, - isSecureTextEntry: true, - keyboardType: .numberPad, - placeholder: "placeholder", - text: "value", - ))) - XCTAssertNotEqual(subject, Alert(title: "🍎", message: "🥝", preferredStyle: .alert) - .add(AlertAction(title: "Cancel", style: .destructive)) - .addPreferred(AlertAction(title: "OK", style: .default)) - .add(AlertTextField( - id: "field", - autocapitalizationType: .allCharacters, - autocorrectionType: .yes, - isSecureTextEntry: true, - keyboardType: .numberPad, - placeholder: "placeholder", - text: "value", - ))) - XCTAssertNotEqual(subject, Alert(title: "🍎", message: "🥝", preferredStyle: .alert)) - XCTAssertNotEqual(subject, Alert(title: "🍎", message: "🥝", preferredStyle: .alert) - .add(AlertAction(title: "Cancel", style: .cancel)) - .addPreferred(AlertAction(title: "OK", style: .default) { _ in }) - .add(AlertTextField( - id: "field", - autocapitalizationType: .allCharacters, - autocorrectionType: .yes, - isSecureTextEntry: true, - keyboardType: .numberPad, - placeholder: "placeholder", - text: "value", - ))) - XCTAssertEqual(subject, Alert(title: "🍎", message: "🥝", preferredStyle: .alert) - .add(AlertAction(title: "Cancel", style: .cancel)) - .addPreferred(AlertAction(title: "OK", style: .default))) - } -} diff --git a/AuthenticatorShared/UI/Platform/Application/Utilities/Alert/AlertTextField.swift b/AuthenticatorShared/UI/Platform/Application/Utilities/Alert/AlertTextField.swift deleted file mode 100644 index 8cdac6d51f..0000000000 --- a/AuthenticatorShared/UI/Platform/Application/Utilities/Alert/AlertTextField.swift +++ /dev/null @@ -1,87 +0,0 @@ -import UIKit - -// MARK: - AlertTextField - -/// A text field that can be added to an `Alert`. This class allows an `AlertAction` to retrieve a -/// value entered by the user when executing its handler. -/// -public class AlertTextField { - /// The identifier for this text field. - public var id: String - - /// The placeholder for this text field. - public var placeholder: String? - - /// The text value entered by the user. - public private(set) var text: String - - /// How the text should be autocapitalized in this field. - public let autocapitalizationType: UITextAutocapitalizationType - - /// How the text should be autocorrected in this field. - public let autocorrectionType: UITextAutocorrectionType - - /// A flag indicating if this text field's contents should be masked. - public let isSecureTextEntry: Bool - - /// The keyboard type for this text field. - public let keyboardType: UIKeyboardType - - /// Creates a new `AlertTextField`. - /// - /// - Parameters: - /// - id: The identifier for this text field. Defaults to a new UUID. - /// - autocapitalizationType: How the text should be autocapitalized in this field. Defaults to `.sentences`. - /// - autocorrectionType: How the text should be autocorrected in this field. Defaults to `.default`. - /// - isSecureTextEntry: A flag indicating if this text field's content should be masked. - /// - keyboardType: The keyboard type for this text field. Defaults to `.default`. - /// - placeholder: The optional placeholder for this text field. Defaults to `nil`. - /// - text: An optional initial value to pre-fill the text field with. - /// - public init( - id: String = UUID().uuidString, - autocapitalizationType: UITextAutocapitalizationType = .sentences, - autocorrectionType: UITextAutocorrectionType = .default, - isSecureTextEntry: Bool = false, - keyboardType: UIKeyboardType = .default, - placeholder: String? = nil, - text: String? = nil, - ) { - self.id = id - self.autocapitalizationType = autocapitalizationType - self.autocorrectionType = autocorrectionType - self.isSecureTextEntry = isSecureTextEntry - self.keyboardType = keyboardType - self.placeholder = placeholder - self.text = text ?? "" - } - - @objc - func textChanged(in textField: UITextField) { - text = textField.text ?? "" - } -} - -extension AlertTextField: Equatable { - public static func == (lhs: AlertTextField, rhs: AlertTextField) -> Bool { - lhs.autocapitalizationType == rhs.autocapitalizationType - && lhs.autocorrectionType == rhs.autocorrectionType - && lhs.id == rhs.id - && lhs.isSecureTextEntry == rhs.isSecureTextEntry - && lhs.keyboardType == rhs.keyboardType - && lhs.placeholder == rhs.placeholder - && lhs.text == rhs.text - } -} - -extension AlertTextField: Hashable { - public func hash(into hasher: inout Hasher) { - hasher.combine(autocapitalizationType) - hasher.combine(autocorrectionType) - hasher.combine(id) - hasher.combine(isSecureTextEntry) - hasher.combine(keyboardType) - hasher.combine(placeholder) - hasher.combine(text) - } -} diff --git a/AuthenticatorShared/UI/Platform/Application/Utilities/Alert/TestHelpers/Alert+TestHelpers.swift b/AuthenticatorShared/UI/Platform/Application/Utilities/Alert/TestHelpers/Alert+TestHelpers.swift deleted file mode 100644 index 8f9a3447c5..0000000000 --- a/AuthenticatorShared/UI/Platform/Application/Utilities/Alert/TestHelpers/Alert+TestHelpers.swift +++ /dev/null @@ -1,28 +0,0 @@ -import Foundation - -@testable import AuthenticatorShared - -enum AlertError: LocalizedError { - case alertActionNotFound(title: String) - - var errorDescription: String? { - switch self { - case let .alertActionNotFound(title): - "Unable to locate an alert action for the title: \(title)" - } - } -} - -extension Alert { - /// Simulates a user interaction with the alert action that matches the provided title. - /// - /// - Parameter title: The title of the alert action to trigger. - /// - Throws: Throws an `AlertError` if the alert action cannot be found. - /// - func tapAction(title: String) async throws { - guard let alertAction = alertActions.first(where: { $0.title == title }) else { - throw AlertError.alertActionNotFound(title: title) - } - await alertAction.handler?(alertAction, alertTextFields) - } -} diff --git a/AuthenticatorShared/UI/Platform/Application/Utilities/AnyCoordinator.swift b/AuthenticatorShared/UI/Platform/Application/Utilities/AnyCoordinator.swift index 8f2c5b2069..4dc1a535e0 100644 --- a/AuthenticatorShared/UI/Platform/Application/Utilities/AnyCoordinator.swift +++ b/AuthenticatorShared/UI/Platform/Application/Utilities/AnyCoordinator.swift @@ -60,7 +60,7 @@ open class AnyCoordinator: Coordinator { doNavigate(route, context) } - open func showAlert(_ alert: Alert) { + open func showAlert(_ alert: BitwardenKit.Alert) { doShowAlert(alert) } diff --git a/AuthenticatorShared/UI/Platform/Application/Utilities/Coordinator.swift b/AuthenticatorShared/UI/Platform/Application/Utilities/Coordinator.swift index 524d9d180f..aab83acbf0 100644 --- a/AuthenticatorShared/UI/Platform/Application/Utilities/Coordinator.swift +++ b/AuthenticatorShared/UI/Platform/Application/Utilities/Coordinator.swift @@ -34,7 +34,7 @@ public protocol Coordinator: AnyObject { /// /// - Parameter alert: The alert to show. /// - func showAlert(_ alert: Alert) + func showAlert(_ alert: BitwardenKit.Alert) /// Shows the loading overlay view. /// @@ -141,7 +141,7 @@ extension Coordinator where Self: HasNavigator { /// /// - Parameter alert: The alert to show. /// - func showAlert(_ alert: Alert) { + func showAlert(_ alert: BitwardenKit.Alert) { navigator?.present(alert) } diff --git a/AuthenticatorShared/UI/Platform/Settings/Extensions/Alert+Settings.swift b/AuthenticatorShared/UI/Platform/Settings/Extensions/Alert+Settings.swift index 575b242a27..e1fde99625 100644 --- a/AuthenticatorShared/UI/Platform/Settings/Extensions/Alert+Settings.swift +++ b/AuthenticatorShared/UI/Platform/Settings/Extensions/Alert+Settings.swift @@ -1,3 +1,4 @@ +import BitwardenKit import BitwardenResources // MARK: - Alert + Settings diff --git a/AuthenticatorShared/UI/Platform/Settings/Extensions/AlertSettingsTests.swift b/AuthenticatorShared/UI/Platform/Settings/Extensions/AlertSettingsTests.swift index 3df33fca0e..881608d022 100644 --- a/AuthenticatorShared/UI/Platform/Settings/Extensions/AlertSettingsTests.swift +++ b/AuthenticatorShared/UI/Platform/Settings/Extensions/AlertSettingsTests.swift @@ -1,3 +1,4 @@ +import BitwardenKit import BitwardenResources import XCTest diff --git a/AuthenticatorShared/UI/Vault/AuthenticatorItem/AuthenticatorItemRoute.swift b/AuthenticatorShared/UI/Vault/AuthenticatorItem/AuthenticatorItemRoute.swift index e75c4c1ea7..6cfa28211e 100644 --- a/AuthenticatorShared/UI/Vault/AuthenticatorItem/AuthenticatorItemRoute.swift +++ b/AuthenticatorShared/UI/Vault/AuthenticatorItem/AuthenticatorItemRoute.swift @@ -1,3 +1,4 @@ +import BitwardenKit import BitwardenSdk import SwiftUI @@ -9,7 +10,7 @@ enum AuthenticatorItemRoute: Equatable { /// /// - Parameter alert: The alert to display. /// - case alert(_ alert: Alert) + case alert(_ alert: BitwardenKit.Alert) /// A route to dismiss the screen currently presented modally. /// diff --git a/AuthenticatorShared/UI/Vault/Extensions/Alert+Scan.swift b/AuthenticatorShared/UI/Vault/Extensions/Alert+Scan.swift index 622db9399d..66006b8d6a 100644 --- a/AuthenticatorShared/UI/Vault/Extensions/Alert+Scan.swift +++ b/AuthenticatorShared/UI/Vault/Extensions/Alert+Scan.swift @@ -1,3 +1,4 @@ +import BitwardenKit import BitwardenResources import UIKit diff --git a/AuthenticatorShared/UI/Vault/Extensions/Alert+Vault.swift b/AuthenticatorShared/UI/Vault/Extensions/Alert+Vault.swift index 82428f05a0..57c705c67e 100644 --- a/AuthenticatorShared/UI/Vault/Extensions/Alert+Vault.swift +++ b/AuthenticatorShared/UI/Vault/Extensions/Alert+Vault.swift @@ -1,3 +1,4 @@ +import BitwardenKit import BitwardenResources import UIKit diff --git a/AuthenticatorShared/UI/Vault/VaultItem/AuthenticatorKeyCapture/Extensions/Alert+ScanError.swift b/AuthenticatorShared/UI/Vault/VaultItem/AuthenticatorKeyCapture/Extensions/Alert+ScanError.swift index d2793db3bf..0da327dd4f 100644 --- a/AuthenticatorShared/UI/Vault/VaultItem/AuthenticatorKeyCapture/Extensions/Alert+ScanError.swift +++ b/AuthenticatorShared/UI/Vault/VaultItem/AuthenticatorKeyCapture/Extensions/Alert+ScanError.swift @@ -1,3 +1,4 @@ +import BitwardenKit import BitwardenResources extension Alert { diff --git a/BitwardenKit/Application/TestHelpers/Support/BitwardenTestCase.swift b/BitwardenKit/Application/TestHelpers/Support/BitwardenTestCase.swift index b6d5e78290..89274d5cdd 100644 --- a/BitwardenKit/Application/TestHelpers/Support/BitwardenTestCase.swift +++ b/BitwardenKit/Application/TestHelpers/Support/BitwardenTestCase.swift @@ -1,9 +1,29 @@ +import BitwardenKit import TestHelpers import XCTest open class BitwardenTestCase: BaseBitwardenTestCase { @MainActor override open class func setUp() { + // Apply default appearances for snapshot tests. + UI.applyDefaultAppearances() + TestDataHelpers.defaultBundle = Bundle(for: Self.self) } + + /// Executes any logic that should be applied before each test runs. + /// + @MainActor + override open func setUp() { + super.setUp() + UI.animated = false + UI.sizeCategory = .large + } + + /// Executes any logic that should be applied after each test runs. + /// + override open func tearDown() { + super.tearDown() + UI.animated = false + } } diff --git a/BitwardenShared/UI/Platform/Application/Utilities/Alert/Alert/Alert+Error.swift b/BitwardenKit/UI/Platform/Application/Utilities/Alert/Alert+Error.swift similarity index 97% rename from BitwardenShared/UI/Platform/Application/Utilities/Alert/Alert/Alert+Error.swift rename to BitwardenKit/UI/Platform/Application/Utilities/Alert/Alert+Error.swift index 9944501b1e..7304c95c1f 100644 --- a/BitwardenShared/UI/Platform/Application/Utilities/Alert/Alert/Alert+Error.swift +++ b/BitwardenKit/UI/Platform/Application/Utilities/Alert/Alert+Error.swift @@ -1,10 +1,9 @@ -import BitwardenKit import BitwardenResources import Foundation // MARK: - Alert+Error -extension Alert { +public extension Alert { // MARK: Methods /// The default alert style for a given error with a standard ok button to dismiss. diff --git a/BitwardenShared/UI/Platform/Application/Utilities/Alert/Alert/Alert.swift b/BitwardenKit/UI/Platform/Application/Utilities/Alert/Alert.swift similarity index 92% rename from BitwardenShared/UI/Platform/Application/Utilities/Alert/Alert/Alert.swift rename to BitwardenKit/UI/Platform/Application/Utilities/Alert/Alert.swift index b76dc9c740..2e7ddd096d 100644 --- a/BitwardenShared/UI/Platform/Application/Utilities/Alert/Alert/Alert.swift +++ b/BitwardenKit/UI/Platform/Application/Utilities/Alert/Alert.swift @@ -9,22 +9,22 @@ public class Alert { // MARK: Properties /// A list of actions that the user can tap on in the alert. - var alertActions: [AlertAction] = [] + public var alertActions: [AlertAction] = [] /// A list of text fields that the user can use to enter text. - var alertTextFields: [AlertTextField] = [] + public var alertTextFields: [AlertTextField] = [] /// The message that is displayed in the alert. - let message: String? + public let message: String? /// The preferred action for the user to take in the alert, which emphasis is given. - var preferredAction: AlertAction? + public var preferredAction: AlertAction? /// The alert's style. - let preferredStyle: UIAlertController.Style + public let preferredStyle: UIAlertController.Style /// The title of the message that is displayed at the top of the alert. - let title: String? + public let title: String? // MARK: Initialization @@ -60,7 +60,7 @@ public class Alert { /// - Returns: `self` to allow `add(_:)` methods to be chained. /// @discardableResult - func add(_ action: AlertAction) -> Self { + public func add(_ action: AlertAction) -> Self { alertActions.append(action) return self } @@ -72,7 +72,7 @@ public class Alert { /// - Returns: `self` to allow `add(_:)` methods to be chained. /// @discardableResult - func add(_ textField: AlertTextField) -> Self { + public func add(_ textField: AlertTextField) -> Self { alertTextFields.append(textField) return self } @@ -86,7 +86,7 @@ public class Alert { /// - Returns: `self` to allow `add(_:)` methods to be chained. /// @discardableResult - func addPreferred(_ action: AlertAction) -> Self { + public func addPreferred(_ action: AlertAction) -> Self { alertActions.append(action) preferredAction = action return self @@ -98,7 +98,7 @@ public class Alert { /// - Returns An initialized `UIAlertController` that has the `AlertAction`s added. /// @MainActor - func createAlertController(onDismissed: (() -> Void)? = nil) -> UIAlertController { + public func createAlertController(onDismissed: (() -> Void)? = nil) -> UIAlertController { let alertController = AlertController(title: title, message: message, preferredStyle: preferredStyle) alertController.onDismissed = onDismissed diff --git a/BitwardenShared/UI/Platform/Application/Utilities/Alert/Alert/AlertAction.swift b/BitwardenKit/UI/Platform/Application/Utilities/Alert/AlertAction.swift similarity index 93% rename from BitwardenShared/UI/Platform/Application/Utilities/Alert/Alert/AlertAction.swift rename to BitwardenKit/UI/Platform/Application/Utilities/Alert/AlertAction.swift index 80f122adeb..ef67ca5ba6 100644 --- a/BitwardenShared/UI/Platform/Application/Utilities/Alert/Alert/AlertAction.swift +++ b/BitwardenKit/UI/Platform/Application/Utilities/Alert/AlertAction.swift @@ -9,16 +9,16 @@ public class AlertAction { // MARK: Properties /// An optional handler that is called when the user taps on the action from the alert. - let handler: ((AlertAction, [AlertTextField]) async -> Void)? + public let handler: ((AlertAction, [AlertTextField]) async -> Void)? /// Condition that determines if the action should be enabled. Defaults to always enabled. - var shouldEnableAction: (([AlertTextField]) -> Bool)? + public var shouldEnableAction: (([AlertTextField]) -> Bool)? /// The style of the action. - let style: UIAlertAction.Style + public let style: UIAlertAction.Style /// The title of the alert action to display in the alert. - let title: String + public let title: String // MARK: Initialization diff --git a/BitwardenShared/UI/Platform/Application/Utilities/Alert/Alert/AlertErrorTests.swift b/BitwardenKit/UI/Platform/Application/Utilities/Alert/AlertErrorTests.swift similarity index 86% rename from BitwardenShared/UI/Platform/Application/Utilities/Alert/Alert/AlertErrorTests.swift rename to BitwardenKit/UI/Platform/Application/Utilities/Alert/AlertErrorTests.swift index c5f13d9d7a..8b20eaab75 100644 --- a/BitwardenShared/UI/Platform/Application/Utilities/Alert/Alert/AlertErrorTests.swift +++ b/BitwardenKit/UI/Platform/Application/Utilities/Alert/AlertErrorTests.swift @@ -1,8 +1,8 @@ -import BitwardenKit import BitwardenResources +import TestHelpers import XCTest -@testable import BitwardenShared +@testable import BitwardenKit class AlertErrorTests: BitwardenTestCase { /// `defaultAlert(title:message:)` constructs an `Alert` with the title, message, and an OK button. @@ -17,10 +17,10 @@ class AlertErrorTests: BitwardenTestCase { /// `defaultAlert(error:)` constructs an `Alert` with the title and message based on the error, /// and an OK button. func test_defaultAlertError() { - let subject = Alert.defaultAlert(error: StateServiceError.noActiveAccount) + let subject = Alert.defaultAlert(error: BitwardenTestError.example) XCTAssertEqual(subject.title, Localizations.anErrorHasOccurred) - XCTAssertEqual(subject.message, StateServiceError.noActiveAccount.errorDescription) + XCTAssertEqual(subject.message, BitwardenTestError.example.errorDescription) XCTAssertEqual(subject.alertActions, [AlertAction(title: Localizations.ok, style: .cancel)]) } diff --git a/BitwardenShared/UI/Platform/Application/Utilities/Alert/Alert/AlertPresentable.swift b/BitwardenKit/UI/Platform/Application/Utilities/Alert/AlertPresentable.swift similarity index 99% rename from BitwardenShared/UI/Platform/Application/Utilities/Alert/Alert/AlertPresentable.swift rename to BitwardenKit/UI/Platform/Application/Utilities/Alert/AlertPresentable.swift index 8a206ea758..86195583d2 100644 --- a/BitwardenShared/UI/Platform/Application/Utilities/Alert/Alert/AlertPresentable.swift +++ b/BitwardenKit/UI/Platform/Application/Utilities/Alert/AlertPresentable.swift @@ -1,4 +1,3 @@ -import BitwardenKit import OSLog import UIKit diff --git a/BitwardenShared/UI/Platform/Application/Utilities/Alert/Alert/AlertPresentableTests.swift b/BitwardenKit/UI/Platform/Application/Utilities/Alert/AlertPresentableTests.swift similarity index 93% rename from BitwardenShared/UI/Platform/Application/Utilities/Alert/Alert/AlertPresentableTests.swift rename to BitwardenKit/UI/Platform/Application/Utilities/Alert/AlertPresentableTests.swift index 47363b7029..679c337287 100644 --- a/BitwardenShared/UI/Platform/Application/Utilities/Alert/Alert/AlertPresentableTests.swift +++ b/BitwardenKit/UI/Platform/Application/Utilities/Alert/AlertPresentableTests.swift @@ -1,6 +1,7 @@ +import BitwardenKitMocks import XCTest -@testable import BitwardenShared +@testable import BitwardenKit // MARK: - AlertPresentableTests @@ -14,7 +15,7 @@ class AlertPresentableTests: BitwardenTestCase { override func setUp() { super.setUp() - rootViewController = UIViewController() + rootViewController = MockUIViewController() subject = AlertPresentableSubject() subject.rootViewController = rootViewController setKeyWindowRoot(viewController: rootViewController) diff --git a/BitwardenKit/UI/Platform/Application/Utilities/Alert/AlertTests.swift b/BitwardenKit/UI/Platform/Application/Utilities/Alert/AlertTests.swift new file mode 100644 index 0000000000..cc36e3d64d --- /dev/null +++ b/BitwardenKit/UI/Platform/Application/Utilities/Alert/AlertTests.swift @@ -0,0 +1,92 @@ +import BitwardenKitMocks +import BitwardenResources +import XCTest + +@testable import BitwardenKit + +// MARK: - AlertTests + +class AlertTests: BitwardenTestCase { + // MARK: Properties + + var subject: Alert! + + // MARK: Setup and Teardown + + override func setUp() { + super.setUp() + + subject = Alert.fixture(alertActions: [AlertAction.cancel()], + alertTextFields: [AlertTextField.fixture(autocorrectionType: .no)]) + .addPreferred(AlertAction.ok()) + } + + override func tearDown() { + super.tearDown() + subject = nil + } + + // MARK: Tests + + /// `createAlertController` returns a `UIAlertController` based on the alert details. + @MainActor + func test_createAlertController() throws { + let alertController = subject.createAlertController() + + XCTAssertEqual(alertController.title, "🍎") + XCTAssertEqual(alertController.message, "🥝") + XCTAssertEqual(alertController.preferredStyle, .alert) + XCTAssertEqual(alertController.actions.count, 2) + XCTAssertEqual(alertController.actions[0].title, "Cancel") + XCTAssertEqual(alertController.actions[0].style, .cancel) + XCTAssertEqual(alertController.actions[1].title, "OK") + XCTAssertEqual(alertController.actions[1].style, .default) + XCTAssertEqual(alertController.textFields?.count, 1) + + let textField = try XCTUnwrap(alertController.textFields?.first) + XCTAssertEqual(textField.text, "value") + XCTAssertEqual(textField.placeholder, "placeholder") + XCTAssertEqual(textField.autocapitalizationType, .allCharacters) + XCTAssertEqual(textField.autocorrectionType, .no) + XCTAssertEqual(textField.isSecureTextEntry, true) + XCTAssertEqual(textField.keyboardType, .numberPad) + XCTAssertEqual(alertController.preferredAction?.title, "OK") + } + + /// `createAlertController` sets an `onDismissed` closure that's called when the alert is dismissed. + @MainActor + func test_createAlertController_onDismissed() { + var dismissedCalled = false + let alertController = subject.createAlertController { dismissedCalled = true } + let rootViewController = MockUIViewController() + setKeyWindowRoot(viewController: rootViewController) + + rootViewController.present(alertController, animated: false) + XCTAssertFalse(dismissedCalled) + rootViewController.dismiss(animated: false) + waitFor(rootViewController.presentedViewController == nil) + XCTAssertTrue(dismissedCalled) + } + + /// `debugDescription` contains the alert's properties + func test_debugDescription() { + XCTAssertEqual( + subject!.debugDescription, + "Alert(title: 🍎, message: 🥝, alertActions: [BitwardenKit.AlertAction, BitwardenKit.AlertAction]," + + " alertTextFields: [BitwardenKit.AlertTextField])", + ) + } + + /// Alert conforms to `Equatable`. + func test_equatable() { + XCTAssertEqual(subject, Alert.fixture(alertActions: [AlertAction.cancel()]) + .addPreferred(AlertAction.ok())) + XCTAssertNotEqual(subject, Alert.fixture(alertActions: [AlertAction.cancel(style: .destructive)]) + .addPreferred(AlertAction.ok())) + XCTAssertNotEqual(subject, Alert(title: "🍎", message: "🥝", preferredStyle: .alert)) + XCTAssertNotEqual(subject, Alert.fixture(alertActions: [AlertAction.cancel()]) + .addPreferred(AlertAction.ok { _, _ in })) + XCTAssertEqual(subject, Alert.fixture(alertActions: [AlertAction.cancel()]) + .addPreferred(AlertAction.ok())) + } +} diff --git a/BitwardenShared/UI/Platform/Application/Utilities/Alert/Alert/AlertTextField.swift b/BitwardenKit/UI/Platform/Application/Utilities/Alert/AlertTextField.swift similarity index 86% rename from BitwardenShared/UI/Platform/Application/Utilities/Alert/Alert/AlertTextField.swift rename to BitwardenKit/UI/Platform/Application/Utilities/Alert/AlertTextField.swift index 1cb90dcbeb..ed618b1c6f 100644 --- a/BitwardenShared/UI/Platform/Application/Utilities/Alert/Alert/AlertTextField.swift +++ b/BitwardenKit/UI/Platform/Application/Utilities/Alert/AlertTextField.swift @@ -59,8 +59,16 @@ public class AlertTextField { self.text = text ?? "" } + /// Updates the text field's internal text value when the UITextField content changes. + /// + /// This method is typically connected as a target-action for the `.editingChanged` + /// control event of a `UITextField`. It synchronizes the text field's content with + /// the internal `text` property and triggers the optional `onTextChanged` callback. + /// + /// - Parameters: + /// - textField: The `UITextField` whose content has changed. @objc - func textChanged(in textField: UITextField) { + public func textChanged(in textField: UITextField) { text = textField.text ?? "" onTextChanged?() } diff --git a/BitwardenShared/UI/Platform/Application/Utilities/Alert/Alert/TestHelpers/Alert+TestHelpers.swift b/BitwardenKit/UI/Platform/Application/Utilities/Alert/Mocks/Alert+TestHelpers.swift similarity index 98% rename from BitwardenShared/UI/Platform/Application/Utilities/Alert/Alert/TestHelpers/Alert+TestHelpers.swift rename to BitwardenKit/UI/Platform/Application/Utilities/Alert/Mocks/Alert+TestHelpers.swift index 8f07d4028d..8d200e2b8c 100644 --- a/BitwardenShared/UI/Platform/Application/Utilities/Alert/Alert/TestHelpers/Alert+TestHelpers.swift +++ b/BitwardenKit/UI/Platform/Application/Utilities/Alert/Mocks/Alert+TestHelpers.swift @@ -1,7 +1,7 @@ import BitwardenResources import UIKit -@testable import BitwardenShared +@testable import BitwardenKit enum AlertError: LocalizedError { case alertActionNotFound(title: String) @@ -17,7 +17,7 @@ enum AlertError: LocalizedError { } } -extension Alert { +public extension Alert { /// Simulates tapping the cancel button of the alert. func tapCancel() async throws { try await tapAction(title: Localizations.cancel) diff --git a/BitwardenKit/UI/Platform/Application/Utilities/Mocks/MockUIViewController.swift b/BitwardenKit/UI/Platform/Application/Utilities/Mocks/MockUIViewController.swift new file mode 100644 index 0000000000..39b00ee54c --- /dev/null +++ b/BitwardenKit/UI/Platform/Application/Utilities/Mocks/MockUIViewController.swift @@ -0,0 +1,217 @@ +import BitwardenKit +import UIKit +import XCTest + +// MARK: - MockUIViewController + +/// A mock UIViewController that can be used in tests that normally rely on the existence of a host app +/// because of details about how UIViewControllers present/dismiss other UIViewControllers. +public class MockUIViewController: UIViewController { + // MARK: Static properties + + /// A size for the `mockWindow` and `mockView` objects to have. + /// This happens to be the size of the iPhone 5, 5C, 5S, and SE. + private static var mockWindowSize = CGRect(x: 0, y: 0, width: 320, height: 568) + + // MARK: Presentation Tracking + + /// Indicates whether the `present` method has been called. + public var presentCalled = false + + /// The view controller that was presented, if any. + public var presentedView: UIViewController? + + /// Indicates whether the presentation was animated. + public var presentAnimated = false + + /// The completion handler passed to the `present` method. + public var presentCompletion: (() -> Void)? + + /// Returns the currently presented view controller. + override public var presentedViewController: UIViewController? { + presentedView + } + + // MARK: Dismissal Tracking + + /// Indicates whether the `dismiss` method has been called. + public var dismissCalled = false + + /// Indicates whether the dismissal was animated. + public var dismissAnimated = false + + /// The completion handler passed to the `dismiss` method. + public var dismissCompletion: (() -> Void)? + + // MARK: Navigation Tracking + + /// Indicates whether the `pushViewController` method has been called. + public var pushViewControllerCalled = false + + /// The view controller that was pushed, if any. + public var pushedViewController: UIViewController? + + /// Indicates whether the `popViewController` method has been called. + public var popViewControllerCalled = false + + // MARK: Navigation Controller Support + + /// Internal storage for a navigation controller. + private var _navigationController: UINavigationController? + + /// Returns the internally stored navigation controller, bypassing the superclass one. + override public var navigationController: UINavigationController? { + get { _navigationController } + set { _navigationController = newValue } + } + + // MARK: Mock Window and View Hierarchy + + /// The mock window used for testing view hierarchy. + private var mockWindow: UIWindow? + + /// The mock view used as the main view. + private var mockView: UIView? + + /// Returns the mock view or the default view if no mock view is set. + override public var view: UIView! { + get { + mockView ?? super.view + } + set { + mockView = newValue + super.view = newValue + } + } + + /// Returns whether the mock view or default view is loaded. + override public var isViewLoaded: Bool { + mockView != nil || super.isViewLoaded + } + + // MARK: Initialization + + /// Initializes the mock view controller with the specified nib name and bundle. + /// + /// - Parameters: + /// - nibNameOrNil: The name of the nib file to load, or nil if no nib should be loaded. + /// - nibBundleOrNil: The bundle containing the nib file, or nil for the main bundle. + override init( + nibName nibNameOrNil: String?, + bundle nibBundleOrNil: Bundle?, + ) { + super.init( + nibName: nibNameOrNil, + bundle: nibBundleOrNil, + ) + setUpMockHierarchy() + } + + /// Initializes the mock view controller from a coder. + /// + /// - Parameters: + /// - coder: The coder to initialize from. + required init?(coder: NSCoder) { + super.init(coder: coder) + setUpMockHierarchy() + } + + // MARK: View Life Cycle Methods + + /// Called after the view controller's view is loaded into memory. + /// Ensures that a mock view exists even if `loadView` wasn't called. + override public func viewDidLoad() { + super.viewDidLoad() + // Ensure we have a view even if loadView wasn't called + if view == nil { + view = UIView(frame: MockUIViewController.mockWindowSize) + } + } + + /// Creates the view controller's view programmatically. + /// Sets up a mock view with the predefined mock window size. + override public func loadView() { + if mockView == nil { + mockView = UIView(frame: MockUIViewController.mockWindowSize) + } + view = mockView + } + + // MARK: UIViewController Overrides + + /// Presents a view controller modally and tracks the presentation details for testing. + /// + /// - Parameters: + /// - viewControllerToPresent: The view controller to present. + /// - animated: Whether to animate the presentation. + /// - completion: A completion handler to call after the presentation finishes. + override public func present( + _ viewControllerToPresent: UIViewController, + animated: Bool, + completion: (() -> Void)? = nil, + ) { + presentCalled = true + presentedView = viewControllerToPresent + presentAnimated = animated + presentCompletion = completion + + // Set up the presented view controller's hierarchy + viewControllerToPresent.beginAppearanceTransition(true, animated: animated) + viewControllerToPresent.endAppearanceTransition() + + // Call completion if provided + completion?() + } + + /// Dismisses the currently presented view controller and tracks the dismissal details for testing. + /// + /// - Parameters: + /// - animated: Whether to animate the dismissal. + /// - completion: A completion handler to call after the dismissal finishes. + override public func dismiss(animated: Bool, completion: (() -> Void)? = nil) { + dismissCalled = true + dismissAnimated = animated + dismissCompletion = completion + + if let presentedView { + presentedView.beginAppearanceTransition(false, animated: animated) + presentedView.endAppearanceTransition() + } + + // Clear the presented view controller + presentedView = nil + + completion?() + } + + // MARK: Helper Methods + + /// Resets and clears all local variables, to prepare the mock for reuse. + public func reset() { + presentCalled = false + presentedView = nil + presentAnimated = false + presentCompletion = nil + + dismissCalled = false + dismissAnimated = false + dismissCompletion = nil + + pushViewControllerCalled = false + pushedViewController = nil + popViewControllerCalled = false + } + + // MARK: Mock Hierarchy + + /// Sets up a `UIWindow` and `UIView` to use as mocks in the view hierarchy. + private func setUpMockHierarchy() { + // Create a mock window to avoid issues with view hierarchy + mockWindow = UIWindow(frame: MockUIViewController.mockWindowSize) + mockWindow?.rootViewController = self + + // Create a mock view + mockView = UIView(frame: mockWindow?.frame ?? .zero) + view = mockView + } +} diff --git a/BitwardenKit/UI/Platform/Application/Utilities/StateProcessor.swift b/BitwardenKit/UI/Platform/Application/Utilities/StateProcessor.swift index c2711409b4..a8504ed492 100644 --- a/BitwardenKit/UI/Platform/Application/Utilities/StateProcessor.swift +++ b/BitwardenKit/UI/Platform/Application/Utilities/StateProcessor.swift @@ -1,4 +1,3 @@ -import BitwardenKit import Combine import Foundation diff --git a/BitwardenKit/UI/Platform/Application/Views/LoadingOverlay/LoadingOverlayView+SnapshotTests.swift b/BitwardenKit/UI/Platform/Application/Views/LoadingOverlay/LoadingOverlayView+SnapshotTests.swift index 476d406851..6eafc1634f 100644 --- a/BitwardenKit/UI/Platform/Application/Views/LoadingOverlay/LoadingOverlayView+SnapshotTests.swift +++ b/BitwardenKit/UI/Platform/Application/Views/LoadingOverlay/LoadingOverlayView+SnapshotTests.swift @@ -3,7 +3,7 @@ import SnapshotTesting import TestHelpers import XCTest -@testable import BitwardenShared +@testable import BitwardenKit class LoadingOverlayViewTests: BitwardenTestCase { // MARK: Tests diff --git a/BitwardenKit/UI/Platform/Application/Views/SectionView+SnapshotTests.swift b/BitwardenKit/UI/Platform/Application/Views/SectionView+SnapshotTests.swift index 8fb14d28a5..f7a813ea29 100644 --- a/BitwardenKit/UI/Platform/Application/Views/SectionView+SnapshotTests.swift +++ b/BitwardenKit/UI/Platform/Application/Views/SectionView+SnapshotTests.swift @@ -2,7 +2,7 @@ import SnapshotTesting import XCTest -@testable import BitwardenShared +@testable import BitwardenKit class SectionViewTests: BitwardenTestCase { // MARK: Tests diff --git a/BitwardenKit/UI/Platform/PreviewContent/Alert+Fixtures.swift b/BitwardenKit/UI/Platform/PreviewContent/Alert+Fixtures.swift new file mode 100644 index 0000000000..0e449c1764 --- /dev/null +++ b/BitwardenKit/UI/Platform/PreviewContent/Alert+Fixtures.swift @@ -0,0 +1,73 @@ +import UIKit + +#if DEBUG +public extension Alert { + static func fixture( + title: String = "🍎", + message: String? = "🥝", + preferredStyle: UIAlertController.Style = .alert, + alertActions: [AlertAction] = [.ok()], + alertTextFields: [AlertTextField] = [.fixture()], + ) -> Alert { + Alert( + title: title, + message: message, + preferredStyle: preferredStyle, + alertActions: alertActions, + alertTextFields: alertTextFields, + ) + } +} + +public extension AlertAction { + static func ok( + title: String = "OK", + style: UIAlertAction.Style = .default, + handler: ((AlertAction, [AlertTextField]) async -> Void)? = nil, + shouldEnableAction: (([AlertTextField]) -> Bool)? = nil, + ) -> AlertAction { + AlertAction( + title: title, + style: style, + handler: handler, + shouldEnableAction: shouldEnableAction, + ) + } + + static func cancel( + title: String = "Cancel", + style: UIAlertAction.Style = .cancel, + handler: ((AlertAction, [AlertTextField]) async -> Void)? = nil, + shouldEnableAction: (([AlertTextField]) -> Bool)? = nil, + ) -> AlertAction { + AlertAction( + title: title, + style: style, + handler: handler, + shouldEnableAction: shouldEnableAction, + ) + } +} + +public extension AlertTextField { + static func fixture( + id: String = "field", + autocapitalizationType: UITextAutocapitalizationType = .allCharacters, + autocorrectionType: UITextAutocorrectionType = .yes, + isSecureTextEntry: Bool = true, + keyboardType: UIKeyboardType = .numberPad, + placeholder: String? = "placeholder", + text: String = "value", + ) -> AlertTextField { + AlertTextField( + id: id, + autocapitalizationType: autocapitalizationType, + autocorrectionType: autocorrectionType, + isSecureTextEntry: isSecureTextEntry, + keyboardType: keyboardType, + placeholder: placeholder, + text: text, + ) + } +} +#endif diff --git a/BitwardenShared/UI/Auth/Landing/LandingProcessor.swift b/BitwardenShared/UI/Auth/Landing/LandingProcessor.swift index 2523ab03e4..fed7f8006f 100644 --- a/BitwardenShared/UI/Auth/Landing/LandingProcessor.swift +++ b/BitwardenShared/UI/Auth/Landing/LandingProcessor.swift @@ -191,7 +191,7 @@ extension LandingProcessor: ProfileSwitcherHandler { // No-Op for the landing processor. } - func showAlert(_ alert: Alert) { + func showAlert(_ alert: BitwardenKit.Alert) { coordinator.showAlert(alert) } diff --git a/BitwardenShared/UI/Auth/ProfileSwitcher/ProfileSwitcherHandler.swift b/BitwardenShared/UI/Auth/ProfileSwitcher/ProfileSwitcherHandler.swift index 863132deaa..2b33db96b9 100644 --- a/BitwardenShared/UI/Auth/ProfileSwitcher/ProfileSwitcherHandler.swift +++ b/BitwardenShared/UI/Auth/ProfileSwitcher/ProfileSwitcherHandler.swift @@ -68,7 +68,7 @@ protocol ProfileSwitcherHandler: AnyObject { // sourcery: AutoMockable /// /// - Parameter alert: The alert to show. /// - func showAlert(_ alert: Alert) + func showAlert(_ alert: BitwardenKit.Alert) /// Shows the profile switcher; this is used on iOS >=26 for displaying the sheet; /// on iOS <26, `profileSwitcherState.isVisible` is used instead. diff --git a/BitwardenShared/UI/Auth/ProfileSwitcher/TestHelpers/MockProfileSwitcherHandlerProcessor.swift b/BitwardenShared/UI/Auth/ProfileSwitcher/TestHelpers/MockProfileSwitcherHandlerProcessor.swift index a09054912e..1a819e7ebc 100644 --- a/BitwardenShared/UI/Auth/ProfileSwitcher/TestHelpers/MockProfileSwitcherHandlerProcessor.swift +++ b/BitwardenShared/UI/Auth/ProfileSwitcher/TestHelpers/MockProfileSwitcherHandlerProcessor.swift @@ -5,7 +5,7 @@ import BitwardenKitMocks class MockProfileSwitcherHandlerProcessor: MockProcessor, ProfileSwitcherHandler { - var alertsShown = [BitwardenShared.Alert]() + var alertsShown = [BitwardenKit.Alert]() var allowLockAndLogout = true var dismissProfileSwitcherCalled = false var handleAuthEvents = [AuthEvent]() @@ -31,7 +31,7 @@ class MockProfileSwitcherHandlerProcessor: func showAddAccount() {} - func showAlert(_ alert: BitwardenShared.Alert) { + func showAlert(_ alert: BitwardenKit.Alert) { alertsShown.append(alert) } diff --git a/BitwardenShared/UI/Auth/Utilities/TestHelpers/MockUserVerificationHelperDelegate.swift b/BitwardenShared/UI/Auth/Utilities/TestHelpers/MockUserVerificationHelperDelegate.swift index 0f796f77b8..14326e861c 100644 --- a/BitwardenShared/UI/Auth/Utilities/TestHelpers/MockUserVerificationHelperDelegate.swift +++ b/BitwardenShared/UI/Auth/Utilities/TestHelpers/MockUserVerificationHelperDelegate.swift @@ -1,14 +1,15 @@ +import BitwardenKit import Foundation import XCTest @testable import BitwardenShared class MockUserVerificationHelperDelegate: UserVerificationDelegate { - var alertShown = [Alert]() - var alertShownHandler: ((Alert) async throws -> Void)? + var alertShown = [BitwardenKit.Alert]() + var alertShownHandler: ((BitwardenKit.Alert) async throws -> Void)? var alertOnDismissed: (() -> Void)? - func showAlert(_ alert: Alert) { + func showAlert(_ alert: BitwardenKit.Alert) { alertShown.append(alert) Task { do { @@ -19,7 +20,7 @@ class MockUserVerificationHelperDelegate: UserVerificationDelegate { } } - func showAlert(_ alert: BitwardenShared.Alert, onDismissed: (() -> Void)?) { + func showAlert(_ alert: BitwardenKit.Alert, onDismissed: (() -> Void)?) { showAlert(alert) alertOnDismissed = onDismissed } diff --git a/BitwardenShared/UI/Auth/Utilities/TestHelpers/MockVaultUnlockSetupHelper.swift b/BitwardenShared/UI/Auth/Utilities/TestHelpers/MockVaultUnlockSetupHelper.swift index 16b5414ddd..045baaccc9 100644 --- a/BitwardenShared/UI/Auth/Utilities/TestHelpers/MockVaultUnlockSetupHelper.swift +++ b/BitwardenShared/UI/Auth/Utilities/TestHelpers/MockVaultUnlockSetupHelper.swift @@ -1,3 +1,4 @@ +import BitwardenKit @testable import BitwardenShared class MockVaultUnlockSetupHelper: VaultUnlockSetupHelper { diff --git a/BitwardenShared/UI/Auth/Utilities/UserVerificationHelper.swift b/BitwardenShared/UI/Auth/Utilities/UserVerificationHelper.swift index e7d1250fb4..5b145993ba 100644 --- a/BitwardenShared/UI/Auth/Utilities/UserVerificationHelper.swift +++ b/BitwardenShared/UI/Auth/Utilities/UserVerificationHelper.swift @@ -226,7 +226,7 @@ protocol UserVerificationDelegate: AnyObject { /// /// - Parameter alert: The alert to show. /// - func showAlert(_ alert: Alert) + func showAlert(_ alert: BitwardenKit.Alert) /// Shows an alert to the user /// @@ -234,5 +234,5 @@ protocol UserVerificationDelegate: AnyObject { /// - alert: The alert to show. /// - onDismissed: An optional closure that is called when the alert is dismissed. /// - func showAlert(_ alert: Alert, onDismissed: (() -> Void)?) + func showAlert(_ alert: BitwardenKit.Alert, onDismissed: (() -> Void)?) } diff --git a/BitwardenShared/UI/Auth/Utilities/VaultUnlockSetupHelper.swift b/BitwardenShared/UI/Auth/Utilities/VaultUnlockSetupHelper.swift index 96a6e4e62a..cbc1c0f714 100644 --- a/BitwardenShared/UI/Auth/Utilities/VaultUnlockSetupHelper.swift +++ b/BitwardenShared/UI/Auth/Utilities/VaultUnlockSetupHelper.swift @@ -1,3 +1,4 @@ +import BitwardenKit import BitwardenResources // MARK: - VaultUnlockSetupHelper diff --git a/BitwardenShared/UI/Auth/Utilities/VaultUnlockSetupHelperTests.swift b/BitwardenShared/UI/Auth/Utilities/VaultUnlockSetupHelperTests.swift index e5a31712dd..e20e8b8d9c 100644 --- a/BitwardenShared/UI/Auth/Utilities/VaultUnlockSetupHelperTests.swift +++ b/BitwardenShared/UI/Auth/Utilities/VaultUnlockSetupHelperTests.swift @@ -1,3 +1,4 @@ +import BitwardenKit import BitwardenKitMocks import BitwardenResources import TestHelpers diff --git a/BitwardenShared/UI/Auth/VaultUnlock/VaultUnlockProcessor.swift b/BitwardenShared/UI/Auth/VaultUnlock/VaultUnlockProcessor.swift index acb6b87c74..b0e8253d0d 100644 --- a/BitwardenShared/UI/Auth/VaultUnlock/VaultUnlockProcessor.swift +++ b/BitwardenShared/UI/Auth/VaultUnlock/VaultUnlockProcessor.swift @@ -340,7 +340,7 @@ extension VaultUnlockProcessor: ProfileSwitcherHandler { coordinator.navigate(to: .landing) } - func showAlert(_ alert: Alert) { + func showAlert(_ alert: BitwardenKit.Alert) { coordinator.showAlert(alert) } diff --git a/BitwardenShared/UI/Platform/Application/AppCoordinator.swift b/BitwardenShared/UI/Platform/Application/AppCoordinator.swift index 6a46b1775d..0821f59560 100644 --- a/BitwardenShared/UI/Platform/Application/AppCoordinator.swift +++ b/BitwardenShared/UI/Platform/Application/AppCoordinator.swift @@ -1,3 +1,4 @@ +import BitwardenKit import BitwardenResources import BitwardenSdk import SwiftUI diff --git a/BitwardenShared/UI/Platform/Application/AppProcessor+Fido2Tests.swift b/BitwardenShared/UI/Platform/Application/AppProcessor+Fido2Tests.swift index 32a1c84dc7..2e6ab64d2c 100644 --- a/BitwardenShared/UI/Platform/Application/AppProcessor+Fido2Tests.swift +++ b/BitwardenShared/UI/Platform/Application/AppProcessor+Fido2Tests.swift @@ -1,6 +1,7 @@ // swiftlint:disable:this file_name import AuthenticationServices +import BitwardenKit import BitwardenKitMocks import BitwardenSdk import Foundation diff --git a/BitwardenShared/UI/Platform/Application/AppProcessor.swift b/BitwardenShared/UI/Platform/Application/AppProcessor.swift index a8f27dc7e3..4c8fe4bb76 100644 --- a/BitwardenShared/UI/Platform/Application/AppProcessor.swift +++ b/BitwardenShared/UI/Platform/Application/AppProcessor.swift @@ -735,7 +735,7 @@ extension AppProcessor: Fido2UserInterfaceHelperDelegate { } } - func showAlert(_ alert: Alert) { + func showAlert(_ alert: BitwardenKit.Alert) { coordinator?.showAlert(alert) } diff --git a/BitwardenShared/UI/Platform/Application/Extensions/Alert+ExtensionsTests.swift b/BitwardenShared/UI/Platform/Application/Extensions/Alert+ExtensionsTests.swift index 1e7f971b7b..6e5c61461b 100644 --- a/BitwardenShared/UI/Platform/Application/Extensions/Alert+ExtensionsTests.swift +++ b/BitwardenShared/UI/Platform/Application/Extensions/Alert+ExtensionsTests.swift @@ -1,5 +1,6 @@ // swiftlint:disable:this file_name +import BitwardenKit import BitwardenResources import XCTest diff --git a/BitwardenShared/UI/Platform/Application/TestHelpers/MockUIViewController.swift b/BitwardenShared/UI/Platform/Application/TestHelpers/MockUIViewController.swift deleted file mode 100644 index 16cb3b891e..0000000000 --- a/BitwardenShared/UI/Platform/Application/TestHelpers/MockUIViewController.swift +++ /dev/null @@ -1,19 +0,0 @@ -import XCTest - -@testable import BitwardenShared - -// MARK: - MockUIViewController - -class MockUIViewController: UIViewController { - var presentCalled = false - var presentedView: UIViewController? - - override func present( - _ viewControllerToPresent: UIViewController, - animated _: Bool, - completion _: (() -> Void)? = nil, - ) { - presentCalled = true - presentedView = viewControllerToPresent - } -} diff --git a/BitwardenShared/UI/Platform/Application/Utilities/Coordinator.swift b/BitwardenShared/UI/Platform/Application/Utilities/Coordinator.swift index eedd683e5f..5149f6c5e3 100644 --- a/BitwardenShared/UI/Platform/Application/Utilities/Coordinator.swift +++ b/BitwardenShared/UI/Platform/Application/Utilities/Coordinator.swift @@ -36,7 +36,7 @@ protocol Coordinator: AnyObject { /// /// - Parameter alert: The alert to show. /// - func showAlert(_ alert: Alert) + func showAlert(_ alert: BitwardenKit.Alert) /// Shows the alert. /// @@ -44,7 +44,7 @@ protocol Coordinator: AnyObject { /// - alert: The alert to show. /// - onDismissed: An optional closure that is called when the alert is dismissed. /// - func showAlert(_ alert: Alert, onDismissed: (() -> Void)?) + func showAlert(_ alert: BitwardenKit.Alert, onDismissed: (() -> Void)?) /// Shows an alert for an error that occurred. /// @@ -167,7 +167,7 @@ extension Coordinator { /// /// - Parameter alert: The alert to show. /// - func showAlert(_ alert: Alert) { + func showAlert(_ alert: BitwardenKit.Alert) { showAlert(alert, onDismissed: nil) } diff --git a/BitwardenShared/UI/Platform/Application/Utilities/CoordinatorTests.swift b/BitwardenShared/UI/Platform/Application/Utilities/CoordinatorTests.swift index 5eb404f2ee..ca92490f93 100644 --- a/BitwardenShared/UI/Platform/Application/Utilities/CoordinatorTests.swift +++ b/BitwardenShared/UI/Platform/Application/Utilities/CoordinatorTests.swift @@ -1,3 +1,4 @@ +import BitwardenKit import BitwardenKitMocks import BitwardenResources import TestHelpers diff --git a/BitwardenShared/UI/Platform/Extensions/Alert+Account.swift b/BitwardenShared/UI/Platform/Extensions/Alert+Account.swift index 02287de62f..4386308a27 100644 --- a/BitwardenShared/UI/Platform/Extensions/Alert+Account.swift +++ b/BitwardenShared/UI/Platform/Extensions/Alert+Account.swift @@ -1,3 +1,4 @@ +import BitwardenKit import BitwardenResources // MARK: - Alert+Account diff --git a/BitwardenShared/UI/Platform/Extensions/Alert+Platform.swift b/BitwardenShared/UI/Platform/Extensions/Alert+Platform.swift index 326e512269..82fb28008e 100644 --- a/BitwardenShared/UI/Platform/Extensions/Alert+Platform.swift +++ b/BitwardenShared/UI/Platform/Extensions/Alert+Platform.swift @@ -1,7 +1,8 @@ -// MARK: - Alert+Account - +import BitwardenKit import BitwardenResources +// MARK: - Alert+Account + extension Alert { // MARK: Methods diff --git a/BitwardenShared/UI/Platform/Extensions/AlertAccountTests.swift b/BitwardenShared/UI/Platform/Extensions/AlertAccountTests.swift index dcc17edd81..e55d2f3396 100644 --- a/BitwardenShared/UI/Platform/Extensions/AlertAccountTests.swift +++ b/BitwardenShared/UI/Platform/Extensions/AlertAccountTests.swift @@ -1,3 +1,4 @@ +import BitwardenKit import BitwardenResources import XCTest diff --git a/BitwardenShared/UI/Platform/Extensions/AlertPlatformTests.swift b/BitwardenShared/UI/Platform/Extensions/AlertPlatformTests.swift index 34a690004f..5c2b6c8a4e 100644 --- a/BitwardenShared/UI/Platform/Extensions/AlertPlatformTests.swift +++ b/BitwardenShared/UI/Platform/Extensions/AlertPlatformTests.swift @@ -1,3 +1,4 @@ +import BitwardenKit import BitwardenResources import XCTest diff --git a/BitwardenShared/UI/Platform/PreviewContent/Alert+Fixtures.swift b/BitwardenShared/UI/Platform/PreviewContent/Alert+Fixtures.swift index 31fc1535c3..fdeed86b84 100644 --- a/BitwardenShared/UI/Platform/PreviewContent/Alert+Fixtures.swift +++ b/BitwardenShared/UI/Platform/PreviewContent/Alert+Fixtures.swift @@ -1,3 +1,4 @@ +import BitwardenKit import UIKit #if DEBUG diff --git a/BitwardenShared/UI/Platform/Settings/Extensions/Alert+Settings.swift b/BitwardenShared/UI/Platform/Settings/Extensions/Alert+Settings.swift index c35277a1db..f67fbaa4ce 100644 --- a/BitwardenShared/UI/Platform/Settings/Extensions/Alert+Settings.swift +++ b/BitwardenShared/UI/Platform/Settings/Extensions/Alert+Settings.swift @@ -1,3 +1,4 @@ +import BitwardenKit import BitwardenResources // MARK: - Alert + Settings diff --git a/BitwardenShared/UI/Platform/Settings/Extensions/AlertSettingsTests.swift b/BitwardenShared/UI/Platform/Settings/Extensions/AlertSettingsTests.swift index a9abe6b76a..f9cfb32c2e 100644 --- a/BitwardenShared/UI/Platform/Settings/Extensions/AlertSettingsTests.swift +++ b/BitwardenShared/UI/Platform/Settings/Extensions/AlertSettingsTests.swift @@ -1,3 +1,4 @@ +import BitwardenKit import BitwardenResources import XCTest diff --git a/BitwardenShared/UI/Platform/Settings/Settings/AutoFill/AutoFillProcessorTests.swift b/BitwardenShared/UI/Platform/Settings/Settings/AutoFill/AutoFillProcessorTests.swift index 589b949b17..93aa9a8165 100644 --- a/BitwardenShared/UI/Platform/Settings/Settings/AutoFill/AutoFillProcessorTests.swift +++ b/BitwardenShared/UI/Platform/Settings/Settings/AutoFill/AutoFillProcessorTests.swift @@ -1,3 +1,4 @@ +import BitwardenKit import BitwardenKitMocks import BitwardenResources import XCTest diff --git a/BitwardenShared/UI/Platform/Settings/Settings/Vault/Folders/AddEditFolder/AddEditFolder/AddEditFolderProcessorTests.swift b/BitwardenShared/UI/Platform/Settings/Settings/Vault/Folders/AddEditFolder/AddEditFolder/AddEditFolderProcessorTests.swift index 5905e83a00..3ec0cdd9c9 100644 --- a/BitwardenShared/UI/Platform/Settings/Settings/Vault/Folders/AddEditFolder/AddEditFolder/AddEditFolderProcessorTests.swift +++ b/BitwardenShared/UI/Platform/Settings/Settings/Vault/Folders/AddEditFolder/AddEditFolder/AddEditFolderProcessorTests.swift @@ -1,3 +1,4 @@ +import BitwardenKit import BitwardenKitMocks import BitwardenResources import BitwardenSdk diff --git a/BitwardenShared/UI/Tools/Send/SendItem/AddEditSendItem/AddEditSendItemProcessor.swift b/BitwardenShared/UI/Tools/Send/SendItem/AddEditSendItem/AddEditSendItemProcessor.swift index e3b4bacf42..9a1b16c11e 100644 --- a/BitwardenShared/UI/Tools/Send/SendItem/AddEditSendItem/AddEditSendItemProcessor.swift +++ b/BitwardenShared/UI/Tools/Send/SendItem/AddEditSendItem/AddEditSendItemProcessor.swift @@ -401,7 +401,7 @@ extension AddEditSendItemProcessor: ProfileSwitcherHandler { // No-Op for the AddEditSendItemProcessor. } - func showAlert(_ alert: Alert) { + func showAlert(_ alert: BitwardenKit.Alert) { coordinator.showAlert(alert) } diff --git a/BitwardenShared/UI/Vault/Extensions/Alert+Vault.swift b/BitwardenShared/UI/Vault/Extensions/Alert+Vault.swift index a4d9eb1f7c..bf948cd59a 100644 --- a/BitwardenShared/UI/Vault/Extensions/Alert+Vault.swift +++ b/BitwardenShared/UI/Vault/Extensions/Alert+Vault.swift @@ -1,3 +1,4 @@ +import BitwardenKit import BitwardenResources import BitwardenSdk import UIKit diff --git a/BitwardenShared/UI/Vault/Extensions/AlertVaultTests.swift b/BitwardenShared/UI/Vault/Extensions/AlertVaultTests.swift index 16a890e47f..958ca03b90 100644 --- a/BitwardenShared/UI/Vault/Extensions/AlertVaultTests.swift +++ b/BitwardenShared/UI/Vault/Extensions/AlertVaultTests.swift @@ -1,3 +1,4 @@ +import BitwardenKit import BitwardenResources import BitwardenSdk import XCTest diff --git a/BitwardenShared/UI/Vault/Helpers/AutofillHelper.swift b/BitwardenShared/UI/Vault/Helpers/AutofillHelper.swift index 7920746634..d0b9ed9178 100644 --- a/BitwardenShared/UI/Vault/Helpers/AutofillHelper.swift +++ b/BitwardenShared/UI/Vault/Helpers/AutofillHelper.swift @@ -1,3 +1,4 @@ +import BitwardenKit import BitwardenResources @preconcurrency import BitwardenSdk diff --git a/BitwardenShared/UI/Vault/Helpers/AutofillHelperTests.swift b/BitwardenShared/UI/Vault/Helpers/AutofillHelperTests.swift index d0e4b73175..65d6bc1cd8 100644 --- a/BitwardenShared/UI/Vault/Helpers/AutofillHelperTests.swift +++ b/BitwardenShared/UI/Vault/Helpers/AutofillHelperTests.swift @@ -1,3 +1,4 @@ +import BitwardenKit import BitwardenKitMocks import BitwardenResources import BitwardenSdk diff --git a/BitwardenShared/UI/Vault/Helpers/TestHelpers/MockTextAutofillHelperDelegate.swift b/BitwardenShared/UI/Vault/Helpers/TestHelpers/MockTextAutofillHelperDelegate.swift index 6d10faa6f5..de6e3de7ec 100644 --- a/BitwardenShared/UI/Vault/Helpers/TestHelpers/MockTextAutofillHelperDelegate.swift +++ b/BitwardenShared/UI/Vault/Helpers/TestHelpers/MockTextAutofillHelperDelegate.swift @@ -1,7 +1,8 @@ +import BitwardenKit @testable import BitwardenShared class MockTextAutofillHelperDelegate: TextAutofillHelperDelegate { - var alertsShown = [Alert]() + var alertsShown = [BitwardenKit.Alert]() var alertOnDismissed: (() -> Void)? var completeTextRequestText: String? @@ -9,7 +10,7 @@ class MockTextAutofillHelperDelegate: TextAutofillHelperDelegate { completeTextRequestText = text } - func showAlert(_ alert: BitwardenShared.Alert, onDismissed: (() -> Void)?) { + func showAlert(_ alert: BitwardenKit.Alert, onDismissed: (() -> Void)?) { alertsShown.append(alert) alertOnDismissed = onDismissed } diff --git a/BitwardenShared/UI/Vault/Helpers/TextAutofillHelper.swift b/BitwardenShared/UI/Vault/Helpers/TextAutofillHelper.swift index 8f0d94dc1f..99c7dabafa 100644 --- a/BitwardenShared/UI/Vault/Helpers/TextAutofillHelper.swift +++ b/BitwardenShared/UI/Vault/Helpers/TextAutofillHelper.swift @@ -31,7 +31,7 @@ protocol TextAutofillHelperDelegate: AnyObject { /// - alert: The alert to show. /// - onDismissed: An optional closure that is called when the alert is dismissed. /// - func showAlert(_ alert: Alert, onDismissed: (() -> Void)?) + func showAlert(_ alert: BitwardenKit.Alert, onDismissed: (() -> Void)?) } @MainActor diff --git a/BitwardenShared/UI/Vault/Vault/AutofillList/VaultAutofillListProcessor+Fido2Tests.swift b/BitwardenShared/UI/Vault/Vault/AutofillList/VaultAutofillListProcessor+Fido2Tests.swift index 223d157987..7fce08f272 100644 --- a/BitwardenShared/UI/Vault/Vault/AutofillList/VaultAutofillListProcessor+Fido2Tests.swift +++ b/BitwardenShared/UI/Vault/Vault/AutofillList/VaultAutofillListProcessor+Fido2Tests.swift @@ -1,6 +1,7 @@ // swiftlint:disable:this file_name import AuthenticationServices +import BitwardenKit import BitwardenKitMocks import BitwardenResources import BitwardenSdk diff --git a/BitwardenShared/UI/Platform/Application/Utilities/Alert/Alert/AlertTests.swift b/BitwardenShared/UI/Vault/Vault/VaultList/VaultListProcessor+MoreOptionsTests.swift similarity index 57% rename from BitwardenShared/UI/Platform/Application/Utilities/Alert/Alert/AlertTests.swift rename to BitwardenShared/UI/Vault/Vault/VaultList/VaultListProcessor+MoreOptionsTests.swift index bda88e936c..d0f29b9a63 100644 --- a/BitwardenShared/UI/Platform/Application/Utilities/Alert/Alert/AlertTests.swift +++ b/BitwardenShared/UI/Vault/Vault/VaultList/VaultListProcessor+MoreOptionsTests.swift @@ -1,95 +1,15 @@ +// swiftlint:disable:this file_name + +import BitwardenKit import BitwardenResources import BitwardenSdk import XCTest @testable import BitwardenShared -// MARK: - AlertTests - -class AlertTests: BitwardenTestCase { - // MARK: Properties - - var subject: Alert! - - // MARK: Setup and Teardown - - override func setUp() { - super.setUp() - - subject = Alert.fixture(alertActions: [AlertAction.cancel()], - alertTextFields: [AlertTextField.fixture(autocorrectionType: .no)]) - .addPreferred(AlertAction.ok()) - } - - override func tearDown() { - super.tearDown() - subject = nil - } - - // MARK: Tests - - /// `createAlertController` returns a `UIAlertController` based on the alert details. - @MainActor - func test_createAlertController() throws { - let alertController = subject.createAlertController() - - XCTAssertEqual(alertController.title, "🍎") - XCTAssertEqual(alertController.message, "🥝") - XCTAssertEqual(alertController.preferredStyle, .alert) - XCTAssertEqual(alertController.actions.count, 2) - XCTAssertEqual(alertController.actions[0].title, "Cancel") - XCTAssertEqual(alertController.actions[0].style, .cancel) - XCTAssertEqual(alertController.actions[1].title, "OK") - XCTAssertEqual(alertController.actions[1].style, .default) - XCTAssertEqual(alertController.textFields?.count, 1) - - let textField = try XCTUnwrap(alertController.textFields?.first) - XCTAssertEqual(textField.text, "value") - XCTAssertEqual(textField.placeholder, "placeholder") - XCTAssertEqual(textField.autocapitalizationType, .allCharacters) - XCTAssertEqual(textField.autocorrectionType, .no) - XCTAssertEqual(textField.isSecureTextEntry, true) - XCTAssertEqual(textField.keyboardType, .numberPad) - XCTAssertEqual(alertController.preferredAction?.title, "OK") - } - - /// `createAlertController` sets an `onDismissed` closure that's called when the alert is dismissed. - @MainActor - func test_createAlertController_onDismissed() { - var dismissedCalled = false - let alertController = subject.createAlertController { dismissedCalled = true } - let rootViewController = UIViewController() - setKeyWindowRoot(viewController: rootViewController) - - rootViewController.present(alertController, animated: false) - XCTAssertFalse(dismissedCalled) - rootViewController.dismiss(animated: false) - waitFor(rootViewController.presentedViewController == nil) - XCTAssertTrue(dismissedCalled) - } - - /// `debugDescription` contains the alert's properties - func test_debugDescription() { - XCTAssertEqual( - subject!.debugDescription, - "Alert(title: 🍎, message: 🥝, alertActions: [BitwardenShared.AlertAction, BitwardenShared.AlertAction]," - + " alertTextFields: [BitwardenShared.AlertTextField])", - ) - } - - /// Alert conforms to `Equatable`. - func test_equatable() { - XCTAssertEqual(subject, Alert.fixture(alertActions: [AlertAction.cancel()]) - .addPreferred(AlertAction.ok())) - XCTAssertNotEqual(subject, Alert.fixture(alertActions: [AlertAction.cancel(style: .destructive)]) - .addPreferred(AlertAction.ok())) - XCTAssertNotEqual(subject, Alert(title: "🍎", message: "🥝", preferredStyle: .alert)) - XCTAssertNotEqual(subject, Alert.fixture(alertActions: [AlertAction.cancel()]) - .addPreferred(AlertAction.ok { _, _ in })) - XCTAssertEqual(subject, Alert.fixture(alertActions: [AlertAction.cancel()]) - .addPreferred(AlertAction.ok())) - } +// MARK: - VaultListProcessor MoreOptions Tests +class VaultListProcessorMoreOptionsTests: BitwardenTestCase { @MainActor func test_vault_moreOptions_login_canViewPassword() async throws { // swiftlint:disable:this function_body_length var capturedAction: MoreOptionsAction? diff --git a/BitwardenShared/UI/Vault/Vault/VaultList/VaultListProcessor.swift b/BitwardenShared/UI/Vault/Vault/VaultList/VaultListProcessor.swift index 51f28d861d..4e5da972d6 100644 --- a/BitwardenShared/UI/Vault/Vault/VaultList/VaultListProcessor.swift +++ b/BitwardenShared/UI/Vault/Vault/VaultList/VaultListProcessor.swift @@ -640,7 +640,7 @@ extension VaultListProcessor: ProfileSwitcherHandler { coordinator.navigate(to: .addAccount) } - func showAlert(_ alert: Alert) { + func showAlert(_ alert: BitwardenKit.Alert) { coordinator.showAlert(alert) } diff --git a/BitwardenShared/UI/Vault/VaultItem/AddEditItem/Extensions/Alert+ScanError.swift b/BitwardenShared/UI/Vault/VaultItem/AddEditItem/Extensions/Alert+ScanError.swift index 07493173cc..0b26b5803e 100644 --- a/BitwardenShared/UI/Vault/VaultItem/AddEditItem/Extensions/Alert+ScanError.swift +++ b/BitwardenShared/UI/Vault/VaultItem/AddEditItem/Extensions/Alert+ScanError.swift @@ -1,3 +1,4 @@ +import BitwardenKit import BitwardenResources extension Alert { diff --git a/GlobalTestHelpers-bwa/MockCoordinator.swift b/GlobalTestHelpers-bwa/MockCoordinator.swift index 811c623958..48b6ed0173 100644 --- a/GlobalTestHelpers-bwa/MockCoordinator.swift +++ b/GlobalTestHelpers-bwa/MockCoordinator.swift @@ -8,7 +8,7 @@ enum MockCoordinatorError: Error { } class MockCoordinator: Coordinator { - var alertShown = [Alert]() + var alertShown = [BitwardenKit.Alert]() var contexts: [AnyObject?] = [] var events = [Event]() var isLoadingOverlayShowing = false @@ -31,7 +31,7 @@ class MockCoordinator: Coordinator { contexts.append(context) } - func showAlert(_ alert: Alert) { + func showAlert(_ alert: BitwardenKit.Alert) { alertShown.append(alert) } diff --git a/GlobalTestHelpers-bwa/MockStackNavigator.swift b/GlobalTestHelpers-bwa/MockStackNavigator.swift index a04feb7284..3ff9a19948 100644 --- a/GlobalTestHelpers-bwa/MockStackNavigator.swift +++ b/GlobalTestHelpers-bwa/MockStackNavigator.swift @@ -1,4 +1,5 @@ import AuthenticatorShared +import BitwardenKit import SwiftUI final class MockStackNavigator: StackNavigator { @@ -22,7 +23,7 @@ final class MockStackNavigator: StackNavigator { } var actions: [NavigationAction] = [] - var alerts: [AuthenticatorShared.Alert] = [] + var alerts: [BitwardenKit.Alert] = [] var isEmpty = true var isPresenting: Bool { actions.last?.type == .presented } var rootViewController: UIViewController? @@ -67,7 +68,7 @@ final class MockStackNavigator: StackNavigator { return viewControllersToPop } - func present(_ alert: AuthenticatorShared.Alert) { + func present(_ alert: BitwardenKit.Alert) { alerts.append(alert) } diff --git a/GlobalTestHelpers/MockCoordinator.swift b/GlobalTestHelpers/MockCoordinator.swift index d34dfb489d..20e1849bab 100644 --- a/GlobalTestHelpers/MockCoordinator.swift +++ b/GlobalTestHelpers/MockCoordinator.swift @@ -8,7 +8,7 @@ enum MockCoordinatorError: Error { } class MockCoordinator: Coordinator { - var alertShown = [Alert]() + var alertShown = [BitwardenKit.Alert]() var alertOnDismissed: (() -> Void)? var contexts: [AnyObject?] = [] var errorAlertsShown = [Error]() @@ -34,7 +34,7 @@ class MockCoordinator: Coordinator { contexts.append(context) } - func showAlert(_ alert: BitwardenShared.Alert, onDismissed: (() -> Void)?) { + func showAlert(_ alert: BitwardenKit.Alert, onDismissed: (() -> Void)?) { alertShown.append(alert) alertOnDismissed = onDismissed } diff --git a/GlobalTestHelpers/MockRootNavigator.swift b/GlobalTestHelpers/MockRootNavigator.swift index 046d4ca4f1..4900cd67d9 100644 --- a/GlobalTestHelpers/MockRootNavigator.swift +++ b/GlobalTestHelpers/MockRootNavigator.swift @@ -1,3 +1,4 @@ +import BitwardenKit import BitwardenShared import UIKit diff --git a/GlobalTestHelpers/MockStackNavigator.swift b/GlobalTestHelpers/MockStackNavigator.swift index c26eee25aa..79436ee5f3 100644 --- a/GlobalTestHelpers/MockStackNavigator.swift +++ b/GlobalTestHelpers/MockStackNavigator.swift @@ -1,3 +1,4 @@ +import BitwardenKit import BitwardenShared import SwiftUI @@ -25,7 +26,7 @@ final class MockStackNavigator: StackNavigator { var actions: [NavigationAction] = [] var alertOnDismissed: (() -> Void)? - var alerts: [BitwardenShared.Alert] = [] + var alerts: [BitwardenKit.Alert] = [] var isEmpty = true var isNavigationBarHidden = false var isPresenting = false @@ -71,11 +72,11 @@ final class MockStackNavigator: StackNavigator { return viewControllersToPop } - func present(_ alert: BitwardenShared.Alert) { + func present(_ alert: BitwardenKit.Alert) { alerts.append(alert) } - func present(_ alert: BitwardenShared.Alert, onDismissed: (() -> Void)?) { + func present(_ alert: BitwardenKit.Alert, onDismissed: (() -> Void)?) { alerts.append(alert) alertOnDismissed = onDismissed } diff --git a/GlobalTestHelpers/Support/BitwardenTestCase.swift b/GlobalTestHelpers/Support/BitwardenTestCase.swift index d420ad0821..0caa45100f 100644 --- a/GlobalTestHelpers/Support/BitwardenTestCase.swift +++ b/GlobalTestHelpers/Support/BitwardenTestCase.swift @@ -27,21 +27,4 @@ open class BitwardenTestCase: BaseBitwardenTestCase { super.tearDown() UI.animated = false } - - /// Nests a `UIView` within a root view controller in the test window. Allows testing - /// changes to the view that require the view to exist within a window or are dependent on safe - /// area layouts. - /// - /// This is currently in the `BitwardenShared` copy of `BitwardenTestCase` - /// because it relies on `UIView.addConstrained(:)`, which is still in `BitwardenShared`. - /// - /// - Parameters: - /// - view: The `UIView` to add to a root view controller. - /// - open func setKeyWindowRoot(view: UIView) { - let viewController = UIViewController() - viewController.view.addConstrained(subview: view) - window.rootViewController = viewController - window.makeKeyAndVisible() - } }