From 01abb3bc7e4ee0a7d1b806fc63662a1ad6106688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20N=C3=A4sman?= Date: Wed, 1 Jun 2022 16:37:19 +0200 Subject: [PATCH] Move SearchResult from @backstage/plugin-search to @backstage/plugin-search-react and deprecate it in @backstage/plugin-search MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Anders Näsman --- plugins/search-react/api-report.md | 9 ++ plugins/search-react/package.json | 7 +- .../SearchResult/SearchResult.stories.tsx | 11 +- .../SearchResult/SearchResult.test.tsx | 125 ++++++++++++++++++ .../components/SearchResult/SearchResult.tsx | 60 +++++++++ .../src/components/SearchResult/index.tsx | 18 +++ plugins/search-react/src/components/index.ts | 1 + .../components/SearchResult/SearchResult.tsx | 3 + 8 files changed, 225 insertions(+), 9 deletions(-) rename plugins/{search => search-react}/src/components/SearchResult/SearchResult.stories.tsx (92%) create mode 100644 plugins/search-react/src/components/SearchResult/SearchResult.test.tsx create mode 100644 plugins/search-react/src/components/SearchResult/SearchResult.tsx create mode 100644 plugins/search-react/src/components/SearchResult/index.tsx diff --git a/plugins/search-react/api-report.md b/plugins/search-react/api-report.md index 00325d6f64..09dd3d77a8 100644 --- a/plugins/search-react/api-report.md +++ b/plugins/search-react/api-report.md @@ -12,6 +12,7 @@ import { PropsWithChildren } from 'react'; import { default as React_2 } from 'react'; import { ReactElement } from 'react'; import { SearchQuery } from '@backstage/plugin-search-common'; +import { SearchResult as SearchResult_2 } from '@backstage/plugin-search-common'; import { SearchResultSet } from '@backstage/plugin-search-common'; // @public (undocumented) @@ -120,6 +121,14 @@ export type SearchFilterWrapperProps = SearchFilterComponentProps & { debug?: boolean; }; +// @public (undocumented) +export const SearchResult: ({ children }: SearchResultProps) => JSX.Element; + +// @public (undocumented) +export type SearchResultProps = { + children: (results: { results: SearchResult_2[] }) => JSX.Element; +}; + // @public (undocumented) export const SelectFilter: (props: SearchFilterComponentProps) => JSX.Element; diff --git a/plugins/search-react/package.json b/plugins/search-react/package.json index ac8a43dee4..04932bd5ea 100644 --- a/plugins/search-react/package.json +++ b/plugins/search-react/package.json @@ -31,13 +31,16 @@ "start": "backstage-cli package start" }, "dependencies": { + "@backstage/plugin-search": "0.8.2-next.1", "@backstage/plugin-search-common": "^0.3.5-next.0", + "@backstage/core-components": "^0.9.5-next.1", "@backstage/core-plugin-api": "^1.0.3-next.0", "@backstage/version-bridge": "^1.0.1", - "react-use": "^17.3.2", "@backstage/types": "^1.0.0", "@material-ui/core": "^4.12.2", - "@material-ui/lab": "4.0.0-alpha.57" + "@material-ui/lab": "4.0.0-alpha.57", + "react-router": "6.0.0-beta.0", + "react-use": "^17.3.2" }, "peerDependencies": { "@types/react": "^16.13.1 || ^17.0.0", diff --git a/plugins/search/src/components/SearchResult/SearchResult.stories.tsx b/plugins/search-react/src/components/SearchResult/SearchResult.stories.tsx similarity index 92% rename from plugins/search/src/components/SearchResult/SearchResult.stories.tsx rename to plugins/search-react/src/components/SearchResult/SearchResult.stories.tsx index 207f1ae1d7..38be586f97 100644 --- a/plugins/search/src/components/SearchResult/SearchResult.stories.tsx +++ b/plugins/search-react/src/components/SearchResult/SearchResult.stories.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2021 The Backstage Authors + * Copyright 2022 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. @@ -18,13 +18,10 @@ import { Link } from '@backstage/core-components'; import { List, ListItem } from '@material-ui/core'; import React, { ComponentType } from 'react'; import { MemoryRouter } from 'react-router'; -import { DefaultResultListItem } from '../DefaultResultListItem'; +import { DefaultResultListItem } from '@backstage/plugin-search'; -import { - searchApiRef, - MockSearchApi, - SearchContextProvider, -} from '@backstage/plugin-search-react'; +import { searchApiRef, MockSearchApi } from '../../api'; +import { SearchContextProvider } from '../../context'; import { SearchResult } from './SearchResult'; import { TestApiProvider } from '@backstage/test-utils'; diff --git a/plugins/search-react/src/components/SearchResult/SearchResult.test.tsx b/plugins/search-react/src/components/SearchResult/SearchResult.test.tsx new file mode 100644 index 0000000000..10437b3af7 --- /dev/null +++ b/plugins/search-react/src/components/SearchResult/SearchResult.test.tsx @@ -0,0 +1,125 @@ +/* + * Copyright 2022 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 { renderInTestApp } from '@backstage/test-utils'; +import { waitFor } from '@testing-library/react'; +import React from 'react'; +import { useSearch } from '../../context'; +import { SearchResult } from './SearchResult'; + +jest.mock('../../context', () => ({ + ...jest.requireActual('../../context'), + useSearch: jest.fn().mockReturnValue({ + result: {}, + }), +})); + +describe('SearchResult', () => { + it('Progress rendered on Loading state', async () => { + (useSearch as jest.Mock).mockReturnValueOnce({ + result: { loading: true }, + }); + + const { getByRole } = await renderInTestApp( + {() => <>}, + ); + + await waitFor(() => { + expect(getByRole('progressbar')).toBeInTheDocument(); + }); + }); + + it('Alert rendered on Error state', async () => { + const error = new Error('some error'); + (useSearch as jest.Mock).mockReturnValueOnce({ + result: { loading: false, error }, + }); + + const { getByRole } = await renderInTestApp( + {() => <>}, + ); + + await waitFor(() => { + expect(getByRole('alert')).toHaveTextContent( + new RegExp(`Error encountered while fetching search results.*${error}`), + ); + }); + }); + + it('On no result value state', async () => { + (useSearch as jest.Mock).mockReturnValueOnce({ + result: { loading: false, error: '', value: undefined }, + }); + + const { getByRole } = await renderInTestApp( + {() => <>}, + ); + + await waitFor(() => { + expect( + getByRole('heading', { name: 'Sorry, no results were found' }), + ).toBeInTheDocument(); + }); + }); + + it('On empty result value state', async () => { + (useSearch as jest.Mock).mockReturnValueOnce({ + result: { loading: false, error: '', value: { results: [] } }, + }); + + const { getByRole } = await renderInTestApp( + {() => <>}, + ); + + await waitFor(() => { + expect( + getByRole('heading', { name: 'Sorry, no results were found' }), + ).toBeInTheDocument(); + }); + }); + + it('Calls children with results set to result.value', async () => { + (useSearch as jest.Mock).mockReturnValueOnce({ + result: { + loading: false, + error: '', + value: { + totalCount: 1, + results: [ + { + type: 'some-type', + document: { + title: 'some-title', + text: 'some-text', + location: 'some-location', + }, + }, + ], + }, + }, + }); + + const { getByText } = await renderInTestApp( + + {({ results }) => { + return <>Results {results.length}; + }} + , + ); + + expect(getByText('Results 1')).toBeInTheDocument(); + }); +}); diff --git a/plugins/search-react/src/components/SearchResult/SearchResult.tsx b/plugins/search-react/src/components/SearchResult/SearchResult.tsx new file mode 100644 index 0000000000..a1ce3cea25 --- /dev/null +++ b/plugins/search-react/src/components/SearchResult/SearchResult.tsx @@ -0,0 +1,60 @@ +/* + * Copyright 2022 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 { + EmptyState, + Progress, + ResponseErrorPanel, +} from '@backstage/core-components'; +import { SearchResult } from '@backstage/plugin-search-common'; +import React from 'react'; +import { useSearch } from '../../context'; + +/** + * @public + */ +export type SearchResultProps = { + children: (results: { results: SearchResult[] }) => JSX.Element; +}; + +/** + * @public + */ +export const SearchResultComponent = ({ children }: SearchResultProps) => { + const { + result: { loading, error, value }, + } = useSearch(); + + if (loading) { + return ; + } + if (error) { + return ( + + ); + } + + if (!value?.results.length) { + return ; + } + + return <>{children({ results: value.results })}; +}; + +export { SearchResultComponent as SearchResult }; diff --git a/plugins/search-react/src/components/SearchResult/index.tsx b/plugins/search-react/src/components/SearchResult/index.tsx new file mode 100644 index 0000000000..2032fbbed9 --- /dev/null +++ b/plugins/search-react/src/components/SearchResult/index.tsx @@ -0,0 +1,18 @@ +/* + * Copyright 2022 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 { SearchResult } from './SearchResult'; +export type { SearchResultProps } from './SearchResult'; diff --git a/plugins/search-react/src/components/index.ts b/plugins/search-react/src/components/index.ts index 8e45c66fce..db9214d299 100644 --- a/plugins/search-react/src/components/index.ts +++ b/plugins/search-react/src/components/index.ts @@ -16,3 +16,4 @@ export * from './HighlightedSearchResultText'; export * from './SearchFilter'; +export * from './SearchResult'; diff --git a/plugins/search/src/components/SearchResult/SearchResult.tsx b/plugins/search/src/components/SearchResult/SearchResult.tsx index a62dddb924..77ee883164 100644 --- a/plugins/search/src/components/SearchResult/SearchResult.tsx +++ b/plugins/search/src/components/SearchResult/SearchResult.tsx @@ -27,6 +27,9 @@ type Props = { children: (results: { results: SearchResult[] }) => JSX.Element; }; +/** + * @deprecated Moved to `@backstage/plugin-search-react`. + */ export const SearchResultComponent = ({ children }: Props) => { const { result: { loading, error, value },