diff --git a/plugins/scaffolder/src/components/fields/OwnershipEntityRefPicker/OwnershipEntityRefPicker.test.tsx b/plugins/scaffolder/src/components/fields/OwnershipEntityRefPicker/OwnershipEntityRefPicker.test.tsx
index a290adbaee..81bbb21438 100644
--- a/plugins/scaffolder/src/components/fields/OwnershipEntityRefPicker/OwnershipEntityRefPicker.test.tsx
+++ b/plugins/scaffolder/src/components/fields/OwnershipEntityRefPicker/OwnershipEntityRefPicker.test.tsx
@@ -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('', () => {
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 = {
- 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;
- render();
+ render(
+
+
+ ,
+ );
- 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;
- render();
+ const { queryByText, getByRole } = render(
+
+
+ ,
+ );
- // 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;
+ // Simulate user input
+ const inputField = getByRole('combobox');
+ userEvent.click(inputField);
+ userEvent.type(inputField, 'group');
- const { container } = render();
- 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();
});
});
diff --git a/plugins/scaffolder/src/components/fields/OwnershipEntityRefPicker/OwnershipEntityRefPicker.tsx b/plugins/scaffolder/src/components/fields/OwnershipEntityRefPicker/OwnershipEntityRefPicker.tsx
index 5fee57eb86..f89c6ff789 100644
--- a/plugins/scaffolder/src/components/fields/OwnershipEntityRefPicker/OwnershipEntityRefPicker.tsx
+++ b/plugins/scaffolder/src/components/fields/OwnershipEntityRefPicker/OwnershipEntityRefPicker.tsx
@@ -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([]);
- const [selectedGroup, setSelectedGroup] = useState('');
+ const [selectedGroup, setSelectedGroup] = useState(null);
useEffect(() => {
const fetchUserGroups = async () => {
@@ -63,20 +67,20 @@ export const OwnershipEntityRefPicker = (
0 && !formData}
+ error={rawErrors?.length > 0}
>
setSelectedGroup(value || '')}
getOptionLabel={group => group}
renderInput={params => (