Firebase Stub: simple implementation of data changes listener

This commit is contained in:
gianantoniopini
2021-01-14 14:48:07 +01:00
parent 6d36c27889
commit 46781bba60
3 changed files with 135 additions and 79 deletions

View File

@ -281,7 +281,7 @@ describe('FirebaseStub', () => {
expect(reference.equalToValue).toHaveLength(0); expect(reference.equalToValue).toHaveLength(0);
}); });
it('triggers callback with resumes when creating new one', async () => { it('triggers callback with resumes when creating a new one', async () => {
const userUid = DatabaseConstants.user1.uid; const userUid = DatabaseConstants.user1.uid;
let snapshotValue = null; let snapshotValue = null;
@ -333,5 +333,56 @@ describe('FirebaseStub', () => {
expect(snapshotValue[newResume.id]).toBeTruthy(); expect(snapshotValue[newResume.id]).toBeTruthy();
expect(snapshotValue[newResume.id].id).toBe(newResume.id); expect(snapshotValue[newResume.id].id).toBe(newResume.id);
}); });
it('triggers callback with resumes when updating an existing one', async () => {
const userUid = DatabaseConstants.user1.uid;
let snapshotValue = null;
const callback = jest.fn((snapshot) => {
snapshotValue = snapshot.val();
});
FirebaseStub.database()
.ref(DatabaseConstants.resumesPath)
.orderByChild('user')
.equalTo(userUid)
.on('value', callback);
await waitFor(() => callback.mock.calls[0][0]);
expect(callback.mock.calls).toHaveLength(1);
callback.mockClear();
expect(snapshotValue).not.toBeNull();
expect(Object.keys(snapshotValue)).toHaveLength(2);
Object.values(snapshotValue).forEach((resume) =>
expect(resume.user).toEqual(userUid),
);
snapshotValue = null;
const existingResume = (
await FirebaseStub.database()
.ref(
`${DatabaseConstants.resumesPath}/${DatabaseConstants.demoStateResume1Id}`,
)
.once('value')
).val();
expect(existingResume).toBeTruthy();
expect(existingResume.user).toEqual(userUid);
existingResume.name = 'Test Resume renamed';
await FirebaseStub.database()
.ref(`${DatabaseConstants.resumesPath}/${existingResume.id}`)
.update(existingResume);
await waitFor(() => callback.mock.calls[0][0]);
expect(callback.mock.calls).toHaveLength(1);
callback.mockClear();
expect(snapshotValue).not.toBeNull();
expect(Object.keys(snapshotValue)).toHaveLength(2);
expect(snapshotValue[existingResume.id]).toBeTruthy();
expect(snapshotValue[existingResume.id].name).toBe(existingResume.name);
});
}); });
}); });

View File

