How to test delete query Firebase/mockFirebase in React Native? - javascript

I am building an App using React Native and Firebase, and I want to test Delete function using jest/firestore-jest-mock.
My Problem is when I try this query it always return True.
"devDependencies": {
"#babel/core": "~7.9.0",
"firestore-jest-mock": "^0.7.1",
"jest-expo": "^40.0.1",
},
Here is my code:
// Fake DATABASE
mockFirebase({
database: {
categories_costs: [{ id: 'key1',title: 'test1', icon: 'icontest1' }],
},
});
const firebase = require('firebase');
const db = firebase.firestore();
// test with wrong ID
test('Delete Document In categories_income', () => {
return db.collection('categories_costs')
.doc('keyNoExist')
.delete()
.then(() => {
//Always get into this part
console.log("Document successfully deleted!");
}).catch((error) => {
console.error("Error removing document: ", error);
});
});
The test passed correctly like this although the id is wrong.

This is my solution for it, in this part it check if the document exist and then remove it (the fake data).
test('Delete categories_income Bykey', () => {
const ref = db.collection('categories_income').doc('key1');
return ref.get().then((doc) => {
if (doc.exists) {
ref.delete().then(() => {
expect(mockCollection).toHaveBeenCalledWith('categories_income');
}).catch((error) => {
expect(error).toEqual('error')
});
}else{
throw new Error("Document Does Not Exist")
}
});
});

Related

How to await firebase to start, avoiding Error: "Client is offline" on React Native?

I need help retrieving firebase data in React Native, using expo.
This code works fine when I refresh the app, but when it first starts, it throws an error:
Error: Error: Client is offline.
Maybe I need to do it async and await, I have tried some ways, but no success.
componentDidMount = async () => {
var radioFireData = null;
const { names } = this.props;
const dbRef = ref(db, "records/");
get(child(dbRef, "flap/"))
.then((snapshot) => {
if (snapshot.exists()) {
radioFireData = snapshot.val();
this.setState({ checked: radioFireData[names] });
} else {
console.log("No data available");
}
})
.catch((error) => {
console.log(error);
});
};
Here it is... Maybe I can help someone.
componentDidMount(){
const { names } = this.props;
const reference = ref(db, "records/" + "/flap/");
onValue(
reference,
(snapshot) => {
const data = snapshot.val();
this.setState({ checked: data[names] });
},
{
onlyOnce: true,
}
);
};

How can I decrement a field value from firestore after submitting a form successfully?

I have these collection of items from firestore:
availability : true
stocks: 100
item: item1
I kind of wanted to decrement the stocks after submitting the form: I have these where() to compare if what the user chose is the same item from the one saved in the firestore.
function incrementCounter(collref) {
collref = firestore
.collection("items")
.doc()
.where(selectedItem, "==", selectedItem);
collref.update({
stocks: firestore.FieldValue.increment(-1),
});
}
This is how I'll submit my form and I've set the incrementCounter() after saving it:
const handleSubmit = (e) => {
e.preventDefault();
try {
const userRef = firestore.collection("users").doc(id);
const ref = userRef.set(
{
....
},
},
{ merge: true }
);
console.log(" saved");
incrementCounter();
} catch (err) {
console.log(err);
}
};
There's no error in submitting the form. However, the incrementCounter() is not working and displays this error:
TypeError: _Firebase_utils__WEBPACK_IMPORTED_MODULE_5__.firestore.collection(...).doc(...).where is not a function
There are few problems here
There should be async-await for both functions
Your fieldValue should start from firebase.firestore.FieldValue not firestoreFieldValue
Also where clause is used for collection, not doc() so remove that as well. Also I don't think this will update the full collection but do check it and see. (The error you are getting is because of this)
I don't know how you are importing firebase in this application and I don't know how you have declared firestore but mostly firestore variable is declared like this
const firestore = firebase.firestore();
In here firestore is a function, not a property
But when you are using it in the FieldValue then it should be like
firebase.firestore.FieldValue.increment(-1),
Notice that firestore here is a property not a function
Your full code should be like this
async function incrementCounter(collref) {
collref = firestore
.collection("items")
.where(selectedItem, "==", selectedItem);
const newRef = await collref.get();
for(let i in newRef.docs){
const doc = newRef.docs[i];
await doc.update({
stocks: firebase.firestore.FieldValue.increment(-1),
});
// You can also batch this
}
}
const handleSubmit = async (e) => {
e.preventDefault();
try {
const userRef = firestore.collection("users").doc(id);
const ref = await userRef.set(
{
....
},
},
{ merge: true }
);
console.log(" saved");
await incrementCounter();
} catch (err) {
console.log(err);
}
};
The where() method exists on a CollectionReference and not a DocumentReference. You also need to get references to those documents first so first get all the matching documents and then update all of them using Promise.all() or Batch Writes:
function incrementCounter() {
// not param required ^^
const collref = firestore
.collection("items")
// .doc() <-- remove this
.where(selectedItem, "==", selectedItem);
// ^^^ ^^^
// doc field field value
// "item" {selectedItemName}
collRef.get().then(async (qSnap) => {
const updates = []
qSnap.docs.forEach((doc) => {
updates.push(doc.ref.update({ stocks: firebase.firestore.FieldValue.increment(-1) }))
})
await Promise.all(updates)
})
}
If you are updating less than 500 documents, consider using batch writes to make sure all updates either fail or pass:
collRef.get().then(async (qSnap) => {
const batch = firestore.batch()
qSnap.docs.forEach((doc) => {
batch.update(doc.ref, { stocks: firebase.firestore.FieldValue.increment(-1) })
})
await batch.commit()
})
You can read more about batch writes in the documentation

