diff --git a/plugins/search-react/src/components/SearchBar/SearchBar.test.tsx b/plugins/search-react/src/components/SearchBar/SearchBar.test.tsx index d65278f7ec..fba50f74d8 100644 --- a/plugins/search-react/src/components/SearchBar/SearchBar.test.tsx +++ b/plugins/search-react/src/components/SearchBar/SearchBar.test.tsx @@ -17,59 +17,59 @@ import React from 'react'; import { screen, render, waitFor, act } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; - import { configApiRef, analyticsApiRef } from '@backstage/core-plugin-api'; -import { ApiProvider, ConfigReader } from '@backstage/core-app-api'; -import { MockAnalyticsApi, TestApiRegistry } from '@backstage/test-utils'; - +import { ConfigReader } from '@backstage/core-app-api'; +import { + MockAnalyticsApi, + TestApiProvider, + renderWithEffects, +} from '@backstage/test-utils'; import { searchApiRef } from '../../api'; import { SearchContextProvider } from '../../context'; import { SearchBar } from './SearchBar'; -jest.mock('@backstage/core-plugin-api', () => ({ - ...jest.requireActual('@backstage/core-plugin-api'), -})); +const createInitialState = ({ + term = '', + filters = {}, + types = ['*'], + pageCursor = '', +} = {}) => ({ + term, + filters, + types, + pageCursor, +}); describe('SearchBar', () => { - const initialState = { - term: '', - filters: {}, - types: ['*'], - pageCursor: '', - }; + const query = jest.fn().mockResolvedValue({ results: [] }); - const query = jest.fn().mockResolvedValue({}); - const analyticsApiSpy = new MockAnalyticsApi(); - let apiRegistry: TestApiRegistry; + const analyticsApiMock = new MockAnalyticsApi(); - apiRegistry = TestApiRegistry.from( - [ - configApiRef, - new ConfigReader({ - app: { title: 'Mock title' }, - }), - ], - [searchApiRef, { query }], - ); + const configApiMock = new ConfigReader({ + app: { title: 'Mock title' }, + }); - const name = 'Search'; - const term = 'term'; + const searchApiMock = { query }; - afterAll(() => { - jest.resetAllMocks(); + beforeEach(() => { + jest.clearAllMocks(); }); it('Renders without exploding', async () => { - render( - - + await renderWithEffects( + + - , + , ); await waitFor(() => { - expect(screen.getByRole('textbox', { name })).toBeInTheDocument(); expect( screen.getByPlaceholderText('Search in Mock title'), ).toBeInTheDocument(); @@ -77,59 +77,72 @@ describe('SearchBar', () => { }); it('Renders with custom placeholder', async () => { - render( - - - + const placeholder = 'placeholder'; + + await renderWithEffects( + + + - , - , + , ); await waitFor(() => { - expect( - screen.getByPlaceholderText('This is a custom placeholder'), - ).toBeInTheDocument(); + expect(screen.getByPlaceholderText(placeholder)).toBeInTheDocument(); }); }); it('Renders based on initial search', async () => { + const term = 'term'; + render( - - + + - , - , + , ); await waitFor(() => { - expect(screen.getByRole('textbox', { name })).toHaveValue(term); + expect(screen.getByRole('textbox', { name: 'Search' })).toHaveValue(term); }); }); it('Updates term state when text is entered', async () => { - const user = userEvent.setup({ delay: null }); jest.useFakeTimers(); - const defaultDebounceTime = 200; + const user = userEvent.setup({ delay: null }); - render( - - + await renderWithEffects( + + - , - , + , ); - const textbox = screen.getByRole('textbox', { name }); + const textbox = screen.getByRole('textbox', { name: 'Search' }); const value = 'value'; await user.type(textbox, value); act(() => { - jest.advanceTimersByTime(defaultDebounceTime); + jest.advanceTimersByTime(200); }); await waitFor(() => { @@ -139,26 +152,36 @@ describe('SearchBar', () => { expect(query).toHaveBeenLastCalledWith( expect.objectContaining({ term: value }), ); + jest.useRealTimers(); }); it('Clear button clears term state', async () => { - render( - - + const term = 'term'; + + await renderWithEffects( + + - , + , ); + const textbox = screen.getByRole('textbox', { name: 'Search' }); + await waitFor(() => { - expect(screen.getByRole('textbox', { name })).toHaveValue(term); + expect(textbox).toHaveValue(term); }); await userEvent.click(screen.getByRole('button', { name: 'Clear' })); await waitFor(() => { - expect(screen.getByRole('textbox', { name })).toHaveValue(''); + expect(textbox).toHaveValue(''); }); expect(query).toHaveBeenLastCalledWith( @@ -167,12 +190,19 @@ describe('SearchBar', () => { }); it('Should not show clear button', async () => { - render( - - + const term = 'term'; + + await renderWithEffects( + + - , + , ); await waitFor(() => { @@ -183,170 +213,168 @@ describe('SearchBar', () => { }); it('Adheres to provided debounceTime', async () => { - const user = userEvent.setup({ delay: null }); jest.useFakeTimers(); - + const user = userEvent.setup({ delay: null }); const debounceTime = 600; - render( - - + await renderWithEffects( + + - , - , + , ); - await waitFor(() => { - expect(screen.getByRole('textbox', { name })).toBeInTheDocument(); - }); - - const textbox = screen.getByRole('textbox', { name }); + const textbox = await screen.findByRole('textbox', { name: 'Search' }); const value = 'value'; await user.type(textbox, value); - expect(query).not.toHaveBeenLastCalledWith( - expect.objectContaining({ term: value }), - ); + await waitFor(() => { + expect(query).not.toHaveBeenLastCalledWith( + expect.objectContaining({ term: value }), + ); + }); act(() => { jest.advanceTimersByTime(debounceTime); }); - expect(textbox).toHaveValue(value); + + await waitFor(() => { + expect(textbox).toHaveValue(value); + }); expect(query).toHaveBeenLastCalledWith( expect.objectContaining({ term: value }), ); + jest.useRealTimers(); }); it('does not capture analytics event if not enabled in app', async () => { - const user = userEvent.setup({ delay: null }); jest.useFakeTimers(); + const user = userEvent.setup({ delay: null }); - const debounceTime = 600; - - render( - - - + await renderWithEffects( + + + - , - , + , ); - await waitFor(() => { - expect(screen.getByRole('textbox', { name })).toBeInTheDocument(); - }); - - const textbox = screen.getByRole('textbox', { name }); + const textbox = await screen.findByRole('textbox', { name: 'Search' }); const value = 'value'; await user.type(textbox, value); act(() => { - jest.advanceTimersByTime(debounceTime); + jest.advanceTimersByTime(200); }); - await waitFor(() => expect(textbox).toHaveValue(value)); + await waitFor(() => { + expect(textbox).toHaveValue(value); + }); + + expect(analyticsApiMock.getEvents()).toHaveLength(0); - expect(analyticsApiSpy.getEvents()).toHaveLength(0); jest.useRealTimers(); }); it('captures analytics events if enabled in app', async () => { - const user = userEvent.setup({ delay: null }); jest.useFakeTimers(); + const user = userEvent.setup({ delay: null }); + const types = ['techdocs', 'software-catalog']; - const debounceTime = 600; - - apiRegistry = TestApiRegistry.from( - [analyticsApiRef, analyticsApiSpy], - [ - configApiRef, - new ConfigReader({ - app: { - title: 'Mock title', - analytics: { - ga: { - trackingId: 'xyz123', + await renderWithEffects( + - - + }), + ], + ]} + > + + - , + , ); - await waitFor(() => { - expect(screen.getByRole('textbox', { name })).toBeInTheDocument(); - }); + const textbox = await screen.findByRole('textbox', { name: 'Search' }); - const textbox = screen.getByRole('textbox', { name }); - - const value = 'value'; + let value = 'value'; await user.type(textbox, value); - expect(analyticsApiSpy.getEvents()).toHaveLength(0); + expect(analyticsApiMock.getEvents()).toHaveLength(0); act(() => { - jest.advanceTimersByTime(debounceTime); + jest.advanceTimersByTime(200); }); await waitFor(() => expect(textbox).toHaveValue(value)); - expect(analyticsApiSpy.getEvents()).toHaveLength(1); - expect(analyticsApiSpy.getEvents()[0]).toEqual({ + expect(analyticsApiMock.getEvents()).toHaveLength(1); + expect(analyticsApiMock.getEvents()[0]).toEqual({ action: 'search', context: { extension: 'SearchBar', pluginId: 'search', routeRef: 'unknown', - searchTypes: 'software-catalog,techdocs', + searchTypes: types.toString(), }, - subject: 'value', + subject: value, }); await user.clear(textbox); + value = 'new value'; + // make sure new term is captured - await user.type(textbox, 'new value'); + await user.type(textbox, value); act(() => { - jest.advanceTimersByTime(debounceTime); + jest.advanceTimersByTime(200); }); - await waitFor(() => expect(textbox).toHaveValue('new value')); + await waitFor(() => expect(textbox).toHaveValue(value)); - expect(analyticsApiSpy.getEvents()).toHaveLength(2); - expect(analyticsApiSpy.getEvents()[1]).toEqual({ + expect(analyticsApiMock.getEvents()).toHaveLength(2); + expect(analyticsApiMock.getEvents()[1]).toEqual({ action: 'search', context: { extension: 'SearchBar', pluginId: 'search', routeRef: 'unknown', - searchTypes: 'software-catalog,techdocs', + searchTypes: types.toString(), }, - subject: 'new value', + subject: value, }); + jest.useRealTimers(); }); }); diff --git a/plugins/search/src/components/SearchBar/SearchBar.test.tsx b/plugins/search/src/components/SearchBar/SearchBar.test.tsx deleted file mode 100644 index 4d8a46024c..0000000000 --- a/plugins/search/src/components/SearchBar/SearchBar.test.tsx +++ /dev/null @@ -1,382 +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 React from 'react'; -import { screen, render, waitFor, act } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { configApiRef, analyticsApiRef } from '@backstage/core-plugin-api'; -import { ConfigReader } from '@backstage/core-app-api'; -import { - MockAnalyticsApi, - TestApiProvider, - renderWithEffects, -} from '@backstage/test-utils'; -import { - SearchContextProvider, - searchApiRef, - SearchBar, -} from '@backstage/plugin-search-react'; - -const createInitialState = ({ - term = '', - filters = {}, - types = ['*'], - pageCursor = '', -} = {}) => ({ - term, - filters, - types, - pageCursor, -}); - -describe('SearchBar', () => { - const query = jest.fn().mockResolvedValue({ results: [] }); - - const analyticsApiMock = new MockAnalyticsApi(); - - const configApiMock = new ConfigReader({ - app: { title: 'Mock title' }, - }); - - const searchApiMock = { query }; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('Renders without exploding', async () => { - await renderWithEffects( - - - - - , - ); - - await waitFor(() => { - expect( - screen.getByPlaceholderText('Search in Mock title'), - ).toBeInTheDocument(); - }); - }); - - it('Renders with custom placeholder', async () => { - const placeholder = 'placeholder'; - - await renderWithEffects( - - - - - , - ); - - await waitFor(() => { - expect(screen.getByPlaceholderText(placeholder)).toBeInTheDocument(); - }); - }); - - it('Renders based on initial search', async () => { - const term = 'term'; - - render( - - - - - , - ); - - await waitFor(() => { - expect(screen.getByRole('textbox', { name: 'Search' })).toHaveValue(term); - }); - }); - - it('Updates term state when text is entered', async () => { - jest.useFakeTimers(); - const user = userEvent.setup({ delay: null }); - - await renderWithEffects( - - - - - , - ); - - const textbox = screen.getByRole('textbox', { name: 'Search' }); - - const value = 'value'; - - await user.type(textbox, value); - - act(() => { - jest.advanceTimersByTime(200); - }); - - await waitFor(() => { - expect(textbox).toHaveValue(value); - }); - - expect(query).toHaveBeenLastCalledWith( - expect.objectContaining({ term: value }), - ); - - jest.useRealTimers(); - }); - - it('Clear button clears term state', async () => { - const term = 'term'; - - await renderWithEffects( - - - - - , - ); - - const textbox = screen.getByRole('textbox', { name: 'Search' }); - - await waitFor(() => { - expect(textbox).toHaveValue(term); - }); - - await userEvent.click(screen.getByRole('button', { name: 'Clear' })); - - await waitFor(() => { - expect(textbox).toHaveValue(''); - }); - - expect(query).toHaveBeenLastCalledWith( - expect.objectContaining({ term: '' }), - ); - }); - - it('Should not show clear button', async () => { - const term = 'term'; - - await renderWithEffects( - - - - - , - ); - - await waitFor(() => { - expect( - screen.queryByRole('button', { name: 'Clear' }), - ).not.toBeInTheDocument(); - }); - }); - - it('Adheres to provided debounceTime', async () => { - jest.useFakeTimers(); - const user = userEvent.setup({ delay: null }); - const debounceTime = 600; - - await renderWithEffects( - - - - - , - ); - - const textbox = await screen.findByRole('textbox', { name: 'Search' }); - - const value = 'value'; - - await user.type(textbox, value); - - await waitFor(() => { - expect(query).not.toHaveBeenLastCalledWith( - expect.objectContaining({ term: value }), - ); - }); - - act(() => { - jest.advanceTimersByTime(debounceTime); - }); - - await waitFor(() => { - expect(textbox).toHaveValue(value); - }); - - expect(query).toHaveBeenLastCalledWith( - expect.objectContaining({ term: value }), - ); - - jest.useRealTimers(); - }); - - it('does not capture analytics event if not enabled in app', async () => { - jest.useFakeTimers(); - const user = userEvent.setup({ delay: null }); - - await renderWithEffects( - - - - - , - ); - - const textbox = await screen.findByRole('textbox', { name: 'Search' }); - - const value = 'value'; - - await user.type(textbox, value); - - act(() => { - jest.advanceTimersByTime(200); - }); - - await waitFor(() => { - expect(textbox).toHaveValue(value); - }); - - expect(analyticsApiMock.getEvents()).toHaveLength(0); - - jest.useRealTimers(); - }); - - it('captures analytics events if enabled in app', async () => { - jest.useFakeTimers(); - const user = userEvent.setup({ delay: null }); - const types = ['techdocs', 'software-catalog']; - - await renderWithEffects( - - - - - , - ); - - const textbox = await screen.findByRole('textbox', { name: 'Search' }); - - let value = 'value'; - - await user.type(textbox, value); - - expect(analyticsApiMock.getEvents()).toHaveLength(0); - - act(() => { - jest.advanceTimersByTime(200); - }); - - await waitFor(() => expect(textbox).toHaveValue(value)); - - expect(analyticsApiMock.getEvents()).toHaveLength(1); - expect(analyticsApiMock.getEvents()[0]).toEqual({ - action: 'search', - context: { - extension: 'SearchBar', - pluginId: 'search', - routeRef: 'unknown', - searchTypes: types.toString(), - }, - subject: value, - }); - - await user.clear(textbox); - - value = 'new value'; - - // make sure new term is captured - await user.type(textbox, value); - - act(() => { - jest.advanceTimersByTime(200); - }); - - await waitFor(() => expect(textbox).toHaveValue(value)); - - expect(analyticsApiMock.getEvents()).toHaveLength(2); - expect(analyticsApiMock.getEvents()[1]).toEqual({ - action: 'search', - context: { - extension: 'SearchBar', - pluginId: 'search', - routeRef: 'unknown', - searchTypes: types.toString(), - }, - subject: value, - }); - - jest.useRealTimers(); - }); -});