rtl 13 fixes for tests with type issues

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2023-10-13 09:21:51 +02:00
parent d60fbcb553
commit 0935b0d60f
33 changed files with 862 additions and 963 deletions
@@ -16,7 +16,7 @@
import React from 'react';
import { ApiProvider } from '@backstage/core-app-api';
import { MockConfigApi, TestApiRegistry } from '@backstage/test-utils';
import { renderHook } from '@testing-library/react';
import { act, renderHook } from '@testing-library/react';
import { searchApiRef } from '../../api';
import { SearchContextProvider, useSearch } from '../../context';
@@ -67,7 +67,7 @@ describe('SearchFilter.hooks', () => {
it('should set non-empty string value', async () => {
const expectedFilter = 'someField';
const expectedValue = 'someValue';
const { result, waitForNextUpdate } = renderHook(
const { result } = renderHook(
() => {
useDefaultFilterValue(expectedFilter, expectedValue);
return useSearch();
@@ -77,7 +77,7 @@ describe('SearchFilter.hooks', () => {
},
);
await waitForNextUpdate();
await act(async () => {});
expect(result.current.filters[expectedFilter]).toEqual(expectedValue);
});
@@ -85,7 +85,7 @@ describe('SearchFilter.hooks', () => {
it('should set non-empty array value', async () => {
const expectedFilter = 'someField';
const expectedValue = ['someValue', 'anotherValue'];
const { result, waitForNextUpdate } = renderHook(
const { result } = renderHook(
() => {
useDefaultFilterValue(expectedFilter, expectedValue);
return useSearch();
@@ -95,7 +95,7 @@ describe('SearchFilter.hooks', () => {
},
);
await waitForNextUpdate();
await act(async () => {});
expect(result.current.filters[expectedFilter]).toEqual(expectedValue);
});
@@ -103,24 +103,25 @@ describe('SearchFilter.hooks', () => {
it('should not set undefined value', async () => {
const expectedFilter = 'someField';
const expectedValue = 'notEmpty';
const { result, waitForNextUpdate } = renderHook(
const { result } = renderHook(
() => {
useDefaultFilterValue(expectedFilter, undefined);
return useSearch();
},
{
wrapper,
initialProps: {
overrides: {
filters: {
[expectedFilter]: expectedValue,
wrapper: ({ children }) =>
wrapper({
children,
overrides: {
filters: {
[expectedFilter]: expectedValue,
},
},
},
},
}),
},
);
await waitForNextUpdate();
await act(async () => {});
expect(result.current.filters[expectedFilter]).toEqual(expectedValue);
});
@@ -128,24 +129,25 @@ describe('SearchFilter.hooks', () => {
it('should not set null value', async () => {
const expectedFilter = 'someField';
const expectedValue = 'notEmpty';
const { result, waitForNextUpdate } = renderHook(
const { result } = renderHook(
() => {
useDefaultFilterValue(expectedFilter, null);
return useSearch();
},
{
wrapper,
initialProps: {
overrides: {
filters: {
[expectedFilter]: expectedValue,
wrapper: ({ children }) =>
wrapper({
children,
overrides: {
filters: {
[expectedFilter]: expectedValue,
},
},
},
},
}),
},
);
await waitForNextUpdate();
await act(async () => {});
expect(result.current.filters[expectedFilter]).toEqual(expectedValue);
});
@@ -153,24 +155,25 @@ describe('SearchFilter.hooks', () => {
it('should not set empty string value', async () => {
const expectedFilter = 'someField';
const expectedValue = 'notEmpty';
const { result, waitForNextUpdate } = renderHook(
const { result } = renderHook(
() => {
useDefaultFilterValue(expectedFilter, '');
return useSearch();
},
{
wrapper,
initialProps: {
overrides: {
filters: {
[expectedFilter]: expectedValue,
wrapper: ({ children }) =>
wrapper({
children,
overrides: {
filters: {
[expectedFilter]: expectedValue,
},
},
},
},
}),
},
);
await waitForNextUpdate();
await act(async () => {});
expect(result.current.filters[expectedFilter]).toEqual(expectedValue);
});
@@ -178,24 +181,25 @@ describe('SearchFilter.hooks', () => {
it('should not set empty array value', async () => {
const expectedFilter = 'someField';
const expectedValue = ['not', 'empty'];
const { result, waitForNextUpdate } = renderHook(
const { result } = renderHook(
() => {
useDefaultFilterValue(expectedFilter, []);
return useSearch();
},
{
wrapper,
initialProps: {
overrides: {
filters: {
[expectedFilter]: expectedValue,
wrapper: ({ children }) =>
wrapper({
children,
overrides: {
filters: {
[expectedFilter]: expectedValue,
},
},
},
},
}),
},
);
await waitForNextUpdate();
await act(async () => {});
expect(result.current.filters[expectedFilter]).toEqual(expectedValue);
});
@@ -203,24 +207,25 @@ describe('SearchFilter.hooks', () => {
it('should not affect unrelated filters', async () => {
const expectedFilter = 'someField';
const expectedValue = 'someValue';
const { result, waitForNextUpdate } = renderHook(
const { result } = renderHook(
() => {
useDefaultFilterValue(expectedFilter, expectedValue);
return useSearch();
},
{
wrapper,
initialProps: {
overrides: {
filters: {
unrelatedField: 'unrelatedValue',
wrapper: ({ children }) =>
wrapper({
children,
overrides: {
filters: {
unrelatedField: 'unrelatedValue',
},
},
},
},
}),
},
);
await waitForNextUpdate();
await act(async () => {});
expect(result.current.filters.unrelatedField).toEqual('unrelatedValue');
});
@@ -240,14 +245,15 @@ describe('SearchFilter.hooks', () => {
it('should return resolved values of provided async function', async () => {
const expectedValues = ['value1', 'value2'];
const asyncFn = () => Promise.resolve(expectedValues);
const { result, waitForNextUpdate } = renderHook(() =>
const { result } = renderHook(() =>
useAsyncFilterValues(asyncFn, '', undefined, 1000),
);
expect(result.current.loading).toEqual(true);
jest.runAllTimers();
await waitForNextUpdate();
await act(async () => {
jest.runAllTimers();
});
expect(result.current.loading).toEqual(false);
expect(result.current.value).toEqual(expectedValues);
@@ -261,11 +267,15 @@ describe('SearchFilter.hooks', () => {
expect(asyncFn).not.toHaveBeenCalled();
// Advance timers by 600ms
jest.advanceTimersByTime(600);
await act(async () => {
jest.advanceTimersByTime(600);
});
expect(asyncFn).not.toHaveBeenCalled();
// Another 600ms to exceed the 1000ms debounce
jest.advanceTimersByTime(600);
await act(async () => {
jest.advanceTimersByTime(600);
});
expect(asyncFn).toHaveBeenCalled();
});
@@ -273,21 +283,23 @@ describe('SearchFilter.hooks', () => {
const asyncFn = jest
.fn()
.mockImplementation((x: string) => Promise.resolve([x]));
const { rerender, waitForNextUpdate } = renderHook(
const { rerender } = renderHook(
(props: { inputValue: string } = { inputValue: '' }) =>
useAsyncFilterValues(asyncFn, props.inputValue, undefined, 1000),
);
expect(asyncFn).not.toHaveBeenCalled();
jest.runAllTimers();
await waitForNextUpdate();
await act(async () => {
jest.runAllTimers();
});
expect(asyncFn).toHaveBeenCalledTimes(1);
expect(asyncFn).toHaveBeenCalledWith('');
// Re-render with different input value.
rerender({ inputValue: 'somethingElse' });
jest.runAllTimers();
await waitForNextUpdate();
await act(async () => {
jest.runAllTimers();
});
expect(asyncFn).toHaveBeenCalledTimes(2);
expect(asyncFn).toHaveBeenLastCalledWith('somethingElse');
});
@@ -295,15 +307,16 @@ describe('SearchFilter.hooks', () => {
it('should not call provided method more than once when re-rendered with same input', async () => {
const expectedValues = ['value1', 'value2'];
const asyncFn = jest.fn().mockResolvedValue(expectedValues);
const { rerender, waitForNextUpdate } = renderHook(
const { rerender } = renderHook(
(props: { inputValue: string } = { inputValue: '' }) =>
useAsyncFilterValues(asyncFn, props.inputValue, undefined, 1000),
);
expect(asyncFn).not.toHaveBeenCalled();
jest.runAllTimers();
await waitForNextUpdate();
await act(async () => {
jest.runAllTimers();
});
expect(asyncFn).toHaveBeenCalledTimes(1);
// Re-render multiple times with the same input.
@@ -15,8 +15,13 @@
*/
import { configApiRef } from '@backstage/core-plugin-api';
import { render, screen, waitFor } from '@testing-library/react';
import { act, renderHook } from '@testing-library/react';
import {
render,
screen,
waitFor,
act,
renderHook,
} from '@testing-library/react';
import { MockConfigApi, TestApiProvider } from '@backstage/test-utils';
import React from 'react';
import {
@@ -70,10 +75,8 @@ describe('SearchContext', () => {
});
it('Throws error when no context is set', () => {
const { result } = renderHook(() => useSearch());
expect(result.error).toEqual(
Error('useSearch must be used within a SearchContextProvider'),
expect(() => renderHook(() => useSearch())).toThrow(
'useSearch must be used within a SearchContextProvider',
);
});
@@ -82,28 +85,22 @@ describe('SearchContext', () => {
expect(hook.result.current).toEqual(false);
const { result, waitForNextUpdate } = renderHook(
() => useSearchContextCheck(),
{
wrapper,
initialProps: {
initialState,
},
},
);
const { result } = renderHook(() => useSearchContextCheck(), {
wrapper: ({ children }) => wrapper({ children, initialState }),
});
await waitForNextUpdate();
await act(async () => {});
expect(result.current).toEqual(true);
});
describe('Uses initial state values', () => {
it('Uses default initial state values', async () => {
const { result, waitForNextUpdate } = renderHook(() => useSearch(), {
const { result } = renderHook(() => useSearch(), {
wrapper,
});
await waitForNextUpdate();
await act(async () => {});
expect(result.current).toEqual(
expect.objectContaining({
@@ -117,34 +114,32 @@ describe('SearchContext', () => {
});
it('Uses provided initial state values', async () => {
const { result, waitForNextUpdate } = renderHook(() => useSearch(), {
wrapper,
initialProps: {
initialState,
},
const { result } = renderHook(() => useSearch(), {
wrapper: ({ children }) => wrapper({ children, initialState }),
});
await waitForNextUpdate();
await act(async () => {});
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: {
search: {
query: {
pageLimit: 100,
const { result } = renderHook(() => useSearch(), {
wrapper: ({ children }) =>
wrapper({
children,
initialState,
config: {
search: {
query: {
pageLimit: 100,
},
},
},
},
},
}),
});
await waitForNextUpdate();
await act(async () => {});
expect(result.current).toEqual(
expect.objectContaining({ ...initialState, pageLimit: 100 }),
@@ -154,131 +149,122 @@ describe('SearchContext', () => {
describe('Resets cursor', () => {
it('When term is cleared', async () => {
const { result, waitForNextUpdate } = renderHook(() => useSearch(), {
wrapper,
initialProps: {
initialState: {
...initialState,
term: 'first term',
pageCursor: 'SOMEPAGE',
},
},
const { result } = renderHook(() => useSearch(), {
wrapper: ({ children }) =>
wrapper({
children,
initialState: {
...initialState,
term: 'first term',
pageCursor: 'SOMEPAGE',
},
}),
});
await waitForNextUpdate();
await act(async () => {});
expect(result.current.term).toEqual('first term');
expect(result.current.pageCursor).toEqual('SOMEPAGE');
act(() => {
await act(async () => {
result.current.setTerm('');
});
await waitForNextUpdate();
expect(result.current.pageCursor).toBeUndefined();
});
it('When term is set (and different from previous)', async () => {
const { result, waitForNextUpdate } = renderHook(() => useSearch(), {
wrapper,
initialProps: {
initialState: {
...initialState,
term: 'first term',
pageCursor: 'SOMEPAGE',
},
},
const { result } = renderHook(() => useSearch(), {
wrapper: ({ children }) =>
wrapper({
children,
initialState: {
...initialState,
term: 'first term',
pageCursor: 'SOMEPAGE',
},
}),
});
await waitForNextUpdate();
await act(async () => {});
expect(result.current.term).toEqual('first term');
expect(result.current.pageCursor).toEqual('SOMEPAGE');
act(() => {
await act(async () => {
result.current.setTerm('second term');
});
await waitForNextUpdate();
expect(result.current.pageCursor).toBeUndefined();
});
it('When filters are cleared', async () => {
const { result, waitForNextUpdate } = renderHook(() => useSearch(), {
wrapper,
initialProps: {
initialState: {
...initialState,
term: 'first term',
filters: { foo: 'bar' },
pageCursor: 'SOMEPAGE',
},
},
const { result } = renderHook(() => useSearch(), {
wrapper: ({ children }) =>
wrapper({
children,
initialState: {
...initialState,
term: 'first term',
filters: { foo: 'bar' },
pageCursor: 'SOMEPAGE',
},
}),
});
await waitForNextUpdate();
await act(async () => {});
expect(result.current.filters).toEqual({ foo: 'bar' });
expect(result.current.pageCursor).toEqual('SOMEPAGE');
act(() => {
await act(async () => {
result.current.setFilters({});
});
await waitForNextUpdate();
expect(result.current.pageCursor).toBeUndefined();
});
it('When filters are set (and different from previous)', async () => {
const { result, waitForNextUpdate } = renderHook(() => useSearch(), {
wrapper,
initialProps: {
initialState: {
...initialState,
term: 'first term',
filters: { foo: 'bar' },
pageCursor: 'SOMEPAGE',
},
},
const { result } = renderHook(() => useSearch(), {
wrapper: ({ children }) =>
wrapper({
children,
initialState: {
...initialState,
term: 'first term',
filters: { foo: 'bar' },
pageCursor: 'SOMEPAGE',
},
}),
});
await waitForNextUpdate();
await act(async () => {});
expect(result.current.filters).toEqual({ foo: 'bar' });
expect(result.current.pageCursor).toEqual('SOMEPAGE');
act(() => {
await act(async () => {
result.current.setFilters({ foo: 'test' });
});
await waitForNextUpdate();
expect(result.current.pageCursor).toBeUndefined();
});
});
describe('Performs search (and sets results)', () => {
it('When term is set', async () => {
const { result, waitForNextUpdate } = renderHook(() => useSearch(), {
wrapper,
initialProps: {
initialState,
},
const { result } = renderHook(() => useSearch(), {
wrapper: ({ children }) => wrapper({ children, initialState }),
});
await waitForNextUpdate();
await act(async () => {});
const term = 'term';
act(() => {
await act(async () => {
result.current.setTerm(term);
});
await waitForNextUpdate();
expect(searchApiMock.query).toHaveBeenLastCalledWith({
term,
types: ['*'],
@@ -287,23 +273,18 @@ describe('SearchContext', () => {
});
it('When types is set', async () => {
const { result, waitForNextUpdate } = renderHook(() => useSearch(), {
wrapper,
initialProps: {
initialState,
},
const { result } = renderHook(() => useSearch(), {
wrapper: ({ children }) => wrapper({ children, initialState }),
});
await waitForNextUpdate();
await act(async () => {});
const types = ['type'];
act(() => {
await act(async () => {
result.current.setTypes(types);
});
await waitForNextUpdate();
expect(searchApiMock.query).toHaveBeenLastCalledWith({
types,
term: '',
@@ -312,23 +293,18 @@ describe('SearchContext', () => {
});
it('When filters are set', async () => {
const { result, waitForNextUpdate } = renderHook(() => useSearch(), {
wrapper,
initialProps: {
initialState,
},
const { result } = renderHook(() => useSearch(), {
wrapper: ({ children }) => wrapper({ children, initialState }),
});
await waitForNextUpdate();
await act(async () => {});
const filters = { filter: 'filter' };
act(() => {
await act(async () => {
result.current.setFilters(filters);
});
await waitForNextUpdate();
expect(searchApiMock.query).toHaveBeenLastCalledWith({
filters,
term: '',
@@ -337,23 +313,18 @@ describe('SearchContext', () => {
});
it('When page limit is set', async () => {
const { result, waitForNextUpdate } = renderHook(() => useSearch(), {
wrapper,
initialProps: {
initialState,
},
const { result } = renderHook(() => useSearch(), {
wrapper: ({ children }) => wrapper({ children, initialState }),
});
await waitForNextUpdate();
await act(async () => {});
const pageLimit = 30;
act(() => {
await act(async () => {
result.current.setPageLimit(pageLimit);
});
await waitForNextUpdate();
expect(searchApiMock.query).toHaveBeenLastCalledWith({
pageLimit,
term: '',
@@ -363,23 +334,18 @@ describe('SearchContext', () => {
});
it('When page cursor is set', async () => {
const { result, waitForNextUpdate } = renderHook(() => useSearch(), {
wrapper,
initialProps: {
initialState,
},
const { result } = renderHook(() => useSearch(), {
wrapper: ({ children }) => wrapper({ children, initialState }),
});
await waitForNextUpdate();
await act(async () => {});
const pageCursor = 'SOMEPAGE';
act(() => {
await act(async () => {
result.current.setPageCursor(pageCursor);
});
await waitForNextUpdate();
expect(searchApiMock.query).toHaveBeenLastCalledWith({
pageCursor,
term: '',
@@ -394,24 +360,19 @@ describe('SearchContext', () => {
nextPageCursor: 'NEXT',
});
const { result, waitForNextUpdate } = renderHook(() => useSearch(), {
wrapper,
initialProps: {
initialState,
},
const { result } = renderHook(() => useSearch(), {
wrapper: ({ children }) => wrapper({ children, initialState }),
});
await waitForNextUpdate();
await act(async () => {});
expect(result.current.fetchNextPage).toBeDefined();
expect(result.current.fetchPreviousPage).toBeUndefined();
act(() => {
await act(async () => {
result.current.fetchNextPage!();
});
await waitForNextUpdate();
expect(searchApiMock.query).toHaveBeenLastCalledWith({
term: '',
types: ['*'],
@@ -426,24 +387,19 @@ describe('SearchContext', () => {
previousPageCursor: 'PREVIOUS',
});
const { result, waitForNextUpdate } = renderHook(() => useSearch(), {
wrapper,
initialProps: {
initialState,
},
const { result } = renderHook(() => useSearch(), {
wrapper: ({ children }) => wrapper({ children, initialState }),
});
await waitForNextUpdate();
await act(async () => {});
expect(result.current.fetchNextPage).toBeUndefined();
expect(result.current.fetchPreviousPage).toBeDefined();
act(() => {
await act(async () => {
result.current.fetchPreviousPage!();
});
await waitForNextUpdate();
expect(searchApiMock.query).toHaveBeenLastCalledWith({
term: '',
types: ['*'],