[firestore] Add FieldPath support to DocumentReference and WriteBatch update methods

This commit is contained in:
Chris Bianca 2018-01-12 11:49:45 +00:00
parent 5e062868fc
commit 3dacb35291
5 changed files with 94 additions and 33 deletions

View File

@ -4,6 +4,8 @@
*/ */
import CollectionReference from './CollectionReference'; import CollectionReference from './CollectionReference';
import DocumentSnapshot from './DocumentSnapshot'; import DocumentSnapshot from './DocumentSnapshot';
import FieldPath from './FieldPath';
import { mergeFieldPathData } from './utils';
import { buildNativeMap } from './utils/serialize'; import { buildNativeMap } from './utils/serialize';
import { getAppEventName, SharedEventEmitter } from '../../utils/events'; import { getAppEventName, SharedEventEmitter } from '../../utils/events';
import { getLogger } from '../../utils/log'; import { getLogger } from '../../utils/log';
@ -192,10 +194,13 @@ export default class DocumentReference {
for (let i = 0; i < args.length; i += 2) { for (let i = 0; i < args.length; i += 2) {
const key = args[i]; const key = args[i];
const value = args[i + 1]; const value = args[i + 1];
if (!isString(key)) { if (isString(key)) {
throw new Error(`DocumentReference.update failed: Argument at index ${i} must be a string`); data[key] = value;
} else if (key instanceof FieldPath) {
data = mergeFieldPathData(data, key._segments, value);
} else {
throw new Error(`DocumentReference.update failed: Argument at index ${i} must be a string or FieldPath`);
} }
data[key] = value;
} }
} }
const nativeData = buildNativeMap(data); const nativeData = buildNativeMap(data);

View File

