From 155cd499fbcdec69c8c647a6db75c627828c7434 Mon Sep 17 00:00:00 2001 From: gianantoniopini <63844628+gianantoniopini@users.noreply.github.com> Date: Wed, 20 Jan 2021 12:57:31 +0100 Subject: [PATCH 01/14] Builder test: added test for handling non-existent resume, added resetting of language Storage item --- __mocks__/gatsby.js | 1 + src/pages/app/__tests__/builder.test.js | 157 +++++++++++++++--------- 2 files changed, 99 insertions(+), 59 deletions(-) diff --git a/__mocks__/gatsby.js b/__mocks__/gatsby.js index a7d4a32a5..e9339f5d9 100644 --- a/__mocks__/gatsby.js +++ b/__mocks__/gatsby.js @@ -76,5 +76,6 @@ module.exports = { href: to, }), ), + navigate: jest.fn(), useStaticQuery, }; diff --git a/src/pages/app/__tests__/builder.test.js b/src/pages/app/__tests__/builder.test.js index 52480685e..23a032144 100644 --- a/src/pages/app/__tests__/builder.test.js +++ b/src/pages/app/__tests__/builder.test.js @@ -1,3 +1,4 @@ +import { navigate as mockNavigateFunction } from 'gatsby'; import React from 'react'; import { act, @@ -23,18 +24,22 @@ import Builder from '../builder'; describe('Builder', () => { let resumeId = null; let resume = null; - let mockUpdateFunction = null; + let mockDatabaseUpdateFunction = null; - beforeEach(async () => { + async function setup( + resumeIdParameter, + waitForDatabaseUpdateFunctionToHaveBeenCalled = true, + ) { FirebaseStub.database().initializeData(); - resumeId = DatabaseConstants.demoStateResume1Id; + resumeId = resumeIdParameter; resume = ( await FirebaseStub.database() .ref(`${DatabaseConstants.resumesPath}/${resumeId}`) .once('value') ).val(); - mockUpdateFunction = jest.spyOn( + + mockDatabaseUpdateFunction = jest.spyOn( FirebaseStub.database().ref( `${DatabaseConstants.resumesPath}/${resumeId}`, ), @@ -48,7 +53,7 @@ describe('Builder', () => { - + @@ -61,20 +66,40 @@ describe('Builder', () => { await FirebaseStub.auth().signInAnonymously(); }); - await waitFor(() => mockUpdateFunction.mock.calls[0][0], { - timeout: DebounceWaitTime, + if (waitForDatabaseUpdateFunctionToHaveBeenCalled) { + await waitFor(() => mockDatabaseUpdateFunction.mock.calls[0][0], { + timeout: DebounceWaitTime, + }); + mockDatabaseUpdateFunction.mockClear(); + } + } + + describe('handles errors', () => { + describe('if resume does not exist', () => { + beforeEach(async () => { + await setup('xxxxxx', false); + }); + + it('navigates to Dashboard', () => { + expect(resume).toBeNull(); + expect(mockNavigateFunction).toHaveBeenCalledTimes(1); + expect(mockNavigateFunction).toHaveBeenCalledWith('/app/dashboard'); + }); }); - 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); + beforeEach(async () => { + await setup(DatabaseConstants.demoStateResume1Id); + }); + + it('first and last name', () => { + expect(screen.getByLabelText(/first name/i)).toHaveDisplayValue( + resume.profile.firstName, + ); + expect(screen.getByLabelText(/last name/i)).toHaveDisplayValue( + resume.profile.lastName, + ); expect( screen.getAllByText(new RegExp(resume.profile.firstName, 'i')).length, ).toBeTruthy(); @@ -84,9 +109,58 @@ describe('Builder', () => { }); }); + describe('settings', () => { + const languageStorageItemKey = 'language'; + + beforeEach(async () => { + await setup(DatabaseConstants.demoStateResume1Id); + }); + + it('allow to change the language', async () => { + const languageElement = screen.getByLabelText(/language/i); + const italianLanguageCode = 'it'; + const now = new Date().getTime(); + + fireEvent.change(languageElement, { + target: { value: italianLanguageCode }, + }); + + expect(languageElement).toHaveValue(italianLanguageCode); + + expect(screen.queryByLabelText(/date of birth/i)).toBeNull(); + expect(screen.getByLabelText(/data di nascita/i)).toBeInTheDocument(); + + const languageStorageItem = localStorage.getItem(languageStorageItemKey); + expect(languageStorageItem).toBe(italianLanguageCode); + + await waitFor(() => mockDatabaseUpdateFunction.mock.calls[0][0], { + timeout: DebounceWaitTime, + }); + expect(mockDatabaseUpdateFunction).toHaveBeenCalledTimes(1); + const mockDatabaseUpdateFunctionCallArgument = + mockDatabaseUpdateFunction.mock.calls[0][0]; + expect(mockDatabaseUpdateFunctionCallArgument.id).toBe(resumeId); + expect(mockDatabaseUpdateFunctionCallArgument.metadata.language).toBe( + italianLanguageCode, + ); + expect( + mockDatabaseUpdateFunctionCallArgument.updatedAt, + ).toBeGreaterThanOrEqual(now); + }); + + afterEach(() => { + const englishLanguageCode = 'en'; + localStorage.setItem(languageStorageItemKey, englishLanguageCode); + }); + }); + describe('updates data', () => { + beforeEach(async () => { + await setup(DatabaseConstants.demoStateResume1Id); + }); + it('when input value is changed', async () => { - const input = screen.getByLabelText(new RegExp('address line 1', 'i')); + const input = screen.getByLabelText(/address line 1/i); const newInputValue = 'test street 123'; const now = new Date().getTime(); @@ -94,54 +168,19 @@ describe('Builder', () => { expect(input.value).toBe(newInputValue); - await waitFor(() => mockUpdateFunction.mock.calls[0][0], { + await waitFor(() => mockDatabaseUpdateFunction.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( + expect(mockDatabaseUpdateFunction).toHaveBeenCalledTimes(1); + const mockDatabaseUpdateFunctionCallArgument = + mockDatabaseUpdateFunction.mock.calls[0][0]; + expect(mockDatabaseUpdateFunctionCallArgument.id).toBe(resumeId); + expect(mockDatabaseUpdateFunctionCallArgument.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, - ); + mockDatabaseUpdateFunctionCallArgument.updatedAt, + ).toBeGreaterThanOrEqual(now); }); }); }); From 3d39576a901fa5ab75a6e97b31dc51bf825047cc Mon Sep 17 00:00:00 2001 From: gianantoniopini <63844628+gianantoniopini@users.noreply.github.com> Date: Wed, 20 Jan 2021 13:40:58 +0100 Subject: [PATCH 02/14] Builder test: changed some queries to use *ByRole --- src/pages/app/__tests__/builder.test.js | 36 ++++++++++++++----------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/src/pages/app/__tests__/builder.test.js b/src/pages/app/__tests__/builder.test.js index 23a032144..e703defea 100644 --- a/src/pages/app/__tests__/builder.test.js +++ b/src/pages/app/__tests__/builder.test.js @@ -94,12 +94,12 @@ describe('Builder', () => { }); it('first and last name', () => { - expect(screen.getByLabelText(/first name/i)).toHaveDisplayValue( - resume.profile.firstName, - ); - expect(screen.getByLabelText(/last name/i)).toHaveDisplayValue( - resume.profile.lastName, - ); + expect( + screen.getByRole('textbox', { name: /first name/i }), + ).toHaveDisplayValue(resume.profile.firstName); + expect( + screen.getByRole('textbox', { name: /last name/i }), + ).toHaveDisplayValue(resume.profile.lastName); expect( screen.getAllByText(new RegExp(resume.profile.firstName, 'i')).length, ).toBeTruthy(); @@ -127,16 +127,18 @@ describe('Builder', () => { expect(languageElement).toHaveValue(italianLanguageCode); - expect(screen.queryByLabelText(/date of birth/i)).toBeNull(); + expect(screen.queryByLabelText(/date of birth/i)).not.toBeInTheDocument(); expect(screen.getByLabelText(/data di nascita/i)).toBeInTheDocument(); const languageStorageItem = localStorage.getItem(languageStorageItemKey); expect(languageStorageItem).toBe(italianLanguageCode); - await waitFor(() => mockDatabaseUpdateFunction.mock.calls[0][0], { - timeout: DebounceWaitTime, - }); - expect(mockDatabaseUpdateFunction).toHaveBeenCalledTimes(1); + await waitFor( + () => expect(mockDatabaseUpdateFunction).toHaveBeenCalledTimes(1), + { + timeout: DebounceWaitTime, + }, + ); const mockDatabaseUpdateFunctionCallArgument = mockDatabaseUpdateFunction.mock.calls[0][0]; expect(mockDatabaseUpdateFunctionCallArgument.id).toBe(resumeId); @@ -160,7 +162,7 @@ describe('Builder', () => { }); it('when input value is changed', async () => { - const input = screen.getByLabelText(/address line 1/i); + const input = screen.getByRole('textbox', { name: /address line 1/i }); const newInputValue = 'test street 123'; const now = new Date().getTime(); @@ -168,10 +170,12 @@ describe('Builder', () => { expect(input.value).toBe(newInputValue); - await waitFor(() => mockDatabaseUpdateFunction.mock.calls[0][0], { - timeout: DebounceWaitTime, - }); - expect(mockDatabaseUpdateFunction).toHaveBeenCalledTimes(1); + await waitFor( + () => expect(mockDatabaseUpdateFunction).toHaveBeenCalledTimes(1), + { + timeout: DebounceWaitTime, + }, + ); const mockDatabaseUpdateFunctionCallArgument = mockDatabaseUpdateFunction.mock.calls[0][0]; expect(mockDatabaseUpdateFunctionCallArgument.id).toBe(resumeId); From 1215071ab96e0262e60816fc7a5ba5a44e06c13e Mon Sep 17 00:00:00 2001 From: gianantoniopini <63844628+gianantoniopini@users.noreply.github.com> Date: Wed, 20 Jan 2021 14:27:27 +0100 Subject: [PATCH 03/14] Dashboard tests: added test for loading screen --- src/components/router/LoadingScreen.js | 2 +- src/pages/app/__tests__/dashboard.test.js | 70 ++++++++++++++++------- 2 files changed, 50 insertions(+), 22 deletions(-) diff --git a/src/components/router/LoadingScreen.js b/src/components/router/LoadingScreen.js index 19b6ec3ad..67e387318 100644 --- a/src/components/router/LoadingScreen.js +++ b/src/components/router/LoadingScreen.js @@ -4,7 +4,7 @@ import { getRandomTip } from '../../data/tips'; import Logo from '../shared/Logo'; const LoadingScreen = () => ( - +
diff --git a/src/pages/app/__tests__/dashboard.test.js b/src/pages/app/__tests__/dashboard.test.js index 6ab6cf91b..93d1615e2 100644 --- a/src/pages/app/__tests__/dashboard.test.js +++ b/src/pages/app/__tests__/dashboard.test.js @@ -1,5 +1,10 @@ import React from 'react'; -import { act, render, screen, waitFor } from '@testing-library/react'; +import { + act, + render, + screen, + waitForElementToBeRemoved, +} from '@testing-library/react'; import FirebaseStub, { DatabaseConstants } from 'gatsby-plugin-firebase'; @@ -16,8 +21,9 @@ import Dashboard from '../dashboard'; describe('Dashboard', () => { let resumes = null; const user = DatabaseConstants.user1; + const loadingScreenTestId = 'loading-screen'; - beforeEach(async () => { + async function setup(waitForLoadingScreenToDisappear) { FirebaseStub.database().initializeData(); resumes = ( @@ -48,29 +54,51 @@ describe('Dashboard', () => { await FirebaseStub.auth().signInAnonymously(); }); - await waitFor(() => screen.getByText('Create Resume')); + if (waitForLoadingScreenToDisappear) { + await waitForElementToBeRemoved(() => + screen.getByTestId(loadingScreenTestId), + ); + } + } + + describe('after loading', () => { + beforeEach(async () => { + await setup(true); + }); + + describe('renders', () => { + it('document title', () => { + expect(document.title).toEqual('Dashboard | Reactive Resume'); + }); + + it('create resume', () => { + expect(screen.getByText(/create resume/i)).toBeInTheDocument(); + }); + + it('preview of user resumes', () => { + 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(); + }); + }); }); - describe('renders', () => { - it('document title', async () => { - expect(document.title).toEqual('Dashboard | Reactive Resume'); + describe('while loading', () => { + beforeEach(async () => { + await setup(false); }); - 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(); + describe('renders', () => { + it('loading screen', () => { + expect(screen.getByTestId(loadingScreenTestId)).toBeInTheDocument(); + }); }); }); }); From 4c63f68215e26486bcfb219b6244c4c10ec4df50 Mon Sep 17 00:00:00 2001 From: gianantoniopini <63844628+gianantoniopini@users.noreply.github.com> Date: Wed, 20 Jan 2021 15:52:35 +0100 Subject: [PATCH 04/14] Builder tests: added test for loading screen --- .../constants/database.js | 6 +++ .../database/reference.js | 6 ++- src/pages/app/__tests__/builder.test.js | 28 +++++++++-- src/pages/app/__tests__/dashboard.test.js | 46 +++++++++---------- 4 files changed, 56 insertions(+), 30 deletions(-) diff --git a/__mocks__/gatsby-plugin-firebase/constants/database.js b/__mocks__/gatsby-plugin-firebase/constants/database.js index 6f4fbe5d5..62a5459e0 100644 --- a/__mocks__/gatsby-plugin-firebase/constants/database.js +++ b/__mocks__/gatsby-plugin-firebase/constants/database.js @@ -20,6 +20,8 @@ const user2 = { isAnonymous: AuthConstants.anonymousUser2.isAnonymous, }; +const defaultDelayInMilliseconds = 100; + class Database { static get valueEventType() { return valueEventType; @@ -60,6 +62,10 @@ class Database { static get user2() { return user2; } + + static get defaultDelayInMilliseconds() { + return defaultDelayInMilliseconds; + } } export default Database; diff --git a/__mocks__/gatsby-plugin-firebase/database/reference.js b/__mocks__/gatsby-plugin-firebase/database/reference.js index 3b3447d62..7e37737b8 100644 --- a/__mocks__/gatsby-plugin-firebase/database/reference.js +++ b/__mocks__/gatsby-plugin-firebase/database/reference.js @@ -149,7 +149,7 @@ class Reference { const debouncedEventCallback = debounce( this.eventCallbacks[eventType], - 100, + DatabaseConstants.defaultDelayInMilliseconds, ); debouncedEventCallback(snapshot); } @@ -183,6 +183,10 @@ class Reference { throw new Error('eventType should be a string.'); } + await new Promise((resolve) => + setTimeout(resolve, DatabaseConstants.defaultDelayInMilliseconds), + ); + return Promise.resolve(this._dataSnapshot); } diff --git a/src/pages/app/__tests__/builder.test.js b/src/pages/app/__tests__/builder.test.js index e703defea..4f2388933 100644 --- a/src/pages/app/__tests__/builder.test.js +++ b/src/pages/app/__tests__/builder.test.js @@ -6,6 +6,7 @@ import { render, screen, waitFor, + waitForElementToBeRemoved, } from '@testing-library/react'; import FirebaseStub, { DatabaseConstants } from 'gatsby-plugin-firebase'; @@ -25,9 +26,11 @@ describe('Builder', () => { let resumeId = null; let resume = null; let mockDatabaseUpdateFunction = null; + const loadingScreenTestId = 'loading-screen'; async function setup( resumeIdParameter, + waitForLoadingScreenToDisappear = true, waitForDatabaseUpdateFunctionToHaveBeenCalled = true, ) { FirebaseStub.database().initializeData(); @@ -66,6 +69,12 @@ describe('Builder', () => { await FirebaseStub.auth().signInAnonymously(); }); + if (waitForLoadingScreenToDisappear) { + await waitForElementToBeRemoved(() => + screen.getByTestId(loadingScreenTestId), + ); + } + if (waitForDatabaseUpdateFunctionToHaveBeenCalled) { await waitFor(() => mockDatabaseUpdateFunction.mock.calls[0][0], { timeout: DebounceWaitTime, @@ -77,12 +86,13 @@ describe('Builder', () => { describe('handles errors', () => { describe('if resume does not exist', () => { beforeEach(async () => { - await setup('xxxxxx', false); + await setup('xxxxxx', false, false); }); - it('navigates to Dashboard', () => { - expect(resume).toBeNull(); - expect(mockNavigateFunction).toHaveBeenCalledTimes(1); + it('navigates to Dashboard', async () => { + await waitFor(() => + expect(mockNavigateFunction).toHaveBeenCalledTimes(1), + ); expect(mockNavigateFunction).toHaveBeenCalledWith('/app/dashboard'); }); }); @@ -187,4 +197,14 @@ describe('Builder', () => { ).toBeGreaterThanOrEqual(now); }); }); + + describe('while loading', () => { + beforeEach(async () => { + await setup(DatabaseConstants.demoStateResume1Id, false, false); + }); + + it('renders loading screen', () => { + expect(screen.getByTestId(loadingScreenTestId)).toBeInTheDocument(); + }); + }); }); diff --git a/src/pages/app/__tests__/dashboard.test.js b/src/pages/app/__tests__/dashboard.test.js index 93d1615e2..ac1a74524 100644 --- a/src/pages/app/__tests__/dashboard.test.js +++ b/src/pages/app/__tests__/dashboard.test.js @@ -23,7 +23,7 @@ describe('Dashboard', () => { const user = DatabaseConstants.user1; const loadingScreenTestId = 'loading-screen'; - async function setup(waitForLoadingScreenToDisappear) { + async function setup(waitForLoadingScreenToDisappear = true) { FirebaseStub.database().initializeData(); resumes = ( @@ -61,32 +61,30 @@ describe('Dashboard', () => { } } - describe('after loading', () => { + describe('renders', () => { beforeEach(async () => { - await setup(true); + await setup(); }); - describe('renders', () => { - it('document title', () => { - expect(document.title).toEqual('Dashboard | Reactive Resume'); - }); + it('document title', () => { + expect(document.title).toEqual('Dashboard | Reactive Resume'); + }); - it('create resume', () => { - expect(screen.getByText(/create resume/i)).toBeInTheDocument(); - }); + it('create resume', () => { + expect(screen.getByText(/create resume/i)).toBeInTheDocument(); + }); - it('preview of user resumes', () => { - expect(Object.keys(resumes)).toHaveLength(2); + it('preview of user resumes', () => { + 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(); - }); + 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(); }); }); @@ -95,10 +93,8 @@ describe('Dashboard', () => { await setup(false); }); - describe('renders', () => { - it('loading screen', () => { - expect(screen.getByTestId(loadingScreenTestId)).toBeInTheDocument(); - }); + it('renders loading screen', () => { + expect(screen.getByTestId(loadingScreenTestId)).toBeInTheDocument(); }); }); }); From af966bdf7b289b3f746e1066f44c4ee971549643 Mon Sep 17 00:00:00 2001 From: gianantoniopini <63844628+gianantoniopini@users.noreply.github.com> Date: Thu, 21 Jan 2021 16:45:33 +0100 Subject: [PATCH 05/14] Firebase Stub: introduced a delay in all async functions to better mimic real Firebase --- __mocks__/gatsby-plugin-firebase/auth/auth.js | 3 +++ .../gatsby-plugin-firebase/constants/auth.js | 6 +++++ .../database/reference.js | 17 +++++++++----- .../gatsby-plugin-firebase/utils/index.js | 5 +++++ src/pages/app/__tests__/builder.test.js | 22 +++++++++++++------ src/pages/app/__tests__/dashboard.test.js | 7 ++---- 6 files changed, 42 insertions(+), 18 deletions(-) create mode 100644 __mocks__/gatsby-plugin-firebase/utils/index.js diff --git a/__mocks__/gatsby-plugin-firebase/auth/auth.js b/__mocks__/gatsby-plugin-firebase/auth/auth.js index 3d1183d4d..b27aaf102 100644 --- a/__mocks__/gatsby-plugin-firebase/auth/auth.js +++ b/__mocks__/gatsby-plugin-firebase/auth/auth.js @@ -2,6 +2,7 @@ import { v4 as uuidv4 } from 'uuid'; import Constants from '../constants/auth'; +import delay from '../utils/index'; const singleton = Symbol(''); const singletonEnforcer = Symbol(''); @@ -49,6 +50,8 @@ class Auth { async signInAnonymously() { const user = Constants.anonymousUser1; + await delay(Constants.defaultDelayInMilliseconds); + this.onAuthStateChangedObservers.forEach((observer) => observer(user)); return Promise.resolve(user); diff --git a/__mocks__/gatsby-plugin-firebase/constants/auth.js b/__mocks__/gatsby-plugin-firebase/constants/auth.js index 9280bfd9b..13f8fcc40 100644 --- a/__mocks__/gatsby-plugin-firebase/constants/auth.js +++ b/__mocks__/gatsby-plugin-firebase/constants/auth.js @@ -14,6 +14,8 @@ const anonymousUser2 = { uid: 'anonym456', }; +const defaultDelayInMilliseconds = 100; + class Auth { static get anonymousUser1() { return anonymousUser1; @@ -22,6 +24,10 @@ class Auth { static get anonymousUser2() { return anonymousUser2; } + + static get defaultDelayInMilliseconds() { + return defaultDelayInMilliseconds; + } } export default Auth; diff --git a/__mocks__/gatsby-plugin-firebase/database/reference.js b/__mocks__/gatsby-plugin-firebase/database/reference.js index 7e37737b8..6d4223145 100644 --- a/__mocks__/gatsby-plugin-firebase/database/reference.js +++ b/__mocks__/gatsby-plugin-firebase/database/reference.js @@ -4,6 +4,7 @@ import { debounce } from 'lodash'; import DatabaseConstants from '../constants/database'; import DataSnapshot from './dataSnapshot'; +import delay from '../utils/index'; const parsePath = (path) => { if (!path) { @@ -183,9 +184,7 @@ class Reference { throw new Error('eventType should be a string.'); } - await new Promise((resolve) => - setTimeout(resolve, DatabaseConstants.defaultDelayInMilliseconds), - ); + await delay(DatabaseConstants.defaultDelayInMilliseconds); return Promise.resolve(this._dataSnapshot); } @@ -196,21 +195,27 @@ class Reference { } async update(value) { + await delay(DatabaseConstants.defaultDelayInMilliseconds); + this._handleDataUpdate(value); - return Promise.resolve(true); + return Promise.resolve(); } async remove() { + await delay(DatabaseConstants.defaultDelayInMilliseconds); + this._handleDataUpdate(null); - return Promise.resolve(true); + return Promise.resolve(); } async set(value) { + await delay(DatabaseConstants.defaultDelayInMilliseconds); + this._handleDataUpdate(value); - return Promise.resolve(true); + return Promise.resolve(); } } diff --git a/__mocks__/gatsby-plugin-firebase/utils/index.js b/__mocks__/gatsby-plugin-firebase/utils/index.js new file mode 100644 index 000000000..0b290076e --- /dev/null +++ b/__mocks__/gatsby-plugin-firebase/utils/index.js @@ -0,0 +1,5 @@ +const delay = async (milliseconds) => { + await new Promise((resolve) => setTimeout(resolve, milliseconds)); +}; + +export default delay; diff --git a/src/pages/app/__tests__/builder.test.js b/src/pages/app/__tests__/builder.test.js index 4f2388933..d1551e62a 100644 --- a/src/pages/app/__tests__/builder.test.js +++ b/src/pages/app/__tests__/builder.test.js @@ -1,7 +1,6 @@ import { navigate as mockNavigateFunction } from 'gatsby'; import React from 'react'; import { - act, fireEvent, render, screen, @@ -31,7 +30,7 @@ describe('Builder', () => { async function setup( resumeIdParameter, waitForLoadingScreenToDisappear = true, - waitForDatabaseUpdateFunctionToHaveBeenCalled = true, + waitForDatabaseUpdateFunctionToHaveCompleted = true, ) { FirebaseStub.database().initializeData(); @@ -49,6 +48,8 @@ describe('Builder', () => { 'update', ); + FirebaseStub.auth().signInAnonymously(); + render( @@ -65,20 +66,17 @@ describe('Builder', () => { , ); - await act(async () => { - await FirebaseStub.auth().signInAnonymously(); - }); - if (waitForLoadingScreenToDisappear) { await waitForElementToBeRemoved(() => screen.getByTestId(loadingScreenTestId), ); } - if (waitForDatabaseUpdateFunctionToHaveBeenCalled) { + if (waitForDatabaseUpdateFunctionToHaveCompleted) { await waitFor(() => mockDatabaseUpdateFunction.mock.calls[0][0], { timeout: DebounceWaitTime, }); + await waitFor(() => mockDatabaseUpdateFunction.mock.results[0].value); mockDatabaseUpdateFunction.mockClear(); } } @@ -158,6 +156,11 @@ describe('Builder', () => { expect( mockDatabaseUpdateFunctionCallArgument.updatedAt, ).toBeGreaterThanOrEqual(now); + await waitFor(() => + expect( + mockDatabaseUpdateFunction.mock.results[0].value, + ).resolves.toBeUndefined(), + ); }); afterEach(() => { @@ -195,6 +198,11 @@ describe('Builder', () => { expect( mockDatabaseUpdateFunctionCallArgument.updatedAt, ).toBeGreaterThanOrEqual(now); + await waitFor(() => + expect( + mockDatabaseUpdateFunction.mock.results[0].value, + ).resolves.toBeUndefined(), + ); }); }); diff --git a/src/pages/app/__tests__/dashboard.test.js b/src/pages/app/__tests__/dashboard.test.js index ac1a74524..a2ff6adf6 100644 --- a/src/pages/app/__tests__/dashboard.test.js +++ b/src/pages/app/__tests__/dashboard.test.js @@ -1,6 +1,5 @@ import React from 'react'; import { - act, render, screen, waitForElementToBeRemoved, @@ -34,6 +33,8 @@ describe('Dashboard', () => { .once('value') ).val(); + FirebaseStub.auth().signInAnonymously(); + render( @@ -50,10 +51,6 @@ describe('Dashboard', () => { , ); - await act(async () => { - await FirebaseStub.auth().signInAnonymously(); - }); - if (waitForLoadingScreenToDisappear) { await waitForElementToBeRemoved(() => screen.getByTestId(loadingScreenTestId), From 306db64ff1e58e0704e83ee28b101423ebf88932 Mon Sep 17 00:00:00 2001 From: gianantoniopini <63844628+gianantoniopini@users.noreply.github.com> Date: Thu, 21 Jan 2021 16:56:05 +0100 Subject: [PATCH 06/14] Firebase Stub: minor tweak in reference .on function --- __mocks__/gatsby-plugin-firebase/database/reference.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/__mocks__/gatsby-plugin-firebase/database/reference.js b/__mocks__/gatsby-plugin-firebase/database/reference.js index 6d4223145..ea04ee405 100644 --- a/__mocks__/gatsby-plugin-firebase/database/reference.js +++ b/__mocks__/gatsby-plugin-firebase/database/reference.js @@ -175,6 +175,8 @@ class Reference { if (eventType === DatabaseConstants.valueEventType) { this.triggerEventCallback(eventType); } + + return callback; } async once(eventType) { From 0bab0d01d1dd2c848d78fa89debc5fbba8a9ad89 Mon Sep 17 00:00:00 2001 From: gianantoniopini <63844628+gianantoniopini@users.noreply.github.com> Date: Fri, 22 Jan 2021 06:05:00 +0100 Subject: [PATCH 07/14] Removed testId from LoadingScreen --- src/components/router/LoadingScreen.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/router/LoadingScreen.js b/src/components/router/LoadingScreen.js index 67e387318..19b6ec3ad 100644 --- a/src/components/router/LoadingScreen.js +++ b/src/components/router/LoadingScreen.js @@ -4,7 +4,7 @@ import { getRandomTip } from '../../data/tips'; import Logo from '../shared/Logo'; const LoadingScreen = () => ( - +
From 16ed433705e1855e15a3cbcfbf6c62afe0306ea8 Mon Sep 17 00:00:00 2001 From: gianantoniopini <63844628+gianantoniopini@users.noreply.github.com> Date: Fri, 22 Jan 2021 06:36:11 +0100 Subject: [PATCH 08/14] Exported LoadingScreen testId and language Storage item key in order to be used in unit tests --- src/components/router/LoadingScreen.js | 5 ++++- src/contexts/SettingsContext.js | 8 +++++--- src/pages/app/__tests__/builder.test.js | 9 +++++---- src/pages/app/__tests__/dashboard.test.js | 2 +- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/components/router/LoadingScreen.js b/src/components/router/LoadingScreen.js index 19b6ec3ad..b5f9ba412 100644 --- a/src/components/router/LoadingScreen.js +++ b/src/components/router/LoadingScreen.js @@ -3,8 +3,10 @@ import React, { memo } from 'react'; import { getRandomTip } from '../../data/tips'; import Logo from '../shared/Logo'; +const dataTestId = 'loading-screen'; + const LoadingScreen = () => ( - +
@@ -17,3 +19,4 @@ const LoadingScreen = () => ( ); export default memo(LoadingScreen); +export { dataTestId }; diff --git a/src/contexts/SettingsContext.js b/src/contexts/SettingsContext.js index e17e6b1d3..a3277a53f 100644 --- a/src/contexts/SettingsContext.js +++ b/src/contexts/SettingsContext.js @@ -2,6 +2,8 @@ import i18next from 'i18next'; import React, { createContext, memo, useEffect, useState } from 'react'; import themeConfig from '../data/themeConfig'; +const languageStorageItemKey = 'language'; + const defaultState = { theme: 'Dark', setTheme: () => {}, @@ -18,7 +20,7 @@ const SettingsProvider = ({ children }) => { useEffect(() => { const prefTheme = localStorage.getItem('theme') || defaultState.theme; const prefLanguage = - localStorage.getItem('language') || defaultState.language; + localStorage.getItem(languageStorageItemKey) || defaultState.language; setTheme(prefTheme); setLanguage(prefLanguage); }, []); @@ -32,7 +34,7 @@ const SettingsProvider = ({ children }) => { }, [theme]); useEffect(() => { - localStorage.setItem('language', language); + localStorage.setItem(languageStorageItemKey, language); i18next.changeLanguage(language); }, [language]); @@ -54,4 +56,4 @@ export default SettingsContext; const memoizedProvider = memo(SettingsProvider); -export { memoizedProvider as SettingsProvider }; +export { memoizedProvider as SettingsProvider, languageStorageItemKey }; diff --git a/src/pages/app/__tests__/builder.test.js b/src/pages/app/__tests__/builder.test.js index d1551e62a..c6f0ebd54 100644 --- a/src/pages/app/__tests__/builder.test.js +++ b/src/pages/app/__tests__/builder.test.js @@ -10,7 +10,11 @@ import { import FirebaseStub, { DatabaseConstants } from 'gatsby-plugin-firebase'; -import { SettingsProvider } from '../../../contexts/SettingsContext'; +import { dataTestId as loadingScreenTestId } from '../../../components/router/LoadingScreen'; +import { + SettingsProvider, + languageStorageItemKey, +} from '../../../contexts/SettingsContext'; import { ModalProvider } from '../../../contexts/ModalContext'; import { UserProvider } from '../../../contexts/UserContext'; import { @@ -25,7 +29,6 @@ describe('Builder', () => { let resumeId = null; let resume = null; let mockDatabaseUpdateFunction = null; - const loadingScreenTestId = 'loading-screen'; async function setup( resumeIdParameter, @@ -118,8 +121,6 @@ describe('Builder', () => { }); describe('settings', () => { - const languageStorageItemKey = 'language'; - beforeEach(async () => { await setup(DatabaseConstants.demoStateResume1Id); }); diff --git a/src/pages/app/__tests__/dashboard.test.js b/src/pages/app/__tests__/dashboard.test.js index a2ff6adf6..a8e65ae5c 100644 --- a/src/pages/app/__tests__/dashboard.test.js +++ b/src/pages/app/__tests__/dashboard.test.js @@ -9,6 +9,7 @@ import FirebaseStub, { DatabaseConstants } from 'gatsby-plugin-firebase'; import '../../../i18n/index'; import '../../../utils/dayjs'; +import { dataTestId as loadingScreenTestId } from '../../../components/router/LoadingScreen'; import { SettingsProvider } from '../../../contexts/SettingsContext'; import { ModalProvider } from '../../../contexts/ModalContext'; import { UserProvider } from '../../../contexts/UserContext'; @@ -20,7 +21,6 @@ import Dashboard from '../dashboard'; describe('Dashboard', () => { let resumes = null; const user = DatabaseConstants.user1; - const loadingScreenTestId = 'loading-screen'; async function setup(waitForLoadingScreenToDisappear = true) { FirebaseStub.database().initializeData(); From cb2aeec70a4d34f5f758fc40920269d8222b88bb Mon Sep 17 00:00:00 2001 From: gianantoniopini <63844628+gianantoniopini@users.noreply.github.com> Date: Fri, 22 Jan 2021 10:43:06 +0100 Subject: [PATCH 09/14] Dashboard: added unit tests for Delete resume --- src/components/dashboard/ResumePreview.js | 5 ++ src/pages/app/__tests__/dashboard.test.js | 66 +++++++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/src/components/dashboard/ResumePreview.js b/src/components/dashboard/ResumePreview.js index d0a794e80..52fca4681 100644 --- a/src/components/dashboard/ResumePreview.js +++ b/src/components/dashboard/ResumePreview.js @@ -9,6 +9,8 @@ import DatabaseContext from '../../contexts/DatabaseContext'; import ModalContext from '../../contexts/ModalContext'; import styles from './ResumePreview.module.css'; +const menuToggleDataTestIdPrefix = 'resume-preview-menu-toggle-'; + const ResumePreview = ({ resume }) => { const { t, i18n } = useTranslation(); const [anchorEl, setAnchorEl] = useState(null); @@ -54,6 +56,7 @@ const ResumePreview = ({ resume }) => { onClick={handleOpen} /> { }; export default ResumePreview; + +export { menuToggleDataTestIdPrefix }; diff --git a/src/pages/app/__tests__/dashboard.test.js b/src/pages/app/__tests__/dashboard.test.js index a8e65ae5c..f65cfbb5a 100644 --- a/src/pages/app/__tests__/dashboard.test.js +++ b/src/pages/app/__tests__/dashboard.test.js @@ -1,7 +1,10 @@ import React from 'react'; import { + fireEvent, + queryByText, render, screen, + waitFor, waitForElementToBeRemoved, } from '@testing-library/react'; @@ -10,6 +13,7 @@ import FirebaseStub, { DatabaseConstants } from 'gatsby-plugin-firebase'; import '../../../i18n/index'; import '../../../utils/dayjs'; import { dataTestId as loadingScreenTestId } from '../../../components/router/LoadingScreen'; +import { menuToggleDataTestIdPrefix as resumePreviewMenuToggleDataTestIdPrefix } from '../../../components/dashboard/ResumePreview'; import { SettingsProvider } from '../../../contexts/SettingsContext'; import { ModalProvider } from '../../../contexts/ModalContext'; import { UserProvider } from '../../../contexts/UserContext'; @@ -85,6 +89,68 @@ describe('Dashboard', () => { }); }); + describe('when resume is deleted', () => { + let mockDatabaseRemoveFunction = null; + let resumeToDelete = null; + let undeletedResume = null; + let resumeToDeleteId = null; + + beforeEach(async () => { + await setup(); + + [resumeToDelete, undeletedResume] = Object.values(resumes); + resumeToDeleteId = resumeToDelete.id; + + mockDatabaseRemoveFunction = jest.spyOn( + FirebaseStub.database().ref( + `${DatabaseConstants.resumesPath}/${resumeToDeleteId}`, + ), + 'remove', + ); + + const resumeToDeleteMenuToggle = screen.getByTestId( + `${resumePreviewMenuToggleDataTestIdPrefix}${resumeToDeleteId}`, + ); + fireEvent.click(resumeToDeleteMenuToggle); + + const menuItems = screen.getAllByRole('menuitem'); + let deleteMenuItem = null; + for (let index = 0; index < menuItems.length; index++) { + if (queryByText(menuItems[index], /delete/i)) { + deleteMenuItem = menuItems[index]; + break; + } + } + fireEvent.click(deleteMenuItem); + }); + + it('removes it from database and preview', async () => { + await waitFor(() => + expect(mockDatabaseRemoveFunction).toHaveBeenCalledTimes(1), + ); + + await waitFor(() => + expect(screen.queryByText(resumeToDelete.name)).toBeNull(), + ); + expect(screen.getByText(undeletedResume.name)).toBeInTheDocument(); + }); + + /* + it('displays notification', async () => { + await waitFor(() => + expect( + screen.getByText(`${resumeToDelete.name} was deleted successfully`), + ).toBeInTheDocument(), + ); + }); + */ + + it('closes menu', () => { + const menuItems = screen.queryAllByRole('menuitem'); + expect(menuItems).toHaveLength(0); + }); + }); + describe('while loading', () => { beforeEach(async () => { await setup(false); From dc18e84bc2cbfabcbde6594f6c1ba6d5b2971cc6 Mon Sep 17 00:00:00 2001 From: gianantoniopini <63844628+gianantoniopini@users.noreply.github.com> Date: Fri, 22 Jan 2021 16:42:21 +0100 Subject: [PATCH 10/14] Dashboard: updated unit tests for Delete resume --- .../database/reference.js | 11 ++- src/pages/app/__tests__/builder.test.js | 11 ++- src/pages/app/__tests__/dashboard.test.js | 71 +++++++++++++------ 3 files changed, 63 insertions(+), 30 deletions(-) diff --git a/__mocks__/gatsby-plugin-firebase/database/reference.js b/__mocks__/gatsby-plugin-firebase/database/reference.js index ea04ee405..305c82b78 100644 --- a/__mocks__/gatsby-plugin-firebase/database/reference.js +++ b/__mocks__/gatsby-plugin-firebase/database/reference.js @@ -1,6 +1,5 @@ /* eslint-disable no-underscore-dangle */ import { v4 as uuidv4 } from 'uuid'; -import { debounce } from 'lodash'; import DatabaseConstants from '../constants/database'; import DataSnapshot from './dataSnapshot'; @@ -148,11 +147,7 @@ class Reference { ? this._dataSnapshot : new DataSnapshot(() => this._getData(), snapshotValue); - const debouncedEventCallback = debounce( - this.eventCallbacks[eventType], - DatabaseConstants.defaultDelayInMilliseconds, - ); - debouncedEventCallback(snapshot); + this.eventCallbacks[eventType](snapshot); } equalTo(value) { @@ -173,7 +168,9 @@ class Reference { this.eventCallbacks[eventType] = callback; if (eventType === DatabaseConstants.valueEventType) { - this.triggerEventCallback(eventType); + setTimeout(() => { + this.triggerEventCallback(eventType); + }, DatabaseConstants.defaultDelayInMilliseconds); } return callback; diff --git a/src/pages/app/__tests__/builder.test.js b/src/pages/app/__tests__/builder.test.js index c6f0ebd54..558609357 100644 --- a/src/pages/app/__tests__/builder.test.js +++ b/src/pages/app/__tests__/builder.test.js @@ -23,6 +23,7 @@ import { } from '../../../contexts/DatabaseContext'; import { ResumeProvider } from '../../../contexts/ResumeContext'; import { StorageProvider } from '../../../contexts/StorageContext'; +import Wrapper from '../../../components/shared/Wrapper'; import Builder from '../builder'; describe('Builder', () => { @@ -60,7 +61,9 @@ describe('Builder', () => { - + + + @@ -212,6 +215,12 @@ describe('Builder', () => { await setup(DatabaseConstants.demoStateResume1Id, false, false); }); + afterEach(async () => { + await waitForElementToBeRemoved(() => + screen.getByTestId(loadingScreenTestId), + ); + }); + it('renders loading screen', () => { expect(screen.getByTestId(loadingScreenTestId)).toBeInTheDocument(); }); diff --git a/src/pages/app/__tests__/dashboard.test.js b/src/pages/app/__tests__/dashboard.test.js index f65cfbb5a..385493b92 100644 --- a/src/pages/app/__tests__/dashboard.test.js +++ b/src/pages/app/__tests__/dashboard.test.js @@ -20,6 +20,7 @@ import { UserProvider } from '../../../contexts/UserContext'; import { DatabaseProvider } from '../../../contexts/DatabaseContext'; import { ResumeProvider } from '../../../contexts/ResumeContext'; import { StorageProvider } from '../../../contexts/StorageContext'; +import Wrapper from '../../../components/shared/Wrapper'; import Dashboard from '../dashboard'; describe('Dashboard', () => { @@ -46,7 +47,9 @@ describe('Dashboard', () => { - + + + @@ -67,25 +70,33 @@ describe('Dashboard', () => { await setup(); }); - it('document title', () => { - expect(document.title).toEqual('Dashboard | Reactive Resume'); + it('document title', async () => { + await waitFor(() => { + expect(document.title).toEqual('Dashboard | Reactive Resume'); + }); }); - it('create resume', () => { - expect(screen.getByText(/create resume/i)).toBeInTheDocument(); + it('create resume', async () => { + await waitFor(() => { + expect(screen.getByText(/create resume/i)).toBeInTheDocument(); + }); }); - it('preview of user resumes', () => { + 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(); + await waitFor(() => { + 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(); + await waitFor(() => { + expect( + screen.getByText(Object.values(resumes)[1].name), + ).toBeInTheDocument(); + }); }); }); @@ -95,6 +106,10 @@ describe('Dashboard', () => { let undeletedResume = null; let resumeToDeleteId = null; + const waitForDatabaseRemoveFunctionToHaveCompleted = async () => { + await waitFor(() => mockDatabaseRemoveFunction.mock.results[0].value); + }; + beforeEach(async () => { await setup(); @@ -108,9 +123,13 @@ describe('Dashboard', () => { 'remove', ); - const resumeToDeleteMenuToggle = screen.getByTestId( - `${resumePreviewMenuToggleDataTestIdPrefix}${resumeToDeleteId}`, - ); + let resumeToDeleteMenuToggle = null; + await waitFor(() => { + resumeToDeleteMenuToggle = screen.queryByTestId( + `${resumePreviewMenuToggleDataTestIdPrefix}${resumeToDeleteId}`, + ); + return resumeToDeleteMenuToggle ? Promise.resolve() : Promise.reject(); + }); fireEvent.click(resumeToDeleteMenuToggle); const menuItems = screen.getAllByRole('menuitem'); @@ -124,26 +143,28 @@ describe('Dashboard', () => { fireEvent.click(deleteMenuItem); }); + afterEach(async () => { + await waitForDatabaseRemoveFunctionToHaveCompleted(); + }); + it('removes it from database and preview', async () => { await waitFor(() => expect(mockDatabaseRemoveFunction).toHaveBeenCalledTimes(1), ); + await waitForDatabaseRemoveFunctionToHaveCompleted(); + await waitFor(() => expect(screen.queryByText(resumeToDelete.name)).toBeNull(), ); expect(screen.getByText(undeletedResume.name)).toBeInTheDocument(); }); - /* it('displays notification', async () => { - await waitFor(() => - expect( - screen.getByText(`${resumeToDelete.name} was deleted successfully`), - ).toBeInTheDocument(), - ); + await waitFor(() => { + expect(screen.getByRole('alert')).toBeInTheDocument(); + }); }); - */ it('closes menu', () => { const menuItems = screen.queryAllByRole('menuitem'); @@ -156,6 +177,12 @@ describe('Dashboard', () => { await setup(false); }); + afterEach(async () => { + await waitForElementToBeRemoved(() => + screen.getByTestId(loadingScreenTestId), + ); + }); + it('renders loading screen', () => { expect(screen.getByTestId(loadingScreenTestId)).toBeInTheDocument(); }); From 9a7bdb188b015ec133eee8171dd1276426d06b76 Mon Sep 17 00:00:00 2001 From: gianantoniopini <63844628+gianantoniopini@users.noreply.github.com> Date: Fri, 22 Jan 2021 17:25:04 +0100 Subject: [PATCH 11/14] Builder: updated unit test handling non-existent resume --- __mocks__/gatsby-plugin-firebase/auth/auth.js | 2 +- .../gatsby-plugin-firebase/database/reference.js | 2 +- __mocks__/gatsby.js | 12 +++++++++++- .../{gatsby-plugin-firebase => }/utils/index.js | 0 src/pages/app/__tests__/builder.test.js | 14 +++++++++++++- 5 files changed, 26 insertions(+), 4 deletions(-) rename __mocks__/{gatsby-plugin-firebase => }/utils/index.js (100%) diff --git a/__mocks__/gatsby-plugin-firebase/auth/auth.js b/__mocks__/gatsby-plugin-firebase/auth/auth.js index b27aaf102..fc5eb043b 100644 --- a/__mocks__/gatsby-plugin-firebase/auth/auth.js +++ b/__mocks__/gatsby-plugin-firebase/auth/auth.js @@ -2,7 +2,7 @@ import { v4 as uuidv4 } from 'uuid'; import Constants from '../constants/auth'; -import delay from '../utils/index'; +import delay from '../../utils/index'; const singleton = Symbol(''); const singletonEnforcer = Symbol(''); diff --git a/__mocks__/gatsby-plugin-firebase/database/reference.js b/__mocks__/gatsby-plugin-firebase/database/reference.js index 305c82b78..08e345fdb 100644 --- a/__mocks__/gatsby-plugin-firebase/database/reference.js +++ b/__mocks__/gatsby-plugin-firebase/database/reference.js @@ -3,7 +3,7 @@ import { v4 as uuidv4 } from 'uuid'; import DatabaseConstants from '../constants/database'; import DataSnapshot from './dataSnapshot'; -import delay from '../utils/index'; +import delay from '../../utils/index'; const parsePath = (path) => { if (!path) { diff --git a/__mocks__/gatsby.js b/__mocks__/gatsby.js index e9339f5d9..09ea71c73 100644 --- a/__mocks__/gatsby.js +++ b/__mocks__/gatsby.js @@ -1,5 +1,7 @@ import React from 'react'; +import delay from './utils/index'; + const Gatsby = jest.requireActual('gatsby'); const fluidImageShapes = [ @@ -67,6 +69,14 @@ const useStaticQuery = () => ({ }, }); +const defaultDelayInMilliseconds = 100; + +const navigate = async () => { + await delay(defaultDelayInMilliseconds); + + return Promise.resolve(); +}; + module.exports = { ...Gatsby, graphql: jest.fn(), @@ -76,6 +86,6 @@ module.exports = { href: to, }), ), - navigate: jest.fn(), + navigate: jest.fn(navigate), useStaticQuery, }; diff --git a/__mocks__/gatsby-plugin-firebase/utils/index.js b/__mocks__/utils/index.js similarity index 100% rename from __mocks__/gatsby-plugin-firebase/utils/index.js rename to __mocks__/utils/index.js diff --git a/src/pages/app/__tests__/builder.test.js b/src/pages/app/__tests__/builder.test.js index 558609357..cc9b80ef0 100644 --- a/src/pages/app/__tests__/builder.test.js +++ b/src/pages/app/__tests__/builder.test.js @@ -89,15 +89,27 @@ describe('Builder', () => { describe('handles errors', () => { describe('if resume does not exist', () => { + const waitForNavigateFunctionToHaveCompleted = async () => { + await waitFor(() => mockNavigateFunction.mock.results[0].value); + }; + beforeEach(async () => { await setup('xxxxxx', false, false); }); - it('navigates to Dashboard', async () => { + afterEach(async () => { + await waitForNavigateFunctionToHaveCompleted(); + }); + + it('navigates to Dashboard and displays notification', async () => { await waitFor(() => expect(mockNavigateFunction).toHaveBeenCalledTimes(1), ); expect(mockNavigateFunction).toHaveBeenCalledWith('/app/dashboard'); + + await waitFor(() => { + expect(screen.getByRole('alert')).toBeInTheDocument(); + }); }); }); }); From 5317f5e525e78843301e918178ba418e7ff8a9d1 Mon Sep 17 00:00:00 2001 From: gianantoniopini <63844628+gianantoniopini@users.noreply.github.com> Date: Sat, 23 Jan 2021 11:45:06 +0100 Subject: [PATCH 12/14] Builder and Dashboard unit tests refactoring/cleanup --- .../__tests__/gatsby-plugin-firebase.test.js | 17 ++-- __mocks__/gatsby-plugin-firebase/auth/auth.js | 4 - src/pages/app/__tests__/builder.test.js | 76 ++++++++--------- src/pages/app/__tests__/dashboard.test.js | 82 ++++++++++--------- 4 files changed, 86 insertions(+), 93 deletions(-) diff --git a/__mocks__/__tests__/gatsby-plugin-firebase.test.js b/__mocks__/__tests__/gatsby-plugin-firebase.test.js index 84e0a9ca3..f80e06c08 100644 --- a/__mocks__/__tests__/gatsby-plugin-firebase.test.js +++ b/__mocks__/__tests__/gatsby-plugin-firebase.test.js @@ -6,10 +6,6 @@ import FirebaseStub, { describe('FirebaseStub', () => { describe('auth', () => { - afterEach(() => { - FirebaseStub.auth().dispose(); - }); - it('reuses existing Auth instance', () => { const auth1 = FirebaseStub.auth(); const auth2 = FirebaseStub.auth(); @@ -49,14 +45,15 @@ describe('FirebaseStub', () => { const observer = () => {}; const unsubscribe = FirebaseStub.auth().onAuthStateChanged(observer); expect(unsubscribe).toBeTruthy(); - expect(FirebaseStub.auth().onAuthStateChangedObservers).toHaveLength(1); - expect(FirebaseStub.auth().onAuthStateChangedObservers[0]).toEqual( - observer, - ); + expect( + FirebaseStub.auth().onAuthStateChangedObservers.indexOf(observer), + ).toBeGreaterThanOrEqual(0); unsubscribe(); - expect(FirebaseStub.auth().onAuthStateChangedObservers).toHaveLength(0); + expect( + FirebaseStub.auth().onAuthStateChangedObservers.indexOf(observer), + ).not.toBeGreaterThanOrEqual(0); }); }); @@ -258,7 +255,7 @@ describe('FirebaseStub', () => { ); }); - it('previously set query parameters are not kept when retrieving reference again', async () => { + it('previously set query parameters are not kept when retrieving reference again', () => { let reference = null; reference = FirebaseStub.database().ref(DatabaseConstants.resumesPath); diff --git a/__mocks__/gatsby-plugin-firebase/auth/auth.js b/__mocks__/gatsby-plugin-firebase/auth/auth.js index fc5eb043b..853bb1334 100644 --- a/__mocks__/gatsby-plugin-firebase/auth/auth.js +++ b/__mocks__/gatsby-plugin-firebase/auth/auth.js @@ -33,10 +33,6 @@ class Auth { return this._onAuthStateChangedObservers; } - dispose() { - this._onAuthStateChangedObservers = []; - } - onAuthStateChanged(observer) { this.onAuthStateChangedObservers.push(observer); diff --git a/src/pages/app/__tests__/builder.test.js b/src/pages/app/__tests__/builder.test.js index cc9b80ef0..4615e5aa5 100644 --- a/src/pages/app/__tests__/builder.test.js +++ b/src/pages/app/__tests__/builder.test.js @@ -31,10 +31,31 @@ describe('Builder', () => { let resume = null; let mockDatabaseUpdateFunction = null; + const fnWaitForDatabaseUpdateToHaveCompleted = async () => { + await waitFor(() => mockDatabaseUpdateFunction.mock.calls[0][0], { + timeout: DebounceWaitTime, + }); + await waitFor(() => mockDatabaseUpdateFunction.mock.results[0].value); + }; + + const expectDatabaseUpdateToHaveCompleted = async () => { + await waitFor( + () => expect(mockDatabaseUpdateFunction).toHaveBeenCalledTimes(1), + { + timeout: DebounceWaitTime, + }, + ); + await waitFor(() => + expect( + mockDatabaseUpdateFunction.mock.results[0].value, + ).resolves.toBeUndefined(), + ); + }; + async function setup( resumeIdParameter, waitForLoadingScreenToDisappear = true, - waitForDatabaseUpdateFunctionToHaveCompleted = true, + waitForDatabaseUpdateToHaveCompleted = true, ) { FirebaseStub.database().initializeData(); @@ -78,29 +99,18 @@ describe('Builder', () => { ); } - if (waitForDatabaseUpdateFunctionToHaveCompleted) { - await waitFor(() => mockDatabaseUpdateFunction.mock.calls[0][0], { - timeout: DebounceWaitTime, - }); - await waitFor(() => mockDatabaseUpdateFunction.mock.results[0].value); + if (waitForDatabaseUpdateToHaveCompleted) { + await fnWaitForDatabaseUpdateToHaveCompleted(); mockDatabaseUpdateFunction.mockClear(); } } describe('handles errors', () => { describe('if resume does not exist', () => { - const waitForNavigateFunctionToHaveCompleted = async () => { - await waitFor(() => mockNavigateFunction.mock.results[0].value); - }; - beforeEach(async () => { await setup('xxxxxx', false, false); }); - afterEach(async () => { - await waitForNavigateFunctionToHaveCompleted(); - }); - it('navigates to Dashboard and displays notification', async () => { await waitFor(() => expect(mockNavigateFunction).toHaveBeenCalledTimes(1), @@ -110,6 +120,12 @@ describe('Builder', () => { await waitFor(() => { expect(screen.getByRole('alert')).toBeInTheDocument(); }); + + await waitFor(() => + expect( + mockNavigateFunction.mock.results[0].value, + ).resolves.toBeUndefined(), + ); }); }); }); @@ -157,12 +173,7 @@ describe('Builder', () => { const languageStorageItem = localStorage.getItem(languageStorageItemKey); expect(languageStorageItem).toBe(italianLanguageCode); - await waitFor( - () => expect(mockDatabaseUpdateFunction).toHaveBeenCalledTimes(1), - { - timeout: DebounceWaitTime, - }, - ); + await expectDatabaseUpdateToHaveCompleted(); const mockDatabaseUpdateFunctionCallArgument = mockDatabaseUpdateFunction.mock.calls[0][0]; expect(mockDatabaseUpdateFunctionCallArgument.id).toBe(resumeId); @@ -172,11 +183,6 @@ describe('Builder', () => { expect( mockDatabaseUpdateFunctionCallArgument.updatedAt, ).toBeGreaterThanOrEqual(now); - await waitFor(() => - expect( - mockDatabaseUpdateFunction.mock.results[0].value, - ).resolves.toBeUndefined(), - ); }); afterEach(() => { @@ -199,12 +205,7 @@ describe('Builder', () => { expect(input.value).toBe(newInputValue); - await waitFor( - () => expect(mockDatabaseUpdateFunction).toHaveBeenCalledTimes(1), - { - timeout: DebounceWaitTime, - }, - ); + await expectDatabaseUpdateToHaveCompleted(); const mockDatabaseUpdateFunctionCallArgument = mockDatabaseUpdateFunction.mock.calls[0][0]; expect(mockDatabaseUpdateFunctionCallArgument.id).toBe(resumeId); @@ -214,11 +215,6 @@ describe('Builder', () => { expect( mockDatabaseUpdateFunctionCallArgument.updatedAt, ).toBeGreaterThanOrEqual(now); - await waitFor(() => - expect( - mockDatabaseUpdateFunction.mock.results[0].value, - ).resolves.toBeUndefined(), - ); }); }); @@ -227,14 +223,14 @@ describe('Builder', () => { await setup(DatabaseConstants.demoStateResume1Id, false, false); }); - afterEach(async () => { + it('renders loading screen', async () => { + expect(screen.getByTestId(loadingScreenTestId)).toBeInTheDocument(); + await waitForElementToBeRemoved(() => screen.getByTestId(loadingScreenTestId), ); - }); - it('renders loading screen', () => { - expect(screen.getByTestId(loadingScreenTestId)).toBeInTheDocument(); + await fnWaitForDatabaseUpdateToHaveCompleted(); }); }); }); diff --git a/src/pages/app/__tests__/dashboard.test.js b/src/pages/app/__tests__/dashboard.test.js index 385493b92..26b8bccc1 100644 --- a/src/pages/app/__tests__/dashboard.test.js +++ b/src/pages/app/__tests__/dashboard.test.js @@ -24,13 +24,23 @@ import Wrapper from '../../../components/shared/Wrapper'; import Dashboard from '../dashboard'; describe('Dashboard', () => { - let resumes = null; + let userResumes = null; const user = DatabaseConstants.user1; + const waitForResumeToBeRenderedInPreview = async (resume) => { + await screen.findByText(resume.name); + }; + + const expectResumeToBeRenderedInPreview = async (resume) => { + await waitFor(() => { + expect(screen.getByText(resume.name)).toBeInTheDocument(); + }); + }; + async function setup(waitForLoadingScreenToDisappear = true) { FirebaseStub.database().initializeData(); - resumes = ( + userResumes = ( await FirebaseStub.database() .ref(DatabaseConstants.resumesPath) .orderByChild('user') @@ -83,20 +93,10 @@ describe('Dashboard', () => { }); it('preview of user resumes', async () => { - expect(Object.keys(resumes)).toHaveLength(2); + expect(Object.keys(userResumes)).toHaveLength(2); - expect(Object.values(resumes)[0].user).toEqual(user.uid); - await waitFor(() => { - expect( - screen.getByText(Object.values(resumes)[0].name), - ).toBeInTheDocument(); - }); - expect(Object.values(resumes)[1].user).toEqual(user.uid); - await waitFor(() => { - expect( - screen.getByText(Object.values(resumes)[1].name), - ).toBeInTheDocument(); - }); + await expectResumeToBeRenderedInPreview(Object.values(userResumes)[0]); + await expectResumeToBeRenderedInPreview(Object.values(userResumes)[1]); }); }); @@ -106,14 +106,25 @@ describe('Dashboard', () => { let undeletedResume = null; let resumeToDeleteId = null; - const waitForDatabaseRemoveFunctionToHaveCompleted = async () => { + const waitForDatabaseRemoveToHaveCompleted = async () => { await waitFor(() => mockDatabaseRemoveFunction.mock.results[0].value); }; + const expectDatabaseRemoveToHaveCompleted = async () => { + await waitFor(() => + expect(mockDatabaseRemoveFunction).toHaveBeenCalledTimes(1), + ); + await waitFor(() => + expect( + mockDatabaseRemoveFunction.mock.results[0].value, + ).resolves.toBeUndefined(), + ); + }; + beforeEach(async () => { await setup(); - [resumeToDelete, undeletedResume] = Object.values(resumes); + [resumeToDelete, undeletedResume] = Object.values(userResumes); resumeToDeleteId = resumeToDelete.id; mockDatabaseRemoveFunction = jest.spyOn( @@ -123,13 +134,9 @@ describe('Dashboard', () => { 'remove', ); - let resumeToDeleteMenuToggle = null; - await waitFor(() => { - resumeToDeleteMenuToggle = screen.queryByTestId( - `${resumePreviewMenuToggleDataTestIdPrefix}${resumeToDeleteId}`, - ); - return resumeToDeleteMenuToggle ? Promise.resolve() : Promise.reject(); - }); + const resumeToDeleteMenuToggle = await screen.findByTestId( + `${resumePreviewMenuToggleDataTestIdPrefix}${resumeToDeleteId}`, + ); fireEvent.click(resumeToDeleteMenuToggle); const menuItems = screen.getAllByRole('menuitem'); @@ -143,32 +150,28 @@ describe('Dashboard', () => { fireEvent.click(deleteMenuItem); }); - afterEach(async () => { - await waitForDatabaseRemoveFunctionToHaveCompleted(); - }); - it('removes it from database and preview', async () => { - await waitFor(() => - expect(mockDatabaseRemoveFunction).toHaveBeenCalledTimes(1), - ); - - await waitForDatabaseRemoveFunctionToHaveCompleted(); + await expectDatabaseRemoveToHaveCompleted(); await waitFor(() => expect(screen.queryByText(resumeToDelete.name)).toBeNull(), ); - expect(screen.getByText(undeletedResume.name)).toBeInTheDocument(); + await expectResumeToBeRenderedInPreview(undeletedResume); }); it('displays notification', async () => { await waitFor(() => { expect(screen.getByRole('alert')).toBeInTheDocument(); }); + + await waitForDatabaseRemoveToHaveCompleted(); }); - it('closes menu', () => { + it('closes menu', async () => { const menuItems = screen.queryAllByRole('menuitem'); expect(menuItems).toHaveLength(0); + + await waitForDatabaseRemoveToHaveCompleted(); }); }); @@ -177,14 +180,15 @@ describe('Dashboard', () => { await setup(false); }); - afterEach(async () => { + it('renders loading screen', async () => { + expect(screen.getByTestId(loadingScreenTestId)).toBeInTheDocument(); + await waitForElementToBeRemoved(() => screen.getByTestId(loadingScreenTestId), ); - }); - it('renders loading screen', () => { - expect(screen.getByTestId(loadingScreenTestId)).toBeInTheDocument(); + await waitForResumeToBeRenderedInPreview(Object.values(userResumes)[0]); + await waitForResumeToBeRenderedInPreview(Object.values(userResumes)[1]); }); }); }); From 74335a177193c0222ffdf69edcebfbf926a2a546 Mon Sep 17 00:00:00 2001 From: gianantoniopini <63844628+gianantoniopini@users.noreply.github.com> Date: Sat, 23 Jan 2021 12:21:02 +0100 Subject: [PATCH 13/14] Builder and Dashboard unit tests: fine tuning of assertions related to toast/notification --- src/pages/app/__tests__/builder.test.js | 11 ++++++++--- src/pages/app/__tests__/dashboard.test.js | 11 ++++++++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/pages/app/__tests__/builder.test.js b/src/pages/app/__tests__/builder.test.js index 4615e5aa5..dea1dfc4d 100644 --- a/src/pages/app/__tests__/builder.test.js +++ b/src/pages/app/__tests__/builder.test.js @@ -2,6 +2,7 @@ import { navigate as mockNavigateFunction } from 'gatsby'; import React from 'react'; import { fireEvent, + getByText, render, screen, waitFor, @@ -117,9 +118,13 @@ describe('Builder', () => { ); expect(mockNavigateFunction).toHaveBeenCalledWith('/app/dashboard'); - await waitFor(() => { - expect(screen.getByRole('alert')).toBeInTheDocument(); - }); + const notification = await screen.findByRole('alert'); + expect( + getByText( + notification, + /The resume you were looking for does not exist anymore/i, + ), + ).toBeInTheDocument(); await waitFor(() => expect( diff --git a/src/pages/app/__tests__/dashboard.test.js b/src/pages/app/__tests__/dashboard.test.js index 26b8bccc1..9a6b25231 100644 --- a/src/pages/app/__tests__/dashboard.test.js +++ b/src/pages/app/__tests__/dashboard.test.js @@ -1,6 +1,7 @@ import React from 'react'; import { fireEvent, + getByText, queryByText, render, screen, @@ -160,9 +161,13 @@ describe('Dashboard', () => { }); it('displays notification', async () => { - await waitFor(() => { - expect(screen.getByRole('alert')).toBeInTheDocument(); - }); + const notification = await screen.findByRole('alert'); + expect( + getByText( + notification, + new RegExp(`${resumeToDelete.name} was deleted successfully`, 'i'), + ), + ).toBeInTheDocument(); await waitForDatabaseRemoveToHaveCompleted(); }); From c01be98cfcc6e097f1ed814567696fad54c6732d Mon Sep 17 00:00:00 2001 From: gianantoniopini <63844628+gianantoniopini@users.noreply.github.com> Date: Sat, 23 Jan 2021 14:05:37 +0100 Subject: [PATCH 14/14] Builder: added unit test for resume in initial state --- src/pages/app/__tests__/builder.test.js | 18 ++++++++++++++++++ src/pages/app/__tests__/dashboard.test.js | 1 + 2 files changed, 19 insertions(+) diff --git a/src/pages/app/__tests__/builder.test.js b/src/pages/app/__tests__/builder.test.js index dea1dfc4d..f6ad40f8a 100644 --- a/src/pages/app/__tests__/builder.test.js +++ b/src/pages/app/__tests__/builder.test.js @@ -125,6 +125,7 @@ describe('Builder', () => { /The resume you were looking for does not exist anymore/i, ), ).toBeInTheDocument(); + fireEvent.click(notification); await waitFor(() => expect( @@ -238,4 +239,21 @@ describe('Builder', () => { await fnWaitForDatabaseUpdateToHaveCompleted(); }); }); + + describe('with resume in initial state', () => { + beforeEach(async () => { + await setup(DatabaseConstants.initialStateResumeId, false, false); + }); + + it('displays load demo data notification', async () => { + const notification = await screen.findByRole('alert'); + expect( + getByText( + notification, + /Not sure where to begin\? Try loading demo data/i, + ), + ).toBeInTheDocument(); + fireEvent.click(notification); + }); + }); }); diff --git a/src/pages/app/__tests__/dashboard.test.js b/src/pages/app/__tests__/dashboard.test.js index 9a6b25231..eede7d67a 100644 --- a/src/pages/app/__tests__/dashboard.test.js +++ b/src/pages/app/__tests__/dashboard.test.js @@ -168,6 +168,7 @@ describe('Dashboard', () => { new RegExp(`${resumeToDelete.name} was deleted successfully`, 'i'), ), ).toBeInTheDocument(); + fireEvent.click(notification); await waitForDatabaseRemoveToHaveCompleted(); });