import {mountWithTheme} from 'sentry-test/enzyme'; import {initializeOrg} from 'sentry-test/initializeOrg'; import {act} from 'sentry-test/reactTestingLibrary'; import StreamGroup from 'sentry/components/stream/group'; import TagStore from 'sentry/stores/tagStore'; import IssueList from 'sentry/views/issueList/overview'; // Mock (need to toggling real time polling) jest.mock('sentry/views/issueList/sidebar', () => jest.fn(() => null)); jest.mock('sentry/views/issueList/filters', () => jest.fn(() => null)); jest.mock('sentry/components/stream/group', () => jest.fn(() => null)); jest.mock('js-cookie', () => ({ get: jest.fn(), set: jest.fn(), })); const PREVIOUS_PAGE_CURSOR = '1443575731'; const DEFAULT_LINKS_HEADER = `; rel="previous"; results="false"; cursor="${PREVIOUS_PAGE_CURSOR}:0:1", ` + '; rel="next"; results="true"; cursor="1443575000:0:0"'; jest.useFakeTimers(); describe('IssueList -> Polling', function () { let wrapper; let issuesRequest; let pollRequest; const {organization, project, router, routerContext} = initializeOrg({ organization: { access: ['releases'], }, }); const savedSearch = TestStubs.Search({ id: '789', query: 'is:unresolved', name: 'Unresolved Issues', projectId: project.id, }); const group = TestStubs.Group({project}); const defaultProps = { location: {query: {query: 'is:unresolved'}, search: 'query=is:unresolved'}, params: {orgId: organization.slug}, organization, }; /* helpers */ const createWrapper = async ({params, location, ...p} = {}) => { const newRouter = { ...router, params: { ...router.params, ...params, }, location: { ...router.location, ...location, }, }; wrapper = mountWithTheme( , routerContext ); await act(async () => { await Promise.resolve(); jest.runAllTimers(); }); wrapper.update(); return wrapper; }; beforeEach(function () { // The tests fail because we have a "component update was not wrapped in act" error. // It should be safe to ignore this error, but we should remove the mock once we move to react testing library // eslint-disable-next-line no-console jest.spyOn(console, 'error').mockImplementation(jest.fn()); MockApiClient.clearMockResponses(); MockApiClient.addMockResponse({ url: '/organizations/org-slug/searches/', body: [savedSearch], }); MockApiClient.addMockResponse({ url: '/organizations/org-slug/recent-searches/', body: [], }); MockApiClient.addMockResponse({ url: '/organizations/org-slug/issues-count/', method: 'GET', body: [{}], }); MockApiClient.addMockResponse({ url: '/organizations/org-slug/processingissues/', method: 'GET', body: [ { project: 'test-project', numIssues: 1, hasIssues: true, lastSeen: '2019-01-16T15:39:11.081Z', }, ], }); MockApiClient.addMockResponse({ url: '/organizations/org-slug/tags/', method: 'GET', body: TestStubs.Tags(), }); MockApiClient.addMockResponse({ url: '/organizations/org-slug/users/', method: 'GET', body: [TestStubs.Member({projects: [project.slug]})], }); MockApiClient.addMockResponse({ url: '/organizations/org-slug/recent-searches/', method: 'GET', body: [], }); MockApiClient.addMockResponse({ url: '/organizations/org-slug/searches/', body: [savedSearch], }); issuesRequest = MockApiClient.addMockResponse({ url: '/organizations/org-slug/issues/', body: [group], headers: { Link: DEFAULT_LINKS_HEADER, }, }); const groupStats = TestStubs.GroupStats(); MockApiClient.addMockResponse({ url: '/organizations/org-slug/issues-stats/', body: [groupStats], }); pollRequest = MockApiClient.addMockResponse({ url: `http://127.0.0.1:8000/api/0/organizations/org-slug/issues/?cursor=${PREVIOUS_PAGE_CURSOR}:0:1`, body: [], headers: { Link: DEFAULT_LINKS_HEADER, }, }); StreamGroup.mockClear(); TagStore.init(); }); afterEach(function () { MockApiClient.clearMockResponses(); if (wrapper) { wrapper.unmount(); } wrapper = null; TagStore.teardown(); }); it('toggles polling for new issues', async function () { await createWrapper(); expect(issuesRequest).toHaveBeenCalledWith( expect.anything(), expect.objectContaining({ // Should be called with default query data: expect.stringContaining('is%3Aunresolved'), }) ); // Enable real time control expect(wrapper.find('button[data-test-id="real-time"] IconPlay')).toHaveLength(1); wrapper.find('button[data-test-id="real-time"]').simulate('click'); expect(wrapper.find('button[data-test-id="real-time"] IconPlay')).toHaveLength(0); // Each poll request gets delayed by additional 3s, up to max of 60s jest.advanceTimersByTime(3001); expect(pollRequest).toHaveBeenCalledTimes(1); jest.advanceTimersByTime(6001); expect(pollRequest).toHaveBeenCalledTimes(2); // Pauses wrapper.find('button[data-test-id="real-time"]').simulate('click'); expect(wrapper.find('button[data-test-id="real-time"] IconPlay')).toHaveLength(1); jest.advanceTimersByTime(12001); expect(pollRequest).toHaveBeenCalledTimes(2); }); it('stops polling for new issues when endpoint returns a 401', async function () { pollRequest = MockApiClient.addMockResponse({ url: `http://127.0.0.1:8000/api/0/organizations/org-slug/issues/?cursor=${PREVIOUS_PAGE_CURSOR}:0:1`, body: [], statusCode: 401, }); await createWrapper(); // Enable real time control wrapper.find('button[data-test-id="real-time"]').simulate('click'); expect(wrapper.find('button[data-test-id="real-time"] IconPlay')).toHaveLength(0); // Each poll request gets delayed by additional 3s, up to max of 60s jest.advanceTimersByTime(3001); expect(pollRequest).toHaveBeenCalledTimes(1); jest.advanceTimersByTime(9001); expect(pollRequest).toHaveBeenCalledTimes(1); }); it('stops polling for new issues when endpoint returns a 403', async function () { pollRequest = MockApiClient.addMockResponse({ url: `http://127.0.0.1:8000/api/0/organizations/org-slug/issues/?cursor=${PREVIOUS_PAGE_CURSOR}:0:1`, body: [], statusCode: 403, }); await createWrapper(); // Enable real time control expect(wrapper.find('button[data-test-id="real-time"] IconPlay')).toHaveLength(1); wrapper.find('button[data-test-id="real-time"]').simulate('click'); expect(wrapper.find('button[data-test-id="real-time"] IconPlay')).toHaveLength(0); // Each poll request gets delayed by additional 3s, up to max of 60s jest.advanceTimersByTime(3001); expect(pollRequest).toHaveBeenCalledTimes(1); jest.advanceTimersByTime(9001); expect(pollRequest).toHaveBeenCalledTimes(1); }); it('stops polling for new issues when endpoint returns a 404', async function () { pollRequest = MockApiClient.addMockResponse({ url: `http://127.0.0.1:8000/api/0/organizations/org-slug/issues/?cursor=${PREVIOUS_PAGE_CURSOR}:0:1`, body: [], statusCode: 404, }); await createWrapper(); // Enable real time control wrapper.find('button[data-test-id="real-time"]').simulate('click'); expect(wrapper.find('button[data-test-id="real-time"] IconPlay')).toHaveLength(0); // Each poll request gets delayed by additional 3s, up to max of 60s jest.advanceTimersByTime(3001); expect(pollRequest).toHaveBeenCalledTimes(1); jest.advanceTimersByTime(9001); expect(pollRequest).toHaveBeenCalledTimes(1); }); });