mirror of
https://github.com/firebase/firebase-ios-sdk
synced 2026-11-10 22:45:59 +05:45
* add a new property `timestampsInSnapshotsEnabled` to `FirestoreSettings`, `false` by default; * add a verbose warning message urging users to opt into the new behavior; * set `timestampsInSnapshotsEnabled` to true in the integration tests to reduce the verbose console spam during the test run and make sure the flag won't break anything once it's flipped.
585 lines
23 KiB
Plaintext
585 lines
23 KiB
Plaintext
/*
|
|
* 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/Source/Model/FSTFieldValue.h"
|
|
|
|
#import <FirebaseFirestore/FIRGeoPoint.h>
|
|
#import <FirebaseFirestore/FIRTimestamp.h>
|
|
#import <XCTest/XCTest.h>
|
|
|
|
#import "Firestore/Source/API/FIRFirestore+Internal.h"
|
|
#import "Firestore/Source/API/FSTUserDataConverter.h"
|
|
#import "Firestore/Source/Model/FSTFieldValue.h"
|
|
|
|
#import "Firestore/Example/Tests/API/FSTAPIHelpers.h"
|
|
#import "Firestore/Example/Tests/Util/FSTHelpers.h"
|
|
|
|
#include "Firestore/core/src/firebase/firestore/model/database_id.h"
|
|
#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
|
|
#include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
|
|
|
|
namespace testutil = firebase::firestore::testutil;
|
|
namespace util = firebase::firestore::util;
|
|
using firebase::firestore::model::DatabaseId;
|
|
|
|
/** Helper to wrap the values in a set of equality groups using FSTTestFieldValue(). */
|
|
NSArray *FSTWrapGroups(NSArray *groups) {
|
|
NSMutableArray *wrapped = [NSMutableArray array];
|
|
for (NSArray<id> *group in groups) {
|
|
NSMutableArray *wrappedGroup = [NSMutableArray array];
|
|
for (id value in group) {
|
|
FSTFieldValue *wrappedValue;
|
|
// Server Timestamp values can't be parsed directly, so we have a couple predefined sentinel
|
|
// strings that can be used instead.
|
|
if ([value isEqual:@"server-timestamp-1"]) {
|
|
wrappedValue = [FSTServerTimestampValue
|
|
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)
|
|
previousValue:nil];
|
|
} else if ([value isKindOfClass:[FSTDocumentKeyReference class]]) {
|
|
// We directly convert these here so that the databaseIDs can be different.
|
|
FSTDocumentKeyReference *reference = (FSTDocumentKeyReference *)value;
|
|
wrappedValue =
|
|
[FSTReferenceValue referenceValue:reference.key databaseID:reference.databaseID];
|
|
} else {
|
|
wrappedValue = FSTTestFieldValue(value);
|
|
}
|
|
[wrappedGroup addObject:wrappedValue];
|
|
}
|
|
[wrapped addObject:wrappedGroup];
|
|
}
|
|
return wrapped;
|
|
}
|
|
|
|
@interface FSTFieldValueTests : XCTestCase
|
|
@end
|
|
|
|
@implementation FSTFieldValueTests {
|
|
NSDate *date1;
|
|
NSDate *date2;
|
|
}
|
|
|
|
- (void)setUp {
|
|
[super setUp];
|
|
// Create a couple date objects for use in tests.
|
|
date1 = FSTTestDate(2016, 5, 20, 10, 20, 0);
|
|
date2 = FSTTestDate(2016, 10, 21, 15, 32, 0);
|
|
}
|
|
|
|
- (void)testWrapIntegers {
|
|
NSArray *values = @[
|
|
@(INT_MIN), @(-1), @0, @1, @2, @(UCHAR_MAX), @(INT_MAX), // Standard integers
|
|
@(LONG_MIN), @(LONG_MAX), @(LLONG_MIN), @(LLONG_MAX) // Larger values
|
|
];
|
|
for (id value in values) {
|
|
FSTFieldValue *wrapped = FSTTestFieldValue(value);
|
|
XCTAssertEqualObjects([wrapped class], [FSTIntegerValue class]);
|
|
XCTAssertEqualObjects([wrapped value], @([value longLongValue]));
|
|
}
|
|
}
|
|
|
|
- (void)testWrapsDoubles {
|
|
// Note that 0x1.0p-1074 is a hex floating point literal representing the minimum subnormal
|
|
// number: <https://en.wikipedia.org/wiki/Denormal_number>.
|
|
NSArray *values = @[
|
|
@(-INFINITY), @(-DBL_MAX), @(LLONG_MIN * -1.0), @(-1.1), @(-0x1.0p-1074), @(-0.0), @(0.0),
|
|
@(0x1.0p-1074), @(DBL_MIN), @(1.1), @(LLONG_MAX * 1.0), @(DBL_MAX), @(INFINITY)
|
|
];
|
|
for (id value in values) {
|
|
FSTFieldValue *wrapped = FSTTestFieldValue(value);
|
|
XCTAssertEqualObjects([wrapped class], [FSTDoubleValue class]);
|
|
XCTAssertEqualObjects([wrapped value], value);
|
|
}
|
|
}
|
|
|
|
- (void)testWrapsNilAndNSNull {
|
|
FSTNullValue *nullValue = [FSTNullValue nullValue];
|
|
XCTAssertEqual(FSTTestFieldValue(nil), nullValue);
|
|
XCTAssertEqual(FSTTestFieldValue([NSNull null]), nullValue);
|
|
XCTAssertEqual([nullValue value], [NSNull null]);
|
|
}
|
|
|
|
- (void)testWrapsBooleans {
|
|
NSArray *values = @[ @YES, @NO, [NSNumber numberWithChar:1], [NSNumber numberWithChar:0] ];
|
|
for (id value in values) {
|
|
FSTFieldValue *wrapped = FSTTestFieldValue(value);
|
|
XCTAssertEqualObjects([wrapped class], [FSTBooleanValue class]);
|
|
XCTAssertEqualObjects([wrapped value], value);
|
|
}
|
|
|
|
// Unsigned chars could conceivably be handled consistently with signed chars but on arm64 these
|
|
// end up being stored as signed shorts.
|
|
FSTFieldValue *wrapped = FSTTestFieldValue([NSNumber numberWithUnsignedChar:1]);
|
|
XCTAssertEqualObjects(wrapped, [FSTIntegerValue integerValue:1]);
|
|
}
|
|
|
|
union DoubleBits {
|
|
double d;
|
|
uint64_t bits;
|
|
};
|
|
|
|
- (void)testNormalizesNaNs {
|
|
// NOTE: With v1beta1 query semantics, it's no longer as important that our NaN representation
|
|
// matches the backend, since all NaNs are defined to sort as equal, but we preserve the
|
|
// normalization and this test regardless for now.
|
|
|
|
// We use a canonical NaN bit pattern that's common for both Java and Objective-C. Specifically:
|
|
// - sign: 0
|
|
// - exponent: 11 bits, all 1
|
|
// - significand: 52 bits, MSB=1, rest=0
|
|
//
|
|
// This matches the Firestore backend which uses Java's Double.doubleToLongBits which is defined
|
|
// to normalize all NaNs to this value.
|
|
union DoubleBits canonical = {.bits = 0x7ff8000000000000ULL};
|
|
|
|
// IEEE 754 specifies that NaN isn't equal to itself.
|
|
XCTAssertTrue(isnan(canonical.d));
|
|
XCTAssertEqual(canonical.bits, canonical.bits);
|
|
XCTAssertNotEqual(canonical.d, canonical.d);
|
|
|
|
// All permutations of the 51 other non-MSB significand bits are also NaNs.
|
|
union DoubleBits alternate = {.bits = 0x7fff000000000000ULL};
|
|
XCTAssertTrue(isnan(alternate.d));
|
|
XCTAssertNotEqual(alternate.bits, canonical.bits);
|
|
XCTAssertNotEqual(alternate.d, canonical.d);
|
|
|
|
// Even though at the C-level assignment preserves non-canonical NaNs, NSNumber normalizes all
|
|
// NaNs to single shared instance, kCFNumberNaN. That NaN has no public definition for its value
|
|
// but it happens to match what we need.
|
|
union DoubleBits normalized = {.d = [[NSNumber numberWithDouble:alternate.d] doubleValue]};
|
|
XCTAssertEqual(normalized.bits, canonical.bits);
|
|
|
|
// Ensure we get the same normalization behavior (currently implemented explicitly by checking
|
|
// for isnan() and then explicitly assigning NAN).
|
|
union DoubleBits result;
|
|
result.d = [[FSTDoubleValue doubleValue:canonical.d] internalValue];
|
|
XCTAssertEqual(result.bits, canonical.bits);
|
|
|
|
result.d = [[FSTDoubleValue doubleValue:alternate.d] internalValue];
|
|
XCTAssertEqual(result.bits, canonical.bits);
|
|
|
|
// A NaN that's canonical except it has the sign bit set (would be negative if signs mattered)
|
|
union DoubleBits negative = {.bits = 0xfff8000000000000ULL};
|
|
result.d = [[FSTDoubleValue doubleValue:negative.d] internalValue];
|
|
XCTAssertTrue(isnan(negative.d));
|
|
XCTAssertEqual(result.bits, canonical.bits);
|
|
|
|
// A signaling NaN with significand where MSB is 0, and some non-MSB bit is one.
|
|
union DoubleBits signaling = {.bits = 0xfff4000000000000ULL};
|
|
XCTAssertTrue(isnan(signaling.d));
|
|
result.d = [[FSTDoubleValue doubleValue:signaling.d] internalValue];
|
|
XCTAssertEqual(result.bits, canonical.bits);
|
|
}
|
|
|
|
- (void)testZeros {
|
|
// Floating point numbers have an explicit sign bit so it's possible to end up with negative
|
|
// zero as a distinct value from positive zero.
|
|
union DoubleBits zero = {.d = 0.0};
|
|
union DoubleBits negativeZero = {.d = -0.0};
|
|
|
|
// IEEE 754 requires these two zeros to compare equal.
|
|
XCTAssertNotEqual(zero.bits, negativeZero.bits);
|
|
XCTAssertEqual(zero.d, negativeZero.d);
|
|
|
|
// NSNumber preserves the negative zero value but compares equal according to IEEE 754.
|
|
union DoubleBits normalized = {.d = [[NSNumber numberWithDouble:negativeZero.d] doubleValue]};
|
|
XCTAssertEqual(normalized.bits, negativeZero.bits);
|
|
XCTAssertEqualObjects([NSNumber numberWithDouble:0.0], [NSNumber numberWithDouble:-0.0]);
|
|
|
|
// FSTDoubleValue preserves positive/negative zero
|
|
union DoubleBits result;
|
|
result.d = [[[FSTDoubleValue doubleValue:zero.d] value] doubleValue];
|
|
XCTAssertEqual(result.bits, zero.bits);
|
|
result.d = [[[FSTDoubleValue doubleValue:negativeZero.d] value] doubleValue];
|
|
XCTAssertEqual(result.bits, negativeZero.bits);
|
|
|
|
// ... but compares positive/negative zero as unequal, compatibly with Firestore.
|
|
XCTAssertNotEqualObjects([FSTDoubleValue doubleValue:0.0], [FSTDoubleValue doubleValue:-0.0]);
|
|
}
|
|
|
|
- (void)testWrapStrings {
|
|
NSArray *values = @[ @"", @"abc" ];
|
|
for (id value in values) {
|
|
FSTFieldValue *wrapped = FSTTestFieldValue(value);
|
|
XCTAssertEqualObjects([wrapped class], [FSTStringValue class]);
|
|
XCTAssertEqualObjects([wrapped value], value);
|
|
}
|
|
}
|
|
|
|
- (void)testWrapDates {
|
|
NSArray *values = @[ FSTTestDate(1900, 12, 1, 1, 20, 30), FSTTestDate(2017, 4, 24, 13, 20, 30) ];
|
|
for (id value in values) {
|
|
FSTFieldValue *wrapped = FSTTestFieldValue(value);
|
|
XCTAssertEqualObjects([wrapped class], [FSTTimestampValue class]);
|
|
XCTAssertEqualObjects([[wrapped value] class], [FIRTimestamp class]);
|
|
XCTAssertEqualObjects([wrapped value], [FIRTimestamp timestampWithDate:value]);
|
|
}
|
|
}
|
|
|
|
- (void)testWrapGeoPoints {
|
|
NSArray *values = @[ FSTTestGeoPoint(1.24, 4.56), FSTTestGeoPoint(-20, 100) ];
|
|
|
|
for (id value in values) {
|
|
FSTFieldValue *wrapped = FSTTestFieldValue(value);
|
|
XCTAssertEqualObjects([wrapped class], [FSTGeoPointValue class]);
|
|
XCTAssertEqualObjects([wrapped value], value);
|
|
}
|
|
}
|
|
|
|
- (void)testWrapBlobs {
|
|
NSArray *values = @[ FSTTestData(1, 2, 3), FSTTestData(1, 2) ];
|
|
for (id value in values) {
|
|
FSTFieldValue *wrapped = FSTTestFieldValue(value);
|
|
XCTAssertEqualObjects([wrapped class], [FSTBlobValue class]);
|
|
XCTAssertEqualObjects([wrapped value], value);
|
|
}
|
|
}
|
|
|
|
- (void)testWrapResourceNames {
|
|
NSArray *values = @[
|
|
FSTTestRef("project", DatabaseId::kDefault, @"foo/bar"),
|
|
FSTTestRef("project", DatabaseId::kDefault, @"foo/baz")
|
|
];
|
|
for (FSTDocumentKeyReference *value in values) {
|
|
FSTFieldValue *wrapped = FSTTestFieldValue(value);
|
|
XCTAssertEqualObjects([wrapped class], [FSTReferenceValue class]);
|
|
XCTAssertEqualObjects([wrapped value], value.key);
|
|
XCTAssertTrue(*((FSTReferenceValue *)wrapped).databaseID ==
|
|
*(const DatabaseId *)(value.databaseID));
|
|
}
|
|
}
|
|
|
|
- (void)testWrapsEmptyObjects {
|
|
XCTAssertEqualObjects(FSTTestFieldValue(@{}), [FSTObjectValue objectValue]);
|
|
}
|
|
|
|
- (void)testWrapsSimpleObjects {
|
|
FSTObjectValue *actual = FSTTestObjectValue(
|
|
@{ @"a" : @"foo",
|
|
@"b" : @(1L),
|
|
@"c" : @YES,
|
|
@"d" : [NSNull null] });
|
|
FSTObjectValue *expected = [[FSTObjectValue alloc] initWithDictionary:@{
|
|
@"a" : [FSTStringValue stringValue:@"foo"],
|
|
@"b" : [FSTIntegerValue integerValue:1LL],
|
|
@"c" : [FSTBooleanValue trueValue],
|
|
@"d" : [FSTNullValue nullValue]
|
|
}];
|
|
XCTAssertEqualObjects(actual, expected);
|
|
}
|
|
|
|
- (void)testWrapsNestedObjects {
|
|
FSTObjectValue *actual = FSTTestObjectValue(@{ @"a" : @{@"b" : @{@"c" : @"foo"}, @"d" : @YES} });
|
|
FSTObjectValue *expected = [[FSTObjectValue alloc] initWithDictionary:@{
|
|
@"a" : [[FSTObjectValue alloc] initWithDictionary:@{
|
|
@"b" :
|
|
[[FSTObjectValue alloc] initWithDictionary:@{@"c" : [FSTStringValue stringValue:@"foo"]}],
|
|
@"d" : [FSTBooleanValue booleanValue:YES]
|
|
}]
|
|
}];
|
|
XCTAssertEqualObjects(actual, expected);
|
|
}
|
|
|
|
- (void)testExtractsFields {
|
|
FSTObjectValue *obj = FSTTestObjectValue(@{ @"foo" : @{@"a" : @YES, @"b" : @"string"} });
|
|
FSTAssertIsKindOfClass(obj, FSTObjectValue);
|
|
|
|
FSTAssertIsKindOfClass([obj valueForPath:testutil::Field("foo")], FSTObjectValue);
|
|
XCTAssertEqualObjects([obj valueForPath:testutil::Field("foo.a")], [FSTBooleanValue trueValue]);
|
|
XCTAssertEqualObjects([obj valueForPath:testutil::Field("foo.b")],
|
|
[FSTStringValue stringValue:@"string"]);
|
|
|
|
XCTAssertNil([obj valueForPath:testutil::Field("foo.a.b")]);
|
|
XCTAssertNil([obj valueForPath:testutil::Field("bar")]);
|
|
XCTAssertNil([obj valueForPath:testutil::Field("bar.a")]);
|
|
}
|
|
|
|
- (void)testOverwritesExistingFields {
|
|
FSTObjectValue *old = FSTTestObjectValue(@{@"a" : @"old"});
|
|
FSTObjectValue *mod =
|
|
[old objectBySettingValue:FSTTestFieldValue(@"mod") forPath:testutil::Field("a")];
|
|
|
|
// Should return a new object, leaving the old one unmodified.
|
|
XCTAssertNotEqual(old, mod);
|
|
XCTAssertEqualObjects(old, FSTTestFieldValue(@{@"a" : @"old"}));
|
|
XCTAssertEqualObjects(mod, FSTTestFieldValue(@{@"a" : @"mod"}));
|
|
}
|
|
|
|
- (void)testAddsNewFields {
|
|
FSTObjectValue *empty = [FSTObjectValue objectValue];
|
|
FSTObjectValue *mod =
|
|
[empty objectBySettingValue:FSTTestFieldValue(@"mod") forPath:testutil::Field("a")];
|
|
XCTAssertNotEqual(empty, mod);
|
|
XCTAssertEqualObjects(empty, FSTTestFieldValue(@{}));
|
|
XCTAssertEqualObjects(mod, FSTTestFieldValue(@{@"a" : @"mod"}));
|
|
|
|
FSTObjectValue *old = mod;
|
|
mod = [old objectBySettingValue:FSTTestFieldValue(@1) forPath:testutil::Field("b")];
|
|
XCTAssertNotEqual(old, mod);
|
|
XCTAssertEqualObjects(old, FSTTestFieldValue(@{@"a" : @"mod"}));
|
|
XCTAssertEqualObjects(mod, FSTTestFieldValue(@{ @"a" : @"mod", @"b" : @1 }));
|
|
}
|
|
|
|
- (void)testImplicitlyCreatesObjects {
|
|
FSTObjectValue *old = FSTTestObjectValue(@{@"a" : @"old"});
|
|
FSTObjectValue *mod =
|
|
[old objectBySettingValue:FSTTestFieldValue(@"mod") forPath:testutil::Field("b.c.d")];
|
|
XCTAssertNotEqual(old, mod);
|
|
XCTAssertEqualObjects(old, FSTTestFieldValue(@{@"a" : @"old"}));
|
|
XCTAssertEqualObjects(mod, FSTTestFieldValue(
|
|
@{ @"a" : @"old",
|
|
@"b" : @{@"c" : @{@"d" : @"mod"}} }));
|
|
}
|
|
|
|
- (void)testCanOverwritePrimitivesWithObjects {
|
|
FSTObjectValue *old = FSTTestObjectValue(@{ @"a" : @{@"b" : @"old"} });
|
|
FSTObjectValue *mod =
|
|
[old objectBySettingValue:FSTTestFieldValue(@{@"b" : @"mod"}) forPath:testutil::Field("a")];
|
|
XCTAssertNotEqual(old, mod);
|
|
XCTAssertEqualObjects(old, FSTTestFieldValue(@{ @"a" : @{@"b" : @"old"} }));
|
|
XCTAssertEqualObjects(mod, FSTTestFieldValue(@{ @"a" : @{@"b" : @"mod"} }));
|
|
}
|
|
|
|
- (void)testAddsToNestedObjects {
|
|
FSTObjectValue *old = FSTTestObjectValue(@{ @"a" : @{@"b" : @"old"} });
|
|
FSTObjectValue *mod =
|
|
[old objectBySettingValue:FSTTestFieldValue(@"mod") forPath:testutil::Field("a.c")];
|
|
XCTAssertNotEqual(old, mod);
|
|
XCTAssertEqualObjects(old, FSTTestFieldValue(@{ @"a" : @{@"b" : @"old"} }));
|
|
XCTAssertEqualObjects(mod, FSTTestFieldValue(@{ @"a" : @{@"b" : @"old", @"c" : @"mod"} }));
|
|
}
|
|
|
|
- (void)testDeletesKeys {
|
|
FSTObjectValue *old = FSTTestObjectValue(@{ @"a" : @1, @"b" : @2 });
|
|
FSTObjectValue *mod = [old objectByDeletingPath:testutil::Field("a")];
|
|
XCTAssertNotEqual(old, mod);
|
|
XCTAssertEqualObjects(old, FSTTestFieldValue(@{ @"a" : @1, @"b" : @2 }));
|
|
XCTAssertEqualObjects(mod, FSTTestFieldValue(@{ @"b" : @2 }));
|
|
|
|
FSTObjectValue *empty = [mod objectByDeletingPath:testutil::Field("b")];
|
|
XCTAssertNotEqual(mod, empty);
|
|
XCTAssertEqualObjects(mod, FSTTestFieldValue(@{ @"b" : @2 }));
|
|
XCTAssertEqualObjects(empty, FSTTestFieldValue(@{}));
|
|
}
|
|
|
|
- (void)testDeletesHandleMissingKeys {
|
|
FSTObjectValue *old = FSTTestObjectValue(@{ @"a" : @{@"b" : @1, @"c" : @2} });
|
|
FSTObjectValue *mod = [old objectByDeletingPath:testutil::Field("b")];
|
|
XCTAssertEqualObjects(old, mod);
|
|
XCTAssertEqualObjects(mod, FSTTestFieldValue(@{ @"a" : @{@"b" : @1, @"c" : @2} }));
|
|
|
|
mod = [old objectByDeletingPath:testutil::Field("a.d")];
|
|
XCTAssertEqualObjects(old, mod);
|
|
XCTAssertEqualObjects(mod, FSTTestFieldValue(@{ @"a" : @{@"b" : @1, @"c" : @2} }));
|
|
|
|
mod = [old objectByDeletingPath:testutil::Field("a.b.c")];
|
|
XCTAssertEqualObjects(old, mod);
|
|
XCTAssertEqualObjects(mod, FSTTestFieldValue(@{ @"a" : @{@"b" : @1, @"c" : @2} }));
|
|
}
|
|
|
|
- (void)testDeletesNestedKeys {
|
|
FSTObjectValue *old = FSTTestObjectValue(
|
|
@{ @"a" : @{@"b" : @1, @"c" : @{@"d" : @2, @"e" : @3}} });
|
|
FSTObjectValue *mod = [old objectByDeletingPath:testutil::Field("a.c.d")];
|
|
XCTAssertNotEqual(old, mod);
|
|
XCTAssertEqualObjects(old, FSTTestFieldValue(
|
|
@{ @"a" : @{@"b" : @1, @"c" : @{@"d" : @2, @"e" : @3}} }));
|
|
XCTAssertEqualObjects(mod, FSTTestFieldValue(@{ @"a" : @{@"b" : @1, @"c" : @{@"e" : @3}} }));
|
|
|
|
old = mod;
|
|
mod = [old objectByDeletingPath:testutil::Field("a.c")];
|
|
XCTAssertEqualObjects(old, FSTTestFieldValue(@{ @"a" : @{@"b" : @1, @"c" : @{@"e" : @3}} }));
|
|
XCTAssertEqualObjects(mod, FSTTestFieldValue(@{ @"a" : @{@"b" : @1} }));
|
|
|
|
old = mod;
|
|
mod = [old objectByDeletingPath:testutil::Field("a")];
|
|
XCTAssertEqualObjects(old, FSTTestFieldValue(@{ @"a" : @{@"b" : @1} }));
|
|
XCTAssertEqualObjects(mod, FSTTestFieldValue(@{}));
|
|
}
|
|
|
|
- (void)testArrays {
|
|
FSTArrayValue *expected = [[FSTArrayValue alloc]
|
|
initWithValueNoCopy:@[ [FSTStringValue stringValue:@"value"], [FSTBooleanValue trueValue] ]];
|
|
|
|
FSTArrayValue *actual = (FSTArrayValue *)FSTTestFieldValue(@[ @"value", @YES ]);
|
|
XCTAssertEqualObjects(actual, expected);
|
|
}
|
|
|
|
- (void)testValueEquality {
|
|
DatabaseId database_id = DatabaseId("project", DatabaseId::kDefault);
|
|
NSArray *groups = @[
|
|
@[ FSTTestFieldValue(@YES), [FSTBooleanValue booleanValue:YES] ],
|
|
@[ FSTTestFieldValue(@NO), [FSTBooleanValue booleanValue:NO] ],
|
|
@[ FSTTestFieldValue([NSNull null]), [FSTNullValue nullValue] ],
|
|
@[ FSTTestFieldValue(@(0.0 / 0.0)), FSTTestFieldValue(@(NAN)), [FSTDoubleValue nanValue] ],
|
|
// -0.0 and 0.0 compare: the same (but are not isEqual:)
|
|
@[ FSTTestFieldValue(@(-0.0)) ], @[ FSTTestFieldValue(@0.0) ],
|
|
@[ FSTTestFieldValue(@1), FSTTestFieldValue(@1LL), [FSTIntegerValue integerValue:1LL] ],
|
|
// double and unit64_t values can compare: the same (but won't be isEqual:)
|
|
@[ FSTTestFieldValue(@1.0), [FSTDoubleValue doubleValue:1.0] ],
|
|
@[ FSTTestFieldValue(@1.1), [FSTDoubleValue doubleValue:1.1] ],
|
|
@[
|
|
FSTTestFieldValue(FSTTestData(0, 1, 2, -1)), [FSTBlobValue blobValue:FSTTestData(0, 1, 2, -1)]
|
|
],
|
|
@[ FSTTestFieldValue(FSTTestData(0, 1, -1)) ],
|
|
@[ FSTTestFieldValue(@"string"), [FSTStringValue stringValue:@"string"] ],
|
|
@[ FSTTestFieldValue(@"strin") ],
|
|
@[ FSTTestFieldValue(@"e\u0301b") ], // latin small letter e + combining acute accent
|
|
@[ FSTTestFieldValue(@"\u00e9a") ], // latin small letter e with acute accent
|
|
@[
|
|
FSTTestFieldValue(date1),
|
|
[FSTTimestampValue timestampValue:[FIRTimestamp timestampWithDate:date1]]
|
|
],
|
|
@[ FSTTestFieldValue(date2) ],
|
|
@[
|
|
// NOTE: ServerTimestampValues can't be parsed via FSTTestFieldValue().
|
|
[FSTServerTimestampValue
|
|
serverTimestampValueWithLocalWriteTime:[FIRTimestamp timestampWithDate:date1]
|
|
previousValue:nil],
|
|
[FSTServerTimestampValue
|
|
serverTimestampValueWithLocalWriteTime:[FIRTimestamp timestampWithDate:date1]
|
|
previousValue:nil]
|
|
],
|
|
@[ [FSTServerTimestampValue
|
|
serverTimestampValueWithLocalWriteTime:[FIRTimestamp timestampWithDate:date2]
|
|
previousValue:nil] ],
|
|
@[
|
|
FSTTestFieldValue(FSTTestGeoPoint(0, 1)),
|
|
[FSTGeoPointValue geoPointValue:FSTTestGeoPoint(0, 1)]
|
|
],
|
|
@[ FSTTestFieldValue(FSTTestGeoPoint(1, 0)) ],
|
|
@[
|
|
[FSTReferenceValue referenceValue:FSTTestDocKey(@"coll/doc1") databaseID:&database_id],
|
|
FSTTestFieldValue(FSTTestRef("project", DatabaseId::kDefault, @"coll/doc1"))
|
|
],
|
|
@[ FSTTestRef("project", "(default)", @"coll/doc2") ],
|
|
@[ FSTTestFieldValue(@[ @"foo", @"bar" ]), FSTTestFieldValue(@[ @"foo", @"bar" ]) ],
|
|
@[ FSTTestFieldValue(@[ @"foo", @"bar", @"baz" ]) ], @[ FSTTestFieldValue(@[ @"foo" ]) ],
|
|
@[
|
|
FSTTestFieldValue(
|
|
@{ @"bar" : @1,
|
|
@"foo" : @2 }),
|
|
FSTTestFieldValue(
|
|
@{ @"foo" : @2,
|
|
@"bar" : @1 })
|
|
],
|
|
@[ FSTTestFieldValue(
|
|
@{ @"bar" : @2,
|
|
@"foo" : @1 }) ],
|
|
@[ FSTTestFieldValue(
|
|
@{ @"bar" : @1,
|
|
@"foo" : @1 }) ],
|
|
@[ FSTTestFieldValue(
|
|
@{ @"foo" : @1 }) ]
|
|
];
|
|
|
|
FSTAssertEqualityGroups(groups);
|
|
}
|
|
|
|
- (void)testValueOrdering {
|
|
NSArray *groups = @[
|
|
// null first
|
|
@[ [NSNull null] ],
|
|
|
|
// booleans
|
|
@[ @NO ], @[ @YES ],
|
|
|
|
// numbers
|
|
@[ @(0.0 / 0.0) ], @[ @(-INFINITY) ], @[ @(-DBL_MAX) ], @[ @(LLONG_MIN) ], @[ @(-1.1) ],
|
|
@[ @(-1.0), @(-1LL) ], // longs and doubles compare the same
|
|
@[ @(-DBL_MIN) ],
|
|
@[ @(-0x1.0p-1074) ], // negative smallest subnormal
|
|
@[ @(-0.0), @(0.0), @(0LL) ], // zeros all compare the same
|
|
@[ @(0x1.0p-1074) ], // positive smallest subnormal
|
|
@[ @(DBL_MIN) ], @[ @1.0, @1LL ], // longs and doubles compare the same
|
|
@[ @1.1 ], @[ @(LLONG_MAX) ], @[ @(DBL_MAX) ], @[ @(INFINITY) ],
|
|
|
|
// timestamps
|
|
@[ date1 ], @[ date2 ],
|
|
|
|
// server timestamps come after all concrete timestamps.
|
|
// NOTE: server timestamps can't be parsed directly, so we have special sentinel strings (see
|
|
// FSTWrapGroups()).
|
|
@[ @"server-timestamp-1" ], @[ @"server-timestamp-2" ],
|
|
|
|
// strings
|
|
@[ @"" ], @[ @"\000\ud7ff\ue000\uffff" ], @[ @"(╯°□°)╯︵ ┻━┻" ], @[ @"a" ], @[ @"abc def" ],
|
|
@[ @"e\u0301b" ], // latin small letter e + combining acute accent + latin small letter b
|
|
@[ @"æ" ],
|
|
@[ @"\u00e9a" ], // latin small letter e with acute accent + latin small letter a
|
|
|
|
// blobs
|
|
@[ FSTTestData(-1) ], @[ FSTTestData(0, -1) ], @[ FSTTestData(0, 1, 2, 3, 4, -1) ],
|
|
@[ FSTTestData(0, 1, 2, 4, 3, -1) ], @[ FSTTestData(255, -1) ],
|
|
|
|
// resource names
|
|
@[ FSTTestRef("p1", "d1", @"c1/doc1") ], @[ FSTTestRef("p1", "d1", @"c1/doc2") ],
|
|
@[ FSTTestRef("p1", "d1", @"c10/doc1") ], @[ FSTTestRef("p1", "d1", @"c2/doc1") ],
|
|
@[ FSTTestRef("p1", "d2", @"c1/doc1") ], @[ FSTTestRef("p2", "d1", @"c1/doc1") ],
|
|
|
|
// Geo points
|
|
@[ FSTTestGeoPoint(-90, -180) ], @[ FSTTestGeoPoint(-90, 0) ], @[ FSTTestGeoPoint(-90, 180) ],
|
|
@[ FSTTestGeoPoint(0, -180) ], @[ FSTTestGeoPoint(0, 0) ], @[ FSTTestGeoPoint(0, 180) ],
|
|
@[ FSTTestGeoPoint(1, -180) ], @[ FSTTestGeoPoint(1, 0) ], @[ FSTTestGeoPoint(1, 180) ],
|
|
@[ FSTTestGeoPoint(90, -180) ], @[ FSTTestGeoPoint(90, 0) ], @[ FSTTestGeoPoint(90, 180) ],
|
|
|
|
// Arrays
|
|
@[ @[] ], @[ @[ @"bar" ] ], @[ @[ @"foo" ] ], @[ @[ @"foo", @1 ] ], @[ @[ @"foo", @2 ] ],
|
|
@[ @[ @"foo", @"0" ] ],
|
|
|
|
// Objects
|
|
@[
|
|
@{ @"bar" : @0 }
|
|
],
|
|
@[
|
|
@{ @"bar" : @0,
|
|
@"foo" : @1 }
|
|
],
|
|
@[
|
|
@{ @"foo" : @1 }
|
|
],
|
|
@[
|
|
@{ @"foo" : @2 }
|
|
],
|
|
@[ @{@"foo" : @"0"} ]
|
|
];
|
|
|
|
NSArray *wrapped = FSTWrapGroups(groups);
|
|
FSTAssertComparisons(wrapped);
|
|
}
|
|
|
|
- (void)testValue {
|
|
NSDate *date = [NSDate date];
|
|
id input = @{ @"array" : @[ @1, date ], @"obj" : @{@"date" : date, @"string" : @"hi"} };
|
|
FSTObjectValue *value = FSTTestObjectValue(input);
|
|
id output = [value value];
|
|
{
|
|
XCTAssertTrue([output[@"array"][1] isKindOfClass:[FIRTimestamp class]]);
|
|
FIRTimestamp *actual = output[@"array"][1];
|
|
XCTAssertEqualObjects([FIRTimestamp timestampWithDate:date], actual);
|
|
}
|
|
{
|
|
XCTAssertTrue([output[@"obj"][@"date"] isKindOfClass:[FIRTimestamp class]]);
|
|
FIRTimestamp *actual = output[@"array"][1];
|
|
XCTAssertEqualObjects([FIRTimestamp timestampWithDate:date], actual);
|
|
}
|
|
}
|
|
|
|
@end
|