Files
jobtrackingapp/job-tracker-ui/src/cv-builder-page.test.tsx

425 lines
23 KiB
TypeScript

import React from 'react';
import '@testing-library/jest-dom';
import { act, fireEvent, render, screen, waitFor, within } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import CvBuilderPage from './views/CvBuilderPage';
import { I18nProvider } from './i18n/I18nProvider';
import { ToastProvider } from './toast';
import { api } from './api';
import { ConfirmProvider } from './confirm';
import { PromptProvider } from './prompt';
import { emptyCvVariantSettings } from './cvBuilder';
const mockNavigate = jest.fn();
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useNavigate: () => mockNavigate,
}));
jest.mock('./api', () => ({
api: {
get: jest.fn(),
post: jest.fn(),
put: jest.fn(),
patch: jest.fn(),
delete: jest.fn(),
interceptors: { request: { use: jest.fn() }, response: { use: jest.fn() } },
},
getApiErrorMessage: (_error: any, fallback?: string) => fallback || 'Request failed.',
}));
const mockedApi = api as jest.Mocked<typeof api>;
function renderPage(extra?: React.ReactNode) {
return render(
<MemoryRouter>
<ToastProvider>
<I18nProvider>
<ConfirmProvider>
<PromptProvider>
{extra}
<CvBuilderPage />
</PromptProvider>
</ConfirmProvider>
</I18nProvider>
</ToastProvider>
</MemoryRouter>,
);
}
beforeEach(() => {
window.localStorage.removeItem('uiLanguage');
jest.resetAllMocks();
mockedApi.get.mockImplementation(async (url) => ({ data: url === '/cv/themes' ? themes : [] }) as any);
});
afterEach(() => window.localStorage.removeItem('uiLanguage'));
const themes = [
{ id: 'modern', name: 'Modern', category: 'Professional', description: '', layout: 'header-band', accent: '#3157d5', photoShape: 'none', supportsIcons: false, atsFriendly: true, requiresPro: false, available: true, swatches: ['#3157d5'] },
{ id: 'clean', name: 'Clean', category: 'Professional', description: '', layout: 'single-column', accent: '#444444', photoShape: 'none', supportsIcons: false, atsFriendly: true, requiresPro: false, available: true, swatches: ['#444444'] },
{ id: 'premium', name: 'Premium', category: 'Professional', description: '', layout: 'sidebar', accent: '#333333', photoShape: 'none', supportsIcons: true, atsFriendly: true, requiresPro: true, available: false, swatches: ['#333333'] },
];
const existingCv = {
id: 1, name: 'Frontend CV', themeId: 'modern', language: 'en', version: 2,
jobApplicationId: 17, jobTitle: 'Frontend Engineer', companyName: 'Northstar',
publicSlug: 'original-slug', isPublic: true, updatedAtUtc: '2026-10-12T10:00:00Z',
};
function deferred<T>() {
let resolve!: (value: T) => void;
let reject!: (reason: unknown) => void;
const promise = new Promise<T>((yes, no) => { resolve = yes; reject = no; });
return { promise, resolve, reject };
}
async function selectAction(name: string) {
fireEvent.click(screen.getByRole('button', { name: 'Actions for Frontend CV' }));
const item = await screen.findByRole('menuitem', { name });
item.focus();
fireEvent.click(item);
}
test('the CV library has a section heading and announces its held initial read without showing an empty result', async () => {
const read = deferred<any>();
mockedApi.get.mockImplementation((url) => url === '/cv/variants' ? read.promise : Promise.resolve({ data: themes }) as any);
renderPage();
expect(screen.getByRole('heading', { level: 2, name: 'Your CVs' })).toBeInTheDocument();
expect(screen.getByRole('status')).toHaveTextContent(/Loading/i);
expect(screen.queryByText('No CVs yet')).not.toBeInTheDocument();
await act(async () => { read.resolve({ data: [existingCv] }); });
expect(await screen.findByRole('link', { name: 'Open Frontend CV' })).toBeInTheDocument();
expect(screen.getByRole('status')).toHaveTextContent('Content loaded');
expect(screen.getByRole('status')).not.toHaveTextContent(/Loading/i);
});
test('a failed CV list read has an explicit retry that clears the stale failure after recovery', async () => {
const retry = deferred<any>();
let listReads = 0;
mockedApi.get.mockImplementation((url) => url === '/cv/variants'
? ++listReads === 1 ? Promise.reject(new Error('synthetic unavailable')) : retry.promise
: Promise.resolve({ data: themes }) as any);
renderPage();
expect(await screen.findByRole('alert')).toHaveTextContent('Could not load your CVs.');
expect(screen.queryByText('No CVs yet')).not.toBeInTheDocument();
const retryButton = screen.getByRole('button', { name: 'Retry' });
retryButton.focus();
fireEvent.click(retryButton);
await waitFor(() => expect(mockedApi.get.mock.calls.filter(([url]) => url === '/cv/variants')).toHaveLength(2));
expect(screen.getByRole('status')).toHaveTextContent(/Loading/i);
expect(mockedApi.post).not.toHaveBeenCalled();
await act(async () => { retry.resolve({ data: [existingCv] }); });
expect(await screen.findByRole('link', { name: 'Open Frontend CV' })).toBeInTheDocument();
expect(screen.queryByText('Could not load your CVs.')).not.toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'Retry' })).not.toBeInTheDocument();
});
test('a live language change retains an open CV name and template draft without refetching library data', async () => {
renderPage();
await screen.findByText('No CVs yet');
fireEvent.click(screen.getAllByRole('button', { name: 'New CV' })[0]);
await screen.findByRole('dialog', { name: 'Create a CV' });
fireEvent.change(screen.getByRole('textbox', { name: 'CV name' }), { target: { value: 'My unsaved application CV' } });
fireEvent.click(screen.getByRole('button', { name: 'Clean' }));
const requests = mockedApi.get.mock.calls.length;
act(() => { window.dispatchEvent(new StorageEvent('storage', { key: 'uiLanguage', newValue: 'nb' })); });
expect(await screen.findByRole('dialog', { name: 'Opprett en CV' })).toBeInTheDocument();
expect(screen.getByRole('textbox', { name: 'CV-navn' })).toHaveValue('My unsaved application CV');
expect(screen.getByRole('button', { name: 'Clean' })).toHaveAttribute('aria-pressed', 'true');
expect(mockedApi.get).toHaveBeenCalledTimes(requests);
expect(mockedApi.post).not.toHaveBeenCalled();
});
test('held CV creation locks the draft and gallery, then keeps a failed draft for an explicit retry', async () => {
const write = deferred<any>();
mockedApi.post.mockImplementationOnce(() => write.promise).mockResolvedValueOnce({ data: { id: 42 } } as any);
renderPage();
await screen.findByText('No CVs yet');
fireEvent.click(screen.getAllByRole('button', { name: 'New CV' })[0]);
const dialog = await screen.findByRole('dialog', { name: 'Create a CV' });
const name = within(dialog).getByRole('textbox', { name: 'CV name' });
fireEvent.change(name, { target: { value: 'Application-specific CV' } });
fireEvent.click(within(dialog).getByRole('button', { name: 'Clean' }));
fireEvent.click(within(dialog).getByRole('button', { name: 'Create CV' }));
await waitFor(() => expect(mockedApi.post).toHaveBeenCalledTimes(1));
expect(name).toBeDisabled();
expect(within(dialog).getByRole('button', { name: 'ATS templates' })).toBeDisabled();
expect(within(dialog).getByRole('button', { name: 'All templates' })).toBeDisabled();
expect(within(dialog).getByRole('button', { name: 'Modern' })).toHaveAttribute('aria-disabled', 'true');
expect(within(dialog).getByRole('button', { name: 'Clean' })).toHaveAttribute('aria-disabled', 'true');
expect(within(dialog).getByRole('button', { name: 'Cancel' })).toBeDisabled();
fireEvent.click(within(dialog).getByRole('button', { name: /Creating/ }));
fireEvent.click(within(dialog).getByRole('button', { name: 'Modern' }));
expect(mockedApi.post).toHaveBeenCalledTimes(1);
expect(within(dialog).getByRole('button', { name: 'Clean' })).toHaveAttribute('aria-pressed', 'true');
await act(async () => { write.reject(new Error('synthetic failed create')); });
expect(await within(dialog).findByRole('alert')).toHaveTextContent('Could not create a CV.');
expect(name).toBeEnabled();
expect(name).toHaveValue('Application-specific CV');
expect(within(dialog).getByRole('button', { name: 'Clean' })).toHaveAttribute('aria-pressed', 'true');
expect(mockedApi.post).toHaveBeenCalledTimes(1);
expect(mockNavigate).not.toHaveBeenCalled();
fireEvent.click(within(dialog).getByRole('button', { name: 'Create CV' }));
await waitFor(() => expect(mockedApi.post).toHaveBeenLastCalledWith('/cv/variants', { name: 'Application-specific CV', settings: emptyCvVariantSettings('clean') }));
await waitFor(() => expect(mockNavigate).toHaveBeenCalledWith('/career/builder/42'));
});
test('a locked CV template remains identifiable and cannot change the creation request', async () => {
mockedApi.post.mockResolvedValueOnce({ data: { id: 42 } } as any);
renderPage();
await screen.findByText('No CVs yet');
fireEvent.click(screen.getAllByRole('button', { name: 'New CV' })[0]);
const dialog = await screen.findByRole('dialog', { name: 'Create a CV' });
const premium = within(dialog).getByRole('button', { name: 'Premium' });
expect(premium).toHaveAttribute('aria-disabled', 'true');
expect(premium).toHaveAttribute('tabindex', '-1');
fireEvent.click(premium);
fireEvent.keyDown(premium, { key: 'Enter' });
expect(within(dialog).getByRole('button', { name: 'Modern' })).toHaveAttribute('aria-pressed', 'true');
fireEvent.click(within(dialog).getByRole('button', { name: 'Create CV' }));
await waitFor(() => expect(mockedApi.post).toHaveBeenCalledWith('/cv/variants', expect.objectContaining({ settings: emptyCvVariantSettings('modern') })));
});
test('a held duplicate blocks another action for the same CV and shows the persisted refreshed copy', async () => {
const write = deferred<any>();
let listReads = 0;
mockedApi.get.mockImplementation(async (url) => ({ data: url === '/cv/variants'
? ++listReads === 1 ? [existingCv] : [existingCv, { ...existingCv, id: 2, name: 'Frontend CV (copy)', publicSlug: 'copy-slug', isPublic: false }]
: themes }) as any);
mockedApi.post.mockImplementationOnce(() => write.promise);
renderPage();
await screen.findByText('Frontend CV');
const actions = screen.getByRole('button', { name: 'Actions for Frontend CV' });
await selectAction('Duplicate');
await waitFor(() => expect(mockedApi.post).toHaveBeenCalledWith('/cv/variants/1/duplicate', { name: undefined }));
expect(actions).toBeDisabled();
fireEvent.click(actions);
const duplicate = screen.queryByRole('menuitem', { name: 'Duplicate' });
if (duplicate) fireEvent.click(duplicate);
expect(mockedApi.post).toHaveBeenCalledTimes(1);
expect(listReads).toBe(1);
await act(async () => { write.resolve({ data: { id: 2 } }); });
expect(await screen.findByRole('link', { name: 'Open Frontend CV (copy)' })).toBeInTheDocument();
await waitFor(() => expect(actions).toBeEnabled());
expect(mockedApi.post).toHaveBeenCalledTimes(1);
expect(listReads).toBe(2);
});
test('rename reads the current revision and preserves its settings when saving the new name', async () => {
const settings = { ...emptyCvVariantSettings('clean'), overrides: { summary: { text: 'Owned override' } }, showPageNumbers: true };
mockedApi.get.mockImplementation(async (url) => ({ data: url === '/cv/variants' ? [existingCv]
: url === '/cv/variants/1' ? { ...existingCv, settings, version: 7 } : themes }) as any);
mockedApi.put.mockResolvedValueOnce({ data: { ...existingCv, name: 'Renamed application CV', version: 8, updatedAtUtc: '2026-10-12T11:00:00Z' } } as any);
renderPage();
await screen.findByText('Frontend CV');
await selectAction('Rename');
const input = await screen.findByRole('textbox', { name: 'Give this CV a clear name.' });
fireEvent.change(input, { target: { value: ' Renamed application CV ' } });
fireEvent.click(screen.getByRole('button', { name: 'Rename' }));
await waitFor(() => expect(mockedApi.put).toHaveBeenCalledWith('/cv/variants/1', { name: 'Renamed application CV', settings, source: 'manual', expectedVersion: 7 }));
expect(await screen.findByRole('link', { name: 'Open Renamed application CV' })).toBeInTheDocument();
expect(screen.getByText(/Frontend Engineer at Northstar/)).toBeInTheDocument();
expect(screen.getByText('Public')).toBeInTheDocument();
expect(mockedApi.put).toHaveBeenCalledTimes(1);
});
test('a rename conflict keeps the original library entry and never retries the write automatically', async () => {
const settings = { ...emptyCvVariantSettings('modern'), overrides: { summary: { text: 'Owned override' } } };
mockedApi.get.mockImplementation(async (url) => ({ data: url === '/cv/variants' ? [existingCv]
: url === '/cv/variants/1' ? { ...existingCv, settings, version: 7 } : themes }) as any);
mockedApi.put.mockRejectedValueOnce({ response: { status: 409 } });
renderPage();
await screen.findByText('Frontend CV');
await selectAction('Rename');
fireEvent.change(await screen.findByRole('textbox', { name: 'Give this CV a clear name.' }), { target: { value: 'Conflicting CV name' } });
fireEvent.click(screen.getByRole('button', { name: 'Rename' }));
expect((await screen.findAllByText('Rename failed.')).length).toBeGreaterThan(0);
await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument());
expect(screen.getByRole('link', { name: 'Open Frontend CV' })).toBeInTheDocument();
expect(screen.queryByRole('link', { name: 'Open Conflicting CV name' })).not.toBeInTheDocument();
expect(mockedApi.put).toHaveBeenCalledTimes(1);
expect(mockedApi.delete).not.toHaveBeenCalled();
});
test('cancelling deletion returns focus to the surviving CV action control without removing the CV', async () => {
mockedApi.get.mockImplementation(async (url) => ({ data: url === '/cv/variants' ? [existingCv] : themes }) as any);
renderPage();
await screen.findByText('Frontend CV');
const actions = screen.getByRole('button', { name: 'Actions for Frontend CV' });
actions.focus();
await selectAction('Delete');
await screen.findByRole('heading', { name: 'Delete CV' });
fireEvent.click(screen.getByRole('button', { name: 'Cancel' }));
await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument());
await waitFor(() => expect(actions).toHaveFocus());
expect(screen.getByRole('link', { name: 'Open Frontend CV' })).toBeInTheDocument();
expect(mockedApi.delete).not.toHaveBeenCalled();
});
test('a failed creation keeps its draft and translates its failure after closing and reopening in another language', async () => {
mockedApi.post.mockRejectedValueOnce(new Error('synthetic unavailable'));
renderPage();
await screen.findByText('No CVs yet');
fireEvent.click(screen.getByRole('button', { name: 'New CV' }));
const dialog = await screen.findByRole('dialog', { name: 'Create a CV' });
fireEvent.change(within(dialog).getByRole('textbox', { name: 'CV name' }), { target: { value: 'Keep this failed CV draft' } });
fireEvent.click(within(dialog).getByRole('button', { name: 'Clean' }));
fireEvent.click(within(dialog).getByRole('button', { name: 'Create CV' }));
expect(await within(dialog).findByRole('alert')).toHaveTextContent('Could not create a CV.');
fireEvent.click(within(dialog).getByRole('button', { name: 'Cancel' }));
await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument());
const reads = mockedApi.get.mock.calls.length;
act(() => { window.dispatchEvent(new StorageEvent('storage', { key: 'uiLanguage', newValue: 'nb' })); });
fireEvent.click(screen.getByRole('button', { name: 'Ny CV' }));
const reopened = await screen.findByRole('dialog', { name: 'Opprett en CV' });
expect(within(reopened).getByRole('textbox', { name: 'CV-navn' })).toHaveValue('Keep this failed CV draft');
expect(within(reopened).getByRole('button', { name: 'Clean' })).toHaveAttribute('aria-pressed', 'true');
expect(within(reopened).getByRole('alert')).toHaveTextContent('Kunne ikke opprette en CV.');
expect(within(reopened).queryByText('Could not create a CV.')).not.toBeInTheDocument();
expect(mockedApi.get).toHaveBeenCalledTimes(reads);
expect(mockedApi.post).toHaveBeenCalledTimes(1);
expect(mockNavigate).not.toHaveBeenCalled();
});
test('generic PDF failure follows the current language both after failure and when a held request fails', async () => {
const secondDownload = deferred<any>();
mockedApi.get.mockImplementation(async (url) => ({ data: url === '/cv/variants' ? [existingCv] : themes }) as any);
mockedApi.post.mockRejectedValueOnce(new Error('synthetic unavailable')).mockImplementationOnce(() => secondDownload.promise);
renderPage();
await screen.findByText('Frontend CV');
await selectAction('Download PDF');
expect(await screen.findByRole('alert')).toHaveTextContent('PDF download failed.');
const reads = mockedApi.get.mock.calls.length;
act(() => { window.dispatchEvent(new StorageEvent('storage', { key: 'uiLanguage', newValue: 'nb' })); });
expect(screen.getByRole('alert')).toHaveTextContent('Nedlasting av PDF mislyktes.');
expect(screen.queryByText('PDF download failed.')).not.toBeInTheDocument();
expect(mockedApi.get).toHaveBeenCalledTimes(reads);
expect(mockedApi.post).toHaveBeenCalledTimes(1);
fireEvent.click(screen.getByRole('button', { name: 'Handlinger for Frontend CV' }));
fireEvent.click(await screen.findByRole('menuitem', { name: 'Last ned PDF' }));
await waitFor(() => expect(mockedApi.post).toHaveBeenCalledTimes(2));
act(() => { window.dispatchEvent(new StorageEvent('storage', { key: 'uiLanguage', newValue: 'en' })); });
await act(async () => { secondDownload.reject(new Error('synthetic unavailable')); });
expect(await screen.findByRole('alert')).toHaveTextContent('PDF download failed.');
expect(screen.queryByText('Nedlasting av PDF mislyktes.')).not.toBeInTheDocument();
expect(mockedApi.get).toHaveBeenCalledTimes(reads);
expect(mockedApi.post).toHaveBeenCalledTimes(2);
expect(mockedApi.put).not.toHaveBeenCalled();
});
test('a held deletion preserves focus moved to another surviving control before the response arrives', async () => {
const write = deferred<any>();
mockedApi.get.mockImplementation(async (url) => ({ data: url === '/cv/variants' ? [existingCv] : themes }) as any);
mockedApi.delete.mockImplementationOnce(() => write.promise);
renderPage(<button type="button">Separate workflow control</button>);
await screen.findByText('Frontend CV');
await selectAction('Delete');
fireEvent.click(await screen.findByRole('button', { name: 'Delete CV' }));
await waitFor(() => expect(mockedApi.delete).toHaveBeenCalledWith('/cv/variants/1'));
await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument());
const otherControl = screen.getByRole('button', { name: 'Separate workflow control' });
otherControl.focus();
expect(otherControl).toHaveFocus();
await act(async () => { write.resolve({ data: null }); });
await waitFor(() => expect(screen.queryByRole('link', { name: 'Open Frontend CV' })).not.toBeInTheDocument());
expect(otherControl).toHaveFocus();
expect(mockedApi.delete).toHaveBeenCalledTimes(1);
expect(mockedApi.post).not.toHaveBeenCalled();
});
test.each([
[503, 'PDF generation is busy. Please try again shortly.'],
[504, 'PDF generation took too long. Please try again.'],
])('PDF status %s shows an actionable message without retrying', async (status, message) => {
mockedApi.get.mockResolvedValueOnce({ data: [{ id: 1, name: 'Synthetic CV', themeId: 'modern', version: 1, updatedAtUtc: new Date().toISOString() }] } as any);
mockedApi.post.mockRejectedValueOnce({ response: { status, data: new Blob(['{}'], { type: 'application/problem+json' }) } });
renderPage();
await screen.findByText('Synthetic CV');
fireEvent.click(screen.getByRole('button', { name: 'Actions for Synthetic CV' }));
fireEvent.click(await screen.findByRole('menuitem', { name: 'Download PDF' }));
expect(await screen.findByText(message)).toBeInTheDocument();
expect(mockedApi.post).toHaveBeenCalledTimes(1);
});
test('lists existing CVs from the variants API', async () => {
mockedApi.get.mockResolvedValueOnce({
data: [
{ id: 1, name: 'Frontend CV', themeId: 'modern', language: 'en', publicSlug: 'abc', isPublic: true, version: 2, jobApplicationId: 17, jobTitle: 'Frontend Engineer', companyName: 'Northstar', updatedAtUtc: new Date().toISOString() },
],
} as any);
renderPage();
expect(await screen.findByText('Frontend CV')).toBeInTheDocument();
expect(screen.getByText('Public')).toBeInTheDocument();
expect(screen.getByText(/Frontend Engineer at Northstar/)).toBeInTheDocument();
const cvCard = screen.getByRole('link', { name: 'Open Frontend CV' });
cvCard.focus();
expect(cvCard).toHaveFocus();
fireEvent.keyDown(cvCard, { key: 'Enter' });
expect(mockNavigate).toHaveBeenCalledWith('/career/builder/1');
expect(mockedApi.get).toHaveBeenCalledWith('/cv/variants');
});
test('shows the empty state and creates a CV then navigates to the editor', async () => {
mockedApi.get.mockResolvedValueOnce({ data: [] } as any);
mockedApi.post.mockResolvedValueOnce({ data: { id: 42 } } as any);
renderPage();
expect(await screen.findByText('No CVs yet')).toBeInTheDocument();
fireEvent.click(screen.getAllByRole('button', { name: /New CV/i })[0]);
expect(await screen.findByRole('dialog', { name: 'Create a CV' })).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: 'Create CV' }));
await waitFor(() => expect(mockedApi.post).toHaveBeenCalledWith('/cv/variants', expect.objectContaining({ name: 'Untitled CV' })));
await waitFor(() => expect(mockNavigate).toHaveBeenCalledWith('/career/builder/42'));
});
test('CV dashboard and create flow render in Bokmål', async () => {
window.localStorage.setItem('uiLanguage', 'nb');
mockedApi.get.mockResolvedValueOnce({ data: [] } as any);
renderPage();
expect(await screen.findByText('Ingen CV-er ennå')).toBeInTheDocument();
fireEvent.click(screen.getAllByRole('button', { name: 'Ny CV' })[0]);
expect(await screen.findByRole('dialog', { name: 'Opprett en CV' })).toBeInTheDocument();
expect(screen.getByLabelText(/^CV-navn/)).toHaveValue('CV uten tittel');
expect(screen.getByText('Velg en mal')).toBeInTheDocument();
});
test('CV deletion requires confirmation and supports cancellation', async () => {
mockedApi.get.mockResolvedValueOnce({ data: [
{ id: 1, name: 'Frontend CV', themeId: 'modern', publicSlug: 'abc', isPublic: false, version: 2, jobApplicationId: null, updatedAtUtc: new Date().toISOString() },
] } as any);
mockedApi.delete.mockResolvedValue({ data: null } as any);
renderPage();
await screen.findByText('Frontend CV');
fireEvent.click(screen.getByRole('button', { name: 'Actions for Frontend CV' }));
fireEvent.click(await screen.findByRole('menuitem', { name: 'Delete' }));
expect(await screen.findByRole('dialog', { name: 'Delete CV' })).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: 'Cancel' }));
await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument());
expect(mockedApi.delete).not.toHaveBeenCalled();
fireEvent.click(screen.getByRole('button', { name: 'Actions for Frontend CV' }));
fireEvent.click(await screen.findByRole('menuitem', { name: 'Delete' }));
fireEvent.click(await screen.findByRole('button', { name: 'Delete CV' }));
await waitFor(() => expect(mockedApi.delete).toHaveBeenCalledWith('/cv/variants/1'));
await waitFor(() => expect(screen.queryByText('Frontend CV')).not.toBeInTheDocument());
});