From 4f7dd49a2d57b4106655fc559d9de15fab5301af Mon Sep 17 00:00:00 2001 From: Peter Friese Date: Sun, 28 Sep 2025 10:46:54 +0200 Subject: [PATCH 1/5] Add support for AsyncStream / AsyncSequence for Firestore --- Firestore/Swift/CHANGELOG.md | 5 + .../DocumentReference+AsyncSequence.swift | 51 +++++ .../AsyncAwait/Query+AsyncSequence.swift | 51 +++++ .../Unit/AsyncAwait/AsyncSequenceTests.swift | 183 ++++++++++++++++++ 4 files changed, 290 insertions(+) create mode 100644 Firestore/Swift/Source/AsyncAwait/DocumentReference+AsyncSequence.swift create mode 100644 Firestore/Swift/Source/AsyncAwait/Query+AsyncSequence.swift create mode 100644 Firestore/Swift/Tests/Unit/AsyncAwait/AsyncSequenceTests.swift diff --git a/Firestore/Swift/CHANGELOG.md b/Firestore/Swift/CHANGELOG.md index 742402f80eb..2f38e396ba4 100644 --- a/Firestore/Swift/CHANGELOG.md +++ b/Firestore/Swift/CHANGELOG.md @@ -1,3 +1,8 @@ +# Unreleased +- [added] Added `AsyncSequence` support for `Query.snapshots` and + `DocumentReference.snapshots`, providing a modern, structured-concurrency + alternative to `addSnapshotListener`. + # 10.17.0 - [deprecated] All of the public API from `FirebaseFirestoreSwift` can now be accessed through the `FirebaseFirestore` module. Therefore, diff --git a/Firestore/Swift/Source/AsyncAwait/DocumentReference+AsyncSequence.swift b/Firestore/Swift/Source/AsyncAwait/DocumentReference+AsyncSequence.swift new file mode 100644 index 00000000000..4bde70b8c93 --- /dev/null +++ b/Firestore/Swift/Source/AsyncAwait/DocumentReference+AsyncSequence.swift @@ -0,0 +1,51 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#if SWIFT_PACKAGE + @_exported import FirebaseFirestoreInternalWrapper +#else + @_exported import FirebaseFirestoreInternal +#endif // SWIFT_PACKAGE +import Foundation + +@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) +public extension DocumentReference { + /// An asynchronous sequence of document snapshots. + /// + /// This stream emits a new `DocumentSnapshot` every time the underlying data changes. + var snapshots: AsyncThrowingStream { + return snapshots(includeMetadataChanges: false) + } + + /// An asynchronous sequence of document snapshots. + /// + /// - Parameter includeMetadataChanges: Whether to receive events for metadata-only changes. + /// - Returns: An `AsyncThrowingStream` of `DocumentSnapshot` events. + func snapshots(includeMetadataChanges: Bool) -> AsyncThrowingStream { + return AsyncThrowingStream { continuation in + let listener = self.addSnapshotListener(includeMetadataChanges: includeMetadataChanges) { snapshot, error in + if let error = error { + continuation.finish(throwing: error) + } else if let snapshot = snapshot { + continuation.yield(snapshot) + } + } + continuation.onTermination = { @Sendable _ in + listener.remove() + } + } + } +} diff --git a/Firestore/Swift/Source/AsyncAwait/Query+AsyncSequence.swift b/Firestore/Swift/Source/AsyncAwait/Query+AsyncSequence.swift new file mode 100644 index 00000000000..0d2901ed0d1 --- /dev/null +++ b/Firestore/Swift/Source/AsyncAwait/Query+AsyncSequence.swift @@ -0,0 +1,51 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#if SWIFT_PACKAGE + @_exported import FirebaseFirestoreInternalWrapper +#else + @_exported import FirebaseFirestoreInternal +#endif // SWIFT_PACKAGE +import Foundation + +@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) +public extension Query { + /// An asynchronous sequence of query snapshots. + /// + /// This stream emits a new `QuerySnapshot` every time the underlying data changes. + var snapshots: AsyncThrowingStream { + return snapshots(includeMetadataChanges: false) + } + + /// An asynchronous sequence of query snapshots. + /// + /// - Parameter includeMetadataChanges: Whether to receive events for metadata-only changes. + /// - Returns: An `AsyncThrowingStream` of `QuerySnapshot` events. + func snapshots(includeMetadataChanges: Bool) -> AsyncThrowingStream { + return AsyncThrowingStream { continuation in + let listener = self.addSnapshotListener(includeMetadataChanges: includeMetadataChanges) { snapshot, error in + if let error = error { + continuation.finish(throwing: error) + } else if let snapshot = snapshot { + continuation.yield(snapshot) + } + } + continuation.onTermination = { @Sendable _ in + listener.remove() + } + } + } +} diff --git a/Firestore/Swift/Tests/Unit/AsyncAwait/AsyncSequenceTests.swift b/Firestore/Swift/Tests/Unit/AsyncAwait/AsyncSequenceTests.swift new file mode 100644 index 00000000000..ae1a9d9b159 --- /dev/null +++ b/Firestore/Swift/Tests/Unit/AsyncAwait/AsyncSequenceTests.swift @@ -0,0 +1,183 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import XCTest +@testable import FirebaseFirestore + +// MARK: - Mock Objects for Testing + +private class MockListenerRegistration: ListenerRegistration { + var isRemoved = false + func remove() { + isRemoved = true + } +} + +private typealias SnapshotListener = (QuerySnapshot?, Error?) -> Void +private typealias DocumentSnapshotListener = (DocumentSnapshot?, Error?) -> Void + +private class MockQuery: Query { + var capturedListener: SnapshotListener? + let mockListenerRegistration = MockListenerRegistration() + + override func addSnapshotListener( + _ listener: @escaping SnapshotListener + ) -> ListenerRegistration { + capturedListener = listener + return mockListenerRegistration + } + + override func addSnapshotListener( + includeMetadataChanges: Bool, + listener: @escaping SnapshotListener + ) -> ListenerRegistration { + capturedListener = listener + return mockListenerRegistration + } +} + +private class MockDocumentReference: DocumentReference { + var capturedListener: DocumentSnapshotListener? + let mockListenerRegistration = MockListenerRegistration() + + override func addSnapshotListener( + _ listener: @escaping DocumentSnapshotListener + ) -> ListenerRegistration { + capturedListener = listener + return mockListenerRegistration + } + + override func addSnapshotListener( + includeMetadataChanges: Bool, + listener: @escaping DocumentSnapshotListener + ) -> ListenerRegistration { + capturedListener = listener + return mockListenerRegistration + } +} + +// MARK: - AsyncSequenceTests + +@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) +class AsyncSequenceTests: XCTestCase { + func testQuerySnapshotsYieldsValues() async throws { + let mockQuery = MockQuery() + let expectation = XCTestExpectation(description: "Received snapshot") + + let task = Task { + for try await _ in mockQuery.snapshots { + expectation.fulfill() + break // Exit after first result + } + } + + // Ensure the listener has been set up + XCTAssertNotNil(mockQuery.capturedListener) + + // Simulate a snapshot event + mockQuery.capturedListener?(QuerySnapshot(), nil) + + await fulfillment(of: [expectation], timeout: 1.0) + task.cancel() + } + + func testQuerySnapshotsThrowsErrors() async throws { + let mockQuery = MockQuery() + let expectedError = NSError(domain: "TestError", code: 123, userInfo: nil) + var receivedError: Error? + + let task = Task { + do { + for try await _ in mockQuery.snapshots { + XCTFail("Should not have received a value.") + } + } catch { + receivedError = error + } + } + + // Ensure the listener has been set up + XCTAssertNotNil(mockQuery.capturedListener) + + // Simulate an error event + mockQuery.capturedListener?(nil, expectedError) + + // Allow the task to process the error + try await Task.sleep(nanoseconds: 100_000_000) + + XCTAssertNotNil(receivedError) + XCTAssertEqual((receivedError as NSError?)?.domain, expectedError.domain) + XCTAssertEqual((receivedError as NSError?)?.code, expectedError.code) + task.cancel() + } + + func testQuerySnapshotsCancellationRemovesListener() async throws { + let mockQuery = MockQuery() + + let task = Task { + for try await _ in mockQuery.snapshots { + XCTFail("Should not receive any values as the task is cancelled immediately.") + } + } + + // Ensure the listener was attached before we cancel + XCTAssertNotNil(mockQuery.capturedListener) + XCTAssertFalse(mockQuery.mockListenerRegistration.isRemoved) + + task.cancel() + + // Allow time for the cancellation handler to execute + try await Task.sleep(nanoseconds: 100_000_000) + + XCTAssertTrue(mockQuery.mockListenerRegistration.isRemoved) + } + + func testDocumentReferenceSnapshotsYieldsValues() async throws { + let mockDocRef = MockDocumentReference() + let expectation = XCTestExpectation(description: "Received document snapshot") + + let task = Task { + for try await _ in mockDocRef.snapshots { + expectation.fulfill() + break + } + } + + XCTAssertNotNil(mockDocRef.capturedListener) + mockDocRef.capturedListener?(DocumentSnapshot(), nil) + + await fulfillment(of: [expectation], timeout: 1.0) + task.cancel() + } + + func testDocumentReferenceSnapshotsCancellationRemovesListener() async throws { + let mockDocRef = MockDocumentReference() + + let task = Task { + for try await _ in mockDocRef.snapshots { + XCTFail("Should not receive values.") + } + } + + XCTAssertNotNil(mockDocRef.capturedListener) + XCTAssertFalse(mockDocRef.mockListenerRegistration.isRemoved) + + task.cancel() + try await Task.sleep(nanoseconds: 100_000_000) + + XCTAssertTrue(mockDocRef.mockListenerRegistration.isRemoved) + } +} \ No newline at end of file From 3c84e74542f5aa646d5a75b9f963ad5954e27374 Mon Sep 17 00:00:00 2001 From: Peter Friese Date: Sun, 28 Sep 2025 16:33:57 +0200 Subject: [PATCH 2/5] Add AsyncSequence support for Firestore This change introduces support for `AsyncSequence` to `DocumentReference` and `Query`, allowing developers to use `for await in` to receive real-time updates from Firestore. The implementation wraps the existing snapshot listener APIs in an `AsyncThrowingStream`, providing a modern, Swift-native way to work with real-time data. This change also includes unit tests for the new functionality. --- .../DocumentReference+AsyncSequence.swift | 13 ++++----- .../AsyncAwait/Query+AsyncSequence.swift | 13 ++++----- .../Unit/AsyncAwait/AsyncSequenceTests.swift | 27 ++++++++----------- 3 files changed, 25 insertions(+), 28 deletions(-) diff --git a/Firestore/Swift/Source/AsyncAwait/DocumentReference+AsyncSequence.swift b/Firestore/Swift/Source/AsyncAwait/DocumentReference+AsyncSequence.swift index 4bde70b8c93..3ecf5d5203c 100644 --- a/Firestore/Swift/Source/AsyncAwait/DocumentReference+AsyncSequence.swift +++ b/Firestore/Swift/Source/AsyncAwait/DocumentReference+AsyncSequence.swift @@ -36,13 +36,14 @@ public extension DocumentReference { /// - Returns: An `AsyncThrowingStream` of `DocumentSnapshot` events. func snapshots(includeMetadataChanges: Bool) -> AsyncThrowingStream { return AsyncThrowingStream { continuation in - let listener = self.addSnapshotListener(includeMetadataChanges: includeMetadataChanges) { snapshot, error in - if let error = error { - continuation.finish(throwing: error) - } else if let snapshot = snapshot { - continuation.yield(snapshot) + let listener = self + .addSnapshotListener(includeMetadataChanges: includeMetadataChanges) { snapshot, error in + if let error = error { + continuation.finish(throwing: error) + } else if let snapshot = snapshot { + continuation.yield(snapshot) + } } - } continuation.onTermination = { @Sendable _ in listener.remove() } diff --git a/Firestore/Swift/Source/AsyncAwait/Query+AsyncSequence.swift b/Firestore/Swift/Source/AsyncAwait/Query+AsyncSequence.swift index 0d2901ed0d1..c63181af998 100644 --- a/Firestore/Swift/Source/AsyncAwait/Query+AsyncSequence.swift +++ b/Firestore/Swift/Source/AsyncAwait/Query+AsyncSequence.swift @@ -36,13 +36,14 @@ public extension Query { /// - Returns: An `AsyncThrowingStream` of `QuerySnapshot` events. func snapshots(includeMetadataChanges: Bool) -> AsyncThrowingStream { return AsyncThrowingStream { continuation in - let listener = self.addSnapshotListener(includeMetadataChanges: includeMetadataChanges) { snapshot, error in - if let error = error { - continuation.finish(throwing: error) - } else if let snapshot = snapshot { - continuation.yield(snapshot) + let listener = self + .addSnapshotListener(includeMetadataChanges: includeMetadataChanges) { snapshot, error in + if let error = error { + continuation.finish(throwing: error) + } else if let snapshot = snapshot { + continuation.yield(snapshot) + } } - } continuation.onTermination = { @Sendable _ in listener.remove() } diff --git a/Firestore/Swift/Tests/Unit/AsyncAwait/AsyncSequenceTests.swift b/Firestore/Swift/Tests/Unit/AsyncAwait/AsyncSequenceTests.swift index ae1a9d9b159..10e32d2efde 100644 --- a/Firestore/Swift/Tests/Unit/AsyncAwait/AsyncSequenceTests.swift +++ b/Firestore/Swift/Tests/Unit/AsyncAwait/AsyncSequenceTests.swift @@ -14,8 +14,8 @@ * limitations under the License. */ -import XCTest @testable import FirebaseFirestore +import XCTest // MARK: - Mock Objects for Testing @@ -33,17 +33,14 @@ private class MockQuery: Query { var capturedListener: SnapshotListener? let mockListenerRegistration = MockListenerRegistration() - override func addSnapshotListener( - _ listener: @escaping SnapshotListener - ) -> ListenerRegistration { + override func addSnapshotListener(_ listener: @escaping SnapshotListener) + -> ListenerRegistration { capturedListener = listener return mockListenerRegistration } - override func addSnapshotListener( - includeMetadataChanges: Bool, - listener: @escaping SnapshotListener - ) -> ListenerRegistration { + override func addSnapshotListener(includeMetadataChanges: Bool, + listener: @escaping SnapshotListener) -> ListenerRegistration { capturedListener = listener return mockListenerRegistration } @@ -53,17 +50,15 @@ private class MockDocumentReference: DocumentReference { var capturedListener: DocumentSnapshotListener? let mockListenerRegistration = MockListenerRegistration() - override func addSnapshotListener( - _ listener: @escaping DocumentSnapshotListener - ) -> ListenerRegistration { + override func addSnapshotListener(_ listener: @escaping DocumentSnapshotListener) + -> ListenerRegistration { capturedListener = listener return mockListenerRegistration } - override func addSnapshotListener( - includeMetadataChanges: Bool, - listener: @escaping DocumentSnapshotListener - ) -> ListenerRegistration { + override func addSnapshotListener(includeMetadataChanges: Bool, + listener: @escaping DocumentSnapshotListener) + -> ListenerRegistration { capturedListener = listener return mockListenerRegistration } @@ -180,4 +175,4 @@ class AsyncSequenceTests: XCTestCase { XCTAssertTrue(mockDocRef.mockListenerRegistration.isRemoved) } -} \ No newline at end of file +} From 86aea724ff64c469cb03922fc24154349b80b378 Mon Sep 17 00:00:00 2001 From: Peter Friese Date: Tue, 30 Sep 2025 12:03:22 +0200 Subject: [PATCH 3/5] Update copyright years --- .../Source/AsyncAwait/DocumentReference+AsyncSequence.swift | 2 +- Firestore/Swift/Source/AsyncAwait/Query+AsyncSequence.swift | 2 +- Firestore/Swift/Tests/Unit/AsyncAwait/AsyncSequenceTests.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Firestore/Swift/Source/AsyncAwait/DocumentReference+AsyncSequence.swift b/Firestore/Swift/Source/AsyncAwait/DocumentReference+AsyncSequence.swift index 3ecf5d5203c..08f6c65e9f9 100644 --- a/Firestore/Swift/Source/AsyncAwait/DocumentReference+AsyncSequence.swift +++ b/Firestore/Swift/Source/AsyncAwait/DocumentReference+AsyncSequence.swift @@ -1,5 +1,5 @@ /* - * Copyright 2024 Google LLC + * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/Firestore/Swift/Source/AsyncAwait/Query+AsyncSequence.swift b/Firestore/Swift/Source/AsyncAwait/Query+AsyncSequence.swift index c63181af998..5f759ae656f 100644 --- a/Firestore/Swift/Source/AsyncAwait/Query+AsyncSequence.swift +++ b/Firestore/Swift/Source/AsyncAwait/Query+AsyncSequence.swift @@ -1,5 +1,5 @@ /* - * Copyright 2024 Google LLC + * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/Firestore/Swift/Tests/Unit/AsyncAwait/AsyncSequenceTests.swift b/Firestore/Swift/Tests/Unit/AsyncAwait/AsyncSequenceTests.swift index 10e32d2efde..8f7080485e8 100644 --- a/Firestore/Swift/Tests/Unit/AsyncAwait/AsyncSequenceTests.swift +++ b/Firestore/Swift/Tests/Unit/AsyncAwait/AsyncSequenceTests.swift @@ -1,5 +1,5 @@ /* - * Copyright 2024 Google LLC + * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From cd48b85c13e48d16b5e158cf8b2b5159a41a80eb Mon Sep 17 00:00:00 2001 From: Peter Friese Date: Wed, 1 Oct 2025 06:13:10 +0200 Subject: [PATCH 4/5] Return stream as an AsyncSequence --- .../Source/AsyncAwait/DocumentReference+AsyncSequence.swift | 6 ++++-- Firestore/Swift/Source/AsyncAwait/Query+AsyncSequence.swift | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Firestore/Swift/Source/AsyncAwait/DocumentReference+AsyncSequence.swift b/Firestore/Swift/Source/AsyncAwait/DocumentReference+AsyncSequence.swift index 08f6c65e9f9..1cb4ad2ca2c 100644 --- a/Firestore/Swift/Source/AsyncAwait/DocumentReference+AsyncSequence.swift +++ b/Firestore/Swift/Source/AsyncAwait/DocumentReference+AsyncSequence.swift @@ -26,7 +26,8 @@ public extension DocumentReference { /// An asynchronous sequence of document snapshots. /// /// This stream emits a new `DocumentSnapshot` every time the underlying data changes. - var snapshots: AsyncThrowingStream { + @available(iOS 18.0, *) + var snapshots: some AsyncSequence { return snapshots(includeMetadataChanges: false) } @@ -34,7 +35,8 @@ public extension DocumentReference { /// /// - Parameter includeMetadataChanges: Whether to receive events for metadata-only changes. /// - Returns: An `AsyncThrowingStream` of `DocumentSnapshot` events. - func snapshots(includeMetadataChanges: Bool) -> AsyncThrowingStream { + @available(iOS 18.0, *) + func snapshots(includeMetadataChanges: Bool) -> some AsyncSequence { return AsyncThrowingStream { continuation in let listener = self .addSnapshotListener(includeMetadataChanges: includeMetadataChanges) { snapshot, error in diff --git a/Firestore/Swift/Source/AsyncAwait/Query+AsyncSequence.swift b/Firestore/Swift/Source/AsyncAwait/Query+AsyncSequence.swift index 5f759ae656f..0cbc3487085 100644 --- a/Firestore/Swift/Source/AsyncAwait/Query+AsyncSequence.swift +++ b/Firestore/Swift/Source/AsyncAwait/Query+AsyncSequence.swift @@ -26,7 +26,8 @@ public extension Query { /// An asynchronous sequence of query snapshots. /// /// This stream emits a new `QuerySnapshot` every time the underlying data changes. - var snapshots: AsyncThrowingStream { + @available(iOS 18.0, *) + var snapshots: some AsyncSequence { return snapshots(includeMetadataChanges: false) } @@ -34,7 +35,8 @@ public extension Query { /// /// - Parameter includeMetadataChanges: Whether to receive events for metadata-only changes. /// - Returns: An `AsyncThrowingStream` of `QuerySnapshot` events. - func snapshots(includeMetadataChanges: Bool) -> AsyncThrowingStream { + @available(iOS 18.0, *) + func snapshots(includeMetadataChanges: Bool) -> some AsyncSequence { return AsyncThrowingStream { continuation in let listener = self .addSnapshotListener(includeMetadataChanges: includeMetadataChanges) { snapshot, error in From d1b23276466b73666930203bbfc48273dec6f96d Mon Sep 17 00:00:00 2001 From: Peter Friese Date: Thu, 2 Oct 2025 23:13:13 +0100 Subject: [PATCH 5/5] Update availability specification --- .../Source/AsyncAwait/DocumentReference+AsyncSequence.swift | 4 ++-- .../Swift/Tests/Unit/AsyncAwait/AsyncSequenceTests.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Firestore/Swift/Source/AsyncAwait/DocumentReference+AsyncSequence.swift b/Firestore/Swift/Source/AsyncAwait/DocumentReference+AsyncSequence.swift index 1cb4ad2ca2c..4edfa2e1d42 100644 --- a/Firestore/Swift/Source/AsyncAwait/DocumentReference+AsyncSequence.swift +++ b/Firestore/Swift/Source/AsyncAwait/DocumentReference+AsyncSequence.swift @@ -26,7 +26,7 @@ public extension DocumentReference { /// An asynchronous sequence of document snapshots. /// /// This stream emits a new `DocumentSnapshot` every time the underlying data changes. - @available(iOS 18.0, *) + @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) var snapshots: some AsyncSequence { return snapshots(includeMetadataChanges: false) } @@ -35,7 +35,7 @@ public extension DocumentReference { /// /// - Parameter includeMetadataChanges: Whether to receive events for metadata-only changes. /// - Returns: An `AsyncThrowingStream` of `DocumentSnapshot` events. - @available(iOS 18.0, *) + @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) func snapshots(includeMetadataChanges: Bool) -> some AsyncSequence { return AsyncThrowingStream { continuation in let listener = self diff --git a/Firestore/Swift/Tests/Unit/AsyncAwait/AsyncSequenceTests.swift b/Firestore/Swift/Tests/Unit/AsyncAwait/AsyncSequenceTests.swift index 8f7080485e8..c5bf0c44128 100644 --- a/Firestore/Swift/Tests/Unit/AsyncAwait/AsyncSequenceTests.swift +++ b/Firestore/Swift/Tests/Unit/AsyncAwait/AsyncSequenceTests.swift @@ -66,7 +66,7 @@ private class MockDocumentReference: DocumentReference { // MARK: - AsyncSequenceTests -@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) +@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) class AsyncSequenceTests: XCTestCase { func testQuerySnapshotsYieldsValues() async throws { let mockQuery = MockQuery()