diff --git a/README.md b/README.md index a662b688..f6c31c3c 100644 --- a/README.md +++ b/README.md @@ -68,11 +68,11 @@ If you are interested in what drove the need for this checkout [the why section] const cypressFirebasePlugin = require("cypress-firebase").plugin; module.exports = (on, config) => { - const extendedConfig = cypressFirebasePlugin(on, config, admin) + const extendedConfig = cypressFirebasePlugin(on, config, admin); // Add other plugins/tasks such as code coverage here - return extendedConfig + return extendedConfig; }; ``` @@ -85,7 +85,8 @@ If you are interested in what drove the need for this checkout [the why section] }); }); ``` -1. From the root of your project, start Cypress with the command `$(npm bin)/cypress open`. In the Cypress window, click your new test (`test_hello_world.js`) to run it. + +1. From the root of your project, start Cypress with the command `$(npm bin)/cypress open`. In the Cypress window, click your new test (`test_hello_world.js`) to run it. 1. Look in your Firestore instance and see the `test_hello_world` collection to confirm that a document was added. 1. Pat yourself on the back, you are all setup to access Firebase/Firestore from within your tests! @@ -198,6 +199,19 @@ const fakeProject = { some: "data" }; cy.callRtdb("set", "projects/ABC123", fakeProject, { withMeta: true }); ``` +_Set Data With Timestamps_ + +```javascript +import firebase from "firebase/app"; +import "firebase/database"; + +const fakeProject = { + some: "data", + createdAt: firebase.database.ServerValue.TIMESTAMP, +}; +cy.callRtdb("set", "projects/ABC123", fakeProject); +``` + _Get/Verify Data_ ```javascript @@ -236,6 +250,22 @@ _Basic_ cy.callFirestore("set", "project/test-project", "fakeProject.json"); ``` +_Set Data With Server Timestamps_ + +```javascript +import firebase from "firebase/app"; +import "firebase/firestore"; + +const fakeProject = { + some: "data", + // Use new firebase.firestore.Timestamp.now in place of serverTimestamp() + createdAt: firebase.firestore.Timestamp.now(), + // Or use fromDate if you would like to specify a date + // createdAt: firebase.firestore.Timestamp.fromDate(new Date()) +}; +cy.callFirestore("set", "projects/ABC123", fakeProject); +``` + _Recursive Delete_ ```javascript @@ -444,13 +474,13 @@ Pass `commandNames` in the `options` object to `attachCustomCommands`: const options = { // Key is current command name, value is new command name commandNames: { - login: 'newNameForLogin', - logout: 'newNameForLogout', - callRtdb: 'newNameForCallRtdb', - callFirestore: 'newNameForCallFirestore', - getAuthUser: 'newNameForGetAuthUser', - } -} + login: "newNameForLogin", + logout: "newNameForLogout", + callRtdb: "newNameForCallRtdb", + callFirestore: "newNameForCallFirestore", + getAuthUser: "newNameForGetAuthUser", + }, +}; attachCustomCommands({ Cypress, cy, firebase }, options); ``` @@ -548,6 +578,14 @@ When testing, tests should have admin read/write access to the database for seed - [fireadmin.io][fireadmin-url] - A Firebase project management tool ([here is the source][fireadmin-source]) - [cv19assist.com](https://cv19assist.com) - App for connecting volunteers with at-health-risk population during the coronavirus pandemic. ([here is the source](https://github.com/CV19Assist/app)) +## Troubleshooting + +1. An error is coming from cypress mentioning "Error converting circular structure to JSON" + +The issue is most likely due to a circular object, such as a timestamp, being included in data you are attempting to write to Firestore. Instead of using `firebase.firestore.FieldValue.serverTimestamp()` you should instead use `firebase.firestore.Timestamp.now()` or you would like to specify a certain date `firebase.firestore.Timestamp.fromDate(new Date('01/01/18'))`. + +This comes from the fact that cypress stringifies values as it is passing them from the browser environment to the node environment through `cy.task`. + [1]: #cylogin [2]: #examples [3]: #currentuser diff --git a/package.json b/package.json index 181b9f24..2ab1756f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cypress-firebase", - "version": "1.3.0", + "version": "1.4.0", "description": "Utilities to help testing Firebase projects with Cypress.", "main": "lib/index.js", "module": "lib/index.js", diff --git a/src/attachCustomCommands.ts b/src/attachCustomCommands.ts index 5b31c7a1..e3d62825 100644 --- a/src/attachCustomCommands.ts +++ b/src/attachCustomCommands.ts @@ -415,7 +415,7 @@ export default function attachCustomCommands( dataToWrite.createdBy = Cypress.env('TEST_UID'); } if (!dataToWrite.createdAt) { - dataToWrite.createdAt = firebase.firestore.FieldValue.serverTimestamp(); + dataToWrite.createdAt = firebase.firestore.Timestamp.now(); } } taskSettings.data = dataToWrite; diff --git a/src/tasks.ts b/src/tasks.ts index ed5aa63a..6fde9f56 100644 --- a/src/tasks.ts +++ b/src/tasks.ts @@ -38,6 +38,28 @@ function optionsToRtdbRef(baseRef: any, options?: CallRtdbOptions): any { return newRef; } +/** + * @param dataVal - Value of data + * @param firestoreStatics - Statics from firestore instance + * @returns Value converted into timestamp object if possible + */ +function convertValueToTimestampIfPossible( + dataVal: any, + firestoreStatics: typeof admin.firestore, +): admin.firestore.FieldValue { + if (dataVal?._methodName === 'FieldValue.serverTimestamp') { + return firestoreStatics.FieldValue.serverTimestamp(); + } + if ( + typeof dataVal?.seconds === 'number' && + typeof dataVal?.nanoseconds === 'number' + ) { + return new firestoreStatics.Timestamp(dataVal.seconds, dataVal.nanoseconds); + } + + return dataVal; +} + /** * @param data - Data to be set in firestore * @param firestoreStatics - Statics from Firestore object @@ -52,19 +74,22 @@ function getDataWithTimestamps( return data; } return Object.keys(data).reduce((acc, currKey) => { - /* eslint-disable-next-line no-underscore-dangle */ - if (typeof data[currKey] === 'object' && !data[currKey]._methodName) { + if ( + typeof data[currKey] === 'object' && + /* eslint-disable-next-line no-underscore-dangle */ + !data[currKey]._methodName && + !data[currKey].seconds + ) { return { ...acc, [currKey]: getDataWithTimestamps(data[currKey], firestoreStatics), }; } - const isTimestamp = - data[currKey]?._methodName === 'FieldValue.serverTimestamp'; - const value = isTimestamp - ? firestoreStatics.FieldValue.serverTimestamp() - : data[currKey]; + const value = convertValueToTimestampIfPossible( + data[currKey], + firestoreStatics, + ); return { ...acc, @@ -218,6 +243,7 @@ export function callFirestore( // Tests do not have statics since they are using @firebase/testing options?.statics || (adminInstance.firestore as typeof admin.firestore), ); + if (action === 'set') { return adminInstance .firestore()