sentryAppExternalIssueActions.spec.tsx 6.2 KB

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