useApiRequests.spec.tsx 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. import * as React from 'react';
  2. import {createMemoryHistory, Route, Router, RouterContext} from 'react-router';
  3. import {render, screen, waitFor} from 'sentry-test/reactTestingLibrary';
  4. import useApiRequests from 'sentry/utils/useApiRequests';
  5. import {RouteContext} from 'sentry/views/routeContext';
  6. describe('useApiRequests', () => {
  7. afterEach(() => {
  8. MockApiClient.clearMockResponses();
  9. });
  10. describe('error handling', () => {
  11. function HomePage() {
  12. const {renderComponent, data} = useApiRequests<{message: {value?: string}}>({
  13. endpoints: [['message', '/some/path/to/something/']],
  14. shouldRenderBadRequests: true,
  15. });
  16. return renderComponent(<div>{data.message?.value}</div>);
  17. }
  18. function UniqueErrorsAsyncComponent() {
  19. const {renderComponent, data} = useApiRequests({
  20. endpoints: [
  21. ['first', '/first/path/'],
  22. ['second', '/second/path/'],
  23. ['third', '/third/path/'],
  24. ],
  25. shouldRenderBadRequests: true,
  26. });
  27. // @ts-expect-error
  28. return renderComponent(<div>{data.message?.value}</div>);
  29. }
  30. const memoryHistory = createMemoryHistory();
  31. memoryHistory.push('/');
  32. function App() {
  33. return (
  34. <Router
  35. history={memoryHistory}
  36. render={props => {
  37. return (
  38. <RouteContext.Provider value={props}>
  39. <RouterContext {...props} />
  40. </RouteContext.Provider>
  41. );
  42. }}
  43. >
  44. <Route path="/" component={HomePage} />
  45. <Route path="/unique-error" component={UniqueErrorsAsyncComponent} />
  46. </Router>
  47. );
  48. }
  49. it('renders on successful request', async function () {
  50. const mockRequest = MockApiClient.addMockResponse({
  51. url: '/some/path/to/something/',
  52. method: 'GET',
  53. body: {
  54. value: 'hi',
  55. },
  56. });
  57. render(<App />);
  58. await waitFor(() => {
  59. expect(screen.getByText('hi')).toBeInTheDocument();
  60. });
  61. expect(mockRequest).toHaveBeenCalledTimes(1);
  62. });
  63. it('renders error message', async function () {
  64. MockApiClient.addMockResponse({
  65. url: '/some/path/to/something/',
  66. method: 'GET',
  67. body: {
  68. detail: 'oops there was a problem',
  69. },
  70. statusCode: 400,
  71. });
  72. render(<App />);
  73. await waitFor(() => {
  74. expect(screen.getByText('oops there was a problem')).toBeInTheDocument();
  75. });
  76. });
  77. it('renders only unique error message', async function () {
  78. MockApiClient.addMockResponse({
  79. url: '/first/path/',
  80. method: 'GET',
  81. body: {
  82. detail: 'oops there was a problem 1',
  83. },
  84. statusCode: 400,
  85. });
  86. MockApiClient.addMockResponse({
  87. url: '/second/path/',
  88. method: 'GET',
  89. body: {
  90. detail: 'oops there was a problem 2',
  91. },
  92. statusCode: 400,
  93. });
  94. MockApiClient.addMockResponse({
  95. url: '/third/path/',
  96. method: 'GET',
  97. body: {
  98. detail: 'oops there was a different problem',
  99. },
  100. statusCode: 400,
  101. });
  102. memoryHistory.push('/unique-error');
  103. render(<App />);
  104. await waitFor(() => {
  105. expect(screen.getByTestId('loading-error')).toHaveTextContent(
  106. 'oops there was a problem 1'
  107. );
  108. });
  109. await waitFor(() => {
  110. expect(screen.getByTestId('loading-error')).toHaveTextContent(
  111. 'oops there was a problem 2'
  112. );
  113. });
  114. await waitFor(() => {
  115. expect(screen.getByTestId('loading-error')).toHaveTextContent(
  116. 'oops there was a different problem'
  117. );
  118. });
  119. });
  120. });
  121. describe('slow requests', () => {
  122. it('calls onLoadAllEndpointsSuccess when all endpoints have been loaded', async () => {
  123. const requestQueue: Array<() => void> = [];
  124. jest.spyOn(MockApiClient.prototype, 'request').mockImplementation((_, options) => {
  125. requestQueue.push(() => {
  126. return options?.success?.({message: 'good'});
  127. });
  128. });
  129. const mockOnAllEndpointsSuccess = jest.fn();
  130. function MultiRouteComponent() {
  131. const {remainingRequests} = useApiRequests({
  132. endpoints: [
  133. ['data', '/some/path/to/something/'],
  134. ['project', '/another/path/here'],
  135. ],
  136. onLoadAllEndpointsSuccess: mockOnAllEndpointsSuccess,
  137. });
  138. return <div data-test-id="remaining-requests">{remainingRequests}</div>;
  139. }
  140. const memoryHistory = createMemoryHistory();
  141. memoryHistory.push('/multi');
  142. function App() {
  143. return (
  144. <Router
  145. history={memoryHistory}
  146. render={props => {
  147. return (
  148. <RouteContext.Provider value={props}>
  149. <RouterContext {...props} />
  150. </RouteContext.Provider>
  151. );
  152. }}
  153. >
  154. <Route path="/multi" component={MultiRouteComponent} />
  155. </Router>
  156. );
  157. }
  158. render(<App />);
  159. expect(await screen.findByText('2')).toBeInTheDocument();
  160. const req1 = requestQueue.shift();
  161. req1?.();
  162. expect(await screen.findByText('1')).toBeInTheDocument();
  163. const req2 = requestQueue.shift();
  164. req2?.();
  165. expect(await screen.findByText('0')).toBeInTheDocument();
  166. expect(mockOnAllEndpointsSuccess).toHaveBeenCalled();
  167. jest.restoreAllMocks();
  168. });
  169. });
  170. });