Merge pull request #498 from gianantoniopini/develop

Addition of unit tests related to Delete Account
This commit is contained in:
Amruth Pillai
2021-06-03 08:37:25 +05:30
committed by GitHub
32 changed files with 900 additions and 105 deletions
+17
View File
@@ -0,0 +1,17 @@
import { delay } from '../../src/utils/index';
const ReachRouter = jest.requireActual('@reach/router');
const defaultDelayInMilliseconds = 100;
// eslint-disable-next-line no-unused-vars
const navigate = async (to, options) => {
await delay(defaultDelayInMilliseconds);
return Promise.resolve();
};
module.exports = {
...ReachRouter,
navigate: jest.fn(navigate),
};
@@ -0,0 +1,35 @@
import FirebaseStub, { AuthConstants } from '../../gatsby-plugin-firebase';
test('sets current user to anonymous user 1', async () => {
await FirebaseStub.auth().signInAnonymously();
const { currentUser } = FirebaseStub.auth();
expect(currentUser).toBeTruthy();
expect(currentUser.uid).toEqual(AuthConstants.anonymousUser1.uid);
});
test('calls onAuthStateChanged observer with anonymous user 1', 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.uid).toEqual(AuthConstants.anonymousUser1.uid);
expect(error).toBeNull();
});
test('returns anonymous user 1', async () => {
const user = await FirebaseStub.auth().signInAnonymously();
expect(user).toBeTruthy();
expect(user.uid).toEqual(AuthConstants.anonymousUser1.uid);
});
@@ -0,0 +1,54 @@
import FirebaseStub, { AuthConstants } from '../../gatsby-plugin-firebase';
describe('with Google auth provider', () => {
test('sets current user to Google user 3', async () => {
await FirebaseStub.auth().signInWithPopup(
new FirebaseStub.auth.GoogleAuthProvider(),
);
const { currentUser } = FirebaseStub.auth();
expect(currentUser).toBeTruthy();
expect(currentUser.uid).toEqual(AuthConstants.googleUser3.uid);
});
test('sets current user provider data', async () => {
const provider = new FirebaseStub.auth.GoogleAuthProvider();
await FirebaseStub.auth().signInWithPopup(provider);
const { currentUser } = FirebaseStub.auth();
expect(currentUser).toBeTruthy();
expect(currentUser.providerData).toBeTruthy();
expect(currentUser.providerData).toHaveLength(1);
expect(currentUser.providerData[0].providerId).toEqual(provider.providerId);
});
test('calls onAuthStateChanged observer with Google user 3', async () => {
let user = null;
let error = null;
FirebaseStub.auth().onAuthStateChanged(
(_user) => {
user = _user;
},
(_error) => {
error = _error;
},
);
await FirebaseStub.auth().signInWithPopup(
new FirebaseStub.auth.GoogleAuthProvider(),
);
expect(user).toBeTruthy();
expect(user.uid).toEqual(AuthConstants.googleUser3.uid);
expect(error).toBeNull();
});
test('returns Google user 3', async () => {
const user = await FirebaseStub.auth().signInWithPopup(
new FirebaseStub.auth.GoogleAuthProvider(),
);
expect(user).toBeTruthy();
expect(user.uid).toEqual(AuthConstants.googleUser3.uid);
});
});
@@ -0,0 +1,29 @@
import FirebaseStub from '../../gatsby-plugin-firebase';
test('sets current user to null', async () => {
await FirebaseStub.auth().signInAnonymously();
await FirebaseStub.auth().signOut();
const { currentUser } = FirebaseStub.auth();
expect(currentUser).toBeNull();
});
test('calls onAuthStateChanged observer with null', async () => {
let user = null;
let error = null;
FirebaseStub.auth().onAuthStateChanged(
(_user) => {
user = _user;
},
(_error) => {
error = _error;
},
);
await FirebaseStub.auth().signInAnonymously();
await FirebaseStub.auth().signOut();
expect(user).toBeNull();
expect(error).toBeNull();
});
@@ -1,4 +1,4 @@
import FirebaseStub, { AuthConstants } from '../../gatsby-plugin-firebase';
import FirebaseStub from '../../gatsby-plugin-firebase';
test('reuses existing Auth instance', () => {
const auth1 = FirebaseStub.auth();
@@ -9,32 +9,6 @@ test('reuses existing Auth instance', () => {
expect(auth1.uuid).toEqual(auth2.uuid);
});
test('returns anonymous user 1 when signing in anonymously', async () => {
const user = await FirebaseStub.auth().signInAnonymously();
expect(user).toBeTruthy();
expect(user).toEqual(AuthConstants.anonymousUser1);
});
test('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();
});
test('onAuthStateChanged unsubscribe removes observer', () => {
const observer = () => {};
const unsubscribe = FirebaseStub.auth().onAuthStateChanged(observer);
@@ -49,3 +23,13 @@ test('onAuthStateChanged unsubscribe removes observer', () => {
FirebaseStub.auth().onAuthStateChangedObservers.indexOf(observer),
).not.toBeGreaterThanOrEqual(0);
});
test('current user delete calls signOut', async () => {
const mockSignOut = jest.spyOn(FirebaseStub.auth(), 'signOut');
await FirebaseStub.auth().signInAnonymously();
const { currentUser } = FirebaseStub.auth();
await currentUser.delete();
expect(mockSignOut).toHaveBeenCalledTimes(1);
});
@@ -24,7 +24,7 @@ test('initializing data sets up resumes and users', async () => {
const resumesDataSnapshot = await resumesRef.once('value');
const resumes = resumesDataSnapshot.val();
expect(resumes).toBeTruthy();
expect(Object.keys(resumes)).toHaveLength(3);
expect(Object.keys(resumes)).toHaveLength(5);
const demoStateResume1 = resumes[DatabaseConstants.demoStateResume1Id];
expect(demoStateResume1).toBeTruthy();
expect(demoStateResume1.id).toEqual(DatabaseConstants.demoStateResume1Id);
@@ -33,20 +33,35 @@ test('initializing data sets up resumes and users', async () => {
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 initialStateResume1 = resumes[DatabaseConstants.initialStateResume1Id];
expect(initialStateResume1).toBeTruthy();
expect(initialStateResume1.id).toEqual(
DatabaseConstants.initialStateResume1Id,
);
expect(initialStateResume1.user).toEqual(DatabaseConstants.user1.uid);
const demoStateResume3 = resumes[DatabaseConstants.demoStateResume3Id];
expect(demoStateResume3).toBeTruthy();
expect(demoStateResume3.id).toEqual(DatabaseConstants.demoStateResume3Id);
expect(demoStateResume3.user).toEqual(DatabaseConstants.user3.uid);
const initialStateResume2 = resumes[DatabaseConstants.initialStateResume2Id];
expect(initialStateResume2).toBeTruthy();
expect(initialStateResume2.id).toEqual(
DatabaseConstants.initialStateResume2Id,
);
expect(initialStateResume2.user).toEqual(DatabaseConstants.user3.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);
expect(Object.keys(users)).toHaveLength(3);
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);
const googleUser3 = users[DatabaseConstants.user3.uid];
expect(googleUser3).toBeTruthy();
expect(googleUser3).toEqual(DatabaseConstants.user3);
});
@@ -0,0 +1,19 @@
import FirebaseStub from '../../gatsby-plugin-firebase';
test('reuses existing Functions instance', () => {
const functions1 = FirebaseStub.functions();
const functions2 = FirebaseStub.functions();
expect(functions1.uuid).toBeTruthy();
expect(functions2.uuid).toBeTruthy();
expect(functions1.uuid).toEqual(functions2.uuid);
});
test('deleteUser function returns true', async () => {
const deleteUser = FirebaseStub.functions().httpsCallable('deleteUser');
const result = await deleteUser();
expect(result).toBeTruthy();
expect(result.data).toEqual(true);
});
+10 -1
View File
@@ -2,6 +2,9 @@ import Auth from './gatsby-plugin-firebase/auth/auth';
import AuthConstants from './gatsby-plugin-firebase/constants/auth';
import Database from './gatsby-plugin-firebase/database/database';
import DatabaseConstants from './gatsby-plugin-firebase/constants/database';
import Functions from './gatsby-plugin-firebase/functions/functions';
import FunctionsConstants from './gatsby-plugin-firebase/constants/functions';
import GoogleAuthProvider from './gatsby-plugin-firebase/auth/googleAuthProvider';
class FirebaseStub {
static auth() {
@@ -11,8 +14,14 @@ class FirebaseStub {
static database() {
return Database.instance;
}
static functions() {
return Functions.instance;
}
}
FirebaseStub.auth.GoogleAuthProvider = GoogleAuthProvider;
FirebaseStub.database.ServerValue = {};
Object.defineProperty(FirebaseStub.database.ServerValue, 'TIMESTAMP', {
get() {
@@ -21,4 +30,4 @@ Object.defineProperty(FirebaseStub.database.ServerValue, 'TIMESTAMP', {
});
export default FirebaseStub;
export { AuthConstants, DatabaseConstants };
export { AuthConstants, DatabaseConstants, FunctionsConstants };
+74 -4
View File
@@ -2,7 +2,10 @@
import { v4 as uuidv4 } from 'uuid';
import { delay } from '../../../src/utils/index';
import AuthProvider from './authProvider';
import Constants from '../constants/auth';
import GoogleAuthProvider from './googleAuthProvider';
import User from './user';
const singleton = Symbol('');
const singletonEnforcer = Symbol('');
@@ -14,6 +17,7 @@ class Auth {
}
this._uuid = uuidv4();
this._currentUser = null;
this._onAuthStateChangedObservers = [];
}
@@ -25,6 +29,10 @@ class Auth {
return this[singleton];
}
get currentUser() {
return this._currentUser;
}
get uuid() {
return this._uuid;
}
@@ -37,19 +45,81 @@ class Auth {
this.onAuthStateChangedObservers.push(observer);
return () => {
this._onAuthStateChangedObservers =
this.onAuthStateChangedObservers.filter((obs) => obs !== observer);
this._onAuthStateChangedObservers = this.onAuthStateChangedObservers.filter(
(obs) => obs !== observer,
);
};
}
async signInAnonymously() {
const user = Constants.anonymousUser1;
this._currentUser = new User(
user.displayName,
user.email,
user.providerId,
user.uid,
user.isAnonymous,
this.signOut.bind(this),
);
await delay(Constants.defaultDelayInMilliseconds);
this.onAuthStateChangedObservers.forEach((observer) => observer(user));
this.onAuthStateChangedObservers.forEach((observer) =>
observer(this._currentUser),
);
return user;
return this._currentUser;
}
/**
* Authenticates with popup.
*
* @param {AuthProvider} provider The provider to authenticate.
*/
async signInWithPopup(provider) {
if (!provider) {
throw new Error('provider must be provided.');
} else if (!(provider instanceof AuthProvider)) {
throw new Error('provider should be an AuthProvider.');
}
if (!(provider instanceof GoogleAuthProvider)) {
throw new Error(
`${provider.constructor.name} is currently not supported.`,
);
}
const user = Constants.googleUser3;
this._currentUser = new User(
user.displayName,
user.email,
user.providerId,
user.uid,
user.isAnonymous,
this.signOut.bind(this),
);
await delay(Constants.defaultDelayInMilliseconds);
this.onAuthStateChangedObservers.forEach((observer) =>
observer(this._currentUser),
);
return this._currentUser;
}
async signOut() {
if (this._currentUser === null) {
return;
}
this._currentUser = null;
await delay(Constants.defaultDelayInMilliseconds);
this.onAuthStateChangedObservers.forEach((observer) => observer(null));
}
}
@@ -0,0 +1,23 @@
/* eslint-disable no-underscore-dangle */
class AuthProvider {
/**
* Creates a new auth provider.
*
* @param {string} providerId Provider ID.
*/
constructor(providerId) {
if (!providerId) {
throw new Error('providerId must be provided.');
} else if (typeof providerId !== 'string') {
throw new Error('providerId should be a string.');
} else {
this._providerId = providerId;
}
}
get providerId() {
return this._providerId;
}
}
export default AuthProvider;
@@ -0,0 +1,10 @@
import AuthProvider from './authProvider';
import Constants from '../constants/auth';
class GoogleAuthProvider extends AuthProvider {
constructor() {
super(Constants.googleAuthProviderId);
}
}
export default GoogleAuthProvider;
@@ -0,0 +1,67 @@
/* eslint-disable no-underscore-dangle */
import Constants from '../constants/auth';
// eslint-disable-next-line no-unused-vars
import AuthProvider from './authProvider';
import UserInfo from './userInfo';
import { delay } from '../../../src/utils/index';
class User extends UserInfo {
/**
* Creates a new user.
*
* @param {string|null} displayName Display name.
* @param {string|null} email Email.
* @param {string} providerId Auth provider ID.
* @param {string} uid The user's unique ID.
* @param {boolean} isAnonymous Is anonymous.
* @param {function():Promise<void>} deleteUser Delete user callback.
*/
constructor(displayName, email, providerId, uid, isAnonymous, deleteUser) {
super(displayName, email, providerId, uid);
if (!deleteUser) {
throw new Error('deleteUser must be provided.');
} else if (typeof deleteUser !== 'function') {
throw new Error('deleteUser should be a function.');
} else {
this._deleteUser = deleteUser;
}
this._isAnonymous = isAnonymous;
this._providerData = [];
if (!isAnonymous) {
this._providerData.push(
new UserInfo(displayName, email, providerId, uid),
);
}
}
get isAnonymous() {
return this._isAnonymous;
}
get providerData() {
return this._providerData;
}
async delete() {
await delay(Constants.defaultDelayInMilliseconds);
await this._deleteUser();
}
/**
* Reauthenticates the user with popup.
*
* @param {AuthProvider} provider The provider to authenticate.
*/
// eslint-disable-next-line no-unused-vars
async reauthenticateWithPopup(provider) {
await delay(Constants.defaultDelayInMilliseconds);
return this;
}
}
export default User;
@@ -0,0 +1,47 @@
/* eslint-disable no-underscore-dangle */
class UserInfo {
/**
* Creates a new user profile information.
*
* @param {string|null} displayName Display name.
* @param {string|null} email Email.
* @param {string} providerId Auth provider ID.
* @param {string} uid The user's unique ID.
*/
constructor(displayName, email, providerId, uid) {
if (!uid) {
throw new Error('uid must be provided.');
} else if (typeof uid !== 'string') {
throw new Error('uid should be a string.');
} else {
this._uid = uid;
}
if (typeof providerId !== 'string') {
throw new Error('providerId should be a string.');
} else {
this._providerId = providerId;
}
this._displayName = displayName;
this._email = email;
}
get displayName() {
return this._displayName;
}
get email() {
return this._email;
}
get providerId() {
return this._providerId;
}
get uid() {
return this._uid;
}
}
export default UserInfo;
@@ -1,22 +1,36 @@
const googleAuthProviderId = 'google.com';
const anonymousUser1 = {
displayName: 'Anonymous User 1',
email: 'anonymous1@noemail.com',
isAnonymous: true,
name: 'Anonymous 1',
uid: 'anonym123',
providerId: '',
uid: 'anonym1',
};
const anonymousUser2 = {
displayName: 'Anonymous User 2',
email: 'anonymous2@noemail.com',
isAnonymous: true,
name: 'Anonymous 2',
uid: 'anonym456',
providerId: '',
uid: 'anonym2',
};
const googleUser3 = {
displayName: 'Google User 3',
email: 'google3@noemail.com',
isAnonymous: false,
providerId: googleAuthProviderId,
uid: 'google3',
};
const defaultDelayInMilliseconds = 100;
class Auth {
static get googleAuthProviderId() {
return googleAuthProviderId;
}
static get anonymousUser1() {
return anonymousUser1;
}
@@ -25,6 +39,10 @@ class Auth {
return anonymousUser2;
}
static get googleUser3() {
return googleUser3;
}
static get defaultDelayInMilliseconds() {
return defaultDelayInMilliseconds;
}
@@ -9,7 +9,9 @@ const connectedPath = '.info/connected';
const demoStateResume1Id = 'demo_1';
const demoStateResume2Id = 'demo_2';
const initialStateResumeId = 'initst';
const demoStateResume3Id = 'demo_3';
const initialStateResume1Id = 'init_1';
const initialStateResume2Id = 'init_2';
const user1 = {
uid: AuthConstants.anonymousUser1.uid,
@@ -19,6 +21,10 @@ const user2 = {
uid: AuthConstants.anonymousUser2.uid,
isAnonymous: AuthConstants.anonymousUser2.isAnonymous,
};
const user3 = {
uid: AuthConstants.googleUser3.uid,
isAnonymous: AuthConstants.googleUser3.isAnonymous,
};
const defaultDelayInMilliseconds = 100;
@@ -51,8 +57,16 @@ class Database {
return demoStateResume2Id;
}
static get initialStateResumeId() {
return initialStateResumeId;
static get demoStateResume3Id() {
return demoStateResume3Id;
}
static get initialStateResume1Id() {
return initialStateResume1Id;
}
static get initialStateResume2Id() {
return initialStateResume2Id;
}
static get user1() {
@@ -63,6 +77,10 @@ class Database {
return user2;
}
static get user3() {
return user3;
}
static get defaultDelayInMilliseconds() {
return defaultDelayInMilliseconds;
}
@@ -0,0 +1,15 @@
const deleteUserFunctionName = 'deleteUser';
const defaultDelayInMilliseconds = 100;
class Functions {
static get deleteUserFunctionName() {
return deleteUserFunctionName;
}
static get defaultDelayInMilliseconds() {
return defaultDelayInMilliseconds;
}
}
export default Functions;
@@ -101,9 +101,9 @@ class Database {
initializeData() {
const resumes = {};
const date = new Date('December 15, 2020 11:20:25');
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();
@@ -114,11 +114,24 @@ class Database {
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;
const initialStateResume1 = readFile('../../../src/data/initialState.json');
initialStateResume1.updatedAt = date.valueOf();
initialStateResume1.createdAt = date.valueOf();
initialStateResume1.user = DatabaseConstants.user1.uid;
resumes[DatabaseConstants.initialStateResume1Id] = initialStateResume1;
const demoStateResume3 = readFile('../../../src/data/demoState.json');
demoStateResume3.updatedAt = date.valueOf();
date.setMonth(date.getMonth() - 2);
demoStateResume3.createdAt = date.valueOf();
demoStateResume3.user = DatabaseConstants.user3.uid;
resumes[DatabaseConstants.demoStateResume3Id] = demoStateResume3;
const initialStateResume2 = readFile('../../../src/data/initialState.json');
initialStateResume2.updatedAt = date.valueOf();
initialStateResume2.createdAt = date.valueOf();
initialStateResume2.user = DatabaseConstants.user3.uid;
resumes[DatabaseConstants.initialStateResume2Id] = initialStateResume2;
Object.keys(resumes).forEach((key) => {
const resume = resumes[key];
@@ -131,6 +144,7 @@ class Database {
const users = {};
users[DatabaseConstants.user1.uid] = DatabaseConstants.user1;
users[DatabaseConstants.user2.uid] = DatabaseConstants.user2;
users[DatabaseConstants.user3.uid] = DatabaseConstants.user3;
this._data[DatabaseConstants.usersPath] = users;
}
@@ -0,0 +1,54 @@
/* eslint-disable no-underscore-dangle */
import { v4 as uuidv4 } from 'uuid';
import FunctionsConstants from '../constants/functions';
import HttpsCallableResult from './httpsCallableResult';
import { delay } from '../../../src/utils/index';
const singleton = Symbol('');
const singletonEnforcer = Symbol('');
const deleteUser = async () => {
await delay(FunctionsConstants.defaultDelayInMilliseconds);
return new HttpsCallableResult(true);
};
class Functions {
constructor(enforcer) {
if (enforcer !== singletonEnforcer) {
throw new Error('Cannot construct singleton');
}
this._uuid = uuidv4();
this._httpsCallables = {};
this._httpsCallables[
FunctionsConstants.deleteUserFunctionName
] = deleteUser;
}
static get instance() {
if (!this[singleton]) {
this[singleton] = new Functions(singletonEnforcer);
}
return this[singleton];
}
get uuid() {
return this._uuid;
}
httpsCallable(name) {
if (!name) {
throw new Error('name must be provided.');
} else if (typeof name !== 'string') {
throw new Error('name should be a string.');
}
return this._httpsCallables[name];
}
}
export default Functions;
@@ -0,0 +1,19 @@
/* eslint-disable no-underscore-dangle */
import { v4 as uuidv4 } from 'uuid';
class HttpsCallableResult {
constructor(data) {
this._uuid = uuidv4();
this._data = data;
}
get data() {
return this._data;
}
get uuid() {
return this._uuid;
}
}
export default HttpsCallableResult;
+18 -38
View File
@@ -4,24 +4,18 @@ import { delay } from '../src/utils/index';
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',
const imageData = {
images: {
fallback: {
src: `image_src.jpg`,
srcSet: `image_src_set.jpg 1x`,
},
},
{
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',
},
];
layout: `fixed`,
width: 1,
height: 2,
};
const childImageSharp = { gatsbyImageData: imageData };
const useStaticQuery = () => ({
site: {
@@ -33,39 +27,25 @@ const useStaticQuery = () => ({
},
},
file: {
childImageSharp: {
fluid: fluidImageShapes[0],
},
childImageSharp,
},
onyx: {
childImageSharp: {
fluid: fluidImageShapes[0],
},
childImageSharp,
},
pikachu: {
childImageSharp: {
fluid: fluidImageShapes[1],
},
childImageSharp,
},
gengar: {
childImageSharp: {
fluid: fluidImageShapes[0],
},
childImageSharp,
},
castform: {
childImageSharp: {
fluid: fluidImageShapes[1],
},
childImageSharp,
},
glalie: {
childImageSharp: {
fluid: fluidImageShapes[0],
},
childImageSharp,
},
celebi: {
childImageSharp: {
fluid: fluidImageShapes[1],
},
childImageSharp,
},
});
+1 -1
View File
@@ -2,4 +2,4 @@ const babelOptions = {
presets: ['babel-preset-gatsby'],
};
module.exports = require('babel-jest').createTransformer(babelOptions);
module.exports = require('babel-jest').default.createTransformer(babelOptions);
+4 -1
View File
@@ -1,3 +1,5 @@
const esModules = ['gatsby', 'nanoevents'].join('|');
module.exports = {
testRegex: '/*.test.js$',
collectCoverage: true,
@@ -26,11 +28,12 @@ module.exports = {
'.+\\.(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)/)`],
transformIgnorePatterns: [`node_modules/(?!${esModules})`],
globals: {
__PATH_PREFIX__: ``,
},
testURL: `http://localhost`,
setupFiles: [`<rootDir>/loadershim.js`],
setupFilesAfterEnv: [`<rootDir>/jest.setup.js`],
testEnvironment: 'jsdom',
};
+3 -3
View File
@@ -66,8 +66,8 @@ const UserProvider = ({ children }) => {
}
};
const logout = () => {
firebase.auth().signOut();
const logout = async () => {
await firebase.auth().signOut();
localStorage.removeItem('user');
setUser(null);
navigate('/');
@@ -123,7 +123,7 @@ const UserProvider = ({ children }) => {
} catch (error) {
toast.error(error.message);
} finally {
logout();
await logout();
toast(
"It's sad to see you go, but we respect your privacy. All your data has been deleted successfully. Hope to see you again soon!",
);
@@ -7,13 +7,14 @@ import {
setupAndWait,
} from './helpers/builder';
const testTimeoutInMilliseconds = 20000;
const testTimeoutInMilliseconds = 30000;
jest.setTimeout(testTimeoutInMilliseconds);
test('when input value is changed, updates database', async () => {
const resumeId = DatabaseConstants.demoStateResume1Id;
const { mockDatabaseUpdateFunction } = await setupAndWait(
resumeId,
false,
true,
true,
);
@@ -20,6 +20,7 @@ jest.setTimeout(testTimeoutInMilliseconds);
test('renders first and last name', async () => {
const { resume } = await setupAndWait(
DatabaseConstants.demoStateResume1Id,
false,
true,
true,
);
@@ -53,7 +54,7 @@ test('renders loading screen', async () => {
});
test('if resume is in initial state, renders load demo data notification', async () => {
await setup(DatabaseConstants.initialStateResumeId);
await setup(DatabaseConstants.initialStateResume1Id);
const notification = await screen.findByRole('alert');
expect(
@@ -0,0 +1,284 @@
import { navigate as mockNavigateFunction } from '@reach/router';
import { toast } from 'react-toastify';
import { fireEvent, screen, waitFor } from '@testing-library/react';
import FirebaseStub, {
AuthConstants,
DatabaseConstants,
FunctionsConstants,
} from 'gatsby-plugin-firebase';
import { setupAndWait } from './helpers/builder';
import { delay } from '../../../utils/index';
const testTimeoutInMilliseconds = 60000;
jest.setTimeout(testTimeoutInMilliseconds);
async function setup(signInWithGoogle, failReauthentication) {
const resumeId = signInWithGoogle
? DatabaseConstants.demoStateResume3Id
: DatabaseConstants.demoStateResume1Id;
await setupAndWait(resumeId, signInWithGoogle, true, true);
let mockFirebaseCurrentUserReauthenticateWithPopupErrorMessage;
let mockFirebaseCurrentUserReauthenticateWithPopup;
if (failReauthentication) {
mockFirebaseCurrentUserReauthenticateWithPopupErrorMessage =
'Error occurred while reauthenticating.';
mockFirebaseCurrentUserReauthenticateWithPopup = jest.fn(async () => {
await delay(AuthConstants.defaultDelayInMilliseconds);
throw new Error(
mockFirebaseCurrentUserReauthenticateWithPopupErrorMessage,
);
});
FirebaseStub.auth().currentUser.reauthenticateWithPopup = mockFirebaseCurrentUserReauthenticateWithPopup;
} else {
mockFirebaseCurrentUserReauthenticateWithPopupErrorMessage = null;
mockFirebaseCurrentUserReauthenticateWithPopup = jest.spyOn(
FirebaseStub.auth().currentUser,
'reauthenticateWithPopup',
);
}
const mockFirebaseDeleteUserCloudFunction = jest.fn(async () => {
await delay(FunctionsConstants.defaultDelayInMilliseconds);
});
const mockFirebaseFunctionsHttpsCallable = jest.fn((name) =>
name === FunctionsConstants.deleteUserFunctionName
? mockFirebaseDeleteUserCloudFunction
: undefined,
);
FirebaseStub.functions().httpsCallable = mockFirebaseFunctionsHttpsCallable;
const mockFirebaseCurrentUserDelete = jest.spyOn(
FirebaseStub.auth().currentUser,
'delete',
);
const mockFirebaseAuthSignOutErrorMessage =
'Error occurred while signing out.';
const mockFirebaseAuthSignOut = jest.fn(async () => {
await delay(AuthConstants.defaultDelayInMilliseconds);
throw new Error(mockFirebaseAuthSignOutErrorMessage);
});
FirebaseStub.auth().signOut = mockFirebaseAuthSignOut;
// eslint-disable-next-line no-unused-vars
const mockToastError = jest.fn((content, options) => {});
toast.error = mockToastError;
const deleteAccountButtonText = /Delete Account/i;
const areYouSureButtonText = /Are you sure?/i;
const deleteAccountButton = screen.getByRole('button', {
name: deleteAccountButtonText,
});
const waitForDeleteAccountButtonTextToChangeTo = async (text) => {
await waitFor(() => expect(deleteAccountButton).toHaveTextContent(text), {
timeout: 30000,
});
};
return {
deleteAccountButton,
areYouSureButtonText,
deleteAccountButtonText,
waitForDeleteAccountButtonTextToChangeTo,
mockFirebaseCurrentUserReauthenticateWithPopup,
mockFirebaseCurrentUserReauthenticateWithPopupErrorMessage,
mockFirebaseDeleteUserCloudFunction,
mockFirebaseCurrentUserDelete,
mockFirebaseAuthSignOut,
mockFirebaseAuthSignOutErrorMessage,
mockToastError,
};
}
test('prompts for confirmation', async () => {
const {
deleteAccountButton,
areYouSureButtonText,
waitForDeleteAccountButtonTextToChangeTo,
mockFirebaseDeleteUserCloudFunction,
mockFirebaseCurrentUserDelete,
mockFirebaseAuthSignOut,
mockToastError,
} = await setup(false, false);
fireEvent.click(deleteAccountButton);
await expect(
waitForDeleteAccountButtonTextToChangeTo(areYouSureButtonText),
).resolves.toBeUndefined();
expect(mockFirebaseDeleteUserCloudFunction).not.toHaveBeenCalled();
expect(mockFirebaseCurrentUserDelete).not.toHaveBeenCalled();
expect(mockFirebaseAuthSignOut).not.toHaveBeenCalled();
expect(mockNavigateFunction).not.toHaveBeenCalled();
expect(mockToastError).not.toHaveBeenCalled();
});
test('calls Firebase delete user cloud function', async () => {
const {
deleteAccountButton,
deleteAccountButtonText,
mockFirebaseDeleteUserCloudFunction,
waitForDeleteAccountButtonTextToChangeTo,
} = await setup(false, false);
fireEvent.click(deleteAccountButton);
fireEvent.click(deleteAccountButton);
await waitForDeleteAccountButtonTextToChangeTo(deleteAccountButtonText);
await waitFor(() =>
expect(mockFirebaseDeleteUserCloudFunction).toHaveBeenCalledTimes(1),
);
});
test('calls Firebase current user delete', async () => {
const {
deleteAccountButton,
deleteAccountButtonText,
mockFirebaseCurrentUserDelete,
waitForDeleteAccountButtonTextToChangeTo,
} = await setup(false, false);
fireEvent.click(deleteAccountButton);
fireEvent.click(deleteAccountButton);
await waitForDeleteAccountButtonTextToChangeTo(deleteAccountButtonText);
await waitFor(() =>
expect(mockFirebaseCurrentUserDelete).toHaveBeenCalledTimes(1),
);
});
test('calls Firebase auth sign out', async () => {
const {
deleteAccountButton,
deleteAccountButtonText,
mockFirebaseAuthSignOut,
waitForDeleteAccountButtonTextToChangeTo,
} = await setup(false, false);
fireEvent.click(deleteAccountButton);
fireEvent.click(deleteAccountButton);
await waitForDeleteAccountButtonTextToChangeTo(deleteAccountButtonText);
await waitFor(() => expect(mockFirebaseAuthSignOut).toHaveBeenCalledTimes(1));
});
describe('if an error occurs while signing out the current user', () => {
test('does not navigate away', async () => {
const {
deleteAccountButton,
deleteAccountButtonText,
waitForDeleteAccountButtonTextToChangeTo,
} = await setup(false, false);
fireEvent.click(deleteAccountButton);
fireEvent.click(deleteAccountButton);
await waitForDeleteAccountButtonTextToChangeTo(deleteAccountButtonText);
expect(mockNavigateFunction).not.toHaveBeenCalled();
});
test('displays error notifications', async () => {
const {
deleteAccountButton,
deleteAccountButtonText,
mockFirebaseAuthSignOutErrorMessage,
waitForDeleteAccountButtonTextToChangeTo,
mockToastError,
} = await setup(false, false);
fireEvent.click(deleteAccountButton);
fireEvent.click(deleteAccountButton);
await waitForDeleteAccountButtonTextToChangeTo(deleteAccountButtonText);
expect(mockToastError).toHaveBeenCalledTimes(2);
expect(mockToastError).toHaveBeenNthCalledWith(
1,
mockFirebaseAuthSignOutErrorMessage,
);
expect(mockToastError).toHaveBeenNthCalledWith(
2,
'An error occurred deleting your account.',
);
});
});
describe('if the current user is signed in with Google', () => {
test('reauthenticates the user', async () => {
const googleAuthProvider = new FirebaseStub.auth.GoogleAuthProvider();
const {
deleteAccountButton,
deleteAccountButtonText,
mockFirebaseCurrentUserReauthenticateWithPopup,
waitForDeleteAccountButtonTextToChangeTo,
} = await setup(true, false);
fireEvent.click(deleteAccountButton);
fireEvent.click(deleteAccountButton);
await waitForDeleteAccountButtonTextToChangeTo(deleteAccountButtonText);
expect(
mockFirebaseCurrentUserReauthenticateWithPopup,
).toHaveBeenCalledTimes(1);
expect(mockFirebaseCurrentUserReauthenticateWithPopup).toHaveBeenCalledWith(
googleAuthProvider,
);
});
describe('and reauthentication fails', () => {
test('does not proceed further', async () => {
const {
deleteAccountButton,
deleteAccountButtonText,
waitForDeleteAccountButtonTextToChangeTo,
mockFirebaseDeleteUserCloudFunction,
mockFirebaseCurrentUserDelete,
mockFirebaseAuthSignOut,
} = await setup(true, true);
fireEvent.click(deleteAccountButton);
fireEvent.click(deleteAccountButton);
await waitForDeleteAccountButtonTextToChangeTo(deleteAccountButtonText);
expect(mockFirebaseDeleteUserCloudFunction).not.toHaveBeenCalled();
expect(mockFirebaseCurrentUserDelete).not.toHaveBeenCalled();
expect(mockFirebaseAuthSignOut).not.toHaveBeenCalled();
expect(mockNavigateFunction).not.toHaveBeenCalled();
});
test('displays error notifications', async () => {
const {
deleteAccountButton,
deleteAccountButtonText,
waitForDeleteAccountButtonTextToChangeTo,
mockFirebaseCurrentUserReauthenticateWithPopupErrorMessage,
mockToastError,
} = await setup(true, true);
fireEvent.click(deleteAccountButton);
fireEvent.click(deleteAccountButton);
await waitForDeleteAccountButtonTextToChangeTo(deleteAccountButtonText);
expect(mockToastError).toHaveBeenCalledTimes(2);
expect(mockToastError).toHaveBeenNthCalledWith(
1,
mockFirebaseCurrentUserReauthenticateWithPopupErrorMessage,
);
expect(mockToastError).toHaveBeenNthCalledWith(
2,
'An error occurred deleting your account.',
);
});
});
});
@@ -16,6 +16,7 @@ test('allows to change the language', async () => {
const resumeId = DatabaseConstants.demoStateResume1Id;
const { mockDatabaseUpdateFunction } = await setupAndWait(
resumeId,
false,
true,
true,
);
@@ -19,6 +19,7 @@ test('allows to drag & drop', async () => {
const resumeId = DatabaseConstants.demoStateResume1Id;
const { resume, mockDatabaseUpdateFunction } = await setupAndWait(
resumeId,
false,
true,
true,
);
@@ -44,7 +44,7 @@ async function setup() {
);
const resumeToDeleteId = resumeToDelete.id;
const [undeletedResume] = Object.values(userResumes).filter(
(resume) => resume.id === DatabaseConstants.initialStateResumeId,
(resume) => resume.id === DatabaseConstants.initialStateResume1Id,
);
const mockDatabaseRemoveFunction = jest.spyOn(
+10 -2
View File
@@ -80,6 +80,7 @@ const dragAndDropListItem = (listItemElement, direction) => {
// eslint-disable-next-line no-underscore-dangle
async function _setup(
resumeId,
signInWithGoogle,
waitForLoadingScreenToDisappear,
waitForDatabaseUpdateToHaveCompleted,
) {
@@ -96,7 +97,12 @@ async function _setup(
'update',
);
FirebaseStub.auth().signInAnonymously();
if (signInWithGoogle) {
const provider = new FirebaseStub.auth.GoogleAuthProvider();
FirebaseStub.auth().signInWithPopup(provider);
} else {
FirebaseStub.auth().signInAnonymously();
}
render(
<SettingsProvider>
@@ -132,17 +138,19 @@ async function _setup(
}
async function setup(resumeId) {
const returnValue = await _setup(resumeId, false, false);
const returnValue = await _setup(resumeId, false, false, false);
return returnValue;
}
async function setupAndWait(
resumeId,
signInWithGoogle,
waitForLoadingScreenToDisappear,
waitForDatabaseUpdateToHaveCompleted,
) {
const returnValue = await _setup(
resumeId,
signInWithGoogle,
waitForLoadingScreenToDisappear,
waitForDatabaseUpdateToHaveCompleted,
);
+3 -3
View File
@@ -21,7 +21,7 @@ async function setup(resumeId) {
}
test('renders correctly', async () => {
const resume = await setup(DatabaseConstants.initialStateResumeId);
const resume = await setup(DatabaseConstants.initialStateResume1Id);
const { container } = render(<Castform data={resume} />);
@@ -30,7 +30,7 @@ test('renders correctly', async () => {
});
test('date of birth is not shown if not provided', async () => {
const resume = await setup(DatabaseConstants.initialStateResumeId);
const resume = await setup(DatabaseConstants.initialStateResume1Id);
render(<Castform data={resume} />);
@@ -38,7 +38,7 @@ test('date of birth is not shown if not provided', async () => {
});
test('date of birth is shown if provided', async () => {
const resume = await setup(DatabaseConstants.initialStateResumeId);
const resume = await setup(DatabaseConstants.initialStateResume1Id);
const birthDate = new Date(1990, 0, 20);
const birthDateFormatted = '20 January 1990';
+2 -3
View File
@@ -137,6 +137,5 @@ export const b64toBlob = (b64Data, contentType = '', sliceSize = 512) => {
return blob;
};
export const delay = async (milliseconds) => {
await new Promise((resolve) => setTimeout(resolve, milliseconds));
};
export const delay = (milliseconds) =>
new Promise((resolve) => setTimeout(resolve, milliseconds));