feat(core): add support config & button/error components
This adds a new app.support top level config that can be used to configure various support links and other information. This can later be extended for plugins to append their own specific support items.
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/core': patch
|
||||
---
|
||||
|
||||
Added a new useSupportConfig hook that reads a new `app.support` config key. Also updated the SupportButton and ErrorPage components to use the new config.
|
||||
@@ -2,6 +2,19 @@ app:
|
||||
title: Backstage Example App
|
||||
baseUrl: http://localhost:3000
|
||||
googleAnalyticsTrackingId: # UA-000000-0
|
||||
support:
|
||||
url: https://github.com/backstage/backstage/issues # Used by common ErrorPage
|
||||
items: # Used by common SupportButton component
|
||||
- title: Issues
|
||||
icon: github
|
||||
links:
|
||||
- url: https://github.com/backstage/backstage/issues
|
||||
title: GitHub Issues
|
||||
- title: Discord Chatroom
|
||||
icon: chat
|
||||
links:
|
||||
- url: https://discord.gg/MUpMjP2
|
||||
title: '#backstage'
|
||||
|
||||
backend:
|
||||
baseUrl: http://localhost:7000
|
||||
|
||||
@@ -149,7 +149,7 @@ class AppContextImpl implements AppContext {
|
||||
return this.app.getPlugins();
|
||||
}
|
||||
|
||||
getSystemIcon(key: string): IconComponent {
|
||||
getSystemIcon(key: IconKey): IconComponent | undefined {
|
||||
return this.app.getSystemIcon(key);
|
||||
}
|
||||
|
||||
@@ -206,7 +206,7 @@ export class PrivateAppImpl implements BackstageApp {
|
||||
return this.plugins;
|
||||
}
|
||||
|
||||
getSystemIcon(key: IconKey): IconComponent {
|
||||
getSystemIcon(key: IconKey): IconComponent | undefined {
|
||||
return this.icons[key];
|
||||
}
|
||||
|
||||
|
||||
@@ -171,7 +171,7 @@ export type BackstageApp = {
|
||||
/**
|
||||
* Get a common or custom icon for this app.
|
||||
*/
|
||||
getSystemIcon(key: IconKey): IconComponent;
|
||||
getSystemIcon(key: IconKey): IconComponent | undefined;
|
||||
|
||||
/**
|
||||
* Provider component that should wrap the Router created with getRouter()
|
||||
@@ -202,7 +202,7 @@ export type AppContext = {
|
||||
/**
|
||||
* Get a common or custom icon for this app.
|
||||
*/
|
||||
getSystemIcon(key: IconKey): IconComponent;
|
||||
getSystemIcon(key: IconKey): IconComponent | undefined;
|
||||
|
||||
/**
|
||||
* Get the components registered for various purposes in the app.
|
||||
|
||||
@@ -15,31 +15,46 @@
|
||||
*/
|
||||
|
||||
import { SvgIconProps } from '@material-ui/core';
|
||||
import MuiBrokenImageIcon from '@material-ui/icons/BrokenImage';
|
||||
import MuiChatIcon from '@material-ui/icons/Chat';
|
||||
import MuiDashboardIcon from '@material-ui/icons/Dashboard';
|
||||
import MuiEmailIcon from '@material-ui/icons/Email';
|
||||
import MuiGitHubIcon from '@material-ui/icons/GitHub';
|
||||
import MuiHelpIcon from '@material-ui/icons/Help';
|
||||
import PeopleIcon from '@material-ui/icons/People';
|
||||
import PersonIcon from '@material-ui/icons/Person';
|
||||
import MuiPeopleIcon from '@material-ui/icons/People';
|
||||
import MuiPersonIcon from '@material-ui/icons/Person';
|
||||
import MuiWarningIcon from '@material-ui/icons/Warning';
|
||||
import React from 'react';
|
||||
import { useApp } from '../app/AppContext';
|
||||
import { IconComponent, SystemIconKey, IconComponentMap } from './types';
|
||||
import { IconComponent, IconComponentMap, SystemIconKey } from './types';
|
||||
|
||||
export const defaultSystemIcons: IconComponentMap = {
|
||||
user: PersonIcon,
|
||||
group: PeopleIcon,
|
||||
brokenImage: MuiBrokenImageIcon,
|
||||
chat: MuiChatIcon,
|
||||
dashboard: MuiDashboardIcon,
|
||||
email: MuiEmailIcon,
|
||||
github: MuiGitHubIcon,
|
||||
group: MuiPeopleIcon,
|
||||
help: MuiHelpIcon,
|
||||
user: MuiPersonIcon,
|
||||
warning: MuiWarningIcon,
|
||||
};
|
||||
|
||||
const overridableSystemIcon = (key: SystemIconKey): IconComponent => {
|
||||
const Component = (props: SvgIconProps) => {
|
||||
const app = useApp();
|
||||
const Icon = app.getSystemIcon(key);
|
||||
return <Icon {...props} />;
|
||||
return Icon ? <Icon {...props} /> : <MuiBrokenImageIcon {...props} />;
|
||||
};
|
||||
return Component;
|
||||
};
|
||||
|
||||
export const BrokenImageIcon = overridableSystemIcon('brokenImage');
|
||||
export const ChatIcon = overridableSystemIcon('chat');
|
||||
export const DashboardIcon = overridableSystemIcon('dashboard');
|
||||
export const EmailIcon = overridableSystemIcon('email');
|
||||
export const GitHubIcon = overridableSystemIcon('github');
|
||||
export const GroupIcon = overridableSystemIcon('group');
|
||||
export const HelpIcon = overridableSystemIcon('help');
|
||||
export const UserIcon = overridableSystemIcon('user');
|
||||
export const WarningIcon = overridableSystemIcon('warning');
|
||||
|
||||
@@ -17,7 +17,16 @@
|
||||
import { ComponentType } from 'react';
|
||||
import { SvgIconProps } from '@material-ui/core';
|
||||
|
||||
export type SystemIconKey = 'user' | 'group' | 'dashboard' | 'help';
|
||||
export type SystemIconKey =
|
||||
| 'brokenImage'
|
||||
| 'chat'
|
||||
| 'dashboard'
|
||||
| 'email'
|
||||
| 'github'
|
||||
| 'group'
|
||||
| 'help'
|
||||
| 'user'
|
||||
| 'warning';
|
||||
|
||||
export type IconComponent = ComponentType<SvgIconProps>;
|
||||
export type IconKey = SystemIconKey | string;
|
||||
|
||||
Vendored
+35
@@ -30,6 +30,41 @@ export interface Config {
|
||||
* @visibility frontend
|
||||
*/
|
||||
title?: string;
|
||||
|
||||
/**
|
||||
* Information about support of this Backstage instance and how to contact the integrator team.
|
||||
*/
|
||||
support?: {
|
||||
/**
|
||||
* The primary support url.
|
||||
* @visibility frontend
|
||||
*/
|
||||
url: string;
|
||||
/**
|
||||
* A list of categorized support item groupings.
|
||||
*/
|
||||
items: {
|
||||
/**
|
||||
* The title of the support item grouping.
|
||||
* @visibility frontend
|
||||
*/
|
||||
title: string;
|
||||
/**
|
||||
* An optional icon for the support item grouping.
|
||||
* @visibility frontend
|
||||
*/
|
||||
icon?: string;
|
||||
/**
|
||||
* A list of support links for the Backstage instance.
|
||||
*/
|
||||
links?: {
|
||||
/** @visibility frontend */
|
||||
url: string;
|
||||
/** @visibility frontend */
|
||||
title?: string;
|
||||
}[];
|
||||
}[];
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,35 +14,26 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, {
|
||||
Fragment,
|
||||
useState,
|
||||
MouseEventHandler,
|
||||
PropsWithChildren,
|
||||
} from 'react';
|
||||
import { HelpIcon, useApp } from '@backstage/core-api';
|
||||
import {
|
||||
Button,
|
||||
Link,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemIcon,
|
||||
Popover,
|
||||
Typography,
|
||||
makeStyles,
|
||||
ListItemText,
|
||||
makeStyles,
|
||||
Popover,
|
||||
} from '@material-ui/core';
|
||||
import GroupIcon from '@material-ui/icons/Group';
|
||||
import HelpIcon from '@material-ui/icons/Help';
|
||||
import React, {
|
||||
Fragment,
|
||||
MouseEventHandler,
|
||||
PropsWithChildren,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { SupportItem, SupportItemLink, useSupportConfig } from '../../hooks';
|
||||
import { Link } from '../Link';
|
||||
|
||||
// import { EmailIcon, SlackIcon, SupportIcon } from 'shared/icons';
|
||||
// import { Button, Link } from 'shared/components';
|
||||
// import { StackOverflow, StackOverflowTag } from 'shared/components/layout';
|
||||
|
||||
type Props = {
|
||||
slackChannel?: string | string[];
|
||||
email?: string | string[];
|
||||
plugin?: any;
|
||||
};
|
||||
type Props = {};
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
leftIcon: {
|
||||
@@ -50,17 +41,45 @@ const useStyles = makeStyles(theme => ({
|
||||
},
|
||||
popoverList: {
|
||||
minWidth: 260,
|
||||
maxWidth: 320,
|
||||
maxWidth: 400,
|
||||
},
|
||||
}));
|
||||
|
||||
export const SupportButton = ({
|
||||
slackChannel = '#backstage',
|
||||
email = [],
|
||||
children,
|
||||
}: // plugin,
|
||||
PropsWithChildren<Props>) => {
|
||||
// TODO: get plugin manifest with hook
|
||||
const SupportIcon = ({ icon }: { icon: string | undefined }) => {
|
||||
const app = useApp();
|
||||
const Icon = icon ? app.getSystemIcon(icon) ?? HelpIcon : HelpIcon;
|
||||
return <Icon />;
|
||||
};
|
||||
|
||||
const SupportLink = ({ link }: { link: SupportItemLink }) => (
|
||||
<Link to={link.url} target="_blank" rel="noreferrer noopener">
|
||||
{link.title ?? link.url}
|
||||
</Link>
|
||||
);
|
||||
|
||||
const SupportListItem = ({ item }: { item: SupportItem }) => {
|
||||
return (
|
||||
<ListItem>
|
||||
<ListItemIcon>
|
||||
<SupportIcon icon={item.icon} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={item.title}
|
||||
secondary={
|
||||
<>
|
||||
{item.links &&
|
||||
item.links.map(link => (
|
||||
<SupportLink link={link} key={link.url} />
|
||||
))}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
);
|
||||
};
|
||||
|
||||
export const SupportButton = ({ children }: PropsWithChildren<Props>) => {
|
||||
const { items } = useSupportConfig();
|
||||
|
||||
const [popoverOpen, setPopoverOpen] = useState(false);
|
||||
const [anchorEl, setAnchorEl] = useState<Element | null>(null);
|
||||
@@ -75,12 +94,6 @@ PropsWithChildren<Props>) => {
|
||||
setPopoverOpen(false);
|
||||
};
|
||||
|
||||
// const tags = plugin ? plugin.stackoverflowTags : undefined;
|
||||
const slackChannels = Array.isArray(slackChannel)
|
||||
? slackChannel
|
||||
: [slackChannel];
|
||||
const contactEmails = Array.isArray(email) ? email : [email];
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Button
|
||||
@@ -111,53 +124,8 @@ PropsWithChildren<Props>) => {
|
||||
{child}
|
||||
</ListItem>
|
||||
))}
|
||||
{/* {tags && tags.length > 0 && (
|
||||
<ListItem alignItems="flex-start">
|
||||
<StackOverflow>
|
||||
{tags.map((tag, i) => (
|
||||
<StackOverflowTag key={i} tag={tag} />
|
||||
))}
|
||||
</StackOverflow>
|
||||
</ListItem>
|
||||
)} */}
|
||||
{slackChannels && (
|
||||
<ListItem>
|
||||
<ListItemIcon>
|
||||
<GroupIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
disableTypography
|
||||
primary={<Typography>Support</Typography>}
|
||||
secondary={
|
||||
<div>
|
||||
{slackChannels.map((channel, i) => (
|
||||
<Link key={i}>{channel}</Link>
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
)}
|
||||
{contactEmails.length > 0 && (
|
||||
<ListItem>
|
||||
<ListItemIcon>
|
||||
<GroupIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
disableTypography
|
||||
primary={<Typography>Contact</Typography>}
|
||||
secondary={
|
||||
<div>
|
||||
{contactEmails.map((em, index) => (
|
||||
<Typography key={index}>
|
||||
<Link>{em}</Link>
|
||||
</Typography>
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
)}
|
||||
{items &&
|
||||
items.map((item, i) => <SupportListItem item={item} key={i} />)}
|
||||
</List>
|
||||
</Popover>
|
||||
</Fragment>
|
||||
|
||||
@@ -15,3 +15,9 @@
|
||||
*/
|
||||
|
||||
export { useQueryParamState } from './useQueryParamState';
|
||||
export { useSupportConfig } from './useSupportConfig';
|
||||
export type {
|
||||
SupportConfig,
|
||||
SupportItem,
|
||||
SupportItemLink,
|
||||
} from './useSupportConfig';
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* 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 { useApi, configApiRef } from '@backstage/core-api';
|
||||
|
||||
export type SupportItemLink = {
|
||||
url: string;
|
||||
title: string;
|
||||
};
|
||||
|
||||
export type SupportItem = {
|
||||
title: string;
|
||||
icon?: string;
|
||||
links?: SupportItemLink[];
|
||||
};
|
||||
|
||||
export type SupportConfig = {
|
||||
url: string;
|
||||
items: SupportItem[];
|
||||
};
|
||||
|
||||
export function useSupportConfig(): SupportConfig {
|
||||
const config = useApi(configApiRef);
|
||||
const supportConfig = config.getOptional('app.support') as SupportConfig;
|
||||
|
||||
return {
|
||||
url: supportConfig?.url ?? 'https://github.com/backstage/backstage/issues',
|
||||
items: supportConfig?.items ?? [
|
||||
{
|
||||
title: 'Support Not Configured',
|
||||
icon: 'warning',
|
||||
links: [
|
||||
{
|
||||
// TODO: Update to dedicated support page on backstage.io/docs
|
||||
title: 'Add `app.support` config key',
|
||||
url:
|
||||
'https://github.com/andrewthauer/backstage/blob/master/app-config.yaml',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import { makeStyles } from '@material-ui/core/styles';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
import { MicDrop } from './MicDrop';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { useSupportConfig } from '../../hooks';
|
||||
|
||||
interface IErrorPageProps {
|
||||
status: string;
|
||||
@@ -53,6 +54,7 @@ export const ErrorPage = ({
|
||||
}: IErrorPageProps) => {
|
||||
const classes = useStyles();
|
||||
const navigate = useNavigate();
|
||||
const support = useSupportConfig();
|
||||
|
||||
return (
|
||||
<Grid container spacing={0} className={classes.container}>
|
||||
@@ -71,13 +73,11 @@ export const ErrorPage = ({
|
||||
<Link data-testid="go-back-link" onClick={() => navigate(-1)}>
|
||||
Go back
|
||||
</Link>
|
||||
... or if you think this is a bug, please file an{' '}
|
||||
<Link
|
||||
href="https://github.com/backstage/backstage/issues"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
issue.
|
||||
</Link>
|
||||
... or please{' '}
|
||||
<Link href={support.url} rel="noopener noreferrer">
|
||||
contact support
|
||||
</Link>{' '}
|
||||
if you think this is a bug.
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
@@ -33,9 +33,8 @@ export const EntityLinksCard = ({ cols = undefined }: Props) => {
|
||||
const { entity } = useEntity();
|
||||
const app = useApp();
|
||||
|
||||
const iconResolver = (key: IconKey | undefined): IconComponent => {
|
||||
return app.getSystemIcon(key ?? '') ?? LanguageIcon;
|
||||
};
|
||||
const iconResolver = (key: IconKey | undefined): IconComponent =>
|
||||
key ? app.getSystemIcon(key) ?? LanguageIcon : LanguageIcon;
|
||||
|
||||
const links = entity?.metadata?.links;
|
||||
|
||||
|
||||
+20
-16
@@ -17,9 +17,9 @@
|
||||
import { DomainEntity } from '@backstage/catalog-model';
|
||||
import { ApiProvider, ApiRegistry } from '@backstage/core';
|
||||
import { catalogApiRef } from '@backstage/plugin-catalog-react';
|
||||
import { render, waitFor } from '@testing-library/react';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { DomainExplorerContent } from './DomainExplorerContent';
|
||||
|
||||
describe('<DomainExplorerContent />', () => {
|
||||
@@ -33,11 +33,9 @@ describe('<DomainExplorerContent />', () => {
|
||||
};
|
||||
|
||||
const Wrapper = ({ children }: { children?: React.ReactNode }) => (
|
||||
<MemoryRouter>
|
||||
<ApiProvider apis={ApiRegistry.with(catalogApiRef, catalogApi)}>
|
||||
{children}
|
||||
</ApiProvider>
|
||||
</MemoryRouter>
|
||||
<ApiProvider apis={ApiRegistry.with(catalogApiRef, catalogApi)}>
|
||||
{children}
|
||||
</ApiProvider>
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -69,9 +67,11 @@ describe('<DomainExplorerContent />', () => {
|
||||
];
|
||||
catalogApi.getEntities.mockResolvedValue({ items: entities });
|
||||
|
||||
const { getByText } = render(<DomainExplorerContent />, {
|
||||
wrapper: Wrapper,
|
||||
});
|
||||
const { getByText } = await renderInTestApp(
|
||||
<Wrapper>
|
||||
<DomainExplorerContent />
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByText('artists')).toBeInTheDocument();
|
||||
@@ -82,9 +82,11 @@ describe('<DomainExplorerContent />', () => {
|
||||
it('renders empty state', async () => {
|
||||
catalogApi.getEntities.mockResolvedValue({ items: [] });
|
||||
|
||||
const { getByText } = render(<DomainExplorerContent />, {
|
||||
wrapper: Wrapper,
|
||||
});
|
||||
const { getByText } = await renderInTestApp(
|
||||
<Wrapper>
|
||||
<DomainExplorerContent />
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(getByText('No domains to display')).toBeInTheDocument(),
|
||||
@@ -95,9 +97,11 @@ describe('<DomainExplorerContent />', () => {
|
||||
const catalogError = new Error('Network timeout');
|
||||
catalogApi.getEntities.mockRejectedValueOnce(catalogError);
|
||||
|
||||
const { getByText } = render(<DomainExplorerContent />, {
|
||||
wrapper: Wrapper,
|
||||
});
|
||||
const { getByText } = await renderInTestApp(
|
||||
<Wrapper>
|
||||
<DomainExplorerContent />
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(getByText(/Could not load domains/)).toBeInTheDocument(),
|
||||
|
||||
@@ -19,11 +19,11 @@ import {
|
||||
ExploreTool,
|
||||
exploreToolsConfigRef,
|
||||
} from '@backstage/plugin-explore-react';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
import { lightTheme } from '@backstage/theme';
|
||||
import { ThemeProvider } from '@material-ui/core';
|
||||
import { render, waitFor } from '@testing-library/react';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { ToolExplorerContent } from './ToolExplorerContent';
|
||||
|
||||
describe('<ToolExplorerContent />', () => {
|
||||
@@ -33,13 +33,11 @@ describe('<ToolExplorerContent />', () => {
|
||||
|
||||
const Wrapper = ({ children }: { children?: React.ReactNode }) => (
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<MemoryRouter>
|
||||
<ApiProvider
|
||||
apis={ApiRegistry.with(exploreToolsConfigRef, exploreToolsConfigApi)}
|
||||
>
|
||||
{children}
|
||||
</ApiProvider>
|
||||
</MemoryRouter>
|
||||
<ApiProvider
|
||||
apis={ApiRegistry.with(exploreToolsConfigRef, exploreToolsConfigApi)}
|
||||
>
|
||||
{children}
|
||||
</ApiProvider>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
@@ -70,9 +68,11 @@ describe('<ToolExplorerContent />', () => {
|
||||
];
|
||||
exploreToolsConfigApi.getTools.mockResolvedValue(tools);
|
||||
|
||||
const { getByText } = render(<ToolExplorerContent />, {
|
||||
wrapper: Wrapper,
|
||||
});
|
||||
const { getByText } = await renderInTestApp(
|
||||
<Wrapper>
|
||||
<ToolExplorerContent />
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByText('Lighthouse')).toBeInTheDocument();
|
||||
@@ -83,9 +83,11 @@ describe('<ToolExplorerContent />', () => {
|
||||
it('renders empty state', async () => {
|
||||
exploreToolsConfigApi.getTools.mockResolvedValue([]);
|
||||
|
||||
const { getByText } = render(<ToolExplorerContent />, {
|
||||
wrapper: Wrapper,
|
||||
});
|
||||
const { getByText } = await renderInTestApp(
|
||||
<Wrapper>
|
||||
<ToolExplorerContent />
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(getByText('No tools to display')).toBeInTheDocument(),
|
||||
|
||||
@@ -14,23 +14,23 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import ProfileCatalog from './ProfileCatalog';
|
||||
import { ThemeProvider } from '@material-ui/core';
|
||||
import { lightTheme } from '@backstage/theme';
|
||||
import {
|
||||
ApiProvider,
|
||||
ApiRegistry,
|
||||
githubAuthApiRef,
|
||||
GithubAuth,
|
||||
githubAuthApiRef,
|
||||
OAuthRequestManager,
|
||||
UrlPatternDiscovery,
|
||||
} from '@backstage/core';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
import { lightTheme } from '@backstage/theme';
|
||||
import { ThemeProvider } from '@material-ui/core';
|
||||
import React from 'react';
|
||||
import { gitOpsApiRef, GitOpsRestApi } from '../../api';
|
||||
import ProfileCatalog from './ProfileCatalog';
|
||||
|
||||
describe('ProfileCatalog', () => {
|
||||
it('should render', () => {
|
||||
it('should render', async () => {
|
||||
const oauthRequestApi = new OAuthRequestManager();
|
||||
const apis = ApiRegistry.from([
|
||||
[gitOpsApiRef, new GitOpsRestApi('http://localhost:3008')],
|
||||
@@ -44,15 +44,15 @@ describe('ProfileCatalog', () => {
|
||||
}),
|
||||
],
|
||||
]);
|
||||
const rendered = render(
|
||||
|
||||
const { getByText } = await renderInTestApp(
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<ApiProvider apis={apis}>
|
||||
<ProfileCatalog />
|
||||
</ApiProvider>
|
||||
</ThemeProvider>,
|
||||
);
|
||||
expect(
|
||||
rendered.getByText('Create GitOps-managed Cluster'),
|
||||
).toBeInTheDocument();
|
||||
|
||||
expect(getByText('Create GitOps-managed Cluster')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
+20
-22
@@ -21,11 +21,10 @@ import {
|
||||
errorApiRef,
|
||||
} from '@backstage/core';
|
||||
import { catalogApiRef } from '@backstage/plugin-catalog-react';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
import { lightTheme } from '@backstage/theme';
|
||||
import { ThemeProvider } from '@material-ui/core';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { RegisterComponentPage } from './RegisterComponentPage';
|
||||
|
||||
const errorApi: jest.Mocked<typeof errorApiRef.T> = {
|
||||
@@ -44,30 +43,29 @@ const catalogApi: jest.Mocked<typeof catalogApiRef.T> = {
|
||||
};
|
||||
|
||||
const Wrapper = ({ children }: { children?: React.ReactNode }) => (
|
||||
<MemoryRouter>
|
||||
<ApiProvider
|
||||
apis={ApiRegistry.with(errorApiRef, errorApi).with(
|
||||
catalogApiRef,
|
||||
catalogApi,
|
||||
)}
|
||||
>
|
||||
<ThemeProvider theme={lightTheme}>{children}</ThemeProvider>
|
||||
</ApiProvider>
|
||||
</MemoryRouter>
|
||||
<ApiProvider
|
||||
apis={ApiRegistry.with(errorApiRef, errorApi).with(
|
||||
catalogApiRef,
|
||||
catalogApi,
|
||||
)}
|
||||
>
|
||||
<ThemeProvider theme={lightTheme}>{children}</ThemeProvider>
|
||||
</ApiProvider>
|
||||
);
|
||||
|
||||
describe('RegisterComponentPage', () => {
|
||||
it('should render', () => {
|
||||
render(
|
||||
<RegisterComponentPage
|
||||
catalogRouteRef={createRouteRef({
|
||||
path: '/catalog',
|
||||
title: 'Service Catalog',
|
||||
})}
|
||||
/>,
|
||||
{ wrapper: Wrapper },
|
||||
it('should render', async () => {
|
||||
const { getByText } = await renderInTestApp(
|
||||
<Wrapper>
|
||||
<RegisterComponentPage
|
||||
catalogRouteRef={createRouteRef({
|
||||
path: '/catalog',
|
||||
title: 'Service Catalog',
|
||||
})}
|
||||
/>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
expect(screen.getByText('Register existing component')).toBeInTheDocument();
|
||||
expect(getByText('Register existing component')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,16 +14,19 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { render, waitForElement } from '@testing-library/react';
|
||||
import { ThemeProvider } from '@material-ui/core';
|
||||
import { ApiProvider, ApiRegistry, errorApiRef } from '@backstage/core';
|
||||
import {
|
||||
MockErrorApi,
|
||||
renderInTestApp,
|
||||
wrapInTestApp,
|
||||
} from '@backstage/test-utils';
|
||||
import { lightTheme } from '@backstage/theme';
|
||||
import { ApiRegistry, ApiProvider, errorApiRef } from '@backstage/core';
|
||||
|
||||
import { ThemeProvider } from '@material-ui/core';
|
||||
import { render, waitForElement } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import GetBBoxPolyfill from '../utils/polyfills/getBBox';
|
||||
import { RadarPage } from './RadarPage';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { MockErrorApi, wrapInTestApp } from '@backstage/test-utils';
|
||||
|
||||
describe('RadarPage', () => {
|
||||
beforeAll(() => {
|
||||
@@ -67,12 +70,10 @@ describe('RadarPage', () => {
|
||||
svgProps: { 'data-testid': 'tech-radar-svg' },
|
||||
};
|
||||
|
||||
const { getByText, getByTestId } = render(
|
||||
wrapInTestApp(
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<RadarPage {...techRadarProps} />
|
||||
</ThemeProvider>,
|
||||
),
|
||||
const { getByText, getByTestId } = await renderInTestApp(
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<RadarPage {...techRadarProps} />
|
||||
</ThemeProvider>,
|
||||
);
|
||||
|
||||
await waitForElement(() => getByTestId('tech-radar-svg'));
|
||||
@@ -94,7 +95,7 @@ describe('RadarPage', () => {
|
||||
svgProps: { 'data-testid': 'tech-radar-svg' },
|
||||
};
|
||||
|
||||
const { queryByTestId } = render(
|
||||
const { queryByTestId } = await renderInTestApp(
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<ApiProvider apis={ApiRegistry.with(errorApiRef, errorApi)}>
|
||||
<RadarPage {...techRadarProps} />
|
||||
|
||||
@@ -14,23 +14,22 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import WelcomePage from './WelcomePage';
|
||||
import { ThemeProvider } from '@material-ui/core';
|
||||
import { lightTheme } from '@backstage/theme';
|
||||
import {
|
||||
ApiProvider,
|
||||
ApiRegistry,
|
||||
errorApiRef,
|
||||
configApiRef,
|
||||
ConfigReader,
|
||||
errorApiRef,
|
||||
} from '@backstage/core';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
import { lightTheme } from '@backstage/theme';
|
||||
import { ThemeProvider } from '@material-ui/core';
|
||||
import React from 'react';
|
||||
import WelcomePage from './WelcomePage';
|
||||
|
||||
describe('WelcomePage', () => {
|
||||
it('should render', () => {
|
||||
// TODO: use common test app with mock implementations of all core APIs
|
||||
const rendered = render(
|
||||
it('should render', async () => {
|
||||
const { baseElement } = await renderInTestApp(
|
||||
<ApiProvider
|
||||
apis={ApiRegistry.from([
|
||||
[errorApiRef, { post: jest.fn() }],
|
||||
@@ -42,6 +41,6 @@ describe('WelcomePage', () => {
|
||||
</ThemeProvider>
|
||||
</ApiProvider>,
|
||||
);
|
||||
expect(rendered.baseElement).toBeInTheDocument();
|
||||
expect(baseElement).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user