diff --git a/.changeset/flat-badgers-attack.md b/.changeset/flat-badgers-attack.md new file mode 100644 index 0000000000..69250d0ded --- /dev/null +++ b/.changeset/flat-badgers-attack.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Support i18n for core components diff --git a/packages/core-components/api-report-alpha.md b/packages/core-components/api-report-alpha.md new file mode 100644 index 0000000000..a51e590812 --- /dev/null +++ b/packages/core-components/api-report-alpha.md @@ -0,0 +1,73 @@ +## API Report File for "@backstage/core-components" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { TranslationRef } from '@backstage/core-plugin-api/alpha'; + +// @alpha (undocumented) +export const coreComponentsTranslationRef: TranslationRef< + 'core-components', + { + readonly 'link.openNewWindow': 'Opens in a new window'; + readonly 'table.filter.title': 'Filters'; + readonly 'table.filter.clearAll': 'Clear all'; + readonly 'signIn.title': 'Sign In'; + readonly 'signIn.loginFailed': 'Login failed'; + readonly 'signIn.customProvider.title': 'Custom User'; + readonly 'signIn.customProvider.subtitle2': 'This selection will not be stored.'; + readonly 'signIn.customProvider.subtitle': 'Enter your own User ID and credentials.'; + readonly 'signIn.customProvider.userId': 'User ID'; + readonly 'signIn.customProvider.tokenInvalid': 'Token is not a valid OpenID Connect JWT Token'; + readonly 'signIn.customProvider.continue': 'Continue'; + readonly 'signIn.customProvider.idToken': 'ID Token (optional)'; + readonly 'signIn.guestProvider.title': 'Guest'; + readonly 'signIn.guestProvider.description': 'You will not have a verified identity, meaning some features might be unavailable.'; + readonly 'signIn.guestProvider.enter': 'Enter'; + readonly 'signIn.guestProvider.subtitle': 'Enter as a Guest User.'; + readonly 'sidebar.shipToContent': 'Skip to content'; + readonly 'sidebar.starredIntroText': 'Fun fact! As you explore all the awesome plugins in Backstage, you can actually pin them to this side nav.Keep an eye out for the little star icon (⭐) next to the plugin name and give it a click!'; + readonly 'sidebar.recentlyViewedIntroText': 'And your recently viewed plugins will pop up here!'; + readonly 'sidebar.dismiss': 'Dismiss'; + readonly 'copyTextButton.tooltipText': 'Text copied to clipboard'; + readonly 'simpleStepper.finish': 'Finish'; + readonly 'simpleStepper.reset': 'Reset'; + readonly 'simpleStepper.next': 'Next'; + readonly 'simpleStepper.skip': 'Skip'; + readonly 'simpleStepper.back': 'Back'; + readonly 'errorPage.title': 'Looks like someone dropped the mic!'; + readonly 'errorPage.subtitle': 'ERROR {{status}}: {{statusMessage}}'; + readonly 'errorPage.goBack': 'Go back'; + readonly 'errorPage.orPlease': '... or please '; + readonly 'errorPage.contactSupport': 'contact support'; + readonly 'errorPage.isBug': 'if you think this is a bug.'; + readonly 'emptyState.missingAnnotation.title': 'Missing Annotation'; + readonly 'emptyState.missingAnnotation.actionTitle': 'Add the annotation to your component YAML as shown in the highlighted example below:'; + readonly 'emptyState.missingAnnotation.readMore': 'Read more'; + readonly 'emptyState.missingAnnotation.descriptionPrefix_one': 'The annotation '; + readonly 'emptyState.missingAnnotation.descriptionPrefix_other': 'The annotations '; + readonly 'emptyState.missingAnnotation.descriptionSuffix_one': ' is missing. You need to add the annotation to your component if you want to enable this tool.'; + readonly 'emptyState.missingAnnotation.descriptionSuffix_other': ' are missing. You need to add the annotations to your component if you want to enable this tool.'; + readonly 'supportConfig.title': 'Support Not Configured'; + readonly 'supportConfig.links.title': 'Add `app.support` config key'; + readonly 'errorBoundary.title': 'Please contact {{slackChannel}} for help.'; + readonly 'oauthRequestDialog.title': 'Login Required'; + readonly 'oauthRequestDialog.authRedirectTitle': 'This will trigger a http redirect to OAuth Login.'; + readonly 'oauthRequestDialog.login': 'Log in'; + readonly 'oauthRequestDialog.rejectAll': 'Reject All'; + readonly 'supportButton.title': 'Support'; + readonly 'supportButton.close': 'Close'; + readonly 'alertDisplay.message_one': '({{ num }} older message)'; + readonly 'alertDisplay.message_other': '({{ num }} older messages)'; + readonly 'autoLogout.stillTherePrompt.title': 'Logging out due to inactivity'; + readonly 'autoLogout.stillTherePrompt.description': 'You are about to be disconnected in'; + readonly 'autoLogout.stillTherePrompt.second_one': 'second'; + readonly 'autoLogout.stillTherePrompt.second_other': 'seconds'; + readonly 'autoLogout.stillTherePrompt.descriptionSuffix': 'Are you still there?'; + readonly 'autoLogout.stillTherePrompt.buttonText': "Yes! Don't log me out"; + readonly 'proxiedSignInPage.title': 'You do not appear to be signed in. Please try reloading the browser page.'; + } +>; + +// (No @packageDocumentation comment for this package) +``` diff --git a/packages/core-components/package.json b/packages/core-components/package.json index 73de44d5ed..04edbeb212 100644 --- a/packages/core-components/package.json +++ b/packages/core-components/package.json @@ -22,11 +22,15 @@ "types": "src/index.ts", "exports": { ".": "./src/index.ts", + "./alpha": "./src/alpha.ts", "./testUtils": "./src/testUtils.ts", "./package.json": "./package.json" }, "typesVersions": { "*": { + "alpha": [ + "src/alpha.ts" + ], "testUtils": [ "src/testUtils.ts" ], diff --git a/packages/core-components/src/alpha.ts b/packages/core-components/src/alpha.ts new file mode 100644 index 0000000000..e1f7678bae --- /dev/null +++ b/packages/core-components/src/alpha.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export * from './translation'; diff --git a/packages/core-components/src/components/AlertDisplay/AlertDisplay.tsx b/packages/core-components/src/components/AlertDisplay/AlertDisplay.tsx index b1c8273749..19527f76c2 100644 --- a/packages/core-components/src/components/AlertDisplay/AlertDisplay.tsx +++ b/packages/core-components/src/components/AlertDisplay/AlertDisplay.tsx @@ -14,13 +14,14 @@ * limitations under the License. */ import { alertApiRef, AlertMessage, useApi } from '@backstage/core-plugin-api'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; import IconButton from '@material-ui/core/IconButton'; import Snackbar from '@material-ui/core/Snackbar'; import Typography from '@material-ui/core/Typography'; import CloseIcon from '@material-ui/icons/Close'; import { Alert } from '@material-ui/lab'; -import pluralize from 'pluralize'; import React, { useEffect, useState } from 'react'; +import { coreComponentsTranslationRef } from '../../translation'; /** * Properties for {@link AlertDisplay} @@ -63,6 +64,7 @@ export type AlertDisplayProps = { export function AlertDisplay(props: AlertDisplayProps) { const [messages, setMessages] = useState>([]); const alertApi = useApi(alertApiRef); + const { t } = useTranslationRef(coreComponentsTranslationRef); const { anchorOrigin = { vertical: 'top', horizontal: 'center' }, @@ -121,10 +123,12 @@ export function AlertDisplay(props: AlertDisplayProps) { {String(firstMessage.message)} {messages.length > 1 && ( - {` (${messages.length - 1} older ${pluralize( - 'message', - messages.length - 1, - )})`} + + {t('alertDisplay.message', { + num: String(messages.length - 1), + count: messages.length - 1, + })} + )} diff --git a/packages/core-components/src/components/AutoLogout/Autologout.test.tsx b/packages/core-components/src/components/AutoLogout/Autologout.test.tsx index 0d414524a5..164ad716d2 100644 --- a/packages/core-components/src/components/AutoLogout/Autologout.test.tsx +++ b/packages/core-components/src/components/AutoLogout/Autologout.test.tsx @@ -18,16 +18,18 @@ import { createMocks } from 'react-idle-timer'; import { MessageChannel } from 'worker_threads'; import { ApiProvider } from '@backstage/core-app-api'; import { identityApiRef } from '@backstage/core-plugin-api'; -import { TestApiRegistry } from '@backstage/test-utils'; +import { TestApiRegistry, renderInTestApp } from '@backstage/test-utils'; import React from 'react'; import { AutoLogout } from './AutoLogout'; -import { cleanup, render } from '@testing-library/react'; +import { cleanup } from '@testing-library/react'; // Mock the signOut function of identityApiRef const mockSignOut = jest.fn(); -const mockIdentityApi = { signOut: mockSignOut }; - +const mockIdentityApi = { + signOut: mockSignOut, + getCredentials: jest.fn().mockReturnValue({ token: 'xxx' }), +}; const apis = TestApiRegistry.from([identityApiRef, mockIdentityApi]); describe('AutoLogout', () => { @@ -44,27 +46,29 @@ describe('AutoLogout', () => { }); it('should throw error if idleTimeoutMinutes is smaller than promptBeforeSeconds', async () => { - expect(() => - render( - - - , - ), - ).toThrow(); + await expect( + async () => + await renderInTestApp( + + + , + ), + ).rejects.toThrow(); }); it('should throw error if idleTimeoutMinutes is smaller than 30 seconds', async () => { - expect(() => - render( - - -
Test Child
-
, - ), - ).toThrow(); + await expect( + async () => + await renderInTestApp( + + +
Test Child
+
, + ), + ).rejects.toThrow(); }); }); diff --git a/packages/core-components/src/components/AutoLogout/StillTherePrompt.tsx b/packages/core-components/src/components/AutoLogout/StillTherePrompt.tsx index e0cfc8dd56..c704cf9910 100644 --- a/packages/core-components/src/components/AutoLogout/StillTherePrompt.tsx +++ b/packages/core-components/src/components/AutoLogout/StillTherePrompt.tsx @@ -22,6 +22,8 @@ import DialogContentText from '@material-ui/core/DialogContentText'; import DialogTitle from '@material-ui/core/DialogTitle'; import React, { useEffect } from 'react'; import { IIdleTimer } from 'react-idle-timer'; +import { coreComponentsTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; export interface StillTherePromptProps { idleTimer: IIdleTimer; @@ -41,6 +43,7 @@ export const StillTherePrompt = (props: StillTherePromptProps) => { remainingTime, setRemainingTime, } = props; + const { t } = useTranslationRef(coreComponentsTranslationRef); useEffect(() => { const interval = setInterval(() => { @@ -61,18 +64,18 @@ export const StillTherePrompt = (props: StillTherePromptProps) => { remainingTime - promptTimeoutMillis / 1000, 0, ); - const seconds = timeTillPrompt > 1 ? 'seconds' : 'second'; return ( - Logging out due to inactivity + {t('autoLogout.stillTherePrompt.title')} - You are about to be disconnected in{' '} + {t('autoLogout.stillTherePrompt.description')}{' '} - {Math.ceil(remainingTime / 1000)} {seconds} + {Math.ceil(remainingTime / 1000)}{' '} + {t('autoLogout.stillTherePrompt.second', { count: timeTillPrompt })} - . Are you still there? + . {t('autoLogout.stillTherePrompt.descriptionSuffix')} @@ -82,7 +85,7 @@ export const StillTherePrompt = (props: StillTherePromptProps) => { variant="contained" size="small" > - Yes! Don't log me out + {t('autoLogout.stillTherePrompt.buttonText')} diff --git a/packages/core-components/src/components/CopyTextButton/CopyTextButton.tsx b/packages/core-components/src/components/CopyTextButton/CopyTextButton.tsx index bd900a931d..91b046354a 100644 --- a/packages/core-components/src/components/CopyTextButton/CopyTextButton.tsx +++ b/packages/core-components/src/components/CopyTextButton/CopyTextButton.tsx @@ -20,6 +20,8 @@ import Tooltip from '@material-ui/core/Tooltip'; import CopyIcon from '@material-ui/icons/FileCopy'; import React, { MouseEventHandler, useEffect, useState } from 'react'; import useCopyToClipboard from 'react-use/lib/useCopyToClipboard'; +import { coreComponentsTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** * Properties for {@link CopyTextButton} @@ -78,10 +80,11 @@ export interface CopyTextButtonProps { * ``` */ export function CopyTextButton(props: CopyTextButtonProps) { + const { t } = useTranslationRef(coreComponentsTranslationRef); const { text, tooltipDelay = 1000, - tooltipText = 'Text copied to clipboard', + tooltipText = t('copyTextButton.tooltipText'), 'aria-label': ariaLabel = 'Copy text', } = props; const errorApi = useApi(errorApiRef); diff --git a/packages/core-components/src/components/EmptyState/MissingAnnotationEmptyState.tsx b/packages/core-components/src/components/EmptyState/MissingAnnotationEmptyState.tsx index 81b4714531..8c3f8c186f 100644 --- a/packages/core-components/src/components/EmptyState/MissingAnnotationEmptyState.tsx +++ b/packages/core-components/src/components/EmptyState/MissingAnnotationEmptyState.tsx @@ -23,6 +23,8 @@ import React from 'react'; import { CodeSnippet } from '../CodeSnippet'; import { Link } from '../Link'; import { EmptyState } from './EmptyState'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { coreComponentsTranslationRef } from '../../translation'; const COMPONENT_YAML_TEMPLATE = `apiVersion: backstage.io/v1alpha1 kind: Component @@ -76,11 +78,13 @@ function generateComponentYaml(annotations: string[]) { return COMPONENT_YAML_TEMPLATE.replace(ANNOTATION_YAML, annotationYaml); } -function generateDescription(annotations: string[]) { - const isSingular = annotations.length <= 1; +function useGenerateDescription(annotations: string[]) { + const { t } = useTranslationRef(coreComponentsTranslationRef); return ( <> - The {isSingular ? 'annotation' : 'annotations'}{' '} + {t('emptyState.missingAnnotation.descriptionPrefix', { + count: annotations.length, + })} {annotations .map(ann => {ann}) .reduce((prev, curr) => ( @@ -88,9 +92,9 @@ function generateDescription(annotations: string[]) { {prev}, {curr} ))}{' '} - {isSingular ? 'is' : 'are'} missing. You need to add the{' '} - {isSingular ? 'annotation' : 'annotations'} to your component if you want - to enable this tool. + {t('emptyState.missingAnnotation.descriptionSuffix', { + count: annotations.length, + })} ); } @@ -106,17 +110,17 @@ export function MissingAnnotationEmptyState(props: Props) { readMoreUrl || 'https://backstage.io/docs/features/software-catalog/well-known-annotations'; const classes = useStyles(); + const { t } = useTranslationRef(coreComponentsTranslationRef); return ( - Add the annotation to your component YAML as shown in the - highlighted example below: + {t('emptyState.missingAnnotation.actionTitle')} } diff --git a/packages/core-components/src/components/Link/Link.tsx b/packages/core-components/src/components/Link/Link.tsx index e957390da8..b426402ba9 100644 --- a/packages/core-components/src/components/Link/Link.tsx +++ b/packages/core-components/src/components/Link/Link.tsx @@ -14,6 +14,7 @@ * limitations under the License. */ import { configApiRef, useAnalytics, useApi } from '@backstage/core-plugin-api'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; // eslint-disable-next-line no-restricted-imports import MaterialLink, { LinkProps as MaterialLinkProps, @@ -29,6 +30,7 @@ import { LinkProps as RouterLinkProps, Route, } from 'react-router-dom'; +import { coreComponentsTranslationRef } from '../../translation'; export function isReactRouterBeta(): boolean { const [obj] = createRoutesFromChildren(} />); @@ -161,6 +163,7 @@ export const Link = React.forwardRef( ({ onClick, noTrack, ...props }, ref) => { const classes = useStyles(); const analytics = useAnalytics(); + const { t } = useTranslationRef(coreComponentsTranslationRef); // Adding the base path to URLs breaks react-router v6 stable, so we only // do it for beta. The react router version won't change at runtime so it is @@ -199,7 +202,7 @@ export const Link = React.forwardRef( > {props.children} - , Opens in a new window + {`, ${t('link.openNewWindow')}`} ) : ( diff --git a/packages/core-components/src/components/OAuthRequestDialog/LoginRequestListItem.tsx b/packages/core-components/src/components/OAuthRequestDialog/LoginRequestListItem.tsx index 782624362d..375ea66953 100644 --- a/packages/core-components/src/components/OAuthRequestDialog/LoginRequestListItem.tsx +++ b/packages/core-components/src/components/OAuthRequestDialog/LoginRequestListItem.tsx @@ -23,6 +23,8 @@ import Button from '@material-ui/core/Button'; import React, { useState } from 'react'; import { isError } from '@backstage/errors'; import { PendingOAuthRequest } from '@backstage/core-plugin-api'; +import { coreComponentsTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; export type LoginRequestListItemClassKey = 'root'; @@ -44,6 +46,7 @@ type RowProps = { const LoginRequestListItem = ({ request, busy, setBusy }: RowProps) => { const classes = useItemStyles(); const [error, setError] = useState(); + const { t } = useTranslationRef(coreComponentsTranslationRef); const handleContinue = async () => { setBusy(true); @@ -68,7 +71,7 @@ const LoginRequestListItem = ({ request, busy, setBusy }: RowProps) => { secondary={error && {error}} /> ); diff --git a/packages/core-components/src/components/OAuthRequestDialog/OAuthRequestDialog.tsx b/packages/core-components/src/components/OAuthRequestDialog/OAuthRequestDialog.tsx index 7c17e74e4a..2257b776d9 100644 --- a/packages/core-components/src/components/OAuthRequestDialog/OAuthRequestDialog.tsx +++ b/packages/core-components/src/components/OAuthRequestDialog/OAuthRequestDialog.tsx @@ -30,6 +30,8 @@ import { oauthRequestApiRef, } from '@backstage/core-plugin-api'; import Typography from '@material-ui/core/Typography'; +import { coreComponentsTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; export type OAuthRequestDialogClassKey = | 'dialog' @@ -63,6 +65,7 @@ export function OAuthRequestDialog(_props: {}) { const [busy, setBusy] = useState(false); const oauthRequestApi = useApi(oauthRequestApiRef); const configApi = useApi(configApiRef); + const { t } = useTranslationRef(coreComponentsTranslationRef); const authRedirect = configApi.getOptionalBoolean('enableExperimentalRedirectFlow') ?? false; @@ -94,12 +97,10 @@ export function OAuthRequestDialog(_props: {}) { variant="h1" variantMapping={{ h1: 'span' }} > - Login Required + {t('oauthRequestDialog.title')} {authRedirect ? ( - - This will trigger a http redirect to OAuth Login. - + {t('oauthRequestDialog.authRedirectTitle')} ) : null} @@ -118,7 +119,9 @@ export function OAuthRequestDialog(_props: {}) { - + ); diff --git a/packages/core-components/src/components/SimpleStepper/SimpleStepperFooter.tsx b/packages/core-components/src/components/SimpleStepper/SimpleStepperFooter.tsx index 224b66533a..8db3ac8d72 100644 --- a/packages/core-components/src/components/SimpleStepper/SimpleStepperFooter.tsx +++ b/packages/core-components/src/components/SimpleStepper/SimpleStepperFooter.tsx @@ -20,6 +20,8 @@ import React, { PropsWithChildren, ReactNode, useContext } from 'react'; import { VerticalStepperContext } from './SimpleStepper'; import { StepActions } from './types'; +import { coreComponentsTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; export type SimpleStepperFooterClassKey = 'root'; @@ -55,9 +57,12 @@ interface BackBtnProps extends CommonBtnProps { disabled?: boolean; stepIndex: number; } -export const RestartBtn = ({ text, handleClick }: RestartBtnProps) => ( - -); +export const RestartBtn = ({ text, handleClick }: RestartBtnProps) => { + const { t } = useTranslationRef(coreComponentsTranslationRef); + return ( + + ); +}; const NextBtn = ({ text, @@ -65,39 +70,48 @@ const NextBtn = ({ disabled, last, stepIndex, -}: NextBtnProps) => ( - -); +}: NextBtnProps) => { + const { t } = useTranslationRef(coreComponentsTranslationRef); + return ( + + ); +}; -const SkipBtn = ({ text, handleClick, disabled, stepIndex }: SkipBtnProps) => ( - -); +const SkipBtn = ({ text, handleClick, disabled, stepIndex }: SkipBtnProps) => { + const { t } = useTranslationRef(coreComponentsTranslationRef); + return ( + + ); +}; -const BackBtn = ({ text, handleClick, disabled, stepIndex }: BackBtnProps) => ( - -); +const BackBtn = ({ text, handleClick, disabled, stepIndex }: BackBtnProps) => { + const { t } = useTranslationRef(coreComponentsTranslationRef); + return ( + + ); +}; export type SimpleStepperFooterProps = { actions?: StepActions; diff --git a/packages/core-components/src/components/SupportButton/SupportButton.tsx b/packages/core-components/src/components/SupportButton/SupportButton.tsx index 726500c77e..623eda3098 100644 --- a/packages/core-components/src/components/SupportButton/SupportButton.tsx +++ b/packages/core-components/src/components/SupportButton/SupportButton.tsx @@ -31,6 +31,8 @@ import React, { MouseEventHandler, useState } from 'react'; import { SupportItem, SupportItemLink, useSupportConfig } from '../../hooks'; import { HelpIcon } from '../../icons'; import { Link } from '../Link'; +import { coreComponentsTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; type SupportButtonProps = { title?: string; @@ -85,6 +87,7 @@ const SupportListItem = ({ item }: { item: SupportItem }) => { }; export function SupportButton(props: SupportButtonProps) { + const { t } = useTranslationRef(coreComponentsTranslationRef); const { title, items, children } = props; const { items: configItems } = useSupportConfig(); @@ -125,7 +128,7 @@ export function SupportButton(props: SupportButtonProps) { onClick={onClickHandler} startIcon={} > - Support + {t('supportButton.title')} )} @@ -171,7 +174,7 @@ export function SupportButton(props: SupportButtonProps) { onClick={popoverCloseHandler} aria-label="Close" > - Close + {t('supportButton.close')} diff --git a/packages/core-components/src/components/Table/Filters.tsx b/packages/core-components/src/components/Table/Filters.tsx index 568b22b933..999676f4d9 100644 --- a/packages/core-components/src/components/Table/Filters.tsx +++ b/packages/core-components/src/components/Table/Filters.tsx @@ -21,6 +21,8 @@ import React, { useEffect, useState } from 'react'; import { Select } from '../Select'; import { SelectProps } from '../Select/Select'; +import { coreComponentsTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; export type TableFiltersClassKey = 'root' | 'value' | 'heder' | 'filters'; @@ -76,6 +78,7 @@ export const Filters = (props: Props) => { const classes = useFilterStyles(); const { onChangeFilters } = props; + const { t } = useTranslationRef(coreComponentsTranslationRef); const [selectedFilters, setSelectedFilters] = useState({ ...props.selectedFilters, @@ -96,9 +99,9 @@ export const Filters = (props: Props) => { return ( - Filters + {t('table.filter.title')} diff --git a/packages/core-components/src/hooks/useSupportConfig.ts b/packages/core-components/src/hooks/useSupportConfig.ts index e80eb457a1..49cc44c0f7 100644 --- a/packages/core-components/src/hooks/useSupportConfig.ts +++ b/packages/core-components/src/hooks/useSupportConfig.ts @@ -15,6 +15,8 @@ */ import { useApiHolder, configApiRef } from '@backstage/core-plugin-api'; +import { coreComponentsTranslationRef } from '../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; export type SupportItemLink = { url: string; @@ -32,30 +34,34 @@ export type SupportConfig = { 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/backstage/backstage/blob/master/app-config.yaml', - }, - ], - }, - ], +const useDefaultSupportConfig = () => { + const { t } = useTranslationRef(coreComponentsTranslationRef); + return { + url: 'https://github.com/backstage/backstage/issues', + items: [ + { + title: t('supportConfig.title'), + icon: 'warning', + links: [ + { + // TODO: Update to dedicated support page on backstage.io/docs + title: t('supportConfig.links.title'), + url: 'https://github.com/backstage/backstage/blob/master/app-config.yaml', + }, + ], + }, + ], + }; }; export function useSupportConfig(): SupportConfig { const apiHolder = useApiHolder(); const config = apiHolder.get(configApiRef); const supportConfig = config?.getOptionalConfig('app.support'); + const defaultSupportConfig = useDefaultSupportConfig(); if (!supportConfig) { - return DEFAULT_SUPPORT_CONFIG; + return defaultSupportConfig; } return { diff --git a/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.tsx b/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.tsx index 0ecaedfc19..8f746882a1 100644 --- a/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.tsx +++ b/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.tsx @@ -18,6 +18,8 @@ import Typography from '@material-ui/core/Typography'; import React, { ComponentClass, Component, ErrorInfo } from 'react'; import { LinkButton } from '../../components/LinkButton'; import { ErrorPanel } from '../../components/ErrorPanel'; +import { coreComponentsTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; type SlackChannel = { name: string; @@ -37,14 +39,21 @@ type State = { const SlackLink = (props: { slackChannel?: string | SlackChannel }) => { const { slackChannel } = props; + const { t } = useTranslationRef(coreComponentsTranslationRef); if (!slackChannel) { return null; } else if (typeof slackChannel === 'string') { - return Please contact {slackChannel} for help.; + return ( + {t('errorBoundary.title', { slackChannel })} + ); } else if (!slackChannel.href) { return ( - Please contact {slackChannel.name} for help. + + {t('errorBoundary.title', { + slackChannel: slackChannel.name, + })} + ); } diff --git a/packages/core-components/src/layout/ErrorPage/ErrorPage.tsx b/packages/core-components/src/layout/ErrorPage/ErrorPage.tsx index 131df3da3a..f80161ff3c 100644 --- a/packages/core-components/src/layout/ErrorPage/ErrorPage.tsx +++ b/packages/core-components/src/layout/ErrorPage/ErrorPage.tsx @@ -23,6 +23,8 @@ import { Link } from '../../components/Link'; import { useSupportConfig } from '../../hooks'; import { MicDrop } from './MicDrop'; import { StackDetails } from './StackDetails'; +import { coreComponentsTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; interface IErrorPageProps { status?: string; @@ -68,6 +70,7 @@ export function ErrorPage(props: IErrorPageProps) { const classes = useStyles(); const navigate = useNavigate(); const support = useSupportConfig(); + const { t } = useTranslationRef(coreComponentsTranslationRef); return ( @@ -77,21 +80,23 @@ export function ErrorPage(props: IErrorPageProps) { variant="body1" className={classes.subtitle} > - ERROR {status}: {statusMessage} + {t('errorPage.subtitle', { status: status || '', statusMessage })} {additionalInfo} - Looks like someone dropped the mic! + {t('errorPage.title')} navigate(-1)}> - Go back + {t('errorPage.goBack')} - ... or please{' '} - contact support if you - think this is a bug. + {t('errorPage.orPlease')} + + {t('errorPage.contactSupport')} + + {t('errorPage.isBug')} {stack && } diff --git a/packages/core-components/src/layout/ProxiedSignInPage/ProxiedSignInPage.tsx b/packages/core-components/src/layout/ProxiedSignInPage/ProxiedSignInPage.tsx index ea095cd8ab..98fca7c6ef 100644 --- a/packages/core-components/src/layout/ProxiedSignInPage/ProxiedSignInPage.tsx +++ b/packages/core-components/src/layout/ProxiedSignInPage/ProxiedSignInPage.tsx @@ -24,6 +24,8 @@ import { useAsync, useMountEffect } from '@react-hookz/web'; import { ErrorPanel } from '../../components/ErrorPanel'; import { Progress } from '../../components/Progress'; import { ProxiedSignInIdentity } from './ProxiedSignInIdentity'; +import { coreComponentsTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** * Props for {@link ProxiedSignInPage}. @@ -61,6 +63,7 @@ export type ProxiedSignInPageProps = SignInPageProps & { */ export const ProxiedSignInPage = (props: ProxiedSignInPageProps) => { const discoveryApi = useApi(discoveryApiRef); + const { t } = useTranslationRef(coreComponentsTranslationRef); const [{ status, error }, { execute }] = useAsync(async () => { const identity = new ProxiedSignInIdentity({ @@ -79,12 +82,7 @@ export const ProxiedSignInPage = (props: ProxiedSignInPageProps) => { if (status === 'loading') { return ; } else if (error) { - return ( - - ); + return ; } return null; diff --git a/packages/core-components/src/layout/Sidebar/Bar.tsx b/packages/core-components/src/layout/Sidebar/Bar.tsx index 550c614522..f8c6036158 100644 --- a/packages/core-components/src/layout/Sidebar/Bar.tsx +++ b/packages/core-components/src/layout/Sidebar/Bar.tsx @@ -34,6 +34,8 @@ import { MobileSidebar } from './MobileSidebar'; import { useContent } from './Page'; import { SidebarOpenStateProvider } from './SidebarOpenStateContext'; import { useSidebarPinState } from './SidebarPinStateContext'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { coreComponentsTranslationRef } from '../../translation'; /** @public */ export type SidebarClassKey = 'drawer' | 'drawerOpen'; @@ -250,6 +252,7 @@ function A11ySkipSidebar() { const { sidebarConfig } = useContext(SidebarConfigContext); const { focusContent, contentRef } = useContent(); const classes = useStyles({ sidebarConfig }); + const { t } = useTranslationRef(coreComponentsTranslationRef); if (!contentRef?.current) { return null; @@ -260,7 +263,7 @@ function A11ySkipSidebar() { variant="contained" className={classnames(classes.visuallyHidden)} > - Skip to content + {t('sidebar.shipToContent')} ); } diff --git a/packages/core-components/src/layout/SignInPage/SignInPage.tsx b/packages/core-components/src/layout/SignInPage/SignInPage.tsx index 6092f2862f..95d96b701a 100644 --- a/packages/core-components/src/layout/SignInPage/SignInPage.tsx +++ b/packages/core-components/src/layout/SignInPage/SignInPage.tsx @@ -35,6 +35,8 @@ import { Page } from '../Page'; import { getSignInProviders, useSignInProviders } from './providers'; import { GridItem, useStyles } from './styles'; import { IdentityProviders, SignInProviderConfig } from './types'; +import { coreComponentsTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; type MultiSignInPageProps = SignInPageProps & { providers: IdentityProviders; @@ -95,6 +97,7 @@ export const SingleSignInPage = ({ const classes = useStyles(); const authApi = useApi(provider.apiRef); const configApi = useApi(configApiRef); + const { t } = useTranslationRef(coreComponentsTranslationRef); const [error, setError] = useState(); @@ -174,7 +177,7 @@ export const SingleSignInPage = ({ login({ showPopup: true }); }} > - Sign In + {t('signIn.title')} } > diff --git a/packages/core-components/src/layout/SignInPage/commonProvider.tsx b/packages/core-components/src/layout/SignInPage/commonProvider.tsx index 1827981e6f..07a9d9c98e 100644 --- a/packages/core-components/src/layout/SignInPage/commonProvider.tsx +++ b/packages/core-components/src/layout/SignInPage/commonProvider.tsx @@ -28,6 +28,8 @@ import { useApi, errorApiRef } from '@backstage/core-plugin-api'; import { GridItem } from './styles'; import { ForwardedError } from '@backstage/errors'; import { UserIdentity } from './UserIdentity'; +import { coreComponentsTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; const Component: ProviderComponent = ({ config, @@ -38,6 +40,7 @@ const Component: ProviderComponent = ({ const { apiRef, title, message } = config as SignInProviderConfig; const authApi = useApi(apiRef); const errorApi = useApi(errorApiRef); + const { t } = useTranslationRef(coreComponentsTranslationRef); const handleLogin = async () => { try { @@ -63,7 +66,7 @@ const Component: ProviderComponent = ({ ); } catch (error) { onSignInFailure(); - errorApi.post(new ForwardedError('Login failed', error)); + errorApi.post(new ForwardedError(t('signIn.loginFailed'), error)); } }; @@ -74,7 +77,7 @@ const Component: ProviderComponent = ({ title={title} actions={ } > diff --git a/packages/core-components/src/layout/SignInPage/customProvider.tsx b/packages/core-components/src/layout/SignInPage/customProvider.tsx index ad02715447..ebab5dce5c 100644 --- a/packages/core-components/src/layout/SignInPage/customProvider.tsx +++ b/packages/core-components/src/layout/SignInPage/customProvider.tsx @@ -27,6 +27,8 @@ import { InfoCard } from '../InfoCard/InfoCard'; import { ProviderComponent, ProviderLoader, SignInProvider } from './types'; import { GridItem } from './styles'; import { UserIdentity } from './UserIdentity'; +import { coreComponentsTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; // accept base64url format according to RFC7515 (https://tools.ietf.org/html/rfc7515#section-3) const ID_TOKEN_REGEX = /^[a-z0-9_\-]+\.[a-z0-9_\-]+\.[a-z0-9_\-]+$/i; @@ -63,6 +65,7 @@ const asInputRef = (renderResult: UseFormRegisterReturn) => { const Component: ProviderComponent = ({ onSignInStarted, onSignInSuccess }) => { const classes = useFormStyles(); + const { t } = useTranslationRef(coreComponentsTranslationRef); const { register, handleSubmit, formState } = useForm({ mode: 'onChange', }); @@ -84,18 +87,18 @@ const Component: ProviderComponent = ({ onSignInStarted, onSignInSuccess }) => { return ( - + - Enter your own User ID and credentials. + {t('signIn.customProvider.subtitle')}
- This selection will not be stored. + {t('signIn.customProvider.subtitle2')}
@@ -111,10 +114,10 @@ const Component: ProviderComponent = ({ onSignInStarted, onSignInSuccess }) => { validate: token => !token || ID_TOKEN_REGEX.test(token) || - 'Token is not a valid OpenID Connect JWT Token', + t('signIn.customProvider.tokenInvalid'), }), )} - label="ID Token (optional)" + label={t('signIn.customProvider.idToken')} margin="normal" autoComplete="off" error={Boolean(errors.idToken)} @@ -130,7 +133,7 @@ const Component: ProviderComponent = ({ onSignInStarted, onSignInSuccess }) => { className={classes.button} disabled={!formState?.isDirty || !isEmpty(errors)} > - Continue + {t('signIn.customProvider.continue')}
diff --git a/packages/core-components/src/translation.ts b/packages/core-components/src/translation.ts new file mode 100644 index 0000000000..a9fc17e7f6 --- /dev/null +++ b/packages/core-components/src/translation.ts @@ -0,0 +1,130 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createTranslationRef } from '@backstage/core-plugin-api/alpha'; + +/** @alpha */ +export const coreComponentsTranslationRef = createTranslationRef({ + id: 'core-components', + messages: { + signIn: { + title: 'Sign In', + loginFailed: 'Login failed', + customProvider: { + title: 'Custom User', + subtitle: 'Enter your own User ID and credentials.', + subtitle2: 'This selection will not be stored.', + userId: 'User ID', + tokenInvalid: 'Token is not a valid OpenID Connect JWT Token', + continue: 'Continue', + idToken: 'ID Token (optional)', + }, + guestProvider: { + title: 'Guest', + subtitle: 'Enter as a Guest User.', + description: + 'You will not have a verified identity, meaning some features might be unavailable.', + enter: 'Enter', + }, + }, + sidebar: { + shipToContent: 'Skip to content', + starredIntroText: + 'Fun fact! As you explore all the awesome plugins in Backstage, you can actually pin them to this side nav.Keep an eye out for the little star icon (⭐) next to the plugin name and give it a click!', + recentlyViewedIntroText: + 'And your recently viewed plugins will pop up here!', + dismiss: 'Dismiss', + }, + copyTextButton: { + tooltipText: 'Text copied to clipboard', + }, + simpleStepper: { + reset: 'Reset', + finish: 'Finish', + next: 'Next', + skip: 'Skip', + back: 'Back', + }, + errorPage: { + subtitle: 'ERROR {{status}}: {{statusMessage}}', + title: 'Looks like someone dropped the mic!', + goBack: 'Go back', + orPlease: '... or please ', + contactSupport: 'contact support', + isBug: 'if you think this is a bug.', + }, + emptyState: { + missingAnnotation: { + title: 'Missing Annotation', + actionTitle: + 'Add the annotation to your component YAML as shown in the highlighted example below:', + readMore: 'Read more', + descriptionPrefix_one: 'The annotation ', + descriptionPrefix_other: 'The annotations ', + descriptionSuffix_one: + ' is missing. You need to add the annotation to your component if you want to enable this tool.', + descriptionSuffix_other: + ' are missing. You need to add the annotations to your component if you want to enable this tool.', + }, + }, + supportConfig: { + title: 'Support Not Configured', + links: { + title: 'Add `app.support` config key', + }, + }, + errorBoundary: { + title: 'Please contact {{slackChannel}} for help.', + }, + oauthRequestDialog: { + title: 'Login Required', + authRedirectTitle: 'This will trigger a http redirect to OAuth Login.', + login: 'Log in', + rejectAll: 'Reject All', + }, + link: { + openNewWindow: 'Opens in a new window', + }, + supportButton: { + title: 'Support', + close: 'Close', + }, + table: { + filter: { + title: 'Filters', + clearAll: 'Clear all', + }, + }, + alertDisplay: { + message_one: '({{ num }} older message)', + message_other: '({{ num }} older messages)', + }, + autoLogout: { + stillTherePrompt: { + title: 'Logging out due to inactivity', + description: 'You are about to be disconnected in', + second_one: 'second', + second_other: 'seconds', + descriptionSuffix: 'Are you still there?', + buttonText: "Yes! Don't log me out", + }, + }, + proxiedSignInPage: { + title: + 'You do not appear to be signed in. Please try reloading the browser page.', + }, + }, +}); diff --git a/plugins/badges/src/components/EntityBadgesDialog.test.tsx b/plugins/badges/src/components/EntityBadgesDialog.test.tsx index 5d0e7fe849..94efa1eec8 100644 --- a/plugins/badges/src/components/EntityBadgesDialog.test.tsx +++ b/plugins/badges/src/components/EntityBadgesDialog.test.tsx @@ -16,7 +16,7 @@ import React from 'react'; import { Entity } from '@backstage/catalog-model'; -import { renderWithEffects, TestApiProvider } from '@backstage/test-utils'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; import { BadgesApi, badgesApiRef } from '../api'; import { EntityBadgesDialog } from './EntityBadgesDialog'; import { EntityProvider } from '@backstage/plugin-catalog-react'; @@ -43,7 +43,7 @@ describe('EntityBadgesDialog', () => { kind: 'MockKind', } as Entity; - const rendered = await renderWithEffects( + const rendered = await renderInTestApp( ({ describe('BitriseArtifactsComponent', () => { entityValue = { entity: { metadata: {} } }; - const renderComponent = () => render(); + const renderComponent = () => renderInTestApp(); it('should display an empty state if an app annotation is missing', async () => { - const rendered = renderComponent(); + const rendered = await renderComponent(); expect(await rendered.findByText('Missing Annotation')).toBeInTheDocument(); }); diff --git a/plugins/git-release-manager/package.json b/plugins/git-release-manager/package.json index 85b31d6311..5dc5315209 100644 --- a/plugins/git-release-manager/package.json +++ b/plugins/git-release-manager/package.json @@ -52,6 +52,7 @@ "@backstage/cli": "workspace:^", "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", + "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^14.0.0", diff --git a/plugins/git-release-manager/src/features/Features.test.tsx b/plugins/git-release-manager/src/features/Features.test.tsx index abde878648..2bd411b196 100644 --- a/plugins/git-release-manager/src/features/Features.test.tsx +++ b/plugins/git-release-manager/src/features/Features.test.tsx @@ -21,6 +21,10 @@ import { Features } from './Features'; import { mockCalverProject } from '../test-helpers/test-helpers'; import { TEST_IDS } from '../test-helpers/test-ids'; import { mockApiClient } from '../test-helpers/mock-api-client'; +import { MockErrorApi, TestApiProvider } from '@backstage/test-utils'; +import { translationApiRef } from '@backstage/core-plugin-api/alpha'; +import { MockTranslationApi } from '@backstage/test-utils'; +import { errorApiRef } from '@backstage/core-plugin-api'; jest.mock('@backstage/core-plugin-api', () => ({ ...jest.requireActual('@backstage/core-plugin-api'), @@ -35,18 +39,26 @@ jest.mock('../contexts/ProjectContext', () => ({ describe('Features', () => { it('should omit features omitted via configuration', async () => { const { getByTestId } = render( - [
Custom 1
,
Custom 2
], - }, - }} - />, + + [
Custom 1
,
Custom 2
], + }, + }} + /> + , +
, ); await waitFor(() => getByTestId(TEST_IDS.info.info)); diff --git a/plugins/git-release-manager/src/features/Info/Info.test.tsx b/plugins/git-release-manager/src/features/Info/Info.test.tsx index 1fcd53eb9c..c747a84d6e 100644 --- a/plugins/git-release-manager/src/features/Info/Info.test.tsx +++ b/plugins/git-release-manager/src/features/Info/Info.test.tsx @@ -15,14 +15,13 @@ */ import React from 'react'; -import { render } from '@testing-library/react'; - import { mockCalverProject, mockReleaseBranch, mockReleaseCandidateCalver, } from '../../test-helpers/test-helpers'; import { Info } from './Info'; +import { renderInTestApp } from '@backstage/test-utils'; jest.mock('../../contexts/ProjectContext', () => ({ useProjectContext: () => ({ @@ -32,7 +31,7 @@ jest.mock('../../contexts/ProjectContext', () => ({ describe('Info', () => { it('should return early if no latestRelease exists', async () => { - const { findByText } = render( + const { findByText } = await renderInTestApp( ({ ...jest.requireActual('@backstage/core-plugin-api'), @@ -56,13 +60,21 @@ describe('PatchBody', () => { }); const { getByTestId } = render( - , + + + , + , ); expect(getByTestId(TEST_IDS.patch.loading)).toBeInTheDocument(); @@ -74,13 +86,20 @@ describe('PatchBody', () => { it('should render not-prerelease description', async () => { const { getByTestId } = render( - , + + + , ); expect(getByTestId(TEST_IDS.patch.loading)).toBeInTheDocument(); diff --git a/plugins/gocd/src/components/GoCdBuildsComponent/GoCdBuildsComponent.test.tsx b/plugins/gocd/src/components/GoCdBuildsComponent/GoCdBuildsComponent.test.tsx index ebc3502901..57a219eb50 100644 --- a/plugins/gocd/src/components/GoCdBuildsComponent/GoCdBuildsComponent.test.tsx +++ b/plugins/gocd/src/components/GoCdBuildsComponent/GoCdBuildsComponent.test.tsx @@ -18,7 +18,7 @@ import { Entity } from '@backstage/catalog-model'; import { ConfigReader } from '@backstage/core-app-api'; import { ConfigApi, configApiRef } from '@backstage/core-plugin-api'; import { EntityProvider } from '@backstage/plugin-catalog-react'; -import { renderWithEffects, TestApiProvider } from '@backstage/test-utils'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; import { GoCdBuildsComponent } from './GoCdBuildsComponent'; import { gocdApiRef } from '../../plugin'; import { GoCdApi } from '../../api/gocdApi'; @@ -41,7 +41,7 @@ describe('GoCdArtifactsComponent', () => { }; const renderComponent = () => - renderWithEffects( + renderInTestApp( ', () => { it('renders with mandatory parameters', async () => { - const { getByText } = await render( + const { getByText } = await renderInTestApp( , ); expect(getByText('My title')).toBeInTheDocument(); }); it('renders skeleton when loading is true', async () => { - const { container } = await render( + const { container } = await renderInTestApp( , ); expect(container.querySelectorAll('li')).toHaveLength(8); @@ -36,7 +35,7 @@ describe('', () => { }); it('renders specified amount of items', async () => { - const { container } = await render( + const { container } = await renderInTestApp( ', () => { }); it('renders some items hidden', async () => { - const { container } = await render( + const { container } = await renderInTestApp( ', () => { }); it('renders all items when not collapsed', async () => { - const { container } = await render( + const { container } = await renderInTestApp( ', () => { }); it('renders visit with time-ago', async () => { - const { container, getByText } = await render( - - - , - , + const { container, getByText } = await renderInTestApp( + , ); expect(container.querySelectorAll('li')).toHaveLength(1); expect(getByText('Explore Backstage')).toBeInTheDocument(); @@ -102,23 +98,20 @@ describe('', () => { }); it('renders visit with hits', async () => { - const { container, getByText } = await render( - - - , - , + const { container, getByText } = await renderInTestApp( + , ); expect(container.querySelectorAll('li')).toHaveLength(1); expect(getByText('Explore Backstage')).toBeInTheDocument(); @@ -126,23 +119,20 @@ describe('', () => { }); it('renders text warning about few items', async () => { - const { getByText } = await render( - - - , - , + const { getByText } = await renderInTestApp( + , ); expect( getByText('The more pages you visit, the more pages will appear here.'), @@ -150,10 +140,8 @@ describe('', () => { }); it('renders text warning about no items', async () => { - const { getByText } = await render( - - , - , + const { getByText } = await renderInTestApp( + , ); expect(getByText('There are no visits to show yet.')).toBeInTheDocument(); }); diff --git a/plugins/kubernetes-react/src/components/Pods/FixDialog/FixDialog.test.tsx b/plugins/kubernetes-react/src/components/Pods/FixDialog/FixDialog.test.tsx index 68bfce9c96..7972f4401c 100644 --- a/plugins/kubernetes-react/src/components/Pods/FixDialog/FixDialog.test.tsx +++ b/plugins/kubernetes-react/src/components/Pods/FixDialog/FixDialog.test.tsx @@ -15,10 +15,9 @@ */ import React from 'react'; -import { render } from '@testing-library/react'; - import { FixDialog } from './FixDialog'; import { Pod } from 'kubernetes-models/v1/Pod'; +import { renderInTestApp } from '@backstage/test-utils'; jest.mock('../Events', () => ({ Events: () => { @@ -33,8 +32,8 @@ jest.mock('../PodLogs', () => ({ })); describe('FixDialog', () => { - it('docs link should render', () => { - const { getByText } = render( + it('docs link should render', async () => { + const { getByText } = await renderInTestApp( { expect(getByText('fix1')).toBeInTheDocument(); expect(getByText('fix2')).toBeInTheDocument(); }); - it('events button should render', () => { - const { getByText } = render( + it('events button should render', async () => { + const { getByText } = await renderInTestApp( { expect(getByText('fix1')).toBeInTheDocument(); expect(getByText('fix2')).toBeInTheDocument(); }); - it('Logs button should render', () => { - const { getByText } = render( + it('Logs button should render', async () => { + const { getByText } = await renderInTestApp( { @@ -46,7 +46,7 @@ const validResultWithTitle = { describe('TechDocsSearchResultListItem test', () => { it('should render search doc passed in', async () => { - const { findByText } = render( + const { findByText } = await renderInTestApp( , ); @@ -61,7 +61,7 @@ describe('TechDocsSearchResultListItem test', () => { }); it('should use title if defined', async () => { - const { findByText } = render( + const { findByText } = await renderInTestApp( { }); it('should use entity title if defined', async () => { - const { findByText } = render( + const { findByText } = await renderInTestApp( , ); diff --git a/plugins/todo/src/components/TodoList/TodoList.test.tsx b/plugins/todo/src/components/TodoList/TodoList.test.tsx index 15d4334610..872538c5b7 100644 --- a/plugins/todo/src/components/TodoList/TodoList.test.tsx +++ b/plugins/todo/src/components/TodoList/TodoList.test.tsx @@ -16,7 +16,7 @@ import { Entity } from '@backstage/catalog-model'; import { EntityProvider } from '@backstage/plugin-catalog-react'; -import { renderWithEffects, TestApiProvider } from '@backstage/test-utils'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; import React from 'react'; import { TodoApi, todoApiRef } from '../../api'; import { TodoList } from './TodoList'; @@ -43,7 +43,7 @@ describe('TodoList', () => { kind: 'MockKind', } as Entity; - const rendered = await renderWithEffects( + const rendered = await renderInTestApp( diff --git a/plugins/vault/src/components/EntityVaultCard/EntityVaultCard.test.tsx b/plugins/vault/src/components/EntityVaultCard/EntityVaultCard.test.tsx index ec62ea8757..ccf99bb259 100644 --- a/plugins/vault/src/components/EntityVaultCard/EntityVaultCard.test.tsx +++ b/plugins/vault/src/components/EntityVaultCard/EntityVaultCard.test.tsx @@ -16,9 +16,12 @@ import React from 'react'; import { setupServer } from 'msw/node'; -import { setupRequestMockHandlers } from '@backstage/test-utils'; +import { + renderInTestApp, + setupRequestMockHandlers, +} from '@backstage/test-utils'; import { ComponentEntity } from '@backstage/catalog-model'; -import { render, waitFor } from '@testing-library/react'; +import { waitFor } from '@testing-library/react'; import { EntityVaultCard } from './EntityVaultCard'; import { EntityProvider } from '@backstage/plugin-catalog-react'; @@ -40,7 +43,7 @@ describe('EntityVaultCard', () => { }; it('should render missing entity annotation', async () => { - const rendered = render( + const rendered = await renderInTestApp( , diff --git a/yarn.lock b/yarn.lock index 578ae66433..6ffc2bcb4a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6722,6 +6722,7 @@ __metadata: "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" "@backstage/integration": "workspace:^" + "@backstage/test-utils": "workspace:^" "@material-ui/core": ^4.12.2 "@material-ui/icons": ^4.9.1 "@material-ui/lab": 4.0.0-alpha.61