mirror of
https://github.com/bitwarden/ios.git
synced 2025-12-10 17:46:07 -06:00
[PM-26060] Consolidate Alert to BitwardenKit (#2081)
This commit is contained in:
parent
87e3a223a8
commit
f90ffd8b16
@ -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)],
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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)])
|
||||
}
|
||||
}
|
||||
@ -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 {}
|
||||
@ -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?
|
||||
}
|
||||
@ -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)))
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -60,7 +60,7 @@ open class AnyCoordinator<Route, Event>: Coordinator {
|
||||
doNavigate(route, context)
|
||||
}
|
||||
|
||||
open func showAlert(_ alert: Alert) {
|
||||
open func showAlert(_ alert: BitwardenKit.Alert) {
|
||||
doShowAlert(alert)
|
||||
}
|
||||
|
||||
|
||||
@ -34,7 +34,7 @@ public protocol Coordinator<Route, Event>: 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)
|
||||
}
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import BitwardenKit
|
||||
import BitwardenResources
|
||||
|
||||
// MARK: - Alert + Settings
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import BitwardenKit
|
||||
import BitwardenResources
|
||||
import XCTest
|
||||
|
||||
|
||||
@ -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.
|
||||
///
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import BitwardenKit
|
||||
import BitwardenResources
|
||||
import UIKit
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import BitwardenKit
|
||||
import BitwardenResources
|
||||
import UIKit
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import BitwardenKit
|
||||
import BitwardenResources
|
||||
|
||||
extension Alert {
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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.
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)])
|
||||
}
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import BitwardenKit
|
||||
import OSLog
|
||||
import UIKit
|
||||
|
||||
@ -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)
|
||||
@ -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()))
|
||||
}
|
||||
}
|
||||
@ -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?()
|
||||
}
|
||||
@ -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)
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,3 @@
|
||||
import BitwardenKit
|
||||
import Combine
|
||||
import Foundation
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ import SnapshotTesting
|
||||
import TestHelpers
|
||||
import XCTest
|
||||
|
||||
@testable import BitwardenShared
|
||||
@testable import BitwardenKit
|
||||
|
||||
class LoadingOverlayViewTests: BitwardenTestCase {
|
||||
// MARK: Tests
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
import SnapshotTesting
|
||||
import XCTest
|
||||
|
||||
@testable import BitwardenShared
|
||||
@testable import BitwardenKit
|
||||
|
||||
class SectionViewTests: BitwardenTestCase {
|
||||
// MARK: Tests
|
||||
|
||||
73
BitwardenKit/UI/Platform/PreviewContent/Alert+Fixtures.swift
Normal file
73
BitwardenKit/UI/Platform/PreviewContent/Alert+Fixtures.swift
Normal file
@ -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
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -5,7 +5,7 @@ import BitwardenKitMocks
|
||||
class MockProfileSwitcherHandlerProcessor:
|
||||
MockProcessor<ProfileSwitcherState, ProfileSwitcherAction, ProfileSwitcherEffect>,
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import BitwardenKit
|
||||
@testable import BitwardenShared
|
||||
|
||||
class MockVaultUnlockSetupHelper: VaultUnlockSetupHelper {
|
||||
|
||||
@ -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)?)
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import BitwardenKit
|
||||
import BitwardenResources
|
||||
|
||||
// MARK: - VaultUnlockSetupHelper
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import BitwardenKit
|
||||
import BitwardenKitMocks
|
||||
import BitwardenResources
|
||||
import TestHelpers
|
||||
|
||||
@ -340,7 +340,7 @@ extension VaultUnlockProcessor: ProfileSwitcherHandler {
|
||||
coordinator.navigate(to: .landing)
|
||||
}
|
||||
|
||||
func showAlert(_ alert: Alert) {
|
||||
func showAlert(_ alert: BitwardenKit.Alert) {
|
||||
coordinator.showAlert(alert)
|
||||
}
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import BitwardenKit
|
||||
import BitwardenResources
|
||||
import BitwardenSdk
|
||||
import SwiftUI
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
// swiftlint:disable:this file_name
|
||||
|
||||
import AuthenticationServices
|
||||
import BitwardenKit
|
||||
import BitwardenKitMocks
|
||||
import BitwardenSdk
|
||||
import Foundation
|
||||
|
||||
@ -735,7 +735,7 @@ extension AppProcessor: Fido2UserInterfaceHelperDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func showAlert(_ alert: Alert) {
|
||||
func showAlert(_ alert: BitwardenKit.Alert) {
|
||||
coordinator?.showAlert(alert)
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
// swiftlint:disable:this file_name
|
||||
|
||||
import BitwardenKit
|
||||
import BitwardenResources
|
||||
import XCTest
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -36,7 +36,7 @@ protocol Coordinator<Route, Event>: 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<Route, Event>: 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)
|
||||
}
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import BitwardenKit
|
||||
import BitwardenKitMocks
|
||||
import BitwardenResources
|
||||
import TestHelpers
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import BitwardenKit
|
||||
import BitwardenResources
|
||||
|
||||
// MARK: - Alert+Account
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
// MARK: - Alert+Account
|
||||
|
||||
import BitwardenKit
|
||||
import BitwardenResources
|
||||
|
||||
// MARK: - Alert+Account
|
||||
|
||||
extension Alert {
|
||||
// MARK: Methods
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import BitwardenKit
|
||||
import BitwardenResources
|
||||
import XCTest
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import BitwardenKit
|
||||
import BitwardenResources
|
||||
import XCTest
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import BitwardenKit
|
||||
import UIKit
|
||||
|
||||
#if DEBUG
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import BitwardenKit
|
||||
import BitwardenResources
|
||||
|
||||
// MARK: - Alert + Settings
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import BitwardenKit
|
||||
import BitwardenResources
|
||||
import XCTest
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import BitwardenKit
|
||||
import BitwardenKitMocks
|
||||
import BitwardenResources
|
||||
import XCTest
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import BitwardenKit
|
||||
import BitwardenKitMocks
|
||||
import BitwardenResources
|
||||
import BitwardenSdk
|
||||
|
||||
@ -401,7 +401,7 @@ extension AddEditSendItemProcessor: ProfileSwitcherHandler {
|
||||
// No-Op for the AddEditSendItemProcessor.
|
||||
}
|
||||
|
||||
func showAlert(_ alert: Alert) {
|
||||
func showAlert(_ alert: BitwardenKit.Alert) {
|
||||
coordinator.showAlert(alert)
|
||||
}
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import BitwardenKit
|
||||
import BitwardenResources
|
||||
import BitwardenSdk
|
||||
import UIKit
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import BitwardenKit
|
||||
import BitwardenResources
|
||||
import BitwardenSdk
|
||||
import XCTest
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import BitwardenKit
|
||||
import BitwardenResources
|
||||
@preconcurrency import BitwardenSdk
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import BitwardenKit
|
||||
import BitwardenKitMocks
|
||||
import BitwardenResources
|
||||
import BitwardenSdk
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
// swiftlint:disable:this file_name
|
||||
|
||||
import AuthenticationServices
|
||||
import BitwardenKit
|
||||
import BitwardenKitMocks
|
||||
import BitwardenResources
|
||||
import BitwardenSdk
|
||||
|
||||
@ -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?
|
||||
@ -640,7 +640,7 @@ extension VaultListProcessor: ProfileSwitcherHandler {
|
||||
coordinator.navigate(to: .addAccount)
|
||||
}
|
||||
|
||||
func showAlert(_ alert: Alert) {
|
||||
func showAlert(_ alert: BitwardenKit.Alert) {
|
||||
coordinator.showAlert(alert)
|
||||
}
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import BitwardenKit
|
||||
import BitwardenResources
|
||||
|
||||
extension Alert {
|
||||
|
||||
@ -8,7 +8,7 @@ enum MockCoordinatorError: Error {
|
||||
}
|
||||
|
||||
class MockCoordinator<Route, Event>: Coordinator {
|
||||
var alertShown = [Alert]()
|
||||
var alertShown = [BitwardenKit.Alert]()
|
||||
var contexts: [AnyObject?] = []
|
||||
var events = [Event]()
|
||||
var isLoadingOverlayShowing = false
|
||||
@ -31,7 +31,7 @@ class MockCoordinator<Route, Event>: Coordinator {
|
||||
contexts.append(context)
|
||||
}
|
||||
|
||||
func showAlert(_ alert: Alert) {
|
||||
func showAlert(_ alert: BitwardenKit.Alert) {
|
||||
alertShown.append(alert)
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@ enum MockCoordinatorError: Error {
|
||||
}
|
||||
|
||||
class MockCoordinator<Route, Event>: Coordinator {
|
||||
var alertShown = [Alert]()
|
||||
var alertShown = [BitwardenKit.Alert]()
|
||||
var alertOnDismissed: (() -> Void)?
|
||||
var contexts: [AnyObject?] = []
|
||||
var errorAlertsShown = [Error]()
|
||||
@ -34,7 +34,7 @@ class MockCoordinator<Route, Event>: Coordinator {
|
||||
contexts.append(context)
|
||||
}
|
||||
|
||||
func showAlert(_ alert: BitwardenShared.Alert, onDismissed: (() -> Void)?) {
|
||||
func showAlert(_ alert: BitwardenKit.Alert, onDismissed: (() -> Void)?) {
|
||||
alertShown.append(alert)
|
||||
alertOnDismissed = onDismissed
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import BitwardenKit
|
||||
import BitwardenShared
|
||||
import UIKit
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user