reactTestingLibrary.tsx 4.9 KB

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