useInitialTimeOffsetMs.spec.tsx 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. import {initializeOrg} from 'sentry-test/initializeOrg';
  2. import {reactHooks} from 'sentry-test/reactTestingLibrary';
  3. import fetchReplayClicks from 'sentry/utils/replays/fetchReplayClicks';
  4. import useInitialTimeOffsetMs from 'sentry/utils/replays/hooks/useInitialTimeOffsetMs';
  5. import {useLocation} from 'sentry/utils/useLocation';
  6. jest.mock('sentry/utils/useLocation');
  7. jest.mock('sentry/utils/replays/fetchReplayClicks');
  8. const MockUseLocation = useLocation as jest.MockedFunction<typeof useLocation>;
  9. const MockFetchReplayClicks = fetchReplayClicks as jest.MockedFunction<
  10. typeof fetchReplayClicks
  11. >;
  12. const {organization, project} = initializeOrg();
  13. const replay = TestStubs.ReplayRecord();
  14. const NOON = '2023-04-14T12:00:00';
  15. const FIVE_PAST_NOON = '2023-04-14T12:05:00';
  16. function mockQuery(query: Record<string, string>) {
  17. MockUseLocation.mockReturnValue({
  18. pathname: '',
  19. search: '',
  20. query,
  21. hash: '',
  22. state: undefined,
  23. action: 'PUSH',
  24. key: '',
  25. });
  26. }
  27. describe('useInitialTimeOffsetMs', () => {
  28. beforeEach(() => {
  29. MockUseLocation.mockClear();
  30. MockFetchReplayClicks.mockClear();
  31. });
  32. describe('fromOffset', () => {
  33. it('should return an offset, in ms, if `t` exists in the query', async () => {
  34. const offsetInSeconds = 23;
  35. mockQuery({t: String(offsetInSeconds)});
  36. const {result, waitForNextUpdate} = reactHooks.renderHook(useInitialTimeOffsetMs, {
  37. initialProps: {
  38. orgSlug: organization.slug,
  39. replaySlug: `${project.slug}:${replay.id}`,
  40. replayStartTimestampMs: undefined,
  41. },
  42. });
  43. await waitForNextUpdate();
  44. expect(result.current).toBe(23 * 1000);
  45. });
  46. it('should prefer reading `t` over the other qs params', async () => {
  47. const offsetInSeconds = 23;
  48. mockQuery({
  49. t: String(offsetInSeconds),
  50. event_t: FIVE_PAST_NOON,
  51. query: 'click.tag:button',
  52. });
  53. const {result, waitForNextUpdate} = reactHooks.renderHook(useInitialTimeOffsetMs, {
  54. initialProps: {
  55. orgSlug: organization.slug,
  56. replaySlug: `${project.slug}:${replay.id}`,
  57. replayStartTimestampMs: undefined,
  58. },
  59. });
  60. await waitForNextUpdate();
  61. expect(result.current).toBe(23 * 1000);
  62. expect(MockFetchReplayClicks).toHaveBeenCalledTimes(0);
  63. });
  64. });
  65. describe('fromEventTimestamp', () => {
  66. it('should calculate the difference between an event timestamp and the replay start timestamp', async () => {
  67. const noon = '2023-04-14T12:00:00';
  68. const fivePastNoon = '2023-04-14T12:05:00';
  69. mockQuery({event_t: fivePastNoon});
  70. const {result, waitForNextUpdate} = reactHooks.renderHook(useInitialTimeOffsetMs, {
  71. initialProps: {
  72. orgSlug: organization.slug,
  73. replaySlug: `${project.slug}:${replay.id}`,
  74. replayStartTimestampMs: new Date(noon).getTime(),
  75. },
  76. });
  77. await waitForNextUpdate();
  78. // Expecting 5 minutes difference, in ms
  79. expect(result.current).toBe(5 * 60 * 1000);
  80. });
  81. it('should return 0 offset if there is no replayStartTimetsamp, then recalculate when the startTimestamp appears', async () => {
  82. mockQuery({event_t: FIVE_PAST_NOON});
  83. const {result, rerender, waitForNextUpdate} = reactHooks.renderHook(
  84. useInitialTimeOffsetMs,
  85. {
  86. initialProps: {
  87. orgSlug: organization.slug,
  88. replaySlug: `${project.slug}:${replay.id}`,
  89. replayStartTimestampMs: undefined,
  90. },
  91. }
  92. );
  93. await waitForNextUpdate();
  94. expect(result.current).toBe(0);
  95. rerender({
  96. orgSlug: organization.slug,
  97. replaySlug: `${project.slug}:${replay.id}`,
  98. replayStartTimestampMs: new Date(NOON).getTime(),
  99. });
  100. await waitForNextUpdate();
  101. // Expecting 5 minutes difference, in ms
  102. expect(result.current).toBe(5 * 60 * 1000);
  103. });
  104. it('should prefer reading `event_t` over the other search query params', async () => {
  105. mockQuery({
  106. event_t: FIVE_PAST_NOON,
  107. query: 'click.tag:button',
  108. });
  109. MockFetchReplayClicks.mockResolvedValue({
  110. fetchError: undefined,
  111. pageLinks: '',
  112. clicks: [],
  113. });
  114. const {result, waitForNextUpdate} = reactHooks.renderHook(useInitialTimeOffsetMs, {
  115. initialProps: {
  116. orgSlug: organization.slug,
  117. replaySlug: `${project.slug}:${replay.id}`,
  118. replayStartTimestampMs: new Date(NOON).getTime(),
  119. },
  120. });
  121. await waitForNextUpdate();
  122. expect(result.current).toBe(5 * 60 * 1000);
  123. expect(MockFetchReplayClicks).toHaveBeenCalledTimes(0);
  124. });
  125. });
  126. describe('fromListPageQuery', () => {
  127. it('should skip this strategy if there is no `click.*` term in the query', async () => {
  128. mockQuery({query: 'user.email:*@sentry.io'});
  129. const {result, waitForNextUpdate} = reactHooks.renderHook(useInitialTimeOffsetMs, {
  130. initialProps: {
  131. orgSlug: organization.slug,
  132. replaySlug: `${project.slug}:${replay.id}`,
  133. replayStartTimestampMs: new Date(NOON).getTime(),
  134. },
  135. });
  136. await waitForNextUpdate();
  137. expect(MockFetchReplayClicks).toHaveBeenCalledTimes(0);
  138. expect(result.current).toBe(0);
  139. });
  140. it('should request a list of click results, and calculate the offset from the first result', async () => {
  141. mockQuery({query: 'click.tag:button'});
  142. MockFetchReplayClicks.mockResolvedValue({
  143. fetchError: undefined,
  144. pageLinks: '',
  145. clicks: [{node_id: 7, timestamp: FIVE_PAST_NOON}],
  146. });
  147. const {result, waitForNextUpdate} = reactHooks.renderHook(useInitialTimeOffsetMs, {
  148. initialProps: {
  149. orgSlug: organization.slug,
  150. replaySlug: `${project.slug}:${replay.id}`,
  151. replayStartTimestampMs: new Date(NOON).getTime(),
  152. },
  153. });
  154. await waitForNextUpdate();
  155. expect(MockFetchReplayClicks).toHaveBeenCalledTimes(1);
  156. // Expecting 5 minutes difference, in ms
  157. expect(result.current).toBe(5 * 60 * 1000);
  158. });
  159. it('should not call call fetch twice when props change', async () => {
  160. mockQuery({query: 'click.tag:button'});
  161. MockFetchReplayClicks.mockResolvedValue({
  162. fetchError: undefined,
  163. pageLinks: '',
  164. clicks: [{node_id: 7, timestamp: FIVE_PAST_NOON}],
  165. });
  166. const {result, rerender, waitForNextUpdate} = reactHooks.renderHook(
  167. useInitialTimeOffsetMs,
  168. {
  169. initialProps: {
  170. orgSlug: organization.slug,
  171. replaySlug: `${project.slug}:${replay.id}`,
  172. replayStartTimestampMs: undefined,
  173. },
  174. }
  175. );
  176. await waitForNextUpdate();
  177. expect(MockFetchReplayClicks).toHaveBeenCalledTimes(0);
  178. expect(result.current).toBe(0);
  179. rerender({
  180. orgSlug: organization.slug,
  181. replaySlug: `${project.slug}:${replay.id}`,
  182. replayStartTimestampMs: new Date(NOON).getTime(),
  183. });
  184. await waitForNextUpdate();
  185. expect(MockFetchReplayClicks).toHaveBeenCalledTimes(1);
  186. expect(result.current).toBe(5 * 60 * 1000);
  187. });
  188. });
  189. });