contextPickerModal.spec.tsx 8.9 KB

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