From 68feef33cb9fba6e7e95fbf696e0a09aea533cc7 Mon Sep 17 00:00:00 2001 From: Matt Czech Date: Wed, 5 Nov 2025 11:17:23 -0600 Subject: [PATCH] [PM-26063] Consolidate ViewInspector test helpers (#2101) --- .../DebugMenuView+ViewInspectorTests.swift | 1 + .../ExportItemsView+ViewInspectorTests.swift | 1 + ...electLanguageView+ViewInspectorTests.swift | 1 + .../SettingsView+ViewInspectorTests.swift | 1 + .../ScanCodeView+ViewInspectorTests.swift | 1 + ...eRegistrationView+ViewInspectorTests.swift | 1 + .../ExpiredLinkView+ViewInspectorTests.swift | 1 + .../CheckEmailView+ViewInspectorTests.swift | 1 + .../DebugMenuView+ViewInspectorTests.swift | 1 + ...lightRecorderView+ViewInspectorTests.swift | 1 + .../GeneratorView+ViewInspectorTests.swift | 1 + ...dEditSendItemView+ViewInspectorTests.swift | 1 + .../VaultListView+ViewInspectorTests.swift | 1 + .../AddEditItemView+ViewInspectorTests.swift | 1 + Docs/Architecture.md | 3 +- .../Extensions/InspectableView.swift | 344 ------------------ .../InspectableView.swift | 126 ++++--- project-bwa.yml | 6 +- project-bwk.yml | 12 + project-pm.yml | 13 +- 20 files changed, 107 insertions(+), 411 deletions(-) delete mode 100644 GlobalTestHelpers-bwa/Extensions/InspectableView.swift rename {GlobalTestHelpers/Extensions => ViewInspectorTestHelpers}/InspectableView.swift (81%) diff --git a/AuthenticatorShared/UI/DebugMenu/DebugMenuView+ViewInspectorTests.swift b/AuthenticatorShared/UI/DebugMenu/DebugMenuView+ViewInspectorTests.swift index d8515faef..e6a658d4b 100644 --- a/AuthenticatorShared/UI/DebugMenu/DebugMenuView+ViewInspectorTests.swift +++ b/AuthenticatorShared/UI/DebugMenu/DebugMenuView+ViewInspectorTests.swift @@ -3,6 +3,7 @@ import BitwardenKit import BitwardenKitMocks import BitwardenResources import ViewInspector +import ViewInspectorTestHelpers import XCTest @testable import AuthenticatorShared diff --git a/AuthenticatorShared/UI/Platform/Settings/Settings/ExportItems/ExportItemsView+ViewInspectorTests.swift b/AuthenticatorShared/UI/Platform/Settings/Settings/ExportItems/ExportItemsView+ViewInspectorTests.swift index f0deab2a1..f861de130 100644 --- a/AuthenticatorShared/UI/Platform/Settings/Settings/ExportItems/ExportItemsView+ViewInspectorTests.swift +++ b/AuthenticatorShared/UI/Platform/Settings/Settings/ExportItems/ExportItemsView+ViewInspectorTests.swift @@ -2,6 +2,7 @@ import BitwardenKit import BitwardenKitMocks import BitwardenResources +import ViewInspectorTestHelpers import XCTest @testable import AuthenticatorShared diff --git a/AuthenticatorShared/UI/Platform/Settings/Settings/SelectLanguage/SelectLanguageView+ViewInspectorTests.swift b/AuthenticatorShared/UI/Platform/Settings/Settings/SelectLanguage/SelectLanguageView+ViewInspectorTests.swift index 251489b11..797aed682 100644 --- a/AuthenticatorShared/UI/Platform/Settings/Settings/SelectLanguage/SelectLanguageView+ViewInspectorTests.swift +++ b/AuthenticatorShared/UI/Platform/Settings/Settings/SelectLanguage/SelectLanguageView+ViewInspectorTests.swift @@ -2,6 +2,7 @@ import BitwardenKit import BitwardenKitMocks import BitwardenResources +import ViewInspectorTestHelpers import XCTest // MARK: - SelectLanguageViewTests diff --git a/AuthenticatorShared/UI/Platform/Settings/Settings/SettingsView+ViewInspectorTests.swift b/AuthenticatorShared/UI/Platform/Settings/Settings/SettingsView+ViewInspectorTests.swift index 208250b09..a66582c79 100644 --- a/AuthenticatorShared/UI/Platform/Settings/Settings/SettingsView+ViewInspectorTests.swift +++ b/AuthenticatorShared/UI/Platform/Settings/Settings/SettingsView+ViewInspectorTests.swift @@ -2,6 +2,7 @@ import BitwardenKit import BitwardenKitMocks import BitwardenResources +import ViewInspectorTestHelpers import XCTest // MARK: - SettingsViewTests diff --git a/AuthenticatorShared/UI/Vault/VaultItem/AuthenticatorKeyCapture/ScanCodeView+ViewInspectorTests.swift b/AuthenticatorShared/UI/Vault/VaultItem/AuthenticatorKeyCapture/ScanCodeView+ViewInspectorTests.swift index 9daf31bf0..6e7960474 100644 --- a/AuthenticatorShared/UI/Vault/VaultItem/AuthenticatorKeyCapture/ScanCodeView+ViewInspectorTests.swift +++ b/AuthenticatorShared/UI/Vault/VaultItem/AuthenticatorKeyCapture/ScanCodeView+ViewInspectorTests.swift @@ -4,6 +4,7 @@ import BitwardenKit import BitwardenKitMocks import BitwardenResources import ViewInspector +import ViewInspectorTestHelpers import XCTest @testable import AuthenticatorShared diff --git a/BitwardenShared/UI/Auth/CompleteRegistration/CompleteRegistrationView+ViewInspectorTests.swift b/BitwardenShared/UI/Auth/CompleteRegistration/CompleteRegistrationView+ViewInspectorTests.swift index e3d13b78d..62193fa47 100644 --- a/BitwardenShared/UI/Auth/CompleteRegistration/CompleteRegistrationView+ViewInspectorTests.swift +++ b/BitwardenShared/UI/Auth/CompleteRegistration/CompleteRegistrationView+ViewInspectorTests.swift @@ -4,6 +4,7 @@ import BitwardenKitMocks import BitwardenResources import SwiftUI import ViewInspector +import ViewInspectorTestHelpers import XCTest @testable import BitwardenShared diff --git a/BitwardenShared/UI/Auth/CompleteRegistration/ExpiredLink/ExpiredLinkView+ViewInspectorTests.swift b/BitwardenShared/UI/Auth/CompleteRegistration/ExpiredLink/ExpiredLinkView+ViewInspectorTests.swift index 8dd7a5569..2b67249e1 100644 --- a/BitwardenShared/UI/Auth/CompleteRegistration/ExpiredLink/ExpiredLinkView+ViewInspectorTests.swift +++ b/BitwardenShared/UI/Auth/CompleteRegistration/ExpiredLink/ExpiredLinkView+ViewInspectorTests.swift @@ -4,6 +4,7 @@ import BitwardenKitMocks import BitwardenResources import SwiftUI import ViewInspector +import ViewInspectorTestHelpers import XCTest @testable import BitwardenShared diff --git a/BitwardenShared/UI/Auth/StartRegistration/CheckEmail/CheckEmailView+ViewInspectorTests.swift b/BitwardenShared/UI/Auth/StartRegistration/CheckEmail/CheckEmailView+ViewInspectorTests.swift index 7d8c36e4c..a5a0d4720 100644 --- a/BitwardenShared/UI/Auth/StartRegistration/CheckEmail/CheckEmailView+ViewInspectorTests.swift +++ b/BitwardenShared/UI/Auth/StartRegistration/CheckEmail/CheckEmailView+ViewInspectorTests.swift @@ -4,6 +4,7 @@ import BitwardenKitMocks import BitwardenResources import SwiftUI import ViewInspector +import ViewInspectorTestHelpers import XCTest @testable import BitwardenShared diff --git a/BitwardenShared/UI/Platform/DebugMenu/DebugMenuView+ViewInspectorTests.swift b/BitwardenShared/UI/Platform/DebugMenu/DebugMenuView+ViewInspectorTests.swift index 233dcdf99..75104e223 100644 --- a/BitwardenShared/UI/Platform/DebugMenu/DebugMenuView+ViewInspectorTests.swift +++ b/BitwardenShared/UI/Platform/DebugMenu/DebugMenuView+ViewInspectorTests.swift @@ -2,6 +2,7 @@ import BitwardenKit import BitwardenKitMocks import BitwardenResources +import ViewInspectorTestHelpers import XCTest @testable import BitwardenShared diff --git a/BitwardenShared/UI/Platform/Settings/Settings/About/EnableFlightRecorder/EnableFlightRecorderView+ViewInspectorTests.swift b/BitwardenShared/UI/Platform/Settings/Settings/About/EnableFlightRecorder/EnableFlightRecorderView+ViewInspectorTests.swift index 25b4ad0d5..2ee2f2833 100644 --- a/BitwardenShared/UI/Platform/Settings/Settings/About/EnableFlightRecorder/EnableFlightRecorderView+ViewInspectorTests.swift +++ b/BitwardenShared/UI/Platform/Settings/Settings/About/EnableFlightRecorder/EnableFlightRecorderView+ViewInspectorTests.swift @@ -2,6 +2,7 @@ import BitwardenKit import BitwardenKitMocks import BitwardenResources +import ViewInspectorTestHelpers import XCTest @testable import BitwardenShared diff --git a/BitwardenShared/UI/Tools/Generator/Generator/GeneratorView+ViewInspectorTests.swift b/BitwardenShared/UI/Tools/Generator/Generator/GeneratorView+ViewInspectorTests.swift index 47e083191..fdaa94801 100644 --- a/BitwardenShared/UI/Tools/Generator/Generator/GeneratorView+ViewInspectorTests.swift +++ b/BitwardenShared/UI/Tools/Generator/Generator/GeneratorView+ViewInspectorTests.swift @@ -4,6 +4,7 @@ import BitwardenKitMocks import BitwardenResources import SwiftUI import ViewInspector +import ViewInspectorTestHelpers import XCTest @testable import BitwardenShared diff --git a/BitwardenShared/UI/Tools/Send/SendItem/AddEditSendItem/AddEditSendItemView+ViewInspectorTests.swift b/BitwardenShared/UI/Tools/Send/SendItem/AddEditSendItem/AddEditSendItemView+ViewInspectorTests.swift index 845224537..fa1b20891 100644 --- a/BitwardenShared/UI/Tools/Send/SendItem/AddEditSendItem/AddEditSendItemView+ViewInspectorTests.swift +++ b/BitwardenShared/UI/Tools/Send/SendItem/AddEditSendItem/AddEditSendItemView+ViewInspectorTests.swift @@ -4,6 +4,7 @@ import BitwardenKitMocks import BitwardenResources import SwiftUI import ViewInspector +import ViewInspectorTestHelpers import XCTest @testable import BitwardenShared diff --git a/BitwardenShared/UI/Vault/Vault/VaultList/VaultListView+ViewInspectorTests.swift b/BitwardenShared/UI/Vault/Vault/VaultList/VaultListView+ViewInspectorTests.swift index 4433ce895..c21d97b37 100644 --- a/BitwardenShared/UI/Vault/Vault/VaultList/VaultListView+ViewInspectorTests.swift +++ b/BitwardenShared/UI/Vault/Vault/VaultList/VaultListView+ViewInspectorTests.swift @@ -5,6 +5,7 @@ import BitwardenResources import BitwardenSdk import SwiftUI import ViewInspector +import ViewInspectorTestHelpers import XCTest @testable import BitwardenShared diff --git a/BitwardenShared/UI/Vault/VaultItem/AddEditItem/AddEditItemView+ViewInspectorTests.swift b/BitwardenShared/UI/Vault/VaultItem/AddEditItem/AddEditItemView+ViewInspectorTests.swift index 7bfb3c631..e5a2f6c8e 100644 --- a/BitwardenShared/UI/Vault/VaultItem/AddEditItem/AddEditItemView+ViewInspectorTests.swift +++ b/BitwardenShared/UI/Vault/VaultItem/AddEditItem/AddEditItemView+ViewInspectorTests.swift @@ -5,6 +5,7 @@ import BitwardenResources import BitwardenSdk import SwiftUI import ViewInspector +import ViewInspectorTestHelpers import XCTest @testable import BitwardenShared diff --git a/Docs/Architecture.md b/Docs/Architecture.md index 9fc947d42..b2f3896e0 100644 --- a/Docs/Architecture.md +++ b/Docs/Architecture.md @@ -60,7 +60,8 @@ The iOS repository contains two main apps: Bitwarden Password Manager and Bitwar - `GlobalTestHelpers`: Shared functionality between the app's test targets. - `BitwardenKitMocks`: Mock implementations for BitwardenKit components. - `AuthenticatorBridgeKitMocks`: Mock implementations for AuthenticatorBridgeKit components. -- `TestHelpers`: Additional test utilities and helpers. +- `TestHelpers`: Additional test utilities and helpers. +- `ViewInspectorTestHelpers`: ViewInspector-specific test helpers for UI testing. ### Architecture Structure diff --git a/GlobalTestHelpers-bwa/Extensions/InspectableView.swift b/GlobalTestHelpers-bwa/Extensions/InspectableView.swift deleted file mode 100644 index 3099aa91a..000000000 --- a/GlobalTestHelpers-bwa/Extensions/InspectableView.swift +++ /dev/null @@ -1,344 +0,0 @@ -import Foundation -import SwiftUI -import ViewInspector - -/// A generic type wrapper around `AsyncButton` to allow `ViewInspector` to find instances of `AsyncButton` without -/// needing to know the type of its `Label`. -/// -struct AsyncButtonType: BaseViewType { - static var typePrefix: String = "AsyncButton" - - static var namespacedPrefixes: [String] = [ - "BitwardenKit.AsyncButton", - ] -} - -/// A generic type wrapper around `BitwardenTextField` to allow `ViewInspector` to find instances of -/// `BitwardenTextField` without needing to know the details of it's implementation. -/// -struct BitwardenTextFieldType: BaseViewType { - static var typePrefix: String = "BitwardenTextField" - - static var namespacedPrefixes: [String] = [ - "BitwardenKit.BitwardenTextField", - ] -} - -/// A generic type wrapper around ` BitwardenMenuFieldType` to allow `ViewInspector` to find instances of -/// ` BitwardenMenuFieldType` without needing to know the details of it's implementation. -/// -struct BitwardenMenuFieldType: BaseViewType { - static var typePrefix: String = "BitwardenMenuField" - - static var namespacedPrefixes: [String] = [ - "BitwardenKit.BitwardenMenuField", - ] -} - -/// A generic type wrapper around `BitwardenMultilineTextField` to allow `ViewInspector` to find -/// instances of `BitwardenMultilineTextField` without needing to know the details of it's -/// implementation. -/// -struct BitwardenMultilineTextFieldType: BaseViewType { - static var typePrefix: String = "BitwardenMultilineTextField" - - static var namespacedPrefixes: [String] = [ - "AuthenticatorShared.BitwardenMultilineTextField", - ] -} - -/// A generic type wrapper around `SettingsMenuField` to allow `ViewInspector` to find instances of -/// `SettingsMenuField` without needing to know the details of it's implementation. -/// -struct SettingsMenuFieldType: BaseViewType { - static var typePrefix: String = "SettingsMenuField" - - static var namespacedPrefixes: [String] = [ - "BitwardenKit.SettingsMenuField", - ] -} - -// MARK: InspectableView - -extension InspectableView { - // MARK: Methods - - /// Attempts to locate an async button with the provided title. - /// - /// - Parameters: - /// - title: The title to use while searching for a button. - /// - locale: The locale for text extraction. - /// - Returns: An async button, if one can be located. - /// - Throws: Throws an error if a view was unable to be located. - /// - func find( - asyncButton title: String, - locale _: Locale = .testsDefault, - ) throws -> InspectableView { - try find(AsyncButtonType.self, containing: title) - } - - /// Attempts to locate an async button with the provided accessibility label. - /// - /// - Parameters: - /// - accessibilityLabel: The accessibility label to use while searching for a button. - /// - locale: The locale for text extraction. - /// - Returns: A button, if one can be located. - /// - Throws: Throws an error if a view was unable to be located. - /// - func find( - asyncButtonWithAccessibilityLabel accessibilityLabel: String, - locale: Locale = .testsDefault, - ) throws -> InspectableView { - try find(AsyncButtonType.self) { view in - try view.accessibilityLabel().string(locale: locale) == accessibilityLabel - } - } - - /// Attempts to locate a bitwarden menu field with the provided title. - /// - /// - Parameters: - /// - title: The title to use while searching for a menu field. - /// - locale: The locale for text extraction. - /// - Returns: A `BitwardenMenuFieldType`, if one can be located. - /// - Throws: Throws an error if a view was unable to be located. - /// - func find( - bitwardenMenuField title: String, - locale: Locale = .testsDefault, - ) throws -> InspectableView { - try find(BitwardenMenuFieldType.self, containing: title, locale: locale) - } - - /// Attempts to locate a bitwarden multiline text field with the provided title. - /// - /// - Parameters: - /// - title: The title to use while searching for a text field. - /// - locale: The locale for text extraction. - /// - Returns: A `BitwardenMultilineTextFieldType`, if one can be located. - /// - Throws: Throws an error if a view was unable to be located. - /// - func find( - bitwardenMultilineTextField title: String, - locale: Locale = .testsDefault, - ) throws -> InspectableView { - try find(BitwardenMultilineTextFieldType.self, containing: title, locale: locale) - } - - /// Attempts to locate a bitwarden text field with the provided title. - /// - /// - Parameters: - /// - title: The title to use while searching for a text field. - /// - locale: The locale for text extraction. - /// - Returns: A `BitwardenTextFieldType`, if one can be located. - /// - Throws: Throws an error if a view was unable to be located. - /// - func find( - bitwardenTextField title: String, - locale: Locale = .testsDefault, - ) throws -> InspectableView { - try find(BitwardenTextFieldType.self, containing: title, locale: locale) - } - - /// Attempts to locate a bitwarden text field with the provided accessibility label. - /// - /// - Parameters: - /// - accessibilityLabel: The accessibility label to use while searching for a button. - /// - locale: The locale for text extraction. - /// - Returns: A `BitwardenTextFieldType`, if one can be located. - /// - Throws: Throws an error if a view was unable to be located. - /// - func find( - bitwardenTextFieldWithAccessibilityLabel accessibilityLabel: String, - locale: Locale = .testsDefault, - ) throws -> InspectableView { - try find(BitwardenTextFieldType.self) { view in - try view.accessibilityLabel().string(locale: locale) == accessibilityLabel - } - } - - /// Attempts to locate a button with the provided id. - /// - /// - Parameter id: The id to use while searching for a button. - /// - Returns: A button, if one can be located. - /// - Throws: Throws an error if a view was unable to be located. - /// - func find(buttonWithId id: AnyHashable) throws -> InspectableView { - try find(ViewType.Button.self) { view in - try view.id() == id - } - } - - /// Attempts to locate a button with the provided accessibility label. - /// - /// - Parameter accessibilityLabel: The accessibility label to use while searching for a button. - /// - Returns: A button, if one can be located. - /// - Throws: Throws an error if a view was unable to be located. - /// - func find( - buttonWithAccessibilityLabel accessibilityLabel: String, - locale: Locale = .testsDefault, - ) throws -> InspectableView { - try find(ViewType.Button.self) { view in - try view.accessibilityLabel().string(locale: locale) == accessibilityLabel - } - } - - /// Attempts to locate a picker with the provided label. - /// - /// - Parameter label: The label to use while searching for a picker. - /// - Returns: A picker, if one can be located. - /// - Throws: Throws an error if a picker was unable to be located. - /// - func find( - picker label: String, - ) throws -> InspectableView { - try find(ViewType.Picker.self, containing: label) - } - - /// Attempts to locate a text field with the provided label. - /// - /// - Parameter label: The label to use while searching for a text field. - /// - Returns: A text field, if one can be located. - /// - Throws: Throws an error if a view was unable to be located. - /// - func find(textField label: String) throws -> InspectableView { - try find(ViewType.TextField.self, containing: label) - } - - /// Attempts to locate a secure field with the provided label. - /// - /// - Parameter label: The label to use while searching for a secure field. - /// - Returns: A secure field, if one can be located. - /// - Throws: Throws an error if a view was unable to be located. - /// - func find(secureField label: String) throws -> InspectableView { - try find(ViewType.SecureField.self, containing: label) - } - - /// Attempts to locate a settings menu field with the provided title. - /// - /// - Parameters: - /// - title: The title to use while searching for a menu field. - /// - locale: The locale for text extraction. - /// - Returns: A `SettingsMenuField`, if one can be located. - /// - Throws: Throws an error if a view was unable to be located. - /// - func find( - settingsMenuField title: String, - locale: Locale = .testsDefault, - ) throws -> InspectableView { - try find(SettingsMenuFieldType.self, containing: title, locale: locale) - } - - /// Attempts to locate a slider with the provided accessibility label. - /// - /// - Parameter accessibilityLabel: The accessibility label to use while searching for a slider. - /// - Returns: A slider, if one can be located. - /// - Throws: Throws an error if a view was unable to be located. - /// - func find( - sliderWithAccessibilityLabel accessibilityLabel: String, - locale: Locale = .testsDefault, - ) throws -> InspectableView { - try find(ViewType.Slider.self) { view in - try view.accessibilityLabel().string(locale: locale) == accessibilityLabel - } - } - - /// Attempts to locate a toggle with the provided accessibility label. - /// - /// - Parameter accessibilityLabel: The accessibility label to use while searching for a toggle. - /// - Returns: A toggle, if one can be located. - /// - Throws: Throws an error if a view was unable to be located. - /// - func find( - toggleWithAccessibilityLabel accessibilityLabel: String, - locale: Locale = .testsDefault, - ) throws -> InspectableView { - try find(ViewType.Toggle.self) { view in - try view.accessibilityLabel().string(locale: locale) == accessibilityLabel - } - } - - // MARK: Toolbar - - /// Attempts to locate the toolbar cancel default button. - /// - /// - Returns: A cancel toolbar button, if one can be located. - /// - Throws: Throws an error if a view was unable to be located. - /// - func findCancelToolbarButton() throws -> InspectableView { - try find(ViewType.Button.self) { view in - try view.accessibilityIdentifier() == "CancelButton" - } - } -} - -extension InspectableView where View == AsyncButtonType { - /// Simulates a tap on an `AsyncButton`. This method is asynchronous and allows the entire `async` `action` on the - /// button to run before returning. - /// - func tap() async throws { - typealias Callback = () async -> Void - let mirror = Mirror(reflecting: self) - if let action = mirror.descendant("content", "view", "action") as? Callback { - await action() - } else { - throw InspectionError.attributeNotFound( - label: "action", - type: String(describing: AsyncButtonType.self), - ) - } - } -} - -extension InspectableView where View == BitwardenTextFieldType { - /// Locates the raw binding on this textfield's text value. Can be used to simulate updating the text field. - /// - func inputBinding() throws -> Binding { - let mirror = Mirror(reflecting: self) - if let binding = mirror.descendant("content", "view", "_text") as? Binding { - return binding - } else { - throw InspectionError.attributeNotFound( - label: "_text", - type: String(describing: BitwardenTextFieldType.self), - ) - } - } -} - -extension InspectableView where View == BitwardenMultilineTextFieldType { - /// Locates the raw binding on this textfield's text value. Can be used to simulate updating the text field. - /// - func inputBinding() throws -> Binding { - let mirror = Mirror(reflecting: self) - if let binding = mirror.descendant("content", "view", "_text") as? Binding { - return binding - } else { - throw InspectionError.attributeNotFound( - label: "_text", - type: String(describing: BitwardenMultilineTextFieldType.self), - ) - } - } -} - -extension InspectableView where View == BitwardenMenuFieldType { - /// Selects a new value in the menu field. - /// - func select(newValue: any Hashable) throws { - let picker = try find(ViewType.Picker.self) - try picker.select(value: newValue) - } -} - -extension InspectableView where View == SettingsMenuFieldType { - /// Selects a new value in the menu field. - /// - func select(newValue: any Hashable) throws { - let picker = try find(ViewType.Picker.self) - try picker.select(value: newValue) - } -} diff --git a/GlobalTestHelpers/Extensions/InspectableView.swift b/ViewInspectorTestHelpers/InspectableView.swift similarity index 81% rename from GlobalTestHelpers/Extensions/InspectableView.swift rename to ViewInspectorTestHelpers/InspectableView.swift index ac6f21bbc..fd5ec5a1f 100644 --- a/GlobalTestHelpers/Extensions/InspectableView.swift +++ b/ViewInspectorTestHelpers/InspectableView.swift @@ -6,12 +6,12 @@ import XCTest // swiftlint:disable file_length /// A generic type wrapper around `ActionCard` to allow `ViewInspector` to find instances of -/// `ActionCard` without needing to know the details of it's implementation. +/// `ActionCard` without needing to know the details of its implementation. /// -struct ActionCardType: BaseViewType { - static var typePrefix: String = "ActionCard" +public struct ActionCardType: BaseViewType { + public static var typePrefix: String = "ActionCard" - static var namespacedPrefixes: [String] = [ + public static var namespacedPrefixes: [String] = [ "BitwardenShared.ActionCard", ] } @@ -19,106 +19,118 @@ struct ActionCardType: BaseViewType { /// A generic type wrapper around `AsyncButton` to allow `ViewInspector` to find instances of `AsyncButton` without /// needing to know the type of its `Label`. /// -struct AsyncButtonType: BaseViewType { - static var typePrefix: String = "AsyncButton" +public struct AsyncButtonType: BaseViewType { + public static var typePrefix: String = "AsyncButton" - static var namespacedPrefixes: [String] = [ + public static var namespacedPrefixes: [String] = [ "BitwardenKit.AsyncButton", ] } /// A generic type wrapper around `BitwardenSlider` to allow `ViewInspector` to find instances of `BitwardenSlider` -/// without needing to know the details of it's implementation. +/// without needing to know the details of its implementation. /// -struct BitwardenSliderType: BaseViewType { - static var typePrefix: String = "BitwardenSlider" +public struct BitwardenSliderType: BaseViewType { + public static var typePrefix: String = "BitwardenSlider" - static var namespacedPrefixes: [String] = [ + public static var namespacedPrefixes: [String] = [ "BitwardenKit.BitwardenSlider", ] } /// A generic type wrapper around `BitwardenStepper` to allow `ViewInspector` to find instances of -/// `BitwardenStepper` without needing to know the details of it's implementation. +/// `BitwardenStepper` without needing to know the details of its implementation. /// -struct BitwardenStepperType: BaseViewType { - static var typePrefix: String = "BitwardenStepper" +public struct BitwardenStepperType: BaseViewType { + public static var typePrefix: String = "BitwardenStepper" - static var namespacedPrefixes: [String] = [ + public static var namespacedPrefixes: [String] = [ "BitwardenKit.BitwardenStepper", ] } /// A generic type wrapper around `BitwardenTextField` to allow `ViewInspector` to find instances of -/// `BitwardenTextField` without needing to know the details of it's implementation. +/// `BitwardenTextField` without needing to know the details of its implementation. /// -struct BitwardenTextFieldType: BaseViewType { - static var typePrefix: String = "BitwardenTextField" +public struct BitwardenTextFieldType: BaseViewType { + public static var typePrefix: String = "BitwardenTextField" - static var namespacedPrefixes: [String] = [ + public static var namespacedPrefixes: [String] = [ "BitwardenKit.BitwardenTextField", ] } /// A generic type wrapper around ` BitwardenMenuFieldType` to allow `ViewInspector` to find instances of -/// ` BitwardenMenuFieldType` without needing to know the details of it's implementation. +/// ` BitwardenMenuFieldType` without needing to know the details of its implementation. /// -struct BitwardenMenuFieldType: BaseViewType { - static var typePrefix: String = "BitwardenMenuField" +public struct BitwardenMenuFieldType: BaseViewType { + public static var typePrefix: String = "BitwardenMenuField" - static var namespacedPrefixes: [String] = [ + public static var namespacedPrefixes: [String] = [ "BitwardenKit.BitwardenMenuField", ] } -/// A generic type wrapper around `BitwardenUITextViewType` to allow `ViewInspector` to find -/// instances of `BitwardenUITextViewType` without needing to know the details of it's +/// A generic type wrapper around `BitwardenMultilineTextField` to allow `ViewInspector` to find +/// instances of `BitwardenMultilineTextField` without needing to know the details of its /// implementation. /// -struct BitwardenUITextViewType: BaseViewType { - static var typePrefix: String = "BitwardenUITextView" +public struct BitwardenMultilineTextFieldType: BaseViewType { + public static var typePrefix: String = "BitwardenMultilineTextField" - static var namespacedPrefixes: [String] = [ + public static var namespacedPrefixes: [String] = [ + "AuthenticatorShared.BitwardenMultilineTextField", + ] +} + +/// A generic type wrapper around `BitwardenUITextViewType` to allow `ViewInspector` to find +/// instances of `BitwardenUITextViewType` without needing to know the details of its +/// implementation. +/// +public struct BitwardenUITextViewType: BaseViewType { + public static var typePrefix: String = "BitwardenUITextView" + + public static var namespacedPrefixes: [String] = [ "BitwardenKit.BitwardenUITextView", ] } /// A generic type wrapper around `FloatingActionButton` to allow `ViewInspector` to find instances -/// of `FloatingActionButton` without needing to know the details of it's implementation. +/// of `FloatingActionButton` without needing to know the details of its implementation. /// -struct FloatingActionButtonType: BaseViewType { - static var typePrefix: String = "FloatingActionButton" +public struct FloatingActionButtonType: BaseViewType { + public static var typePrefix: String = "FloatingActionButton" - static var namespacedPrefixes: [String] = [ + public static var namespacedPrefixes: [String] = [ "BitwardenShared.FloatingActionButton", ] } /// A generic type wrapper around `LoadingView` to allow `ViewInspector` to find instances of -/// `LoadingView` without needing to know the details of it's implementation. +/// `LoadingView` without needing to know the details of its implementation. /// -struct LoadingViewType: BaseViewType { - static var typePrefix: String = "LoadingView" +public struct LoadingViewType: BaseViewType { + public static var typePrefix: String = "LoadingView" - static var namespacedPrefixes: [String] = [ + public static var namespacedPrefixes: [String] = [ "BitwardenShared.LoadingView", ] } /// A generic type wrapper around `SettingsMenuField` to allow `ViewInspector` to find instances of -/// `SettingsMenuField` without needing to know the details of it's implementation. +/// `SettingsMenuField` without needing to know the details of its implementation. /// -struct SettingsMenuFieldType: BaseViewType { - static var typePrefix: String = "SettingsMenuField" +public struct SettingsMenuFieldType: BaseViewType { + public static var typePrefix: String = "SettingsMenuField" - static var namespacedPrefixes: [String] = [ + public static var namespacedPrefixes: [String] = [ "BitwardenKit.SettingsMenuField", ] } // MARK: InspectableView -extension InspectableView { +public extension InspectableView { // MARK: Methods /// Attempts to locate an action card with the provided title. @@ -369,7 +381,7 @@ extension InspectableView { } } -extension InspectableView where View == AsyncButtonType { +public extension InspectableView where View == AsyncButtonType { /// Simulates a tap on an `AsyncButton`. This method is asynchronous and allows the entire `async` `action` on the /// button to run before returning. /// @@ -387,7 +399,7 @@ extension InspectableView where View == AsyncButtonType { } } -extension InspectableView where View == BitwardenTextFieldType { +public extension InspectableView where View == BitwardenTextFieldType { /// Locates the raw binding on this textfield's text value. Can be used to simulate updating the text field. /// func inputBinding() throws -> Binding { @@ -403,7 +415,23 @@ extension InspectableView where View == BitwardenTextFieldType { } } -extension InspectableView where View == BitwardenSliderType { +public extension InspectableView where View == BitwardenMultilineTextFieldType { + /// Locates the raw binding on this textfield's text value. Can be used to simulate updating the text field. + /// + func inputBinding() throws -> Binding { + let mirror = Mirror(reflecting: self) + if let binding = mirror.descendant("content", "view", "_text") as? Binding { + return binding + } else { + throw InspectionError.attributeNotFound( + label: "_text", + type: String(describing: BitwardenMultilineTextFieldType.self), + ) + } + } +} + +public extension InspectableView where View == BitwardenSliderType { /// Simulates a drag gesture on the slider to set a new value. /// func setValue(_ value: Double) throws { @@ -425,7 +453,7 @@ extension InspectableView where View == BitwardenSliderType { } } -extension InspectableView where View == BitwardenUITextViewType { +public extension InspectableView where View == BitwardenUITextViewType { /// Locates the raw binding on this textfield's text value. Can be used to simulate updating the text field. /// func inputBinding() throws -> Binding { @@ -441,7 +469,7 @@ extension InspectableView where View == BitwardenUITextViewType { } } -extension InspectableView where View == BitwardenMenuFieldType { +public extension InspectableView where View == BitwardenMenuFieldType { /// Selects a new value in the menu field. /// func select(newValue: any Hashable) throws { @@ -450,7 +478,7 @@ extension InspectableView where View == BitwardenMenuFieldType { } } -extension InspectableView where View == SettingsMenuFieldType { +public extension InspectableView where View == SettingsMenuFieldType { /// Selects a new value in the menu field. /// func select(newValue: any Hashable) throws { @@ -459,7 +487,7 @@ extension InspectableView where View == SettingsMenuFieldType { } } -extension InspectableView where View == BitwardenStepperType { +public extension InspectableView where View == BitwardenStepperType { /// Decrements the stepper. /// func decrement() throws { @@ -475,7 +503,7 @@ extension InspectableView where View == BitwardenStepperType { } } -extension InspectableView where View == FloatingActionButtonType { +public extension InspectableView where View == FloatingActionButtonType { /// Simulates a tap on an `AsyncButton` within a `FloatingActionButton`. This method is /// asynchronous and allows the entire `async` `action` on the button to run before returning. /// diff --git a/project-bwa.yml b/project-bwa.yml index 0b3b2f0e8..793b6437d 100644 --- a/project-bwa.yml +++ b/project-bwa.yml @@ -151,7 +151,6 @@ targets: - target: BitwardenKit/AuthenticatorBridgeKit - target: BitwardenKit/TestHelpers - package: SnapshotTesting - - package: ViewInspector randomExecutionOrder: true AuthenticatorShared: @@ -237,8 +236,6 @@ targets: - "**/*SnapshotTests.*" - "**/*ViewInspectorTests.*" - path: GlobalTestHelpers-bwa - excludes: - - "**/InspectableView.*" - path: AuthenticatorShared/Sourcery/Generated optional: true - path: AuthenticatorShared/Sourcery/Generated/AutoMockable.generated.swift @@ -267,8 +264,6 @@ targets: - "**/TestHelpers/*" - "**/Fixtures/*" - path: GlobalTestHelpers-bwa - excludes: - - "**/InspectableView.*" - path: AuthenticatorShared/Sourcery/Generated optional: true - path: AuthenticatorShared/Sourcery/Generated/AutoMockable.generated.swift @@ -308,5 +303,6 @@ targets: - target: BitwardenKit/AuthenticatorBridgeKitMocks - target: BitwardenKit/BitwardenKitMocks - target: BitwardenKit/TestHelpers + - target: BitwardenKit/ViewInspectorTestHelpers - package: ViewInspector randomExecutionOrder: true diff --git a/project-bwk.yml b/project-bwk.yml index 599706ebc..8d30700cb 100644 --- a/project-bwk.yml +++ b/project-bwk.yml @@ -210,6 +210,7 @@ targets: - target: BitwardenKit - target: BitwardenKitMocks - target: TestHelpers + - target: ViewInspectorTestHelpers - package: ViewInspector randomExecutionOrder: true BitwardenResources: @@ -284,4 +285,15 @@ targets: dependencies: - target: Networking - package: SnapshotTesting + ViewInspectorTestHelpers: + type: framework + platform: iOS + settings: + base: + ENABLE_TESTING_SEARCH_PATHS: YES + GENERATE_INFOPLIST_FILE: YES + sources: + - path: ViewInspectorTestHelpers + dependencies: + - package: ViewInspector diff --git a/project-pm.yml b/project-pm.yml index 1a5002a9e..e86b45706 100644 --- a/project-pm.yml +++ b/project-pm.yml @@ -221,8 +221,6 @@ targets: - "**/*Tests.*" - "**/TestHelpers/*" - path: GlobalTestHelpers - excludes: - - "**/InspectableView.*" dependencies: - target: Bitwarden - target: BitwardenShared @@ -259,8 +257,6 @@ targets: - "**/*Tests.*" - "**/TestHelpers/*" - path: GlobalTestHelpers - excludes: - - "**/InspectableView.*" dependencies: - target: BitwardenActionExtension - target: BitwardenShared @@ -296,8 +292,6 @@ targets: - "**/*Tests.*" - "**/TestHelpers/*" - path: GlobalTestHelpers - excludes: - - "**/InspectableView.*" dependencies: - target: BitwardenAutoFillExtension - target: BitwardenShared @@ -333,8 +327,6 @@ targets: - "**/*Tests.*" - "**/TestHelpers/*" - path: GlobalTestHelpers - excludes: - - "**/InspectableView.*" dependencies: - target: BitwardenShareExtension - target: BitwardenShared @@ -420,8 +412,6 @@ targets: - "**/*SnapshotTests.*" - "**/*ViewInspectorTests.*" - path: GlobalTestHelpers - excludes: - - "**/InspectableView.*" - path: BitwardenShared/Sourcery/Generated optional: true - path: BitwardenShared/Sourcery/Generated/AutoMockable.generated.swift @@ -453,8 +443,6 @@ targets: - "**/TestHelpers/*" - "**/Fixtures/*" - path: GlobalTestHelpers - excludes: - - "**/InspectableView.*" - path: BitwardenShared/Sourcery/Generated optional: true - path: BitwardenShared/Sourcery/Generated/AutoMockable.generated.swift @@ -495,6 +483,7 @@ targets: - target: BitwardenShared - target: BitwardenKit/BitwardenKitMocks - target: BitwardenKit/TestHelpers + - target: BitwardenKit/ViewInspectorTestHelpers - package: ViewInspector randomExecutionOrder: true