sentryAppExternalIssueForm.spec.jsx 8.5 KB

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