sentryAppExternalIssueForm.spec.jsx 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. import React from 'react';
  2. import {mountWithTheme} from 'sentry-test/enzyme';
  3. import {selectByValue, changeInputValue} from 'sentry-test/select-new';
  4. import {Client} from 'app/api';
  5. import {addQueryParamsToExistingUrl} from 'app/utils/queryString';
  6. import SentryAppExternalIssueForm from 'app/components/group/sentryAppExternalIssueForm';
  7. const optionLabelSelector = label => `[label="${label}"]`;
  8. describe('SentryAppExternalIssueForm', () => {
  9. let wrapper;
  10. let group;
  11. let sentryApp;
  12. let sentryAppInstallation;
  13. let component;
  14. let submitUrl;
  15. let externalIssueRequest;
  16. beforeEach(() => {
  17. group = TestStubs.Group({
  18. title: 'ApiError: Broken',
  19. shortId: 'SEN123',
  20. permalink: 'https://sentry.io/organizations/sentry/issues/123/?project=1',
  21. });
  22. component = TestStubs.SentryAppComponent();
  23. sentryApp = TestStubs.SentryApp();
  24. sentryAppInstallation = TestStubs.SentryAppInstallation({sentryApp});
  25. submitUrl = `/sentry-app-installations/${sentryAppInstallation.uuid}/external-issues/`;
  26. externalIssueRequest = Client.addMockResponse({
  27. url: submitUrl,
  28. method: 'POST',
  29. body: {},
  30. });
  31. });
  32. describe('create', () => {
  33. beforeEach(() => {
  34. wrapper = mountWithTheme(
  35. <SentryAppExternalIssueForm
  36. group={group}
  37. sentryAppInstallation={sentryAppInstallation}
  38. appName={sentryApp.name}
  39. config={component.schema.create}
  40. action="create"
  41. api={new Client()}
  42. />,
  43. TestStubs.routerContext()
  44. );
  45. });
  46. it('renders each required_fields field', () => {
  47. component.schema.create.required_fields.forEach(field => {
  48. expect(wrapper.exists(`#${field.name}`)).toBe(true);
  49. });
  50. });
  51. it('does not submit form if required fields are not set', () => {
  52. wrapper.find('form').simulate('submit');
  53. expect(externalIssueRequest).not.toHaveBeenCalled();
  54. });
  55. it('submits to the New External Issue endpoint', () => {
  56. selectByValue(wrapper, 'number_1', {name: 'numbers', control: true});
  57. wrapper.find('form').simulate('submit');
  58. expect(externalIssueRequest).toHaveBeenCalledWith(
  59. submitUrl,
  60. expect.objectContaining({
  61. data: {
  62. action: 'create',
  63. description:
  64. 'Sentry Issue: [SEN123](https://sentry.io/organizations/sentry/issues/123/?project=1&referrer=Sample%20App)',
  65. groupId: '1',
  66. numbers: 'number_1',
  67. title: 'ApiError: Broken',
  68. },
  69. method: 'POST',
  70. })
  71. );
  72. });
  73. it('renders prepopulated defaults', () => {
  74. const titleField = wrapper.find('Input#title');
  75. const descriptionField = wrapper.find('TextArea#description');
  76. const url = addQueryParamsToExistingUrl(group.permalink, {
  77. referrer: sentryApp.name,
  78. });
  79. expect(titleField.prop('value')).toEqual(`${group.title}`);
  80. expect(descriptionField.prop('value')).toEqual(
  81. `Sentry Issue: [${group.shortId}](${url})`
  82. );
  83. });
  84. });
  85. describe('link', () => {
  86. beforeEach(() => {
  87. wrapper = mountWithTheme(
  88. <SentryAppExternalIssueForm
  89. group={group}
  90. sentryAppInstallation={sentryAppInstallation}
  91. appName={sentryApp.name}
  92. config={component.schema.link}
  93. action="link"
  94. api={new Client()}
  95. />,
  96. TestStubs.routerContext()
  97. );
  98. });
  99. it('renders each required_fields field', () => {
  100. component.schema.link.required_fields.forEach(field => {
  101. expect(wrapper.exists(`#${field.name}`)).toBe(true);
  102. });
  103. });
  104. it('submits to the New External Issue endpoint', () => {
  105. wrapper
  106. .find('input[name="issue"]')
  107. .simulate('change', {target: {value: 'my issue'}});
  108. wrapper.find('form').simulate('submit');
  109. expect(externalIssueRequest).toHaveBeenCalledWith(
  110. submitUrl,
  111. expect.objectContaining({
  112. data: {
  113. action: 'link',
  114. groupId: '1',
  115. issue: 'my issue',
  116. },
  117. method: 'POST',
  118. })
  119. );
  120. });
  121. });
  122. });
  123. describe('SentryAppExternalIssueForm Async Field', () => {
  124. let wrapper;
  125. let group;
  126. let sentryApp;
  127. let sentryAppInstallation;
  128. const component = TestStubs.SentryAppComponentAsync();
  129. beforeEach(() => {
  130. group = TestStubs.Group({
  131. title: 'ApiError: Broken',
  132. shortId: 'SEN123',
  133. permalink: 'https://sentry.io/organizations/sentry/issues/123/?project=1',
  134. });
  135. sentryApp = TestStubs.SentryApp();
  136. sentryAppInstallation = TestStubs.SentryAppInstallation({sentryApp});
  137. });
  138. afterEach(() => {
  139. Client.clearMockResponses();
  140. });
  141. describe('renders', () => {
  142. it('renders each required_fields field', async function () {
  143. const mockGetOptions = Client.addMockResponse({
  144. method: 'GET',
  145. url:
  146. '/sentry-app-installations/d950595e-cba2-46f6-8a94-b79e42806f98/external-requests/',
  147. body: {
  148. choices: [
  149. [1, 'Issue 1'],
  150. [2, 'Issue 2'],
  151. ],
  152. },
  153. });
  154. wrapper = mountWithTheme(
  155. <SentryAppExternalIssueForm
  156. group={group}
  157. sentryAppInstallation={sentryAppInstallation}
  158. appName={sentryApp.name}
  159. config={component.schema.create}
  160. action="create"
  161. api={new Client()}
  162. />,
  163. TestStubs.routerContext()
  164. );
  165. const thisInput = wrapper.find('input').at(0);
  166. changeInputValue(thisInput, 'I');
  167. await tick();
  168. wrapper.update();
  169. expect(mockGetOptions).toHaveBeenCalled();
  170. expect(wrapper.find(optionLabelSelector('Issue 1')).exists()).toBe(true);
  171. expect(wrapper.find(optionLabelSelector('Issue 2')).exists()).toBe(true);
  172. });
  173. });
  174. });
  175. describe('SentryAppExternalIssueForm Dependent fields', () => {
  176. let wrapper;
  177. let group;
  178. let sentryApp;
  179. let sentryAppInstallation;
  180. const component = TestStubs.SentryAppComponentDependent();
  181. beforeEach(() => {
  182. group = TestStubs.Group({
  183. title: 'ApiError: Broken',
  184. shortId: 'SEN123',
  185. permalink: 'https://sentry.io/organizations/sentry/issues/123/?project=1',
  186. });
  187. sentryApp = TestStubs.SentryApp();
  188. sentryAppInstallation = TestStubs.SentryAppInstallation({sentryApp});
  189. wrapper = mountWithTheme(
  190. <SentryAppExternalIssueForm
  191. group={group}
  192. sentryAppInstallation={sentryAppInstallation}
  193. appName={sentryApp.name}
  194. config={component.schema.create}
  195. action="create"
  196. api={new Client()}
  197. />,
  198. TestStubs.routerContext()
  199. );
  200. });
  201. afterEach(() => {
  202. Client.clearMockResponses();
  203. });
  204. describe('create', () => {
  205. it('load options for field that has dependencies when the dependent option is selected', async () => {
  206. const url = `/sentry-app-installations/${sentryAppInstallation.uuid}/external-requests/`;
  207. Client.addMockResponse(
  208. {
  209. method: 'GET',
  210. url,
  211. body: {
  212. choices: [
  213. ['A', 'project A'],
  214. ['B', 'project B'],
  215. ],
  216. },
  217. },
  218. {
  219. predicate: (_url, options) => {
  220. return options.query.uri === '/integrations/sentry/projects';
  221. },
  222. }
  223. );
  224. const boardMock = Client.addMockResponse(
  225. {
  226. method: 'GET',
  227. url,
  228. body: {
  229. choices: [
  230. ['R', 'board R'],
  231. ['S', 'board S'],
  232. ],
  233. },
  234. },
  235. {
  236. predicate: (_url, {query}) => {
  237. return (
  238. query.uri === '/integrations/sentry/boards' &&
  239. query.dependentData === JSON.stringify({project_id: 'A'})
  240. );
  241. },
  242. }
  243. );
  244. const projectInput = wrapper.find('[data-test-id="project_id"] input').at(0);
  245. changeInputValue(projectInput, 'p');
  246. await tick();
  247. wrapper.update();
  248. expect(wrapper.find(optionLabelSelector('project A')).exists()).toBe(true);
  249. expect(wrapper.find(optionLabelSelector('project B')).exists()).toBe(true);
  250. //project select should be disabled and we shouldn't fetch the options yet
  251. expect(wrapper.find('SelectControl#board_id').prop('disabled')).toBe(true);
  252. expect(boardMock).not.toHaveBeenCalled();
  253. //when we set the value for project we should get the values for the board
  254. selectByValue(wrapper, 'A', {name: 'project_id'});
  255. await tick();
  256. wrapper.update();
  257. expect(boardMock).toHaveBeenCalled();
  258. expect(wrapper.find('SelectControl#board_id').prop('disabled')).toBe(false);
  259. const boardInput = wrapper.find('[data-test-id="board_id"] input').at(0);
  260. changeInputValue(boardInput, 'b');
  261. await tick();
  262. wrapper.update();
  263. expect(wrapper.find(optionLabelSelector('board R')).exists()).toBe(true);
  264. expect(wrapper.find(optionLabelSelector('board S')).exists()).toBe(true);
  265. });
  266. });
  267. });