diff --git a/__mocks__/__tests__/gatsby-plugin-firebase.test.js b/__mocks__/__tests__/gatsby-plugin-firebase.test.js index dc11cf38..985f278c 100644 --- a/__mocks__/__tests__/gatsby-plugin-firebase.test.js +++ b/__mocks__/__tests__/gatsby-plugin-firebase.test.js @@ -384,5 +384,63 @@ describe('FirebaseStub', () => { expect(snapshotValue[existingResume.id]).toBeTruthy(); expect(snapshotValue[existingResume.id].name).toBe(existingResume.name); }); + + it('triggers callback with removed resume when removing an existing one', async () => { + const userUid = DatabaseConstants.user1.uid; + + let valueCallbackSnapshotValue = null; + const valueCallback = jest.fn((snapshot) => { + valueCallbackSnapshotValue = snapshot.val(); + }); + FirebaseStub.database() + .ref(DatabaseConstants.resumesPath) + .orderByChild('user') + .equalTo(userUid) + .on('value', valueCallback); + await waitFor(() => valueCallback.mock.calls[0][0]); + expect(valueCallback.mock.calls).toHaveLength(1); + valueCallback.mockClear(); + expect(valueCallbackSnapshotValue).not.toBeNull(); + expect(Object.keys(valueCallbackSnapshotValue)).toHaveLength(2); + Object.values(valueCallbackSnapshotValue).forEach((resume) => + expect(resume.user).toEqual(userUid), + ); + valueCallbackSnapshotValue = null; + + let childRemovedCallbackSnapshotValue = null; + const childRemovedCallback = jest.fn((snapshot) => { + childRemovedCallbackSnapshotValue = snapshot.val(); + }); + FirebaseStub.database() + .ref(DatabaseConstants.resumesPath) + .orderByChild('user') + .equalTo(userUid) + .on('child_removed', childRemovedCallback); + + const removedResume = ( + await FirebaseStub.database() + .ref( + `${DatabaseConstants.resumesPath}/${DatabaseConstants.demoStateResume1Id}`, + ) + .once('value') + ).val(); + expect(removedResume).toBeTruthy(); + expect(removedResume.user).toEqual(userUid); + await FirebaseStub.database() + .ref(`${DatabaseConstants.resumesPath}/${removedResume.id}`) + .remove(); + + await waitFor(() => childRemovedCallback.mock.calls[0][0]); + expect(childRemovedCallback.mock.calls).toHaveLength(1); + childRemovedCallback.mockClear(); + expect(childRemovedCallbackSnapshotValue).toBeTruthy(); + expect(childRemovedCallbackSnapshotValue.id).toBe(removedResume.id); + + await waitFor(() => valueCallback.mock.calls[0][0]); + expect(valueCallback.mock.calls).toHaveLength(1); + valueCallback.mockClear(); + expect(valueCallbackSnapshotValue).toBeTruthy(); + expect(removedResume.id in valueCallbackSnapshotValue).toBe(false); + }); }); }); diff --git a/__mocks__/gatsby-plugin-firebase/constants/database.js b/__mocks__/gatsby-plugin-firebase/constants/database.js index 3c17834d..6ea40ad8 100644 --- a/__mocks__/gatsby-plugin-firebase/constants/database.js +++ b/__mocks__/gatsby-plugin-firebase/constants/database.js @@ -1,6 +1,7 @@ import AuthConstants from './auth'; const valueEventType = 'value'; +const childRemovedEventType = 'child_removed'; const resumesPath = 'resumes'; const usersPath = 'users'; @@ -24,6 +25,10 @@ class Database { return valueEventType; } + static get childRemovedEventType() { + return childRemovedEventType; + } + static get resumesPath() { return resumesPath; } diff --git a/__mocks__/gatsby-plugin-firebase/database/dataSnapshot.js b/__mocks__/gatsby-plugin-firebase/database/dataSnapshot.js index f6b3c704..07f029fe 100644 --- a/__mocks__/gatsby-plugin-firebase/database/dataSnapshot.js +++ b/__mocks__/gatsby-plugin-firebase/database/dataSnapshot.js @@ -1,16 +1,6 @@ /* eslint-disable no-underscore-dangle */ -import DatabaseConstants from '../constants/database'; - class DataSnapshot { - constructor(eventType, getData, value = undefined) { - if (!eventType) { - throw new Error('eventType must be provided.'); - } else if (typeof eventType !== 'string') { - throw new Error('eventType should be a string.'); - } - - this._eventType = eventType; - + constructor(getData, value = undefined) { if (!getData) { throw new Error('getData must be provided.'); } else if (typeof getData !== 'function') { @@ -22,20 +12,12 @@ class DataSnapshot { this._value = value; } - get eventType() { - return this._eventType; - } - get value() { return this._value; } val() { - if (this.eventType === DatabaseConstants.valueEventType) { - return typeof this.value !== 'undefined' ? this.value : this._getData(); - } - - return undefined; + return typeof this.value !== 'undefined' ? this.value : this._getData(); } } diff --git a/__mocks__/gatsby-plugin-firebase/database/reference.js b/__mocks__/gatsby-plugin-firebase/database/reference.js index 117906ed..fe685bf7 100644 --- a/__mocks__/gatsby-plugin-firebase/database/reference.js +++ b/__mocks__/gatsby-plugin-firebase/database/reference.js @@ -17,7 +17,7 @@ class Reference { this._uuid = uuidv4(); - this._dataSnapshots = {}; + this._dataSnapshot = null; if (!getDatabaseData) { throw new Error('getDatabaseData must be provided.'); @@ -91,31 +91,42 @@ class Reference { throw new Error('value must be provided.'); } - this._setDatabaseData(this.path, value); - - this.debounceEventCallback(DatabaseConstants.valueEventType); + const currentData = this._getData(); const pathElements = this.path.split('/'); + let parentReference = null; if (pathElements.length === 2) { - const parentReference = this._getReference(pathElements[0]); + parentReference = this._getReference(pathElements[0]); + } + + this._setDatabaseData(this.path, value); + + if (value === null) { if (parentReference) { + parentReference.debounceEventCallback( + DatabaseConstants.childRemovedEventType, + currentData, + ); parentReference.debounceEventCallback(DatabaseConstants.valueEventType); } + } else { + const eventType = DatabaseConstants.valueEventType; + this.debounceEventCallback(eventType); + if (parentReference) { + parentReference.debounceEventCallback(eventType); + } } } - debounceEventCallback(eventType) { + debounceEventCallback(eventType, snapshotValue = undefined) { if (!(eventType in this.eventCallbacks)) { return; } - let snapshot = new DataSnapshot(eventType, () => this._getData(), null); - - if (this.path === DatabaseConstants.connectedPath) { - snapshot = new DataSnapshot(eventType, () => this._getData(), true); - } else if (this.path === DatabaseConstants.resumesPath) { - snapshot = new DataSnapshot(eventType, () => this._getData()); - } + const snapshot = + this.path === DatabaseConstants.connectedPath + ? new DataSnapshot(() => this._getData(), true) + : new DataSnapshot(() => this._getData(), snapshotValue); const debouncedEventCallback = debounce( this.eventCallbacks[eventType], @@ -139,23 +150,27 @@ class Reference { } on(eventType, callback) { - if (eventType !== DatabaseConstants.valueEventType) { - return; - } - this._eventCallbacks[eventType] = callback; - this.debounceEventCallback(eventType); + if (eventType === DatabaseConstants.valueEventType) { + this.debounceEventCallback(eventType); + } } async once(eventType) { - const newDataSnapshot = new DataSnapshot(eventType, () => this._getData()); - const existingDataSnapshot = this._dataSnapshots[newDataSnapshot.eventType]; - if (existingDataSnapshot) { - return Promise.resolve(existingDataSnapshot); + if (!eventType) { + throw new Error('eventType must be provided.'); + } else if (typeof eventType !== 'string') { + throw new Error('eventType should be a string.'); } - this._dataSnapshots[newDataSnapshot.eventType] = newDataSnapshot; + if (this._dataSnapshot) { + return Promise.resolve(this._dataSnapshot); + } + + const newDataSnapshot = new DataSnapshot(() => this._getData()); + this._dataSnapshot = newDataSnapshot; + return Promise.resolve(newDataSnapshot); } @@ -170,6 +185,12 @@ class Reference { return Promise.resolve(true); } + async remove() { + this._setData(null); + + return Promise.resolve(true); + } + async set(value) { this._setData(value);