contextPickerModal.spec.tsx 9.1 KB

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