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
@@ -0,0 +1,135 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import type { PersistentVolumeClaimKind } from '@console/internal/module/k8s';
import { AccessModeSelector, getPVCAccessModes } from '../access-mode';

jest.mock('@console/internal/components/storage/shared', () => ({
getAccessModeOptions: () => [
{ value: 'ReadWriteOnce', title: 'Single user (RWO)' },
{ value: 'ReadWriteMany', title: 'Shared access (RWX)' },
{ value: 'ReadOnlyMany', title: 'Read only (ROX)' },
{ value: 'ReadWriteOncePod', title: 'Read write once pod (RWOP)' },
],
getAccessModeForProvisioner: jest.fn(() => ['ReadWriteOnce', 'ReadWriteMany']),
}));

describe('AccessModeSelector', () => {
describe('getPVCAccessModes', () => {
it('should return access mode titles from PVC resource', () => {
const mockPVC = {
apiVersion: 'v1',
kind: 'PersistentVolumeClaim',
metadata: { name: 'test-pvc', namespace: 'default' },
spec: {
accessModes: ['ReadWriteOnce'],
resources: { requests: { storage: '1Gi' } },
storageClassName: 'gp2',
},
} as PersistentVolumeClaimKind;

const result = getPVCAccessModes(mockPVC, 'title');
expect(result).toEqual(['Single user (RWO)']);
});

it('should return access mode values from PVC resource', () => {
const mockPVC = {
apiVersion: 'v1',
kind: 'PersistentVolumeClaim',
metadata: { name: 'test-pvc', namespace: 'default' },
spec: {
accessModes: ['ReadWriteMany'],
resources: { requests: { storage: '1Gi' } },
storageClassName: 'gp2',
},
} as PersistentVolumeClaimKind;

const result = getPVCAccessModes(mockPVC, 'value');
expect(result).toEqual(['ReadWriteMany']);
});

it('should return empty array when PVC has no access modes', () => {
const mockPVC = {
apiVersion: 'v1',
kind: 'PersistentVolumeClaim',
metadata: { name: 'test-pvc', namespace: 'default' },
spec: {
accessModes: [],
resources: { requests: { storage: '1Gi' } },
storageClassName: 'gp2',
},
} as PersistentVolumeClaimKind;

const result = getPVCAccessModes(mockPVC, 'value');
expect(result).toEqual([]);
});

it('should handle undefined PVC resource', () => {
const result = getPVCAccessModes(undefined, 'value');
expect(result).toEqual([]);
});
});

describe('AccessModeSelector', () => {
const defaultProps = {
onChange: jest.fn(),
loaded: true,
provisioner: 'kubernetes.io/aws-ebs',
};

beforeEach(() => {
jest.clearAllMocks();
});

it('should render access mode label', () => {
render(<AccessModeSelector {...defaultProps} />);

expect(screen.getByText('Access mode')).toBeVisible();
});

it('should show loading skeleton when not loaded', () => {
render(<AccessModeSelector {...defaultProps} loaded={false} />);

expect(screen.getByText('Access mode')).toBeVisible();
expect(screen.getByRole('status', { busy: true })).toBeVisible();
});

it('should render select dropdown when loaded', async () => {
render(<AccessModeSelector {...defaultProps} />);

expect(await screen.findByRole('button')).toBeVisible();
});

it('should display description when provided', async () => {
const description = 'Select the access mode for your storage';
render(<AccessModeSelector {...defaultProps} description={description} />);

expect(await screen.findByText(description)).toBeVisible();
});

it('should open dropdown when clicked', async () => {
const user = userEvent.setup();
render(<AccessModeSelector {...defaultProps} />);

const toggle = await screen.findByRole('button');
await user.click(toggle);

expect(await screen.findByRole('listbox')).toBeVisible();
});

it('should call onChange when access mode is selected', async () => {
const user = userEvent.setup();
const onChange = jest.fn();
render(<AccessModeSelector {...defaultProps} onChange={onChange} />);

const toggle = await screen.findByRole('button');
await user.click(toggle);

expect(await screen.findByRole('listbox')).toBeVisible();

const option = screen.getByText('Shared access (RWX)');
await user.click(option);

expect(onChange).toHaveBeenCalledWith('ReadWriteMany');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,9 @@ export const AccessModeSelector: FC<AccessModeSelectorProps> = (props) => {
{description}
</p>
)}
{(!loaded || !allowedAccessModes) && <div className="skeleton-text" />}
{(!loaded || !allowedAccessModes) && (
<div className="skeleton-text" role="status" aria-busy="true" />
)}
</FormGroup>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ const ClusterConfigurationCustomField: FC<ClusterConfigurationCustomFieldProps>
return (
<ErrorBoundaryInline>
<FormLayout>
<CustomComponent {...field.props} readonly={item.readonly} />
<div role="group" aria-label={item.label} data-readonly={String(item.readonly)}>
<CustomComponent {...field.props} readonly={item.readonly} />
</div>
</FormLayout>
</ErrorBoundaryInline>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,29 @@
import type { FC } from 'react';
import { Form } from '@patternfly/react-core';
import { useTranslation } from 'react-i18next';
import ClusterConfigurationField from './ClusterConfigurationField';
import type { ResolvedClusterConfigurationItem } from './types';

type ClusterConfigurationFormProps = {
items: ResolvedClusterConfigurationItem[];
items?: ResolvedClusterConfigurationItem[];
};

const ClusterConfigurationForm: FC<ClusterConfigurationFormProps> = ({ items }) =>
items?.length > 0 ? (
<Form onSubmit={(event) => event.preventDefault()}>
const ClusterConfigurationForm: FC<ClusterConfigurationFormProps> = ({ items }) => {
const { t } = useTranslation();

if (!items?.length) {
return null;
}

return (
<Form
aria-label={t('console-app~Cluster configuration')}
onSubmit={(event) => event.preventDefault()}
>
{items.map((item) => (
<ClusterConfigurationField key={item.id} item={item} />
))}
</Form>
) : null;
);
};
export default ClusterConfigurationForm;
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import type { ReactElement } from 'react';
import { render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router';
import type { ClusterConfigurationCustomField as ClusterConfigurationCustomFieldType } from '@console/dynamic-plugin-sdk/src';
import { ClusterConfigurationFieldType } from '@console/dynamic-plugin-sdk/src';
import type { ResolvedCodeRefProperties } from '@console/dynamic-plugin-sdk/src/types';
import ClusterConfigurationCustomField from '../ClusterConfigurationCustomField';
import type { ResolvedClusterConfigurationItem } from '../types';

const renderWithRouter = (ui: ReactElement) => render(<MemoryRouter>{ui}</MemoryRouter>);

describe('ClusterConfigurationCustomField', () => {
const MockCustomComponent = () => <div>Custom Component Content</div>;

const createMockItem = (readonly: boolean): ResolvedClusterConfigurationItem => ({
id: 'test-item',
groupId: 'test-group',
label: 'Test Custom Field',
description: 'Test description',
field: {
type: ClusterConfigurationFieldType.custom,
component: MockCustomComponent,
},
readonly,
});

const createMockField = (
props?: ClusterConfigurationCustomFieldType['props'],
): ResolvedCodeRefProperties<ClusterConfigurationCustomFieldType> => ({
type: ClusterConfigurationFieldType.custom,
component: MockCustomComponent,
props,
});

it('should render custom component', () => {
const item = createMockItem(false);
const field = createMockField();

renderWithRouter(<ClusterConfigurationCustomField item={item} field={field} />);

expect(screen.getByRole('group', { name: 'Test Custom Field' })).toBeVisible();
expect(screen.getByText('Custom Component Content')).toBeVisible();
});

it('should wrap content in error boundary', () => {
const item = createMockItem(false);
const field = createMockField();

renderWithRouter(<ClusterConfigurationCustomField item={item} field={field} />);

expect(screen.getByRole('region', { name: 'Inline error boundary' })).toBeVisible();
});

it('should wrap content in form layout', () => {
const item = createMockItem(false);
const field = createMockField();

renderWithRouter(<ClusterConfigurationCustomField item={item} field={field} />);

expect(screen.getByRole('region', { name: 'Form layout' })).toBeVisible();
});

it('should pass readonly prop to custom component when false', () => {
const item = createMockItem(false);
const field = createMockField();

renderWithRouter(<ClusterConfigurationCustomField item={item} field={field} />);

expect(screen.getByRole('group', { name: 'Test Custom Field' })).toHaveAttribute(
'data-readonly',
'false',
);
});

it('should pass readonly prop to custom component when true', () => {
const item = createMockItem(true);
const field = createMockField();

renderWithRouter(<ClusterConfigurationCustomField item={item} field={field} />);

expect(screen.getByRole('group', { name: 'Test Custom Field' })).toHaveAttribute(
'data-readonly',
'true',
);
});

it('should pass custom props to component', () => {
const CustomComponentWithProps = ({
customProp,
}: {
readonly: boolean;
customProp?: string;
}) => <p aria-label="Extension with custom props">{customProp}</p>;

const item = createMockItem(false);
const field: ResolvedCodeRefProperties<ClusterConfigurationCustomFieldType> = {
type: ClusterConfigurationFieldType.custom,
component: CustomComponentWithProps,
props: { customProp: 'test-value' },
};

renderWithRouter(<ClusterConfigurationCustomField item={item} field={field} />);

expect(
screen.getByRole('paragraph', { name: 'Extension with custom props' }),
).toHaveTextContent('test-value');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { render, screen } from '@testing-library/react';
import { ClusterConfigurationFieldType } from '@console/dynamic-plugin-sdk/src';
import ClusterConfigurationField from '../ClusterConfigurationField';
import type { ResolvedClusterConfigurationItem } from '../types';

jest.mock('../ClusterConfigurationCustomField', () => ({
__esModule: true,
default: ({ item }: { item: ResolvedClusterConfigurationItem }) => (
<div role="region" aria-label={item.label} data-readonly={String(item.readonly)}>
{item.label}
</div>
),
}));

describe('ClusterConfigurationField', () => {
const createMockItem = (): ResolvedClusterConfigurationItem => ({
id: 'test-item',
groupId: 'test-group',
label: 'Test Field Label',
description: 'Test description',
field: {
type: ClusterConfigurationFieldType.custom,
component: jest.fn(),
},
readonly: false,
});

it('should render ClusterConfigurationCustomField for custom field type', () => {
const item = createMockItem();
render(<ClusterConfigurationField item={item} />);

expect(screen.getByRole('region', { name: 'Test Field Label' })).toBeVisible();
expect(screen.getByRole('region', { name: 'Test Field Label' })).toHaveAttribute(
'data-readonly',
'false',
);
expect(screen.getByText('Test Field Label')).toBeVisible();
});

it('should render null for unsupported field type', () => {
const item = {
...createMockItem(),
field: {
type: 'unsupported' as typeof ClusterConfigurationFieldType.custom,
component: jest.fn(),
},
};

render(<ClusterConfigurationField item={item} />);
expect(screen.queryByRole('region')).not.toBeInTheDocument();
});

it('should pass readonly state to child component', () => {
const item = {
...createMockItem(),
readonly: true,
};
render(<ClusterConfigurationField item={item} />);

expect(screen.getByRole('region', { name: 'Test Field Label' })).toHaveAttribute(
'data-readonly',
'true',
);
});

it('should render with correct item properties', () => {
const item: ResolvedClusterConfigurationItem = {
id: 'unique-id',
groupId: 'group-1',
label: 'Unique Label',
description: 'Unique description',
field: {
type: ClusterConfigurationFieldType.custom,
component: jest.fn(),
},
readonly: false,
};

render(<ClusterConfigurationField item={item} />);

expect(screen.getByRole('region', { name: 'Unique Label' })).toBeVisible();
});

it('should handle text field type by returning null', () => {
const item = {
...createMockItem(),
field: {
type: ClusterConfigurationFieldType.text as typeof ClusterConfigurationFieldType.custom,
component: jest.fn(),
},
};

render(<ClusterConfigurationField item={item} />);
expect(screen.queryByRole('region')).not.toBeInTheDocument();
});
});
Loading