contextPickerModal.spec.tsx 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. import selectEvent from 'react-select-event';
  2. import {act, render, screen, waitFor} from 'sentry-test/reactTestingLibrary';
  3. import ContextPickerModal from 'sentry/components/contextPickerModal';
  4. import ConfigStore from 'sentry/stores/configStore';
  5. import OrganizationsStore from 'sentry/stores/organizationsStore';
  6. import OrganizationStore from 'sentry/stores/organizationStore';
  7. import ProjectsStore from 'sentry/stores/projectsStore';
  8. import {makeCloseButton, ModalBody, ModalFooter} from './globalModal/components';
  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. 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. act(() => OrganizationsStore.load([]));
  24. act(() => OrganizationStore.reset());
  25. jest.clearAllMocks();
  26. });
  27. const getComponent = (props = {}) => (
  28. <ContextPickerModal
  29. Header={() => <div />}
  30. Body={ModalBody}
  31. nextPath="/test/:orgId/path/"
  32. needOrg
  33. onFinish={onFinish}
  34. needProject={false}
  35. CloseButton={makeCloseButton(() => {})}
  36. Footer={ModalFooter}
  37. closeModal={jest.fn()}
  38. {...props}
  39. />
  40. );
  41. it('renders with only org selector when no org is selected', function () {
  42. render(getComponent());
  43. expect(screen.getByText('Select an Organization')).toBeInTheDocument();
  44. expect(screen.queryByText('Select a Project to continue')).not.toBeInTheDocument();
  45. });
  46. it('calls onFinish, if project id is not needed, and only 1 org', async function () {
  47. OrganizationsStore.load([org2]);
  48. OrganizationStore.onUpdate(org2);
  49. MockApiClient.addMockResponse({
  50. url: `/organizations/${org2.slug}/projects/`,
  51. body: [],
  52. });
  53. render(getComponent());
  54. await waitFor(() => {
  55. expect(onFinish).toHaveBeenCalledWith('/test/org2/path/');
  56. });
  57. });
  58. it('calls onFinish if there is only 1 org and 1 project', async function () {
  59. OrganizationsStore.load([org2]);
  60. OrganizationStore.onUpdate(org2);
  61. const fetchProjectsForOrg = MockApiClient.addMockResponse({
  62. url: `/organizations/${org2.slug}/projects/`,
  63. body: [project2],
  64. });
  65. render(
  66. getComponent({
  67. needOrg: true,
  68. needProject: true,
  69. nextPath: '/test/:orgId/path/:projectId/',
  70. })
  71. );
  72. await waitFor(() => {
  73. expect(fetchProjectsForOrg).toHaveBeenCalled();
  74. expect(onFinish).toHaveBeenLastCalledWith('/test/org2/path/project2/');
  75. });
  76. });
  77. it('selects an org and calls `onFinish` with URL with organization slug', async function () {
  78. OrganizationsStore.load([org]);
  79. render(getComponent());
  80. MockApiClient.addMockResponse({
  81. url: `/organizations/${org.slug}/projects/`,
  82. body: [],
  83. });
  84. await selectEvent.select(screen.getByText('Select an Organization'), 'org-slug');
  85. await waitFor(() => {
  86. expect(onFinish).toHaveBeenCalledWith('/test/org-slug/path/');
  87. });
  88. });
  89. it('renders with project selector and org selector selected when org is already selected', async function () {
  90. OrganizationsStore.load([org]);
  91. OrganizationStore.onUpdate(org);
  92. const fetchProjectsForOrg = MockApiClient.addMockResponse({
  93. url: `/organizations/${org.slug}/projects/`,
  94. body: [project, project2, project4],
  95. });
  96. render(
  97. getComponent({
  98. needOrg: true,
  99. needProject: true,
  100. })
  101. );
  102. await waitFor(() => {
  103. expect(fetchProjectsForOrg).toHaveBeenCalled();
  104. });
  105. // Default to org in latest context
  106. // Should see 1 selected, and 1 as an option
  107. expect(screen.getAllByText('org-slug')).toHaveLength(2);
  108. expect(screen.getByText('My Projects')).toBeInTheDocument();
  109. expect(screen.getByText(project.slug)).toBeInTheDocument();
  110. expect(screen.getByText(project2.slug)).toBeInTheDocument();
  111. expect(screen.getByText('All Projects')).toBeInTheDocument();
  112. expect(screen.getByText(project4.slug)).toBeInTheDocument();
  113. });
  114. it('can select org and project', async function () {
  115. const organizations = [
  116. {
  117. ...org,
  118. projects: [project],
  119. },
  120. {
  121. ...org2,
  122. projects: [project2, TestStubs.Project({slug: 'project3'})],
  123. },
  124. ];
  125. const fetchProjectsForOrg = MockApiClient.addMockResponse({
  126. url: `/organizations/${org2.slug}/projects/`,
  127. body: organizations[1].projects,
  128. });
  129. OrganizationsStore.load(organizations);
  130. render(
  131. getComponent({
  132. needOrg: true,
  133. needProject: true,
  134. nextPath: '/test/:orgId/path/:projectId/',
  135. organizations,
  136. })
  137. );
  138. // Should not have anything selected
  139. expect(screen.getByText('Select an Organization')).toBeInTheDocument();
  140. // Select org2
  141. await selectEvent.select(screen.getByText('Select an Organization'), org2.slug);
  142. // <Projects> will fetch projects for org2
  143. expect(fetchProjectsForOrg).toHaveBeenCalled();
  144. expect(screen.getByText('My Projects')).toBeInTheDocument();
  145. expect(screen.getByText(project2.slug)).toBeInTheDocument();
  146. expect(screen.getByText('project3')).toBeInTheDocument();
  147. expect(screen.queryByText('All Projects')).not.toBeInTheDocument();
  148. // Select project3
  149. await selectEvent.select(screen.getByText(/Select a Project/), 'project3');
  150. expect(onFinish).toHaveBeenCalledWith('/test/org2/path/project3/');
  151. });
  152. it('isSuperUser and selects an integrationConfig and calls `onFinish` with URL to that configuration', async function () {
  153. OrganizationsStore.load([org]);
  154. OrganizationStore.onUpdate(org);
  155. ConfigStore.config.user = TestStubs.User({isSuperuser: true});
  156. const provider = {slug: 'github'};
  157. const configUrl = `/api/0/organizations/${org.slug}/integrations/?provider_key=${provider.slug}&includeConfig=0`;
  158. const integration = TestStubs.GitHubIntegration();
  159. const fetchGithubConfigs = MockApiClient.addMockResponse({
  160. url: configUrl,
  161. body: [integration],
  162. });
  163. MockApiClient.addMockResponse({
  164. url: `/organizations/${org.slug}/projects/`,
  165. body: [],
  166. });
  167. render(
  168. getComponent({
  169. needOrg: false,
  170. needProject: false,
  171. nextPath: `/settings/${org.slug}/integrations/${provider.slug}/`,
  172. configUrl,
  173. })
  174. );
  175. await waitFor(() => {
  176. expect(fetchGithubConfigs).toHaveBeenCalled();
  177. });
  178. await selectEvent.select(
  179. screen.getByText(/Select a configuration/i),
  180. integration.domainName
  181. );
  182. expect(onFinish).toHaveBeenCalledWith(
  183. `/settings/${org.slug}/integrations/github/${integration.id}/`
  184. );
  185. });
  186. it('not superUser and cannot select an integrationConfig and calls `onFinish` with URL to integration overview page', async function () {
  187. OrganizationsStore.load([org]);
  188. OrganizationStore.onUpdate(org);
  189. ConfigStore.config.user = TestStubs.User({isSuperuser: false});
  190. const provider = {slug: 'github'};
  191. const configUrl = `/api/0/organizations/${org.slug}/integrations/?provider_key=${provider.slug}&includeConfig=0`;
  192. const fetchGithubConfigs = MockApiClient.addMockResponse({
  193. url: configUrl,
  194. body: [TestStubs.GitHubIntegration()],
  195. });
  196. MockApiClient.addMockResponse({
  197. url: `/organizations/${org.slug}/projects/`,
  198. body: [],
  199. });
  200. render(
  201. getComponent({
  202. needOrg: false,
  203. needProject: false,
  204. nextPath: `/settings/${org.slug}/integrations/${provider.slug}/`,
  205. configUrl,
  206. })
  207. );
  208. await waitFor(() => {
  209. expect(fetchGithubConfigs).toHaveBeenCalled();
  210. });
  211. });
  212. it('is superUser and no integration configurations and calls `onFinish` with URL to integration overview page', async function () {
  213. OrganizationsStore.load([org]);
  214. OrganizationStore.onUpdate(org);
  215. ConfigStore.config = TestStubs.User({isSuperuser: false});
  216. const provider = {slug: 'github'};
  217. const configUrl = `/api/0/organizations/${org.slug}/integrations/?provider_key=${provider.slug}&includeConfig=0`;
  218. const fetchGithubConfigs = MockApiClient.addMockResponse({
  219. url: configUrl,
  220. body: [],
  221. });
  222. MockApiClient.addMockResponse({
  223. url: `/organizations/${org.slug}/projects/`,
  224. body: [],
  225. });
  226. render(
  227. getComponent({
  228. needOrg: false,
  229. needProject: false,
  230. nextPath: `/settings/${org.slug}/integrations/${provider.slug}/`,
  231. configUrl,
  232. })
  233. );
  234. await waitFor(() => {
  235. expect(fetchGithubConfigs).toHaveBeenCalled();
  236. });
  237. expect(onFinish).toHaveBeenCalledWith(`/settings/${org.slug}/integrations/github/`);
  238. });
  239. });