useRouteActivatedHook.spec.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. import {LocationFixture} from 'sentry-fixture/locationFixture';
  2. import {OrganizationFixture} from 'sentry-fixture/organization';
  3. import {RouterPropsFixture} from 'sentry-fixture/routerPropsFixture';
  4. import {ProjectFixture} from 'getsentry-test/fixtures/project';
  5. import {SubscriptionFixture} from 'getsentry-test/fixtures/subscription';
  6. import {act, renderHook} from 'sentry-test/reactTestingLibrary';
  7. import useRouteActivatedHook, {
  8. DELAY_TIME_MS,
  9. } from 'getsentry/hooks/useRouteActivatedHook';
  10. import SubscriptionStore from 'getsentry/stores/subscriptionStore';
  11. import rawTrackAnalyticsEvent from 'getsentry/utils/rawTrackAnalyticsEvent';
  12. const DEFAULT_ADVANCE_PERIOD = DELAY_TIME_MS * 1.2;
  13. const HALF_ADVANCE_PERIOD = DELAY_TIME_MS * 0.6;
  14. jest.mock('getsentry/utils/rawTrackAnalyticsEvent');
  15. describe('useRouteActivatedHook', function () {
  16. const organization = OrganizationFixture();
  17. const project = ProjectFixture({organization});
  18. const subscription = SubscriptionFixture({
  19. organization,
  20. });
  21. function genProps(extraRouteParams = {}): any {
  22. const props = {
  23. organization,
  24. ...RouterPropsFixture({
  25. location: LocationFixture({
  26. pathname: `/settings/${organization.slug}/${project.slug}/`,
  27. }),
  28. params: {orgId: organization.slug},
  29. routes: [
  30. {path: '/'},
  31. {path: '/settings/'},
  32. {path: ':orgId/'},
  33. {path: 'projects/:projectId/'},
  34. ],
  35. }),
  36. ...extraRouteParams,
  37. };
  38. return props;
  39. }
  40. const props = genProps();
  41. beforeEach(function () {
  42. SubscriptionStore.set(organization.slug, subscription);
  43. });
  44. afterEach(function () {
  45. (rawTrackAnalyticsEvent as jest.Mock).mockClear();
  46. });
  47. it('calls rawTrackAnalyticsEvent after one seconds if org is set', function () {
  48. jest.useFakeTimers();
  49. const {result} = renderHook(useRouteActivatedHook, {
  50. initialProps: props,
  51. });
  52. act(() => result.current.setOrganization(organization));
  53. const loadTime = Date.now();
  54. act(() => jest.advanceTimersByTime(HALF_ADVANCE_PERIOD));
  55. expect(rawTrackAnalyticsEvent).toHaveBeenCalledTimes(0);
  56. act(() => jest.advanceTimersByTime(HALF_ADVANCE_PERIOD));
  57. expect(rawTrackAnalyticsEvent).toHaveBeenCalledWith(
  58. {
  59. eventName: 'Page View: Settings :OrgId Projects :ProjectId',
  60. eventKey: 'page_view.settings.:org_id.projects.:project_id',
  61. parameterized_path: 'settings.:org_id.projects.:project_id',
  62. organization: expect.objectContaining(organization),
  63. subscription: expect.objectContaining(subscription),
  64. url: `http://localhost/settings/${organization.slug}/${project.slug}/`,
  65. },
  66. {time: loadTime}
  67. );
  68. expect(rawTrackAnalyticsEvent).toHaveBeenCalledTimes(1);
  69. });
  70. it('does not call rawTrackAnalyticsEvent if org is not set', function () {
  71. jest.useFakeTimers();
  72. renderHook(useRouteActivatedHook, {
  73. initialProps: props,
  74. });
  75. act(() => jest.advanceTimersByTime(DEFAULT_ADVANCE_PERIOD));
  76. expect(rawTrackAnalyticsEvent).toHaveBeenCalledTimes(0);
  77. });
  78. it('only calls rawTrackAnalyticsEvent once and ignores later param updates', function () {
  79. jest.useFakeTimers();
  80. const {result} = renderHook(useRouteActivatedHook, {
  81. initialProps: props,
  82. });
  83. const loadTime = Date.now();
  84. act(() => result.current.setOrganization(organization));
  85. act(() => result.current.setRouteAnalyticsParams({foo: 'bar'}));
  86. act(() => jest.advanceTimersByTime(DEFAULT_ADVANCE_PERIOD));
  87. act(() => result.current.setRouteAnalyticsParams({field: 'value', foo: 'baz'}));
  88. expect(rawTrackAnalyticsEvent).toHaveBeenCalledWith(
  89. {
  90. eventName: 'Page View: Settings :OrgId Projects :ProjectId',
  91. eventKey: 'page_view.settings.:org_id.projects.:project_id',
  92. parameterized_path: 'settings.:org_id.projects.:project_id',
  93. organization: expect.objectContaining(organization),
  94. subscription: expect.objectContaining(subscription),
  95. url: `http://localhost/settings/${organization.slug}/${project.slug}/`,
  96. foo: 'bar',
  97. },
  98. {time: loadTime}
  99. );
  100. expect(rawTrackAnalyticsEvent).toHaveBeenCalledTimes(1);
  101. });
  102. it('only calls rawTrackAnalyticsEvent once when URL query params are updated', function () {
  103. jest.useFakeTimers();
  104. const {result, rerender} = renderHook(useRouteActivatedHook, {
  105. initialProps: props,
  106. });
  107. act(() => result.current.setOrganization(organization));
  108. act(() =>
  109. rerender(
  110. genProps({
  111. location: LocationFixture({
  112. pathname: `/settings/${organization.slug}/${project.slug}/`,
  113. search: '?foo=bar',
  114. }),
  115. })
  116. )
  117. );
  118. act(() => jest.advanceTimersByTime(DEFAULT_ADVANCE_PERIOD));
  119. expect(rawTrackAnalyticsEvent).toHaveBeenCalledTimes(1);
  120. });
  121. it('disable route analytics', function () {
  122. jest.useFakeTimers();
  123. const {result} = renderHook(useRouteActivatedHook, {
  124. initialProps: props,
  125. });
  126. act(() => result.current.setOrganization(organization));
  127. act(() => result.current.setDisableRouteAnalytics());
  128. act(() => jest.advanceTimersByTime(DEFAULT_ADVANCE_PERIOD));
  129. expect(rawTrackAnalyticsEvent).toHaveBeenCalledTimes(0);
  130. });
  131. it('disables and re-enables analytics', function () {
  132. jest.useFakeTimers();
  133. const {result} = renderHook(useRouteActivatedHook, {
  134. initialProps: props,
  135. });
  136. act(() => result.current.setOrganization(organization));
  137. act(() => result.current.setDisableRouteAnalytics(true));
  138. act(() => jest.advanceTimersByTime(DEFAULT_ADVANCE_PERIOD));
  139. expect(rawTrackAnalyticsEvent).toHaveBeenCalledTimes(0);
  140. act(() => result.current.setDisableRouteAnalytics(false));
  141. act(() => jest.advanceTimersByTime(DEFAULT_ADVANCE_PERIOD));
  142. expect(rawTrackAnalyticsEvent).toHaveBeenCalledTimes(1);
  143. });
  144. it('re-initializes after route changes', function () {
  145. jest.useFakeTimers();
  146. const {result, rerender} = renderHook(useRouteActivatedHook, {
  147. initialProps: props,
  148. });
  149. act(() => result.current.setOrganization(organization));
  150. // set every field to make sure we reset them
  151. act(() => result.current.setDisableRouteAnalytics());
  152. act(() => result.current.setEventNames('foo', 'bar'));
  153. act(() => result.current.setRouteAnalyticsParams({ignore: 'yes'}));
  154. act(() => jest.advanceTimersByTime(DEFAULT_ADVANCE_PERIOD));
  155. expect(rawTrackAnalyticsEvent).toHaveBeenCalledTimes(0);
  156. const newProps = genProps({
  157. location: LocationFixture({
  158. pathname: `/organizations/${organization.slug}/releases/some-release/`,
  159. }),
  160. routes: [
  161. {path: '/'},
  162. {path: '/organizations/:orgId/releases/'},
  163. {path: ':release/'},
  164. ],
  165. ...LocationFixture({
  166. pathname: `/organizations/${organization.slug}/releases/some-release/`,
  167. }),
  168. });
  169. const loadTime = Date.now();
  170. act(() => rerender(newProps));
  171. act(() => jest.advanceTimersByTime(DEFAULT_ADVANCE_PERIOD));
  172. expect(rawTrackAnalyticsEvent).toHaveBeenCalledWith(
  173. {
  174. eventName: 'Page View: Organizations :OrgId Releases :Release',
  175. eventKey: 'page_view.organizations.:org_id.releases.:release',
  176. parameterized_path: 'organizations.:org_id.releases.:release',
  177. organization: expect.objectContaining(organization),
  178. subscription: expect.objectContaining(subscription),
  179. url: `http://localhost/organizations/${organization.slug}/releases/some-release/`,
  180. },
  181. {time: loadTime}
  182. );
  183. expect(rawTrackAnalyticsEvent).toHaveBeenCalledTimes(1);
  184. });
  185. it('overrwite event names', function () {
  186. jest.useFakeTimers();
  187. const {result} = renderHook(useRouteActivatedHook, {
  188. initialProps: props,
  189. });
  190. act(() => result.current.setOrganization(organization));
  191. act(() => result.current.setEventNames('test.event', 'Test Event'));
  192. const loadTime = Date.now();
  193. act(() => jest.advanceTimersByTime(DEFAULT_ADVANCE_PERIOD));
  194. expect(rawTrackAnalyticsEvent).toHaveBeenCalledWith(
  195. {
  196. eventName: 'Test Event',
  197. eventKey: 'test.event',
  198. parameterized_path: 'settings.:org_id.projects.:project_id',
  199. organization: expect.objectContaining(organization),
  200. subscription: expect.objectContaining(subscription),
  201. url: `http://localhost/settings/${organization.slug}/${project.slug}/`,
  202. },
  203. {time: loadTime}
  204. );
  205. expect(rawTrackAnalyticsEvent).toHaveBeenCalledTimes(1);
  206. });
  207. it('route changes triggers early analytics event', function () {
  208. jest.useFakeTimers();
  209. let loadTime = Date.now();
  210. const {result, rerender} = renderHook(useRouteActivatedHook, {
  211. initialProps: props,
  212. });
  213. act(() => result.current.setOrganization(organization));
  214. act(() => jest.advanceTimersByTime(HALF_ADVANCE_PERIOD));
  215. const newProps = genProps({
  216. location: LocationFixture({
  217. pathname: `/organizations/${organization.slug}/releases/some-release/`,
  218. }),
  219. routes: [
  220. {path: '/'},
  221. {path: '/organizations/:orgId/releases/'},
  222. {path: ':release/'},
  223. ],
  224. ...LocationFixture({
  225. pathname: `/organizations/${organization.slug}/releases/some-release/`,
  226. }),
  227. });
  228. expect(rawTrackAnalyticsEvent).toHaveBeenCalledTimes(0);
  229. // emit the first event from the previous page
  230. act(() => rerender(newProps));
  231. expect(rawTrackAnalyticsEvent).toHaveBeenCalledWith(
  232. {
  233. eventName: 'Page View: Settings :OrgId Projects :ProjectId',
  234. eventKey: 'page_view.settings.:org_id.projects.:project_id',
  235. parameterized_path: 'settings.:org_id.projects.:project_id',
  236. organization: expect.objectContaining(organization),
  237. subscription: expect.objectContaining(subscription),
  238. url: `http://localhost/settings/${organization.slug}/${project.slug}/`,
  239. },
  240. {time: loadTime}
  241. );
  242. expect(rawTrackAnalyticsEvent).toHaveBeenCalledTimes(1);
  243. loadTime = Date.now();
  244. // should emit the second event now
  245. act(() => jest.advanceTimersByTime(DEFAULT_ADVANCE_PERIOD));
  246. expect(rawTrackAnalyticsEvent).toHaveBeenCalledWith(
  247. {
  248. eventName: 'Page View: Organizations :OrgId Releases :Release',
  249. eventKey: 'page_view.organizations.:org_id.releases.:release',
  250. parameterized_path: 'organizations.:org_id.releases.:release',
  251. organization: expect.objectContaining(organization),
  252. subscription: expect.objectContaining(subscription),
  253. url: `http://localhost/organizations/${organization.slug}/releases/some-release/`,
  254. },
  255. {time: loadTime}
  256. );
  257. expect(rawTrackAnalyticsEvent).toHaveBeenCalledTimes(2);
  258. });
  259. });