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 },