import React from 'react'; import {shallow, mount} from 'enzyme'; import {Dashboard} from 'app/views/projectsDashboard'; import ProjectsStatsStore from 'app/stores/projectsStatsStore'; import * as projectsActions from 'app/actionCreators/projects'; jest.unmock('lodash/debounce'); jest.mock('lodash/debounce', () => { const debounceMap = new Map(); const mockDebounce = (fn, timeout) => { return (...args) => { if (debounceMap.has(fn)) { clearTimeout(debounceMap.get(fn)); } debounceMap.set( fn, setTimeout(() => { fn.apply(fn, args); debounceMap.delete(fn); }, timeout) ); }; }; return mockDebounce; }); describe('OrganizationDashboard', function() { const org = TestStubs.Organization(); const routerContext = TestStubs.routerContext([ {router: TestStubs.router({params: {orgId: org.slug}})}, ]); const team = TestStubs.Team(); const teams = [team]; beforeEach(function() { MockApiClient.addMockResponse({ url: `/teams/${org.slug}/${team.slug}/members/`, body: [], }); ProjectsStatsStore.reset(); }); afterEach(function() { MockApiClient.clearMockResponses(); }); describe('empty state', function() { it('renders with no projects', function() { const wrapper = mount( , routerContext ); expect(wrapper.find('Button[data-test-id="create-project"]').exists()).toBe(false); expect(wrapper.find('NoProjectMessage').exists()).toBe(true); }); it('renders with 1 project, with no first event', function() { const projects = [TestStubs.Project({teams})]; const wrapper = mount( , routerContext ); expect(wrapper.find('Button[data-test-id="create-project"]').exists()).toBe(true); expect(wrapper.find('TeamSection').exists()).toBe(true); expect(wrapper.find('Resources').exists()).toBe(true); }); }); describe('with projects', function() { it('renders TeamSection with two projects', function() { const projects = [ TestStubs.Project({ teams, firstEvent: true, }), TestStubs.Project({ teams, isBookmarked: true, firstEvent: true, }), ]; const wrapper = shallow( , routerContext ); expect(wrapper.find('Button[data-test-id="create-project"]').exists()).toBe(true); expect(wrapper.find('NoProjectMessage').exists()).toBe(false); expect(wrapper.find('TeamSection').exists()).toBe(true); expect(wrapper.find('Resources').exists()).toBe(false); }); it('renders bookmarked projects first in team list', function() { const projects = [ TestStubs.Project({ id: '1', slug: 'm', teams, isBookmarked: false, stats: [], }), TestStubs.Project({ id: '2', slug: 'm-fave', teams, isBookmarked: true, stats: [], }), TestStubs.Project({ id: '3', slug: 'a-fave', teams, isBookmarked: true, stats: [], }), TestStubs.Project({ id: '4', slug: 'z-fave', teams, isBookmarked: true, stats: [], }), TestStubs.Project({ id: '5', slug: 'a', teams, isBookmarked: false, stats: [], }), TestStubs.Project({ id: '6', slug: 'z', teams, isBookmarked: false, stats: [], }), ]; MockApiClient.addMockResponse({ url: `/organizations/${org.slug}/projects/`, body: [ TestStubs.Project({ teams, stats: [[1517281200, 2], [1517310000, 1]], }), ], }); jest.useFakeTimers(); const wrapper = mount( , routerContext ); jest.runAllTimers(); jest.useRealTimers(); const projectCards = wrapper.find('LazyLoadMock ProjectCard'); expect(projectCards.at(0).prop('data-test-id')).toBe('a-fave'); expect(projectCards.at(1).prop('data-test-id')).toBe('m-fave'); expect(projectCards.at(2).prop('data-test-id')).toBe('z-fave'); expect(projectCards.at(3).prop('data-test-id')).toBe('a'); expect(projectCards.at(4).prop('data-test-id')).toBe('m'); expect(projectCards.at(5).prop('data-test-id')).toBe('z'); }); }); describe('ProjectsStatsStore', function() { const projects = [ TestStubs.Project({ id: '1', slug: 'm', teams, isBookmarked: false, }), TestStubs.Project({ id: '2', slug: 'm-fave', teams, isBookmarked: true, }), TestStubs.Project({ id: '3', slug: 'a-fave', teams, isBookmarked: true, }), TestStubs.Project({ id: '4', slug: 'z-fave', teams, isBookmarked: true, }), TestStubs.Project({ id: '5', slug: 'a', teams, isBookmarked: false, }), TestStubs.Project({ id: '6', slug: 'z', teams, isBookmarked: false, }), ]; it('uses ProjectsStatsStore to load stats', async function() { jest.useFakeTimers(); ProjectsStatsStore.onStatsLoadSuccess([{...projects[0], stats: [[1517281200, 2]]}]); const loadStatsSpy = jest.spyOn(projectsActions, 'loadStatsForProject'); const mock = MockApiClient.addMockResponse({ url: `/organizations/${org.slug}/projects/`, body: projects.map(project => ({ ...project, stats: [[1517281200, 2], [1517310000, 1]], })), }); const wrapper = mount( , routerContext ); expect(loadStatsSpy).toHaveBeenCalledTimes(6); expect(mock).not.toHaveBeenCalled(); // Has 5 Loading Cards because 1 project has been loaded in store already expect(wrapper.find('LoadingCard')).toHaveLength(5); // Advance timers so that batched request fires jest.advanceTimersByTime(51); expect(mock).toHaveBeenCalledTimes(1); // query ids = 3, 2, 4 = bookmarked // 1 - already loaded in store so shouldn't be in query expect(mock).toHaveBeenCalledWith( expect.anything(), expect.objectContaining({ query: expect.objectContaining({ query: 'id:3 id:2 id:4 id:5 id:6', }), }) ); jest.useRealTimers(); await tick(); await tick(); wrapper.update(); expect(wrapper.find('LoadingCard')).toHaveLength(0); expect(wrapper.find('Chart')).toHaveLength(6); // Resets store when it unmounts wrapper.unmount(); expect(ProjectsStatsStore.getAll()).toEqual({}); }); }); });