import {AutofixCodebaseChangeData} from 'sentry-fixture/autofixCodebaseChangeData'; import {AutofixStepFixture} from 'sentry-fixture/autofixStep'; import { render, renderGlobalModal, screen, userEvent, waitFor, within, } from 'sentry-test/reactTestingLibrary'; import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator'; import AutofixMessageBox from 'sentry/components/events/autofix/autofixMessageBox'; import {AutofixStatus, AutofixStepType} from 'sentry/components/events/autofix/types'; jest.mock('sentry/actionCreators/indicator'); describe('AutofixMessageBox', () => { const defaultProps = { displayText: 'Test display text', groupId: '123', runId: '456', actionText: 'Send', allowEmptyMessage: false, responseRequired: false, step: null, onSend: null, }; const changesStepProps = { ...defaultProps, isChangesStep: true, step: AutofixStepFixture({ type: AutofixStepType.CHANGES, changes: [AutofixCodebaseChangeData()], }), }; const prCreatedProps = { ...changesStepProps, step: AutofixStepFixture({ type: AutofixStepType.CHANGES, status: AutofixStatus.COMPLETED, changes: [AutofixCodebaseChangeData()], }), }; const multiplePRsProps = { ...changesStepProps, step: AutofixStepFixture({ type: AutofixStepType.CHANGES, status: AutofixStatus.COMPLETED, changes: [ AutofixCodebaseChangeData({ repo_name: 'example/repo1', pull_request: { pr_url: 'https://github.com/example/repo1/pull/1', pr_number: 1, }, }), AutofixCodebaseChangeData({ repo_name: 'example/repo1', pull_request: { pr_url: 'https://github.com/example/repo2/pull/2', pr_number: 2, }, }), ], }), }; beforeEach(() => { (addSuccessMessage as jest.Mock).mockClear(); (addErrorMessage as jest.Mock).mockClear(); MockApiClient.clearMockResponses(); MockApiClient.addMockResponse({ url: '/issues/123/autofix/setup/?check_write_access=true', method: 'GET', body: { genAIConsent: {ok: true}, integration: {ok: true}, }, }); }); it('renders correctly with default props', () => { render(); expect(screen.getByText('Test display text')).toBeInTheDocument(); expect( screen.getByPlaceholderText('Share helpful context or directions...') ).toBeInTheDocument(); expect(screen.getByRole('button', {name: 'Send'})).toBeInTheDocument(); }); it('calls onSend when provided and button is clicked', async () => { const onSendMock = jest.fn(); render(); const input = screen.getByPlaceholderText('Share helpful context or directions...'); await userEvent.type(input, 'Test message'); await userEvent.click(screen.getByRole('button', {name: 'Send'})); expect(onSendMock).toHaveBeenCalledWith('Test message'); }); it('sends interjection message when onSend is not provided', async () => { MockApiClient.addMockResponse({ method: 'POST', url: '/issues/123/autofix/update/', body: {}, }); render(); const input = screen.getByPlaceholderText('Share helpful context or directions...'); await userEvent.type(input, 'Test message'); await userEvent.click(screen.getByRole('button', {name: 'Send'})); await waitFor(() => { expect(addSuccessMessage).toHaveBeenCalledWith('Thanks for the input.'); }); }); it('displays error message when API request fails', async () => { MockApiClient.addMockResponse({ url: '/issues/123/autofix/update/', method: 'POST', body: { detail: 'Internal Error', }, statusCode: 500, }); render(); const input = screen.getByPlaceholderText('Share helpful context or directions...'); await userEvent.type(input, 'Test message'); await userEvent.click(screen.getByRole('button', {name: 'Send'})); await waitFor(() => { expect(addErrorMessage).toHaveBeenCalledWith( 'Something went wrong when sending Autofix your message.' ); }); }); it('renders step icon and title when step is provided', () => { const stepProps = { ...defaultProps, step: AutofixStepFixture(), }; render(); expect(screen.getByText(AutofixStepFixture().title)).toBeInTheDocument(); }); it('renders required input style when responseRequired is true', () => { render(); expect( screen.getByPlaceholderText('Please answer to continue...') ).toBeInTheDocument(); }); it('handles suggested root cause selection correctly', async () => { const onSendMock = jest.fn(); render( ); // Test suggested root cause await userEvent.click(screen.getByRole('button', {name: 'Use suggested root cause'})); const input = screen.getByPlaceholderText( '(Optional) Provide any instructions for the fix...' ); await userEvent.type(input, 'Use this suggestion'); await userEvent.click(screen.getByRole('button', {name: 'Send'})); expect(onSendMock).toHaveBeenCalledWith('Use this suggestion', false); }); it('handles custom root cause selection correctly', async () => { const onSendMock = jest.fn(); render( ); // Test custom root cause await userEvent.click( screen.getByRole('button', {name: 'Propose your own root cause'}) ); const customInput = screen.getByPlaceholderText('Propose your own root cause...'); await userEvent.type(customInput, 'Custom root cause'); await userEvent.click(screen.getByRole('button', {name: 'Send'})); expect(onSendMock).toHaveBeenCalledWith('Custom root cause', true); }); it('renders segmented control for changes step', () => { render(); expect(screen.getByRole('button', {name: 'Iterate'})).toBeInTheDocument(); expect(screen.getByRole('button', {name: 'Use this code'})).toBeInTheDocument(); expect(screen.getByRole('button', {name: 'Add tests'})).toBeInTheDocument(); }); it('shows feedback input when "Iterate" is selected', async () => { render(); await userEvent.click(screen.getByRole('button', {name: 'Iterate'})); expect( screen.getByPlaceholderText('Share helpful context or directions...') ).toBeInTheDocument(); expect(screen.getByRole('button', {name: 'Send'})).toBeInTheDocument(); }); it('shows "Draft PR" button when "Approve" is selected', async () => { MockApiClient.addMockResponse({ url: '/issues/123/autofix/setup/?check_write_access=true', method: 'GET', body: { genAIConsent: {ok: true}, integration: {ok: true}, githubWriteIntegration: { repos: [{ok: true, owner: 'owner', name: 'hello-world', id: 100}], }, }, }); render(); await userEvent.click(screen.getByRole('button', {name: 'Use this code'})); expect(screen.getByText('Push the above changes to a branch?')).toBeInTheDocument(); expect(screen.getByRole('button', {name: 'Draft PR'})).toBeInTheDocument(); }); it('shows "Draft PRs" button with correct text for multiple changes', async () => { MockApiClient.addMockResponse({ url: '/issues/123/autofix/setup/?check_write_access=true', method: 'GET', body: { genAIConsent: {ok: true}, integration: {ok: true}, githubWriteIntegration: { repos: [{ok: true, owner: 'owner', name: 'hello-world', id: 100}], }, }, }); const multipleChangesProps = { ...changesStepProps, step: { ...changesStepProps.step, changes: [AutofixCodebaseChangeData(), AutofixCodebaseChangeData()], }, }; render(); await userEvent.click(screen.getByRole('button', {name: 'Use this code'})); expect(screen.getByText('Push the above changes to 2 branches?')).toBeInTheDocument(); expect(screen.getByRole('button', {name: 'Draft PRs'})).toBeInTheDocument(); }); it('shows "View PR" buttons when PRs are created', () => { render(); expect(screen.getByRole('button', {name: /View PR in/})).toBeInTheDocument(); expect(screen.getByRole('button', {name: /View PR in/})).toHaveAttribute( 'href', 'https://github.com/owner/hello-world/pull/200' ); }); it('shows multiple "View PR" buttons for multiple PRs', () => { render(); const viewPRButtons = screen.getAllByRole('button', {name: /View PR in/}); expect(viewPRButtons).toHaveLength(2); expect(viewPRButtons[0]).toHaveAttribute( 'href', 'https://github.com/example/repo1/pull/1' ); expect(viewPRButtons[1]).toHaveAttribute( 'href', 'https://github.com/example/repo2/pull/2' ); }); it('shows "Draft PRs" button that opens setup modal when setup is incomplete', async () => { MockApiClient.addMockResponse({ url: '/issues/123/autofix/setup/?check_write_access=true', method: 'GET', body: { genAIConsent: {ok: true}, integration: {ok: true}, githubWriteIntegration: { repos: [ {ok: false, provider: 'github', owner: 'owner', name: 'hello-world', id: 100}, ], }, }, }); MockApiClient.addMockResponse({ url: '/issues/123/autofix/setup/', method: 'GET', body: { genAIConsent: {ok: true}, integration: {ok: true}, }, }); render(); await userEvent.click(screen.getByRole('button', {name: 'Use this code'})); expect(screen.getByText('Push the above changes to a branch?')).toBeInTheDocument(); const createPRsButton = screen.getByRole('button', {name: 'Draft PR'}); expect(createPRsButton).toBeInTheDocument(); renderGlobalModal(); await userEvent.click(createPRsButton); expect(await screen.findByRole('dialog')).toBeInTheDocument(); expect( within(screen.getByRole('dialog')).getByText('Allow Autofix to Make Pull Requests') ).toBeInTheDocument(); }); it('shows option buttons for changes step', () => { render(); expect(screen.getByRole('button', {name: 'Use this code'})).toBeInTheDocument(); expect(screen.getByRole('button', {name: 'Iterate'})).toBeInTheDocument(); expect(screen.getByRole('button', {name: 'Add tests'})).toBeInTheDocument(); }); it('shows "Test" button and static message when "Test" is selected', async () => { render(); await userEvent.click(screen.getByRole('button', {name: 'Add tests'})); expect( screen.getByText('Write unit tests to make sure the issue is fixed?') ).toBeInTheDocument(); expect(screen.getByRole('button', {name: 'Add Tests'})).toBeInTheDocument(); }); it('sends correct message when "Test" is clicked without onSend prop', async () => { MockApiClient.addMockResponse({ method: 'POST', url: '/issues/123/autofix/update/', body: {}, }); render(); await userEvent.click(screen.getByRole('button', {name: 'Add tests'})); await userEvent.click(screen.getByRole('button', {name: 'Add Tests'})); await waitFor(() => { expect(addSuccessMessage).toHaveBeenCalledWith('Thanks for the input.'); }); }); });