feat: adding a new TemplateListPage and CategoryPicker with tests
Signed-off-by: blam <ben@blam.sh>
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { useEntityTypeFilter } from '@backstage/plugin-catalog-react';
|
||||
import { CategoryPicker } from './CategoryPicker';
|
||||
import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
|
||||
import { alertApiRef } from '@backstage/core-plugin-api';
|
||||
import { fireEvent } from '@testing-library/react';
|
||||
|
||||
jest.mock('@backstage/plugin-catalog-react', () => ({
|
||||
useEntityTypeFilter: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('CategoryPicker', () => {
|
||||
const mockAlertApi = { post: jest.fn() };
|
||||
|
||||
beforeEach(() => {
|
||||
mockAlertApi.post.mockClear();
|
||||
});
|
||||
|
||||
it('should post the error to errorApi if an errors is returned', async () => {
|
||||
(useEntityTypeFilter as jest.Mock).mockReturnValue({
|
||||
error: new Error('something broked'),
|
||||
});
|
||||
|
||||
await renderInTestApp(
|
||||
<TestApiProvider apis={[[alertApiRef, mockAlertApi]]}>
|
||||
<CategoryPicker />
|
||||
</TestApiProvider>,
|
||||
);
|
||||
|
||||
expect(mockAlertApi.post).toHaveBeenCalledWith({
|
||||
message: expect.stringContaining('something broked'),
|
||||
severity: 'error',
|
||||
});
|
||||
});
|
||||
|
||||
it('should render loading if the hook is loading', async () => {
|
||||
(useEntityTypeFilter as jest.Mock).mockReturnValue({
|
||||
loading: true,
|
||||
});
|
||||
|
||||
const { findByTestId } = await renderInTestApp(
|
||||
<TestApiProvider apis={[[alertApiRef, mockAlertApi]]}>
|
||||
<CategoryPicker />
|
||||
</TestApiProvider>,
|
||||
);
|
||||
|
||||
expect(await findByTestId('progress')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render if there is no available types', async () => {
|
||||
(useEntityTypeFilter as jest.Mock).mockReturnValue({
|
||||
availableTypes: null,
|
||||
});
|
||||
|
||||
const { queryByText } = await renderInTestApp(
|
||||
<TestApiProvider apis={[[alertApiRef, mockAlertApi]]}>
|
||||
<CategoryPicker />
|
||||
</TestApiProvider>,
|
||||
);
|
||||
|
||||
expect(queryByText('Categories')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the autocomplete with the availableTypes', async () => {
|
||||
const mockAvailableTypes = ['foo', 'bar'];
|
||||
|
||||
(useEntityTypeFilter as jest.Mock).mockReturnValue({
|
||||
availableTypes: mockAvailableTypes,
|
||||
});
|
||||
|
||||
const { getByRole } = await renderInTestApp(
|
||||
<TestApiProvider apis={[[alertApiRef, mockAlertApi]]}>
|
||||
<CategoryPicker />
|
||||
</TestApiProvider>,
|
||||
);
|
||||
|
||||
const openButton = getByRole('button', { name: 'Open' });
|
||||
openButton.click();
|
||||
|
||||
expect(getByRole('checkbox', { name: 'Foo' })).toBeInTheDocument();
|
||||
expect(getByRole('checkbox', { name: 'Bar' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should call setSelectedTypes when one of the options are called', async () => {
|
||||
const mockAvailableTypes = ['foo', 'bar'];
|
||||
const mockSetSelectedTypes = jest.fn();
|
||||
|
||||
(useEntityTypeFilter as jest.Mock).mockReturnValue({
|
||||
availableTypes: mockAvailableTypes,
|
||||
setSelectedTypes: mockSetSelectedTypes,
|
||||
});
|
||||
|
||||
const { getByRole } = await renderInTestApp(
|
||||
<TestApiProvider apis={[[alertApiRef, mockAlertApi]]}>
|
||||
<CategoryPicker />
|
||||
</TestApiProvider>,
|
||||
);
|
||||
|
||||
const openButton = getByRole('button', { name: 'Open' });
|
||||
await fireEvent(openButton, new MouseEvent('click', { bubbles: true }));
|
||||
|
||||
const fooCheckbox = getByRole('checkbox', { name: 'Foo' });
|
||||
await fireEvent(fooCheckbox, new MouseEvent('click', { bubbles: true }));
|
||||
|
||||
expect(mockSetSelectedTypes).toHaveBeenCalledWith(['foo']);
|
||||
|
||||
await fireEvent(openButton, new MouseEvent('click', { bubbles: true }));
|
||||
|
||||
const barCheckbox = getByRole('checkbox', { name: 'Bar' });
|
||||
await fireEvent(barCheckbox, new MouseEvent('click', { bubbles: true }));
|
||||
|
||||
expect(mockSetSelectedTypes).toHaveBeenCalledWith(['foo', 'bar']);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import capitalize from 'lodash/capitalize';
|
||||
import { Progress } from '@backstage/core-components';
|
||||
import {
|
||||
Box,
|
||||
Checkbox,
|
||||
FormControlLabel,
|
||||
TextField,
|
||||
Typography,
|
||||
} from '@material-ui/core';
|
||||
import CheckBoxIcon from '@material-ui/icons/CheckBox';
|
||||
import CheckBoxOutlineBlankIcon from '@material-ui/icons/CheckBoxOutlineBlank';
|
||||
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
|
||||
import { Autocomplete } from '@material-ui/lab';
|
||||
import { useEntityTypeFilter } from '@backstage/plugin-catalog-react';
|
||||
import { alertApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
|
||||
const icon = <CheckBoxOutlineBlankIcon fontSize="small" />;
|
||||
const checkedIcon = <CheckBoxIcon fontSize="small" />;
|
||||
|
||||
export const CategoryPicker = () => {
|
||||
const alertApi = useApi(alertApiRef);
|
||||
const { error, loading, availableTypes, selectedTypes, setSelectedTypes } =
|
||||
useEntityTypeFilter();
|
||||
|
||||
if (loading) return <Progress />;
|
||||
|
||||
if (error) {
|
||||
alertApi.post({
|
||||
message: `Failed to load entity types with error: ${error}`,
|
||||
severity: 'error',
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!availableTypes) return null;
|
||||
|
||||
return (
|
||||
<Box pb={1} pt={1}>
|
||||
<Typography variant="button">Categories</Typography>
|
||||
<Autocomplete
|
||||
multiple
|
||||
aria-label="Categories"
|
||||
options={availableTypes}
|
||||
value={selectedTypes}
|
||||
onChange={(_: object, value: string[]) => setSelectedTypes(value)}
|
||||
renderOption={(option, { selected }) => (
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
icon={icon}
|
||||
checkedIcon={checkedIcon}
|
||||
checked={selected}
|
||||
/>
|
||||
}
|
||||
label={capitalize(option)}
|
||||
/>
|
||||
)}
|
||||
size="small"
|
||||
popupIcon={<ExpandMoreIcon />}
|
||||
renderInput={params => <TextField {...params} variant="outlined" />}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { makeStyles } from '@material-ui/core';
|
||||
import {
|
||||
Content,
|
||||
ContentHeader,
|
||||
CreateButton,
|
||||
Header,
|
||||
Lifecycle,
|
||||
Page,
|
||||
SupportButton,
|
||||
} from '@backstage/core-components';
|
||||
import {
|
||||
EntityKindPicker,
|
||||
EntityListProvider,
|
||||
EntitySearchBar,
|
||||
EntityTagPicker,
|
||||
UserListPicker,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { CategoryPicker } from './CategoryPicker';
|
||||
|
||||
export type TemplateListGroup = {
|
||||
title?: string;
|
||||
titleComponent?: React.ReactNode;
|
||||
filter: (entity: Entity) => boolean;
|
||||
};
|
||||
|
||||
export type TemplateListPageProps = {
|
||||
TemplateCardComponent?: React.ComponentType<{
|
||||
template: TemplateEntityV1beta3;
|
||||
}>;
|
||||
groups?: TemplateListGroup[];
|
||||
};
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
contentWrapper: {
|
||||
display: 'grid',
|
||||
gridTemplateAreas: "'filters' 'grid'",
|
||||
gridTemplateColumns: '250px 1fr',
|
||||
gridColumnGap: theme.spacing(2),
|
||||
},
|
||||
}));
|
||||
|
||||
export const TemplateListPage = (props: TemplateListPageProps) => {
|
||||
const styles = useStyles();
|
||||
return (
|
||||
<EntityListProvider>
|
||||
<Page themeId="home">
|
||||
<Header
|
||||
pageTitleOverride="Create a New Component"
|
||||
title={
|
||||
<>
|
||||
Create a New Component <Lifecycle shorthand />
|
||||
</>
|
||||
}
|
||||
subtitle="Create new software components using standard templates"
|
||||
/>
|
||||
<Content>
|
||||
<ContentHeader title="Available Templates">
|
||||
{/* {allowed && (
|
||||
<CreateButton
|
||||
title="Register Existing Component"
|
||||
to={registerComponentLink && registerComponentLink()}
|
||||
/>
|
||||
)} */}
|
||||
<SupportButton>
|
||||
Create new software components using standard templates. Different
|
||||
templates create different kinds of components (services,
|
||||
websites, documentation, ...).
|
||||
</SupportButton>
|
||||
</ContentHeader>
|
||||
|
||||
<div className={styles.contentWrapper}>
|
||||
<div>
|
||||
<EntitySearchBar />
|
||||
<EntityKindPicker initialFilter="template" hidden />
|
||||
<UserListPicker
|
||||
initialFilter="all"
|
||||
availableFilters={['all', 'starred']}
|
||||
/>
|
||||
<CategoryPicker />
|
||||
<EntityTagPicker />
|
||||
</div>
|
||||
<div>
|
||||
{/* {groups &&
|
||||
groups.map((group, index) => (
|
||||
<TemplateList
|
||||
key={index}
|
||||
TemplateCardComponent={TemplateCardComponent}
|
||||
group={group}
|
||||
/>
|
||||
))}
|
||||
<TemplateList
|
||||
key="other"
|
||||
TemplateCardComponent={TemplateCardComponent}
|
||||
group={otherTemplatesGroup}
|
||||
/>*/}
|
||||
</div>
|
||||
</div>
|
||||
</Content>
|
||||
</Page>
|
||||
</EntityListProvider>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export { TemplateListPage } from './TemplateListPage';
|
||||
Reference in New Issue
Block a user