Use PropsWithChildren and allow router configuration in test utils render

This commit is contained in:
Tom Ratcliffe 2024-04-09 13:14:58 +01:00 committed by Tom Ratcliffe
parent 045ec86984
commit 488c5b8396

View File

@ -2,7 +2,7 @@ import { ToolkitStore } from '@reduxjs/toolkit/dist/configureStore';
import { render, RenderOptions } from '@testing-library/react'; import { render, RenderOptions } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import { KBarProvider } from 'kbar'; import { KBarProvider } from 'kbar';
import React from 'react'; import React, { ComponentProps, Fragment, PropsWithChildren } from 'react';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { MemoryRouter } from 'react-router-dom'; import { MemoryRouter } from 'react-router-dom';
import { PreloadedState } from 'redux'; import { PreloadedState } from 'redux';
@ -17,24 +17,26 @@ import { configureStore } from 'app/store/configureStore';
import { StoreState } from 'app/types/store'; import { StoreState } from 'app/types/store';
interface ExtendedRenderOptions extends RenderOptions { interface ExtendedRenderOptions extends RenderOptions {
/**
* Optional store to use for rendering. If not provided, a fresh store will be generated
* via `configureStore` method
*/
store?: ToolkitStore;
/** /**
* Partial state to use for preloading store when rendering tests * Partial state to use for preloading store when rendering tests
*/ */
preloadedState?: PreloadedState<StoreState>; preloadedState?: PreloadedState<StoreState>;
/** /**
* Optional * Should the wrapper be generated with a wrapping Router component?
* Useful if you're testing something that needs more nuanced routing behaviour
* and you want full control over it instead
*/ */
store?: ToolkitStore;
renderWithRouter?: boolean; renderWithRouter?: boolean;
} /**
* Props to pass to `MemoryRouter`, if being used
/**
* Wraps children in empty fragment - used to conditionally render in a router or not,
* as needed by tests
*/ */
const FragmentWrapper = ({ children }: { children: React.ReactNode }) => { routerOptions?: ComponentProps<typeof MemoryRouter>;
return children; }
};
/** /**
* Get a wrapper component that implements all of the providers that components * Get a wrapper component that implements all of the providers that components
@ -43,22 +45,16 @@ const FragmentWrapper = ({ children }: { children: React.ReactNode }) => {
const getWrapper = ({ const getWrapper = ({
store, store,
renderWithRouter, renderWithRouter,
routerOptions,
grafanaContext, grafanaContext,
}: { }: ExtendedRenderOptions & {
store?: ToolkitStore;
/**
* Should the wrapper be generated with a wrapping Router component?
* Useful if you're testing something that needs more nuanced routing behaviour
* and you want full control over it instead
*/
renderWithRouter?: boolean;
grafanaContext?: GrafanaContextType; grafanaContext?: GrafanaContextType;
}): React.FC<{ children: React.ReactNode }> => { }) => {
const reduxStore = store || configureStore(); const reduxStore = store || configureStore();
/** /**
* Conditional router - either a MemoryRouter or just a Fragment * Conditional router - either a MemoryRouter or just a Fragment
*/ */
const PotentialRouter = renderWithRouter ? MemoryRouter : FragmentWrapper; const PotentialRouter = renderWithRouter ? MemoryRouter : Fragment;
const context = { const context = {
...getGrafanaContextMock(), ...getGrafanaContextMock(),
@ -69,14 +65,14 @@ const getWrapper = ({
* Returns a wrapper that should (closely) match the main `AppWrapper`, so any tests are rendering * Returns a wrapper that should (closely) match the main `AppWrapper`, so any tests are rendering
* in mostly the same providers as a "real" hierarchy * in mostly the same providers as a "real" hierarchy
*/ */
return function Wrapper({ children }: { children?: React.ReactNode }) { return function Wrapper({ children }: PropsWithChildren) {
return ( return (
<Provider store={reduxStore}> <Provider store={reduxStore}>
<ErrorBoundaryAlert style="page"> <ErrorBoundaryAlert style="page">
<GrafanaContext.Provider value={context}> <GrafanaContext.Provider value={context}>
<ThemeProvider value={config.theme2}> <ThemeProvider value={config.theme2}>
<KBarProvider> <KBarProvider>
<PotentialRouter> <PotentialRouter {...routerOptions}>
<ModalsContextProvider>{children}</ModalsContextProvider> <ModalsContextProvider>{children}</ModalsContextProvider>
</PotentialRouter> </PotentialRouter>
</KBarProvider> </KBarProvider>
@ -99,7 +95,7 @@ const customRender = (
) => { ) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const store = renderOptions.preloadedState ? configureStore(renderOptions?.preloadedState as any) : undefined; const store = renderOptions.preloadedState ? configureStore(renderOptions?.preloadedState as any) : undefined;
const AllTheProviders = renderOptions.wrapper || getWrapper({ store, renderWithRouter }); const AllTheProviders = renderOptions.wrapper || getWrapper({ store, renderWithRouter, ...renderOptions });
return { return {
...render(ui, { wrapper: AllTheProviders, ...renderOptions }), ...render(ui, { wrapper: AllTheProviders, ...renderOptions }),