import {initializeOrg} from 'sentry-test/initializeOrg'; import {render, screen, waitFor} from 'sentry-test/reactTestingLibrary'; import ProjectsStore from 'sentry/stores/projectsStore'; import GroupReplays from 'sentry/views/issueDetails/groupReplays'; jest.mock('sentry/utils/useMedia', () => ({ __esModule: true, default: jest.fn(() => true), })); const mockEventsUrl = '/organizations/org-slug/events/'; const mockReplayUrl = '/organizations/org-slug/replays/'; type InitializeOrgProps = { organizationProps?: { features?: string[]; }; }; function init({ organizationProps = {features: ['session-replay-ui']}, }: InitializeOrgProps) { const mockProject = TestStubs.Project(); const {router, organization, routerContext} = initializeOrg({ organization: { ...organizationProps, }, project: mockProject, projects: [mockProject], router: { routes: [ {path: '/'}, {path: '/organizations/:orgId/issues/:groupId/'}, {path: 'replays/'}, ], location: { pathname: '/organizations/org-slug/replays/', query: {}, }, }, }); ProjectsStore.init(); ProjectsStore.loadInitialData(organization.projects); return {router, organization, routerContext}; } describe('GroupReplays', () => { beforeEach(() => { MockApiClient.clearMockResponses(); }); describe('Replay Feature Disabled', () => { const mockGroup = TestStubs.Group(); const {router, organization, routerContext} = init({ organizationProps: {features: []}, }); it("should show a message when the organization doesn't have access to the replay feature", () => { render(, { context: routerContext, organization, router, }); expect( screen.getByText("You don't have access to this feature") ).toBeInTheDocument(); }); }); describe('Replay Feature Enabled', () => { const {router, organization, routerContext} = init({}); it('should query the events endpoint with the fetched replayIds', async () => { const mockGroup = TestStubs.Group(); const mockEventsApi = MockApiClient.addMockResponse({ url: mockEventsUrl, body: { data: [ {replayId: '346789a703f6454384f1de473b8b9fcc', 'count()': 1}, {replayId: 'b05dae9b6be54d21a4d5ad9f8f02b780', 'count()': 1}, ], }, }); const mockReplayApi = MockApiClient.addMockResponse({ url: mockReplayUrl, body: { data: [], }, }); render(, { context: routerContext, organization, router, }); await waitFor(() => { expect(mockEventsApi).toHaveBeenCalledWith( mockEventsUrl, expect.objectContaining({ query: { environment: [], field: ['replayId', 'count()'], per_page: 50, project: ['2'], query: `issue.id:${mockGroup.id} !replayId:""`, statsPeriod: '14d', }, }) ); // Expect api path to have the correct query params expect(mockReplayApi).toHaveBeenCalledWith( mockReplayUrl, expect.objectContaining({ query: expect.objectContaining({ environment: [], field: [ 'activity', 'count_errors', 'duration', 'finished_at', 'id', 'project_id', 'started_at', 'urls', 'user', ], per_page: 50, project: ['2'], query: 'id:[346789a703f6454384f1de473b8b9fcc,b05dae9b6be54d21a4d5ad9f8f02b780]', sort: '-started_at', statsPeriod: '14d', }), }) ); }); }); it('should show empty message when no replays are found', async () => { const mockGroup = TestStubs.Group(); const mockEventsApi = MockApiClient.addMockResponse({ url: mockEventsUrl, body: { data: [ {replayId: '346789a703f6454384f1de473b8b9fcc', 'count()': 1}, {replayId: 'b05dae9b6be54d21a4d5ad9f8f02b780', 'count()': 1}, ], }, }); const mockReplayApi = MockApiClient.addMockResponse({ url: mockReplayUrl, body: { data: [], }, }); const {container} = render(, { context: routerContext, organization, router, }); expect( await screen.findByText('There are no items to display') ).toBeInTheDocument(); expect(mockEventsApi).toHaveBeenCalledTimes(1); expect(mockReplayApi).toHaveBeenCalledTimes(1); expect(container).toSnapshot(); }); it('should display error message when api call fails', async () => { const mockGroup = TestStubs.Group(); const mockEventsApi = MockApiClient.addMockResponse({ url: mockEventsUrl, body: { data: [ {replayId: '346789a703f6454384f1de473b8b9fcc', 'count()': 1}, {replayId: 'b05dae9b6be54d21a4d5ad9f8f02b780', 'count()': 1}, ], }, }); const mockReplayApi = MockApiClient.addMockResponse({ url: mockReplayUrl, statusCode: 500, body: { detail: 'Invalid number: asdf. Expected number.', }, }); render(, { context: routerContext, organization, router, }); await waitFor(() => { expect(mockEventsApi).toHaveBeenCalledTimes(1); expect(mockReplayApi).toHaveBeenCalledTimes(1); expect( screen.getByText('Invalid number: asdf. Expected number.') ).toBeInTheDocument(); }); }); it('should display default error message when api call fails without a body', async () => { const mockGroup = TestStubs.Group(); const mockEventsApi = MockApiClient.addMockResponse({ url: mockEventsUrl, body: { data: [ {replayId: '346789a703f6454384f1de473b8b9fcc', 'count()': 1}, {replayId: 'b05dae9b6be54d21a4d5ad9f8f02b780', 'count()': 1}, ], }, }); const mockReplayApi = MockApiClient.addMockResponse({ url: mockReplayUrl, statusCode: 500, body: {}, }); render(, { context: routerContext, organization, router, }); await waitFor(() => { expect(mockEventsApi).toHaveBeenCalledTimes(1); expect(mockReplayApi).toHaveBeenCalledTimes(1); expect( screen.getByText( 'Sorry, the list of replays could not be loaded. This could be due to invalid search parameters or an internal systems error.' ) ).toBeInTheDocument(); }); }); it('should show loading indicator when loading replays', async () => { const mockGroup = TestStubs.Group(); const mockEventsApi = MockApiClient.addMockResponse({ url: mockEventsUrl, body: { data: [ {replayId: '346789a703f6454384f1de473b8b9fcc', 'count()': 1}, {replayId: 'b05dae9b6be54d21a4d5ad9f8f02b780', 'count()': 1}, ], }, }); const mockReplayApi = MockApiClient.addMockResponse({ url: mockReplayUrl, statusCode: 200, body: { data: [], }, }); render(, { context: routerContext, organization, router, }); expect(screen.getByTestId('loading-indicator')).toBeInTheDocument(); await waitFor(() => { expect(mockEventsApi).toHaveBeenCalledTimes(1); expect(mockReplayApi).toHaveBeenCalledTimes(1); }); }); it('should show a list of replays and have the correct values', async () => { const mockGroup = TestStubs.Group(); const mockEventsApi = MockApiClient.addMockResponse({ url: mockEventsUrl, body: { data: [ {replayId: '346789a703f6454384f1de473b8b9fcc', 'count()': 1}, {replayId: 'b05dae9b6be54d21a4d5ad9f8f02b780', 'count()': 1}, ], }, }); const mockReplayApi = MockApiClient.addMockResponse({ url: mockReplayUrl, statusCode: 200, body: { data: [ { count_errors: 1, duration: 52346, finished_at: '2022-09-15T06:54:00+00:00', id: '346789a703f6454384f1de473b8b9fcc', project_id: '2', started_at: '2022-09-15T06:50:03+00:00', urls: [ 'https://dev.getsentry.net:7999/organizations/sentry-emerging-tech/replays/', '/organizations/sentry-emerging-tech/replays/?project=2', ], user: { id: '147086', name: '', email: '', ip: '127.0.0.1', display_name: 'testDisplayName', }, }, { count_errors: 4, duration: 400, finished_at: '2022-09-21T21:40:38+00:00', id: 'b05dae9b6be54d21a4d5ad9f8f02b780', project_id: '2', started_at: '2022-09-21T21:30:44+00:00', urls: [ 'https://dev.getsentry.net:7999/organizations/sentry-emerging-tech/replays/?project=2&statsPeriod=24h', '/organizations/sentry-emerging-tech/issues/', '/organizations/sentry-emerging-tech/issues/?project=2', ], user: { id: '147086', name: '', email: '', ip: '127.0.0.1', display_name: 'testDisplayName', }, }, ], }, }); // Mock the system date to be 2022-09-28 jest.useFakeTimers().setSystemTime(new Date('Sep 28, 2022 11:29:13 PM UTC')); render(, { context: routerContext, organization, router, }); await waitFor(() => { expect(mockEventsApi).toHaveBeenCalledTimes(1); expect(mockReplayApi).toHaveBeenCalledTimes(1); }); // Expect the table to have 2 rows expect(screen.getAllByText('testDisplayName')).toHaveLength(2); const expectedQuery = 'query=&referrer=%2Forganizations%2F%3AorgId%2Fissues%2F%3AgroupId%2Freplays%2F&statsPeriod=14d&yAxis=count%28%29'; // Expect the first row to have the correct href expect(screen.getAllByRole('link', {name: 'testDisplayName'})[0]).toHaveAttribute( 'href', `/organizations/org-slug/replays/project-slug:346789a703f6454384f1de473b8b9fcc/?${expectedQuery}` ); // Expect the second row to have the correct href expect(screen.getAllByRole('link', {name: 'testDisplayName'})[1]).toHaveAttribute( 'href', `/organizations/org-slug/replays/project-slug:b05dae9b6be54d21a4d5ad9f8f02b780/?${expectedQuery}` ); // Expect the first row to have the correct duration expect(screen.getByText('14hr 32min 26s')).toBeInTheDocument(); // Expect the second row to have the correct duration expect(screen.getByText('6min 40s')).toBeInTheDocument(); // Expect the first row to have the correct errors expect(screen.getAllByTestId('replay-table-count-errors')[0]).toHaveTextContent( '1' ); // Expect the second row to have the correct errors expect(screen.getAllByTestId('replay-table-count-errors')[1]).toHaveTextContent( '4' ); // Expect the first row to have the correct date expect(screen.getByText('14 days ago')).toBeInTheDocument(); // Expect the second row to have the correct date expect(screen.getByText('7 days ago')).toBeInTheDocument(); }); }); describe('sorting', () => { let mockEventsApi; let mockReplayApi; beforeEach(() => { mockEventsApi = MockApiClient.addMockResponse({ url: mockEventsUrl, body: { data: [ {replayId: '346789a703f6454384f1de473b8b9fcc', 'count()': 1}, {replayId: 'b05dae9b6be54d21a4d5ad9f8f02b780', 'count()': 1}, ], }, }); mockReplayApi = MockApiClient.addMockResponse({ url: mockReplayUrl, body: { data: [], }, statusCode: 200, }); }); it('should not call the events api again when sorting the visible rows', async () => { const mockGroup = TestStubs.Group(); const {router, organization, routerContext} = init({}); const {rerender} = render(, { context: routerContext, organization, router, }); await waitFor(() => { expect(mockEventsApi).toHaveBeenCalledTimes(1); expect(mockReplayApi).toHaveBeenCalledTimes(1); expect(mockReplayApi).toHaveBeenLastCalledWith( mockReplayUrl, expect.objectContaining({ query: expect.objectContaining({ sort: '-started_at', }), }) ); }); // Change the sort order then tell react to re-render router.location.query.sort = 'duration'; rerender(); await waitFor(() => { expect(mockEventsApi).toHaveBeenCalledTimes(1); expect(mockReplayApi).toHaveBeenCalledTimes(2); expect(mockReplayApi).toHaveBeenLastCalledWith( mockReplayUrl, expect.objectContaining({ query: expect.objectContaining({ sort: 'duration', }), }) ); }); }); }); });