From ef3f2a802f46ec9d64eefc7ca48aa4d53daea263 Mon Sep 17 00:00:00 2001 From: gianantoniopini <63844628+gianantoniopini@users.noreply.github.com> Date: Mon, 25 Jan 2021 15:15:02 +0100 Subject: [PATCH 01/18] Dashboard page: added first unit test for create resume interaction --- src/components/dashboard/CreateResume.js | 5 +++ src/pages/app/__tests__/dashboard.test.js | 41 +++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/src/components/dashboard/CreateResume.js b/src/components/dashboard/CreateResume.js index 0f15b601b..a1ec0b4ef 100644 --- a/src/components/dashboard/CreateResume.js +++ b/src/components/dashboard/CreateResume.js @@ -5,6 +5,8 @@ import ModalContext from '../../contexts/ModalContext'; import { handleKeyUp } from '../../utils'; import styles from './CreateResume.module.css'; +const createResumeButtonDataTestId = 'create-resume-button'; + const CreateResume = () => { const { t } = useTranslation(); const { emitter, events } = useContext(ModalContext); @@ -17,6 +19,7 @@ const CreateResume = () => {
{ }; export default memo(CreateResume); + +export { createResumeButtonDataTestId }; diff --git a/src/pages/app/__tests__/dashboard.test.js b/src/pages/app/__tests__/dashboard.test.js index eede7d67a..331f940b5 100644 --- a/src/pages/app/__tests__/dashboard.test.js +++ b/src/pages/app/__tests__/dashboard.test.js @@ -14,6 +14,7 @@ import FirebaseStub, { DatabaseConstants } from 'gatsby-plugin-firebase'; import '../../../i18n/index'; import '../../../utils/dayjs'; import { dataTestId as loadingScreenTestId } from '../../../components/router/LoadingScreen'; +import { createResumeButtonDataTestId } from '../../../components/dashboard/CreateResume'; import { menuToggleDataTestIdPrefix as resumePreviewMenuToggleDataTestIdPrefix } from '../../../components/dashboard/ResumePreview'; import { SettingsProvider } from '../../../contexts/SettingsContext'; import { ModalProvider } from '../../../contexts/ModalContext'; @@ -101,6 +102,46 @@ describe('Dashboard', () => { }); }); + describe('when resume is created', () => { + beforeEach(async () => { + await setup(); + + const dashboardCreateResumeButton = await screen.findByTestId( + createResumeButtonDataTestId, + ); + fireEvent.click(dashboardCreateResumeButton); + }); + + describe('with name shorter than 5 characters', () => { + it('displays validation error and notification', async () => { + const nameTextBox = screen.getByRole('textbox', { name: /name/i }); + fireEvent.change(nameTextBox, { target: { value: 'CV 1' } }); + + fireEvent.focusOut(nameTextBox); + + await waitFor(() => + expect( + screen.getByText(/Please enter at least 5 characters/i), + ).toBeInTheDocument(), + ); + + const modalCreateResumeButton = screen.getByRole('button', { + name: /create resume/i, + }); + fireEvent.click(modalCreateResumeButton); + + const notification = await screen.findByRole('alert'); + expect( + getByText( + notification, + /You might need to fill up all the required fields/i, + ), + ).toBeInTheDocument(); + fireEvent.click(notification); + }); + }); + }); + describe('when resume is deleted', () => { let mockDatabaseRemoveFunction = null; let resumeToDelete = null; From 9d486c11cc287575b8bba2c23846b8084e5d09aa Mon Sep 17 00:00:00 2001 From: gianantoniopini <63844628+gianantoniopini@users.noreply.github.com> Date: Mon, 25 Jan 2021 16:44:08 +0100 Subject: [PATCH 02/18] Dashboard page: added unit test related to create resume interaction --- jest.setup.js | 3 ++ package-lock.json | 10 ++++++ package.json | 1 + src/pages/app/__tests__/dashboard.test.js | 44 ++++++++++++++++++++++- 4 files changed, 57 insertions(+), 1 deletion(-) diff --git a/jest.setup.js b/jest.setup.js index 666127af3..91550372b 100644 --- a/jest.setup.js +++ b/jest.setup.js @@ -1 +1,4 @@ import '@testing-library/jest-dom/extend-expect'; +import fetchMock from 'jest-fetch-mock'; + +fetchMock.enableMocks(); diff --git a/package-lock.json b/package-lock.json index 480ac2e88..5bc6c4b15 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16432,6 +16432,16 @@ } } }, + "jest-fetch-mock": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz", + "integrity": "sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw==", + "dev": true, + "requires": { + "cross-fetch": "^3.0.4", + "promise-polyfill": "^8.1.3" + } + }, "jest-get-type": { "version": "25.2.6", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz", diff --git a/package.json b/package.json index bfc9d28c8..920f33f38 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,7 @@ "gatsby-plugin-eslint": "^2.0.8", "identity-obj-proxy": "^3.0.0", "jest": "^26.6.3", + "jest-fetch-mock": "^3.0.3", "prettier": "2.2.1", "stylelint": "^13.9.0", "stylelint-config-standard": "^20.0.0", diff --git a/src/pages/app/__tests__/dashboard.test.js b/src/pages/app/__tests__/dashboard.test.js index 331f940b5..d5eba5098 100644 --- a/src/pages/app/__tests__/dashboard.test.js +++ b/src/pages/app/__tests__/dashboard.test.js @@ -103,18 +103,32 @@ describe('Dashboard', () => { }); describe('when resume is created', () => { + let nameTextBox = null; + + const waitForModalToHaveBeenClosed = async () => { + await waitFor(() => + screen.queryByRole('textbox', { name: /name/i }) + ? Promise.reject() + : Promise.resolve(), + ); + }; + beforeEach(async () => { await setup(); + fetch.resetMocks(); + fetch.mockReturnValueOnce({ url: 'https://test-url-123456789.com' }); + const dashboardCreateResumeButton = await screen.findByTestId( createResumeButtonDataTestId, ); fireEvent.click(dashboardCreateResumeButton); + + nameTextBox = screen.getByRole('textbox', { name: /name/i }); }); describe('with name shorter than 5 characters', () => { it('displays validation error and notification', async () => { - const nameTextBox = screen.getByRole('textbox', { name: /name/i }); fireEvent.change(nameTextBox, { target: { value: 'CV 1' } }); fireEvent.focusOut(nameTextBox); @@ -140,6 +154,34 @@ describe('Dashboard', () => { fireEvent.click(notification); }); }); + + describe('with valid name', () => { + it('renders loading message', async () => { + fireEvent.change(nameTextBox, { + target: { value: 'Resume for SW development roles' }, + }); + + const modalCreateResumeButton = screen.getByRole('button', { + name: /create resume/i, + }); + fireEvent.click(modalCreateResumeButton); + + expect( + screen.getByRole('button', { + name: /loading/i, + }), + ).toBeInTheDocument(); + await waitFor(() => + expect( + screen.queryByRole('button', { + name: /loading/i, + }), + ).toBeNull(), + ); + + await waitForModalToHaveBeenClosed(); + }); + }); }); describe('when resume is deleted', () => { From 87fee2348031f3ed061833508eccfdb3b01c38f9 Mon Sep 17 00:00:00 2001 From: gianantoniopini <63844628+gianantoniopini@users.noreply.github.com> Date: Mon, 25 Jan 2021 17:30:05 +0100 Subject: [PATCH 03/18] Dashboard page: added unit test related to create resume interaction --- src/pages/app/__tests__/dashboard.test.js | 28 +++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/pages/app/__tests__/dashboard.test.js b/src/pages/app/__tests__/dashboard.test.js index d5eba5098..5e5e9ce3c 100644 --- a/src/pages/app/__tests__/dashboard.test.js +++ b/src/pages/app/__tests__/dashboard.test.js @@ -105,7 +105,7 @@ describe('Dashboard', () => { describe('when resume is created', () => { let nameTextBox = null; - const waitForModalToHaveBeenClosed = async () => { + const waitForModalWindowToHaveBeenClosed = async () => { await waitFor(() => screen.queryByRole('textbox', { name: /name/i }) ? Promise.reject() @@ -156,16 +156,20 @@ describe('Dashboard', () => { }); describe('with valid name', () => { - it('renders loading message', async () => { + const resumeName = 'Resume for SW development roles'; + + beforeEach(() => { fireEvent.change(nameTextBox, { - target: { value: 'Resume for SW development roles' }, + target: { value: resumeName }, }); const modalCreateResumeButton = screen.getByRole('button', { name: /create resume/i, }); fireEvent.click(modalCreateResumeButton); + }); + it('renders loading message', async () => { expect( screen.getByRole('button', { name: /loading/i, @@ -179,7 +183,23 @@ describe('Dashboard', () => { ).toBeNull(), ); - await waitForModalToHaveBeenClosed(); + await waitForModalWindowToHaveBeenClosed(); + await waitForResumeToBeRenderedInPreview({ name: resumeName }); + }); + + it('closes modal window', async () => { + await waitFor(() => + expect(screen.queryByRole('textbox', { name: /name/i })).toBeNull(), + ); + + await waitForResumeToBeRenderedInPreview({ name: resumeName }); + }); + + // eslint-disable-next-line jest/expect-expect + it('renders created resume in preview', async () => { + await waitForModalWindowToHaveBeenClosed(); + + await expectResumeToBeRenderedInPreview({ name: resumeName }); }); }); }); From b196ca1bf546b9d263b2a65d66aca15d383fb317 Mon Sep 17 00:00:00 2001 From: gianantoniopini <63844628+gianantoniopini@users.noreply.github.com> Date: Mon, 25 Jan 2021 17:54:36 +0100 Subject: [PATCH 04/18] Dashboard page: added unit test related to create resume interaction --- src/pages/app/__tests__/dashboard.test.js | 26 +++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/pages/app/__tests__/dashboard.test.js b/src/pages/app/__tests__/dashboard.test.js index 5e5e9ce3c..8a6974032 100644 --- a/src/pages/app/__tests__/dashboard.test.js +++ b/src/pages/app/__tests__/dashboard.test.js @@ -157,8 +157,11 @@ describe('Dashboard', () => { describe('with valid name', () => { const resumeName = 'Resume for SW development roles'; + let now = 0; beforeEach(() => { + now = new Date().getTime(); + fireEvent.change(nameTextBox, { target: { value: resumeName }, }); @@ -201,6 +204,29 @@ describe('Dashboard', () => { await expectResumeToBeRenderedInPreview({ name: resumeName }); }); + + it('adds resume in initial state to database', async () => { + await waitForModalWindowToHaveBeenClosed(); + await waitForResumeToBeRenderedInPreview({ name: resumeName }); + + const actualUserResumes = ( + await FirebaseStub.database() + .ref(DatabaseConstants.resumesPath) + .orderByChild('user') + .equalTo(user.uid) + .once('value') + ).val(); + expect(Object.values(actualUserResumes)).toHaveLength(3); + + const actualUserResumesFiltered = Object.values( + actualUserResumes, + ).filter((resume) => resume.name === resumeName); + expect(actualUserResumesFiltered).toHaveLength(1); + const createdResume = actualUserResumesFiltered[0]; + expect(createdResume.createdAt).toBeTruthy(); + expect(createdResume.createdAt).toBeGreaterThanOrEqual(now); + expect(createdResume.createdAt).toEqual(createdResume.updatedAt); + }); }); }); From e27bedcf889aecf06969832708d4dfc07388951b Mon Sep 17 00:00:00 2001 From: gianantoniopini <63844628+gianantoniopini@users.noreply.github.com> Date: Tue, 26 Jan 2021 13:19:49 +0100 Subject: [PATCH 05/18] Dashboard page: unit tests refactoring --- __mocks__/gatsby-plugin-firebase/auth/auth.js | 2 +- .../database/reference.js | 8 +--- src/pages/app/__tests__/dashboard.test.js | 44 +++++++++++++------ 3 files changed, 32 insertions(+), 22 deletions(-) diff --git a/__mocks__/gatsby-plugin-firebase/auth/auth.js b/__mocks__/gatsby-plugin-firebase/auth/auth.js index 853bb1334..b2fd1eb61 100644 --- a/__mocks__/gatsby-plugin-firebase/auth/auth.js +++ b/__mocks__/gatsby-plugin-firebase/auth/auth.js @@ -50,7 +50,7 @@ class Auth { this.onAuthStateChangedObservers.forEach((observer) => observer(user)); - return Promise.resolve(user); + return user; } } diff --git a/__mocks__/gatsby-plugin-firebase/database/reference.js b/__mocks__/gatsby-plugin-firebase/database/reference.js index 08e345fdb..aca79397d 100644 --- a/__mocks__/gatsby-plugin-firebase/database/reference.js +++ b/__mocks__/gatsby-plugin-firebase/database/reference.js @@ -185,7 +185,7 @@ class Reference { await delay(DatabaseConstants.defaultDelayInMilliseconds); - return Promise.resolve(this._dataSnapshot); + return this._dataSnapshot; } orderByChild(path) { @@ -197,24 +197,18 @@ class Reference { await delay(DatabaseConstants.defaultDelayInMilliseconds); this._handleDataUpdate(value); - - return Promise.resolve(); } async remove() { await delay(DatabaseConstants.defaultDelayInMilliseconds); this._handleDataUpdate(null); - - return Promise.resolve(); } async set(value) { await delay(DatabaseConstants.defaultDelayInMilliseconds); this._handleDataUpdate(value); - - return Promise.resolve(); } } diff --git a/src/pages/app/__tests__/dashboard.test.js b/src/pages/app/__tests__/dashboard.test.js index 8a6974032..b5efa2c49 100644 --- a/src/pages/app/__tests__/dashboard.test.js +++ b/src/pages/app/__tests__/dashboard.test.js @@ -29,13 +29,13 @@ describe('Dashboard', () => { let userResumes = null; const user = DatabaseConstants.user1; - const waitForResumeToBeRenderedInPreview = async (resume) => { - await screen.findByText(resume.name); + const waitForResumeToBeRenderedInPreview = async (resumeName) => { + await screen.findByText(resumeName); }; - const expectResumeToBeRenderedInPreview = async (resume) => { + const expectResumeToBeRenderedInPreview = async (resumeName) => { await waitFor(() => { - expect(screen.getByText(resume.name)).toBeInTheDocument(); + expect(screen.getByText(resumeName)).toBeInTheDocument(); }); }; @@ -92,13 +92,22 @@ describe('Dashboard', () => { await waitFor(() => { expect(screen.getByText(/create resume/i)).toBeInTheDocument(); }); + await waitFor(() => { + expect( + screen.getByTestId(createResumeButtonDataTestId), + ).toBeInTheDocument(); + }); }); it('preview of user resumes', async () => { expect(Object.keys(userResumes)).toHaveLength(2); - await expectResumeToBeRenderedInPreview(Object.values(userResumes)[0]); - await expectResumeToBeRenderedInPreview(Object.values(userResumes)[1]); + await expectResumeToBeRenderedInPreview( + Object.values(userResumes)[0].name, + ); + await expectResumeToBeRenderedInPreview( + Object.values(userResumes)[1].name, + ); }); }); @@ -187,7 +196,7 @@ describe('Dashboard', () => { ); await waitForModalWindowToHaveBeenClosed(); - await waitForResumeToBeRenderedInPreview({ name: resumeName }); + await waitForResumeToBeRenderedInPreview(resumeName); }); it('closes modal window', async () => { @@ -195,19 +204,22 @@ describe('Dashboard', () => { expect(screen.queryByRole('textbox', { name: /name/i })).toBeNull(), ); - await waitForResumeToBeRenderedInPreview({ name: resumeName }); + await waitForResumeToBeRenderedInPreview(resumeName); }); - // eslint-disable-next-line jest/expect-expect it('renders created resume in preview', async () => { await waitForModalWindowToHaveBeenClosed(); - await expectResumeToBeRenderedInPreview({ name: resumeName }); + await waitFor(() => + expect( + expectResumeToBeRenderedInPreview(resumeName), + ).resolves.toBeUndefined(), + ); }); it('adds resume in initial state to database', async () => { await waitForModalWindowToHaveBeenClosed(); - await waitForResumeToBeRenderedInPreview({ name: resumeName }); + await waitForResumeToBeRenderedInPreview(resumeName); const actualUserResumes = ( await FirebaseStub.database() @@ -286,7 +298,7 @@ describe('Dashboard', () => { await waitFor(() => expect(screen.queryByText(resumeToDelete.name)).toBeNull(), ); - await expectResumeToBeRenderedInPreview(undeletedResume); + await expectResumeToBeRenderedInPreview(undeletedResume.name); }); it('displays notification', async () => { @@ -322,8 +334,12 @@ describe('Dashboard', () => { screen.getByTestId(loadingScreenTestId), ); - await waitForResumeToBeRenderedInPreview(Object.values(userResumes)[0]); - await waitForResumeToBeRenderedInPreview(Object.values(userResumes)[1]); + await waitForResumeToBeRenderedInPreview( + Object.values(userResumes)[0].name, + ); + await waitForResumeToBeRenderedInPreview( + Object.values(userResumes)[1].name, + ); }); }); }); From 8018d7f52778895741de38ae621bf43f5ecabe10 Mon Sep 17 00:00:00 2001 From: gianantoniopini <63844628+gianantoniopini@users.noreply.github.com> Date: Tue, 26 Jan 2021 14:22:27 +0100 Subject: [PATCH 06/18] Dashboard page: refactored setup of fetch mock, added more assertions to unit test --- __mocks__/gatsby-plugin-firebase/auth/auth.js | 2 +- .../database/reference.js | 2 +- __mocks__/gatsby.js | 2 +- __mocks__/utils/index.js | 5 ---- src/pages/app/__tests__/dashboard.test.js | 26 ++++++++++++++++--- src/utils/index.js | 9 ++++++- 6 files changed, 34 insertions(+), 12 deletions(-) delete mode 100644 __mocks__/utils/index.js diff --git a/__mocks__/gatsby-plugin-firebase/auth/auth.js b/__mocks__/gatsby-plugin-firebase/auth/auth.js index b2fd1eb61..116ea452c 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 '../../../src/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 aca79397d..629bb12e3 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 '../../../src/utils/index'; const parsePath = (path) => { if (!path) { diff --git a/__mocks__/gatsby.js b/__mocks__/gatsby.js index 09ea71c73..bac68ffb9 100644 --- a/__mocks__/gatsby.js +++ b/__mocks__/gatsby.js @@ -1,6 +1,6 @@ import React from 'react'; -import delay from './utils/index'; +import { delay } from '../src/utils/index'; const Gatsby = jest.requireActual('gatsby'); diff --git a/__mocks__/utils/index.js b/__mocks__/utils/index.js deleted file mode 100644 index 0b290076e..000000000 --- a/__mocks__/utils/index.js +++ /dev/null @@ -1,5 +0,0 @@ -const delay = async (milliseconds) => { - await new Promise((resolve) => setTimeout(resolve, milliseconds)); -}; - -export default delay; diff --git a/src/pages/app/__tests__/dashboard.test.js b/src/pages/app/__tests__/dashboard.test.js index b5efa2c49..b8c0171ee 100644 --- a/src/pages/app/__tests__/dashboard.test.js +++ b/src/pages/app/__tests__/dashboard.test.js @@ -13,6 +13,7 @@ import FirebaseStub, { DatabaseConstants } from 'gatsby-plugin-firebase'; import '../../../i18n/index'; import '../../../utils/dayjs'; +import { unsplashPhotoRequestUrl, delay } from '../../../utils/index'; import { dataTestId as loadingScreenTestId } from '../../../components/router/LoadingScreen'; import { createResumeButtonDataTestId } from '../../../components/dashboard/CreateResume'; import { menuToggleDataTestIdPrefix as resumePreviewMenuToggleDataTestIdPrefix } from '../../../components/dashboard/ResumePreview'; @@ -112,8 +113,25 @@ describe('Dashboard', () => { }); describe('when resume is created', () => { + const unsplashPhotoResponseUrl = 'https://test-url-123456789.com'; let nameTextBox = null; + const setupFetchMock = () => { + fetch.resetMocks(); + + fetch.mockImplementationOnce(async (input) => { + await delay(100); + + if (input === unsplashPhotoRequestUrl) { + return { + url: unsplashPhotoResponseUrl, + }; + } + + throw new Error('Unsupported input.'); + }); + }; + const waitForModalWindowToHaveBeenClosed = async () => { await waitFor(() => screen.queryByRole('textbox', { name: /name/i }) @@ -123,10 +141,9 @@ describe('Dashboard', () => { }; beforeEach(async () => { - await setup(); + setupFetchMock(); - fetch.resetMocks(); - fetch.mockReturnValueOnce({ url: 'https://test-url-123456789.com' }); + await setup(); const dashboardCreateResumeButton = await screen.findByTestId( createResumeButtonDataTestId, @@ -235,6 +252,9 @@ describe('Dashboard', () => { ).filter((resume) => resume.name === resumeName); expect(actualUserResumesFiltered).toHaveLength(1); const createdResume = actualUserResumesFiltered[0]; + expect(createdResume.id).toBeTruthy(); + expect(createdResume.preview).toBeTruthy(); + expect(createdResume.preview).toEqual(unsplashPhotoResponseUrl); expect(createdResume.createdAt).toBeTruthy(); expect(createdResume.createdAt).toBeGreaterThanOrEqual(now); expect(createdResume.createdAt).toEqual(createdResume.updatedAt); diff --git a/src/utils/index.js b/src/utils/index.js index 4acdb57ca..e6ee56817 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -54,8 +54,11 @@ export const getFieldProps = (formik, schema, name) => ({ ...formik.getFieldProps(name), }); +export const unsplashPhotoRequestUrl = + 'https://source.unsplash.com/featured/400x600'; + export const getUnsplashPhoto = async () => { - const response = await fetch('https://source.unsplash.com/featured/400x600'); + const response = await fetch(unsplashPhotoRequestUrl); return response.url; }; @@ -121,3 +124,7 @@ export const b64toBlob = (b64Data, contentType = '', sliceSize = 512) => { const blob = new Blob(byteArrays, { type: contentType }); return blob; }; + +export const delay = async (milliseconds) => { + await new Promise((resolve) => setTimeout(resolve, milliseconds)); +}; From a20fe10c062b0675c633262b6a274395c7ffabb3 Mon Sep 17 00:00:00 2001 From: gianantoniopini <63844628+gianantoniopini@users.noreply.github.com> Date: Tue, 26 Jan 2021 17:38:05 +0100 Subject: [PATCH 07/18] Dashboard page: added unit test for delete resume interaction --- src/pages/app/__tests__/dashboard.test.js | 119 +++++++++++++++++++--- 1 file changed, 107 insertions(+), 12 deletions(-) diff --git a/src/pages/app/__tests__/dashboard.test.js b/src/pages/app/__tests__/dashboard.test.js index b8c0171ee..dadb81cda 100644 --- a/src/pages/app/__tests__/dashboard.test.js +++ b/src/pages/app/__tests__/dashboard.test.js @@ -9,6 +9,8 @@ import { waitForElementToBeRemoved, } from '@testing-library/react'; +import fetchMock from 'jest-fetch-mock'; + import FirebaseStub, { DatabaseConstants } from 'gatsby-plugin-firebase'; import '../../../i18n/index'; @@ -40,6 +42,18 @@ describe('Dashboard', () => { }); }; + const waitForModalWindowToHaveBeenClosed = async () => { + await waitFor(() => + screen.queryByRole('textbox', { name: /name/i }) + ? Promise.reject() + : Promise.resolve(), + ); + }; + + const dismissNotification = (notification) => { + fireEvent.click(notification); + }; + async function setup(waitForLoadingScreenToDisappear = true) { FirebaseStub.database().initializeData(); @@ -117,9 +131,9 @@ describe('Dashboard', () => { let nameTextBox = null; const setupFetchMock = () => { - fetch.resetMocks(); + fetchMock.resetMocks(); - fetch.mockImplementationOnce(async (input) => { + fetchMock.mockImplementationOnce(async (input) => { await delay(100); if (input === unsplashPhotoRequestUrl) { @@ -132,14 +146,6 @@ describe('Dashboard', () => { }); }; - const waitForModalWindowToHaveBeenClosed = async () => { - await waitFor(() => - screen.queryByRole('textbox', { name: /name/i }) - ? Promise.reject() - : Promise.resolve(), - ); - }; - beforeEach(async () => { setupFetchMock(); @@ -177,7 +183,7 @@ describe('Dashboard', () => { /You might need to fill up all the required fields/i, ), ).toBeInTheDocument(); - fireEvent.click(notification); + dismissNotification(notification); }); }); @@ -329,7 +335,7 @@ describe('Dashboard', () => { new RegExp(`${resumeToDelete.name} was deleted successfully`, 'i'), ), ).toBeInTheDocument(); - fireEvent.click(notification); + dismissNotification(notification); await waitForDatabaseRemoveToHaveCompleted(); }); @@ -342,6 +348,95 @@ describe('Dashboard', () => { }); }); + describe('when resume is renamed', () => { + // const mockDatabaseUpdateFunction = null; + let resumeToRename = null; + let resumeToRenameId = null; + let nameTextBox = null; + + /* + const waitForDatabaseUpdateToHaveCompleted = async () => { + await waitFor(() => mockDatabaseUpdateFunction.mock.calls[0][0]); + await waitFor(() => mockDatabaseUpdateFunction.mock.results[0].value); + }; + + const expectDatabaseUpdateToHaveCompleted = async () => { + await waitFor(() => + expect(mockDatabaseUpdateFunction).toHaveBeenCalledTimes(1), + ); + await waitFor(() => + expect( + mockDatabaseUpdateFunction.mock.results[0].value, + ).resolves.toBeUndefined(), + ); + }; + */ + + beforeEach(async () => { + await setup(); + + [resumeToRename] = Object.values(userResumes); + resumeToRenameId = resumeToRename.id; + + /* + mockDatabaseUpdateFunction = jest.spyOn( + FirebaseStub.database().ref( + `${DatabaseConstants.resumesPath}/${resumeToRenameId}`, + ), + 'update', + ); + */ + + const resumeToRenameMenuToggle = await screen.findByTestId( + `${resumePreviewMenuToggleDataTestIdPrefix}${resumeToRenameId}`, + ); + fireEvent.click(resumeToRenameMenuToggle); + + const menuItems = screen.getAllByRole('menuitem'); + let renameMenuItem = null; + for (let index = 0; index < menuItems.length; index++) { + if (queryByText(menuItems[index], /rename/i)) { + renameMenuItem = menuItems[index]; + break; + } + } + fireEvent.click(renameMenuItem); + + nameTextBox = screen.getByRole('textbox', { name: /name/i }); + }); + + describe('with name shorter than 5 characters', () => { + it('displays validation error and notification', async () => { + fireEvent.change(nameTextBox, { target: { value: 'CV 2' } }); + + fireEvent.focusOut(nameTextBox); + + await waitFor(() => + expect( + screen.getByText(/Please enter at least 5 characters/i), + ).toBeInTheDocument(), + ); + + const modalEditResumeButton = screen.getByRole('button', { + name: /edit resume/i, + }); + fireEvent.click(modalEditResumeButton); + + // const notification = await screen.findByRole('alert'); + const notification = await screen.findByText( + /You might need to fill up all the required fields/i, + ); + expect( + getByText( + notification, + /You might need to fill up all the required fields/i, + ), + ).toBeInTheDocument(); + dismissNotification(notification); + }); + }); + }); + describe('while loading', () => { beforeEach(async () => { await setup(false); From 5d9527e46ef6192f2fddd51b5ba0e129a87953fa Mon Sep 17 00:00:00 2001 From: gianantoniopini <63844628+gianantoniopini@users.noreply.github.com> Date: Wed, 27 Jan 2021 10:35:58 +0100 Subject: [PATCH 08/18] Dashboard page: unit tests refactoring --- src/pages/app/__tests__/dashboard.test.js | 70 +++++++++++++++-------- 1 file changed, 47 insertions(+), 23 deletions(-) diff --git a/src/pages/app/__tests__/dashboard.test.js b/src/pages/app/__tests__/dashboard.test.js index dadb81cda..c1f21cd68 100644 --- a/src/pages/app/__tests__/dashboard.test.js +++ b/src/pages/app/__tests__/dashboard.test.js @@ -43,10 +43,8 @@ describe('Dashboard', () => { }; const waitForModalWindowToHaveBeenClosed = async () => { - await waitFor(() => - screen.queryByRole('textbox', { name: /name/i }) - ? Promise.reject() - : Promise.resolve(), + await waitForElementToBeRemoved(() => + screen.getByRole('textbox', { name: /name/i }), ); }; @@ -54,6 +52,17 @@ describe('Dashboard', () => { fireEvent.click(notification); }; + const findAndDismissNotification = async () => { + const notification = await screen.findByRole('alert'); + dismissNotification(notification); + }; + + const fnWaitForLoadingScreenToDisappear = async () => { + await waitForElementToBeRemoved(() => + screen.getByTestId(loadingScreenTestId), + ); + }; + async function setup(waitForLoadingScreenToDisappear = true) { FirebaseStub.database().initializeData(); @@ -86,9 +95,7 @@ describe('Dashboard', () => { ); if (waitForLoadingScreenToDisappear) { - await waitForElementToBeRemoved(() => - screen.getByTestId(loadingScreenTestId), - ); + await fnWaitForLoadingScreenToDisappear(); } } @@ -205,17 +212,17 @@ describe('Dashboard', () => { }); it('renders loading message', async () => { - expect( + await waitFor(() => + expect( + screen.getByRole('button', { + name: /loading/i, + }), + ).toBeInTheDocument(), + ); + await waitForElementToBeRemoved(() => screen.getByRole('button', { name: /loading/i, }), - ).toBeInTheDocument(); - await waitFor(() => - expect( - screen.queryByRole('button', { - name: /loading/i, - }), - ).toBeNull(), ); await waitForModalWindowToHaveBeenClosed(); @@ -224,7 +231,7 @@ describe('Dashboard', () => { it('closes modal window', async () => { await waitFor(() => - expect(screen.queryByRole('textbox', { name: /name/i })).toBeNull(), + expect(waitForModalWindowToHaveBeenClosed()).resolves.toBeUndefined(), ); await waitForResumeToBeRenderedInPreview(resumeName); @@ -289,11 +296,22 @@ describe('Dashboard', () => { ); }; + const waitForResumeToDisappearFromPreview = async (resumeName) => { + await waitFor(() => + screen.queryByText(resumeName) ? Promise.reject() : Promise.resolve(), + ); + }; + beforeEach(async () => { await setup(); - [resumeToDelete, undeletedResume] = Object.values(userResumes); + [resumeToDelete] = Object.values(userResumes).filter( + (resume) => resume.id === DatabaseConstants.demoStateResume1Id, + ); resumeToDeleteId = resumeToDelete.id; + [undeletedResume] = Object.values(userResumes).filter( + (resume) => resume.id === DatabaseConstants.initialStateResumeId, + ); mockDatabaseRemoveFunction = jest.spyOn( FirebaseStub.database().ref( @@ -319,10 +337,14 @@ describe('Dashboard', () => { }); it('removes it from database and preview', async () => { + await findAndDismissNotification(); + await expectDatabaseRemoveToHaveCompleted(); await waitFor(() => - expect(screen.queryByText(resumeToDelete.name)).toBeNull(), + expect( + waitForResumeToDisappearFromPreview(resumeToDelete.name), + ).resolves.toBeUndefined(), ); await expectResumeToBeRenderedInPreview(undeletedResume.name); }); @@ -338,13 +360,16 @@ describe('Dashboard', () => { dismissNotification(notification); await waitForDatabaseRemoveToHaveCompleted(); + await waitForResumeToDisappearFromPreview(resumeToDelete.name); }); it('closes menu', async () => { const menuItems = screen.queryAllByRole('menuitem'); expect(menuItems).toHaveLength(0); + await findAndDismissNotification(); await waitForDatabaseRemoveToHaveCompleted(); + await waitForResumeToDisappearFromPreview(resumeToDelete.name); }); }); @@ -375,7 +400,9 @@ describe('Dashboard', () => { beforeEach(async () => { await setup(); - [resumeToRename] = Object.values(userResumes); + [resumeToRename] = Object.values(userResumes).filter( + (resume) => resume.id === DatabaseConstants.demoStateResume1Id, + ); resumeToRenameId = resumeToRename.id; /* @@ -444,10 +471,7 @@ describe('Dashboard', () => { it('renders loading screen', async () => { expect(screen.getByTestId(loadingScreenTestId)).toBeInTheDocument(); - - await waitForElementToBeRemoved(() => - screen.getByTestId(loadingScreenTestId), - ); + await fnWaitForLoadingScreenToDisappear(); await waitForResumeToBeRenderedInPreview( Object.values(userResumes)[0].name, From da8bf8bc365592b9b6218fe5ce624a163e3000be Mon Sep 17 00:00:00 2001 From: gianantoniopini <63844628+gianantoniopini@users.noreply.github.com> Date: Wed, 27 Jan 2021 11:33:11 +0100 Subject: [PATCH 09/18] Dashboard page: added more unit tests for rename resume interaction --- src/pages/app/__tests__/builder.test.js | 3 +- src/pages/app/__tests__/dashboard.test.js | 86 +++++++++++++++++++++-- 2 files changed, 83 insertions(+), 6 deletions(-) diff --git a/src/pages/app/__tests__/builder.test.js b/src/pages/app/__tests__/builder.test.js index f6ad40f8a..e22d6e9d7 100644 --- a/src/pages/app/__tests__/builder.test.js +++ b/src/pages/app/__tests__/builder.test.js @@ -102,8 +102,9 @@ describe('Builder', () => { if (waitForDatabaseUpdateToHaveCompleted) { await fnWaitForDatabaseUpdateToHaveCompleted(); - mockDatabaseUpdateFunction.mockClear(); } + + mockDatabaseUpdateFunction.mockClear(); } describe('handles errors', () => { diff --git a/src/pages/app/__tests__/dashboard.test.js b/src/pages/app/__tests__/dashboard.test.js index c1f21cd68..421b38096 100644 --- a/src/pages/app/__tests__/dashboard.test.js +++ b/src/pages/app/__tests__/dashboard.test.js @@ -374,12 +374,11 @@ describe('Dashboard', () => { }); describe('when resume is renamed', () => { - // const mockDatabaseUpdateFunction = null; + let mockDatabaseUpdateFunction = null; let resumeToRename = null; let resumeToRenameId = null; let nameTextBox = null; - /* const waitForDatabaseUpdateToHaveCompleted = async () => { await waitFor(() => mockDatabaseUpdateFunction.mock.calls[0][0]); await waitFor(() => mockDatabaseUpdateFunction.mock.results[0].value); @@ -395,7 +394,6 @@ describe('Dashboard', () => { ).resolves.toBeUndefined(), ); }; - */ beforeEach(async () => { await setup(); @@ -405,14 +403,13 @@ describe('Dashboard', () => { ); resumeToRenameId = resumeToRename.id; - /* mockDatabaseUpdateFunction = jest.spyOn( FirebaseStub.database().ref( `${DatabaseConstants.resumesPath}/${resumeToRenameId}`, ), 'update', ); - */ + mockDatabaseUpdateFunction.mockClear(); const resumeToRenameMenuToggle = await screen.findByTestId( `${resumePreviewMenuToggleDataTestIdPrefix}${resumeToRenameId}`, @@ -432,6 +429,10 @@ describe('Dashboard', () => { nameTextBox = screen.getByRole('textbox', { name: /name/i }); }); + it('renders current name in modal window', async () => { + expect(nameTextBox).toHaveValue(resumeToRename.name); + }); + describe('with name shorter than 5 characters', () => { it('displays validation error and notification', async () => { fireEvent.change(nameTextBox, { target: { value: 'CV 2' } }); @@ -462,6 +463,81 @@ describe('Dashboard', () => { dismissNotification(notification); }); }); + + describe('with valid name', () => { + let resumeNewName = null; + let now = 0; + + beforeEach(() => { + resumeNewName = `${resumeToRename.name} - renamed`; + now = new Date().getTime(); + + fireEvent.change(nameTextBox, { + target: { value: resumeNewName }, + }); + + const modalEditResumeButton = screen.getByRole('button', { + name: /edit resume/i, + }); + fireEvent.click(modalEditResumeButton); + }); + + it('renders loading message', async () => { + await waitFor(() => + expect( + screen.getByRole('button', { + name: /loading/i, + }), + ).toBeInTheDocument(), + ); + await waitForElementToBeRemoved(() => + screen.getByRole('button', { + name: /loading/i, + }), + ); + + await waitForModalWindowToHaveBeenClosed(); + await waitForDatabaseUpdateToHaveCompleted(); + await waitForResumeToBeRenderedInPreview(resumeNewName); + }); + + it('closes modal window', async () => { + await waitFor(() => + expect(waitForModalWindowToHaveBeenClosed()).resolves.toBeUndefined(), + ); + + await waitForDatabaseUpdateToHaveCompleted(); + await waitForResumeToBeRenderedInPreview(resumeNewName); + }); + + it('renders renamed resume in preview', async () => { + await waitForModalWindowToHaveBeenClosed(); + await waitForDatabaseUpdateToHaveCompleted(); + + await waitFor(() => + expect( + expectResumeToBeRenderedInPreview(resumeNewName), + ).resolves.toBeUndefined(), + ); + }); + + it('updates database', async () => { + await waitForModalWindowToHaveBeenClosed(); + + await expectDatabaseUpdateToHaveCompleted(); + const mockDatabaseUpdateFunctionCallArgument = + mockDatabaseUpdateFunction.mock.calls[0][0]; + expect(mockDatabaseUpdateFunctionCallArgument.id).toBe( + resumeToRenameId, + ); + expect(mockDatabaseUpdateFunctionCallArgument.name).toBe(resumeNewName); + expect( + mockDatabaseUpdateFunctionCallArgument.updatedAt, + ).toBeGreaterThanOrEqual(now); + + await waitForResumeToBeRenderedInPreview(resumeNewName); + }); + }); }); describe('while loading', () => { From c748e6a0b49b623427acde804005d67fc55658c3 Mon Sep 17 00:00:00 2001 From: gianantoniopini <63844628+gianantoniopini@users.noreply.github.com> Date: Mon, 1 Feb 2021 10:49:02 +0100 Subject: [PATCH 10/18] Dashboard page: removed unit tests related to rename resume interaction (they are generating warnings) --- src/pages/app/__tests__/dashboard.test.js | 169 +--------------------- 1 file changed, 1 insertion(+), 168 deletions(-) diff --git a/src/pages/app/__tests__/dashboard.test.js b/src/pages/app/__tests__/dashboard.test.js index 421b38096..a9bea6c5b 100644 --- a/src/pages/app/__tests__/dashboard.test.js +++ b/src/pages/app/__tests__/dashboard.test.js @@ -170,7 +170,7 @@ describe('Dashboard', () => { it('displays validation error and notification', async () => { fireEvent.change(nameTextBox, { target: { value: 'CV 1' } }); - fireEvent.focusOut(nameTextBox); + fireEvent.blur(nameTextBox); await waitFor(() => expect( @@ -373,173 +373,6 @@ describe('Dashboard', () => { }); }); - describe('when resume is renamed', () => { - let mockDatabaseUpdateFunction = null; - let resumeToRename = null; - let resumeToRenameId = null; - let nameTextBox = null; - - const waitForDatabaseUpdateToHaveCompleted = async () => { - await waitFor(() => mockDatabaseUpdateFunction.mock.calls[0][0]); - await waitFor(() => mockDatabaseUpdateFunction.mock.results[0].value); - }; - - const expectDatabaseUpdateToHaveCompleted = async () => { - await waitFor(() => - expect(mockDatabaseUpdateFunction).toHaveBeenCalledTimes(1), - ); - await waitFor(() => - expect( - mockDatabaseUpdateFunction.mock.results[0].value, - ).resolves.toBeUndefined(), - ); - }; - - beforeEach(async () => { - await setup(); - - [resumeToRename] = Object.values(userResumes).filter( - (resume) => resume.id === DatabaseConstants.demoStateResume1Id, - ); - resumeToRenameId = resumeToRename.id; - - mockDatabaseUpdateFunction = jest.spyOn( - FirebaseStub.database().ref( - `${DatabaseConstants.resumesPath}/${resumeToRenameId}`, - ), - 'update', - ); - mockDatabaseUpdateFunction.mockClear(); - - const resumeToRenameMenuToggle = await screen.findByTestId( - `${resumePreviewMenuToggleDataTestIdPrefix}${resumeToRenameId}`, - ); - fireEvent.click(resumeToRenameMenuToggle); - - const menuItems = screen.getAllByRole('menuitem'); - let renameMenuItem = null; - for (let index = 0; index < menuItems.length; index++) { - if (queryByText(menuItems[index], /rename/i)) { - renameMenuItem = menuItems[index]; - break; - } - } - fireEvent.click(renameMenuItem); - - nameTextBox = screen.getByRole('textbox', { name: /name/i }); - }); - - it('renders current name in modal window', async () => { - expect(nameTextBox).toHaveValue(resumeToRename.name); - }); - - describe('with name shorter than 5 characters', () => { - it('displays validation error and notification', async () => { - fireEvent.change(nameTextBox, { target: { value: 'CV 2' } }); - - fireEvent.focusOut(nameTextBox); - - await waitFor(() => - expect( - screen.getByText(/Please enter at least 5 characters/i), - ).toBeInTheDocument(), - ); - - const modalEditResumeButton = screen.getByRole('button', { - name: /edit resume/i, - }); - fireEvent.click(modalEditResumeButton); - - // const notification = await screen.findByRole('alert'); - const notification = await screen.findByText( - /You might need to fill up all the required fields/i, - ); - expect( - getByText( - notification, - /You might need to fill up all the required fields/i, - ), - ).toBeInTheDocument(); - dismissNotification(notification); - }); - }); - - describe('with valid name', () => { - let resumeNewName = null; - let now = 0; - - beforeEach(() => { - resumeNewName = `${resumeToRename.name} - renamed`; - now = new Date().getTime(); - - fireEvent.change(nameTextBox, { - target: { value: resumeNewName }, - }); - - const modalEditResumeButton = screen.getByRole('button', { - name: /edit resume/i, - }); - fireEvent.click(modalEditResumeButton); - }); - - it('renders loading message', async () => { - await waitFor(() => - expect( - screen.getByRole('button', { - name: /loading/i, - }), - ).toBeInTheDocument(), - ); - await waitForElementToBeRemoved(() => - screen.getByRole('button', { - name: /loading/i, - }), - ); - - await waitForModalWindowToHaveBeenClosed(); - await waitForDatabaseUpdateToHaveCompleted(); - await waitForResumeToBeRenderedInPreview(resumeNewName); - }); - - it('closes modal window', async () => { - await waitFor(() => - expect(waitForModalWindowToHaveBeenClosed()).resolves.toBeUndefined(), - ); - - await waitForDatabaseUpdateToHaveCompleted(); - await waitForResumeToBeRenderedInPreview(resumeNewName); - }); - - it('renders renamed resume in preview', async () => { - await waitForModalWindowToHaveBeenClosed(); - await waitForDatabaseUpdateToHaveCompleted(); - - await waitFor(() => - expect( - expectResumeToBeRenderedInPreview(resumeNewName), - ).resolves.toBeUndefined(), - ); - }); - - it('updates database', async () => { - await waitForModalWindowToHaveBeenClosed(); - - await expectDatabaseUpdateToHaveCompleted(); - const mockDatabaseUpdateFunctionCallArgument = - mockDatabaseUpdateFunction.mock.calls[0][0]; - expect(mockDatabaseUpdateFunctionCallArgument.id).toBe( - resumeToRenameId, - ); - expect(mockDatabaseUpdateFunctionCallArgument.name).toBe(resumeNewName); - expect( - mockDatabaseUpdateFunctionCallArgument.updatedAt, - ).toBeGreaterThanOrEqual(now); - - await waitForResumeToBeRenderedInPreview(resumeNewName); - }); - }); - }); - describe('while loading', () => { beforeEach(async () => { await setup(false); From 5ee077dac5e71855b2952d28a6b6eb57f96615e0 Mon Sep 17 00:00:00 2001 From: gianantoniopini <63844628+gianantoniopini@users.noreply.github.com> Date: Mon, 1 Feb 2021 12:10:15 +0100 Subject: [PATCH 11/18] Dashboard page: added unit tests related to duplicate resume interaction --- src/pages/app/__tests__/dashboard.test.js | 138 ++++++++++++++++++---- 1 file changed, 114 insertions(+), 24 deletions(-) diff --git a/src/pages/app/__tests__/dashboard.test.js b/src/pages/app/__tests__/dashboard.test.js index a9bea6c5b..7b0da3b6f 100644 --- a/src/pages/app/__tests__/dashboard.test.js +++ b/src/pages/app/__tests__/dashboard.test.js @@ -31,6 +31,7 @@ import Dashboard from '../dashboard'; describe('Dashboard', () => { let userResumes = null; const user = DatabaseConstants.user1; + const unsplashPhotoResponseUrl = 'https://test-url-123456789.com'; const waitForResumeToBeRenderedInPreview = async (resumeName) => { await screen.findByText(resumeName); @@ -57,13 +58,36 @@ describe('Dashboard', () => { dismissNotification(notification); }; - const fnWaitForLoadingScreenToDisappear = async () => { + const waitForLoadingScreenToDisappearFn = async () => { await waitForElementToBeRemoved(() => screen.getByTestId(loadingScreenTestId), ); }; - async function setup(waitForLoadingScreenToDisappear = true) { + const setupFetchMockFn = () => { + fetchMock.resetMocks(); + + fetchMock.mockImplementationOnce(async (input) => { + await delay(100); + + if (input === unsplashPhotoRequestUrl) { + return { + url: unsplashPhotoResponseUrl, + }; + } + + throw new Error('Unsupported input.'); + }); + }; + + async function setup( + waitForLoadingScreenToDisappear = true, + setupFetchMock = false, + ) { + if (setupFetchMock) { + setupFetchMockFn(); + } + FirebaseStub.database().initializeData(); userResumes = ( @@ -95,7 +119,7 @@ describe('Dashboard', () => { ); if (waitForLoadingScreenToDisappear) { - await fnWaitForLoadingScreenToDisappear(); + await waitForLoadingScreenToDisappearFn(); } } @@ -134,29 +158,10 @@ describe('Dashboard', () => { }); describe('when resume is created', () => { - const unsplashPhotoResponseUrl = 'https://test-url-123456789.com'; let nameTextBox = null; - const setupFetchMock = () => { - fetchMock.resetMocks(); - - fetchMock.mockImplementationOnce(async (input) => { - await delay(100); - - if (input === unsplashPhotoRequestUrl) { - return { - url: unsplashPhotoResponseUrl, - }; - } - - throw new Error('Unsupported input.'); - }); - }; - beforeEach(async () => { - setupFetchMock(); - - await setup(); + await setup(true, true); const dashboardCreateResumeButton = await screen.findByTestId( createResumeButtonDataTestId, @@ -373,6 +378,91 @@ describe('Dashboard', () => { }); }); + describe('when resume is duplicated', () => { + let resumeToDuplicate = null; + let resumeToDuplicateId = null; + let duplicateResumeName = null; + let now = 0; + + beforeEach(async () => { + await setup(true, true); + + now = new Date().getTime(); + + [resumeToDuplicate] = Object.values(userResumes).filter( + (resume) => resume.id === DatabaseConstants.demoStateResume1Id, + ); + resumeToDuplicateId = resumeToDuplicate.id; + duplicateResumeName = `${resumeToDuplicate.name} Copy`; + + const resumeToDuplicateMenuToggle = await screen.findByTestId( + `${resumePreviewMenuToggleDataTestIdPrefix}${resumeToDuplicateId}`, + ); + fireEvent.click(resumeToDuplicateMenuToggle); + + const menuItems = screen.getAllByRole('menuitem'); + let duplicateMenuItem = null; + for (let index = 0; index < menuItems.length; index++) { + if (queryByText(menuItems[index], /duplicate/i)) { + duplicateMenuItem = menuItems[index]; + break; + } + } + fireEvent.click(duplicateMenuItem); + }); + + it('renders duplicate resume in preview', async () => { + await waitFor(() => + expect( + expectResumeToBeRenderedInPreview(duplicateResumeName), + ).resolves.toBeUndefined(), + ); + }); + + it('adds duplicate resume to database', async () => { + await waitForResumeToBeRenderedInPreview(duplicateResumeName); + + const actualUserResumes = ( + await FirebaseStub.database() + .ref(DatabaseConstants.resumesPath) + .orderByChild('user') + .equalTo(user.uid) + .once('value') + ).val(); + expect(Object.values(actualUserResumes)).toHaveLength(3); + + const actualUserResumesFiltered = Object.values(actualUserResumes).filter( + (resume) => + resume.name === duplicateResumeName && + resume.id !== resumeToDuplicateId, + ); + expect(actualUserResumesFiltered).toHaveLength(1); + const createdResume = actualUserResumesFiltered[0]; + expect(createdResume.id).toBeTruthy(); + expect(createdResume.preview).toBeTruthy(); + expect(createdResume.preview).toEqual(unsplashPhotoResponseUrl); + expect(createdResume.createdAt).toBeTruthy(); + expect(createdResume.createdAt).toBeGreaterThanOrEqual(now); + expect(createdResume.createdAt).toEqual(createdResume.updatedAt); + const expectedResume = { + ...resumeToDuplicate, + id: createdResume.id, + name: createdResume.name, + preview: createdResume.preview, + createdAt: createdResume.createdAt, + updatedAt: createdResume.updatedAt, + }; + expect(createdResume).toEqual(expectedResume); + }); + + it('closes menu', async () => { + const menuItems = screen.queryAllByRole('menuitem'); + expect(menuItems).toHaveLength(0); + + await waitForResumeToBeRenderedInPreview(duplicateResumeName); + }); + }); + describe('while loading', () => { beforeEach(async () => { await setup(false); @@ -380,7 +470,7 @@ describe('Dashboard', () => { it('renders loading screen', async () => { expect(screen.getByTestId(loadingScreenTestId)).toBeInTheDocument(); - await fnWaitForLoadingScreenToDisappear(); + await waitForLoadingScreenToDisappearFn(); await waitForResumeToBeRenderedInPreview( Object.values(userResumes)[0].name, From e39026c8b4ad3ecf5264a23ae004accea361516b Mon Sep 17 00:00:00 2001 From: gianantoniopini <63844628+gianantoniopini@users.noreply.github.com> Date: Mon, 1 Feb 2021 18:00:52 +0100 Subject: [PATCH 12/18] Dashboard page: unit tests separated in different files --- .../__tests__/dashboard.createResume.test.js | 151 ++++++ .../__tests__/dashboard.deleteResume.test.js | 121 +++++ .../dashboard.duplicateResume.test.js | 103 ++++ .../app/__tests__/dashboard.rendering.test.js | 50 ++ src/pages/app/__tests__/dashboard.test.js | 483 ------------------ src/pages/app/__tests__/helpers/dashboard.js | 158 ++++++ 6 files changed, 583 insertions(+), 483 deletions(-) create mode 100644 src/pages/app/__tests__/dashboard.createResume.test.js create mode 100644 src/pages/app/__tests__/dashboard.deleteResume.test.js create mode 100644 src/pages/app/__tests__/dashboard.duplicateResume.test.js create mode 100644 src/pages/app/__tests__/dashboard.rendering.test.js delete mode 100644 src/pages/app/__tests__/dashboard.test.js create mode 100644 src/pages/app/__tests__/helpers/dashboard.js diff --git a/src/pages/app/__tests__/dashboard.createResume.test.js b/src/pages/app/__tests__/dashboard.createResume.test.js new file mode 100644 index 000000000..72fc0ad64 --- /dev/null +++ b/src/pages/app/__tests__/dashboard.createResume.test.js @@ -0,0 +1,151 @@ +import { + fireEvent, + getByText, + screen, + waitFor, + waitForElementToBeRemoved, +} from '@testing-library/react'; + +import FirebaseStub, { DatabaseConstants } from 'gatsby-plugin-firebase'; + +import { createResumeButtonDataTestId } from '../../../components/dashboard/CreateResume'; +import { + waitForResumeToBeRenderedInPreview, + expectResumeToBeRenderedInPreview, + waitForModalWindowToHaveBeenClosed, + dismissNotification, + unsplashPhotoResponseUrl, + setupWithFetchMockAndWaitForLoadingScreenToDisappear, +} from './helpers/dashboard'; + +const tooShortResumeName = 'CV 1'; +const validResumeName = 'Resume for SW development roles'; + +async function setup() { + const user = DatabaseConstants.user1; + await setupWithFetchMockAndWaitForLoadingScreenToDisappear(user); + + const dashboardCreateResumeButton = await screen.findByTestId( + createResumeButtonDataTestId, + ); + fireEvent.click(dashboardCreateResumeButton); + + const nameTextBox = screen.getByRole('textbox', { name: /name/i }); + return { user, nameTextBox }; +} + +async function setupAndSubmit(resumeName) { + const { user, nameTextBox } = await setup(); + + fireEvent.change(nameTextBox, { + target: { value: resumeName }, + }); + + const modalCreateResumeButton = screen.getByRole('button', { + name: /create resume/i, + }); + fireEvent.click(modalCreateResumeButton); + + return { user }; +} + +describe('with invalid name', () => { + it('displays validation error', async () => { + const { nameTextBox } = await setup(); + + fireEvent.change(nameTextBox, { target: { value: tooShortResumeName } }); + fireEvent.blur(nameTextBox); + + await waitFor(() => + expect( + screen.getByText(/Please enter at least 5 characters/i), + ).toBeInTheDocument(), + ); + }); + + it('displays notification', async () => { + await setupAndSubmit(tooShortResumeName); + + const notification = await screen.findByRole('alert'); + expect( + getByText( + notification, + /You might need to fill up all the required fields/i, + ), + ).toBeInTheDocument(); + dismissNotification(notification); + }); +}); + +describe('with valid name', () => { + it('renders loading message', async () => { + await setupAndSubmit(validResumeName); + + await waitFor(() => + expect( + screen.getByRole('button', { + name: /loading/i, + }), + ).toBeInTheDocument(), + ); + await waitForElementToBeRemoved(() => + screen.getByRole('button', { + name: /loading/i, + }), + ); + + await waitForModalWindowToHaveBeenClosed(); + await waitForResumeToBeRenderedInPreview(validResumeName); + }); + + it('closes modal window', async () => { + await setupAndSubmit(validResumeName); + + await waitFor(() => + expect(waitForModalWindowToHaveBeenClosed()).resolves.toBeUndefined(), + ); + + await waitForResumeToBeRenderedInPreview(validResumeName); + }); + + it('renders created resume in preview', async () => { + await setupAndSubmit(validResumeName); + + await waitForModalWindowToHaveBeenClosed(); + + await waitFor(() => + expect( + expectResumeToBeRenderedInPreview(validResumeName), + ).resolves.toBeUndefined(), + ); + }); + + it('adds resume in initial state to database', async () => { + const now = new Date().getTime(); + const { user } = await setupAndSubmit(validResumeName); + + await waitForModalWindowToHaveBeenClosed(); + await waitForResumeToBeRenderedInPreview(validResumeName); + + const actualUserResumes = ( + await FirebaseStub.database() + .ref(DatabaseConstants.resumesPath) + .orderByChild('user') + .equalTo(user.uid) + .once('value') + ).val(); + expect(Object.values(actualUserResumes)).toHaveLength(3); + + const actualUserResumesFiltered = Object.values(actualUserResumes).filter( + (resume) => resume.name === validResumeName, + ); + expect(actualUserResumesFiltered).toHaveLength(1); + const createdResume = actualUserResumesFiltered[0]; + expect(createdResume.id).toBeTruthy(); + expect(createdResume.preview).toBeTruthy(); + expect(createdResume.preview).toEqual(unsplashPhotoResponseUrl); + expect(createdResume.createdAt).toBeTruthy(); + expect(createdResume.createdAt).toBeGreaterThanOrEqual(now); + expect(createdResume.createdAt).toEqual(createdResume.updatedAt); + }); +}); diff --git a/src/pages/app/__tests__/dashboard.deleteResume.test.js b/src/pages/app/__tests__/dashboard.deleteResume.test.js new file mode 100644 index 000000000..9d3b39b30 --- /dev/null +++ b/src/pages/app/__tests__/dashboard.deleteResume.test.js @@ -0,0 +1,121 @@ +import { + fireEvent, + getByText, + queryByText, + screen, + waitFor, +} from '@testing-library/react'; + +import FirebaseStub, { DatabaseConstants } from 'gatsby-plugin-firebase'; + +import { menuToggleDataTestIdPrefix as resumePreviewMenuToggleDataTestIdPrefix } from '../../../components/dashboard/ResumePreview'; +import { + setupAndWaitForLoadingScreenToDisappear, + waitForResumeToDisappearFromPreview, + expectResumeToBeRenderedInPreview, + dismissNotification, + findAndDismissNotification, +} from './helpers/dashboard'; + +const waitForDatabaseRemoveToHaveCompleted = async ( + mockDatabaseRemoveFunction, +) => { + await waitFor(() => mockDatabaseRemoveFunction.mock.results[0].value); +}; + +const expectDatabaseRemoveToHaveCompleted = async ( + mockDatabaseRemoveFunction, +) => { + await waitFor(() => + expect(mockDatabaseRemoveFunction).toHaveBeenCalledTimes(1), + ); + await waitFor(() => + expect( + mockDatabaseRemoveFunction.mock.results[0].value, + ).resolves.toBeUndefined(), + ); +}; + +async function setup() { + const userResumes = await setupAndWaitForLoadingScreenToDisappear( + DatabaseConstants.user1, + ); + + const [resumeToDelete] = Object.values(userResumes).filter( + (resume) => resume.id === DatabaseConstants.demoStateResume1Id, + ); + const resumeToDeleteId = resumeToDelete.id; + const [undeletedResume] = Object.values(userResumes).filter( + (resume) => resume.id === DatabaseConstants.initialStateResumeId, + ); + + const mockDatabaseRemoveFunction = jest.spyOn( + FirebaseStub.database().ref( + `${DatabaseConstants.resumesPath}/${resumeToDeleteId}`, + ), + 'remove', + ); + + const resumeToDeleteMenuToggle = await screen.findByTestId( + `${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); + + return { resumeToDelete, undeletedResume, mockDatabaseRemoveFunction }; +} + +it('removes resume from database and preview', async () => { + const { + resumeToDelete, + undeletedResume, + mockDatabaseRemoveFunction, + } = await setup(); + + await findAndDismissNotification(); + + await expectDatabaseRemoveToHaveCompleted(mockDatabaseRemoveFunction); + + await waitFor(() => + expect( + waitForResumeToDisappearFromPreview(resumeToDelete.name), + ).resolves.toBeUndefined(), + ); + await expectResumeToBeRenderedInPreview(undeletedResume.name); +}); + +it('displays notification', async () => { + const { resumeToDelete, mockDatabaseRemoveFunction } = await setup(); + + const notification = await screen.findByRole('alert'); + expect( + getByText( + notification, + new RegExp(`${resumeToDelete.name} was deleted successfully`, 'i'), + ), + ).toBeInTheDocument(); + dismissNotification(notification); + + await waitForDatabaseRemoveToHaveCompleted(mockDatabaseRemoveFunction); + await waitForResumeToDisappearFromPreview(resumeToDelete.name); +}); + +it('closes menu', async () => { + const { resumeToDelete, mockDatabaseRemoveFunction } = await setup(); + + const menuItems = screen.queryAllByRole('menuitem'); + expect(menuItems).toHaveLength(0); + + await findAndDismissNotification(); + await waitForDatabaseRemoveToHaveCompleted(mockDatabaseRemoveFunction); + await waitForResumeToDisappearFromPreview(resumeToDelete.name); +}); diff --git a/src/pages/app/__tests__/dashboard.duplicateResume.test.js b/src/pages/app/__tests__/dashboard.duplicateResume.test.js new file mode 100644 index 000000000..432714182 --- /dev/null +++ b/src/pages/app/__tests__/dashboard.duplicateResume.test.js @@ -0,0 +1,103 @@ +import { + fireEvent, + queryByText, + screen, + waitFor, +} from '@testing-library/react'; + +import FirebaseStub, { DatabaseConstants } from 'gatsby-plugin-firebase'; + +import { menuToggleDataTestIdPrefix as resumePreviewMenuToggleDataTestIdPrefix } from '../../../components/dashboard/ResumePreview'; +import { + setupWithFetchMockAndWaitForLoadingScreenToDisappear, + waitForResumeToBeRenderedInPreview, + expectResumeToBeRenderedInPreview, + unsplashPhotoResponseUrl, +} from './helpers/dashboard'; + +async function setup() { + const user = DatabaseConstants.user1; + const userResumes = await setupWithFetchMockAndWaitForLoadingScreenToDisappear( + user, + ); + + const [resumeToDuplicate] = Object.values(userResumes).filter( + (resume) => resume.id === DatabaseConstants.demoStateResume1Id, + ); + const resumeToDuplicateId = resumeToDuplicate.id; + const duplicateResumeName = `${resumeToDuplicate.name} Copy`; + + const resumeToDuplicateMenuToggle = await screen.findByTestId( + `${resumePreviewMenuToggleDataTestIdPrefix}${resumeToDuplicateId}`, + ); + fireEvent.click(resumeToDuplicateMenuToggle); + + const menuItems = screen.getAllByRole('menuitem'); + let duplicateMenuItem = null; + for (let index = 0; index < menuItems.length; index++) { + if (queryByText(menuItems[index], /duplicate/i)) { + duplicateMenuItem = menuItems[index]; + break; + } + } + fireEvent.click(duplicateMenuItem); + + return { user, resumeToDuplicate, duplicateResumeName }; +} + +it('renders duplicate resume in preview', async () => { + const { duplicateResumeName } = await setup(); + + await waitFor(() => + expect( + expectResumeToBeRenderedInPreview(duplicateResumeName), + ).resolves.toBeUndefined(), + ); +}); + +it('adds duplicate resume to database', async () => { + const now = new Date().getTime(); + const { user, resumeToDuplicate, duplicateResumeName } = await setup(); + + await waitForResumeToBeRenderedInPreview(duplicateResumeName); + + const actualUserResumes = ( + await FirebaseStub.database() + .ref(DatabaseConstants.resumesPath) + .orderByChild('user') + .equalTo(user.uid) + .once('value') + ).val(); + expect(Object.values(actualUserResumes)).toHaveLength(3); + + const actualUserResumesFiltered = Object.values(actualUserResumes).filter( + (resume) => + resume.name === duplicateResumeName && resume.id !== resumeToDuplicate.id, + ); + expect(actualUserResumesFiltered).toHaveLength(1); + const createdResume = actualUserResumesFiltered[0]; + expect(createdResume.id).toBeTruthy(); + expect(createdResume.preview).toBeTruthy(); + expect(createdResume.preview).toEqual(unsplashPhotoResponseUrl); + expect(createdResume.createdAt).toBeTruthy(); + expect(createdResume.createdAt).toBeGreaterThanOrEqual(now); + expect(createdResume.createdAt).toEqual(createdResume.updatedAt); + const expectedResume = { + ...resumeToDuplicate, + id: createdResume.id, + name: createdResume.name, + preview: createdResume.preview, + createdAt: createdResume.createdAt, + updatedAt: createdResume.updatedAt, + }; + expect(createdResume).toEqual(expectedResume); +}); + +it('closes menu', async () => { + const { duplicateResumeName } = await setup(); + + const menuItems = screen.queryAllByRole('menuitem'); + expect(menuItems).toHaveLength(0); + + await waitForResumeToBeRenderedInPreview(duplicateResumeName); +}); diff --git a/src/pages/app/__tests__/dashboard.rendering.test.js b/src/pages/app/__tests__/dashboard.rendering.test.js new file mode 100644 index 000000000..76271428e --- /dev/null +++ b/src/pages/app/__tests__/dashboard.rendering.test.js @@ -0,0 +1,50 @@ +import { screen, waitFor } from '@testing-library/react'; + +import { DatabaseConstants } from 'gatsby-plugin-firebase'; + +import { createResumeButtonDataTestId } from '../../../components/dashboard/CreateResume'; +import setup, { + setupAndWaitForLoadingScreenToDisappear, + expectResumeToBeRenderedInPreview, + expectLoadingScreenToBeRendered, + waitForLoadingScreenToDisappear, +} from './helpers/dashboard'; + +const user = DatabaseConstants.user1; + +it('renders loading screen', async () => { + await setup(user); + + expect(expectLoadingScreenToBeRendered()).toBeUndefined(); + await waitForLoadingScreenToDisappear(); +}); + +it('renders document title', async () => { + await setupAndWaitForLoadingScreenToDisappear(user); + + await waitFor(() => { + expect(document.title).toEqual('Dashboard | Reactive Resume'); + }); +}); + +it('renders create resume', async () => { + await setupAndWaitForLoadingScreenToDisappear(user); + + await waitFor(() => { + expect(screen.getByText(/create resume/i)).toBeInTheDocument(); + }); + await waitFor(() => { + expect( + screen.getByTestId(createResumeButtonDataTestId), + ).toBeInTheDocument(); + }); +}); + +it('renders preview of user resumes', async () => { + const userResumes = await setupAndWaitForLoadingScreenToDisappear(user); + + expect(Object.keys(userResumes)).toHaveLength(2); + + await expectResumeToBeRenderedInPreview(Object.values(userResumes)[0].name); + await expectResumeToBeRenderedInPreview(Object.values(userResumes)[1].name); +}); diff --git a/src/pages/app/__tests__/dashboard.test.js b/src/pages/app/__tests__/dashboard.test.js deleted file mode 100644 index 7b0da3b6f..000000000 --- a/src/pages/app/__tests__/dashboard.test.js +++ /dev/null @@ -1,483 +0,0 @@ -import React from 'react'; -import { - fireEvent, - getByText, - queryByText, - render, - screen, - waitFor, - waitForElementToBeRemoved, -} from '@testing-library/react'; - -import fetchMock from 'jest-fetch-mock'; - -import FirebaseStub, { DatabaseConstants } from 'gatsby-plugin-firebase'; - -import '../../../i18n/index'; -import '../../../utils/dayjs'; -import { unsplashPhotoRequestUrl, delay } from '../../../utils/index'; -import { dataTestId as loadingScreenTestId } from '../../../components/router/LoadingScreen'; -import { createResumeButtonDataTestId } from '../../../components/dashboard/CreateResume'; -import { menuToggleDataTestIdPrefix as resumePreviewMenuToggleDataTestIdPrefix } from '../../../components/dashboard/ResumePreview'; -import { SettingsProvider } from '../../../contexts/SettingsContext'; -import { ModalProvider } from '../../../contexts/ModalContext'; -import { UserProvider } from '../../../contexts/UserContext'; -import { DatabaseProvider } from '../../../contexts/DatabaseContext'; -import { ResumeProvider } from '../../../contexts/ResumeContext'; -import { StorageProvider } from '../../../contexts/StorageContext'; -import Wrapper from '../../../components/shared/Wrapper'; -import Dashboard from '../dashboard'; - -describe('Dashboard', () => { - let userResumes = null; - const user = DatabaseConstants.user1; - const unsplashPhotoResponseUrl = 'https://test-url-123456789.com'; - - const waitForResumeToBeRenderedInPreview = async (resumeName) => { - await screen.findByText(resumeName); - }; - - const expectResumeToBeRenderedInPreview = async (resumeName) => { - await waitFor(() => { - expect(screen.getByText(resumeName)).toBeInTheDocument(); - }); - }; - - const waitForModalWindowToHaveBeenClosed = async () => { - await waitForElementToBeRemoved(() => - screen.getByRole('textbox', { name: /name/i }), - ); - }; - - const dismissNotification = (notification) => { - fireEvent.click(notification); - }; - - const findAndDismissNotification = async () => { - const notification = await screen.findByRole('alert'); - dismissNotification(notification); - }; - - const waitForLoadingScreenToDisappearFn = async () => { - await waitForElementToBeRemoved(() => - screen.getByTestId(loadingScreenTestId), - ); - }; - - const setupFetchMockFn = () => { - fetchMock.resetMocks(); - - fetchMock.mockImplementationOnce(async (input) => { - await delay(100); - - if (input === unsplashPhotoRequestUrl) { - return { - url: unsplashPhotoResponseUrl, - }; - } - - throw new Error('Unsupported input.'); - }); - }; - - async function setup( - waitForLoadingScreenToDisappear = true, - setupFetchMock = false, - ) { - if (setupFetchMock) { - setupFetchMockFn(); - } - - FirebaseStub.database().initializeData(); - - userResumes = ( - await FirebaseStub.database() - .ref(DatabaseConstants.resumesPath) - .orderByChild('user') - .equalTo(user.uid) - .once('value') - ).val(); - - FirebaseStub.auth().signInAnonymously(); - - render( - - - - - - - - - - - - - - - , - ); - - if (waitForLoadingScreenToDisappear) { - await waitForLoadingScreenToDisappearFn(); - } - } - - describe('renders', () => { - beforeEach(async () => { - await setup(); - }); - - it('document title', async () => { - await waitFor(() => { - expect(document.title).toEqual('Dashboard | Reactive Resume'); - }); - }); - - it('create resume', async () => { - await waitFor(() => { - expect(screen.getByText(/create resume/i)).toBeInTheDocument(); - }); - await waitFor(() => { - expect( - screen.getByTestId(createResumeButtonDataTestId), - ).toBeInTheDocument(); - }); - }); - - it('preview of user resumes', async () => { - expect(Object.keys(userResumes)).toHaveLength(2); - - await expectResumeToBeRenderedInPreview( - Object.values(userResumes)[0].name, - ); - await expectResumeToBeRenderedInPreview( - Object.values(userResumes)[1].name, - ); - }); - }); - - describe('when resume is created', () => { - let nameTextBox = null; - - beforeEach(async () => { - await setup(true, true); - - const dashboardCreateResumeButton = await screen.findByTestId( - createResumeButtonDataTestId, - ); - fireEvent.click(dashboardCreateResumeButton); - - nameTextBox = screen.getByRole('textbox', { name: /name/i }); - }); - - describe('with name shorter than 5 characters', () => { - it('displays validation error and notification', async () => { - fireEvent.change(nameTextBox, { target: { value: 'CV 1' } }); - - fireEvent.blur(nameTextBox); - - await waitFor(() => - expect( - screen.getByText(/Please enter at least 5 characters/i), - ).toBeInTheDocument(), - ); - - const modalCreateResumeButton = screen.getByRole('button', { - name: /create resume/i, - }); - fireEvent.click(modalCreateResumeButton); - - const notification = await screen.findByRole('alert'); - expect( - getByText( - notification, - /You might need to fill up all the required fields/i, - ), - ).toBeInTheDocument(); - dismissNotification(notification); - }); - }); - - describe('with valid name', () => { - const resumeName = 'Resume for SW development roles'; - let now = 0; - - beforeEach(() => { - now = new Date().getTime(); - - fireEvent.change(nameTextBox, { - target: { value: resumeName }, - }); - - const modalCreateResumeButton = screen.getByRole('button', { - name: /create resume/i, - }); - fireEvent.click(modalCreateResumeButton); - }); - - it('renders loading message', async () => { - await waitFor(() => - expect( - screen.getByRole('button', { - name: /loading/i, - }), - ).toBeInTheDocument(), - ); - await waitForElementToBeRemoved(() => - screen.getByRole('button', { - name: /loading/i, - }), - ); - - await waitForModalWindowToHaveBeenClosed(); - await waitForResumeToBeRenderedInPreview(resumeName); - }); - - it('closes modal window', async () => { - await waitFor(() => - expect(waitForModalWindowToHaveBeenClosed()).resolves.toBeUndefined(), - ); - - await waitForResumeToBeRenderedInPreview(resumeName); - }); - - it('renders created resume in preview', async () => { - await waitForModalWindowToHaveBeenClosed(); - - await waitFor(() => - expect( - expectResumeToBeRenderedInPreview(resumeName), - ).resolves.toBeUndefined(), - ); - }); - - it('adds resume in initial state to database', async () => { - await waitForModalWindowToHaveBeenClosed(); - await waitForResumeToBeRenderedInPreview(resumeName); - - const actualUserResumes = ( - await FirebaseStub.database() - .ref(DatabaseConstants.resumesPath) - .orderByChild('user') - .equalTo(user.uid) - .once('value') - ).val(); - expect(Object.values(actualUserResumes)).toHaveLength(3); - - const actualUserResumesFiltered = Object.values( - actualUserResumes, - ).filter((resume) => resume.name === resumeName); - expect(actualUserResumesFiltered).toHaveLength(1); - const createdResume = actualUserResumesFiltered[0]; - expect(createdResume.id).toBeTruthy(); - expect(createdResume.preview).toBeTruthy(); - expect(createdResume.preview).toEqual(unsplashPhotoResponseUrl); - expect(createdResume.createdAt).toBeTruthy(); - expect(createdResume.createdAt).toBeGreaterThanOrEqual(now); - expect(createdResume.createdAt).toEqual(createdResume.updatedAt); - }); - }); - }); - - describe('when resume is deleted', () => { - let mockDatabaseRemoveFunction = null; - let resumeToDelete = null; - let undeletedResume = null; - let resumeToDeleteId = null; - - 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(), - ); - }; - - const waitForResumeToDisappearFromPreview = async (resumeName) => { - await waitFor(() => - screen.queryByText(resumeName) ? Promise.reject() : Promise.resolve(), - ); - }; - - beforeEach(async () => { - await setup(); - - [resumeToDelete] = Object.values(userResumes).filter( - (resume) => resume.id === DatabaseConstants.demoStateResume1Id, - ); - resumeToDeleteId = resumeToDelete.id; - [undeletedResume] = Object.values(userResumes).filter( - (resume) => resume.id === DatabaseConstants.initialStateResumeId, - ); - - mockDatabaseRemoveFunction = jest.spyOn( - FirebaseStub.database().ref( - `${DatabaseConstants.resumesPath}/${resumeToDeleteId}`, - ), - 'remove', - ); - - const resumeToDeleteMenuToggle = await screen.findByTestId( - `${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 findAndDismissNotification(); - - await expectDatabaseRemoveToHaveCompleted(); - - await waitFor(() => - expect( - waitForResumeToDisappearFromPreview(resumeToDelete.name), - ).resolves.toBeUndefined(), - ); - await expectResumeToBeRenderedInPreview(undeletedResume.name); - }); - - it('displays notification', async () => { - const notification = await screen.findByRole('alert'); - expect( - getByText( - notification, - new RegExp(`${resumeToDelete.name} was deleted successfully`, 'i'), - ), - ).toBeInTheDocument(); - dismissNotification(notification); - - await waitForDatabaseRemoveToHaveCompleted(); - await waitForResumeToDisappearFromPreview(resumeToDelete.name); - }); - - it('closes menu', async () => { - const menuItems = screen.queryAllByRole('menuitem'); - expect(menuItems).toHaveLength(0); - - await findAndDismissNotification(); - await waitForDatabaseRemoveToHaveCompleted(); - await waitForResumeToDisappearFromPreview(resumeToDelete.name); - }); - }); - - describe('when resume is duplicated', () => { - let resumeToDuplicate = null; - let resumeToDuplicateId = null; - let duplicateResumeName = null; - let now = 0; - - beforeEach(async () => { - await setup(true, true); - - now = new Date().getTime(); - - [resumeToDuplicate] = Object.values(userResumes).filter( - (resume) => resume.id === DatabaseConstants.demoStateResume1Id, - ); - resumeToDuplicateId = resumeToDuplicate.id; - duplicateResumeName = `${resumeToDuplicate.name} Copy`; - - const resumeToDuplicateMenuToggle = await screen.findByTestId( - `${resumePreviewMenuToggleDataTestIdPrefix}${resumeToDuplicateId}`, - ); - fireEvent.click(resumeToDuplicateMenuToggle); - - const menuItems = screen.getAllByRole('menuitem'); - let duplicateMenuItem = null; - for (let index = 0; index < menuItems.length; index++) { - if (queryByText(menuItems[index], /duplicate/i)) { - duplicateMenuItem = menuItems[index]; - break; - } - } - fireEvent.click(duplicateMenuItem); - }); - - it('renders duplicate resume in preview', async () => { - await waitFor(() => - expect( - expectResumeToBeRenderedInPreview(duplicateResumeName), - ).resolves.toBeUndefined(), - ); - }); - - it('adds duplicate resume to database', async () => { - await waitForResumeToBeRenderedInPreview(duplicateResumeName); - - const actualUserResumes = ( - await FirebaseStub.database() - .ref(DatabaseConstants.resumesPath) - .orderByChild('user') - .equalTo(user.uid) - .once('value') - ).val(); - expect(Object.values(actualUserResumes)).toHaveLength(3); - - const actualUserResumesFiltered = Object.values(actualUserResumes).filter( - (resume) => - resume.name === duplicateResumeName && - resume.id !== resumeToDuplicateId, - ); - expect(actualUserResumesFiltered).toHaveLength(1); - const createdResume = actualUserResumesFiltered[0]; - expect(createdResume.id).toBeTruthy(); - expect(createdResume.preview).toBeTruthy(); - expect(createdResume.preview).toEqual(unsplashPhotoResponseUrl); - expect(createdResume.createdAt).toBeTruthy(); - expect(createdResume.createdAt).toBeGreaterThanOrEqual(now); - expect(createdResume.createdAt).toEqual(createdResume.updatedAt); - const expectedResume = { - ...resumeToDuplicate, - id: createdResume.id, - name: createdResume.name, - preview: createdResume.preview, - createdAt: createdResume.createdAt, - updatedAt: createdResume.updatedAt, - }; - expect(createdResume).toEqual(expectedResume); - }); - - it('closes menu', async () => { - const menuItems = screen.queryAllByRole('menuitem'); - expect(menuItems).toHaveLength(0); - - await waitForResumeToBeRenderedInPreview(duplicateResumeName); - }); - }); - - describe('while loading', () => { - beforeEach(async () => { - await setup(false); - }); - - it('renders loading screen', async () => { - expect(screen.getByTestId(loadingScreenTestId)).toBeInTheDocument(); - await waitForLoadingScreenToDisappearFn(); - - await waitForResumeToBeRenderedInPreview( - Object.values(userResumes)[0].name, - ); - await waitForResumeToBeRenderedInPreview( - Object.values(userResumes)[1].name, - ); - }); - }); -}); diff --git a/src/pages/app/__tests__/helpers/dashboard.js b/src/pages/app/__tests__/helpers/dashboard.js new file mode 100644 index 000000000..f97226da0 --- /dev/null +++ b/src/pages/app/__tests__/helpers/dashboard.js @@ -0,0 +1,158 @@ +import React from 'react'; +import { + fireEvent, + render, + screen, + waitFor, + waitForElementToBeRemoved, +} from '@testing-library/react'; + +import fetchMock from 'jest-fetch-mock'; + +import FirebaseStub, { DatabaseConstants } from 'gatsby-plugin-firebase'; + +import '../../../../i18n/index'; +import '../../../../utils/dayjs'; +import { dataTestId as loadingScreenTestId } from '../../../../components/router/LoadingScreen'; +import { unsplashPhotoRequestUrl, delay } from '../../../../utils/index'; +import { SettingsProvider } from '../../../../contexts/SettingsContext'; +import { ModalProvider } from '../../../../contexts/ModalContext'; +import { UserProvider } from '../../../../contexts/UserContext'; +import { DatabaseProvider } from '../../../../contexts/DatabaseContext'; +import { ResumeProvider } from '../../../../contexts/ResumeContext'; +import { StorageProvider } from '../../../../contexts/StorageContext'; +import Wrapper from '../../../../components/shared/Wrapper'; +import Dashboard from '../../dashboard'; + +const waitForResumeToBeRenderedInPreview = async (resumeName) => { + await screen.findByText(resumeName); +}; + +const waitForResumeToDisappearFromPreview = async (resumeName) => { + await waitFor(() => + screen.queryByText(resumeName) ? Promise.reject() : Promise.resolve(), + ); +}; + +const expectResumeToBeRenderedInPreview = async (resumeName) => { + await waitFor(() => { + expect(screen.getByText(resumeName)).toBeInTheDocument(); + }); +}; + +const waitForModalWindowToHaveBeenClosed = async () => { + await waitForElementToBeRemoved(() => + screen.getByRole('textbox', { name: /name/i }), + ); +}; + +const dismissNotification = (notification) => { + fireEvent.click(notification); +}; + +const findAndDismissNotification = async () => { + const notification = await screen.findByRole('alert'); + dismissNotification(notification); +}; + +const expectLoadingScreenToBeRendered = () => { + expect(screen.getByTestId(loadingScreenTestId)).toBeInTheDocument(); +}; + +const waitForLoadingScreenToDisappearFn = async () => { + await waitForElementToBeRemoved(() => + screen.getByTestId(loadingScreenTestId), + ); +}; + +const unsplashPhotoResponseUrl = 'https://test-url-123456789.com'; + +const setupFetchMockFn = () => { + fetchMock.resetMocks(); + + fetchMock.mockImplementationOnce(async (input) => { + await delay(100); + + if (input === unsplashPhotoRequestUrl) { + return { + url: unsplashPhotoResponseUrl, + }; + } + + throw new Error('Unsupported input.'); + }); +}; + +// eslint-disable-next-line no-underscore-dangle +async function _setup(user, waitForLoadingScreenToDisappear, setupFetchMock) { + if (setupFetchMock) { + setupFetchMockFn(); + } + + FirebaseStub.database().initializeData(); + + const userResumes = ( + await FirebaseStub.database() + .ref(DatabaseConstants.resumesPath) + .orderByChild('user') + .equalTo(user.uid) + .once('value') + ).val(); + + FirebaseStub.auth().signInAnonymously(); + + render( + + + + + + + + + + + + + + + , + ); + + if (waitForLoadingScreenToDisappear) { + await waitForLoadingScreenToDisappearFn(); + } + + return userResumes; +} + +async function setup(user) { + const userResumes = await _setup(user, false, false); + return userResumes; +} + +async function setupAndWaitForLoadingScreenToDisappear(user) { + const userResumes = await _setup(user, true, false); + return userResumes; +} + +async function setupWithFetchMockAndWaitForLoadingScreenToDisappear(user) { + const userResumes = await _setup(user, true, true); + return userResumes; +} + +export default setup; + +export { + setupAndWaitForLoadingScreenToDisappear, + setupWithFetchMockAndWaitForLoadingScreenToDisappear, + waitForResumeToBeRenderedInPreview, + waitForResumeToDisappearFromPreview, + expectResumeToBeRenderedInPreview, + waitForModalWindowToHaveBeenClosed, + dismissNotification, + findAndDismissNotification, + expectLoadingScreenToBeRendered, + waitForLoadingScreenToDisappearFn as waitForLoadingScreenToDisappear, + unsplashPhotoResponseUrl, +}; From f72d2639e5d44ea25ec4694a54784b2572a2b131 Mon Sep 17 00:00:00 2001 From: gianantoniopini <63844628+gianantoniopini@users.noreply.github.com> Date: Tue, 2 Feb 2021 10:27:30 +0100 Subject: [PATCH 13/18] Builder page: unit tests separated in different files --- .../__tests__/builder.dataPersistence.test.js | 39 +++ .../__tests__/builder.errorHandling.test.js | 24 ++ .../app/__tests__/builder.rendering.test.js | 63 +++++ .../app/__tests__/builder.settings.test.js | 54 ++++ src/pages/app/__tests__/builder.test.js | 260 ------------------ src/pages/app/__tests__/helpers/builder.js | 127 +++++++++ src/pages/app/__tests__/helpers/dashboard.js | 6 +- 7 files changed, 311 insertions(+), 262 deletions(-) create mode 100644 src/pages/app/__tests__/builder.dataPersistence.test.js create mode 100644 src/pages/app/__tests__/builder.errorHandling.test.js create mode 100644 src/pages/app/__tests__/builder.rendering.test.js create mode 100644 src/pages/app/__tests__/builder.settings.test.js delete mode 100644 src/pages/app/__tests__/builder.test.js create mode 100644 src/pages/app/__tests__/helpers/builder.js diff --git a/src/pages/app/__tests__/builder.dataPersistence.test.js b/src/pages/app/__tests__/builder.dataPersistence.test.js new file mode 100644 index 000000000..9f1fef644 --- /dev/null +++ b/src/pages/app/__tests__/builder.dataPersistence.test.js @@ -0,0 +1,39 @@ +import { fireEvent, screen } from '@testing-library/react'; + +import { DatabaseConstants } from 'gatsby-plugin-firebase'; + +import { + setupAndWait, + expectDatabaseUpdateToHaveCompleted, +} from './helpers/builder'; + +const testTimeoutInMilliseconds = 20000; +jest.setTimeout(testTimeoutInMilliseconds); + +test('when input value is changed, updates database', async () => { + const resumeId = DatabaseConstants.demoStateResume1Id; + const { mockDatabaseUpdateFunction } = await setupAndWait( + resumeId, + true, + true, + ); + + const input = screen.getByRole('textbox', { name: /address line 1/i }); + const newInputValue = 'test street 123'; + const now = new Date().getTime(); + + fireEvent.change(input, { target: { value: newInputValue } }); + + expect(input.value).toBe(newInputValue); + + await expectDatabaseUpdateToHaveCompleted(mockDatabaseUpdateFunction); + const mockDatabaseUpdateFunctionCallArgument = + mockDatabaseUpdateFunction.mock.calls[0][0]; + expect(mockDatabaseUpdateFunctionCallArgument.id).toBe(resumeId); + expect(mockDatabaseUpdateFunctionCallArgument.profile.address.line1).toBe( + newInputValue, + ); + expect( + mockDatabaseUpdateFunctionCallArgument.updatedAt, + ).toBeGreaterThanOrEqual(now); +}); diff --git a/src/pages/app/__tests__/builder.errorHandling.test.js b/src/pages/app/__tests__/builder.errorHandling.test.js new file mode 100644 index 000000000..f573f9b50 --- /dev/null +++ b/src/pages/app/__tests__/builder.errorHandling.test.js @@ -0,0 +1,24 @@ +import { navigate as mockNavigateFunction } from 'gatsby'; +import { fireEvent, getByText, screen, waitFor } from '@testing-library/react'; + +import setup from './helpers/builder'; + +test('if resume does not exist, navigates to Dashboard and displays notification', async () => { + await setup('xxxxxx'); + + await waitFor(() => expect(mockNavigateFunction).toHaveBeenCalledTimes(1)); + expect(mockNavigateFunction).toHaveBeenCalledWith('/app/dashboard'); + + const notification = await screen.findByRole('alert'); + expect( + getByText( + notification, + /The resume you were looking for does not exist anymore/i, + ), + ).toBeInTheDocument(); + fireEvent.click(notification); + + await waitFor(() => + expect(mockNavigateFunction.mock.results[0].value).resolves.toBeUndefined(), + ); +}); diff --git a/src/pages/app/__tests__/builder.rendering.test.js b/src/pages/app/__tests__/builder.rendering.test.js new file mode 100644 index 000000000..950296522 --- /dev/null +++ b/src/pages/app/__tests__/builder.rendering.test.js @@ -0,0 +1,63 @@ +import { + fireEvent, + getByText, + screen, + waitForElementToBeRemoved, +} from '@testing-library/react'; + +import { DatabaseConstants } from 'gatsby-plugin-firebase'; + +import { dataTestId as loadingScreenTestId } from '../../../components/router/LoadingScreen'; + +import setup, { + setupAndWait, + waitForDatabaseUpdateToHaveCompleted, +} from './helpers/builder'; + +const testTimeoutInMilliseconds = 10000; +jest.setTimeout(testTimeoutInMilliseconds); + +test('renders first and last name', async () => { + const { resume } = await setupAndWait( + DatabaseConstants.demoStateResume1Id, + true, + true, + ); + + 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(); + expect( + screen.getAllByText(new RegExp(resume.profile.lastName, 'i')).length, + ).toBeTruthy(); +}); + +test('renders loading screen', async () => { + const { mockDatabaseUpdateFunction } = await setup( + DatabaseConstants.demoStateResume1Id, + ); + + expect(screen.getByTestId(loadingScreenTestId)).toBeInTheDocument(); + + await waitForElementToBeRemoved(() => + screen.getByTestId(loadingScreenTestId), + ); + + await waitForDatabaseUpdateToHaveCompleted(mockDatabaseUpdateFunction); +}); + +test('if resume is in initial state, renders load demo data notification', async () => { + await setup(DatabaseConstants.initialStateResumeId); + + 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__/builder.settings.test.js b/src/pages/app/__tests__/builder.settings.test.js new file mode 100644 index 000000000..7db8a1c1a --- /dev/null +++ b/src/pages/app/__tests__/builder.settings.test.js @@ -0,0 +1,54 @@ +import { fireEvent, screen } from '@testing-library/react'; + +import { DatabaseConstants } from 'gatsby-plugin-firebase'; + +import { languageStorageItemKey } from '../../../contexts/SettingsContext'; + +import { + setupAndWait, + expectDatabaseUpdateToHaveCompleted, +} from './helpers/builder'; + +const testTimeoutInMilliseconds = 20000; +jest.setTimeout(testTimeoutInMilliseconds); + +test('allows to change the language', async () => { + const resumeId = DatabaseConstants.demoStateResume1Id; + const { mockDatabaseUpdateFunction } = await setupAndWait( + resumeId, + true, + true, + ); + + 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)).not.toBeInTheDocument(); + expect(screen.getByLabelText(/data di nascita/i)).toBeInTheDocument(); + + const languageStorageItem = localStorage.getItem(languageStorageItemKey); + expect(languageStorageItem).toBe(italianLanguageCode); + + await expectDatabaseUpdateToHaveCompleted(mockDatabaseUpdateFunction); + 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); +}); diff --git a/src/pages/app/__tests__/builder.test.js b/src/pages/app/__tests__/builder.test.js deleted file mode 100644 index e22d6e9d7..000000000 --- a/src/pages/app/__tests__/builder.test.js +++ /dev/null @@ -1,260 +0,0 @@ -import { navigate as mockNavigateFunction } from 'gatsby'; -import React from 'react'; -import { - fireEvent, - getByText, - render, - screen, - waitFor, - waitForElementToBeRemoved, -} from '@testing-library/react'; - -import FirebaseStub, { DatabaseConstants } from 'gatsby-plugin-firebase'; - -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 { - DatabaseProvider, - DebounceWaitTime, -} 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', () => { - let resumeId = null; - 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, - waitForDatabaseUpdateToHaveCompleted = true, - ) { - FirebaseStub.database().initializeData(); - - resumeId = resumeIdParameter; - resume = ( - await FirebaseStub.database() - .ref(`${DatabaseConstants.resumesPath}/${resumeId}`) - .once('value') - ).val(); - - mockDatabaseUpdateFunction = jest.spyOn( - FirebaseStub.database().ref( - `${DatabaseConstants.resumesPath}/${resumeId}`, - ), - 'update', - ); - - FirebaseStub.auth().signInAnonymously(); - - render( - - - - - - - - - - - - - - - , - ); - - if (waitForLoadingScreenToDisappear) { - await waitForElementToBeRemoved(() => - screen.getByTestId(loadingScreenTestId), - ); - } - - if (waitForDatabaseUpdateToHaveCompleted) { - await fnWaitForDatabaseUpdateToHaveCompleted(); - } - - mockDatabaseUpdateFunction.mockClear(); - } - - describe('handles errors', () => { - describe('if resume does not exist', () => { - beforeEach(async () => { - await setup('xxxxxx', false, false); - }); - - it('navigates to Dashboard and displays notification', async () => { - await waitFor(() => - expect(mockNavigateFunction).toHaveBeenCalledTimes(1), - ); - expect(mockNavigateFunction).toHaveBeenCalledWith('/app/dashboard'); - - const notification = await screen.findByRole('alert'); - expect( - getByText( - notification, - /The resume you were looking for does not exist anymore/i, - ), - ).toBeInTheDocument(); - fireEvent.click(notification); - - await waitFor(() => - expect( - mockNavigateFunction.mock.results[0].value, - ).resolves.toBeUndefined(), - ); - }); - }); - }); - - describe('renders', () => { - beforeEach(async () => { - await setup(DatabaseConstants.demoStateResume1Id); - }); - - it('first and last name', () => { - 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(); - expect( - screen.getAllByText(new RegExp(resume.profile.lastName, 'i')).length, - ).toBeTruthy(); - }); - }); - - describe('settings', () => { - 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)).not.toBeInTheDocument(); - expect(screen.getByLabelText(/data di nascita/i)).toBeInTheDocument(); - - const languageStorageItem = localStorage.getItem(languageStorageItemKey); - expect(languageStorageItem).toBe(italianLanguageCode); - - await expectDatabaseUpdateToHaveCompleted(); - 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.getByRole('textbox', { name: /address line 1/i }); - const newInputValue = 'test street 123'; - const now = new Date().getTime(); - - fireEvent.change(input, { target: { value: newInputValue } }); - - expect(input.value).toBe(newInputValue); - - await expectDatabaseUpdateToHaveCompleted(); - const mockDatabaseUpdateFunctionCallArgument = - mockDatabaseUpdateFunction.mock.calls[0][0]; - expect(mockDatabaseUpdateFunctionCallArgument.id).toBe(resumeId); - expect(mockDatabaseUpdateFunctionCallArgument.profile.address.line1).toBe( - newInputValue, - ); - expect( - mockDatabaseUpdateFunctionCallArgument.updatedAt, - ).toBeGreaterThanOrEqual(now); - }); - }); - - describe('while loading', () => { - beforeEach(async () => { - await setup(DatabaseConstants.demoStateResume1Id, false, false); - }); - - it('renders loading screen', async () => { - expect(screen.getByTestId(loadingScreenTestId)).toBeInTheDocument(); - - await waitForElementToBeRemoved(() => - screen.getByTestId(loadingScreenTestId), - ); - - 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__/helpers/builder.js b/src/pages/app/__tests__/helpers/builder.js new file mode 100644 index 000000000..b05eca1b7 --- /dev/null +++ b/src/pages/app/__tests__/helpers/builder.js @@ -0,0 +1,127 @@ +import React from 'react'; +import { + render, + screen, + waitFor, + waitForElementToBeRemoved, +} from '@testing-library/react'; + +import FirebaseStub, { DatabaseConstants } from 'gatsby-plugin-firebase'; + +import { dataTestId as loadingScreenTestId } from '../../../../components/router/LoadingScreen'; +import { SettingsProvider } from '../../../../contexts/SettingsContext'; +import { ModalProvider } from '../../../../contexts/ModalContext'; +import { UserProvider } from '../../../../contexts/UserContext'; +import { + DatabaseProvider, + DebounceWaitTime, +} from '../../../../contexts/DatabaseContext'; +import { ResumeProvider } from '../../../../contexts/ResumeContext'; +import { StorageProvider } from '../../../../contexts/StorageContext'; +import Wrapper from '../../../../components/shared/Wrapper'; +import Builder from '../../builder'; + +const waitForDatabaseUpdateToHaveCompletedFn = async ( + mockDatabaseUpdateFunction, +) => { + await waitFor(() => mockDatabaseUpdateFunction.mock.calls[0][0], { + timeout: DebounceWaitTime, + }); + await waitFor(() => mockDatabaseUpdateFunction.mock.results[0].value); +}; + +const expectDatabaseUpdateToHaveCompleted = async ( + mockDatabaseUpdateFunction, +) => { + await waitFor( + () => expect(mockDatabaseUpdateFunction).toHaveBeenCalledTimes(1), + { + timeout: DebounceWaitTime, + }, + ); + await waitFor(() => + expect( + mockDatabaseUpdateFunction.mock.results[0].value, + ).resolves.toBeUndefined(), + ); +}; + +// eslint-disable-next-line no-underscore-dangle +async function _setup( + resumeId, + waitForLoadingScreenToDisappear, + waitForDatabaseUpdateToHaveCompleted, +) { + FirebaseStub.database().initializeData(); + + const resume = ( + await FirebaseStub.database() + .ref(`${DatabaseConstants.resumesPath}/${resumeId}`) + .once('value') + ).val(); + + const mockDatabaseUpdateFunction = jest.spyOn( + FirebaseStub.database().ref(`${DatabaseConstants.resumesPath}/${resumeId}`), + 'update', + ); + + FirebaseStub.auth().signInAnonymously(); + + render( + + + + + + + + + + + + + + + , + ); + + if (waitForLoadingScreenToDisappear) { + await waitForElementToBeRemoved(() => + screen.getByTestId(loadingScreenTestId), + ); + } + + if (waitForDatabaseUpdateToHaveCompleted) { + await waitForDatabaseUpdateToHaveCompletedFn(mockDatabaseUpdateFunction); + } + + mockDatabaseUpdateFunction.mockClear(); + + return { resume, mockDatabaseUpdateFunction }; +} + +async function setup(resumeId) { + const returnValue = await _setup(resumeId, false, false); + return returnValue; +} + +async function setupAndWait( + resumeId, + waitForLoadingScreenToDisappear, + waitForDatabaseUpdateToHaveCompleted, +) { + const returnValue = await _setup( + resumeId, + waitForLoadingScreenToDisappear, + waitForDatabaseUpdateToHaveCompleted, + ); + return returnValue; +} + +export default setup; + +export { + setupAndWait, + waitForDatabaseUpdateToHaveCompletedFn as waitForDatabaseUpdateToHaveCompleted, + expectDatabaseUpdateToHaveCompleted, +}; diff --git a/src/pages/app/__tests__/helpers/dashboard.js b/src/pages/app/__tests__/helpers/dashboard.js index f97226da0..4eba4f3eb 100644 --- a/src/pages/app/__tests__/helpers/dashboard.js +++ b/src/pages/app/__tests__/helpers/dashboard.js @@ -41,8 +41,10 @@ const expectResumeToBeRenderedInPreview = async (resumeName) => { }; const waitForModalWindowToHaveBeenClosed = async () => { - await waitForElementToBeRemoved(() => - screen.getByRole('textbox', { name: /name/i }), + await waitFor(() => + screen.queryByRole('textbox', { name: /name/i }) + ? Promise.reject() + : Promise.resolve(), ); }; From 4634b4f0a040d172bfed58f09241d3158b151c3f Mon Sep 17 00:00:00 2001 From: gianantoniopini <63844628+gianantoniopini@users.noreply.github.com> Date: Tue, 2 Feb 2021 10:40:26 +0100 Subject: [PATCH 14/18] Castform: unit tests refactoring to reduce nesting --- src/templates/__tests__/Castform.test.js | 75 ++++++++++++------------ 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/src/templates/__tests__/Castform.test.js b/src/templates/__tests__/Castform.test.js index 6f29ffed6..51cc84c0e 100644 --- a/src/templates/__tests__/Castform.test.js +++ b/src/templates/__tests__/Castform.test.js @@ -6,47 +6,48 @@ import FirebaseStub, { DatabaseConstants } from 'gatsby-plugin-firebase'; import '../../i18n/index'; import Castform from '../Castform'; -describe('Castform', () => { - let resume = {}; +const birthDateLabelMatcher = /Date of birth/i; - beforeEach(async () => { - FirebaseStub.database().initializeData(); +async function setup(resumeId) { + FirebaseStub.database().initializeData(); - const resumeId = DatabaseConstants.initialStateResumeId; - resume = ( - await FirebaseStub.database() - .ref(`${DatabaseConstants.resumesPath}/${resumeId}`) - .once('value') - ).val(); - }); + const resume = ( + await FirebaseStub.database() + .ref(`${DatabaseConstants.resumesPath}/${resumeId}`) + .once('value') + ).val(); - it('renders correctly', () => { - const { container } = render(); + return resume; +} - expect(container).toBeTruthy(); - expect(container).toBeInTheDocument(); - }); +test('renders correctly', async () => { + const resume = await setup(DatabaseConstants.initialStateResumeId); - describe('date of birth', () => { - const birthDateLabelMatcher = /Date of birth/i; + const { container } = render(); - it('is not shown if not provided', () => { - render(); - - expect(screen.queryByText(birthDateLabelMatcher)).toBeNull(); - }); - - it('is shown if provided', () => { - const birthDate = new Date(1990, 0, 20); - const birthDateFormatted = '20 January 1990'; - resume.profile.birthDate = birthDate; - - render(); - - expect(screen.getByText(birthDateLabelMatcher)).toBeTruthy(); - expect(screen.getByText(birthDateLabelMatcher)).toBeInTheDocument(); - expect(screen.getByText(birthDateFormatted)).toBeTruthy(); - expect(screen.getByText(birthDateFormatted)).toBeInTheDocument(); - }); - }); + expect(container).toBeTruthy(); + expect(container).toBeInTheDocument(); +}); + +test('date of birth is not shown if not provided', async () => { + const resume = await setup(DatabaseConstants.initialStateResumeId); + + render(); + + expect(screen.queryByText(birthDateLabelMatcher)).toBeNull(); +}); + +test('date of birth is shown if provided', async () => { + const resume = await setup(DatabaseConstants.initialStateResumeId); + + const birthDate = new Date(1990, 0, 20); + const birthDateFormatted = '20 January 1990'; + resume.profile.birthDate = birthDate; + + render(); + + expect(screen.getByText(birthDateLabelMatcher)).toBeTruthy(); + expect(screen.getByText(birthDateLabelMatcher)).toBeInTheDocument(); + expect(screen.getByText(birthDateFormatted)).toBeTruthy(); + expect(screen.getByText(birthDateFormatted)).toBeInTheDocument(); }); From 23386839a691aebb198cce97ca405942ee2f17af Mon Sep 17 00:00:00 2001 From: gianantoniopini <63844628+gianantoniopini@users.noreply.github.com> Date: Tue, 2 Feb 2021 12:39:16 +0100 Subject: [PATCH 15/18] Firebase Stub: unit tests separated in different files --- .../__tests__/gatsby-plugin-firebase.test.js | 552 ------------------ .../gatsby-plugin-firebase/auth.test.js | 51 ++ .../database.ref.filtering.test.js | 53 ++ .../database.ref.off.test.js | 51 ++ .../database.ref.on.valueEventType.test.js | 45 ++ .../database.ref.once.test.js | 59 ++ .../database.ref.remove.test.js | 77 +++ .../database.ref.set.test.js | 71 +++ .../database.ref.test.js | 26 + .../database.ref.update.test.js | 86 +++ .../gatsby-plugin-firebase/database.test.js | 52 ++ 11 files changed, 571 insertions(+), 552 deletions(-) delete mode 100644 __mocks__/__tests__/gatsby-plugin-firebase.test.js create mode 100644 __mocks__/__tests__/gatsby-plugin-firebase/auth.test.js create mode 100644 __mocks__/__tests__/gatsby-plugin-firebase/database.ref.filtering.test.js create mode 100644 __mocks__/__tests__/gatsby-plugin-firebase/database.ref.off.test.js create mode 100644 __mocks__/__tests__/gatsby-plugin-firebase/database.ref.on.valueEventType.test.js create mode 100644 __mocks__/__tests__/gatsby-plugin-firebase/database.ref.once.test.js create mode 100644 __mocks__/__tests__/gatsby-plugin-firebase/database.ref.remove.test.js create mode 100644 __mocks__/__tests__/gatsby-plugin-firebase/database.ref.set.test.js create mode 100644 __mocks__/__tests__/gatsby-plugin-firebase/database.ref.test.js create mode 100644 __mocks__/__tests__/gatsby-plugin-firebase/database.ref.update.test.js create mode 100644 __mocks__/__tests__/gatsby-plugin-firebase/database.test.js diff --git a/__mocks__/__tests__/gatsby-plugin-firebase.test.js b/__mocks__/__tests__/gatsby-plugin-firebase.test.js deleted file mode 100644 index f80e06c08..000000000 --- a/__mocks__/__tests__/gatsby-plugin-firebase.test.js +++ /dev/null @@ -1,552 +0,0 @@ -import { waitFor } from '@testing-library/react'; -import FirebaseStub, { - AuthConstants, - DatabaseConstants, -} from '../gatsby-plugin-firebase'; - -describe('FirebaseStub', () => { - describe('auth', () => { - it('reuses existing Auth instance', () => { - const auth1 = FirebaseStub.auth(); - const auth2 = FirebaseStub.auth(); - - expect(auth1.uuid).toBeTruthy(); - expect(auth2.uuid).toBeTruthy(); - expect(auth1.uuid).toEqual(auth2.uuid); - }); - - it('returns anonymous user 1 when signing in anonymously', async () => { - const user = await FirebaseStub.auth().signInAnonymously(); - - expect(user).toBeTruthy(); - expect(user).toEqual(AuthConstants.anonymousUser1); - }); - - it('calls onAuthStateChanged observer with anonymous user 1 when signing in anonymously', async () => { - let user = null; - let error = null; - FirebaseStub.auth().onAuthStateChanged( - (_user) => { - user = _user; - }, - (_error) => { - error = _error; - }, - ); - - await FirebaseStub.auth().signInAnonymously(); - - expect(user).toBeTruthy(); - expect(user).toEqual(AuthConstants.anonymousUser1); - expect(error).toBeNull(); - }); - - it('onAuthStateChanged unsubscribe removes observer', () => { - const observer = () => {}; - const unsubscribe = FirebaseStub.auth().onAuthStateChanged(observer); - expect(unsubscribe).toBeTruthy(); - expect( - FirebaseStub.auth().onAuthStateChangedObservers.indexOf(observer), - ).toBeGreaterThanOrEqual(0); - - unsubscribe(); - - expect( - FirebaseStub.auth().onAuthStateChangedObservers.indexOf(observer), - ).not.toBeGreaterThanOrEqual(0); - }); - }); - - describe('database', () => { - beforeEach(() => { - FirebaseStub.database().initializeData(); - }); - - it('reuses existing Database instance', () => { - const database1 = FirebaseStub.database(); - const database2 = FirebaseStub.database(); - - expect(database1.uuid).toBeTruthy(); - expect(database2.uuid).toBeTruthy(); - expect(database1.uuid).toEqual(database2.uuid); - }); - - describe('ref function', () => { - it('reuses existing Reference instance', () => { - const ref1 = FirebaseStub.database().ref( - `${DatabaseConstants.resumesPath}/123`, - ); - const ref2 = FirebaseStub.database().ref( - `${DatabaseConstants.resumesPath}/123`, - ); - - expect(ref1).toBeTruthy(); - expect(ref2).toBeTruthy(); - expect(ref1).toEqual(ref2); - }); - - it('leading slash in reference path is ignored', () => { - const path = `${DatabaseConstants.resumesPath}/123`; - - const ref1 = FirebaseStub.database().ref(path); - expect(ref1).toBeTruthy(); - expect(ref1.path).toEqual(path); - - const ref2 = FirebaseStub.database().ref(`/${path}`); - expect(ref2).toBeTruthy(); - expect(ref2).toEqual(ref1); - }); - }); - - it('ServerValue.TIMESTAMP returns current time in milliseconds', () => { - const now = new Date().getTime(); - const timestamp = FirebaseStub.database.ServerValue.TIMESTAMP; - - expect(timestamp).toBeTruthy(); - expect(timestamp).toBeGreaterThanOrEqual(now); - }); - - it('initializing data sets up resumes and users', async () => { - const resumesRef = FirebaseStub.database().ref( - DatabaseConstants.resumesPath, - ); - const resumesDataSnapshot = await resumesRef.once('value'); - const resumes = resumesDataSnapshot.val(); - expect(resumes).toBeTruthy(); - expect(Object.keys(resumes)).toHaveLength(3); - const demoStateResume1 = resumes[DatabaseConstants.demoStateResume1Id]; - expect(demoStateResume1).toBeTruthy(); - expect(demoStateResume1.id).toEqual(DatabaseConstants.demoStateResume1Id); - expect(demoStateResume1.user).toEqual(DatabaseConstants.user1.uid); - const demoStateResume2 = resumes[DatabaseConstants.demoStateResume2Id]; - expect(demoStateResume2).toBeTruthy(); - expect(demoStateResume2.id).toEqual(DatabaseConstants.demoStateResume2Id); - expect(demoStateResume2.user).toEqual(DatabaseConstants.user2.uid); - const initialStateResume = - resumes[DatabaseConstants.initialStateResumeId]; - expect(initialStateResume).toBeTruthy(); - expect(initialStateResume.id).toEqual( - DatabaseConstants.initialStateResumeId, - ); - expect(initialStateResume.user).toEqual(DatabaseConstants.user1.uid); - - const usersRef = FirebaseStub.database().ref(DatabaseConstants.usersPath); - const usersDataSnapshot = await usersRef.once('value'); - const users = usersDataSnapshot.val(); - expect(users).toBeTruthy(); - expect(Object.keys(users)).toHaveLength(2); - const anonymousUser1 = users[DatabaseConstants.user1.uid]; - expect(anonymousUser1).toBeTruthy(); - expect(anonymousUser1).toEqual(DatabaseConstants.user1); - const anonymousUser2 = users[DatabaseConstants.user2.uid]; - expect(anonymousUser2).toBeTruthy(); - expect(anonymousUser2).toEqual(DatabaseConstants.user2); - }); - - it('retrieves resume if it exists', async () => { - const resume = ( - await FirebaseStub.database() - .ref( - `${DatabaseConstants.resumesPath}/${DatabaseConstants.demoStateResume1Id}`, - ) - .once('value') - ).val(); - - expect(resume).toBeTruthy(); - expect(resume.id).toEqual(DatabaseConstants.demoStateResume1Id); - }); - - it('retrieves null if resume does not exist', async () => { - const resumeId = 'invalidResumeId'; - - const resume = ( - await FirebaseStub.database() - .ref(`${DatabaseConstants.resumesPath}/${resumeId}`) - .once('value') - ).val(); - - expect(resume).toBeNull(); - }); - - it('retrieves user if it exists', async () => { - const user = ( - await FirebaseStub.database() - .ref(`${DatabaseConstants.usersPath}/${DatabaseConstants.user1.uid}`) - .once('value') - ).val(); - - expect(user).toBeTruthy(); - expect(user).toEqual(DatabaseConstants.user1); - }); - - it('retrieves null if user does not exist', async () => { - const userId = 'invalidUserId'; - - const user = ( - await FirebaseStub.database() - .ref(`${DatabaseConstants.usersPath}/${userId}`) - .once('value') - ).val(); - - expect(user).toBeNull(); - }); - - describe('on function', () => { - describe('value event', () => { - it('triggers event with true if on the connected reference path', async () => { - let snapshotValue = null; - - FirebaseStub.database() - .ref(DatabaseConstants.connectedPath) - .on('value', (snapshot) => { - snapshotValue = snapshot.val(); - }); - - await waitFor(() => - snapshotValue ? Promise.resolve(true) : Promise.reject(), - ); - - expect(snapshotValue).not.toBeNull(); - expect(snapshotValue).toBe(true); - }); - - it('triggers event with resumes if on the resumes reference path', async () => { - const resumesDataSnapshot = await FirebaseStub.database() - .ref(DatabaseConstants.resumesPath) - .once('value'); - const resumes = resumesDataSnapshot.val(); - let snapshotValue = null; - - FirebaseStub.database() - .ref(DatabaseConstants.resumesPath) - .on('value', (snapshot) => { - snapshotValue = snapshot.val(); - }); - - await waitFor(() => - snapshotValue ? Promise.resolve(true) : Promise.reject(), - ); - - expect(snapshotValue).not.toBeNull(); - expect(snapshotValue).toEqual(resumes); - }); - }); - }); - - it('can filter resumes by user', async () => { - let snapshotValue = null; - - FirebaseStub.database() - .ref(DatabaseConstants.resumesPath) - .orderByChild('user') - .equalTo(DatabaseConstants.user1.uid) - .on('value', (snapshot) => { - snapshotValue = snapshot.val(); - }); - - await waitFor(() => - snapshotValue ? Promise.resolve(true) : Promise.reject(), - ); - - expect(snapshotValue).not.toBeNull(); - expect(Object.keys(snapshotValue)).toHaveLength(2); - Object.values(snapshotValue).forEach((resume) => - expect(resume.user).toEqual(DatabaseConstants.user1.uid), - ); - }); - - it('previously set query parameters are not kept when retrieving reference again', () => { - let reference = null; - - reference = FirebaseStub.database().ref(DatabaseConstants.resumesPath); - expect(reference).toBeTruthy(); - const { uuid } = reference; - expect(reference.orderByChildPath).toHaveLength(0); - expect(reference.equalToValue).toHaveLength(0); - - reference = FirebaseStub.database() - .ref(DatabaseConstants.resumesPath) - .orderByChild('user') - .equalTo('testuser1'); - expect(reference).toBeTruthy(); - expect(reference.uuid).toBe(uuid); - expect(reference.orderByChildPath).toBe('user'); - expect(reference.equalToValue).toBe('testuser1'); - - reference = FirebaseStub.database().ref(DatabaseConstants.resumesPath); - expect(reference).toBeTruthy(); - expect(reference.uuid).toBe(uuid); - expect(reference.orderByChildPath).toHaveLength(0); - expect(reference.equalToValue).toHaveLength(0); - }); - - describe('set function', () => { - it('inserts data', async () => { - const existingResume = ( - await FirebaseStub.database() - .ref( - `${DatabaseConstants.resumesPath}/${DatabaseConstants.demoStateResume1Id}`, - ) - .once('value') - ).val(); - expect(existingResume).toBeTruthy(); - - const newResume = JSON.parse(JSON.stringify(existingResume)); - newResume.id = 'newre1'; - newResume.name = `Test Resume ${newResume.id}`; - await FirebaseStub.database() - .ref(`${DatabaseConstants.resumesPath}/${newResume.id}`) - .set(newResume); - - const actualResume = ( - await FirebaseStub.database() - .ref(`${DatabaseConstants.resumesPath}/${newResume.id}`) - .once('value') - ).val(); - expect(actualResume).toBeTruthy(); - expect(actualResume).toEqual(newResume); - }); - - it('triggers events', async () => { - let snapshotValue = null; - const callback = jest.fn((snapshot) => { - snapshotValue = snapshot.val(); - }); - FirebaseStub.database() - .ref(DatabaseConstants.resumesPath) - .orderByChild('user') - .equalTo(DatabaseConstants.user1.uid) - .on('value', callback); - await waitFor(() => callback.mock.calls[0][0]); - callback.mockClear(); - snapshotValue = null; - - const existingResume = ( - await FirebaseStub.database() - .ref( - `${DatabaseConstants.resumesPath}/${DatabaseConstants.demoStateResume1Id}`, - ) - .once('value') - ).val(); - expect(existingResume).toBeTruthy(); - - const newResume = JSON.parse(JSON.stringify(existingResume)); - newResume.id = 'newre1'; - newResume.name = `Test Resume ${newResume.id}`; - await FirebaseStub.database() - .ref(`${DatabaseConstants.resumesPath}/${newResume.id}`) - .set(newResume); - - await waitFor(() => callback.mock.calls[0][0]); - - expect(callback.mock.calls).toHaveLength(1); - expect(snapshotValue).not.toBeNull(); - expect(Object.keys(snapshotValue)).toHaveLength(3); - expect(snapshotValue[newResume.id]).toBeTruthy(); - expect(snapshotValue[newResume.id]).toEqual(newResume); - }); - }); - - describe('update function', () => { - it('can spy on it', async () => { - const referencePath = `${DatabaseConstants.resumesPath}/123456`; - const functionSpy = jest.spyOn( - FirebaseStub.database().ref(referencePath), - 'update', - ); - const updateArgument = 'test value 123'; - - await FirebaseStub.database().ref(referencePath).update(updateArgument); - - expect(functionSpy).toHaveBeenCalledTimes(1); - const functionCallArgument = functionSpy.mock.calls[0][0]; - expect(functionCallArgument).toBeTruthy(); - expect(functionCallArgument).toEqual(updateArgument); - }); - - it('updates data', async () => { - const resumeId = DatabaseConstants.demoStateResume1Id; - const existingResume = ( - await FirebaseStub.database() - .ref(`${DatabaseConstants.resumesPath}/${resumeId}`) - .once('value') - ).val(); - expect(existingResume).toBeTruthy(); - - const resumeName = 'Test Resume renamed'; - existingResume.name = resumeName; - await FirebaseStub.database() - .ref(`${DatabaseConstants.resumesPath}/${resumeId}`) - .update(existingResume); - - const actualResume = ( - await FirebaseStub.database() - .ref(`${DatabaseConstants.resumesPath}/${resumeId}`) - .once('value') - ).val(); - expect(actualResume).toBeTruthy(); - expect(existingResume).toEqual(actualResume); - expect(actualResume.name).toEqual(resumeName); - }); - - it('triggers events', async () => { - let snapshotValue = null; - const callback = jest.fn((snapshot) => { - snapshotValue = snapshot.val(); - }); - FirebaseStub.database() - .ref(DatabaseConstants.resumesPath) - .orderByChild('user') - .equalTo(DatabaseConstants.user1.uid) - .on('value', callback); - await waitFor(() => callback.mock.calls[0][0]); - callback.mockClear(); - snapshotValue = null; - - const existingResume = ( - await FirebaseStub.database() - .ref( - `${DatabaseConstants.resumesPath}/${DatabaseConstants.demoStateResume1Id}`, - ) - .once('value') - ).val(); - expect(existingResume).toBeTruthy(); - - existingResume.name = 'Test Resume renamed'; - await FirebaseStub.database() - .ref(`${DatabaseConstants.resumesPath}/${existingResume.id}`) - .update(existingResume); - - await waitFor(() => callback.mock.calls[0][0]); - - expect(callback.mock.calls).toHaveLength(1); - expect(snapshotValue).not.toBeNull(); - expect(Object.keys(snapshotValue)).toHaveLength(2); - expect(snapshotValue[existingResume.id]).toBeTruthy(); - expect(snapshotValue[existingResume.id]).toEqual(existingResume); - }); - }); - - describe('remove function', () => { - it('deletes data', async () => { - const resumeId = DatabaseConstants.demoStateResume1Id; - const removedResume = ( - await FirebaseStub.database() - .ref(`${DatabaseConstants.resumesPath}/${resumeId}`) - .once('value') - ).val(); - expect(removedResume).toBeTruthy(); - - await FirebaseStub.database() - .ref(`${DatabaseConstants.resumesPath}/${resumeId}`) - .remove(); - - const actualResume = ( - await FirebaseStub.database() - .ref(`${DatabaseConstants.resumesPath}/${resumeId}`) - .once('value') - ).val(); - expect(actualResume).toBeNull(); - }); - - it('triggers events', async () => { - const userUid = DatabaseConstants.user1.uid; - - let valueCallbackSnapshotValue = null; - const valueCallback = jest.fn((snapshot) => { - valueCallbackSnapshotValue = snapshot.val(); - }); - FirebaseStub.database() - .ref(DatabaseConstants.resumesPath) - .orderByChild('user') - .equalTo(userUid) - .on('value', valueCallback); - await waitFor(() => valueCallback.mock.calls[0][0]); - valueCallback.mockClear(); - valueCallbackSnapshotValue = null; - - let childRemovedCallbackSnapshotValue = null; - const childRemovedCallback = jest.fn((snapshot) => { - childRemovedCallbackSnapshotValue = snapshot.val(); - }); - FirebaseStub.database() - .ref(DatabaseConstants.resumesPath) - .orderByChild('user') - .equalTo(userUid) - .on('child_removed', childRemovedCallback); - - const removedResume = ( - await FirebaseStub.database() - .ref( - `${DatabaseConstants.resumesPath}/${DatabaseConstants.demoStateResume1Id}`, - ) - .once('value') - ).val(); - expect(removedResume).toBeTruthy(); - expect(removedResume.user).toEqual(userUid); - await FirebaseStub.database() - .ref(`${DatabaseConstants.resumesPath}/${removedResume.id}`) - .remove(); - - await waitFor(() => childRemovedCallback.mock.calls[0][0]); - expect(childRemovedCallback.mock.calls).toHaveLength(1); - expect(childRemovedCallbackSnapshotValue).toBeTruthy(); - expect(childRemovedCallbackSnapshotValue).toEqual(removedResume); - - await waitFor(() => valueCallback.mock.calls[0][0]); - expect(valueCallback.mock.calls).toHaveLength(1); - expect(valueCallbackSnapshotValue).toBeTruthy(); - expect(removedResume.id in valueCallbackSnapshotValue).toBe(false); - }); - }); - - describe('off function', () => { - it('removes event callbacks', async () => { - const userUid = DatabaseConstants.user1.uid; - - let valueCallbackSnapshotValue = null; - const valueCallback = jest.fn((snapshot) => { - valueCallbackSnapshotValue = snapshot.val(); - }); - FirebaseStub.database() - .ref(DatabaseConstants.resumesPath) - .orderByChild('user') - .equalTo(userUid) - .on('value', valueCallback); - await waitFor(() => valueCallback.mock.calls[0][0]); - valueCallback.mockClear(); - valueCallbackSnapshotValue = null; - - let childRemovedCallbackSnapshotValue = null; - const childRemovedCallback = jest.fn((snapshot) => { - childRemovedCallbackSnapshotValue = snapshot.val(); - }); - FirebaseStub.database() - .ref(DatabaseConstants.resumesPath) - .orderByChild('user') - .equalTo(userUid) - .on('child_removed', childRemovedCallback); - - const removedResume = ( - await FirebaseStub.database() - .ref( - `${DatabaseConstants.resumesPath}/${DatabaseConstants.demoStateResume1Id}`, - ) - .once('value') - ).val(); - expect(removedResume).toBeTruthy(); - - FirebaseStub.database().ref(DatabaseConstants.resumesPath).off(); - - await FirebaseStub.database() - .ref(`${DatabaseConstants.resumesPath}/${removedResume.id}`) - .remove(); - - expect(childRemovedCallback.mock.calls).toHaveLength(0); - expect(childRemovedCallbackSnapshotValue).toBeNull(); - expect(valueCallback.mock.calls).toHaveLength(0); - expect(valueCallbackSnapshotValue).toBeNull(); - }); - }); - }); -}); diff --git a/__mocks__/__tests__/gatsby-plugin-firebase/auth.test.js b/__mocks__/__tests__/gatsby-plugin-firebase/auth.test.js new file mode 100644 index 000000000..d70a5c1da --- /dev/null +++ b/__mocks__/__tests__/gatsby-plugin-firebase/auth.test.js @@ -0,0 +1,51 @@ +import FirebaseStub, { AuthConstants } from '../../gatsby-plugin-firebase'; + +test('reuses existing Auth instance', () => { + const auth1 = FirebaseStub.auth(); + const auth2 = FirebaseStub.auth(); + + expect(auth1.uuid).toBeTruthy(); + expect(auth2.uuid).toBeTruthy(); + expect(auth1.uuid).toEqual(auth2.uuid); +}); + +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); + expect(unsubscribe).toBeTruthy(); + expect( + FirebaseStub.auth().onAuthStateChangedObservers.indexOf(observer), + ).toBeGreaterThanOrEqual(0); + + unsubscribe(); + + expect( + FirebaseStub.auth().onAuthStateChangedObservers.indexOf(observer), + ).not.toBeGreaterThanOrEqual(0); +}); diff --git a/__mocks__/__tests__/gatsby-plugin-firebase/database.ref.filtering.test.js b/__mocks__/__tests__/gatsby-plugin-firebase/database.ref.filtering.test.js new file mode 100644 index 000000000..83a73880b --- /dev/null +++ b/__mocks__/__tests__/gatsby-plugin-firebase/database.ref.filtering.test.js @@ -0,0 +1,53 @@ +import { waitFor } from '@testing-library/react'; +import FirebaseStub, { DatabaseConstants } from '../../gatsby-plugin-firebase'; + +test('can filter resumes by user', async () => { + FirebaseStub.database().initializeData(); + + let snapshotValue = null; + + FirebaseStub.database() + .ref(DatabaseConstants.resumesPath) + .orderByChild('user') + .equalTo(DatabaseConstants.user1.uid) + .on('value', (snapshot) => { + snapshotValue = snapshot.val(); + }); + + await waitFor(() => + snapshotValue ? Promise.resolve(true) : Promise.reject(), + ); + + expect(snapshotValue).not.toBeNull(); + expect(Object.keys(snapshotValue)).toHaveLength(2); + Object.values(snapshotValue).forEach((resume) => + expect(resume.user).toEqual(DatabaseConstants.user1.uid), + ); +}); + +test('previously set query parameters are not kept when retrieving reference again', () => { + FirebaseStub.database().initializeData(); + + let reference = null; + + reference = FirebaseStub.database().ref(DatabaseConstants.resumesPath); + expect(reference).toBeTruthy(); + const { uuid } = reference; + expect(reference.orderByChildPath).toHaveLength(0); + expect(reference.equalToValue).toHaveLength(0); + + reference = FirebaseStub.database() + .ref(DatabaseConstants.resumesPath) + .orderByChild('user') + .equalTo('testuser1'); + expect(reference).toBeTruthy(); + expect(reference.uuid).toBe(uuid); + expect(reference.orderByChildPath).toBe('user'); + expect(reference.equalToValue).toBe('testuser1'); + + reference = FirebaseStub.database().ref(DatabaseConstants.resumesPath); + expect(reference).toBeTruthy(); + expect(reference.uuid).toBe(uuid); + expect(reference.orderByChildPath).toHaveLength(0); + expect(reference.equalToValue).toHaveLength(0); +}); diff --git a/__mocks__/__tests__/gatsby-plugin-firebase/database.ref.off.test.js b/__mocks__/__tests__/gatsby-plugin-firebase/database.ref.off.test.js new file mode 100644 index 000000000..a9f68fa0d --- /dev/null +++ b/__mocks__/__tests__/gatsby-plugin-firebase/database.ref.off.test.js @@ -0,0 +1,51 @@ +import { waitFor } from '@testing-library/react'; +import FirebaseStub, { DatabaseConstants } from '../../gatsby-plugin-firebase'; + +test('removes event callbacks', async () => { + FirebaseStub.database().initializeData(); + + const userUid = DatabaseConstants.user1.uid; + + let valueCallbackSnapshotValue = null; + const valueCallback = jest.fn((snapshot) => { + valueCallbackSnapshotValue = snapshot.val(); + }); + FirebaseStub.database() + .ref(DatabaseConstants.resumesPath) + .orderByChild('user') + .equalTo(userUid) + .on('value', valueCallback); + await waitFor(() => valueCallback.mock.calls[0][0]); + valueCallback.mockClear(); + valueCallbackSnapshotValue = null; + + let childRemovedCallbackSnapshotValue = null; + const childRemovedCallback = jest.fn((snapshot) => { + childRemovedCallbackSnapshotValue = snapshot.val(); + }); + FirebaseStub.database() + .ref(DatabaseConstants.resumesPath) + .orderByChild('user') + .equalTo(userUid) + .on('child_removed', childRemovedCallback); + + const removedResume = ( + await FirebaseStub.database() + .ref( + `${DatabaseConstants.resumesPath}/${DatabaseConstants.demoStateResume1Id}`, + ) + .once('value') + ).val(); + expect(removedResume).toBeTruthy(); + + FirebaseStub.database().ref(DatabaseConstants.resumesPath).off(); + + await FirebaseStub.database() + .ref(`${DatabaseConstants.resumesPath}/${removedResume.id}`) + .remove(); + + expect(childRemovedCallback.mock.calls).toHaveLength(0); + expect(childRemovedCallbackSnapshotValue).toBeNull(); + expect(valueCallback.mock.calls).toHaveLength(0); + expect(valueCallbackSnapshotValue).toBeNull(); +}); diff --git a/__mocks__/__tests__/gatsby-plugin-firebase/database.ref.on.valueEventType.test.js b/__mocks__/__tests__/gatsby-plugin-firebase/database.ref.on.valueEventType.test.js new file mode 100644 index 000000000..9901b782b --- /dev/null +++ b/__mocks__/__tests__/gatsby-plugin-firebase/database.ref.on.valueEventType.test.js @@ -0,0 +1,45 @@ +import { waitFor } from '@testing-library/react'; +import FirebaseStub, { DatabaseConstants } from '../../gatsby-plugin-firebase'; + +test('triggers event with true if on the connected reference path', async () => { + FirebaseStub.database().initializeData(); + + let snapshotValue = null; + + FirebaseStub.database() + .ref(DatabaseConstants.connectedPath) + .on('value', (snapshot) => { + snapshotValue = snapshot.val(); + }); + + await waitFor(() => + snapshotValue ? Promise.resolve(true) : Promise.reject(), + ); + + expect(snapshotValue).not.toBeNull(); + expect(snapshotValue).toBe(true); +}); + +test('triggers event with resumes if on the resumes reference path', async () => { + FirebaseStub.database().initializeData(); + + const resumesDataSnapshot = await FirebaseStub.database() + .ref(DatabaseConstants.resumesPath) + .once('value'); + const resumes = resumesDataSnapshot.val(); + + let snapshotValue = null; + + FirebaseStub.database() + .ref(DatabaseConstants.resumesPath) + .on('value', (snapshot) => { + snapshotValue = snapshot.val(); + }); + + await waitFor(() => + snapshotValue ? Promise.resolve(true) : Promise.reject(), + ); + + expect(snapshotValue).not.toBeNull(); + expect(snapshotValue).toEqual(resumes); +}); diff --git a/__mocks__/__tests__/gatsby-plugin-firebase/database.ref.once.test.js b/__mocks__/__tests__/gatsby-plugin-firebase/database.ref.once.test.js new file mode 100644 index 000000000..9c018f89e --- /dev/null +++ b/__mocks__/__tests__/gatsby-plugin-firebase/database.ref.once.test.js @@ -0,0 +1,59 @@ +import FirebaseStub, { DatabaseConstants } from '../../gatsby-plugin-firebase'; + +test('retrieves resume if it exists', async () => { + FirebaseStub.database().initializeData(); + + const resumeId = DatabaseConstants.demoStateResume1Id; + + const resume = ( + await FirebaseStub.database() + .ref(`${DatabaseConstants.resumesPath}/${resumeId}`) + .once('value') + ).val(); + + expect(resume).toBeTruthy(); + expect(resume.id).toEqual(resumeId); +}); + +test('retrieves null if resume does not exist', async () => { + FirebaseStub.database().initializeData(); + + const resumeId = 'invalidResumeId'; + + const resume = ( + await FirebaseStub.database() + .ref(`${DatabaseConstants.resumesPath}/${resumeId}`) + .once('value') + ).val(); + + expect(resume).toBeNull(); +}); + +test('retrieves user if it exists', async () => { + FirebaseStub.database().initializeData(); + + const expectedUser = DatabaseConstants.user1; + + const user = ( + await FirebaseStub.database() + .ref(`${DatabaseConstants.usersPath}/${expectedUser.uid}`) + .once('value') + ).val(); + + expect(user).toBeTruthy(); + expect(user).toEqual(expectedUser); +}); + +test('retrieves null if user does not exist', async () => { + FirebaseStub.database().initializeData(); + + const userId = 'invalidUserId'; + + const user = ( + await FirebaseStub.database() + .ref(`${DatabaseConstants.usersPath}/${userId}`) + .once('value') + ).val(); + + expect(user).toBeNull(); +}); diff --git a/__mocks__/__tests__/gatsby-plugin-firebase/database.ref.remove.test.js b/__mocks__/__tests__/gatsby-plugin-firebase/database.ref.remove.test.js new file mode 100644 index 000000000..3d96a0c37 --- /dev/null +++ b/__mocks__/__tests__/gatsby-plugin-firebase/database.ref.remove.test.js @@ -0,0 +1,77 @@ +import { waitFor } from '@testing-library/react'; +import FirebaseStub, { DatabaseConstants } from '../../gatsby-plugin-firebase'; + +test('deletes data', async () => { + FirebaseStub.database().initializeData(); + + const resumeId = DatabaseConstants.demoStateResume1Id; + const removedResume = ( + await FirebaseStub.database() + .ref(`${DatabaseConstants.resumesPath}/${resumeId}`) + .once('value') + ).val(); + expect(removedResume).toBeTruthy(); + + await FirebaseStub.database() + .ref(`${DatabaseConstants.resumesPath}/${resumeId}`) + .remove(); + + const actualResume = ( + await FirebaseStub.database() + .ref(`${DatabaseConstants.resumesPath}/${resumeId}`) + .once('value') + ).val(); + expect(actualResume).toBeNull(); +}); + +test('triggers events', async () => { + FirebaseStub.database().initializeData(); + + const userUid = DatabaseConstants.user1.uid; + + let valueCallbackSnapshotValue = null; + const valueCallback = jest.fn((snapshot) => { + valueCallbackSnapshotValue = snapshot.val(); + }); + FirebaseStub.database() + .ref(DatabaseConstants.resumesPath) + .orderByChild('user') + .equalTo(userUid) + .on('value', valueCallback); + await waitFor(() => valueCallback.mock.calls[0][0]); + valueCallback.mockClear(); + valueCallbackSnapshotValue = null; + + let childRemovedCallbackSnapshotValue = null; + const childRemovedCallback = jest.fn((snapshot) => { + childRemovedCallbackSnapshotValue = snapshot.val(); + }); + FirebaseStub.database() + .ref(DatabaseConstants.resumesPath) + .orderByChild('user') + .equalTo(userUid) + .on('child_removed', childRemovedCallback); + + const removedResume = ( + await FirebaseStub.database() + .ref( + `${DatabaseConstants.resumesPath}/${DatabaseConstants.demoStateResume1Id}`, + ) + .once('value') + ).val(); + expect(removedResume).toBeTruthy(); + expect(removedResume.user).toEqual(userUid); + await FirebaseStub.database() + .ref(`${DatabaseConstants.resumesPath}/${removedResume.id}`) + .remove(); + + await waitFor(() => childRemovedCallback.mock.calls[0][0]); + expect(childRemovedCallback.mock.calls).toHaveLength(1); + expect(childRemovedCallbackSnapshotValue).toBeTruthy(); + expect(childRemovedCallbackSnapshotValue).toEqual(removedResume); + + await waitFor(() => valueCallback.mock.calls[0][0]); + expect(valueCallback.mock.calls).toHaveLength(1); + expect(valueCallbackSnapshotValue).toBeTruthy(); + expect(removedResume.id in valueCallbackSnapshotValue).toBe(false); +}); diff --git a/__mocks__/__tests__/gatsby-plugin-firebase/database.ref.set.test.js b/__mocks__/__tests__/gatsby-plugin-firebase/database.ref.set.test.js new file mode 100644 index 000000000..1ac8a1e15 --- /dev/null +++ b/__mocks__/__tests__/gatsby-plugin-firebase/database.ref.set.test.js @@ -0,0 +1,71 @@ +import { waitFor } from '@testing-library/react'; +import FirebaseStub, { DatabaseConstants } from '../../gatsby-plugin-firebase'; + +test('inserts data', async () => { + FirebaseStub.database().initializeData(); + + const existingResume = ( + await FirebaseStub.database() + .ref( + `${DatabaseConstants.resumesPath}/${DatabaseConstants.demoStateResume1Id}`, + ) + .once('value') + ).val(); + expect(existingResume).toBeTruthy(); + + const newResume = JSON.parse(JSON.stringify(existingResume)); + newResume.id = 'newre1'; + newResume.name = `Test Resume ${newResume.id}`; + await FirebaseStub.database() + .ref(`${DatabaseConstants.resumesPath}/${newResume.id}`) + .set(newResume); + + const actualResume = ( + await FirebaseStub.database() + .ref(`${DatabaseConstants.resumesPath}/${newResume.id}`) + .once('value') + ).val(); + expect(actualResume).toBeTruthy(); + expect(actualResume).toEqual(newResume); +}); + +test('triggers events', async () => { + FirebaseStub.database().initializeData(); + + let snapshotValue = null; + const callback = jest.fn((snapshot) => { + snapshotValue = snapshot.val(); + }); + FirebaseStub.database() + .ref(DatabaseConstants.resumesPath) + .orderByChild('user') + .equalTo(DatabaseConstants.user1.uid) + .on('value', callback); + await waitFor(() => callback.mock.calls[0][0]); + callback.mockClear(); + snapshotValue = null; + + const existingResume = ( + await FirebaseStub.database() + .ref( + `${DatabaseConstants.resumesPath}/${DatabaseConstants.demoStateResume1Id}`, + ) + .once('value') + ).val(); + expect(existingResume).toBeTruthy(); + + const newResume = JSON.parse(JSON.stringify(existingResume)); + newResume.id = 'newre1'; + newResume.name = `Test Resume ${newResume.id}`; + await FirebaseStub.database() + .ref(`${DatabaseConstants.resumesPath}/${newResume.id}`) + .set(newResume); + + await waitFor(() => callback.mock.calls[0][0]); + + expect(callback.mock.calls).toHaveLength(1); + expect(snapshotValue).not.toBeNull(); + expect(Object.keys(snapshotValue)).toHaveLength(3); + expect(snapshotValue[newResume.id]).toBeTruthy(); + expect(snapshotValue[newResume.id]).toEqual(newResume); +}); diff --git a/__mocks__/__tests__/gatsby-plugin-firebase/database.ref.test.js b/__mocks__/__tests__/gatsby-plugin-firebase/database.ref.test.js new file mode 100644 index 000000000..9ea786b52 --- /dev/null +++ b/__mocks__/__tests__/gatsby-plugin-firebase/database.ref.test.js @@ -0,0 +1,26 @@ +import FirebaseStub, { DatabaseConstants } from '../../gatsby-plugin-firebase'; + +test('reuses existing Reference instance', () => { + const ref1 = FirebaseStub.database().ref( + `${DatabaseConstants.resumesPath}/123`, + ); + const ref2 = FirebaseStub.database().ref( + `${DatabaseConstants.resumesPath}/123`, + ); + + expect(ref1).toBeTruthy(); + expect(ref2).toBeTruthy(); + expect(ref1).toEqual(ref2); +}); + +test('leading slash in reference path is ignored', () => { + const path = `${DatabaseConstants.resumesPath}/123`; + + const ref1 = FirebaseStub.database().ref(path); + expect(ref1).toBeTruthy(); + expect(ref1.path).toEqual(path); + + const ref2 = FirebaseStub.database().ref(`/${path}`); + expect(ref2).toBeTruthy(); + expect(ref2).toEqual(ref1); +}); diff --git a/__mocks__/__tests__/gatsby-plugin-firebase/database.ref.update.test.js b/__mocks__/__tests__/gatsby-plugin-firebase/database.ref.update.test.js new file mode 100644 index 000000000..1c9d30bfc --- /dev/null +++ b/__mocks__/__tests__/gatsby-plugin-firebase/database.ref.update.test.js @@ -0,0 +1,86 @@ +import { waitFor } from '@testing-library/react'; +import FirebaseStub, { DatabaseConstants } from '../../gatsby-plugin-firebase'; + +test('can spy on it', async () => { + FirebaseStub.database().initializeData(); + + const referencePath = `${DatabaseConstants.resumesPath}/123456`; + const functionSpy = jest.spyOn( + FirebaseStub.database().ref(referencePath), + 'update', + ); + const updateArgument = 'test value 123'; + + await FirebaseStub.database().ref(referencePath).update(updateArgument); + + expect(functionSpy).toHaveBeenCalledTimes(1); + const functionCallArgument = functionSpy.mock.calls[0][0]; + expect(functionCallArgument).toBeTruthy(); + expect(functionCallArgument).toEqual(updateArgument); +}); + +test('updates data', async () => { + FirebaseStub.database().initializeData(); + + const resumeId = DatabaseConstants.demoStateResume1Id; + const existingResume = ( + await FirebaseStub.database() + .ref(`${DatabaseConstants.resumesPath}/${resumeId}`) + .once('value') + ).val(); + expect(existingResume).toBeTruthy(); + + const resumeName = 'Test Resume renamed'; + existingResume.name = resumeName; + await FirebaseStub.database() + .ref(`${DatabaseConstants.resumesPath}/${resumeId}`) + .update(existingResume); + + const actualResume = ( + await FirebaseStub.database() + .ref(`${DatabaseConstants.resumesPath}/${resumeId}`) + .once('value') + ).val(); + expect(actualResume).toBeTruthy(); + expect(existingResume).toEqual(actualResume); + expect(actualResume.name).toEqual(resumeName); +}); + +test('triggers events', async () => { + FirebaseStub.database().initializeData(); + + let snapshotValue = null; + const callback = jest.fn((snapshot) => { + snapshotValue = snapshot.val(); + }); + FirebaseStub.database() + .ref(DatabaseConstants.resumesPath) + .orderByChild('user') + .equalTo(DatabaseConstants.user1.uid) + .on('value', callback); + await waitFor(() => callback.mock.calls[0][0]); + callback.mockClear(); + snapshotValue = null; + + const existingResume = ( + await FirebaseStub.database() + .ref( + `${DatabaseConstants.resumesPath}/${DatabaseConstants.demoStateResume1Id}`, + ) + .once('value') + ).val(); + expect(existingResume).toBeTruthy(); + + existingResume.name = 'Test Resume renamed'; + await FirebaseStub.database() + .ref(`${DatabaseConstants.resumesPath}/${existingResume.id}`) + .update(existingResume); + + await waitFor(() => callback.mock.calls[0][0]); + + expect(callback.mock.calls).toHaveLength(1); + expect(snapshotValue).not.toBeNull(); + expect(Object.keys(snapshotValue)).toHaveLength(2); + expect(snapshotValue[existingResume.id]).toBeTruthy(); + expect(snapshotValue[existingResume.id]).toEqual(existingResume); +}); diff --git a/__mocks__/__tests__/gatsby-plugin-firebase/database.test.js b/__mocks__/__tests__/gatsby-plugin-firebase/database.test.js new file mode 100644 index 000000000..8a97c0a94 --- /dev/null +++ b/__mocks__/__tests__/gatsby-plugin-firebase/database.test.js @@ -0,0 +1,52 @@ +import FirebaseStub, { DatabaseConstants } from '../../gatsby-plugin-firebase'; + +test('reuses existing Database instance', () => { + const database1 = FirebaseStub.database(); + const database2 = FirebaseStub.database(); + + expect(database1.uuid).toBeTruthy(); + expect(database2.uuid).toBeTruthy(); + expect(database1.uuid).toEqual(database2.uuid); +}); + +test('ServerValue.TIMESTAMP returns current time in milliseconds', () => { + const now = new Date().getTime(); + const timestamp = FirebaseStub.database.ServerValue.TIMESTAMP; + + expect(timestamp).toBeTruthy(); + expect(timestamp).toBeGreaterThanOrEqual(now); +}); + +test('initializing data sets up resumes and users', async () => { + FirebaseStub.database().initializeData(); + + const resumesRef = FirebaseStub.database().ref(DatabaseConstants.resumesPath); + const resumesDataSnapshot = await resumesRef.once('value'); + const resumes = resumesDataSnapshot.val(); + expect(resumes).toBeTruthy(); + expect(Object.keys(resumes)).toHaveLength(3); + const demoStateResume1 = resumes[DatabaseConstants.demoStateResume1Id]; + expect(demoStateResume1).toBeTruthy(); + expect(demoStateResume1.id).toEqual(DatabaseConstants.demoStateResume1Id); + expect(demoStateResume1.user).toEqual(DatabaseConstants.user1.uid); + const demoStateResume2 = resumes[DatabaseConstants.demoStateResume2Id]; + expect(demoStateResume2).toBeTruthy(); + expect(demoStateResume2.id).toEqual(DatabaseConstants.demoStateResume2Id); + expect(demoStateResume2.user).toEqual(DatabaseConstants.user2.uid); + const initialStateResume = resumes[DatabaseConstants.initialStateResumeId]; + expect(initialStateResume).toBeTruthy(); + expect(initialStateResume.id).toEqual(DatabaseConstants.initialStateResumeId); + expect(initialStateResume.user).toEqual(DatabaseConstants.user1.uid); + + const usersRef = FirebaseStub.database().ref(DatabaseConstants.usersPath); + const usersDataSnapshot = await usersRef.once('value'); + const users = usersDataSnapshot.val(); + expect(users).toBeTruthy(); + expect(Object.keys(users)).toHaveLength(2); + const anonymousUser1 = users[DatabaseConstants.user1.uid]; + expect(anonymousUser1).toBeTruthy(); + expect(anonymousUser1).toEqual(DatabaseConstants.user1); + const anonymousUser2 = users[DatabaseConstants.user2.uid]; + expect(anonymousUser2).toBeTruthy(); + expect(anonymousUser2).toEqual(DatabaseConstants.user2); +}); From fd79891a1a4c3dd57c1ab6666940c99b519c0c03 Mon Sep 17 00:00:00 2001 From: gianantoniopini <63844628+gianantoniopini@users.noreply.github.com> Date: Tue, 2 Feb 2021 14:01:17 +0100 Subject: [PATCH 16/18] Dashboard: unit tests related to rename resume --- .../__tests__/dashboard.createResume.test.js | 4 +- .../__tests__/dashboard.deleteResume.test.js | 6 +- .../dashboard.duplicateResume.test.js | 6 +- .../__tests__/dashboard.renameResume.test.js | 214 ++++++++++++++++++ .../app/__tests__/dashboard.rendering.test.js | 8 +- src/pages/app/__tests__/helpers/dashboard.js | 19 +- 6 files changed, 237 insertions(+), 20 deletions(-) create mode 100644 src/pages/app/__tests__/dashboard.renameResume.test.js diff --git a/src/pages/app/__tests__/dashboard.createResume.test.js b/src/pages/app/__tests__/dashboard.createResume.test.js index 72fc0ad64..17a5d6167 100644 --- a/src/pages/app/__tests__/dashboard.createResume.test.js +++ b/src/pages/app/__tests__/dashboard.createResume.test.js @@ -15,7 +15,7 @@ import { waitForModalWindowToHaveBeenClosed, dismissNotification, unsplashPhotoResponseUrl, - setupWithFetchMockAndWaitForLoadingScreenToDisappear, + setupWithFetchMockAndWait, } from './helpers/dashboard'; const tooShortResumeName = 'CV 1'; @@ -23,7 +23,7 @@ const validResumeName = 'Resume for SW development roles'; async function setup() { const user = DatabaseConstants.user1; - await setupWithFetchMockAndWaitForLoadingScreenToDisappear(user); + await setupWithFetchMockAndWait(user, true); const dashboardCreateResumeButton = await screen.findByTestId( createResumeButtonDataTestId, diff --git a/src/pages/app/__tests__/dashboard.deleteResume.test.js b/src/pages/app/__tests__/dashboard.deleteResume.test.js index 9d3b39b30..1b84ac1d5 100644 --- a/src/pages/app/__tests__/dashboard.deleteResume.test.js +++ b/src/pages/app/__tests__/dashboard.deleteResume.test.js @@ -10,7 +10,7 @@ import FirebaseStub, { DatabaseConstants } from 'gatsby-plugin-firebase'; import { menuToggleDataTestIdPrefix as resumePreviewMenuToggleDataTestIdPrefix } from '../../../components/dashboard/ResumePreview'; import { - setupAndWaitForLoadingScreenToDisappear, + setupAndWait, waitForResumeToDisappearFromPreview, expectResumeToBeRenderedInPreview, dismissNotification, @@ -37,9 +37,7 @@ const expectDatabaseRemoveToHaveCompleted = async ( }; async function setup() { - const userResumes = await setupAndWaitForLoadingScreenToDisappear( - DatabaseConstants.user1, - ); + const userResumes = await setupAndWait(DatabaseConstants.user1, true); const [resumeToDelete] = Object.values(userResumes).filter( (resume) => resume.id === DatabaseConstants.demoStateResume1Id, diff --git a/src/pages/app/__tests__/dashboard.duplicateResume.test.js b/src/pages/app/__tests__/dashboard.duplicateResume.test.js index 432714182..9b9c254c9 100644 --- a/src/pages/app/__tests__/dashboard.duplicateResume.test.js +++ b/src/pages/app/__tests__/dashboard.duplicateResume.test.js @@ -9,7 +9,7 @@ import FirebaseStub, { DatabaseConstants } from 'gatsby-plugin-firebase'; import { menuToggleDataTestIdPrefix as resumePreviewMenuToggleDataTestIdPrefix } from '../../../components/dashboard/ResumePreview'; import { - setupWithFetchMockAndWaitForLoadingScreenToDisappear, + setupWithFetchMockAndWait, waitForResumeToBeRenderedInPreview, expectResumeToBeRenderedInPreview, unsplashPhotoResponseUrl, @@ -17,9 +17,7 @@ import { async function setup() { const user = DatabaseConstants.user1; - const userResumes = await setupWithFetchMockAndWaitForLoadingScreenToDisappear( - user, - ); + const userResumes = await setupWithFetchMockAndWait(user, true); const [resumeToDuplicate] = Object.values(userResumes).filter( (resume) => resume.id === DatabaseConstants.demoStateResume1Id, diff --git a/src/pages/app/__tests__/dashboard.renameResume.test.js b/src/pages/app/__tests__/dashboard.renameResume.test.js new file mode 100644 index 000000000..4ad7556f8 --- /dev/null +++ b/src/pages/app/__tests__/dashboard.renameResume.test.js @@ -0,0 +1,214 @@ +import { + fireEvent, + getByText, + queryByText, + screen, + waitFor, + waitForElementToBeRemoved, +} from '@testing-library/react'; + +import FirebaseStub, { DatabaseConstants } from 'gatsby-plugin-firebase'; + +import { menuToggleDataTestIdPrefix as resumePreviewMenuToggleDataTestIdPrefix } from '../../../components/dashboard/ResumePreview'; +import { + setupAndWait, + waitForResumeToBeRenderedInPreview, + expectResumeToBeRenderedInPreview, + dismissNotification, + waitForModalWindowToHaveBeenClosed, +} from './helpers/dashboard'; + +const tooShortResumeName = 'CV 2'; +const validResumeName = 'Resume for SW development roles - renamed'; + +const waitForDatabaseUpdateToHaveCompleted = async ( + mockDatabaseUpdateFunction, +) => { + await waitFor(() => mockDatabaseUpdateFunction.mock.calls[0][0]); + await waitFor(() => mockDatabaseUpdateFunction.mock.results[0].value); +}; + +const expectDatabaseUpdateToHaveCompleted = async ( + mockDatabaseUpdateFunction, +) => { + await waitFor(() => + expect(mockDatabaseUpdateFunction).toHaveBeenCalledTimes(1), + ); + await waitFor(() => + expect( + mockDatabaseUpdateFunction.mock.results[0].value, + ).resolves.toBeUndefined(), + ); +}; + +async function setup() { + const userResumes = await setupAndWait(DatabaseConstants.user1, true); + + const [resumeToRename] = Object.values(userResumes).filter( + (resume) => resume.id === DatabaseConstants.demoStateResume1Id, + ); + const resumeToRenameId = resumeToRename.id; + + const mockDatabaseUpdateFunction = jest.spyOn( + FirebaseStub.database().ref( + `${DatabaseConstants.resumesPath}/${resumeToRenameId}`, + ), + 'update', + ); + mockDatabaseUpdateFunction.mockClear(); + + const resumeToRenameMenuToggle = await screen.findByTestId( + `${resumePreviewMenuToggleDataTestIdPrefix}${resumeToRenameId}`, + ); + fireEvent.click(resumeToRenameMenuToggle); + + const menuItems = screen.getAllByRole('menuitem'); + let renameMenuItem = null; + for (let index = 0; index < menuItems.length; index++) { + if (queryByText(menuItems[index], /rename/i)) { + renameMenuItem = menuItems[index]; + break; + } + } + fireEvent.click(renameMenuItem); + + const nameTextBox = screen.getByRole('textbox', { name: /name/i }); + + return { resumeToRename, mockDatabaseUpdateFunction, nameTextBox }; +} + +async function setupAndSubmit(resumeNewName) { + const { + resumeToRename, + mockDatabaseUpdateFunction, + nameTextBox, + } = await setup(); + + fireEvent.change(nameTextBox, { + target: { value: resumeNewName }, + }); + + const modalEditResumeButton = screen.getByRole('button', { + name: /edit resume/i, + }); + fireEvent.click(modalEditResumeButton); + + return { + renamedResume: resumeToRename, + mockDatabaseUpdateFunction, + nameTextBox, + }; +} + +test('renders current name in modal window', async () => { + const { resumeToRename, nameTextBox } = await setup(); + + expect(nameTextBox).toHaveValue(resumeToRename.name); +}); + +describe('with invalid name', () => { + test('displays validation error', async () => { + const { nameTextBox } = await setup(); + + fireEvent.change(nameTextBox, { target: { value: tooShortResumeName } }); + fireEvent.blur(nameTextBox); + + await waitFor(() => + expect( + screen.getByText(/Please enter at least 5 characters/i), + ).toBeInTheDocument(), + ); + }); + + test('displays notification', async () => { + await setupAndSubmit(tooShortResumeName); + + const notification = await screen.findByRole('alert'); + /* + const notification = await screen.findByText( + /You might need to fill up all the required fields/i, + ); + */ + expect( + getByText( + notification, + /You might need to fill up all the required fields/i, + ), + ).toBeInTheDocument(); + dismissNotification(notification); + }); +}); + +describe('with valid name', () => { + test('renders loading message', async () => { + const { mockDatabaseUpdateFunction } = await setupAndSubmit( + validResumeName, + ); + + await waitFor(() => + expect( + screen.getByRole('button', { + name: /loading/i, + }), + ).toBeInTheDocument(), + ); + await waitForElementToBeRemoved(() => + screen.getByRole('button', { + name: /loading/i, + }), + ); + + await waitForModalWindowToHaveBeenClosed(); + await waitForDatabaseUpdateToHaveCompleted(mockDatabaseUpdateFunction); + await waitForResumeToBeRenderedInPreview(validResumeName); + }); + + test('closes modal window', async () => { + const { mockDatabaseUpdateFunction } = await setupAndSubmit( + validResumeName, + ); + + await waitFor(() => + expect(waitForModalWindowToHaveBeenClosed()).resolves.toBeUndefined(), + ); + + await waitForDatabaseUpdateToHaveCompleted(mockDatabaseUpdateFunction); + await waitForResumeToBeRenderedInPreview(validResumeName); + }); + + test('renders renamed resume in preview', async () => { + const { mockDatabaseUpdateFunction } = await setupAndSubmit( + validResumeName, + ); + + await waitForModalWindowToHaveBeenClosed(); + await waitForDatabaseUpdateToHaveCompleted(mockDatabaseUpdateFunction); + + await waitFor(() => + expect( + expectResumeToBeRenderedInPreview(validResumeName), + ).resolves.toBeUndefined(), + ); + }); + + test('updates database', async () => { + const now = new Date().getTime(); + const { renamedResume, mockDatabaseUpdateFunction } = await setupAndSubmit( + validResumeName, + ); + const renamedResumeId = renamedResume.id; + + await waitForModalWindowToHaveBeenClosed(); + + await expectDatabaseUpdateToHaveCompleted(mockDatabaseUpdateFunction); + const mockDatabaseUpdateFunctionCallArgument = + mockDatabaseUpdateFunction.mock.calls[0][0]; + expect(mockDatabaseUpdateFunctionCallArgument.id).toBe(renamedResumeId); + expect(mockDatabaseUpdateFunctionCallArgument.name).toBe(validResumeName); + expect( + mockDatabaseUpdateFunctionCallArgument.updatedAt, + ).toBeGreaterThanOrEqual(now); + + await waitForResumeToBeRenderedInPreview(validResumeName); + }); +}); diff --git a/src/pages/app/__tests__/dashboard.rendering.test.js b/src/pages/app/__tests__/dashboard.rendering.test.js index 76271428e..838736771 100644 --- a/src/pages/app/__tests__/dashboard.rendering.test.js +++ b/src/pages/app/__tests__/dashboard.rendering.test.js @@ -4,7 +4,7 @@ import { DatabaseConstants } from 'gatsby-plugin-firebase'; import { createResumeButtonDataTestId } from '../../../components/dashboard/CreateResume'; import setup, { - setupAndWaitForLoadingScreenToDisappear, + setupAndWait, expectResumeToBeRenderedInPreview, expectLoadingScreenToBeRendered, waitForLoadingScreenToDisappear, @@ -20,7 +20,7 @@ it('renders loading screen', async () => { }); it('renders document title', async () => { - await setupAndWaitForLoadingScreenToDisappear(user); + await setupAndWait(user, true); await waitFor(() => { expect(document.title).toEqual('Dashboard | Reactive Resume'); @@ -28,7 +28,7 @@ it('renders document title', async () => { }); it('renders create resume', async () => { - await setupAndWaitForLoadingScreenToDisappear(user); + await setupAndWait(user, true); await waitFor(() => { expect(screen.getByText(/create resume/i)).toBeInTheDocument(); @@ -41,7 +41,7 @@ it('renders create resume', async () => { }); it('renders preview of user resumes', async () => { - const userResumes = await setupAndWaitForLoadingScreenToDisappear(user); + const userResumes = await setupAndWait(user, true); expect(Object.keys(userResumes)).toHaveLength(2); diff --git a/src/pages/app/__tests__/helpers/dashboard.js b/src/pages/app/__tests__/helpers/dashboard.js index 4eba4f3eb..884ec0ed5 100644 --- a/src/pages/app/__tests__/helpers/dashboard.js +++ b/src/pages/app/__tests__/helpers/dashboard.js @@ -133,21 +133,28 @@ async function setup(user) { return userResumes; } -async function setupAndWaitForLoadingScreenToDisappear(user) { - const userResumes = await _setup(user, true, false); +async function setupAndWait(user, waitForLoadingScreenToDisappear) { + const userResumes = await _setup( + user, + waitForLoadingScreenToDisappear, + false, + ); return userResumes; } -async function setupWithFetchMockAndWaitForLoadingScreenToDisappear(user) { - const userResumes = await _setup(user, true, true); +async function setupWithFetchMockAndWait( + user, + waitForLoadingScreenToDisappear, +) { + const userResumes = await _setup(user, waitForLoadingScreenToDisappear, true); return userResumes; } export default setup; export { - setupAndWaitForLoadingScreenToDisappear, - setupWithFetchMockAndWaitForLoadingScreenToDisappear, + setupAndWait, + setupWithFetchMockAndWait, waitForResumeToBeRenderedInPreview, waitForResumeToDisappearFromPreview, expectResumeToBeRenderedInPreview, From a2b637fa71332c51f71e99e3acb3513b0b27b519 Mon Sep 17 00:00:00 2001 From: gianantoniopini <63844628+gianantoniopini@users.noreply.github.com> Date: Tue, 2 Feb 2021 14:02:56 +0100 Subject: [PATCH 17/18] Dashboard: unit tests related to rename resume --- src/pages/app/__tests__/dashboard.renameResume.test.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/pages/app/__tests__/dashboard.renameResume.test.js b/src/pages/app/__tests__/dashboard.renameResume.test.js index 4ad7556f8..b458e1506 100644 --- a/src/pages/app/__tests__/dashboard.renameResume.test.js +++ b/src/pages/app/__tests__/dashboard.renameResume.test.js @@ -124,11 +124,6 @@ describe('with invalid name', () => { await setupAndSubmit(tooShortResumeName); const notification = await screen.findByRole('alert'); - /* - const notification = await screen.findByText( - /You might need to fill up all the required fields/i, - ); - */ expect( getByText( notification, From f2bc5e36b33f4f3639ca84503f4ad2c024687510 Mon Sep 17 00:00:00 2001 From: gianantoniopini <63844628+gianantoniopini@users.noreply.github.com> Date: Tue, 2 Feb 2021 14:03:35 +0100 Subject: [PATCH 18/18] Dashboard: removed unit tests related to rename resume --- .../__tests__/dashboard.renameResume.test.js | 209 ------------------ 1 file changed, 209 deletions(-) delete mode 100644 src/pages/app/__tests__/dashboard.renameResume.test.js diff --git a/src/pages/app/__tests__/dashboard.renameResume.test.js b/src/pages/app/__tests__/dashboard.renameResume.test.js deleted file mode 100644 index b458e1506..000000000 --- a/src/pages/app/__tests__/dashboard.renameResume.test.js +++ /dev/null @@ -1,209 +0,0 @@ -import { - fireEvent, - getByText, - queryByText, - screen, - waitFor, - waitForElementToBeRemoved, -} from '@testing-library/react'; - -import FirebaseStub, { DatabaseConstants } from 'gatsby-plugin-firebase'; - -import { menuToggleDataTestIdPrefix as resumePreviewMenuToggleDataTestIdPrefix } from '../../../components/dashboard/ResumePreview'; -import { - setupAndWait, - waitForResumeToBeRenderedInPreview, - expectResumeToBeRenderedInPreview, - dismissNotification, - waitForModalWindowToHaveBeenClosed, -} from './helpers/dashboard'; - -const tooShortResumeName = 'CV 2'; -const validResumeName = 'Resume for SW development roles - renamed'; - -const waitForDatabaseUpdateToHaveCompleted = async ( - mockDatabaseUpdateFunction, -) => { - await waitFor(() => mockDatabaseUpdateFunction.mock.calls[0][0]); - await waitFor(() => mockDatabaseUpdateFunction.mock.results[0].value); -}; - -const expectDatabaseUpdateToHaveCompleted = async ( - mockDatabaseUpdateFunction, -) => { - await waitFor(() => - expect(mockDatabaseUpdateFunction).toHaveBeenCalledTimes(1), - ); - await waitFor(() => - expect( - mockDatabaseUpdateFunction.mock.results[0].value, - ).resolves.toBeUndefined(), - ); -}; - -async function setup() { - const userResumes = await setupAndWait(DatabaseConstants.user1, true); - - const [resumeToRename] = Object.values(userResumes).filter( - (resume) => resume.id === DatabaseConstants.demoStateResume1Id, - ); - const resumeToRenameId = resumeToRename.id; - - const mockDatabaseUpdateFunction = jest.spyOn( - FirebaseStub.database().ref( - `${DatabaseConstants.resumesPath}/${resumeToRenameId}`, - ), - 'update', - ); - mockDatabaseUpdateFunction.mockClear(); - - const resumeToRenameMenuToggle = await screen.findByTestId( - `${resumePreviewMenuToggleDataTestIdPrefix}${resumeToRenameId}`, - ); - fireEvent.click(resumeToRenameMenuToggle); - - const menuItems = screen.getAllByRole('menuitem'); - let renameMenuItem = null; - for (let index = 0; index < menuItems.length; index++) { - if (queryByText(menuItems[index], /rename/i)) { - renameMenuItem = menuItems[index]; - break; - } - } - fireEvent.click(renameMenuItem); - - const nameTextBox = screen.getByRole('textbox', { name: /name/i }); - - return { resumeToRename, mockDatabaseUpdateFunction, nameTextBox }; -} - -async function setupAndSubmit(resumeNewName) { - const { - resumeToRename, - mockDatabaseUpdateFunction, - nameTextBox, - } = await setup(); - - fireEvent.change(nameTextBox, { - target: { value: resumeNewName }, - }); - - const modalEditResumeButton = screen.getByRole('button', { - name: /edit resume/i, - }); - fireEvent.click(modalEditResumeButton); - - return { - renamedResume: resumeToRename, - mockDatabaseUpdateFunction, - nameTextBox, - }; -} - -test('renders current name in modal window', async () => { - const { resumeToRename, nameTextBox } = await setup(); - - expect(nameTextBox).toHaveValue(resumeToRename.name); -}); - -describe('with invalid name', () => { - test('displays validation error', async () => { - const { nameTextBox } = await setup(); - - fireEvent.change(nameTextBox, { target: { value: tooShortResumeName } }); - fireEvent.blur(nameTextBox); - - await waitFor(() => - expect( - screen.getByText(/Please enter at least 5 characters/i), - ).toBeInTheDocument(), - ); - }); - - test('displays notification', async () => { - await setupAndSubmit(tooShortResumeName); - - const notification = await screen.findByRole('alert'); - expect( - getByText( - notification, - /You might need to fill up all the required fields/i, - ), - ).toBeInTheDocument(); - dismissNotification(notification); - }); -}); - -describe('with valid name', () => { - test('renders loading message', async () => { - const { mockDatabaseUpdateFunction } = await setupAndSubmit( - validResumeName, - ); - - await waitFor(() => - expect( - screen.getByRole('button', { - name: /loading/i, - }), - ).toBeInTheDocument(), - ); - await waitForElementToBeRemoved(() => - screen.getByRole('button', { - name: /loading/i, - }), - ); - - await waitForModalWindowToHaveBeenClosed(); - await waitForDatabaseUpdateToHaveCompleted(mockDatabaseUpdateFunction); - await waitForResumeToBeRenderedInPreview(validResumeName); - }); - - test('closes modal window', async () => { - const { mockDatabaseUpdateFunction } = await setupAndSubmit( - validResumeName, - ); - - await waitFor(() => - expect(waitForModalWindowToHaveBeenClosed()).resolves.toBeUndefined(), - ); - - await waitForDatabaseUpdateToHaveCompleted(mockDatabaseUpdateFunction); - await waitForResumeToBeRenderedInPreview(validResumeName); - }); - - test('renders renamed resume in preview', async () => { - const { mockDatabaseUpdateFunction } = await setupAndSubmit( - validResumeName, - ); - - await waitForModalWindowToHaveBeenClosed(); - await waitForDatabaseUpdateToHaveCompleted(mockDatabaseUpdateFunction); - - await waitFor(() => - expect( - expectResumeToBeRenderedInPreview(validResumeName), - ).resolves.toBeUndefined(), - ); - }); - - test('updates database', async () => { - const now = new Date().getTime(); - const { renamedResume, mockDatabaseUpdateFunction } = await setupAndSubmit( - validResumeName, - ); - const renamedResumeId = renamedResume.id; - - await waitForModalWindowToHaveBeenClosed(); - - await expectDatabaseUpdateToHaveCompleted(mockDatabaseUpdateFunction); - const mockDatabaseUpdateFunctionCallArgument = - mockDatabaseUpdateFunction.mock.calls[0][0]; - expect(mockDatabaseUpdateFunctionCallArgument.id).toBe(renamedResumeId); - expect(mockDatabaseUpdateFunctionCallArgument.name).toBe(validResumeName); - expect( - mockDatabaseUpdateFunctionCallArgument.updatedAt, - ).toBeGreaterThanOrEqual(now); - - await waitForResumeToBeRenderedInPreview(validResumeName); - }); -});