diff --git a/lib/modules/firestore/DocumentReference.js b/lib/modules/firestore/DocumentReference.js index e401d4f1..1e77823a 100644 --- a/lib/modules/firestore/DocumentReference.js +++ b/lib/modules/firestore/DocumentReference.js @@ -4,6 +4,8 @@ */ import CollectionReference from './CollectionReference'; import DocumentSnapshot from './DocumentSnapshot'; +import FieldPath from './FieldPath'; +import { mergeFieldPathData } from './utils'; import { buildNativeMap } from './utils/serialize'; import { getAppEventName, SharedEventEmitter } from '../../utils/events'; import { getLogger } from '../../utils/log'; @@ -192,10 +194,13 @@ export default class DocumentReference { for (let i = 0; i < args.length; i += 2) { const key = args[i]; const value = args[i + 1]; - if (!isString(key)) { - throw new Error(`DocumentReference.update failed: Argument at index ${i} must be a string`); + if (isString(key)) { + 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); diff --git a/lib/modules/firestore/WriteBatch.js b/lib/modules/firestore/WriteBatch.js index 2b315de2..75bc555a 100644 --- a/lib/modules/firestore/WriteBatch.js +++ b/lib/modules/firestore/WriteBatch.js @@ -2,6 +2,8 @@ * @flow * WriteBatch representation wrapper */ +import FieldPath from './FieldPath'; +import { mergeFieldPathData } from './utils'; import { buildNativeMap } from './utils/serialize'; import { isObject, isString } from '../../utils'; import { getNativeModule } from '../../utils/native'; @@ -67,19 +69,22 @@ export default class WriteBatch { let data = {}; if (args.length === 1) { 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]; } 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 { for (let i = 0; i < args.length; i += 2) { const key = args[i]; const value = args[i + 1]; - if (!isString(key)) { - throw new Error(`DocumentReference.update failed: Argument at index ${i + 1} must be a string`); + if (isString(key)) { + 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; } } diff --git a/lib/modules/firestore/utils/index.js b/lib/modules/firestore/utils/index.js new file mode 100644 index 00000000..e3070e53 --- /dev/null +++ b/lib/modules/firestore/utils/index.js @@ -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), + }; +}; diff --git a/tests/src/tests/firestore/documentReferenceTests.js b/tests/src/tests/firestore/documentReferenceTests.js index a6aa2f65..64fd6341 100644 --- a/tests/src/tests/firestore/documentReferenceTests.js +++ b/tests/src/tests/firestore/documentReferenceTests.js @@ -66,9 +66,7 @@ function documentReferenceTests({ describe, it, context, firebase }) { unsubscribe(); }); - }); - context('onSnapshot()', () => { 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 currentDataValue = { name: 'doc1' }; @@ -101,9 +99,7 @@ function documentReferenceTests({ describe, it, context, firebase }) { unsubscribe(); }); - }); - context('onSnapshot()', () => { it('allows binding multiple callbacks to the same ref', async () => { // Setup const docRef = firebase.native.firestore().doc('document-tests/doc1'); @@ -153,9 +149,7 @@ function documentReferenceTests({ describe, it, context, firebase }) { unsubscribeA(); unsubscribeB(); }); - }); - context('onSnapshot()', () => { it('listener stops listening when unsubscribed', async () => { // Setup const docRef = firebase.native.firestore().doc('document-tests/doc1'); @@ -228,9 +222,7 @@ function documentReferenceTests({ describe, it, context, firebase }) { callbackA.should.be.calledTwice(); callbackB.should.be.calledThrice(); }); - }); - context('onSnapshot()', () => { it('supports options and callbacks', async () => { const docRef = firebase.native.firestore().doc('document-tests/doc1'); const currentDataValue = { name: 'doc1' }; @@ -266,9 +258,7 @@ function documentReferenceTests({ describe, it, context, firebase }) { unsubscribe(); }); - }); - context('onSnapshot()', () => { it('supports observer', async () => { const docRef = firebase.native.firestore().doc('document-tests/doc1'); const currentDataValue = { name: 'doc1' }; @@ -308,9 +298,7 @@ function documentReferenceTests({ describe, it, context, firebase }) { unsubscribe(); }); - }); - context('onSnapshot()', () => { it('supports options and observer', async () => { const docRef = firebase.native.firestore().doc('document-tests/doc1'); const currentDataValue = { name: 'doc1' }; @@ -361,9 +349,7 @@ function documentReferenceTests({ describe, it, context, firebase }) { doc.data().name.should.equal('doc2'); }); }); - }); - context('set()', () => { it('should merge Document', () => { return firebase.native.firestore() .doc('document-tests/doc1') @@ -374,9 +360,7 @@ function documentReferenceTests({ describe, it, context, firebase }) { doc.data().merge.should.equal('merge'); }); }); - }); - context('set()', () => { it('should overwrite Document', () => { return firebase.native.firestore() .doc('document-tests/doc1') @@ -398,9 +382,7 @@ function documentReferenceTests({ describe, it, context, firebase }) { doc.data().name.should.equal('updated'); }); }); - }); - context('update()', () => { it('should update Document using key/value pairs', () => { return firebase.native.firestore() .doc('document-tests/doc1') @@ -410,6 +392,40 @@ function documentReferenceTests({ describe, it, context, firebase }) { 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', () => { @@ -422,9 +438,7 @@ function documentReferenceTests({ describe, it, context, firebase }) { const doc = await docRef.get(); should.equal(doc.data().field, true); }); - }); - context('types', () => { it('should handle Date field', async () => { const date = new Date(); 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.getTime(), date.getTime()); }); - }); - context('types', () => { it('should handle DocumentReference field', async () => { const docRef = firebase.native.firestore().doc('document-tests/reference'); await docRef.set({ @@ -449,9 +461,7 @@ function documentReferenceTests({ describe, it, context, firebase }) { const doc = await docRef.get(); should.equal(doc.data().field.path, 'test/field'); }); - }); - context('types', () => { it('should handle GeoPoint field', async () => { const docRef = firebase.native.firestore().doc('document-tests/reference'); await docRef.set({ diff --git a/tests/src/tests/firestore/firestoreTests.js b/tests/src/tests/firestore/firestoreTests.js index 29fc9703..4df7964e 100644 --- a/tests/src/tests/firestore/firestoreTests.js +++ b/tests/src/tests/firestore/firestoreTests.js @@ -37,6 +37,13 @@ function firestoreTests({ describe, it, context, firebase }) { .set(sfRef, { name: 'San Francisco' }) .update(nycRef, { population: 1000000 }) .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 }) .delete(ayRef) .commit() @@ -53,7 +60,9 @@ function firestoreTests({ describe, it, context, firebase }) { nycDoc.data().population.should.equal(1000000); 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'); }); }); });