Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions static/app/components/feedback/useDeleteFeedback.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import {Fragment} from 'react';
import {OrganizationFixture} from 'sentry-fixture/organization';

import {
render,
screen,
userEvent,
waitFor,
within,
} from 'sentry-test/reactTestingLibrary';

import {GlobalModal} from '@sentry/scraps/modal';

import {clearIndicators} from 'sentry/actionCreators/indicator';
import {useDeleteFeedback} from 'sentry/components/feedback/useDeleteFeedback';
import Indicators from 'sentry/components/indicators';

const mockRefetchFeedbackList = jest.fn();

jest.mock('sentry/components/feedback/list/useRefetchFeedbackList', () => ({
useRefetchFeedbackList: () => ({refetchFeedbackList: mockRefetchFeedbackList}),
}));

const organization = OrganizationFixture();
const initialPath = `/organizations/${organization.slug}/issues/feedback/123/`;

function DeleteFeedbackButton() {
const deleteFeedback = useDeleteFeedback(['123'], 'project');

return <button onClick={deleteFeedback}>Delete feedback</button>;
}

function renderDeleteFeedback() {
return render(
<Fragment>
<GlobalModal />
<DeleteFeedbackButton />
<Indicators />
</Fragment>,
{
organization,
initialRouterConfig: {
location: {pathname: initialPath},
route: '/organizations/:orgId/issues/feedback/:feedbackId/',
},
}
);
}

async function confirmDelete() {
await userEvent.click(screen.getByRole('button', {name: 'Delete feedback'}));
await userEvent.click(
within(await screen.findByRole('dialog')).getByRole('button', {name: 'Delete'})
);
}

