externalIssueForm.spec.jsx 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
  2. import ExternalIssueForm from 'sentry/components/group/externalIssueForm';
  3. jest.mock('lodash/debounce', () => {
  4. const debounceMap = new Map();
  5. const mockDebounce =
  6. (fn, timeout) =>
  7. (...args) => {
  8. if (debounceMap.has(fn)) {
  9. clearTimeout(debounceMap.get(fn));
  10. }
  11. debounceMap.set(
  12. fn,
  13. setTimeout(() => {
  14. fn.apply(fn, args);
  15. debounceMap.delete(fn);
  16. }, timeout)
  17. );
  18. };
  19. return mockDebounce;
  20. });
  21. describe('ExternalIssueForm', () => {
  22. let group, integration, formConfig;
  23. const onChange = jest.fn();
  24. beforeEach(() => {
  25. MockApiClient.clearMockResponses();
  26. group = TestStubs.Group();
  27. integration = TestStubs.GitHubIntegration({externalIssues: []});
  28. });
  29. afterEach(() => {
  30. jest.useRealTimers();
  31. jest.clearAllMocks();
  32. });
  33. const renderComponent = (action = 'Create') => {
  34. MockApiClient.addMockResponse({
  35. url: `/groups/${group.id}/integrations/${integration.id}/`,
  36. body: formConfig,
  37. match: [MockApiClient.matchQuery({action: 'create'})],
  38. });
  39. const wrapper = render(
  40. <ExternalIssueForm
  41. Body={p => p.children}
  42. Header={p => p.children}
  43. group={group}
  44. integration={integration}
  45. onChange={onChange}
  46. />
  47. );
  48. userEvent.click(screen.getByText(action));
  49. return wrapper;
  50. };
  51. describe('create', () => {
  52. // TODO: expand tests
  53. beforeEach(() => {
  54. formConfig = {
  55. createIssueConfig: [],
  56. };
  57. MockApiClient.addMockResponse({
  58. url: `/groups/${group.id}/integrations/${integration.id}/`,
  59. body: formConfig,
  60. });
  61. });
  62. it('renders', () => {
  63. const {container} = renderComponent();
  64. expect(container).toSnapshot();
  65. });
  66. });
  67. describe('link', () => {
  68. let externalIssueField, getFormConfigRequest;
  69. beforeEach(() => {
  70. externalIssueField = {
  71. name: 'externalIssue',
  72. default: '',
  73. required: true,
  74. choices: [],
  75. url: '/search',
  76. label: 'Issue',
  77. type: 'select',
  78. };
  79. formConfig = {
  80. status: 'active',
  81. name: 'scefali',
  82. domainName: 'github.com/scefali',
  83. linkIssueConfig: [
  84. {
  85. url: '/search',
  86. required: true,
  87. name: 'repo',
  88. default: 'scefali/test',
  89. updatesForm: true,
  90. choices: [
  91. ['scefali/test', 'test'],
  92. ['scefali/ZeldaBazaar', 'ZeldaBazaar'],
  93. ],
  94. type: 'select',
  95. label: 'GitHub Repository',
  96. },
  97. externalIssueField,
  98. {
  99. help: "Leave blank if you don't want to add a comment to the GitHub issue.",
  100. default: 'Default Value',
  101. required: false,
  102. label: 'Comment',
  103. type: 'textarea',
  104. name: 'comment',
  105. },
  106. ],
  107. accountType: 'User',
  108. provider: {
  109. canAdd: true,
  110. aspects: {
  111. disable_dialog: {
  112. body: 'Before deleting this integration, you must uninstall this integration from GitHub. After uninstalling, your integration will be disabled at which point you can choose to delete this integration.',
  113. actionText: 'Visit GitHub',
  114. },
  115. removal_dialog: {
  116. body: 'Deleting this integration will delete all associated repositories and commit data. This action cannot be undone. Are you sure you want to delete your integration?',
  117. actionText: 'Delete',
  118. },
  119. },
  120. features: ['commits', 'issue-basic'],
  121. canDisable: true,
  122. key: 'github',
  123. name: 'GitHub',
  124. },
  125. id: '5',
  126. };
  127. getFormConfigRequest = MockApiClient.addMockResponse({
  128. url: `/groups/${group.id}/integrations/${integration.id}/`,
  129. body: formConfig,
  130. match: [MockApiClient.matchQuery({action: 'link'})],
  131. });
  132. });
  133. it('renders and loads options', () => {
  134. const {container} = renderComponent('Link');
  135. expect(getFormConfigRequest).toHaveBeenCalled();
  136. expect(container).toSnapshot();
  137. });
  138. describe('options loaded', () => {
  139. beforeEach(() => {
  140. const mockFetchPromise = () =>
  141. new Promise(resolve => {
  142. setTimeout(() => {
  143. resolve({
  144. json: () =>
  145. Promise.resolve([
  146. {
  147. label: '#1337 ref(js): Convert Form to a FC',
  148. value: 1337,
  149. },
  150. {
  151. label: '#2345 perf: Make it faster',
  152. value: 2345,
  153. },
  154. ]),
  155. ok: true,
  156. });
  157. }, 50);
  158. });
  159. window.fetch = jest.fn().mockImplementation(mockFetchPromise);
  160. MockApiClient.addMockResponse({
  161. url: `/groups/${group.id}/integrations/${integration.id}/?action=link`,
  162. body: formConfig,
  163. });
  164. });
  165. afterEach(() => {
  166. window.fetch.mockClear();
  167. delete window.fetch;
  168. });
  169. it('fast typing is debounced and uses trailing call when fetching data', async () => {
  170. renderComponent('Link');
  171. jest.useFakeTimers();
  172. userEvent.click(screen.getAllByText('Issue').at(1));
  173. userEvent.type(screen.getByRole('textbox', {name: 'Issue'}), 'doOT');
  174. expect(window.fetch).toHaveBeenCalledTimes(0);
  175. jest.advanceTimersByTime(300);
  176. expect(window.fetch).toHaveBeenCalledTimes(1);
  177. expect(window.fetch).toHaveBeenCalledWith(
  178. '/search?field=externalIssue&query=doOT&repo=scefali%2Ftest'
  179. );
  180. expect(await screen.findByText('#2345 perf: Make it faster')).toBeInTheDocument();
  181. });
  182. });
  183. });
  184. });