aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Firestore/CHANGELOG.md16
-rw-r--r--Firestore/Example/Firestore.xcodeproj/project.pbxproj42
-rw-r--r--Firestore/Example/SwiftBuildTest/main.swift101
-rw-r--r--Firestore/Example/Tests/API/FIRCollectionReferenceTests.m43
-rw-r--r--Firestore/Example/Tests/API/FIRDocumentReferenceTests.m43
-rw-r--r--Firestore/Example/Tests/API/FIRDocumentSnapshotTests.m62
-rw-r--r--Firestore/Example/Tests/API/FIRFieldPathTests.m45
-rw-r--r--Firestore/Example/Tests/API/FIRFieldValueTests.m48
-rw-r--r--Firestore/Example/Tests/API/FIRGeoPointTests.m21
-rw-r--r--Firestore/Example/Tests/API/FIRQuerySnapshotTests.m57
-rw-r--r--Firestore/Example/Tests/API/FIRQueryTests.m84
-rw-r--r--Firestore/Example/Tests/API/FIRSnapshotMetadataTests.m51
-rw-r--r--Firestore/Example/Tests/API/FSTAPIHelpers.h73
-rw-r--r--Firestore/Example/Tests/API/FSTAPIHelpers.m113
-rw-r--r--Firestore/Example/Tests/Core/FSTEventManagerTests.m16
-rw-r--r--Firestore/Example/Tests/Core/FSTQueryListenerTests.m52
-rw-r--r--Firestore/Example/Tests/Core/FSTQueryTests.m105
-rw-r--r--Firestore/Example/Tests/Core/FSTViewSnapshotTest.m2
-rw-r--r--Firestore/Example/Tests/Integration/API/FIRDatabaseTests.m37
-rw-r--r--Firestore/Example/Tests/Integration/API/FIRListenerRegistrationTests.m31
-rw-r--r--Firestore/Example/Tests/Integration/API/FIRQueryTests.m85
-rw-r--r--Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.m179
-rw-r--r--Firestore/Example/Tests/Integration/API/FIRWriteBatchTests.m31
-rw-r--r--Firestore/Example/Tests/Integration/FSTSmokeTests.m6
-rw-r--r--Firestore/Example/Tests/Local/FSTEagerGarbageCollectorTests.m12
-rw-r--r--Firestore/Example/Tests/Local/FSTLocalSerializerTests.m2
-rw-r--r--Firestore/Example/Tests/Local/FSTLocalStoreTests.m29
-rw-r--r--Firestore/Example/Tests/Local/FSTMutationQueueTests.m2
-rw-r--r--Firestore/Example/Tests/Local/FSTQueryCacheTests.m38
-rw-r--r--Firestore/Example/Tests/Local/FSTReferenceSetTests.m9
-rw-r--r--Firestore/Example/Tests/Local/FSTRemoteDocumentCacheTests.m2
-rw-r--r--Firestore/Example/Tests/Model/FSTDocumentTests.m63
-rw-r--r--Firestore/Example/Tests/Model/FSTFieldValueTests.m16
-rw-r--r--Firestore/Example/Tests/Model/FSTMutationTests.m49
-rw-r--r--Firestore/Example/Tests/Remote/FSTSerializerBetaTests.m23
-rw-r--r--Firestore/Example/Tests/SpecTests/FSTSpecTests.m10
-rw-r--r--Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.h3
-rw-r--r--Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.m7
-rw-r--r--Firestore/Example/Tests/SpecTests/json/listen_spec_test.json14
-rw-r--r--Firestore/Example/Tests/SpecTests/json/offline_spec_test.json165
-rw-r--r--Firestore/Example/Tests/Util/FSTEventAccumulator.h5
-rw-r--r--Firestore/Example/Tests/Util/FSTEventAccumulator.m5
-rw-r--r--Firestore/Example/Tests/Util/FSTHelpers.h4
-rw-r--r--Firestore/Example/Tests/Util/FSTHelpers.m15
-rw-r--r--Firestore/Example/Tests/Util/FSTIntegrationTestCase.h5
-rw-r--r--Firestore/Example/Tests/Util/FSTIntegrationTestCase.mm39
-rw-r--r--Firestore/Example/Tests/Util/XCTestCase+Await.h7
-rw-r--r--Firestore/Example/Tests/Util/XCTestCase+Await.m8
-rw-r--r--Firestore/Source/API/FIRCollectionReference.mm24
-rw-r--r--Firestore/Source/API/FIRDocumentChange.m22
-rw-r--r--Firestore/Source/API/FIRDocumentReference.m4
-rw-r--r--Firestore/Source/API/FIRDocumentSnapshot.m132
-rw-r--r--Firestore/Source/API/FIRFieldPath.m2
-rw-r--r--Firestore/Source/API/FIRFirestore.m115
-rw-r--r--Firestore/Source/API/FIRQuery.m91
-rw-r--r--Firestore/Source/API/FIRQuerySnapshot.m44
-rw-r--r--Firestore/Source/API/FIRSetOptions.m1
-rw-r--r--Firestore/Source/API/FIRSnapshotMetadata.m22
-rw-r--r--Firestore/Source/API/FIRSnapshotOptions+Internal.h38
-rw-r--r--Firestore/Source/API/FIRSnapshotOptions.m72
-rw-r--r--Firestore/Source/API/FIRWriteBatch.m6
-rw-r--r--Firestore/Source/Core/FSTEventManager.h2
-rw-r--r--Firestore/Source/Core/FSTEventManager.m8
-rw-r--r--Firestore/Source/Core/FSTFirestoreClient.h2
-rw-r--r--Firestore/Source/Core/FSTFirestoreClient.m7
-rw-r--r--Firestore/Source/Core/FSTQuery.m4
-rw-r--r--Firestore/Source/Core/FSTSyncEngine.h3
-rw-r--r--Firestore/Source/Core/FSTSyncEngine.m15
-rw-r--r--Firestore/Source/Core/FSTTypes.h9
-rw-r--r--Firestore/Source/Core/FSTView.h7
-rw-r--r--Firestore/Source/Core/FSTView.m24
-rw-r--r--Firestore/Source/Local/FSTDocumentReference.m2
-rw-r--r--Firestore/Source/Model/FSTDatabaseID.m2
-rw-r--r--Firestore/Source/Model/FSTFieldValue.h103
-rw-r--r--Firestore/Source/Model/FSTFieldValue.m86
-rw-r--r--Firestore/Source/Model/FSTMutation.h16
-rw-r--r--Firestore/Source/Model/FSTMutation.m56
-rw-r--r--Firestore/Source/Model/FSTMutationBatch.m2
-rw-r--r--Firestore/Source/Public/FIRDocumentChange.h4
-rw-r--r--Firestore/Source/Public/FIRDocumentReference.h2
-rw-r--r--Firestore/Source/Public/FIRDocumentSnapshot.h143
-rw-r--r--Firestore/Source/Public/FIRFirestore.h17
-rw-r--r--Firestore/Source/Public/FIRQuery.h13
-rw-r--r--Firestore/Source/Public/FIRQuerySnapshot.h4
-rw-r--r--Firestore/Source/Public/FIRWriteBatch.h7
-rw-r--r--Firestore/Source/Remote/FSTRemoteStore.h2
-rw-r--r--Firestore/Source/Remote/FSTRemoteStore.m120
-rw-r--r--Firestore/Source/Remote/FSTStream.m2
89 files changed, 2506 insertions, 605 deletions
diff --git a/.gitignore b/.gitignore
index 10ada7a..79daac4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -59,5 +59,6 @@ Podfile.lock
*.xcworkspace
# CMake
+.downloads
Debug
Release
diff --git a/Firestore/CHANGELOG.md b/Firestore/CHANGELOG.md
index ae9f994..59858e6 100644
--- a/Firestore/CHANGELOG.md
+++ b/Firestore/CHANGELOG.md
@@ -1,4 +1,18 @@
-# Unreleased
+# Unreleased (firestore-api-changes)
+- [changed] Removed the includeMetadataChanges property in FIRDocumentListenOptions
+ to avoid confusion with the factory method of the same name.
+- [changed] Added a commit method that takes no completion handler to FIRWriteBatch.
+- [feature] Queries can now be created from an NSPredicate.
+- [added] Added SnapshotOptions API to control how DocumentSnapshots return unresolved
+ server timestamps.
+- [changed] For non-existing documents, DocumentSnapshot.data() now returns `nil`
+ instead of throwing an exception. A non-nullable QueryDocumentSnapshot is
+ introduced for Queries to reduce the number of nil-checks in your code.
+- [changed] Snapshot listeners (with the `includeMetadataChanges` option
+ enabled) now receive an event with `snapshot.metadata.isFromCache` set to
+ `true` if the SDK loses its connection to the backend. A new event with
+ `snapshot.metadata.isFromCache` set to false will be raised once the
+ connection is restored and the query is in sync with the backend again.
# v0.9.4
- [changed] Firestore no longer has a direct dependency on FirebaseAuth.
diff --git a/Firestore/Example/Firestore.xcodeproj/project.pbxproj b/Firestore/Example/Firestore.xcodeproj/project.pbxproj
index a8ad799..06f790c 100644
--- a/Firestore/Example/Firestore.xcodeproj/project.pbxproj
+++ b/Firestore/Example/Firestore.xcodeproj/project.pbxproj
@@ -63,6 +63,15 @@
6ED54761B845349D43DB6B78 /* Pods_Firestore_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 75A6FE51C1A02DF38F62FAAD /* Pods_Firestore_Example.framework */; };
71719F9F1E33DC2100824A3D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 71719F9D1E33DC2100824A3D /* LaunchScreen.storyboard */; };
873B8AEB1B1F5CCA007FD442 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 873B8AEA1B1F5CCA007FD442 /* Main.storyboard */; };
+ AB382F7C1FE02A1F007CA955 /* FIRDocumentReferenceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AB382F7B1FE02A1F007CA955 /* FIRDocumentReferenceTests.m */; };
+ AB382F7E1FE03059007CA955 /* FIRFieldPathTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AB382F7D1FE03059007CA955 /* FIRFieldPathTests.m */; };
+ AB9945261FE2D71100DFC1E6 /* FIRCollectionReferenceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AB9945251FE2D71100DFC1E6 /* FIRCollectionReferenceTests.m */; };
+ AB9945281FE2DE0C00DFC1E6 /* FIRSnapshotMetadataTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AB9945271FE2DE0C00DFC1E6 /* FIRSnapshotMetadataTests.m */; };
+ AB99452A1FE2F9EB00DFC1E6 /* FIRDocumentSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AB9945291FE2F9EB00DFC1E6 /* FIRDocumentSnapshotTests.m */; };
+ AB99452C1FE3018D00DFC1E6 /* FIRQuerySnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AB99452B1FE3018D00DFC1E6 /* FIRQuerySnapshotTests.m */; };
+ AB99452E1FE30AC800DFC1E6 /* FIRFieldValueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AB99452D1FE30AC800DFC1E6 /* FIRFieldValueTests.m */; };
+ ABAEEF4F1FD5F8B100C966CB /* FIRQueryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = ABAEEF4E1FD5F8B100C966CB /* FIRQueryTests.m */; };
+ ABF341051FE860CA00C48322 /* FSTAPIHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = ABF341021FE8593500C48322 /* FSTAPIHelpers.m */; };
AFE6114F0D4DAECBA7B7C089 /* Pods_Firestore_IntegrationTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B2FA635DF5D116A67A7441CD /* Pods_Firestore_IntegrationTests.framework */; };
C4E749275AD0FBDF9F4716A8 /* Pods_SwiftBuildTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 32AD40BF6B0E849B07FFD05E /* Pods_SwiftBuildTest.framework */; };
D5B2532E4676014F57A7EAB9 /* FSTStreamTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D5B25C0D4AADFCA3ADB883E4 /* FSTStreamTests.m */; };
@@ -234,6 +243,16 @@
8E002F4AD5D9B6197C940847 /* Firestore.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = Firestore.podspec; path = ../Firestore.podspec; sourceTree = "<group>"; };
9D52E67EE96AA7E5D6F69748 /* Pods-Firestore_IntegrationTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_IntegrationTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_IntegrationTests/Pods-Firestore_IntegrationTests.debug.xcconfig"; sourceTree = "<group>"; };
9EF477AD4B2B643FD320867A /* Pods-Firestore_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Example/Pods-Firestore_Example.debug.xcconfig"; sourceTree = "<group>"; };
+ AB382F7B1FE02A1F007CA955 /* FIRDocumentReferenceTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRDocumentReferenceTests.m; sourceTree = "<group>"; };
+ AB382F7D1FE03059007CA955 /* FIRFieldPathTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRFieldPathTests.m; sourceTree = "<group>"; };
+ AB9945251FE2D71100DFC1E6 /* FIRCollectionReferenceTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FIRCollectionReferenceTests.m; sourceTree = "<group>"; };
+ AB9945271FE2DE0C00DFC1E6 /* FIRSnapshotMetadataTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FIRSnapshotMetadataTests.m; sourceTree = "<group>"; };
+ AB9945291FE2F9EB00DFC1E6 /* FIRDocumentSnapshotTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FIRDocumentSnapshotTests.m; sourceTree = "<group>"; };
+ AB99452B1FE3018D00DFC1E6 /* FIRQuerySnapshotTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FIRQuerySnapshotTests.m; sourceTree = "<group>"; };
+ AB99452D1FE30AC800DFC1E6 /* FIRFieldValueTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FIRFieldValueTests.m; sourceTree = "<group>"; };
+ ABAEEF4E1FD5F8B100C966CB /* FIRQueryTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRQueryTests.m; sourceTree = "<group>"; };
+ ABF341011FE858B500C48322 /* FSTAPIHelpers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FSTAPIHelpers.h; sourceTree = "<group>"; };
+ ABF341021FE8593500C48322 /* FSTAPIHelpers.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTAPIHelpers.m; sourceTree = "<group>"; };
B2FA635DF5D116A67A7441CD /* Pods_Firestore_IntegrationTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_IntegrationTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
CE00BABB5A3AAB44A4C209E2 /* Pods-Firestore_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Tests/Pods-Firestore_Tests.debug.xcconfig"; sourceTree = "<group>"; };
D3CC3DC5338DCAF43A211155 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = "<group>"; };
@@ -589,7 +608,17 @@
DE51B1831F0D48AC0013853F /* API */ = {
isa = PBXGroup;
children = (
+ AB99452D1FE30AC800DFC1E6 /* FIRFieldValueTests.m */,
+ AB99452B1FE3018D00DFC1E6 /* FIRQuerySnapshotTests.m */,
+ AB9945291FE2F9EB00DFC1E6 /* FIRDocumentSnapshotTests.m */,
+ AB9945271FE2DE0C00DFC1E6 /* FIRSnapshotMetadataTests.m */,
+ AB9945251FE2D71100DFC1E6 /* FIRCollectionReferenceTests.m */,
+ AB382F7D1FE03059007CA955 /* FIRFieldPathTests.m */,
+ AB382F7B1FE02A1F007CA955 /* FIRDocumentReferenceTests.m */,
DE51B1841F0D48AC0013853F /* FIRGeoPointTests.m */,
+ ABAEEF4E1FD5F8B100C966CB /* FIRQueryTests.m */,
+ ABF341011FE858B500C48322 /* FSTAPIHelpers.h */,
+ ABF341021FE8593500C48322 /* FSTAPIHelpers.m */,
);
path = API;
sourceTree = "<group>";
@@ -923,8 +952,7 @@
inputPaths = (
"${SRCROOT}/Pods/Target Support Files/Pods-SwiftBuildTest/Pods-SwiftBuildTest-frameworks.sh",
"${BUILT_PRODUCTS_DIR}/BoringSSL/openssl.framework",
- "${BUILT_PRODUCTS_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework",
- "${BUILT_PRODUCTS_DIR}/GoogleToolboxForMac-f0850809/GoogleToolboxForMac.framework",
+ "${BUILT_PRODUCTS_DIR}/GoogleToolboxForMac-Defines-NSData+zlib/GoogleToolboxForMac.framework",
"${BUILT_PRODUCTS_DIR}/Protobuf/Protobuf.framework",
"${BUILT_PRODUCTS_DIR}/gRPC/GRPCClient.framework",
"${BUILT_PRODUCTS_DIR}/gRPC-Core/grpc.framework",
@@ -936,7 +964,6 @@
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/openssl.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GTMSessionFetcher.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleToolboxForMac.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Protobuf.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GRPCClient.framework",
@@ -1167,7 +1194,9 @@
files = (
DE2EF0881F3D0B6E003D0CDC /* FSTTreeSortedDictionaryTests.m in Sources */,
DE51B1FD1F0D492C0013853F /* FSTSpecTests.m in Sources */,
+ ABAEEF4F1FD5F8B100C966CB /* FIRQueryTests.m in Sources */,
DE51B2001F0D493A0013853F /* FSTComparisonTests.m in Sources */,
+ ABF341051FE860CA00C48322 /* FSTAPIHelpers.m in Sources */,
DE51B1CC1F0D48C00013853F /* FIRGeoPointTests.m in Sources */,
DE51B1E11F0D490D0013853F /* FSTMemoryRemoteDocumentCacheTests.m in Sources */,
DE51B1FF1F0D493A0013853F /* FSTAssertTests.m in Sources */,
@@ -1179,6 +1208,7 @@
DE51B2011F0D493E0013853F /* FSTHelpers.m in Sources */,
DE51B1F61F0D491B0013853F /* FSTSerializerBetaTests.m in Sources */,
DE51B1F01F0D49140013853F /* FSTFieldValueTests.m in Sources */,
+ AB9945281FE2DE0C00DFC1E6 /* FIRSnapshotMetadataTests.m in Sources */,
5491BC721FB44593008B3588 /* FSTIntegrationTestCase.mm in Sources */,
DE2EF0861F3D0B6E003D0CDC /* FSTImmutableSortedDictionary+Testing.m in Sources */,
DE51B1DE1F0D490D0013853F /* FSTMemoryLocalStoreTests.m in Sources */,
@@ -1192,6 +1222,7 @@
5436F32420008FAD006E51E3 /* string_printf_test.cc in Sources */,
DE51B1EF1F0D49140013853F /* FSTDocumentTests.m in Sources */,
DE51B1DC1F0D490D0013853F /* FSTLocalSerializerTests.m in Sources */,
+ AB99452A1FE2F9EB00DFC1E6 /* FIRDocumentSnapshotTests.m in Sources */,
DE51B1E71F0D490D0013853F /* FSTRemoteDocumentChangeBufferTests.m in Sources */,
DE51B1E51F0D490D0013853F /* FSTReferenceSetTests.m in Sources */,
DE51B1EA1F0D490D0013853F /* FSTLevelDBRemoteDocumentCacheTests.mm in Sources */,
@@ -1203,6 +1234,7 @@
DE51B1DB1F0D490D0013853F /* FSTLevelDBQueryCacheTests.m in Sources */,
54764FAB1FAA0C320085E60A /* string_util_test.cc in Sources */,
54E9282C1F339CAD00C1953E /* XCTestCase+Await.m in Sources */,
+ AB99452E1FE30AC800DFC1E6 /* FIRFieldValueTests.m in Sources */,
DE51B1DF1F0D490D0013853F /* FSTMemoryMutationQueueTests.m in Sources */,
DE51B1F31F0D491B0013853F /* FSTDatastoreTests.m in Sources */,
DE51B1D01F0D48CD0013853F /* FSTQueryTests.m in Sources */,
@@ -1215,7 +1247,9 @@
DE51B1D91F0D490D0013853F /* FSTEagerGarbageCollectorTests.m in Sources */,
DE51B1E21F0D490D0013853F /* FSTMutationQueueTests.m in Sources */,
DE51B1E81F0D490D0013853F /* FSTLevelDBKeyTests.mm in Sources */,
+ AB9945261FE2D71100DFC1E6 /* FIRCollectionReferenceTests.m in Sources */,
DE51B1E31F0D490D0013853F /* FSTPersistenceTestHelpers.m in Sources */,
+ AB382F7C1FE02A1F007CA955 /* FIRDocumentReferenceTests.m in Sources */,
DE51B1CF1F0D48CD0013853F /* FSTQueryListenerTests.m in Sources */,
DE51B1DA1F0D490D0013853F /* FSTLevelDBLocalStoreTests.m in Sources */,
DE51B1FA1F0D492C0013853F /* FSTLevelDBSpecTests.m in Sources */,
@@ -1224,7 +1258,9 @@
DE51B1CE1F0D48CD0013853F /* FSTEventManagerTests.m in Sources */,
DE51B1E41F0D490D0013853F /* FSTQueryCacheTests.m in Sources */,
DE51B1CD1F0D48CD0013853F /* FSTDatabaseInfoTests.m in Sources */,
+ AB382F7E1FE03059007CA955 /* FIRFieldPathTests.m in Sources */,
DE51B1F21F0D49140013853F /* FSTPathTests.m in Sources */,
+ AB99452C1FE3018D00DFC1E6 /* FIRQuerySnapshotTests.m in Sources */,
54740A571FC914BA00713A1A /* secure_random_test.cc in Sources */,
DE51B1DD1F0D490D0013853F /* FSTLocalStoreTests.m in Sources */,
D5B25474286C9800CE42B8C2 /* FSTTestDispatchQueue.m in Sources */,
diff --git a/Firestore/Example/SwiftBuildTest/main.swift b/Firestore/Example/SwiftBuildTest/main.swift
index bea8b56..260735b 100644
--- a/Firestore/Example/SwiftBuildTest/main.swift
+++ b/Firestore/Example/SwiftBuildTest/main.swift
@@ -27,6 +27,8 @@ func main() {
writeDocument(at: documentRef);
+ writeDocuments(at: documentRef, database: db);
+
addDocument(to: collectionRef);
readDocument(at: documentRef);
@@ -37,6 +39,8 @@ func main() {
listenToDocuments(matching: query);
+ enableDisableNetwork(database: db);
+
types();
}
@@ -129,6 +133,47 @@ func writeDocument(at docRef: DocumentReference) {
}
}
+func enableDisableNetwork(database db: Firestore) {
+ // closure syntax
+ db.disableNetwork(completion: { (error) in
+ if let e = error {
+ print("Uh oh! \(e)")
+ return
+ }
+ })
+ // trailing block syntax
+ db.enableNetwork { (error) in
+ if let e = error {
+ print("Uh oh! \(e)")
+ return
+ }
+ }
+}
+
+func writeDocuments(at docRef: DocumentReference, database db: Firestore) {
+ var batch: WriteBatch;
+
+ batch = db.batch();
+ batch.setData(["a" : "b"], forDocument:docRef);
+ batch.setData(["c" : "d"], forDocument:docRef);
+ // commit without completion callback.
+ batch.commit();
+ print("Batch write without completion complete!");
+
+ batch = db.batch();
+ batch.setData(["a" : "b"], forDocument:docRef);
+ batch.setData(["c" : "d"], forDocument:docRef);
+ // commit with completion callback via trailing closure syntax.
+ batch.commit() { error in
+ if let error = error {
+ print("Uh oh! \(error)");
+ return;
+ }
+ print("Batch write callback complete!");
+ }
+ print("Batch write with completion complete!");
+}
+
func addDocument(to collectionRef: CollectionReference) {
collectionRef.addDocument(data: ["foo": 42]);
@@ -141,11 +186,20 @@ func readDocument(at docRef: DocumentReference) {
// Trailing closure syntax.
docRef.getDocument() { document, error in
if let document = document {
- // NOTE that document is nullable.
- let data = document.data();
+ // Note that both document and document.data() is nullable.
+ if let data = document.data() {
print("Read document: \(data)")
-
- // Fields are read via subscript notation.
+ }
+ if let data = document.data(with:SnapshotOptions.serverTimestampBehavior(.estimate)) {
+ print("Read document: \(data)")
+ }
+ if let foo = document.get("foo") {
+ print("Field: \(foo)")
+ }
+ if let foo = document.get("foo", options: SnapshotOptions.serverTimestampBehavior(.previous)) {
+ print("Field: \(foo)")
+ }
+ // Fields can also be read via subscript notation.
if let foo = document["foo"] {
print("Field: \(foo)")
}
@@ -181,24 +235,27 @@ func readDocuments(matching query: Query) {
func listenToDocument(at docRef: DocumentReference) {
- let listener = docRef.addSnapshotListener() { document, error in
- if let error = error {
- print("Uh oh! Listen canceled: \(error)")
- return
- }
+ let listener = docRef.addSnapshotListener() { document, error in
+ if let error = error {
+ print("Uh oh! Listen canceled: \(error)")
+ return
+ }
- if let document = document {
- print("Current document: \(document.data())");
- if (document.metadata.isFromCache) {
- print("From Cache")
- } else {
- print("From Server")
- }
- }
+ if let document = document {
+ // Note that document.data() is nullable.
+ if let data : [String:Any] = document.data() {
+ print("Current document: \(data)");
+ }
+ if document.metadata.isFromCache {
+ print("From Cache")
+ } else {
+ print("From Server")
+ }
}
+ }
- // Unsubscribe.
- listener.remove();
+ // Unsubscribe.
+ listener.remove();
}
func listenToDocuments(matching query: Query) {
@@ -215,7 +272,9 @@ func listenToDocuments(matching query: Query) {
// TODO(mikelehen): Figure out how to make "for..in" syntax work
// directly on documentSet.
for document in snap.documents {
- print("Doc: ", document.data())
+ // Note that document.data() is not nullable.
+ let data : [String:Any] = document.data()
+ print("Doc: ", data)
}
}
}
@@ -258,7 +317,7 @@ func transactions() {
let balanceA = try transaction.getDocument(accA)["balance"] as! Double
let balanceB = try transaction.getDocument(accB)["balance"] as! Double
- if (balanceA < amount) {
+ if balanceA < amount {
errorPointer?.pointee = NSError(domain: "Foo", code: 123, userInfo: nil)
return nil
}
diff --git a/Firestore/Example/Tests/API/FIRCollectionReferenceTests.m b/Firestore/Example/Tests/API/FIRCollectionReferenceTests.m
new file mode 100644
index 0000000..73ae38d
--- /dev/null
+++ b/Firestore/Example/Tests/API/FIRCollectionReferenceTests.m
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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/XCTest.h>
+
+#import "FirebaseFirestore/FIRCollectionReference.h"
+
+#import "Firestore/Example/Tests/API/FSTAPIHelpers.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FIRCollectionReferenceTests : XCTestCase
+@end
+
+@implementation FIRCollectionReferenceTests
+
+- (void)testEquals {
+ FIRCollectionReference *referenceFoo = FSTTestCollectionRef(@"foo");
+ FIRCollectionReference *referenceFooDup = FSTTestCollectionRef(@"foo");
+ FIRCollectionReference *referenceBar = FSTTestCollectionRef(@"bar");
+ XCTAssertEqualObjects(referenceFoo, referenceFooDup);
+ XCTAssertNotEqualObjects(referenceFoo, referenceBar);
+
+ XCTAssertEqual([referenceFoo hash], [referenceFooDup hash]);
+ XCTAssertNotEqual([referenceFoo hash], [referenceBar hash]);
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firestore/Example/Tests/API/FIRDocumentReferenceTests.m b/Firestore/Example/Tests/API/FIRDocumentReferenceTests.m
new file mode 100644
index 0000000..4e301d0
--- /dev/null
+++ b/Firestore/Example/Tests/API/FIRDocumentReferenceTests.m
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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/XCTest.h>
+
+#import "FirebaseFirestore/FIRDocumentReference.h"
+
+#import "Firestore/Example/Tests/API/FSTAPIHelpers.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FIRDocumentReferenceTests : XCTestCase
+@end
+
+@implementation FIRDocumentReferenceTests
+
+- (void)testEquals {
+ FIRDocumentReference *referenceFoo = FSTTestDocRef(@"rooms/foo");
+ FIRDocumentReference *referenceFooDup = FSTTestDocRef(@"rooms/foo");
+ FIRDocumentReference *referenceBar = FSTTestDocRef(@"rooms/bar");
+ XCTAssertEqualObjects(referenceFoo, referenceFooDup);
+ XCTAssertNotEqualObjects(referenceFoo, referenceBar);
+
+ XCTAssertEqual([referenceFoo hash], [referenceFooDup hash]);
+ XCTAssertNotEqual([referenceFoo hash], [referenceBar hash]);
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firestore/Example/Tests/API/FIRDocumentSnapshotTests.m b/Firestore/Example/Tests/API/FIRDocumentSnapshotTests.m
new file mode 100644
index 0000000..e865928
--- /dev/null
+++ b/Firestore/Example/Tests/API/FIRDocumentSnapshotTests.m
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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/XCTest.h>
+
+#import "FirebaseFirestore/FIRDocumentSnapshot.h"
+
+#import "Firestore/Example/Tests/API/FSTAPIHelpers.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FIRDocumentSnapshotTests : XCTestCase
+@end
+
+@implementation FIRDocumentSnapshotTests
+
+- (void)testEquals {
+ FIRDocumentSnapshot *base = FSTTestDocSnapshot(@"rooms/foo", 1, @{ @"a" : @1 }, NO, NO);
+ FIRDocumentSnapshot *baseDup = FSTTestDocSnapshot(@"rooms/foo", 1, @{ @"a" : @1 }, NO, NO);
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnonnull"
+ FIRDocumentSnapshot *nilData = FSTTestDocSnapshot(@"rooms/foo", 1, nil, NO, NO);
+ FIRDocumentSnapshot *nilDataDup = FSTTestDocSnapshot(@"rooms/foo", 1, nil, NO, NO);
+#pragma clang diagnostic pop
+ FIRDocumentSnapshot *differentPath = FSTTestDocSnapshot(@"rooms/bar", 1, @{ @"a" : @1 }, NO, NO);
+ FIRDocumentSnapshot *differentData = FSTTestDocSnapshot(@"rooms/bar", 1, @{ @"b" : @1 }, NO, NO);
+ FIRDocumentSnapshot *hasMutations = FSTTestDocSnapshot(@"rooms/bar", 1, @{ @"a" : @1 }, YES, NO);
+ FIRDocumentSnapshot *fromCache = FSTTestDocSnapshot(@"rooms/bar", 1, @{ @"a" : @1 }, NO, YES);
+ XCTAssertEqualObjects(base, baseDup);
+ XCTAssertEqualObjects(nilData, nilDataDup);
+ XCTAssertNotEqualObjects(base, nilData);
+ XCTAssertNotEqualObjects(nilData, base);
+ XCTAssertNotEqualObjects(base, differentPath);
+ XCTAssertNotEqualObjects(base, differentData);
+ XCTAssertNotEqualObjects(base, hasMutations);
+ XCTAssertNotEqualObjects(base, fromCache);
+
+ XCTAssertEqual([base hash], [baseDup hash]);
+ XCTAssertEqual([nilData hash], [nilDataDup hash]);
+ XCTAssertNotEqual([base hash], [nilData hash]);
+ XCTAssertNotEqual([base hash], [differentPath hash]);
+ XCTAssertNotEqual([base hash], [differentData hash]);
+ XCTAssertNotEqual([base hash], [hasMutations hash]);
+ XCTAssertNotEqual([base hash], [fromCache hash]);
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firestore/Example/Tests/API/FIRFieldPathTests.m b/Firestore/Example/Tests/API/FIRFieldPathTests.m
new file mode 100644
index 0000000..f8177c8
--- /dev/null
+++ b/Firestore/Example/Tests/API/FIRFieldPathTests.m
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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/XCTest.h>
+
+#import "FirebaseFirestore/FIRFieldPath.h"
+#import "Firestore/Source/API/FIRFieldPath+Internal.h"
+#import "Firestore/Source/Model/FSTPath.h"
+
+#import "Firestore/Example/Tests/Util/FSTHelpers.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FIRFieldPathTests : XCTestCase
+@end
+
+@implementation FIRFieldPathTests
+
+- (void)testEquals {
+ FIRFieldPath *foo = [[FIRFieldPath alloc] initPrivate:FSTTestFieldPath(@"foo.ooo.oooo")];
+ FIRFieldPath *fooDup = [[FIRFieldPath alloc] initPrivate:FSTTestFieldPath(@"foo.ooo.oooo")];
+ FIRFieldPath *bar = [[FIRFieldPath alloc] initPrivate:FSTTestFieldPath(@"baa.aaa.aaar")];
+ XCTAssertEqualObjects(foo, fooDup);
+ XCTAssertNotEqualObjects(foo, bar);
+
+ XCTAssertEqual([foo hash], [fooDup hash]);
+ XCTAssertNotEqual([foo hash], [bar hash]);
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firestore/Example/Tests/API/FIRFieldValueTests.m b/Firestore/Example/Tests/API/FIRFieldValueTests.m
new file mode 100644
index 0000000..8c9db99
--- /dev/null
+++ b/Firestore/Example/Tests/API/FIRFieldValueTests.m
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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 FirebaseFirestore;
+
+#import <XCTest/XCTest.h>
+
+#import "FirebaseFirestore/FIRFieldValue.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FIRFieldValueTests : XCTestCase
+@end
+
+@implementation FIRFieldValueTests
+
+- (void)testEquals {
+ FIRFieldValue *delete = [FIRFieldValue fieldValueForDelete];
+ FIRFieldValue *deleteDup = [FIRFieldValue fieldValueForDelete];
+ FIRFieldValue *serverTimestamp = [FIRFieldValue fieldValueForServerTimestamp];
+ FIRFieldValue *serverTimestampDup = [FIRFieldValue fieldValueForServerTimestamp];
+ XCTAssertEqualObjects(delete, deleteDup);
+ XCTAssertNotEqualObjects(delete, nil);
+ XCTAssertEqualObjects(serverTimestamp, serverTimestampDup);
+ XCTAssertNotEqualObjects(serverTimestamp, nil);
+ XCTAssertNotEqualObjects(delete, serverTimestamp);
+
+ XCTAssertEqual([delete hash], [deleteDup hash]);
+ XCTAssertEqual([serverTimestamp hash], [serverTimestamp hash]);
+ XCTAssertNotEqual([delete hash], [serverTimestamp hash]);
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firestore/Example/Tests/API/FIRGeoPointTests.m b/Firestore/Example/Tests/API/FIRGeoPointTests.m
index b505de0..8abda10 100644
--- a/Firestore/Example/Tests/API/FIRGeoPointTests.m
+++ b/Firestore/Example/Tests/API/FIRGeoPointTests.m
@@ -28,16 +28,17 @@ NS_ASSUME_NONNULL_BEGIN
@implementation FIRGeoPointTests
- (void)testEquals {
- XCTAssertEqualObjects([[FIRGeoPoint alloc] initWithLatitude:0 longitude:0],
- [[FIRGeoPoint alloc] initWithLatitude:0 longitude:0]);
- XCTAssertEqualObjects([[FIRGeoPoint alloc] initWithLatitude:1.23 longitude:4.56],
- [[FIRGeoPoint alloc] initWithLatitude:1.23 longitude:4.56]);
- XCTAssertNotEqualObjects([[FIRGeoPoint alloc] initWithLatitude:0 longitude:0],
- [[FIRGeoPoint alloc] initWithLatitude:1 longitude:0]);
- XCTAssertNotEqualObjects([[FIRGeoPoint alloc] initWithLatitude:0 longitude:0],
- [[FIRGeoPoint alloc] initWithLatitude:0 longitude:1]);
- XCTAssertNotEqualObjects([[FIRGeoPoint alloc] initWithLatitude:0 longitude:0],
- [[NSObject alloc] init]);
+ FIRGeoPoint *foo = FSTTestGeoPoint(1.23, 4.56);
+ FIRGeoPoint *fooDup = FSTTestGeoPoint(1.23, 4.56);
+ FIRGeoPoint *differentLatitude = FSTTestGeoPoint(1.23, 0);
+ FIRGeoPoint *differentLongitude = FSTTestGeoPoint(0, 4.56);
+ XCTAssertEqualObjects(foo, fooDup);
+ XCTAssertNotEqualObjects(foo, differentLatitude);
+ XCTAssertNotEqualObjects(foo, differentLongitude);
+
+ XCTAssertEqual([foo hash], [fooDup hash]);
+ XCTAssertNotEqual([foo hash], [differentLatitude hash]);
+ XCTAssertNotEqual([foo hash], [differentLongitude hash]);
}
- (void)testComparison {
diff --git a/Firestore/Example/Tests/API/FIRQuerySnapshotTests.m b/Firestore/Example/Tests/API/FIRQuerySnapshotTests.m
new file mode 100644
index 0000000..4637c49
--- /dev/null
+++ b/Firestore/Example/Tests/API/FIRQuerySnapshotTests.m
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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/XCTest.h>
+
+#import "FirebaseFirestore/FIRQuerySnapshot.h"
+#import "Firestore/Source/Model/FSTPath.h"
+
+#import "Firestore/Example/Tests/API/FSTAPIHelpers.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FIRQuerySnapshotTests : XCTestCase
+@end
+
+@implementation FIRQuerySnapshotTests
+
+- (void)testEquals {
+ FIRQuerySnapshot *foo = FSTTestQuerySnapshot(@"foo", @{}, @{ @"a" : @{@"a" : @1} }, YES, NO);
+ FIRQuerySnapshot *fooDup = FSTTestQuerySnapshot(@"foo", @{}, @{ @"a" : @{@"a" : @1} }, YES, NO);
+ FIRQuerySnapshot *differentPath = FSTTestQuerySnapshot(@"bar", @{},
+ @{ @"a" : @{@"a" : @1} }, YES, NO);
+ FIRQuerySnapshot *differentDoc = FSTTestQuerySnapshot(@"foo",
+ @{ @"a" : @{@"b" : @1} }, @{}, YES, NO);
+ FIRQuerySnapshot *noPendingWrites = FSTTestQuerySnapshot(@"foo", @{},
+ @{ @"a" : @{@"a" : @1} }, NO, NO);
+ FIRQuerySnapshot *fromCache = FSTTestQuerySnapshot(@"foo", @{},
+ @{ @"a" : @{@"a" : @1} }, YES, YES);
+ XCTAssertEqualObjects(foo, fooDup);
+ XCTAssertNotEqualObjects(foo, differentPath);
+ XCTAssertNotEqualObjects(foo, differentDoc);
+ XCTAssertNotEqualObjects(foo, noPendingWrites);
+ XCTAssertNotEqualObjects(foo, fromCache);
+
+ XCTAssertEqual([foo hash], [fooDup hash]);
+ XCTAssertNotEqual([foo hash], [differentPath hash]);
+ XCTAssertNotEqual([foo hash], [differentDoc hash]);
+ XCTAssertNotEqual([foo hash], [noPendingWrites hash]);
+ XCTAssertNotEqual([foo hash], [fromCache hash]);
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firestore/Example/Tests/API/FIRQueryTests.m b/Firestore/Example/Tests/API/FIRQueryTests.m
new file mode 100644
index 0000000..1b5236d
--- /dev/null
+++ b/Firestore/Example/Tests/API/FIRQueryTests.m
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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/XCTest.h>
+
+#import "FirebaseFirestore/FIRQuery.h"
+#import "Firestore/Source/API/FIRQuery+Internal.h"
+#import "Firestore/Source/Core/FSTQuery.h"
+#import "Firestore/Source/Model/FSTPath.h"
+
+#import "Firestore/Example/Tests/API/FSTAPIHelpers.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FIRQueryTests : XCTestCase
+@end
+
+@implementation FIRQueryTests
+
+- (void)testEquals {
+ FIRFirestore *firestore = FSTTestFirestore();
+ FIRQuery *queryFoo = [FIRQuery referenceWithQuery:FSTTestQuery(@"foo") firestore:firestore];
+ FIRQuery *queryFooDup = [FIRQuery referenceWithQuery:FSTTestQuery(@"foo") firestore:firestore];
+ FIRQuery *queryBar = [FIRQuery referenceWithQuery:FSTTestQuery(@"bar") firestore:firestore];
+ XCTAssertEqualObjects(queryFoo, queryFooDup);
+ XCTAssertNotEqualObjects(queryFoo, queryBar);
+ XCTAssertEqualObjects([queryFoo queryWhereField:@"f" isEqualTo:@1],
+ [queryFoo queryWhereField:@"f" isEqualTo:@1]);
+ XCTAssertNotEqualObjects([queryFoo queryWhereField:@"f" isEqualTo:@1],
+ [queryFoo queryWhereField:@"f" isEqualTo:@2]);
+
+ XCTAssertEqual([queryFoo hash], [queryFooDup hash]);
+ XCTAssertNotEqual([queryFoo hash], [queryBar hash]);
+ XCTAssertEqual([[queryFoo queryWhereField:@"f" isEqualTo:@1] hash],
+ [[queryFoo queryWhereField:@"f" isEqualTo:@1] hash]);
+ XCTAssertNotEqual([[queryFoo queryWhereField:@"f" isEqualTo:@1] hash],
+ [[queryFoo queryWhereField:@"f" isEqualTo:@2] hash]);
+}
+
+- (void)testFilteringWithPredicate {
+ FIRFirestore *firestore = FSTTestFirestore();
+ FIRQuery *query = [FIRQuery referenceWithQuery:FSTTestQuery(@"foo") firestore:firestore];
+ FIRQuery *query1 = [query queryWhereField:@"f" isLessThanOrEqualTo:@1];
+ FIRQuery *query2 = [query queryFilteredUsingPredicate:[NSPredicate predicateWithFormat:@"f<=1"]];
+ FIRQuery *query3 =
+ [[query queryWhereField:@"f1" isLessThan:@2] queryWhereField:@"f2" isEqualTo:@3];
+ FIRQuery *query4 =
+ [query queryFilteredUsingPredicate:[NSPredicate predicateWithFormat:@"f1<2 && f2==3"]];
+ FIRQuery *query5 =
+ [[[[[query queryWhereField:@"f1" isLessThan:@2] queryWhereField:@"f2" isEqualTo:@3]
+ queryWhereField:@"f1"
+ isLessThanOrEqualTo:@"four"] queryWhereField:@"f1"
+ isGreaterThanOrEqualTo:@"five"] queryWhereField:@"f1"
+ isGreaterThan:@6];
+ FIRQuery *query6 = [query
+ queryFilteredUsingPredicate:
+ [NSPredicate predicateWithFormat:@"f1<2 && f2==3 && f1<='four' && f1>='five' && f1>6"]];
+ FIRQuery *query7 = [query
+ queryFilteredUsingPredicate:
+ [NSPredicate predicateWithFormat:@"2>f1 && 3==f2 && 'four'>=f1 && 'five'<=f1 && 6<f1"]];
+ XCTAssertEqualObjects(query1, query2);
+ XCTAssertNotEqualObjects(query2, query3);
+ XCTAssertEqualObjects(query3, query4);
+ XCTAssertNotEqualObjects(query4, query5);
+ XCTAssertEqualObjects(query5, query6);
+ XCTAssertEqualObjects(query6, query7);
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firestore/Example/Tests/API/FIRSnapshotMetadataTests.m b/Firestore/Example/Tests/API/FIRSnapshotMetadataTests.m
new file mode 100644
index 0000000..cf50765
--- /dev/null
+++ b/Firestore/Example/Tests/API/FIRSnapshotMetadataTests.m
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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/XCTest.h>
+
+#import "FirebaseFirestore/FIRSnapshotMetadata.h"
+#import "Firestore/Source/API/FIRSnapshotMetadata+Internal.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FIRSnapshotMetadataTests : XCTestCase
+@end
+
+@implementation FIRSnapshotMetadataTests
+
+- (void)testEquals {
+ FIRSnapshotMetadata *foo =
+ [FIRSnapshotMetadata snapshotMetadataWithPendingWrites:YES fromCache:YES];
+ FIRSnapshotMetadata *fooDup =
+ [FIRSnapshotMetadata snapshotMetadataWithPendingWrites:YES fromCache:YES];
+ FIRSnapshotMetadata *bar =
+ [FIRSnapshotMetadata snapshotMetadataWithPendingWrites:YES fromCache:NO];
+ FIRSnapshotMetadata *baz =
+ [FIRSnapshotMetadata snapshotMetadataWithPendingWrites:NO fromCache:YES];
+ XCTAssertEqualObjects(foo, fooDup);
+ XCTAssertNotEqualObjects(foo, bar);
+ XCTAssertNotEqualObjects(foo, baz);
+ XCTAssertNotEqualObjects(bar, baz);
+
+ XCTAssertEqual([foo hash], [fooDup hash]);
+ XCTAssertNotEqual([foo hash], [bar hash]);
+ XCTAssertNotEqual([foo hash], [baz hash]);
+ XCTAssertNotEqual([bar hash], [baz hash]);
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firestore/Example/Tests/API/FSTAPIHelpers.h b/Firestore/Example/Tests/API/FSTAPIHelpers.h
new file mode 100644
index 0000000..dcd8209
--- /dev/null
+++ b/Firestore/Example/Tests/API/FSTAPIHelpers.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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 <Foundation/Foundation.h>
+
+#import "FirebaseFirestore/FIRCollectionReference.h"
+#import "FirebaseFirestore/FIRDocumentSnapshot.h"
+#import "FirebaseFirestore/FIRFirestore.h"
+#import "FirebaseFirestore/FIRQuerySnapshot.h"
+
+#import "Firestore/Example/Tests/Util/FSTHelpers.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+#if __cplusplus
+extern "C" {
+#endif
+
+/** A convenience method for creating dummy singleton FIRFirestore for tests. */
+FIRFirestore *FSTTestFirestore();
+
+/** A convenience method for creating a doc snapshot for tests. */
+FIRDocumentSnapshot *FSTTestDocSnapshot(NSString *path,
+ FSTTestSnapshotVersion version,
+ NSDictionary<NSString *, id> *data,
+ BOOL hasMutations,
+ BOOL fromCache);
+
+/** A convenience method for creating a collection reference from a path string. */
+FIRCollectionReference *FSTTestCollectionRef(NSString *path);
+
+/** A convenience method for creating a document reference from a path string. */
+FIRDocumentReference *FSTTestDocRef(NSString *path);
+
+/**
+ * A convenience method for creating a particular query snapshot for tests.
+ *
+ * @param path To be used in constructing the query.
+ * @param oldDocs Provides the prior set of documents in the QuerySnapshot. Each dictionary entry
+ * maps to a document, with the key being the document id, and the value being the document
+ * contents.
+ * @param docsToAdd Specifies data to be added into the query snapshot as of now. Each dictionary
+ * entry maps to a document, with the key being the document id, and the value being the document
+ * contents.
+ * @param hasPendingWrites Whether the query snapshot has pending writes to the server.
+ * @param fromCache Whether the query snapshot is cache result.
+ * @returns A query snapshot that consists of both sets of documents.
+ */
+FIRQuerySnapshot *FSTTestQuerySnapshot(
+ NSString *path,
+ NSDictionary<NSString *, NSDictionary<NSString *, id> *> *oldDocs,
+ NSDictionary<NSString *, NSDictionary<NSString *, id> *> *docsToAdd,
+ BOOL hasPendingWrites,
+ BOOL fromCache);
+
+#if __cplusplus
+} // extern "C"
+#endif
+
+NS_ASSUME_NONNULL_END
diff --git a/Firestore/Example/Tests/API/FSTAPIHelpers.m b/Firestore/Example/Tests/API/FSTAPIHelpers.m
new file mode 100644
index 0000000..507e2ff
--- /dev/null
+++ b/Firestore/Example/Tests/API/FSTAPIHelpers.m
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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 "Firestore/Example/Tests/API/FSTAPIHelpers.h"
+
+#import "FirebaseFirestore/FIRDocumentReference.h"
+#import "FirebaseFirestore/FIRSnapshotMetadata.h"
+#import "Firestore/Source/API/FIRCollectionReference+Internal.h"
+#import "Firestore/Source/API/FIRDocumentReference+Internal.h"
+#import "Firestore/Source/API/FIRDocumentSnapshot+Internal.h"
+#import "Firestore/Source/API/FIRFirestore+Internal.h"
+#import "Firestore/Source/API/FIRQuerySnapshot+Internal.h"
+#import "Firestore/Source/API/FIRSnapshotMetadata+Internal.h"
+#import "Firestore/Source/Core/FSTQuery.h"
+#import "Firestore/Source/Core/FSTViewSnapshot.h"
+#import "Firestore/Source/Model/FSTDocument.h"
+#import "Firestore/Source/Model/FSTDocumentKey.h"
+#import "Firestore/Source/Model/FSTDocumentSet.h"
+#import "Firestore/Source/Model/FSTPath.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+FIRFirestore *FSTTestFirestore() {
+ static FIRFirestore *sharedInstance = nil;
+ static dispatch_once_t onceToken;
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnonnull"
+ dispatch_once(&onceToken, ^{
+ sharedInstance = [[FIRFirestore alloc] initWithProjectID:@"abc"
+ database:@"abc"
+ persistenceKey:@"db123"
+ credentialsProvider:nil
+ workerDispatchQueue:nil
+ firebaseApp:nil];
+ });
+#pragma clang diagnostic pop
+ return sharedInstance;
+}
+
+FIRDocumentSnapshot *FSTTestDocSnapshot(NSString *path,
+ FSTTestSnapshotVersion version,
+ NSDictionary<NSString *, id> *data,
+ BOOL hasMutations,
+ BOOL fromCache) {
+ FSTDocument *doc = data ? FSTTestDoc(path, version, data, hasMutations) : nil;
+ return [FIRDocumentSnapshot snapshotWithFirestore:FSTTestFirestore()
+ documentKey:FSTTestDocKey(path)
+ document:doc
+ fromCache:fromCache];
+}
+
+FIRCollectionReference *FSTTestCollectionRef(NSString *path) {
+ return [FIRCollectionReference referenceWithPath:FSTTestPath(path) firestore:FSTTestFirestore()];
+}
+
+FIRDocumentReference *FSTTestDocRef(NSString *path) {
+ return [FIRDocumentReference referenceWithPath:FSTTestPath(path) firestore:FSTTestFirestore()];
+}
+
+/** A convenience method for creating a query snapshots for tests. */
+FIRQuerySnapshot *FSTTestQuerySnapshot(
+ NSString *path,
+ NSDictionary<NSString *, NSDictionary<NSString *, id> *> *oldDocs,
+ NSDictionary<NSString *, NSDictionary<NSString *, id> *> *docsToAdd,
+ BOOL hasPendingWrites,
+ BOOL fromCache) {
+ FIRSnapshotMetadata *metadata =
+ [FIRSnapshotMetadata snapshotMetadataWithPendingWrites:hasPendingWrites fromCache:fromCache];
+ FSTDocumentSet *oldDocuments = FSTTestDocSet(FSTDocumentComparatorByKey, @[]);
+ for (NSString *key in oldDocs) {
+ oldDocuments = [oldDocuments
+ documentSetByAddingDocument:FSTTestDoc([NSString stringWithFormat:@"%@/%@", path, key], 1,
+ oldDocs[key], hasPendingWrites)];
+ }
+ FSTDocumentSet *newDocuments = oldDocuments;
+ NSArray<FSTDocumentViewChange *> *documentChanges = [NSArray array];
+ for (NSString *key in docsToAdd) {
+ FSTDocument *docToAdd = FSTTestDoc([NSString stringWithFormat:@"%@/%@", path, key], 1,
+ docsToAdd[key], hasPendingWrites);
+ newDocuments = [newDocuments documentSetByAddingDocument:docToAdd];
+ documentChanges = [documentChanges
+ arrayByAddingObject:[FSTDocumentViewChange
+ changeWithDocument:docToAdd
+ type:FSTDocumentViewChangeTypeAdded]];
+ }
+ FSTViewSnapshot *viewSnapshot = [[FSTViewSnapshot alloc] initWithQuery:FSTTestQuery(path)
+ documents:newDocuments
+ oldDocuments:oldDocuments
+ documentChanges:documentChanges
+ fromCache:fromCache
+ hasPendingWrites:hasPendingWrites
+ syncStateChanged:YES];
+ return [FIRQuerySnapshot snapshotWithFirestore:FSTTestFirestore()
+ originalQuery:FSTTestQuery(path)
+ snapshot:viewSnapshot
+ metadata:metadata];
+}
+
+NS_ASSUME_NONNULL_END
diff --git a/Firestore/Example/Tests/Core/FSTEventManagerTests.m b/Firestore/Example/Tests/Core/FSTEventManagerTests.m
index 99021ce..fcde17d 100644
--- a/Firestore/Example/Tests/Core/FSTEventManagerTests.m
+++ b/Firestore/Example/Tests/Core/FSTEventManagerTests.m
@@ -52,7 +52,7 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testHandlesManyListenersPerQuery {
- FSTQuery *query = [FSTQuery queryWithPath:FSTTestPath(@"foo/bar")];
+ FSTQuery *query = FSTTestQuery(@"foo/bar");
FSTQueryListener *listener1 = [self noopListenerForQuery:query];
FSTQueryListener *listener2 = [self noopListenerForQuery:query];
@@ -73,7 +73,7 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testHandlesUnlistenOnUnknownListenerGracefully {
- FSTQuery *query = [FSTQuery queryWithPath:FSTTestPath(@"foo/bar")];
+ FSTQuery *query = FSTTestQuery(@"foo/bar");
FSTQueryListener *listener = [self noopListenerForQuery:query];
FSTSyncEngine *syncEngineMock = OCMStrictClassMock([FSTSyncEngine class]);
@@ -95,8 +95,8 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testNotifiesListenersInTheRightOrder {
- FSTQuery *query1 = [FSTQuery queryWithPath:FSTTestPath(@"foo/bar")];
- FSTQuery *query2 = [FSTQuery queryWithPath:FSTTestPath(@"bar/baz")];
+ FSTQuery *query1 = FSTTestQuery(@"foo/bar");
+ FSTQuery *query2 = FSTTestQuery(@"bar/baz");
NSMutableArray *eventOrder = [NSMutableArray array];
FSTQueryListener *listener1 = [self makeMockListenerForQuery:query1
@@ -135,15 +135,15 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testWillForwardOnlineStateChanges {
- FSTQuery *query = [FSTQuery queryWithPath:FSTTestPath(@"foo/bar")];
+ FSTQuery *query = FSTTestQuery(@"foo/bar");
FSTQueryListener *fakeListener = OCMClassMock([FSTQueryListener class]);
NSMutableArray *events = [NSMutableArray array];
OCMStub([fakeListener query]).andReturn(query);
- OCMStub([fakeListener clientDidChangeOnlineState:FSTOnlineStateUnknown])
+ OCMStub([fakeListener applyChangedOnlineState:FSTOnlineStateUnknown])
.andDo(^(NSInvocation *invocation) {
[events addObject:@(FSTOnlineStateUnknown)];
});
- OCMStub([fakeListener clientDidChangeOnlineState:FSTOnlineStateHealthy])
+ OCMStub([fakeListener applyChangedOnlineState:FSTOnlineStateHealthy])
.andDo(^(NSInvocation *invocation) {
[events addObject:@(FSTOnlineStateHealthy)];
});
@@ -154,7 +154,7 @@ NS_ASSUME_NONNULL_BEGIN
[eventManager addListener:fakeListener];
XCTAssertEqualObjects(events, @[ @(FSTOnlineStateUnknown) ]);
- [eventManager watchStreamDidChangeOnlineState:FSTOnlineStateHealthy];
+ [eventManager applyChangedOnlineState:FSTOnlineStateHealthy];
XCTAssertEqualObjects(events, (@[ @(FSTOnlineStateUnknown), @(FSTOnlineStateHealthy) ]));
}
diff --git a/Firestore/Example/Tests/Core/FSTQueryListenerTests.m b/Firestore/Example/Tests/Core/FSTQueryListenerTests.m
index 1bb7a47..4856b5f 100644
--- a/Firestore/Example/Tests/Core/FSTQueryListenerTests.m
+++ b/Firestore/Example/Tests/Core/FSTQueryListenerTests.m
@@ -45,7 +45,7 @@ NS_ASSUME_NONNULL_BEGIN
NSMutableArray<FSTViewSnapshot *> *accum = [NSMutableArray array];
NSMutableArray<FSTViewSnapshot *> *otherAccum = [NSMutableArray array];
- FSTQuery *query = [FSTQuery queryWithPath:FSTTestPath(@"rooms")];
+ FSTQuery *query = FSTTestQuery(@"rooms");
FSTDocument *doc1 = FSTTestDoc(@"rooms/Eros", 1, @{@"name" : @"Eros"}, NO);
FSTDocument *doc2 = FSTTestDoc(@"rooms/Hades", 2, @{@"name" : @"Hades"}, NO);
FSTDocument *doc2prime =
@@ -88,7 +88,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testRaisesErrorEvent {
NSMutableArray<NSError *> *accum = [NSMutableArray array];
- FSTQuery *query = [FSTQuery queryWithPath:FSTTestPath(@"rooms/Eros")];
+ FSTQuery *query = FSTTestQuery(@"rooms/Eros");
FSTQueryListener *listener = [self listenToQuery:query
handler:^(FSTViewSnapshot *snapshot, NSError *error) {
@@ -104,7 +104,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testRaisesEventForEmptyCollectionAfterSync {
NSMutableArray<FSTViewSnapshot *> *accum = [NSMutableArray array];
- FSTQuery *query = [FSTQuery queryWithPath:FSTTestPath(@"rooms")];
+ FSTQuery *query = FSTTestQuery(@"rooms");
FSTQueryListener *listener = [self listenToQuery:query accumulatingSnapshots:accum];
@@ -126,7 +126,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testMutingAsyncListenerPreventsAllSubsequentEvents {
NSMutableArray<FSTViewSnapshot *> *accum = [NSMutableArray array];
- FSTQuery *query = [FSTQuery queryWithPath:FSTTestPath(@"rooms/Eros")];
+ FSTQuery *query = FSTTestQuery(@"rooms/Eros");
FSTDocument *doc1 = FSTTestDoc(@"rooms/Eros", 3, @{@"name" : @"Eros"}, NO);
FSTDocument *doc2 = FSTTestDoc(@"rooms/Eros", 4, @{@"name" : @"Eros2"}, NO);
@@ -166,7 +166,7 @@ NS_ASSUME_NONNULL_BEGIN
NSMutableArray<FSTViewSnapshot *> *filteredAccum = [NSMutableArray array];
NSMutableArray<FSTViewSnapshot *> *fullAccum = [NSMutableArray array];
- FSTQuery *query = [FSTQuery queryWithPath:FSTTestPath(@"rooms")];
+ FSTQuery *query = FSTTestQuery(@"rooms");
FSTDocument *doc1 = FSTTestDoc(@"rooms/Eros", 1, @{@"name" : @"Eros"}, NO);
FSTDocument *doc2 = FSTTestDoc(@"rooms/Hades", 2, @{@"name" : @"Hades"}, NO);
@@ -204,7 +204,7 @@ NS_ASSUME_NONNULL_BEGIN
NSMutableArray<FSTViewSnapshot *> *filteredAccum = [NSMutableArray array];
NSMutableArray<FSTViewSnapshot *> *fullAccum = [NSMutableArray array];
- FSTQuery *query = [FSTQuery queryWithPath:FSTTestPath(@"rooms")];
+ FSTQuery *query = FSTTestQuery(@"rooms");
FSTDocument *doc1 = FSTTestDoc(@"rooms/Eros", 1, @{@"name" : @"Eros"}, YES);
FSTDocument *doc2 = FSTTestDoc(@"rooms/Hades", 2, @{@"name" : @"Hades"}, NO);
FSTDocument *doc1Prime = FSTTestDoc(@"rooms/Eros", 1, @{@"name" : @"Eros"}, NO);
@@ -253,7 +253,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testRaisesQueryMetadataEventsOnlyWhenHasPendingWritesOnTheQueryChanges {
NSMutableArray<FSTViewSnapshot *> *fullAccum = [NSMutableArray array];
- FSTQuery *query = [FSTQuery queryWithPath:FSTTestPath(@"rooms")];
+ FSTQuery *query = FSTTestQuery(@"rooms");
FSTDocument *doc1 = FSTTestDoc(@"rooms/Eros", 1, @{@"name" : @"Eros"}, YES);
FSTDocument *doc2 = FSTTestDoc(@"rooms/Hades", 2, @{@"name" : @"Hades"}, YES);
FSTDocument *doc1Prime = FSTTestDoc(@"rooms/Eros", 1, @{@"name" : @"Eros"}, NO);
@@ -290,7 +290,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testMetadataOnlyDocumentChangesAreFilteredOutWhenIncludeDocumentMetadataChangesIsFalse {
NSMutableArray<FSTViewSnapshot *> *filteredAccum = [NSMutableArray array];
- FSTQuery *query = [FSTQuery queryWithPath:FSTTestPath(@"rooms")];
+ FSTQuery *query = FSTTestQuery(@"rooms");
FSTDocument *doc1 = FSTTestDoc(@"rooms/Eros", 1, @{@"name" : @"Eros"}, YES);
FSTDocument *doc2 = FSTTestDoc(@"rooms/Hades", 2, @{@"name" : @"Hades"}, NO);
FSTDocument *doc1Prime = FSTTestDoc(@"rooms/Eros", 1, @{@"name" : @"Eros"}, NO);
@@ -322,7 +322,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testWillWaitForSyncIfOnline {
NSMutableArray<FSTViewSnapshot *> *events = [NSMutableArray array];
- FSTQuery *query = [FSTQuery queryWithPath:FSTTestPath(@"rooms")];
+ FSTQuery *query = FSTTestQuery(@"rooms");
FSTDocument *doc1 = FSTTestDoc(@"rooms/Eros", 1, @{@"name" : @"Eros"}, NO);
FSTDocument *doc2 = FSTTestDoc(@"rooms/Hades", 2, @{@"name" : @"Hades"}, NO);
FSTQueryListener *listener =
@@ -340,10 +340,10 @@ NS_ASSUME_NONNULL_BEGIN
[FSTTargetChange changeWithDocuments:@[ doc1, doc2 ]
currentStatusUpdate:FSTCurrentStatusUpdateMarkCurrent]);
- [listener clientDidChangeOnlineState:FSTOnlineStateHealthy]; // no event
+ [listener applyChangedOnlineState:FSTOnlineStateHealthy]; // no event
[listener queryDidChangeViewSnapshot:snap1];
- [listener clientDidChangeOnlineState:FSTOnlineStateUnknown];
- [listener clientDidChangeOnlineState:FSTOnlineStateHealthy];
+ [listener applyChangedOnlineState:FSTOnlineStateUnknown];
+ [listener applyChangedOnlineState:FSTOnlineStateHealthy];
[listener queryDidChangeViewSnapshot:snap2];
[listener queryDidChangeViewSnapshot:snap3];
@@ -365,7 +365,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testWillRaiseInitialEventWhenGoingOffline {
NSMutableArray<FSTViewSnapshot *> *events = [NSMutableArray array];
- FSTQuery *query = [FSTQuery queryWithPath:FSTTestPath(@"rooms")];
+ FSTQuery *query = FSTTestQuery(@"rooms");
FSTDocument *doc1 = FSTTestDoc(@"rooms/Eros", 1, @{@"name" : @"Eros"}, NO);
FSTDocument *doc2 = FSTTestDoc(@"rooms/Hades", 2, @{@"name" : @"Hades"}, NO);
FSTQueryListener *listener =
@@ -379,12 +379,12 @@ NS_ASSUME_NONNULL_BEGIN
FSTViewSnapshot *snap1 = FSTTestApplyChanges(view, @[ doc1 ], nil);
FSTViewSnapshot *snap2 = FSTTestApplyChanges(view, @[ doc2 ], nil);
- [listener clientDidChangeOnlineState:FSTOnlineStateHealthy]; // no event
- [listener queryDidChangeViewSnapshot:snap1]; // no event
- [listener clientDidChangeOnlineState:FSTOnlineStateFailed]; // event
- [listener clientDidChangeOnlineState:FSTOnlineStateUnknown]; // no event
- [listener clientDidChangeOnlineState:FSTOnlineStateFailed]; // no event
- [listener queryDidChangeViewSnapshot:snap2]; // another event
+ [listener applyChangedOnlineState:FSTOnlineStateHealthy]; // no event
+ [listener queryDidChangeViewSnapshot:snap1]; // no event
+ [listener applyChangedOnlineState:FSTOnlineStateFailed]; // event
+ [listener applyChangedOnlineState:FSTOnlineStateUnknown]; // no event
+ [listener applyChangedOnlineState:FSTOnlineStateFailed]; // no event
+ [listener queryDidChangeViewSnapshot:snap2]; // another event
FSTDocumentViewChange *change1 =
[FSTDocumentViewChange changeWithDocument:doc1 type:FSTDocumentViewChangeTypeAdded];
@@ -411,7 +411,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testWillRaiseInitialEventWhenGoingOfflineAndThereAreNoDocs {
NSMutableArray<FSTViewSnapshot *> *events = [NSMutableArray array];
- FSTQuery *query = [FSTQuery queryWithPath:FSTTestPath(@"rooms")];
+ FSTQuery *query = FSTTestQuery(@"rooms");
FSTQueryListener *listener = [self listenToQuery:query
options:[FSTListenOptions defaultOptions]
accumulatingSnapshots:events];
@@ -419,9 +419,9 @@ NS_ASSUME_NONNULL_BEGIN
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
FSTViewSnapshot *snap1 = FSTTestApplyChanges(view, @[], nil);
- [listener clientDidChangeOnlineState:FSTOnlineStateHealthy]; // no event
- [listener queryDidChangeViewSnapshot:snap1]; // no event
- [listener clientDidChangeOnlineState:FSTOnlineStateFailed]; // event
+ [listener applyChangedOnlineState:FSTOnlineStateHealthy]; // no event
+ [listener queryDidChangeViewSnapshot:snap1]; // no event
+ [listener applyChangedOnlineState:FSTOnlineStateFailed]; // event
FSTViewSnapshot *expectedSnap = [[FSTViewSnapshot alloc]
initWithQuery:query
@@ -437,7 +437,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testWillRaiseInitialEventWhenStartingOfflineAndThereAreNoDocs {
NSMutableArray<FSTViewSnapshot *> *events = [NSMutableArray array];
- FSTQuery *query = [FSTQuery queryWithPath:FSTTestPath(@"rooms")];
+ FSTQuery *query = FSTTestQuery(@"rooms");
FSTQueryListener *listener = [self listenToQuery:query
options:[FSTListenOptions defaultOptions]
accumulatingSnapshots:events];
@@ -445,8 +445,8 @@ NS_ASSUME_NONNULL_BEGIN
FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
FSTViewSnapshot *snap1 = FSTTestApplyChanges(view, @[], nil);
- [listener clientDidChangeOnlineState:FSTOnlineStateFailed]; // no event
- [listener queryDidChangeViewSnapshot:snap1]; // event
+ [listener applyChangedOnlineState:FSTOnlineStateFailed]; // no event
+ [listener queryDidChangeViewSnapshot:snap1]; // event
FSTViewSnapshot *expectedSnap = [[FSTViewSnapshot alloc]
initWithQuery:query
diff --git a/Firestore/Example/Tests/Core/FSTQueryTests.m b/Firestore/Example/Tests/Core/FSTQueryTests.m
index 1fd0e8b..3d2bd82 100644
--- a/Firestore/Example/Tests/Core/FSTQueryTests.m
+++ b/Firestore/Example/Tests/Core/FSTQueryTests.m
@@ -61,9 +61,7 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testOrderBy {
- FSTResourcePath *path =
- [FSTResourcePath pathWithSegments:@[ @"rooms", @"Firestore", @"messages" ]];
- FSTQuery *query = [FSTQuery queryWithPath:path];
+ FSTQuery *query = FSTTestQuery(@"rooms/Firestore/messages");
query =
[query queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:FSTTestFieldPath(@"length")
ascending:NO]];
@@ -80,29 +78,25 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testMatchesBasedOnDocumentKey {
- FSTResourcePath *queryKey =
- [FSTResourcePath pathWithSegments:@[ @"rooms", @"eros", @"messages", @"1" ]];
FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, NO);
FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/2", 0, @{@"text" : @"msg2"}, NO);
FSTDocument *doc3 = FSTTestDoc(@"rooms/other/messages/1", 0, @{@"text" : @"msg3"}, NO);
// document query
- FSTQuery *query = [FSTQuery queryWithPath:queryKey];
+ FSTQuery *query = FSTTestQuery(@"rooms/eros/messages/1");
XCTAssertTrue([query matchesDocument:doc1]);
XCTAssertFalse([query matchesDocument:doc2]);
XCTAssertFalse([query matchesDocument:doc3]);
}
- (void)testMatchesCorrectlyForShallowAncestorQuery {
- FSTResourcePath *queryPath =
- [FSTResourcePath pathWithSegments:@[ @"rooms", @"eros", @"messages" ]];
FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, NO);
FSTDocument *doc1Meta = FSTTestDoc(@"rooms/eros/messages/1/meta/1", 0, @{@"meta" : @"mv"}, NO);
FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/2", 0, @{@"text" : @"msg2"}, NO);
FSTDocument *doc3 = FSTTestDoc(@"rooms/other/messages/1", 0, @{@"text" : @"msg3"}, NO);
// shallow ancestor query
- FSTQuery *query = [FSTQuery queryWithPath:queryPath];
+ FSTQuery *query = FSTTestQuery(@"rooms/eros/messages");
XCTAssertTrue([query matchesDocument:doc1]);
XCTAssertFalse([query matchesDocument:doc1Meta]);
XCTAssertTrue([query matchesDocument:doc2]);
@@ -110,21 +104,20 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testEmptyFieldsAreAllowedForQueries {
- FSTResourcePath *queryPath = [FSTResourcePath pathWithString:@"rooms/eros/messages"];
FSTDocument *doc1 = FSTTestDoc(@"rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, NO);
FSTDocument *doc2 = FSTTestDoc(@"rooms/eros/messages/2", 0, @{}, NO);
- FSTQuery *query = [[FSTQuery queryWithPath:queryPath]
+ FSTQuery *query = [FSTTestQuery(@"rooms/eros/messages")
queryByAddingFilter:FSTTestFilter(@"text", @"==", @"msg1")];
XCTAssertTrue([query matchesDocument:doc1]);
XCTAssertFalse([query matchesDocument:doc2]);
}
- (void)testMatchesPrimitiveValuesForFilters {
- FSTQuery *query1 = [[FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"collection" ]]]
- queryByAddingFilter:FSTTestFilter(@"sort", @">=", @(2))];
- FSTQuery *query2 = [[FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"collection" ]]]
- queryByAddingFilter:FSTTestFilter(@"sort", @"<=", @(2))];
+ FSTQuery *query1 =
+ [FSTTestQuery(@"collection") queryByAddingFilter:FSTTestFilter(@"sort", @">=", @(2))];
+ FSTQuery *query2 =
+ [FSTTestQuery(@"collection") queryByAddingFilter:FSTTestFilter(@"sort", @"<=", @(2))];
FSTDocument *doc1 = FSTTestDoc(@"collection/1", 0, @{ @"sort" : @1 }, NO);
FSTDocument *doc2 = FSTTestDoc(@"collection/2", 0, @{ @"sort" : @2 }, NO);
@@ -149,7 +142,7 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testNullFilter {
- FSTQuery *query = [[FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"collection" ]]]
+ FSTQuery *query = [FSTTestQuery(@"collection")
queryByAddingFilter:FSTTestFilter(@"sort", @"==", [NSNull null])];
FSTDocument *doc1 = FSTTestDoc(@"collection/1", 0, @{@"sort" : [NSNull null]}, NO);
FSTDocument *doc2 = FSTTestDoc(@"collection/2", 0, @{ @"sort" : @2 }, NO);
@@ -165,8 +158,8 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testNanFilter {
- FSTQuery *query = [[FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"collection" ]]]
- queryByAddingFilter:FSTTestFilter(@"sort", @"==", @(NAN))];
+ FSTQuery *query =
+ [FSTTestQuery(@"collection") queryByAddingFilter:FSTTestFilter(@"sort", @"==", @(NAN))];
FSTDocument *doc1 = FSTTestDoc(@"collection/1", 0, @{ @"sort" : @(NAN) }, NO);
FSTDocument *doc2 = FSTTestDoc(@"collection/2", 0, @{ @"sort" : @2 }, NO);
FSTDocument *doc3 = FSTTestDoc(@"collection/2", 0, @{ @"sort" : @3.1 }, NO);
@@ -181,10 +174,10 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testDoesNotMatchComplexObjectsForFilters {
- FSTQuery *query1 = [[FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"collection" ]]]
- queryByAddingFilter:FSTTestFilter(@"sort", @"<=", @(2))];
- FSTQuery *query2 = [[FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"collection" ]]]
- queryByAddingFilter:FSTTestFilter(@"sort", @">=", @(2))];
+ FSTQuery *query1 =
+ [FSTTestQuery(@"collection") queryByAddingFilter:FSTTestFilter(@"sort", @"<=", @(2))];
+ FSTQuery *query2 =
+ [FSTTestQuery(@"collection") queryByAddingFilter:FSTTestFilter(@"sort", @">=", @(2))];
FSTDocument *doc1 = FSTTestDoc(@"collection/1", 0, @{ @"sort" : @2 }, NO);
FSTDocument *doc2 = FSTTestDoc(@"collection/2", 0, @{ @"sort" : @[] }, NO);
@@ -212,7 +205,7 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testDoesntRemoveComplexObjectsWithOrderBy {
- FSTQuery *query1 = [[FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"collection" ]]]
+ FSTQuery *query1 = [FSTTestQuery(@"collection")
queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:FSTTestFieldPath(@"sort")
ascending:YES]];
@@ -232,9 +225,7 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testFiltersBasedOnArrayValue {
- FSTQuery *baseQuery =
- [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"collection" ]]];
-
+ FSTQuery *baseQuery = FSTTestQuery(@"collection");
FSTDocument *doc1 = FSTTestDoc(@"collection/doc", 0, @{ @"tags" : @[ @"foo", @1, @YES ] }, NO);
NSArray<id<FSTFilter>> *matchingFilters =
@@ -256,9 +247,7 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testFiltersBasedOnObjectValue {
- FSTQuery *baseQuery =
- [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"collection" ]]];
-
+ FSTQuery *baseQuery = FSTTestQuery(@"collection");
FSTDocument *doc1 =
FSTTestDoc(@"collection/doc", 0,
@{ @"tags" : @{@"foo" : @"foo", @"a" : @0, @"b" : @YES, @"c" : @(NAN)} }, NO);
@@ -310,7 +299,7 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testSortsDocumentsInTheCorrectOrder {
- FSTQuery *query = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"collection" ]]];
+ FSTQuery *query = FSTTestQuery(@"collection");
query =
[query queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:FSTTestFieldPath(@"sort")
ascending:YES]];
@@ -339,7 +328,7 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testSortsDocumentsUsingMultipleFields {
- FSTQuery *query = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"collection" ]]];
+ FSTQuery *query = FSTTestQuery(@"collection");
query =
[query queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:FSTTestFieldPath(@"sort1")
ascending:YES]];
@@ -366,7 +355,7 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testSortsDocumentsWithDescendingToo {
- FSTQuery *query = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"collection" ]]];
+ FSTQuery *query = FSTTestQuery(@"collection");
query =
[query queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:FSTTestFieldPath(@"sort1")
ascending:NO]];
@@ -393,40 +382,40 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testEquality {
- FSTQuery *q11 = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
+ FSTQuery *q11 = FSTTestQuery(@"foo");
q11 = [q11 queryByAddingFilter:FSTTestFilter(@"i1", @"<", @(2))];
q11 = [q11 queryByAddingFilter:FSTTestFilter(@"i2", @"==", @(3))];
- FSTQuery *q12 = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
+ FSTQuery *q12 = FSTTestQuery(@"foo");
q12 = [q12 queryByAddingFilter:FSTTestFilter(@"i2", @"==", @(3))];
q12 = [q12 queryByAddingFilter:FSTTestFilter(@"i1", @"<", @(2))];
- FSTQuery *q21 = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
- FSTQuery *q22 = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
+ FSTQuery *q21 = FSTTestQuery(@"foo");
+ FSTQuery *q22 = FSTTestQuery(@"foo");
- FSTQuery *q31 = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo", @"bar" ]]];
- FSTQuery *q32 = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo", @"bar" ]]];
+ FSTQuery *q31 = FSTTestQuery(@"foo/bar");
+ FSTQuery *q32 = FSTTestQuery(@"foo/bar");
- FSTQuery *q41 = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
+ FSTQuery *q41 = FSTTestQuery(@"foo");
q41 = [q41 queryByAddingSortBy:@"foo" ascending:YES];
q41 = [q41 queryByAddingSortBy:@"bar" ascending:YES];
- FSTQuery *q42 = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
+ FSTQuery *q42 = FSTTestQuery(@"foo");
q42 = [q42 queryByAddingSortBy:@"foo" ascending:YES];
q42 = [q42 queryByAddingSortBy:@"bar" ascending:YES];
- FSTQuery *q43Diff = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
+ FSTQuery *q43Diff = FSTTestQuery(@"foo");
q43Diff = [q43Diff queryByAddingSortBy:@"bar" ascending:YES];
q43Diff = [q43Diff queryByAddingSortBy:@"foo" ascending:YES];
- FSTQuery *q51 = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
+ FSTQuery *q51 = FSTTestQuery(@"foo");
q51 = [q51 queryByAddingSortBy:@"foo" ascending:YES];
q51 = [q51 queryByAddingFilter:FSTTestFilter(@"foo", @">", @(2))];
- FSTQuery *q52 = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
+ FSTQuery *q52 = FSTTestQuery(@"foo");
q52 = [q52 queryByAddingFilter:FSTTestFilter(@"foo", @">", @(2))];
q52 = [q52 queryByAddingSortBy:@"foo" ascending:YES];
- FSTQuery *q53Diff = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
+ FSTQuery *q53Diff = FSTTestQuery(@"foo");
q53Diff = [q53Diff queryByAddingFilter:FSTTestFilter(@"bar", @">", @(2))];
q53Diff = [q53Diff queryByAddingSortBy:@"bar" ascending:YES];
- FSTQuery *q61 = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
+ FSTQuery *q61 = FSTTestQuery(@"foo");
q61 = [q61 queryBySettingLimit:10];
// XCTAssertEqualObjects(q11, q12); // TODO(klimt): not canonical yet
@@ -458,40 +447,40 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testUniqueIds {
- FSTQuery *q11 = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
+ FSTQuery *q11 = FSTTestQuery(@"foo");
q11 = [q11 queryByAddingFilter:FSTTestFilter(@"i1", @"<", @(2))];
q11 = [q11 queryByAddingFilter:FSTTestFilter(@"i2", @"==", @(3))];
- FSTQuery *q12 = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
+ FSTQuery *q12 = FSTTestQuery(@"foo");
q12 = [q12 queryByAddingFilter:FSTTestFilter(@"i2", @"==", @(3))];
q12 = [q12 queryByAddingFilter:FSTTestFilter(@"i1", @"<", @(2))];
- FSTQuery *q21 = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
- FSTQuery *q22 = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
+ FSTQuery *q21 = FSTTestQuery(@"foo");
+ FSTQuery *q22 = FSTTestQuery(@"foo");
- FSTQuery *q31 = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo", @"bar" ]]];
- FSTQuery *q32 = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo", @"bar" ]]];
+ FSTQuery *q31 = FSTTestQuery(@"foo/bar");
+ FSTQuery *q32 = FSTTestQuery(@"foo/bar");
- FSTQuery *q41 = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
+ FSTQuery *q41 = FSTTestQuery(@"foo");
q41 = [q41 queryByAddingSortBy:@"foo" ascending:YES];
q41 = [q41 queryByAddingSortBy:@"bar" ascending:YES];
- FSTQuery *q42 = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
+ FSTQuery *q42 = FSTTestQuery(@"foo");
q42 = [q42 queryByAddingSortBy:@"foo" ascending:YES];
q42 = [q42 queryByAddingSortBy:@"bar" ascending:YES];
- FSTQuery *q43Diff = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
+ FSTQuery *q43Diff = FSTTestQuery(@"foo");
q43Diff = [q43Diff queryByAddingSortBy:@"bar" ascending:YES];
q43Diff = [q43Diff queryByAddingSortBy:@"foo" ascending:YES];
- FSTQuery *q51 = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
+ FSTQuery *q51 = FSTTestQuery(@"foo");
q51 = [q51 queryByAddingSortBy:@"foo" ascending:YES];
q51 = [q51 queryByAddingFilter:FSTTestFilter(@"foo", @">", @(2))];
- FSTQuery *q52 = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
+ FSTQuery *q52 = FSTTestQuery(@"foo");
q52 = [q52 queryByAddingFilter:FSTTestFilter(@"foo", @">", @(2))];
q52 = [q52 queryByAddingSortBy:@"foo" ascending:YES];
- FSTQuery *q53Diff = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
+ FSTQuery *q53Diff = FSTTestQuery(@"foo");
q53Diff = [q53Diff queryByAddingFilter:FSTTestFilter(@"bar", @">", @(2))];
q53Diff = [q53Diff queryByAddingSortBy:@"bar" ascending:YES];
- FSTQuery *q61 = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
+ FSTQuery *q61 = FSTTestQuery(@"foo");
q61 = [q61 queryBySettingLimit:10];
// XCTAssertEqual(q11.hash, q12.hash); // TODO(klimt): not canonical yet
diff --git a/Firestore/Example/Tests/Core/FSTViewSnapshotTest.m b/Firestore/Example/Tests/Core/FSTViewSnapshotTest.m
index 5d3787a..fe3e42d 100644
--- a/Firestore/Example/Tests/Core/FSTViewSnapshotTest.m
+++ b/Firestore/Example/Tests/Core/FSTViewSnapshotTest.m
@@ -107,7 +107,7 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testViewSnapshotConstructor {
- FSTQuery *query = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"a" ]]];
+ FSTQuery *query = FSTTestQuery(@"a");
FSTDocumentSet *documents = [FSTDocumentSet documentSetWithComparator:FSTDocumentComparatorByKey];
FSTDocumentSet *oldDocuments = documents;
documents = [documents documentSetByAddingDocument:FSTTestDoc(@"c/a", 1, @{}, NO)];
diff --git a/Firestore/Example/Tests/Integration/API/FIRDatabaseTests.m b/Firestore/Example/Tests/Integration/API/FIRDatabaseTests.m
index 087eb01..f557ee6 100644
--- a/Firestore/Example/Tests/Integration/API/FIRDatabaseTests.m
+++ b/Firestore/Example/Tests/Integration/API/FIRDatabaseTests.m
@@ -16,6 +16,7 @@
@import FirebaseFirestore;
+#import <FirebaseFirestore/FIRFirestore.h>
#import <XCTest/XCTest.h>
#import "Firestore/Example/Tests/Util/FSTIntegrationTestCase.h"
@@ -83,6 +84,13 @@
XCTAssertFalse(result.exists);
}
+- (void)testCanRetrieveDocumentThatDoesNotExist {
+ FIRDocumentReference *doc = [[self.db collectionWithPath:@"rooms"] documentWithAutoID];
+ FIRDocumentSnapshot *result = [self readDocumentForRef:doc];
+ XCTAssertNil(result.data);
+ XCTAssertNil(result[@"foo"]);
+}
+
- (void)testCannotUpdateNonexistentDocument {
FIRDocumentReference *doc = [[self.db collectionWithPath:@"rooms"] documentWithAutoID];
@@ -844,7 +852,7 @@
FIRFirestore *firestore = doc.firestore;
NSDictionary<NSString *, id> *data = @{@"a" : @"b"};
- [firestore.client disableNetworkWithCompletion:^(NSError *error) {
+ [firestore disableNetworkWithCompletion:^(NSError *error) {
XCTAssertNil(error);
[doc setData:data
@@ -853,7 +861,7 @@
[writeEpectation fulfill];
}];
- [firestore.client enableNetworkWithCompletion:^(NSError *error) {
+ [firestore enableNetworkWithCompletion:^(NSError *error) {
XCTAssertNil(error);
[networkExpectation fulfill];
}];
@@ -883,7 +891,7 @@
__weak FIRDocumentReference *weakDoc = doc;
- [firestore.client disableNetworkWithCompletion:^(NSError *error) {
+ [firestore disableNetworkWithCompletion:^(NSError *error) {
XCTAssertNil(error);
[doc setData:data
completion:^(NSError *_Nullable error) {
@@ -904,7 +912,7 @@
// Verify that we are reading from cache.
XCTAssertTrue(snapshot.metadata.fromCache);
XCTAssertEqualObjects(snapshot.data, data);
- [firestore.client enableNetworkWithCompletion:^(NSError *error) {
+ [firestore enableNetworkWithCompletion:^(NSError *error) {
[networkExpectation fulfill];
}];
}];
@@ -931,4 +939,25 @@
[self readSnapshotForRef:[self documentRef] requireOnline:YES];
}
+- (void)testCanDisableNetwork {
+ FIRDocumentReference *doc = [self documentRef];
+ FIRFirestore *firestore = doc.firestore;
+
+ [firestore enableNetworkWithCompletion:[self completionForExpectationWithName:@"Enable network"]];
+ [self awaitExpectations];
+ [firestore
+ enableNetworkWithCompletion:[self completionForExpectationWithName:@"Enable network again"]];
+ [self awaitExpectations];
+ [firestore
+ disableNetworkWithCompletion:[self completionForExpectationWithName:@"Disable network"]];
+ [self awaitExpectations];
+ [firestore
+ disableNetworkWithCompletion:[self
+ completionForExpectationWithName:@"Disable network again"]];
+ [self awaitExpectations];
+ [firestore
+ enableNetworkWithCompletion:[self completionForExpectationWithName:@"Final enable network"]];
+ [self awaitExpectations];
+}
+
@end
diff --git a/Firestore/Example/Tests/Integration/API/FIRListenerRegistrationTests.m b/Firestore/Example/Tests/Integration/API/FIRListenerRegistrationTests.m
index 9751844..52d73b1 100644
--- a/Firestore/Example/Tests/Integration/API/FIRListenerRegistrationTests.m
+++ b/Firestore/Example/Tests/Integration/API/FIRListenerRegistrationTests.m
@@ -128,35 +128,4 @@
[two remove];
}
-- (void)testWatchSurvivesNetworkDisconnect {
- XCTestExpectation *testExpectiation =
- [self expectationWithDescription:@"testWatchSurvivesNetworkDisconnect"];
-
- FIRCollectionReference *collectionRef = [self collectionRef];
- FIRDocumentReference *docRef = [collectionRef documentWithAutoID];
-
- FIRFirestore *firestore = collectionRef.firestore;
-
- FIRQueryListenOptions *options = [[[FIRQueryListenOptions options]
- includeDocumentMetadataChanges:YES] includeQueryMetadataChanges:YES];
-
- [collectionRef addSnapshotListenerWithOptions:options
- listener:^(FIRQuerySnapshot *snapshot, NSError *error) {
- XCTAssertNil(error);
- if (!snapshot.empty && !snapshot.metadata.fromCache) {
- [testExpectiation fulfill];
- }
- }];
-
- [firestore.client disableNetworkWithCompletion:^(NSError *error) {
- XCTAssertNil(error);
- [docRef setData:@{@"foo" : @"bar"}];
- [firestore.client enableNetworkWithCompletion:^(NSError *error) {
- XCTAssertNil(error);
- }];
- }];
-
- [self awaitExpectations];
-}
-
@end
diff --git a/Firestore/Example/Tests/Integration/API/FIRQueryTests.m b/Firestore/Example/Tests/Integration/API/FIRQueryTests.m
index 180b423..831c897 100644
--- a/Firestore/Example/Tests/Integration/API/FIRQueryTests.m
+++ b/Firestore/Example/Tests/Integration/API/FIRQueryTests.m
@@ -18,9 +18,10 @@
#import <XCTest/XCTest.h>
-#import "Firestore/Source/Core/FSTFirestoreClient.h"
-
+#import "Firestore/Example/Tests/Util/FSTEventAccumulator.h"
#import "Firestore/Example/Tests/Util/FSTIntegrationTestCase.h"
+#import "Firestore/Source/API/FIRFirestore+Internal.h"
+#import "Firestore/Source/Core/FSTFirestoreClient.h"
@interface FIRQueryTests : FSTIntegrationTestCase
@end
@@ -111,6 +112,23 @@
XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(snapshot), (@[ @"b", @"a" ]));
}
+- (void)testQueryWithPredicate {
+ FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
+ @"a" : @{@"a" : @1},
+ @"b" : @{@"a" : @2},
+ @"c" : @{@"a" : @3}
+ }];
+
+ NSPredicate *predicate = [NSPredicate predicateWithFormat:@"a < 3"];
+ FIRQuery *query = [collRef queryFilteredUsingPredicate:predicate];
+ query = [query queryOrderedByFieldPath:[[FIRFieldPath alloc] initWithFields:@[ @"a" ]]
+ descending:YES];
+
+ FIRQuerySnapshot *snapshot = [self readDocumentSetForRef:query];
+
+ XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(snapshot), (@[ @"b", @"a" ]));
+}
+
- (void)testFilterOnInfinity {
FIRCollectionReference *collRef = [self collectionRefWithDocuments:@{
@"a" : @{@"inf" : @(INFINITY)},
@@ -194,4 +212,67 @@
XCTAssertEqualObjects(FIRQuerySnapshotGetData(docs), (@[ testDocs[@"ab"], testDocs[@"ba"] ]));
}
+- (void)testWatchSurvivesNetworkDisconnect {
+ XCTestExpectation *testExpectiation =
+ [self expectationWithDescription:@"testWatchSurvivesNetworkDisconnect"];
+
+ FIRCollectionReference *collectionRef = [self collectionRef];
+ FIRDocumentReference *docRef = [collectionRef documentWithAutoID];
+
+ FIRFirestore *firestore = collectionRef.firestore;
+
+ FIRQueryListenOptions *options = [[[FIRQueryListenOptions options]
+ includeDocumentMetadataChanges:YES] includeQueryMetadataChanges:YES];
+
+ [collectionRef addSnapshotListenerWithOptions:options
+ listener:^(FIRQuerySnapshot *snapshot, NSError *error) {
+ XCTAssertNil(error);
+ if (!snapshot.empty && !snapshot.metadata.fromCache) {
+ [testExpectiation fulfill];
+ }
+ }];
+
+ [firestore disableNetworkWithCompletion:^(NSError *error) {
+ XCTAssertNil(error);
+ [docRef setData:@{@"foo" : @"bar"}];
+ [firestore enableNetworkWithCompletion:^(NSError *error) {
+ XCTAssertNil(error);
+ }];
+ }];
+
+ [self awaitExpectations];
+}
+
+- (void)testQueriesFireFromCacheWhenOffline {
+ NSDictionary *testDocs = @{
+ @"a" : @{@"foo" : @1},
+ };
+ FIRCollectionReference *collection = [self collectionRefWithDocuments:testDocs];
+
+ FIRQueryListenOptions *options = [[[FIRQueryListenOptions options]
+ includeDocumentMetadataChanges:YES] includeQueryMetadataChanges:YES];
+ id<FIRListenerRegistration> registration =
+ [collection addSnapshotListenerWithOptions:options
+ listener:self.eventAccumulator.valueEventHandler];
+
+ FIRQuerySnapshot *querySnap = [self.eventAccumulator awaitEventWithName:@"initial event"];
+ XCTAssertEqualObjects(FIRQuerySnapshotGetData(querySnap), @[ @{ @"foo" : @1 } ]);
+ XCTAssertEqual(querySnap.metadata.isFromCache, NO);
+
+ [self disableNetwork];
+ querySnap = [self.eventAccumulator awaitEventWithName:@"offline event with isFromCache=YES"];
+ XCTAssertEqual(querySnap.metadata.isFromCache, YES);
+
+ // TODO(b/70631617): There's currently a backend bug that prevents us from using a resume token
+ // right away (against hexa at least). So we sleep. :-( :-( Anything over ~10ms seems to be
+ // sufficient.
+ [NSThread sleepForTimeInterval:0.2f];
+
+ [self enableNetwork];
+ querySnap = [self.eventAccumulator awaitEventWithName:@"back online event with isFromCache=NO"];
+ XCTAssertEqual(querySnap.metadata.isFromCache, NO);
+
+ [registration remove];
+}
+
@end
diff --git a/Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.m b/Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.m
index 2ee3966..cc0ab29 100644
--- a/Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.m
+++ b/Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.m
@@ -18,10 +18,10 @@
#import <XCTest/XCTest.h>
-#import "Firestore/Source/Core/FSTFirestoreClient.h"
-
#import "Firestore/Example/Tests/Util/FSTEventAccumulator.h"
#import "Firestore/Example/Tests/Util/FSTIntegrationTestCase.h"
+#import "Firestore/Source/API/FIRFirestore+Internal.h"
+#import "Firestore/Source/Core/FSTFirestoreClient.h"
@interface FIRServerTimestampTests : FSTIntegrationTestCase
@end
@@ -42,11 +42,20 @@
// Listener registration for a listener maintained during the course of the test.
id<FIRListenerRegistration> _listenerRegistration;
+
+ // Snapshot options that return the previous value for pending server timestamps.
+ FIRSnapshotOptions *_returnPreviousValue;
+ FIRSnapshotOptions *_returnEstimatedValue;
}
- (void)setUp {
[super setUp];
+ _returnPreviousValue =
+ [FIRSnapshotOptions serverTimestampBehavior:FIRServerTimestampBehaviorPrevious];
+ _returnEstimatedValue =
+ [FIRSnapshotOptions serverTimestampBehavior:FIRServerTimestampBehaviorEstimate];
+
// Data written in tests via set.
_setData = @{
@"a" : @42,
@@ -63,7 +72,7 @@
_docRef = [self documentRef];
_accumulator = [FSTEventAccumulator accumulatorForTest:self];
- _listenerRegistration = [_docRef addSnapshotListener:_accumulator.handler];
+ _listenerRegistration = [_docRef addSnapshotListener:_accumulator.valueEventHandler];
// Wait for initial nil snapshot to avoid potential races.
FIRDocumentSnapshot *initialSnapshot = [_accumulator awaitEventWithName:@"initial event"];
@@ -76,8 +85,10 @@
[super tearDown];
}
-// Returns the expected data, with an arbitrary timestamp substituted in.
-- (NSDictionary *)expectedDataWithTimestamp:(id _Nullable)timestamp {
+#pragma mark - Test Helpers
+
+/** Returns the expected data, with the specified timestamp substituted in. */
+- (NSDictionary *)expectedDataWithTimestamp:(nullable id)timestamp {
return @{ @"a" : @42, @"when" : timestamp, @"deep" : @{@"when" : timestamp} };
}
@@ -88,26 +99,65 @@
XCTAssertEqualObjects(initialDataSnap.data, _initialData);
}
-/** Waits for a snapshot containing _setData but with NSNull for the timestamps. */
-- (void)waitForLocalEvent {
- FIRDocumentSnapshot *localSnap = [_accumulator awaitEventWithName:@"Local event."];
- XCTAssertEqualObjects(localSnap.data, [self expectedDataWithTimestamp:[NSNull null]]);
+/** Waits for a snapshot with local writes. */
+- (FIRDocumentSnapshot *)waitForLocalEvent {
+ FIRDocumentSnapshot *snapshot;
+ do {
+ snapshot = [_accumulator awaitEventWithName:@"Local event."];
+ } while (!snapshot.metadata.hasPendingWrites);
+ return snapshot;
+}
+
+/** Waits for a snapshot that has no pending writes */
+- (FIRDocumentSnapshot *)waitForRemoteEvent {
+ FIRDocumentSnapshot *snapshot;
+ do {
+ snapshot = [_accumulator awaitEventWithName:@"Remote event."];
+ } while (snapshot.metadata.hasPendingWrites);
+ return snapshot;
+}
+
+/** Verifies a snapshot containing _setData but with NSNull for the timestamps. */
+- (void)verifyTimestampsAreNullInSnapshot:(FIRDocumentSnapshot *)snapshot {
+ XCTAssertEqualObjects(snapshot.data, [self expectedDataWithTimestamp:[NSNull null]]);
+}
+
+/** Verifies a snapshot containing _setData but with a local estimate for the timestamps. */
+- (void)verifyTimestampsAreEstimatedInSnapshot:(FIRDocumentSnapshot *)snapshot {
+ id timestamp = [snapshot valueForField:@"when" options:_returnEstimatedValue];
+ XCTAssertTrue([timestamp isKindOfClass:[NSDate class]]);
+ XCTAssertEqualObjects([snapshot dataWithOptions:_returnEstimatedValue],
+ [self expectedDataWithTimestamp:timestamp]);
}
-/** Waits for a snapshot containing _setData but with resolved server timestamps. */
-- (void)waitForRemoteEvent {
- // server event should have a resolved timestamp; verify it.
- FIRDocumentSnapshot *remoteSnap = [_accumulator awaitEventWithName:@"Remote event"];
- XCTAssertTrue(remoteSnap.exists);
- NSDate *when = remoteSnap[@"when"];
+/**
+ * Verifies a snapshot containing _setData but using the previous field value for server
+ * timestamps.
+ */
+- (void)verifyTimestampsInSnapshot:(FIRDocumentSnapshot *)snapshot
+ fromPreviousSnapshot:(nullable FIRDocumentSnapshot *)previousSnapshot {
+ if (previousSnapshot == nil) {
+ XCTAssertEqualObjects([snapshot dataWithOptions:_returnPreviousValue],
+ [self expectedDataWithTimestamp:[NSNull null]]);
+ } else {
+ XCTAssertEqualObjects([snapshot dataWithOptions:_returnPreviousValue],
+ [self expectedDataWithTimestamp:previousSnapshot[@"when"]]);
+ }
+}
+
+/** Verifies a snapshot containing _setData but with resolved server timestamps. */
+- (void)verifySnapshotWithResolvedTimestamps:(FIRDocumentSnapshot *)snapshot {
+ XCTAssertTrue(snapshot.exists);
+ NSDate *when = snapshot[@"when"];
XCTAssertTrue([when isKindOfClass:[NSDate class]]);
// Tolerate up to 10 seconds of clock skew between client and server.
XCTAssertEqualWithAccuracy(when.timeIntervalSinceNow, 0, 10);
// Validate the rest of the document.
- XCTAssertEqualObjects(remoteSnap.data, [self expectedDataWithTimestamp:when]);
+ XCTAssertEqualObjects(snapshot.data, [self expectedDataWithTimestamp:when]);
}
+/** Runs a transaction block. */
- (void)runTransactionBlock:(void (^)(FIRTransaction *transaction))transactionBlock {
XCTestExpectation *expectation = [self expectationWithDescription:@"transaction complete"];
[_docRef.firestore runTransactionWithBlock:^id(FIRTransaction *transaction, NSError **pError) {
@@ -121,17 +171,102 @@
[self awaitExpectations];
}
+#pragma mark - Test Cases
+
- (void)testServerTimestampsWorkViaSet {
[self writeDocumentRef:_docRef data:_setData];
- [self waitForLocalEvent];
- [self waitForRemoteEvent];
+ [self verifyTimestampsAreNullInSnapshot:[self waitForLocalEvent]];
+ [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]];
}
- (void)testServerTimestampsWorkViaUpdate {
[self writeInitialData];
[self updateDocumentRef:_docRef data:_updateData];
- [self waitForLocalEvent];
- [self waitForRemoteEvent];
+ [self verifyTimestampsAreNullInSnapshot:[self waitForLocalEvent]];
+ [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]];
+}
+
+- (void)testServerTimestampsWithEstimatedValue {
+ [self writeDocumentRef:_docRef data:_setData];
+ [self verifyTimestampsAreEstimatedInSnapshot:[self waitForLocalEvent]];
+ [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]];
+}
+
+- (void)testServerTimestampsWithPreviousValue {
+ [self writeDocumentRef:_docRef data:_setData];
+ [self verifyTimestampsInSnapshot:[self waitForLocalEvent] fromPreviousSnapshot:nil];
+ FIRDocumentSnapshot *remoteSnapshot = [self waitForRemoteEvent];
+
+ [_docRef updateData:_updateData];
+ [self verifyTimestampsInSnapshot:[self waitForLocalEvent] fromPreviousSnapshot:remoteSnapshot];
+
+ [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]];
+}
+
+- (void)testServerTimestampsWithPreviousValueOfDifferentType {
+ [self writeDocumentRef:_docRef data:_setData];
+ [self verifyTimestampsInSnapshot:[self waitForLocalEvent] fromPreviousSnapshot:nil];
+ [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]];
+
+ [_docRef updateData:@{@"a" : [FIRFieldValue fieldValueForServerTimestamp]}];
+ FIRDocumentSnapshot *localSnapshot = [self waitForLocalEvent];
+ XCTAssertEqualObjects([localSnapshot valueForField:@"a"], [NSNull null]);
+ XCTAssertEqualObjects([localSnapshot valueForField:@"a" options:_returnPreviousValue], @42);
+ XCTAssertTrue([[localSnapshot valueForField:@"a" options:_returnEstimatedValue]
+ isKindOfClass:[NSDate class]]);
+
+ FIRDocumentSnapshot *remoteSnapshot = [self waitForRemoteEvent];
+ XCTAssertTrue([[remoteSnapshot valueForField:@"a"] isKindOfClass:[NSDate class]]);
+ XCTAssertTrue([[remoteSnapshot valueForField:@"a" options:_returnPreviousValue]
+ isKindOfClass:[NSDate class]]);
+ XCTAssertTrue([[remoteSnapshot valueForField:@"a" options:_returnEstimatedValue]
+ isKindOfClass:[NSDate class]]);
+}
+
+- (void)testServerTimestampsWithConsecutiveUpdates {
+ [self writeDocumentRef:_docRef data:_setData];
+ [self verifyTimestampsInSnapshot:[self waitForLocalEvent] fromPreviousSnapshot:nil];
+ [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]];
+
+ [self disableNetwork];
+
+ [_docRef updateData:@{@"a" : [FIRFieldValue fieldValueForServerTimestamp]}];
+ FIRDocumentSnapshot *localSnapshot = [self waitForLocalEvent];
+ XCTAssertEqualObjects([localSnapshot valueForField:@"a" options:_returnPreviousValue], @42);
+
+ [_docRef updateData:@{@"a" : [FIRFieldValue fieldValueForServerTimestamp]}];
+ localSnapshot = [self waitForLocalEvent];
+ XCTAssertEqualObjects([localSnapshot valueForField:@"a" options:_returnPreviousValue], @42);
+
+ [self enableNetwork];
+
+ FIRDocumentSnapshot *remoteSnapshot = [self waitForRemoteEvent];
+ XCTAssertTrue([[remoteSnapshot valueForField:@"a"] isKindOfClass:[NSDate class]]);
+}
+
+- (void)testServerTimestampsPreviousValueFromLocalMutation {
+ [self writeDocumentRef:_docRef data:_setData];
+ [self verifyTimestampsInSnapshot:[self waitForLocalEvent] fromPreviousSnapshot:nil];
+ [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]];
+
+ [self disableNetwork];
+
+ [_docRef updateData:@{@"a" : [FIRFieldValue fieldValueForServerTimestamp]}];
+ FIRDocumentSnapshot *localSnapshot = [self waitForLocalEvent];
+ XCTAssertEqualObjects([localSnapshot valueForField:@"a" options:_returnPreviousValue], @42);
+
+ [_docRef updateData:@{ @"a" : @1337 }];
+ localSnapshot = [self waitForLocalEvent];
+ XCTAssertEqualObjects([localSnapshot valueForField:@"a"], @1337);
+
+ [_docRef updateData:@{@"a" : [FIRFieldValue fieldValueForServerTimestamp]}];
+ localSnapshot = [self waitForLocalEvent];
+ XCTAssertEqualObjects([localSnapshot valueForField:@"a" options:_returnPreviousValue], @1337);
+
+ [self enableNetwork];
+
+ FIRDocumentSnapshot *remoteSnapshot = [self waitForRemoteEvent];
+ XCTAssertTrue([[remoteSnapshot valueForField:@"a"] isKindOfClass:[NSDate class]]);
}
- (void)testServerTimestampsWorkViaTransactionSet {
@@ -139,7 +274,7 @@
[transaction setData:_setData forDocument:_docRef];
}];
- [self waitForRemoteEvent];
+ [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]];
}
- (void)testServerTimestampsWorkViaTransactionUpdate {
@@ -147,7 +282,7 @@
[self runTransactionBlock:^(FIRTransaction *transaction) {
[transaction updateData:_updateData forDocument:_docRef];
}];
- [self waitForRemoteEvent];
+ [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]];
}
- (void)testServerTimestampsFailViaUpdateOnNonexistentDocument {
diff --git a/Firestore/Example/Tests/Integration/API/FIRWriteBatchTests.m b/Firestore/Example/Tests/Integration/API/FIRWriteBatchTests.m
index 562c29f..5e7f6d7 100644
--- a/Firestore/Example/Tests/Integration/API/FIRWriteBatchTests.m
+++ b/Firestore/Example/Tests/Integration/API/FIRWriteBatchTests.m
@@ -35,6 +35,29 @@
[self awaitExpectations];
}
+- (void)testCommitWithoutCompletionHandler {
+ FIRDocumentReference *doc = [self documentRef];
+ FIRWriteBatch *batch1 = [doc.firestore batch];
+ [batch1 setData:@{@"aa" : @"bb"} forDocument:doc];
+ [batch1 commitWithCompletion:nil];
+ FIRDocumentSnapshot *snapshot1 = [self readDocumentForRef:doc];
+ XCTAssertTrue(snapshot1.exists);
+ XCTAssertEqualObjects(snapshot1.data, @{@"aa" : @"bb"});
+
+ FIRWriteBatch *batch2 = [doc.firestore batch];
+ [batch2 setData:@{@"cc" : @"dd"} forDocument:doc];
+ [batch2 commit];
+
+ // TODO(b/70631617): There's currently a backend bug that prevents us from using a resume token
+ // right away (against hexa at least). So we sleep. :-( :-( Anything over ~10ms seems to be
+ // sufficient.
+ [NSThread sleepForTimeInterval:0.2f];
+
+ FIRDocumentSnapshot *snapshot2 = [self readDocumentForRef:doc];
+ XCTAssertTrue(snapshot2.exists);
+ XCTAssertEqualObjects(snapshot2.data, @{@"cc" : @"dd"});
+}
+
- (void)testSetDocuments {
FIRDocumentReference *doc = [self documentRef];
XCTestExpectation *batchExpectation = [self expectationWithDescription:@"batch written"];
@@ -131,7 +154,7 @@
FSTEventAccumulator *accumulator = [FSTEventAccumulator accumulatorForTest:self];
[collection addSnapshotListenerWithOptions:[[FIRQueryListenOptions options]
includeQueryMetadataChanges:YES]
- listener:accumulator.handler];
+ listener:accumulator.valueEventHandler];
FIRQuerySnapshot *initialSnap = [accumulator awaitEventWithName:@"initial event"];
XCTAssertEqual(initialSnap.count, 0);
@@ -161,7 +184,7 @@
FSTEventAccumulator *accumulator = [FSTEventAccumulator accumulatorForTest:self];
[collection addSnapshotListenerWithOptions:[[FIRQueryListenOptions options]
includeQueryMetadataChanges:YES]
- listener:accumulator.handler];
+ listener:accumulator.valueEventHandler];
FIRQuerySnapshot *initialSnap = [accumulator awaitEventWithName:@"initial event"];
XCTAssertEqual(initialSnap.count, 0);
@@ -195,7 +218,7 @@
FSTEventAccumulator *accumulator = [FSTEventAccumulator accumulatorForTest:self];
[collection addSnapshotListenerWithOptions:[[FIRQueryListenOptions options]
includeQueryMetadataChanges:YES]
- listener:accumulator.handler];
+ listener:accumulator.valueEventHandler];
FIRQuerySnapshot *initialSnap = [accumulator awaitEventWithName:@"initial event"];
XCTAssertEqual(initialSnap.count, 0);
@@ -227,7 +250,7 @@
FSTEventAccumulator *accumulator = [FSTEventAccumulator accumulatorForTest:self];
[doc
addSnapshotListenerWithOptions:[[FIRDocumentListenOptions options] includeMetadataChanges:YES]
- listener:accumulator.handler];
+ listener:accumulator.valueEventHandler];
FIRDocumentSnapshot *initialSnap = [accumulator awaitEventWithName:@"initial event"];
XCTAssertFalse(initialSnap.exists);
diff --git a/Firestore/Example/Tests/Integration/FSTSmokeTests.m b/Firestore/Example/Tests/Integration/FSTSmokeTests.m
index 847474a..ad75e50 100644
--- a/Firestore/Example/Tests/Integration/FSTSmokeTests.m
+++ b/Firestore/Example/Tests/Integration/FSTSmokeTests.m
@@ -48,7 +48,7 @@
[self writeDocumentRef:writerRef data:data];
id<FIRListenerRegistration> listenerRegistration =
- [readerRef addSnapshotListener:self.eventAccumulator.handler];
+ [readerRef addSnapshotListener:self.eventAccumulator.valueEventHandler];
FIRDocumentSnapshot *doc = [self.eventAccumulator awaitEventWithName:@"snapshot"];
XCTAssertEqual([doc class], [FIRDocumentSnapshot class]);
@@ -62,7 +62,7 @@
[self readerAndWriterOnDocumentRef:^(NSString *path, FIRDocumentReference *readerRef,
FIRDocumentReference *writerRef) {
id<FIRListenerRegistration> listenerRegistration =
- [readerRef addSnapshotListener:self.eventAccumulator.handler];
+ [readerRef addSnapshotListener:self.eventAccumulator.valueEventHandler];
FIRDocumentSnapshot *doc1 = [self.eventAccumulator awaitEventWithName:@"null snapshot"];
XCTAssertFalse(doc1.exists);
@@ -82,7 +82,7 @@
- (void)testWillFireValueEventsForEmptyCollections {
FIRCollectionReference *collection = [self.db collectionWithPath:@"empty-collection"];
id<FIRListenerRegistration> listenerRegistration =
- [collection addSnapshotListener:self.eventAccumulator.handler];
+ [collection addSnapshotListener:self.eventAccumulator.valueEventHandler];
FIRQuerySnapshot *snap = [self.eventAccumulator awaitEventWithName:@"empty query snapshot"];
XCTAssertEqual([snap class], [FIRQuerySnapshot class]);
diff --git a/Firestore/Example/Tests/Local/FSTEagerGarbageCollectorTests.m b/Firestore/Example/Tests/Local/FSTEagerGarbageCollectorTests.m
index 1dd6d62..53f0202 100644
--- a/Firestore/Example/Tests/Local/FSTEagerGarbageCollectorTests.m
+++ b/Firestore/Example/Tests/Local/FSTEagerGarbageCollectorTests.m
@@ -35,7 +35,7 @@ NS_ASSUME_NONNULL_BEGIN
FSTReferenceSet *referenceSet = [[FSTReferenceSet alloc] init];
[gc addGarbageSource:referenceSet];
- FSTDocumentKey *key = [FSTDocumentKey keyWithPathString:@"foo/bar"];
+ FSTDocumentKey *key = FSTTestDocKey(@"foo/bar");
[referenceSet addReferenceToKey:key forID:1];
FSTAssertEqualSets([gc collectGarbage], @[]);
XCTAssertFalse([referenceSet isEmpty]);
@@ -50,9 +50,9 @@ NS_ASSUME_NONNULL_BEGIN
FSTReferenceSet *referenceSet = [[FSTReferenceSet alloc] init];
[gc addGarbageSource:referenceSet];
- FSTDocumentKey *key1 = [FSTDocumentKey keyWithPathString:@"foo/bar"];
- FSTDocumentKey *key2 = [FSTDocumentKey keyWithPathString:@"foo/baz"];
- FSTDocumentKey *key3 = [FSTDocumentKey keyWithPathString:@"foo/blah"];
+ FSTDocumentKey *key1 = FSTTestDocKey(@"foo/bar");
+ FSTDocumentKey *key2 = FSTTestDocKey(@"foo/baz");
+ FSTDocumentKey *key3 = FSTTestDocKey(@"foo/blah");
[referenceSet addReferenceToKey:key1 forID:1];
[referenceSet addReferenceToKey:key2 forID:1];
[referenceSet addReferenceToKey:key3 forID:2];
@@ -77,12 +77,12 @@ NS_ASSUME_NONNULL_BEGIN
[gc addGarbageSource:localViews];
[gc addGarbageSource:mutations];
- FSTDocumentKey *key1 = [FSTDocumentKey keyWithPathString:@"foo/bar"];
+ FSTDocumentKey *key1 = FSTTestDocKey(@"foo/bar");
[remoteTargets addReferenceToKey:key1 forID:1];
[localViews addReferenceToKey:key1 forID:1];
[mutations addReferenceToKey:key1 forID:10];
- FSTDocumentKey *key2 = [FSTDocumentKey keyWithPathString:@"foo/baz"];
+ FSTDocumentKey *key2 = FSTTestDocKey(@"foo/baz");
[mutations addReferenceToKey:key2 forID:10];
XCTAssertFalse([remoteTargets isEmpty]);
diff --git a/Firestore/Example/Tests/Local/FSTLocalSerializerTests.m b/Firestore/Example/Tests/Local/FSTLocalSerializerTests.m
index 90f9ca3..27c3dc3 100644
--- a/Firestore/Example/Tests/Local/FSTLocalSerializerTests.m
+++ b/Firestore/Example/Tests/Local/FSTLocalSerializerTests.m
@@ -70,7 +70,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testEncodesMutationBatch {
FSTMutation *set = FSTTestSetMutation(@"foo/bar", @{ @"a" : @"b", @"num" : @1 });
FSTMutation *patch = [[FSTPatchMutation alloc]
- initWithKey:[FSTDocumentKey keyWithPathString:@"bar/baz"]
+ initWithKey:FSTTestDocKey(@"bar/baz")
fieldMask:[[FSTFieldMask alloc] initWithFields:@[ FSTTestFieldPath(@"a") ]]
value:FSTTestObjectValue(
@{ @"a" : @"b",
diff --git a/Firestore/Example/Tests/Local/FSTLocalStoreTests.m b/Firestore/Example/Tests/Local/FSTLocalStoreTests.m
index 245e1c4..45d1815 100644
--- a/Firestore/Example/Tests/Local/FSTLocalStoreTests.m
+++ b/Firestore/Example/Tests/Local/FSTLocalStoreTests.m
@@ -196,8 +196,7 @@ FSTDocumentVersionDictionary *FSTVersionDictionary(FSTMutation *mutation,
NSEnumerator<NSString *> *keyPathEnumerator = keyPaths.objectEnumerator; \
[actual enumerateKeysAndObjectsUsingBlock:^(FSTDocumentKey * actualKey, \
FSTMaybeDocument * value, BOOL * stop) { \
- FSTDocumentKey *expectedKey = \
- [FSTDocumentKey keyWithPathString:[keyPathEnumerator nextObject]]; \
+ FSTDocumentKey *expectedKey = FSTTestDocKey([keyPathEnumerator nextObject]); \
XCTAssertEqualObjects(actualKey, expectedKey); \
XCTAssertTrue([value isKindOfClass:[FSTDeletedDocument class]]); \
}]; \
@@ -213,11 +212,11 @@ FSTDocumentVersionDictionary *FSTVersionDictionary(FSTMutation *mutation,
} while (0)
/** Asserts that the given local store does not contain the given document. */
-#define FSTAssertNotContains(keyPathString) \
- do { \
- FSTDocumentKey *key = [FSTDocumentKey keyWithPathString:keyPathString]; \
- FSTMaybeDocument *actual = [self.localStore readDocument:key]; \
- XCTAssertNil(actual); \
+#define FSTAssertNotContains(keyPathString) \
+ do { \
+ FSTDocumentKey *key = FSTTestDocKey(keyPathString); \
+ FSTMaybeDocument *actual = [self.localStore readDocument:key]; \
+ XCTAssertNil(actual); \
} while (0)
- (void)testMutationBatchKeys {
@@ -261,7 +260,7 @@ FSTDocumentVersionDictionary *FSTVersionDictionary(FSTMutation *mutation,
if ([self isTestBaseClass]) return;
// Start a query that requires acks to be held.
- FSTQuery *query = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
+ FSTQuery *query = FSTTestQuery(@"foo");
[self allocateQuery:query];
[self writeMutation:FSTTestSetMutation(@"foo/bar", @{@"foo" : @"bar"})];
@@ -554,7 +553,7 @@ FSTDocumentVersionDictionary *FSTVersionDictionary(FSTMutation *mutation,
- (void)testCollectsGarbageAfterChangeBatch {
if ([self isTestBaseClass]) return;
- FSTQuery *query = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
+ FSTQuery *query = FSTTestQuery(@"foo");
[self allocateQuery:query];
FSTAssertTargetID(2);
@@ -637,7 +636,7 @@ FSTDocumentVersionDictionary *FSTVersionDictionary(FSTMutation *mutation,
- (void)testPinsDocumentsInTheLocalView {
if ([self isTestBaseClass]) return;
- FSTQuery *query = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
+ FSTQuery *query = FSTTestQuery(@"foo");
[self allocateQuery:query];
FSTAssertTargetID(2);
@@ -685,7 +684,7 @@ FSTDocumentVersionDictionary *FSTVersionDictionary(FSTMutation *mutation,
FSTTestSetMutation(@"foo/baz", @{@"foo" : @"baz"}),
FSTTestSetMutation(@"foo/bar/Foo/Bar", @{@"Foo" : @"Bar"})
]];
- FSTQuery *query = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo", @"bar" ]]];
+ FSTQuery *query = FSTTestQuery(@"foo/bar");
FSTDocumentDictionary *docs = [self.localStore executeQuery:query];
XCTAssertEqualObjects([docs values], @[ FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES) ]);
}
@@ -700,7 +699,7 @@ FSTDocumentVersionDictionary *FSTVersionDictionary(FSTMutation *mutation,
FSTTestSetMutation(@"foo/bar/Foo/Bar", @{@"Foo" : @"Bar"}),
FSTTestSetMutation(@"fooo/blah", @{@"fooo" : @"blah"})
]];
- FSTQuery *query = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
+ FSTQuery *query = FSTTestQuery(@"foo");
FSTDocumentDictionary *docs = [self.localStore executeQuery:query];
XCTAssertEqualObjects([docs values], (@[
FSTTestDoc(@"foo/bar", 0, @{@"foo" : @"bar"}, YES),
@@ -711,7 +710,7 @@ FSTDocumentVersionDictionary *FSTVersionDictionary(FSTMutation *mutation,
- (void)testCanExecuteMixedCollectionQueries {
if ([self isTestBaseClass]) return;
- FSTQuery *query = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
+ FSTQuery *query = FSTTestQuery(@"foo");
[self allocateQuery:query];
FSTAssertTargetID(2);
@@ -736,7 +735,7 @@ FSTDocumentVersionDictionary *FSTVersionDictionary(FSTMutation *mutation,
// This test only works in the absence of the FSTEagerGarbageCollector.
[self restartWithNoopGarbageCollector];
- FSTQuery *query = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo", @"bar" ]]];
+ FSTQuery *query = FSTTestQuery(@"foo/bar");
FSTQueryData *queryData = [self.localStore allocateQuery:query];
FSTBoxedTargetID *targetID = @(queryData.targetID);
NSData *resumeToken = FSTTestResumeTokenFromSnapshotVersion(1000);
@@ -770,7 +769,7 @@ FSTDocumentVersionDictionary *FSTVersionDictionary(FSTMutation *mutation,
if ([self isTestBaseClass]) return;
[self restartWithNoopGarbageCollector];
- FSTQuery *query = [FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"foo" ]]];
+ FSTQuery *query = FSTTestQuery(@"foo");
[self allocateQuery:query];
FSTAssertTargetID(2);
diff --git a/Firestore/Example/Tests/Local/FSTMutationQueueTests.m b/Firestore/Example/Tests/Local/FSTMutationQueueTests.m
index f168ac9..020a0a7 100644
--- a/Firestore/Example/Tests/Local/FSTMutationQueueTests.m
+++ b/Firestore/Example/Tests/Local/FSTMutationQueueTests.m
@@ -301,7 +301,7 @@ NS_ASSUME_NONNULL_BEGIN
[self.persistence commitGroup:group];
NSArray<FSTMutationBatch *> *expected = @[ batches[1], batches[2], batches[4] ];
- FSTQuery *query = [FSTQuery queryWithPath:FSTTestPath(@"foo")];
+ FSTQuery *query = FSTTestQuery(@"foo");
NSArray<FSTMutationBatch *> *matches =
[self.mutationQueue allMutationBatchesAffectingQuery:query];
diff --git a/Firestore/Example/Tests/Local/FSTQueryCacheTests.m b/Firestore/Example/Tests/Local/FSTQueryCacheTests.m
index 1fed440..0b80bd9 100644
--- a/Firestore/Example/Tests/Local/FSTQueryCacheTests.m
+++ b/Firestore/Example/Tests/Local/FSTQueryCacheTests.m
@@ -70,10 +70,8 @@ NS_ASSUME_NONNULL_BEGIN
// Type information is currently lost in our canonicalID implementations so this currently an
// easy way to force colliding canonicalIDs
- FSTQuery *q1 = [[FSTQuery queryWithPath:FSTTestPath(@"a")]
- queryByAddingFilter:FSTTestFilter(@"foo", @"==", @(1))];
- FSTQuery *q2 = [[FSTQuery queryWithPath:FSTTestPath(@"a")]
- queryByAddingFilter:FSTTestFilter(@"foo", @"==", @"1")];
+ FSTQuery *q1 = [FSTTestQuery(@"a") queryByAddingFilter:FSTTestFilter(@"foo", @"==", @(1))];
+ FSTQuery *q2 = [FSTTestQuery(@"a") queryByAddingFilter:FSTTestFilter(@"foo", @"==", @"1")];
XCTAssertEqualObjects(q1.canonicalID, q2.canonicalID);
FSTQueryData *data1 = [self queryDataWithQuery:q1 targetID:1 version:1];
@@ -141,8 +139,8 @@ NS_ASSUME_NONNULL_BEGIN
FSTQueryData *rooms = [self queryDataWithQuery:_queryRooms targetID:1 version:1];
[self addQueryData:rooms];
- FSTDocumentKey *key1 = [FSTDocumentKey keyWithPathString:@"rooms/foo"];
- FSTDocumentKey *key2 = [FSTDocumentKey keyWithPathString:@"rooms/bar"];
+ FSTDocumentKey *key1 = FSTTestDocKey(@"rooms/foo");
+ FSTDocumentKey *key2 = FSTTestDocKey(@"rooms/bar");
[self addMatchingKey:key1 forTargetID:rooms.targetID];
[self addMatchingKey:key2 forTargetID:rooms.targetID];
@@ -157,7 +155,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testAddOrRemoveMatchingKeys {
if ([self isTestBaseClass]) return;
- FSTDocumentKey *key = [FSTDocumentKey keyWithPathString:@"foo/bar"];
+ FSTDocumentKey *key = FSTTestDocKey(@"foo/bar");
XCTAssertFalse([self.queryCache containsKey:key]);
@@ -177,9 +175,9 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testRemoveMatchingKeysForTargetID {
if ([self isTestBaseClass]) return;
- FSTDocumentKey *key1 = [FSTDocumentKey keyWithPathString:@"foo/bar"];
- FSTDocumentKey *key2 = [FSTDocumentKey keyWithPathString:@"foo/baz"];
- FSTDocumentKey *key3 = [FSTDocumentKey keyWithPathString:@"foo/blah"];
+ FSTDocumentKey *key1 = FSTTestDocKey(@"foo/bar");
+ FSTDocumentKey *key2 = FSTTestDocKey(@"foo/baz");
+ FSTDocumentKey *key3 = FSTTestDocKey(@"foo/blah");
[self addMatchingKey:key1 forTargetID:1];
[self addMatchingKey:key2 forTargetID:1];
@@ -207,15 +205,15 @@ NS_ASSUME_NONNULL_BEGIN
FSTAssertEqualSets([garbageCollector collectGarbage], @[]);
FSTQueryData *rooms = [self queryDataWithQuery:FSTTestQuery(@"rooms") targetID:1 version:1];
- FSTDocumentKey *room1 = [FSTDocumentKey keyWithPathString:@"rooms/bar"];
- FSTDocumentKey *room2 = [FSTDocumentKey keyWithPathString:@"rooms/foo"];
+ FSTDocumentKey *room1 = FSTTestDocKey(@"rooms/bar");
+ FSTDocumentKey *room2 = FSTTestDocKey(@"rooms/foo");
[self addQueryData:rooms];
[self addMatchingKey:room1 forTargetID:rooms.targetID];
[self addMatchingKey:room2 forTargetID:rooms.targetID];
FSTQueryData *halls = [self queryDataWithQuery:FSTTestQuery(@"halls") targetID:2 version:1];
- FSTDocumentKey *hall1 = [FSTDocumentKey keyWithPathString:@"halls/bar"];
- FSTDocumentKey *hall2 = [FSTDocumentKey keyWithPathString:@"halls/foo"];
+ FSTDocumentKey *hall1 = FSTTestDocKey(@"halls/bar");
+ FSTDocumentKey *hall2 = FSTTestDocKey(@"halls/foo");
[self addQueryData:halls];
[self addMatchingKey:hall1 forTargetID:halls.targetID];
[self addMatchingKey:hall2 forTargetID:halls.targetID];
@@ -235,9 +233,9 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testMatchingKeysForTargetID {
if ([self isTestBaseClass]) return;
- FSTDocumentKey *key1 = [FSTDocumentKey keyWithPathString:@"foo/bar"];
- FSTDocumentKey *key2 = [FSTDocumentKey keyWithPathString:@"foo/baz"];
- FSTDocumentKey *key3 = [FSTDocumentKey keyWithPathString:@"foo/blah"];
+ FSTDocumentKey *key1 = FSTTestDocKey(@"foo/bar");
+ FSTDocumentKey *key2 = FSTTestDocKey(@"foo/baz");
+ FSTDocumentKey *key3 = FSTTestDocKey(@"foo/blah");
[self addMatchingKey:key1 forTargetID:1];
[self addMatchingKey:key2 forTargetID:1];
@@ -259,8 +257,8 @@ NS_ASSUME_NONNULL_BEGIN
FSTQueryData *query1 = [[FSTQueryData alloc] initWithQuery:FSTTestQuery(@"rooms")
targetID:1
purpose:FSTQueryPurposeListen];
- FSTDocumentKey *key1 = [FSTDocumentKey keyWithPathString:@"rooms/bar"];
- FSTDocumentKey *key2 = [FSTDocumentKey keyWithPathString:@"rooms/foo"];
+ FSTDocumentKey *key1 = FSTTestDocKey(@"rooms/bar");
+ FSTDocumentKey *key2 = FSTTestDocKey(@"rooms/foo");
[self addQueryData:query1];
[self addMatchingKey:key1 forTargetID:1];
[self addMatchingKey:key2 forTargetID:1];
@@ -268,7 +266,7 @@ NS_ASSUME_NONNULL_BEGIN
FSTQueryData *query2 = [[FSTQueryData alloc] initWithQuery:FSTTestQuery(@"halls")
targetID:2
purpose:FSTQueryPurposeListen];
- FSTDocumentKey *key3 = [FSTDocumentKey keyWithPathString:@"halls/foo"];
+ FSTDocumentKey *key3 = FSTTestDocKey(@"halls/foo");
[self addQueryData:query2];
[self addMatchingKey:key3 forTargetID:2];
XCTAssertEqual([self.queryCache highestTargetID], 2);
diff --git a/Firestore/Example/Tests/Local/FSTReferenceSetTests.m b/Firestore/Example/Tests/Local/FSTReferenceSetTests.m
index 0b852a2..802117a 100644
--- a/Firestore/Example/Tests/Local/FSTReferenceSetTests.m
+++ b/Firestore/Example/Tests/Local/FSTReferenceSetTests.m
@@ -18,6 +18,7 @@
#import <XCTest/XCTest.h>
+#import "Firestore/Example/Tests/Util/FSTHelpers.h"
#import "Firestore/Source/Model/FSTDocumentKey.h"
NS_ASSUME_NONNULL_BEGIN
@@ -28,7 +29,7 @@ NS_ASSUME_NONNULL_BEGIN
@implementation FSTReferenceSetTests
- (void)testAddOrRemoveReferences {
- FSTDocumentKey *key = [FSTDocumentKey keyWithPathString:@"foo/bar"];
+ FSTDocumentKey *key = FSTTestDocKey(@"foo/bar");
FSTReferenceSet *referenceSet = [[FSTReferenceSet alloc] init];
XCTAssertTrue([referenceSet isEmpty]);
@@ -53,9 +54,9 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testRemoveAllReferencesForTargetID {
- FSTDocumentKey *key1 = [FSTDocumentKey keyWithPathString:@"foo/bar"];
- FSTDocumentKey *key2 = [FSTDocumentKey keyWithPathString:@"foo/baz"];
- FSTDocumentKey *key3 = [FSTDocumentKey keyWithPathString:@"foo/blah"];
+ FSTDocumentKey *key1 = FSTTestDocKey(@"foo/bar");
+ FSTDocumentKey *key2 = FSTTestDocKey(@"foo/baz");
+ FSTDocumentKey *key3 = FSTTestDocKey(@"foo/blah");
FSTReferenceSet *referenceSet = [[FSTReferenceSet alloc] init];
[referenceSet addReferenceToKey:key1 forID:1];
diff --git a/Firestore/Example/Tests/Local/FSTRemoteDocumentCacheTests.m b/Firestore/Example/Tests/Local/FSTRemoteDocumentCacheTests.m
index 16fe3bf..d240604 100644
--- a/Firestore/Example/Tests/Local/FSTRemoteDocumentCacheTests.m
+++ b/Firestore/Example/Tests/Local/FSTRemoteDocumentCacheTests.m
@@ -112,7 +112,7 @@ static const int kVersion = 42;
[self setTestDocumentAtPath:@"b/2"];
[self setTestDocumentAtPath:@"c/1"];
- FSTQuery *query = [FSTQuery queryWithPath:FSTTestPath(@"b")];
+ FSTQuery *query = FSTTestQuery(@"b");
FSTDocumentDictionary *results = [self.remoteDocumentCache documentsMatchingQuery:query];
NSArray *expected =
@[ FSTTestDoc(@"b/1", kVersion, _kDocData, NO), FSTTestDoc(@"b/2", kVersion, _kDocData, NO) ];
diff --git a/Firestore/Example/Tests/Model/FSTDocumentTests.m b/Firestore/Example/Tests/Model/FSTDocumentTests.m
index e56ab34..59f526d 100644
--- a/Firestore/Example/Tests/Model/FSTDocumentTests.m
+++ b/Firestore/Example/Tests/Model/FSTDocumentTests.m
@@ -33,20 +33,20 @@ NS_ASSUME_NONNULL_BEGIN
@implementation FSTDocumentTests
- (void)testConstructor {
- FSTDocumentKey *key = [FSTDocumentKey keyWithPathString:@"messages/first"];
+ FSTDocumentKey *key = FSTTestDocKey(@"messages/first");
FSTSnapshotVersion *version = FSTTestVersion(1);
FSTObjectValue *data = FSTTestObjectValue(@{ @"a" : @1 });
FSTDocument *doc =
[FSTDocument documentWithData:data key:key version:version hasLocalMutations:NO];
- XCTAssertEqualObjects(doc.key, [FSTDocumentKey keyWithPathString:@"messages/first"]);
+ XCTAssertEqualObjects(doc.key, FSTTestDocKey(@"messages/first"));
XCTAssertEqualObjects(doc.version, version);
XCTAssertEqualObjects(doc.data, data);
XCTAssertEqual(doc.hasLocalMutations, NO);
}
- (void)testExtractsFields {
- FSTDocumentKey *key = [FSTDocumentKey keyWithPathString:@"rooms/eros"];
+ FSTDocumentKey *key = FSTTestDocKey(@"rooms/eros");
FSTSnapshotVersion *version = FSTTestVersion(1);
FSTObjectValue *data = FSTTestObjectValue(@{
@"desc" : @"Discuss all the project related stuff",
@@ -62,38 +62,31 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testIsEqual {
- FSTDocumentKey *key1 = [FSTDocumentKey keyWithPathString:@"messages/first"];
- FSTDocumentKey *key2 = [FSTDocumentKey keyWithPathString:@"messages/second"];
- FSTObjectValue *data1 = FSTTestObjectValue(@{ @"a" : @1 });
- FSTObjectValue *data2 = FSTTestObjectValue(@{ @"b" : @1 });
- FSTSnapshotVersion *version1 = FSTTestVersion(1);
-
- FSTDocument *doc1 =
- [FSTDocument documentWithData:data1 key:key1 version:version1 hasLocalMutations:NO];
- FSTDocument *doc2 =
- [FSTDocument documentWithData:data1 key:key1 version:version1 hasLocalMutations:NO];
-
- XCTAssertEqualObjects(doc1, doc2);
- XCTAssertEqualObjects(
- doc1, [FSTDocument documentWithData:FSTTestObjectValue(
- @{ @"a" : @1 })
- key:[FSTDocumentKey keyWithPathString:@"messages/first"]
- version:version1
- hasLocalMutations:NO]);
-
- FSTSnapshotVersion *version2 = FSTTestVersion(2);
- XCTAssertNotEqualObjects(
- doc1, [FSTDocument documentWithData:data2 key:key1 version:version1 hasLocalMutations:NO]);
- XCTAssertNotEqualObjects(
- doc1, [FSTDocument documentWithData:data1 key:key2 version:version1 hasLocalMutations:NO]);
- XCTAssertNotEqualObjects(
- doc1, [FSTDocument documentWithData:data1 key:key1 version:version2 hasLocalMutations:NO]);
- XCTAssertNotEqualObjects(
- doc1, [FSTDocument documentWithData:data1 key:key1 version:version1 hasLocalMutations:YES]);
-
- XCTAssertEqualObjects(
- [FSTDocument documentWithData:data1 key:key1 version:version1 hasLocalMutations:YES],
- [FSTDocument documentWithData:data1 key:key1 version:version1 hasLocalMutations:5]);
+ XCTAssertEqualObjects(FSTTestDoc(@"messages/first", 1,
+ @{ @"a" : @1 }, NO),
+ FSTTestDoc(@"messages/first", 1,
+ @{ @"a" : @1 }, NO));
+ XCTAssertNotEqualObjects(FSTTestDoc(@"messages/first", 1,
+ @{ @"a" : @1 }, NO),
+ FSTTestDoc(@"messages/first", 1,
+ @{ @"b" : @1 }, NO));
+ XCTAssertNotEqualObjects(FSTTestDoc(@"messages/first", 1,
+ @{ @"a" : @1 }, NO),
+ FSTTestDoc(@"messages/second", 1,
+ @{ @"b" : @1 }, NO));
+ XCTAssertNotEqualObjects(FSTTestDoc(@"messages/first", 1,
+ @{ @"a" : @1 }, NO),
+ FSTTestDoc(@"messages/first", 2,
+ @{ @"a" : @1 }, NO));
+ XCTAssertNotEqualObjects(FSTTestDoc(@"messages/first", 1,
+ @{ @"a" : @1 }, NO),
+ FSTTestDoc(@"messages/first", 1,
+ @{ @"a" : @1 }, YES));
+
+ XCTAssertEqualObjects(FSTTestDoc(@"messages/first", 1,
+ @{ @"a" : @1 }, YES),
+ FSTTestDoc(@"messages/first", 1,
+ @{ @"a" : @1 }, 5));
}
@end
diff --git a/Firestore/Example/Tests/Model/FSTFieldValueTests.m b/Firestore/Example/Tests/Model/FSTFieldValueTests.m
index acf95f0..785fc6b 100644
--- a/Firestore/Example/Tests/Model/FSTFieldValueTests.m
+++ b/Firestore/Example/Tests/Model/FSTFieldValueTests.m
@@ -26,6 +26,7 @@
#import "Firestore/Source/Model/FSTFieldValue.h"
#import "Firestore/Source/Model/FSTPath.h"
+#import "Firestore/Example/Tests/API/FSTAPIHelpers.h"
#import "Firestore/Example/Tests/Util/FSTHelpers.h"
/** Helper to wrap the values in a set of equality groups using FSTTestFieldValue(). */
@@ -39,10 +40,12 @@ NSArray *FSTWrapGroups(NSArray *groups) {
// strings that can be used instead.
if ([value isEqual:@"server-timestamp-1"]) {
wrappedValue = [FSTServerTimestampValue
- serverTimestampValueWithLocalWriteTime:FSTTestTimestamp(2016, 5, 20, 10, 20, 0)];
+ serverTimestampValueWithLocalWriteTime:FSTTestTimestamp(2016, 5, 20, 10, 20, 0)
+ previousValue:nil];
} else if ([value isEqual:@"server-timestamp-2"]) {
wrappedValue = [FSTServerTimestampValue
- serverTimestampValueWithLocalWriteTime:FSTTestTimestamp(2016, 10, 21, 15, 32, 0)];
+ serverTimestampValueWithLocalWriteTime:FSTTestTimestamp(2016, 10, 21, 15, 32, 0)
+ previousValue:nil];
} else if ([value isKindOfClass:[FSTDocumentKeyReference class]]) {
// We directly convert these here so that the databaseIDs can be different.
FSTDocumentKeyReference *reference = (FSTDocumentKeyReference *)value;
@@ -441,12 +444,15 @@ union DoubleBits {
@[
// NOTE: ServerTimestampValues can't be parsed via FSTTestFieldValue().
[FSTServerTimestampValue
- serverTimestampValueWithLocalWriteTime:[FSTTimestamp timestampWithDate:date1]],
+ serverTimestampValueWithLocalWriteTime:[FSTTimestamp timestampWithDate:date1]
+ previousValue:nil],
[FSTServerTimestampValue
- serverTimestampValueWithLocalWriteTime:[FSTTimestamp timestampWithDate:date1]]
+ serverTimestampValueWithLocalWriteTime:[FSTTimestamp timestampWithDate:date1]
+ previousValue:nil]
],
@[ [FSTServerTimestampValue
- serverTimestampValueWithLocalWriteTime:[FSTTimestamp timestampWithDate:date2]] ],
+ serverTimestampValueWithLocalWriteTime:[FSTTimestamp timestampWithDate:date2]
+ previousValue:nil] ],
@[
FSTTestFieldValue(FSTTestGeoPoint(0, 1)),
[FSTGeoPointValue geoPointValue:FSTTestGeoPoint(0, 1)]
diff --git a/Firestore/Example/Tests/Model/FSTMutationTests.m b/Firestore/Example/Tests/Model/FSTMutationTests.m
index 678755e..47fa9b3 100644
--- a/Firestore/Example/Tests/Model/FSTMutationTests.m
+++ b/Firestore/Example/Tests/Model/FSTMutationTests.m
@@ -42,7 +42,7 @@
FSTDocument *baseDoc = FSTTestDoc(@"collection/key", 0, docData, NO);
FSTMutation *set = FSTTestSetMutation(@"collection/key", @{@"bar" : @"bar-value"});
- FSTMaybeDocument *setDoc = [set applyTo:baseDoc localWriteTime:_timestamp];
+ FSTMaybeDocument *setDoc = [set applyTo:baseDoc baseDocument:baseDoc localWriteTime:_timestamp];
NSDictionary *expectedData = @{@"bar" : @"bar-value"};
XCTAssertEqualObjects(setDoc, FSTTestDoc(@"collection/key", 0, expectedData, YES));
@@ -54,7 +54,8 @@
FSTMutation *patch =
FSTTestPatchMutation(@"collection/key", @{@"foo.bar" : @"new-bar-value"}, nil);
- FSTMaybeDocument *patchedDoc = [patch applyTo:baseDoc localWriteTime:_timestamp];
+ FSTMaybeDocument *patchedDoc =
+ [patch applyTo:baseDoc baseDocument:baseDoc localWriteTime:_timestamp];
NSDictionary *expectedData = @{ @"foo" : @{@"bar" : @"new-bar-value"}, @"baz" : @"baz-value" };
XCTAssertEqualObjects(patchedDoc, FSTTestDoc(@"collection/key", 0, expectedData, YES));
@@ -64,13 +65,14 @@
NSDictionary *docData = @{ @"foo" : @{@"bar" : @"bar-value", @"baz" : @"baz-value"} };
FSTDocument *baseDoc = FSTTestDoc(@"collection/key", 0, docData, NO);
- FSTDocumentKey *key = [FSTDocumentKey keyWithPathString:@"collection/key"];
+ FSTDocumentKey *key = FSTTestDocKey(@"collection/key");
FSTFieldMask *mask = [[FSTFieldMask alloc] initWithFields:@[ FSTTestFieldPath(@"foo.bar") ]];
FSTMutation *patch = [[FSTPatchMutation alloc] initWithKey:key
fieldMask:mask
value:[FSTObjectValue objectValue]
precondition:[FSTPrecondition none]];
- FSTMaybeDocument *patchedDoc = [patch applyTo:baseDoc localWriteTime:_timestamp];
+ FSTMaybeDocument *patchedDoc =
+ [patch applyTo:baseDoc baseDocument:baseDoc localWriteTime:_timestamp];
NSDictionary *expectedData = @{ @"foo" : @{@"baz" : @"baz-value"} };
XCTAssertEqualObjects(patchedDoc, FSTTestDoc(@"collection/key", 0, expectedData, YES));
@@ -82,7 +84,8 @@
FSTMutation *patch =
FSTTestPatchMutation(@"collection/key", @{@"foo.bar" : @"new-bar-value"}, nil);
- FSTMaybeDocument *patchedDoc = [patch applyTo:baseDoc localWriteTime:_timestamp];
+ FSTMaybeDocument *patchedDoc =
+ [patch applyTo:baseDoc baseDocument:baseDoc localWriteTime:_timestamp];
NSDictionary *expectedData = @{ @"foo" : @{@"bar" : @"new-bar-value"}, @"baz" : @"baz-value" };
XCTAssertEqualObjects(patchedDoc, FSTTestDoc(@"collection/key", 0, expectedData, YES));
@@ -91,7 +94,8 @@
- (void)testPatchingDeletedDocumentsDoesNothing {
FSTMaybeDocument *baseDoc = FSTTestDeletedDoc(@"collection/key", 0);
FSTMutation *patch = FSTTestPatchMutation(@"collection/key", @{@"foo" : @"bar"}, nil);
- FSTMaybeDocument *patchedDoc = [patch applyTo:baseDoc localWriteTime:_timestamp];
+ FSTMaybeDocument *patchedDoc =
+ [patch applyTo:baseDoc baseDocument:baseDoc localWriteTime:_timestamp];
XCTAssertEqualObjects(patchedDoc, baseDoc);
}
@@ -100,7 +104,8 @@
FSTDocument *baseDoc = FSTTestDoc(@"collection/key", 0, docData, NO);
FSTMutation *transform = FSTTestTransformMutation(@"collection/key", @[ @"foo.bar" ]);
- FSTMaybeDocument *transformedDoc = [transform applyTo:baseDoc localWriteTime:_timestamp];
+ FSTMaybeDocument *transformedDoc =
+ [transform applyTo:baseDoc baseDocument:baseDoc localWriteTime:_timestamp];
// Server timestamps aren't parsed, so we manually insert it.
FSTObjectValue *expectedData = FSTTestObjectValue(
@@ -108,7 +113,8 @@
@"baz" : @"baz-value" });
expectedData =
[expectedData objectBySettingValue:[FSTServerTimestampValue
- serverTimestampValueWithLocalWriteTime:_timestamp]
+ serverTimestampValueWithLocalWriteTime:_timestamp
+ previousValue:nil]
forPath:FSTTestFieldPath(@"foo.bar")];
FSTDocument *expectedDoc = [FSTDocument documentWithData:expectedData
@@ -129,8 +135,10 @@
initWithVersion:FSTTestVersion(1)
transformResults:@[ [FSTTimestampValue timestampValue:_timestamp] ]];
- FSTMaybeDocument *transformedDoc =
- [transform applyTo:baseDoc localWriteTime:_timestamp mutationResult:mutationResult];
+ FSTMaybeDocument *transformedDoc = [transform applyTo:baseDoc
+ baseDocument:baseDoc
+ localWriteTime:_timestamp
+ mutationResult:mutationResult];
NSDictionary *expectedData =
@{ @"foo" : @{@"bar" : _timestamp.approximateDateValue},
@@ -143,7 +151,8 @@
FSTDocument *baseDoc = FSTTestDoc(@"collection/key", 0, docData, NO);
FSTMutation *mutation = FSTTestDeleteMutation(@"collection/key");
- FSTMaybeDocument *result = [mutation applyTo:baseDoc localWriteTime:_timestamp];
+ FSTMaybeDocument *result =
+ [mutation applyTo:baseDoc baseDocument:baseDoc localWriteTime:_timestamp];
XCTAssertEqualObjects(result, FSTTestDeletedDoc(@"collection/key", 0));
}
@@ -154,8 +163,10 @@
FSTMutation *set = FSTTestSetMutation(@"collection/key", @{@"foo" : @"new-bar"});
FSTMutationResult *mutationResult =
[[FSTMutationResult alloc] initWithVersion:FSTTestVersion(4) transformResults:nil];
- FSTMaybeDocument *setDoc =
- [set applyTo:baseDoc localWriteTime:_timestamp mutationResult:mutationResult];
+ FSTMaybeDocument *setDoc = [set applyTo:baseDoc
+ baseDocument:baseDoc
+ localWriteTime:_timestamp
+ mutationResult:mutationResult];
NSDictionary *expectedData = @{@"foo" : @"new-bar"};
XCTAssertEqualObjects(setDoc, FSTTestDoc(@"collection/key", 0, expectedData, NO));
@@ -168,8 +179,10 @@
FSTMutation *patch = FSTTestPatchMutation(@"collection/key", @{@"foo" : @"new-bar"}, nil);
FSTMutationResult *mutationResult =
[[FSTMutationResult alloc] initWithVersion:FSTTestVersion(4) transformResults:nil];
- FSTMaybeDocument *patchedDoc =
- [patch applyTo:baseDoc localWriteTime:_timestamp mutationResult:mutationResult];
+ FSTMaybeDocument *patchedDoc = [patch applyTo:baseDoc
+ baseDocument:baseDoc
+ localWriteTime:_timestamp
+ mutationResult:mutationResult];
NSDictionary *expectedData = @{@"foo" : @"new-bar"};
XCTAssertEqualObjects(patchedDoc, FSTTestDoc(@"collection/key", 0, expectedData, NO));
@@ -179,8 +192,10 @@
do { \
FSTMutationResult *mutationResult = \
[[FSTMutationResult alloc] initWithVersion:FSTTestVersion(0) transformResults:nil]; \
- FSTMaybeDocument *actual = \
- [mutation applyTo:base localWriteTime:_timestamp mutationResult:mutationResult]; \
+ FSTMaybeDocument *actual = [mutation applyTo:base \
+ baseDocument:base \
+ localWriteTime:_timestamp \
+ mutationResult:mutationResult]; \
XCTAssertEqualObjects(actual, expected); \
} while (0);
diff --git a/Firestore/Example/Tests/Remote/FSTSerializerBetaTests.m b/Firestore/Example/Tests/Remote/FSTSerializerBetaTests.m
index 528076f..bba04c5 100644
--- a/Firestore/Example/Tests/Remote/FSTSerializerBetaTests.m
+++ b/Firestore/Example/Tests/Remote/FSTSerializerBetaTests.m
@@ -44,6 +44,7 @@
#import "Firestore/Source/Model/FSTPath.h"
#import "Firestore/Source/Remote/FSTWatchChange.h"
+#import "Firestore/Example/Tests/API/FSTAPIHelpers.h"
#import "Firestore/Example/Tests/Util/FSTHelpers.h"
NS_ASSUME_NONNULL_BEGIN
@@ -428,7 +429,7 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - encodedQuery
- (void)testEncodesFirstLevelKeyQueries {
- FSTQuery *q = [FSTQuery queryWithPath:FSTTestPath(@"docs/1")];
+ FSTQuery *q = FSTTestQuery(@"docs/1");
FSTQueryData *model = [self queryDataForQuery:q];
GCFSTarget *expected = [GCFSTarget message];
@@ -439,7 +440,7 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testEncodesFirstLevelAncestorQueries {
- FSTQuery *q = [FSTQuery queryWithPath:FSTTestPath(@"messages")];
+ FSTQuery *q = FSTTestQuery(@"messages");
FSTQueryData *model = [self queryDataForQuery:q];
GCFSTarget *expected = [GCFSTarget message];
@@ -455,7 +456,7 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testEncodesNestedAncestorQueries {
- FSTQuery *q = [FSTQuery queryWithPath:FSTTestPath(@"rooms/1/messages/10/attachments")];
+ FSTQuery *q = FSTTestQuery(@"rooms/1/messages/10/attachments");
FSTQueryData *model = [self queryDataForQuery:q];
GCFSTarget *expected = [GCFSTarget message];
@@ -471,8 +472,7 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testEncodesSingleFiltersAtFirstLevelCollections {
- FSTQuery *q = [[FSTQuery queryWithPath:FSTTestPath(@"docs")]
- queryByAddingFilter:FSTTestFilter(@"prop", @"<", @(42))];
+ FSTQuery *q = [FSTTestQuery(@"docs") queryByAddingFilter:FSTTestFilter(@"prop", @"<", @(42))];
FSTQueryData *model = [self queryDataForQuery:q];
GCFSTarget *expected = [GCFSTarget message];
@@ -495,7 +495,7 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testEncodesMultipleFiltersOnDeeperCollections {
- FSTQuery *q = [[[FSTQuery queryWithPath:FSTTestPath(@"rooms/1/messages/10/attachments")]
+ FSTQuery *q = [[FSTTestQuery(@"rooms/1/messages/10/attachments")
queryByAddingFilter:FSTTestFilter(@"prop", @">=", @(42))]
queryByAddingFilter:FSTTestFilter(@"author", @"==", @"dimond")];
FSTQueryData *model = [self queryDataForQuery:q];
@@ -546,8 +546,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)unaryFilterTestWithValue:(id)value
expectedUnaryOperator:(GCFSStructuredQuery_UnaryFilter_Operator)
operator{
- FSTQuery *q = [[FSTQuery queryWithPath:FSTTestPath(@"docs")]
- queryByAddingFilter:FSTTestFilter(@"prop", @"==", value)];
+ FSTQuery *q = [FSTTestQuery(@"docs") queryByAddingFilter:FSTTestFilter(@"prop", @"==", value)];
FSTQueryData *model = [self queryDataForQuery:q];
GCFSTarget *expected = [GCFSTarget message];
@@ -567,7 +566,7 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testEncodesSortOrders {
- FSTQuery *q = [[FSTQuery queryWithPath:FSTTestPath(@"docs")]
+ FSTQuery *q = [FSTTestQuery(@"docs")
queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:FSTTestFieldPath(@"prop")
ascending:YES]];
FSTQueryData *model = [self queryDataForQuery:q];
@@ -587,7 +586,7 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testEncodesSortOrdersDescending {
- FSTQuery *q = [[FSTQuery queryWithPath:FSTTestPath(@"rooms/1/messages/10/attachments")]
+ FSTQuery *q = [FSTTestQuery(@"rooms/1/messages/10/attachments")
queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:FSTTestFieldPath(@"prop")
ascending:NO]];
FSTQueryData *model = [self queryDataForQuery:q];
@@ -607,7 +606,7 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testEncodesLimits {
- FSTQuery *q = [[FSTQuery queryWithPath:FSTTestPath(@"docs")] queryBySettingLimit:26];
+ FSTQuery *q = [FSTTestQuery(@"docs") queryBySettingLimit:26];
FSTQueryData *model = [self queryDataForQuery:q];
GCFSTarget *expected = [GCFSTarget message];
@@ -624,7 +623,7 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)testEncodesResumeTokens {
- FSTQuery *q = [FSTQuery queryWithPath:FSTTestPath(@"docs")];
+ FSTQuery *q = FSTTestQuery(@"docs");
FSTQueryData *model = [[FSTQueryData alloc] initWithQuery:q
targetID:1
purpose:FSTQueryPurposeListen
diff --git a/Firestore/Example/Tests/SpecTests/FSTSpecTests.m b/Firestore/Example/Tests/SpecTests/FSTSpecTests.m
index 2c1b8db..3abcb48 100644
--- a/Firestore/Example/Tests/SpecTests/FSTSpecTests.m
+++ b/Firestore/Example/Tests/SpecTests/FSTSpecTests.m
@@ -103,11 +103,11 @@ static NSString *const kNoIOSTag = @"no-ios";
- (nullable FSTQuery *)parseQuery:(id)querySpec {
if ([querySpec isKindOfClass:[NSString class]]) {
- return [FSTQuery queryWithPath:[FSTResourcePath pathWithString:querySpec]];
+ return FSTTestQuery(querySpec);
} else if ([querySpec isKindOfClass:[NSDictionary class]]) {
NSDictionary *queryDict = (NSDictionary *)querySpec;
NSString *path = queryDict[@"path"];
- __block FSTQuery *query = [FSTQuery queryWithPath:[FSTResourcePath pathWithString:path]];
+ __block FSTQuery *query = FSTTestQuery(path);
if (queryDict[@"limit"]) {
NSNumber *limit = queryDict[@"limit"];
query = [query queryBySettingLimit:limit.integerValue];
@@ -156,7 +156,7 @@ static NSString *const kNoIOSTag = @"no-ios";
FSTTargetID actualID = [self.driver addUserListenerWithQuery:query];
FSTTargetID expectedID = [listenSpec[0] intValue];
- XCTAssertEqual(actualID, expectedID);
+ XCTAssertEqual(actualID, expectedID, @"targetID assigned to listen");
}
- (void)doUnlisten:(NSArray *)unlistenSpec {
@@ -237,7 +237,7 @@ static NSString *const kNoIOSTag = @"no-ios";
}
} else if (watchEntity[@"doc"]) {
NSArray *docSpec = watchEntity[@"doc"];
- FSTDocumentKey *key = [FSTDocumentKey keyWithPathString:docSpec[0]];
+ FSTDocumentKey *key = FSTTestDocKey(docSpec[0]);
FSTObjectValue *value = FSTTestObjectValue(docSpec[2]);
FSTSnapshotVersion *version = [self parseVersion:docSpec[1]];
FSTMaybeDocument *doc =
@@ -249,7 +249,7 @@ static NSString *const kNoIOSTag = @"no-ios";
document:doc];
[self.driver receiveWatchChange:change snapshotVersion:[self parseVersion:watchSnapshot]];
} else if (watchEntity[@"key"]) {
- FSTDocumentKey *docKey = [FSTDocumentKey keyWithPathString:watchEntity[@"key"]];
+ FSTDocumentKey *docKey = FSTTestDocKey(watchEntity[@"key"]);
FSTWatchChange *change =
[[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[]
removedTargetIDs:watchEntity[@"removedTargets"]
diff --git a/Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.h b/Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.h
index 3d031bd..7cb2726 100644
--- a/Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.h
+++ b/Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.h
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#import <Firestore/Source/Remote/FSTRemoteStore.h>
#import <Foundation/Foundation.h>
#import "Firestore/Source/Core/FSTTypes.h"
@@ -76,7 +77,7 @@ typedef NSDictionary<FSTUser *, NSArray<FSTOutstandingWrite *> *> FSTOutstanding
*
* Each method on the driver injects a different event into the system.
*/
-@interface FSTSyncEngineTestDriver : NSObject
+@interface FSTSyncEngineTestDriver : NSObject <FSTOnlineStateDelegate>
/**
* Initializes the underlying FSTSyncEngine with the given local persistence implementation and
diff --git a/Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.m b/Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.m
index 896a292..da63933 100644
--- a/Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.m
+++ b/Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.m
@@ -119,7 +119,7 @@ NS_ASSUME_NONNULL_BEGIN
_remoteStore.syncEngine = _syncEngine;
_eventManager = [FSTEventManager eventManagerWithSyncEngine:_syncEngine];
- _remoteStore.onlineStateDelegate = _eventManager;
+ _remoteStore.onlineStateDelegate = self;
// Set up internal event tracking for the spec tests.
NSMutableArray<FSTQueryEvent *> *events = [NSMutableArray array];
@@ -139,6 +139,11 @@ NS_ASSUME_NONNULL_BEGIN
return self;
}
+- (void)applyChangedOnlineState:(FSTOnlineState)onlineState {
+ [self.syncEngine applyChangedOnlineState:onlineState];
+ [self.eventManager applyChangedOnlineState:onlineState];
+}
+
- (void)start {
[self.localStore start];
[self.remoteStore start];
diff --git a/Firestore/Example/Tests/SpecTests/json/listen_spec_test.json b/Firestore/Example/Tests/SpecTests/json/listen_spec_test.json
index e607710..7bfe557 100644
--- a/Firestore/Example/Tests/SpecTests/json/listen_spec_test.json
+++ b/Firestore/Example/Tests/SpecTests/json/listen_spec_test.json
@@ -1608,7 +1608,19 @@
"stateExpect": {
"activeTargets": {},
"limboDocs": []
- }
+ },
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "errorCode": 0,
+ "fromCache": true,
+ "hasPendingWrites": false
+ }
+ ]
},
{
"enableNetwork": true,
diff --git a/Firestore/Example/Tests/SpecTests/json/offline_spec_test.json b/Firestore/Example/Tests/SpecTests/json/offline_spec_test.json
index f542a6e..3981cec 100644
--- a/Firestore/Example/Tests/SpecTests/json/offline_spec_test.json
+++ b/Firestore/Example/Tests/SpecTests/json/offline_spec_test.json
@@ -294,5 +294,170 @@
]
}
]
+ },
+ "Queries revert to fromCache=true when offline.": {
+ "describeName": "Offline:",
+ "itName": "Queries revert to fromCache=true when offline.",
+ "tags": [],
+ "config": {
+ "useGarbageCollection": true
+ },
+ "steps": [
+ {
+ "userListen": [
+ 2,
+ {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ }
+ ],
+ "stateExpect": {
+ "activeTargets": {
+ "2": {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": ""
+ }
+ }
+ }
+ },
+ {
+ "watchAck": [
+ 2
+ ]
+ },
+ {
+ "watchEntity": {
+ "docs": [
+ [
+ "collection/a",
+ 1000,
+ {
+ "key": "a"
+ }
+ ]
+ ],
+ "targets": [
+ 2
+ ]
+ }
+ },
+ {
+ "watchCurrent": [
+ [
+ 2
+ ],
+ "resume-token-1000"
+ ],
+ "watchSnapshot": 1000,
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "added": [
+ [
+ "collection/a",
+ 1000,
+ {
+ "key": "a"
+ }
+ ]
+ ],
+ "errorCode": 0,
+ "fromCache": false,
+ "hasPendingWrites": false
+ }
+ ]
+ },
+ {
+ "watchStreamClose": {
+ "error": {
+ "code": 14,
+ "message": "Simulated Backend Error"
+ }
+ },
+ "stateExpect": {
+ "activeTargets": {
+ "2": {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "resumeToken": "resume-token-1000"
+ }
+ }
+ }
+ },
+ {
+ "watchStreamClose": {
+ "error": {
+ "code": 14,
+ "message": "Simulated Backend Error"
+ }
+ }
+ },
+ {
+ "watchStreamClose": {
+ "error": {
+ "code": 14,
+ "message": "Simulated Backend Error"
+ }
+ },
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "errorCode": 0,
+ "fromCache": true,
+ "hasPendingWrites": false
+ }
+ ]
+ },
+ {
+ "watchAck": [
+ 2
+ ]
+ },
+ {
+ "watchEntity": {
+ "docs": [],
+ "targets": [
+ 2
+ ]
+ }
+ },
+ {
+ "watchCurrent": [
+ [
+ 2
+ ],
+ "resume-token-1000"
+ ],
+ "watchSnapshot": 1000,
+ "expect": [
+ {
+ "query": {
+ "path": "collection",
+ "filters": [],
+ "orderBys": []
+ },
+ "errorCode": 0,
+ "fromCache": false,
+ "hasPendingWrites": false
+ }
+ ]
+ }
+ ]
}
}
diff --git a/Firestore/Example/Tests/Util/FSTEventAccumulator.h b/Firestore/Example/Tests/Util/FSTEventAccumulator.h
index ae5392c..baa501b 100644
--- a/Firestore/Example/Tests/Util/FSTEventAccumulator.h
+++ b/Firestore/Example/Tests/Util/FSTEventAccumulator.h
@@ -23,7 +23,7 @@
NS_ASSUME_NONNULL_BEGIN
-typedef void (^FSTGenericEventHandler)(id _Nullable, NSError *error);
+typedef void (^FSTValueEventHandler)(id _Nullable, NSError *_Nullable error);
@interface FSTEventAccumulator : NSObject
@@ -35,7 +35,8 @@ typedef void (^FSTGenericEventHandler)(id _Nullable, NSError *error);
- (NSArray<id> *)awaitEvents:(NSUInteger)events name:(NSString *)name;
-@property(nonatomic, strong, readonly) FSTGenericEventHandler handler;
+@property(nonatomic, strong, readonly) FSTValueEventHandler valueEventHandler;
+
@end
NS_ASSUME_NONNULL_END
diff --git a/Firestore/Example/Tests/Util/FSTEventAccumulator.m b/Firestore/Example/Tests/Util/FSTEventAccumulator.m
index b44ec67..c4c1602 100644
--- a/Firestore/Example/Tests/Util/FSTEventAccumulator.m
+++ b/Firestore/Example/Tests/Util/FSTEventAccumulator.m
@@ -68,9 +68,8 @@ NS_ASSUME_NONNULL_BEGIN
return events[0];
}
-// Overrides the handler property
-- (void (^)(id _Nullable, NSError *))handler {
- return ^void(id _Nullable value, NSError *error) {
+- (void (^)(id _Nullable, NSError *_Nullable))valueEventHandler {
+ return ^void(id _Nullable value, NSError *_Nullable error) {
// We can't store nil in the _events array, but these are still interesting to tests so store
// NSNull instead.
id event = value ? value : [NSNull null];
diff --git a/Firestore/Example/Tests/Util/FSTHelpers.h b/Firestore/Example/Tests/Util/FSTHelpers.h
index 91ccbcf..4dbf910 100644
--- a/Firestore/Example/Tests/Util/FSTHelpers.h
+++ b/Firestore/Example/Tests/Util/FSTHelpers.h
@@ -16,7 +16,6 @@
#import <Foundation/Foundation.h>
-#import "Firestore/Source/API/FIRDocumentReference+Internal.h"
#import "Firestore/Source/Core/FSTTypes.h"
#import "Firestore/Source/Model/FSTDocumentDictionary.h"
#import "Firestore/Source/Model/FSTDocumentKeySet.h"
@@ -33,7 +32,6 @@
@class FSTPatchMutation;
@class FSTQuery;
@class FSTRemoteEvent;
-@class FSTResourceName;
@class FSTResourcePath;
@class FSTSetMutation;
@class FSTSnapshotVersion;
@@ -145,6 +143,8 @@ NSDate *FSTTestDate(int year, int month, int day, int hour, int minute, int seco
*/
NSData *FSTTestData(int bytes, ...);
+// Note that FIRGeoPoint is a model class in addition to an API class, so we put this helper here
+// instead of FSTAPIHelpers.h
/** Creates a new GeoPoint from the latitude and longitude values */
FIRGeoPoint *FSTTestGeoPoint(double latitude, double longitude);
diff --git a/Firestore/Example/Tests/Util/FSTHelpers.m b/Firestore/Example/Tests/Util/FSTHelpers.m
index f01bddb..f2b3605 100644
--- a/Firestore/Example/Tests/Util/FSTHelpers.m
+++ b/Firestore/Example/Tests/Util/FSTHelpers.m
@@ -24,6 +24,7 @@
#import "Firestore/Source/Core/FSTSnapshotVersion.h"
#import "Firestore/Source/Core/FSTTimestamp.h"
#import "Firestore/Source/Core/FSTView.h"
+#import "Firestore/Source/Core/FSTViewSnapshot.h"
#import "Firestore/Source/Local/FSTLocalViewChanges.h"
#import "Firestore/Source/Local/FSTQueryData.h"
#import "Firestore/Source/Model/FSTDatabaseID.h"
@@ -138,7 +139,7 @@ FSTDocument *FSTTestDoc(NSString *path,
FSTTestSnapshotVersion version,
NSDictionary<NSString *, id> *data,
BOOL hasMutations) {
- FSTDocumentKey *key = [FSTDocumentKey keyWithPathString:path];
+ FSTDocumentKey *key = FSTTestDocKey(path);
return [FSTDocument documentWithData:FSTTestObjectValue(data)
key:key
version:FSTTestVersion(version)
@@ -146,7 +147,7 @@ FSTDocument *FSTTestDoc(NSString *path,
}
FSTDeletedDocument *FSTTestDeletedDoc(NSString *path, FSTTestSnapshotVersion version) {
- FSTDocumentKey *key = [FSTDocumentKey keyWithPathString:path];
+ FSTDocumentKey *key = FSTTestDocKey(path);
return [FSTDeletedDocument documentWithKey:key version:FSTTestVersion(version)];
}
@@ -214,7 +215,7 @@ FSTSortOrder *FSTTestOrderBy(NSString *field, NSString *direction) {
}
NSComparator FSTTestDocComparator(NSString *fieldPath) {
- FSTQuery *query = [[FSTQuery queryWithPath:[FSTResourcePath pathWithSegments:@[ @"docs" ]]]
+ FSTQuery *query = [FSTTestQuery(@"docs")
queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:FSTTestFieldPath(fieldPath)
ascending:YES]];
return [query comparator];
@@ -229,7 +230,7 @@ FSTDocumentSet *FSTTestDocSet(NSComparator comp, NSArray<FSTDocument *> *docs) {
}
FSTSetMutation *FSTTestSetMutation(NSString *path, NSDictionary<NSString *, id> *values) {
- return [[FSTSetMutation alloc] initWithKey:[FSTDocumentKey keyWithPathString:path]
+ return [[FSTSetMutation alloc] initWithKey:FSTTestDocKey(path)
value:FSTTestObjectValue(values)
precondition:[FSTPrecondition none]];
}
@@ -274,7 +275,7 @@ FSTTransformMutation *FSTTestTransformMutation(NSString *path,
}
FSTDeleteMutation *FSTTestDeleteMutation(NSString *path) {
- return [[FSTDeleteMutation alloc] initWithKey:[FSTDocumentKey keyWithPathString:path]
+ return [[FSTDeleteMutation alloc] initWithKey:FSTTestDocKey(path)
precondition:[FSTPrecondition none]];
}
@@ -334,12 +335,12 @@ FSTLocalViewChanges *FSTTestViewChanges(FSTQuery *query,
NSArray<NSString *> *removedKeys) {
FSTDocumentKeySet *added = [FSTDocumentKeySet keySet];
for (NSString *keyPath in addedKeys) {
- FSTDocumentKey *key = [FSTDocumentKey keyWithPathString:keyPath];
+ FSTDocumentKey *key = FSTTestDocKey(keyPath);
added = [added setByAddingObject:key];
}
FSTDocumentKeySet *removed = [FSTDocumentKeySet keySet];
for (NSString *keyPath in removedKeys) {
- FSTDocumentKey *key = [FSTDocumentKey keyWithPathString:keyPath];
+ FSTDocumentKey *key = FSTTestDocKey(keyPath);
removed = [removed setByAddingObject:key];
}
return [FSTLocalViewChanges changesForQuery:query addedKeys:added removedKeys:removed];
diff --git a/Firestore/Example/Tests/Util/FSTIntegrationTestCase.h b/Firestore/Example/Tests/Util/FSTIntegrationTestCase.h
index 88f9346..ac54253 100644
--- a/Firestore/Example/Tests/Util/FSTIntegrationTestCase.h
+++ b/Firestore/Example/Tests/Util/FSTIntegrationTestCase.h
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#import <Firestore/Source/Core/FSTTypes.h>
#import <Foundation/Foundation.h>
#import <XCTest/XCTest.h>
@@ -82,6 +83,10 @@ extern "C" {
- (void)deleteDocumentRef:(FIRDocumentReference *)ref;
+- (void)disableNetwork;
+
+- (void)enableNetwork;
+
/**
* "Blocks" the current thread/run loop until the block returns YES.
* Should only be called on the main thread.
diff --git a/Firestore/Example/Tests/Util/FSTIntegrationTestCase.mm b/Firestore/Example/Tests/Util/FSTIntegrationTestCase.mm
index 3d30a77..839e4a5 100644
--- a/Firestore/Example/Tests/Util/FSTIntegrationTestCase.mm
+++ b/Firestore/Example/Tests/Util/FSTIntegrationTestCase.mm
@@ -18,6 +18,7 @@
#import <FirebaseCore/FIRLogger.h>
#import <FirebaseFirestore/FirebaseFirestore-umbrella.h>
+#import <Firestore/Source/Core/FSTFirestoreClient.h>
#import <GRPCClient/GRPCCall+ChannelArg.h>
#import <GRPCClient/GRPCCall+Tests.h>
@@ -158,11 +159,7 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)shutdownFirestore:(FIRFirestore *)firestore {
- XCTestExpectation *shutdownCompletion = [self expectationWithDescription:@"shutdown"];
- [firestore shutdownWithCompletion:^(NSError *_Nullable error) {
- XCTAssertNil(error);
- [shutdownCompletion fulfill];
- }];
+ [firestore shutdownWithCompletion:[self completionForExpectationWithName:@"shutdown"]];
[self awaitExpectations];
}
@@ -261,31 +258,29 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)writeDocumentRef:(FIRDocumentReference *)ref data:(NSDictionary<NSString *, id> *)data {
- XCTestExpectation *expectation = [self expectationWithDescription:@"setData"];
- [ref setData:data
- completion:^(NSError *_Nullable error) {
- XCTAssertNil(error);
- [expectation fulfill];
- }];
+ [ref setData:data completion:[self completionForExpectationWithName:@"setData"]];
[self awaitExpectations];
}
- (void)updateDocumentRef:(FIRDocumentReference *)ref data:(NSDictionary<id, id> *)data {
- XCTestExpectation *expectation = [self expectationWithDescription:@"updateData"];
- [ref updateData:data
- completion:^(NSError *_Nullable error) {
- XCTAssertNil(error);
- [expectation fulfill];
- }];
+ [ref updateData:data completion:[self completionForExpectationWithName:@"updateData"]];
[self awaitExpectations];
}
- (void)deleteDocumentRef:(FIRDocumentReference *)ref {
- XCTestExpectation *expectation = [self expectationWithDescription:@"deleteDocument"];
- [ref deleteDocumentWithCompletion:^(NSError *_Nullable error) {
- XCTAssertNil(error);
- [expectation fulfill];
- }];
+ [ref deleteDocumentWithCompletion:[self completionForExpectationWithName:@"deleteDocument"]];
+ [self awaitExpectations];
+}
+
+- (void)disableNetwork {
+ [self.db.client
+ disableNetworkWithCompletion:[self completionForExpectationWithName:@"Disable Network."]];
+ [self awaitExpectations];
+}
+
+- (void)enableNetwork {
+ [self.db.client
+ enableNetworkWithCompletion:[self completionForExpectationWithName:@"Enable Network."]];
[self awaitExpectations];
}
diff --git a/Firestore/Example/Tests/Util/XCTestCase+Await.h b/Firestore/Example/Tests/Util/XCTestCase+Await.h
index 9d575f9..7a8feb8 100644
--- a/Firestore/Example/Tests/Util/XCTestCase+Await.h
+++ b/Firestore/Example/Tests/Util/XCTestCase+Await.h
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#import <Firestore/Source/Core/FSTTypes.h>
#import <XCTest/XCTest.h>
@interface XCTestCase (Await)
@@ -29,4 +30,10 @@
*/
- (double)defaultExpectationWaitSeconds;
+/**
+ * Returns a completion block that fulfills a newly-created expectation with the specified
+ * name.
+ */
+- (FSTVoidErrorBlock)completionForExpectationWithName:(NSString *)expectationName;
+
@end
diff --git a/Firestore/Example/Tests/Util/XCTestCase+Await.m b/Firestore/Example/Tests/Util/XCTestCase+Await.m
index 15c67ca..7f4356c 100644
--- a/Firestore/Example/Tests/Util/XCTestCase+Await.m
+++ b/Firestore/Example/Tests/Util/XCTestCase+Await.m
@@ -35,4 +35,12 @@ static const double kExpectationWaitSeconds = 10.0;
return kExpectationWaitSeconds;
}
+- (FSTVoidErrorBlock)completionForExpectationWithName:(NSString *)expectationName {
+ XCTestExpectation *expectation = [self expectationWithDescription:expectationName];
+ return ^(NSError *error) {
+ XCTAssertNil(error);
+ [expectation fulfill];
+ };
+}
+
@end
diff --git a/Firestore/Source/API/FIRCollectionReference.mm b/Firestore/Source/API/FIRCollectionReference.mm
index 92cccc6..70a14c2 100644
--- a/Firestore/Source/API/FIRCollectionReference.mm
+++ b/Firestore/Source/API/FIRCollectionReference.mm
@@ -15,6 +15,7 @@
*/
#import "FIRCollectionReference.h"
+#import "FIRFirestore.h"
#include "Firestore/core/src/firebase/firestore/util/autoid.h"
@@ -65,6 +66,29 @@ NS_ASSUME_NONNULL_BEGIN
FSTFail(@"Use FIRCollectionReference initWithPath: initializer.");
}
+// NSObject Methods
+- (BOOL)isEqual:(nullable id)other {
+ if (other == self) return YES;
+ if (![[other class] isEqual:[self class]]) return NO;
+
+ return [self isEqualToReference:other];
+}
+
+- (BOOL)isEqualToReference:(nullable FIRCollectionReference *)reference {
+ if (self == reference) return YES;
+ if (reference == nil) return NO;
+ if (self.firestore != reference.firestore && ![self.firestore isEqual:reference.firestore])
+ return NO;
+ if (self.query != reference.query && ![self.query isEqual:reference.query]) return NO;
+ return YES;
+}
+
+- (NSUInteger)hash {
+ NSUInteger hash = [self.firestore hash];
+ hash = hash * 31u + [self.query hash];
+ return hash;
+}
+
- (NSString *)collectionID {
return [self.query.path lastSegment];
}
diff --git a/Firestore/Source/API/FIRDocumentChange.m b/Firestore/Source/API/FIRDocumentChange.m
index 970dc90..d1d9999 100644
--- a/Firestore/Source/API/FIRDocumentChange.m
+++ b/Firestore/Source/API/FIRDocumentChange.m
@@ -57,11 +57,11 @@ NS_ASSUME_NONNULL_BEGIN
NSUInteger index = 0;
NSMutableArray<FIRDocumentChange *> *changes = [NSMutableArray array];
for (FSTDocumentViewChange *change in snapshot.documentChanges) {
- FIRDocumentSnapshot *document =
- [FIRDocumentSnapshot snapshotWithFirestore:firestore
- documentKey:change.document.key
- document:change.document
- fromCache:snapshot.isFromCache];
+ FIRQueryDocumentSnapshot *document =
+ [FIRQueryDocumentSnapshot snapshotWithFirestore:firestore
+ documentKey:change.document.key
+ document:change.document
+ fromCache:snapshot.isFromCache];
FSTAssert(change.type == FSTDocumentViewChangeTypeAdded,
@"Invalid event type for first snapshot");
FSTAssert(!lastDocument ||
@@ -79,11 +79,11 @@ NS_ASSUME_NONNULL_BEGIN
FSTDocumentSet *indexTracker = snapshot.oldDocuments;
NSMutableArray<FIRDocumentChange *> *changes = [NSMutableArray array];
for (FSTDocumentViewChange *change in snapshot.documentChanges) {
- FIRDocumentSnapshot *document =
- [FIRDocumentSnapshot snapshotWithFirestore:firestore
- documentKey:change.document.key
- document:change.document
- fromCache:snapshot.isFromCache];
+ FIRQueryDocumentSnapshot *document =
+ [FIRQueryDocumentSnapshot snapshotWithFirestore:firestore
+ documentKey:change.document.key
+ document:change.document
+ fromCache:snapshot.isFromCache];
NSUInteger oldIndex = NSNotFound;
NSUInteger newIndex = NSNotFound;
@@ -112,7 +112,7 @@ NS_ASSUME_NONNULL_BEGIN
@implementation FIRDocumentChange
- (instancetype)initWithType:(FIRDocumentChangeType)type
- document:(FIRDocumentSnapshot *)document
+ document:(FIRQueryDocumentSnapshot *)document
oldIndex:(NSUInteger)oldIndex
newIndex:(NSUInteger)newIndex {
if (self = [super init]) {
diff --git a/Firestore/Source/API/FIRDocumentReference.m b/Firestore/Source/API/FIRDocumentReference.m
index 1c80ea9..87e6631 100644
--- a/Firestore/Source/API/FIRDocumentReference.m
+++ b/Firestore/Source/API/FIRDocumentReference.m
@@ -48,6 +48,8 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)initWithIncludeMetadataChanges:(BOOL)includeMetadataChanges
NS_DESIGNATED_INITIALIZER;
+@property(nonatomic, assign, readonly) BOOL includeMetadataChanges;
+
@end
@implementation FIRDocumentListenOptions
@@ -114,7 +116,7 @@ NS_ASSUME_NONNULL_BEGIN
- (BOOL)isEqual:(nullable id)other {
if (other == self) return YES;
- if (!other || ![[other class] isEqual:[self class]]) return NO;
+ if (![[other class] isEqual:[self class]]) return NO;
return [self isEqualToReference:other];
}
diff --git a/Firestore/Source/API/FIRDocumentSnapshot.m b/Firestore/Source/API/FIRDocumentSnapshot.m
index b78472e..358ddac 100644
--- a/Firestore/Source/API/FIRDocumentSnapshot.m
+++ b/Firestore/Source/API/FIRDocumentSnapshot.m
@@ -20,11 +20,13 @@
#import "Firestore/Source/API/FIRFieldPath+Internal.h"
#import "Firestore/Source/API/FIRFirestore+Internal.h"
#import "Firestore/Source/API/FIRSnapshotMetadata+Internal.h"
+#import "Firestore/Source/API/FIRSnapshotOptions+Internal.h"
#import "Firestore/Source/Model/FSTDatabaseID.h"
#import "Firestore/Source/Model/FSTDocument.h"
#import "Firestore/Source/Model/FSTDocumentKey.h"
#import "Firestore/Source/Model/FSTFieldValue.h"
#import "Firestore/Source/Model/FSTPath.h"
+#import "Firestore/Source/Util/FSTAssert.h"
#import "Firestore/Source/Util/FSTUsageValidation.h"
NS_ASSUME_NONNULL_BEGIN
@@ -76,6 +78,37 @@ NS_ASSUME_NONNULL_BEGIN
return self;
}
+// NSObject Methods
+- (BOOL)isEqual:(nullable id)other {
+ if (other == self) return YES;
+ // self class could be FIRDocumentSnapshot or subtype. So we compare with base type explicitly.
+ if (![other isKindOfClass:[FIRDocumentSnapshot class]]) return NO;
+
+ return [self isEqualToSnapshot:other];
+}
+
+- (BOOL)isEqualToSnapshot:(nullable FIRDocumentSnapshot *)snapshot {
+ if (self == snapshot) return YES;
+ if (snapshot == nil) return NO;
+ if (self.firestore != snapshot.firestore && ![self.firestore isEqual:snapshot.firestore])
+ return NO;
+ if (self.internalKey != snapshot.internalKey && ![self.internalKey isEqual:snapshot.internalKey])
+ return NO;
+ if (self.internalDocument != snapshot.internalDocument &&
+ ![self.internalDocument isEqual:snapshot.internalDocument])
+ return NO;
+ if (self.fromCache != snapshot.fromCache) return NO;
+ return YES;
+}
+
+- (NSUInteger)hash {
+ NSUInteger hash = [self.firestore hash];
+ hash = hash * 31u + [self.internalKey hash];
+ hash = hash * 31u + [self.internalDocument hash];
+ hash = hash * 31u + (self.fromCache ? 1 : 0);
+ return hash;
+}
+
@dynamic exists;
- (BOOL)exists {
@@ -99,40 +132,48 @@ NS_ASSUME_NONNULL_BEGIN
return _cachedMetadata;
}
-- (NSDictionary<NSString *, id> *)data {
- FSTDocument *document = self.internalDocument;
-
- if (!document) {
- FSTThrowInvalidUsage(
- @"NonExistentDocumentException",
- @"Document '%@' doesn't exist. "
- @"Check document.exists to make sure the document exists before calling document.data.",
- self.internalKey);
- }
+- (nullable NSDictionary<NSString *, id> *)data {
+ return [self dataWithOptions:[FIRSnapshotOptions defaultOptions]];
+}
- return [self convertedObject:[self.internalDocument data]];
+- (nullable NSDictionary<NSString *, id> *)dataWithOptions:(FIRSnapshotOptions *)options {
+ return self.internalDocument == nil
+ ? nil
+ : [self convertedObject:[self.internalDocument data]
+ options:[FSTFieldValueOptions optionsForSnapshotOptions:options]];
}
-- (nullable id)objectForKeyedSubscript:(id)key {
+- (nullable id)valueForField:(id)field {
+ return [self valueForField:field options:[FIRSnapshotOptions defaultOptions]];
+}
+
+- (nullable id)valueForField:(id)field options:(FIRSnapshotOptions *)options {
FIRFieldPath *fieldPath;
- if ([key isKindOfClass:[NSString class]]) {
- fieldPath = [FIRFieldPath pathWithDotSeparatedString:key];
- } else if ([key isKindOfClass:[FIRFieldPath class]]) {
- fieldPath = key;
+ if ([field isKindOfClass:[NSString class]]) {
+ fieldPath = [FIRFieldPath pathWithDotSeparatedString:field];
+ } else if ([field isKindOfClass:[FIRFieldPath class]]) {
+ fieldPath = field;
} else {
FSTThrowInvalidArgument(@"Subscript key must be an NSString or FIRFieldPath.");
}
FSTFieldValue *fieldValue = [[self.internalDocument data] valueForPath:fieldPath.internalValue];
- return [self convertedValue:fieldValue];
+ return fieldValue == nil
+ ? nil
+ : [self convertedValue:fieldValue
+ options:[FSTFieldValueOptions optionsForSnapshotOptions:options]];
}
-- (id)convertedValue:(FSTFieldValue *)value {
+- (nullable id)objectForKeyedSubscript:(id)key {
+ return [self valueForField:key];
+}
+
+- (id)convertedValue:(FSTFieldValue *)value options:(FSTFieldValueOptions *)options {
if ([value isKindOfClass:[FSTObjectValue class]]) {
- return [self convertedObject:(FSTObjectValue *)value];
+ return [self convertedObject:(FSTObjectValue *)value options:options];
} else if ([value isKindOfClass:[FSTArrayValue class]]) {
- return [self convertedArray:(FSTArrayValue *)value];
+ return [self convertedArray:(FSTArrayValue *)value options:options];
} else if ([value isKindOfClass:[FSTReferenceValue class]]) {
FSTReferenceValue *ref = (FSTReferenceValue *)value;
FSTDatabaseID *refDatabase = ref.databaseID;
@@ -146,30 +187,69 @@ NS_ASSUME_NONNULL_BEGIN
self.reference.path, refDatabase.projectID, refDatabase.databaseID, database.projectID,
database.databaseID);
}
- return [FIRDocumentReference referenceWithKey:ref.value firestore:self.firestore];
+ return [FIRDocumentReference referenceWithKey:[ref valueWithOptions:options]
+ firestore:self.firestore];
} else {
- return value.value;
+ return [value valueWithOptions:options];
}
}
-- (NSDictionary<NSString *, id> *)convertedObject:(FSTObjectValue *)objectValue {
+- (NSDictionary<NSString *, id> *)convertedObject:(FSTObjectValue *)objectValue
+ options:(FSTFieldValueOptions *)options {
NSMutableDictionary *result = [NSMutableDictionary dictionary];
[objectValue.internalValue
enumerateKeysAndObjectsUsingBlock:^(NSString *key, FSTFieldValue *value, BOOL *stop) {
- result[key] = [self convertedValue:value];
+ result[key] = [self convertedValue:value options:options];
}];
return result;
}
-- (NSArray<id> *)convertedArray:(FSTArrayValue *)arrayValue {
+- (NSArray<id> *)convertedArray:(FSTArrayValue *)arrayValue
+ options:(FSTFieldValueOptions *)options {
NSArray<FSTFieldValue *> *internalValue = arrayValue.internalValue;
NSMutableArray *result = [NSMutableArray arrayWithCapacity:internalValue.count];
[internalValue enumerateObjectsUsingBlock:^(id value, NSUInteger idx, BOOL *stop) {
- [result addObject:[self convertedValue:value]];
+ [result addObject:[self convertedValue:value options:options]];
}];
return result;
}
@end
+@interface FIRQueryDocumentSnapshot ()
+
+- (instancetype)initWithFirestore:(FIRFirestore *)firestore
+ documentKey:(FSTDocumentKey *)documentKey
+ document:(FSTDocument *)document
+ fromCache:(BOOL)fromCache NS_DESIGNATED_INITIALIZER;
+
+@end
+
+@implementation FIRQueryDocumentSnapshot
+
+- (instancetype)initWithFirestore:(FIRFirestore *)firestore
+ documentKey:(FSTDocumentKey *)documentKey
+ document:(FSTDocument *)document
+ fromCache:(BOOL)fromCache {
+ self = [super initWithFirestore:firestore
+ documentKey:documentKey
+ document:document
+ fromCache:fromCache];
+ return self;
+}
+
+- (NSDictionary<NSString *, id> *)data {
+ NSDictionary<NSString *, id> *data = [super data];
+ FSTAssert(data, @"Document in a QueryDocumentSnapshot should exist");
+ return data;
+}
+
+- (NSDictionary<NSString *, id> *)dataWithOptions:(FIRSnapshotOptions *)options {
+ NSDictionary<NSString *, id> *data = [super dataWithOptions:options];
+ FSTAssert(data, @"Document in a QueryDocumentSnapshot should exist");
+ return data;
+}
+
+@end
+
NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/API/FIRFieldPath.m b/Firestore/Source/API/FIRFieldPath.m
index d0a70c0..f4e532f 100644
--- a/Firestore/Source/API/FIRFieldPath.m
+++ b/Firestore/Source/API/FIRFieldPath.m
@@ -80,7 +80,7 @@ NS_ASSUME_NONNULL_BEGIN
return [[[self class] alloc] initPrivate:self.internalValue];
}
-- (BOOL)isEqual:(id)object {
+- (BOOL)isEqual:(nullable id)object {
if (self == object) {
return YES;
}
diff --git a/Firestore/Source/API/FIRFirestore.m b/Firestore/Source/API/FIRFirestore.m
index 7814ce1..9df3711 100644
--- a/Firestore/Source/API/FIRFirestore.m
+++ b/Firestore/Source/API/FIRFirestore.m
@@ -50,13 +50,17 @@ NSString *const FIRFirestoreErrorDomain = @"FIRFirestoreErrorDomain";
@property(nonatomic, strong) id<FSTCredentialsProvider> credentialsProvider;
@property(nonatomic, strong) FSTDispatchQueue *workerDispatchQueue;
-@property(nonatomic, strong) FSTFirestoreClient *client;
+// Note that `client` is updated after initialization, but marking this readwrite would generate an
+// incorrect setter (since we make the assignment to `client` inside an `@synchronized` block.
+@property(nonatomic, strong, readonly) FSTFirestoreClient *client;
@property(nonatomic, strong, readonly) FSTUserDataConverter *dataConverter;
@end
@implementation FIRFirestore {
+ // All guarded by @synchronized(self)
FIRFirestoreSettings *_settings;
+ FSTFirestoreClient *_client;
}
+ (NSMutableDictionary<NSString *, FIRFirestore *> *)instances {
@@ -154,64 +158,74 @@ NSString *const FIRFirestoreErrorDomain = @"FIRFirestoreErrorDomain";
}
- (FIRFirestoreSettings *)settings {
- // Disallow mutation of our internal settings
- return [_settings copy];
+ @synchronized(self) {
+ // Disallow mutation of our internal settings
+ return [_settings copy];
+ }
}
- (void)setSettings:(FIRFirestoreSettings *)settings {
- // As a special exception, don't throw if the same settings are passed repeatedly. This should
- // make it more friendly to create a Firestore instance.
- if (_client && ![_settings isEqual:settings]) {
- FSTThrowInvalidUsage(@"FIRIllegalStateException",
- @"Firestore instance has already been started and its settings can no "
- "longer be changed. You can only set settings before calling any "
- "other methods on a Firestore instance.");
+ @synchronized(self) {
+ // As a special exception, don't throw if the same settings are passed repeatedly. This should
+ // make it more friendly to create a Firestore instance.
+ if (_client && ![_settings isEqual:settings]) {
+ FSTThrowInvalidUsage(@"FIRIllegalStateException",
+ @"Firestore instance has already been started and its settings can no "
+ "longer be changed. You can only set settings before calling any "
+ "other methods on a Firestore instance.");
+ }
+ _settings = [settings copy];
}
- _settings = [settings copy];
}
/**
- * Ensures that the FirestoreClient is configured.
- * @return self
+ * Ensures that the FirestoreClient is configured and returns it.
*/
-- (instancetype)firestoreWithConfiguredClient {
- if (!_client) {
- // These values are validated elsewhere; this is just double-checking:
- FSTAssert(_settings.host, @"FirestoreSettings.host cannot be nil.");
- FSTAssert(_settings.dispatchQueue, @"FirestoreSettings.dispatchQueue cannot be nil.");
-
- FSTDatabaseInfo *databaseInfo =
- [FSTDatabaseInfo databaseInfoWithDatabaseID:_databaseID
- persistenceKey:_persistenceKey
- host:_settings.host
- sslEnabled:_settings.sslEnabled];
-
- FSTDispatchQueue *userDispatchQueue = [FSTDispatchQueue queueWith:_settings.dispatchQueue];
-
- _client = [FSTFirestoreClient clientWithDatabaseInfo:databaseInfo
- usePersistence:_settings.persistenceEnabled
- credentialsProvider:_credentialsProvider
- userDispatchQueue:userDispatchQueue
- workerDispatchQueue:_workerDispatchQueue];
+- (FSTFirestoreClient *)client {
+ [self ensureClientConfigured];
+ return _client;
+}
+
+- (void)ensureClientConfigured {
+ @synchronized(self) {
+ if (!_client) {
+ // These values are validated elsewhere; this is just double-checking:
+ FSTAssert(_settings.host, @"FirestoreSettings.host cannot be nil.");
+ FSTAssert(_settings.dispatchQueue, @"FirestoreSettings.dispatchQueue cannot be nil.");
+
+ FSTDatabaseInfo *databaseInfo =
+ [FSTDatabaseInfo databaseInfoWithDatabaseID:_databaseID
+ persistenceKey:_persistenceKey
+ host:_settings.host
+ sslEnabled:_settings.sslEnabled];
+
+ FSTDispatchQueue *userDispatchQueue = [FSTDispatchQueue queueWith:_settings.dispatchQueue];
+
+ _client = [FSTFirestoreClient clientWithDatabaseInfo:databaseInfo
+ usePersistence:_settings.persistenceEnabled
+ credentialsProvider:_credentialsProvider
+ userDispatchQueue:userDispatchQueue
+ workerDispatchQueue:_workerDispatchQueue];
+ }
}
- return self;
}
- (FIRCollectionReference *)collectionWithPath:(NSString *)collectionPath {
if (!collectionPath) {
FSTThrowInvalidArgument(@"Collection path cannot be nil.");
}
+ [self ensureClientConfigured];
FSTResourcePath *path = [FSTResourcePath pathWithString:collectionPath];
- return
- [FIRCollectionReference referenceWithPath:path firestore:self.firestoreWithConfiguredClient];
+ return [FIRCollectionReference referenceWithPath:path firestore:self];
}
- (FIRDocumentReference *)documentWithPath:(NSString *)documentPath {
if (!documentPath) {
FSTThrowInvalidArgument(@"Document path cannot be nil.");
}
+ [self ensureClientConfigured];
FSTResourcePath *path = [FSTResourcePath pathWithString:documentPath];
- return [FIRDocumentReference referenceWithPath:path firestore:self.firestoreWithConfiguredClient];
+ return [FIRDocumentReference referenceWithPath:path firestore:self];
}
- (void)runTransactionWithBlock:(id _Nullable (^)(FIRTransaction *, NSError **))updateBlock
@@ -241,12 +255,13 @@ NSString *const FIRFirestoreErrorDomain = @"FIRFirestoreErrorDomain";
internalCompletion(result, error);
});
};
- [self firestoreWithConfiguredClient];
[self.client transactionWithRetries:5 updateBlock:wrappedUpdate completion:completion];
}
- (FIRWriteBatch *)batch {
- return [FIRWriteBatch writeBatchWithFirestore:[self firestoreWithConfiguredClient]];
+ [self ensureClientConfigured];
+
+ return [FIRWriteBatch writeBatchWithFirestore:self];
}
- (void)runTransactionWithBlock:(id _Nullable (^)(FIRTransaction *, NSError **error))updateBlock
@@ -264,11 +279,19 @@ NSString *const FIRFirestoreErrorDomain = @"FIRFirestoreErrorDomain";
}
- (void)shutdownWithCompletion:(nullable void (^)(NSError *_Nullable error))completion {
- if (!self.client) {
+ FSTFirestoreClient *client;
+ @synchronized(self) {
+ client = _client;
+ _client = nil;
+ }
+
+ if (!client) {
+ // We should be dispatching the callback on the user dispatch queue but if the client is nil
+ // here that queue was never created.
completion(nil);
- return;
+ } else {
+ [client shutdownWithCompletion:completion];
}
- return [self.client shutdownWithCompletion:completion];
}
+ (BOOL)isLoggingEnabled {
@@ -279,6 +302,16 @@ NSString *const FIRFirestoreErrorDomain = @"FIRFirestoreErrorDomain";
FIRSetLoggerLevel(logging ? FIRLoggerLevelDebug : FIRLoggerLevelNotice);
}
+- (void)enableNetworkWithCompletion:(nullable void (^)(NSError *_Nullable error))completion {
+ [self ensureClientConfigured];
+ [self.client enableNetworkWithCompletion:completion];
+}
+
+- (void)disableNetworkWithCompletion:(nullable void (^)(NSError *_Nullable))completion {
+ [self ensureClientConfigured];
+ [self.client disableNetworkWithCompletion:completion];
+}
+
@end
NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/API/FIRQuery.m b/Firestore/Source/API/FIRQuery.m
index 12e79c5..2feca39 100644
--- a/Firestore/Source/API/FIRQuery.m
+++ b/Firestore/Source/API/FIRQuery.m
@@ -107,7 +107,7 @@ NS_ASSUME_NONNULL_BEGIN
- (BOOL)isEqual:(nullable id)other {
if (other == self) return YES;
- if (!other || ![[other class] isEqual:[self class]]) return NO;
+ if (![[other class] isEqual:[self class]]) return NO;
return [self isEqualToQuery:other];
}
@@ -256,6 +256,95 @@ addSnapshotListenerInternalWithOptions:(FSTListenOptions *)internalOptions
value:value];
}
+- (FIRQuery *)queryFilteredUsingComparisonPredicate:(NSPredicate *)predicate {
+ NSComparisonPredicate *comparison = (NSComparisonPredicate *)predicate;
+ if (comparison.comparisonPredicateModifier != NSDirectPredicateModifier) {
+ FSTThrowInvalidArgument(@"Invalid query. Predicate cannot have an aggregate modifier.");
+ }
+ NSString *path;
+ id value = nil;
+ if ([comparison.leftExpression expressionType] == NSKeyPathExpressionType &&
+ [comparison.rightExpression expressionType] == NSConstantValueExpressionType) {
+ path = comparison.leftExpression.keyPath;
+ value = comparison.rightExpression.constantValue;
+ switch (comparison.predicateOperatorType) {
+ case NSEqualToPredicateOperatorType:
+ return [self queryWhereField:path isEqualTo:value];
+ case NSLessThanPredicateOperatorType:
+ return [self queryWhereField:path isLessThan:value];
+ case NSLessThanOrEqualToPredicateOperatorType:
+ return [self queryWhereField:path isLessThanOrEqualTo:value];
+ case NSGreaterThanPredicateOperatorType:
+ return [self queryWhereField:path isGreaterThan:value];
+ case NSGreaterThanOrEqualToPredicateOperatorType:
+ return [self queryWhereField:path isGreaterThanOrEqualTo:value];
+ default:; // Fallback below to throw assertion.
+ }
+ } else if ([comparison.leftExpression expressionType] == NSConstantValueExpressionType &&
+ [comparison.rightExpression expressionType] == NSKeyPathExpressionType) {
+ path = comparison.rightExpression.keyPath;
+ value = comparison.leftExpression.constantValue;
+ switch (comparison.predicateOperatorType) {
+ case NSEqualToPredicateOperatorType:
+ return [self queryWhereField:path isEqualTo:value];
+ case NSLessThanPredicateOperatorType:
+ return [self queryWhereField:path isGreaterThan:value];
+ case NSLessThanOrEqualToPredicateOperatorType:
+ return [self queryWhereField:path isGreaterThanOrEqualTo:value];
+ case NSGreaterThanPredicateOperatorType:
+ return [self queryWhereField:path isLessThan:value];
+ case NSGreaterThanOrEqualToPredicateOperatorType:
+ return [self queryWhereField:path isLessThanOrEqualTo:value];
+ default:; // Fallback below to throw assertion.
+ }
+ } else {
+ FSTThrowInvalidArgument(
+ @"Invalid query. Predicate comparisons must include a key path and a constant.");
+ }
+ // Fallback cases of unsupported comparison operator.
+ switch (comparison.predicateOperatorType) {
+ case NSCustomSelectorPredicateOperatorType:
+ FSTThrowInvalidArgument(@"Invalid query. Custom predicate filters are not supported.");
+ break;
+ default:
+ FSTThrowInvalidArgument(@"Invalid query. Operator type %lu is not supported.",
+ (unsigned long)comparison.predicateOperatorType);
+ }
+}
+
+- (FIRQuery *)queryFilteredUsingCompoundPredicate:(NSPredicate *)predicate {
+ NSCompoundPredicate *compound = (NSCompoundPredicate *)predicate;
+ if (compound.compoundPredicateType != NSAndPredicateType || compound.subpredicates.count == 0) {
+ FSTThrowInvalidArgument(@"Invalid query. Only compound queries using AND are supported.");
+ }
+ FIRQuery *query = self;
+ for (NSPredicate *pred in compound.subpredicates) {
+ query = [query queryFilteredUsingPredicate:pred];
+ }
+ return query;
+}
+
+- (FIRQuery *)queryFilteredUsingPredicate:(NSPredicate *)predicate {
+ if ([predicate isKindOfClass:[NSComparisonPredicate class]]) {
+ return [self queryFilteredUsingComparisonPredicate:predicate];
+ } else if ([predicate isKindOfClass:[NSCompoundPredicate class]]) {
+ return [self queryFilteredUsingCompoundPredicate:predicate];
+ } else if ([predicate isKindOfClass:[[NSPredicate
+ predicateWithBlock:^BOOL(id obj, NSDictionary *bindings) {
+ return true;
+ }] class]]) {
+ FSTThrowInvalidArgument(
+ @"Invalid query. Block-based predicates are not "
+ "supported. Please use predicateWithFormat to "
+ "create predicates instead.");
+ } else {
+ FSTThrowInvalidArgument(
+ @"Invalid query. Expect comparison or compound of "
+ "comparison predicate. Please use "
+ "predicateWithFormat to create predicates.");
+ }
+}
+
- (FIRQuery *)queryOrderedByField:(NSString *)field {
return
[self queryOrderedByFieldPath:[FIRFieldPath pathWithDotSeparatedString:field] descending:NO];
diff --git a/Firestore/Source/API/FIRQuerySnapshot.m b/Firestore/Source/API/FIRQuerySnapshot.m
index 6bc6761..177e461 100644
--- a/Firestore/Source/API/FIRQuerySnapshot.m
+++ b/Firestore/Source/API/FIRQuerySnapshot.m
@@ -16,6 +16,7 @@
#import "Firestore/Source/API/FIRQuerySnapshot+Internal.h"
+#import "FIRFirestore.h"
#import "FIRSnapshotMetadata.h"
#import "Firestore/Source/API/FIRDocumentChange+Internal.h"
#import "Firestore/Source/API/FIRDocumentSnapshot+Internal.h"
@@ -57,7 +58,7 @@ NS_ASSUME_NONNULL_BEGIN
@implementation FIRQuerySnapshot {
// Cached value of the documents property.
- NSArray<FIRDocumentSnapshot *> *_documents;
+ NSArray<FIRQueryDocumentSnapshot *> *_documents;
// Cached value of the documentChanges property.
NSArray<FIRDocumentChange *> *_documentChanges;
@@ -76,6 +77,35 @@ NS_ASSUME_NONNULL_BEGIN
return self;
}
+// NSObject Methods
+- (BOOL)isEqual:(nullable id)other {
+ if (other == self) return YES;
+ if (![[other class] isEqual:[self class]]) return NO;
+
+ return [self isEqualToSnapshot:other];
+}
+
+- (BOOL)isEqualToSnapshot:(nullable FIRQuerySnapshot *)snapshot {
+ if (self == snapshot) return YES;
+ if (snapshot == nil) return NO;
+ if (self.firestore != snapshot.firestore && ![self.firestore isEqual:snapshot.firestore])
+ return NO;
+ if (self.originalQuery != snapshot.originalQuery &&
+ ![self.originalQuery isEqual:snapshot.originalQuery])
+ return NO;
+ if (self.snapshot != snapshot.snapshot && ![self.snapshot isEqual:snapshot.snapshot]) return NO;
+ if (self.metadata != snapshot.metadata && ![self.metadata isEqual:snapshot.metadata]) return NO;
+ return YES;
+}
+
+- (NSUInteger)hash {
+ NSUInteger hash = [self.firestore hash];
+ hash = hash * 31u + [self.originalQuery hash];
+ hash = hash * 31u + [self.snapshot hash];
+ hash = hash * 31u + [self.metadata hash];
+ return hash;
+}
+
@dynamic empty;
- (FIRQuery *)query {
@@ -93,18 +123,18 @@ NS_ASSUME_NONNULL_BEGIN
return self.snapshot.documents.count;
}
-- (NSArray<FIRDocumentSnapshot *> *)documents {
+- (NSArray<FIRQueryDocumentSnapshot *> *)documents {
if (!_documents) {
FSTDocumentSet *documentSet = self.snapshot.documents;
FIRFirestore *firestore = self.firestore;
BOOL fromCache = self.metadata.fromCache;
- NSMutableArray<FIRDocumentSnapshot *> *result = [NSMutableArray array];
+ NSMutableArray<FIRQueryDocumentSnapshot *> *result = [NSMutableArray array];
for (FSTDocument *document in documentSet.documentEnumerator) {
- [result addObject:[FIRDocumentSnapshot snapshotWithFirestore:firestore
- documentKey:document.key
- document:document
- fromCache:fromCache]];
+ [result addObject:[FIRQueryDocumentSnapshot snapshotWithFirestore:firestore
+ documentKey:document.key
+ document:document
+ fromCache:fromCache]];
}
_documents = result;
diff --git a/Firestore/Source/API/FIRSetOptions.m b/Firestore/Source/API/FIRSetOptions.m
index 623deaa..743bcc7 100644
--- a/Firestore/Source/API/FIRSetOptions.m
+++ b/Firestore/Source/API/FIRSetOptions.m
@@ -15,7 +15,6 @@
*/
#import "Firestore/Source/API/FIRSetOptions+Internal.h"
-#import "Firestore/Source/Model/FSTMutation.h"
NS_ASSUME_NONNULL_BEGIN
diff --git a/Firestore/Source/API/FIRSnapshotMetadata.m b/Firestore/Source/API/FIRSnapshotMetadata.m
index 224015f..d957a8d 100644
--- a/Firestore/Source/API/FIRSnapshotMetadata.m
+++ b/Firestore/Source/API/FIRSnapshotMetadata.m
@@ -44,6 +44,28 @@ NS_ASSUME_NONNULL_BEGIN
return self;
}
+// NSObject Methods
+- (BOOL)isEqual:(nullable id)other {
+ if (other == self) return YES;
+ if (![[other class] isEqual:[self class]]) return NO;
+
+ return [self isEqualToMetadata:other];
+}
+
+- (BOOL)isEqualToMetadata:(nullable FIRSnapshotMetadata *)metadata {
+ if (self == metadata) return YES;
+ if (metadata == nil) return NO;
+ if (self.pendingWrites != metadata.pendingWrites) return NO;
+ if (self.fromCache != metadata.fromCache) return NO;
+ return YES;
+}
+
+- (NSUInteger)hash {
+ NSUInteger hash = self.pendingWrites ? 1 : 0;
+ hash = hash * 31u + (self.fromCache ? 1 : 0);
+ return hash;
+}
+
@end
NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/API/FIRSnapshotOptions+Internal.h b/Firestore/Source/API/FIRSnapshotOptions+Internal.h
new file mode 100644
index 0000000..64e7dbc
--- /dev/null
+++ b/Firestore/Source/API/FIRSnapshotOptions+Internal.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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 "FIRDocumentSnapshot.h"
+
+#import <Foundation/Foundation.h>
+
+#import "Firestore/Source/Model/FSTFieldValue.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FIRSnapshotOptions (Internal)
+
+/** Returns a default instance of FIRSnapshotOptions that specifies no options. */
++ (instancetype)defaultOptions;
+
+/* Initializes a new instance with the specified server timestamp behavior. */
+- (instancetype)initWithServerTimestampBehavior:(FSTServerTimestampBehavior)serverTimestampBehavior;
+
+/* Returns the server timestamp behavior. Returns -1 if no behavior is specified. */
+- (FSTServerTimestampBehavior)serverTimestampBehavior;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/API/FIRSnapshotOptions.m b/Firestore/Source/API/FIRSnapshotOptions.m
new file mode 100644
index 0000000..72ea3cc
--- /dev/null
+++ b/Firestore/Source/API/FIRSnapshotOptions.m
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2017 Google
+ *
+ * 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 "FIRDocumentSnapshot.h"
+
+#import "Firestore/Source/API/FIRSnapshotOptions+Internal.h"
+#import "Firestore/Source/Util/FSTAssert.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FIRSnapshotOptions ()
+
+@property(nonatomic) FSTServerTimestampBehavior serverTimestampBehavior;
+
+@end
+
+@implementation FIRSnapshotOptions
+
+- (instancetype)initWithServerTimestampBehavior:
+ (FSTServerTimestampBehavior)serverTimestampBehavior {
+ self = [super init];
+
+ if (self) {
+ _serverTimestampBehavior = serverTimestampBehavior;
+ }
+
+ return self;
+}
+
++ (instancetype)defaultOptions {
+ static FIRSnapshotOptions *sharedInstance = nil;
+ static dispatch_once_t onceToken;
+
+ dispatch_once(&onceToken, ^{
+ sharedInstance =
+ [[FIRSnapshotOptions alloc] initWithServerTimestampBehavior:FSTServerTimestampBehaviorNone];
+ });
+
+ return sharedInstance;
+}
+
++ (instancetype)serverTimestampBehavior:(FIRServerTimestampBehavior)serverTimestampBehavior {
+ switch (serverTimestampBehavior) {
+ case FIRServerTimestampBehaviorEstimate:
+ return [[FIRSnapshotOptions alloc]
+ initWithServerTimestampBehavior:FSTServerTimestampBehaviorEstimate];
+ case FIRServerTimestampBehaviorPrevious:
+ return [[FIRSnapshotOptions alloc]
+ initWithServerTimestampBehavior:FSTServerTimestampBehaviorPrevious];
+ case FIRServerTimestampBehaviorNone:
+ return [FIRSnapshotOptions defaultOptions];
+ default:
+ FSTFail(@"Encountered unknown server timestamp behavior: %d", (int)serverTimestampBehavior);
+ }
+}
+
+@end
+
+NS_ASSUME_NONNULL_END \ No newline at end of file
diff --git a/Firestore/Source/API/FIRWriteBatch.m b/Firestore/Source/API/FIRWriteBatch.m
index b918a9a..b1cfa09 100644
--- a/Firestore/Source/API/FIRWriteBatch.m
+++ b/Firestore/Source/API/FIRWriteBatch.m
@@ -93,7 +93,11 @@ NS_ASSUME_NONNULL_BEGIN
return self;
}
-- (void)commitWithCompletion:(void (^)(NSError *_Nullable error))completion {
+- (void)commit {
+ [self commitWithCompletion:nil];
+}
+
+- (void)commitWithCompletion:(nullable void (^)(NSError *_Nullable error))completion {
[self verifyNotCommitted];
self.committed = TRUE;
[self.firestore.client writeMutations:self.mutations completion:completion];
diff --git a/Firestore/Source/Core/FSTEventManager.h b/Firestore/Source/Core/FSTEventManager.h
index edd2a96..8eafd4b 100644
--- a/Firestore/Source/Core/FSTEventManager.h
+++ b/Firestore/Source/Core/FSTEventManager.h
@@ -62,7 +62,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)queryDidChangeViewSnapshot:(FSTViewSnapshot *)snapshot;
- (void)queryDidError:(NSError *)error;
-- (void)clientDidChangeOnlineState:(FSTOnlineState)onlineState;
+- (void)applyChangedOnlineState:(FSTOnlineState)onlineState;
@property(nonatomic, strong, readonly) FSTQuery *query;
diff --git a/Firestore/Source/Core/FSTEventManager.m b/Firestore/Source/Core/FSTEventManager.m
index 3e1b99b..bc204a0 100644
--- a/Firestore/Source/Core/FSTEventManager.m
+++ b/Firestore/Source/Core/FSTEventManager.m
@@ -151,7 +151,7 @@ NS_ASSUME_NONNULL_BEGIN
self.viewSnapshotHandler(nil, error);
}
-- (void)clientDidChangeOnlineState:(FSTOnlineState)onlineState {
+- (void)applyChangedOnlineState:(FSTOnlineState)onlineState {
self.onlineState = onlineState;
if (self.snapshot && !self.raisedInitialEvent &&
[self shouldRaiseInitialEventForSnapshot:self.snapshot onlineState:onlineState]) {
@@ -268,7 +268,7 @@ NS_ASSUME_NONNULL_BEGIN
}
[queryInfo.listeners addObject:listener];
- [listener clientDidChangeOnlineState:self.onlineState];
+ [listener applyChangedOnlineState:self.onlineState];
if (queryInfo.viewSnapshot) {
[listener queryDidChangeViewSnapshot:queryInfo.viewSnapshot];
@@ -321,11 +321,11 @@ NS_ASSUME_NONNULL_BEGIN
[self.queries removeObjectForKey:query];
}
-- (void)watchStreamDidChangeOnlineState:(FSTOnlineState)onlineState {
+- (void)applyChangedOnlineState:(FSTOnlineState)onlineState {
self.onlineState = onlineState;
for (FSTQueryListenersInfo *info in self.queries.objectEnumerator) {
for (FSTQueryListener *listener in info.listeners) {
- [listener clientDidChangeOnlineState:onlineState];
+ [listener applyChangedOnlineState:onlineState];
}
}
}
diff --git a/Firestore/Source/Core/FSTFirestoreClient.h b/Firestore/Source/Core/FSTFirestoreClient.h
index 6a1e11b..0ecf2f6 100644
--- a/Firestore/Source/Core/FSTFirestoreClient.h
+++ b/Firestore/Source/Core/FSTFirestoreClient.h
@@ -38,7 +38,7 @@ NS_ASSUME_NONNULL_BEGIN
* SDK architecture. It is responsible for creating the worker queue that is shared by all of the
* other components in the system.
*/
-@interface FSTFirestoreClient : NSObject
+@interface FSTFirestoreClient : NSObject <FSTOnlineStateDelegate>
/**
* Creates and returns a FSTFirestoreClient with the given parameters.
diff --git a/Firestore/Source/Core/FSTFirestoreClient.m b/Firestore/Source/Core/FSTFirestoreClient.m
index 2e0e407..fff644d 100644
--- a/Firestore/Source/Core/FSTFirestoreClient.m
+++ b/Firestore/Source/Core/FSTFirestoreClient.m
@@ -172,7 +172,7 @@ NS_ASSUME_NONNULL_BEGIN
// Setup wiring for remote store.
_remoteStore.syncEngine = _syncEngine;
- _remoteStore.onlineStateDelegate = _eventManager;
+ _remoteStore.onlineStateDelegate = self;
// NOTE: RemoteStore depends on LocalStore (for persisting stream tokens, refilling mutation
// queue, etc.) so must be started after LocalStore.
@@ -187,6 +187,11 @@ NS_ASSUME_NONNULL_BEGIN
[self.syncEngine userDidChange:user];
}
+- (void)applyChangedOnlineState:(FSTOnlineState)onlineState {
+ [self.syncEngine applyChangedOnlineState:onlineState];
+ [self.eventManager applyChangedOnlineState:onlineState];
+}
+
- (void)disableNetworkWithCompletion:(nullable FSTVoidErrorBlock)completion {
[self.workerDispatchQueue dispatchAsync:^{
[self.remoteStore disableNetwork];
diff --git a/Firestore/Source/Core/FSTQuery.m b/Firestore/Source/Core/FSTQuery.m
index 0bfd917..13657f7 100644
--- a/Firestore/Source/Core/FSTQuery.m
+++ b/Firestore/Source/Core/FSTQuery.m
@@ -205,7 +205,7 @@ NSString *FSTStringFromQueryRelationOperator(FSTRelationFilterOperator filterOpe
- (BOOL)isEqual:(id)other {
if (other == self) return YES;
- if (!other || ![[other class] isEqual:[self class]]) return NO;
+ if (![[other class] isEqual:[self class]]) return NO;
return [self.field isEqual:((FSTNullFilter *)other).field];
}
@@ -246,7 +246,7 @@ NSString *FSTStringFromQueryRelationOperator(FSTRelationFilterOperator filterOpe
- (BOOL)isEqual:(id)other {
if (other == self) return YES;
- if (!other || ![[other class] isEqual:[self class]]) return NO;
+ if (![[other class] isEqual:[self class]]) return NO;
return [self.field isEqual:((FSTNanFilter *)other).field];
}
diff --git a/Firestore/Source/Core/FSTSyncEngine.h b/Firestore/Source/Core/FSTSyncEngine.h
index bb45196..7060155 100644
--- a/Firestore/Source/Core/FSTSyncEngine.h
+++ b/Firestore/Source/Core/FSTSyncEngine.h
@@ -100,6 +100,9 @@ NS_ASSUME_NONNULL_BEGIN
- (void)userDidChange:(FSTUser *)user;
+/** Applies an FSTOnlineState change to the sync engine and notifies any views of the change. */
+- (void)applyChangedOnlineState:(FSTOnlineState)onlineState;
+
@end
NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Core/FSTSyncEngine.m b/Firestore/Source/Core/FSTSyncEngine.m
index 98658e4..27ab73e 100644
--- a/Firestore/Source/Core/FSTSyncEngine.m
+++ b/Firestore/Source/Core/FSTSyncEngine.m
@@ -318,6 +318,21 @@ NS_ASSUME_NONNULL_BEGIN
[self emitNewSnapshotsWithChanges:changes remoteEvent:remoteEvent];
}
+- (void)applyChangedOnlineState:(FSTOnlineState)onlineState {
+ NSMutableArray<FSTViewSnapshot *> *newViewSnapshots = [NSMutableArray array];
+ [self.queryViewsByQuery
+ enumerateKeysAndObjectsUsingBlock:^(FSTQuery *query, FSTQueryView *queryView, BOOL *stop) {
+ FSTViewChange *viewChange = [queryView.view applyChangedOnlineState:onlineState];
+ FSTAssert(viewChange.limboChanges.count == 0,
+ @"OnlineState should not affect limbo documents.");
+ if (viewChange.snapshot) {
+ [newViewSnapshots addObject:viewChange.snapshot];
+ }
+ }];
+
+ [self.delegate handleViewSnapshots:newViewSnapshots];
+}
+
- (void)rejectListenWithTargetID:(FSTBoxedTargetID *)targetID error:(NSError *)error {
[self assertDelegateExistsForSelector:_cmd];
diff --git a/Firestore/Source/Core/FSTTypes.h b/Firestore/Source/Core/FSTTypes.h
index c10f1bf..b47bd0b 100644
--- a/Firestore/Source/Core/FSTTypes.h
+++ b/Firestore/Source/Core/FSTTypes.h
@@ -67,8 +67,8 @@ typedef void (^FSTTransactionBlock)(FSTTransaction *transaction,
typedef NS_ENUM(NSUInteger, FSTOnlineState) {
/**
* The Firestore client is in an unknown online state. This means the client is either not
- * actively trying to establish a connection or it was previously in an unknown state and is
- * trying to establish a connection.
+ * actively trying to establish a connection or it is currently trying to establish a connection,
+ * but it has not succeeded or failed yet.
*/
FSTOnlineStateUnknown,
@@ -80,9 +80,8 @@ typedef NS_ENUM(NSUInteger, FSTOnlineState) {
FSTOnlineStateHealthy,
/**
- * The client has tried to establish a connection but has failed.
- * This state is reached after either a connection attempt failed or a healthy stream was closed
- * for unexpected reasons.
+ * The client considers itself offline. It is either trying to establish a connection but
+ * failing, or it has been explicitly marked offline via a call to `disableNetwork`.
*/
FSTOnlineStateFailed
};
diff --git a/Firestore/Source/Core/FSTView.h b/Firestore/Source/Core/FSTView.h
index ed230a3..6ff77cd 100644
--- a/Firestore/Source/Core/FSTView.h
+++ b/Firestore/Source/Core/FSTView.h
@@ -16,6 +16,7 @@
#import <Foundation/Foundation.h>
+#import "Firestore/Source/Core/FSTTypes.h"
#import "Firestore/Source/Model/FSTDocumentDictionary.h"
#import "Firestore/Source/Model/FSTDocumentKeySet.h"
@@ -138,6 +139,12 @@ typedef NS_ENUM(NSInteger, FSTLimboDocumentChangeType) {
- (FSTViewChange *)applyChangesToDocuments:(FSTViewDocumentChanges *)docChanges
targetChange:(nullable FSTTargetChange *)targetChange;
+/**
+ * Applies an FSTOnlineState change to the view, potentially generating an FSTViewChange if the
+ * view's syncState changes as a result.
+ */
+- (FSTViewChange *)applyChangedOnlineState:(FSTOnlineState)onlineState;
+
@end
NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Core/FSTView.m b/Firestore/Source/Core/FSTView.m
index 9b44bf4..78019c6 100644
--- a/Firestore/Source/Core/FSTView.m
+++ b/Firestore/Source/Core/FSTView.m
@@ -94,6 +94,12 @@ NS_ASSUME_NONNULL_BEGIN
return self.type == otherChange.type && [self.key isEqual:otherChange.key];
}
+- (NSUInteger)hash {
+ NSUInteger hash = self.type;
+ hash = hash * 31u + [self.key hash];
+ return hash;
+}
+
@end
#pragma mark - FSTViewChange
@@ -330,6 +336,24 @@ static NSComparisonResult FSTCompareDocumentViewChangeTypes(FSTDocumentViewChang
}
}
+- (FSTViewChange *)applyChangedOnlineState:(FSTOnlineState)onlineState {
+ if (self.isCurrent && onlineState == FSTOnlineStateFailed) {
+ // If we're offline, set `current` to NO and then call applyChanges to refresh our syncState
+ // and generate an FSTViewChange as appropriate. We are guaranteed to get a new FSTTargetChange
+ // that sets `current` back to YES once the client is back online.
+ self.current = NO;
+ return
+ [self applyChangesToDocuments:[[FSTViewDocumentChanges alloc]
+ initWithDocumentSet:self.documentSet
+ changeSet:[FSTDocumentViewChangeSet changeSet]
+ needsRefill:NO
+ mutatedKeys:self.mutatedKeys]];
+ } else {
+ // No effect, just return a no-op FSTViewChange.
+ return [[FSTViewChange alloc] initWithSnapshot:nil limboChanges:@[]];
+ }
+}
+
#pragma mark - Private methods
/** Returns whether the doc for the given key should be in limbo. */
diff --git a/Firestore/Source/Local/FSTDocumentReference.m b/Firestore/Source/Local/FSTDocumentReference.m
index 1631789..25a5935 100644
--- a/Firestore/Source/Local/FSTDocumentReference.m
+++ b/Firestore/Source/Local/FSTDocumentReference.m
@@ -34,7 +34,7 @@ NS_ASSUME_NONNULL_BEGIN
- (BOOL)isEqual:(id)other {
if (other == self) return YES;
- if (!other || ![[other class] isEqual:[self class]]) return NO;
+ if (![[other class] isEqual:[self class]]) return NO;
FSTDocumentReference *reference = (FSTDocumentReference *)other;
diff --git a/Firestore/Source/Model/FSTDatabaseID.m b/Firestore/Source/Model/FSTDatabaseID.m
index 4d0448a..bff5855 100644
--- a/Firestore/Source/Model/FSTDatabaseID.m
+++ b/Firestore/Source/Model/FSTDatabaseID.m
@@ -48,7 +48,7 @@ NSString *const kDefaultDatabaseID = @"(default)";
- (BOOL)isEqual:(id)other {
if (other == self) return YES;
- if (!other || ![[other class] isEqual:[self class]]) return NO;
+ if (![[other class] isEqual:[self class]]) return NO;
return [self isEqualToDatabaseId:other];
}
diff --git a/Firestore/Source/Model/FSTFieldValue.h b/Firestore/Source/Model/FSTFieldValue.h
index 6de9793..93fd5c4 100644
--- a/Firestore/Source/Model/FSTFieldValue.h
+++ b/Firestore/Source/Model/FSTFieldValue.h
@@ -22,7 +22,9 @@
@class FSTDocumentKey;
@class FSTFieldPath;
@class FSTTimestamp;
+@class FSTFieldValueOptions;
@class FIRGeoPoint;
+@class FIRSnapshotOptions;
NS_ASSUME_NONNULL_BEGIN
@@ -40,6 +42,32 @@ typedef NS_ENUM(NSInteger, FSTTypeOrder) {
FSTTypeOrderObject,
};
+/** Defines the return value for pending server timestamps. */
+typedef NS_ENUM(NSInteger, FSTServerTimestampBehavior) {
+ FSTServerTimestampBehaviorNone,
+ FSTServerTimestampBehaviorEstimate,
+ FSTServerTimestampBehaviorPrevious
+};
+
+/** Holds properties that define field value deserialization options. */
+@interface FSTFieldValueOptions : NSObject
+
+@property(nonatomic, readonly, assign) FSTServerTimestampBehavior serverTimestampBehavior;
+
+- (instancetype)init NS_UNAVAILABLE;
+
+/**
+ * Creates an FSTFieldValueOptions instance that specifies deserialization behavior for pending
+ * server timestamps.
+ */
+- (instancetype)initWithServerTimestampBehavior:(FSTServerTimestampBehavior)serverTimestampBehavior
+ NS_DESIGNATED_INITIALIZER;
+
+/** Creates an FSTFieldValueOption instance from FIRSnapshotOptions. */
++ (instancetype)optionsForSnapshotOptions:(FIRSnapshotOptions *)value;
+
+@end
+
/**
* Abstract base class representing an immutable data value as stored in Firestore. FSTFieldValue
* represents all the different kinds of values that can be stored in fields in a document.
@@ -58,7 +86,7 @@ typedef NS_ENUM(NSInteger, FSTTypeOrder) {
* - Array
* - Object
*/
-@interface FSTFieldValue : NSObject
+@interface FSTFieldValue <__covariant T> : NSObject
/** Returns the FSTTypeOrder for this value. */
- (FSTTypeOrder)typeOrder;
@@ -69,7 +97,15 @@ typedef NS_ENUM(NSInteger, FSTTypeOrder) {
* TODO(mikelehen): This conversion should probably happen at the API level and right now `value` is
* used inappropriately in the serializer implementation, etc. We need to do some reworking.
*/
-- (id)value;
+- (T)value;
+
+/**
+ * Converts an FSTFieldValue into the value that users will see in document snapshots.
+ *
+ * Options can be provided to configure the deserialization of some field values (such as server
+ * timestamps).
+ */
+- (T)valueWithOptions:(FSTFieldValueOptions *)options;
/** Compares against another FSTFieldValue. */
- (NSComparisonResult)compare:(FSTFieldValue *)other;
@@ -79,26 +115,24 @@ typedef NS_ENUM(NSInteger, FSTTypeOrder) {
/**
* A null value stored in Firestore. The |value| of a FSTNullValue is [NSNull null].
*/
-@interface FSTNullValue : FSTFieldValue
+@interface FSTNullValue : FSTFieldValue <NSNull *>
+ (instancetype)nullValue;
-- (id)value;
@end
/**
* A boolean value stored in Firestore.
*/
-@interface FSTBooleanValue : FSTFieldValue
+@interface FSTBooleanValue : FSTFieldValue <NSNumber *>
+ (instancetype)trueValue;
+ (instancetype)falseValue;
+ (instancetype)booleanValue:(BOOL)value;
-- (NSNumber *)value;
@end
/**
* Base class inherited from by FSTIntegerValue and FSTDoubleValue. It implements proper number
* comparisons between the two types.
*/
-@interface FSTNumberValue : FSTFieldValue
+@interface FSTNumberValue : FSTFieldValue <NSNumber *>
@end
/**
@@ -106,7 +140,6 @@ typedef NS_ENUM(NSInteger, FSTTypeOrder) {
*/
@interface FSTIntegerValue : FSTNumberValue
+ (instancetype)integerValue:(int64_t)value;
-- (NSNumber *)value;
- (int64_t)internalValue;
@end
@@ -116,24 +149,21 @@ typedef NS_ENUM(NSInteger, FSTTypeOrder) {
@interface FSTDoubleValue : FSTNumberValue
+ (instancetype)doubleValue:(double)value;
+ (instancetype)nanValue;
-- (NSNumber *)value;
- (double)internalValue;
@end
/**
* A string stored in Firestore.
*/
-@interface FSTStringValue : FSTFieldValue
+@interface FSTStringValue : FSTFieldValue <NSString *>
+ (instancetype)stringValue:(NSString *)value;
-- (NSString *)value;
@end
/**
* A timestamp value stored in Firestore.
*/
-@interface FSTTimestampValue : FSTFieldValue
+@interface FSTTimestampValue : FSTFieldValue <NSDate *>
+ (instancetype)timestampValue:(FSTTimestamp *)value;
-- (NSDate *)value;
- (FSTTimestamp *)internalValue;
@end
@@ -144,46 +174,54 @@ typedef NS_ENUM(NSInteger, FSTTypeOrder) {
* - FSTServerTimestampValue instances are created as the result of applying an FSTTransformMutation
* (see [FSTTransformMutation applyTo]). They can only exist in the local view of a document.
* Therefore they do not need to be parsed or serialized.
- * - When evaluated locally (e.g. via FSTDocumentSnapshot data), they evaluate to NSNull (at least
- * for now, see b/62064202).
+ * - When evaluated locally (e.g. via FSTDocumentSnapshot data), they by default evaluate to NSNull.
+ * This behavior can be configured by passing custom FSTFieldValueOptions to `valueWithOptions:`.
* - They sort after all FSTTimestampValues. With respect to other FSTServerTimestampValues, they
* sort by their localWriteTime.
*/
-@interface FSTServerTimestampValue : FSTFieldValue
-+ (instancetype)serverTimestampValueWithLocalWriteTime:(FSTTimestamp *)localWriteTime;
-- (NSNull *)value;
+@interface FSTServerTimestampValue : FSTFieldValue <id>
++ (instancetype)serverTimestampValueWithLocalWriteTime:(FSTTimestamp *)localWriteTime
+ previousValue:(nullable FSTFieldValue *)previousValue;
+
@property(nonatomic, strong, readonly) FSTTimestamp *localWriteTime;
+@property(nonatomic, strong, readonly, nullable) FSTFieldValue *previousValue;
+
@end
/**
* A geo point value stored in Firestore.
*/
-@interface FSTGeoPointValue : FSTFieldValue
+@interface FSTGeoPointValue : FSTFieldValue <FIRGeoPoint *>
+ (instancetype)geoPointValue:(FIRGeoPoint *)value;
-- (FIRGeoPoint *)value;
+- (FIRGeoPoint *)valueWithOptions:(FSTFieldValueOptions *)options;
@end
/**
* A blob value stored in Firestore.
*/
-@interface FSTBlobValue : FSTFieldValue
+@interface FSTBlobValue : FSTFieldValue <NSData *>
+ (instancetype)blobValue:(NSData *)value;
-- (NSData *)value;
+- (NSData *)valueWithOptions:(FSTFieldValueOptions *)options;
@end
/**
* A reference value stored in Firestore.
*/
-@interface FSTReferenceValue : FSTFieldValue
+@interface FSTReferenceValue : FSTFieldValue <FSTDocumentKey *>
+ (instancetype)referenceValue:(FSTDocumentKey *)value databaseID:(FSTDatabaseID *)databaseID;
-- (FSTDocumentKey *)value;
+- (FSTDocumentKey *)valueWithOptions:(FSTFieldValueOptions *)options;
@property(nonatomic, strong, readonly) FSTDatabaseID *databaseID;
@end
/**
* A structured object value stored in Firestore.
*/
-@interface FSTObjectValue : FSTFieldValue
+// clang-format off
+@interface FSTObjectValue : FSTFieldValue < NSDictionary<NSString *, id> * >
+
+- (instancetype)init NS_UNAVAILABLE;
+// clang-format on
+
/** Returns an empty FSTObjectValue. */
+ (instancetype)objectValue;
@@ -198,9 +236,7 @@ typedef NS_ENUM(NSInteger, FSTTypeOrder) {
- (instancetype)initWithImmutableDictionary:
(FSTImmutableSortedDictionary<NSString *, FSTFieldValue *> *)value NS_DESIGNATED_INITIALIZER;
-- (instancetype)init NS_UNAVAILABLE;
-
-- (NSDictionary<NSString *, id> *)value;
+- (NSDictionary<NSString *, id> *)valueWithOptions:(FSTFieldValueOptions *)options;
- (FSTImmutableSortedDictionary<NSString *, FSTFieldValue *> *)internalValue;
/** Returns the value at the given path if it exists. Returns nil otherwise. */
@@ -222,19 +258,20 @@ typedef NS_ENUM(NSInteger, FSTTypeOrder) {
/**
* An array value stored in Firestore.
*/
-@interface FSTArrayValue : FSTFieldValue
+// clang-format off
+@interface FSTArrayValue : FSTFieldValue < NSArray <id> * >
+
+- (instancetype)init NS_UNAVAILABLE;
+// clang-format on
/**
* Initializes this instance with the given array of wrapped values.
*
* @param value An immutable array of FSTFieldValue objects. Caller is responsible for copying the
- * value or releasing all references.
+ * value or releasing all references.
*/
- (instancetype)initWithValueNoCopy:(NSArray<FSTFieldValue *> *)value NS_DESIGNATED_INITIALIZER;
-- (instancetype)init NS_UNAVAILABLE;
-
-- (NSArray<id> *)value;
- (NSArray<FSTFieldValue *> *)internalValue;
@end
diff --git a/Firestore/Source/Model/FSTFieldValue.m b/Firestore/Source/Model/FSTFieldValue.m
index 95ad306..a6326a7 100644
--- a/Firestore/Source/Model/FSTFieldValue.m
+++ b/Firestore/Source/Model/FSTFieldValue.m
@@ -17,6 +17,7 @@
#import "Firestore/Source/Model/FSTFieldValue.h"
#import "Firestore/Source/API/FIRGeoPoint+Internal.h"
+#import "Firestore/Source/API/FIRSnapshotOptions+Internal.h"
#import "Firestore/Source/Core/FSTTimestamp.h"
#import "Firestore/Source/Model/FSTDatabaseID.h"
#import "Firestore/Source/Model/FSTDocumentKey.h"
@@ -27,6 +28,38 @@
NS_ASSUME_NONNULL_BEGIN
+#pragma mark - FSTFieldValueOptions
+
+@implementation FSTFieldValueOptions
+
++ (instancetype)optionsForSnapshotOptions:(FIRSnapshotOptions *)options {
+ if (options.serverTimestampBehavior == FSTServerTimestampBehaviorNone) {
+ static FSTFieldValueOptions *defaultInstance = nil;
+ static dispatch_once_t onceToken;
+
+ dispatch_once(&onceToken, ^{
+ defaultInstance = [[FSTFieldValueOptions alloc]
+ initWithServerTimestampBehavior:FSTServerTimestampBehaviorNone];
+ });
+ return defaultInstance;
+ } else {
+ return [[FSTFieldValueOptions alloc]
+ initWithServerTimestampBehavior:options.serverTimestampBehavior];
+ }
+}
+
+- (instancetype)initWithServerTimestampBehavior:
+ (FSTServerTimestampBehavior)serverTimestampBehavior {
+ self = [super init];
+
+ if (self) {
+ _serverTimestampBehavior = serverTimestampBehavior;
+ }
+ return self;
+}
+
+@end
+
#pragma mark - FSTFieldValue
@interface FSTFieldValue ()
@@ -40,6 +73,11 @@ NS_ASSUME_NONNULL_BEGIN
}
- (id)value {
+ return [self valueWithOptions:[FSTFieldValueOptions
+ optionsForSnapshotOptions:[FIRSnapshotOptions defaultOptions]]];
+}
+
+- (id)valueWithOptions:(FSTFieldValueOptions *)options {
@throw FSTAbstractMethodException(); // NOLINT
}
@@ -89,7 +127,7 @@ NS_ASSUME_NONNULL_BEGIN
return FSTTypeOrderNull;
}
-- (id)value {
+- (id)valueWithOptions:(FSTFieldValueOptions *)options {
return [NSNull null];
}
@@ -155,7 +193,7 @@ NS_ASSUME_NONNULL_BEGIN
return FSTTypeOrderBoolean;
}
-- (id)value {
+- (id)valueWithOptions:(FSTFieldValueOptions *)options {
return self.internalValue ? @YES : @NO;
}
@@ -233,7 +271,7 @@ NS_ASSUME_NONNULL_BEGIN
return self;
}
-- (id)value {
+- (id)valueWithOptions:(FSTFieldValueOptions *)options {
return @(self.internalValue);
}
@@ -285,7 +323,7 @@ NS_ASSUME_NONNULL_BEGIN
return self;
}
-- (id)value {
+- (id)valueWithOptions:(FSTFieldValueOptions *)options {
return @(self.internalValue);
}
@@ -332,7 +370,7 @@ NS_ASSUME_NONNULL_BEGIN
return FSTTypeOrderString;
}
-- (id)value {
+- (id)valueWithOptions:(FSTFieldValueOptions *)options {
return self.internalValue;
}
@@ -379,7 +417,7 @@ NS_ASSUME_NONNULL_BEGIN
return FSTTypeOrderTimestamp;
}
-- (id)value {
+- (id)valueWithOptions:(FSTFieldValueOptions *)options {
// For developers, we expose Timestamps as Dates.
return self.internalValue.approximateDateValue;
}
@@ -410,14 +448,18 @@ NS_ASSUME_NONNULL_BEGIN
@implementation FSTServerTimestampValue
-+ (instancetype)serverTimestampValueWithLocalWriteTime:(FSTTimestamp *)localWriteTime {
- return [[FSTServerTimestampValue alloc] initWithLocalWriteTime:localWriteTime];
++ (instancetype)serverTimestampValueWithLocalWriteTime:(FSTTimestamp *)localWriteTime
+ previousValue:(nullable FSTFieldValue *)previousValue {
+ return [[FSTServerTimestampValue alloc] initWithLocalWriteTime:localWriteTime
+ previousValue:previousValue];
}
-- (id)initWithLocalWriteTime:(FSTTimestamp *)localWriteTime {
+- (id)initWithLocalWriteTime:(FSTTimestamp *)localWriteTime
+ previousValue:(nullable FSTFieldValue *)previousValue {
self = [super init];
if (self) {
_localWriteTime = localWriteTime;
+ _previousValue = previousValue;
}
return self;
}
@@ -426,9 +468,17 @@ NS_ASSUME_NONNULL_BEGIN
return FSTTypeOrderTimestamp;
}
-- (NSNull *)value {
- // For developers, server timestamps always evaluate to NSNull (for now, at least; b/62064202).
- return [NSNull null];
+- (id)valueWithOptions:(FSTFieldValueOptions *)options {
+ switch (options.serverTimestampBehavior) {
+ case FSTServerTimestampBehaviorNone:
+ return [NSNull null];
+ case FSTServerTimestampBehaviorEstimate:
+ return [self.localWriteTime approximateDateValue];
+ case FSTServerTimestampBehaviorPrevious:
+ return self.previousValue ? [self.previousValue valueWithOptions:options] : [NSNull null];
+ default:
+ FSTFail(@"Unexpected server timestamp option: %d", (int)options.serverTimestampBehavior);
+ }
}
- (BOOL)isEqual:(id)other {
@@ -481,7 +531,7 @@ NS_ASSUME_NONNULL_BEGIN
return FSTTypeOrderGeoPoint;
}
-- (id)value {
+- (id)valueWithOptions:(FSTFieldValueOptions *)options {
return self.internalValue;
}
@@ -529,7 +579,7 @@ NS_ASSUME_NONNULL_BEGIN
return FSTTypeOrderBlob;
}
-- (id)value {
+- (id)valueWithOptions:(FSTFieldValueOptions *)options {
return self.internalValue;
}
@@ -573,7 +623,7 @@ NS_ASSUME_NONNULL_BEGIN
return self;
}
-- (id)value {
+- (id)valueWithOptions:(FSTFieldValueOptions *)options {
return self.key;
}
@@ -648,11 +698,11 @@ NS_ASSUME_NONNULL_BEGIN
return [self initWithImmutableDictionary:dictionary];
}
-- (id)value {
+- (id)valueWithOptions:(FSTFieldValueOptions *)options {
NSMutableDictionary *result = [NSMutableDictionary dictionary];
[self.internalValue
enumerateKeysAndObjectsUsingBlock:^(NSString *key, FSTFieldValue *obj, BOOL *stop) {
- result[key] = [obj value];
+ result[key] = [obj valueWithOptions:options];
}];
return result;
}
@@ -803,7 +853,7 @@ NS_ASSUME_NONNULL_BEGIN
return [self.internalValue hash];
}
-- (id)value {
+- (id)valueWithOptions:(FSTFieldValueOptions *)options {
NSMutableArray *result = [NSMutableArray arrayWithCapacity:_internalValue.count];
[self.internalValue enumerateObjectsUsingBlock:^(FSTFieldValue *obj, NSUInteger idx, BOOL *stop) {
[result addObject:[obj value]];
diff --git a/Firestore/Source/Model/FSTMutation.h b/Firestore/Source/Model/FSTMutation.h
index ef7f1c8..7c5f6de 100644
--- a/Firestore/Source/Model/FSTMutation.h
+++ b/Firestore/Source/Model/FSTMutation.h
@@ -158,8 +158,10 @@ typedef NS_ENUM(NSUInteger, FSTPreconditionExists) {
* Applies this mutation to the given FSTDocument, FSTDeletedDocument or nil, if we don't have
* information about this document. Both the input and returned documents can be nil.
*
- * @param maybeDoc The document to mutate. The input document should nil if it does not currently
- * exist.
+ * @param maybeDoc The current state of the document to mutate. The input document should be nil if
+ * it does not currently exist.
+ * @param baseDoc The state of the document prior to this mutation batch. The input document should
+ * be nil if it the document did not exist.
* @param localWriteTime A timestamp indicating the local write time of the batch this mutation is
* a part of.
* @param mutationResult Optional result info from the backend. If omitted, it's assumed that
@@ -196,16 +198,18 @@ typedef NS_ENUM(NSUInteger, FSTPreconditionExists) {
* apply the transform if the prior mutation resulted in an FSTDocument (always true for an
* FSTSetMutation, but not necessarily for an FSTPatchMutation).
*/
-- (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc
+- (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc
+ baseDocument:(nullable FSTMaybeDocument *)baseDoc
localWriteTime:(FSTTimestamp *)localWriteTime
- mutationResult:(FSTMutationResult *_Nullable)mutationResult;
+ mutationResult:(nullable FSTMutationResult *)mutationResult;
/**
* A helper version of applyTo for applying mutations locally (without a mutation result from the
* backend).
*/
-- (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc
- localWriteTime:(FSTTimestamp *)localWriteTime;
+- (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc
+ baseDocument:(nullable FSTMaybeDocument *)baseDoc
+ localWriteTime:(nullable FSTTimestamp *)localWriteTime;
@property(nonatomic, strong, readonly) FSTDocumentKey *key;
diff --git a/Firestore/Source/Model/FSTMutation.m b/Firestore/Source/Model/FSTMutation.m
index 5b47280..c249138 100644
--- a/Firestore/Source/Model/FSTMutation.m
+++ b/Firestore/Source/Model/FSTMutation.m
@@ -97,7 +97,7 @@ NS_ASSUME_NONNULL_BEGIN
- (BOOL)isEqual:(id)other {
if (other == self) return YES;
- if (!other || ![[other class] isEqual:[self class]]) return NO;
+ if (![[other class] isEqual:[self class]]) return NO;
FSTFieldTransform *otherFieldTransform = other;
return [self.path isEqual:otherFieldTransform.path] &&
[self.transform isEqual:otherFieldTransform.transform];
@@ -236,15 +236,18 @@ NS_ASSUME_NONNULL_BEGIN
return self;
}
-- (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc
+- (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc
+ baseDocument:(nullable FSTMaybeDocument *)baseDoc
localWriteTime:(FSTTimestamp *)localWriteTime
- mutationResult:(FSTMutationResult *_Nullable)mutationResult {
+ mutationResult:(nullable FSTMutationResult *)mutationResult {
@throw FSTAbstractMethodException(); // NOLINT
}
-- (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc
- localWriteTime:(FSTTimestamp *)localWriteTime {
- return [self applyTo:maybeDoc localWriteTime:localWriteTime mutationResult:nil];
+- (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc
+ baseDocument:(nullable FSTMaybeDocument *)baseDoc
+ localWriteTime:(nullable FSTTimestamp *)localWriteTime {
+ return
+ [self applyTo:maybeDoc baseDocument:baseDoc localWriteTime:localWriteTime mutationResult:nil];
}
@end
@@ -287,9 +290,10 @@ NS_ASSUME_NONNULL_BEGIN
return result;
}
-- (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc
+- (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc
+ baseDocument:(nullable FSTMaybeDocument *)baseDoc
localWriteTime:(FSTTimestamp *)localWriteTime
- mutationResult:(FSTMutationResult *_Nullable)mutationResult {
+ mutationResult:(nullable FSTMutationResult *)mutationResult {
if (mutationResult) {
FSTAssert(!mutationResult.transformResults, @"Transform results received by FSTSetMutation.");
}
@@ -362,9 +366,10 @@ NS_ASSUME_NONNULL_BEGIN
self.key, self.fieldMask, self.value, self.precondition];
}
-- (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc
+- (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc
+ baseDocument:(nullable FSTMaybeDocument *)baseDoc
localWriteTime:(FSTTimestamp *)localWriteTime
- mutationResult:(FSTMutationResult *_Nullable)mutationResult {
+ mutationResult:(nullable FSTMutationResult *)mutationResult {
if (mutationResult) {
FSTAssert(!mutationResult.transformResults, @"Transform results received by FSTPatchMutation.");
}
@@ -451,9 +456,10 @@ NS_ASSUME_NONNULL_BEGIN
self.key, self.fieldTransforms, self.precondition];
}
-- (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc
+- (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc
+ baseDocument:(nullable FSTMaybeDocument *)baseDoc
localWriteTime:(FSTTimestamp *)localWriteTime
- mutationResult:(FSTMutationResult *_Nullable)mutationResult {
+ mutationResult:(nullable FSTMutationResult *)mutationResult {
if (mutationResult) {
FSTAssert(mutationResult.transformResults,
@"Transform results missing for FSTTransformMutation.");
@@ -473,8 +479,9 @@ NS_ASSUME_NONNULL_BEGIN
BOOL hasLocalMutations = (mutationResult == nil);
NSArray<FSTFieldValue *> *transformResults =
- mutationResult ? mutationResult.transformResults
- : [self localTransformResultsWithWriteTime:localWriteTime];
+ mutationResult
+ ? mutationResult.transformResults
+ : [self localTransformResultsWithBaseDocument:baseDoc writeTime:localWriteTime];
FSTObjectValue *newData = [self transformObject:doc.data transformResults:transformResults];
return [FSTDocument documentWithData:newData
key:doc.key
@@ -486,16 +493,26 @@ NS_ASSUME_NONNULL_BEGIN
* Creates an array of "transform results" (a transform result is a field value representing the
* result of applying a transform) for use when applying an FSTTransformMutation locally.
*
+ * @param baseDocument The document prior to applying this mutation batch.
* @param localWriteTime The local time of the transform mutation (used to generate
* FSTServerTimestampValues).
* @return The transform results array.
*/
-- (NSArray<FSTFieldValue *> *)localTransformResultsWithWriteTime:(FSTTimestamp *)localWriteTime {
+- (NSArray<FSTFieldValue *> *)localTransformResultsWithBaseDocument:
+ (FSTMaybeDocument *_Nullable)baseDocument
+ writeTime:(FSTTimestamp *)localWriteTime {
NSMutableArray<FSTFieldValue *> *transformResults = [NSMutableArray array];
for (FSTFieldTransform *fieldTransform in self.fieldTransforms) {
if ([fieldTransform.transform isKindOfClass:[FSTServerTimestampTransform class]]) {
- [transformResults addObject:[FSTServerTimestampValue
- serverTimestampValueWithLocalWriteTime:localWriteTime]];
+ FSTFieldValue *previousValue = nil;
+
+ if ([baseDocument isMemberOfClass:[FSTDocument class]]) {
+ previousValue = [((FSTDocument *)baseDocument) fieldForPath:fieldTransform.path];
+ }
+
+ [transformResults
+ addObject:[FSTServerTimestampValue serverTimestampValueWithLocalWriteTime:localWriteTime
+ previousValue:previousValue]];
} else {
FSTFail(@"Encountered unknown transform: %@", fieldTransform);
}
@@ -551,9 +568,10 @@ NS_ASSUME_NONNULL_BEGIN
stringWithFormat:@"<FSTDeleteMutation key=%@ precondition=%@>", self.key, self.precondition];
}
-- (FSTMaybeDocument *_Nullable)applyTo:(FSTMaybeDocument *_Nullable)maybeDoc
+- (nullable FSTMaybeDocument *)applyTo:(nullable FSTMaybeDocument *)maybeDoc
+ baseDocument:(nullable FSTMaybeDocument *)baseDoc
localWriteTime:(FSTTimestamp *)localWriteTime
- mutationResult:(FSTMutationResult *_Nullable)mutationResult {
+ mutationResult:(nullable FSTMutationResult *)mutationResult {
if (mutationResult) {
FSTAssert(!mutationResult.transformResults,
@"Transform results received by FSTDeleteMutation.");
diff --git a/Firestore/Source/Model/FSTMutationBatch.m b/Firestore/Source/Model/FSTMutationBatch.m
index 3677908..01adca7 100644
--- a/Firestore/Source/Model/FSTMutationBatch.m
+++ b/Firestore/Source/Model/FSTMutationBatch.m
@@ -71,6 +71,7 @@ const FSTBatchID kFSTBatchIDUnknown = -1;
mutationBatchResult:(FSTMutationBatchResult *_Nullable)mutationBatchResult {
FSTAssert(!maybeDoc || [maybeDoc.key isEqualToKey:documentKey],
@"applyTo: key %@ doesn't match maybeDoc key %@", documentKey, maybeDoc.key);
+ FSTMaybeDocument *baseDoc = maybeDoc;
if (mutationBatchResult) {
FSTAssert(mutationBatchResult.mutationResults.count == self.mutations.count,
@"Mismatch between mutations length (%lu) and results length (%lu)",
@@ -83,6 +84,7 @@ const FSTBatchID kFSTBatchIDUnknown = -1;
FSTMutationResult *_Nullable mutationResult = mutationBatchResult.mutationResults[i];
if ([mutation.key isEqualToKey:documentKey]) {
maybeDoc = [mutation applyTo:maybeDoc
+ baseDocument:baseDoc
localWriteTime:self.localWriteTime
mutationResult:mutationResult];
}
diff --git a/Firestore/Source/Public/FIRDocumentChange.h b/Firestore/Source/Public/FIRDocumentChange.h
index 022c81b..4717067 100644
--- a/Firestore/Source/Public/FIRDocumentChange.h
+++ b/Firestore/Source/Public/FIRDocumentChange.h
@@ -18,7 +18,7 @@
NS_ASSUME_NONNULL_BEGIN
-@class FIRDocumentSnapshot;
+@class FIRQueryDocumentSnapshot;
/** An enumeration of document change types. */
typedef NS_ENUM(NSInteger, FIRDocumentChangeType) {
@@ -47,7 +47,7 @@ NS_SWIFT_NAME(DocumentChange)
@property(nonatomic, readonly) FIRDocumentChangeType type;
/** The document affected by this change. */
-@property(nonatomic, strong, readonly) FIRDocumentSnapshot *document;
+@property(nonatomic, strong, readonly) FIRQueryDocumentSnapshot *document;
/**
* The index of the changed document in the result set immediately prior to this FIRDocumentChange
diff --git a/Firestore/Source/Public/FIRDocumentReference.h b/Firestore/Source/Public/FIRDocumentReference.h
index 439e727..7fcc7a8 100644
--- a/Firestore/Source/Public/FIRDocumentReference.h
+++ b/Firestore/Source/Public/FIRDocumentReference.h
@@ -36,8 +36,6 @@ NS_SWIFT_NAME(DocumentListenOptions)
- (instancetype)init;
-@property(nonatomic, assign, readonly) BOOL includeMetadataChanges;
-
/**
* Sets the includeMetadataChanges option which controls whether metadata-only changes (i.e. only
* `FIRDocumentSnapshot.metadata` changed) should trigger snapshot events. Default is NO.
diff --git a/Firestore/Source/Public/FIRDocumentSnapshot.h b/Firestore/Source/Public/FIRDocumentSnapshot.h
index 3e67c25..6e79a7f 100644
--- a/Firestore/Source/Public/FIRDocumentSnapshot.h
+++ b/Firestore/Source/Public/FIRDocumentSnapshot.h
@@ -22,9 +22,61 @@
NS_ASSUME_NONNULL_BEGIN
/**
+ * Controls the return value for server timestamps that have not yet been set to
+ * their final value.
+ */
+typedef NS_ENUM(NSInteger, FIRServerTimestampBehavior) {
+ /**
+ * Return `NSNull` for `FieldValue.serverTimestamp()` fields that have not yet
+ * been set to their final value.
+ */
+ FIRServerTimestampBehaviorNone,
+
+ /**
+ * Return a local estimates for `FieldValue.serverTimestamp()`
+ * fields that have not yet been set to their final value. This estimate will
+ * likely differ from the final value and may cause these pending values to
+ * change once the server result becomes available.
+ */
+ FIRServerTimestampBehaviorEstimate,
+
+ /**
+ * Return the previous value for `FieldValue.serverTimestamp()` fields that
+ * have not yet been set to their final value.
+ */
+ FIRServerTimestampBehaviorPrevious
+} NS_SWIFT_NAME(ServerTimestampBehavior);
+
+/**
+ * Options that configure how data is retrieved from a `DocumentSnapshot`
+ * (e.g. the desired behavior for server timestamps that have not yet been set
+ * to their final value).
+ */
+NS_SWIFT_NAME(SnapshotOptions)
+@interface FIRSnapshotOptions : NSObject
+
+/** */
+- (instancetype)init __attribute__((unavailable("FIRSnapshotOptions cannot be created directly.")));
+
+/**
+ * If set, controls the return value for `FieldValue.serverTimestamp()`
+ * fields that have not yet been set to their final value.
+ *
+ * If omitted, `NSNull` will be returned by default.
+ *
+ * @return The created `FIRSnapshotOptions` object.
+ */
++ (instancetype)serverTimestampBehavior:(FIRServerTimestampBehavior)serverTimestampBehavior;
+
+@end
+
+/**
* A `FIRDocumentSnapshot` contains data read from a document in your Firestore database. The data
* can be extracted with the `data` property or by using subscript syntax to access a specific
* field.
+ *
+ * For a `FIRDocumentSnapshot` that points to a non-existing document, any data access will return
+ * `nil`. You can use the `exists` property to explicitly verify a documents existence.
*/
NS_SWIFT_NAME(DocumentSnapshot)
@interface FIRDocumentSnapshot : NSObject
@@ -46,21 +98,104 @@ NS_SWIFT_NAME(DocumentSnapshot)
@property(nonatomic, strong, readonly) FIRSnapshotMetadata *metadata;
/**
- * Retrieves all fields in the document as an `NSDictionary`.
+ * Retrieves all fields in the document as an `NSDictionary`. Returns `nil` if the document doesn't
+ * exist.
*
- * @return An `NSDictionary` containing all fields in the document.
+ * Server-provided timestamps that have not yet been set to their final value will be returned as
+ * `NSNull`. You can use `dataWithOptions()` to configure this behavior.
+ *
+ * @return An `NSDictionary` containing all fields in the document or `nil` if the document doesn't
+ * exist.
*/
-- (NSDictionary<NSString *, id> *)data;
+- (nullable NSDictionary<NSString *, id> *)data;
+
+/**
+ * Retrieves all fields in the document as a `Dictionary`. Returns `nil` if the document doesn't
+ * exist.
+ *
+ * @param options `SnapshotOptions` to configure how data is returned from the snapshot (e.g. the
+ * desired behavior for server timestamps that have not yet been set to their final value).
+ * @return A `Dictionary` containing all fields in the document or `nil` if the document doesn't
+ * exist.
+ */
+- (nullable NSDictionary<NSString *, id> *)dataWithOptions:(FIRSnapshotOptions *)options;
+
+/**
+ * Retrieves a specific field from the document. Returns `nil` if the document or the field doesn't
+ * exist.
+ *
+ * The timestamps that have not yet been set to their final value will be returned as `NSNull`. The
+ * can use `get(_:options:)` to configure this behavior.
+ *
+ * @param field The field to retrieve.
+ * @return The value contained in the field or `nil` if the document or field doesn't exist.
+ */
+- (nullable id)valueForField:(id)field NS_SWIFT_NAME(get(_:));
+
+/**
+ * Retrieves a specific field from the document. Returns `nil` if the document or the field doesn't
+ * exist.
+ *
+ * The timestamps that have not yet been set to their final value will be returned as `NSNull`. The
+ * can use `get(_:options:)` to configure this behavior.
+ *
+ * @param field The field to retrieve.
+ * @param options `SnapshotOptions` to configure how data is returned from the snapshot (e.g. the
+ * desired behavior for server timestamps that have not yet been set to their final value).
+ * @return The value contained in the field or `nil` if the document or field doesn't exist.
+ */
+// clang-format off
+- (nullable id)valueForField:(id)field
+ options:(FIRSnapshotOptions *)options
+ NS_SWIFT_NAME(get(_:options:));
+// clang-format on
/**
* Retrieves a specific field from the document.
*
* @param key The field to retrieve.
*
- * @return The value contained in the field or `nil` if the field doesn't exist.
+ * @return The value contained in the field or `nil` if the document or field doesn't exist.
*/
- (nullable id)objectForKeyedSubscript:(id)key;
@end
+/**
+ * A `FIRQueryDocumentSnapshot` contains data read from a document in your Firestore database as
+ * part of a query. The document is guaranteed to exist and its data can be extracted with the
+ * `data` property or by using subscript syntax to access a specific field.
+ *
+ * A `FIRQueryDocumentSnapshot` offers the same API surface as a `FIRDocumentSnapshot`. As
+ * deleted documents are not returned from queries, its `exists` property will always be true and
+ * `data:` will never return `nil`.
+ */
+NS_SWIFT_NAME(QueryDocumentSnapshot)
+@interface FIRQueryDocumentSnapshot : FIRDocumentSnapshot
+
+/** */
+- (instancetype)init
+ __attribute__((unavailable("FIRQueryDocumentSnapshot cannot be created directly.")));
+
+/**
+ * Retrieves all fields in the document as an `NSDictionary`.
+ *
+ * Server-provided timestamps that have not yet been set to their final value will be returned as
+ * `NSNull`. You can use `dataWithOptions()` to configure this behavior.
+ *
+ * @return An `NSDictionary` containing all fields in the document.
+ */
+- (NSDictionary<NSString *, id> *)data;
+
+/**
+ * Retrieves all fields in the document as a `Dictionary`.
+ *
+ * @param options `SnapshotOptions` to configure how data is returned from the snapshot (e.g. the
+ * desired behavior for server timestamps that have not yet been set to their final value).
+ * @return A `Dictionary` containing all fields in the document.
+ */
+- (NSDictionary<NSString *, id> *)dataWithOptions:(FIRSnapshotOptions *)options;
+
+@end
+
NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Public/FIRFirestore.h b/Firestore/Source/Public/FIRFirestore.h
index 91a96a5..4c85aba 100644
--- a/Firestore/Source/Public/FIRFirestore.h
+++ b/Firestore/Source/Public/FIRFirestore.h
@@ -139,6 +139,23 @@ NS_SWIFT_NAME(Firestore)
+ (void)enableLogging:(BOOL)logging
DEPRECATED_MSG_ATTRIBUTE("Use FIRSetLoggerLevel(FIRLoggerLevelDebug) to enable logging");
+#pragma mark - Network
+
+/**
+ * Re-enables usage of the network by this Firestore instance after a prior call to
+ * `disableNetworkWithCompletion`. Completion block, if provided, will be called once network uasge
+ * has been enabled.
+ */
+- (void)enableNetworkWithCompletion:(nullable void (^)(NSError *_Nullable error))completion;
+
+/**
+ * Disables usage of the network by this Firestore instance. It can be re-enabled by via
+ * `enableNetworkWithCompletion`. While the network is disabled, any snapshot listeners or get calls
+ * will return results from cache and any write operations will be queued until the network is
+ * restored. The completion block, if provided, will be called once network usage has been disabled.
+ */
+- (void)disableNetworkWithCompletion:(nullable void (^)(NSError *_Nullable error))completion;
+
@end
NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Public/FIRQuery.h b/Firestore/Source/Public/FIRQuery.h
index 0f3aeed..ff15ac6 100644
--- a/Firestore/Source/Public/FIRQuery.h
+++ b/Firestore/Source/Public/FIRQuery.h
@@ -256,6 +256,19 @@ NS_SWIFT_NAME(Query)
isGreaterThanOrEqualTo:(id)value NS_SWIFT_NAME(whereField(_:isGreaterThanOrEqualTo:));
// clang-format on
+/**
+ * Creates and returns a new `FIRQuery` with the additional filter that documents must
+ * satisfy the specified predicate.
+ *
+ * @param predicate The predicate the document must satisfy. Can be either comparison
+ * or compound of comparison. In particular, block-based predicate is not supported.
+ *
+ * @return The created `FIRQuery`.
+ */
+// clang-format off
+- (FIRQuery *)queryFilteredUsingPredicate:(NSPredicate *)predicate NS_SWIFT_NAME(filter(using:));
+// clang-format on
+
#pragma mark - Sorting Data
/**
* Creates and returns a new `FIRQuery` that's additionally sorted by the specified field.
diff --git a/Firestore/Source/Public/FIRQuerySnapshot.h b/Firestore/Source/Public/FIRQuerySnapshot.h
index c49a07a..1266832 100644
--- a/Firestore/Source/Public/FIRQuerySnapshot.h
+++ b/Firestore/Source/Public/FIRQuerySnapshot.h
@@ -19,8 +19,8 @@
NS_ASSUME_NONNULL_BEGIN
@class FIRDocumentChange;
-@class FIRDocumentSnapshot;
@class FIRQuery;
+@class FIRQueryDocumentSnapshot;
@class FIRSnapshotMetadata;
/**
@@ -50,7 +50,7 @@ NS_SWIFT_NAME(QuerySnapshot)
@property(nonatomic, readonly) NSInteger count;
/** An Array of the `FIRDocumentSnapshots` that make up this document set. */
-@property(nonatomic, strong, readonly) NSArray<FIRDocumentSnapshot *> *documents;
+@property(nonatomic, strong, readonly) NSArray<FIRQueryDocumentSnapshot *> *documents;
/**
* An array of the documents that changed since the last snapshot. If this is the first snapshot,
diff --git a/Firestore/Source/Public/FIRWriteBatch.h b/Firestore/Source/Public/FIRWriteBatch.h
index 5f0034c..8ff1bec 100644
--- a/Firestore/Source/Public/FIRWriteBatch.h
+++ b/Firestore/Source/Public/FIRWriteBatch.h
@@ -94,6 +94,11 @@ NS_SWIFT_NAME(WriteBatch)
/**
* Commits all of the writes in this write batch as a single atomic unit.
+ */
+- (void)commit;
+
+/**
+ * Commits all of the writes in this write batch as a single atomic unit.
*
* @param completion A block to be called once all of the writes in the batch have been
* successfully written to the backend as an atomic unit. This block will only execute
@@ -101,7 +106,7 @@ NS_SWIFT_NAME(WriteBatch)
* completion handler will not be called when the device is offline, though local
* changes will be visible immediately.
*/
-- (void)commitWithCompletion:(void (^)(NSError *_Nullable error))completion;
+- (void)commitWithCompletion:(nullable void (^)(NSError *_Nullable error))completion;
@end
diff --git a/Firestore/Source/Remote/FSTRemoteStore.h b/Firestore/Source/Remote/FSTRemoteStore.h
index 313ddb7..18331ff 100644
--- a/Firestore/Source/Remote/FSTRemoteStore.h
+++ b/Firestore/Source/Remote/FSTRemoteStore.h
@@ -83,7 +83,7 @@ NS_ASSUME_NONNULL_BEGIN
@protocol FSTOnlineStateDelegate <NSObject>
/** Called whenever the online state of the watch stream changes */
-- (void)watchStreamDidChangeOnlineState:(FSTOnlineState)onlineState;
+- (void)applyChangedOnlineState:(FSTOnlineState)onlineState;
@end
diff --git a/Firestore/Source/Remote/FSTRemoteStore.m b/Firestore/Source/Remote/FSTRemoteStore.m
index 063e487..a0c5059 100644
--- a/Firestore/Source/Remote/FSTRemoteStore.m
+++ b/Firestore/Source/Remote/FSTRemoteStore.m
@@ -160,27 +160,38 @@ static const int kOnlineAttemptsBeforeFailure = 2;
[self enableNetwork];
}
-- (void)setOnlineStateToHealthy {
- self.shouldWarnOffline = NO;
- [self updateAndNotifyAboutOnlineState:FSTOnlineStateHealthy];
-}
-
-- (void)setOnlineStateToUnknown {
- // The state is set to unknown when a healthy stream is closed (e.g. due to a token timeout) or
- // when we have no active listens and therefore there's no need to start the stream. Assuming
- // there is (possibly in the future) an active listen, then we will eventually move to state
- // Online or Failed, but we always want to make at least kOnlineAttemptsBeforeFailure attempts
- // before failing, so we reset the count here.
- self.watchStreamFailures = 0;
- [self updateAndNotifyAboutOnlineState:FSTOnlineStateUnknown];
+/**
+ * Updates our OnlineState to the new state, updating local state and notifying the
+ * onlineStateHandler as appropriate.
+ */
+- (void)updateOnlineState:(FSTOnlineState)newState {
+ // Update and broadcast the new state.
+ if (newState != self.watchStreamOnlineState) {
+ if (newState == FSTOnlineStateHealthy) {
+ // We've connected to watch at least once. Don't warn the developer about being offline going
+ // forward.
+ self.shouldWarnOffline = NO;
+ } else if (newState == FSTOnlineStateUnknown) {
+ // The state is set to unknown when a healthy stream is closed (e.g. due to a token timeout)
+ // or when we have no active listens and therefore there's no need to start the stream.
+ // Assuming there is (possibly in the future) an active listen, then we will eventually move
+ // to state Online or Failed, but we always want to make at least kOnlineAttemptsBeforeFailure
+ // attempts before failing, so we reset the count here.
+ self.watchStreamFailures = 0;
+ }
+ self.watchStreamOnlineState = newState;
+ [self.onlineStateDelegate applyChangedOnlineState:newState];
+ }
}
+/**
+ * Updates our FSTOnlineState as appropriate after the watch stream reports a failure. The first
+ * failure moves us to the 'Unknown' state. We then may allow multiple failures (based on
+ * kOnlineAttemptsBeforeFailure) before we actually transition to FSTOnlineStateFailed.
+ */
- (void)updateOnlineStateAfterFailure {
- // The first failure after we are successfully connected moves us to the 'Unknown' state. We
- // then may make multiple attempts (based on kOnlineAttemptsBeforeFailure) before we actually
- // report failure.
if (self.watchStreamOnlineState == FSTOnlineStateHealthy) {
- [self setOnlineStateToUnknown];
+ [self updateOnlineState:FSTOnlineStateUnknown];
} else {
self.watchStreamFailures++;
if (self.watchStreamFailures >= kOnlineAttemptsBeforeFailure) {
@@ -188,19 +199,11 @@ static const int kOnlineAttemptsBeforeFailure = 2;
FSTWarn(@"Could not reach Firestore backend.");
self.shouldWarnOffline = NO;
}
- [self updateAndNotifyAboutOnlineState:FSTOnlineStateFailed];
+ [self updateOnlineState:FSTOnlineStateFailed];
}
}
}
-- (void)updateAndNotifyAboutOnlineState:(FSTOnlineState)watchStreamOnlineState {
- BOOL didChange = (watchStreamOnlineState != self.watchStreamOnlineState);
- self.watchStreamOnlineState = watchStreamOnlineState;
- if (didChange) {
- [self.onlineStateDelegate watchStreamDidChangeOnlineState:watchStreamOnlineState];
- }
-}
-
#pragma mark Online/Offline state
- (BOOL)isNetworkEnabled {
@@ -210,8 +213,9 @@ static const int kOnlineAttemptsBeforeFailure = 2;
}
- (void)enableNetwork {
- FSTAssert(self.watchStream == nil, @"enableNetwork: called with non-null watchStream.");
- FSTAssert(self.writeStream == nil, @"enableNetwork: called with non-null writeStream.");
+ if ([self isNetworkEnabled]) {
+ return;
+ }
// Create new streams (but note they're not started yet).
self.watchStream = [self.datastore createWatchStream];
@@ -227,47 +231,51 @@ static const int kOnlineAttemptsBeforeFailure = 2;
[self fillWritePipeline]; // This may start the writeStream.
// We move back to the unknown state because we might not want to re-open the stream
- [self setOnlineStateToUnknown];
+ [self updateOnlineState:FSTOnlineStateUnknown];
}
- (void)disableNetwork {
- [self updateAndNotifyAboutOnlineState:FSTOnlineStateFailed];
+ [self disableNetworkInternal];
+ // Set the FSTOnlineState to failed so get()'s return from cache, etc.
+ [self updateOnlineState:FSTOnlineStateFailed];
+}
- // NOTE: We're guaranteed not to get any further events from these streams (not even a close
- // event).
- [self.watchStream stop];
- [self.writeStream stop];
+/** Disables the network, setting the FSTOnlineState to the specified targetOnlineState. */
+- (void)disableNetworkInternal {
+ if ([self isNetworkEnabled]) {
+ // NOTE: We're guaranteed not to get any further events from these streams (not even a close
+ // event).
+ [self.watchStream stop];
+ [self.writeStream stop];
- [self cleanUpWatchStreamState];
- [self cleanUpWriteStreamState];
+ [self cleanUpWatchStreamState];
+ [self cleanUpWriteStreamState];
- self.writeStream = nil;
- self.watchStream = nil;
+ self.writeStream = nil;
+ self.watchStream = nil;
+ }
}
#pragma mark Shutdown
- (void)shutdown {
FSTLog(@"FSTRemoteStore %p shutting down", (__bridge void *)self);
-
- // Don't fire initial listener callbacks on shutdown.
- self.onlineStateDelegate = nil;
-
- // For now, all shutdown logic is handled by disableNetwork(). We might expand on this in the
- // future.
- if ([self isNetworkEnabled]) {
- [self disableNetwork];
- }
+ [self disableNetworkInternal];
+ // Set the FSTOnlineState to Unknown (rather than Failed) to avoid potentially triggering
+ // spurious listener events with cached data, etc.
+ [self updateOnlineState:FSTOnlineStateUnknown];
}
- (void)userDidChange:(FSTUser *)user {
FSTLog(@"FSTRemoteStore %p changing users: %@", (__bridge void *)self, user);
-
- // Tear down and re-create our network streams. This will ensure we get a fresh auth token
- // for the new user and re-fill the write pipeline with new mutations from the LocalStore
- // (since mutations are per-user).
- [self disableNetwork];
- [self enableNetwork];
+ if ([self isNetworkEnabled]) {
+ // Tear down and re-create our network streams. This will ensure we get a fresh auth token
+ // for the new user and re-fill the write pipeline with new mutations from the LocalStore
+ // (since mutations are per-user).
+ [self disableNetworkInternal];
+ [self updateOnlineState:FSTOnlineStateUnknown];
+ [self enableNetwork];
+ }
}
#pragma mark Watch Stream
@@ -348,7 +356,7 @@ static const int kOnlineAttemptsBeforeFailure = 2;
- (void)watchStreamDidChange:(FSTWatchChange *)change
snapshotVersion:(FSTSnapshotVersion *)snapshotVersion {
// Mark the connection as healthy because we got a message from the server.
- [self setOnlineStateToHealthy];
+ [self updateOnlineState:FSTOnlineStateHealthy];
FSTWatchTargetChange *watchTargetChange =
[change isKindOfClass:[FSTWatchTargetChange class]] ? (FSTWatchTargetChange *)change : nil;
@@ -391,7 +399,7 @@ static const int kOnlineAttemptsBeforeFailure = 2;
} else {
// We don't need to restart the watch stream because there are no active targets. The online
// state is set to unknown because there is no active attempt at establishing a connection.
- [self setOnlineStateToUnknown];
+ [self updateOnlineState:FSTOnlineStateUnknown];
}
}
@@ -532,6 +540,8 @@ static const int kOnlineAttemptsBeforeFailure = 2;
- (void)cleanUpWriteStreamState {
self.lastBatchSeen = kFSTBatchIDUnknown;
+ FSTLog(@"Stopping write stream with %lu pending writes",
+ (unsigned long)[self.pendingWrites count]);
[self.pendingWrites removeAllObjects];
}
diff --git a/Firestore/Source/Remote/FSTStream.m b/Firestore/Source/Remote/FSTStream.m
index 2c039be..5719ec8 100644
--- a/Firestore/Source/Remote/FSTStream.m
+++ b/Firestore/Source/Remote/FSTStream.m
@@ -542,7 +542,7 @@ static const NSTimeInterval kIdleTimeout = 60.0;
FSTWeakify(self);
[self.workerDispatchQueue dispatchAsync:^{
FSTStrongify(self);
- if (!self || ![self isStarted]) {
+ if (![self isStarted]) {
FSTLog(@"%@ Ignoring stream message from inactive stream.", NSStringFromClass([self class]));
}