[firestore] Support FieldValue.delete() and FieldValue.serverTimestamp()
This commit is contained in:
parent
ca3dd7aa01
commit
f348ba8a8c
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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;
|
|
@ -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) => {
|
||||
|
|
Loading…
Reference in New Issue