@ -39,7 +39,7 @@ class Database {
return this._uuid; return this._uuid;
} }
getData(dataPath) { _getData(dataPath) {
if (!dataPath) { if (!dataPath) {
throw new Error('dataPath must be provided.'); throw new Error('dataPath must be provided.');
} }
@ -64,12 +64,41 @@ class Database {
return null; return null;
} }
getReference(referencePath) { _getReference(referencePath) {
return referencePath in this._references return referencePath in this._references
? this._references[referencePath] ? this._references[referencePath]
: null; : null;
} }
_setData(dataPath, value) {
if (!dataPath) {
throw new Error('dataPath must be provided.');
}
if (typeof value === 'undefined') {
throw new Error('value is undefined.');
}
const dataPathElements = dataPath.split('/');
if (dataPathElements.length !== 2) {
return;
}
if (!(dataPathElements[0] in this._data)) {
return;
}
if (!dataPathElements[1]) {
return;
}
if (value === null) {
delete this._data[dataPathElements[0]][dataPathElements[1]];
} else {
this._data[dataPathElements[0]][dataPathElements[1]] = value;
}
}
initializeData() { initializeData() {
const resumes = {}; const resumes = {};
@ -106,7 +135,7 @@ class Database {
} }
ref(referencePath) { ref(referencePath) {
const existingRef = this.getReference(referencePath); const existingRef = this._getReference(referencePath);
if (existingRef) { if (existingRef) {
existingRef.initializeQueryParameters(); existingRef.initializeQueryParameters();
return existingRef; return existingRef;
@ -114,38 +143,13 @@ class Database {
const newRef = new Reference( const newRef = new Reference(
referencePath, referencePath,
(dataPath) => this.getData(dataPath), (dataPath) => this._getData(dataPath),
(dataPath, value) => this.setData(dataPath, value), (dataPath, value) => this._setData(dataPath, value),
(refPath) => this.getReference(refPath), (refPath) => this._getReference(refPath),
); );
this._references[newRef.path] = newRef; this._references[newRef.path] = newRef;
return newRef; return newRef;
} }
setData(dataPath, value) {
if (!dataPath) {
throw new Error('dataPath must be provided.');
}
if (typeof value === 'undefined') {
throw new Error('value is undefined.');
}
const dataPathElements = dataPath.split('/');
if (dataPathElements.length !== 2) {
return;
}
if (!(dataPathElements[0] in this._data)) {
return;
}
if (!dataPathElements[1]) {
return;
}
this._data[dataPathElements[0]][dataPathElements[1]] = value;
}
} }
export default Database; export default Database;

View File

@ -68,27 +68,7 @@ class Reference {
return this._equalToValue; return this._equalToValue;
} }
debounceEventCallback(eventType) { _getData() {
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 debouncedEventCallback = debounce(
this.eventCallbacks[eventType],
100,
);
debouncedEventCallback(snapshot);
}
getData() {
const databaseData = this._getDatabaseData(this.path); const databaseData = this._getDatabaseData(this.path);
if (!databaseData) { if (!databaseData) {
@ -106,6 +86,49 @@ class Reference {
return databaseData; return databaseData;
} }
_setData(value) {
if (typeof value === 'undefined') {
throw new Error('value must be provided.');
}
this._setDatabaseData(this.path, value);
this.debounceEventCallback(DatabaseConstants.valueEventType);
const pathElements = this.path.split('/');
if (pathElements.length === 2) {
const parentReference = this._getReference(pathElements[0]);
if (parentReference) {
parentReference.debounceEventCallback(DatabaseConstants.valueEventType);
}
}
}
debounceEventCallback(eventType) {
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 debouncedEventCallback = debounce(
this.eventCallbacks[eventType],
100,
);
debouncedEventCallback(snapshot);
}
equalTo(value) {
this._equalToValue = value;
return this;
}
initializeQueryParameters() { initializeQueryParameters() {
this._orderByChildPath = ''; this._orderByChildPath = '';
this._equalToValue = ''; this._equalToValue = '';
@ -126,7 +149,7 @@ class Reference {
} }
async once(eventType) { async once(eventType) {
const newDataSnapshot = new DataSnapshot(eventType, () => this.getData()); const newDataSnapshot = new DataSnapshot(eventType, () => this._getData());
const existingDataSnapshot = this._dataSnapshots[newDataSnapshot.eventType]; const existingDataSnapshot = this._dataSnapshots[newDataSnapshot.eventType];
if (existingDataSnapshot) { if (existingDataSnapshot) {
return Promise.resolve(existingDataSnapshot); return Promise.resolve(existingDataSnapshot);
@ -141,36 +164,14 @@ class Reference {
return this; return this;
} }
equalTo(value) {
this._equalToValue = value;
return this;
}
async update(value) { async update(value) {
if (typeof value === 'undefined') { this._setData(value);
throw new Error('value must be provided.');
}
const result = this !== null; return Promise.resolve(true);
return Promise.resolve(result);
} }
async set(value) { async set(value) {
if (typeof value === 'undefined') { this._setData(value);
throw new Error('value must be provided.');
}
this._setDatabaseData(this.path, value);
this.debounceEventCallback(DatabaseConstants.valueEventType);
const pathElements = this.path.split('/');
if (pathElements.length === 2) {
const parentReference = this._getReference(pathElements[0]);
if (parentReference) {
parentReference.debounceEventCallback(DatabaseConstants.valueEventType);
}
}
return Promise.resolve(true); return Promise.resolve(true);
} }