contextPickerModal.spec.jsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. import {mountWithTheme} from 'sentry-test/enzyme';
  2. import {act} from 'sentry-test/reactTestingLibrary';
  3. import {selectByValue} from 'sentry-test/select-new';
  4. import ContextPickerModal from 'sentry/components/contextPickerModal';
  5. import ConfigStore from 'sentry/stores/configStore';
  6. import OrganizationsStore from 'sentry/stores/organizationsStore';
  7. import OrganizationStore from 'sentry/stores/organizationStore';
  8. import ProjectsStore from 'sentry/stores/projectsStore';
  9. describe('ContextPickerModal', function () {
  10. let project, project2, project4, org, org2;
  11. const onFinish = jest.fn();
  12. beforeEach(function () {
  13. act(() => ProjectsStore.reset());
  14. MockApiClient.clearMockResponses();
  15. onFinish.mockReset();
  16. project = TestStubs.Project();
  17. org = TestStubs.Organization({projects: [project]});
  18. project2 = TestStubs.Project({slug: 'project2'});
  19. org2 = TestStubs.Organization({
  20. slug: 'org2',
  21. id: '21',
  22. });
  23. project4 = TestStubs.Project({slug: 'project4', isMember: false});
  24. });
  25. afterEach(async function () {
  26. act(() => OrganizationsStore.load([]));
  27. act(() => OrganizationStore.reset());
  28. await act(tick);
  29. });
  30. const getComponent = props => (
  31. <ContextPickerModal
  32. Header={() => <div />}
  33. Body="div"
  34. nextPath="/test/:orgId/path/"
  35. organizations={[org, org2]}
  36. needOrg
  37. onFinish={onFinish}
  38. {...props}
  39. />
  40. );
  41. it('renders with only org selector when no org is selected', async function () {
  42. const wrapper = mountWithTheme(getComponent());
  43. expect(wrapper.find('StyledSelectControl[name="organization"]').exists()).toBe(true);
  44. expect(wrapper.find('StyledSelectControl[name="project"]').exists()).toBe(false);
  45. await tick();
  46. wrapper.unmount();
  47. });
  48. it('calls onFinish, if project id is not needed, and only 1 org', async function () {
  49. OrganizationsStore.load([org2]);
  50. OrganizationStore.onUpdate(org2);
  51. MockApiClient.addMockResponse({
  52. url: `/organizations/${org2.slug}/projects/`,
  53. body: [],
  54. });
  55. const wrapper = mountWithTheme(getComponent());
  56. expect(onFinish).toHaveBeenCalledWith('/test/org2/path/');
  57. await tick();
  58. wrapper.unmount();
  59. });
  60. it('calls onFinish if there is only 1 org and 1 project', async function () {
  61. expect(onFinish).not.toHaveBeenCalled();
  62. OrganizationsStore.load([org2]);
  63. OrganizationStore.onUpdate(org2);
  64. const fetchProjectsForOrg = MockApiClient.addMockResponse({
  65. url: `/organizations/${org2.slug}/projects/`,
  66. body: [project2],
  67. });
  68. const wrapper = mountWithTheme(
  69. getComponent({
  70. needOrg: true,
  71. needProject: true,
  72. nextPath: '/test/:orgId/path/:projectId/',
  73. })
  74. );
  75. expect(fetchProjectsForOrg).toHaveBeenCalled();
  76. expect(onFinish).not.toHaveBeenCalled();
  77. await act(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({}));
  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: 'My Projects',
  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: 'All Projects',
  135. options: [
  136. {
  137. value: project4.slug,
  138. label: project4.slug,
  139. isDisabled: true,
  140. },
  141. ],
  142. },
  143. ]);
  144. await act(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. );
  171. await tick();
  172. wrapper.update();
  173. // Should not have anything selected
  174. expect(
  175. wrapper.find('StyledSelectControl[name="organization"]').prop('value')
  176. ).toBeUndefined();
  177. // Select org2
  178. selectByValue(wrapper, org2.slug, {control: true});
  179. await tick();
  180. wrapper.update();
  181. // <Projects> will fetch projects for org2
  182. expect(fetchProjectsForOrg).toHaveBeenCalled();
  183. expect(wrapper.find('StyledSelectControl[name="project"]').prop('options')).toEqual([
  184. {
  185. label: 'My Projects',
  186. options: [
  187. {
  188. value: project2.slug,
  189. label: project2.slug,
  190. isDisabled: false,
  191. },
  192. {
  193. value: 'project3',
  194. label: 'project3',
  195. isDisabled: false,
  196. },
  197. ],
  198. },
  199. {
  200. label: 'All Projects',
  201. options: [],
  202. },
  203. ]);
  204. // Select project3
  205. selectByValue(wrapper, 'project3', {control: true, name: 'project'});
  206. expect(onFinish).toHaveBeenCalledWith('/test/org2/path/project3/');
  207. await act(tick);
  208. wrapper.unmount();
  209. });
  210. it('isSuperUser and selects an integrationConfig and calls `onFinish` with URL to that configuration', async function () {
  211. OrganizationsStore.load([org]);
  212. OrganizationStore.onUpdate(org);
  213. ConfigStore.config = {
  214. user: {isSuperuser: true},
  215. };
  216. const provider = {slug: 'github'};
  217. const configUrl = `/api/0/organizations/${org.slug}/integrations/?provider_key=${provider.slug}&includeConfig=0`;
  218. const integration = TestStubs.GitHubIntegration();
  219. const fetchGithubConfigs = MockApiClient.addMockResponse({
  220. url: configUrl,
  221. body: [integration],
  222. });
  223. MockApiClient.addMockResponse({
  224. url: `/organizations/${org.slug}/projects/`,
  225. body: [],
  226. });
  227. const wrapper = mountWithTheme(
  228. getComponent({
  229. needOrg: false,
  230. needProject: false,
  231. nextPath: `/settings/${org.slug}/integrations/${provider.slug}/`,
  232. configUrl,
  233. })
  234. );
  235. expect(fetchGithubConfigs).toHaveBeenCalled();
  236. expect(wrapper.find('StyledSelectControl').prop('name')).toEqual('configurations');
  237. selectByValue(wrapper, integration.id, {control: true, name: 'configurations'});
  238. expect(onFinish).toHaveBeenCalledWith(
  239. `/settings/${org.slug}/integrations/github/${integration.id}/`
  240. );
  241. await tick();
  242. wrapper.unmount();
  243. });
  244. it('not superUser and cannot select an integrationConfig and calls `onFinish` with URL to integration overview page', async function () {
  245. OrganizationsStore.load([org]);
  246. OrganizationStore.onUpdate(org);
  247. ConfigStore.config = {
  248. user: {isSuperuser: false},
  249. };
  250. const provider = {slug: 'github'};
  251. const configUrl = `/api/0/organizations/${org.slug}/integrations/?provider_key=${provider.slug}&includeConfig=0`;
  252. const fetchGithubConfigs = MockApiClient.addMockResponse({
  253. url: configUrl,
  254. body: [TestStubs.GitHubIntegration()],
  255. });
  256. MockApiClient.addMockResponse({
  257. url: `/organizations/${org.slug}/projects/`,
  258. body: [],
  259. });
  260. const wrapper = mountWithTheme(
  261. getComponent({
  262. needOrg: false,
  263. needProject: false,
  264. nextPath: `/settings/${org.slug}/integrations/${provider.slug}/`,
  265. configUrl,
  266. })
  267. );
  268. expect(fetchGithubConfigs).toHaveBeenCalled();
  269. expect(wrapper.find('StyledSelectControl').exists()).toBeFalsy();
  270. expect(onFinish).toHaveBeenCalledWith(`/settings/${org.slug}/integrations/github/`);
  271. await tick();
  272. wrapper.unmount();
  273. });
  274. it('is superUser and no integration configurations and calls `onFinish` with URL to integration overview page', async function () {
  275. OrganizationsStore.load([org]);
  276. OrganizationStore.onUpdate(org);
  277. ConfigStore.config = {
  278. user: {isSuperuser: true},
  279. };
  280. const provider = {slug: 'github'};
  281. const configUrl = `/api/0/organizations/${org.slug}/integrations/?provider_key=${provider.slug}&includeConfig=0`;
  282. const fetchGithubConfigs = MockApiClient.addMockResponse({
  283. url: configUrl,
  284. body: [],
  285. });
  286. MockApiClient.addMockResponse({
  287. url: `/organizations/${org.slug}/projects/`,
  288. body: [],
  289. });
  290. const wrapper = mountWithTheme(
  291. getComponent({
  292. needOrg: false,
  293. needProject: false,
  294. nextPath: `/settings/${org.slug}/integrations/${provider.slug}/`,
  295. configUrl,
  296. })
  297. );
  298. expect(fetchGithubConfigs).toHaveBeenCalled();
  299. expect(wrapper.find('StyledSelectControl').exists()).toBeFalsy();
  300. expect(onFinish).toHaveBeenCalledWith(`/settings/${org.slug}/integrations/github/`);
  301. await tick();
  302. wrapper.unmount();
  303. });
  304. });