autofixInsightCards.spec.tsx 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. import {render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrary';
  2. import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
  3. import AutofixInsightCards from 'sentry/components/events/autofix/autofixInsightCards';
  4. import type {AutofixInsight} from 'sentry/components/events/autofix/types';
  5. jest.mock('sentry/utils/marked', () => ({
  6. singleLineRenderer: jest.fn(text => text),
  7. }));
  8. jest.mock('sentry/actionCreators/indicator');
  9. const sampleInsights: AutofixInsight[] = [
  10. {
  11. breadcrumb_context: [
  12. {
  13. body: 'Breadcrumb body',
  14. category: 'ui',
  15. level: 'info',
  16. data_as_json: '{"testData": "testValue"}',
  17. type: 'info',
  18. },
  19. ],
  20. codebase_context: [
  21. {
  22. snippet: 'console.log("Hello, World!");',
  23. repo_name: 'sample-repo',
  24. file_path: 'src/index.js',
  25. },
  26. ],
  27. insight: 'Sample insight 1',
  28. justification: 'Sample justification 1',
  29. stacktrace_context: [
  30. {
  31. code_snippet: 'function() { throw new Error("Test error"); }',
  32. repo_name: 'sample-repo',
  33. file_name: 'src/error.js',
  34. vars_as_json: '{"testVar": "testValue"}',
  35. col_no: 1,
  36. line_no: 1,
  37. function: 'testFunction',
  38. },
  39. ],
  40. },
  41. {
  42. insight: 'User message',
  43. justification: 'USER',
  44. breadcrumb_context: [],
  45. stacktrace_context: [],
  46. codebase_context: [],
  47. },
  48. ];
  49. const sampleRepos = [
  50. {
  51. external_id: '1',
  52. name: 'sample-repo',
  53. default_branch: 'main',
  54. provider: 'github',
  55. url: 'github.com/org/sample-repo',
  56. },
  57. ];
  58. beforeEach(() => {
  59. (addSuccessMessage as jest.Mock).mockClear();
  60. (addErrorMessage as jest.Mock).mockClear();
  61. MockApiClient.clearMockResponses();
  62. });
  63. describe('AutofixInsightCards', () => {
  64. const renderComponent = (props = {}) => {
  65. return render(
  66. <AutofixInsightCards
  67. insights={sampleInsights}
  68. repos={sampleRepos}
  69. hasStepAbove={false}
  70. hasStepBelow={false}
  71. groupId="1"
  72. runId="1"
  73. stepIndex={0}
  74. {...props}
  75. />
  76. );
  77. };
  78. it('renders insights correctly', () => {
  79. renderComponent();
  80. expect(screen.getByText('Sample insight 1')).toBeInTheDocument();
  81. expect(screen.getByText('User message')).toBeInTheDocument();
  82. });
  83. it('renders breadcrumb context correctly', async () => {
  84. renderComponent();
  85. const contextButton = screen.getByText('Sample insight 1');
  86. await userEvent.click(contextButton);
  87. expect(screen.getByText('Breadcrumb body')).toBeInTheDocument();
  88. expect(screen.getByText('info')).toBeInTheDocument();
  89. });
  90. it('renders codebase context correctly', async () => {
  91. renderComponent();
  92. const contextButton = screen.getByText('Sample insight 1');
  93. await userEvent.click(contextButton);
  94. expect(screen.getByText('console.log("Hello, World!");')).toBeInTheDocument();
  95. expect(screen.getByText('src/index.js')).toBeInTheDocument();
  96. });
  97. it('renders stacktrace context correctly', async () => {
  98. renderComponent();
  99. const contextButton = screen.getByText('Sample insight 1');
  100. await userEvent.click(contextButton);
  101. expect(
  102. screen.getByText('function() { throw new Error("Test error"); }')
  103. ).toBeInTheDocument();
  104. expect(screen.getByText('src/error.js')).toBeInTheDocument();
  105. expect(screen.getByText('testVar')).toBeInTheDocument();
  106. });
  107. it('renders user messages differently', () => {
  108. renderComponent();
  109. const userMessage = screen.getByText('User message');
  110. expect(userMessage.closest('div')).toHaveStyle('color: inherit');
  111. });
  112. it('toggles context expansion correctly', async () => {
  113. renderComponent();
  114. const contextButton = screen.getByText('Sample insight 1');
  115. await userEvent.click(contextButton);
  116. expect(screen.getByText('Sample justification 1')).toBeInTheDocument();
  117. await userEvent.click(contextButton);
  118. expect(screen.queryByText('Sample justification 1')).not.toBeInTheDocument();
  119. });
  120. it('renders multiple insights correctly', () => {
  121. const multipleInsights = [
  122. ...sampleInsights,
  123. {
  124. insight: 'Another insight',
  125. justification: 'Another justification',
  126. },
  127. ];
  128. renderComponent({insights: multipleInsights});
  129. expect(screen.getByText('Sample insight 1')).toBeInTheDocument();
  130. expect(screen.getByText('User message')).toBeInTheDocument();
  131. expect(screen.getByText('Another insight')).toBeInTheDocument();
  132. });
  133. it('renders "Rethink from here" buttons', () => {
  134. renderComponent();
  135. const rethinkButtons = screen.getAllByRole('button', {name: 'Rethink from here'});
  136. expect(rethinkButtons.length).toBeGreaterThan(0);
  137. });
  138. it('shows rethink input overlay when "Rethink from here" is clicked', async () => {
  139. renderComponent();
  140. const rethinkButton = screen.getByRole('button', {name: 'Rethink from here'});
  141. await userEvent.click(rethinkButton);
  142. expect(
  143. screen.getByPlaceholderText(
  144. 'You should know X... Dive deeper into Y... Look at Z...'
  145. )
  146. ).toBeInTheDocument();
  147. });
  148. it('hides rethink input overlay when clicked outside', async () => {
  149. renderComponent();
  150. const rethinkButton = screen.getByRole('button', {name: 'Rethink from here'});
  151. await userEvent.click(rethinkButton);
  152. expect(
  153. screen.getByPlaceholderText(
  154. 'You should know X... Dive deeper into Y... Look at Z...'
  155. )
  156. ).toBeInTheDocument();
  157. await userEvent.click(document.body);
  158. expect(
  159. screen.queryByPlaceholderText(
  160. 'You should know X... Dive deeper into Y... Look at Z...'
  161. )
  162. ).not.toBeInTheDocument();
  163. });
  164. it('submits rethink request when form is submitted', async () => {
  165. const mockApi = MockApiClient.addMockResponse({
  166. url: '/issues/1/autofix/update/',
  167. method: 'POST',
  168. });
  169. renderComponent();
  170. const rethinkButton = screen.getByRole('button', {name: 'Rethink from here'});
  171. await userEvent.click(rethinkButton);
  172. const input = screen.getByPlaceholderText(
  173. 'You should know X... Dive deeper into Y... Look at Z...'
  174. );
  175. await userEvent.type(input, 'Rethink this part');
  176. const submitButton = screen.getByLabelText(
  177. 'Restart analysis from this point in the chain'
  178. );
  179. await userEvent.click(submitButton);
  180. expect(mockApi).toHaveBeenCalledWith(
  181. '/issues/1/autofix/update/',
  182. expect.objectContaining({
  183. method: 'POST',
  184. data: expect.objectContaining({
  185. run_id: '1',
  186. payload: expect.objectContaining({
  187. type: 'restart_from_point_with_feedback',
  188. message: 'Rethink this part',
  189. step_index: 0,
  190. retain_insight_card_index: 0,
  191. }),
  192. }),
  193. })
  194. );
  195. });
  196. it('shows success message after successful rethink submission', async () => {
  197. MockApiClient.addMockResponse({
  198. url: '/issues/1/autofix/update/',
  199. method: 'POST',
  200. });
  201. renderComponent();
  202. const rethinkButton = screen.getByRole('button', {name: 'Rethink from here'});
  203. await userEvent.click(rethinkButton);
  204. const input = screen.getByPlaceholderText(
  205. 'You should know X... Dive deeper into Y... Look at Z...'
  206. );
  207. await userEvent.type(input, 'Rethink this part');
  208. const submitButton = screen.getByLabelText(
  209. 'Restart analysis from this point in the chain'
  210. );
  211. await userEvent.click(submitButton);
  212. await waitFor(() => {
  213. expect(addSuccessMessage).toHaveBeenCalledWith('Thanks, rethinking this...');
  214. });
  215. });
  216. it('shows error message after failed rethink submission', async () => {
  217. MockApiClient.addMockResponse({
  218. url: '/issues/1/autofix/update/',
  219. method: 'POST',
  220. statusCode: 500,
  221. });
  222. renderComponent();
  223. const rethinkButton = screen.getByRole('button', {name: 'Rethink from here'});
  224. await userEvent.click(rethinkButton);
  225. const input = screen.getByPlaceholderText(
  226. 'You should know X... Dive deeper into Y... Look at Z...'
  227. );
  228. await userEvent.type(input, 'Rethink this part');
  229. const submitButton = screen.getByLabelText(
  230. 'Restart analysis from this point in the chain'
  231. );
  232. await userEvent.click(submitButton);
  233. await waitFor(() => {
  234. expect(addErrorMessage).toHaveBeenCalledWith(
  235. 'Something went wrong when sending Autofix your message.'
  236. );
  237. });
  238. });
  239. });