import React from 'react'; import '@testing-library/jest-dom'; import { fireEvent, render, screen, waitFor } 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'; 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; function renderPage() { return render( , ); } beforeEach(() => { jest.clearAllMocks(); }); test('lists existing CVs from the variants API', async () => { mockedApi.get.mockResolvedValueOnce({ data: [ { id: 1, name: 'Frontend CV', themeId: 'modern', publicSlug: 'abc', isPublic: true, version: 2, jobApplicationId: null, updatedAtUtc: new Date().toISOString() }, ], } as any); renderPage(); expect(await screen.findByText('Frontend CV')).toBeInTheDocument(); expect(screen.getByText('Public')).toBeInTheDocument(); 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]); await waitFor(() => expect(mockedApi.post).toHaveBeenCalledWith('/cv/variants', expect.objectContaining({ name: 'Untitled CV' }))); await waitFor(() => expect(mockNavigate).toHaveBeenCalledWith('/career/builder/42')); }); 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()); });