globalSelectionHeader.spec.jsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. import React from 'react';
  2. import {initializeOrg} from 'app-test/helpers/initializeOrg';
  3. import {mount} from 'enzyme';
  4. import GlobalSelectionHeader from 'app/components/organizations/globalSelectionHeader';
  5. import GlobalSelectionStore from 'app/stores/globalSelectionStore';
  6. import * as globalActions from 'app/actionCreators/globalSelection';
  7. import ProjectsStore from 'app/stores/projectsStore';
  8. import ConfigStore from 'app/stores/configStore';
  9. const changeQuery = (routerContext, query) => ({
  10. ...routerContext,
  11. context: {
  12. ...routerContext.context,
  13. router: {
  14. ...routerContext.context.router,
  15. location: {
  16. query,
  17. },
  18. },
  19. },
  20. });
  21. describe('GlobalSelectionHeader', function() {
  22. const {organization, router, routerContext} = initializeOrg({
  23. organization: TestStubs.Organization({features: ['global-views']}),
  24. router: {
  25. location: {query: {}},
  26. },
  27. });
  28. beforeAll(function() {
  29. jest.spyOn(globalActions, 'updateDateTime');
  30. jest.spyOn(globalActions, 'updateEnvironments');
  31. jest.spyOn(globalActions, 'updateProjects');
  32. });
  33. beforeEach(function() {
  34. GlobalSelectionStore.reset();
  35. [
  36. globalActions.updateDateTime,
  37. globalActions.updateProjects,
  38. globalActions.updateEnvironments,
  39. router.push,
  40. router.replace,
  41. ].forEach(mock => mock.mockClear());
  42. });
  43. it('does not update router if there is custom routing', function() {
  44. mount(
  45. <GlobalSelectionHeader organization={organization} hasCustomRouting />,
  46. routerContext
  47. );
  48. expect(router.push).not.toHaveBeenCalled();
  49. });
  50. it('does not update router if org in URL params is different than org in context/props', function() {
  51. mount(<GlobalSelectionHeader organization={organization} hasCustomRouting />, {
  52. ...routerContext,
  53. context: {
  54. ...routerContext.context,
  55. router: {...routerContext.context.router, params: {orgId: 'diff-org'}},
  56. },
  57. });
  58. expect(router.push).not.toHaveBeenCalled();
  59. });
  60. it('does not replace URL with values from store when mounted with no query params', function() {
  61. mount(<GlobalSelectionHeader organization={organization} />, routerContext);
  62. expect(router.replace).not.toHaveBeenCalled();
  63. });
  64. it('only updates GlobalSelection store when mounted with query params', async function() {
  65. mount(
  66. <GlobalSelectionHeader organization={organization} />,
  67. changeQuery(routerContext, {
  68. statsPeriod: '7d',
  69. })
  70. );
  71. expect(router.push).not.toHaveBeenCalled();
  72. expect(globalActions.updateDateTime).toHaveBeenCalledWith({
  73. period: '7d',
  74. utc: null,
  75. start: null,
  76. end: null,
  77. });
  78. expect(globalActions.updateProjects).toHaveBeenCalledWith([]);
  79. expect(globalActions.updateEnvironments).toHaveBeenCalledWith([]);
  80. await tick();
  81. expect(GlobalSelectionStore.get()).toEqual({
  82. datetime: {
  83. period: '7d',
  84. utc: null,
  85. start: null,
  86. end: null,
  87. },
  88. environments: [],
  89. projects: [],
  90. });
  91. });
  92. it('updates GlobalSelection store when re-rendered with different query params', async function() {
  93. const wrapper = mount(
  94. <GlobalSelectionHeader organization={organization} />,
  95. changeQuery(routerContext, {
  96. statsPeriod: '7d',
  97. })
  98. );
  99. wrapper.setContext(
  100. changeQuery(routerContext, {
  101. statsPeriod: '21d',
  102. }).context
  103. );
  104. await tick();
  105. wrapper.update();
  106. expect(globalActions.updateDateTime).toHaveBeenCalledWith({
  107. period: '21d',
  108. utc: null,
  109. start: null,
  110. end: null,
  111. });
  112. expect(globalActions.updateProjects).toHaveBeenCalledWith([]);
  113. expect(globalActions.updateEnvironments).toHaveBeenCalledWith([]);
  114. expect(GlobalSelectionStore.get()).toEqual({
  115. datetime: {
  116. period: '21d',
  117. utc: null,
  118. start: null,
  119. end: null,
  120. },
  121. environments: [],
  122. projects: [],
  123. });
  124. });
  125. it('updates GlobalSelection store with default period', async function() {
  126. mount(
  127. <GlobalSelectionHeader organization={organization} />,
  128. changeQuery(routerContext, {
  129. environment: 'prod',
  130. })
  131. );
  132. expect(router.push).not.toHaveBeenCalled();
  133. expect(globalActions.updateDateTime).toHaveBeenCalledWith({
  134. period: '14d',
  135. utc: null,
  136. start: null,
  137. end: null,
  138. });
  139. expect(globalActions.updateProjects).toHaveBeenCalledWith([]);
  140. expect(globalActions.updateEnvironments).toHaveBeenCalledWith(['prod']);
  141. await tick();
  142. expect(GlobalSelectionStore.get()).toEqual({
  143. datetime: {
  144. period: '14d',
  145. utc: null,
  146. start: null,
  147. end: null,
  148. },
  149. environments: ['prod'],
  150. projects: [],
  151. });
  152. });
  153. it('updates GlobalSelection store with empty date selections', async function() {
  154. const wrapper = mount(
  155. <GlobalSelectionHeader organization={organization} />,
  156. changeQuery(routerContext, {
  157. statsPeriod: '7d',
  158. })
  159. );
  160. wrapper.setContext(
  161. changeQuery(routerContext, {
  162. statsPeriod: null,
  163. }).context
  164. );
  165. await tick();
  166. wrapper.update();
  167. expect(globalActions.updateDateTime).toHaveBeenCalledWith({
  168. period: null,
  169. utc: null,
  170. start: null,
  171. end: null,
  172. });
  173. expect(globalActions.updateProjects).toHaveBeenCalledWith([]);
  174. expect(globalActions.updateEnvironments).toHaveBeenCalledWith([]);
  175. expect(GlobalSelectionStore.get()).toEqual({
  176. datetime: {
  177. period: null,
  178. utc: null,
  179. start: null,
  180. end: null,
  181. },
  182. environments: [],
  183. projects: [],
  184. });
  185. });
  186. it('does not update store if url params have not changed', async function() {
  187. const wrapper = mount(
  188. <GlobalSelectionHeader organization={organization} />,
  189. changeQuery(routerContext, {
  190. statsPeriod: '7d',
  191. })
  192. );
  193. [
  194. globalActions.updateDateTime,
  195. globalActions.updateProjects,
  196. globalActions.updateEnvironments,
  197. ].forEach(mock => mock.mockClear());
  198. wrapper.setContext(
  199. changeQuery(routerContext, {
  200. statsPeriod: '7d',
  201. }).context
  202. );
  203. await tick();
  204. wrapper.update();
  205. expect(globalActions.updateDateTime).not.toHaveBeenCalled();
  206. expect(globalActions.updateProjects).not.toHaveBeenCalled();
  207. expect(globalActions.updateEnvironments).not.toHaveBeenCalled();
  208. expect(GlobalSelectionStore.get()).toEqual({
  209. datetime: {
  210. period: '7d',
  211. utc: null,
  212. start: null,
  213. end: null,
  214. },
  215. environments: [],
  216. projects: [],
  217. });
  218. });
  219. describe('Single project selection mode', function() {
  220. it('selects first project if more than one is requested', function() {
  221. const initializationObj = initializeOrg({
  222. router: {
  223. params: {orgId: 'org-slug'}, // we need this to be set to make sure org in context is same as current org in URL
  224. location: {query: {project: [1, 2]}},
  225. },
  226. });
  227. mount(
  228. <GlobalSelectionHeader organization={initializationObj.organization} />,
  229. initializationObj.routerContext
  230. );
  231. expect(globalActions.updateProjects).toHaveBeenCalledWith([1]);
  232. });
  233. it('selects first project if none (i.e. all) is requested', function() {
  234. const project = TestStubs.Project({id: '3'});
  235. const org = TestStubs.Organization({projects: [project]});
  236. ProjectsStore.loadInitialData(org.projects);
  237. const initializationObj = initializeOrg({
  238. organization: org,
  239. router: {
  240. params: {orgId: 'org-slug'},
  241. location: {query: {}},
  242. },
  243. });
  244. mount(
  245. <GlobalSelectionHeader organization={initializationObj.organization} />,
  246. initializationObj.routerContext
  247. );
  248. expect(globalActions.updateProjects).toHaveBeenCalledWith([3]);
  249. });
  250. });
  251. describe('forceProject selection mode', function() {
  252. const initialData = initializeOrg({
  253. organization: {features: ['global-views']},
  254. projects: [
  255. {id: 1, slug: 'staging-project', environments: ['staging']},
  256. {id: 2, slug: 'prod-project', environments: ['prod']},
  257. ],
  258. router: {
  259. location: {query: {}},
  260. },
  261. });
  262. const wrapper = mount(
  263. <GlobalSelectionHeader
  264. organization={initialData.organization}
  265. forceProject={initialData.organization.projects[0]}
  266. />,
  267. initialData.routerContext
  268. );
  269. it('renders a back button to the forced project', function() {
  270. const back = wrapper.find('BackButtonWrapper');
  271. expect(back).toHaveLength(1);
  272. });
  273. it('renders only environments from the forced project', async function() {
  274. await wrapper.find('MultipleEnvironmentSelector HeaderItem').simulate('click');
  275. await wrapper.update();
  276. const items = wrapper.find('MultipleEnvironmentSelector EnvironmentSelectorItem');
  277. expect(items.length).toEqual(1);
  278. expect(items.at(0).text()).toBe('staging');
  279. });
  280. });
  281. describe('projects list', function() {
  282. let wrapper, memberProject, nonMemberProject, initialData;
  283. beforeEach(function() {
  284. memberProject = TestStubs.Project({id: '3', isMember: true});
  285. nonMemberProject = TestStubs.Project({id: '4', isMember: false});
  286. const org = TestStubs.Organization({projects: [memberProject, nonMemberProject]});
  287. ProjectsStore.loadInitialData(org.projects);
  288. initialData = initializeOrg({
  289. organization: org,
  290. router: {
  291. location: {query: {}},
  292. },
  293. });
  294. wrapper = mount(
  295. <GlobalSelectionHeader organization={initialData.organization} />,
  296. initialData.routerContext
  297. );
  298. });
  299. it('gets member projects', function() {
  300. expect(wrapper.find('MultipleProjectSelector').prop('projects')).toEqual([
  301. memberProject,
  302. ]);
  303. });
  304. it('gets all projects if superuser', function() {
  305. ConfigStore.config = {
  306. user: {
  307. isSuperuser: true,
  308. },
  309. };
  310. wrapper = mount(
  311. <GlobalSelectionHeader organization={initialData.organization} />,
  312. initialData.routerContext
  313. );
  314. expect(wrapper.find('MultipleProjectSelector').prop('projects')).toEqual([
  315. memberProject,
  316. ]);
  317. expect(wrapper.find('MultipleProjectSelector').prop('nonMemberProjects')).toEqual([
  318. nonMemberProject,
  319. ]);
  320. });
  321. });
  322. });