refactor: move configApi to SearchContext

Signed-off-by: Enrico Alvarenga <enricomalvarenga@gmail.com>
This commit is contained in:
Enrico Alvarenga
2023-08-02 10:44:04 -07:00
committed by Camila Belo
parent 2ddf648a01
commit 40de4a7210
5 changed files with 98 additions and 110 deletions
@@ -14,28 +14,32 @@
* limitations under the License.
*/
import { useApi } from '@backstage/core-plugin-api';
import { configApiRef } from '@backstage/core-plugin-api';
import { render, screen, waitFor } from '@testing-library/react';
import { act, renderHook } from '@testing-library/react-hooks';
import { MockConfigApi, TestApiProvider } from '@backstage/test-utils';
import React from 'react';
import {
SearchContextProvider,
useSearch,
useSearchContextCheck,
} from './SearchContext';
jest.mock('@backstage/core-plugin-api', () => ({
...jest.requireActual('@backstage/core-plugin-api'),
useApi: jest.fn(),
}));
import { searchApiRef } from '../api';
describe('SearchContext', () => {
const query = jest.fn();
const wrapper = ({ children, initialState }: any) => (
<SearchContextProvider initialState={initialState}>
{children}
</SearchContextProvider>
const wrapper = ({ children, initialState, config = {} }: any) => (
<TestApiProvider
apis={[
[configApiRef, new MockConfigApi(config)],
[searchApiRef, { query }],
]}
>
<SearchContextProvider initialState={initialState}>
{children}
</SearchContextProvider>
</TestApiProvider>
);
const initialState = {
@@ -46,7 +50,6 @@ describe('SearchContext', () => {
beforeEach(() => {
query.mockResolvedValue({});
(useApi as jest.Mock).mockReturnValue({ query: query });
});
afterAll(() => {
@@ -56,11 +59,7 @@ describe('SearchContext', () => {
it('Passes children', async () => {
const text = 'text';
render(
<SearchContextProvider initialState={initialState}>
{text}
</SearchContextProvider>,
);
render(wrapper({ children: text, initialState }));
await waitFor(() => {
expect(screen.getByText(text)).toBeInTheDocument();
@@ -95,17 +94,61 @@ describe('SearchContext', () => {
expect(result.current).toEqual(true);
});
it('Uses initial state values', async () => {
const { result, waitForNextUpdate } = renderHook(() => useSearch(), {
wrapper,
initialProps: {
initialState,
},
describe('Uses initial state values', () => {
it('Uses default initial state values', async () => {
const { result, waitForNextUpdate } = renderHook(() => useSearch(), {
wrapper,
});
await waitForNextUpdate();
expect(result.current).toEqual(
expect.objectContaining({
term: '',
types: [],
filters: {},
pageLimit: undefined,
pageCursor: undefined,
}),
);
});
await waitForNextUpdate();
it('Uses provided initial state values', async () => {
const { result, waitForNextUpdate } = renderHook(() => useSearch(), {
wrapper,
initialProps: {
initialState,
},
});
expect(result.current).toEqual(expect.objectContaining(initialState));
await waitForNextUpdate();
expect(result.current).toEqual(expect.objectContaining(initialState));
});
it('Uses page limit provided via config api', async () => {
const { result, waitForNextUpdate } = renderHook(() => useSearch(), {
wrapper,
initialProps: {
initialState,
config: {
app: {
search: {
query: {
pageLimit: 100,
},
},
},
},
},
});
await waitForNextUpdate();
expect(result.current).toEqual(
expect.objectContaining({ ...initialState, pageLimit: 100 }),
);
});
});
describe('Resets cursor', () => {
@@ -30,7 +30,11 @@ import {
createVersionedValueMap,
} from '@backstage/version-bridge';
import { JsonObject } from '@backstage/types';
import { AnalyticsContext, useApi } from '@backstage/core-plugin-api';
import {
AnalyticsContext,
useApi,
configApiRef,
} from '@backstage/core-plugin-api';
import { SearchResultSet } from '@backstage/plugin-search-common';
import { searchApiRef } from '../api';
@@ -95,25 +99,16 @@ export const useSearchContextCheck = () => {
};
/**
* @public
* The initial state of `SearchContextProvider`.
*
* Constructs an object representing the initial state default
* configuration for the SearchPage component
*/
export const getSearchContextInitialStateDefaults = (): SearchContextState => ({
export const searchInitialState = {
term: '',
types: [],
filters: {},
pageLimit: undefined,
pageCursor: undefined,
});
/**
* The initial state of `SearchContextProvider`.
*
*/
const searchInitialState: SearchContextState =
getSearchContextInitialStateDefaults();
};
const useSearchContextValue = (
initialValue: SearchContextState = searchInitialState,
@@ -248,10 +243,20 @@ export const SearchContextProvider = (props: SearchContextProviderProps) => {
const { initialState, inheritParentContextIfAvailable, children } = props;
const hasParentContext = useSearchContextCheck();
const configApi = useApi(configApiRef);
const searchContextInitialState = {
...searchInitialState,
...(initialState || {}),
pageLimit:
configApi.getOptionalNumber('app.search.query.pageLimit') ||
initialState?.pageLimit,
};
return hasParentContext && inheritParentContextIfAvailable ? (
<>{children}</>
) : (
<LocalSearchContext initialState={initialState}>
<LocalSearchContext initialState={searchContextInitialState}>
{children}
</LocalSearchContext>
);
+4 -4
View File
@@ -21,12 +21,12 @@ export interface Config {
*/
search?: {
/**
* An object representing the initial state configuration for the SearchPage
* component. By configuring and modifying the values of the initialState
* object attributes, you can customize the initial state of the search component
* An object representing the default search query configuration.
* By configuring and modifying the values of this object,
* you can customize the default values of the search component
* and define how it behaves when it is first loaded or reset.
*/
initialState?: {
query?: {
/**
* A number indicating the maximum number of results to be returned
* per page during pagination.
@@ -14,41 +14,18 @@
* limitations under the License.
*/
import {
MockConfigApi,
TestApiProvider,
renderInTestApp,
} from '@backstage/test-utils';
import { renderInTestApp } from '@backstage/test-utils';
import React from 'react';
import { useLocation } from 'react-router-dom';
import {
type SearchContextState,
useSearch,
getSearchContextInitialStateDefaults,
} from '@backstage/plugin-search-react';
import { useSearch } from '@backstage/plugin-search-react';
import { SearchPage } from './SearchPage';
import { configApiRef } from '@backstage/core-plugin-api';
import type { JsonObject } from '@backstage/types';
const TestingContext = React.createContext<SearchContextState>(
getSearchContextInitialStateDefaults(),
);
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useLocation: jest.fn().mockReturnValue({
search: '',
}),
useOutlet: jest.fn().mockImplementation(() => (
<TestingContext.Consumer>
{value => (
<>
<h1>Route Children</h1>
<div>Page Limit: {value.pageLimit} </div>
</>
)}
</TestingContext.Consumer>
)),
useOutlet: jest.fn().mockReturnValue('Route Children'),
}));
const setTermMock = jest.fn();
@@ -60,11 +37,7 @@ jest.mock('@backstage/plugin-search-react', () => ({
...jest.requireActual('@backstage/plugin-search-react'),
SearchContextProvider: jest
.fn()
.mockImplementation(({ initialState, children }) => (
<TestingContext.Provider value={initialState}>
{children}
</TestingContext.Provider>
)),
.mockImplementation(({ children }) => children),
useSearch: jest.fn().mockReturnValue({
term: '',
setTerm: (term: any) => setTermMock(term),
@@ -77,13 +50,6 @@ jest.mock('@backstage/plugin-search-react', () => ({
}),
}));
const renderSearchPageWithConfig = (config: JsonObject = {}) =>
renderInTestApp(
<TestApiProvider apis={[[configApiRef, new MockConfigApi(config)]]}>
<SearchPage />
</TestApiProvider>,
);
describe('SearchPage', () => {
const origReplaceState = window.history.replaceState;
@@ -110,7 +76,7 @@ describe('SearchPage', () => {
});
// When we render the page...
await renderSearchPageWithConfig();
await renderInTestApp(<SearchPage />);
// Then search context should be set with these values...
expect(setTermMock).toHaveBeenCalledWith(expectedTerm);
@@ -120,7 +86,7 @@ describe('SearchPage', () => {
});
it('renders provided router element', async () => {
const { getByText } = await renderSearchPageWithConfig();
const { getByText } = await renderInTestApp(<SearchPage />);
expect(getByText('Route Children')).toBeInTheDocument();
});
@@ -140,27 +106,9 @@ describe('SearchPage', () => {
'?query=bieber&types[]=software-catalog&pageCursor=SOMEPAGE&filters[anyKey]=anyValue',
);
await renderSearchPageWithConfig();
await renderInTestApp(<SearchPage />);
const calls = (window.history.replaceState as jest.Mock).mock.calls[0];
expect(calls[2]).toContain(expectedLocation);
});
it('initializes the SearchPage with a custom page limit', async () => {
const { getByText } = await renderSearchPageWithConfig({
app: {
search: {
initialState: {
pageLimit: 50,
},
},
},
});
const calls = (window.history.replaceState as jest.Mock).mock.calls[0];
const expectedLocation = encodeURI('?query=&pageCursor=');
expect(calls[2]).toContain(expectedLocation);
expect(getByText('Page Limit: 50')).toBeInTheDocument();
});
});
@@ -21,10 +21,8 @@ import { useLocation, useOutlet } from 'react-router-dom';
import {
SearchContextProvider,
useSearch,
getSearchContextInitialStateDefaults,
} from '@backstage/plugin-search-react';
import { JsonObject } from '@backstage/types';
import { configApiRef, useApi } from '@backstage/core-plugin-api';
export const UrlUpdater = () => {
const location = useLocation();
@@ -93,15 +91,9 @@ export const UrlUpdater = () => {
*/
export const SearchPage = () => {
const outlet = useOutlet();
const configApi = useApi(configApiRef);
const initialState = {
...getSearchContextInitialStateDefaults(),
pageLimit: configApi.getOptionalNumber('app.search.initialState.pageLimit'),
};
return (
<SearchContextProvider initialState={initialState}>
<SearchContextProvider>
<UrlUpdater />
{outlet}
</SearchContextProvider>