From 40de4a7210666555e60f2a33813499eb3bd586ea Mon Sep 17 00:00:00 2001 From: Enrico Alvarenga Date: Wed, 2 Aug 2023 10:44:04 -0700 Subject: [PATCH] refactor: move configApi to SearchContext Signed-off-by: Enrico Alvarenga --- .../src/context/SearchContext.test.tsx | 91 ++++++++++++++----- .../src/context/SearchContext.tsx | 33 ++++--- plugins/search/config.d.ts | 8 +- .../components/SearchPage/SearchPage.test.tsx | 66 ++------------ .../src/components/SearchPage/SearchPage.tsx | 10 +- 5 files changed, 98 insertions(+), 110 deletions(-) diff --git a/plugins/search-react/src/context/SearchContext.test.tsx b/plugins/search-react/src/context/SearchContext.test.tsx index edee2f153f..139a1b2939 100644 --- a/plugins/search-react/src/context/SearchContext.test.tsx +++ b/plugins/search-react/src/context/SearchContext.test.tsx @@ -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) => ( - - {children} - + const wrapper = ({ children, initialState, config = {} }: any) => ( + + + {children} + + ); 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( - - {text} - , - ); + 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', () => { diff --git a/plugins/search-react/src/context/SearchContext.tsx b/plugins/search-react/src/context/SearchContext.tsx index 0a4bdf283b..ea041d6bc1 100644 --- a/plugins/search-react/src/context/SearchContext.tsx +++ b/plugins/search-react/src/context/SearchContext.tsx @@ -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} ) : ( - + {children} ); diff --git a/plugins/search/config.d.ts b/plugins/search/config.d.ts index ffbbd7636b..7ec00425d3 100644 --- a/plugins/search/config.d.ts +++ b/plugins/search/config.d.ts @@ -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. diff --git a/plugins/search/src/components/SearchPage/SearchPage.test.tsx b/plugins/search/src/components/SearchPage/SearchPage.test.tsx index f364cb8dd4..be9e7f7b10 100644 --- a/plugins/search/src/components/SearchPage/SearchPage.test.tsx +++ b/plugins/search/src/components/SearchPage/SearchPage.test.tsx @@ -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( - getSearchContextInitialStateDefaults(), -); jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), useLocation: jest.fn().mockReturnValue({ search: '', }), - useOutlet: jest.fn().mockImplementation(() => ( - - {value => ( - <> -

Route Children

-
Page Limit: {value.pageLimit}
- - )} -
- )), + 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 }) => ( - - {children} - - )), + .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( - - - , - ); - describe('SearchPage', () => { const origReplaceState = window.history.replaceState; @@ -110,7 +76,7 @@ describe('SearchPage', () => { }); // When we render the page... - await renderSearchPageWithConfig(); + await renderInTestApp(); // 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(); 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(); 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(); - }); }); diff --git a/plugins/search/src/components/SearchPage/SearchPage.tsx b/plugins/search/src/components/SearchPage/SearchPage.tsx index edac4d9b2f..3ee444bd32 100644 --- a/plugins/search/src/components/SearchPage/SearchPage.tsx +++ b/plugins/search/src/components/SearchPage/SearchPage.tsx @@ -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 ( - + {outlet}