FSTMutationTests.mm 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. /*
  2. * Copyright 2017 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import "Firestore/Source/Model/FSTMutation.h"
  17. #import <FirebaseFirestore/FIRFieldValue.h>
  18. #import <FirebaseFirestore/FIRTimestamp.h>
  19. #import <XCTest/XCTest.h>
  20. #include <vector>
  21. #import "Firestore/Source/API/FIRFieldValue+Internal.h"
  22. #import "Firestore/Source/Model/FSTDocument.h"
  23. #import "Firestore/Source/Model/FSTFieldValue.h"
  24. #import "Firestore/Example/Tests/Util/FSTHelpers.h"
  25. #include "Firestore/core/src/firebase/firestore/model/document_key.h"
  26. #include "Firestore/core/src/firebase/firestore/model/field_mask.h"
  27. #include "Firestore/core/src/firebase/firestore/model/field_transform.h"
  28. #include "Firestore/core/src/firebase/firestore/model/precondition.h"
  29. #include "Firestore/core/src/firebase/firestore/model/transform_operations.h"
  30. #include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
  31. namespace testutil = firebase::firestore::testutil;
  32. using firebase::firestore::model::ArrayTransform;
  33. using firebase::firestore::model::DocumentKey;
  34. using firebase::firestore::model::FieldMask;
  35. using firebase::firestore::model::FieldPath;
  36. using firebase::firestore::model::FieldTransform;
  37. using firebase::firestore::model::Precondition;
  38. using firebase::firestore::model::TransformOperation;
  39. @interface FSTMutationTests : XCTestCase
  40. @end
  41. @implementation FSTMutationTests {
  42. FIRTimestamp *_timestamp;
  43. }
  44. - (void)setUp {
  45. _timestamp = [FIRTimestamp timestamp];
  46. }
  47. - (void)testAppliesSetsToDocuments {
  48. NSDictionary *docData = @{@"foo" : @"foo-value", @"baz" : @"baz-value"};
  49. FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, NO);
  50. FSTMutation *set = FSTTestSetMutation(@"collection/key", @{@"bar" : @"bar-value"});
  51. FSTMaybeDocument *setDoc = [set applyTo:baseDoc baseDocument:baseDoc localWriteTime:_timestamp];
  52. NSDictionary *expectedData = @{@"bar" : @"bar-value"};
  53. XCTAssertEqualObjects(setDoc, FSTTestDoc("collection/key", 0, expectedData, YES));
  54. }
  55. - (void)testAppliesPatchesToDocuments {
  56. NSDictionary *docData = @{ @"foo" : @{@"bar" : @"bar-value"}, @"baz" : @"baz-value" };
  57. FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, NO);
  58. FSTMutation *patch = FSTTestPatchMutation("collection/key", @{@"foo.bar" : @"new-bar-value"}, {});
  59. FSTMaybeDocument *patchedDoc =
  60. [patch applyTo:baseDoc baseDocument:baseDoc localWriteTime:_timestamp];
  61. NSDictionary *expectedData = @{ @"foo" : @{@"bar" : @"new-bar-value"}, @"baz" : @"baz-value" };
  62. XCTAssertEqualObjects(patchedDoc, FSTTestDoc("collection/key", 0, expectedData, YES));
  63. }
  64. - (void)testDeletesValuesFromTheFieldMask {
  65. NSDictionary *docData = @{ @"foo" : @{@"bar" : @"bar-value", @"baz" : @"baz-value"} };
  66. FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, NO);
  67. DocumentKey key = testutil::Key("collection/key");
  68. FSTMutation *patch = [[FSTPatchMutation alloc] initWithKey:key
  69. fieldMask:{testutil::Field("foo.bar")}
  70. value:[FSTObjectValue objectValue]
  71. precondition:Precondition::None()];
  72. FSTMaybeDocument *patchedDoc =
  73. [patch applyTo:baseDoc baseDocument:baseDoc localWriteTime:_timestamp];
  74. NSDictionary *expectedData = @{ @"foo" : @{@"baz" : @"baz-value"} };
  75. XCTAssertEqualObjects(patchedDoc, FSTTestDoc("collection/key", 0, expectedData, YES));
  76. }
  77. - (void)testPatchesPrimitiveValue {
  78. NSDictionary *docData = @{@"foo" : @"foo-value", @"baz" : @"baz-value"};
  79. FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, NO);
  80. FSTMutation *patch = FSTTestPatchMutation("collection/key", @{@"foo.bar" : @"new-bar-value"}, {});
  81. FSTMaybeDocument *patchedDoc =
  82. [patch applyTo:baseDoc baseDocument:baseDoc localWriteTime:_timestamp];
  83. NSDictionary *expectedData = @{ @"foo" : @{@"bar" : @"new-bar-value"}, @"baz" : @"baz-value" };
  84. XCTAssertEqualObjects(patchedDoc, FSTTestDoc("collection/key", 0, expectedData, YES));
  85. }
  86. - (void)testPatchingDeletedDocumentsDoesNothing {
  87. FSTMaybeDocument *baseDoc = FSTTestDeletedDoc("collection/key", 0);
  88. FSTMutation *patch = FSTTestPatchMutation("collection/key", @{@"foo" : @"bar"}, {});
  89. FSTMaybeDocument *patchedDoc =
  90. [patch applyTo:baseDoc baseDocument:baseDoc localWriteTime:_timestamp];
  91. XCTAssertEqualObjects(patchedDoc, baseDoc);
  92. }
  93. - (void)testAppliesLocalServerTimestampTransformToDocuments {
  94. NSDictionary *docData = @{ @"foo" : @{@"bar" : @"bar-value"}, @"baz" : @"baz-value" };
  95. FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, NO);
  96. FSTMutation *transform = FSTTestTransformMutation(
  97. @"collection/key", @{@"foo.bar" : [FIRFieldValue fieldValueForServerTimestamp]});
  98. FSTMaybeDocument *transformedDoc =
  99. [transform applyTo:baseDoc baseDocument:baseDoc localWriteTime:_timestamp];
  100. // Server timestamps aren't parsed, so we manually insert it.
  101. FSTObjectValue *expectedData = FSTTestObjectValue(
  102. @{ @"foo" : @{@"bar" : @"<server-timestamp>"},
  103. @"baz" : @"baz-value" });
  104. expectedData =
  105. [expectedData objectBySettingValue:[FSTServerTimestampValue
  106. serverTimestampValueWithLocalWriteTime:_timestamp
  107. previousValue:nil]
  108. forPath:testutil::Field("foo.bar")];
  109. FSTDocument *expectedDoc = [FSTDocument documentWithData:expectedData
  110. key:FSTTestDocKey(@"collection/key")
  111. version:testutil::Version(0)
  112. hasLocalMutations:YES];
  113. XCTAssertEqualObjects(transformedDoc, expectedDoc);
  114. }
  115. // NOTE: This is more a test of FSTUserDataConverter code than FSTMutation code but we don't have
  116. // unit tests for it currently. We could consider removing this test once we have integration tests.
  117. - (void)testCreateArrayUnionTransform {
  118. FSTTransformMutation *transform = FSTTestTransformMutation(@"collection/key", @{
  119. @"foo" : [FIRFieldValue fieldValueForArrayUnion:@[ @"tag" ]],
  120. @"bar.baz" :
  121. [FIRFieldValue fieldValueForArrayUnion:@[ @YES,
  122. @{ @"nested" : @{@"a" : @[ @1, @2 ]} } ]]
  123. });
  124. XCTAssertEqual(transform.fieldTransforms.size(), 2);
  125. const FieldTransform &first = transform.fieldTransforms[0];
  126. XCTAssertEqual(first.path(), FieldPath({"foo"}));
  127. {
  128. std::vector<FSTFieldValue *> expectedElements{FSTTestFieldValue(@"tag")};
  129. ArrayTransform expected(TransformOperation::Type::ArrayUnion, expectedElements);
  130. XCTAssertEqual(static_cast<const ArrayTransform &>(first.transformation()), expected);
  131. }
  132. const FieldTransform &second = transform.fieldTransforms[1];
  133. XCTAssertEqual(second.path(), FieldPath({"bar", "baz"}));
  134. {
  135. std::vector<FSTFieldValue *> expectedElements {
  136. FSTTestFieldValue(@YES), FSTTestFieldValue(@{ @"nested" : @{@"a" : @[ @1, @2 ]} })
  137. };
  138. ArrayTransform expected(TransformOperation::Type::ArrayUnion, expectedElements);
  139. XCTAssertEqual(static_cast<const ArrayTransform &>(second.transformation()), expected);
  140. }
  141. }
  142. // NOTE: This is more a test of FSTUserDataConverter code than FSTMutation code but we don't have
  143. // unit tests for it currently. We could consider removing this test once we have integration tests.
  144. - (void)testCreateArrayRemoveTransform {
  145. FSTTransformMutation *transform = FSTTestTransformMutation(@"collection/key", @{
  146. @"foo" : [FIRFieldValue fieldValueForArrayRemove:@[ @"tag" ]],
  147. });
  148. XCTAssertEqual(transform.fieldTransforms.size(), 1);
  149. const FieldTransform &first = transform.fieldTransforms[0];
  150. XCTAssertEqual(first.path(), FieldPath({"foo"}));
  151. {
  152. std::vector<FSTFieldValue *> expectedElements{FSTTestFieldValue(@"tag")};
  153. const ArrayTransform expected(TransformOperation::Type::ArrayRemove, expectedElements);
  154. XCTAssertEqual(static_cast<const ArrayTransform &>(first.transformation()), expected);
  155. }
  156. }
  157. - (void)testAppliesLocalArrayUnionTransformToMissingField {
  158. auto baseDoc = @{};
  159. auto transform = @{ @"missing" : [FIRFieldValue fieldValueForArrayUnion:@[ @1, @2 ]] };
  160. auto expected = @{ @"missing" : @[ @1, @2 ] };
  161. [self transformBaseDoc:baseDoc with:transform expecting:expected];
  162. }
  163. - (void)testAppliesLocalArrayUnionTransformToNonArrayField {
  164. auto baseDoc = @{ @"non-array" : @42 };
  165. auto transform = @{ @"non-array" : [FIRFieldValue fieldValueForArrayUnion:@[ @1, @2 ]] };
  166. auto expected = @{ @"non-array" : @[ @1, @2 ] };
  167. [self transformBaseDoc:baseDoc with:transform expecting:expected];
  168. }
  169. - (void)testAppliesLocalArrayUnionTransformWithNonExistingElements {
  170. auto baseDoc = @{ @"array" : @[ @1, @3 ] };
  171. auto transform = @{ @"array" : [FIRFieldValue fieldValueForArrayUnion:@[ @2, @4 ]] };
  172. auto expected = @{ @"array" : @[ @1, @3, @2, @4 ] };
  173. [self transformBaseDoc:baseDoc with:transform expecting:expected];
  174. }
  175. - (void)testAppliesLocalArrayUnionTransformWithExistingElements {
  176. auto baseDoc = @{ @"array" : @[ @1, @3 ] };
  177. auto transform = @{ @"array" : [FIRFieldValue fieldValueForArrayUnion:@[ @1, @3 ]] };
  178. auto expected = @{ @"array" : @[ @1, @3 ] };
  179. [self transformBaseDoc:baseDoc with:transform expecting:expected];
  180. }
  181. - (void)testAppliesLocalArrayUnionTransformWithDuplicateExistingElements {
  182. // Duplicate entries in your existing array should be preserved.
  183. auto baseDoc = @{ @"array" : @[ @1, @2, @2, @3 ] };
  184. auto transform = @{ @"array" : [FIRFieldValue fieldValueForArrayUnion:@[ @2 ]] };
  185. auto expected = @{ @"array" : @[ @1, @2, @2, @3 ] };
  186. [self transformBaseDoc:baseDoc with:transform expecting:expected];
  187. }
  188. - (void)testAppliesLocalArrayUnionTransformWithDuplicateUnionElements {
  189. // Duplicate entries in your union array should only be added once.
  190. auto baseDoc = @{ @"array" : @[ @1, @3 ] };
  191. auto transform = @{ @"array" : [FIRFieldValue fieldValueForArrayUnion:@[ @2, @2 ]] };
  192. auto expected = @{ @"array" : @[ @1, @3, @2 ] };
  193. [self transformBaseDoc:baseDoc with:transform expecting:expected];
  194. }
  195. - (void)testAppliesLocalArrayUnionTransformWithNonPrimitiveElements {
  196. // Union nested object values (one existing, one not).
  197. auto baseDoc = @{ @"array" : @[ @1, @{@"a" : @"b"} ] };
  198. auto transform =
  199. @{ @"array" : [FIRFieldValue fieldValueForArrayUnion:@[ @{@"a" : @"b"}, @{@"c" : @"d"} ]] };
  200. auto expected = @{ @"array" : @[ @1, @{@"a" : @"b"}, @{@"c" : @"d"} ] };
  201. [self transformBaseDoc:baseDoc with:transform expecting:expected];
  202. }
  203. - (void)testAppliesLocalArrayUnionTransformWithPartiallyOverlappingElements {
  204. // Union objects that partially overlap an existing object.
  205. auto baseDoc = @{ @"array" : @[ @1, @{@"a" : @"b", @"c" : @"d"} ] };
  206. auto transform =
  207. @{ @"array" : [FIRFieldValue fieldValueForArrayUnion:@[ @{@"a" : @"b"}, @{@"c" : @"d"} ]] };
  208. auto expected =
  209. @{ @"array" : @[ @1, @{@"a" : @"b", @"c" : @"d"}, @{@"a" : @"b"}, @{@"c" : @"d"} ] };
  210. [self transformBaseDoc:baseDoc with:transform expecting:expected];
  211. }
  212. - (void)testAppliesLocalArrayRemoveTransformToMissingField {
  213. auto baseDoc = @{};
  214. auto transform = @{ @"missing" : [FIRFieldValue fieldValueForArrayRemove:@[ @1, @2 ]] };
  215. auto expected = @{ @"missing" : @[] };
  216. [self transformBaseDoc:baseDoc with:transform expecting:expected];
  217. }
  218. - (void)testAppliesLocalArrayRemoveTransformToNonArrayField {
  219. auto baseDoc = @{ @"non-array" : @42 };
  220. auto transform = @{ @"non-array" : [FIRFieldValue fieldValueForArrayRemove:@[ @1, @2 ]] };
  221. auto expected = @{ @"non-array" : @[] };
  222. [self transformBaseDoc:baseDoc with:transform expecting:expected];
  223. }
  224. - (void)testAppliesLocalArrayRemoveTransformWithNonExistingElements {
  225. auto baseDoc = @{ @"array" : @[ @1, @3 ] };
  226. auto transform = @{ @"array" : [FIRFieldValue fieldValueForArrayRemove:@[ @2, @4 ]] };
  227. auto expected = @{ @"array" : @[ @1, @3 ] };
  228. [self transformBaseDoc:baseDoc with:transform expecting:expected];
  229. }
  230. - (void)testAppliesLocalArrayRemoveTransformWithExistingElements {
  231. auto baseDoc = @{ @"array" : @[ @1, @2, @3, @4 ] };
  232. auto transform = @{ @"array" : [FIRFieldValue fieldValueForArrayRemove:@[ @1, @3 ]] };
  233. auto expected = @{ @"array" : @[ @2, @4 ] };
  234. [self transformBaseDoc:baseDoc with:transform expecting:expected];
  235. }
  236. - (void)testAppliesLocalArrayRemoveTransformWithNonPrimitiveElements {
  237. // Remove nested object values (one existing, one not).
  238. auto baseDoc = @{ @"array" : @[ @1, @{@"a" : @"b"} ] };
  239. auto transform =
  240. @{ @"array" : [FIRFieldValue fieldValueForArrayRemove:@[ @{@"a" : @"b"}, @{@"c" : @"d"} ]] };
  241. auto expected = @{ @"array" : @[ @1 ] };
  242. [self transformBaseDoc:baseDoc with:transform expecting:expected];
  243. }
  244. // Helper to test a particular transform scenario.
  245. - (void)transformBaseDoc:(NSDictionary<NSString *, id> *)baseData
  246. with:(NSDictionary<NSString *, id> *)transformData
  247. expecting:(NSDictionary<NSString *, id> *)expectedData {
  248. FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, baseData, NO);
  249. FSTMutation *transform = FSTTestTransformMutation(@"collection/key", transformData);
  250. FSTMaybeDocument *transformedDoc =
  251. [transform applyTo:baseDoc baseDocument:baseDoc localWriteTime:_timestamp];
  252. FSTDocument *expectedDoc = [FSTDocument documentWithData:FSTTestObjectValue(expectedData)
  253. key:FSTTestDocKey(@"collection/key")
  254. version:testutil::Version(0)
  255. hasLocalMutations:YES];
  256. XCTAssertEqualObjects(transformedDoc, expectedDoc);
  257. }
  258. - (void)testAppliesServerAckedServerTimestampTransformToDocuments {
  259. NSDictionary *docData = @{ @"foo" : @{@"bar" : @"bar-value"}, @"baz" : @"baz-value" };
  260. FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, NO);
  261. FSTMutation *transform = FSTTestTransformMutation(
  262. @"collection/key", @{@"foo.bar" : [FIRFieldValue fieldValueForServerTimestamp]});
  263. FSTMutationResult *mutationResult = [[FSTMutationResult alloc]
  264. initWithVersion:testutil::Version(1)
  265. transformResults:@[ [FSTTimestampValue timestampValue:_timestamp] ]];
  266. FSTMaybeDocument *transformedDoc = [transform applyTo:baseDoc
  267. baseDocument:baseDoc
  268. localWriteTime:_timestamp
  269. mutationResult:mutationResult];
  270. NSDictionary *expectedData =
  271. @{ @"foo" : @{@"bar" : _timestamp.dateValue},
  272. @"baz" : @"baz-value" };
  273. XCTAssertEqualObjects(transformedDoc, FSTTestDoc("collection/key", 0, expectedData, NO));
  274. }
  275. - (void)testAppliesServerAckedArrayTransformsToDocuments {
  276. NSDictionary *docData = @{ @"array_1" : @[ @1, @2 ], @"array_2" : @[ @"a", @"b" ] };
  277. FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, NO);
  278. FSTMutation *transform = FSTTestTransformMutation(@"collection/key", @{
  279. @"array_1" : [FIRFieldValue fieldValueForArrayUnion:@[ @2, @3 ]],
  280. @"array_2" : [FIRFieldValue fieldValueForArrayRemove:@[ @"a", @"c" ]]
  281. });
  282. // Server just sends null transform results for array operations.
  283. FSTMutationResult *mutationResult = [[FSTMutationResult alloc]
  284. initWithVersion:testutil::Version(1)
  285. transformResults:@[ [FSTNullValue nullValue], [FSTNullValue nullValue] ]];
  286. FSTMaybeDocument *transformedDoc = [transform applyTo:baseDoc
  287. baseDocument:baseDoc
  288. localWriteTime:_timestamp
  289. mutationResult:mutationResult];
  290. NSDictionary *expectedData = @{ @"array_1" : @[ @1, @2, @3 ], @"array_2" : @[ @"b" ] };
  291. XCTAssertEqualObjects(transformedDoc, FSTTestDoc("collection/key", 0, expectedData, NO));
  292. }
  293. - (void)testDeleteDeletes {
  294. NSDictionary *docData = @{@"foo" : @"bar"};
  295. FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, NO);
  296. FSTMutation *mutation = FSTTestDeleteMutation(@"collection/key");
  297. FSTMaybeDocument *result =
  298. [mutation applyTo:baseDoc baseDocument:baseDoc localWriteTime:_timestamp];
  299. XCTAssertEqualObjects(result, FSTTestDeletedDoc("collection/key", 0));
  300. }
  301. - (void)testSetWithMutationResult {
  302. NSDictionary *docData = @{@"foo" : @"bar"};
  303. FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, NO);
  304. FSTMutation *set = FSTTestSetMutation(@"collection/key", @{@"foo" : @"new-bar"});
  305. FSTMutationResult *mutationResult =
  306. [[FSTMutationResult alloc] initWithVersion:testutil::Version(4) transformResults:nil];
  307. FSTMaybeDocument *setDoc = [set applyTo:baseDoc
  308. baseDocument:baseDoc
  309. localWriteTime:_timestamp
  310. mutationResult:mutationResult];
  311. NSDictionary *expectedData = @{@"foo" : @"new-bar"};
  312. XCTAssertEqualObjects(setDoc, FSTTestDoc("collection/key", 0, expectedData, NO));
  313. }
  314. - (void)testPatchWithMutationResult {
  315. NSDictionary *docData = @{@"foo" : @"bar"};
  316. FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, NO);
  317. FSTMutation *patch = FSTTestPatchMutation("collection/key", @{@"foo" : @"new-bar"}, {});
  318. FSTMutationResult *mutationResult =
  319. [[FSTMutationResult alloc] initWithVersion:testutil::Version(4) transformResults:nil];
  320. FSTMaybeDocument *patchedDoc = [patch applyTo:baseDoc
  321. baseDocument:baseDoc
  322. localWriteTime:_timestamp
  323. mutationResult:mutationResult];
  324. NSDictionary *expectedData = @{@"foo" : @"new-bar"};
  325. XCTAssertEqualObjects(patchedDoc, FSTTestDoc("collection/key", 0, expectedData, NO));
  326. }
  327. #define ASSERT_VERSION_TRANSITION(mutation, base, expected) \
  328. do { \
  329. FSTMutationResult *mutationResult = \
  330. [[FSTMutationResult alloc] initWithVersion:testutil::Version(0) transformResults:nil]; \
  331. FSTMaybeDocument *actual = [mutation applyTo:base \
  332. baseDocument:base \
  333. localWriteTime:_timestamp \
  334. mutationResult:mutationResult]; \
  335. XCTAssertEqualObjects(actual, expected); \
  336. } while (0);
  337. /**
  338. * Tests the transition table documented in FSTMutation.h.
  339. */
  340. - (void)testTransitions {
  341. FSTDocument *docV0 = FSTTestDoc("collection/key", 0, @{}, NO);
  342. FSTDeletedDocument *deletedV0 = FSTTestDeletedDoc("collection/key", 0);
  343. FSTDocument *docV3 = FSTTestDoc("collection/key", 3, @{}, NO);
  344. FSTDeletedDocument *deletedV3 = FSTTestDeletedDoc("collection/key", 3);
  345. FSTMutation *setMutation = FSTTestSetMutation(@"collection/key", @{});
  346. FSTMutation *patchMutation = FSTTestPatchMutation("collection/key", {}, {});
  347. FSTMutation *deleteMutation = FSTTestDeleteMutation(@"collection/key");
  348. ASSERT_VERSION_TRANSITION(setMutation, docV3, docV3);
  349. ASSERT_VERSION_TRANSITION(setMutation, deletedV3, docV0);
  350. ASSERT_VERSION_TRANSITION(setMutation, nil, docV0);
  351. ASSERT_VERSION_TRANSITION(patchMutation, docV3, docV3);
  352. ASSERT_VERSION_TRANSITION(patchMutation, deletedV3, deletedV3);
  353. ASSERT_VERSION_TRANSITION(patchMutation, nil, nil);
  354. ASSERT_VERSION_TRANSITION(deleteMutation, docV3, deletedV0);
  355. ASSERT_VERSION_TRANSITION(deleteMutation, deletedV3, deletedV0);
  356. ASSERT_VERSION_TRANSITION(deleteMutation, nil, deletedV0);
  357. }
  358. #undef ASSERT_TRANSITION
  359. @end