sentryAppExternalIssueActions.spec.tsx 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. import {Organization} from 'sentry-fixture/organization';
  2. import {SentryApp} from 'sentry-fixture/sentryApp';
  3. import {SentryAppInstallation} from 'sentry-fixture/sentryAppInstallation';
  4. import {
  5. render,
  6. renderGlobalModal,
  7. screen,
  8. userEvent,
  9. } from 'sentry-test/reactTestingLibrary';
  10. import SentryAppExternalIssueActions from 'sentry/components/group/sentryAppExternalIssueActions';
  11. describe('SentryAppExternalIssueActions', () => {
  12. const group = TestStubs.Group();
  13. const sentryApp = SentryApp();
  14. const component = TestStubs.SentryAppComponent({
  15. sentryApp: {
  16. uuid: sentryApp.uuid,
  17. slug: sentryApp.slug,
  18. name: sentryApp.name,
  19. },
  20. });
  21. // unable to use the selectByValue here so remove the select option
  22. component.schema.create.required_fields.pop();
  23. const install = SentryAppInstallation({});
  24. const submitUrl = `/sentry-app-installations/${install.uuid}/external-issue-actions/`;
  25. const externalIssue = TestStubs.PlatformExternalIssue({
  26. groupId: group.id,
  27. serviceType: component.sentryApp.slug,
  28. });
  29. beforeEach(() => {
  30. MockApiClient.addMockResponse({
  31. url: `/sentry-apps/${sentryApp.slug}/interaction/`,
  32. method: 'POST',
  33. });
  34. });
  35. afterEach(() => {
  36. MockApiClient.clearMockResponses();
  37. jest.clearAllMocks();
  38. });
  39. it('renders without an external issue linked', async () => {
  40. render(
  41. <SentryAppExternalIssueActions
  42. event={TestStubs.Event()}
  43. organization={Organization()}
  44. group={group}
  45. sentryAppInstallation={install}
  46. sentryAppComponent={component}
  47. />
  48. );
  49. renderGlobalModal();
  50. // Link to open the modal
  51. const link = screen.getByRole('link', {name: `${component.sentryApp.name} Issue`});
  52. expect(link).toBeInTheDocument();
  53. // Renders the add icon
  54. expect(screen.getByLabelText('Add')).toBeInTheDocument();
  55. // Open The Modal
  56. await userEvent.click(link);
  57. expect(screen.getByRole('dialog')).toBeInTheDocument();
  58. // renders the Create Issue form fields, based on schema
  59. expect(component.schema.create.required_fields).toHaveLength(2);
  60. for (const field of component.schema.create.required_fields) {
  61. expect(screen.getByRole('textbox', {name: field.label})).toBeInTheDocument();
  62. }
  63. // Click the link tab
  64. await userEvent.click(screen.getByText('Link'));
  65. // renders the Link Issue form fields, based on schema
  66. expect(component.schema.link.required_fields).toHaveLength(1);
  67. for (const field of component.schema.link.required_fields) {
  68. expect(screen.getByRole('textbox', {name: field.label})).toBeInTheDocument();
  69. }
  70. });
  71. it('links to an existing Issue', async () => {
  72. const request = MockApiClient.addMockResponse({
  73. url: submitUrl,
  74. method: 'POST',
  75. body: externalIssue,
  76. });
  77. render(
  78. <SentryAppExternalIssueActions
  79. event={TestStubs.Event()}
  80. organization={Organization()}
  81. group={group}
  82. sentryAppInstallation={install}
  83. sentryAppComponent={component}
  84. />
  85. );
  86. const {waitForModalToHide} = renderGlobalModal();
  87. // Open The Modal
  88. await userEvent.click(
  89. screen.getByRole('link', {name: `${component.sentryApp.name} Issue`})
  90. );
  91. // Click the link tab
  92. await userEvent.click(screen.getByText('Link'));
  93. await userEvent.type(screen.getByRole('textbox', {name: 'Issue'}), '99');
  94. await userEvent.click(screen.getByRole('button', {name: 'Save Changes'}));
  95. await waitForModalToHide();
  96. expect(request).toHaveBeenCalledWith(
  97. submitUrl,
  98. expect.objectContaining({
  99. data: expect.objectContaining({
  100. action: 'link',
  101. issue: '99',
  102. groupId: group.id,
  103. }),
  104. })
  105. );
  106. });
  107. it('creates a new Issue', async () => {
  108. const request = MockApiClient.addMockResponse({
  109. url: submitUrl,
  110. method: 'POST',
  111. body: externalIssue,
  112. });
  113. render(
  114. <SentryAppExternalIssueActions
  115. event={TestStubs.Event()}
  116. organization={Organization()}
  117. group={group}
  118. sentryAppInstallation={install}
  119. sentryAppComponent={component}
  120. />
  121. );
  122. const {waitForModalToHide} = renderGlobalModal();
  123. // Open The Modal
  124. await userEvent.click(
  125. screen.getByRole('link', {name: `${component.sentryApp.name} Issue`})
  126. );
  127. await userEvent.clear(screen.getByRole('textbox', {name: 'Title'}));
  128. await userEvent.type(screen.getByRole('textbox', {name: 'Title'}), 'foo');
  129. await userEvent.clear(screen.getByRole('textbox', {name: 'Description'}));
  130. await userEvent.type(screen.getByRole('textbox', {name: 'Description'}), 'bar');
  131. await userEvent.click(screen.getByRole('button', {name: 'Save Changes'}));
  132. await waitForModalToHide();
  133. expect(request).toHaveBeenCalledWith(
  134. submitUrl,
  135. expect.objectContaining({
  136. data: expect.objectContaining({
  137. action: 'create',
  138. title: 'foo',
  139. description: 'bar',
  140. groupId: group.id,
  141. }),
  142. })
  143. );
  144. });
  145. it('renders with an external issue linked', () => {
  146. render(
  147. <SentryAppExternalIssueActions
  148. event={TestStubs.Event()}
  149. organization={Organization()}
  150. group={group}
  151. sentryAppComponent={component}
  152. sentryAppInstallation={install}
  153. externalIssue={externalIssue}
  154. />
  155. );
  156. // Renders a link to the external issue
  157. const link = screen.getByRole('link', {name: externalIssue.displayName});
  158. expect(link).toBeInTheDocument();
  159. expect(link).toHaveAttribute('href', externalIssue.webUrl);
  160. // Renders the remove issue button
  161. expect(screen.getByLabelText('Remove')).toBeInTheDocument();
  162. });
  163. it('deletes a Linked Issue', async () => {
  164. const request = MockApiClient.addMockResponse({
  165. url: `/issues/${group.id}/external-issues/${externalIssue.id}/`,
  166. method: 'DELETE',
  167. });
  168. render(
  169. <SentryAppExternalIssueActions
  170. event={TestStubs.Event()}
  171. organization={Organization()}
  172. group={group}
  173. sentryAppComponent={component}
  174. sentryAppInstallation={install}
  175. externalIssue={externalIssue}
  176. />
  177. );
  178. await userEvent.click(screen.getByLabelText('Remove'));
  179. expect(request).toHaveBeenCalledTimes(1);
  180. });
  181. });