Move CategoryPicker to scaffolder-react

Signed-off-by: Min Kim <minkimcello@gmail.com>
This commit is contained in:
Min Kim
2023-03-17 15:25:57 -04:00
parent 9793d8bab1
commit 7d51e8cb0a
6 changed files with 34 additions and 11 deletions
@@ -190,6 +190,9 @@ export interface TemplateCardProps {
template: TemplateEntityV1beta3;
}
// @alpha
export const TemplateCategoryPicker: () => JSX.Element | null;
// @alpha
export const TemplateGroup: (props: TemplateGroupProps) => JSX.Element;
@@ -0,0 +1,152 @@
/*
* 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 { TemplateCategoryPicker } from './TemplateCategoryPicker';
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('TemplateCategoryPicker', () => {
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]]}>
<TemplateCategoryPicker />
</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]]}>
<TemplateCategoryPicker />
</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]]}>
<TemplateCategoryPicker />
</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]]}>
<TemplateCategoryPicker />
</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]]}>
<TemplateCategoryPicker />
</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']);
});
it('should render the selectedTypes already in the document', async () => {
const mockAvailableTypes = ['foo', 'bar'];
const mockSelectedTypes = ['foo'];
(useEntityTypeFilter as jest.Mock).mockReturnValue({
availableTypes: mockAvailableTypes,
selectedTypes: mockSelectedTypes,
});
const { getByRole } = await renderInTestApp(
<TestApiProvider apis={[[alertApiRef, mockAlertApi]]}>
<TemplateCategoryPicker />
</TestApiProvider>,
);
const openButton = getByRole('button', { name: 'Open' });
await fireEvent(openButton, new MouseEvent('click', { bubbles: true }));
const fooCheckbox = getByRole('checkbox', { name: 'Foo' });
expect(fooCheckbox).toBeChecked();
});
});
@@ -0,0 +1,87 @@
/*
* 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" />;
/**
* The Category Picker that is rendered on the left side for picking
* categories and filtering the template list.
*
* @alpha
*/
export const TemplateCategoryPicker = () => {
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,16 @@
/*
* Copyright 2023 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 { TemplateCategoryPicker } from './TemplateCategoryPicker';
@@ -22,3 +22,4 @@ export * from './TemplateOutputs';
export * from './Form';
export * from './TaskSteps';
export * from './TaskLogStream';
export * from './TemplateCategoryPicker';