diff --git a/__mocks__/__tests__/gatsby-plugin-firebase.test.js b/__mocks__/__tests__/gatsby-plugin-firebase.test.js index 84e0a9ca3..f80e06c08 100644 --- a/__mocks__/__tests__/gatsby-plugin-firebase.test.js +++ b/__mocks__/__tests__/gatsby-plugin-firebase.test.js @@ -6,10 +6,6 @@ import FirebaseStub, { describe('FirebaseStub', () => { describe('auth', () => { - afterEach(() => { - FirebaseStub.auth().dispose(); - }); - it('reuses existing Auth instance', () => { const auth1 = FirebaseStub.auth(); const auth2 = FirebaseStub.auth(); @@ -49,14 +45,15 @@ describe('FirebaseStub', () => { const observer = () => {}; const unsubscribe = FirebaseStub.auth().onAuthStateChanged(observer); expect(unsubscribe).toBeTruthy(); - expect(FirebaseStub.auth().onAuthStateChangedObservers).toHaveLength(1); - expect(FirebaseStub.auth().onAuthStateChangedObservers[0]).toEqual( - observer, - ); + expect( + FirebaseStub.auth().onAuthStateChangedObservers.indexOf(observer), + ).toBeGreaterThanOrEqual(0); unsubscribe(); - expect(FirebaseStub.auth().onAuthStateChangedObservers).toHaveLength(0); + expect( + FirebaseStub.auth().onAuthStateChangedObservers.indexOf(observer), + ).not.toBeGreaterThanOrEqual(0); }); }); @@ -258,7 +255,7 @@ describe('FirebaseStub', () => { ); }); - it('previously set query parameters are not kept when retrieving reference again', async () => { + it('previously set query parameters are not kept when retrieving reference again', () => { let reference = null; reference = FirebaseStub.database().ref(DatabaseConstants.resumesPath); diff --git a/__mocks__/gatsby-plugin-firebase/auth/auth.js b/__mocks__/gatsby-plugin-firebase/auth/auth.js index 3d1183d4d..853bb1334 100644 --- a/__mocks__/gatsby-plugin-firebase/auth/auth.js +++ b/__mocks__/gatsby-plugin-firebase/auth/auth.js @@ -2,6 +2,7 @@ import { v4 as uuidv4 } from 'uuid'; import Constants from '../constants/auth'; +import delay from '../../utils/index'; const singleton = Symbol(''); const singletonEnforcer = Symbol(''); @@ -32,10 +33,6 @@ class Auth { return this._onAuthStateChangedObservers; } - dispose() { - this._onAuthStateChangedObservers = []; - } - onAuthStateChanged(observer) { this.onAuthStateChangedObservers.push(observer); @@ -49,6 +46,8 @@ class Auth { async signInAnonymously() { const user = Constants.anonymousUser1; + await delay(Constants.defaultDelayInMilliseconds); + this.onAuthStateChangedObservers.forEach((observer) => observer(user)); return Promise.resolve(user); diff --git a/__mocks__/gatsby-plugin-firebase/constants/auth.js b/__mocks__/gatsby-plugin-firebase/constants/auth.js index 9280bfd9b..13f8fcc40 100644 --- a/__mocks__/gatsby-plugin-firebase/constants/auth.js +++ b/__mocks__/gatsby-plugin-firebase/constants/auth.js @@ -14,6 +14,8 @@ const anonymousUser2 = { uid: 'anonym456', }; +const defaultDelayInMilliseconds = 100; + class Auth { static get anonymousUser1() { return anonymousUser1; @@ -22,6 +24,10 @@ class Auth { static get anonymousUser2() { return anonymousUser2; } + + static get defaultDelayInMilliseconds() { + return defaultDelayInMilliseconds; + } } export default Auth; diff --git a/__mocks__/gatsby-plugin-firebase/constants/database.js b/__mocks__/gatsby-plugin-firebase/constants/database.js index 6f4fbe5d5..62a5459e0 100644 --- a/__mocks__/gatsby-plugin-firebase/constants/database.js +++ b/__mocks__/gatsby-plugin-firebase/constants/database.js @@ -20,6 +20,8 @@ const user2 = { isAnonymous: AuthConstants.anonymousUser2.isAnonymous, }; +const defaultDelayInMilliseconds = 100; + class Database { static get valueEventType() { return valueEventType; @@ -60,6 +62,10 @@ class Database { static get user2() { return user2; } + + static get defaultDelayInMilliseconds() { + return defaultDelayInMilliseconds; + } } export default Database; diff --git a/__mocks__/gatsby-plugin-firebase/database/reference.js b/__mocks__/gatsby-plugin-firebase/database/reference.js index 3b3447d62..08e345fdb 100644 --- a/__mocks__/gatsby-plugin-firebase/database/reference.js +++ b/__mocks__/gatsby-plugin-firebase/database/reference.js @@ -1,9 +1,9 @@ /* eslint-disable no-underscore-dangle */ import { v4 as uuidv4 } from 'uuid'; -import { debounce } from 'lodash'; import DatabaseConstants from '../constants/database'; import DataSnapshot from './dataSnapshot'; +import delay from '../../utils/index'; const parsePath = (path) => { if (!path) { @@ -147,11 +147,7 @@ class Reference { ? this._dataSnapshot : new DataSnapshot(() => this._getData(), snapshotValue); - const debouncedEventCallback = debounce( - this.eventCallbacks[eventType], - 100, - ); - debouncedEventCallback(snapshot); + this.eventCallbacks[eventType](snapshot); } equalTo(value) { @@ -172,8 +168,12 @@ class Reference { this.eventCallbacks[eventType] = callback; if (eventType === DatabaseConstants.valueEventType) { - this.triggerEventCallback(eventType); + setTimeout(() => { + this.triggerEventCallback(eventType); + }, DatabaseConstants.defaultDelayInMilliseconds); } + + return callback; } async once(eventType) { @@ -183,6 +183,8 @@ class Reference { throw new Error('eventType should be a string.'); } + await delay(DatabaseConstants.defaultDelayInMilliseconds); + return Promise.resolve(this._dataSnapshot); } @@ -192,21 +194,27 @@ class Reference { } async update(value) { + await delay(DatabaseConstants.defaultDelayInMilliseconds); + this._handleDataUpdate(value); - return Promise.resolve(true); + return Promise.resolve(); } async remove() { + await delay(DatabaseConstants.defaultDelayInMilliseconds); + this._handleDataUpdate(null); - return Promise.resolve(true); + return Promise.resolve(); } async set(value) { + await delay(DatabaseConstants.defaultDelayInMilliseconds); + this._handleDataUpdate(value); - return Promise.resolve(true); + return Promise.resolve(); } } diff --git a/__mocks__/gatsby.js b/__mocks__/gatsby.js index a7d4a32a5..09ea71c73 100644 --- a/__mocks__/gatsby.js +++ b/__mocks__/gatsby.js @@ -1,5 +1,7 @@ import React from 'react'; +import delay from './utils/index'; + const Gatsby = jest.requireActual('gatsby'); const fluidImageShapes = [ @@ -67,6 +69,14 @@ const useStaticQuery = () => ({ }, }); +const defaultDelayInMilliseconds = 100; + +const navigate = async () => { + await delay(defaultDelayInMilliseconds); + + return Promise.resolve(); +}; + module.exports = { ...Gatsby, graphql: jest.fn(), @@ -76,5 +86,6 @@ module.exports = { href: to, }), ), + navigate: jest.fn(navigate), useStaticQuery, }; diff --git a/__mocks__/utils/index.js b/__mocks__/utils/index.js new file mode 100644 index 000000000..0b290076e --- /dev/null +++ b/__mocks__/utils/index.js @@ -0,0 +1,5 @@ +const delay = async (milliseconds) => { + await new Promise((resolve) => setTimeout(resolve, milliseconds)); +}; + +export default delay; diff --git a/src/components/dashboard/ResumePreview.js b/src/components/dashboard/ResumePreview.js index d0a794e80..52fca4681 100644 --- a/src/components/dashboard/ResumePreview.js +++ b/src/components/dashboard/ResumePreview.js @@ -9,6 +9,8 @@ import DatabaseContext from '../../contexts/DatabaseContext'; import ModalContext from '../../contexts/ModalContext'; import styles from './ResumePreview.module.css'; +const menuToggleDataTestIdPrefix = 'resume-preview-menu-toggle-'; + const ResumePreview = ({ resume }) => { const { t, i18n } = useTranslation(); const [anchorEl, setAnchorEl] = useState(null); @@ -54,6 +56,7 @@ const ResumePreview = ({ resume }) => { onClick={handleOpen} /> { }; export default ResumePreview; + +export { menuToggleDataTestIdPrefix }; diff --git a/src/components/router/LoadingScreen.js b/src/components/router/LoadingScreen.js index 19b6ec3ad..b5f9ba412 100644 --- a/src/components/router/LoadingScreen.js +++ b/src/components/router/LoadingScreen.js @@ -3,8 +3,10 @@ import React, { memo } from 'react'; import { getRandomTip } from '../../data/tips'; import Logo from '../shared/Logo'; +const dataTestId = 'loading-screen'; + const LoadingScreen = () => ( - +
@@ -17,3 +19,4 @@ const LoadingScreen = () => ( ); export default memo(LoadingScreen); +export { dataTestId }; diff --git a/src/contexts/SettingsContext.js b/src/contexts/SettingsContext.js index e17e6b1d3..a3277a53f 100644 --- a/src/contexts/SettingsContext.js +++ b/src/contexts/SettingsContext.js @@ -2,6 +2,8 @@ import i18next from 'i18next'; import React, { createContext, memo, useEffect, useState } from 'react'; import themeConfig from '../data/themeConfig'; +const languageStorageItemKey = 'language'; + const defaultState = { theme: 'Dark', setTheme: () => {}, @@ -18,7 +20,7 @@ const SettingsProvider = ({ children }) => { useEffect(() => { const prefTheme = localStorage.getItem('theme') || defaultState.theme; const prefLanguage = - localStorage.getItem('language') || defaultState.language; + localStorage.getItem(languageStorageItemKey) || defaultState.language; setTheme(prefTheme); setLanguage(prefLanguage); }, []); @@ -32,7 +34,7 @@ const SettingsProvider = ({ children }) => { }, [theme]); useEffect(() => { - localStorage.setItem('language', language); + localStorage.setItem(languageStorageItemKey, language); i18next.changeLanguage(language); }, [language]); @@ -54,4 +56,4 @@ export default SettingsContext; const memoizedProvider = memo(SettingsProvider); -export { memoizedProvider as SettingsProvider }; +export { memoizedProvider as SettingsProvider, languageStorageItemKey }; diff --git a/src/pages/app/__tests__/builder.test.js b/src/pages/app/__tests__/builder.test.js index 52480685e..f6ad40f8a 100644 --- a/src/pages/app/__tests__/builder.test.js +++ b/src/pages/app/__tests__/builder.test.js @@ -1,15 +1,21 @@ +import { navigate as mockNavigateFunction } from 'gatsby'; import React from 'react'; import { - act, fireEvent, + getByText, render, screen, waitFor, + waitForElementToBeRemoved, } from '@testing-library/react'; import FirebaseStub, { DatabaseConstants } from 'gatsby-plugin-firebase'; -import { SettingsProvider } from '../../../contexts/SettingsContext'; +import { dataTestId as loadingScreenTestId } from '../../../components/router/LoadingScreen'; +import { + SettingsProvider, + languageStorageItemKey, +} from '../../../contexts/SettingsContext'; import { ModalProvider } from '../../../contexts/ModalContext'; import { UserProvider } from '../../../contexts/UserContext'; import { @@ -18,29 +24,58 @@ import { } from '../../../contexts/DatabaseContext'; import { ResumeProvider } from '../../../contexts/ResumeContext'; import { StorageProvider } from '../../../contexts/StorageContext'; +import Wrapper from '../../../components/shared/Wrapper'; import Builder from '../builder'; describe('Builder', () => { let resumeId = null; let resume = null; - let mockUpdateFunction = null; + let mockDatabaseUpdateFunction = null; - beforeEach(async () => { + 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 = DatabaseConstants.demoStateResume1Id; + resumeId = resumeIdParameter; resume = ( await FirebaseStub.database() .ref(`${DatabaseConstants.resumesPath}/${resumeId}`) .once('value') ).val(); - mockUpdateFunction = jest.spyOn( + + mockDatabaseUpdateFunction = jest.spyOn( FirebaseStub.database().ref( `${DatabaseConstants.resumesPath}/${resumeId}`, ), 'update', ); + FirebaseStub.auth().signInAnonymously(); + render( @@ -48,7 +83,9 @@ describe('Builder', () => { - + + + @@ -57,23 +94,59 @@ describe('Builder', () => { , ); - await act(async () => { - await FirebaseStub.auth().signInAnonymously(); - }); + if (waitForLoadingScreenToDisappear) { + await waitForElementToBeRemoved(() => + screen.getByTestId(loadingScreenTestId), + ); + } - await waitFor(() => mockUpdateFunction.mock.calls[0][0], { - timeout: DebounceWaitTime, + 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(), + ); + }); }); - mockUpdateFunction.mockClear(); }); describe('renders', () => { - it('first and last name', async () => { + beforeEach(async () => { + await setup(DatabaseConstants.demoStateResume1Id); + }); + + it('first and last name', () => { expect( - screen.getByLabelText(new RegExp('first name', 'i')), + screen.getByRole('textbox', { name: /first name/i }), ).toHaveDisplayValue(resume.profile.firstName); expect( - screen.getByLabelText(new RegExp('last name', 'i')), + screen.getByRole('textbox', { name: /last name/i }), ).toHaveDisplayValue(resume.profile.lastName); expect( screen.getAllByText(new RegExp(resume.profile.firstName, 'i')).length, @@ -84,9 +157,53 @@ describe('Builder', () => { }); }); + 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.getByLabelText(new RegExp('address line 1', 'i')); + const input = screen.getByRole('textbox', { name: /address line 1/i }); const newInputValue = 'test street 123'; const now = new Date().getTime(); @@ -94,54 +211,49 @@ describe('Builder', () => { expect(input.value).toBe(newInputValue); - await waitFor(() => mockUpdateFunction.mock.calls[0][0], { - timeout: DebounceWaitTime, - }); - expect(mockUpdateFunction).toHaveBeenCalledTimes(1); - const mockUpdateFunctionCallArgument = - mockUpdateFunction.mock.calls[0][0]; - expect(mockUpdateFunctionCallArgument.id).toBe(resume.id); - expect(mockUpdateFunctionCallArgument.profile.address.line1).toBe( + await expectDatabaseUpdateToHaveCompleted(); + const mockDatabaseUpdateFunctionCallArgument = + mockDatabaseUpdateFunction.mock.calls[0][0]; + expect(mockDatabaseUpdateFunctionCallArgument.id).toBe(resumeId); + expect(mockDatabaseUpdateFunctionCallArgument.profile.address.line1).toBe( newInputValue, ); - expect(mockUpdateFunctionCallArgument.updatedAt).toBeGreaterThanOrEqual( - now, - ); + expect( + mockDatabaseUpdateFunctionCallArgument.updatedAt, + ).toBeGreaterThanOrEqual(now); }); }); - describe('settings', () => { - it('allow to change the language', async () => { - const languageSelectElement = screen.getByLabelText('Language'); - const newLanguage = 'it'; - const now = new Date().getTime(); + describe('while loading', () => { + beforeEach(async () => { + await setup(DatabaseConstants.demoStateResume1Id, false, false); + }); - fireEvent.change(languageSelectElement, { - target: { value: newLanguage }, - }); + it('renders loading screen', async () => { + expect(screen.getByTestId(loadingScreenTestId)).toBeInTheDocument(); - expect(languageSelectElement).toHaveValue(newLanguage); + 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( - screen.queryByLabelText(new RegExp('date of birth', 'i')), - ).toBeNull(); - expect( - screen.getByLabelText(new RegExp('data di nascita', 'i')), + getByText( + notification, + /Not sure where to begin\? Try loading demo data/i, + ), ).toBeInTheDocument(); - - await waitFor(() => mockUpdateFunction.mock.calls[0][0], { - timeout: DebounceWaitTime, - }); - expect(mockUpdateFunction).toHaveBeenCalledTimes(1); - const mockUpdateFunctionCallArgument = - mockUpdateFunction.mock.calls[0][0]; - expect(mockUpdateFunctionCallArgument.id).toBe(resume.id); - expect(mockUpdateFunctionCallArgument.metadata.language).toBe( - newLanguage, - ); - expect(mockUpdateFunctionCallArgument.updatedAt).toBeGreaterThanOrEqual( - now, - ); + fireEvent.click(notification); }); }); }); diff --git a/src/pages/app/__tests__/dashboard.test.js b/src/pages/app/__tests__/dashboard.test.js index 6ab6cf91b..eede7d67a 100644 --- a/src/pages/app/__tests__/dashboard.test.js +++ b/src/pages/app/__tests__/dashboard.test.js @@ -1,26 +1,47 @@ import React from 'react'; -import { act, render, screen, waitFor } from '@testing-library/react'; +import { + fireEvent, + getByText, + queryByText, + render, + screen, + waitFor, + waitForElementToBeRemoved, +} from '@testing-library/react'; import FirebaseStub, { DatabaseConstants } from 'gatsby-plugin-firebase'; import '../../../i18n/index'; import '../../../utils/dayjs'; +import { dataTestId as loadingScreenTestId } from '../../../components/router/LoadingScreen'; +import { menuToggleDataTestIdPrefix as resumePreviewMenuToggleDataTestIdPrefix } from '../../../components/dashboard/ResumePreview'; import { SettingsProvider } from '../../../contexts/SettingsContext'; import { ModalProvider } from '../../../contexts/ModalContext'; import { UserProvider } from '../../../contexts/UserContext'; 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 resumes = null; + let userResumes = null; const user = DatabaseConstants.user1; - beforeEach(async () => { + const waitForResumeToBeRenderedInPreview = async (resume) => { + await screen.findByText(resume.name); + }; + + const expectResumeToBeRenderedInPreview = async (resume) => { + await waitFor(() => { + expect(screen.getByText(resume.name)).toBeInTheDocument(); + }); + }; + + async function setup(waitForLoadingScreenToDisappear = true) { FirebaseStub.database().initializeData(); - resumes = ( + userResumes = ( await FirebaseStub.database() .ref(DatabaseConstants.resumesPath) .orderByChild('user') @@ -28,6 +49,8 @@ describe('Dashboard', () => { .once('value') ).val(); + FirebaseStub.auth().signInAnonymously(); + render( @@ -35,7 +58,9 @@ describe('Dashboard', () => { - + + + @@ -44,33 +69,132 @@ describe('Dashboard', () => { , ); - await act(async () => { - await FirebaseStub.auth().signInAnonymously(); - }); - - await waitFor(() => screen.getByText('Create Resume')); - }); + if (waitForLoadingScreenToDisappear) { + await waitForElementToBeRemoved(() => + screen.getByTestId(loadingScreenTestId), + ); + } + } describe('renders', () => { + beforeEach(async () => { + await setup(); + }); + it('document title', async () => { - expect(document.title).toEqual('Dashboard | Reactive Resume'); + await waitFor(() => { + expect(document.title).toEqual('Dashboard | Reactive Resume'); + }); }); it('create resume', async () => { - expect(screen.getByText('Create Resume')).toBeInTheDocument(); + await waitFor(() => { + expect(screen.getByText(/create resume/i)).toBeInTheDocument(); + }); }); it('preview of user resumes', async () => { - expect(Object.keys(resumes)).toHaveLength(2); + expect(Object.keys(userResumes)).toHaveLength(2); - expect(Object.values(resumes)[0].user).toEqual(user.uid); + await expectResumeToBeRenderedInPreview(Object.values(userResumes)[0]); + await expectResumeToBeRenderedInPreview(Object.values(userResumes)[1]); + }); + }); + + 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(), + ); + }; + + beforeEach(async () => { + await setup(); + + [resumeToDelete, undeletedResume] = Object.values(userResumes); + resumeToDeleteId = resumeToDelete.id; + + 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 expectDatabaseRemoveToHaveCompleted(); + + await waitFor(() => + expect(screen.queryByText(resumeToDelete.name)).toBeNull(), + ); + await expectResumeToBeRenderedInPreview(undeletedResume); + }); + + it('displays notification', async () => { + const notification = await screen.findByRole('alert'); expect( - screen.getByText(Object.values(resumes)[0].name), - ).toBeInTheDocument(); - expect(Object.values(resumes)[1].user).toEqual(user.uid); - expect( - screen.getByText(Object.values(resumes)[1].name), + getByText( + notification, + new RegExp(`${resumeToDelete.name} was deleted successfully`, 'i'), + ), ).toBeInTheDocument(); + fireEvent.click(notification); + + await waitForDatabaseRemoveToHaveCompleted(); + }); + + it('closes menu', async () => { + const menuItems = screen.queryAllByRole('menuitem'); + expect(menuItems).toHaveLength(0); + + await waitForDatabaseRemoveToHaveCompleted(); + }); + }); + + describe('while loading', () => { + beforeEach(async () => { + await setup(false); + }); + + it('renders loading screen', async () => { + expect(screen.getByTestId(loadingScreenTestId)).toBeInTheDocument(); + + await waitForElementToBeRemoved(() => + screen.getByTestId(loadingScreenTestId), + ); + + await waitForResumeToBeRenderedInPreview(Object.values(userResumes)[0]); + await waitForResumeToBeRenderedInPreview(Object.values(userResumes)[1]); }); }); });