reactTestingLibrary.tsx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. import {Component} from 'react';
  2. import type {InjectedRouter} from 'react-router';
  3. import {cache} from '@emotion/css'; // eslint-disable-line @emotion/no-vanilla
  4. import {CacheProvider, ThemeProvider} from '@emotion/react';
  5. import * as rtl from '@testing-library/react'; // eslint-disable-line no-restricted-imports
  6. import userEvent from '@testing-library/user-event'; // eslint-disable-line no-restricted-imports
  7. import {makeTestQueryClient} from 'sentry-test/queryClient';
  8. import GlobalModal from 'sentry/components/globalModal';
  9. import type {Organization} from 'sentry/types/organization';
  10. import {QueryClientProvider} from 'sentry/utils/queryClient';
  11. import {lightTheme} from 'sentry/utils/theme';
  12. import {OrganizationContext} from 'sentry/views/organizationContext';
  13. import {RouteContext} from 'sentry/views/routeContext';
  14. import {instrumentUserEvent} from '../instrumentedEnv/userEventIntegration';
  15. import {initializeOrg} from './initializeOrg';
  16. type ProviderOptions = {
  17. /**
  18. * Sets legacy context providers. This value is directly passed to a
  19. * `getChildContext`.
  20. */
  21. context?: Record<string, any>;
  22. /**
  23. * Sets the OrganizationContext. You may pass null to provide no organization
  24. */
  25. organization?: Partial<Organization> | null;
  26. /**
  27. * Sets the RouterContext
  28. */
  29. router?: Partial<InjectedRouter>;
  30. };
  31. type Options = ProviderOptions & rtl.RenderOptions;
  32. function createProvider(contextDefs: Record<string, any>) {
  33. return class ContextProvider extends Component<{children?: React.ReactNode}> {
  34. static childContextTypes = contextDefs.childContextTypes;
  35. getChildContext() {
  36. return contextDefs.context;
  37. }
  38. render() {
  39. return this.props.children;
  40. }
  41. };
  42. }
  43. function makeAllTheProviders({context, ...initializeOrgOptions}: ProviderOptions) {
  44. const {organization, router, routerContext} = initializeOrg(
  45. initializeOrgOptions as any
  46. );
  47. const ContextProvider = context
  48. ? createProvider(context)
  49. : createProvider(routerContext);
  50. // In some cases we may want to not provide an organization at all
  51. const optionalOrganization =
  52. initializeOrgOptions.organization === null ? null : organization;
  53. return function ({children}: {children?: React.ReactNode}) {
  54. return (
  55. <ContextProvider>
  56. <CacheProvider value={{...cache, compat: true}}>
  57. <ThemeProvider theme={lightTheme}>
  58. <QueryClientProvider client={makeTestQueryClient()}>
  59. <RouteContext.Provider
  60. value={{
  61. router,
  62. location: router.location,
  63. params: router.params,
  64. routes: router.routes,
  65. }}
  66. >
  67. <OrganizationContext.Provider value={optionalOrganization}>
  68. {children}
  69. </OrganizationContext.Provider>
  70. </RouteContext.Provider>
  71. </QueryClientProvider>
  72. </ThemeProvider>
  73. </CacheProvider>
  74. </ContextProvider>
  75. );
  76. };
  77. }
  78. /**
  79. * Try avoiding unnecessary context and just mount your component. If it works,
  80. * then you dont need anything else.
  81. *
  82. * render(<TestedComponent />);
  83. *
  84. * If your component requires routerContext or organization to render, pass it
  85. * via context options argument. render(<TestedComponent />, {context:
  86. * routerContext, organization});
  87. */
  88. function render(ui: React.ReactElement, options?: Options) {
  89. options = options ?? {};
  90. const {context, organization, ...otherOptions} = options;
  91. let {router} = options;
  92. if (router === undefined && context?.context?.router) {
  93. router = context.context.router;
  94. }
  95. const AllTheProviders = makeAllTheProviders({
  96. context,
  97. organization,
  98. router,
  99. });
  100. return rtl.render(ui, {wrapper: AllTheProviders, ...otherOptions});
  101. }
  102. /**
  103. * @deprecated
  104. * Use userEvent over fireEvent where possible.
  105. * More details: https://kentcdodds.com/blog/common-mistakes-with-react-testing-library#not-using-testing-libraryuser-event
  106. */
  107. const fireEvent = rtl.fireEvent;
  108. function renderGlobalModal(options?: Options) {
  109. const result = render(<GlobalModal />, options);
  110. /**
  111. * Helper that waits for the modal to be removed from the DOM. You may need to
  112. * wait for the modal to be removed to avoid any act warnings.
  113. */
  114. function waitForModalToHide() {
  115. return rtl.waitFor(() => {
  116. expect(rtl.screen.queryByRole('dialog')).not.toBeInTheDocument();
  117. });
  118. }
  119. return {...result, waitForModalToHide};
  120. }
  121. /**
  122. * This cannot be implemented as a Sentry Integration because Jest creates an
  123. * isolated environment for each test suite. This means that if we were to apply
  124. * the monkey patching ahead of time, it would be shadowed by Jest.
  125. */
  126. instrumentUserEvent();
  127. // eslint-disable-next-line no-restricted-imports, import/export
  128. export * from '@testing-library/react';
  129. // eslint-disable-next-line import/export
  130. export {render, renderGlobalModal, userEvent, fireEvent};