Skip to content

Conversation

zxu123
Copy link
Contributor

@zxu123 zxu123 commented Dec 14, 2017

Discussion

Testing

  • Adds unit test for all relevant isEqual(), either already implemented in master or in this branch
  • All unit tests and integration tests pass.

API Changes

See proposal go/firestoreisequal

*   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.
@mikelehen
Copy link
Contributor

I think I may be able to get to this before Gil. :-)

Copy link
Contributor

@mikelehen mikelehen left a 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:

  1. I think we need to implement hash as well as isEqual.
  2. 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;
Copy link
Contributor

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

}

if (![object isKindOfClass:[FIRFieldPath class]]) {
if (!object || ![object isKindOfClass:[FIRFieldPath class]]) {
Copy link
Contributor

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

Copy link
Contributor Author

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])
Copy link
Contributor

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:

  1. These sorts of nil checks aren't necessary nor required: http://google.github.io/styleguide/objcguide.html#nil-checks
  2. 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.

Copy link
Contributor Author

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.)

Copy link
Contributor

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;
}
Copy link
Contributor

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.

Copy link
Contributor Author

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];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

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 });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

date => data I think

Copy link
Contributor Author

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],
Copy link
Contributor

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?

Copy link
Contributor Author

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"];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

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]);
Copy link
Contributor

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?

Copy link
Contributor Author

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];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@mikelehen mikelehen assigned zxu123 and unassigned mikelehen Dec 15, 2017
Copy link
Contributor

@mikelehen mikelehen left a 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.
Copy link
Contributor

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. :-)

Copy link
Contributor Author

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));
Copy link
Contributor

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?

Copy link
Contributor Author

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));
Copy link
Contributor

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?

Copy link
Contributor Author

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));
Copy link
Contributor

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).

Copy link
Contributor Author

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]);
Copy link
Contributor

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:

  1. 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).

  2. 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.

Copy link
Contributor Author

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])
Copy link
Contributor

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]))
Copy link
Contributor

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. :-)

Copy link
Contributor Author

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])
Copy link
Contributor

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 ?

Copy link
Contributor Author

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.

Copy link
Contributor

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;
Copy link
Contributor

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.

Copy link
Contributor Author

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;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ooh! Good catch.

@mikelehen mikelehen removed their assignment Dec 18, 2017
Copy link
Contributor Author

@zxu123 zxu123 left a 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.
Copy link
Contributor Author

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];
Copy link
Contributor Author

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));
Copy link
Contributor Author

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));
Copy link
Contributor Author

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));
Copy link
Contributor Author

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. */
Copy link
Contributor Author

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)];
Copy link
Contributor Author

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;
Copy link
Contributor Author

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]))
Copy link
Contributor Author

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])
Copy link
Contributor Author

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.

Copy link
Contributor

@mikelehen mikelehen left a 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 */; };
Copy link
Contributor

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"
Copy link
Contributor

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"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Contributor Author

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);
Copy link
Contributor

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.

Copy link
Contributor Author

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]);
Copy link
Contributor

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)

Copy link
Contributor Author

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" {
Copy link
Contributor

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).

Copy link
Contributor

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.

Copy link
Contributor Author

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"
Copy link
Contributor

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

Copy link
Contributor Author

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;
Copy link
Contributor

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)

Copy link
Contributor Author

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;
Copy link
Contributor

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?

Copy link
Contributor Author

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"
Copy link
Contributor

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])
Copy link
Contributor

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!

@mikelehen mikelehen removed their assignment Dec 19, 2017
- (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"
Copy link
Contributor

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" {
Copy link
Contributor

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.

Copy link
Contributor

@mikelehen mikelehen left a 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,
Copy link
Contributor

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.
Copy link
Contributor

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.

@mikelehen mikelehen removed their assignment Dec 19, 2017
@zxu123 zxu123 merged commit 9ddf363 into firestore-api-changes Dec 19, 2017
@zxu123 zxu123 deleted the zxu/isEqual branch December 19, 2017 18:49
minafarid pushed a commit to minafarid/firebase-ios-sdk that referenced this pull request Jun 6, 2018
* 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
@firebase firebase locked and limited conversation to collaborators Nov 10, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants