mirror of
https://github.com/firebase/firebase-ios-sdk
synced 2026-11-10 22:45:59 +05:45
* lint.sh now lints Objective-C++ too * cpplint checks system-style includes that should be user-style This prevents it from recognizing project sources as if they were C system headers and then complaining that they're in the wrong place. * cpplint checks #imports and #includes * cpplint checks that C++ system headers aren't #imported * cpplint checks for C system headers that could be C++ system headers * cpplint checks that Objective-C sources include their headers
584 lines
23 KiB
Plaintext
584 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/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
|