Merge pull request #429 from gianantoniopini/develop
Addition of some unit tests to Builder and Dashboard pages
This commit is contained in:
commit
6c31d3dff3
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
const delay = async (milliseconds) => {
|
||||
await new Promise((resolve) => setTimeout(resolve, milliseconds));
|
||||
};
|
||||
|
||||
export default delay;
|
|
@ -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}
|
||||
/>
|
||||
<MdMoreHoriz
|
||||
data-testid={`${menuToggleDataTestIdPrefix}${resume.id}`}
|
||||
color="#fff"
|
||||
size="48"
|
||||
className="cursor-pointer"
|
||||
|
@ -96,3 +99,5 @@ const ResumePreview = ({ resume }) => {
|
|||
};
|
||||
|
||||
export default ResumePreview;
|
||||
|
||||
export { menuToggleDataTestIdPrefix };
|
||||
|
|
|
@ -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 = () => (
|
||||
<Modal open hideBackdrop>
|
||||
<Modal data-testid={dataTestId} open hideBackdrop>
|
||||
<Fade in>
|
||||
<div className="w-screen h-screen flex justify-center items-center outline-none">
|
||||
<div className="flex flex-col items-center">
|
||||
|
@ -17,3 +19,4 @@ const LoadingScreen = () => (
|
|||
);
|
||||
|
||||
export default memo(LoadingScreen);
|
||||
export { dataTestId };
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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(
|
||||
<SettingsProvider>
|
||||
<ModalProvider>
|
||||
|
@ -48,7 +83,9 @@ describe('Builder', () => {
|
|||
<DatabaseProvider>
|
||||
<ResumeProvider>
|
||||
<StorageProvider>
|
||||
<Builder id={resume.id} />
|
||||
<Wrapper>
|
||||
<Builder id={resumeId} />
|
||||
</Wrapper>
|
||||
</StorageProvider>
|
||||
</ResumeProvider>
|
||||
</DatabaseProvider>
|
||||
|
@ -57,23 +94,59 @@ describe('Builder', () => {
|
|||
</SettingsProvider>,
|
||||
);
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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(
|
||||
<SettingsProvider>
|
||||
<ModalProvider>
|
||||
|
@ -35,7 +58,9 @@ describe('Dashboard', () => {
|
|||
<DatabaseProvider>
|
||||
<ResumeProvider>
|
||||
<StorageProvider>
|
||||
<Dashboard user={user} />
|
||||
<Wrapper>
|
||||
<Dashboard user={user} />
|
||||
</Wrapper>
|
||||
</StorageProvider>
|
||||
</ResumeProvider>
|
||||
</DatabaseProvider>
|
||||
|
@ -44,33 +69,132 @@ describe('Dashboard', () => {
|
|||
</SettingsProvider>,
|
||||
);
|
||||
|
||||
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]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue