import {ConfigFixture} from 'sentry-fixture/config';
import {EnvironmentsFixture} from 'sentry-fixture/environments';
import {EventFixture} from 'sentry-fixture/event';
import {GroupFixture} from 'sentry-fixture/group';
import {LocationFixture} from 'sentry-fixture/locationFixture';
import {ProjectFixture} from 'sentry-fixture/project';
import {TeamFixture} from 'sentry-fixture/team';
import {UserFixture} from 'sentry-fixture/user';
import {initializeOrg} from 'sentry-test/initializeOrg';
import {act, render, screen, waitFor} from 'sentry-test/reactTestingLibrary';
import ConfigStore from 'sentry/stores/configStore';
import GroupStore from 'sentry/stores/groupStore';
import OrganizationStore from 'sentry/stores/organizationStore';
import PageFiltersStore from 'sentry/stores/pageFiltersStore';
import ProjectsStore from 'sentry/stores/projectsStore';
import type {Environment, Group} from 'sentry/types';
import {IssueCategory} from 'sentry/types';
import GroupDetails from 'sentry/views/issueDetails/groupDetails';
jest.unmock('sentry/utils/recreateRoute');
const SAMPLE_EVENT_ALERT_TEXT =
'You are viewing a sample error. Configure Sentry to start viewing real errors.';
describe('groupDetails', () => {
const group = GroupFixture({issueCategory: IssueCategory.ERROR});
const event = EventFixture();
const project = ProjectFixture({teams: [TeamFixture()]});
const routes = [
{path: '/', childRoutes: []},
{childRoutes: []},
{
path: '/organizations/:orgId/issues/:groupId/',
childRoutes: [],
},
{},
];
const initRouter = {
location: {
pathname: `/organizations/org-slug/issues/${group.id}/`,
query: {},
search: '?foo=bar',
hash: '#hash',
},
params: {
groupId: group.id,
},
routes,
};
const defaultInit = initializeOrg<{groupId: string}>({
project,
router: initRouter,
});
const recommendedUser = UserFixture({
options: {
...UserFixture().options,
defaultIssueEvent: 'recommended',
},
});
const latestUser = UserFixture({
options: {
...UserFixture().options,
defaultIssueEvent: 'latest',
},
});
const oldestUser = UserFixture({
options: {
...UserFixture().options,
defaultIssueEvent: 'oldest',
},
});
function MockComponent({
group: groupProp,
environments,
eventError,
}: {
environments?: Environment[];
eventError?: boolean;
group?: Group;
}) {
return (
Group Details Mock
title: {groupProp?.title}
environment: {environments?.join(' ')}
{eventError &&
eventError
}
);
}
const createWrapper = (init = defaultInit) => {
return render(
,
{context: init.routerContext, organization: init.organization, router: init.router}
);
};
beforeEach(() => {
MockApiClient.clearMockResponses();
OrganizationStore.onUpdate(defaultInit.organization);
act(() => ProjectsStore.loadInitialData(defaultInit.organization.projects));
MockApiClient.addMockResponse({
url: `/organizations/${defaultInit.organization.slug}/issues/${group.id}/`,
body: {...group},
});
MockApiClient.addMockResponse({
url: `/organizations/${defaultInit.organization.slug}/issues/${group.id}/events/recommended/`,
statusCode: 200,
body: {
...event,
},
});
MockApiClient.addMockResponse({
url: `/projects/org-slug/${project.slug}/issues/`,
method: 'PUT',
body: {
hasSeen: false,
},
});
MockApiClient.addMockResponse({
url: '/organizations/org-slug/projects/',
body: [project],
});
MockApiClient.addMockResponse({
url: `/organizations/${defaultInit.organization.slug}/issues/${group.id}/first-last-release/`,
method: 'GET',
});
MockApiClient.addMockResponse({
url: `/organizations/${defaultInit.organization.slug}/events/`,
statusCode: 200,
body: {
data: [
{
'count()': 1,
},
],
},
});
MockApiClient.addMockResponse({
url: `/organizations/${defaultInit.organization.slug}/environments/`,
body: EnvironmentsFixture(),
});
MockApiClient.addMockResponse({
url: `/organizations/${defaultInit.organization.slug}/issues/${group.id}/tags/`,
body: [],
});
MockApiClient.addMockResponse({
url: '/organizations/org-slug/replay-count/',
body: {},
});
});
afterEach(() => {
act(() => ProjectsStore.reset());
GroupStore.reset();
PageFiltersStore.reset();
MockApiClient.clearMockResponses();
});
it('renders', async function () {
act(() => ProjectsStore.reset());
createWrapper();
expect(screen.queryByText(group.title)).not.toBeInTheDocument();
act(() => ProjectsStore.loadInitialData(defaultInit.organization.projects));
expect(await screen.findByText(group.title, {exact: false})).toBeInTheDocument();
// Sample event alert should not show up
expect(screen.queryByText(SAMPLE_EVENT_ALERT_TEXT)).not.toBeInTheDocument();
});
it('renders error when issue is not found', async function () {
MockApiClient.addMockResponse({
url: `/organizations/${defaultInit.organization.slug}/issues/${group.id}/`,
statusCode: 404,
});
MockApiClient.addMockResponse({
url: `/organization/${defaultInit.organization.slug}/issues/${group.id}/events/recommended/`,
statusCode: 404,
});
createWrapper();
await waitFor(() =>
expect(screen.queryByTestId('loading-indicator')).not.toBeInTheDocument()
);
expect(
await screen.findByText('The issue you were looking for was not found.')
).toBeInTheDocument();
});
it('renders MissingProjectMembership when trying to access issue in project the user does not belong to', async function () {
MockApiClient.addMockResponse({
url: `/organizations/${defaultInit.organization.slug}/issues/${group.id}/`,
statusCode: 403,
});
MockApiClient.addMockResponse({
url: `/organizations/${defaultInit.organization.slug}/issues/${group.id}/events/recommended/`,
statusCode: 403,
});
createWrapper();
await waitFor(() =>
expect(screen.queryByTestId('loading-indicator')).not.toBeInTheDocument()
);
expect(
await screen.findByText(
'No teams have access to this project yet. Ask an admin to add your team to this project.'
)
).toBeInTheDocument();
});
it('fetches issue details for a given environment', async function () {
const init = initializeOrg({
router: {
...initRouter,
location: LocationFixture({
...initRouter.location,
query: {environment: 'staging'},
}),
},
});
createWrapper(init);
await waitFor(() =>
expect(screen.queryByTestId('loading-indicator')).not.toBeInTheDocument()
);
expect(await screen.findByText('environment: staging')).toBeInTheDocument();
});
it('renders issue event error', async function () {
MockApiClient.addMockResponse({
url: `/organizations/${defaultInit.organization.slug}/issues/${group.id}/events/recommended/`,
statusCode: 404,
});
createWrapper();
expect(await screen.findByText('eventError')).toBeInTheDocument();
});
it('renders substatus badge', async function () {
MockApiClient.addMockResponse({
url: `/organizations/${defaultInit.organization.slug}/issues/${group.id}/`,
body: {
...group,
inbox: null,
status: 'unresolved',
substatus: 'ongoing',
},
});
createWrapper({
...defaultInit,
organization: {...defaultInit.organization},
});
expect(await screen.findByText('Ongoing')).toBeInTheDocument();
});
it('renders alert for sample event', async function () {
MockApiClient.addMockResponse({
url: `/organizations/${defaultInit.organization.slug}/issues/${group.id}/tags/`,
body: [{key: 'sample_event'}],
});
createWrapper();
expect(await screen.findByText(SAMPLE_EVENT_ALERT_TEXT)).toBeInTheDocument();
});
it('renders error when project does not exist', async function () {
MockApiClient.addMockResponse({
url: `/projects/org-slug/other-project-slug/issues/`,
method: 'PUT',
});
MockApiClient.addMockResponse({
url: `/organizations/${defaultInit.organization.slug}/issues/${group.id}/`,
body: {...group, project: {slug: 'other-project-slug'}},
});
createWrapper();
expect(
await screen.findByText('The project other-project-slug does not exist')
).toBeInTheDocument();
});
it('uses /recommended endpoint when feature flag is on and no event is provided', async function () {
const recommendedMock = MockApiClient.addMockResponse({
url: `/organizations/${defaultInit.organization.slug}/issues/${group.id}/events/recommended/`,
statusCode: 200,
body: event,
});
createWrapper();
await waitFor(() => expect(recommendedMock).toHaveBeenCalledTimes(1));
});
it('uses /latest endpoint when default is set to latest', async function () {
ConfigStore.loadInitialData(ConfigFixture({user: latestUser}));
const latestMock = MockApiClient.addMockResponse({
url: `/organizations/${defaultInit.organization.slug}/issues/${group.id}/events/latest/`,
statusCode: 200,
body: event,
});
createWrapper();
await waitFor(() => expect(latestMock).toHaveBeenCalledTimes(1));
});
it('uses /oldest endpoint when default is set to oldest', async function () {
ConfigStore.loadInitialData(ConfigFixture({user: oldestUser}));
const oldestMock = MockApiClient.addMockResponse({
url: `/organizations/${defaultInit.organization.slug}/issues/${group.id}/events/oldest/`,
statusCode: 200,
body: event,
});
createWrapper();
await waitFor(() => expect(oldestMock).toHaveBeenCalledTimes(1));
});
it('uses /recommended endpoint when default is set to recommended', async function () {
ConfigStore.loadInitialData(ConfigFixture({user: recommendedUser}));
const recommendedMock = MockApiClient.addMockResponse({
url: `/organizations/${defaultInit.organization.slug}/issues/${group.id}/events/recommended/`,
statusCode: 200,
body: event,
});
createWrapper();
await waitFor(() => expect(recommendedMock).toHaveBeenCalledTimes(1));
});
});