@ -2,6 +2,8 @@
* @flow * @flow
* WriteBatch representation wrapper * WriteBatch representation wrapper
*/ */
import FieldPath from './FieldPath';
import { mergeFieldPathData } from './utils';
import { buildNativeMap } from './utils/serialize'; import { buildNativeMap } from './utils/serialize';
import { isObject, isString } from '../../utils'; import { isObject, isString } from '../../utils';
import { getNativeModule } from '../../utils/native'; import { getNativeModule } from '../../utils/native';
@ -67,19 +69,22 @@ export default class WriteBatch {
let data = {}; let data = {};
if (args.length === 1) { if (args.length === 1) {
if (!isObject(args[0])) { if (!isObject(args[0])) {
throw new Error('DocumentReference.update failed: If using two arguments, the second must be an object.'); throw new Error('WriteBatch.update failed: If using two arguments, the second must be an object.');
} }
data = args[0]; data = args[0];
} else if (args.length % 2 === 1) { } else if (args.length % 2 === 1) {
throw new Error('DocumentReference.update failed: Must have a document reference, followed by either a single object argument, or equal numbers of key/value pairs.'); throw new Error('WriteBatch.update failed: Must have a document reference, followed by either a single object argument, or equal numbers of key/value pairs.');
} else { } else {
for (let i = 0; i < args.length; i += 2) { for (let i = 0; i < args.length; i += 2) {
const key = args[i]; const key = args[i];
const value = args[i + 1]; const value = args[i + 1];
if (!isString(key)) { if (isString(key)) {
throw new Error(`DocumentReference.update failed: Argument at index ${i + 1} must be a string`); data[key] = value;
} else if (key instanceof FieldPath) {
data = mergeFieldPathData(data, key._segments, value);
} else {
throw new Error(`WriteBatch.update failed: Argument at index ${i} must be a string or FieldPath`);
} }
data[key] = value;
} }
} }

View File

@ -0,0 +1,32 @@
/**
* @flow
*/
const buildFieldPathData = (segments: string[], value: any): Object => {
if (segments.length === 1) {
return {
[segments[0]]: value,
};
}
return {
[segments[0]]: buildFieldPathData(segments.slice(1), value),
};
};
export const mergeFieldPathData = (data: Object, segments: string[], value: any): Object => {
if (segments.length === 1) {
return {
...data,
[segments[0]]: value,
};
} else if (data[segments[0]]) {
return {
...data,
[segments[0]]: mergeFieldPathData(data[segments[0]], segments.slice(1), value),
};
}
return {
...data,
[segments[0]]: buildFieldPathData(segments.slice(1), value),
};
};

View File

@ -66,9 +66,7 @@ function documentReferenceTests({ describe, it, context, firebase }) {
unsubscribe(); unsubscribe();
}); });
});
context('onSnapshot()', () => {
it('doesn\'t call callback when the ref is updated with the same value', async () => { it('doesn\'t call callback when the ref is updated with the same value', async () => {
const docRef = firebase.native.firestore().doc('document-tests/doc1'); const docRef = firebase.native.firestore().doc('document-tests/doc1');
const currentDataValue = { name: 'doc1' }; const currentDataValue = { name: 'doc1' };
@ -101,9 +99,7 @@ function documentReferenceTests({ describe, it, context, firebase }) {
unsubscribe(); unsubscribe();
}); });
});
context('onSnapshot()', () => {
it('allows binding multiple callbacks to the same ref', async () => { it('allows binding multiple callbacks to the same ref', async () => {
// Setup // Setup
const docRef = firebase.native.firestore().doc('document-tests/doc1'); const docRef = firebase.native.firestore().doc('document-tests/doc1');
@ -153,9 +149,7 @@ function documentReferenceTests({ describe, it, context, firebase }) {
unsubscribeA(); unsubscribeA();
unsubscribeB(); unsubscribeB();
}); });
});
context('onSnapshot()', () => {
it('listener stops listening when unsubscribed', async () => { it('listener stops listening when unsubscribed', async () => {
// Setup // Setup
const docRef = firebase.native.firestore().doc('document-tests/doc1'); const docRef = firebase.native.firestore().doc('document-tests/doc1');
@ -228,9 +222,7 @@ function documentReferenceTests({ describe, it, context, firebase }) {
callbackA.should.be.calledTwice(); callbackA.should.be.calledTwice();
callbackB.should.be.calledThrice(); callbackB.should.be.calledThrice();
}); });
});
context('onSnapshot()', () => {
it('supports options and callbacks', async () => { it('supports options and callbacks', async () => {
const docRef = firebase.native.firestore().doc('document-tests/doc1'); const docRef = firebase.native.firestore().doc('document-tests/doc1');
const currentDataValue = { name: 'doc1' }; const currentDataValue = { name: 'doc1' };
@ -266,9 +258,7 @@ function documentReferenceTests({ describe, it, context, firebase }) {
unsubscribe(); unsubscribe();
}); });
});
context('onSnapshot()', () => {
it('supports observer', async () => { it('supports observer', async () => {
const docRef = firebase.native.firestore().doc('document-tests/doc1'); const docRef = firebase.native.firestore().doc('document-tests/doc1');
const currentDataValue = { name: 'doc1' }; const currentDataValue = { name: 'doc1' };
@ -308,9 +298,7 @@ function documentReferenceTests({ describe, it, context, firebase }) {
unsubscribe(); unsubscribe();
}); });
});
context('onSnapshot()', () => {
it('supports options and observer', async () => { it('supports options and observer', async () => {
const docRef = firebase.native.firestore().doc('document-tests/doc1'); const docRef = firebase.native.firestore().doc('document-tests/doc1');
const currentDataValue = { name: 'doc1' }; const currentDataValue = { name: 'doc1' };
@ -361,9 +349,7 @@ function documentReferenceTests({ describe, it, context, firebase }) {
doc.data().name.should.equal('doc2'); doc.data().name.should.equal('doc2');
}); });
}); });
});
context('set()', () => {
it('should merge Document', () => { it('should merge Document', () => {
return firebase.native.firestore() return firebase.native.firestore()
.doc('document-tests/doc1') .doc('document-tests/doc1')
@ -374,9 +360,7 @@ function documentReferenceTests({ describe, it, context, firebase }) {
doc.data().merge.should.equal('merge'); doc.data().merge.should.equal('merge');
}); });
}); });
});
context('set()', () => {
it('should overwrite Document', () => { it('should overwrite Document', () => {
return firebase.native.firestore() return firebase.native.firestore()
.doc('document-tests/doc1') .doc('document-tests/doc1')
@ -398,9 +382,7 @@ function documentReferenceTests({ describe, it, context, firebase }) {
doc.data().name.should.equal('updated'); doc.data().name.should.equal('updated');
}); });
}); });
});
context('update()', () => {
it('should update Document using key/value pairs', () => { it('should update Document using key/value pairs', () => {
return firebase.native.firestore() return firebase.native.firestore()
.doc('document-tests/doc1') .doc('document-tests/doc1')
@ -410,6 +392,40 @@ function documentReferenceTests({ describe, it, context, firebase }) {
doc.data().name.should.equal('updated'); doc.data().name.should.equal('updated');
}); });
}); });
it('should update Document using FieldPath/value pair', () => {
return firebase.native.firestore()
.doc('document-tests/doc1')
.update(new firebase.native.firestore.FieldPath('name'), 'Name')
.then(async () => {
const doc = await firebase.native.firestore().doc('document-tests/doc1').get();
doc.data().name.should.equal('Name');
});
});
it('should update Document using nested FieldPath and value pair', () => {
return firebase.native.firestore()
.doc('document-tests/doc1')
.update(new firebase.native.firestore.FieldPath('nested', 'name'), 'Nested Name')
.then(async () => {
const doc = await firebase.native.firestore().doc('document-tests/doc1').get();
doc.data().nested.name.should.equal('Nested Name');
});
});
it('should update Document using multiple FieldPath/value pairs', () => {
return firebase.native.firestore()
.doc('document-tests/doc1')
.update(
new firebase.native.firestore.FieldPath('nested', 'firstname'), 'First Name',
new firebase.native.firestore.FieldPath('nested', 'lastname'), 'Last Name',
)
.then(async () => {
const doc = await firebase.native.firestore().doc('document-tests/doc1').get();
doc.data().nested.firstname.should.equal('First Name');
doc.data().nested.lastname.should.equal('Last Name');
});
});
}); });
context('types', () => { context('types', () => {
@ -422,9 +438,7 @@ function documentReferenceTests({ describe, it, context, firebase }) {
const doc = await docRef.get(); const doc = await docRef.get();
should.equal(doc.data().field, true); should.equal(doc.data().field, true);
}); });
});
context('types', () => {
it('should handle Date field', async () => { it('should handle Date field', async () => {
const date = new Date(); const date = new Date();
const docRef = firebase.native.firestore().doc('document-tests/reference'); const docRef = firebase.native.firestore().doc('document-tests/reference');
@ -437,9 +451,7 @@ function documentReferenceTests({ describe, it, context, firebase }) {
should.equal(doc.data().field.toISOString(), date.toISOString()); should.equal(doc.data().field.toISOString(), date.toISOString());
should.equal(doc.data().field.getTime(), date.getTime()); should.equal(doc.data().field.getTime(), date.getTime());
}); });
});
context('types', () => {
it('should handle DocumentReference field', async () => { it('should handle DocumentReference field', async () => {
const docRef = firebase.native.firestore().doc('document-tests/reference'); const docRef = firebase.native.firestore().doc('document-tests/reference');
await docRef.set({ await docRef.set({
@ -449,9 +461,7 @@ function documentReferenceTests({ describe, it, context, firebase }) {
const doc = await docRef.get(); const doc = await docRef.get();
should.equal(doc.data().field.path, 'test/field'); should.equal(doc.data().field.path, 'test/field');
}); });
});
context('types', () => {
it('should handle GeoPoint field', async () => { it('should handle GeoPoint field', async () => {
const docRef = firebase.native.firestore().doc('document-tests/reference'); const docRef = firebase.native.firestore().doc('document-tests/reference');
await docRef.set({ await docRef.set({

View File

@ -37,6 +37,13 @@ function firestoreTests({ describe, it, context, firebase }) {
.set(sfRef, { name: 'San Francisco' }) .set(sfRef, { name: 'San Francisco' })
.update(nycRef, { population: 1000000 }) .update(nycRef, { population: 1000000 })
.update(sfRef, 'name', 'San Fran') .update(sfRef, 'name', 'San Fran')
.update(sfRef, new firebase.native.firestore.FieldPath('name'), 'San Fran FieldPath')
.update(sfRef, new firebase.native.firestore.FieldPath('nested', 'name'), 'Nested Nme')
.update(
sfRef,
new firebase.native.firestore.FieldPath('nested', 'firstname'), 'First Name',
new firebase.native.firestore.FieldPath('nested', 'lastname'), 'Last Name',
)
.set(lRef, { population: 3000000 }, { merge: true }) .set(lRef, { population: 3000000 }, { merge: true })
.delete(ayRef) .delete(ayRef)
.commit() .commit()
@ -53,7 +60,9 @@ function firestoreTests({ describe, it, context, firebase }) {
nycDoc.data().population.should.equal(1000000); nycDoc.data().population.should.equal(1000000);
const sfDoc = await sfRef.get(); const sfDoc = await sfRef.get();
sfDoc.data().name.should.equal('San Fran'); sfDoc.data().name.should.equal('San Fran FieldPath');
sfDoc.data().nested.firstname.should.equal('First Name');
sfDoc.data().nested.lastname.should.equal('Last Name');
}); });
}); });
}); });