From 4e97c47c9390654e6a9d6232a368674e74ae7a1c Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Wed, 26 May 2021 11:04:47 +0200 Subject: [PATCH] test(plugins/search): complete coverage of SearchContext Signed-off-by: Camila Belo Signed-off-by: Eric Peterson --- .../SearchBarNext/SearchBarNext.test.tsx | 32 ++- .../SearchContext/SearchContext.test.tsx | 214 +++++++++++++++--- .../SearchContext/SearchContext.tsx | 2 +- 3 files changed, 218 insertions(+), 30 deletions(-) diff --git a/plugins/search/src/components/SearchBarNext/SearchBarNext.test.tsx b/plugins/search/src/components/SearchBarNext/SearchBarNext.test.tsx index 3a5cb3e97a..f29cc53b71 100644 --- a/plugins/search/src/components/SearchBarNext/SearchBarNext.test.tsx +++ b/plugins/search/src/components/SearchBarNext/SearchBarNext.test.tsx @@ -16,11 +16,41 @@ import React from 'react'; import { renderInTestApp } from '@backstage/test-utils'; +import { useApi } from '@backstage/core'; + +import { SearchContextProvider } from '../SearchContext'; import { SearchBarNext } from './SearchBarNext'; +jest.mock('@backstage/core', () => ({ + ...jest.requireActual('@backstage/core'), + useApi: jest.fn(), +})); + describe('', () => { + const _alphaPerformSearch = jest.fn(); + + const initialState = { + term: '', + pageCursor: '', + filters: {}, + types: ['*'], + }; + + beforeEach(() => { + _alphaPerformSearch.mockResolvedValue([]); + (useApi as jest.Mock).mockReturnValue({ _alphaPerformSearch }); + }); + + afterAll(() => { + jest.resetAllMocks(); + }); + it('renders without exploding', async () => { - const { getByRole } = await renderInTestApp(); + const { getByRole } = await renderInTestApp( + + + , + ); expect( getByRole('textbox', { name: 'search backstage' }), diff --git a/plugins/search/src/components/SearchContext/SearchContext.test.tsx b/plugins/search/src/components/SearchContext/SearchContext.test.tsx index cce315f62b..c45d74eade 100644 --- a/plugins/search/src/components/SearchContext/SearchContext.test.tsx +++ b/plugins/search/src/components/SearchContext/SearchContext.test.tsx @@ -15,47 +15,205 @@ */ import React from 'react'; -import { renderInTestApp } from '@backstage/test-utils'; -import * as SearchContext from './SearchContext'; +import { render, screen, waitFor } from '@testing-library/react'; +import { renderHook, act } from '@testing-library/react-hooks'; -const mockContextState = ({ term }: { term: string }) => { - return { - term, +import { useApi } from '@backstage/core'; + +import { useSearch, SearchContextProvider } from './SearchContext'; + +jest.mock('@backstage/core', () => ({ + ...jest.requireActual('@backstage/core'), + useApi: jest.fn(), +})); + +describe('SearchContext', () => { + const _alphaPerformSearch = jest.fn(); + + const wrapper = ({ children, initialState }: any) => ( + + {children} + + ); + + const initialState = { + term: '', pageCursor: '', filters: {}, types: ['*'], - result: { results: [], loading: false, error: undefined }, - setTerm: jest.fn(), - setFilters: jest.fn(), - setTypes: jest.fn(), - setPageCursor: jest.fn(), }; -}; -const MockSearchContextConsumer = () => { - const { term } = SearchContext.useSearch(); + beforeEach(() => { + _alphaPerformSearch.mockResolvedValue([]); + (useApi as jest.Mock).mockReturnValue({ _alphaPerformSearch }); + }); - return
{term}
; -}; - -describe('useSearch', () => { - afterEach(() => { + afterAll(() => { jest.resetAllMocks(); }); - it('context should use initial term', async () => { - jest.spyOn(SearchContext, 'useSearch'); - const { getByRole } = await renderInTestApp(); - expect(getByRole('heading')).toBeInTheDocument(); + it('Passes children', async () => { + const text = 'text'; + + render( + + {text} + , + ); + + await waitFor(() => { + expect(screen.getByText(text)).toBeInTheDocument(); + }); }); - it('context should use mocked term', async () => { - jest - .spyOn(SearchContext, 'useSearch') - .mockImplementation(() => mockContextState({ term: 'new-term' })); + it('Throws error when no context is set', () => { + const { result } = renderHook(() => useSearch()); - const { getByRole } = await renderInTestApp(); + expect(result.error).toEqual( + Error('useSearch must be used within a SearchContextProvider'), + ); + }); - expect(getByRole('heading', { name: 'new-term' })).toBeInTheDocument(); + 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: '1', + }, + }, + }); + + await waitForNextUpdate(); + + expect(result.current.pageCursor).toBe('1'); + + act(() => { + result.current.setTerm('first term'); + }); + + await waitForNextUpdate(); + + expect(result.current.pageCursor).toBe('1'); + + act(() => { + result.current.setTerm('second term'); + }); + + await waitForNextUpdate(); + + expect(result.current.pageCursor).toBe(''); + }); + + 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(_alphaPerformSearch).toHaveBeenLastCalledWith({ + ...initialState, + 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(_alphaPerformSearch).toHaveBeenLastCalledWith({ + ...initialState, + filters, + }); + }); + + it('When pageCursor is set', async () => { + const { result, waitForNextUpdate } = renderHook(() => useSearch(), { + wrapper, + initialProps: { + initialState, + }, + }); + + await waitForNextUpdate(); + + const pageCursor = 'pageCursor'; + + act(() => { + result.current.setPageCursor(pageCursor); + }); + + await waitForNextUpdate(); + + expect(_alphaPerformSearch).toHaveBeenLastCalledWith({ + ...initialState, + pageCursor, + }); + }); + + 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(_alphaPerformSearch).toHaveBeenLastCalledWith({ + ...initialState, + types, + }); + }); }); }); diff --git a/plugins/search/src/components/SearchContext/SearchContext.tsx b/plugins/search/src/components/SearchContext/SearchContext.tsx index 40a9c78cf6..55ccd096a1 100644 --- a/plugins/search/src/components/SearchContext/SearchContext.tsx +++ b/plugins/search/src/components/SearchContext/SearchContext.tsx @@ -45,7 +45,7 @@ type SettableSearchContext = Omit< 'result' | 'setTerm' | 'setTypes' | 'setFilters' | 'setPageCursor' >; -const SearchContext = createContext({} as SearchContextValue); +const SearchContext = createContext(undefined); export const SearchContextProvider = ({ initialState = {