aboutsummaryrefslogtreecommitdiffhomepage
path: root/Firestore/Example/Tests/Integration/API/FIRArrayTransformTests.mm
diff options
context:
space:
mode:
Diffstat (limited to 'Firestore/Example/Tests/Integration/API/FIRArrayTransformTests.mm')
-rw-r--r--Firestore/Example/Tests/Integration/API/FIRArrayTransformTests.mm289
1 files changed, 289 insertions, 0 deletions
diff --git a/Firestore/Example/Tests/Integration/API/FIRArrayTransformTests.mm b/Firestore/Example/Tests/Integration/API/FIRArrayTransformTests.mm
new file mode 100644
index 0000000..5f0cf92
--- /dev/null
+++ b/Firestore/Example/Tests/Integration/API/FIRArrayTransformTests.mm
@@ -0,0 +1,289 @@
+/*
+ * Copyright 2018 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/FirebaseFirestore.h>
+
+#import <XCTest/XCTest.h>
+
+#import "Firestore/Example/Tests/Util/FSTEventAccumulator.h"
+#import "Firestore/Example/Tests/Util/FSTIntegrationTestCase.h"
+
+/**
+ * Note: Transforms are tested pretty thoroughly in FIRServerTimestampTests (via set, update,
+ * transactions, nested in documents, multiple transforms together, etc.) and so these tests
+ * mostly focus on the array transform semantics.
+ */
+@interface FIRArrayTransformTests : FSTIntegrationTestCase
+@end
+
+@implementation FIRArrayTransformTests {
+ // A document reference to read and write to.
+ FIRDocumentReference *_docRef;
+
+ // Accumulator used to capture events during the test.
+ FSTEventAccumulator *_accumulator;
+
+ // Listener registration for a listener maintained during the course of the test.
+ id<FIRListenerRegistration> _listenerRegistration;
+}
+
+- (void)setUp {
+ [super setUp];
+
+ _docRef = [self documentRef];
+ _accumulator = [FSTEventAccumulator accumulatorForTest:self];
+ _listenerRegistration =
+ [_docRef addSnapshotListenerWithIncludeMetadataChanges:YES
+ listener:_accumulator.valueEventHandler];
+
+ // Wait for initial nil snapshot to avoid potential races.
+ FIRDocumentSnapshot *initialSnapshot = [_accumulator awaitEventWithName:@"initial event"];
+ XCTAssertFalse(initialSnapshot.exists);
+}
+
+- (void)tearDown {
+ [_listenerRegistration remove];
+
+ [super tearDown];
+}
+
+#pragma mark - Test Helpers
+
+/** 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;
+}
+
+/** Writes some initial data and consumes the events generated. */
+- (void)writeInitialData:(NSDictionary<NSString *, id> *)data {
+ [self writeDocumentRef:_docRef data:data];
+ XCTAssertEqualObjects([self waitForLocalEvent].data, data);
+ XCTAssertEqualObjects([self waitForRemoteEvent].data, data);
+}
+
+#pragma mark - Test Cases
+
+- (void)testCreateDocumentWithArrayUnion {
+ [self writeDocumentRef:_docRef
+ data:@{
+ @"array" : [FIRFieldValue fieldValueForArrayUnion:@[ @1, @2 ]]
+ }];
+ id expected = @{ @"array" : @[ @1, @2 ] };
+ XCTAssertEqualObjects([self waitForLocalEvent].data, expected);
+ XCTAssertEqualObjects([self waitForRemoteEvent].data, expected);
+}
+
+- (void)testAppendToArrayViaUpdate {
+ [self writeInitialData:@{ @"array" : @[ @1, @3 ] }];
+
+ [self updateDocumentRef:_docRef
+ data:@{
+ @"array" : [FIRFieldValue fieldValueForArrayUnion:@[ @2, @1, @4 ]]
+ }];
+
+ id expected = @{ @"array" : @[ @1, @3, @2, @4 ] };
+ XCTAssertEqualObjects([self waitForLocalEvent].data, expected);
+ XCTAssertEqualObjects([self waitForRemoteEvent].data, expected);
+}
+
+- (void)testAppendToArrayViaMergeSet {
+ [self writeInitialData:@{ @"array" : @[ @1, @3 ] }];
+
+ [self mergeDocumentRef:_docRef
+ data:@{
+ @"array" : [FIRFieldValue fieldValueForArrayUnion:@[ @2, @1, @4 ]]
+ }];
+
+ id expected = @{ @"array" : @[ @1, @3, @2, @4 ] };
+ XCTAssertEqualObjects([self waitForLocalEvent].data, expected);
+ XCTAssertEqualObjects([self waitForRemoteEvent].data, expected);
+}
+
+- (void)testAppendObjectToArrayViaUpdate {
+ [self writeInitialData:@{ @"array" : @[ @{@"a" : @"hi"} ] }];
+
+ [self updateDocumentRef:_docRef
+ data:@{
+ @"array" : [FIRFieldValue
+ fieldValueForArrayUnion:@[ @{@"a" : @"hi"}, @{@"a" : @"bye"} ]]
+ }];
+
+ id expected = @{ @"array" : @[ @{@"a" : @"hi"}, @{@"a" : @"bye"} ] };
+ XCTAssertEqualObjects([self waitForLocalEvent].data, expected);
+ XCTAssertEqualObjects([self waitForRemoteEvent].data, expected);
+}
+
+- (void)testRemoveFromArrayViaUpdate {
+ [self writeInitialData:@{ @"array" : @[ @1, @3, @1, @3 ] }];
+
+ [self updateDocumentRef:_docRef
+ data:@{
+ @"array" : [FIRFieldValue fieldValueForArrayRemove:@[ @1, @4 ]]
+ }];
+
+ id expected = @{ @"array" : @[ @3, @3 ] };
+ XCTAssertEqualObjects([self waitForLocalEvent].data, expected);
+ XCTAssertEqualObjects([self waitForRemoteEvent].data, expected);
+}
+
+- (void)testRemoveFromArrayViaMergeSet {
+ [self writeInitialData:@{ @"array" : @[ @1, @3, @1, @3 ] }];
+
+ [self mergeDocumentRef:_docRef
+ data:@{
+ @"array" : [FIRFieldValue fieldValueForArrayRemove:@[ @1, @4 ]]
+ }];
+
+ id expected = @{ @"array" : @[ @3, @3 ] };
+ XCTAssertEqualObjects([self waitForLocalEvent].data, expected);
+ XCTAssertEqualObjects([self waitForRemoteEvent].data, expected);
+}
+
+- (void)testRemoveObjectFromArrayViaUpdate {
+ [self writeInitialData:@{ @"array" : @[ @{@"a" : @"hi"}, @{@"a" : @"bye"} ] }];
+
+ [self updateDocumentRef:_docRef
+ data:@{
+ @"array" : [FIRFieldValue fieldValueForArrayRemove:@[ @{@"a" : @"hi"} ]]
+ }];
+
+ id expected = @{ @"array" : @[ @{@"a" : @"bye"} ] };
+ XCTAssertEqualObjects([self waitForLocalEvent].data, expected);
+ XCTAssertEqualObjects([self waitForRemoteEvent].data, expected);
+}
+
+@end
+
+/**
+ * Unlike the FIRArrayTransformTests above, these tests intentionally avoid having any ongoing
+ * listeners so that we can test what gets stored in the offline cache based purely on the write
+ * acknowledgement (without receiving an updated document via watch). As such they also rely on
+ * persistence being enabled so documents remain in the cache after the write.
+ */
+@interface FIRArrayTransformServerApplicationTests : FSTIntegrationTestCase
+@end
+
+@implementation FIRArrayTransformServerApplicationTests {
+ // A document reference to read and write to.
+ FIRDocumentReference *_docRef;
+}
+
+- (void)setUp {
+ [super setUp];
+
+ _docRef = [self documentRef];
+}
+
+/**
+ * Helper that uses a temporary listener to read from cache (returning nil if no document seems
+ * to be in cache). Can probably be replaced with get(source=cache) in the future.
+ */
+- (FIRDocumentSnapshot *_Nullable)getFromCache {
+ FSTEventAccumulator *accumulator = [FSTEventAccumulator accumulatorForTest:self];
+ id<FIRListenerRegistration> listenerRegistration =
+ [_docRef addSnapshotListener:accumulator.valueEventHandler];
+ FIRDocumentSnapshot *snapshot = [accumulator awaitEventWithName:@"listenForOneEvent"];
+ [listenerRegistration remove];
+ if (snapshot.metadata.fromCache) {
+ return snapshot;
+ } else {
+ return nil;
+ }
+}
+
+- (void)testServerApplicationOfSetWithNoCachedBaseDoc {
+ [self writeDocumentRef:_docRef
+ data:@{
+ @"array" : [FIRFieldValue fieldValueForArrayUnion:@[ @1, @2 ]]
+ }];
+ id expected = @{ @"array" : @[ @1, @2 ] };
+ XCTAssertEqualObjects([self getFromCache].data, expected);
+}
+
+- (void)testServerApplicationOfUpdateWithNoCachedBaseDoc {
+ // Write an initial document out-of-band so it's not in our cache
+ [self writeDocumentRef:[[self firestore] documentWithPath:_docRef.path]
+ data:@{
+ @"array" : @[ @42 ]
+ }];
+
+ [self updateDocumentRef:_docRef
+ data:@{
+ @"array" : [FIRFieldValue fieldValueForArrayUnion:@[ @1, @2 ]]
+ }];
+
+ // Nothing should be cached since it was an update and we had no base doc.
+ XCTAssertNil([self getFromCache]);
+}
+
+- (void)testServerApplicationOfMergeSetWithNoCachedBaseDoc {
+ // Write an initial document out-of-band so it's not in our cache
+ [self writeDocumentRef:[[self firestore] documentWithPath:_docRef.path]
+ data:@{
+ @"array" : @[ @42 ]
+ }];
+
+ [self mergeDocumentRef:_docRef
+ data:@{
+ @"array" : [FIRFieldValue fieldValueForArrayUnion:@[ @1, @2 ]]
+ }];
+
+ // Document will be cached but we'll be missing 42.
+ id expected = @{ @"array" : @[ @1, @2 ] };
+ XCTAssertEqualObjects([self getFromCache].data, expected);
+}
+
+- (void)testServerApplicationOfArrayUnionUpdateWithCachedBaseDoc {
+ // Cache a document with an array.
+ [self writeDocumentRef:_docRef data:@{ @"array" : @[ @42 ] }];
+
+ [self updateDocumentRef:_docRef
+ data:@{
+ @"array" : [FIRFieldValue fieldValueForArrayUnion:@[ @1, @2 ]]
+ }];
+
+ // Should have merged the update with the cached doc.
+ id expected = @{ @"array" : @[ @42, @1, @2 ] };
+ XCTAssertEqualObjects([self getFromCache].data, expected);
+}
+
+- (void)testServerApplicationOfArrayRemoveUpdateWithCachedBaseDoc {
+ // Cache a document with an array.
+ [self writeDocumentRef:_docRef data:@{ @"array" : @[ @42, @1, @2 ] }];
+
+ [self updateDocumentRef:_docRef
+ data:@{
+ @"array" : [FIRFieldValue fieldValueForArrayRemove:@[ @1, @2 ]]
+ }];
+
+ // Should have merged the update with the cached doc.
+ id expected = @{ @"array" : @[ @42 ] };
+ XCTAssertEqualObjects([self getFromCache].data, expected);
+}
+@end