Adding test cases and cleaning up.

Signed-off-by: Aramis Sennyey <sennyeya@amazon.com>
This commit is contained in:
Aramis Sennyey
2023-02-10 18:46:00 -05:00
committed by Fredrik Adelöw
parent d7fa409a72
commit c66531a8eb
5 changed files with 280 additions and 90 deletions
@@ -15,11 +15,38 @@
*/
import { Entity, parseEntityRef } from '@backstage/catalog-model';
import { fireEvent, render, screen } from '@testing-library/react';
import { fireEvent, screen } from '@testing-library/react';
import React from 'react';
import { MockEntityListContextProvider } from '../../testUtils/providers';
import { EntityOwnerFilter } from '../../filters';
import { EntityOwnerPicker } from './EntityOwnerPicker';
import { ApiProvider } from '@backstage/core-app-api';
import { renderWithEffects, TestApiRegistry } from '@backstage/test-utils';
import { catalogApiRef, CatalogApi } from '../..';
const ownerEntities: Entity[] = [
{
apiVersion: '1',
kind: 'Group',
metadata: {
name: 'some-owner',
},
},
{
apiVersion: '1',
kind: 'Group',
metadata: {
name: 'some-owner-2',
},
},
{
apiVersion: '1',
kind: 'Group',
metadata: {
name: 'another-owner',
},
},
];
const sampleEntities: Entity[] = [
{
@@ -67,14 +94,68 @@ const sampleEntities: Entity[] = [
},
];
const getEntitiesByRefs = jest.fn(async ({ entityRefs }) => ({
items: entityRefs.map((e: string) =>
ownerEntities.find(f => f.metadata.name === e),
),
}));
const mockCatalogApi: Partial<CatalogApi> = {
getEntitiesByRefs,
};
describe('<EntityOwnerPicker/>', () => {
it('renders all owners', () => {
render(
<MockEntityListContextProvider
value={{ entities: sampleEntities, backendEntities: sampleEntities }}
>
<EntityOwnerPicker />
</MockEntityListContextProvider>,
const mockApis = TestApiRegistry.from([catalogApiRef, mockCatalogApi]);
it('renders display name when available', async () => {
const names: Record<string, string> = {
'some-owner': 'Some Team',
'some-owner-2': 'Other Team',
'another-owner': 'AnotherTeam',
};
getEntitiesByRefs.mockResolvedValueOnce({
items: ownerEntities.map(e => {
e.spec = {
...e.spec,
profile: {
displayName: names[e.metadata.name],
},
};
return e;
}),
});
await renderWithEffects(
<ApiProvider apis={mockApis}>
<MockEntityListContextProvider
value={{ entities: sampleEntities, backendEntities: sampleEntities }}
>
<EntityOwnerPicker />
</MockEntityListContextProvider>
</ApiProvider>,
);
expect(screen.getByText('Owner')).toBeInTheDocument();
fireEvent.click(screen.getByTestId('owner-picker-expand'));
sampleEntities
.flatMap(e => e.relations?.map(r => parseEntityRef(r.targetRef).name))
.forEach(owner => {
expect(screen.getByText(names[owner as string])).toBeInTheDocument();
});
});
/**
* All previous test cases are still applicable for the case where there is no
* owner entity returned from the API or the owner entity returned does not have
* a display name.
*/
it('renders all owners', async () => {
await renderWithEffects(
<ApiProvider apis={mockApis}>
<MockEntityListContextProvider
value={{ entities: sampleEntities, backendEntities: sampleEntities }}
>
<EntityOwnerPicker />
</MockEntityListContextProvider>
</ApiProvider>,
);
expect(screen.getByText('Owner')).toBeInTheDocument();
@@ -86,13 +167,15 @@ describe('<EntityOwnerPicker/>', () => {
});
});
it('renders unique owners in alphabetical order', () => {
render(
<MockEntityListContextProvider
value={{ entities: sampleEntities, backendEntities: sampleEntities }}
>
<EntityOwnerPicker />
</MockEntityListContextProvider>,
it('renders unique owners in alphabetical order', async () => {
await renderWithEffects(
<ApiProvider apis={mockApis}>
<MockEntityListContextProvider
value={{ entities: sampleEntities, backendEntities: sampleEntities }}
>
<EntityOwnerPicker />
</MockEntityListContextProvider>
</ApiProvider>,
);
expect(screen.getByText('Owner')).toBeInTheDocument();
@@ -105,20 +188,22 @@ describe('<EntityOwnerPicker/>', () => {
]);
});
it('respects the query parameter filter value', () => {
it('respects the query parameter filter value', async () => {
const updateFilters = jest.fn();
const queryParameters = { owners: ['another-owner'] };
render(
<MockEntityListContextProvider
value={{
entities: sampleEntities,
backendEntities: sampleEntities,
updateFilters,
queryParameters,
}}
>
<EntityOwnerPicker />
</MockEntityListContextProvider>,
await renderWithEffects(
<ApiProvider apis={mockApis}>
<MockEntityListContextProvider
value={{
entities: sampleEntities,
backendEntities: sampleEntities,
updateFilters,
queryParameters,
}}
>
<EntityOwnerPicker />
</MockEntityListContextProvider>
</ApiProvider>,
);
expect(updateFilters).toHaveBeenLastCalledWith({
@@ -126,18 +211,20 @@ describe('<EntityOwnerPicker/>', () => {
});
});
it('adds owners to filters', () => {
it('adds owners to filters', async () => {
const updateFilters = jest.fn();
render(
<MockEntityListContextProvider
value={{
entities: sampleEntities,
backendEntities: sampleEntities,
updateFilters,
}}
>
<EntityOwnerPicker />
</MockEntityListContextProvider>,
await renderWithEffects(
<ApiProvider apis={mockApis}>
<MockEntityListContextProvider
value={{
entities: sampleEntities,
backendEntities: sampleEntities,
updateFilters,
}}
>
<EntityOwnerPicker />
</MockEntityListContextProvider>
</ApiProvider>,
);
expect(updateFilters).toHaveBeenLastCalledWith({
owners: undefined,
@@ -150,19 +237,21 @@ describe('<EntityOwnerPicker/>', () => {
});
});
it('removes owners from filters', () => {
it('removes owners from filters', async () => {
const updateFilters = jest.fn();
render(
<MockEntityListContextProvider
value={{
entities: sampleEntities,
backendEntities: sampleEntities,
updateFilters,
filters: { owners: new EntityOwnerFilter(['some-owner']) },
}}
>
<EntityOwnerPicker />
</MockEntityListContextProvider>,
await renderWithEffects(
<ApiProvider apis={mockApis}>
<MockEntityListContextProvider
value={{
entities: sampleEntities,
backendEntities: sampleEntities,
updateFilters,
filters: { owners: new EntityOwnerFilter(['some-owner']) },
}}
>
<EntityOwnerPicker />
</MockEntityListContextProvider>
</ApiProvider>,
);
expect(updateFilters).toHaveBeenLastCalledWith({
owners: new EntityOwnerFilter(['some-owner']),
@@ -176,49 +265,55 @@ describe('<EntityOwnerPicker/>', () => {
});
});
it('responds to external queryParameters changes', () => {
it('responds to external queryParameters changes', async () => {
const updateFilters = jest.fn();
const rendered = render(
<MockEntityListContextProvider
value={{
updateFilters,
queryParameters: { owners: ['team-a'] },
backendEntities: sampleEntities,
}}
>
<EntityOwnerPicker />
</MockEntityListContextProvider>,
const rendered = await renderWithEffects(
<ApiProvider apis={mockApis}>
<MockEntityListContextProvider
value={{
updateFilters,
queryParameters: { owners: ['team-a'] },
backendEntities: sampleEntities,
}}
>
<EntityOwnerPicker />
</MockEntityListContextProvider>
</ApiProvider>,
);
expect(updateFilters).toHaveBeenLastCalledWith({
owners: new EntityOwnerFilter(['team-a']),
});
rendered.rerender(
<MockEntityListContextProvider
value={{
updateFilters,
queryParameters: { owners: ['team-b'] },
backendEntities: sampleEntities,
}}
>
<EntityOwnerPicker />
</MockEntityListContextProvider>,
<ApiProvider apis={mockApis}>
<MockEntityListContextProvider
value={{
updateFilters,
queryParameters: { owners: ['team-b'] },
backendEntities: sampleEntities,
}}
>
<EntityOwnerPicker />
</MockEntityListContextProvider>
</ApiProvider>,
);
expect(updateFilters).toHaveBeenLastCalledWith({
owners: new EntityOwnerFilter(['team-b']),
});
});
it('removes owners from filters if there are none available', () => {
it('removes owners from filters if there are none available', async () => {
const updateFilters = jest.fn();
render(
<MockEntityListContextProvider
value={{
updateFilters,
queryParameters: { owners: ['team-a'] },
backendEntities: [],
}}
>
<EntityOwnerPicker />
</MockEntityListContextProvider>,
await renderWithEffects(
<ApiProvider apis={mockApis}>
<MockEntityListContextProvider
value={{
updateFilters,
queryParameters: { owners: ['team-a'] },
backendEntities: [],
}}
>
<EntityOwnerPicker />
</MockEntityListContextProvider>
</ApiProvider>,
);
expect(updateFilters).toHaveBeenLastCalledWith({
owners: undefined,
@@ -40,6 +40,7 @@ import { humanizeEntityRef } from '../EntityRefLink';
import useAsync from 'react-use/lib/useAsync';
import { useApi } from '@backstage/core-plugin-api';
import { catalogApiRef } from '../../api';
import { humanizeEntity } from '../EntityRefLink/humanize';
/** @public */
export type CatalogReactEntityOwnerPickerClassKey = 'input';
@@ -109,9 +110,16 @@ export const EntityOwnerPicker = () => {
entityRefs: availableOwners.map(ref =>
stringifyEntityRef(parseEntityRef(ref, { defaultKind: 'Group' })),
),
fields: [
'kind',
'metadata.name',
'metadata.title',
'spec.profile.displayName',
],
});
return availableOwners.map(
(e, i) => items.at(i) || ({ metadata: { name: e } } as Entity),
(e, i) =>
items.at(i) || ({ metadata: { name: e }, kind: 'Group' } as Entity),
);
}, [availableOwners]);
@@ -136,15 +144,19 @@ export const EntityOwnerPicker = () => {
disableCloseOnSelect
loading={loading}
options={owners || []}
getOptionLabel={option =>
option.metadata.title || option.metadata.name
value={
owners?.filter(e =>
selectedOwners.some(
f => f === humanizeEntityRef(e, { defaultKind: 'Group' }),
),
) ?? []
}
value={owners?.filter(e =>
selectedOwners.some(f => f === e.metadata.name),
)}
onChange={(_: object, value: Entity[]) =>
setSelectedOwners(value.map(e => e.metadata.name))
}
getOptionLabel={option =>
humanizeEntity(option, { defaultKind: 'Group' })
}
renderOption={(option, { selected }) => (
<FormControlLabel
control={
@@ -155,7 +167,7 @@ export const EntityOwnerPicker = () => {
/>
}
onClick={event => event.preventDefault()}
label={option.metadata.title || option.metadata.name}
label={humanizeEntity(option, { defaultKind: 'Group' })}
/>
)}
size="small"
@@ -14,7 +14,8 @@
* limitations under the License.
*/
import { humanizeEntityRef } from './humanize';
import { Entity } from '@backstage/catalog-model';
import { humanizeEntity, humanizeEntityRef } from './humanize';
describe('humanizeEntityRef', () => {
it('formats entity in default namespace', () => {
@@ -210,3 +211,59 @@ describe('humanizeEntityRef', () => {
expect(title).toEqual('component:default/software');
});
});
describe('humanizeEntity', () => {
it('gives a readable name when one is provided at metadata.title', () => {
expect(
humanizeEntity({
metadata: { name: 'my-entity', title: 'My Title' },
} as Entity),
).toBe('My Title');
});
it.each([
[
'User',
{
apiVersion: '1',
kind: 'User',
metadata: {
name: 'user-name',
},
spec: {
profile: {
displayName: 'User Name',
},
},
},
'User Name',
],
[
'Group',
{
apiVersion: '1',
kind: 'User',
metadata: {
name: 'team-name',
},
spec: {
profile: {
displayName: 'Team Name',
},
},
},
'Team Name',
],
])(
'gives a readable name for kind %s when one is provided at spec.profile.displayName',
(_, entity: Entity, expected) => {
expect(humanizeEntity(entity)).toBe(expected);
},
);
it('should pass through to humanizeEntityRef when nothing matches', () => {
expect(
humanizeEntity({ kind: 'Group', metadata: { name: 'test' } } as Entity),
).toBe('group:test');
});
});
@@ -18,6 +18,7 @@ import {
Entity,
CompoundEntityRef,
DEFAULT_NAMESPACE,
UserEntity,
} from '@backstage/catalog-model';
/**
@@ -65,3 +66,28 @@ export function humanizeEntityRef(
: kind;
return `${kind ? `${kind}:` : ''}${namespace ? `${namespace}/` : ''}${name}`;
}
/**
* Convert an entity to its more common name.
*
* @param entity Entity to convert.
* @returns Readable name, defaults to unique identifier.
*/
export function humanizeEntity(
entity: Entity,
opts?: {
defaultKind?: string;
defaultNamespace?: string | false;
},
) {
let title: string | undefined = undefined;
switch (entity.kind) {
case 'User':
case 'Group':
title = (entity as UserEntity).spec?.profile?.displayName;
break;
default:
title = entity.metadata.title;
}
return title || humanizeEntityRef(entity, opts);
}
@@ -18,4 +18,4 @@ export { EntityRefLink } from './EntityRefLink';
export type { EntityRefLinkProps } from './EntityRefLink';
export { EntityRefLinks } from './EntityRefLinks';
export type { EntityRefLinksProps } from './EntityRefLinks';
export { humanizeEntityRef } from './humanize';
export { humanizeEntityRef, humanizeEntity } from './humanize';