Recommended way to import function into a spec and share value between multiple tests in cypress?

I need to access a "random string generator function" in my spec file and call the function and be able to access the returned random value in all the tests within the spec file.
What would be the recommended way to do this?
Edit: bit more about what I want to do :
at the start of the spec I generate a random number,
us that as id to create an entry in 1st test
search for it and edit it in the 2nd test
delete it in the 3rd ...
It's even easier if your random function is synchronous, you can just add it to the Cypress object and use it tests directly.
Place this in cypress/support/index.js or at the top of the test.
Cypress.userName = () => `User-${Math.floor(Math.random() * 100000)}`;
In the test
describe('add, edit, delete a User', () => {
const userName = Cypress.userName(); // same random name for all tests in this block
it('add a user', () => {
cy.get('.username').type(userName);
cy.get('Submit').click();
})
it('search for the user', () => {
cy.get('.search').type(userName);
cy.get('.found-user').should('contain', userName);
})
it('rejects an unknown user', () => {
const anotherUser = Cypress.userName(); // new name provided here
cy.get('.search').type(anotherUser);
cy.get('.found-user').should('not.contain', anotherUser); // not added yet
})
})
As a bonus you don't have to be extra careful to use it('...', function() { all the time, it works with arrow function format it('...', () => {.
You can get random strings that are more appropriate to the usage by using Faker.js.
Sample taken from this article Using Faker to generate data for your Cypress tests
/cypress/plugins/index.js
const faker = require("faker");
module.exports = (on, config) => {
on("task", {
freshUser() {
user = {
username: faker.name.firstName(),
email: faker.internet.email(),
password: "SuperSecret",
};
return user;
},
});
};
In the test
/// <reference types="Cypress" />
let user;
describe("Docket Post Test", () => {
before(function () {
cy.task("freshUser").then((object) => {
user = object;
});
});
it("Register a new user", () => {
cy.apiRegister({
username: user.username,
email: user.email,
password: user.password,
});
});
});
Kevin's full repo is here.
I had a similar requirement for my Tests, the way I am doing it is:
First I created a file data.json under the fixtures folder with the content:
username: "user-102020"
Then I am generating my random string and then saving this value in the fixtures file. I am writing all this in the before() block of my first test since I want to pass on the same random value to all my tests.
before(function() {
const uniqueUsername = `User-${Math.floor(Math.random() * 100000)}`
cy.readFile("cypress/fixtures/data.json", (err, data) => {
if (err) {
return console.error(err);
};
}).then((data) => {
data.username = uniqueUsername
cy.writeFile("cypress/fixtures/data.json", JSON.stringify(data))
})
})
Then in the remaining tests, I am using the username from the data.json fixtures file. With every run, a new random value will be generated and this will be replaced in the data.json file and then used throughout.
describe('Random Test', function() {
before(function() {
cy.visit(url)
cy.fixture('data').then(function(data) {
this.data = data
})
})
it('Validate successful Login', function() {
cy.get('#txtUsername').type(this.data.username)
//Rest of the test
})
})
================
Now as per your question:
Create a custom command. Go to cypress/support/commands.js and write:
Cypress.Commands.add('updateRandomValueToFixtures', (uniqueUsername) => {
cy.readFile("cypress/fixtures/data.json", (err, data) => {
if (err) {
return console.error(err);
};
}).then((data) => {
data.username = uniqueUsername
cy.writeFile("cypress/fixtures/data.json", JSON.stringify(data))
})
})
Create a file data.json under the fixtures folder with the content:
username: "user-102020"
For the first test use:
//Generate a random value
const uniqueUsername = `User-${Math.floor(Math.random() * 100000)}`
//Update the random value in the fixture file
cy.updateRandomValueToFixtures(uniqueUsername)
//Use the uniqueUsername to search
describe('Random Test', function() {
before(function() {
cy.fixture('data').then(function(data) {
this.data = data
})
})
it('Some test', function() {
//this.data.username has the random value, use it to search
})
})
For Second Test use:
//Edit previously created random value and save it in a variable
const uniqueUsername = newValue
//Update the random value in the fixture file
cy.updateRandomValueToFixtures(uniqueUsername)
For the third test:
describe('Random Test', function() {
before(function() {
cy.fixture('data').then(function(data) {
this.data = data
})
})
it('Some test', function() {
//this.data.username has the updated random value, use it to delete
})
})

