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 00000000..72fc0ad6 --- /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 00000000..9d3b39b3 --- /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 00000000..43271418 --- /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 00000000..76271428 --- /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 7b0da3b6..00000000 --- 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 00000000..f97226da --- /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, +};