Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import '@testing-library/jest-dom/vitest';
import { render, screen, waitFor } from '@testing-library/react';
import { Mock, afterEach, describe, expect, it, vi } from 'vitest';
import { useNavigationAdapter, useTranslationProvider } from '@v6y/ui-kit';
import { Mock, afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

import VitalityGeneralInformationView from '../../features/app-details/components/infos/VitalityGeneralInformationView';
import { useClientQuery } from '../../infrastructure/adapters/api/useQueryAdapter';

// Mocks
vi.mock('../../infrastructure/adapters/api/useQueryAdapter', () => ({
useClientQuery: vi.fn(),
}));
Expand All @@ -13,180 +15,211 @@ vi.mock('../../commons/utils/VitalityDataExportUtils', () => ({
exportAppDetailsDataToCSV: vi.fn(),
}));

vi.mock('@v6y/ui-kit', async () => {
const originalModule = await vi.importActual('@v6y/ui-kit');
return {
...originalModule,
useNavigationAdapter: vi.fn(),
useTranslationProvider: vi.fn(),
InfoOutlined: () => <div data-testid="mock-info-outlined" />,
DynamicLoader: (fn: () => any) => {
// This will mock the DynamicLoader to immediately load the component
// allowing us to mock VitalityAppInfos and VitalitySectionView
const Component = fn();
return (props: any) => <Component {...props} />;
},
};
});

// Mock specific components that are dynamically loaded or direct children
// We want to check the props passed to these, not their internal rendering.
vi.mock('../../commons/components/VitalitySectionView', () => ({
default: vi.fn(({ isLoading, isEmpty, children }) => (
<div data-testid="mock-vitality-section-view">
{isLoading && <div data-testid="loading-indicator">Loading...</div>}
{isEmpty && <div data-testid="empty-indicator">Empty</div>}
{!isLoading && !isEmpty && children}
</div>
)),
}));

vi.mock('../../commons/components/application-info/VitalityAppInfos', () => ({
default: vi.fn(({ app }) => <div data-testid="mock-vitality-app-infos">{app?.name}</div>),
}));

const mockUseClientQuery = useClientQuery as Mock;
const mockGetUrlParams = vi.fn();
const mockTranslate = vi.fn((key) => key); // Simple translation mock

describe('VitalityGeneralInformationView', () => {
beforeEach(() => {
(useNavigationAdapter as Mock).mockReturnValue({
getUrlParams: mockGetUrlParams,
});
(useTranslationProvider as Mock).mockReturnValue({
translate: mockTranslate,
});
mockUseClientQuery.mockImplementation(() => ({
isLoading: false,
data: undefined,
refetch: vi.fn(),
}));
console.warn = vi.fn(); // Mock console.warn
});

afterEach(() => {
vi.clearAllMocks();
});

it('shows loading state while fetching data', async () => {
(useClientQuery as Mock).mockReturnValue({
it('shows loading state when useClientQuery indicates loading', () => {
mockGetUrlParams.mockReturnValue(['123']); // Valid ID
mockUseClientQuery.mockReturnValue({
isLoading: true,
data: null,
});

render(<VitalityGeneralInformationView />);

expect(screen.getByTestId('mock-loader')).toBeInTheDocument();
expect(screen.getByTestId('loading-indicator')).toBeInTheDocument();
const sectionViewProps = (vi.mocked(require('../../commons/components/VitalitySectionView')).default as Mock).mock.calls[0][0];
expect(sectionViewProps.isLoading).toBe(true);
expect(sectionViewProps.isEmpty).toBe(true); // Because appInfos is null/undefined initially
});

it('renders application details correctly when data is available', async () => {
(useClientQuery as Mock).mockReturnValue({
it('renders application details correctly when _id is valid and data is available', async () => {
const mockAppInfos = {
_id: 1,
name: 'Vitality App',
acronym: 'VAP',
};
mockGetUrlParams.mockReturnValue(['1']);
mockUseClientQuery.mockReturnValue({
isLoading: false,
data: {
getApplicationDetailsInfoByParams: {
_id: 1,
name: 'Vitality App',
acronym: 'VAP',
description: 'A powerful application for testing.',
contactMail: 'contact@vitality.com',
repo: {
organization: 'Vitality Org',
webUrl: 'https://github.com/vitality-org',
allBranches: ['main', 'develop'],
},
links: [
{ label: 'Website', value: 'https://vitality.com' },
{ label: 'Documentation', value: 'https://docs.vitality.com' },
],
},
},
data: { getApplicationDetailsInfoByParams: mockAppInfos },
});

render(<VitalityGeneralInformationView />);

await waitFor(() => {
expect(screen.getByText('Vitality App')).toBeInTheDocument();
expect(screen.getByText('A powerful application for testing.')).toBeInTheDocument();
expect(screen.getByText('vitality.appListPage.nbBranches2')).toBeInTheDocument();
expect(screen.getByText('Vitality Org')).toBeInTheDocument();
expect(screen.getByText('Website')).toBeInTheDocument();
expect(screen.getByText('Documentation')).toBeInTheDocument();
expect(screen.getByTestId('mock-vitality-app-infos')).toBeInTheDocument();
expect(screen.getByText('Vitality App')).toBeInTheDocument(); // From mock VitalityAppInfos
});
});

it('handles missing application data gracefully', async () => {
(useClientQuery as Mock).mockReturnValue({
isLoading: false,
data: { getApplicationDetailsInfoByParams: {} }, // Empty data
});
const sectionViewProps = (vi.mocked(require('../../commons/components/VitalitySectionView')).default as Mock).mock.calls[0][0];
expect(sectionViewProps.isLoading).toBe(false);
expect(sectionViewProps.isEmpty).toBe(false);

render(<VitalityGeneralInformationView />);
const appInfosProps = (vi.mocked(require('../../commons/components/application-info/VitalityAppInfos')).default as Mock).mock.calls[0][0];
expect(appInfosProps.app).toEqual(mockAppInfos);

await waitFor(() => {
expect(screen.getByTestId('empty-view')).toBeInTheDocument();
});
expect(mockUseClientQuery).toHaveBeenCalledWith(expect.objectContaining({
queryCacheKey: ['getApplicationDetailsInfoByParams', '1'],
variables: { _id: 1 },
enabled: true,
}));
});

it('handles API errors gracefully', async () => {
(useClientQuery as Mock).mockReturnValue({
it('handles valid _id but API returns no appInfos (null)', async () => {
mockGetUrlParams.mockReturnValue(['2']);
mockUseClientQuery.mockReturnValue({
isLoading: false,
data: null, // Simulating an error
data: { getApplicationDetailsInfoByParams: null },
});

render(<VitalityGeneralInformationView />);

await waitFor(() => {
expect(screen.getByTestId('empty-view')).toBeInTheDocument();
expect(screen.getByTestId('empty-indicator')).toBeInTheDocument();
});
const sectionViewProps = (vi.mocked(require('../../commons/components/VitalitySectionView')).default as Mock).mock.calls[0][0];
expect(sectionViewProps.isLoading).toBe(false);
expect(sectionViewProps.isEmpty).toBe(true);
expect(screen.queryByTestId('mock-vitality-app-infos')).not.toBeInTheDocument();
expect(mockUseClientQuery).toHaveBeenCalledWith(expect.objectContaining({
queryCacheKey: ['getApplicationDetailsInfoByParams', '2'],
variables: { _id: 2 },
enabled: true,
}));
});

it('renders application without optional fields gracefully', async () => {
(useClientQuery as Mock).mockReturnValue({
it('handles valid _id but API returns no appInfos (undefined data)', async () => {
mockGetUrlParams.mockReturnValue(['3']);
mockUseClientQuery.mockReturnValue({
isLoading: false,
data: {
getApplicationDetailsInfoByParams: {
_id: 2,
name: 'Minimal App',
acronym: 'MAP',
// No description, contactMail, repo, or links
},
},
data: undefined, // Simulate data being undefined
});

render(<VitalityGeneralInformationView />);

await waitFor(() => {
expect(screen.getByText('Minimal App')).toBeInTheDocument();
expect(screen.getByTestId('empty-indicator')).toBeInTheDocument();
});

expect(screen.queryByText('A powerful application for testing.')).not.toBeInTheDocument();
expect(screen.queryByText('Website')).not.toBeInTheDocument();
expect(screen.queryByText('Vitality Org')).not.toBeInTheDocument();
const sectionViewProps = (vi.mocked(require('../../commons/components/VitalitySectionView')).default as Mock).mock.calls[0][0];
expect(sectionViewProps.isLoading).toBe(false);
expect(sectionViewProps.isEmpty).toBe(true);
expect(mockUseClientQuery).toHaveBeenCalledWith(expect.objectContaining({
queryCacheKey: ['getApplicationDetailsInfoByParams', '3'],
variables: { _id: 3 },
enabled: true,
}));
});

it('does not render application with missing required fields', async () => {
(useClientQuery as Mock).mockReturnValue({
isLoading: false,
data: {
getApplicationDetailsInfoByParams: {
_id: 3,
acronym: '', // Missing name and acronym
description: 'This should not be displayed',
},
},
});

it('handles invalid _id string (e.g., "abc"), disables query, and shows empty state', async () => {
mockGetUrlParams.mockReturnValue(['abc']); // Invalid _id
// useClientQuery will use its default mock return (isLoading: false, data: undefined)
// because enabled should be false

render(<VitalityGeneralInformationView />);

await waitFor(() => {
expect(screen.getByTestId('empty-view')).toBeInTheDocument();
// VitalitySectionView should be in empty state because appInfos is undefined
expect(screen.getByTestId('empty-indicator')).toBeInTheDocument();
});

expect(screen.queryByText('This should not be displayed')).not.toBeInTheDocument();
expect(mockUseClientQuery).toHaveBeenCalledWith(expect.objectContaining({
queryCacheKey: ['getApplicationDetailsInfoByParams', undefined], // numericId will be undefined
variables: { _id: undefined },
enabled: false, // Query should be disabled
}));
const sectionViewProps = (vi.mocked(require('../../commons/components/VitalitySectionView')).default as Mock).mock.calls[0][0];
expect(sectionViewProps.isLoading).toBe(false); // isLoading is false from default mock
expect(sectionViewProps.isEmpty).toBe(true); // isEmpty because appInfos is undefined
expect(console.warn).toHaveBeenCalledWith('Invalid or missing _id parameter:', 'abc');
expect(screen.queryByTestId('mock-vitality-app-infos')).not.toBeInTheDocument();
});

it('renders repository and links correctly when present', async () => {
(useClientQuery as Mock).mockReturnValue({
isLoading: false,
data: {
getApplicationDetailsInfoByParams: {
_id: 4,
name: 'Vitality Repo Test',
acronym: 'VRT',
repo: {
organization: 'Vitality Org',
webUrl: 'https://github.com/vitality-org',
allBranches: ['main', 'feature-x'],
},
links: [{ label: 'Docs', value: 'https://docs.vitality.com' }],
},
},
});
it('handles undefined _id, disables query, and shows empty state', async () => {
mockGetUrlParams.mockReturnValue([undefined]); // _id is undefined
// useClientQuery will use its default mock return (isLoading: false, data: undefined)

render(<VitalityGeneralInformationView />);

await waitFor(() => {
expect(screen.getByText('Vitality Repo Test')).toBeInTheDocument();
expect(screen.getByText('Vitality Org')).toBeInTheDocument();
expect(screen.getByText('Docs')).toBeInTheDocument();
expect(screen.getByTestId('empty-indicator')).toBeInTheDocument();
});

expect(mockUseClientQuery).toHaveBeenCalledWith(expect.objectContaining({
queryCacheKey: ['getApplicationDetailsInfoByParams', undefined],
variables: { _id: undefined },
enabled: false,
}));
const sectionViewProps = (vi.mocked(require('../../commons/components/VitalitySectionView')).default as Mock).mock.calls[0][0];
expect(sectionViewProps.isLoading).toBe(false);
expect(sectionViewProps.isEmpty).toBe(true);
expect(console.warn).toHaveBeenCalledWith('Invalid or missing _id parameter:', undefined);
expect(screen.queryByTestId('mock-vitality-app-infos')).not.toBeInTheDocument();
});

it('does not render empty repository fields', async () => {
(useClientQuery as Mock).mockReturnValue({
isLoading: false,
data: {
getApplicationDetailsInfoByParams: {
_id: 5,
name: 'Vitality No Repo',
acronym: 'VNR',
repo: {
organization: '',
webUrl: '',
allBranches: [],
},
links: [],
},
},
});
it('correctly forms queryCacheKey with valid numeric string _id', () => {
mockGetUrlParams.mockReturnValue(['456']);
mockUseClientQuery.mockReturnValue({ isLoading: false, data: null });

render(<VitalityGeneralInformationView />);

await waitFor(() => {
expect(screen.getByText('Vitality No Repo')).toBeInTheDocument();
});

expect(screen.queryByText('Vitality Org')).not.toBeInTheDocument();
expect(screen.queryByText('Docs')).not.toBeInTheDocument();
expect(mockUseClientQuery).toHaveBeenCalledWith(expect.objectContaining({
queryCacheKey: ['getApplicationDetailsInfoByParams', '456'],
variables: { _id: 456 },
enabled: true,
}));
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,29 @@ interface VitalityGeneralInformationQueryType {

const VitalityGeneralInformationView = ({}) => {
const { getUrlParams } = useNavigationAdapter();
const [_id] = getUrlParams(['_id']);
const [rawId] = getUrlParams(['_id']);
let numericId: number | undefined;

if (typeof rawId === 'string' && /^\d+$/.test(rawId)) {
numericId = parseInt(rawId, 10);
} else {
console.warn('Invalid or missing _id parameter:', rawId);
}

const {
isLoading: isAppDetailsInfosLoading,
data: appDetailsInfos,
}: VitalityGeneralInformationQueryType = useClientQuery({
queryCacheKey: ['getApplicationDetailsInfoByParams', `${_id}`],
queryCacheKey: ['getApplicationDetailsInfoByParams', numericId ? String(numericId) : undefined],
queryBuilder: async () =>
buildClientQuery({
queryBaseUrl: VitalityApiConfig.VITALITY_BFF_URL,
query: GetApplicationDetailsInfosByParams,
variables: {
_id: parseInt(_id as string, 10),
_id: numericId,
},
}),
enabled: numericId !== undefined,
});

const appInfos = appDetailsInfos?.getApplicationDetailsInfoByParams;
Expand All @@ -54,7 +62,7 @@ const VitalityGeneralInformationView = ({}) => {
return (
<VitalitySectionView
isLoading={isAppDetailsInfosLoading}
isEmpty={!appInfos?.name?.length || !appInfos?.acronym?.length}
isEmpty={!appInfos}
title={translate('vitality.appDetailsPage.infos.title')}
description=""
avatar={<InfoOutlined />}
Expand Down
Loading