Firestore Cloud Functions to trigger sms

I have the following cloud function that will trigger a sms message to be sent using Twillio. It works as is but it causes the text message to be sent twice. Is there a way I can modify my function to prevent this? Please note that I am not using the Firebase Realtime Database; I am using the Firebase Firestore Database. This function is being used in conjunction with an Ionic 4 project.
export const textPrayedForNotification = functions.firestore.document('/prayerRequests/{prayerRequestId}').onUpdate(snap => {
const phone: string = snap.after.get('phoneNumber');
const receiveNotifications: boolean = snap.after.get('receiveNotifications');
if (receiveNotifications) {
return client.messages.create({
to: phone,
from: twilioNumber,
body: 'Someone has prayed for you.'
}).then((data: any) => {
console.log(data);
}).catch((error: any) => {
console.log(error);
});
}
});
Update:
Change function to this and now it seems to work.
export const textPrayedForNotification = functions.firestore.document('/prayerRequests/{prayerRequestId}').onUpdate(snap => {
const phone: string = snap.after.get('phoneNumber');
const receiveNotifications: boolean = snap.after.get('receiveNotifications');
const dateLastPrayedBefore = snap.before.get('dateLastPrayed');
const dateLastPrayedAfter = snap.after.get('dateLastPrayed');
if (receiveNotifications) {
if (dateLastPrayedBefore !== dateLastPrayedAfter) {
return client.messages.create({
to: phone,
from: twilioNumber,
body: 'Someone has prayed for you.'
}).then((data: any) => {
console.log(data);
}).catch((error: any) => {
console.log(error);
});
}
}
});

How to delete document from firestore using where clause

