FSTMutationTests.mm 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  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 =
  52. [set applyToLocalDocument:baseDoc baseDocument:baseDoc localWriteTime:_timestamp];
  53. NSDictionary *expectedData = @{@"bar" : @"bar-value"};
  54. XCTAssertEqualObjects(setDoc, FSTTestDoc("collection/key", 0, expectedData, YES));
  55. }
  56. - (void)testAppliesPatchesToDocuments {
  57. NSDictionary *docData = @{@"foo" : @{@"bar" : @"bar-value"}, @"baz" : @"baz-value"};
  58. FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, NO);
  59. FSTMutation *patch = FSTTestPatchMutation("collection/key", @{@"foo.bar" : @"new-bar-value"}, {});
  60. FSTMaybeDocument *patchedDoc =
  61. [patch applyToLocalDocument:baseDoc baseDocument:baseDoc localWriteTime:_timestamp];
  62. NSDictionary *expectedData = @{@"foo" : @{@"bar" : @"new-bar-value"}, @"baz" : @"baz-value"};
  63. XCTAssertEqualObjects(patchedDoc, FSTTestDoc("collection/key", 0, expectedData, YES));
  64. }
  65. - (void)testDeletesValuesFromTheFieldMask {
  66. NSDictionary *docData = @{@"foo" : @{@"bar" : @"bar-value", @"baz" : @"baz-value"}};
  67. FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, NO);
  68. DocumentKey key = testutil::Key("collection/key");
  69. FSTMutation *patch = [[FSTPatchMutation alloc] initWithKey:key
  70. fieldMask:{testutil::Field("foo.bar")}
  71. value:[FSTObjectValue objectValue]
  72. precondition:Precondition::None()];
  73. FSTMaybeDocument *patchedDoc =
  74. [patch applyToLocalDocument:baseDoc baseDocument:baseDoc localWriteTime:_timestamp];
  75. NSDictionary *expectedData = @{@"foo" : @{@"baz" : @"baz-value"}};
  76. XCTAssertEqualObjects(patchedDoc, FSTTestDoc("collection/key", 0, expectedData, YES));
  77. }
  78. - (void)testPatchesPrimitiveValue {
  79. NSDictionary *docData = @{@"foo" : @"foo-value", @"baz" : @"baz-value"};
  80. FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, NO);
  81. FSTMutation *patch = FSTTestPatchMutation("collection/key", @{@"foo.bar" : @"new-bar-value"}, {});
  82. FSTMaybeDocument *patchedDoc =
  83. [patch applyToLocalDocument:baseDoc baseDocument:baseDoc localWriteTime:_timestamp];
  84. NSDictionary *expectedData = @{@"foo" : @{@"bar" : @"new-bar-value"}, @"baz" : @"baz-value"};
  85. XCTAssertEqualObjects(patchedDoc, FSTTestDoc("collection/key", 0, expectedData, YES));
  86. }
  87. - (void)testPatchingDeletedDocumentsDoesNothing {
  88. FSTMaybeDocument *baseDoc = FSTTestDeletedDoc("collection/key", 0);
  89. FSTMutation *patch = FSTTestPatchMutation("collection/key", @{@"foo" : @"bar"}, {});
  90. FSTMaybeDocument *patchedDoc =
  91. [patch applyToLocalDocument:baseDoc baseDocument:baseDoc localWriteTime:_timestamp];
  92. XCTAssertEqualObjects(patchedDoc, baseDoc);
  93. }
  94. - (void)testAppliesLocalServerTimestampTransformToDocuments {
  95. NSDictionary *docData = @{@"foo" : @{@"bar" : @"bar-value"}, @"baz" : @"baz-value"};
  96. FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, NO);
  97. FSTMutation *transform = FSTTestTransformMutation(
  98. @"collection/key", @{@"foo.bar" : [FIRFieldValue fieldValueForServerTimestamp]});
  99. FSTMaybeDocument *transformedDoc =
  100. [transform applyToLocalDocument:baseDoc baseDocument:baseDoc localWriteTime:_timestamp];
  101. // Server timestamps aren't parsed, so we manually insert it.
  102. FSTObjectValue *expectedData = FSTTestObjectValue(
  103. @{@"foo" : @{@"bar" : @"<server-timestamp>"},
  104. @"baz" : @"baz-value"});
  105. expectedData =
  106. [expectedData objectBySettingValue:[FSTServerTimestampValue
  107. serverTimestampValueWithLocalWriteTime:_timestamp
  108. previousValue:nil]
  109. forPath:testutil::Field("foo.bar")];
  110. FSTDocument *expectedDoc = [FSTDocument documentWithData:expectedData
  111. key:FSTTestDocKey(@"collection/key")
  112. version:testutil::Version(0)
  113. hasLocalMutations:YES];
  114. XCTAssertEqualObjects(transformedDoc, expectedDoc);
  115. }
  116. // NOTE: This is more a test of FSTUserDataConverter code than FSTMutation code but we don't have
  117. // unit tests for it currently. We could consider removing this test once we have integration tests.
  118. - (void)testCreateArrayUnionTransform {
  119. FSTTransformMutation *transform = FSTTestTransformMutation(@"collection/key", @{
  120. @"foo" : [FIRFieldValue fieldValueForArrayUnion:@[ @"tag" ]],
  121. @"bar.baz" :
  122. [FIRFieldValue fieldValueForArrayUnion:@[ @YES,
  123. @{@"nested" : @{@"a" : @[ @1, @2 ]}} ]]
  124. });
  125. XCTAssertEqual(transform.fieldTransforms.size(), 2);
  126. const FieldTransform &first = transform.fieldTransforms[0];
  127. XCTAssertEqual(first.path(), FieldPath({"foo"}));
  128. {
  129. std::vector<FSTFieldValue *> expectedElements{FSTTestFieldValue(@"tag")};
  130. ArrayTransform expected(TransformOperation::Type::ArrayUnion, expectedElements);
  131. XCTAssertEqual(static_cast<const ArrayTransform &>(first.transformation()), expected);
  132. }
  133. const FieldTransform &second = transform.fieldTransforms[1];
  134. XCTAssertEqual(second.path(), FieldPath({"bar", "baz"}));
  135. {
  136. std::vector<FSTFieldValue *> expectedElements {
  137. FSTTestFieldValue(@YES), FSTTestFieldValue(@{@"nested" : @{@"a" : @[ @1, @2 ]}})
  138. };
  139. ArrayTransform expected(TransformOperation::Type::ArrayUnion, expectedElements);
  140. XCTAssertEqual(static_cast<const ArrayTransform &>(second.transformation()), expected);
  141. }
  142. }
  143. // NOTE: This is more a test of FSTUserDataConverter code than FSTMutation code but we don't have
  144. // unit tests for it currently. We could consider removing this test once we have integration tests.
  145. - (void)testCreateArrayRemoveTransform {
  146. FSTTransformMutation *transform = FSTTestTransformMutation(@"collection/key", @{
  147. @"foo" : [FIRFieldValue fieldValueForArrayRemove:@[ @"tag" ]],
  148. });
  149. XCTAssertEqual(transform.fieldTransforms.size(), 1);
  150. const FieldTransform &first = transform.fieldTransforms[0];
  151. XCTAssertEqual(first.path(), FieldPath({"foo"}));
  152. {
  153. std::vector<FSTFieldValue *> expectedElements{FSTTestFieldValue(@"tag")};
  154. const ArrayTransform expected(TransformOperation::Type::ArrayRemove, expectedElements);
  155. XCTAssertEqual(static_cast<const ArrayTransform &>(first.transformation()), expected);
  156. }
  157. }
  158. - (void)testAppliesLocalArrayUnionTransformToMissingField {
  159. auto baseDoc = @{};
  160. auto transform = @{@"missing" : [FIRFieldValue fieldValueForArrayUnion:@[ @1, @2 ]]};
  161. auto expected = @{@"missing" : @[ @1, @2 ]};
  162. [self transformBaseDoc:baseDoc with:transform expecting:expected];
  163. }
  164. - (void)testAppliesLocalArrayUnionTransformToNonArrayField {
  165. auto baseDoc = @{@"non-array" : @42};
  166. auto transform = @{@"non-array" : [FIRFieldValue fieldValueForArrayUnion:@[ @1, @2 ]]};
  167. auto expected = @{@"non-array" : @[ @1, @2 ]};
  168. [self transformBaseDoc:baseDoc with:transform expecting:expected];
  169. }
  170. - (void)testAppliesLocalArrayUnionTransformWithNonExistingElements {
  171. auto baseDoc = @{@"array" : @[ @1, @3 ]};
  172. auto transform = @{@"array" : [FIRFieldValue fieldValueForArrayUnion:@[ @2, @4 ]]};
  173. auto expected = @{@"array" : @[ @1, @3, @2, @4 ]};
  174. [self transformBaseDoc:baseDoc with:transform expecting:expected];
  175. }
  176. - (void)testAppliesLocalArrayUnionTransformWithExistingElements {
  177. auto baseDoc = @{@"array" : @[ @1, @3 ]};
  178. auto transform = @{@"array" : [FIRFieldValue fieldValueForArrayUnion:@[ @1, @3 ]]};
  179. auto expected = @{@"array" : @[ @1, @3 ]};
  180. [self transformBaseDoc:baseDoc with:transform expecting:expected];
  181. }
  182. - (void)testAppliesLocalArrayUnionTransformWithDuplicateExistingElements {
  183. // Duplicate entries in your existing array should be preserved.
  184. auto baseDoc = @{@"array" : @[ @1, @2, @2, @3 ]};
  185. auto transform = @{@"array" : [FIRFieldValue fieldValueForArrayUnion:@[ @2 ]]};
  186. auto expected = @{@"array" : @[ @1, @2, @2, @3 ]};
  187. [self transformBaseDoc:baseDoc with:transform expecting:expected];
  188. }
  189. - (void)testAppliesLocalArrayUnionTransformWithDuplicateUnionElements {
  190. // Duplicate entries in your union array should only be added once.
  191. auto baseDoc = @{@"array" : @[ @1, @3 ]};
  192. auto transform = @{@"array" : [FIRFieldValue fieldValueForArrayUnion:@[ @2, @2 ]]};
  193. auto expected = @{@"array" : @[ @1, @3, @2 ]};
  194. [self transformBaseDoc:baseDoc with:transform expecting:expected];
  195. }
  196. - (void)testAppliesLocalArrayUnionTransformWithNonPrimitiveElements {
  197. // Union nested object values (one existing, one not).
  198. auto baseDoc = @{@"array" : @[ @1, @{@"a" : @"b"} ]};
  199. auto transform =
  200. @{@"array" : [FIRFieldValue fieldValueForArrayUnion:@[ @{@"a" : @"b"}, @{@"c" : @"d"} ]]};
  201. auto expected = @{@"array" : @[ @1, @{@"a" : @"b"}, @{@"c" : @"d"} ]};
  202. [self transformBaseDoc:baseDoc with:transform expecting:expected];
  203. }
  204. - (void)testAppliesLocalArrayUnionTransformWithPartiallyOverlappingElements {
  205. // Union objects that partially overlap an existing object.
  206. auto baseDoc = @{@"array" : @[ @1, @{@"a" : @"b", @"c" : @"d"} ]};
  207. auto transform =
  208. @{@"array" : [FIRFieldValue fieldValueForArrayUnion:@[ @{@"a" : @"b"}, @{@"c" : @"d"} ]]};
  209. auto expected =
  210. @{@"array" : @[ @1, @{@"a" : @"b", @"c" : @"d"}, @{@"a" : @"b"}, @{@"c" : @"d"} ]};
  211. [self transformBaseDoc:baseDoc with:transform expecting:expected];
  212. }
  213. - (void)testAppliesLocalArrayRemoveTransformToMissingField {
  214. auto baseDoc = @{};
  215. auto transform = @{@"missing" : [FIRFieldValue fieldValueForArrayRemove:@[ @1, @2 ]]};
  216. auto expected = @{@"missing" : @[]};
  217. [self transformBaseDoc:baseDoc with:transform expecting:expected];
  218. }
  219. - (void)testAppliesLocalArrayRemoveTransformToNonArrayField {
  220. auto baseDoc = @{@"non-array" : @42};
  221. auto transform = @{@"non-array" : [FIRFieldValue fieldValueForArrayRemove:@[ @1, @2 ]]};
  222. auto expected = @{@"non-array" : @[]};
  223. [self transformBaseDoc:baseDoc with:transform expecting:expected];
  224. }
  225. - (void)testAppliesLocalArrayRemoveTransformWithNonExistingElements {
  226. auto baseDoc = @{@"array" : @[ @1, @3 ]};
  227. auto transform = @{@"array" : [FIRFieldValue fieldValueForArrayRemove:@[ @2, @4 ]]};
  228. auto expected = @{@"array" : @[ @1, @3 ]};
  229. [self transformBaseDoc:baseDoc with:transform expecting:expected];
  230. }
  231. - (void)testAppliesLocalArrayRemoveTransformWithExistingElements {
  232. auto baseDoc = @{@"array" : @[ @1, @2, @3, @4 ]};
  233. auto transform = @{@"array" : [FIRFieldValue fieldValueForArrayRemove:@[ @1, @3 ]]};
  234. auto expected = @{@"array" : @[ @2, @4 ]};
  235. [self transformBaseDoc:baseDoc with:transform expecting:expected];
  236. }
  237. - (void)testAppliesLocalArrayRemoveTransformWithNonPrimitiveElements {
  238. // Remove nested object values (one existing, one not).
  239. auto baseDoc = @{@"array" : @[ @1, @{@"a" : @"b"} ]};
  240. auto transform =
  241. @{@"array" : [FIRFieldValue fieldValueForArrayRemove:@[ @{@"a" : @"b"}, @{@"c" : @"d"} ]]};
  242. auto expected = @{@"array" : @[ @1 ]};
  243. [self transformBaseDoc:baseDoc with:transform expecting:expected];
  244. }
  245. // Helper to test a particular transform scenario.
  246. - (void)transformBaseDoc:(NSDictionary<NSString *, id> *)baseData
  247. with:(NSDictionary<NSString *, id> *)transformData
  248. expecting:(NSDictionary<NSString *, id> *)expectedData {
  249. FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, baseData, NO);
  250. FSTMutation *transform = FSTTestTransformMutation(@"collection/key", transformData);
  251. FSTMaybeDocument *transformedDoc =
  252. [transform applyToLocalDocument:baseDoc baseDocument:baseDoc localWriteTime:_timestamp];
  253. FSTDocument *expectedDoc = [FSTDocument documentWithData:FSTTestObjectValue(expectedData)
  254. key:FSTTestDocKey(@"collection/key")
  255. version:testutil::Version(0)
  256. hasLocalMutations:YES];
  257. XCTAssertEqualObjects(transformedDoc, expectedDoc);
  258. }
  259. - (void)testAppliesServerAckedServerTimestampTransformToDocuments {
  260. NSDictionary *docData = @{@"foo" : @{@"bar" : @"bar-value"}, @"baz" : @"baz-value"};
  261. FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, NO);
  262. FSTMutation *transform = FSTTestTransformMutation(
  263. @"collection/key", @{@"foo.bar" : [FIRFieldValue fieldValueForServerTimestamp]});
  264. FSTMutationResult *mutationResult = [[FSTMutationResult alloc]
  265. initWithVersion:testutil::Version(1)
  266. transformResults:@[ [FSTTimestampValue timestampValue:_timestamp] ]];
  267. FSTMaybeDocument *transformedDoc =
  268. [transform applyToRemoteDocument:baseDoc mutationResult:mutationResult];
  269. NSDictionary *expectedData = @{@"foo" : @{@"bar" : _timestamp.dateValue}, @"baz" : @"baz-value"};
  270. XCTAssertEqualObjects(transformedDoc, FSTTestDoc("collection/key", 0, expectedData, NO));
  271. }
  272. - (void)testAppliesServerAckedArrayTransformsToDocuments {
  273. NSDictionary *docData = @{@"array_1" : @[ @1, @2 ], @"array_2" : @[ @"a", @"b" ]};
  274. FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, NO);
  275. FSTMutation *transform = FSTTestTransformMutation(@"collection/key", @{
  276. @"array_1" : [FIRFieldValue fieldValueForArrayUnion:@[ @2, @3 ]],
  277. @"array_2" : [FIRFieldValue fieldValueForArrayRemove:@[ @"a", @"c" ]]
  278. });
  279. // Server just sends null transform results for array operations.
  280. FSTMutationResult *mutationResult = [[FSTMutationResult alloc]
  281. initWithVersion:testutil::Version(1)
  282. transformResults:@[ [FSTNullValue nullValue], [FSTNullValue nullValue] ]];
  283. FSTMaybeDocument *transformedDoc =
  284. [transform applyToRemoteDocument:baseDoc mutationResult:mutationResult];
  285. NSDictionary *expectedData = @{@"array_1" : @[ @1, @2, @3 ], @"array_2" : @[ @"b" ]};
  286. XCTAssertEqualObjects(transformedDoc, FSTTestDoc("collection/key", 0, expectedData, NO));
  287. }
  288. - (void)testDeleteDeletes {
  289. NSDictionary *docData = @{@"foo" : @"bar"};
  290. FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, NO);
  291. FSTMutation *mutation = FSTTestDeleteMutation(@"collection/key");
  292. FSTMaybeDocument *result =
  293. [mutation applyToLocalDocument:baseDoc baseDocument:baseDoc localWriteTime:_timestamp];
  294. XCTAssertEqualObjects(result, FSTTestDeletedDoc("collection/key", 0));
  295. }
  296. - (void)testSetWithMutationResult {
  297. NSDictionary *docData = @{@"foo" : @"bar"};
  298. FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, NO);
  299. FSTMutation *set = FSTTestSetMutation(@"collection/key", @{@"foo" : @"new-bar"});
  300. FSTMutationResult *mutationResult =
  301. [[FSTMutationResult alloc] initWithVersion:testutil::Version(4) transformResults:nil];
  302. FSTMaybeDocument *setDoc = [set applyToRemoteDocument:baseDoc mutationResult:mutationResult];
  303. NSDictionary *expectedData = @{@"foo" : @"new-bar"};
  304. XCTAssertEqualObjects(setDoc, FSTTestDoc("collection/key", 0, expectedData, NO));
  305. }
  306. - (void)testPatchWithMutationResult {
  307. NSDictionary *docData = @{@"foo" : @"bar"};
  308. FSTDocument *baseDoc = FSTTestDoc("collection/key", 0, docData, NO);
  309. FSTMutation *patch = FSTTestPatchMutation("collection/key", @{@"foo" : @"new-bar"}, {});
  310. FSTMutationResult *mutationResult =
  311. [[FSTMutationResult alloc] initWithVersion:testutil::Version(4) transformResults:nil];
  312. FSTMaybeDocument *patchedDoc =
  313. [patch applyToRemoteDocument:baseDoc mutationResult:mutationResult];
  314. NSDictionary *expectedData = @{@"foo" : @"new-bar"};
  315. XCTAssertEqualObjects(patchedDoc, FSTTestDoc("collection/key", 0, expectedData, NO));
  316. }
  317. #define ASSERT_VERSION_TRANSITION(mutation, base, expected) \
  318. do { \
  319. FSTMutationResult *mutationResult = \
  320. [[FSTMutationResult alloc] initWithVersion:testutil::Version(0) transformResults:nil]; \
  321. FSTMaybeDocument *actual = \
  322. [mutation applyToRemoteDocument:base mutationResult:mutationResult]; \
  323. XCTAssertEqualObjects(actual, expected); \
  324. } while (0);
  325. /**
  326. * Tests the transition table documented in FSTMutation.h.
  327. */
  328. - (void)testTransitions {
  329. FSTDocument *docV0 = FSTTestDoc("collection/key", 0, @{}, NO);
  330. FSTDeletedDocument *deletedV0 = FSTTestDeletedDoc("collection/key", 0);
  331. FSTDocument *docV3 = FSTTestDoc("collection/key", 3, @{}, NO);
  332. FSTDeletedDocument *deletedV3 = FSTTestDeletedDoc("collection/key", 3);
  333. FSTMutation *setMutation = FSTTestSetMutation(@"collection/key", @{});
  334. FSTMutation *patchMutation = FSTTestPatchMutation("collection/key", {}, {});
  335. FSTMutation *deleteMutation = FSTTestDeleteMutation(@"collection/key");
  336. ASSERT_VERSION_TRANSITION(setMutation, docV3, docV3);
  337. ASSERT_VERSION_TRANSITION(setMutation, deletedV3, docV0);
  338. ASSERT_VERSION_TRANSITION(setMutation, nil, docV0);
  339. ASSERT_VERSION_TRANSITION(patchMutation, docV3, docV3);
  340. ASSERT_VERSION_TRANSITION(patchMutation, deletedV3, deletedV3);
  341. ASSERT_VERSION_TRANSITION(patchMutation, nil, nil);
  342. ASSERT_VERSION_TRANSITION(deleteMutation, docV3, deletedV0);
  343. ASSERT_VERSION_TRANSITION(deleteMutation, deletedV3, deletedV0);
  344. ASSERT_VERSION_TRANSITION(deleteMutation, nil, deletedV0);
  345. }
  346. #undef ASSERT_TRANSITION
  347. @end