diff --git a/__mocks__/__tests__/gatsby-plugin-firebase_2.test.js b/__mocks__/__tests__/gatsby-plugin-firebase.test.js similarity index 98% rename from __mocks__/__tests__/gatsby-plugin-firebase_2.test.js rename to __mocks__/__tests__/gatsby-plugin-firebase.test.js index 88cac6d7..09602da6 100644 --- a/__mocks__/__tests__/gatsby-plugin-firebase_2.test.js +++ b/__mocks__/__tests__/gatsby-plugin-firebase.test.js @@ -1,4 +1,4 @@ -import FirebaseStub from '../gatsby-plugin-firebase_2'; +import FirebaseStub from '../gatsby-plugin-firebase'; describe('database', () => { it('reuses existing Database instance', () => { diff --git a/__mocks__/gatsby-plugin-firebase.js b/__mocks__/gatsby-plugin-firebase.js index 97d9bdf7..b5217bf3 100644 --- a/__mocks__/gatsby-plugin-firebase.js +++ b/__mocks__/gatsby-plugin-firebase.js @@ -1,171 +1,302 @@ import path from 'path'; import fs from 'fs'; +import { v4 as uuidv4 } from 'uuid'; -const __testUser = { - email: 'test.user@noemail.com', - name: 'Test User', - uid: 'testuser123', -}; -let __onAuthStateChangedObservers = []; -let __resumesDictionary = {}; -let __databaseRefUpdateCalls = []; +class Auth { + static #instance = undefined; + #uuid = ''; + #onAuthStateChangedObservers = []; -const auth = () => { - const __init = () => { - __onAuthStateChangedObservers = []; - }; + constructor() { + if (Auth.#instance) { + return Auth.#instance; + } - const onAuthStateChanged = (observer) => { - __onAuthStateChangedObservers.push(observer); + Auth.#instance = this; + + this.#uuid = uuidv4(); + } + + get uuid() { + return this.#uuid; + } + + get onAuthStateChangedObservers() { + return this.#onAuthStateChangedObservers; + } + + clearOnAuthStateChangedObservers() { + this.#onAuthStateChangedObservers = []; + } + + onAuthStateChanged(observer) { + this.#onAuthStateChangedObservers.push(observer); return () => { - __onAuthStateChangedObservers = __onAuthStateChangedObservers.filter( + this.#onAuthStateChangedObservers = this.#onAuthStateChangedObservers.filter( (observer) => observer !== observer, ); }; + } + + async signInAnonymously() { + this.#onAuthStateChangedObservers.forEach((observer) => + observer(Database.testUser), + ); + + return Promise.resolve(Database.testUser); + } +} + +class Database { + static testUser = { + email: 'test.user@noemail.com', + name: 'Test User', + uid: 'testuser123', }; + static resumesPath = 'resumes'; + static usersPath = 'users'; + static #instance = undefined; + #uuid = ''; + #data = {}; + #references = {}; - const signInAnonymously = async () => { - __onAuthStateChangedObservers.forEach((observer) => observer(__testUser)); + constructor() { + if (Database.#instance) { + return Database.#instance; + } - var result = await Promise.resolve(__testUser); - return result; - }; + Database.#instance = this; - return { - __init, - onAuthStateChanged, - signInAnonymously, - }; -}; + this.#uuid = uuidv4(); + } -const database = () => { - const __demoResumeId = 'demore'; - const __emptyResumeId = 'mtre01'; + get testUser() { + return Database.testUser; + } - const __init = () => { - __resumesDictionary = {}; - __databaseRefUpdateCalls = []; + get demoResumeId() { + return 'demore'; + } + get emptyResumeId() { + return 'mtre01'; + } - const demoResume = __readFile('../src/data/demoState.json'); - __resumesDictionary[__demoResumeId] = demoResume; - const emptyResume = __readFile('../src/data/initialState.json'); - __resumesDictionary[__emptyResumeId] = emptyResume; + get uuid() { + return this.#uuid; + } - for (var key in __resumesDictionary) { - const resume = __resumesDictionary[key]; + static readFile(fileRelativePath) { + const fileAbsolutePath = path.resolve(__dirname, fileRelativePath); + const fileBuffer = fs.readFileSync(fileAbsolutePath); + const fileData = JSON.parse(fileBuffer); + return fileData; + } + + initializeData() { + const resumes = {}; + const demoResume = Database.readFile('../src/data/demoState.json'); + resumes[this.demoResumeId] = demoResume; + const emptyResume = Database.readFile('../src/data/initialState.json'); + resumes[this.emptyResumeId] = emptyResume; + + for (var key in resumes) { + const resume = resumes[key]; resume.id = key; resume.name = `Test Resume ${key}`; - resume.user = __testUser.uid; + resume.user = this.testUser.uid; let date = new Date('December 15, 2020 11:20:25'); resume.updatedAt = date.valueOf(); date.setMonth(date.getMonth() - 2); resume.createdAt = date.valueOf(); } - }; - const __readFile = (fileRelativePath) => { - const fileAbsolutePath = path.resolve(__dirname, fileRelativePath); - const fileBuffer = fs.readFileSync(fileAbsolutePath); - const fileData = JSON.parse(fileBuffer); - return fileData; - }; + this.#data[Database.resumesPath] = resumes; - const ref = (path) => { - if (!path) { - throw new Error('Not implemented.'); + const users = {}; + users[this.testUser.uid] = this.testUser; + this.#data[Database.usersPath] = users; + } + + ref(path) { + const newRef = new Reference(path, () => this.#data); + const existingRef = this.#references[newRef.path]; + if (existingRef) { + return existingRef; } - const resumesPath = path.startsWith('resumes/'); - const usersPath = path.startsWith('users/'); - if (!resumesPath && !usersPath) { - throw new Error('Unknown Reference path.'); + this.#references[newRef.path] = newRef; + return newRef; + } +} + +class Reference { + #rootPath = '.'; + #path = ''; + #uuid = ''; + #dataSnapshots = {}; + #getDatabaseData = () => null; + + constructor(path, getDatabaseData) { + if (typeof path === 'undefined' || path === null) { + this.#path = this.#rootPath; + } else if (typeof path !== 'string') { + throw new Error('path should be a string.'); } - const databaseLocationId = path.substring(path.indexOf('/') + 1); - if (!databaseLocationId) { - throw new Error('Unknown database location id.'); + if (!getDatabaseData) { + throw new Error('getDatabaseData must be provided.'); + } else if (typeof getDatabaseData !== 'function') { + throw new Error('getDatabaseData should be a function.'); } - const once = async (eventType) => { - if (!eventType) { - throw new Error('Event type must be provided.'); + this.#path = path; + this.#uuid = uuidv4(); + this.#getDatabaseData = getDatabaseData; + } + + get path() { + return this.#path; + } + + get uuid() { + return this.#uuid; + } + + getData() { + const databaseData = this.#getDatabaseData(); + + if (!databaseData) { + return null; + } + + if (this.#path === this.#rootPath) { + return databaseData; + } + + if ( + this.#path === Database.resumesPath || + this.#path === Database.usersPath + ) { + return this.#path in databaseData ? databaseData[this.#path] : null; + } + + if ( + this.#path.startsWith(`${Database.resumesPath}/`) || + this.#path.startsWith(`${Database.usersPath}/`) + ) { + const databaseLocationId = this.#path.substring( + this.#path.indexOf('/') + 1, + ); + if (!databaseLocationId) { + throw new Error('Unknown database location id.'); } - if (eventType !== 'value') { - throw new Error('Unknown event type.'); + const pathWithoutId = this.#path.substring(0, this.#path.indexOf('/')); + if (pathWithoutId in databaseData) { + return databaseLocationId in databaseData[pathWithoutId] + ? databaseData[pathWithoutId][databaseLocationId] + : null; } + } - const val = () => { - if (resumesPath) { - return __resumesDictionary[databaseLocationId] - ? __resumesDictionary[databaseLocationId] - : null; - } + return null; + } - if (usersPath) { - return __testUser; - } + async once(eventType) { + const newDataSnapshot = new DataSnapshot(eventType, this); + const existingDataSnapshot = this.#dataSnapshots[newDataSnapshot.eventType]; + if (existingDataSnapshot) { + return Promise.resolve(existingDataSnapshot); + } - return null; - }; + this.#dataSnapshots[newDataSnapshot.eventType] = newDataSnapshot; + return Promise.resolve(newDataSnapshot); + } - return Promise.resolve({ val }); - }; + async update(value) { + return Promise.resolve(true); + } - const set = (value) => { - if (resumesPath) { - if (value === null) { - delete __resumesDictionary[databaseLocationId]; - } else { - __resumesDictionary[databaseLocationId] = value; - } + /* + const update = async (value) => { + if (resumesPath) { + if (value === null) { + delete __resumesDictionary[databaseLocationId]; + } else { + __resumesDictionary[databaseLocationId] = value; } + } - return Promise.resolve(true); - }; - - const update = async (value) => { - if (resumesPath) { - if (value === null) { - delete __resumesDictionary[databaseLocationId]; - } else { - __resumesDictionary[databaseLocationId] = value; - } - } - - __databaseRefUpdateCalls.push(value); - - return Promise.resolve(true); - }; - - return { - once, - set, - update, - __updateCalls: __databaseRefUpdateCalls, - }; + return Promise.resolve(true); }; - return { - __demoResumeId, - __emptyResumeId, - __init, - ref, - }; -}; + const set = (value) => { + if (resumesPath) { + if (value === null) { + delete __resumesDictionary[databaseLocationId]; + } else { + __resumesDictionary[databaseLocationId] = value; + } + } -database.ServerValue = {}; -Object.defineProperty(database.ServerValue, 'TIMESTAMP', { + return Promise.resolve(true); + }; + */ +} + +class DataSnapshot { + #eventType = ''; + #reference = null; + + constructor(eventType, reference) { + 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; + + if (!reference) { + throw new Error('reference must be provided.'); + } else if (!(reference instanceof Reference)) { + throw new Error('reference must be an instance of the Reference class.'); + } + + this.#reference = reference; + } + + get eventType() { + return this.#eventType; + } + + val() { + if (this.eventType === 'value') { + return this.#reference.getData(); + } + + return undefined; + } +} + +class FirebaseStub { + static auth() { + return new Auth(); + } + + static database() { + return new Database(); + } +} + +FirebaseStub.database.ServerValue = {}; +Object.defineProperty(FirebaseStub.database.ServerValue, 'TIMESTAMP', { get() { return new Date().getTime(); }, }); -export default class firebase { - static auth = auth; - - static database = database; -} +export default FirebaseStub; diff --git a/__mocks__/gatsby-plugin-firebase_2.js b/__mocks__/gatsby-plugin-firebase_2.js deleted file mode 100644 index b5217bf3..00000000 --- a/__mocks__/gatsby-plugin-firebase_2.js +++ /dev/null @@ -1,302 +0,0 @@ -import path from 'path'; -import fs from 'fs'; -import { v4 as uuidv4 } from 'uuid'; - -class Auth { - static #instance = undefined; - #uuid = ''; - #onAuthStateChangedObservers = []; - - constructor() { - if (Auth.#instance) { - return Auth.#instance; - } - - Auth.#instance = this; - - this.#uuid = uuidv4(); - } - - get uuid() { - return this.#uuid; - } - - get onAuthStateChangedObservers() { - return this.#onAuthStateChangedObservers; - } - - clearOnAuthStateChangedObservers() { - this.#onAuthStateChangedObservers = []; - } - - onAuthStateChanged(observer) { - this.#onAuthStateChangedObservers.push(observer); - - return () => { - this.#onAuthStateChangedObservers = this.#onAuthStateChangedObservers.filter( - (observer) => observer !== observer, - ); - }; - } - - async signInAnonymously() { - this.#onAuthStateChangedObservers.forEach((observer) => - observer(Database.testUser), - ); - - return Promise.resolve(Database.testUser); - } -} - -class Database { - static testUser = { - email: 'test.user@noemail.com', - name: 'Test User', - uid: 'testuser123', - }; - static resumesPath = 'resumes'; - static usersPath = 'users'; - static #instance = undefined; - #uuid = ''; - #data = {}; - #references = {}; - - constructor() { - if (Database.#instance) { - return Database.#instance; - } - - Database.#instance = this; - - this.#uuid = uuidv4(); - } - - get testUser() { - return Database.testUser; - } - - get demoResumeId() { - return 'demore'; - } - get emptyResumeId() { - return 'mtre01'; - } - - get uuid() { - return this.#uuid; - } - - static readFile(fileRelativePath) { - const fileAbsolutePath = path.resolve(__dirname, fileRelativePath); - const fileBuffer = fs.readFileSync(fileAbsolutePath); - const fileData = JSON.parse(fileBuffer); - return fileData; - } - - initializeData() { - const resumes = {}; - const demoResume = Database.readFile('../src/data/demoState.json'); - resumes[this.demoResumeId] = demoResume; - const emptyResume = Database.readFile('../src/data/initialState.json'); - resumes[this.emptyResumeId] = emptyResume; - - for (var key in resumes) { - const resume = resumes[key]; - - resume.id = key; - resume.name = `Test Resume ${key}`; - resume.user = this.testUser.uid; - - let date = new Date('December 15, 2020 11:20:25'); - resume.updatedAt = date.valueOf(); - date.setMonth(date.getMonth() - 2); - resume.createdAt = date.valueOf(); - } - - this.#data[Database.resumesPath] = resumes; - - const users = {}; - users[this.testUser.uid] = this.testUser; - this.#data[Database.usersPath] = users; - } - - ref(path) { - const newRef = new Reference(path, () => this.#data); - const existingRef = this.#references[newRef.path]; - if (existingRef) { - return existingRef; - } - - this.#references[newRef.path] = newRef; - return newRef; - } -} - -class Reference { - #rootPath = '.'; - #path = ''; - #uuid = ''; - #dataSnapshots = {}; - #getDatabaseData = () => null; - - constructor(path, getDatabaseData) { - if (typeof path === 'undefined' || path === null) { - this.#path = this.#rootPath; - } else if (typeof path !== 'string') { - throw new Error('path should be a string.'); - } - - if (!getDatabaseData) { - throw new Error('getDatabaseData must be provided.'); - } else if (typeof getDatabaseData !== 'function') { - throw new Error('getDatabaseData should be a function.'); - } - - this.#path = path; - this.#uuid = uuidv4(); - this.#getDatabaseData = getDatabaseData; - } - - get path() { - return this.#path; - } - - get uuid() { - return this.#uuid; - } - - getData() { - const databaseData = this.#getDatabaseData(); - - if (!databaseData) { - return null; - } - - if (this.#path === this.#rootPath) { - return databaseData; - } - - if ( - this.#path === Database.resumesPath || - this.#path === Database.usersPath - ) { - return this.#path in databaseData ? databaseData[this.#path] : null; - } - - if ( - this.#path.startsWith(`${Database.resumesPath}/`) || - this.#path.startsWith(`${Database.usersPath}/`) - ) { - const databaseLocationId = this.#path.substring( - this.#path.indexOf('/') + 1, - ); - if (!databaseLocationId) { - throw new Error('Unknown database location id.'); - } - - const pathWithoutId = this.#path.substring(0, this.#path.indexOf('/')); - if (pathWithoutId in databaseData) { - return databaseLocationId in databaseData[pathWithoutId] - ? databaseData[pathWithoutId][databaseLocationId] - : null; - } - } - - return null; - } - - async once(eventType) { - const newDataSnapshot = new DataSnapshot(eventType, this); - const existingDataSnapshot = this.#dataSnapshots[newDataSnapshot.eventType]; - if (existingDataSnapshot) { - return Promise.resolve(existingDataSnapshot); - } - - this.#dataSnapshots[newDataSnapshot.eventType] = newDataSnapshot; - return Promise.resolve(newDataSnapshot); - } - - async update(value) { - return Promise.resolve(true); - } - - /* - const update = async (value) => { - if (resumesPath) { - if (value === null) { - delete __resumesDictionary[databaseLocationId]; - } else { - __resumesDictionary[databaseLocationId] = value; - } - } - - return Promise.resolve(true); - }; - - const set = (value) => { - if (resumesPath) { - if (value === null) { - delete __resumesDictionary[databaseLocationId]; - } else { - __resumesDictionary[databaseLocationId] = value; - } - } - - return Promise.resolve(true); - }; - */ -} - -class DataSnapshot { - #eventType = ''; - #reference = null; - - constructor(eventType, reference) { - 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; - - if (!reference) { - throw new Error('reference must be provided.'); - } else if (!(reference instanceof Reference)) { - throw new Error('reference must be an instance of the Reference class.'); - } - - this.#reference = reference; - } - - get eventType() { - return this.#eventType; - } - - val() { - if (this.eventType === 'value') { - return this.#reference.getData(); - } - - return undefined; - } -} - -class FirebaseStub { - static auth() { - return new Auth(); - } - - static database() { - return new Database(); - } -} - -FirebaseStub.database.ServerValue = {}; -Object.defineProperty(FirebaseStub.database.ServerValue, 'TIMESTAMP', { - get() { - return new Date().getTime(); - }, -}); - -export default FirebaseStub; diff --git a/src/pages/app/__tests__/mock.test.js b/src/pages/app/__tests__/mock.test.js index 3e7429c9..3995727f 100644 --- a/src/pages/app/__tests__/mock.test.js +++ b/src/pages/app/__tests__/mock.test.js @@ -1,9 +1,9 @@ import { cleanup, waitFor } from '@testing-library/react'; -import firebaseMock from 'gatsby-plugin-firebase'; +import FirebaseStub from 'gatsby-plugin-firebase'; beforeEach(() => { - firebaseMock.database().__init(); + FirebaseStub.database().initializeData(); }); afterEach(cleanup); @@ -13,9 +13,9 @@ describe('builder', () => { let resume = null; beforeEach(async () => { - resumeId = firebaseMock.database().__demoResumeId; + resumeId = FirebaseStub.database().demoResumeId; resume = ( - await firebaseMock.database().ref(`resumes/${resumeId}`).once('value') + await FirebaseStub.database().ref(`resumes/${resumeId}`).once('value') ).val(); }); @@ -23,17 +23,19 @@ describe('builder', () => { const now = new Date().getTime(); const newInputValue = 'test street 123'; resume.profile.address.line1 = newInputValue; - const ref = firebaseMock.database().ref(`resumes/${resumeId}`); - const functionSpy = jest.spyOn(ref, 'update'); + const functionSpy = jest.spyOn( + FirebaseStub.database().ref(`resumes/${resumeId}`), + 'update', + ); - await ref.update({ - ...resume, - updatedAt: firebaseMock.database.ServerValue.TIMESTAMP, - }); + await FirebaseStub.database() + .ref(`resumes/${resumeId}`) + .update({ + ...resume, + updatedAt: FirebaseStub.database.ServerValue.TIMESTAMP, + }); - await waitFor(() => expect(functionSpy).toHaveBeenCalledTimes(1), { - timeout: 4000, - }); + await waitFor(() => expect(functionSpy).toHaveBeenCalledTimes(1)); const functionCallArgument = functionSpy.mock.calls[0][0]; expect(functionCallArgument.id).toBe(resume.id); expect(functionCallArgument.profile.address.line1).toBe(newInputValue);