-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Firestore isEqual()
on Public Types
#569
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
* add bone code for `FIRCollectionReference`, `FIRDocumentSnapshot`, `FIRFieldValue`, `FIRQuerySnapshot`, `FIRSnapshotMetadata`; * change inconsistenciness of `FIRFieldPath.isEqual` implementation; * add unit test (and file) for `FIRDocumentReferenceTest.m`, `FIRFieldPathTest.m`, `FIRQueryTests.testEquals`; `FIRGeoPoint` already has test and Blob is internal type.
adding the working code and unit test.
Since `FIRFieldValue` both types are singleton, we do not need override `isEqual`. Add test to test the default `NSObject` `isEqual` works just fine.
I think I may be able to get to this before Gil. :-) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like a good start. I left some miscellaneous tips / suggestions, but the two high-level points:
- I think we need to implement
hash
as well asisEqual
. - I'd like to clean up the tests to reduce boilerplate. I think we can probably do that by using existing helpers and creating new ones as necessary.
// NSObject Methods | ||
- (BOOL)isEqual:(nullable id)other { | ||
if (other == self) return YES; | ||
if (!other || ![[other class] isEqual:[self class]]) return NO; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that this won't work for the FIRQueryDocumentSnapshot class that @schmidt-sebastian is adding. So we'll need to implement isEqual on it as well, or relax this to an isKindOfClass check.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
Firestore/Source/API/FIRFieldPath.m
Outdated
} | ||
|
||
if (![object isKindOfClass:[FIRFieldPath class]]) { | ||
if (!object || ![object isKindOfClass:[FIRFieldPath class]]) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In Obj-C, nil checks like this are not required nor recommended: http://google.github.io/styleguide/objcguide.html#nil-checks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
- (BOOL)isEqualToSnapshot:(nullable FIRDocumentSnapshot *)snapshot { | ||
if (self == snapshot) return YES; | ||
if (snapshot == nil) return NO; | ||
if (self.firestore != snapshot.firestore && ![self.firestore isEqual:snapshot.firestore]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if you're doing reference equality checks before isEqual equality checks as a performance improvement, or perhaps to deal with nil values, but I would drop them since:
- These sorts of nil checks aren't necessary nor required: http://google.github.io/styleguide/objcguide.html#nil-checks
- For performance we can instead make the isEqual implementation short-circuit on reference equality.
As an aside, some people on the team (Gil and I for sure) use AppCode for iOS development, which can automatically generate isEqual and hash implementations for you.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's ignore other properties for now. For the four combination of .firestore.
- case 01) self.firestore == nil, snapshot.firestore != nil, should return NO;
- case 00) self.firestore == nil, snapshot.firestore == nil, should return YES;
- case 11) self.firestore != nil, snapshot.firestore != nil, should return whether self.firestore == snapshot.firestore;
- case 10) self.firestore != nil, snapshot.firestore == nil, should return NO;
As far as self.firestore != nil and FIRFirestore:isEqual implemented correctly, [self.firestore isEqual:snapshot.firestore] is enough to get the result;
As soon as self.firestore == nil, [self.firestore isEqual:snapshot.firestore] will always returns nil and we cannot tell apart 01 and 00 cases. Additional self.firestore != snapshot.firestore helps in this case. (and as side effect, may or may not improve performance by checking reference.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for walking me through this. Since firestore is non-nullable I think only case 11 can actually happen, but on the other hand I just checked and AppCode generates code very similar to yours. And it's probably better to follow a consistent pattern for nullable and non-nullable properties. So carry on! Thanks / sorry.
return NO; | ||
if (self.fromCache != snapshot.fromCache) return NO; | ||
return YES; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we forgot to call this out explicitly in the bug / proposal, but by overriding isEqual, we also need to override hash (for justification, see this SO answer which is for java but the same argument applies).
FYI- Since JavaScript has no standardized equals / hashCode methods, we don't need to worry about it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
persistenceKey:@"db123" | ||
credentialsProvider:nil | ||
workerDispatchQueue:nil | ||
firebaseApp:nil]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you define a helper rather than repeating this (e.g. in https://github.com/firebase/firebase-ios-sdk/blob/master/Firestore/Example/Tests/Util/FSTHelpers.h)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
firebaseApp:nil]; | ||
FSTDocumentKey *keyFoo = [FSTDocumentKey keyWithPathString:@"rooms/foo"]; | ||
FSTDocumentKey *keyBar = [FSTDocumentKey keyWithPathString:@"rooms/bar"]; | ||
FSTObjectValue *dateFoo = FSTTestObjectValue(@{ @"a" : @1 }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
date => data I think
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
XCTAssertEqualObjects([FIRDocumentSnapshot snapshotWithFirestore:firestore | ||
documentKey:keyFoo | ||
document:nil | ||
fromCache:YES], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh boy. This really turns into a giant wall of text. Can we create a helper to reduce this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
@implementation FIRFieldPathTests | ||
|
||
- (void)testEquals { | ||
FSTFieldPath *pathFoo = [FSTFieldPath pathWithServerFormat:@"foo.ooo.oooo"]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
[FIRQuerySnapshot snapshotWithFirestore:firestore | ||
originalQuery:queryFoo | ||
snapshot:snapshotFoo | ||
metadata:metadataBar]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another giant wall of text. Can we improve with a helper of some kind?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
firebaseApp:nil]; | ||
FSTResourcePath *pathFoo = [FSTResourcePath pathWithString:@"foo"]; | ||
FSTResourcePath *pathBar = [FSTResourcePath pathWithString:@"bar"]; | ||
FSTQuery *queryFoo = [FSTQuery queryWithPath:pathFoo]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
…`FSTTestQuery`, `FSTTestDoc`
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! I appreciate you doing the cleanup I suggested (and finding/fixing existing places that needed the same cleanup!). I have a bit more cleanup to ask for (sorry!), but you can push back if you feel I'm going overboard. I do like to minimize boilerplate and maximize clarity in our tests though. And I have a few other miscellaneous comments / suggestions.
|
||
- (void)testEquals { | ||
// Everything is dummy for unit test here. Filtering does not require any app | ||
// specific setting as far as we do not fetch data. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this comment should go inside FSTTestFirestore() rather than on every usage of the method. :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. That's some leftover comment I forgot to remove.
XCTAssertNotEqualObjects(FSTTestDocSnapshot(@"rooms/foo", 1, @{ @"a" : @1 }, NO, NO), | ||
FSTTestDocSnapshot(@"rooms/foo", 1, @{ @"b" : @1 }, NO, NO)); | ||
XCTAssertNotEqualObjects(FSTTestDocSnapshot(@"rooms/foo", 1, @{ @"a" : @1 }, YES, NO), | ||
FSTTestDocSnapshot(@"rooms/foo", 1, @{ @"b" : @1 }, NO, NO)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These differ in both the data and metadata. Intentional?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
XCTAssertNotEqualObjects(FSTTestDocSnapshot(@"rooms/foo", 1, @{ @"a" : @1 }, YES, NO), | ||
FSTTestDocSnapshot(@"rooms/foo", 1, @{ @"b" : @1 }, NO, NO)); | ||
XCTAssertNotEqualObjects(FSTTestDocSnapshot(@"rooms/foo", 1, @{ @"a" : @1 }, NO, YES), | ||
FSTTestDocSnapshot(@"rooms/foo", 1, @{ @"b" : @1 }, NO, NO)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These differ in both the data and metadata. Intentional?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
XCTAssertEqualObjects(FSTTestDocSnapshot(@"rooms/bar", 1, @{ @"a" : @1 }, NO, NO), | ||
FSTTestDocSnapshot(@"rooms/bar", 1, @{ @"a" : @1 }, NO, NO)); | ||
XCTAssertNotEqualObjects(FSTTestDocSnapshot(@"rooms/foo", 1, @{ @"a" : @1 }, NO, NO), | ||
FSTTestDocSnapshot(@"rooms/bar", 1, @{ @"a" : @1 }, NO, NO)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think these are clang-formatted. Can you run scripts/style.sh on your change and commit the relevant changes (unfortunately there are a few unrelated formatting issues in the codebase right now which we need to get cleaned up separately).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
XCTAssertNotEqual([FSTTestDocSnapshot(@"rooms/foo", 1, @{ @"a" : @1 }, YES, NO) hash], | ||
[FSTTestDocSnapshot(@"rooms/foo", 1, @{ @"b" : @1 }, NO, NO) hash]); | ||
XCTAssertNotEqual([FSTTestDocSnapshot(@"rooms/foo", 1, @{ @"a" : @1 }, NO, YES) hash], | ||
[FSTTestDocSnapshot(@"rooms/foo", 1, @{ @"b" : @1 }, NO, NO) hash]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
EDIT: After reviewing further, I see that you've used FSTAssertEqualityGroups() elsewhere, so I'd recommend using it here too!
I'm not a fan of all the repetition. It's hard to tell what's being tested and verify there aren't mistakes. Two possible suggestions:
-
We actually have an FSTAssertEqualityGroups helper macro that takes a lists of "equality groups" (one or more objects that should compare equal) and verifies that only objects in the same equality group compare equal with each other. For an example usage, see here. It also tests the hash implementation, though it only verifies hashes that should be equal are equal. It doesn't test for hashes not being equal, since hash collisions are okay (and unavoidable in the general case).
-
Perhaps create some useful snapshots up front (base, baseDup, nilData, nilDataDup, differentPath, differentData, hasMutations, fromCache, etc.?) and then re-use those in all of the equals / hash assertions? Then it's easy to verify that the snapshot you create matches the named variable, and it's easy to verify that the equals / not-equals assertions you do make sense for the named values.
I would probably do 1 or 2, but not both, and I leave it up to you what to choose. If you do 1, please do it in all of these tests for consistency. 2 is fairly consistent with what you're doing in the other tests already.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done with method 2
- (BOOL)isEqualToSnapshot:(nullable FIRDocumentSnapshot *)snapshot { | ||
if (self == snapshot) return YES; | ||
if (snapshot == nil) return NO; | ||
if (self.firestore != snapshot.firestore && ![self.firestore isEqual:snapshot.firestore]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for walking me through this. Since firestore is non-nullable I think only case 11 can actually happen, but on the other hand I just checked and AppCode generates code very similar to yours. And it's probably better to follow a consistent pattern for nullable and non-nullable properties. So carry on! Thanks / sorry.
if (self.internalKey != snapshot.internalKey && ![self.internalKey isEqual:snapshot.internalKey]) | ||
return NO; | ||
if (self.internalDocument != snapshot.internalDocument && | ||
(!self.internalDocument || ![self.internalDocument isEqual:snapshot.internalDocument])) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't need the !self.internalDocument check do you? nil inequality will be caught by the first check as per your explanation above. :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, the check is redundant. Removed.
- (BOOL)isEqualToSnapshot:(nullable FIRQuerySnapshot *)snapshot { | ||
if (self == snapshot) return YES; | ||
if (snapshot == nil) return NO; | ||
if (self.firestore != snapshot.firestore && ![self.firestore isEqual:snapshot.firestore]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't these be ||
s ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, should be &&. Check the case-analysis above (nil v.s. non-nil, non-nil v.s. non-nil). Another way to look at this is to use double-negation of it. Say, self.firestore is equal iff the reference is equal OR the content is equal. So self.firestore is not equal (case here to return NO) iff the reference is not equal AND the content is not equal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Er, sorry I keep trying to lead you astray. For some reason when I was looking at this I was thinking of the equals case instead of the not-equals case. Carry on!
- (BOOL)isEqual:(nullable id)other { | ||
if (other == self) return YES; | ||
if (!other || ![[other class] isEqual:[self class]]) return NO; | ||
if (![[other class] isEqual:[self class]]) return NO; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you haven't already, can you just double-check that this still works? I'm a tiny bit nervous because AppCode auto-generates these methods with the "!other" check, and I don't know why.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still works, see logic given below:
The only difference happens when other
is nil
. That case one return NO
immediately while the other will check ![[other class] isEqual:[self class]]
, which is equivalent to ![nil isEqual:[self class]]
, which is equivalent to !nil
, which makes the method return NO
as well. So !other
check is redundant.
I have no particular preference on one over the other. So I can change it back if you think with !other
check is easier to understand.
NSUInteger hash = self.type; | ||
hash = hash * 31u + [self.key hash]; | ||
return hash; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ooh! Good catch.
…nt}Ref`, better naming and style fix
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated.
|
||
- (void)testEquals { | ||
// Everything is dummy for unit test here. Filtering does not require any app | ||
// specific setting as far as we do not fetch data. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. That's some leftover comment I forgot to remove.
FIRCollectionReference *referenceFooDup = | ||
[FIRCollectionReference referenceWithPath:pathFoo firestore:firestore]; | ||
FIRCollectionReference *referenceBar = | ||
[FIRCollectionReference referenceWithPath:pathBar firestore:firestore]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
XCTAssertEqualObjects(FSTTestDocSnapshot(@"rooms/bar", 1, @{ @"a" : @1 }, NO, NO), | ||
FSTTestDocSnapshot(@"rooms/bar", 1, @{ @"a" : @1 }, NO, NO)); | ||
XCTAssertNotEqualObjects(FSTTestDocSnapshot(@"rooms/foo", 1, @{ @"a" : @1 }, NO, NO), | ||
FSTTestDocSnapshot(@"rooms/bar", 1, @{ @"a" : @1 }, NO, NO)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
XCTAssertNotEqualObjects(FSTTestDocSnapshot(@"rooms/foo", 1, @{ @"a" : @1 }, NO, NO), | ||
FSTTestDocSnapshot(@"rooms/foo", 1, @{ @"b" : @1 }, NO, NO)); | ||
XCTAssertNotEqualObjects(FSTTestDocSnapshot(@"rooms/foo", 1, @{ @"a" : @1 }, YES, NO), | ||
FSTTestDocSnapshot(@"rooms/foo", 1, @{ @"b" : @1 }, NO, NO)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
XCTAssertNotEqualObjects(FSTTestDocSnapshot(@"rooms/foo", 1, @{ @"a" : @1 }, YES, NO), | ||
FSTTestDocSnapshot(@"rooms/foo", 1, @{ @"b" : @1 }, NO, NO)); | ||
XCTAssertNotEqualObjects(FSTTestDocSnapshot(@"rooms/foo", 1, @{ @"a" : @1 }, NO, YES), | ||
FSTTestDocSnapshot(@"rooms/foo", 1, @{ @"b" : @1 }, NO, NO)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
/** A convenience method for creating a query for the given path (without any other filters). */ | ||
FSTQuery *FSTTestQuery(NSString *path); | ||
|
||
/** A convenience method for creating a particular query snapshots for tests. */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
fromCache:fromCache]; | ||
FSTDocumentSet *oldDocuments = FSTTestDocSet(FSTDocumentComparatorByKey, @[]); | ||
for (NSDictionary<NSString *, id> *data in oldData) { | ||
oldDocuments = [oldDocuments documentSetByAddingDocument:FSTTestDoc(path, 1, data, hasPendingWrites)]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
- (BOOL)isEqual:(nullable id)other { | ||
if (other == self) return YES; | ||
if (!other || ![[other class] isEqual:[self class]]) return NO; | ||
if (![[other class] isEqual:[self class]]) return NO; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still works, see logic given below:
The only difference happens when other
is nil
. That case one return NO
immediately while the other will check ![[other class] isEqual:[self class]]
, which is equivalent to ![nil isEqual:[self class]]
, which is equivalent to !nil
, which makes the method return NO
as well. So !other
check is redundant.
I have no particular preference on one over the other. So I can change it back if you think with !other
check is easier to understand.
if (self.internalKey != snapshot.internalKey && ![self.internalKey isEqual:snapshot.internalKey]) | ||
return NO; | ||
if (self.internalDocument != snapshot.internalDocument && | ||
(!self.internalDocument || ![self.internalDocument isEqual:snapshot.internalDocument])) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, the check is redundant. Removed.
- (BOOL)isEqualToSnapshot:(nullable FIRQuerySnapshot *)snapshot { | ||
if (self == snapshot) return YES; | ||
if (snapshot == nil) return NO; | ||
if (self.firestore != snapshot.firestore && ![self.firestore isEqual:snapshot.firestore]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, should be &&. Check the case-analysis above (nil v.s. non-nil, non-nil v.s. non-nil). Another way to look at this is to use double-negation of it. Say, self.firestore is equal iff the reference is equal OR the content is equal. So self.firestore is not equal (case here to return NO) iff the reference is not equal AND the content is not equal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the changes! Found a few remaining inconsistencies, etc. but I think this is pretty close. :-)
AB99452E1FE30AC800DFC1E6 /* FIRFieldValueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = AB99452D1FE30AC800DFC1E6 /* FIRFieldValueTests.m */; }; | ||
ABAEEF4F1FD5F8B100C966CB /* FIRQueryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = ABAEEF4E1FD5F8B100C966CB /* FIRQueryTests.m */; }; | ||
ABF341051FE860CA00C48322 /* FSTAPIHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = ABF341021FE8593500C48322 /* FSTAPIHelpers.m */; }; | ||
ABF341061FE860CB00C48322 /* FSTAPIHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = ABF341021FE8593500C48322 /* FSTAPIHelpers.m */; }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have trouble reading xcode proj files, but I think perhaps you added FSTAPIHelpers.m to both the Firestore_IntegrationTests target and the Firestore_Tests target, but it should only be in the latter.
#import "Firestore/Source/Model/FSTPath.h" | ||
#import "Firestore/Source/Remote/FSTRemoteEvent.h" | ||
#import "Firestore/Source/Remote/FSTWatchChange.h" | ||
#import "Firestore/Source/Util/FSTAssert.h" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most of these imports are unused. Per AppCode, this is the set you need:
#import "Firestore/Example/Tests/API/FSTAPIHelpers.h"
#import <Firestore/Source/API/FIRDocumentSnapshot+Internal.h>
#import <Firestore/Source/API/FIRQuerySnapshot+Internal.h>
#import <Firestore/Source/API/FIRSnapshotMetadata+Internal.h>
#import <Firestore/Source/Core/FSTViewSnapshot.h>
#import "FirebaseFirestore/FIRCollectionReference.h"
#import "FirebaseFirestore/FIRDocumentReference.h"
#import "FirebaseFirestore/FIRDocumentSnapshot.h"
#import "FirebaseFirestore/FIRFirestore.h"
#import "FirebaseFirestore/FIRGeoPoint.h"
#import "FirebaseFirestore/FIRQuerySnapshot.h"
#import "FirebaseFirestore/FIRSnapshotMetadata.h"
#import "Firestore/Source/API/FIRCollectionReference+Internal.h"
#import "Firestore/Source/API/FIRFirestore+Internal.h"
#import "Firestore/Source/Core/FSTQuery.h"
#import "Firestore/Source/Model/FSTDocument.h"
#import "Firestore/Source/Model/FSTDocumentKey.h"
#import "Firestore/Source/Model/FSTDocumentSet.h"
#import "Firestore/Source/Model/FSTPath.h"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done, also clean-up more and make style consistent (<> vs "")
FIRGeoPoint *foo = FSTTestGeoPoint(0, 0); | ||
FIRGeoPoint *fooDup = FSTTestGeoPoint(0, 0); | ||
FIRGeoPoint *bar = FSTTestGeoPoint(1.23, 4.56); | ||
FIRGeoPoint *barDup = FSTTestGeoPoint(1.23, 4.56); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure bar / barDup is adding anything to these tests. You do the same equals checks as with foo / fooDup. If you want non-zero values, I'd just change foo to be non-zero values.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
XCTAssertEqual([bar hash], [barDup hash]); | ||
XCTAssertNotEqual([foo hash], [differentLatitude hash]); | ||
XCTAssertNotEqual([foo hash], [differentLongitude hash]); | ||
XCTAssertNotEqual([foo hash], [object hash]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did you add the object hash test for this test but not others? (I would drop it in general. I'm not sure what it's protecting against)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
NS_ASSUME_NONNULL_BEGIN | ||
|
||
#if __cplusplus | ||
extern "C" { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't believe this is required (usually you need this when you're going to be including the header file from C/C++ code).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the most part this is going to be required in every Objective-C header as we go so we might as well put these in. It's not worth thinking about whether or not they're required.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ack, will leave as is.
#import "Firestore/Source/Model/FSTFieldValue.h" | ||
#import "Firestore/Source/Model/FSTPath.h" | ||
|
||
#import "Firestore/Example/Tests/API/FSTAPIHelpers.h" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hrm... I didn't realize that these tests would end up relying on FSTAPIHelpers.h. That makes it less obvious that my suggested separation makes sense. :-/
I think the difficulty comes from the fact that FIRGeoPoint is used as both a "model" class and an "API" class. I'd suggest moving FSTTestGeoPoint back into FSTHelpers.h/.m and adding a comment something like:
// Note that FIRGeoPoint is a model class in addition to an API class, so we put this helper here instead of FSTAPIHelpers.h
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
@class FSTView; | ||
@class FSTViewSnapshot; | ||
@class FSTObjectValue; | ||
@protocol FSTFilter; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The vast majority of these are unused. Unfortunately AppCode doesn't seem to tell me which ones. Can you take a manual pass through cleaning these up (e.g. delete them all and then re-add as necessary)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
@class FIRDocumentSnapshot; | ||
@class FIRFirestore; | ||
@class FIRGeoPoint; | ||
@class FIRQuerySnapshot; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think all of these added FIR* forward declarations are no longer necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
#import "FirebaseFirestore/FIRQuerySnapshot.h" | ||
#import "FirebaseFirestore/FIRSnapshotMetadata.h" | ||
#import "Firestore/Source/API/FIRCollectionReference+Internal.h" | ||
#import "Firestore/Source/API/FIRDocumentReference+Internal.h" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the added imports are no longer necessary?
- (BOOL)isEqualToSnapshot:(nullable FIRQuerySnapshot *)snapshot { | ||
if (self == snapshot) return YES; | ||
if (snapshot == nil) return NO; | ||
if (self.firestore != snapshot.firestore && ![self.firestore isEqual:snapshot.firestore]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Er, sorry I keep trying to lead you astray. For some reason when I was looking at this I was thinking of the equals case instead of the not-equals case. Carry on!
- (void)testEquals { | ||
// Everything is dummy for unit test here. Filtering does not require any app | ||
// specific setting as far as we do not fetch data. | ||
FIRFirestore *firestore = [[FIRFirestore alloc] initWithProjectID:@"abc" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that we've repeated this blurb multiple times, could you factor it out into a helper?
NS_ASSUME_NONNULL_BEGIN | ||
|
||
#if __cplusplus | ||
extern "C" { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the most part this is going to be required in every Objective-C header as we go so we might as well put these in. It's not worth thinking about whether or not they're required.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two tiny nits, but otherwise looks good. Thank you!
NSDictionary<NSString *, NSDictionary<NSString *, id> *> *oldData, | ||
NSDictionary<NSString *, NSDictionary<NSString *, id> *> *dataToAdd, | ||
NSDictionary<NSString *, NSDictionary<NSString *, id> *> *oldDocs, | ||
NSDictionary<NSString *, NSDictionary<NSString *, id> *> *DocsToAdd, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lowercase please. :-) (DocsToAdd=>docsToAdd)
* | ||
* @param path To be used in constructing the query. | ||
* @param oldDocs Provides data to construct the query snapshot in the past. It maps each key to a | ||
* document. The key is the document's path relative to the query path. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: "document's path" suggests this could be deeply nested under the query path, but it really must only be a single level deep. I would say:
Provides the prior set of documents in the QuerySnapshot. Each dictionary entry maps to a document, with the key being the document id, and the value being the document contents.
* add bone code for new isEqual and unit test for old isEqual * add bone code for `FIRCollectionReference`, `FIRDocumentSnapshot`, `FIRFieldValue`, `FIRQuerySnapshot`, `FIRSnapshotMetadata`; * change inconsistenciness of `FIRFieldPath.isEqual` implementation; * add unit test (and file) for `FIRDocumentReferenceTest.m`, `FIRFieldPathTest.m`, `FIRQueryTests.testEquals`; `FIRGeoPoint` already has test and Blob is internal type. * Implement isEqual for FIRCollectionReference adding the working code and unit test. * implement isEqual for FIRSnapshotMetadata * Implement isEqual for FIRDocumentSnapshot * Implement isEqual for FIRQuerySnapshot * (un)implement `isEqual` for `FIRFieldValue` Since `FIRFieldValue` both types are singleton, we do not need override `isEqual`. Add test to test the default `NSObject` `isEqual` works just fine. * fix style with `scripts/style.sh` * Implement hash for those with overridden isEqual without hash yet. * refactor to use test helper functions -- FSTTestFirestore, FSTTestPath, FSTTestDocKey * refactor using test helper `FSTTestDocSnapshot`, `FSTTestFieldPath`, `FSTTestQuery`, `FSTTestDoc` * refactoring to use test helper method `FSTTestQuerySnapshot`, * remove unneccessary nil-check, check isKindOfClass instead of isEqual * refactoring: adding `FSTAPIHelpers.{h,m}`, `FSTTest{Collection,Document}Ref`, better naming and style fix * a file forgot in last commit * mainly clean up import and some minor refactoring * fix style via style.sh * minor style fix * add pragma ignored -Wnonnull
Discussion
Testing
isEqual()
, either already implemented in master or in this branchAPI Changes
See proposal go/firestoreisequal