import { fireEvent, render, screen, userEvent, within, } from 'sentry-test/reactTestingLibrary'; import GlobalModal from 'sentry/components/globalModal'; import GroupStore from 'sentry/stores/groupStore'; import SelectedGroupStore from 'sentry/stores/selectedGroupStore'; import {IssueCategory} from 'sentry/types'; import {IssueListActions} from 'sentry/views/issueList/actions'; import {OrganizationContext} from '../organizationContext'; const organization = TestStubs.Organization(); const defaultProps = { allResultsVisible: false, query: '', queryCount: 15, projectId: 'project-slug', selection: { projects: [1], environments: [], datetime: {start: null, end: null, period: null, utc: true}, }, groupIds: ['1', '2', '3'], onRealtimeChange: jest.fn(), onSelectStatsPeriod: jest.fn(), realtimeActive: false, statsPeriod: '24h', onDelete: jest.fn(), }; function WrappedComponent(props) { return ( ); } describe('IssueListActions', function () { afterEach(() => { jest.restoreAllMocks(); }); beforeEach(() => { GroupStore.reset(); SelectedGroupStore.reset(); SelectedGroupStore.add(['1', '2', '3']); }); describe('Bulk', function () { describe('Total results greater than bulk limit', function () { it('after checking "Select all" checkbox, displays bulk select message', function () { render(); userEvent.click(screen.getByRole('checkbox')); expect(screen.getByTestId('issue-list-select-all-notice')).toSnapshot(); }); it('can bulk select', function () { render(); userEvent.click(screen.getByRole('checkbox')); userEvent.click(screen.getByTestId('issue-list-select-all-notice-link')); expect(screen.getByTestId('issue-list-select-all-notice')).toSnapshot(); }); it('bulk resolves', async function () { const apiMock = MockApiClient.addMockResponse({ url: '/organizations/org-slug/issues/', method: 'PUT', }); render(); userEvent.click(screen.getByRole('checkbox')); userEvent.click(screen.getByTestId('issue-list-select-all-notice-link')); userEvent.click(screen.getByRole('button', {name: 'Resolve'})); await screen.findByRole('dialog'); userEvent.click(screen.getByRole('button', {name: 'Bulk resolve issues'})); expect(apiMock).toHaveBeenCalledWith( expect.anything(), expect.objectContaining({ query: { project: [1], }, data: {status: 'resolved', statusDetails: {}}, }) ); }); }); describe('Total results less than bulk limit', function () { it('after checking "Select all" checkbox, displays bulk select message', function () { render(); userEvent.click(screen.getByRole('checkbox')); expect(screen.getByTestId('issue-list-select-all-notice')).toSnapshot(); }); it('can bulk select', function () { render(); userEvent.click(screen.getByRole('checkbox')); userEvent.click(screen.getByTestId('issue-list-select-all-notice-link')); expect(screen.getByTestId('issue-list-select-all-notice')).toSnapshot(); }); it('bulk resolves', function () { const apiMock = MockApiClient.addMockResponse({ url: '/organizations/org-slug/issues/', method: 'PUT', }); render(); userEvent.click(screen.getByRole('checkbox')); userEvent.click(screen.getByTestId('issue-list-select-all-notice-link')); userEvent.click(screen.getByRole('button', {name: 'Resolve'})); const modal = screen.getByRole('dialog'); expect(modal).toSnapshot(); userEvent.click(within(modal).getByRole('button', {name: 'Bulk resolve issues'})); expect(apiMock).toHaveBeenCalledWith( expect.anything(), expect.objectContaining({ query: { project: [1], }, data: {status: 'resolved', statusDetails: {}}, }) ); }); }); describe('Selected on page', function () { it('resolves selected items', function () { const apiMock = MockApiClient.addMockResponse({ url: '/organizations/org-slug/issues/', method: 'PUT', }); jest.spyOn(SelectedGroupStore, 'getSelectedIds').mockReturnValue(new Set(['1'])); render(); const resolveButton = screen.getByRole('button', {name: 'Resolve'}); expect(resolveButton).toBeEnabled(); userEvent.click(resolveButton); expect(apiMock).toHaveBeenCalledWith( expect.anything(), expect.objectContaining({ query: { id: ['1'], project: [1], }, data: {status: 'resolved', statusDetails: {}}, }) ); }); it('can ignore selected items (custom)', async function () { const apiMock = MockApiClient.addMockResponse({ url: '/organizations/org-slug/issues/', method: 'PUT', }); jest.spyOn(SelectedGroupStore, 'getSelectedIds').mockReturnValue(new Set(['1'])); render(); userEvent.click(screen.getByRole('button', {name: 'Ignore options'})); fireEvent.click(screen.getByText(/Until this affects an additional/)); await screen.findByTestId('until-affect-custom'); userEvent.click(screen.getByTestId('until-affect-custom')); const modal = screen.getByRole('dialog'); userEvent.clear(within(modal).getByLabelText('Number of users')); userEvent.type(within(modal).getByLabelText('Number of users'), '300'); userEvent.click(within(modal).getByRole('textbox')); userEvent.click(within(modal).getByText('per week')); userEvent.click(within(modal).getByRole('button', {name: 'Ignore'})); expect(apiMock).toHaveBeenCalledWith( expect.anything(), expect.objectContaining({ query: { id: ['1'], project: [1], }, data: { status: 'ignored', statusDetails: { ignoreUserCount: 300, ignoreUserWindow: 10080, }, }, }) ); }); }); }); it('can resolve but not merge issues from different projects', function () { jest .spyOn(SelectedGroupStore, 'getSelectedIds') .mockImplementation(() => new Set(['1', '2', '3'])); jest.spyOn(GroupStore, 'get').mockImplementation(id => { switch (id) { case '1': return TestStubs.Group({project: TestStubs.Project({slug: 'project-1'})}); default: return TestStubs.Group({project: TestStubs.Project({slug: 'project-2'})}); } }); render(); // Can resolve but not merge issues from multiple projects expect(screen.getByRole('button', {name: 'Resolve'})).toBeEnabled(); expect(screen.getByRole('button', {name: 'Merge Selected Issues'})).toBeDisabled(); }); describe('mark reviewed', function () { it('acknowledges group', function () { const mockOnMarkReviewed = jest.fn(); jest .spyOn(SelectedGroupStore, 'getSelectedIds') .mockImplementation(() => new Set(['1', '2', '3'])); jest.spyOn(GroupStore, 'get').mockImplementation(id => { return TestStubs.Group({ id, inbox: { date_added: '2020-11-24T13:17:42.248751Z', reason: 0, reason_details: null, }, }); }); MockApiClient.warnOnMissingMocks(); render(); const reviewButton = screen.getByRole('button', {name: 'Mark Reviewed'}); expect(reviewButton).toBeEnabled(); userEvent.click(reviewButton); expect(mockOnMarkReviewed).toHaveBeenCalledWith(['1', '2', '3']); }); it('mark reviewed disabled for group that is already reviewed', function () { SelectedGroupStore.add(['1']); SelectedGroupStore.toggleSelectAll(); GroupStore.loadInitialData([TestStubs.Group({id: '1', inbox: null})]); MockApiClient.warnOnMissingMocks(); render(); expect(screen.getByRole('button', {name: 'Mark Reviewed'})).toBeDisabled(); }); }); describe('sort', function () { it('calls onSortChange with new sort value', function () { const mockOnSortChange = jest.fn(); render(); userEvent.click(screen.getByRole('button', {name: 'Last Seen'})); userEvent.click(screen.getByText(/Number of events/)); expect(mockOnSortChange).toHaveBeenCalledWith('freq'); }); }); describe('performance issues', function () { it('disables options that are not supported for performance issues', () => { jest .spyOn(SelectedGroupStore, 'getSelectedIds') .mockImplementation(() => new Set(['1', '2'])); jest.spyOn(GroupStore, 'get').mockImplementation(id => { switch (id) { case '1': return TestStubs.Group({ issueCategory: IssueCategory.ERROR, }); default: return TestStubs.Group({ issueCategory: IssueCategory.PERFORMANCE, }); } }); MockApiClient.warnOnMissingMocks(); render(); // Resolve and ignore are supported expect(screen.getByRole('button', {name: 'Resolve'})).toBeEnabled(); expect(screen.getByRole('button', {name: 'Ignore'})).toBeEnabled(); // Merge is not supported and should be disabled expect(screen.getByRole('button', {name: 'Merge Selected Issues'})).toBeDisabled(); // Open overflow menu userEvent.click(screen.getByRole('button', {name: 'More issue actions'})); // 'Add to Bookmarks' is supported expect( screen.getByRole('menuitemradio', {name: 'Add to Bookmarks'}) ).toHaveAttribute('aria-disabled', 'false'); // Deleting is not supported and menu item should be disabled expect(screen.getByRole('menuitemradio', {name: 'Delete'})).toHaveAttribute( 'aria-disabled', 'true' ); }); describe('bulk action performance issues', function () { const orgWithPerformanceIssues = TestStubs.Organization({ features: ['performance-issues'], }); it('silently filters out performance issues when bulk deleting', function () { const bulkDeleteMock = MockApiClient.addMockResponse({ url: '/organizations/org-slug/issues/', method: 'DELETE', }); render( ); userEvent.click(screen.getByRole('checkbox')); userEvent.click(screen.getByTestId('issue-list-select-all-notice-link')); userEvent.click(screen.getByRole('button', {name: 'More issue actions'})); userEvent.click(screen.getByRole('menuitemradio', {name: 'Delete'})); const modal = screen.getByRole('dialog'); expect( within(modal).getByText(/deleting performance issues is not yet supported/i) ).toBeInTheDocument(); userEvent.click(within(modal).getByRole('button', {name: 'Bulk delete issues'})); expect(bulkDeleteMock).toHaveBeenCalledWith( expect.anything(), expect.objectContaining({ query: expect.objectContaining({ query: 'is:unresolved !issue.category:performance', }), }) ); }); it('silently filters out performance issues when bulk merging', function () { const bulkMergeMock = MockApiClient.addMockResponse({ url: '/organizations/org-slug/issues/', method: 'PUT', }); // Ensure that all issues have the same project so we can merge jest .spyOn(GroupStore, 'get') .mockReturnValue( TestStubs.Group({project: TestStubs.Project({slug: 'project-1'})}) ); render( ); MockApiClient.warnOnMissingMocks(); userEvent.click(screen.getByRole('checkbox')); userEvent.click(screen.getByTestId('issue-list-select-all-notice-link')); userEvent.click(screen.getByRole('button', {name: 'Merge Selected Issues'})); const modal = screen.getByRole('dialog'); expect( within(modal).getByText(/merging performance issues is not yet supported/i) ).toBeInTheDocument(); userEvent.click(within(modal).getByRole('button', {name: 'Bulk merge issues'})); expect(bulkMergeMock).toHaveBeenCalledWith( expect.anything(), expect.objectContaining({ query: expect.objectContaining({ query: 'is:unresolved !issue.category:performance', }), }) ); }); }); }); });