reactTestingLibrary.tsx 4.4 KB

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