Skip to content

Commit

Permalink
[PM-11213] Update to Xcode 16.1 and iOS 18.1 (#1070)
Browse files Browse the repository at this point in the history
  • Loading branch information
KatherineInCode authored Oct 31, 2024
1 parent f588bd5 commit e2fd662
Show file tree
Hide file tree
Showing 22 changed files with 82 additions and 29 deletions.
4 changes: 2 additions & 2 deletions .github/ISSUE_TEMPLATE/bug.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ body:
attributes:
label: Environment Details
placeholder: |
- Device: [e.g. iPhone 15 Pro, iPad Air (5th Generation)]
- OS Version: [e.g. 17.4.1]
- Device: [e.g. iPhone 16 Pro, iPad Air (5th Generation)]
- OS Version: [e.g. 18.0.1]
- type: checkboxes
id: issue-tracking-info
attributes:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ env:
jobs:
build:
name: Build
runs-on: macos-14
runs-on: macos-15
env:
MINT_PATH: .mint/lib
MINT_LINK_PATH: .mint/bin
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ jobs:

test:
name: Test
runs-on: macos-14-xlarge
runs-on: macos-15-xlarge
needs: check-run
permissions:
contents: read
Expand Down
2 changes: 1 addition & 1 deletion .test-simulator-ios-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
18.0
18.1
2 changes: 1 addition & 1 deletion .xcode-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
15.4
16.1
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class ProfileSwitcherViewTests: BitwardenTestCase { // swiftlint:disable:this ty
@MainActor
func test_accountRow_longPress_currentAccount() throws {
let accountRow = try subject.inspect().find(button: "[email protected]")
try accountRow.labelView().callOnLongPressGesture()
try accountRow.labelView().recursiveCallOnLongPressGesture()
let currentAccount = processor.state.activeAccountProfile!
waitFor(!processor.effects.isEmpty)

Expand All @@ -54,14 +54,14 @@ class ProfileSwitcherViewTests: BitwardenTestCase { // swiftlint:disable:this ty
func test_accountRow_longPress_currentAccount_noLockOrLogout() throws {
processor.state.allowLockAndLogout = false
let accountRow = try subject.inspect().find(button: "[email protected]")
XCTAssertThrowsError(try accountRow.labelView().callOnLongPressGesture())
XCTAssertThrowsError(try accountRow.labelView().recursiveCallOnLongPressGesture())
}

/// Tapping a profile row dispatches the `.accountPressed` action.
@MainActor
func test_accountRow_tap_currentAccount() throws {
let accountRow = try subject.inspect().find(button: "[email protected]")
try accountRow.labelView().callOnTapGesture()
try accountRow.labelView().recursiveCallOnTapGesture()
let currentAccount = processor.state.activeAccountProfile!
waitFor(!processor.effects.isEmpty)

Expand Down Expand Up @@ -96,7 +96,7 @@ class ProfileSwitcherViewTests: BitwardenTestCase { // swiftlint:disable:this ty
isVisible: true
)
let alternateRow = try subject.inspect().find(button: "[email protected]")
try alternateRow.labelView().callOnLongPressGesture()
try alternateRow.labelView().recursiveCallOnLongPressGesture()
waitFor(!processor.effects.isEmpty)

XCTAssertEqual(processor.effects.last, .accountLongPressed(alternate))
Expand All @@ -121,7 +121,7 @@ class ProfileSwitcherViewTests: BitwardenTestCase { // swiftlint:disable:this ty
)
let alternateRow = try subject.inspect().find(button: "[email protected]")
_ = try subject.inspect().find(button: "[email protected]")
XCTAssertThrowsError(try alternateRow.labelView().callOnLongPressGesture())
XCTAssertThrowsError(try alternateRow.labelView().recursiveCallOnLongPressGesture())
}

/// Tapping an alternative profile row dispatches the `.accountPressed` action.
Expand All @@ -142,7 +142,7 @@ class ProfileSwitcherViewTests: BitwardenTestCase { // swiftlint:disable:this ty
isVisible: true
)
let addAccountRow = try subject.inspect().find(button: "[email protected]")
try addAccountRow.labelView().callOnTapGesture()
try addAccountRow.labelView().recursiveCallOnTapGesture()
waitFor(!processor.effects.isEmpty)

XCTAssertEqual(processor.effects.last, .accountPressed(alternate))
Expand All @@ -169,7 +169,7 @@ class ProfileSwitcherViewTests: BitwardenTestCase { // swiftlint:disable:this ty
isVisible: true
)
let addAccountRow = try subject.inspect().find(button: "")
try addAccountRow.labelView().callOnTapGesture()
try addAccountRow.labelView().recursiveCallOnTapGesture()
waitFor(!processor.effects.isEmpty)

XCTAssertEqual(processor.effects.last, .accountPressed(secondAlternate))
Expand All @@ -179,7 +179,7 @@ class ProfileSwitcherViewTests: BitwardenTestCase { // swiftlint:disable:this ty
@MainActor
func test_background_tap() throws {
let view = try subject.inspect().view(ProfileSwitcherView.self)
let background = view.first
let background = try view.implicitAnyView().first
try background?.callOnTapGesture()

XCTAssertEqual(processor.dispatchedActions.last, .backgroundPressed)
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ class ExportVaultViewTests: BitwardenTestCase {
let menuField = try subject.inspect().find(bitwardenMenuField: Localizations.fileFormat)
XCTAssertTrue(menuField.isDisabled())

let textfield = try subject.inspect().find(viewWithId: Localizations.masterPassword).textField()
XCTAssertTrue(textfield.isDisabled())
let secureField = try subject.inspect().find(viewWithId: Localizations.masterPassword).secureField()
XCTAssertTrue(secureField.isDisabled())
}

/// Tapping the export vault button sends the `.exportVault` action.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ class VaultListViewTests: BitwardenTestCase { // swiftlint:disable:this type_bod
processor.state.profileSwitcherState.isVisible = true
let accountRow = try subject.inspect().find(button: "[email protected]")
let currentAccount = processor.state.profileSwitcherState.activeAccountProfile!
try accountRow.labelView().callOnLongPressGesture()
try accountRow.labelView().recursiveCallOnLongPressGesture()
waitFor(!processor.effects.isEmpty)

XCTAssertEqual(processor.effects.last, .profileSwitcher(.accountLongPressed(currentAccount)))
Expand All @@ -94,7 +94,7 @@ class VaultListViewTests: BitwardenTestCase { // swiftlint:disable:this type_bod
processor.state.profileSwitcherState.isVisible = true
let accountRow = try subject.inspect().find(button: "[email protected]")
let currentAccount = processor.state.profileSwitcherState.activeAccountProfile!
try accountRow.labelView().callOnTapGesture()
try accountRow.labelView().recursiveCallOnTapGesture()
waitFor(!processor.effects.isEmpty)

XCTAssertEqual(processor.effects.last, .profileSwitcher(.accountPressed(currentAccount)))
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion Docs/Architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -320,4 +320,4 @@ Every type containing logic should be tested. Test files should be named `<TypeT

- **Unit**: Unit tests compose the majority of tests in the suite. These are written using [XCTest](https://developer.apple.com/documentation/xctest) assertions and should be used to test all logic portions within a type.
- **View**: In a SwiftUI view test, [ViewInspector](https://github.com/nalexn/ViewInspector) is used to test any user interactions within the view. This is commonly used to assert that tapping a button sends an action or effect to the processor, but it can also be used to test other view interactions.
- **Snapshot**: In addition to using [ViewInspector](https://github.com/nalexn/ViewInspector) to interact with a view under test, [SnapshotTesting](https://github.com/pointfreeco/swift-snapshot-testing) is used to take snapshots of the view to test for visual changes from one test run to another. The resulting snapshot images are stored in the repository and are compared against on future test runs. Any visual differences on future test runs will result in a failing test. Snapshot tests are usually recorded in light mode, dark mode, and with a large dynamic type size. ⚠️ These tests are done using an **iPhone 15 Pro (18)** simulator, otherwise tests may fail because of subtle differences between iOS versions.
- **Snapshot**: In addition to using [ViewInspector](https://github.com/nalexn/ViewInspector) to interact with a view under test, [SnapshotTesting](https://github.com/pointfreeco/swift-snapshot-testing) is used to take snapshots of the view to test for visual changes from one test run to another. The resulting snapshot images are stored in the repository and are compared against on future test runs. Any visual differences on future test runs will result in a failing test. Snapshot tests are usually recorded in light mode, dark mode, and with a large dynamic type size. ⚠️ These tests are done using an **iPhone 16 Pro (18.1)** simulator, otherwise tests may fail because of subtle differences between iOS versions.
52 changes: 52 additions & 0 deletions GlobalTestHelpers/Extensions/InspectableView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,58 @@ extension InspectableView {
}
}

extension InspectableView where View: SingleViewContent {
/// Overrides the default `button` method in order to find a text field
/// that might be buried beneath added `AnyView` objects.
func button() throws -> InspectableView<ViewType.Button> {
try find(ViewType.Button.self)
}

/// Overrides the default `secureField` method in order to find a text field
/// that might be buried beneath added `AnyView` objects.
func secureField() throws -> InspectableView<ViewType.SecureField> {
try find(ViewType.SecureField.self)
}

/// Overrides the default `text` method in order to find a text field
/// that might be buried beneath added `AnyView` objects.
func text() throws -> InspectableView<ViewType.Text> {
try find(ViewType.Text.self)
}

/// Overrides the default `textField` method in order to find a text field
/// that might be buried beneath added `AnyView` objects.
func textField() throws -> InspectableView<ViewType.TextField> {
try find(ViewType.TextField.self)
}
}

extension InspectableView where View: SingleViewContent {
/// Recursively traverses a child view hierarchy of `AnyView` objects until
/// it finds one that will take a long press gesture, and performs the gesture.
/// This is necessary because Xcode 16 adds additional `AnyView` objects in
/// debug mode.
func recursiveCallOnLongPressGesture() throws {
do {
try callOnLongPressGesture()
} catch {
try implicitAnyView().recursiveCallOnLongPressGesture()
}
}

/// Recursively traverses a child view hierarchy of `AnyView` objects until
/// it finds one that will take a tap gesture, and performs the gesture.
/// This is necessary because Xcode 16 adds additional `AnyView` objects in
/// debug mode.
func recursiveCallOnTapGesture() throws {
do {
try callOnTapGesture()
} catch {
try implicitAnyView().recursiveCallOnTapGesture()
}
}
}

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.
Expand Down
3 changes: 2 additions & 1 deletion GlobalTestHelpers/Extensions/Snapshotting.swift
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,8 @@ extension Snapshotting where Value == UIViewController, Format == UIImage {
static var standardImage: Snapshotting {
.image(
precision: defaultPrecision,
perceptualPrecision: defaultPerceptualPrecision
perceptualPrecision: defaultPerceptualPrecision,
size: ViewImageConfig.iPhone13(.portrait).size
)
}
}
14 changes: 7 additions & 7 deletions GlobalTestHelpers/Support/BitwardenTestCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ open class BitwardenTestCase: XCTestCase {

@MainActor
override open class func setUp() {
if UIDevice.current.name != "iPhone 15 Pro" {
assertionFailure(
"""
Tests must be run using the iPhone 15 Pro simulator. Snapshot tests depend on using the correct device.
"""
)
}
if UIDevice.current.name != "iPhone 15 Pro" || UIDevice.current.systemVersion != "18.1" {
assertionFailure(
"""
Tests must be run using iOS 18.1 on an iPhone 15 Pro simulator. Snapshot tests depend on using the correct device.
"""
)
}

// Apply default appearances for snapshot tests.
UI.applyDefaultAppearances()
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
- **Minimum iOS**: 15.0
- **Target SDK**: 15.0
- **Device Types Supported**: iPhone, iPad
- **Screen Sizes Supported**: iPhone SE to iPhone 15 Pro Max, iPad Mini to iPad Pro 12.9"
- **Screen Sizes Supported**: iPhone SE to iPhone 16 Pro Max, iPad Mini to iPad Pro 12.9"
- **Orientations Supported**: Portrait, Landscape

## Setup
Expand Down Expand Up @@ -66,7 +66,7 @@
### Running Tests
Due to slight snapshot test variations between iOS version, the test target requires running in an iPhone 15 Pro simulator (iOS 18).
The test target requires running in an iPhone 15 Pro simulator running iOS 18.1. It is, however, worth noting that our snapshot tests almost entirely work off of enforced iPhone 12/13/14 dimensions, with a 3x scale; however a few use the dimensions of the chosen simulator, and thus require an iPhone 15 Pro.
1. In Xcode's toolbar, select the project and a connected device or simulator.
- The `Generic iOS Device` used for builds will not work for testing.
Expand Down

0 comments on commit e2fd662

Please sign in to comment.