From b9f893abc05dc9628e783cecee1f12f46899d5dc Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 7 Nov 2025 08:07:09 +0000 Subject: [PATCH 01/12] test: create test to lint code --- .github/workflows/swiftui-auth.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/swiftui-auth.yml b/.github/workflows/swiftui-auth.yml index 44f17a59c5..9aa669b0c1 100644 --- a/.github/workflows/swiftui-auth.yml +++ b/.github/workflows/swiftui-auth.yml @@ -22,6 +22,25 @@ permissions: contents: read jobs: + format-check: + name: Swift Format Check + runs-on: macos-26 + timeout-minutes: 10 + steps: + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + + - name: Install swiftformat + run: brew install swiftformat + + - name: Check FirebaseSwiftUI formatting + run: swiftformat --lint ./FirebaseSwiftUI + + - name: Check SwiftUI Example formatting + run: swiftformat --lint ./samples/swiftui/FirebaseSwiftUIExample + + - name: Check Package.swift formatting + run: swiftformat --lint ./Package.swift + # Package Unit Tests (standalone, no emulator needed) unit-tests: name: Package Unit Tests From 57634d060b8c1ff532cae227687279629807c26e Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 7 Nov 2025 08:25:19 +0000 Subject: [PATCH 02/12] test: fix selector that was crashing tests --- .../FirebaseSwiftUIExampleUITests/MFAEnrolmentUITests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests/MFAEnrolmentUITests.swift b/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests/MFAEnrolmentUITests.swift index 1b8c981f03..9cbd80c226 100644 --- a/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests/MFAEnrolmentUITests.swift +++ b/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests/MFAEnrolmentUITests.swift @@ -196,7 +196,7 @@ final class MFAEnrollmentUITests: XCTestCase { // 6) Select UK country code and enter phone number (without dial code) // Find and tap the country selector - try multiple approaches since it's embedded in the // TextField - let countrySelector = app.buttons["🇺🇸 +1"] + let countrySelector = app.staticTexts["+1"].firstMatch XCTAssertTrue(countrySelector.waitForExistence(timeout: 5)) countrySelector.tap() From 5f28ae983f53cc69da6b742c878df4095f22176f Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 7 Nov 2025 09:09:24 +0000 Subject: [PATCH 03/12] fix: use single sheet for password coordination --- .../FirebaseAuthSwiftUI/Sources/Views/AuthPickerView.swift | 5 +++++ .../Sources/Views/MFAManagementView.swift | 6 +----- .../FirebaseAuthSwiftUI/Sources/Views/SignedInView.swift | 5 +---- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/AuthPickerView.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/AuthPickerView.swift index 9bbd79a976..57b2f40d6b 100644 --- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/AuthPickerView.swift +++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/AuthPickerView.swift @@ -35,6 +35,7 @@ public struct AuthPickerView { extension AuthPickerView: View { public var body: some View { @Bindable var authService = authService + @Bindable var passwordPrompt = authService.passwordPrompt content() .sheet(isPresented: $authService.isPresented) { @Bindable var navigator = authService.navigator @@ -77,6 +78,10 @@ extension AuthPickerView: View { ) .interactiveDismissDisabled(authService.configuration.interactiveDismissEnabled) } + // Centralized password prompt sheet to prevent conflicts + .sheet(isPresented: $passwordPrompt.isPromptingPassword) { + PasswordPromptSheet(coordinator: authService.passwordPrompt) + } // View-layer logic: Handle account conflicts (auto-handle anonymous upgrade, store others for // linking) .onChange(of: authService.currentAccountConflict) { _, conflict in diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/MFAManagementView.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/MFAManagementView.swift index fcbed901a8..bad44783cf 100644 --- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/MFAManagementView.swift +++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/MFAManagementView.swift @@ -55,7 +55,6 @@ public struct MFAManagementView { extension MFAManagementView: View { public var body: some View { - @Bindable var passwordPrompt = authService.passwordPrompt VStack(spacing: 20) { // Title section VStack { @@ -133,10 +132,7 @@ extension MFAManagementView: View { .onAppear { loadEnrolledFactors() } - // Present password prompt when required for reauthentication - .sheet(isPresented: $passwordPrompt.isPromptingPassword) { - PasswordPromptSheet(coordinator: authService.passwordPrompt) - } + // Password prompt sheet now centralized in AuthPickerView } @ViewBuilder diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/SignedInView.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/SignedInView.swift index ec986f5558..d66848437c 100644 --- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/SignedInView.swift +++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/SignedInView.swift @@ -38,7 +38,6 @@ public struct SignedInView { extension SignedInView: View { public var body: some View { - @Bindable var passwordPrompt = authService.passwordPrompt VStack { Text(authService.string.signedInTitle) .font(.largeTitle) @@ -144,9 +143,7 @@ extension SignedInView: View { ) .presentationDetents([.medium]) } - .sheet(isPresented: $passwordPrompt.isPromptingPassword) { - PasswordPromptSheet(coordinator: authService.passwordPrompt) - } + // Password prompt sheet now centralized in AuthPickerView .sheet(isPresented: $showEmailVerificationSent) { VStack(spacing: 24) { Text(authService.string.verifyEmailSheetMessage) From 205091dcf775f0641c1f03702c79fb762cb739d7 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 7 Nov 2025 09:10:03 +0000 Subject: [PATCH 04/12] test: use test helper for pasting into inputs --- .../MFAEnrolmentUITests.swift | 23 +++-------- .../TestUtils.swift | 40 +++++++++++++++++++ 2 files changed, 45 insertions(+), 18 deletions(-) diff --git a/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests/MFAEnrolmentUITests.swift b/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests/MFAEnrolmentUITests.swift index 9cbd80c226..b40ad3072e 100644 --- a/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests/MFAEnrolmentUITests.swift +++ b/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests/MFAEnrolmentUITests.swift @@ -211,17 +211,11 @@ final class MFAEnrollmentUITests: XCTestCase { let phoneField = app.textFields["phone-number-field"] XCTAssertTrue(phoneField.waitForExistence(timeout: 10)) let phoneNumberWithoutDialCode = "7444555666" - UIPasteboard.general.string = phoneNumberWithoutDialCode - phoneField.tap() - phoneField.press(forDuration: 1.2) - app.menuItems["Paste"].tap() + try pasteIntoField(phoneField, text: phoneNumberWithoutDialCode, app: app) let displayNameField = app.textFields["display-name-field"] XCTAssertTrue(displayNameField.waitForExistence(timeout: 10)) - UIPasteboard.general.string = "test user" - displayNameField.tap() - displayNameField.press(forDuration: 1.2) - app.menuItems["Paste"].tap() + try pasteIntoField(displayNameField, text: "test user", app: app) let sendCodeButton = app.buttons["send-sms-button"] XCTAssertTrue(sendCodeButton.waitForExistence(timeout: 10)) @@ -237,10 +231,7 @@ final class MFAEnrollmentUITests: XCTestCase { let fullPhoneNumber = "+44\(phoneNumberWithoutDialCode)" let code = try await getLastSmsCode(specificPhone: fullPhoneNumber) - UIPasteboard.general.string = code - verificationCodeField.tap() - verificationCodeField.press(forDuration: 1.2) - app.menuItems["Paste"].tap() + try pasteIntoField(verificationCodeField, text: code, app: app) // Test resend code button exists let resendButton = app.buttons["resend-code-button"] @@ -396,16 +387,12 @@ final class MFAEnrollmentUITests: XCTestCase { let emailField = app.textFields["email-field"] XCTAssertTrue(emailField.waitForExistence(timeout: 10), "Email field should exist") // Workaround for updating SecureFields with ConnectHardwareKeyboard enabled - UIPasteboard.general.string = email - emailField.press(forDuration: 1.2) - app.menuItems["Paste"].tap() + try pasteIntoField(emailField, text: email, app: app) // Fill password field let passwordField = app.secureTextFields["password-field"] XCTAssertTrue(passwordField.exists, "Password field should exist") - UIPasteboard.general.string = password - passwordField.press(forDuration: 1.2) - app.menuItems["Paste"].tap() + try pasteIntoField(passwordField, text: password, app: app) // Create the user (sign up) let signUpButton = app diff --git a/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests/TestUtils.swift b/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests/TestUtils.swift index 9a8c177ee0..7310243e87 100644 --- a/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests/TestUtils.swift +++ b/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests/TestUtils.swift @@ -29,6 +29,46 @@ func createEmail() -> String { } } +// MARK: - Text Input Helpers + +/// Pastes text into a text field using the system paste menu +/// - Parameters: +/// - field: The XCUIElement representing the text field +/// - text: The text to paste +/// - app: The XCUIApplication instance +@MainActor func pasteIntoField(_ field: XCUIElement, text: String, app: XCUIApplication) throws { + UIPasteboard.general.string = text + field.tap() + + // Give field time to become first responder + usleep(200_000) // 0.2 seconds + + // Press and hold to bring up paste menu + field.press(forDuration: 1.5) + + let pasteMenuItem = app.menuItems["Paste"] + + // Wait for paste menu to appear + if !pasteMenuItem.waitForExistence(timeout: 3) { + // Fallback: try double tap approach + field.doubleTap() + usleep(300_000) // 0.3 seconds + + if !pasteMenuItem.waitForExistence(timeout: 2) { + throw NSError( + domain: "TestError", + code: 1, + userInfo: [ + NSLocalizedDescriptionKey: "Failed to show paste menu for field. Text was: \(text)" + ] + ) + } + } + + pasteMenuItem.tap() + +} + // MARK: - User Creation /// Helper to create a test user in the emulator via REST API (avoids keychain issues) From 845b966aeba650a503560355c50ffc3b0aa3bb46 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 7 Nov 2025 09:10:15 +0000 Subject: [PATCH 05/12] format --- .../FirebaseSwiftUIExampleUITests/TestUtils.swift | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests/TestUtils.swift b/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests/TestUtils.swift index 7310243e87..3c76f3daf2 100644 --- a/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests/TestUtils.swift +++ b/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests/TestUtils.swift @@ -39,34 +39,33 @@ func createEmail() -> String { @MainActor func pasteIntoField(_ field: XCUIElement, text: String, app: XCUIApplication) throws { UIPasteboard.general.string = text field.tap() - + // Give field time to become first responder usleep(200_000) // 0.2 seconds - + // Press and hold to bring up paste menu field.press(forDuration: 1.5) - + let pasteMenuItem = app.menuItems["Paste"] - + // Wait for paste menu to appear if !pasteMenuItem.waitForExistence(timeout: 3) { // Fallback: try double tap approach field.doubleTap() usleep(300_000) // 0.3 seconds - + if !pasteMenuItem.waitForExistence(timeout: 2) { throw NSError( domain: "TestError", code: 1, userInfo: [ - NSLocalizedDescriptionKey: "Failed to show paste menu for field. Text was: \(text)" + NSLocalizedDescriptionKey: "Failed to show paste menu for field. Text was: \(text)", ] ) } } - + pasteMenuItem.tap() - } // MARK: - User Creation From c25d82d7283662fb82b22a62f03c98a22a00068e Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 7 Nov 2025 09:20:51 +0000 Subject: [PATCH 06/12] fix: lint errors --- .../Sources/Views/EnterVerificationCodeView.swift | 2 +- .../Sources/Views/PrivacyTOCsView.swift | 4 ++-- .../FirebaseAuthSwiftUITests/MFAEnrolmentUnitTests.swift | 6 +++--- .../Sources/Components/AuthTextField.swift | 8 ++++---- .../Sources/Components/CountrySelector.swift | 2 +- .../Sources/Components/VerificationCodeInputField.swift | 9 ++++----- .../Sources/Theme/ProviderStyle.swift | 2 +- 7 files changed, 16 insertions(+), 17 deletions(-) diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/EnterVerificationCodeView.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/EnterVerificationCodeView.swift index 3b90934447..b9aa70a1ae 100644 --- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/EnterVerificationCodeView.swift +++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/EnterVerificationCodeView.swift @@ -95,7 +95,7 @@ struct EnterVerificationCodeView: View { return NavigationStack { EnterVerificationCodeView( verificationID: "mock-id", - fullPhoneNumber: "+1 5551234567", + fullPhoneNumber: "+1 5551234567" ) .environment(AuthService()) } diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/PrivacyTOCsView.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/PrivacyTOCsView.swift index d8a4da1be1..f5e6bfb638 100644 --- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/PrivacyTOCsView.swift +++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/PrivacyTOCsView.swift @@ -30,7 +30,7 @@ struct PrivacyTOCsView { let displayMode: DisplayMode - public init(displayMode: DisplayMode = .full) { + init(displayMode: DisplayMode = .full) { self.displayMode = displayMode } @@ -61,7 +61,7 @@ struct PrivacyTOCsView { } extension PrivacyTOCsView: View { - public var body: some View { + var body: some View { Group { if let tosURL = authService.configuration.tosUrl, let privacyURL = authService.configuration.privacyPolicyUrl { diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Tests/FirebaseAuthSwiftUITests/MFAEnrolmentUnitTests.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Tests/FirebaseAuthSwiftUITests/MFAEnrolmentUnitTests.swift index c4564dc308..aa9d53beef 100644 --- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Tests/FirebaseAuthSwiftUITests/MFAEnrolmentUnitTests.swift +++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Tests/FirebaseAuthSwiftUITests/MFAEnrolmentUnitTests.swift @@ -29,7 +29,7 @@ import Testing @Suite("TOTPEnrollmentInfo Tests") struct TOTPEnrollmentInfoTests { @Test("Initialization with shared secret key") - func testInitializationWithSharedSecretKey() { + func initializationWithSharedSecretKey() { let validSecrets = [ "JBSWY3DPEHPK3PXP", "GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ", @@ -47,7 +47,7 @@ struct TOTPEnrollmentInfoTests { } @Test("Initialization with all parameters") - func testInitializationWithAllParameters() throws { + func initializationWithAllParameters() throws { let totpInfo = TOTPEnrollmentInfo( sharedSecretKey: "JBSWY3DPEHPK3PXP", qrCodeURL: URL( @@ -71,7 +71,7 @@ struct TOTPEnrollmentInfoTests { } @Test("Verification status transitions") - func testVerificationStatusTransitions() { + func verificationStatusTransitions() { // Default status is pending var totpInfo = TOTPEnrollmentInfo(sharedSecretKey: "JBSWY3DPEHPK3PXP") #expect(totpInfo.verificationStatus == .pending) diff --git a/FirebaseSwiftUI/FirebaseAuthUIComponents/Sources/Components/AuthTextField.swift b/FirebaseSwiftUI/FirebaseAuthUIComponents/Sources/Components/AuthTextField.swift index 628dd15462..cd10475aaa 100644 --- a/FirebaseSwiftUI/FirebaseAuthUIComponents/Sources/Components/AuthTextField.swift +++ b/FirebaseSwiftUI/FirebaseAuthUIComponents/Sources/Components/AuthTextField.swift @@ -35,12 +35,12 @@ public struct AuthTextField: View { let prompt: String var textAlignment: TextAlignment = .leading var keyboardType: UIKeyboardType = .default - var contentType: UITextContentType? = nil + var contentType: UITextContentType? var isSecureTextField: Bool = false var validations: [FieldValidation] = [] - var formState: ((Bool) -> Void)? = nil - var onSubmit: ((String) -> Void)? = nil - var onChange: ((String) -> Void)? = nil + var formState: ((Bool) -> Void)? + var onSubmit: ((String) -> Void)? + var onChange: ((String) -> Void)? private let leading: () -> Leading? public init(text: Binding, diff --git a/FirebaseSwiftUI/FirebaseAuthUIComponents/Sources/Components/CountrySelector.swift b/FirebaseSwiftUI/FirebaseAuthUIComponents/Sources/Components/CountrySelector.swift index 49d94e9fe1..050d3c37ce 100644 --- a/FirebaseSwiftUI/FirebaseAuthUIComponents/Sources/Components/CountrySelector.swift +++ b/FirebaseSwiftUI/FirebaseAuthUIComponents/Sources/Components/CountrySelector.swift @@ -44,7 +44,7 @@ public struct CountryData: Equatable { public struct CountrySelector: View { @Binding var selectedCountry: CountryData var enabled: Bool = true - var allowedCountries: Set? = nil + var allowedCountries: Set? public init(selectedCountry: Binding, enabled: Bool = true, diff --git a/FirebaseSwiftUI/FirebaseAuthUIComponents/Sources/Components/VerificationCodeInputField.swift b/FirebaseSwiftUI/FirebaseAuthUIComponents/Sources/Components/VerificationCodeInputField.swift index fc99208c02..e0609efa33 100644 --- a/FirebaseSwiftUI/FirebaseAuthUIComponents/Sources/Components/VerificationCodeInputField.swift +++ b/FirebaseSwiftUI/FirebaseAuthUIComponents/Sources/Components/VerificationCodeInputField.swift @@ -116,7 +116,7 @@ public struct VerificationCodeInputField: View { commitCodeChange(truncated) } - if shouldUpdateFocus && (fieldsChanged || forceFocus) { + if shouldUpdateFocus, fieldsChanged || forceFocus { let newFocus = truncated.count < codeLength ? truncated.count : nil DispatchQueue.main.async { withAnimation(.easeInOut(duration: 0.2)) { @@ -125,7 +125,7 @@ public struct VerificationCodeInputField: View { } } - if fieldsChanged && truncated.count == codeLength { + if fieldsChanged, truncated.count == codeLength { DispatchQueue.main.async { onCodeComplete(truncated) } @@ -169,8 +169,7 @@ public struct VerificationCodeInputField: View { commitCodeChange(newCode) onCodeChange(newCode) - if !digit.isEmpty, - let nextIndex = findNextEmptyField(startingFrom: index) { + if !digit.isEmpty, let nextIndex = findNextEmptyField(startingFrom: index) { DispatchQueue.main.async { if focusedIndex != nextIndex { withAnimation(.easeInOut(duration: 0.2)) { @@ -189,7 +188,7 @@ public struct VerificationCodeInputField: View { private func handleBackspace(at index: Int) { // If current field is empty, move to previous field and clear it - if digitFields[index].isEmpty && index > 0 { + if digitFields[index].isEmpty, index > 0 { digitFields[index - 1] = "" DispatchQueue.main.async { let previousIndex = index - 1 diff --git a/FirebaseSwiftUI/FirebaseAuthUIComponents/Sources/Theme/ProviderStyle.swift b/FirebaseSwiftUI/FirebaseAuthUIComponents/Sources/Theme/ProviderStyle.swift index a26a42b9c3..e33c3f8694 100644 --- a/FirebaseSwiftUI/FirebaseAuthUIComponents/Sources/Theme/ProviderStyle.swift +++ b/FirebaseSwiftUI/FirebaseAuthUIComponents/Sources/Theme/ProviderStyle.swift @@ -31,7 +31,7 @@ public struct ProviderStyle: Sendable { public let icon: Image? public let backgroundColor: Color public let contentColor: Color - public var iconTint: Color? = nil + public var iconTint: Color? public let shape: AnyShape = .init(RoundedRectangle(cornerRadius: 4, style: .continuous)) public let elevation: CGFloat From 78b9905bc975335e8945ff810fc40ec95e3c2776 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 7 Nov 2025 09:47:57 +0000 Subject: [PATCH 07/12] fix: more lint errors --- .../FirebaseSwiftUIExampleTests.swift | 8 ++++---- .../MFAResolutionUITests.swift | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleTests/FirebaseSwiftUIExampleTests.swift b/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleTests/FirebaseSwiftUIExampleTests.swift index 8c4dd7cb49..3fb4c90857 100644 --- a/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleTests/FirebaseSwiftUIExampleTests.swift +++ b/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleTests/FirebaseSwiftUIExampleTests.swift @@ -40,7 +40,7 @@ struct FirebaseSwiftUIExampleTests { @Test @MainActor - func testDefaultAuthConfigurationInjection() async throws { + func defaultAuthConfigurationInjection() async throws { let config = AuthConfiguration() let service = AuthService(configuration: config) @@ -58,7 +58,7 @@ struct FirebaseSwiftUIExampleTests { @Test @MainActor - func testCustomAuthConfigurationInjection() async throws { + func customAuthConfigurationInjection() async throws { let emailSettings = ActionCodeSettings() emailSettings.handleCodeInApp = true emailSettings.url = URL(string: "https://example.com/email-link") @@ -97,7 +97,7 @@ struct FirebaseSwiftUIExampleTests { @Test @MainActor - func testCreateEmailPasswordUser() async throws { + func createEmailPasswordUser() async throws { let service = try await prepareFreshAuthService() #expect(service.authenticationState == .unauthenticated) @@ -119,7 +119,7 @@ struct FirebaseSwiftUIExampleTests { @Test @MainActor - func testSignInUser() async throws { + func signInUser() async throws { let service = try await prepareFreshAuthService() let email = createEmail() try await service.createUser(email: email, password: kPassword) diff --git a/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests/MFAResolutionUITests.swift b/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests/MFAResolutionUITests.swift index e1026b2663..12ce4235e4 100644 --- a/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests/MFAResolutionUITests.swift +++ b/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests/MFAResolutionUITests.swift @@ -71,7 +71,7 @@ final class MFAResolutionUITests: XCTestCase { ) let smsButton = app.buttons["sms-method-button"] - if smsButton.exists && smsButton.isEnabled { + if smsButton.exists, smsButton.isEnabled { smsButton.tap() } dismissAlert(app: app) @@ -421,7 +421,7 @@ final class MFAResolutionUITests: XCTestCase { password: String = "123456") throws { // Ensure we're in sign in flow let switchFlowButton = app.buttons["switch-auth-flow"] - if switchFlowButton.exists && switchFlowButton.label.contains("Sign In") { + if switchFlowButton.exists, switchFlowButton.label.contains("Sign In") { switchFlowButton.tap() } From b161bd230eaa70dd880f4f13dc79e6273ede0e5d Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 7 Nov 2025 12:41:42 +0000 Subject: [PATCH 08/12] format --- .../Sources/Views/EmailAuthView.swift | 4 ++-- .../Sources/Views/EmailLinkView.swift | 2 +- .../Sources/Views/EnterPhoneNumberView.swift | 2 +- .../Sources/Views/EnterVerificationCodeView.swift | 2 +- .../Sources/Views/MFAEnrolmentView.swift | 10 +++++----- .../Sources/Views/PasswordRecoveryView.swift | 2 +- .../Sources/Views/UpdatePasswordView.swift | 8 +++----- .../Sources/Components/AuthTextField.swift | 11 ++++++----- .../Components/VerificationCodeInputField.swift | 3 ++- .../Sources/Validation/FormValidator.swift | 3 ++- .../FirebaseSwiftUIExample/App/ContentView.swift | 2 +- 11 files changed, 25 insertions(+), 24 deletions(-) diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/EmailAuthView.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/EmailAuthView.swift index 35608ca6fb..b0c88a2ab3 100644 --- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/EmailAuthView.swift +++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/EmailAuthView.swift @@ -111,7 +111,7 @@ extension EmailAuthView: View { keyboardType: .emailAddress, contentType: .emailAddress, validations: [ - FormValidators.email + FormValidators.email, ], maintainsValidationMessage: authService.authenticationFlow == .signUp, onSubmit: { _ in @@ -161,7 +161,7 @@ extension EmailAuthView: View { contentType: .password, isSecureTextField: true, validations: [ - FormValidators.confirmPassword(password: password) + FormValidators.confirmPassword(password: password), ], maintainsValidationMessage: true, onSubmit: { _ in diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/EmailLinkView.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/EmailLinkView.swift index 2bcf12969f..2eca4ec7c5 100644 --- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/EmailLinkView.swift +++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/EmailLinkView.swift @@ -50,7 +50,7 @@ extension EmailLinkView: View { keyboardType: .emailAddress, contentType: .emailAddress, validations: [ - FormValidators.email + FormValidators.email, ], leading: { Image(systemName: "at") diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/EnterPhoneNumberView.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/EnterPhoneNumberView.swift index 27189e5994..95152335ca 100644 --- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/EnterPhoneNumberView.swift +++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/EnterPhoneNumberView.swift @@ -39,7 +39,7 @@ struct EnterPhoneNumberView: View { keyboardType: .phonePad, contentType: .telephoneNumber, validations: [ - FormValidators.phoneNumber + FormValidators.phoneNumber, ], onChange: { _ in } ) { diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/EnterVerificationCodeView.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/EnterVerificationCodeView.swift index b2a488403d..74713990ee 100644 --- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/EnterVerificationCodeView.swift +++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/EnterVerificationCodeView.swift @@ -52,7 +52,7 @@ struct EnterVerificationCodeView: View { VerificationCodeInputField( code: $verificationCode, validations: [ - FormValidators.verificationCode + FormValidators.verificationCode, ], maintainsValidationMessage: true ) diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/MFAEnrolmentView.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/MFAEnrolmentView.swift index f195edabb7..64a2399805 100644 --- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/MFAEnrolmentView.swift +++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/MFAEnrolmentView.swift @@ -375,7 +375,7 @@ extension MFAEnrolmentView: View { keyboardType: .phonePad, contentType: .telephoneNumber, validations: [ - FormValidators.phoneNumber + FormValidators.phoneNumber, ], maintainsValidationMessage: true, onChange: { _ in } @@ -393,7 +393,7 @@ extension MFAEnrolmentView: View { label: authService.string.displayNameFieldLabel, prompt: authService.string.enterDisplayNameForDevicePrompt, validations: [ - FormValidators.notEmpty(label: "Display name") + FormValidators.notEmpty(label: "Display name"), ], maintainsValidationMessage: true, leading: { @@ -441,7 +441,7 @@ extension MFAEnrolmentView: View { VerificationCodeInputField( code: $verificationCode, validations: [ - FormValidators.verificationCode + FormValidators.verificationCode, ], maintainsValidationMessage: true ) @@ -584,7 +584,7 @@ extension MFAEnrolmentView: View { label: authService.string.displayNameFieldLabel, prompt: authService.string.enterDisplayNameForAuthenticatorPrompt, validations: [ - FormValidators.notEmpty(label: "Display name") + FormValidators.notEmpty(label: "Display name"), ], maintainsValidationMessage: true, leading: { @@ -596,7 +596,7 @@ extension MFAEnrolmentView: View { VerificationCodeInputField( code: $totpCode, validations: [ - FormValidators.verificationCode + FormValidators.verificationCode, ], maintainsValidationMessage: true ) diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/PasswordRecoveryView.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/PasswordRecoveryView.swift index b8b1692f1c..8b3a496d43 100644 --- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/PasswordRecoveryView.swift +++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/PasswordRecoveryView.swift @@ -45,7 +45,7 @@ extension PasswordRecoveryView: View { keyboardType: .emailAddress, contentType: .emailAddress, validations: [ - FormValidators.email + FormValidators.email, ], leading: { Image(systemName: "at") diff --git a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/UpdatePasswordView.swift b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/UpdatePasswordView.swift index 474e041567..d6ff5eea26 100644 --- a/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/UpdatePasswordView.swift +++ b/FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/UpdatePasswordView.swift @@ -46,9 +46,7 @@ public struct UpdatePasswordView { do { try await authService.updatePassword(to: confirmPassword) showAlert = true - } catch { - - } + } catch {} } } } @@ -64,7 +62,7 @@ extension UpdatePasswordView: View { contentType: .password, isSecureTextField: true, validations: [ - FormValidators.atLeast6Characters + FormValidators.atLeast6Characters, ], maintainsValidationMessage: true, leading: { @@ -81,7 +79,7 @@ extension UpdatePasswordView: View { contentType: .password, isSecureTextField: true, validations: [ - FormValidators.confirmPassword(password: password) + FormValidators.confirmPassword(password: password), ], maintainsValidationMessage: true, leading: { diff --git a/FirebaseSwiftUI/FirebaseAuthUIComponents/Sources/Components/AuthTextField.swift b/FirebaseSwiftUI/FirebaseAuthUIComponents/Sources/Components/AuthTextField.swift index 5e4a398ce3..8e96711399 100644 --- a/FirebaseSwiftUI/FirebaseAuthUIComponents/Sources/Components/AuthTextField.swift +++ b/FirebaseSwiftUI/FirebaseAuthUIComponents/Sources/Components/AuthTextField.swift @@ -18,7 +18,7 @@ public struct AuthTextField: View { @FocusState private var isFocused: Bool @State var obscured: Bool = true @State var hasInteracted: Bool = false - + @Binding var text: String let label: String let prompt: String @@ -32,7 +32,7 @@ public struct AuthTextField: View { var onSubmit: ((String) -> Void)? var onChange: ((String) -> Void)? private let leading: () -> Leading? - + public init(text: Binding, label: String, prompt: String, @@ -60,11 +60,11 @@ public struct AuthTextField: View { self.onChange = onChange self.leading = leading } - + var allRequirementsMet: Bool { validations.allSatisfy { $0.isValid(input: text) } } - + public var body: some View { VStack(alignment: .leading) { Text(LocalizedStringResource(stringLiteral: label)) @@ -142,7 +142,8 @@ public struct AuthTextField: View { isFocused = true } } - if !validations.isEmpty && hasInteracted && (maintainsValidationMessage || !allRequirementsMet) { + if !validations + .isEmpty && hasInteracted && (maintainsValidationMessage || !allRequirementsMet) { VStack(alignment: .leading, spacing: 4) { ForEach(validations) { validator in let isValid = validator.isValid(input: text) diff --git a/FirebaseSwiftUI/FirebaseAuthUIComponents/Sources/Components/VerificationCodeInputField.swift b/FirebaseSwiftUI/FirebaseAuthUIComponents/Sources/Components/VerificationCodeInputField.swift index 0b4b6accf7..cc226a33c3 100644 --- a/FirebaseSwiftUI/FirebaseAuthUIComponents/Sources/Components/VerificationCodeInputField.swift +++ b/FirebaseSwiftUI/FirebaseAuthUIComponents/Sources/Components/VerificationCodeInputField.swift @@ -96,7 +96,8 @@ public struct VerificationCodeInputField: View { .frame(maxWidth: .infinity, alignment: .leading) } - if !validations.isEmpty && hasInteracted && (maintainsValidationMessage || !allRequirementsMet) { + if !validations + .isEmpty && hasInteracted && (maintainsValidationMessage || !allRequirementsMet) { VStack(alignment: .leading, spacing: 4) { ForEach(validations) { validator in let isValid = validator.isValid(input: code) diff --git a/FirebaseSwiftUI/FirebaseAuthUIComponents/Sources/Validation/FormValidator.swift b/FirebaseSwiftUI/FirebaseAuthUIComponents/Sources/Validation/FormValidator.swift index de0bdd61ec..9326fc76d9 100644 --- a/FirebaseSwiftUI/FirebaseAuthUIComponents/Sources/Validation/FormValidator.swift +++ b/FirebaseSwiftUI/FirebaseAuthUIComponents/Sources/Validation/FormValidator.swift @@ -41,7 +41,8 @@ public struct FormValidators { } ) - public static func confirmPassword(password: @autoclosure @escaping () -> String) -> FormValidator { + public static func confirmPassword(password: @autoclosure @escaping () -> String) + -> FormValidator { return FormValidator( message: "Passwords must match", validate: { input in diff --git a/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample/App/ContentView.swift b/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample/App/ContentView.swift index 808f825e4a..6e5ec14310 100644 --- a/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample/App/ContentView.swift +++ b/samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExample/App/ContentView.swift @@ -34,7 +34,7 @@ struct ContentView: View { init() { Auth.auth().useEmulator(withHost: "127.0.0.1", port: 9099) Auth.auth().settings?.isAppVerificationDisabledForTesting = true - //Auth.auth().signInAnonymously() + // Auth.auth().signInAnonymously() let actionCodeSettings = ActionCodeSettings() actionCodeSettings.handleCodeInApp = true actionCodeSettings.url = URL(string: "https://flutterfire-e2e-tests.firebaseapp.com") From 591ea244d415733427db6f123e1a67c0ad689dad Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 7 Nov 2025 12:53:32 +0000 Subject: [PATCH 09/12] chore: use script so it can be ran locally --- .github/workflows/swiftui-auth.yml | 10 ++-------- lint-swift.sh | 5 +++++ 2 files changed, 7 insertions(+), 8 deletions(-) create mode 100755 lint-swift.sh diff --git a/.github/workflows/swiftui-auth.yml b/.github/workflows/swiftui-auth.yml index 9aa669b0c1..309f5f7cad 100644 --- a/.github/workflows/swiftui-auth.yml +++ b/.github/workflows/swiftui-auth.yml @@ -32,14 +32,8 @@ jobs: - name: Install swiftformat run: brew install swiftformat - - name: Check FirebaseSwiftUI formatting - run: swiftformat --lint ./FirebaseSwiftUI - - - name: Check SwiftUI Example formatting - run: swiftformat --lint ./samples/swiftui/FirebaseSwiftUIExample - - - name: Check Package.swift formatting - run: swiftformat --lint ./Package.swift + - name: Check Swift formatting + run: bash lint-swift.sh # Package Unit Tests (standalone, no emulator needed) unit-tests: diff --git a/lint-swift.sh b/lint-swift.sh new file mode 100755 index 0000000000..8adfd01705 --- /dev/null +++ b/lint-swift.sh @@ -0,0 +1,5 @@ +swiftformat --lint ./FirebaseSwiftUI + +swiftformat --lint ./samples/swiftui/FirebaseSwiftUIExample + +swiftformat --lint ./Package.swift \ No newline at end of file From 53c11d70b81aaa07c87014a08f141a894a8039d8 Mon Sep 17 00:00:00 2001 From: Russell Wheatley Date: Fri, 7 Nov 2025 14:04:32 +0000 Subject: [PATCH 10/12] Increase timeout for UI tests to 60 minutes --- .github/workflows/swiftui-auth.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/swiftui-auth.yml b/.github/workflows/swiftui-auth.yml index 309f5f7cad..8103692e88 100644 --- a/.github/workflows/swiftui-auth.yml +++ b/.github/workflows/swiftui-auth.yml @@ -142,7 +142,7 @@ jobs: ui-tests: name: UI Tests runs-on: macos-26 - timeout-minutes: 40 + timeout-minutes: 60 steps: - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 @@ -204,4 +204,4 @@ jobs: uses: actions/upload-artifact@v4 with: name: FirebaseSwiftUIExampleUITests.xcresult - path: samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests.xcresult \ No newline at end of file + path: samples/swiftui/FirebaseSwiftUIExample/FirebaseSwiftUIExampleUITests.xcresult From f4d8abc4292221e01cfa51cfcd869fae4d8012c1 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Mon, 10 Nov 2025 15:25:50 +0000 Subject: [PATCH 11/12] chore: add e2eTests to format and lint script --- format-swift.sh | 4 +++- lint-swift.sh | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/format-swift.sh b/format-swift.sh index 0aec197f4a..50777451a5 100755 --- a/format-swift.sh +++ b/format-swift.sh @@ -4,5 +4,7 @@ swiftformat ./FirebaseSwiftUI swiftformat ./samples/swiftui/FirebaseSwiftUIExample +swiftformat ./e2eTest + +swiftformat ./Package.swift -swiftformat ./Package.swift \ No newline at end of file diff --git a/lint-swift.sh b/lint-swift.sh index 8adfd01705..eecd5d1b8f 100755 --- a/lint-swift.sh +++ b/lint-swift.sh @@ -2,4 +2,6 @@ swiftformat --lint ./FirebaseSwiftUI swiftformat --lint ./samples/swiftui/FirebaseSwiftUIExample +swiftformat --lint ./e2eTest + swiftformat --lint ./Package.swift \ No newline at end of file From a0c9add9444bfc56a844b57538231e1df312f019 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Mon, 10 Nov 2025 15:27:24 +0000 Subject: [PATCH 12/12] ci: decrease timeout to 40 mins for UI tests --- .github/workflows/swiftui-auth.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/swiftui-auth.yml b/.github/workflows/swiftui-auth.yml index 1701e9de12..b19fcba95d 100644 --- a/.github/workflows/swiftui-auth.yml +++ b/.github/workflows/swiftui-auth.yml @@ -144,7 +144,7 @@ jobs: ui-tests: name: UI Tests runs-on: macos-26 - timeout-minutes: 60 + timeout-minutes: 40 steps: - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938