feat: added some nice tests for the TemplateListPage

Signed-off-by: blam <ben@blam.sh>
This commit is contained in:
blam
2022-02-05 04:48:49 +01:00
parent a4ad1ddcb6
commit 8db4dc297a
9 changed files with 374 additions and 6 deletions
@@ -0,0 +1,57 @@
/*
* 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 { renderInTestApp } from '@backstage/test-utils';
import React from 'react';
import { RegisterExistingButton } from './RegisterExistingButton';
import { usePermission } from '@backstage/plugin-permission-react';
jest.mock('@backstage/plugin-permission-react', () => ({
usePermission: jest.fn(),
}));
describe('RegisterExistingButton', () => {
beforeEach(() => {
(usePermission as jest.Mock).mockClear();
});
it('should not render if to is unset', async () => {
(usePermission as jest.Mock).mockReturnValue({ allowed: true });
const { queryByText } = await renderInTestApp(
<RegisterExistingButton title="Pick me" />,
);
expect(await queryByText('Pick me')).not.toBeInTheDocument();
});
it('should not render if permissions are not allowed', async () => {
(usePermission as jest.Mock).mockReturnValue({ allowed: false });
const { queryByText } = await renderInTestApp(
<RegisterExistingButton title="Pick me" to="blah" />,
);
expect(await queryByText('Pick me')).not.toBeInTheDocument();
});
it('should render the button with the text', async () => {
(usePermission as jest.Mock).mockReturnValue({ allowed: true });
const { queryByText } = await renderInTestApp(
<RegisterExistingButton title="Pick me" to="blah" />,
);
expect(await queryByText('Pick me')).toBeInTheDocument();
});
});
@@ -0,0 +1,66 @@
/*
* 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 { BackstageTheme } from '@backstage/theme';
import Button from '@material-ui/core/Button';
import IconButton from '@material-ui/core/IconButton';
import useMediaQuery from '@material-ui/core/useMediaQuery';
import React from 'react';
import { Link as RouterLink, LinkProps } from 'react-router-dom';
import AddCircleOutline from '@material-ui/icons/AddCircleOutline';
import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common';
import { usePermission } from '@backstage/plugin-permission-react';
/**
* Properties for {@link RegisterExistingButton}
*
* @public
*/
export type RegisterExistingButtonProps = {
title: string;
} & Partial<Pick<LinkProps, 'to'>>;
/**
* A button that helps users to register an existing component.
* @public
*/
export const RegisterExistingButton = (props: RegisterExistingButtonProps) => {
const { title, to } = props;
const { allowed } = usePermission(catalogEntityCreatePermission);
const isXSScreen = useMediaQuery<BackstageTheme>(theme =>
theme.breakpoints.down('xs'),
);
if (!to || !allowed) {
return null;
}
return isXSScreen ? (
<IconButton
component={RouterLink}
color="primary"
title={title}
size="small"
to={to}
>
<AddCircleOutline />
</IconButton>
) : (
<Button component={RouterLink} variant="contained" color="primary" to={to}>
{title}
</Button>
);
};
@@ -0,0 +1,73 @@
/*
* 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 {
catalogApiRef,
DefaultStarredEntitiesApi,
starredEntitiesApiRef,
} from '@backstage/plugin-catalog-react';
import { permissionApiRef } from '@backstage/plugin-permission-react';
import {
MockStorageApi,
renderInTestApp,
TestApiProvider,
} from '@backstage/test-utils';
import React from 'react';
import { TemplateListPage } from './TemplateListPage';
describe('TemplateListPage', () => {
it('should render the search bar for templates', async () => {
const { getByPlaceholderText } = await renderInTestApp(
<TestApiProvider
apis={[
[catalogApiRef, {}],
[
starredEntitiesApiRef,
new DefaultStarredEntitiesApi({
storageApi: MockStorageApi.create(),
}),
],
[permissionApiRef, {}],
]}
>
<TemplateListPage />
</TestApiProvider>,
);
expect(getByPlaceholderText('Search')).toBeInTheDocument();
});
it('should render the all and starred filters', async () => {
const { getByRole } = await renderInTestApp(
<TestApiProvider
apis={[
[catalogApiRef, {}],
[
starredEntitiesApiRef,
new DefaultStarredEntitiesApi({
storageApi: MockStorageApi.create(),
}),
],
[permissionApiRef, {}],
]}
>
<TemplateListPage />
</TestApiProvider>,
);
expect(getByRole('menuitem', { name: 'All' })).toBeInTheDocument();
expect(getByRole('menuitem', { name: 'Starred' })).toBeInTheDocument();
});
});
@@ -35,6 +35,9 @@ import {
UserListPicker,
} from '@backstage/plugin-catalog-react';
import { CategoryPicker } from './CategoryPicker';
import { RegisterExistingButton } from './RegisterExistingButton';
import { useRouteRef } from '@backstage/core-plugin-api';
import { registerComponentRouteRef } from '../../routes';
export type TemplateListGroup = {
title?: string;
@@ -60,6 +63,8 @@ const useStyles = makeStyles(theme => ({
export const TemplateListPage = (props: TemplateListPageProps) => {
const styles = useStyles();
const registerComponentLink = useRouteRef(registerComponentRouteRef);
return (
<EntityListProvider>
<Page themeId="home">
@@ -74,12 +79,10 @@ export const TemplateListPage = (props: TemplateListPageProps) => {
/>
<Content>
<ContentHeader title="Available Templates">
{/* {allowed && (
<CreateButton
title="Register Existing Component"
to={registerComponentLink && registerComponentLink()}
/>
)} */}
<RegisterExistingButton
title="Register Existing Component"
to={registerComponentLink && registerComponentLink()}
/>
<SupportButton>
Create new software components using standard templates. Different
templates create different kinds of components (services,
@@ -0,0 +1,43 @@
/*
* 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, { useContext } from 'react';
import {
useTemplateSecrets,
SecretsContextProvider,
SecretsContext,
} from './SecretsContext';
import { renderHook, act } from '@testing-library/react-hooks';
describe('SecretsContext', () => {
it('should allow the setting of secrets in the context', async () => {
const { result } = renderHook(
() => ({
hook: useTemplateSecrets(),
context: useContext(SecretsContext),
}),
{
wrapper: ({ children }) => (
<SecretsContextProvider>{children}</SecretsContextProvider>
),
},
);
expect(result.current.context?.secrets.foo).toEqual(undefined);
act(() => result.current.hook.setSecret({ foo: 'bar' }));
expect(result.current.context?.secrets.foo).toEqual('bar');
});
});
@@ -0,0 +1,73 @@
/*
* 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, {
useState,
useCallback,
useContext,
createContext,
PropsWithChildren,
} from 'react';
type SecretsContextContents = {
secrets: Record<string, string>;
setSecrets: React.Dispatch<React.SetStateAction<Record<string, string>>>;
};
/**
* The actual context object.
*/
export const SecretsContext = createContext<SecretsContextContents | undefined>(
undefined,
);
/**
* The Context Provider that holds the state for the secrets.
*
* @public
*/
export const SecretsContextProvider = ({ children }: PropsWithChildren<{}>) => {
const [secrets, setSecrets] = useState<Record<string, string>>({});
return (
<SecretsContext.Provider value={{ secrets, setSecrets }}>
{children}
</SecretsContext.Provider>
);
};
/**
* Hook to access the secrets context.
* @public
*/
export const useTemplateSecrets = () => {
const value = useContext(SecretsContext);
if (!value) {
throw new Error(
'useTemplateSecrets must be used within a SecretsContextProvider',
);
}
const { setSecrets } = value;
const setSecret = useCallback(
(input: Record<string, string>) => {
setSecrets(currentSecrets => ({ ...currentSecrets, ...input }));
},
[setSecrets],
);
return { setSecret };
};
@@ -0,0 +1,20 @@
/*
* 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 {
useTemplateSecrets,
SecretsContext,
SecretsContextProvider,
} from './SecretsContext';
@@ -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 const TemplateWizardPage = () => {};
@@ -0,0 +1,17 @@
/*
* 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 { TemplateWizardPage } from './TemplateWizardPage';
export { useTemplateSecrets } from './SecretsContext';