contextPickerModal.spec.jsx 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. import React from 'react';
  2. import {mountWithTheme} from 'sentry-test/enzyme';
  3. import {selectByValue} from 'sentry-test/select-new';
  4. import ContextPickerModal from 'app/components/contextPickerModal';
  5. import OrganizationsStore from 'app/stores/organizationsStore';
  6. import OrganizationStore from 'app/stores/organizationStore';
  7. import ProjectsStore from 'app/stores/projectsStore';
  8. describe('ContextPickerModal', function () {
  9. let project, project2, project4, org, org2;
  10. const onFinish = jest.fn();
  11. beforeEach(function () {
  12. ProjectsStore.reset();
  13. MockApiClient.clearMockResponses();
  14. onFinish.mockReset();
  15. project = TestStubs.Project();
  16. org = TestStubs.Organization({projects: [project]});
  17. project2 = TestStubs.Project({slug: 'project2'});
  18. org2 = TestStubs.Organization({
  19. slug: 'org2',
  20. id: '21',
  21. });
  22. project4 = TestStubs.Project({slug: 'project4', isMember: false});
  23. });
  24. afterEach(async function () {
  25. OrganizationsStore.load([]);
  26. OrganizationStore.reset();
  27. await tick();
  28. });
  29. const getComponent = props => (
  30. <ContextPickerModal
  31. Header={() => <div />}
  32. Body="div"
  33. nextPath="/test/:orgId/path/"
  34. organizations={[org, org2]}
  35. needOrg
  36. onFinish={onFinish}
  37. {...props}
  38. />
  39. );
  40. it('renders with only org selector when no org is selected', async function () {
  41. const wrapper = mountWithTheme(getComponent());
  42. expect(wrapper.find('StyledSelectControl[name="organization"]').exists()).toBe(true);
  43. expect(wrapper.find('StyledSelectControl[name="project"]').exists()).toBe(false);
  44. await tick();
  45. wrapper.unmount();
  46. });
  47. it('calls onFinish, if project id is not needed, and only 1 org', async function () {
  48. OrganizationsStore.load([org2]);
  49. OrganizationStore.onUpdate(org2);
  50. MockApiClient.addMockResponse({
  51. url: `/organizations/${org2.slug}/projects/`,
  52. body: [],
  53. });
  54. const wrapper = mountWithTheme(getComponent(), TestStubs.routerContext());
  55. expect(onFinish).toHaveBeenCalledWith('/test/org2/path/');
  56. await tick();
  57. wrapper.unmount();
  58. });
  59. it('calls onFinish if there is only 1 org and 1 project', async function () {
  60. expect(onFinish).not.toHaveBeenCalled();
  61. OrganizationsStore.load([org2]);
  62. OrganizationStore.onUpdate(org2);
  63. const fetchProjectsForOrg = MockApiClient.addMockResponse({
  64. url: `/organizations/${org2.slug}/projects/`,
  65. body: [project2],
  66. });
  67. const wrapper = mountWithTheme(
  68. getComponent({
  69. needOrg: true,
  70. needProject: true,
  71. nextPath: '/test/:orgId/path/:projectId/',
  72. }),
  73. TestStubs.routerContext()
  74. );
  75. expect(fetchProjectsForOrg).toHaveBeenCalled();
  76. expect(onFinish).not.toHaveBeenCalled();
  77. await tick();
  78. wrapper.update();
  79. expect(onFinish).toHaveBeenLastCalledWith('/test/org2/path/project2/');
  80. await tick();
  81. wrapper.unmount();
  82. });
  83. it('selects an org and calls `onFinish` with URL with organization slug', async function () {
  84. OrganizationsStore.load([org]);
  85. const wrapper = mountWithTheme(getComponent({}), TestStubs.routerContext());
  86. MockApiClient.addMockResponse({
  87. url: `/organizations/${org.slug}/projects/`,
  88. body: [],
  89. });
  90. selectByValue(wrapper, 'org-slug', {control: true});
  91. await tick();
  92. wrapper.update();
  93. expect(onFinish).toHaveBeenCalledWith('/test/org-slug/path/');
  94. await tick();
  95. wrapper.unmount();
  96. });
  97. it('renders with project selector and org selector selected when org is already selected', async function () {
  98. OrganizationStore.onUpdate(org);
  99. const fetchProjectsForOrg = MockApiClient.addMockResponse({
  100. url: `/organizations/${org.slug}/projects/`,
  101. body: [project, project2, project4],
  102. });
  103. await tick();
  104. const wrapper = mountWithTheme(
  105. getComponent({
  106. needOrg: true,
  107. needProject: true,
  108. })
  109. );
  110. await tick();
  111. wrapper.update();
  112. expect(fetchProjectsForOrg).toHaveBeenCalled();
  113. // Default to org in latest context
  114. expect(wrapper.find('StyledSelectControl[name="organization"]').prop('value')).toBe(
  115. org.slug
  116. );
  117. expect(wrapper.find('StyledSelectControl[name="project"]').prop('options')).toEqual([
  118. {
  119. label: 'Projects I belong to',
  120. options: [
  121. {
  122. value: project.slug,
  123. label: project.slug,
  124. isDisabled: false,
  125. },
  126. {
  127. value: project2.slug,
  128. label: project2.slug,
  129. isDisabled: false,
  130. },
  131. ],
  132. },
  133. {
  134. label: "Projects I don't belong to",
  135. options: [
  136. {
  137. value: project4.slug,
  138. label: project4.slug,
  139. isDisabled: true,
  140. },
  141. ],
  142. },
  143. ]);
  144. await tick();
  145. wrapper.unmount();
  146. });
  147. it('can select org and project', async function () {
  148. const organizations = [
  149. {
  150. ...org,
  151. projects: [project],
  152. },
  153. {
  154. ...org2,
  155. projects: [project2, TestStubs.Project({slug: 'project3'})],
  156. },
  157. ];
  158. const fetchProjectsForOrg = MockApiClient.addMockResponse({
  159. url: `/organizations/${org2.slug}/projects/`,
  160. body: organizations[1].projects,
  161. });
  162. OrganizationsStore.load(organizations);
  163. const wrapper = mountWithTheme(
  164. getComponent({
  165. needOrg: true,
  166. needProject: true,
  167. nextPath: '/test/:orgId/path/:projectId/',
  168. organizations,
  169. }),
  170. TestStubs.routerContext()
  171. );
  172. await tick();
  173. wrapper.update();
  174. // Should not have anything selected
  175. expect(
  176. wrapper.find('StyledSelectControl[name="organization"]').prop('value')
  177. ).toBeUndefined();
  178. // Select org2
  179. selectByValue(wrapper, org2.slug, {control: true});
  180. await tick();
  181. wrapper.update();
  182. // <Projects> will fetch projects for org2
  183. expect(fetchProjectsForOrg).toHaveBeenCalled();
  184. expect(wrapper.find('StyledSelectControl[name="project"]').prop('options')).toEqual([
  185. {
  186. label: 'Projects I belong to',
  187. options: [
  188. {
  189. value: project2.slug,
  190. label: project2.slug,
  191. isDisabled: false,
  192. },
  193. {
  194. value: 'project3',
  195. label: 'project3',
  196. isDisabled: false,
  197. },
  198. ],
  199. },
  200. {
  201. label: "Projects I don't belong to",
  202. options: [],
  203. },
  204. ]);
  205. // Select project3
  206. selectByValue(wrapper, 'project3', {control: true, name: 'project'});
  207. expect(onFinish).toHaveBeenCalledWith('/test/org2/path/project3/');
  208. await tick();
  209. wrapper.unmount();
  210. });
  211. });