writing tests
Signed-off-by: Peter Macdonald <macdonald.peter90@gmail.com>
This commit is contained in:
+148
-74
@@ -15,117 +15,191 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { render, waitFor } from '@testing-library/react';
|
||||
import { CatalogApi } from '@backstage/catalog-client';
|
||||
import { FieldProps } from '@rjsf/core';
|
||||
import { EntityPickerProps } from '../EntityPicker/schema';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { OwnershipEntityRefPicker } from './OwnershipEntityRefPicker';
|
||||
import { TestApiProvider } from '@backstage/test-utils';
|
||||
import { catalogApiRef } from '@backstage/plugin-catalog-react';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { IdentityApi, identityApiRef } from '@backstage/core-plugin-api';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
const makeUserEntity = (
|
||||
kind: string,
|
||||
memberOf: string[],
|
||||
name: string,
|
||||
): Entity => ({
|
||||
apiVersion: 'scaffolder.backstage.io/v1beta3',
|
||||
kind,
|
||||
metadata: { name },
|
||||
spec: {
|
||||
memberOf: memberOf,
|
||||
},
|
||||
});
|
||||
|
||||
const makeGroupEntity = (
|
||||
kind: string,
|
||||
members: string[],
|
||||
name: string,
|
||||
): Entity => ({
|
||||
apiVersion: 'scaffolder.backstage.io/v1beta3',
|
||||
kind,
|
||||
metadata: { name },
|
||||
spec: {
|
||||
members: members,
|
||||
},
|
||||
});
|
||||
// Create a mock IdentityApi
|
||||
const mockIdentityApi: IdentityApi = {
|
||||
getProfileInfo: () =>
|
||||
Promise.resolve({
|
||||
displayName: 'Bob',
|
||||
email: 'bob@example.com',
|
||||
picture: 'https://example.com/picture.jpg',
|
||||
}),
|
||||
getBackstageIdentity: () =>
|
||||
Promise.resolve({
|
||||
id: 'Bob',
|
||||
idToken: 'token',
|
||||
type: 'user',
|
||||
userEntityRef: 'user:default/Bob',
|
||||
ownershipEntityRefs: ['group:default/group1', 'group:default/group2'],
|
||||
}),
|
||||
getCredentials: () => Promise.resolve({ token: 'token' }),
|
||||
signOut: () => Promise.resolve(),
|
||||
};
|
||||
|
||||
describe('<OwnershipEntityRefPicker />', () => {
|
||||
let entities: Entity[];
|
||||
const onChange = jest.fn();
|
||||
const schema = {};
|
||||
const required = false;
|
||||
let uiSchema: EntityPickerProps['uiSchema'];
|
||||
const rawErrors: string[] = [];
|
||||
const formData = undefined;
|
||||
|
||||
let props: FieldProps;
|
||||
|
||||
const catalogApi: jest.Mocked<CatalogApi> = {
|
||||
getLocationById: jest.fn(),
|
||||
getEntityByName: jest.fn(),
|
||||
addLocation: jest.fn(),
|
||||
getLocationByRef: jest.fn(),
|
||||
removeEntityByUid: jest.fn(),
|
||||
getEntities: jest.fn(async () => ({ items: entities })),
|
||||
} as any;
|
||||
|
||||
beforeEach(() => {
|
||||
entities = [
|
||||
makeUserEntity('User', ['group1', 'group2'], 'Bob'),
|
||||
makeGroupEntity('Group', ['Alice', 'Dave'], 'group3'),
|
||||
{
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Group',
|
||||
metadata: { name: 'group1' },
|
||||
spec: { members: ['Bob'] },
|
||||
},
|
||||
{
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Group',
|
||||
metadata: { name: 'group2' },
|
||||
spec: { members: ['Bob'] },
|
||||
},
|
||||
{
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Group',
|
||||
metadata: { name: 'group3' },
|
||||
spec: { members: ['Alice'] },
|
||||
},
|
||||
];
|
||||
|
||||
onChange.mockClear();
|
||||
catalogApi.getEntities.mockClear();
|
||||
});
|
||||
|
||||
afterEach(() => jest.resetAllMocks());
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('should only return the groups a user is part of', async () => {
|
||||
catalogApi.getEntityByRef.mockResolvedValueOnce(entities[0]);
|
||||
it('should only return the groups a user is part of and not the groups a user is not part of', async () => {
|
||||
const userGroups = entities.filter(
|
||||
entity =>
|
||||
entity.spec &&
|
||||
Array.isArray(entity.spec.members) &&
|
||||
entity.spec.members.includes('Bob'),
|
||||
);
|
||||
|
||||
uiSchema = { 'ui:options': { catalogApi } };
|
||||
props = {
|
||||
catalogApi.getEntities.mockResolvedValue({ items: userGroups });
|
||||
|
||||
const props = {
|
||||
onChange,
|
||||
schema,
|
||||
required,
|
||||
uiSchema,
|
||||
rawErrors,
|
||||
formData,
|
||||
} as unknown as FieldProps<any>;
|
||||
|
||||
render(<OwnershipEntityRefPicker {...props} />);
|
||||
render(
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[identityApiRef, mockIdentityApi],
|
||||
[catalogApiRef, catalogApi],
|
||||
]}
|
||||
>
|
||||
<OwnershipEntityRefPicker {...props} />
|
||||
</TestApiProvider>,
|
||||
);
|
||||
|
||||
expect(onChange).toHaveBeenCalledWith(['group1', 'group2']);
|
||||
await waitFor(() =>
|
||||
expect(catalogApi.getEntities).toHaveBeenCalledTimes(1),
|
||||
);
|
||||
|
||||
expect(catalogApi.getEntities).toHaveBeenCalledWith({
|
||||
filter: {
|
||||
kind: ['Group'],
|
||||
'relations.hasMember': ['group:default/group1', 'group:default/group2'],
|
||||
},
|
||||
});
|
||||
|
||||
// Check that getEntities was set up to return the correct data
|
||||
await expect(catalogApi.getEntities.mock.results[0].value).resolves.toEqual(
|
||||
{
|
||||
items: [
|
||||
{
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Group',
|
||||
metadata: { name: 'group1' },
|
||||
spec: { members: ['Bob'] },
|
||||
},
|
||||
{
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Group',
|
||||
metadata: { name: 'group2' },
|
||||
spec: { members: ['Bob'] },
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
await expect(
|
||||
catalogApi.getEntities.mock.results[0].value,
|
||||
).resolves.not.toEqual(
|
||||
expect.objectContaining({
|
||||
items: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
metadata: { name: 'group3' },
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should not return groups a user is not part of', async () => {
|
||||
// Mock 'getEntityByRef' to return the entity that represents Bob
|
||||
catalogApi.getEntityByRef.mockResolvedValueOnce(entities[0]);
|
||||
it('should display the groups a user is part of and not display the groups a user is not part of', async () => {
|
||||
const userGroups = entities.filter(
|
||||
entity =>
|
||||
entity.spec &&
|
||||
Array.isArray(entity.spec.members) &&
|
||||
entity.spec.members.includes('Bob'),
|
||||
);
|
||||
catalogApi.getEntities.mockResolvedValue({ items: userGroups });
|
||||
|
||||
uiSchema = { 'ui:options': { catalogApi } };
|
||||
props = {
|
||||
const props = {
|
||||
onChange,
|
||||
schema,
|
||||
required,
|
||||
uiSchema,
|
||||
rawErrors,
|
||||
formData,
|
||||
} as unknown as FieldProps<any>;
|
||||
|
||||
render(<OwnershipEntityRefPicker {...props} />);
|
||||
const { queryByText, getByRole } = render(
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[identityApiRef, mockIdentityApi],
|
||||
[catalogApiRef, catalogApi],
|
||||
]}
|
||||
>
|
||||
<OwnershipEntityRefPicker {...props} />
|
||||
</TestApiProvider>,
|
||||
);
|
||||
|
||||
// The onChange should not have been called with 'group3', since Bob is not part of 'group3'
|
||||
expect(onChange).not.toHaveBeenCalledWith(['group3']);
|
||||
});
|
||||
await waitFor(() =>
|
||||
expect(catalogApi.getEntities).toHaveBeenCalledTimes(1),
|
||||
);
|
||||
|
||||
it('should render without imploding', () => {
|
||||
uiSchema = { 'ui:options': { catalogApi } };
|
||||
props = {
|
||||
onChange,
|
||||
schema,
|
||||
required,
|
||||
uiSchema,
|
||||
rawErrors,
|
||||
formData,
|
||||
} as unknown as FieldProps<any>;
|
||||
// Simulate user input
|
||||
const inputField = getByRole('combobox');
|
||||
userEvent.click(inputField);
|
||||
userEvent.type(inputField, 'group');
|
||||
|
||||
const { container } = render(<OwnershipEntityRefPicker {...props} />);
|
||||
expect(container).not.toBeNull();
|
||||
// Wait for the dropdown elements to appear
|
||||
await waitFor(() => {
|
||||
const group1Element = queryByText('group1');
|
||||
const group2Element = queryByText('group2');
|
||||
expect(group1Element).toBeInTheDocument();
|
||||
expect(group2Element).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Assert that 'group3' is not rendered in the component
|
||||
expect(queryByText('group3')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
+10
-6
@@ -30,12 +30,16 @@ export { OwnershipEntityRefPickerSchema };
|
||||
export const OwnershipEntityRefPicker = (
|
||||
props: OwnershipEntityRefPickerProps,
|
||||
) => {
|
||||
const { uiSchema, required, rawErrors, formData } = props;
|
||||
const {
|
||||
schema: { title, description },
|
||||
required,
|
||||
rawErrors,
|
||||
} = props;
|
||||
|
||||
const identityApi = useApi(identityApiRef);
|
||||
const catalogApi = useApi(catalogApiRef);
|
||||
const [groups, setGroups] = useState<string[]>([]);
|
||||
const [selectedGroup, setSelectedGroup] = useState('');
|
||||
const [selectedGroup, setSelectedGroup] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchUserGroups = async () => {
|
||||
@@ -63,20 +67,20 @@ export const OwnershipEntityRefPicker = (
|
||||
<FormControl
|
||||
margin="normal"
|
||||
required={required}
|
||||
error={rawErrors?.length > 0 && !formData}
|
||||
error={rawErrors?.length > 0}
|
||||
>
|
||||
<Autocomplete
|
||||
id="OwnershipEntityRefPicker-dropdown"
|
||||
options={groups || []}
|
||||
value={selectedGroup || null}
|
||||
value={selectedGroup}
|
||||
onChange={(_, value) => setSelectedGroup(value || '')}
|
||||
getOptionLabel={group => group}
|
||||
renderInput={params => (
|
||||
<TextField
|
||||
{...params}
|
||||
label={uiSchema['ui:options']?.title}
|
||||
label={title}
|
||||
margin="dense"
|
||||
helperText={uiSchema['ui:options']?.description}
|
||||
helperText={description}
|
||||
FormHelperTextProps={{ margin: 'dense', style: { marginLeft: 0 } }}
|
||||
variant="outlined"
|
||||
required={required}
|
||||
|
||||
Reference in New Issue
Block a user