Merge pull request #4517 from andrewthauer/feat/support-button
Add extended support button config
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,74 @@
|
||||
/*
|
||||
* 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[];
|
||||
};
|
||||
|
||||
const DEFAULT_SUPPORT_CONFIG: SupportConfig = {
|
||||
url: 'https://github.com/backstage/backstage/issues',
|
||||
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',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export function useSupportConfig(): SupportConfig {
|
||||
const config = useApi(configApiRef);
|
||||
const supportConfig = config.getOptionalConfig('app.support');
|
||||
|
||||
if (!supportConfig) {
|
||||
return DEFAULT_SUPPORT_CONFIG;
|
||||
}
|
||||
|
||||
return {
|
||||
url: supportConfig.getString('url'),
|
||||
items: supportConfig.getConfigArray('items').flatMap(itemConf => ({
|
||||
title: itemConf.getString('title'),
|
||||
icon: itemConf.getOptionalString('icon'),
|
||||
links: (itemConf.getOptionalConfigArray('links') ?? []).flatMap(
|
||||
linkConf => ({
|
||||
url: linkConf.getString('url'),
|
||||
title: linkConf.getString('title'),
|
||||
}),
|
||||
),
|
||||
})),
|
||||
};
|
||||
}
|
||||
@@ -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