describe('useDeleteFeedback', () => {
beforeEach(() => {
clearIndicators();
mockRefetchFeedbackList.mockClear();
});

it('navigates after deleting feedback successfully', async () => {
MockApiClient.addMockResponse({
url: `/projects/${organization.slug}/project/issues/`,
method: 'DELETE',
body: {},
});
const {router} = renderDeleteFeedback();

await confirmDelete();

await waitFor(() => {
expect(router.location.pathname).toBe(
`/organizations/${organization.slug}/issues/feedback/`
);
});
expect(mockRefetchFeedbackList).toHaveBeenCalledTimes(1);
});

it('refetches without navigating when deleting feedback fails', async () => {
MockApiClient.addMockResponse({
url: `/projects/${organization.slug}/project/issues/`,
method: 'DELETE',
statusCode: 500,
});
const {router} = renderDeleteFeedback();

await confirmDelete();

expect(
await screen.findByText('Unable to delete events. Please try again.')
).toBeInTheDocument();
expect(mockRefetchFeedbackList).toHaveBeenCalledTimes(1);
expect(router.location.pathname).toBe(initialPath);
});
});
4 changes: 2 additions & 2 deletions static/app/components/feedback/useDeleteFeedback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,7 @@ export const useDeleteFeedback = (feedbackIds: any, projectId: any) => {
itemIds: feedbackIds,
},
{
complete: () => {
refetchFeedbackList();
success: () => {
navigate(
normalizeUrl({
pathname: makeFeedbackPathname({
Expand All @@ -51,6 +50,7 @@ export const useDeleteFeedback = (feedbackIds: any, projectId: any) => {
})
);
},
complete: refetchFeedbackList,
}
);
},
Expand Down
105 changes: 105 additions & 0 deletions static/app/views/issueDetails/actions/index.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@ import {

import {GlobalModal} from '@sentry/scraps/modal';

import {clearIndicators} from 'sentry/actionCreators/indicator';
import {
CMDKCollection,
CommandPaletteProvider,
type CMDKActionData,
} from 'sentry/components/commandPalette/ui/cmdk';
import type {CollectionTreeNode} from 'sentry/components/commandPalette/ui/collection';
import {CommandPaletteSlot} from 'sentry/components/commandPalette/ui/commandPaletteSlot';
import Indicators from 'sentry/components/indicators';
import {mockTour} from 'sentry/components/tours/testUtils';
import {ConfigStore} from 'sentry/stores/configStore';
import {ModalStore} from 'sentry/stores/modalStore';
Expand Down Expand Up @@ -92,6 +94,7 @@ describe('GroupActions', () => {
const analyticsSpy = jest.spyOn(analytics, 'trackAnalytics');

beforeEach(() => {
clearIndicators();
ConfigStore.init();
ProjectsStore.reset();
ProjectsStore.loadInitialData([project]);
Expand Down Expand Up @@ -288,6 +291,45 @@ describe('GroupActions', () => {
);
});

it('does not report success or navigate when deletion fails', async () => {
const org = OrganizationFixture({
...organization,
access: [...organization.access, 'event:admin'],
});
MockApiClient.addMockResponse({
url: `/projects/${org.slug}/${project.slug}/issues/`,
method: 'DELETE',
statusCode: 500,
});
const initialPath = `/organizations/${org.slug}/issues/${group.id}/`;
const {router} = render(
<Fragment>
<GlobalModal />
<GroupActions group={group} project={project} disabled={false} event={null} />
<Indicators />
</Fragment>,
{
organization: org,
initialRouterConfig: {
location: {pathname: initialPath},
route: '/organizations/:orgId/issues/:groupId/',
},
}
);

await userEvent.click(screen.getByLabelText('More Actions'));
await userEvent.click(await screen.findByRole('menuitemradio', {name: 'Delete'}));
await userEvent.click(
within(screen.getByRole('dialog')).getByRole('button', {name: 'Delete'})
);

expect(
await screen.findByText('Unable to delete events. Please try again.')
).toBeInTheDocument();
expect(screen.queryByText('Issue deleted')).not.toBeInTheDocument();
expect(router.location.pathname).toBe(initialPath);
});

it('delete for issue platform', async () => {
const org = OrganizationFixture({
...organization,
Expand Down Expand Up @@ -391,6 +433,69 @@ describe('GroupActions', () => {
);
});

it('does not report success when resolving fails', async () => {
MockApiClient.addMockResponse({
url: `/projects/${organization.slug}/project/issues/`,
method: 'PUT',
statusCode: 500,
});

render(
<Fragment>
<GroupActions group={group} project={project} disabled={false} event={null} />
<Indicators />
</Fragment>,
{organization}
);

await userEvent.click(screen.getByRole('button', {name: 'Resolve'}));

expect(
await screen.findByText('Unable to update events. Please try again.')
).toBeInTheDocument();
expect(screen.queryByText('Issue resolved')).not.toBeInTheDocument();
});

it('refetches group data when resolving fails', async () => {
MockApiClient.addMockResponse({
url: `/projects/${organization.slug}/project/issues/`,
method: 'PUT',
statusCode: 500,
});
const groupFetchApi = MockApiClient.addMockResponse({
url: `/organizations/${organization.slug}/issues/${group.id}/`,
method: 'GET',
body: group,
});

function GroupActionsWrapper() {
const {data: groupData, isLoading} = useGroup({groupId: group.id});

if (isLoading || !groupData) {
return <div>Loading...</div>;
}

return (
<GroupActions group={groupData} project={project} disabled={false} event={null} />
);
}

render(
<Fragment>
<GroupActionsWrapper />
<Indicators />
</Fragment>,
{organization}
);

await waitFor(() => expect(groupFetchApi).toHaveBeenCalledTimes(1));
await userEvent.click(await screen.findByRole('button', {name: 'Resolve'}));
expect(
await screen.findByText('Unable to update events. Please try again.')
).toBeInTheDocument();
await waitFor(() => expect(groupFetchApi).toHaveBeenCalledTimes(2));
});

it('can archive issue', async () => {
const issuesApi = MockApiClient.addMockResponse({
url: `/projects/${organization.slug}/project/issues/`,
Expand Down
6 changes: 4 additions & 2 deletions static/app/views/issueDetails/actions/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ export function GroupActions({group, project, disabled, event}: GroupActionsProp
itemIds: [group.id],
},
{
complete: () => {
success: () => {
clearIndicators();

addSuccessMessage(t('Issue deleted'));
Expand Down Expand Up @@ -294,12 +294,14 @@ export function GroupActions({group, project, disabled, event}: GroupActionsProp
data,
},
{
complete: () => {
success: () => {
clearIndicators();
if (successMessage) {
addSuccessMessage(successMessage);
}
onComplete?.();
},
complete: () => {
queryClient.invalidateQueries({
queryKey: groupQueryKey({
organizationSlug: organization.slug,
Expand Down
Loading