|
@@ -0,0 +1,324 @@
|
|
|
+import {Fragment} from 'react';
|
|
|
+import {GlobalSelectionFixture} from 'sentry-fixture/globalSelection';
|
|
|
+import {GroupFixture} from 'sentry-fixture/group';
|
|
|
+import {GroupStatsFixture} from 'sentry-fixture/groupStats';
|
|
|
+import {LocationFixture} from 'sentry-fixture/locationFixture';
|
|
|
+import {OrganizationFixture} from 'sentry-fixture/organization';
|
|
|
+import {ProjectFixture} from 'sentry-fixture/project';
|
|
|
+import {RouteComponentPropsFixture} from 'sentry-fixture/routeComponentPropsFixture';
|
|
|
+import {TagsFixture} from 'sentry-fixture/tags';
|
|
|
+
|
|
|
+import {
|
|
|
+ render,
|
|
|
+ screen,
|
|
|
+ userEvent,
|
|
|
+ waitFor,
|
|
|
+ within,
|
|
|
+} from 'sentry-test/reactTestingLibrary';
|
|
|
+
|
|
|
+import Indicators from 'sentry/components/indicators';
|
|
|
+import GroupStore from 'sentry/stores/groupStore';
|
|
|
+import IssueListCacheStore from 'sentry/stores/IssueListCacheStore';
|
|
|
+import SelectedGroupStore from 'sentry/stores/selectedGroupStore';
|
|
|
+import TagStore from 'sentry/stores/tagStore';
|
|
|
+import IssueListOverview from 'sentry/views/issueList/overview';
|
|
|
+
|
|
|
+const DEFAULT_LINKS_HEADER =
|
|
|
+ '<http://127.0.0.1:8000/api/0/organizations/org-slug/issues/?cursor=1443575731:0:1>; rel="previous"; results="false"; cursor="1443575731:0:1", ' +
|
|
|
+ '<http://127.0.0.1:8000/api/0/organizations/org-slug/issues/?cursor=1443575000:0:0>; rel="next"; results="true"; cursor="1443575000:0:0"';
|
|
|
+
|
|
|
+describe('IssueListOverview (actions)', function () {
|
|
|
+ const project = ProjectFixture({
|
|
|
+ id: '3559',
|
|
|
+ name: 'Foo Project',
|
|
|
+ slug: 'project-slug',
|
|
|
+ firstEvent: new Date().toISOString(),
|
|
|
+ });
|
|
|
+ const tags = TagsFixture();
|
|
|
+ const groupStats = GroupStatsFixture();
|
|
|
+ const api = new MockApiClient();
|
|
|
+ const organization = OrganizationFixture({features: ['issue-priority-ui']});
|
|
|
+
|
|
|
+ beforeEach(function () {
|
|
|
+ MockApiClient.clearMockResponses();
|
|
|
+ GroupStore.reset();
|
|
|
+ SelectedGroupStore.reset();
|
|
|
+ IssueListCacheStore.reset();
|
|
|
+
|
|
|
+ MockApiClient.addMockResponse({
|
|
|
+ url: '/organizations/org-slug/issues-stats/',
|
|
|
+ body: [groupStats],
|
|
|
+ });
|
|
|
+ MockApiClient.addMockResponse({
|
|
|
+ url: '/organizations/org-slug/searches/',
|
|
|
+ body: [],
|
|
|
+ });
|
|
|
+ MockApiClient.addMockResponse({
|
|
|
+ url: '/organizations/org-slug/recent-searches/',
|
|
|
+ body: [],
|
|
|
+ });
|
|
|
+ MockApiClient.addMockResponse({
|
|
|
+ url: '/organizations/org-slug/recent-searches/',
|
|
|
+ method: 'POST',
|
|
|
+ body: [],
|
|
|
+ });
|
|
|
+ MockApiClient.addMockResponse({
|
|
|
+ url: '/organizations/org-slug/issues-count/',
|
|
|
+ method: 'GET',
|
|
|
+ body: [{}],
|
|
|
+ });
|
|
|
+ MockApiClient.addMockResponse({
|
|
|
+ url: '/organizations/org-slug/processingissues/',
|
|
|
+ method: 'GET',
|
|
|
+ body: [],
|
|
|
+ });
|
|
|
+ MockApiClient.addMockResponse({
|
|
|
+ url: '/organizations/org-slug/tags/',
|
|
|
+ method: 'GET',
|
|
|
+ body: [],
|
|
|
+ });
|
|
|
+ MockApiClient.addMockResponse({
|
|
|
+ url: '/organizations/org-slug/users/',
|
|
|
+ method: 'GET',
|
|
|
+ body: [],
|
|
|
+ });
|
|
|
+ MockApiClient.addMockResponse({
|
|
|
+ url: '/organizations/org-slug/sent-first-event/',
|
|
|
+ body: {sentFirstEvent: true},
|
|
|
+ });
|
|
|
+ MockApiClient.addMockResponse({
|
|
|
+ url: '/organizations/org-slug/projects/',
|
|
|
+ body: [],
|
|
|
+ });
|
|
|
+
|
|
|
+ TagStore.init?.();
|
|
|
+ });
|
|
|
+
|
|
|
+ const defaultProps = {
|
|
|
+ api,
|
|
|
+ savedSearchLoading: false,
|
|
|
+ savedSearches: [],
|
|
|
+ useOrgSavedSearches: true,
|
|
|
+ selection: GlobalSelectionFixture(),
|
|
|
+ organization,
|
|
|
+ tags: [
|
|
|
+ tags.reduce((acc, tag) => {
|
|
|
+ acc[tag.key] = tag;
|
|
|
+
|
|
|
+ return acc;
|
|
|
+ }),
|
|
|
+ ],
|
|
|
+ savedSearch: null,
|
|
|
+ selectedSearchId: null,
|
|
|
+ ...RouteComponentPropsFixture({
|
|
|
+ location: LocationFixture({
|
|
|
+ query: {query: 'is:unresolved issue.priority:[high,medium]'},
|
|
|
+ }),
|
|
|
+ params: {orgId: organization.slug, projectId: project.slug, searchId: undefined},
|
|
|
+ }),
|
|
|
+ };
|
|
|
+
|
|
|
+ describe('status', function () {
|
|
|
+ const group1 = GroupFixture({
|
|
|
+ id: '1',
|
|
|
+ culprit: 'Group 1',
|
|
|
+ shortId: 'JAVASCRIPT-1',
|
|
|
+ });
|
|
|
+ const group2 = GroupFixture({
|
|
|
+ id: '2',
|
|
|
+ culprit: 'Group 2',
|
|
|
+ shortId: 'JAVASCRIPT-2',
|
|
|
+ });
|
|
|
+
|
|
|
+ beforeEach(() => {
|
|
|
+ MockApiClient.addMockResponse({
|
|
|
+ url: '/organizations/org-slug/issues/',
|
|
|
+ body: [group1, group2],
|
|
|
+ headers: {Link: DEFAULT_LINKS_HEADER},
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ it('removes issues after resolving', async function () {
|
|
|
+ const updateIssueMock = MockApiClient.addMockResponse({
|
|
|
+ url: '/organizations/org-slug/issues/',
|
|
|
+ method: 'PUT',
|
|
|
+ });
|
|
|
+
|
|
|
+ render(<IssueListOverview {...defaultProps} />, {organization});
|
|
|
+
|
|
|
+ const groups = await screen.findAllByTestId('group');
|
|
|
+
|
|
|
+ await userEvent.click(
|
|
|
+ within(groups[0]).getByRole('checkbox', {name: /select issue/i})
|
|
|
+ );
|
|
|
+
|
|
|
+ expect(screen.getByText('Group 1')).toBeInTheDocument();
|
|
|
+ expect(screen.getByText('Group 2')).toBeInTheDocument();
|
|
|
+
|
|
|
+ // After action, will refetch so need to mock that response
|
|
|
+ MockApiClient.addMockResponse({
|
|
|
+ url: '/organizations/org-slug/issues/',
|
|
|
+ body: [group2],
|
|
|
+ headers: {Link: DEFAULT_LINKS_HEADER},
|
|
|
+ });
|
|
|
+
|
|
|
+ await userEvent.click(screen.getByRole('button', {name: 'Resolve'}));
|
|
|
+
|
|
|
+ expect(updateIssueMock).toHaveBeenCalledWith(
|
|
|
+ '/organizations/org-slug/issues/',
|
|
|
+ expect.objectContaining({
|
|
|
+ query: expect.objectContaining({id: ['1']}),
|
|
|
+ data: {status: 'resolved', statusDetails: {}, substatus: null},
|
|
|
+ })
|
|
|
+ );
|
|
|
+
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.queryByText('Group 1')).not.toBeInTheDocument();
|
|
|
+ expect(screen.getByText('Group 2')).toBeInTheDocument();
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ it('can undo resolve action', async function () {
|
|
|
+ const updateIssueMock = MockApiClient.addMockResponse({
|
|
|
+ url: '/organizations/org-slug/issues/',
|
|
|
+ method: 'PUT',
|
|
|
+ });
|
|
|
+
|
|
|
+ render(
|
|
|
+ <Fragment>
|
|
|
+ <IssueListOverview {...defaultProps} />
|
|
|
+ <Indicators />
|
|
|
+ </Fragment>,
|
|
|
+ {organization}
|
|
|
+ );
|
|
|
+
|
|
|
+ const groups = await screen.findAllByTestId('group');
|
|
|
+
|
|
|
+ await userEvent.click(
|
|
|
+ within(groups[0]).getByRole('checkbox', {name: /select issue/i})
|
|
|
+ );
|
|
|
+
|
|
|
+ expect(screen.getByText('Group 1')).toBeInTheDocument();
|
|
|
+ expect(screen.getByText('Group 2')).toBeInTheDocument();
|
|
|
+
|
|
|
+ // After action, will refetch so need to mock that response
|
|
|
+ MockApiClient.addMockResponse({
|
|
|
+ url: '/organizations/org-slug/issues/',
|
|
|
+ body: [group2],
|
|
|
+ headers: {Link: DEFAULT_LINKS_HEADER},
|
|
|
+ });
|
|
|
+
|
|
|
+ await userEvent.click(screen.getByRole('button', {name: 'Resolve'}));
|
|
|
+
|
|
|
+ expect(updateIssueMock).toHaveBeenCalledWith(
|
|
|
+ '/organizations/org-slug/issues/',
|
|
|
+ expect.objectContaining({
|
|
|
+ query: expect.objectContaining({id: ['1']}),
|
|
|
+ data: {status: 'resolved', statusDetails: {}, substatus: null},
|
|
|
+ })
|
|
|
+ );
|
|
|
+
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.queryByText('Group 1')).not.toBeInTheDocument();
|
|
|
+ expect(screen.getByText('Group 2')).toBeInTheDocument();
|
|
|
+ });
|
|
|
+
|
|
|
+ // Should show a toast message
|
|
|
+ expect(screen.getByText('Resolved JAVASCRIPT-1')).toBeInTheDocument();
|
|
|
+
|
|
|
+ // Clicking the undo button makes a call to set the status back to unresolved
|
|
|
+ MockApiClient.addMockResponse({
|
|
|
+ url: '/organizations/org-slug/issues/',
|
|
|
+ body: [group1, group2],
|
|
|
+ headers: {Link: DEFAULT_LINKS_HEADER},
|
|
|
+ });
|
|
|
+ await userEvent.click(screen.getByRole('button', {name: 'Undo'}));
|
|
|
+ expect(updateIssueMock).toHaveBeenLastCalledWith(
|
|
|
+ '/organizations/org-slug/issues/',
|
|
|
+ expect.objectContaining({
|
|
|
+ query: expect.objectContaining({id: ['1']}),
|
|
|
+ data: {status: 'unresolved', statusDetails: {}},
|
|
|
+ })
|
|
|
+ );
|
|
|
+ expect(await screen.findByText('Group 1')).toBeInTheDocument();
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ describe('mark reviewed', function () {
|
|
|
+ const group1 = GroupFixture({
|
|
|
+ id: '1',
|
|
|
+ culprit: 'Group 1',
|
|
|
+ shortId: 'JAVASCRIPT-1',
|
|
|
+ inbox: {},
|
|
|
+ });
|
|
|
+ const group2 = GroupFixture({
|
|
|
+ id: '2',
|
|
|
+ culprit: 'Group 2',
|
|
|
+ shortId: 'JAVASCRIPT-2',
|
|
|
+ inbox: {},
|
|
|
+ });
|
|
|
+
|
|
|
+ beforeEach(() => {
|
|
|
+ MockApiClient.addMockResponse({
|
|
|
+ url: '/organizations/org-slug/issues/',
|
|
|
+ body: [group1, group2],
|
|
|
+ headers: {Link: DEFAULT_LINKS_HEADER},
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ it('removes issues after making reviewed (when on for review tab)', async function () {
|
|
|
+ const updateIssueMock = MockApiClient.addMockResponse({
|
|
|
+ url: '/organizations/org-slug/issues/',
|
|
|
+ method: 'PUT',
|
|
|
+ });
|
|
|
+
|
|
|
+ render(
|
|
|
+ <IssueListOverview
|
|
|
+ {...defaultProps}
|
|
|
+ {...RouteComponentPropsFixture({
|
|
|
+ location: LocationFixture({
|
|
|
+ query: {query: 'is:for_review'},
|
|
|
+ }),
|
|
|
+ params: {
|
|
|
+ orgId: organization.slug,
|
|
|
+ projectId: project.slug,
|
|
|
+ searchId: undefined,
|
|
|
+ },
|
|
|
+ })}
|
|
|
+ />,
|
|
|
+ {organization}
|
|
|
+ );
|
|
|
+
|
|
|
+ const groups = await screen.findAllByTestId('group');
|
|
|
+
|
|
|
+ await userEvent.click(
|
|
|
+ within(groups[0]).getByRole('checkbox', {name: /select issue/i})
|
|
|
+ );
|
|
|
+
|
|
|
+ expect(screen.getByText('Group 1')).toBeInTheDocument();
|
|
|
+ expect(screen.getByText('Group 2')).toBeInTheDocument();
|
|
|
+
|
|
|
+ // After action, will refetch so need to mock that response
|
|
|
+ MockApiClient.addMockResponse({
|
|
|
+ url: '/organizations/org-slug/issues/',
|
|
|
+ body: [group2],
|
|
|
+ headers: {Link: DEFAULT_LINKS_HEADER},
|
|
|
+ });
|
|
|
+
|
|
|
+ await userEvent.click(screen.getByRole('button', {name: 'Mark Reviewed'}));
|
|
|
+
|
|
|
+ expect(updateIssueMock).toHaveBeenCalledWith(
|
|
|
+ '/organizations/org-slug/issues/',
|
|
|
+ expect.objectContaining({
|
|
|
+ query: expect.objectContaining({id: ['1']}),
|
|
|
+ data: {inbox: false},
|
|
|
+ })
|
|
|
+ );
|
|
|
+
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.queryByText('Group 1')).not.toBeInTheDocument();
|
|
|
+ expect(screen.getByText('Group 2')).toBeInTheDocument();
|
|
|
+ });
|
|
|
+ });
|
|
|
+ });
|
|
|
+});
|