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.');
});
});
});