Merge pull request #25611 from RikClaessens/use-search-icon

Update the SearchBar to make use of the useApp hook to retrieve the search icon specified in the createApp call
This commit is contained in:
Fredrik Adelöw
2024-08-21 10:47:05 +02:00
committed by GitHub
9 changed files with 110 additions and 43 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/test-utils': minor
---
Added the icons option to the renderInTestApp function's TestAppOptions to be forwarded to the icons option of `createApp`.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-search-react': minor
---
Make use of the `useApp` hook to retrieve the specified search icon in the SearchBar
+5
View File
@@ -8,6 +8,7 @@ import { AnalyticsEvent } from '@backstage/core-plugin-api';
import { ApiHolder } from '@backstage/core-plugin-api';
import { ApiRef } from '@backstage/core-plugin-api';
import { AppComponents } from '@backstage/core-plugin-api';
import { AppIcons } from '@backstage/core-app-api';
import { AuthorizeResult } from '@backstage/plugin-permission-common';
import { ComponentType } from 'react';
import { Config } from '@backstage/config';
@@ -21,6 +22,7 @@ import { EvaluatePermissionRequest } from '@backstage/plugin-permission-common';
import { EvaluatePermissionResponse } from '@backstage/plugin-permission-common';
import { ExternalRouteRef } from '@backstage/core-plugin-api';
import { FetchApi } from '@backstage/core-plugin-api';
import { IconComponent } from '@backstage/core-plugin-api';
import { IdentityApi } from '@backstage/core-plugin-api';
import { JsonObject } from '@backstage/types';
import { JsonValue } from '@backstage/types';
@@ -252,6 +254,9 @@ export type TestAppOptions = {
[path: string]: RouteRef | ExternalRouteRef;
};
components?: Partial<AppComponents>;
icons?: Partial<AppIcons> & {
[key in string]: IconComponent;
};
};
// @public
@@ -23,13 +23,14 @@ import React, {
import { MemoryRouter, Route } from 'react-router-dom';
import { themes, UnifiedThemeProvider } from '@backstage/theme';
import MockIcon from '@material-ui/icons/AcUnit';
import { createSpecializedApp } from '@backstage/core-app-api';
import { AppIcons, createSpecializedApp } from '@backstage/core-app-api';
import {
AppComponents,
attachComponentData,
BootErrorPageProps,
createRouteRef,
ExternalRouteRef,
IconComponent,
RouteRef,
} from '@backstage/core-plugin-api';
import { MatcherFunction, RenderResult } from '@testing-library/react';
@@ -107,6 +108,13 @@ export type TestAppOptions = {
* Components to be forwarded to the `components` option of `createApp`.
*/
components?: Partial<AppComponents>;
/**
* Icons to be forwarded to the `icons` option of `createApp`.
*/
icons?: Partial<AppIcons> & {
[key in string]: IconComponent;
};
};
function isExternalRouteRef(
@@ -145,7 +153,10 @@ export function createTestAppWrapper(
),
...options.components,
},
icons: mockIcons,
icons: {
...mockIcons,
...options.icons,
},
plugins: [],
themes: [
{
@@ -19,7 +19,7 @@ import React, { ComponentType, PropsWithChildren } from 'react';
import Grid from '@material-ui/core/Grid';
import LabelIcon from '@material-ui/icons/Label';
import { TestApiProvider } from '@backstage/test-utils';
import { TestApiProvider, wrapInTestApp } from '@backstage/test-utils';
import { searchApiRef, MockSearchApi } from '../../api';
import { SearchContextProvider } from '../../context';
@@ -31,17 +31,18 @@ export default {
title: 'Plugins/Search/SearchAutocomplete',
component: SearchAutocomplete,
decorators: [
(Story: ComponentType<PropsWithChildren<{}>>) => (
<TestApiProvider apis={[[searchApiRef, new MockSearchApi()]]}>
<SearchContextProvider>
<Grid container direction="row">
<Grid item xs={12}>
<Story />
(Story: ComponentType<PropsWithChildren<{}>>) =>
wrapInTestApp(
<TestApiProvider apis={[[searchApiRef, new MockSearchApi()]]}>
<SearchContextProvider>
<Grid container direction="row">
<Grid item xs={12}>
<Story />
</Grid>
</Grid>
</Grid>
</SearchContextProvider>
</TestApiProvider>
),
</SearchContextProvider>
</TestApiProvider>,
),
],
};
@@ -22,7 +22,7 @@ import LabelIcon from '@material-ui/icons/Label';
import { configApiRef } from '@backstage/core-plugin-api';
import { ConfigReader } from '@backstage/core-app-api';
import { TestApiProvider, renderWithEffects } from '@backstage/test-utils';
import { TestApiProvider, renderInTestApp } from '@backstage/test-utils';
import { searchApiRef } from '../../api';
import { SearchAutocomplete } from './SearchAutocomplete';
@@ -44,7 +44,7 @@ describe('SearchAutocomplete', () => {
});
it('Renders without exploding', async () => {
await renderWithEffects(
await renderInTestApp(
<TestApiProvider
apis={[
[configApiRef, configApiMock],
@@ -59,7 +59,7 @@ describe('SearchAutocomplete', () => {
});
it('Show all options by default when focused', async () => {
await renderWithEffects(
await renderInTestApp(
<TestApiProvider
apis={[
[configApiRef, configApiMock],
@@ -84,7 +84,7 @@ describe('SearchAutocomplete', () => {
});
it('Updates context with the initial value', async () => {
await renderWithEffects(
await renderInTestApp(
<TestApiProvider
apis={[
[configApiRef, configApiMock],
@@ -106,7 +106,7 @@ describe('SearchAutocomplete', () => {
});
it('Updates context when value is cleared', async () => {
await renderWithEffects(
await renderInTestApp(
<TestApiProvider
apis={[
[configApiRef, configApiMock],
@@ -139,7 +139,7 @@ describe('SearchAutocomplete', () => {
});
it('Updates context when an option is select', async () => {
await renderWithEffects(
await renderInTestApp(
<TestApiProvider
apis={[
[configApiRef, configApiMock],
@@ -165,7 +165,7 @@ describe('SearchAutocomplete', () => {
});
it('Shows a circular progress when loading options', async () => {
await renderWithEffects(
await renderInTestApp(
<TestApiProvider
apis={[
[configApiRef, configApiMock],
@@ -184,7 +184,7 @@ describe('SearchAutocomplete', () => {
});
it('Uses the default search autocomplete option component', async () => {
await renderWithEffects(
await renderInTestApp(
<TestApiProvider
apis={[
[configApiRef, configApiMock],
@@ -18,7 +18,7 @@ import React, { ComponentType, PropsWithChildren } from 'react';
import Grid from '@material-ui/core/Grid';
import { makeStyles } from '@material-ui/core/styles';
import { TestApiProvider } from '@backstage/test-utils';
import { TestApiProvider, wrapInTestApp } from '@backstage/test-utils';
import { searchApiRef, MockSearchApi } from '../../api';
import { SearchContextProvider } from '../../context';
@@ -28,18 +28,22 @@ import { SearchBar } from './SearchBar';
export default {
title: 'Plugins/Search/SearchBar',
component: SearchBar,
loaders: [
async () => ({ component: (await import('./SearchBar')).SearchBar }),
],
decorators: [
(Story: ComponentType<PropsWithChildren<{}>>) => (
<TestApiProvider apis={[[searchApiRef, new MockSearchApi()]]}>
<SearchContextProvider>
<Grid container direction="row">
<Grid item xs={12}>
<Story />
(Story: ComponentType<PropsWithChildren<{}>>) =>
wrapInTestApp(
<TestApiProvider apis={[[searchApiRef, new MockSearchApi()]]}>
<SearchContextProvider>
<Grid container direction="row">
<Grid item xs={12}>
<Story />
</Grid>
</Grid>
</Grid>
</SearchContextProvider>
</TestApiProvider>
),
</SearchContextProvider>
</TestApiProvider>,
),
],
};
@@ -22,7 +22,7 @@ import { ConfigReader } from '@backstage/core-app-api';
import {
MockAnalyticsApi,
TestApiProvider,
renderWithEffects,
renderInTestApp,
} from '@backstage/test-utils';
import { searchApiRef } from '../../api';
import { SearchContextProvider } from '../../context';
@@ -61,7 +61,7 @@ describe('SearchBar', () => {
});
it('Renders without exploding', async () => {
await renderWithEffects(
await renderInTestApp(
<TestApiProvider
apis={[
[configApiRef, configApiMock],
@@ -82,7 +82,7 @@ describe('SearchBar', () => {
it('Renders with custom label', async () => {
const label = 'label';
await renderWithEffects(
await renderInTestApp(
<TestApiProvider
apis={[
[configApiRef, configApiMock],
@@ -103,7 +103,7 @@ describe('SearchBar', () => {
it('Renders with custom placeholder', async () => {
const placeholder = 'placeholder';
await renderWithEffects(
await renderInTestApp(
<TestApiProvider
apis={[
[configApiRef, configApiMock],
@@ -124,7 +124,7 @@ describe('SearchBar', () => {
it('Renders based on initial search', async () => {
const term = 'term';
await renderWithEffects(
await renderInTestApp(
<TestApiProvider
apis={[
[configApiRef, configApiMock],
@@ -145,7 +145,7 @@ describe('SearchBar', () => {
it('Updates term state when text is entered', async () => {
jest.useFakeTimers();
await renderWithEffects(
await renderInTestApp(
<TestApiProvider
apis={[
[configApiRef, configApiMock],
@@ -181,7 +181,7 @@ describe('SearchBar', () => {
it('Clear button clears term state', async () => {
const term = 'term';
await renderWithEffects(
await renderInTestApp(
<TestApiProvider
apis={[
[configApiRef, configApiMock],
@@ -210,7 +210,7 @@ describe('SearchBar', () => {
it('Should not show clear button', async () => {
const term = 'term';
await renderWithEffects(
await renderInTestApp(
<TestApiProvider
apis={[
[configApiRef, configApiMock],
@@ -233,7 +233,7 @@ describe('SearchBar', () => {
const debounceTime = 100;
await renderWithEffects(
await renderInTestApp(
<TestApiProvider
apis={[
[configApiRef, configApiMock],
@@ -275,7 +275,7 @@ describe('SearchBar', () => {
it('Does not capture analytics event if not enabled in app', async () => {
const analyticsApiMock = new MockAnalyticsApi();
await renderWithEffects(
await renderInTestApp(
<TestApiProvider
apis={[
[configApiRef, configApiMock],
@@ -298,4 +298,37 @@ describe('SearchBar', () => {
expect(analyticsApiMock.getEvents()).toHaveLength(0);
});
it('Renders custom search icon', async () => {
const CustomSearchIcon = () => (
<svg>
<path id="custom-search-icon" />
</svg>
);
await renderInTestApp(
<TestApiProvider
apis={[
[configApiRef, configApiMock],
[searchApiRef, searchApiMock],
]}
>
<SearchContextProvider>
<SearchBar />
</SearchContextProvider>
</TestApiProvider>,
{
icons: {
search: CustomSearchIcon,
},
},
);
const queryButton = screen.getByLabelText('Query');
expect(queryButton).toBeInTheDocument();
expect(queryButton.innerHTML).toContain(
'<svg><path id="custom-search-icon"></path></svg>',
);
});
});
@@ -18,13 +18,14 @@ import {
AnalyticsContext,
configApiRef,
useApi,
useApp,
} from '@backstage/core-plugin-api';
import IconButton from '@material-ui/core/IconButton';
import InputAdornment from '@material-ui/core/InputAdornment';
import TextField from '@material-ui/core/TextField';
import Button from '@material-ui/core/Button';
import { TextFieldProps } from '@material-ui/core/TextField';
import SearchIcon from '@material-ui/icons/Search';
import DefaultSearchIcon from '@material-ui/icons/Search';
import React, {
ChangeEvent,
ComponentType,
@@ -146,6 +147,8 @@ export const SearchBarBase: ForwardRefExoticComponent<SearchBarBaseProps> =
placeholder ??
`Search in ${configApi.getOptionalString('app.title') || 'Backstage'}`;
const SearchIcon = useApp().getSystemIcon('search') || DefaultSearchIcon;
const startAdornment = (
<InputAdornment position="start">
<IconButton aria-label="Query" size="small" disabled>