diff --git a/.changeset/silver-balloons-play.md b/.changeset/silver-balloons-play.md new file mode 100644 index 0000000000000..24a5c8ac463fb --- /dev/null +++ b/.changeset/silver-balloons-play.md @@ -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. diff --git a/app-config.yaml b/app-config.yaml index 672ba26cd243a..f68f5a30a2915 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -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 diff --git a/packages/core-api/src/app/App.tsx b/packages/core-api/src/app/App.tsx index 3cf5abdb44e0f..1cf2d892d07bf 100644 --- a/packages/core-api/src/app/App.tsx +++ b/packages/core-api/src/app/App.tsx @@ -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]; } diff --git a/packages/core-api/src/app/types.ts b/packages/core-api/src/app/types.ts index 69eb7f718943c..ec7b70689d96e 100644 --- a/packages/core-api/src/app/types.ts +++ b/packages/core-api/src/app/types.ts @@ -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. diff --git a/packages/core-api/src/icons/icons.tsx b/packages/core-api/src/icons/icons.tsx index 0c2b580ea7a92..49db90efccba6 100644 --- a/packages/core-api/src/icons/icons.tsx +++ b/packages/core-api/src/icons/icons.tsx @@ -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 ; + return Icon ? : ; }; 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'); diff --git a/packages/core-api/src/icons/types.ts b/packages/core-api/src/icons/types.ts index be24b223ae195..c4634bd00a942 100644 --- a/packages/core-api/src/icons/types.ts +++ b/packages/core-api/src/icons/types.ts @@ -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; export type IconKey = SystemIconKey | string; diff --git a/packages/core/config.d.ts b/packages/core/config.d.ts index a6f95ae71c279..9e43c6f3ad6ed 100644 --- a/packages/core/config.d.ts +++ b/packages/core/config.d.ts @@ -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; + }[]; + }[]; + }; }; /** diff --git a/packages/core/src/components/SupportButton/SupportButton.tsx b/packages/core/src/components/SupportButton/SupportButton.tsx index 2a1b810582f45..7c532c0f8fc70 100644 --- a/packages/core/src/components/SupportButton/SupportButton.tsx +++ b/packages/core/src/components/SupportButton/SupportButton.tsx @@ -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 { EmailIcon, SlackIcon, SupportIcon } from 'shared/icons'; -// import { Button, Link } from 'shared/components'; -// import { StackOverflow, StackOverflowTag } from 'shared/components/layout'; +import React, { + Fragment, + MouseEventHandler, + PropsWithChildren, + useState, +} from 'react'; +import { SupportItem, SupportItemLink, useSupportConfig } from '../../hooks'; +import { Link } from '../Link'; -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) => { - // TODO: get plugin manifest with hook +const SupportIcon = ({ icon }: { icon: string | undefined }) => { + const app = useApp(); + const Icon = icon ? app.getSystemIcon(icon) ?? HelpIcon : HelpIcon; + return ; +}; + +const SupportLink = ({ link }: { link: SupportItemLink }) => ( + + {link.title ?? link.url} + +); + +const SupportListItem = ({ item }: { item: SupportItem }) => { + return ( + + + + + + {item.links && + item.links.map(link => ( + + ))} + > + } + /> + + ); +}; + +export const SupportButton = ({ children }: PropsWithChildren) => { + const { items } = useSupportConfig(); const [popoverOpen, setPopoverOpen] = useState(false); const [anchorEl, setAnchorEl] = useState(null); @@ -75,12 +94,6 @@ PropsWithChildren) => { setPopoverOpen(false); }; - // const tags = plugin ? plugin.stackoverflowTags : undefined; - const slackChannels = Array.isArray(slackChannel) - ? slackChannel - : [slackChannel]; - const contactEmails = Array.isArray(email) ? email : [email]; - return ( ) => { {child} ))} - {/* {tags && tags.length > 0 && ( - - - {tags.map((tag, i) => ( - - ))} - - - )} */} - {slackChannels && ( - - - - - Support} - secondary={ - - {slackChannels.map((channel, i) => ( - {channel} - ))} - - } - /> - - )} - {contactEmails.length > 0 && ( - - - - - Contact} - secondary={ - - {contactEmails.map((em, index) => ( - - {em} - - ))} - - } - /> - - )} + {items && + items.map((item, i) => )} diff --git a/packages/core/src/hooks/index.ts b/packages/core/src/hooks/index.ts index a77a0eb8a4eda..6408790b8d39a 100644 --- a/packages/core/src/hooks/index.ts +++ b/packages/core/src/hooks/index.ts @@ -15,3 +15,9 @@ */ export { useQueryParamState } from './useQueryParamState'; +export { useSupportConfig } from './useSupportConfig'; +export type { + SupportConfig, + SupportItem, + SupportItemLink, +} from './useSupportConfig'; diff --git a/packages/core/src/hooks/useSupportConfig.ts b/packages/core/src/hooks/useSupportConfig.ts new file mode 100644 index 0000000000000..e77c64dd859eb --- /dev/null +++ b/packages/core/src/hooks/useSupportConfig.ts @@ -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'), + }), + ), + })), + }; +} diff --git a/packages/core/src/layout/ErrorPage/ErrorPage.tsx b/packages/core/src/layout/ErrorPage/ErrorPage.tsx index 776ed4161429d..466c2f46b6b0e 100644 --- a/packages/core/src/layout/ErrorPage/ErrorPage.tsx +++ b/packages/core/src/layout/ErrorPage/ErrorPage.tsx @@ -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 ( @@ -71,13 +73,11 @@ export const ErrorPage = ({ navigate(-1)}> Go back - ... or if you think this is a bug, please file an{' '} - - issue. - + ... or please{' '} + + contact support + {' '} + if you think this is a bug. diff --git a/plugins/catalog/src/components/EntityLinksCard/EntityLinksCard.tsx b/plugins/catalog/src/components/EntityLinksCard/EntityLinksCard.tsx index b0c3db2333dc8..f7e558e285ab9 100644 --- a/plugins/catalog/src/components/EntityLinksCard/EntityLinksCard.tsx +++ b/plugins/catalog/src/components/EntityLinksCard/EntityLinksCard.tsx @@ -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; diff --git a/plugins/explore/src/components/DomainExplorerContent/DomainExplorerContent.test.tsx b/plugins/explore/src/components/DomainExplorerContent/DomainExplorerContent.test.tsx index a52b0d8f2cbb5..f29ad2cd6a3af 100644 --- a/plugins/explore/src/components/DomainExplorerContent/DomainExplorerContent.test.tsx +++ b/plugins/explore/src/components/DomainExplorerContent/DomainExplorerContent.test.tsx @@ -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('', () => { @@ -33,11 +33,9 @@ describe('', () => { }; const Wrapper = ({ children }: { children?: React.ReactNode }) => ( - - - {children} - - + + {children} + ); beforeEach(() => { @@ -69,9 +67,11 @@ describe('', () => { ]; catalogApi.getEntities.mockResolvedValue({ items: entities }); - const { getByText } = render(, { - wrapper: Wrapper, - }); + const { getByText } = await renderInTestApp( + + + , + ); await waitFor(() => { expect(getByText('artists')).toBeInTheDocument(); @@ -82,9 +82,11 @@ describe('', () => { it('renders empty state', async () => { catalogApi.getEntities.mockResolvedValue({ items: [] }); - const { getByText } = render(, { - wrapper: Wrapper, - }); + const { getByText } = await renderInTestApp( + + + , + ); await waitFor(() => expect(getByText('No domains to display')).toBeInTheDocument(), @@ -95,9 +97,11 @@ describe('', () => { const catalogError = new Error('Network timeout'); catalogApi.getEntities.mockRejectedValueOnce(catalogError); - const { getByText } = render(, { - wrapper: Wrapper, - }); + const { getByText } = await renderInTestApp( + + + , + ); await waitFor(() => expect(getByText(/Could not load domains/)).toBeInTheDocument(), diff --git a/plugins/explore/src/components/ToolExplorerContent/ToolExplorerContent.test.tsx b/plugins/explore/src/components/ToolExplorerContent/ToolExplorerContent.test.tsx index b0e1330ff3edf..125a72eb12c4a 100644 --- a/plugins/explore/src/components/ToolExplorerContent/ToolExplorerContent.test.tsx +++ b/plugins/explore/src/components/ToolExplorerContent/ToolExplorerContent.test.tsx @@ -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('', () => { @@ -33,13 +33,11 @@ describe('', () => { const Wrapper = ({ children }: { children?: React.ReactNode }) => ( - - - {children} - - + + {children} + ); @@ -70,9 +68,11 @@ describe('', () => { ]; exploreToolsConfigApi.getTools.mockResolvedValue(tools); - const { getByText } = render(, { - wrapper: Wrapper, - }); + const { getByText } = await renderInTestApp( + + + , + ); await waitFor(() => { expect(getByText('Lighthouse')).toBeInTheDocument(); @@ -83,9 +83,11 @@ describe('', () => { it('renders empty state', async () => { exploreToolsConfigApi.getTools.mockResolvedValue([]); - const { getByText } = render(, { - wrapper: Wrapper, - }); + const { getByText } = await renderInTestApp( + + + , + ); await waitFor(() => expect(getByText('No tools to display')).toBeInTheDocument(), diff --git a/plugins/gitops-profiles/src/components/ProfileCatalog/ProfileCatalog.test.tsx b/plugins/gitops-profiles/src/components/ProfileCatalog/ProfileCatalog.test.tsx index f01ac70e64ea7..046fa58600663 100644 --- a/plugins/gitops-profiles/src/components/ProfileCatalog/ProfileCatalog.test.tsx +++ b/plugins/gitops-profiles/src/components/ProfileCatalog/ProfileCatalog.test.tsx @@ -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( , ); - expect( - rendered.getByText('Create GitOps-managed Cluster'), - ).toBeInTheDocument(); + + expect(getByText('Create GitOps-managed Cluster')).toBeInTheDocument(); }); }); diff --git a/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.test.tsx b/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.test.tsx index e9da8ef84d97a..9dfed8f404ae8 100644 --- a/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.test.tsx +++ b/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.test.tsx @@ -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 = { @@ -44,30 +43,29 @@ const catalogApi: jest.Mocked = { }; const Wrapper = ({ children }: { children?: React.ReactNode }) => ( - - - {children} - - + + {children} + ); describe('RegisterComponentPage', () => { - it('should render', () => { - render( - , - { wrapper: Wrapper }, + it('should render', async () => { + const { getByText } = await renderInTestApp( + + + , ); - expect(screen.getByText('Register existing component')).toBeInTheDocument(); + expect(getByText('Register existing component')).toBeInTheDocument(); }); }); diff --git a/plugins/tech-radar/src/components/RadarPage.test.tsx b/plugins/tech-radar/src/components/RadarPage.test.tsx index ee5cf8e1319dc..918015001b591 100644 --- a/plugins/tech-radar/src/components/RadarPage.test.tsx +++ b/plugins/tech-radar/src/components/RadarPage.test.tsx @@ -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( - - - , - ), + const { getByText, getByTestId } = await renderInTestApp( + + + , ); 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( diff --git a/plugins/welcome/src/components/WelcomePage/WelcomePage.test.tsx b/plugins/welcome/src/components/WelcomePage/WelcomePage.test.tsx index b639da9d50b54..210cdcf92185b 100644 --- a/plugins/welcome/src/components/WelcomePage/WelcomePage.test.tsx +++ b/plugins/welcome/src/components/WelcomePage/WelcomePage.test.tsx @@ -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( { , ); - expect(rendered.baseElement).toBeInTheDocument(); + expect(baseElement).toBeInTheDocument(); }); });