diff --git a/.changeset/many-falcons-joke.md b/.changeset/many-falcons-joke.md new file mode 100644 index 0000000000..6254dcae40 --- /dev/null +++ b/.changeset/many-falcons-joke.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-react': patch +--- + +Skip the very first empty search when going to the landing page diff --git a/plugins/search-react/src/components/SearchPagination/SearchPagination.test.tsx b/plugins/search-react/src/components/SearchPagination/SearchPagination.test.tsx index e482dc3a75..d9cd564ae2 100644 --- a/plugins/search-react/src/components/SearchPagination/SearchPagination.test.tsx +++ b/plugins/search-react/src/components/SearchPagination/SearchPagination.test.tsx @@ -69,7 +69,7 @@ describe('SearchPagination', () => { expect(screen.getByText('Results per page:')).toBeInTheDocument(); expect(screen.getByText('25')).toBeInTheDocument(); expect(screen.getByText('1-25')).toBeInTheDocument(); - expect(screen.getByLabelText('Next page')).toBeEnabled(); + expect(screen.getByLabelText('Next page')).toBeDisabled(); expect(screen.getByLabelText('Previous page')).toBeDisabled(); }); @@ -176,6 +176,12 @@ describe('SearchPagination', () => { }); it('Set page limit in the context', async () => { + const initialState = { + term: 'a', + types: [], + filters: {}, + }; + await renderInTestApp( { [configApiRef, configApiMock], ]} > - + , @@ -205,7 +211,7 @@ describe('SearchPagination', () => { it('Set page cursor in the context', async () => { const initialState = { - term: '', + term: 'a', types: [], filters: {}, pageCursor: 'MQ==', // page: 1 @@ -253,7 +259,7 @@ describe('SearchPagination', () => { it('Resets page cursor when page limit changes', async () => { const initialState = { - term: '', + term: 'a', types: [], filters: {}, pageCursor: 'Mg==', // page: 2 @@ -280,9 +286,7 @@ describe('SearchPagination', () => { pageCursor: undefined, pageLimit: 10, }), - { - signal: expect.any(AbortSignal), - }, + { signal: expect.any(AbortSignal) }, ); }); }); diff --git a/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.test.tsx b/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.test.tsx index cb274be0a0..e1f61f088b 100644 --- a/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.test.tsx +++ b/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.test.tsx @@ -66,7 +66,7 @@ describe('SearchResultGroup', () => { }); it('Renders without exploding', async () => { - query.mockResolvedValueOnce({ + query.mockResolvedValue({ results, }); @@ -96,7 +96,7 @@ describe('SearchResultGroup', () => { }); it('Renders search results from context', async () => { - query.mockResolvedValueOnce({ + query.mockResolvedValue({ results, }); @@ -107,7 +107,9 @@ describe('SearchResultGroup', () => { [analyticsApiRef, analyticsApiMock], ]} > - + } title="Documentation" @@ -128,7 +130,7 @@ describe('SearchResultGroup', () => { }); it('Renders search results using extensions', async () => { - query.mockResolvedValueOnce({ + query.mockResolvedValue({ results, }); @@ -166,7 +168,7 @@ describe('SearchResultGroup', () => { }); it('Defines a default link', async () => { - query.mockResolvedValueOnce({ + query.mockResolvedValue({ results, }); @@ -190,7 +192,7 @@ describe('SearchResultGroup', () => { }); it('Defines a default render result item', async () => { - query.mockResolvedValueOnce({ + query.mockResolvedValue({ results, }); @@ -221,6 +223,10 @@ describe('SearchResultGroup', () => { }); it('Could be customized with no results text', async () => { + query.mockResolvedValue({ + results: [], + }); + await renderInTestApp( { }); it('Could be customized with filters', async () => { - query.mockResolvedValueOnce({ + query.mockResolvedValue({ results, }); @@ -272,7 +278,7 @@ describe('SearchResultGroup', () => { }); it('Could have a text search filter field', async () => { - query.mockResolvedValueOnce({ + query.mockResolvedValue({ results, }); @@ -323,7 +329,7 @@ describe('SearchResultGroup', () => { }); it('Could have a select search filter field', async () => { - query.mockResolvedValueOnce({ + query.mockResolvedValue({ results, }); @@ -376,7 +382,7 @@ describe('SearchResultGroup', () => { }); it('Shows a progress bar when loading results', async () => { - query.mockReturnValueOnce(new Promise(() => {})); + query.mockReturnValue(new Promise(() => {})); await renderInTestApp( { }); it('Does not render result group if no results returned and disableRenderingWithNoResults prop is provided', async () => { - query.mockResolvedValueOnce({ results: [] }); + query.mockResolvedValue({ results: [] }); await renderInTestApp( { }); it('Should render custom component when no results returned', async () => { - query.mockResolvedValueOnce({ results: [] }); + query.mockResolvedValue({ results: [] }); await renderInTestApp( { }); it('Shows an error panel when results rendering fails', async () => { - query.mockRejectedValueOnce(new Error()); + query.mockRejectedValue(new Error()); await renderInTestApp( ( initialValue.pageCursor, ); + const isFirstEmptyMount = useRef(true); const prevTerm = usePrevious(term); const prevFilters = usePrevious(filters); const abortControllerRef = useRef(null); - const result = useAsync(async () => { + const result = useAsync(async (): Promise => { + if (isFirstEmptyMount.current) { + if (!term && !types.length && !Object.keys(filters).length) { + return { + results: [], + numberOfResults: 0, + }; + } + + isFirstEmptyMount.current = false; + } + // Here we cancel the previous request before making a new one if (abortControllerRef.current) { abortControllerRef.current.abort(); diff --git a/plugins/search/report-alpha.api.md b/plugins/search/report-alpha.api.md index 5353fec09e..b129d4ab6c 100644 --- a/plugins/search/report-alpha.api.md +++ b/plugins/search/report-alpha.api.md @@ -264,8 +264,8 @@ export const searchTranslationRef: TranslationRef< readonly 'searchType.tabs.allTitle': 'All'; readonly 'searchType.allResults': 'All Results'; readonly 'searchType.accordion.collapse': 'Collapse'; - readonly 'searchType.accordion.allTitle': 'All'; readonly 'searchType.accordion.numberOfResults': '{{number}} results'; + readonly 'searchType.accordion.allTitle': 'All'; readonly 'sidebarSearchModal.title': 'Search'; } >; diff --git a/plugins/search/src/components/HomePageComponent/HomePageSearchBar.test.tsx b/plugins/search/src/components/HomePageComponent/HomePageSearchBar.test.tsx index e7c47b7d30..270bdbb2eb 100644 --- a/plugins/search/src/components/HomePageComponent/HomePageSearchBar.test.tsx +++ b/plugins/search/src/components/HomePageComponent/HomePageSearchBar.test.tsx @@ -46,12 +46,7 @@ describe('', () => { }, ); - expect(searchApiMock.query).toHaveBeenCalledWith( - expect.objectContaining({ term: '' }), - { - signal: expect.any(AbortSignal), - }, - ); + expect(searchApiMock.query).not.toHaveBeenCalled(); await userEvent.type(screen.getByLabelText('Search'), 'term{enter}'); diff --git a/plugins/search/src/components/SearchModal/SearchModal.test.tsx b/plugins/search/src/components/SearchModal/SearchModal.test.tsx index 8d8e1bb9b9..9424599130 100644 --- a/plugins/search/src/components/SearchModal/SearchModal.test.tsx +++ b/plugins/search/src/components/SearchModal/SearchModal.test.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { screen } from '@testing-library/react'; +import { screen, waitFor } from '@testing-library/react'; import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils'; import userEvent from '@testing-library/user-event'; import { configApiRef } from '@backstage/core-plugin-api'; @@ -63,7 +63,6 @@ describe('SearchModal', () => { ); expect(screen.getByRole('dialog')).toBeInTheDocument(); - expect(searchApiMock.query).toHaveBeenCalledTimes(1); }); it('Should use parent search context if defined', async () => { @@ -106,15 +105,21 @@ describe('SearchModal', () => { ); expect(screen.getByRole('dialog')).toBeInTheDocument(); - expect(searchApiMock.query).toHaveBeenCalledWith( - { - term: '', - filters: {}, - types: [], - pageCursor: undefined, - }, - { signal: expect.any(AbortSignal) }, - ); + + const input = screen.getByLabelText('Search'); + await userEvent.type(input, 'text'); + + await waitFor(() => { + expect(searchApiMock.query).toHaveBeenCalledWith( + { + term: 'text', + filters: {}, + types: [], + pageCursor: undefined, + }, + { signal: expect.any(AbortSignal) }, + ); + }); }); it('Should render a custom Modal correctly', async () => { @@ -146,7 +151,6 @@ describe('SearchModal', () => { }, ); - expect(searchApiMock.query).toHaveBeenCalledTimes(1); await userEvent.keyboard('{Escape}'); expect(toggleModal).toHaveBeenCalledTimes(1); });