FSTFieldValueTests.mm 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  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/FSTFieldValue.h"
  17. #import <FirebaseFirestore/FIRGeoPoint.h>
  18. #import <FirebaseFirestore/FIRTimestamp.h>
  19. #import <XCTest/XCTest.h>
  20. #import "Firestore/Source/API/FIRFirestore+Internal.h"
  21. #import "Firestore/Source/API/FSTUserDataConverter.h"
  22. #import "Firestore/Example/Tests/API/FSTAPIHelpers.h"
  23. #import "Firestore/Example/Tests/Util/FSTHelpers.h"
  24. #include "Firestore/core/include/firebase/firestore/geo_point.h"
  25. #include "Firestore/core/src/firebase/firestore/model/database_id.h"
  26. #include "Firestore/core/src/firebase/firestore/model/field_value.h"
  27. #include "Firestore/core/src/firebase/firestore/util/string_apple.h"
  28. #include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
  29. namespace testutil = firebase::firestore::testutil;
  30. namespace util = firebase::firestore::util;
  31. using firebase::firestore::GeoPoint;
  32. using firebase::firestore::model::DatabaseId;
  33. using firebase::firestore::model::FieldValue;
  34. /** Helper to wrap the values in a set of equality groups using FSTTestFieldValue(). */
  35. NSArray *FSTWrapGroups(NSArray *groups) {
  36. NSMutableArray *wrapped = [NSMutableArray array];
  37. for (NSArray<id> *group in groups) {
  38. NSMutableArray *wrappedGroup = [NSMutableArray array];
  39. for (id value in group) {
  40. FSTFieldValue *wrappedValue;
  41. // Server Timestamp values can't be parsed directly, so we have a couple predefined sentinel
  42. // strings that can be used instead.
  43. if ([value isEqual:@"server-timestamp-1"]) {
  44. wrappedValue = [FSTServerTimestampValue
  45. serverTimestampValueWithLocalWriteTime:FSTTestTimestamp(2016, 5, 20, 10, 20, 0)
  46. previousValue:nil];
  47. } else if ([value isEqual:@"server-timestamp-2"]) {
  48. wrappedValue = [FSTServerTimestampValue
  49. serverTimestampValueWithLocalWriteTime:FSTTestTimestamp(2016, 10, 21, 15, 32, 0)
  50. previousValue:nil];
  51. } else if ([value isKindOfClass:[FSTDocumentKeyReference class]]) {
  52. // We directly convert these here so that the databaseIDs can be different.
  53. FSTDocumentKeyReference *reference = (FSTDocumentKeyReference *)value;
  54. wrappedValue =
  55. [FSTReferenceValue referenceValue:[FSTDocumentKey keyWithDocumentKey:reference.key]
  56. databaseID:reference.databaseID];
  57. } else {
  58. wrappedValue = FSTTestFieldValue(value);
  59. }
  60. [wrappedGroup addObject:wrappedValue];
  61. }
  62. [wrapped addObject:wrappedGroup];
  63. }
  64. return wrapped;
  65. }
  66. @interface FSTFieldValueTests : XCTestCase
  67. @end
  68. @implementation FSTFieldValueTests {
  69. NSDate *date1;
  70. NSDate *date2;
  71. }
  72. - (void)setUp {
  73. [super setUp];
  74. // Create a couple date objects for use in tests.
  75. date1 = FSTTestDate(2016, 5, 20, 10, 20, 0);
  76. date2 = FSTTestDate(2016, 10, 21, 15, 32, 0);
  77. }
  78. union DoubleBits {
  79. double d;
  80. uint64_t bits;
  81. };
  82. - (void)testNormalizesNaNs {
  83. // NOTE: With v1 query semantics, it's no longer as important that our NaN representation matches
  84. // the backend, since all NaNs are defined to sort as equal, but we preserve the normalization and
  85. // this test regardless for now.
  86. // We use a canonical NaN bit pattern that's common for both Java and Objective-C. Specifically:
  87. // - sign: 0
  88. // - exponent: 11 bits, all 1
  89. // - significand: 52 bits, MSB=1, rest=0
  90. //
  91. // This matches the Firestore backend which uses Java's Double.doubleToLongBits which is defined
  92. // to normalize all NaNs to this value.
  93. union DoubleBits canonical = {.bits = 0x7ff8000000000000ULL};
  94. // IEEE 754 specifies that NaN isn't equal to itself.
  95. XCTAssertTrue(isnan(canonical.d));
  96. XCTAssertEqual(canonical.bits, canonical.bits);
  97. XCTAssertNotEqual(canonical.d, canonical.d);
  98. // All permutations of the 51 other non-MSB significand bits are also NaNs.
  99. union DoubleBits alternate = {.bits = 0x7fff000000000000ULL};
  100. XCTAssertTrue(isnan(alternate.d));
  101. XCTAssertNotEqual(alternate.bits, canonical.bits);
  102. XCTAssertNotEqual(alternate.d, canonical.d);
  103. // Even though at the C-level assignment preserves non-canonical NaNs, NSNumber normalizes all
  104. // NaNs to single shared instance, kCFNumberNaN. That NaN has no public definition for its value
  105. // but it happens to match what we need.
  106. union DoubleBits normalized = {.d = [[NSNumber numberWithDouble:alternate.d] doubleValue]};
  107. XCTAssertEqual(normalized.bits, canonical.bits);
  108. // Ensure we get the same normalization behavior (currently implemented explicitly by checking
  109. // for isnan() and then explicitly assigning NAN).
  110. union DoubleBits result;
  111. result.d = FieldValue::FromDouble(canonical.d).double_value();
  112. XCTAssertEqual(result.bits, canonical.bits);
  113. result.d = FieldValue::FromDouble(alternate.d).double_value();
  114. XCTAssertEqual(result.bits, canonical.bits);
  115. // A NaN that's canonical except it has the sign bit set (would be negative if signs mattered)
  116. union DoubleBits negative = {.bits = 0xfff8000000000000ULL};
  117. result.d = FieldValue::FromDouble(negative.d).double_value();
  118. XCTAssertTrue(isnan(negative.d));
  119. XCTAssertEqual(result.bits, canonical.bits);
  120. // A signaling NaN with significand where MSB is 0, and some non-MSB bit is one.
  121. union DoubleBits signaling = {.bits = 0xfff4000000000000ULL};
  122. XCTAssertTrue(isnan(signaling.d));
  123. result.d = FieldValue::FromDouble(signaling.d).double_value();
  124. XCTAssertEqual(result.bits, canonical.bits);
  125. }
  126. - (void)testZeros {
  127. // Floating point numbers have an explicit sign bit so it's possible to end up with negative
  128. // zero as a distinct value from positive zero.
  129. union DoubleBits zero = {.d = 0.0};
  130. union DoubleBits negativeZero = {.d = -0.0};
  131. // IEEE 754 requires these two zeros to compare equal.
  132. XCTAssertNotEqual(zero.bits, negativeZero.bits);
  133. XCTAssertEqual(zero.d, negativeZero.d);
  134. // NSNumber preserves the negative zero value but compares equal according to IEEE 754.
  135. union DoubleBits normalized = {.d = [[NSNumber numberWithDouble:negativeZero.d] doubleValue]};
  136. XCTAssertEqual(normalized.bits, negativeZero.bits);
  137. XCTAssertEqualObjects([NSNumber numberWithDouble:0.0], [NSNumber numberWithDouble:-0.0]);
  138. // FieldValue::FromDouble preserves positive/negative zero
  139. union DoubleBits result;
  140. result.d = FieldValue::FromDouble(zero.d).double_value();
  141. XCTAssertEqual(result.bits, zero.bits);
  142. result.d = FieldValue::FromDouble(negativeZero.d).double_value();
  143. XCTAssertEqual(result.bits, negativeZero.bits);
  144. // ... but compares positive/negative zero as unequal, compatibly with Firestore.
  145. XCTAssertNotEqual(FieldValue::FromDouble(0.0), FieldValue::FromDouble(-0.0));
  146. }
  147. - (void)testExtractsFields {
  148. FSTObjectValue *obj = FSTTestObjectValue(@{@"foo" : @{@"a" : @YES, @"b" : @"string"}});
  149. FSTAssertIsKindOfClass(obj, FSTObjectValue);
  150. FSTAssertIsKindOfClass([obj valueForPath:testutil::Field("foo")], FSTObjectValue);
  151. XCTAssertEqualObjects([obj valueForPath:testutil::Field("foo.a")], FieldValue::True().Wrap());
  152. XCTAssertEqualObjects([obj valueForPath:testutil::Field("foo.b")],
  153. FieldValue::FromString("string").Wrap());
  154. XCTAssertNil([obj valueForPath:testutil::Field("foo.a.b")]);
  155. XCTAssertNil([obj valueForPath:testutil::Field("bar")]);
  156. XCTAssertNil([obj valueForPath:testutil::Field("bar.a")]);
  157. }
  158. - (void)testOverwritesExistingFields {
  159. FSTObjectValue *old = FSTTestObjectValue(@{@"a" : @"old"});
  160. FSTObjectValue *mod = [old objectBySettingValue:FSTTestFieldValue(@"mod")
  161. forPath:testutil::Field("a")];
  162. // Should return a new object, leaving the old one unmodified.
  163. XCTAssertNotEqual(old, mod);
  164. XCTAssertEqualObjects(old, FSTTestFieldValue(@{@"a" : @"old"}));
  165. XCTAssertEqualObjects(mod, FSTTestFieldValue(@{@"a" : @"mod"}));
  166. }
  167. - (void)testAddsNewFields {
  168. FSTObjectValue *empty = [FSTObjectValue objectValue];
  169. FSTObjectValue *mod = [empty objectBySettingValue:FSTTestFieldValue(@"mod")
  170. forPath:testutil::Field("a")];
  171. XCTAssertNotEqual(empty, mod);
  172. XCTAssertEqualObjects(empty, FSTTestFieldValue(@{}));
  173. XCTAssertEqualObjects(mod, FSTTestFieldValue(@{@"a" : @"mod"}));
  174. FSTObjectValue *old = mod;
  175. mod = [old objectBySettingValue:FSTTestFieldValue(@1) forPath:testutil::Field("b")];
  176. XCTAssertNotEqual(old, mod);
  177. XCTAssertEqualObjects(old, FSTTestFieldValue(@{@"a" : @"mod"}));
  178. XCTAssertEqualObjects(mod, FSTTestFieldValue(@{@"a" : @"mod", @"b" : @1}));
  179. }
  180. - (void)testImplicitlyCreatesObjects {
  181. FSTObjectValue *old = FSTTestObjectValue(@{@"a" : @"old"});
  182. FSTObjectValue *mod = [old objectBySettingValue:FSTTestFieldValue(@"mod")
  183. forPath:testutil::Field("b.c.d")];
  184. XCTAssertNotEqual(old, mod);
  185. XCTAssertEqualObjects(old, FSTTestFieldValue(@{@"a" : @"old"}));
  186. XCTAssertEqualObjects(mod,
  187. FSTTestFieldValue(@{@"a" : @"old", @"b" : @{@"c" : @{@"d" : @"mod"}}}));
  188. }
  189. - (void)testCanOverwritePrimitivesWithObjects {
  190. FSTObjectValue *old = FSTTestObjectValue(@{@"a" : @{@"b" : @"old"}});
  191. FSTObjectValue *mod = [old objectBySettingValue:FSTTestFieldValue(@{@"b" : @"mod"})
  192. forPath:testutil::Field("a")];
  193. XCTAssertNotEqual(old, mod);
  194. XCTAssertEqualObjects(old, FSTTestFieldValue(@{@"a" : @{@"b" : @"old"}}));
  195. XCTAssertEqualObjects(mod, FSTTestFieldValue(@{@"a" : @{@"b" : @"mod"}}));
  196. }
  197. - (void)testAddsToNestedObjects {
  198. FSTObjectValue *old = FSTTestObjectValue(@{@"a" : @{@"b" : @"old"}});
  199. FSTObjectValue *mod = [old objectBySettingValue:FSTTestFieldValue(@"mod")
  200. forPath:testutil::Field("a.c")];
  201. XCTAssertNotEqual(old, mod);
  202. XCTAssertEqualObjects(old, FSTTestFieldValue(@{@"a" : @{@"b" : @"old"}}));
  203. XCTAssertEqualObjects(mod, FSTTestFieldValue(@{@"a" : @{@"b" : @"old", @"c" : @"mod"}}));
  204. }
  205. - (void)testDeletesKeys {
  206. FSTObjectValue *old = FSTTestObjectValue(@{@"a" : @1, @"b" : @2});
  207. FSTObjectValue *mod = [old objectByDeletingPath:testutil::Field("a")];
  208. XCTAssertNotEqual(old, mod);
  209. XCTAssertEqualObjects(old, FSTTestFieldValue(@{@"a" : @1, @"b" : @2}));
  210. XCTAssertEqualObjects(mod, FSTTestFieldValue(@{@"b" : @2}));
  211. FSTObjectValue *empty = [mod objectByDeletingPath:testutil::Field("b")];
  212. XCTAssertNotEqual(mod, empty);
  213. XCTAssertEqualObjects(mod, FSTTestFieldValue(@{@"b" : @2}));
  214. XCTAssertEqualObjects(empty, FSTTestFieldValue(@{}));
  215. }
  216. - (void)testDeletesHandleMissingKeys {
  217. FSTObjectValue *old = FSTTestObjectValue(@{@"a" : @{@"b" : @1, @"c" : @2}});
  218. FSTObjectValue *mod = [old objectByDeletingPath:testutil::Field("b")];
  219. XCTAssertEqualObjects(old, mod);
  220. XCTAssertEqualObjects(mod, FSTTestFieldValue(@{@"a" : @{@"b" : @1, @"c" : @2}}));
  221. mod = [old objectByDeletingPath:testutil::Field("a.d")];
  222. XCTAssertEqualObjects(old, mod);
  223. XCTAssertEqualObjects(mod, FSTTestFieldValue(@{@"a" : @{@"b" : @1, @"c" : @2}}));
  224. mod = [old objectByDeletingPath:testutil::Field("a.b.c")];
  225. XCTAssertEqualObjects(old, mod);
  226. XCTAssertEqualObjects(mod, FSTTestFieldValue(@{@"a" : @{@"b" : @1, @"c" : @2}}));
  227. }
  228. - (void)testDeletesNestedKeys {
  229. FSTObjectValue *old = FSTTestObjectValue(@{@"a" : @{@"b" : @1, @"c" : @{@"d" : @2, @"e" : @3}}});
  230. FSTObjectValue *mod = [old objectByDeletingPath:testutil::Field("a.c.d")];
  231. XCTAssertNotEqual(old, mod);
  232. XCTAssertEqualObjects(old,
  233. FSTTestFieldValue(@{@"a" : @{@"b" : @1, @"c" : @{@"d" : @2, @"e" : @3}}}));
  234. XCTAssertEqualObjects(mod, FSTTestFieldValue(@{@"a" : @{@"b" : @1, @"c" : @{@"e" : @3}}}));
  235. old = mod;
  236. mod = [old objectByDeletingPath:testutil::Field("a.c")];
  237. XCTAssertEqualObjects(old, FSTTestFieldValue(@{@"a" : @{@"b" : @1, @"c" : @{@"e" : @3}}}));
  238. XCTAssertEqualObjects(mod, FSTTestFieldValue(@{@"a" : @{@"b" : @1}}));
  239. old = mod;
  240. mod = [old objectByDeletingPath:testutil::Field("a")];
  241. XCTAssertEqualObjects(old, FSTTestFieldValue(@{@"a" : @{@"b" : @1}}));
  242. XCTAssertEqualObjects(mod, FSTTestFieldValue(@{}));
  243. }
  244. - (void)testValueEquality {
  245. DatabaseId database_id("project");
  246. NSArray *groups = @[
  247. @[ FSTTestFieldValue(@YES), FieldValue::True().Wrap() ],
  248. @[ FSTTestFieldValue(@NO), FieldValue::False().Wrap() ],
  249. @[ FSTTestFieldValue([NSNull null]), FieldValue::Null().Wrap() ],
  250. @[ FSTTestFieldValue(@(0.0 / 0.0)), FSTTestFieldValue(@(NAN)), FieldValue::Nan().Wrap() ],
  251. // -0.0 and 0.0 compare: the same (but are not isEqual:)
  252. @[ FSTTestFieldValue(@(-0.0)) ], @[ FSTTestFieldValue(@0.0) ],
  253. @[ FSTTestFieldValue(@1), FSTTestFieldValue(@1LL), FieldValue::FromInteger(1LL).Wrap() ],
  254. // double and unit64_t values can compare: the same (but won't be isEqual:)
  255. @[ FSTTestFieldValue(@1.0), FieldValue::FromDouble(1.0).Wrap() ],
  256. @[ FSTTestFieldValue(@1.1), FieldValue::FromDouble(1.1).Wrap() ],
  257. @[
  258. FSTTestFieldValue(FSTTestData(0, 1, 2, -1)), [FSTBlobValue blobValue:FSTTestData(0, 1, 2, -1)]
  259. ],
  260. @[ FSTTestFieldValue(FSTTestData(0, 1, -1)) ],
  261. @[ FSTTestFieldValue(@"string"), FieldValue::FromString("string").Wrap() ],
  262. @[ FSTTestFieldValue(@"strin") ],
  263. @[ FSTTestFieldValue(@"e\u0301b") ], // latin small letter e + combining acute accent
  264. @[ FSTTestFieldValue(@"\u00e9a") ], // latin small letter e with acute accent
  265. @[
  266. FSTTestFieldValue(date1),
  267. [FSTTimestampValue timestampValue:[FIRTimestamp timestampWithDate:date1]]
  268. ],
  269. @[ FSTTestFieldValue(date2) ],
  270. @[
  271. // NOTE: ServerTimestampValues can't be parsed via FSTTestFieldValue().
  272. [FSTServerTimestampValue
  273. serverTimestampValueWithLocalWriteTime:[FIRTimestamp timestampWithDate:date1]
  274. previousValue:nil],
  275. [FSTServerTimestampValue
  276. serverTimestampValueWithLocalWriteTime:[FIRTimestamp timestampWithDate:date1]
  277. previousValue:nil]
  278. ],
  279. @[ [FSTServerTimestampValue
  280. serverTimestampValueWithLocalWriteTime:[FIRTimestamp timestampWithDate:date2]
  281. previousValue:nil] ],
  282. @[
  283. FSTTestFieldValue(FSTTestGeoPoint(0, 1)),
  284. FieldValue::FromGeoPoint(GeoPoint(0, 1)).Wrap()
  285. ],
  286. @[ FSTTestFieldValue(FSTTestGeoPoint(1, 0)) ],
  287. @[
  288. [FSTReferenceValue
  289. referenceValue:[FSTDocumentKey keyWithDocumentKey:FSTTestDocKey(@"coll/doc1")]
  290. databaseID:database_id],
  291. FSTTestFieldValue(FSTTestRef("project", DatabaseId::kDefault, @"coll/doc1"))
  292. ],
  293. @[ FSTTestRef("project", "(default)", @"coll/doc2") ],
  294. @[ FSTTestFieldValue(@[ @"foo", @"bar" ]), FSTTestFieldValue(@[ @"foo", @"bar" ]) ],
  295. @[ FSTTestFieldValue(@[ @"foo", @"bar", @"baz" ]) ], @[ FSTTestFieldValue(@[ @"foo" ]) ],
  296. @[
  297. FSTTestFieldValue(@{@"bar" : @1, @"foo" : @2}), FSTTestFieldValue(@{@"foo" : @2, @"bar" : @1})
  298. ],
  299. @[ FSTTestFieldValue(@{@"bar" : @2, @"foo" : @1}) ],
  300. @[ FSTTestFieldValue(@{@"bar" : @1, @"foo" : @1}) ], @[ FSTTestFieldValue(@{@"foo" : @1}) ]
  301. ];
  302. FSTAssertEqualityGroups(groups);
  303. }
  304. - (void)testValueOrdering {
  305. NSArray *groups = @[
  306. // null first
  307. @[ [NSNull null] ],
  308. // booleans
  309. @[ @NO ], @[ @YES ],
  310. // numbers
  311. @[ @(0.0 / 0.0) ], @[ @(-INFINITY) ], @[ @(-DBL_MAX) ], @[ @(LLONG_MIN) ], @[ @(-1.1) ],
  312. @[ @(-1.0), @(-1LL) ], // longs and doubles compare the same
  313. @[ @(-DBL_MIN) ],
  314. @[ @(-0x1.0p-1074) ], // negative smallest subnormal
  315. @[ @(-0.0), @(0.0), @(0LL) ], // zeros all compare the same
  316. @[ @(0x1.0p-1074) ], // positive smallest subnormal
  317. @[ @(DBL_MIN) ], @[ @1.0, @1LL ], // longs and doubles compare the same
  318. @[ @1.1 ], @[ @(LLONG_MAX) ], @[ @(DBL_MAX) ], @[ @(INFINITY) ],
  319. // timestamps
  320. @[ date1 ], @[ date2 ],
  321. // server timestamps come after all concrete timestamps.
  322. // NOTE: server timestamps can't be parsed directly, so we have special sentinel strings (see
  323. // FSTWrapGroups()).
  324. @[ @"server-timestamp-1" ], @[ @"server-timestamp-2" ],
  325. // strings
  326. @[ @"" ], @[ @"\000\ud7ff\ue000\uffff" ], @[ @"(╯°□°)╯︵ ┻━┻" ], @[ @"a" ], @[ @"abc def" ],
  327. @[ @"e\u0301b" ], // latin small letter e + combining acute accent + latin small letter b
  328. @[ @"æ" ],
  329. @[ @"\u00e9a" ], // latin small letter e with acute accent + latin small letter a
  330. // blobs
  331. @[ FSTTestData(-1) ], @[ FSTTestData(0, -1) ], @[ FSTTestData(0, 1, 2, 3, 4, -1) ],
  332. @[ FSTTestData(0, 1, 2, 4, 3, -1) ], @[ FSTTestData(255, -1) ],
  333. // resource names
  334. @[ FSTTestRef("p1", "d1", @"c1/doc1") ], @[ FSTTestRef("p1", "d1", @"c1/doc2") ],
  335. @[ FSTTestRef("p1", "d1", @"c10/doc1") ], @[ FSTTestRef("p1", "d1", @"c2/doc1") ],
  336. @[ FSTTestRef("p1", "d2", @"c1/doc1") ], @[ FSTTestRef("p2", "d1", @"c1/doc1") ],
  337. // Geo points
  338. @[ FSTTestGeoPoint(-90, -180) ], @[ FSTTestGeoPoint(-90, 0) ], @[ FSTTestGeoPoint(-90, 180) ],
  339. @[ FSTTestGeoPoint(0, -180) ], @[ FSTTestGeoPoint(0, 0) ], @[ FSTTestGeoPoint(0, 180) ],
  340. @[ FSTTestGeoPoint(1, -180) ], @[ FSTTestGeoPoint(1, 0) ], @[ FSTTestGeoPoint(1, 180) ],
  341. @[ FSTTestGeoPoint(90, -180) ], @[ FSTTestGeoPoint(90, 0) ], @[ FSTTestGeoPoint(90, 180) ],
  342. // Arrays
  343. @[ @[] ], @[ @[ @"bar" ] ], @[ @[ @"foo" ] ], @[ @[ @"foo", @1 ] ], @[ @[ @"foo", @2 ] ],
  344. @[ @[ @"foo", @"0" ] ],
  345. // Objects
  346. @[ @{@"bar" : @0} ], @[ @{@"bar" : @0, @"foo" : @1} ], @[ @{@"foo" : @1} ], @[ @{@"foo" : @2} ],
  347. @[ @{@"foo" : @"0"} ]
  348. ];
  349. NSArray *wrapped = FSTWrapGroups(groups);
  350. FSTAssertComparisons(wrapped);
  351. }
  352. @end