externalIssueForm.spec.jsx 6.1 KB

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