externalIssueForm.spec.jsx 6.0 KB

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