diff --git a/v6y-apps/front/src/__tests__/app-details/VitalityGeneralInformationView-test.tsx b/v6y-apps/front/src/__tests__/app-details/VitalityGeneralInformationView-test.tsx
index ee20d0ab..053b9c4a 100644
--- a/v6y-apps/front/src/__tests__/app-details/VitalityGeneralInformationView-test.tsx
+++ b/v6y-apps/front/src/__tests__/app-details/VitalityGeneralInformationView-test.tsx
@@ -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(),
}));
@@ -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: () =>
,
+ 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) => ;
+ },
+ };
+});
+
+// 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 }) => (
+
+ {isLoading &&
Loading...
}
+ {isEmpty &&
Empty
}
+ {!isLoading && !isEmpty && children}
+
+ )),
+}));
+
+vi.mock('../../commons/components/application-info/VitalityAppInfos', () => ({
+ default: vi.fn(({ app }) => {app?.name}
),
+}));
+
+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();
-
- 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();
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();
+ 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();
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();
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();
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();
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();
- 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,
+ }));
});
});
diff --git a/v6y-apps/front/src/features/app-details/components/infos/VitalityGeneralInformationView.tsx b/v6y-apps/front/src/features/app-details/components/infos/VitalityGeneralInformationView.tsx
index a0c4935b..89635b99 100644
--- a/v6y-apps/front/src/features/app-details/components/infos/VitalityGeneralInformationView.tsx
+++ b/v6y-apps/front/src/features/app-details/components/infos/VitalityGeneralInformationView.tsx
@@ -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;
@@ -54,7 +62,7 @@ const VitalityGeneralInformationView = ({}) => {
return (
}