Skip to content

Commit

Permalink
Implement FieldValue.increment() (#92)
Browse files Browse the repository at this point in the history
  • Loading branch information
kimroen authored Apr 7, 2020
1 parent 303539e commit d37c130
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 4 deletions.
7 changes: 7 additions & 0 deletions src/firebase/firestore/field-value/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ export default class FieldValue {
return { _methodName: 'FieldValue.delete' };
}

increment(operand) {
return {
_methodName: 'FieldValue.increment',
_operand: operand,
};
}

serverTimestamp() {
return { _methodName: 'FieldValue.serverTimestamp' };
}
Expand Down
43 changes: 39 additions & 4 deletions src/unit-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -591,7 +591,7 @@ QUnit.module('Unit | mock-cloud-firestore', (hooks) => {
});

QUnit.test('should set the data using the merge option', async (assert) => {
assert.expect(11);
assert.expect(12);

// Arrange
const db = mockFirebase.firestore();
Expand All @@ -604,6 +604,7 @@ QUnit.module('Unit | mock-cloud-firestore', (hooks) => {
name: firebase.firestore.FieldValue.delete(),
dad: db.collection('users').doc('user_b'),
modifiedOn: firebase.firestore.FieldValue.serverTimestamp(),
activeYears: firebase.firestore.FieldValue.increment(-1),
pinnedBooks: firebase.firestore.FieldValue.arrayUnion('book_100'),
pinnedFoods: firebase.firestore.FieldValue.arrayRemove('food_1'),
}, { merge: true });
Expand All @@ -612,13 +613,14 @@ QUnit.module('Unit | mock-cloud-firestore', (hooks) => {
const snapshot = await ref.get();
const data = snapshot.data();

assert.equal(Object.keys(data).length, 9);
assert.equal(Object.keys(data).length, 10);
assert.deepEqual(data.address, { home: 'Seattle', work: 'Silicon Valley' });
assert.equal(data['address.home'], 'Seattle');
assert.equal(data.age, 15);
assert.deepEqual(data.createdOn.toDate(), new Date('2017-01-01'));
assert.deepEqual(data.dad, db.collection('users').doc('user_b'));
assert.ok(data.modifiedOn.toDate() instanceof Date);
assert.equal(data.activeYears, 1);
assert.deepEqual(data.pinnedBooks, ['book_1', 'book_2', 'book_100']);
assert.deepEqual(data.pinnedFoods, ['food_2']);
assert.equal(data.name, undefined);
Expand All @@ -628,7 +630,7 @@ QUnit.module('Unit | mock-cloud-firestore', (hooks) => {

QUnit.module('function: update', () => {
QUnit.test('should update the data', async (assert) => {
assert.expect(11);
assert.expect(12);

// Arrange
const db = mockFirebase.firestore();
Expand All @@ -642,6 +644,7 @@ QUnit.module('Unit | mock-cloud-firestore', (hooks) => {
'contact.mobile.personal': 67890,
age: firebase.firestore.FieldValue.delete(),
dad: db.collection('users').doc('user_b'),
activeYears: firebase.firestore.FieldValue.increment(1),
modifiedOn: firebase.firestore.FieldValue.serverTimestamp(),
pinnedBooks: firebase.firestore.FieldValue.arrayUnion('book_100'),
pinnedFoods: firebase.firestore.FieldValue.arrayRemove('food_1'),
Expand All @@ -658,12 +661,13 @@ QUnit.module('Unit | mock-cloud-firestore', (hooks) => {
dad,
modifiedOn,
name,
activeYears,
pinnedBooks,
pinnedFoods,
username,
} = snapshot.data();

assert.equal(Object.keys(snapshot.data()).length, 9);
assert.equal(Object.keys(snapshot.data()).length, 10);
assert.deepEqual(address, { work: 'Bay Area', temp: 'Seattle' });
assert.equal(age, undefined);
assert.deepEqual(contact, {
Expand All @@ -672,12 +676,31 @@ QUnit.module('Unit | mock-cloud-firestore', (hooks) => {
assert.deepEqual(createdOn.toDate(), new Date('2017-01-01'));
assert.deepEqual(dad, db.collection('users').doc('user_b'));
assert.ok(modifiedOn.toDate() instanceof Date);
assert.equal(activeYears, 3);
assert.equal(name, 'user_a');
assert.deepEqual(pinnedBooks, ['book_1', 'book_2', 'book_100']);
assert.deepEqual(pinnedFoods, ['food_2']);
assert.equal(username, 'user_a');
});

QUnit.test('should create the field if incrementing a field that is nonexistent', async (assert) => {
assert.expect(1);

// Arrange
const db = mockFirebase.firestore();
const ref = db.collection('users').doc('user_a');

// Act
await ref.update({
nonExistentField: firebase.firestore.FieldValue.increment(1),
});

// Assert
const snapshot = await ref.get();
const data = snapshot.data();
assert.equal(data.nonExistentField, 1);
});

QUnit.test('should throw error when updating data that does not exist', async (assert) => {
assert.expect(1);

Expand Down Expand Up @@ -960,6 +983,18 @@ QUnit.module('Unit | mock-cloud-firestore', (hooks) => {
assert.deepEqual(result, { _methodName: 'FieldValue.serverTimestamp' });
});
});

QUnit.module('function: increment', () => {
QUnit.test('should return an increment operation representation', (assert) => {
assert.expect(1);

// Act
const result = mockFirebase.firestore.FieldValue.increment(1);

// Assert
assert.deepEqual(result, { _methodName: 'FieldValue.increment', _operand: 1 });
});
});
});

QUnit.module('Firestore', () => {
Expand Down
18 changes: 18 additions & 0 deletions src/utils/parse-value/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ function validateValue(value, option) {
}
}

if (methodName === 'FieldValue.increment' && option.type === 'set:merge-false') {
throw new Error(`Function DocumentReference.set() called with invalid data. FieldValue.increment() cannot be used with set() unless you pass {merge:true} (found in field ${option.field})`);
}

if (option.isInArray) {
throw new Error(`Function DocumentReference.${option.type}() called with invalid data. ${methodName} is not currently supported inside arrays`);
}
Expand Down Expand Up @@ -69,6 +73,16 @@ function processArrayRemove(arrayRemove, oldArray = []) {
return newArray;
}

function processIncrement(incrementOperation, oldValue) {
const { _operand: operand } = incrementOperation;

if (typeof oldValue !== 'number') {
return operand;
}

return oldValue + operand;
}

function processFieldValue(newValue, oldValue) {
const { _methodName: methodName } = newValue;

Expand All @@ -84,6 +98,10 @@ function processFieldValue(newValue, oldValue) {
return processArrayRemove(newValue, oldValue);
}

if (methodName === 'FieldValue.increment') {
return processIncrement(newValue, oldValue);
}

return '__FieldValue.delete__';
}

Expand Down
1 change: 1 addition & 0 deletions src/utils/test-helpers/fixture-data.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export default function fixtureData() {
work: 'Silicon Valley',
},
age: 15,
activeYears: 2,
createdOn: new Date('2017-01-01'),
pinnedBooks: ['book_1', 'book_2'],
pinnedFoods: ['food_1', 'food_2', 'food_1'],
Expand Down

0 comments on commit d37c130

Please sign in to comment.