[firestore] Support FieldValue.delete() and FieldValue.serverTimestamp()

This commit is contained in:
Chris Bianca 2017-10-12 09:00:46 +01:00
parent ca3dd7aa01
commit f348ba8a8c
8 changed files with 147 additions and 67 deletions

View File

@ -12,6 +12,7 @@ import com.facebook.react.bridge.WritableMap;
import com.google.firebase.firestore.DocumentChange;
import com.google.firebase.firestore.DocumentReference;
import com.google.firebase.firestore.DocumentSnapshot;
import com.google.firebase.firestore.FieldValue;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.GeoPoint;
import com.google.firebase.firestore.QuerySnapshot;
@ -273,6 +274,16 @@ public class FirestoreSerialize {
Log.e(TAG, "parseTypeMap", exception);
return null;
}
} else if ("fieldvalue".equals(type)) {
String value = typeMap.getString("value");
if ("delete".equals(value)) {
return FieldValue.delete();
} else if ("timestamp".equals(value)) {
return FieldValue.serverTimestamp();
} else {
Log.e(TAG, "parseTypeMap: Invalid fieldvalue: " + value);
return null;
}
} else {
Log.e(TAG, "parseTypeMap: Cannot convert object of type " + type);
return null;

View File

@ -260,7 +260,7 @@ static NSMutableDictionary *_listeners;
} else if ([type isEqualToString:@"reference"]) {
return [firestore documentWithPath:value];
} else if ([type isEqualToString:@"geopoint"]) {
NSDictionary* geopoint = (NSDictionary*)value;
NSDictionary *geopoint = (NSDictionary*)value;
NSNumber *latitude = geopoint[@"latitude"];
NSNumber *longitude = geopoint[@"longitude"];
return [[FIRGeoPoint alloc] initWithLatitude:[latitude doubleValue] longitude:[longitude doubleValue]];
@ -268,6 +268,16 @@ static NSMutableDictionary *_listeners;
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"];
return [dateFormatter dateFromString:value];
} else if ([type isEqualToString:@"fieldvalue"]) {
NSString *string = (NSString*)value;
if ([string isEqualToString:@"delete"]) {
return [FIRFieldValue fieldValueForDelete];
} else if ([string isEqualToString:@"timestamp"]) {
return [FIRFieldValue fieldValueForServerTimestamp];
} else {
// TODO: Log warning
return nil;
}
} else if ([type isEqualToString:@"boolean"] || [type isEqualToString:@"number"] || [type isEqualToString:@"string"] || [type isEqualToString:@"null"]) {
return value;
} else {

View File

@ -0,0 +1,17 @@
/**
* @flow
* FieldValue representation wrapper
*/
export default class FieldValue {
static delete(): FieldValue {
return DELETE_FIELD_VALUE;
}
static serverTimestamp(): FieldValue {
return SERVER_TIMESTAMP_FIELD_VALUE;
}
}
export const DELETE_FIELD_VALUE = new FieldValue();
export const SERVER_TIMESTAMP_FIELD_VALUE = new FieldValue();

View File

@ -2,12 +2,11 @@
* @flow
* Firestore representation wrapper
*/
import { NativeModules } from 'react-native';
import ModuleBase from './../../utils/ModuleBase';
import CollectionReference from './CollectionReference';
import DocumentReference from './DocumentReference';
import DocumentSnapshot from './DocumentSnapshot';
import FieldValue from './FieldValue';
import GeoPoint from './GeoPoint';
import Path from './Path';
import WriteBatch from './WriteBatch';
@ -137,9 +136,6 @@ export default class Firestore extends ModuleBase {
}
export const statics = {
FieldValue: {
delete: () => NativeModules.RNFirebaseFirestore && NativeModules.RNFirebaseFirestore.deleteFieldValue || {},
serverTimestamp: () => NativeModules.RNFirebaseFirestore && NativeModules.RNFirebaseFirestore.serverTimestampFieldValue || {}
},
FieldValue,
GeoPoint,
};

View File

@ -1,6 +1,7 @@
// @flow
import DocumentReference from '../DocumentReference';
import { DELETE_FIELD_VALUE, SERVER_TIMESTAMP_FIELD_VALUE } from '../FieldValue';
import GeoPoint from '../GeoPoint';
import Path from '../Path';
import { typeOf } from '../../../utils';
@ -42,6 +43,12 @@ const buildTypeMap = (value: any): any => {
if (value === null) {
typeMap.type = 'null';
typeMap.value = null;
} else if (value === DELETE_FIELD_VALUE) {
typeMap.type = 'fieldvalue';
typeMap.value = 'delete';
} else if (value === SERVER_TIMESTAMP_FIELD_VALUE) {
typeMap.type = 'fieldvalue';
typeMap.value = 'timestamp';
} else if (type === 'boolean' || type === 'number' || type === 'string') {
typeMap.type = type;
typeMap.value = value;
@ -99,7 +106,9 @@ const parseNativeArray = (firestore: Object, nativeArray: Object[]): any[] => {
const parseTypeMap = (firestore: Object, typeMap: TypeMap): any => {
const { type, value } = typeMap;
if (type === 'boolean' || type === 'number' || type === 'string' || type === 'null') {
if (type === 'null') {
return null;
} else if (type === 'boolean' || type === 'number' || type === 'string') {
return value;
} else if (type === 'array') {
return parseNativeArray(firestore, value);

View File

@ -2,6 +2,8 @@ import sinon from 'sinon';
import 'should-sinon';
import should from 'should';
import { COL_1 } from './index';
function collectionReferenceTests({ describe, it, context, firebase }) {
describe('CollectionReference', () => {
context('class', () => {
@ -54,9 +56,8 @@ function collectionReferenceTests({ describe, it, context, firebase }) {
context('onSnapshot()', () => {
it('calls callback with the initial data and then when document changes', async () => {
const collectionRef = firebase.native.firestore().collection('document-tests');
const currentDocValue = { name: 'doc1' };
const newDocValue = { name: 'updated' };
const collectionRef = firebase.native.firestore().collection('collection-tests');
const newDocValue = { ...COL_1, foo: 'updated' };
const callback = sinon.spy();
@ -70,9 +71,9 @@ function collectionReferenceTests({ describe, it, context, firebase }) {
});
});
callback.should.be.calledWith(currentDocValue);
callback.should.be.calledWith(COL_1);
const docRef = firebase.native.firestore().doc('document-tests/doc1');
const docRef = firebase.native.firestore().doc('collection-tests/col1');
await docRef.set(newDocValue);
await new Promise((resolve2) => {
@ -92,9 +93,8 @@ function collectionReferenceTests({ describe, it, context, firebase }) {
context('onSnapshot()', () => {
it('calls callback with the initial data and then when document is added', async () => {
const collectionRef = firebase.native.firestore().collection('document-tests');
const currentDocValue = { name: 'doc1' };
const newDocValue = { name: 'updated' };
const collectionRef = firebase.native.firestore().collection('collection-tests');
const newDocValue = { foo: 'updated' };
const callback = sinon.spy();
@ -108,9 +108,9 @@ function collectionReferenceTests({ describe, it, context, firebase }) {
});
});
callback.should.be.calledWith(currentDocValue);
callback.should.be.calledWith(COL_1);
const docRef = firebase.native.firestore().doc('document-tests/doc2');
const docRef = firebase.native.firestore().doc('collection-tests/col2');
await docRef.set(newDocValue);
await new Promise((resolve2) => {
@ -119,7 +119,7 @@ function collectionReferenceTests({ describe, it, context, firebase }) {
// Assertions
callback.should.be.calledWith(currentDocValue);
callback.should.be.calledWith(COL_1);
callback.should.be.calledWith(newDocValue);
callback.should.be.calledThrice();
@ -131,8 +131,7 @@ function collectionReferenceTests({ describe, it, context, firebase }) {
context('onSnapshot()', () => {
it('doesn\'t call callback when the ref is updated with the same value', async () => {
const collectionRef = firebase.native.firestore().collection('document-tests');
const currentDocValue = { name: 'doc1' };
const collectionRef = firebase.native.firestore().collection('collection-tests');
const callback = sinon.spy();
@ -146,10 +145,10 @@ function collectionReferenceTests({ describe, it, context, firebase }) {
});
});
callback.should.be.calledWith(currentDocValue);
callback.should.be.calledWith(COL_1);
const docRef = firebase.native.firestore().doc('document-tests/doc1');
await docRef.set(currentDocValue);
const docRef = firebase.native.firestore().doc('collection-tests/col1');
await docRef.set(COL_1);
await new Promise((resolve2) => {
setTimeout(() => resolve2(), 5);
@ -168,9 +167,8 @@ function collectionReferenceTests({ describe, it, context, firebase }) {
context('onSnapshot()', () => {
it('allows binding multiple callbacks to the same ref', async () => {
// Setup
const collectionRef = firebase.native.firestore().collection('document-tests');
const currentDocValue = { name: 'doc1' };
const newDocValue = { name: 'updated' };
const collectionRef = firebase.native.firestore().collection('collection-tests');
const newDocValue = { ...COL_1, foo: 'updated' };
const callbackA = sinon.spy();
const callbackB = sinon.spy();
@ -191,13 +189,13 @@ function collectionReferenceTests({ describe, it, context, firebase }) {
});
});
callbackA.should.be.calledWith(currentDocValue);
callbackA.should.be.calledWith(COL_1);
callbackA.should.be.calledOnce();
callbackB.should.be.calledWith(currentDocValue);
callbackB.should.be.calledWith(COL_1);
callbackB.should.be.calledOnce();
const docRef = firebase.native.firestore().doc('document-tests/doc1');
const docRef = firebase.native.firestore().doc('collection-tests/col1');
await docRef.set(newDocValue);
await new Promise((resolve2) => {
@ -220,9 +218,8 @@ function collectionReferenceTests({ describe, it, context, firebase }) {
context('onSnapshot()', () => {
it('listener stops listening when unsubscribed', async () => {
// Setup
const collectionRef = firebase.native.firestore().collection('document-tests');
const currentDocValue = { name: 'doc1' };
const newDocValue = { name: 'updated' };
const collectionRef = firebase.native.firestore().collection('collection-tests');
const newDocValue = { ...COL_1, foo: 'updated' };
const callbackA = sinon.spy();
const callbackB = sinon.spy();
@ -243,13 +240,13 @@ function collectionReferenceTests({ describe, it, context, firebase }) {
});
});
callbackA.should.be.calledWith(currentDocValue);
callbackA.should.be.calledWith(COL_1);
callbackA.should.be.calledOnce();
callbackB.should.be.calledWith(currentDocValue);
callbackB.should.be.calledWith(COL_1);
callbackB.should.be.calledOnce();
const docRef = firebase.native.firestore().doc('document-tests/doc1');
const docRef = firebase.native.firestore().doc('collection-tests/col1');
await docRef.set(newDocValue);
await new Promise((resolve2) => {
@ -266,13 +263,13 @@ function collectionReferenceTests({ describe, it, context, firebase }) {
unsubscribeA();
await docRef.set(currentDocValue);
await docRef.set(COL_1);
await new Promise((resolve2) => {
setTimeout(() => resolve2(), 5);
});
callbackB.should.be.calledWith(currentDocValue);
callbackB.should.be.calledWith(COL_1);
callbackA.should.be.calledTwice();
callbackB.should.be.calledThrice();
@ -294,9 +291,8 @@ function collectionReferenceTests({ describe, it, context, firebase }) {
context('onSnapshot()', () => {
it('supports options and callback', async () => {
const collectionRef = firebase.native.firestore().collection('document-tests');
const currentDocValue = { name: 'doc1' };
const newDocValue = { name: 'updated' };
const collectionRef = firebase.native.firestore().collection('collection-tests');
const newDocValue = { ...COL_1, foo: 'updated' };
const callback = sinon.spy();
@ -310,9 +306,9 @@ function collectionReferenceTests({ describe, it, context, firebase }) {
});
});
callback.should.be.calledWith(currentDocValue);
callback.should.be.calledWith(COL_1);
const docRef = firebase.native.firestore().doc('document-tests/doc1');
const docRef = firebase.native.firestore().doc('collection-tests/col1');
await docRef.set(newDocValue);
await new Promise((resolve2) => {
@ -331,9 +327,8 @@ function collectionReferenceTests({ describe, it, context, firebase }) {
context('onSnapshot()', () => {
it('supports observer', async () => {
const collectionRef = firebase.native.firestore().collection('document-tests');
const currentDocValue = { name: 'doc1' };
const newDocValue = { name: 'updated' };
const collectionRef = firebase.native.firestore().collection('collection-tests');
const newDocValue = { ...COL_1, foo: 'updated' };
const callback = sinon.spy();
@ -350,9 +345,9 @@ function collectionReferenceTests({ describe, it, context, firebase }) {
unsubscribe = collectionRef.onSnapshot(observer);
});
callback.should.be.calledWith(currentDocValue);
callback.should.be.calledWith(COL_1);
const docRef = firebase.native.firestore().doc('document-tests/doc1');
const docRef = firebase.native.firestore().doc('collection-tests/col1');
await docRef.set(newDocValue);
await new Promise((resolve2) => {
@ -372,9 +367,8 @@ function collectionReferenceTests({ describe, it, context, firebase }) {
context('onSnapshot()', () => {
it('supports options and observer', async () => {
const collectionRef = firebase.native.firestore().collection('document-tests');
const currentDocValue = { name: 'doc1' };
const newDocValue = { name: 'updated' };
const collectionRef = firebase.native.firestore().collection('collection-tests');
const newDocValue = { ...COL_1, foo: 'updated' };
const callback = sinon.spy();
@ -391,9 +385,9 @@ function collectionReferenceTests({ describe, it, context, firebase }) {
unsubscribe = collectionRef.onSnapshot({ includeQueryMetadataChanges: true, includeDocumentMetadataChanges: true }, observer);
});
callback.should.be.calledWith(currentDocValue);
callback.should.be.calledWith(COL_1);
const docRef = firebase.native.firestore().doc('document-tests/doc1');
const docRef = firebase.native.firestore().doc('collection-tests/col1');
await docRef.set(newDocValue);
await new Promise((resolve2) => {

View File

@ -0,0 +1,36 @@
import should from 'should';
function fieldValueTests({ describe, it, context, firebase }) {
describe('FieldValue', () => {
context('delete()', () => {
it('should delete field', () => {
return firebase.native.firestore()
.doc('document-tests/doc2')
.update({
title: firebase.native.firestore.FieldValue.delete(),
})
.then(async () => {
const doc = await firebase.native.firestore().doc('document-tests/doc2').get();
should.equal(doc.data().title, undefined);
});
});
});
context('serverTimestamp()', () => {
it('should set timestamp', () => {
return firebase.native.firestore()
.doc('document-tests/doc2')
.update({
creationDate: firebase.native.firestore.FieldValue.serverTimestamp(),
})
.then(async () => {
const doc = await firebase.native.firestore().doc('document-tests/doc2').get();
doc.data().creationDate.should.be.instanceof(Date);
});
});
});
});
}
export default fieldValueTests;

View File

@ -6,13 +6,26 @@ import TestSuite from '../../../lib/TestSuite';
*/
import collectionReferenceTests from './collectionReferenceTests';
import documentReferenceTests from './documentReferenceTests';
import fieldValueTests from './fieldValueTests';
import firestoreTests from './firestoreTests';
export const COL_1 = {
baz: true,
daz: 123,
foo: 'bar',
gaz: 12.1234567,
naz: null,
};
export const DOC_1 = { name: 'doc1' };
export const DOC_2 = { name: 'doc2', title: 'Document 2' };
const suite = new TestSuite('Firestore', 'firebase.firestore()', firebase);
const testGroups = [
collectionReferenceTests,
documentReferenceTests,
fieldValueTests,
firestoreTests,
];
@ -21,28 +34,22 @@ function firestoreTestSuite(testSuite) {
this.collectionTestsCollection = testSuite.firebase.native.firestore().collection('collection-tests');
this.documentTestsCollection = testSuite.firebase.native.firestore().collection('document-tests');
this.firestoreTestsCollection = testSuite.firebase.native.firestore().collection('firestore-tests');
// Clean the collections in case the last run failed
// Make sure the collections are cleaned and initialised correctly
await cleanCollection(this.collectionTestsCollection);
await cleanCollection(this.documentTestsCollection);
await cleanCollection(this.firestoreTestsCollection);
await this.collectionTestsCollection.add({
baz: true,
daz: 123,
foo: 'bar',
gaz: 12.1234567,
naz: null,
});
const tasks = [];
tasks.push(this.collectionTestsCollection.doc('col1').set(COL_1));
tasks.push(this.documentTestsCollection.doc('doc1').set(DOC_1));
tasks.push(this.documentTestsCollection.doc('doc2').set(DOC_2));
await this.documentTestsCollection.doc('doc1').set({
name: 'doc1',
});
await Promise.all(tasks);
});
testSuite.afterEach(async () => {
await cleanCollection(this.collectionTestsCollection);
await cleanCollection(this.documentTestsCollection);
await cleanCollection(this.firestoreTestsCollection);
// All data will be cleaned an re-initialised before each test
// Adding a clean here slows down the test suite dramatically
});
testGroups.forEach((testGroup) => {