var jobskill_ref = db.collection('job_skills').where('job_id','==',post.job_id);
jobskill_ref.delete();
Error thrown
jobskill_ref.delete is not a function
You can only delete a document once you have a DocumentReference to it. To get that you must first execute the query, then loop over the QuerySnapshot and finally delete each DocumentSnapshot based on its ref.
var jobskill_query = db.collection('job_skills').where('job_id','==',post.job_id);
jobskill_query.get().then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
doc.ref.delete();
});
});
I use batched writes for this. For example:
var jobskill_ref = db.collection('job_skills').where('job_id','==',post.job_id);
let batch = firestore.batch();
jobskill_ref
.get()
.then(snapshot => {
snapshot.docs.forEach(doc => {
batch.delete(doc.ref);
});
return batch.commit();
})
ES6 async/await:
const jobskills = await store
.collection('job_skills')
.where('job_id', '==', post.job_id)
.get();
const batch = store.batch();
jobskills.forEach(doc => {
batch.delete(doc.ref);
});
await batch.commit();
//The following code will find and delete the document from firestore
const doc = await this.noteRef.where('userId', '==', userId).get();
doc.forEach(element => {
element.ref.delete();
console.log(`deleted: ${element.id}`);
});
the key part of Frank's answer that fixed my issues was the .ref in doc.ref.delete()
I originally only had doc.delete() which gave a "not a function" error. now my code looks like this and works perfectly:
let fs = firebase.firestore();
let collectionRef = fs.collection(<your collection here>);
collectionRef.where("name", "==", name)
.get()
.then(querySnapshot => {
querySnapshot.forEach((doc) => {
doc.ref.delete().then(() => {
console.log("Document successfully deleted!");
}).catch(function(error) {
console.error("Error removing document: ", error);
});
});
})
.catch(function(error) {
console.log("Error getting documents: ", error);
});
or try this, but you must have the id beforehand
export const deleteDocument = (id) => {
return (dispatch) => {
firebase.firestore()
.collection("contracts")
.doc(id)
.delete()
}
}
You can now do this:
db.collection("cities").doc("DC").delete().then(function() {
console.log("Document successfully deleted!");
}).catch(function(error) {
console.error("Error removing document: ", error);
});
And of course, you can use await/async:
exports.delete = functions.https.onRequest(async (req, res) => {
try {
var jobskill_ref = db.collection('job_skills').where('job_id','==',post.job_id).get();
jobskill_ref.forEach((doc) => {
doc.ref.delete();
});
} catch (error) {
return res.json({
status: 'error', msg: 'Error while deleting', data: error,
});
}
});
I have no idea why you have to get() them and loop on them, then delete() them, while you can prepare one query with where to delete in one step like any SQL statement, but Google decided to do it like that. so, for now, this is the only option.
If you're using Cloud Firestore on the Client side, you can use a Unique key generator package/module like uuid to generate an ID. Then you set the ID of the document to the ID generated from uuid and store a reference to the ID on the object you're storing in Firestore.
For example:
If you wanted to save a person object to Firestore, first, you'll use uuid to generate an ID for the person, before saving like below.
const uuid = require('uuid')
const person = { name: "Adebola Adeniran", age: 19}
const id = uuid() //generates a unique random ID of type string
const personObjWithId = {person, id}
export const sendToFireStore = async (person) => {
await db.collection("people").doc(id).set(personObjWithId);
};
// To delete, get the ID you've stored with the object and call // the following firestore query
export const deleteFromFireStore = async (id) => {
await db.collection("people").doc(id).delete();
};
Hope this helps anyone using firestore on the Client side.
The way I resolved this is by giving each document a uniqueID, querying on that field, getting the documentID of the returned document, and using that in the delete. Like so:
(Swift)
func rejectFriendRequest(request: Request) {
DispatchQueue.global().async {
self.db.collection("requests")
.whereField("uniqueID", isEqualTo: request.uniqueID)
.getDocuments { querySnapshot, error in
if let e = error {
print("There was an error fetching that document: \(e)")
} else {
self.db.collection("requests")
.document(querySnapshot!.documents.first!.documentID)
.delete() { err in
if let e = err {
print("There was an error deleting that document: \(e)")
} else {
print("Document successfully deleted!")
}
}
}
}
}
}
The code could be cleaned up a bit, but this is the solution I came up with. Hope it can help someone in the future!
const firestoreCollection = db.collection('job_skills')
var docIds = (await firestoreCollection.where("folderId", "==", folderId).get()).docs.map((doc => doc.id))
// for single result
await firestoreCollection.doc(docIds[0]).delete()
// for multiple result
await Promise.all(
docIds.map(
async(docId) => await firestoreCollection.doc(docId).delete()
)
)
delete(seccion: string, subseccion: string)
{
const deletlist = this.db.collection('seccionesclass', ref => ref.where('seccion', '==', seccion).where('subseccion', '==' , subseccion))
deletlist.get().subscribe(delitems => delitems.forEach( doc=> doc.ref.delete()));
alert('record erased');
}
The code for Kotlin, including failure listeners (both for the query and for the delete of each document):
fun deleteJobs(jobId: String) {
db.collection("jobs").whereEqualTo("job_id", jobId).get()
.addOnSuccessListener { documentSnapshots ->
for (documentSnapshot in documentSnapshots)
documentSnapshot.reference.delete().addOnFailureListener { e ->
Log.e(TAG, "deleteJobs: failed to delete document ${documentSnapshot.reference.id}", e)
}
}.addOnFailureListener { e ->
Log.e(TAG, "deleteJobs: query failed", e)
}
}

Categories