Merge branch 'master' into feat/customSearchModal

This commit is contained in:
Emma Indal
2022-04-12 13:07:13 +02:00
committed by GitHub
218 changed files with 6456 additions and 1533 deletions
+5 -4
View File
@@ -83,7 +83,7 @@ export const Router: () => JSX.Element;
// Warning: (ae-missing-release-tag) "SearchApi" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
// @public @deprecated (undocumented)
export interface SearchApi {
// (undocumented)
query(query: SearchQuery): Promise<SearchResultSet>;
@@ -91,7 +91,7 @@ export interface SearchApi {
// Warning: (ae-missing-release-tag) "searchApiRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
// @public @deprecated (undocumented)
export const searchApiRef: ApiRef<SearchApi>;
// @public (undocumented)
@@ -140,7 +140,7 @@ export type SearchBarProps = Partial<SearchBarBaseProps>;
// Warning: (ae-missing-release-tag) "SearchContextProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
// @public @deprecated (undocumented)
export const SearchContextProvider: ({
initialState,
children,
@@ -330,10 +330,11 @@ export type SidebarSearchProps = {
icon?: IconComponent;
};
// Warning: (tsdoc-at-sign-in-word) The "@" character looks like part of a TSDoc tag; use a backslash to escape it
// Warning: (ae-forgotten-export) The symbol "SearchContextValue" needs to be exported by the entry point index.d.ts
// Warning: (ae-missing-release-tag) "useSearch" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
// @public @deprecated (undocumented)
export const useSearch: () => SearchContextValue;
// @public
+1
View File
@@ -39,6 +39,7 @@
"@backstage/core-plugin-api": "^1.0.0",
"@backstage/errors": "^1.0.0",
"@backstage/plugin-catalog-react": "^1.0.1-next.1",
"@backstage/plugin-search-react": "^0.0.0",
"@backstage/plugin-search-common": "^0.3.3-next.1",
"@backstage/theme": "^0.2.15",
"@backstage/types": "^1.0.0",
+7
View File
@@ -21,12 +21,19 @@ import {
} from '@backstage/core-plugin-api';
import { ResponseError } from '@backstage/errors';
import { SearchQuery, SearchResultSet } from '@backstage/plugin-search-common';
import qs from 'qs';
/**
* @deprecated import from `@backstage/plugin-search-react` instead
*/
export const searchApiRef = createApiRef<SearchApi>({
id: 'plugin.search.queryservice',
});
/**
* @deprecated import from `@backstage/plugin-search-react` instead
*/
export interface SearchApi {
query(query: SearchQuery): Promise<SearchResultSet>;
}
@@ -16,7 +16,7 @@
import { Grid, makeStyles, Paper } from '@material-ui/core';
import React, { ComponentType } from 'react';
import { SearchContextProvider } from '../SearchContext/SearchContextForStorybook.stories';
import { SearchContextProviderForStorybook } from '@backstage/plugin-search-react';
import { SearchBar } from './SearchBar';
export default {
@@ -24,13 +24,13 @@ export default {
component: SearchBar,
decorators: [
(Story: ComponentType<{}>) => (
<SearchContextProvider>
<SearchContextProviderForStorybook>
<Grid container direction="row">
<Grid item xs={12}>
<Story />
</Grid>
</Grid>
</SearchContextProvider>
</SearchContextProviderForStorybook>
),
],
};
@@ -1,287 +0,0 @@
/*
* Copyright 2021 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 { useApi } from '@backstage/core-plugin-api';
import { render, screen, waitFor } from '@testing-library/react';
import { act, renderHook } from '@testing-library/react-hooks';
import React from 'react';
import { SearchContextProvider, useSearch } from './SearchContext';
jest.mock('@backstage/core-plugin-api', () => ({
...jest.requireActual('@backstage/core-plugin-api'),
useApi: jest.fn(),
}));
describe('SearchContext', () => {
const query = jest.fn();
const wrapper = ({ children, initialState }: any) => (
<SearchContextProvider initialState={initialState}>
{children}
</SearchContextProvider>
);
const initialState = {
term: '',
filters: {},
types: ['*'],
};
beforeEach(() => {
query.mockResolvedValue({});
(useApi as jest.Mock).mockReturnValue({ query: query });
});
afterAll(() => {
jest.resetAllMocks();
});
it('Passes children', async () => {
const text = 'text';
render(
<SearchContextProvider initialState={initialState}>
{text}
</SearchContextProvider>,
);
await waitFor(() => {
expect(screen.getByText(text)).toBeInTheDocument();
});
});
it('Throws error when no context is set', () => {
const { result } = renderHook(() => useSearch());
expect(result.error).toEqual(
Error('useSearch must be used within a SearchContextProvider'),
);
});
it('Uses initial state values', async () => {
const { result, waitForNextUpdate } = renderHook(() => useSearch(), {
wrapper,
initialProps: {
initialState,
},
});
await waitForNextUpdate();
expect(result.current).toEqual(expect.objectContaining(initialState));
});
it('Resets cursor when term is set (and different from previous)', async () => {
const { result, waitForNextUpdate } = renderHook(() => useSearch(), {
wrapper,
initialProps: {
initialState: {
...initialState,
pageCursor: 'SOMEPAGE',
},
},
});
await waitForNextUpdate();
expect(result.current.pageCursor).toEqual('SOMEPAGE');
act(() => {
result.current.setTerm('first term');
});
act(() => {
result.current.setPageCursor('OTHERPAGE');
});
await waitForNextUpdate();
expect(result.current.pageCursor).toEqual('OTHERPAGE');
act(() => {
result.current.setTerm('second term');
});
await waitForNextUpdate();
expect(result.current.pageCursor).toEqual(undefined);
});
describe('Performs search (and sets results)', () => {
it('When term is set', async () => {
const { result, waitForNextUpdate } = renderHook(() => useSearch(), {
wrapper,
initialProps: {
initialState,
},
});
await waitForNextUpdate();
const term = 'term';
act(() => {
result.current.setTerm(term);
});
await waitForNextUpdate();
expect(query).toHaveBeenLastCalledWith({
filters: {},
types: ['*'],
term,
});
});
it('When filters are set', async () => {
const { result, waitForNextUpdate } = renderHook(() => useSearch(), {
wrapper,
initialProps: {
initialState,
},
});
await waitForNextUpdate();
const filters = { filter: 'filter' };
act(() => {
result.current.setFilters(filters);
});
await waitForNextUpdate();
expect(query).toHaveBeenLastCalledWith({
filters,
types: ['*'],
term: '',
});
});
it('When page is set', async () => {
const { result, waitForNextUpdate } = renderHook(() => useSearch(), {
wrapper,
initialProps: {
initialState,
},
});
await waitForNextUpdate();
act(() => {
result.current.setPageCursor('SOMEPAGE');
});
await waitForNextUpdate();
expect(query).toHaveBeenLastCalledWith({
filters: {},
types: ['*'],
pageCursor: 'SOMEPAGE',
term: '',
});
});
it('When types is set', async () => {
const { result, waitForNextUpdate } = renderHook(() => useSearch(), {
wrapper,
initialProps: {
initialState,
},
});
await waitForNextUpdate();
const types = ['type'];
act(() => {
result.current.setTypes(types);
});
await waitForNextUpdate();
expect(query).toHaveBeenLastCalledWith({
types,
filters: {},
term: '',
});
});
it('provides function for fetch the next page', async () => {
query.mockResolvedValue({
results: [],
nextPageCursor: 'NEXT',
});
const { result, waitForNextUpdate } = renderHook(() => useSearch(), {
wrapper,
initialProps: {
initialState,
},
});
await waitForNextUpdate();
expect(result.current.fetchNextPage).toBeDefined();
expect(result.current.fetchPreviousPage).toBeUndefined();
act(() => {
result.current.fetchNextPage!();
});
await waitForNextUpdate();
expect(query).toHaveBeenLastCalledWith({
types: ['*'],
filters: {},
term: '',
pageCursor: 'NEXT',
});
});
it('provides function for fetch the previous page', async () => {
query.mockResolvedValue({
results: [],
previousPageCursor: 'PREVIOUS',
});
const { result, waitForNextUpdate } = renderHook(() => useSearch(), {
wrapper,
initialProps: {
initialState,
},
});
await waitForNextUpdate();
expect(result.current.fetchNextPage).toBeUndefined();
expect(result.current.fetchPreviousPage).toBeDefined();
act(() => {
result.current.fetchPreviousPage!();
});
await waitForNextUpdate();
expect(query).toHaveBeenLastCalledWith({
types: ['*'],
filters: {},
term: '',
pageCursor: 'PREVIOUS',
});
});
});
});
@@ -51,6 +51,9 @@ export type SearchContextState = {
pageCursor?: string;
};
/**
* @deprecated import from `@backstage/plugin-search-react` instead
*/
export const SearchContext = createContext<SearchContextValue | undefined>(
undefined,
);
@@ -62,6 +65,9 @@ const searchInitialState: SearchContextState = {
types: [],
};
/**
* @deprecated import from `@backstage/plugin-search-react` instead
*/
export const SearchContextProvider = ({
initialState = searchInitialState,
children,
@@ -126,6 +132,9 @@ export const SearchContextProvider = ({
);
};
/**
* @deprecated import from "@backstage/plugin-search-react" instead
*/
export const useSearch = () => {
const context = useContext(SearchContext);
if (context === undefined) {
@@ -1,48 +0,0 @@
/*
* Copyright 2021 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 { ApiProvider } from '@backstage/core-app-api';
import { SearchResultSet } from '@backstage/plugin-search-common';
import { TestApiRegistry } from '@backstage/test-utils';
import React, { ComponentProps, PropsWithChildren } from 'react';
import { searchApiRef } from '../../apis';
import { SearchContextProvider as RealSearchContextProvider } from './SearchContext';
type QueryResultProps = {
mockedResults?: SearchResultSet;
};
/**
* Utility context provider only for use in Storybook stories. You should use
* the real `<SearchContextProvider>` exported by `@backstage/plugin-search` in
* your app instead of this! In some cases (like the search page) it may
* already be provided on your behalf.
*/
export const SearchContextProvider = (
props: ComponentProps<typeof RealSearchContextProvider> & QueryResultProps,
) => {
return (
<SearchApiProvider {...props}>
<RealSearchContextProvider children={props.children} />
</SearchApiProvider>
);
};
export function SearchApiProvider(props: PropsWithChildren<QueryResultProps>) {
const { mockedResults, children } = props;
const query: any = () => Promise.resolve(mockedResults || {});
const apiRegistry = TestApiRegistry.from([searchApiRef, { query }]);
return <ApiProvider apis={apiRegistry} children={children} />;
}
@@ -16,7 +16,7 @@
import { Grid, Paper } from '@material-ui/core';
import React, { ComponentType } from 'react';
import { SearchContextProvider } from '../SearchContext/SearchContextForStorybook.stories';
import { SearchContextProviderForStorybook } from '@backstage/plugin-search-react';
import { SearchFilter } from './SearchFilter';
export default {
@@ -24,13 +24,13 @@ export default {
component: SearchFilter,
decorators: [
(Story: ComponentType<{}>) => (
<SearchContextProvider>
<SearchContextProviderForStorybook>
<Grid container direction="row">
<Grid item xs={4}>
<Story />
</Grid>
</Grid>
</SearchContextProvider>
</SearchContextProviderForStorybook>
),
],
};
@@ -29,7 +29,7 @@ import React, { ComponentType } from 'react';
import { rootRouteRef } from '../../plugin';
import { DefaultResultListItem } from '../DefaultResultListItem';
import { SearchBar } from '../SearchBar';
import { SearchApiProvider } from '../SearchContext/SearchContextForStorybook.stories';
import { SearchApiProviderForStorybook } from '@backstage/plugin-search-react';
import { SearchModal } from './SearchModal';
import { SearchResult } from '../SearchResult';
import { SearchResultPager } from '../SearchResultPager';
@@ -71,9 +71,9 @@ export default {
decorators: [
(Story: ComponentType<{}>) =>
wrapInTestApp(
<SearchApiProvider mockedResults={mockResults}>
<SearchApiProviderForStorybook mockedResults={mockResults}>
<Story />
</SearchApiProvider>,
</SearchApiProviderForStorybook>,
{ mountedRoutes: { '/search': rootRouteRef } },
),
],
@@ -19,7 +19,8 @@ import { List, ListItem } from '@material-ui/core';
import React, { ComponentType } from 'react';
import { MemoryRouter } from 'react-router';
import { DefaultResultListItem } from '../DefaultResultListItem';
import { SearchContextProvider } from '../SearchContext/SearchContextForStorybook.stories';
import { SearchContextProviderForStorybook } from '@backstage/plugin-search-react';
import { SearchResult } from './SearchResult';
const mockResults = {
@@ -57,9 +58,9 @@ export default {
decorators: [
(Story: ComponentType<{}>) => (
<MemoryRouter>
<SearchContextProvider mockedResults={mockResults}>
<SearchContextProviderForStorybook mockedResults={mockResults}>
<Story />
</SearchContextProvider>
</SearchContextProviderForStorybook>
</MemoryRouter>
),
],
@@ -19,7 +19,7 @@ import CatalogIcon from '@material-ui/icons/MenuBook';
import DocsIcon from '@material-ui/icons/Description';
import UsersGroupsIcon from '@material-ui/icons/Person';
import React, { ComponentType } from 'react';
import { SearchContextProvider } from '../SearchContext/SearchContextForStorybook.stories';
import { SearchContextProviderForStorybook } from '@backstage/plugin-search-react';
import { SearchType } from './SearchType';
export default {
@@ -27,13 +27,13 @@ export default {
component: SearchType,
decorators: [
(Story: ComponentType<{}>) => (
<SearchContextProvider>
<SearchContextProviderForStorybook>
<Grid container direction="row">
<Grid item xs={4}>
<Story />
</Grid>
</Grid>
</SearchContextProvider>
</SearchContextProviderForStorybook>
),
],
};