autofixMessageBox.spec.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. import {AutofixCodebaseChangeData} from 'sentry-fixture/autofixCodebaseChangeData';
  2. import {AutofixStepFixture} from 'sentry-fixture/autofixStep';
  3. import {
  4. render,
  5. renderGlobalModal,
  6. screen,
  7. userEvent,
  8. waitFor,
  9. within,
  10. } from 'sentry-test/reactTestingLibrary';
  11. import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
  12. import AutofixMessageBox from 'sentry/components/events/autofix/autofixMessageBox';
  13. import {AutofixStatus, AutofixStepType} from 'sentry/components/events/autofix/types';
  14. jest.mock('sentry/actionCreators/indicator');
  15. describe('AutofixMessageBox', () => {
  16. const defaultProps = {
  17. displayText: 'Test display text',
  18. groupId: '123',
  19. runId: '456',
  20. actionText: 'Send',
  21. allowEmptyMessage: false,
  22. responseRequired: false,
  23. step: null,
  24. onSend: null,
  25. };
  26. const changesStepProps = {
  27. ...defaultProps,
  28. isChangesStep: true,
  29. step: AutofixStepFixture({
  30. type: AutofixStepType.CHANGES,
  31. changes: [AutofixCodebaseChangeData()],
  32. }),
  33. };
  34. const prCreatedProps = {
  35. ...changesStepProps,
  36. step: AutofixStepFixture({
  37. type: AutofixStepType.CHANGES,
  38. status: AutofixStatus.COMPLETED,
  39. changes: [AutofixCodebaseChangeData()],
  40. }),
  41. };
  42. const multiplePRsProps = {
  43. ...changesStepProps,
  44. step: AutofixStepFixture({
  45. type: AutofixStepType.CHANGES,
  46. status: AutofixStatus.COMPLETED,
  47. changes: [
  48. AutofixCodebaseChangeData({
  49. repo_name: 'example/repo1',
  50. pull_request: {
  51. pr_url: 'https://github.com/example/repo1/pull/1',
  52. pr_number: 1,
  53. },
  54. }),
  55. AutofixCodebaseChangeData({
  56. repo_name: 'example/repo1',
  57. pull_request: {
  58. pr_url: 'https://github.com/example/repo2/pull/2',
  59. pr_number: 2,
  60. },
  61. }),
  62. ],
  63. }),
  64. };
  65. beforeEach(() => {
  66. (addSuccessMessage as jest.Mock).mockClear();
  67. (addErrorMessage as jest.Mock).mockClear();
  68. MockApiClient.clearMockResponses();
  69. MockApiClient.addMockResponse({
  70. url: '/issues/123/autofix/setup/?check_write_access=true',
  71. method: 'GET',
  72. body: {
  73. genAIConsent: {ok: true},
  74. integration: {ok: true},
  75. },
  76. });
  77. });
  78. it('renders correctly with default props', () => {
  79. render(<AutofixMessageBox {...defaultProps} />);
  80. expect(screen.getByText('Test display text')).toBeInTheDocument();
  81. expect(
  82. screen.getByPlaceholderText('Share helpful context or directions...')
  83. ).toBeInTheDocument();
  84. expect(screen.getByRole('button', {name: 'Send'})).toBeInTheDocument();
  85. });
  86. it('calls onSend when provided and button is clicked', async () => {
  87. const onSendMock = jest.fn();
  88. render(<AutofixMessageBox {...defaultProps} onSend={onSendMock} />);
  89. const input = screen.getByPlaceholderText('Share helpful context or directions...');
  90. await userEvent.type(input, 'Test message');
  91. await userEvent.click(screen.getByRole('button', {name: 'Send'}));
  92. expect(onSendMock).toHaveBeenCalledWith('Test message');
  93. });
  94. it('sends interjection message when onSend is not provided', async () => {
  95. MockApiClient.addMockResponse({
  96. method: 'POST',
  97. url: '/issues/123/autofix/update/',
  98. body: {},
  99. });
  100. render(<AutofixMessageBox {...defaultProps} />);
  101. const input = screen.getByPlaceholderText('Share helpful context or directions...');
  102. await userEvent.type(input, 'Test message');
  103. await userEvent.click(screen.getByRole('button', {name: 'Send'}));
  104. await waitFor(() => {
  105. expect(addSuccessMessage).toHaveBeenCalledWith('Thanks for the input.');
  106. });
  107. });
  108. it('displays error message when API request fails', async () => {
  109. MockApiClient.addMockResponse({
  110. url: '/issues/123/autofix/update/',
  111. method: 'POST',
  112. body: {
  113. detail: 'Internal Error',
  114. },
  115. statusCode: 500,
  116. });
  117. render(<AutofixMessageBox {...defaultProps} />);
  118. const input = screen.getByPlaceholderText('Share helpful context or directions...');
  119. await userEvent.type(input, 'Test message');
  120. await userEvent.click(screen.getByRole('button', {name: 'Send'}));
  121. await waitFor(() => {
  122. expect(addErrorMessage).toHaveBeenCalledWith(
  123. 'Something went wrong when sending Autofix your message.'
  124. );
  125. });
  126. });
  127. it('renders step icon and title when step is provided', () => {
  128. const stepProps = {
  129. ...defaultProps,
  130. step: AutofixStepFixture(),
  131. };
  132. render(<AutofixMessageBox {...stepProps} />);
  133. expect(screen.getByText(AutofixStepFixture().title)).toBeInTheDocument();
  134. });
  135. it('renders required input style when responseRequired is true', () => {
  136. render(<AutofixMessageBox {...defaultProps} responseRequired />);
  137. expect(
  138. screen.getByPlaceholderText('Please answer to continue...')
  139. ).toBeInTheDocument();
  140. });
  141. it('handles suggested root cause selection correctly', async () => {
  142. const onSendMock = jest.fn();
  143. render(
  144. <AutofixMessageBox {...defaultProps} onSend={onSendMock} isRootCauseSelectionStep />
  145. );
  146. // Test suggested root cause
  147. await userEvent.click(screen.getByRole('button', {name: 'Use suggested root cause'}));
  148. const input = screen.getByPlaceholderText(
  149. '(Optional) Provide any instructions for the fix...'
  150. );
  151. await userEvent.type(input, 'Use this suggestion');
  152. await userEvent.click(screen.getByRole('button', {name: 'Send'}));
  153. expect(onSendMock).toHaveBeenCalledWith('Use this suggestion', false);
  154. });
  155. it('handles custom root cause selection correctly', async () => {
  156. const onSendMock = jest.fn();
  157. render(
  158. <AutofixMessageBox {...defaultProps} onSend={onSendMock} isRootCauseSelectionStep />
  159. );
  160. // Test custom root cause
  161. await userEvent.click(
  162. screen.getByRole('button', {name: 'Propose your own root cause'})
  163. );
  164. const customInput = screen.getByPlaceholderText('Propose your own root cause...');
  165. await userEvent.type(customInput, 'Custom root cause');
  166. await userEvent.click(screen.getByRole('button', {name: 'Send'}));
  167. expect(onSendMock).toHaveBeenCalledWith('Custom root cause', true);
  168. });
  169. it('renders segmented control for changes step', () => {
  170. render(<AutofixMessageBox {...changesStepProps} />);
  171. expect(screen.getByRole('button', {name: 'Iterate'})).toBeInTheDocument();
  172. expect(screen.getByRole('button', {name: 'Approve'})).toBeInTheDocument();
  173. expect(screen.getByRole('button', {name: 'Add tests'})).toBeInTheDocument();
  174. });
  175. it('shows feedback input when "Iterate" is selected', async () => {
  176. render(<AutofixMessageBox {...changesStepProps} />);
  177. await userEvent.click(screen.getByRole('button', {name: 'Iterate'}));
  178. expect(
  179. screen.getByPlaceholderText('Share helpful context or directions...')
  180. ).toBeInTheDocument();
  181. expect(screen.getByRole('button', {name: 'Send'})).toBeInTheDocument();
  182. });
  183. it('shows "Create PR" button when "Approve" is selected', async () => {
  184. MockApiClient.addMockResponse({
  185. url: '/issues/123/autofix/setup/?check_write_access=true',
  186. method: 'GET',
  187. body: {
  188. genAIConsent: {ok: true},
  189. integration: {ok: true},
  190. githubWriteIntegration: {
  191. repos: [{ok: true, owner: 'owner', name: 'hello-world', id: 100}],
  192. },
  193. },
  194. });
  195. render(<AutofixMessageBox {...changesStepProps} />);
  196. await userEvent.click(screen.getByRole('button', {name: 'Approve'}));
  197. expect(
  198. screen.getByText('Draft 1 pull request for the above changes?')
  199. ).toBeInTheDocument();
  200. expect(screen.getByRole('button', {name: 'Create PR'})).toBeInTheDocument();
  201. });
  202. it('shows "Create PRs" button with correct text for multiple changes', async () => {
  203. MockApiClient.addMockResponse({
  204. url: '/issues/123/autofix/setup/?check_write_access=true',
  205. method: 'GET',
  206. body: {
  207. genAIConsent: {ok: true},
  208. integration: {ok: true},
  209. githubWriteIntegration: {
  210. repos: [{ok: true, owner: 'owner', name: 'hello-world', id: 100}],
  211. },
  212. },
  213. });
  214. const multipleChangesProps = {
  215. ...changesStepProps,
  216. step: {
  217. ...changesStepProps.step,
  218. changes: [AutofixCodebaseChangeData(), AutofixCodebaseChangeData()],
  219. },
  220. };
  221. render(<AutofixMessageBox {...multipleChangesProps} />);
  222. await userEvent.click(screen.getByRole('button', {name: 'Approve'}));
  223. expect(
  224. screen.getByText('Draft 2 pull requests for the above changes?')
  225. ).toBeInTheDocument();
  226. expect(screen.getByRole('button', {name: 'Create PRs'})).toBeInTheDocument();
  227. });
  228. it('shows "View PR" buttons when PRs are created', () => {
  229. render(<AutofixMessageBox {...prCreatedProps} />);
  230. expect(screen.getByRole('button', {name: /View PR in/})).toBeInTheDocument();
  231. expect(screen.getByRole('button', {name: /View PR in/})).toHaveAttribute(
  232. 'href',
  233. 'https://github.com/owner/hello-world/pull/200'
  234. );
  235. });
  236. it('shows multiple "View PR" buttons for multiple PRs', () => {
  237. render(<AutofixMessageBox {...multiplePRsProps} />);
  238. const viewPRButtons = screen.getAllByRole('button', {name: /View PR in/});
  239. expect(viewPRButtons).toHaveLength(2);
  240. expect(viewPRButtons[0]).toHaveAttribute(
  241. 'href',
  242. 'https://github.com/example/repo1/pull/1'
  243. );
  244. expect(viewPRButtons[1]).toHaveAttribute(
  245. 'href',
  246. 'https://github.com/example/repo2/pull/2'
  247. );
  248. });
  249. it('shows "Create PRs" button that opens setup modal when setup is incomplete', async () => {
  250. MockApiClient.addMockResponse({
  251. url: '/issues/123/autofix/setup/?check_write_access=true',
  252. method: 'GET',
  253. body: {
  254. genAIConsent: {ok: true},
  255. integration: {ok: true},
  256. githubWriteIntegration: {
  257. repos: [
  258. {ok: false, provider: 'github', owner: 'owner', name: 'hello-world', id: 100},
  259. ],
  260. },
  261. },
  262. });
  263. MockApiClient.addMockResponse({
  264. url: '/issues/123/autofix/setup/',
  265. method: 'GET',
  266. body: {
  267. genAIConsent: {ok: true},
  268. integration: {ok: true},
  269. },
  270. });
  271. render(<AutofixMessageBox {...changesStepProps} />);
  272. await userEvent.click(screen.getByRole('button', {name: 'Approve'}));
  273. expect(
  274. screen.getByText('Draft 1 pull request for the above changes?')
  275. ).toBeInTheDocument();
  276. const createPRsButton = screen.getByRole('button', {name: 'Create PRs'});
  277. expect(createPRsButton).toBeInTheDocument();
  278. renderGlobalModal();
  279. await userEvent.click(createPRsButton);
  280. expect(await screen.findByRole('dialog')).toBeInTheDocument();
  281. expect(
  282. within(screen.getByRole('dialog')).getByText('Allow Autofix to Make Pull Requests')
  283. ).toBeInTheDocument();
  284. });
  285. it('shows segmented control options for changes step', () => {
  286. render(<AutofixMessageBox {...changesStepProps} />);
  287. expect(screen.getByRole('button', {name: 'Approve'})).toBeInTheDocument();
  288. expect(screen.getByRole('button', {name: 'Iterate'})).toBeInTheDocument();
  289. expect(screen.getByRole('button', {name: 'Add tests'})).toBeInTheDocument();
  290. });
  291. it('shows "Test" button and static message when "Test" is selected', async () => {
  292. render(<AutofixMessageBox {...changesStepProps} />);
  293. await userEvent.click(screen.getByRole('button', {name: 'Add tests'}));
  294. expect(
  295. screen.getByText('Write unit tests to make sure the issue is fixed?')
  296. ).toBeInTheDocument();
  297. expect(screen.getByRole('button', {name: 'Add Tests'})).toBeInTheDocument();
  298. });
  299. it('sends correct message when "Test" is clicked without onSend prop', async () => {
  300. MockApiClient.addMockResponse({
  301. method: 'POST',
  302. url: '/issues/123/autofix/update/',
  303. body: {},
  304. });
  305. render(<AutofixMessageBox {...changesStepProps} />);
  306. await userEvent.click(screen.getByRole('button', {name: 'Add tests'}));
  307. await userEvent.click(screen.getByRole('button', {name: 'Add Tests'}));
  308. await waitFor(() => {
  309. expect(addSuccessMessage).toHaveBeenCalledWith('Thanks for the input.');
  310. });
  311. });
  312. });