From 7ef5a9d2610d4517b584d02b4c04b97439acf48c Mon Sep 17 00:00:00 2001 From: Quentin Ruhier Date: Thu, 16 Jan 2025 17:28:43 +0100 Subject: [PATCH] test: add unit tests on pages components --- src/ui/pages/Error/Error.test.tsx | 51 +++++++ src/ui/pages/External/External.test.tsx | 52 +++++++ src/ui/pages/collect/Collect.test.tsx | 62 +++++++++ src/ui/pages/review/Review.test.tsx | 130 ++++++++++++++++++ .../pages/synchronize/LoadingDisplay.test.tsx | 99 +++++++++++++ 5 files changed, 394 insertions(+) create mode 100644 src/ui/pages/Error/Error.test.tsx create mode 100644 src/ui/pages/External/External.test.tsx create mode 100644 src/ui/pages/collect/Collect.test.tsx create mode 100644 src/ui/pages/review/Review.test.tsx create mode 100644 src/ui/pages/synchronize/LoadingDisplay.test.tsx diff --git a/src/ui/pages/Error/Error.test.tsx b/src/ui/pages/Error/Error.test.tsx new file mode 100644 index 00000000..c3d8743e --- /dev/null +++ b/src/ui/pages/Error/Error.test.tsx @@ -0,0 +1,51 @@ +import { render } from '@testing-library/react' +import { isRouteErrorResponse, useRouteError } from 'react-router-dom' +import { afterEach, describe, expect, it, vi } from 'vitest' + +import { ErrorComponent } from '@/ui/components/ErrorComponent' + +import { ErrorPage } from './Error' + +vi.mock('react-router-dom', () => ({ + useRouteError: vi.fn(), + isRouteErrorResponse: vi.fn(), +})) +vi.mock('@/i18n', () => ({ + useTranslation: () => ({ t: (keyMessage: string) => keyMessage }), +})) +vi.mock('@/ui/components/ErrorComponent', () => ({ + ErrorComponent: vi.fn(), +})) + +afterEach(() => { + vi.clearAllMocks() +}) + +describe('ErrorPage', () => { + it('calls ErrorComponent with the correct message for instance of Error', () => { + const mockError = new Error('Something went wrong') + vi.mocked(useRouteError).mockReturnValue(mockError) + + render() + + expect(ErrorComponent).toHaveBeenCalledWith( + expect.objectContaining({ message: 'Something went wrong' }), + {}, + ) + }) + + it('renders ErrorComponent with the correct message for RouteErrorResponse', () => { + const mockRouteError = { status: 404, statusText: 'Not Found' } + vi.mocked(useRouteError).mockReturnValue(mockRouteError) + vi.mocked(isRouteErrorResponse).mockReturnValue(true) + + render() + + expect(ErrorComponent).toHaveBeenCalledWith( + expect.objectContaining({ + message: 'error 404 : Not Found', + }), + {}, + ) + }) +}) diff --git a/src/ui/pages/External/External.test.tsx b/src/ui/pages/External/External.test.tsx new file mode 100644 index 00000000..bb67f715 --- /dev/null +++ b/src/ui/pages/External/External.test.tsx @@ -0,0 +1,52 @@ +import { render } from '@testing-library/react' +import { describe, expect, it, vi } from 'vitest' + +import { CenteredSpinner } from '@/ui/components/CenteredSpinner' +import { ErrorComponent } from '@/ui/components/ErrorComponent' + +import { ExternalRessources } from './External' +import useScript from './useScript' + +vi.mock('@/i18n', () => ({ + getTranslation: () => ({ t: (keyMessage: string) => keyMessage }), +})) +vi.mock('@/ui/components/ErrorComponent', () => ({ + ErrorComponent: vi.fn(), +})) +vi.mock('@/ui/components/CenteredSpinner', () => ({ + CenteredSpinner: vi.fn(), +})) +vi.mock('./useScript', () => ({ + default: vi.fn(), +})) + +describe('ExternalRessources', () => { + it('renders CenteredSpinner when status is "loading"', () => { + vi.mocked(useScript).mockReturnValue('loading') + + render() + + expect(CenteredSpinner).toHaveBeenCalled() + }) + + it('renders when status is "ready"', () => { + vi.mocked(useScript).mockReturnValue('ready') + + const { container } = render() + + // Check that is rendered in the container + const capmiAppElement = container.querySelector('capmi-app') + expect(capmiAppElement).toBeInTheDocument() + }) + + it('renders ErrorComponent with correct message when status is "error"', () => { + vi.mocked(useScript).mockReturnValue('error') + + render() + + expect(ErrorComponent).toHaveBeenCalledWith( + expect.objectContaining({ message: 'externalResourcesLoadedError' }), + {}, + ) + }) +}) diff --git a/src/ui/pages/collect/Collect.test.tsx b/src/ui/pages/collect/Collect.test.tsx new file mode 100644 index 00000000..b70aa41a --- /dev/null +++ b/src/ui/pages/collect/Collect.test.tsx @@ -0,0 +1,62 @@ +import { render } from '@testing-library/react' +import { describe, expect, it, vi } from 'vitest' + +import { useCore } from '@/core' +import { Orchestrator } from '@/ui/components/orchestrator/Orchestrator' +import { useLoaderData } from '@/ui/routing/utils' + +import { Collect } from './Collect' + +vi.mock('@/core', () => ({ + useCore: vi.fn(), +})) +vi.mock('@/ui/routing/utils', () => ({ + useLoaderData: vi.fn(), +})) +vi.mock('@/ui/components/orchestrator/Orchestrator', () => ({ + Orchestrator: vi.fn(), +})) + +describe('Collect Component', () => { + it('renders Orchestrator with the correct props', () => { + const mockLoaderData = { + questionnaire: { id: 'q1', title: 'Questionnaire 1' }, + surveyUnit: { id: 'su1', name: 'Survey Unit 1' }, + } + + vi.mocked(useLoaderData).mockReturnValue(mockLoaderData) + + const mockCollectSurvey = { + getReferentiel: vi.fn(), + changePage: vi.fn(), + changeSurveyUnitState: vi.fn(), + quit: vi.fn(), + retrieveQuestionnaireId: vi.fn(), + loader: vi.fn(), + } + + const mockCore = { + functions: { + collectSurvey: mockCollectSurvey, + }, + } + + vi.mocked(useCore).mockReturnValue(mockCore as any) + + render() + + expect(Orchestrator).toHaveBeenCalledWith( + expect.objectContaining({ + source: mockLoaderData.questionnaire, + surveyUnit: mockLoaderData.surveyUnit, + readonly: false, + onQuit: mockCollectSurvey.quit, + onDefinitiveQuit: mockCollectSurvey.quit, + onChangePage: mockCollectSurvey.changePage, + getReferentiel: mockCollectSurvey.getReferentiel, + onChangeSurveyUnitState: mockCollectSurvey.changeSurveyUnitState, + }), + {}, + ) + }) +}) diff --git a/src/ui/pages/review/Review.test.tsx b/src/ui/pages/review/Review.test.tsx new file mode 100644 index 00000000..f58e0bc6 --- /dev/null +++ b/src/ui/pages/review/Review.test.tsx @@ -0,0 +1,130 @@ +import { render } from '@testing-library/react' +import { afterEach, describe, expect, it, vi } from 'vitest' + +import { useCore } from '@/core' +import type { SurveyUnit } from '@/core/model' +import { Modal } from '@/ui/components/Modal' +import { Orchestrator } from '@/ui/components/orchestrator/Orchestrator' +import { useLoaderData } from '@/ui/routing/utils' + +import { Review } from './Review' + +vi.mock('@/ui/routing/utils', () => ({ + useLoaderData: vi.fn(), +})) + +vi.mock('@/core', () => ({ + useCore: vi.fn(), +})) + +vi.mock('@/i18n', () => ({ + useTranslation: () => ({ t: (keyMessage: string) => keyMessage }), +})) + +vi.mock('@/ui/components/Modal', () => ({ + Modal: vi.fn(), +})) + +vi.mock('@/ui/components/orchestrator/Orchestrator', () => ({ + Orchestrator: vi.fn(), +})) + +describe('Review', () => { + afterEach(() => { + vi.clearAllMocks() + }) + + it('renders Orchestrator with correct props', () => { + const mockLoaderData = { + questionnaire: { id: 'q1' }, + surveyUnit: { id: 'su1' }, + } + + vi.mocked(useLoaderData).mockReturnValue(mockLoaderData) + + const mockReviewSurvey = { + getReferentiel: vi.fn(), + } + + const mockCore = { + functions: { + reviewSurvey: mockReviewSurvey, + }, + } + + vi.mocked(useCore).mockReturnValue(mockCore as any) + + render() + + expect(Orchestrator).toHaveBeenCalledWith( + expect.objectContaining({ + source: mockLoaderData.questionnaire, + surveyUnit: mockLoaderData.surveyUnit, + readonly: true, + onQuit: expect.any(Function), + onDefinitiveQuit: expect.any(Function), + onChangePage: undefined, + getReferentiel: mockReviewSurvey.getReferentiel, + }), + {}, + ) + }) + + it('opens and closes the quit modal', () => { + const mockLoaderData = { + questionnaire: { id: 'q1' }, + surveyUnit: { id: 'su1' }, + } + + vi.mocked(useLoaderData).mockReturnValue(mockLoaderData) + + const mockReviewSurvey = { + getReferentiel: vi.fn(), + } + + const mockCore = { + functions: { + reviewSurvey: mockReviewSurvey, + }, + } + + vi.mocked(useCore).mockReturnValue(mockCore as any) + + const { rerender } = render() + + // Simulate Orchestrator's onQuit call + const orchestratorProps = vi.mocked(Orchestrator).mock.calls[0][0] as { + onQuit: (surveyUnit: SurveyUnit) => void + } + orchestratorProps.onQuit(mockLoaderData.surveyUnit as SurveyUnit) + + rerender() + + expect(Modal).toHaveBeenCalledWith( + expect.objectContaining({ + isOpen: true, + dialogTitle: 'reviewQuitTitle', + dialogContent: 'reviewQuitContent', + buttons: expect.arrayContaining([ + expect.objectContaining({ label: 'cancel', autoFocus: false }), + ]), + onClose: expect.any(Function), + }), + expect.anything(), + ) + + // Simulate Modal's onClose call + const modalProps = vi.mocked(Modal).mock.calls[0][0] + modalProps.onClose() + + // Rerender to reflect state change + rerender() + + expect(Modal).toHaveBeenLastCalledWith( + expect.objectContaining({ + isOpen: false, + }), + expect.anything(), + ) + }) +}) diff --git a/src/ui/pages/synchronize/LoadingDisplay.test.tsx b/src/ui/pages/synchronize/LoadingDisplay.test.tsx new file mode 100644 index 00000000..78373370 --- /dev/null +++ b/src/ui/pages/synchronize/LoadingDisplay.test.tsx @@ -0,0 +1,99 @@ +import LinearProgress from '@mui/material/LinearProgress' +import { render } from '@testing-library/react' +import { afterEach, describe, expect, it, vi } from 'vitest' + +import { LoadingDisplay } from './LoadingDisplay' + +vi.mock('@/i18n', () => ({ + useTranslation: () => ({ t: (keyMessage: string) => keyMessage }), +})) + +vi.mock('@mui/material/LinearProgress', () => ({ + __esModule: true, + default: vi.fn(), +})) + +afterEach(() => { + vi.clearAllMocks() +}) + +describe('LoadingDisplay Component', () => { + it('renders synchronization title and step title', () => { + const props = { + syncStepTitle: 'sync step', + progressBars: [{ label: 'Progress 1', progress: 50 }], + } + + const { getByText } = render() + + expect(getByText('synchronizationInProgress')).toBeInTheDocument() + expect(getByText('sync step')).toBeInTheDocument() + }) + + it('renders progress bars with labels', () => { + const props = { + syncStepTitle: 'sync step', + progressBars: [ + { label: 'Progress 1', progress: 50 }, + { label: 'Progress 2', progress: 75, extraTitle: 'Extra Info' }, + ], + } + + const { getByText } = render() + + // Check progress labels + expect(getByText('Progress 1')).toBeInTheDocument() + expect(getByText('Progress 2: Extra Info')).toBeInTheDocument() + + // Check progress bars + expect(LinearProgress).toHaveBeenCalledTimes(2) + expect(LinearProgress).toHaveBeenCalledWith( + expect.objectContaining({ + variant: 'determinate', + value: 50, + }), + {}, + ) + expect(LinearProgress).toHaveBeenCalledWith( + expect.objectContaining({ + variant: 'determinate', + value: 75, + }), + {}, + ) + }) + + it('renders progress bars without labels', () => { + const props = { + syncStepTitle: 'sync step', + progressBars: [{ progress: 50 }, { progress: 75 }], + } + + render() + + // Check progress bars + expect(LinearProgress).toHaveBeenCalledTimes(2) + expect(LinearProgress).toHaveBeenCalledWith( + expect.objectContaining({ + variant: 'determinate', + value: 50, + }), + {}, + ) + expect(LinearProgress).toHaveBeenCalledWith( + expect.objectContaining({ + variant: 'determinate', + value: 75, + }), + {}, + ) + }) + + it('handles empty progressBars', () => { + const props = { syncStepTitle: 'sync step', progressBars: [] } + render() + + // Check that no progress bars are rendered + expect(LinearProgress).not.toHaveBeenCalled() + }) +})