From 8a1566719692d0d216807386e12bc89746b8cbe5 Mon Sep 17 00:00:00 2001 From: Andrew Thauer <6507159+andrewthauer@users.noreply.github.com> Date: Tue, 23 Feb 2021 15:47:55 -0500 Subject: [PATCH] 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. --- .changeset/silver-balloons-play.md | 5 + app-config.yaml | 13 ++ packages/core-api/src/app/App.tsx | 4 +- packages/core-api/src/app/types.ts | 4 +- packages/core-api/src/icons/icons.tsx | 27 +++- packages/core-api/src/icons/types.ts | 11 +- packages/core/config.d.ts | 35 +++++ .../SupportButton/SupportButton.tsx | 132 +++++++----------- packages/core/src/hooks/index.ts | 6 + packages/core/src/hooks/useSupportConfig.ts | 56 ++++++++ .../core/src/layout/ErrorPage/ErrorPage.tsx | 14 +- .../EntityLinksCard/EntityLinksCard.tsx | 5 +- .../DomainExplorerContent.test.tsx | 36 ++--- .../ToolExplorerContent.test.tsx | 32 +++-- .../ProfileCatalog/ProfileCatalog.test.tsx | 22 +-- .../RegisterComponentPage.test.tsx | 42 +++--- .../src/components/RadarPage.test.tsx | 29 ++-- .../WelcomePage/WelcomePage.test.tsx | 19 ++- 18 files changed, 301 insertions(+), 191 deletions(-) create mode 100644 .changeset/silver-balloons-play.md create mode 100644 packages/core/src/hooks/useSupportConfig.ts diff --git a/.changeset/silver-balloons-play.md b/.changeset/silver-balloons-play.md new file mode 100644 index 0000000000..24a5c8ac46 --- /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 672ba26cd2..f68f5a30a2 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 3cf5abdb44..1cf2d892d0 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 69eb7f7189..ec7b70689d 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 0c2b580ea7..49db90efcc 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 be24b223ae..c4634bd00a 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 a6f95ae71c..bc6c462014 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 2a1b810582..7c532c0f8f 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 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) => { - // 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 a77a0eb8a4..6408790b8d 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 0000000000..91984aa214 --- /dev/null +++ b/packages/core/src/hooks/useSupportConfig.ts @@ -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', + }, + ], + }, + ], + }; +} diff --git a/packages/core/src/layout/ErrorPage/ErrorPage.tsx b/packages/core/src/layout/ErrorPage/ErrorPage.tsx index 776ed41614..466c2f46b6 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 b0c3db2333..f7e558e285 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 a52b0d8f2c..f29ad2cd6a 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 b0e1330ff3..125a72eb12 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 f01ac70e64..046fa58600 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 e9da8ef84d..9dfed8f404 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 ee5cf8e131..918015001b 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 b639da9d50..210cdcf921 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(); }); });