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] 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 00000000..9f1fef64 --- /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 00000000..f573f9b5 --- /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 00000000..95029652 --- /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 00000000..7db8a1c1 --- /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 e22d6e9d..00000000 --- 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 00000000..b05eca1b --- /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 f97226da..4eba4f3e 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(), ); };