mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2025-11-18 18:51:32 +10:00
Merge pull request #423 from gianantoniopini/develop
Unit testing environment set up and addition of some unit tests
This commit is contained in:
@ -8,8 +8,13 @@
|
||||
"FileReader": true,
|
||||
"localStorage": true
|
||||
},
|
||||
"extends": ["airbnb", "prettier"],
|
||||
"plugins": ["prettier"],
|
||||
"extends": [
|
||||
"airbnb",
|
||||
"plugin:jest/recommended",
|
||||
"plugin:jest/style",
|
||||
"prettier"
|
||||
],
|
||||
"plugins": ["jest", "prettier"],
|
||||
"rules": {
|
||||
"import/no-extraneous-dependencies": ["error", { "devDependencies": true }],
|
||||
"react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }],
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -20,6 +20,9 @@ coverage
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Coverage directory used by Jest
|
||||
test-coverage
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
|
||||
555
__mocks__/__tests__/gatsby-plugin-firebase.test.js
Normal file
555
__mocks__/__tests__/gatsby-plugin-firebase.test.js
Normal file
@ -0,0 +1,555 @@
|
||||
import { waitFor } from '@testing-library/react';
|
||||
import FirebaseStub, {
|
||||
AuthConstants,
|
||||
DatabaseConstants,
|
||||
} from '../gatsby-plugin-firebase';
|
||||
|
||||
describe('FirebaseStub', () => {
|
||||
describe('auth', () => {
|
||||
afterEach(() => {
|
||||
FirebaseStub.auth().dispose();
|
||||
});
|
||||
|
||||
it('reuses existing Auth instance', () => {
|
||||
const auth1 = FirebaseStub.auth();
|
||||
const auth2 = FirebaseStub.auth();
|
||||
|
||||
expect(auth1.uuid).toBeTruthy();
|
||||
expect(auth2.uuid).toBeTruthy();
|
||||
expect(auth1.uuid).toEqual(auth2.uuid);
|
||||
});
|
||||
|
||||
it('returns anonymous user 1 when signing in anonymously', async () => {
|
||||
const user = await FirebaseStub.auth().signInAnonymously();
|
||||
|
||||
expect(user).toBeTruthy();
|
||||
expect(user).toEqual(AuthConstants.anonymousUser1);
|
||||
});
|
||||
|
||||
it('calls onAuthStateChanged observer with anonymous user 1 when signing in anonymously', async () => {
|
||||
let user = null;
|
||||
let error = null;
|
||||
FirebaseStub.auth().onAuthStateChanged(
|
||||
(_user) => {
|
||||
user = _user;
|
||||
},
|
||||
(_error) => {
|
||||
error = _error;
|
||||
},
|
||||
);
|
||||
|
||||
await FirebaseStub.auth().signInAnonymously();
|
||||
|
||||
expect(user).toBeTruthy();
|
||||
expect(user).toEqual(AuthConstants.anonymousUser1);
|
||||
expect(error).toBeNull();
|
||||
});
|
||||
|
||||
it('onAuthStateChanged unsubscribe removes observer', () => {
|
||||
const observer = () => {};
|
||||
const unsubscribe = FirebaseStub.auth().onAuthStateChanged(observer);
|
||||
expect(unsubscribe).toBeTruthy();
|
||||
expect(FirebaseStub.auth().onAuthStateChangedObservers).toHaveLength(1);
|
||||
expect(FirebaseStub.auth().onAuthStateChangedObservers[0]).toEqual(
|
||||
observer,
|
||||
);
|
||||
|
||||
unsubscribe();
|
||||
|
||||
expect(FirebaseStub.auth().onAuthStateChangedObservers).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('database', () => {
|
||||
beforeEach(() => {
|
||||
FirebaseStub.database().initializeData();
|
||||
});
|
||||
|
||||
it('reuses existing Database instance', () => {
|
||||
const database1 = FirebaseStub.database();
|
||||
const database2 = FirebaseStub.database();
|
||||
|
||||
expect(database1.uuid).toBeTruthy();
|
||||
expect(database2.uuid).toBeTruthy();
|
||||
expect(database1.uuid).toEqual(database2.uuid);
|
||||
});
|
||||
|
||||
describe('ref function', () => {
|
||||
it('reuses existing Reference instance', () => {
|
||||
const ref1 = FirebaseStub.database().ref(
|
||||
`${DatabaseConstants.resumesPath}/123`,
|
||||
);
|
||||
const ref2 = FirebaseStub.database().ref(
|
||||
`${DatabaseConstants.resumesPath}/123`,
|
||||
);
|
||||
|
||||
expect(ref1).toBeTruthy();
|
||||
expect(ref2).toBeTruthy();
|
||||
expect(ref1).toEqual(ref2);
|
||||
});
|
||||
|
||||
it('leading slash in reference path is ignored', () => {
|
||||
const path = `${DatabaseConstants.resumesPath}/123`;
|
||||
|
||||
const ref1 = FirebaseStub.database().ref(path);
|
||||
expect(ref1).toBeTruthy();
|
||||
expect(ref1.path).toEqual(path);
|
||||
|
||||
const ref2 = FirebaseStub.database().ref(`/${path}`);
|
||||
expect(ref2).toBeTruthy();
|
||||
expect(ref2).toEqual(ref1);
|
||||
});
|
||||
});
|
||||
|
||||
it('ServerValue.TIMESTAMP returns current time in milliseconds', () => {
|
||||
const now = new Date().getTime();
|
||||
const timestamp = FirebaseStub.database.ServerValue.TIMESTAMP;
|
||||
|
||||
expect(timestamp).toBeTruthy();
|
||||
expect(timestamp).toBeGreaterThanOrEqual(now);
|
||||
});
|
||||
|
||||
it('initializing data sets up resumes and users', async () => {
|
||||
const resumesRef = FirebaseStub.database().ref(
|
||||
DatabaseConstants.resumesPath,
|
||||
);
|
||||
const resumesDataSnapshot = await resumesRef.once('value');
|
||||
const resumes = resumesDataSnapshot.val();
|
||||
expect(resumes).toBeTruthy();
|
||||
expect(Object.keys(resumes)).toHaveLength(3);
|
||||
const demoStateResume1 = resumes[DatabaseConstants.demoStateResume1Id];
|
||||
expect(demoStateResume1).toBeTruthy();
|
||||
expect(demoStateResume1.id).toEqual(DatabaseConstants.demoStateResume1Id);
|
||||
expect(demoStateResume1.user).toEqual(DatabaseConstants.user1.uid);
|
||||
const demoStateResume2 = resumes[DatabaseConstants.demoStateResume2Id];
|
||||
expect(demoStateResume2).toBeTruthy();
|
||||
expect(demoStateResume2.id).toEqual(DatabaseConstants.demoStateResume2Id);
|
||||
expect(demoStateResume2.user).toEqual(DatabaseConstants.user2.uid);
|
||||
const initialStateResume =
|
||||
resumes[DatabaseConstants.initialStateResumeId];
|
||||
expect(initialStateResume).toBeTruthy();
|
||||
expect(initialStateResume.id).toEqual(
|
||||
DatabaseConstants.initialStateResumeId,
|
||||
);
|
||||
expect(initialStateResume.user).toEqual(DatabaseConstants.user1.uid);
|
||||
|
||||
const usersRef = FirebaseStub.database().ref(DatabaseConstants.usersPath);
|
||||
const usersDataSnapshot = await usersRef.once('value');
|
||||
const users = usersDataSnapshot.val();
|
||||
expect(users).toBeTruthy();
|
||||
expect(Object.keys(users)).toHaveLength(2);
|
||||
const anonymousUser1 = users[DatabaseConstants.user1.uid];
|
||||
expect(anonymousUser1).toBeTruthy();
|
||||
expect(anonymousUser1).toEqual(DatabaseConstants.user1);
|
||||
const anonymousUser2 = users[DatabaseConstants.user2.uid];
|
||||
expect(anonymousUser2).toBeTruthy();
|
||||
expect(anonymousUser2).toEqual(DatabaseConstants.user2);
|
||||
});
|
||||
|
||||
it('retrieves resume if it exists', async () => {
|
||||
const resume = (
|
||||
await FirebaseStub.database()
|
||||
.ref(
|
||||
`${DatabaseConstants.resumesPath}/${DatabaseConstants.demoStateResume1Id}`,
|
||||
)
|
||||
.once('value')
|
||||
).val();
|
||||
|
||||
expect(resume).toBeTruthy();
|
||||
expect(resume.id).toEqual(DatabaseConstants.demoStateResume1Id);
|
||||
});
|
||||
|
||||
it('retrieves null if resume does not exist', async () => {
|
||||
const resumeId = 'invalidResumeId';
|
||||
|
||||
const resume = (
|
||||
await FirebaseStub.database()
|
||||
.ref(`${DatabaseConstants.resumesPath}/${resumeId}`)
|
||||
.once('value')
|
||||
).val();
|
||||
|
||||
expect(resume).toBeNull();
|
||||
});
|
||||
|
||||
it('retrieves user if it exists', async () => {
|
||||
const user = (
|
||||
await FirebaseStub.database()
|
||||
.ref(`${DatabaseConstants.usersPath}/${DatabaseConstants.user1.uid}`)
|
||||
.once('value')
|
||||
).val();
|
||||
|
||||
expect(user).toBeTruthy();
|
||||
expect(user).toEqual(DatabaseConstants.user1);
|
||||
});
|
||||
|
||||
it('retrieves null if user does not exist', async () => {
|
||||
const userId = 'invalidUserId';
|
||||
|
||||
const user = (
|
||||
await FirebaseStub.database()
|
||||
.ref(`${DatabaseConstants.usersPath}/${userId}`)
|
||||
.once('value')
|
||||
).val();
|
||||
|
||||
expect(user).toBeNull();
|
||||
});
|
||||
|
||||
describe('on function', () => {
|
||||
describe('value event', () => {
|
||||
it('triggers event with true if on the connected reference path', async () => {
|
||||
let snapshotValue = null;
|
||||
|
||||
FirebaseStub.database()
|
||||
.ref(DatabaseConstants.connectedPath)
|
||||
.on('value', (snapshot) => {
|
||||
snapshotValue = snapshot.val();
|
||||
});
|
||||
|
||||
await waitFor(() =>
|
||||
snapshotValue ? Promise.resolve(true) : Promise.reject(),
|
||||
);
|
||||
|
||||
expect(snapshotValue).not.toBeNull();
|
||||
expect(snapshotValue).toBe(true);
|
||||
});
|
||||
|
||||
it('triggers event with resumes if on the resumes reference path', async () => {
|
||||
const resumesDataSnapshot = await FirebaseStub.database()
|
||||
.ref(DatabaseConstants.resumesPath)
|
||||
.once('value');
|
||||
const resumes = resumesDataSnapshot.val();
|
||||
let snapshotValue = null;
|
||||
|
||||
FirebaseStub.database()
|
||||
.ref(DatabaseConstants.resumesPath)
|
||||
.on('value', (snapshot) => {
|
||||
snapshotValue = snapshot.val();
|
||||
});
|
||||
|
||||
await waitFor(() =>
|
||||
snapshotValue ? Promise.resolve(true) : Promise.reject(),
|
||||
);
|
||||
|
||||
expect(snapshotValue).not.toBeNull();
|
||||
expect(snapshotValue).toEqual(resumes);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('can filter resumes by user', async () => {
|
||||
let snapshotValue = null;
|
||||
|
||||
FirebaseStub.database()
|
||||
.ref(DatabaseConstants.resumesPath)
|
||||
.orderByChild('user')
|
||||
.equalTo(DatabaseConstants.user1.uid)
|
||||
.on('value', (snapshot) => {
|
||||
snapshotValue = snapshot.val();
|
||||
});
|
||||
|
||||
await waitFor(() =>
|
||||
snapshotValue ? Promise.resolve(true) : Promise.reject(),
|
||||
);
|
||||
|
||||
expect(snapshotValue).not.toBeNull();
|
||||
expect(Object.keys(snapshotValue)).toHaveLength(2);
|
||||
Object.values(snapshotValue).forEach((resume) =>
|
||||
expect(resume.user).toEqual(DatabaseConstants.user1.uid),
|
||||
);
|
||||
});
|
||||
|
||||
it('previously set query parameters are not kept when retrieving reference again', async () => {
|
||||
let reference = null;
|
||||
|
||||
reference = FirebaseStub.database().ref(DatabaseConstants.resumesPath);
|
||||
expect(reference).toBeTruthy();
|
||||
const { uuid } = reference;
|
||||
expect(reference.orderByChildPath).toHaveLength(0);
|
||||
expect(reference.equalToValue).toHaveLength(0);
|
||||
|
||||
reference = FirebaseStub.database()
|
||||
.ref(DatabaseConstants.resumesPath)
|
||||
.orderByChild('user')
|
||||
.equalTo('testuser1');
|
||||
expect(reference).toBeTruthy();
|
||||
expect(reference.uuid).toBe(uuid);
|
||||
expect(reference.orderByChildPath).toBe('user');
|
||||
expect(reference.equalToValue).toBe('testuser1');
|
||||
|
||||
reference = FirebaseStub.database().ref(DatabaseConstants.resumesPath);
|
||||
expect(reference).toBeTruthy();
|
||||
expect(reference.uuid).toBe(uuid);
|
||||
expect(reference.orderByChildPath).toHaveLength(0);
|
||||
expect(reference.equalToValue).toHaveLength(0);
|
||||
});
|
||||
|
||||
describe('set function', () => {
|
||||
it('inserts data', async () => {
|
||||
const existingResume = (
|
||||
await FirebaseStub.database()
|
||||
.ref(
|
||||
`${DatabaseConstants.resumesPath}/${DatabaseConstants.demoStateResume1Id}`,
|
||||
)
|
||||
.once('value')
|
||||
).val();
|
||||
expect(existingResume).toBeTruthy();
|
||||
|
||||
const newResume = JSON.parse(JSON.stringify(existingResume));
|
||||
newResume.id = 'newre1';
|
||||
newResume.name = `Test Resume ${newResume.id}`;
|
||||
await FirebaseStub.database()
|
||||
.ref(`${DatabaseConstants.resumesPath}/${newResume.id}`)
|
||||
.set(newResume);
|
||||
|
||||
const actualResume = (
|
||||
await FirebaseStub.database()
|
||||
.ref(`${DatabaseConstants.resumesPath}/${newResume.id}`)
|
||||
.once('value')
|
||||
).val();
|
||||
expect(actualResume).toBeTruthy();
|
||||
expect(actualResume).toEqual(newResume);
|
||||
});
|
||||
|
||||
it('triggers events', async () => {
|
||||
let snapshotValue = null;
|
||||
const callback = jest.fn((snapshot) => {
|
||||
snapshotValue = snapshot.val();
|
||||
});
|
||||
FirebaseStub.database()
|
||||
.ref(DatabaseConstants.resumesPath)
|
||||
.orderByChild('user')
|
||||
.equalTo(DatabaseConstants.user1.uid)
|
||||
.on('value', callback);
|
||||
await waitFor(() => callback.mock.calls[0][0]);
|
||||
callback.mockClear();
|
||||
snapshotValue = null;
|
||||
|
||||
const existingResume = (
|
||||
await FirebaseStub.database()
|
||||
.ref(
|
||||
`${DatabaseConstants.resumesPath}/${DatabaseConstants.demoStateResume1Id}`,
|
||||
)
|
||||
.once('value')
|
||||
).val();
|
||||
expect(existingResume).toBeTruthy();
|
||||
|
||||
const newResume = JSON.parse(JSON.stringify(existingResume));
|
||||
newResume.id = 'newre1';
|
||||
newResume.name = `Test Resume ${newResume.id}`;
|
||||
await FirebaseStub.database()
|
||||
.ref(`${DatabaseConstants.resumesPath}/${newResume.id}`)
|
||||
.set(newResume);
|
||||
|
||||
await waitFor(() => callback.mock.calls[0][0]);
|
||||
|
||||
expect(callback.mock.calls).toHaveLength(1);
|
||||
expect(snapshotValue).not.toBeNull();
|
||||
expect(Object.keys(snapshotValue)).toHaveLength(3);
|
||||
expect(snapshotValue[newResume.id]).toBeTruthy();
|
||||
expect(snapshotValue[newResume.id]).toEqual(newResume);
|
||||
});
|
||||
});
|
||||
|
||||
describe('update function', () => {
|
||||
it('can spy on it', async () => {
|
||||
const referencePath = `${DatabaseConstants.resumesPath}/123456`;
|
||||
const functionSpy = jest.spyOn(
|
||||
FirebaseStub.database().ref(referencePath),
|
||||
'update',
|
||||
);
|
||||
const updateArgument = 'test value 123';
|
||||
|
||||
await FirebaseStub.database().ref(referencePath).update(updateArgument);
|
||||
|
||||
expect(functionSpy).toHaveBeenCalledTimes(1);
|
||||
const functionCallArgument = functionSpy.mock.calls[0][0];
|
||||
expect(functionCallArgument).toBeTruthy();
|
||||
expect(functionCallArgument).toEqual(updateArgument);
|
||||
});
|
||||
|
||||
it('updates data', async () => {
|
||||
const resumeId = DatabaseConstants.demoStateResume1Id;
|
||||
const existingResume = (
|
||||
await FirebaseStub.database()
|
||||
.ref(`${DatabaseConstants.resumesPath}/${resumeId}`)
|
||||
.once('value')
|
||||
).val();
|
||||
expect(existingResume).toBeTruthy();
|
||||
|
||||
const resumeName = 'Test Resume renamed';
|
||||
existingResume.name = resumeName;
|
||||
await FirebaseStub.database()
|
||||
.ref(`${DatabaseConstants.resumesPath}/${resumeId}`)
|
||||
.update(existingResume);
|
||||
|
||||
const actualResume = (
|
||||
await FirebaseStub.database()
|
||||
.ref(`${DatabaseConstants.resumesPath}/${resumeId}`)
|
||||
.once('value')
|
||||
).val();
|
||||
expect(actualResume).toBeTruthy();
|
||||
expect(existingResume).toEqual(actualResume);
|
||||
expect(actualResume.name).toEqual(resumeName);
|
||||
});
|
||||
|
||||
it('triggers events', async () => {
|
||||
let snapshotValue = null;
|
||||
const callback = jest.fn((snapshot) => {
|
||||
snapshotValue = snapshot.val();
|
||||
});
|
||||
FirebaseStub.database()
|
||||
.ref(DatabaseConstants.resumesPath)
|
||||
.orderByChild('user')
|
||||
.equalTo(DatabaseConstants.user1.uid)
|
||||
.on('value', callback);
|
||||
await waitFor(() => callback.mock.calls[0][0]);
|
||||
callback.mockClear();
|
||||
snapshotValue = null;
|
||||
|
||||
const existingResume = (
|
||||
await FirebaseStub.database()
|
||||
.ref(
|
||||
`${DatabaseConstants.resumesPath}/${DatabaseConstants.demoStateResume1Id}`,
|
||||
)
|
||||
.once('value')
|
||||
).val();
|
||||
expect(existingResume).toBeTruthy();
|
||||
|
||||
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);
|
||||
expect(snapshotValue).not.toBeNull();
|
||||
expect(Object.keys(snapshotValue)).toHaveLength(2);
|
||||
expect(snapshotValue[existingResume.id]).toBeTruthy();
|
||||
expect(snapshotValue[existingResume.id]).toEqual(existingResume);
|
||||
});
|
||||
});
|
||||
|
||||
describe('remove function', () => {
|
||||
it('deletes data', async () => {
|
||||
const resumeId = DatabaseConstants.demoStateResume1Id;
|
||||
const removedResume = (
|
||||
await FirebaseStub.database()
|
||||
.ref(`${DatabaseConstants.resumesPath}/${resumeId}`)
|
||||
.once('value')
|
||||
).val();
|
||||
expect(removedResume).toBeTruthy();
|
||||
|
||||
await FirebaseStub.database()
|
||||
.ref(`${DatabaseConstants.resumesPath}/${resumeId}`)
|
||||
.remove();
|
||||
|
||||
const actualResume = (
|
||||
await FirebaseStub.database()
|
||||
.ref(`${DatabaseConstants.resumesPath}/${resumeId}`)
|
||||
.once('value')
|
||||
).val();
|
||||
expect(actualResume).toBeNull();
|
||||
});
|
||||
|
||||
it('triggers events', 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]);
|
||||
valueCallback.mockClear();
|
||||
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);
|
||||
expect(childRemovedCallbackSnapshotValue).toBeTruthy();
|
||||
expect(childRemovedCallbackSnapshotValue).toEqual(removedResume);
|
||||
|
||||
await waitFor(() => valueCallback.mock.calls[0][0]);
|
||||
expect(valueCallback.mock.calls).toHaveLength(1);
|
||||
expect(valueCallbackSnapshotValue).toBeTruthy();
|
||||
expect(removedResume.id in valueCallbackSnapshotValue).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('off function', () => {
|
||||
it('removes event callbacks', 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]);
|
||||
valueCallback.mockClear();
|
||||
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();
|
||||
|
||||
FirebaseStub.database().ref(DatabaseConstants.resumesPath).off();
|
||||
|
||||
await FirebaseStub.database()
|
||||
.ref(`${DatabaseConstants.resumesPath}/${removedResume.id}`)
|
||||
.remove();
|
||||
|
||||
expect(childRemovedCallback.mock.calls).toHaveLength(0);
|
||||
expect(childRemovedCallbackSnapshotValue).toBeNull();
|
||||
expect(valueCallback.mock.calls).toHaveLength(0);
|
||||
expect(valueCallbackSnapshotValue).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
2
__mocks__/file-mock.js
Normal file
2
__mocks__/file-mock.js
Normal file
@ -0,0 +1,2 @@
|
||||
const mockFile = 'test-file-stub';
|
||||
export default mockFile;
|
||||
24
__mocks__/gatsby-plugin-firebase.js
Normal file
24
__mocks__/gatsby-plugin-firebase.js
Normal file
@ -0,0 +1,24 @@
|
||||
import Auth from './gatsby-plugin-firebase/auth/auth';
|
||||
import Database from './gatsby-plugin-firebase/database/database';
|
||||
import AuthConstants from './gatsby-plugin-firebase/constants/auth';
|
||||
import DatabaseConstants from './gatsby-plugin-firebase/constants/database';
|
||||
|
||||
class FirebaseStub {
|
||||
static auth() {
|
||||
return Auth.instance;
|
||||
}
|
||||
|
||||
static database() {
|
||||
return Database.instance;
|
||||
}
|
||||
}
|
||||
|
||||
FirebaseStub.database.ServerValue = {};
|
||||
Object.defineProperty(FirebaseStub.database.ServerValue, 'TIMESTAMP', {
|
||||
get() {
|
||||
return new Date().getTime();
|
||||
},
|
||||
});
|
||||
|
||||
export default FirebaseStub;
|
||||
export { AuthConstants, DatabaseConstants };
|
||||
58
__mocks__/gatsby-plugin-firebase/auth/auth.js
Normal file
58
__mocks__/gatsby-plugin-firebase/auth/auth.js
Normal file
@ -0,0 +1,58 @@
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import Constants from '../constants/auth';
|
||||
|
||||
const singleton = Symbol('');
|
||||
const singletonEnforcer = Symbol('');
|
||||
|
||||
class Auth {
|
||||
constructor(enforcer) {
|
||||
if (enforcer !== singletonEnforcer) {
|
||||
throw new Error('Cannot construct singleton');
|
||||
}
|
||||
|
||||
this._uuid = uuidv4();
|
||||
this._onAuthStateChangedObservers = [];
|
||||
}
|
||||
|
||||
static get instance() {
|
||||
if (!this[singleton]) {
|
||||
this[singleton] = new Auth(singletonEnforcer);
|
||||
}
|
||||
|
||||
return this[singleton];
|
||||
}
|
||||
|
||||
get uuid() {
|
||||
return this._uuid;
|
||||
}
|
||||
|
||||
get onAuthStateChangedObservers() {
|
||||
return this._onAuthStateChangedObservers;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._onAuthStateChangedObservers = [];
|
||||
}
|
||||
|
||||
onAuthStateChanged(observer) {
|
||||
this.onAuthStateChangedObservers.push(observer);
|
||||
|
||||
return () => {
|
||||
this._onAuthStateChangedObservers = this.onAuthStateChangedObservers.filter(
|
||||
(obs) => obs !== observer,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
async signInAnonymously() {
|
||||
const user = Constants.anonymousUser1;
|
||||
|
||||
this.onAuthStateChangedObservers.forEach((observer) => observer(user));
|
||||
|
||||
return Promise.resolve(user);
|
||||
}
|
||||
}
|
||||
|
||||
export default Auth;
|
||||
27
__mocks__/gatsby-plugin-firebase/constants/auth.js
Normal file
27
__mocks__/gatsby-plugin-firebase/constants/auth.js
Normal file
@ -0,0 +1,27 @@
|
||||
const anonymousUser1 = {
|
||||
displayName: 'Anonymous User 1',
|
||||
email: 'anonymous1@noemail.com',
|
||||
isAnonymous: true,
|
||||
name: 'Anonymous 1',
|
||||
uid: 'anonym123',
|
||||
};
|
||||
|
||||
const anonymousUser2 = {
|
||||
displayName: 'Anonymous User 2',
|
||||
email: 'anonymous2@noemail.com',
|
||||
isAnonymous: true,
|
||||
name: 'Anonymous 2',
|
||||
uid: 'anonym456',
|
||||
};
|
||||
|
||||
class Auth {
|
||||
static get anonymousUser1() {
|
||||
return anonymousUser1;
|
||||
}
|
||||
|
||||
static get anonymousUser2() {
|
||||
return anonymousUser2;
|
||||
}
|
||||
}
|
||||
|
||||
export default Auth;
|
||||
65
__mocks__/gatsby-plugin-firebase/constants/database.js
Normal file
65
__mocks__/gatsby-plugin-firebase/constants/database.js
Normal file
@ -0,0 +1,65 @@
|
||||
import AuthConstants from './auth';
|
||||
|
||||
const valueEventType = 'value';
|
||||
const childRemovedEventType = 'child_removed';
|
||||
|
||||
const resumesPath = 'resumes';
|
||||
const usersPath = 'users';
|
||||
const connectedPath = '.info/connected';
|
||||
|
||||
const demoStateResume1Id = 'demo_1';
|
||||
const demoStateResume2Id = 'demo_2';
|
||||
const initialStateResumeId = 'initst';
|
||||
|
||||
const user1 = {
|
||||
uid: AuthConstants.anonymousUser1.uid,
|
||||
isAnonymous: AuthConstants.anonymousUser1.isAnonymous,
|
||||
};
|
||||
const user2 = {
|
||||
uid: AuthConstants.anonymousUser2.uid,
|
||||
isAnonymous: AuthConstants.anonymousUser2.isAnonymous,
|
||||
};
|
||||
|
||||
class Database {
|
||||
static get valueEventType() {
|
||||
return valueEventType;
|
||||
}
|
||||
|
||||
static get childRemovedEventType() {
|
||||
return childRemovedEventType;
|
||||
}
|
||||
|
||||
static get resumesPath() {
|
||||
return resumesPath;
|
||||
}
|
||||
|
||||
static get usersPath() {
|
||||
return usersPath;
|
||||
}
|
||||
|
||||
static get connectedPath() {
|
||||
return connectedPath;
|
||||
}
|
||||
|
||||
static get demoStateResume1Id() {
|
||||
return demoStateResume1Id;
|
||||
}
|
||||
|
||||
static get demoStateResume2Id() {
|
||||
return demoStateResume2Id;
|
||||
}
|
||||
|
||||
static get initialStateResumeId() {
|
||||
return initialStateResumeId;
|
||||
}
|
||||
|
||||
static get user1() {
|
||||
return user1;
|
||||
}
|
||||
|
||||
static get user2() {
|
||||
return user2;
|
||||
}
|
||||
}
|
||||
|
||||
export default Database;
|
||||
24
__mocks__/gatsby-plugin-firebase/database/dataSnapshot.js
Normal file
24
__mocks__/gatsby-plugin-firebase/database/dataSnapshot.js
Normal file
@ -0,0 +1,24 @@
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
class DataSnapshot {
|
||||
constructor(getData, value = undefined) {
|
||||
if (!getData) {
|
||||
throw new Error('getData must be provided.');
|
||||
} else if (typeof getData !== 'function') {
|
||||
throw new Error('getData should be a function.');
|
||||
}
|
||||
|
||||
this._getData = getData;
|
||||
|
||||
this._value = value;
|
||||
}
|
||||
|
||||
get value() {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
val() {
|
||||
return typeof this.value !== 'undefined' ? this.value : this._getData();
|
||||
}
|
||||
}
|
||||
|
||||
export default DataSnapshot;
|
||||
156
__mocks__/gatsby-plugin-firebase/database/database.js
Normal file
156
__mocks__/gatsby-plugin-firebase/database/database.js
Normal file
@ -0,0 +1,156 @@
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import DatabaseConstants from '../constants/database';
|
||||
import Reference from './reference';
|
||||
|
||||
const singleton = Symbol('');
|
||||
const singletonEnforcer = Symbol('');
|
||||
|
||||
const readFile = (fileRelativePath) => {
|
||||
const fileAbsolutePath = path.resolve(__dirname, fileRelativePath);
|
||||
const fileBuffer = fs.readFileSync(fileAbsolutePath);
|
||||
const fileData = JSON.parse(fileBuffer);
|
||||
return fileData;
|
||||
};
|
||||
|
||||
class Database {
|
||||
constructor(enforcer) {
|
||||
if (enforcer !== singletonEnforcer) {
|
||||
throw new Error('Cannot construct singleton');
|
||||
}
|
||||
|
||||
this._uuid = uuidv4();
|
||||
this._data = {};
|
||||
this._references = {};
|
||||
}
|
||||
|
||||
static get instance() {
|
||||
if (!this[singleton]) {
|
||||
this[singleton] = new Database(singletonEnforcer);
|
||||
}
|
||||
|
||||
return this[singleton];
|
||||
}
|
||||
|
||||
get uuid() {
|
||||
return this._uuid;
|
||||
}
|
||||
|
||||
_getData(dataPath) {
|
||||
if (!dataPath) {
|
||||
throw new Error('dataPath must be provided.');
|
||||
}
|
||||
|
||||
const dataPathElements = dataPath.split('/');
|
||||
if (!(dataPathElements[0] in this._data)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (dataPathElements.length === 1) {
|
||||
return this._data[dataPathElements[0]];
|
||||
}
|
||||
|
||||
if (dataPathElements.length === 2) {
|
||||
if (!(dataPathElements[1] in this._data[dataPathElements[0]])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this._data[dataPathElements[0]][dataPathElements[1]];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
_getReference(referencePath) {
|
||||
return referencePath in this._references
|
||||
? this._references[referencePath]
|
||||
: 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() {
|
||||
const resumes = {};
|
||||
|
||||
const demoStateResume1 = readFile('../../../src/data/demoState.json');
|
||||
const date = new Date('December 15, 2020 11:20:25');
|
||||
demoStateResume1.updatedAt = date.valueOf();
|
||||
date.setMonth(date.getMonth() - 2);
|
||||
demoStateResume1.createdAt = date.valueOf();
|
||||
demoStateResume1.user = DatabaseConstants.user1.uid;
|
||||
resumes[DatabaseConstants.demoStateResume1Id] = demoStateResume1;
|
||||
|
||||
const demoStateResume2 = JSON.parse(JSON.stringify(demoStateResume1));
|
||||
demoStateResume2.user = DatabaseConstants.user2.uid;
|
||||
resumes[DatabaseConstants.demoStateResume2Id] = demoStateResume2;
|
||||
|
||||
const initialStateResume = readFile('../../../src/data/initialState.json');
|
||||
initialStateResume.updatedAt = date.valueOf();
|
||||
initialStateResume.createdAt = date.valueOf();
|
||||
initialStateResume.user = DatabaseConstants.user1.uid;
|
||||
resumes[DatabaseConstants.initialStateResumeId] = initialStateResume;
|
||||
|
||||
Object.keys(resumes).forEach((key) => {
|
||||
const resume = resumes[key];
|
||||
resume.id = key;
|
||||
resume.name = `Test Resume ${key}`;
|
||||
});
|
||||
|
||||
this._data[DatabaseConstants.resumesPath] = resumes;
|
||||
|
||||
const users = {};
|
||||
users[DatabaseConstants.user1.uid] = DatabaseConstants.user1;
|
||||
users[DatabaseConstants.user2.uid] = DatabaseConstants.user2;
|
||||
this._data[DatabaseConstants.usersPath] = users;
|
||||
}
|
||||
|
||||
ref(referencePath) {
|
||||
const newRef = new Reference(
|
||||
referencePath,
|
||||
(dataPath) => this._getData(dataPath),
|
||||
(dataPath, value) => this._setData(dataPath, value),
|
||||
(refPath) => this._getReference(refPath),
|
||||
);
|
||||
|
||||
const existingRef = this._getReference(newRef.path);
|
||||
if (existingRef) {
|
||||
existingRef.initializeQueryParameters();
|
||||
return existingRef;
|
||||
}
|
||||
|
||||
this._references[newRef.path] = newRef;
|
||||
return newRef;
|
||||
}
|
||||
}
|
||||
|
||||
export default Database;
|
||||
213
__mocks__/gatsby-plugin-firebase/database/reference.js
Normal file
213
__mocks__/gatsby-plugin-firebase/database/reference.js
Normal file
@ -0,0 +1,213 @@
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
import DatabaseConstants from '../constants/database';
|
||||
import DataSnapshot from './dataSnapshot';
|
||||
|
||||
const parsePath = (path) => {
|
||||
if (!path) {
|
||||
throw new Error('path must be provided.');
|
||||
} else if (typeof path !== 'string') {
|
||||
throw new Error('path should be a string.');
|
||||
} else {
|
||||
let parsedPath = path.trim();
|
||||
|
||||
if (parsedPath[0] === '/') {
|
||||
parsedPath = parsedPath.substring(1);
|
||||
}
|
||||
|
||||
return parsedPath;
|
||||
}
|
||||
};
|
||||
|
||||
class Reference {
|
||||
constructor(path, getDatabaseData, setDatabaseData, getReference) {
|
||||
this._path = parsePath(path);
|
||||
|
||||
this._uuid = uuidv4();
|
||||
|
||||
if (this.path === DatabaseConstants.connectedPath) {
|
||||
this._dataSnapshot = new DataSnapshot(() => {}, true);
|
||||
} else {
|
||||
this._dataSnapshot = new DataSnapshot(() => this._getData());
|
||||
}
|
||||
|
||||
if (!getDatabaseData) {
|
||||
throw new Error('getDatabaseData must be provided.');
|
||||
} else if (typeof getDatabaseData !== 'function') {
|
||||
throw new Error('getDatabaseData should be a function.');
|
||||
}
|
||||
|
||||
this._getDatabaseData = getDatabaseData;
|
||||
|
||||
if (!setDatabaseData) {
|
||||
throw new Error('setDatabaseData must be provided.');
|
||||
} else if (typeof getDatabaseData !== 'function') {
|
||||
throw new Error('setDatabaseData should be a function.');
|
||||
}
|
||||
|
||||
this._setDatabaseData = setDatabaseData;
|
||||
|
||||
if (!getReference) {
|
||||
throw new Error('getReference must be provided.');
|
||||
} else if (typeof getDatabaseData !== 'function') {
|
||||
throw new Error('getReference should be a function.');
|
||||
}
|
||||
|
||||
this._getReference = getReference;
|
||||
|
||||
this._eventCallbacks = {};
|
||||
|
||||
this.initializeQueryParameters();
|
||||
}
|
||||
|
||||
get path() {
|
||||
return this._path;
|
||||
}
|
||||
|
||||
get uuid() {
|
||||
return this._uuid;
|
||||
}
|
||||
|
||||
get eventCallbacks() {
|
||||
return this._eventCallbacks;
|
||||
}
|
||||
|
||||
get orderByChildPath() {
|
||||
return this._orderByChildPath;
|
||||
}
|
||||
|
||||
get equalToValue() {
|
||||
return this._equalToValue;
|
||||
}
|
||||
|
||||
_getData() {
|
||||
const databaseData = this._getDatabaseData(this.path);
|
||||
|
||||
if (!databaseData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.orderByChildPath && this.equalToValue) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(databaseData).filter(
|
||||
([, value]) => value[this.orderByChildPath] === this.equalToValue,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return databaseData;
|
||||
}
|
||||
|
||||
_getParent() {
|
||||
const pathElements = this.path.split('/');
|
||||
|
||||
let parent = null;
|
||||
if (pathElements.length === 2) {
|
||||
parent = this._getReference(pathElements[0]);
|
||||
}
|
||||
|
||||
return parent;
|
||||
}
|
||||
|
||||
_handleDataUpdate(value) {
|
||||
if (typeof value === 'undefined') {
|
||||
throw new Error('value must be provided.');
|
||||
}
|
||||
|
||||
const currentData = this._getData();
|
||||
const parentReference = this._getParent();
|
||||
|
||||
this._setDatabaseData(this.path, value);
|
||||
|
||||
if (value === null) {
|
||||
if (parentReference) {
|
||||
parentReference.triggerEventCallback(
|
||||
DatabaseConstants.childRemovedEventType,
|
||||
currentData,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this.triggerEventCallback(DatabaseConstants.valueEventType);
|
||||
}
|
||||
|
||||
if (parentReference) {
|
||||
parentReference.triggerEventCallback(DatabaseConstants.valueEventType);
|
||||
}
|
||||
}
|
||||
|
||||
triggerEventCallback(eventType, snapshotValue = undefined) {
|
||||
if (!(eventType in this.eventCallbacks)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const snapshot =
|
||||
this.path === DatabaseConstants.connectedPath
|
||||
? this._dataSnapshot
|
||||
: new DataSnapshot(() => this._getData(), snapshotValue);
|
||||
|
||||
const debouncedEventCallback = debounce(
|
||||
this.eventCallbacks[eventType],
|
||||
100,
|
||||
);
|
||||
debouncedEventCallback(snapshot);
|
||||
}
|
||||
|
||||
equalTo(value) {
|
||||
this._equalToValue = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
initializeQueryParameters() {
|
||||
this._orderByChildPath = '';
|
||||
this._equalToValue = '';
|
||||
}
|
||||
|
||||
off() {
|
||||
this._eventCallbacks = {};
|
||||
}
|
||||
|
||||
on(eventType, callback) {
|
||||
this.eventCallbacks[eventType] = callback;
|
||||
|
||||
if (eventType === DatabaseConstants.valueEventType) {
|
||||
this.triggerEventCallback(eventType);
|
||||
}
|
||||
}
|
||||
|
||||
async once(eventType) {
|
||||
if (!eventType) {
|
||||
throw new Error('eventType must be provided.');
|
||||
} else if (typeof eventType !== 'string') {
|
||||
throw new Error('eventType should be a string.');
|
||||
}
|
||||
|
||||
return Promise.resolve(this._dataSnapshot);
|
||||
}
|
||||
|
||||
orderByChild(path) {
|
||||
this._orderByChildPath = path;
|
||||
return this;
|
||||
}
|
||||
|
||||
async update(value) {
|
||||
this._handleDataUpdate(value);
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
async remove() {
|
||||
this._handleDataUpdate(null);
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
async set(value) {
|
||||
this._handleDataUpdate(value);
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
export default Reference;
|
||||
80
__mocks__/gatsby.js
Normal file
80
__mocks__/gatsby.js
Normal file
@ -0,0 +1,80 @@
|
||||
import React from 'react';
|
||||
|
||||
const Gatsby = jest.requireActual('gatsby');
|
||||
|
||||
const fluidImageShapes = [
|
||||
{
|
||||
aspectRatio: 2,
|
||||
src: 'test_image.jpg',
|
||||
srcSet: 'some srcSet',
|
||||
srcSetWebp: 'some srcSetWebp',
|
||||
sizes: '(max-width: 600px) 100vw, 600px',
|
||||
base64: 'string_of_base64',
|
||||
},
|
||||
{
|
||||
aspectRatio: 3,
|
||||
src: 'test_image_2.jpg',
|
||||
srcSet: 'some other srcSet',
|
||||
srcSetWebp: 'some other srcSetWebp',
|
||||
sizes: '(max-width: 400px) 100vw, 400px',
|
||||
base64: 'string_of_base64',
|
||||
},
|
||||
];
|
||||
|
||||
const useStaticQuery = () => ({
|
||||
site: {
|
||||
siteMetadata: {
|
||||
title: 'Test title',
|
||||
description: 'Test description',
|
||||
author: 'Test author',
|
||||
siteUrl: 'https://testsiteurl/',
|
||||
},
|
||||
},
|
||||
file: {
|
||||
childImageSharp: {
|
||||
fluid: fluidImageShapes[0],
|
||||
},
|
||||
},
|
||||
onyx: {
|
||||
childImageSharp: {
|
||||
fluid: fluidImageShapes[0],
|
||||
},
|
||||
},
|
||||
pikachu: {
|
||||
childImageSharp: {
|
||||
fluid: fluidImageShapes[1],
|
||||
},
|
||||
},
|
||||
gengar: {
|
||||
childImageSharp: {
|
||||
fluid: fluidImageShapes[0],
|
||||
},
|
||||
},
|
||||
castform: {
|
||||
childImageSharp: {
|
||||
fluid: fluidImageShapes[1],
|
||||
},
|
||||
},
|
||||
glalie: {
|
||||
childImageSharp: {
|
||||
fluid: fluidImageShapes[0],
|
||||
},
|
||||
},
|
||||
celebi: {
|
||||
childImageSharp: {
|
||||
fluid: fluidImageShapes[1],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
...Gatsby,
|
||||
graphql: jest.fn(),
|
||||
Link: jest.fn().mockImplementation(({ to, ...rest }) =>
|
||||
React.createElement('a', {
|
||||
...rest,
|
||||
href: to,
|
||||
}),
|
||||
),
|
||||
useStaticQuery,
|
||||
};
|
||||
5
jest-preprocess.js
Normal file
5
jest-preprocess.js
Normal file
@ -0,0 +1,5 @@
|
||||
const babelOptions = {
|
||||
presets: ['babel-preset-gatsby'],
|
||||
};
|
||||
|
||||
module.exports = require('babel-jest').createTransformer(babelOptions);
|
||||
36
jest.config.js
Normal file
36
jest.config.js
Normal file
@ -0,0 +1,36 @@
|
||||
module.exports = {
|
||||
testRegex: '/*.test.js$',
|
||||
collectCoverage: true,
|
||||
collectCoverageFrom: [
|
||||
'**/*.{js,jsx}',
|
||||
'!\\.cache/**',
|
||||
'!node_modules/**',
|
||||
'!public/**',
|
||||
'!test-coverage/**',
|
||||
],
|
||||
coverageDirectory: 'test-coverage',
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
branches: 0,
|
||||
functions: 0,
|
||||
lines: 0,
|
||||
statements: 0,
|
||||
},
|
||||
},
|
||||
verbose: true,
|
||||
transform: {
|
||||
'^.+\\.jsx?$': `<rootDir>/jest-preprocess.js`,
|
||||
},
|
||||
moduleNameMapper: {
|
||||
'.+\\.(css|styl|less|sass|scss)$': `identity-obj-proxy`,
|
||||
'.+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': `<rootDir>/__mocks__/file-mock.js`,
|
||||
},
|
||||
testPathIgnorePatterns: [`node_modules`, `\\.cache`],
|
||||
transformIgnorePatterns: [`node_modules/(?!(gatsby)/)`],
|
||||
globals: {
|
||||
__PATH_PREFIX__: ``,
|
||||
},
|
||||
testURL: `http://localhost`,
|
||||
setupFiles: [`<rootDir>/loadershim.js`],
|
||||
setupFilesAfterEnv: [`<rootDir>/jest.setup.js`],
|
||||
};
|
||||
1
jest.setup.js
Normal file
1
jest.setup.js
Normal file
@ -0,0 +1 @@
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
3
loadershim.js
Normal file
3
loadershim.js
Normal file
@ -0,0 +1,3 @@
|
||||
global.___loader = {
|
||||
enqueue: jest.fn(),
|
||||
};
|
||||
4884
package-lock.json
generated
4884
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@ -7,13 +7,14 @@
|
||||
"scripts": {
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint --fix .",
|
||||
"prebuild": "npm run test",
|
||||
"build": "gatsby build",
|
||||
"develop": "gatsby develop",
|
||||
"format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,md}\"",
|
||||
"start": "npm run develop",
|
||||
"serve": "gatsby serve",
|
||||
"clean": "gatsby clean",
|
||||
"test": "echo \"Write tests! -> https://gatsby.dev/unit-testing\" && exit 1"
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@material-ui/core": "^4.11.2",
|
||||
@ -61,14 +62,21 @@
|
||||
"yup": "^0.32.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/jest-dom": "^5.11.8",
|
||||
"@testing-library/react": "^11.2.3",
|
||||
"babel-jest": "^26.6.3",
|
||||
"babel-preset-gatsby": "^0.10.0",
|
||||
"eslint": "^7.17.0",
|
||||
"eslint-config-airbnb": "^18.2.1",
|
||||
"eslint-config-prettier": "^7.1.0",
|
||||
"eslint-loader": "^4.0.2",
|
||||
"eslint-plugin-jest": "^24.1.3",
|
||||
"eslint-plugin-jsx-a11y": "^6.4.1",
|
||||
"eslint-plugin-prettier": "^3.3.1",
|
||||
"eslint-plugin-react": "^7.22.0",
|
||||
"gatsby-plugin-eslint": "^2.0.8",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"jest": "^26.6.3",
|
||||
"prettier": "2.2.1",
|
||||
"stylelint": "^13.8.0",
|
||||
"stylelint-config-standard": "^20.0.0",
|
||||
|
||||
@ -128,4 +128,7 @@ export default DatabaseContext;
|
||||
|
||||
const memoizedProvider = memo(DatabaseProvider);
|
||||
|
||||
export { memoizedProvider as DatabaseProvider };
|
||||
export {
|
||||
memoizedProvider as DatabaseProvider,
|
||||
DEBOUNCE_WAIT_TIME as DebounceWaitTime,
|
||||
};
|
||||
|
||||
147
src/pages/app/__tests__/builder.test.js
Normal file
147
src/pages/app/__tests__/builder.test.js
Normal file
@ -0,0 +1,147 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
act,
|
||||
fireEvent,
|
||||
render,
|
||||
screen,
|
||||
waitFor,
|
||||
} from '@testing-library/react';
|
||||
|
||||
import FirebaseStub, { DatabaseConstants } from 'gatsby-plugin-firebase';
|
||||
|
||||
import { SettingsProvider } from '../../../contexts/SettingsContext';
|
||||
import { ModalProvider } from '../../../contexts/ModalContext';
|
||||
import { UserProvider } from '../../../contexts/UserContext';
|
||||
import {
|
||||
DatabaseProvider,
|
||||
DebounceWaitTime,
|
||||
} from '../../../contexts/DatabaseContext';
|
||||
import { ResumeProvider } from '../../../contexts/ResumeContext';
|
||||
import { StorageProvider } from '../../../contexts/StorageContext';
|
||||
import Builder from '../builder';
|
||||
|
||||
describe('Builder', () => {
|
||||
let resumeId = null;
|
||||
let resume = null;
|
||||
let mockUpdateFunction = null;
|
||||
|
||||
beforeEach(async () => {
|
||||
FirebaseStub.database().initializeData();
|
||||
|
||||
resumeId = DatabaseConstants.demoStateResume1Id;
|
||||
resume = (
|
||||
await FirebaseStub.database()
|
||||
.ref(`${DatabaseConstants.resumesPath}/${resumeId}`)
|
||||
.once('value')
|
||||
).val();
|
||||
mockUpdateFunction = jest.spyOn(
|
||||
FirebaseStub.database().ref(
|
||||
`${DatabaseConstants.resumesPath}/${resumeId}`,
|
||||
),
|
||||
'update',
|
||||
);
|
||||
|
||||
render(
|
||||
<SettingsProvider>
|
||||
<ModalProvider>
|
||||
<UserProvider>
|
||||
<DatabaseProvider>
|
||||
<ResumeProvider>
|
||||
<StorageProvider>
|
||||
<Builder id={resume.id} />
|
||||
</StorageProvider>
|
||||
</ResumeProvider>
|
||||
</DatabaseProvider>
|
||||
</UserProvider>
|
||||
</ModalProvider>
|
||||
</SettingsProvider>,
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await FirebaseStub.auth().signInAnonymously();
|
||||
});
|
||||
|
||||
await waitFor(() => mockUpdateFunction.mock.calls[0][0], {
|
||||
timeout: DebounceWaitTime,
|
||||
});
|
||||
mockUpdateFunction.mockClear();
|
||||
});
|
||||
|
||||
describe('renders', () => {
|
||||
it('first and last name', async () => {
|
||||
expect(
|
||||
screen.getByLabelText(new RegExp('first name', 'i')),
|
||||
).toHaveDisplayValue(resume.profile.firstName);
|
||||
expect(
|
||||
screen.getByLabelText(new RegExp('last name', 'i')),
|
||||
).toHaveDisplayValue(resume.profile.lastName);
|
||||
expect(
|
||||
screen.getAllByText(new RegExp(resume.profile.firstName, 'i')).length,
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
screen.getAllByText(new RegExp(resume.profile.lastName, 'i')).length,
|
||||
).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('updates data', () => {
|
||||
it('when input value is changed', async () => {
|
||||
const input = screen.getByLabelText(new RegExp('address line 1', 'i'));
|
||||
const newInputValue = 'test street 123';
|
||||
const now = new Date().getTime();
|
||||
|
||||
fireEvent.change(input, { target: { value: newInputValue } });
|
||||
|
||||
expect(input.value).toBe(newInputValue);
|
||||
|
||||
await waitFor(() => mockUpdateFunction.mock.calls[0][0], {
|
||||
timeout: DebounceWaitTime,
|
||||
});
|
||||
expect(mockUpdateFunction).toHaveBeenCalledTimes(1);
|
||||
const mockUpdateFunctionCallArgument =
|
||||
mockUpdateFunction.mock.calls[0][0];
|
||||
expect(mockUpdateFunctionCallArgument.id).toBe(resume.id);
|
||||
expect(mockUpdateFunctionCallArgument.profile.address.line1).toBe(
|
||||
newInputValue,
|
||||
);
|
||||
expect(mockUpdateFunctionCallArgument.updatedAt).toBeGreaterThanOrEqual(
|
||||
now,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('settings', () => {
|
||||
it('allow to change the language', async () => {
|
||||
const languageSelectElement = screen.getByLabelText('Language');
|
||||
const newLanguage = 'it';
|
||||
const now = new Date().getTime();
|
||||
|
||||
fireEvent.change(languageSelectElement, {
|
||||
target: { value: newLanguage },
|
||||
});
|
||||
|
||||
expect(languageSelectElement).toHaveValue(newLanguage);
|
||||
|
||||
expect(
|
||||
screen.queryByLabelText(new RegExp('date of birth', 'i')),
|
||||
).toBeNull();
|
||||
expect(
|
||||
screen.getByLabelText(new RegExp('data di nascita', 'i')),
|
||||
).toBeInTheDocument();
|
||||
|
||||
await waitFor(() => mockUpdateFunction.mock.calls[0][0], {
|
||||
timeout: DebounceWaitTime,
|
||||
});
|
||||
expect(mockUpdateFunction).toHaveBeenCalledTimes(1);
|
||||
const mockUpdateFunctionCallArgument =
|
||||
mockUpdateFunction.mock.calls[0][0];
|
||||
expect(mockUpdateFunctionCallArgument.id).toBe(resume.id);
|
||||
expect(mockUpdateFunctionCallArgument.metadata.language).toBe(
|
||||
newLanguage,
|
||||
);
|
||||
expect(mockUpdateFunctionCallArgument.updatedAt).toBeGreaterThanOrEqual(
|
||||
now,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
76
src/pages/app/__tests__/dashboard.test.js
Normal file
76
src/pages/app/__tests__/dashboard.test.js
Normal file
@ -0,0 +1,76 @@
|
||||
import React from 'react';
|
||||
import { act, render, screen, waitFor } from '@testing-library/react';
|
||||
|
||||
import FirebaseStub, { DatabaseConstants } from 'gatsby-plugin-firebase';
|
||||
|
||||
import '../../../i18n/index';
|
||||
import '../../../utils/dayjs';
|
||||
import { SettingsProvider } from '../../../contexts/SettingsContext';
|
||||
import { ModalProvider } from '../../../contexts/ModalContext';
|
||||
import { UserProvider } from '../../../contexts/UserContext';
|
||||
import { DatabaseProvider } from '../../../contexts/DatabaseContext';
|
||||
import { ResumeProvider } from '../../../contexts/ResumeContext';
|
||||
import { StorageProvider } from '../../../contexts/StorageContext';
|
||||
import Dashboard from '../dashboard';
|
||||
|
||||
describe('Dashboard', () => {
|
||||
let resumes = null;
|
||||
const user = DatabaseConstants.user1;
|
||||
|
||||
beforeEach(async () => {
|
||||
FirebaseStub.database().initializeData();
|
||||
|
||||
resumes = (
|
||||
await FirebaseStub.database()
|
||||
.ref(DatabaseConstants.resumesPath)
|
||||
.orderByChild('user')
|
||||
.equalTo(user.uid)
|
||||
.once('value')
|
||||
).val();
|
||||
|
||||
render(
|
||||
<SettingsProvider>
|
||||
<ModalProvider>
|
||||
<UserProvider>
|
||||
<DatabaseProvider>
|
||||
<ResumeProvider>
|
||||
<StorageProvider>
|
||||
<Dashboard user={user} />
|
||||
</StorageProvider>
|
||||
</ResumeProvider>
|
||||
</DatabaseProvider>
|
||||
</UserProvider>
|
||||
</ModalProvider>
|
||||
</SettingsProvider>,
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await FirebaseStub.auth().signInAnonymously();
|
||||
});
|
||||
|
||||
await waitFor(() => screen.getByText('Create Resume'));
|
||||
});
|
||||
|
||||
describe('renders', () => {
|
||||
it('document title', async () => {
|
||||
expect(document.title).toEqual('Dashboard | Reactive Resume');
|
||||
});
|
||||
|
||||
it('create resume', async () => {
|
||||
expect(screen.getByText('Create Resume')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('preview of user resumes', async () => {
|
||||
expect(Object.keys(resumes)).toHaveLength(2);
|
||||
|
||||
expect(Object.values(resumes)[0].user).toEqual(user.uid);
|
||||
expect(
|
||||
screen.getByText(Object.values(resumes)[0].name),
|
||||
).toBeInTheDocument();
|
||||
expect(Object.values(resumes)[1].user).toEqual(user.uid);
|
||||
expect(
|
||||
screen.getByText(Object.values(resumes)[1].name),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
52
src/templates/__tests__/Castform.test.js
Normal file
52
src/templates/__tests__/Castform.test.js
Normal file
@ -0,0 +1,52 @@
|
||||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
|
||||
import FirebaseStub, { DatabaseConstants } from 'gatsby-plugin-firebase';
|
||||
|
||||
import '../../i18n/index';
|
||||
import Castform from '../Castform';
|
||||
|
||||
describe('Castform', () => {
|
||||
let resume = {};
|
||||
|
||||
beforeEach(async () => {
|
||||
FirebaseStub.database().initializeData();
|
||||
|
||||
const resumeId = DatabaseConstants.initialStateResumeId;
|
||||
resume = (
|
||||
await FirebaseStub.database()
|
||||
.ref(`${DatabaseConstants.resumesPath}/${resumeId}`)
|
||||
.once('value')
|
||||
).val();
|
||||
});
|
||||
|
||||
it('renders correctly', () => {
|
||||
const { container } = render(<Castform data={resume} />);
|
||||
|
||||
expect(container).toBeTruthy();
|
||||
expect(container).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe('date of birth', () => {
|
||||
const birthDateLabelMatcher = /Date of birth/i;
|
||||
|
||||
it('is not shown if not provided', () => {
|
||||
render(<Castform data={resume} />);
|
||||
|
||||
expect(screen.queryByText(birthDateLabelMatcher)).toBeNull();
|
||||
});
|
||||
|
||||
it('is shown if provided', () => {
|
||||
const birthDate = new Date(1990, 0, 20);
|
||||
const birthDateFormatted = '20 January 1990';
|
||||
resume.profile.birthDate = birthDate;
|
||||
|
||||
render(<Castform data={resume} />);
|
||||
|
||||
expect(screen.getByText(birthDateLabelMatcher)).toBeTruthy();
|
||||
expect(screen.getByText(birthDateLabelMatcher)).toBeInTheDocument();
|
||||
expect(screen.getByText(birthDateFormatted)).toBeTruthy();
|
||||
expect(screen.getByText(birthDateFormatted)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user