From d494ebc7309b363a8d131b1d719131864196e04c Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Mon, 10 Sep 2018 14:19:27 +0200 Subject: [PATCH] flattened team state, tests for TeamMembers --- .../app/features/teams/TeamMembers.test.tsx | 79 +++++ public/app/features/teams/TeamMembers.tsx | 24 +- public/app/features/teams/TeamPages.tsx | 4 +- .../app/features/teams/__mocks__/teamMocks.ts | 26 +- .../__snapshots__/TeamMembers.test.tsx.snap | 317 ++++++++++++++++++ .../__snapshots__/TeamPages.test.tsx.snap | 21 +- public/app/features/teams/state/actions.ts | 1 + .../app/features/teams/state/reducers.test.ts | 36 +- public/app/features/teams/state/reducers.ts | 11 +- .../features/teams/state/selectors.test.ts | 22 +- public/app/features/teams/state/selectors.ts | 11 +- public/app/types/index.ts | 5 +- 12 files changed, 493 insertions(+), 64 deletions(-) create mode 100644 public/app/features/teams/TeamMembers.test.tsx create mode 100644 public/app/features/teams/__snapshots__/TeamMembers.test.tsx.snap diff --git a/public/app/features/teams/TeamMembers.test.tsx b/public/app/features/teams/TeamMembers.test.tsx new file mode 100644 index 00000000000..cae37e184fb --- /dev/null +++ b/public/app/features/teams/TeamMembers.test.tsx @@ -0,0 +1,79 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import { TeamMembers, Props } from './TeamMembers'; +import { TeamMember } from '../../types'; +import { getMockTeamMember, getMockTeamMembers } from './__mocks__/teamMocks'; + +const setup = (propOverrides?: object) => { + const props: Props = { + members: [] as TeamMember[], + searchMemberQuery: '', + setSearchMemberQuery: jest.fn(), + loadTeamMembers: jest.fn(), + addTeamMember: jest.fn(), + removeTeamMember: jest.fn(), + }; + + Object.assign(props, propOverrides); + + const wrapper = shallow(); + const instance = wrapper.instance() as TeamMembers; + + return { + wrapper, + instance, + }; +}; + +describe('Render', () => { + it('should render component', () => { + const { wrapper } = setup(); + + expect(wrapper).toMatchSnapshot(); + }); + + it('should render team members', () => { + const { wrapper } = setup({ + members: getMockTeamMembers(5), + }); + + expect(wrapper).toMatchSnapshot(); + }); +}); + +describe('Functions', () => { + describe('on search member query change', () => { + it('it should call setSearchMemberQuery', () => { + const { instance } = setup(); + const mockEvent = { target: { value: 'member' } }; + + instance.onSearchQueryChange(mockEvent); + + expect(instance.props.setSearchMemberQuery).toHaveBeenCalledWith('member'); + }); + }); + + describe('on remove member', () => { + const { instance } = setup(); + const mockTeamMember = getMockTeamMember(); + + instance.onRemoveMember(mockTeamMember); + + expect(instance.props.removeTeamMember).toHaveBeenCalledWith(1); + }); + + describe('on add user to team', () => { + const { wrapper, instance } = setup(); + + wrapper.state().newTeamMember = { + id: 1, + label: '', + avatarUrl: '', + login: '', + }; + + instance.onAddUserToTeam(); + + expect(instance.props.addTeamMember).toHaveBeenCalledWith(1); + }); +}); diff --git a/public/app/features/teams/TeamMembers.tsx b/public/app/features/teams/TeamMembers.tsx index 115fb40e184..5ad688aabf8 100644 --- a/public/app/features/teams/TeamMembers.tsx +++ b/public/app/features/teams/TeamMembers.tsx @@ -1,16 +1,14 @@ import React, { PureComponent } from 'react'; import { connect } from 'react-redux'; -import { hot } from 'react-hot-loader'; import SlideDown from 'app/core/components/Animations/SlideDown'; import { UserPicker, User } from 'app/core/components/Picker/UserPicker'; import DeleteButton from 'app/core/components/DeleteButton/DeleteButton'; -import { Team, TeamMember } from '../../types'; +import { TeamMember } from '../../types'; import { loadTeamMembers, addTeamMember, removeTeamMember, setSearchMemberQuery } from './state/actions'; -import { getSearchMemberQuery, getTeam } from './state/selectors'; -import { getRouteParamsId } from '../../core/selectors/location'; +import { getSearchMemberQuery, getTeamMembers } from './state/selectors'; -interface Props { - team: Team; +export interface Props { + members: TeamMember[]; searchMemberQuery: string; loadTeamMembers: typeof loadTeamMembers; addTeamMember: typeof addTeamMember; @@ -37,7 +35,7 @@ export class TeamMembers extends PureComponent { this.props.setSearchMemberQuery(event.target.value); }; - removeMember(member: TeamMember) { + onRemoveMember(member: TeamMember) { this.props.removeTeamMember(member.userId); } @@ -63,7 +61,7 @@ export class TeamMembers extends PureComponent { {member.login} {member.email} - this.removeMember(member)} /> + this.onRemoveMember(member)} /> ); @@ -71,7 +69,7 @@ export class TeamMembers extends PureComponent { render() { const { newTeamMember, isAdding } = this.state; - const { team, searchMemberQuery } = this.props; + const { searchMemberQuery, members } = this.props; const newTeamMemberValue = newTeamMember && newTeamMember.id.toString(); return ( @@ -125,7 +123,7 @@ export class TeamMembers extends PureComponent { - {team.members && team.members.map(member => this.renderMember(member))} + {members && members.map(member => this.renderMember(member))} @@ -134,10 +132,8 @@ export class TeamMembers extends PureComponent { } function mapStateToProps(state) { - const teamId = getRouteParamsId(state.location); - return { - team: getTeam(state.team, teamId), + members: getTeamMembers(state.team), searchMemberQuery: getSearchMemberQuery(state.team), }; } @@ -149,4 +145,4 @@ const mapDispatchToProps = { setSearchMemberQuery, }; -export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(TeamMembers)); +export default connect(mapStateToProps, mapDispatchToProps)(TeamMembers); diff --git a/public/app/features/teams/TeamPages.tsx b/public/app/features/teams/TeamPages.tsx index 4395c0bfbef..2528c3c87b8 100644 --- a/public/app/features/teams/TeamPages.tsx +++ b/public/app/features/teams/TeamPages.tsx @@ -41,10 +41,10 @@ export class TeamPages extends PureComponent { } componentDidMount() { - this.loadTeam(); + this.fetchTeam(); } - async loadTeam() { + async fetchTeam() { const { loadTeam, teamId } = this.props; await loadTeam(teamId); diff --git a/public/app/features/teams/__mocks__/teamMocks.ts b/public/app/features/teams/__mocks__/teamMocks.ts index 21c0cf012f0..7050997c387 100644 --- a/public/app/features/teams/__mocks__/teamMocks.ts +++ b/public/app/features/teams/__mocks__/teamMocks.ts @@ -1,4 +1,4 @@ -import { Team } from '../../../types'; +import { Team, TeamMember } from '../../../types'; export const getMultipleMockTeams = (numberOfTeams: number): Team[] => { let teams: Team[] = []; @@ -9,9 +9,6 @@ export const getMultipleMockTeams = (numberOfTeams: number): Team[] => { avatarUrl: 'some/url/', email: `test-${i}@test.com`, memberCount: i, - search: '', - members: [], - groups: [], }); } @@ -25,13 +22,26 @@ export const getMockTeam = (): Team => { avatarUrl: 'some/url/', email: 'test@test.com', memberCount: 1, - search: '', - members: [], - groups: [], }; }; -export const getMockTeamMember = () => { +export const getMockTeamMembers = (amount: number): TeamMember[] => { + let teamMembers: TeamMember[] = []; + + for (let i = 1; i <= amount; i++) { + teamMembers.push({ + userId: i, + teamId: 1, + avatarUrl: 'some/url/', + email: 'test@test.com', + login: `testUser-${i}`, + }); + } + + return teamMembers; +}; + +export const getMockTeamMember = (): TeamMember => { return { userId: 1, teamId: 1, diff --git a/public/app/features/teams/__snapshots__/TeamMembers.test.tsx.snap b/public/app/features/teams/__snapshots__/TeamMembers.test.tsx.snap new file mode 100644 index 00000000000..2a42897e2b9 --- /dev/null +++ b/public/app/features/teams/__snapshots__/TeamMembers.test.tsx.snap @@ -0,0 +1,317 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Render should render component 1`] = ` +
+
+
+ +
+
+ +
+ +
+ +
+ Add Team Member +
+
+ +
+
+
+
+ + + + + + + + +
+ + Name + + Email + +
+
+
+`; + +exports[`Render should render team members 1`] = ` +
+
+
+ +
+
+ +
+ +
+ +
+ Add Team Member +
+
+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + Name + + Email + +
+ + + testUser-1 + + test@test.com + + +
+ + + testUser-2 + + test@test.com + + +
+ + + testUser-3 + + test@test.com + + +
+ + + testUser-4 + + test@test.com + + +
+ + + testUser-5 + + test@test.com + + +
+
+
+`; diff --git a/public/app/features/teams/__snapshots__/TeamPages.test.tsx.snap b/public/app/features/teams/__snapshots__/TeamPages.test.tsx.snap index 3c19d726e41..563d3d3bb99 100644 --- a/public/app/features/teams/__snapshots__/TeamPages.test.tsx.snap +++ b/public/app/features/teams/__snapshots__/TeamPages.test.tsx.snap @@ -21,12 +21,9 @@ exports[`Render should render group sync page 1`] = ` Object { "avatarUrl": "some/url/", "email": "test@test.com", - "groups": Array [], "id": 1, "memberCount": 1, - "members": Array [], "name": "test", - "search": "", } } /> @@ -42,20 +39,7 @@ exports[`Render should render member page if team not empty 1`] = `
- +
`; @@ -73,12 +57,9 @@ exports[`Render should render settings page 1`] = ` Object { "avatarUrl": "some/url/", "email": "test@test.com", - "groups": Array [], "id": 1, "memberCount": 1, - "members": Array [], "name": "test", - "search": "", } } /> diff --git a/public/app/features/teams/state/actions.ts b/public/app/features/teams/state/actions.ts index e407737bb20..4786edf60a8 100644 --- a/public/app/features/teams/state/actions.ts +++ b/public/app/features/teams/state/actions.ts @@ -117,6 +117,7 @@ export function loadTeam(id: number): ThunkResult { } export function loadTeamMembers(): ThunkResult { + console.log('loading team members'); return async (dispatch, getStore) => { const team = getStore().team.team; diff --git a/public/app/features/teams/state/reducers.test.ts b/public/app/features/teams/state/reducers.test.ts index 492ec71ba4b..7f7a33d60ac 100644 --- a/public/app/features/teams/state/reducers.test.ts +++ b/public/app/features/teams/state/reducers.test.ts @@ -31,22 +31,42 @@ describe('teams reducer', () => { }); describe('team reducer', () => { + it('should set team', () => { + const payload = getMockTeam(); + + const action: Action = { + type: ActionTypes.LoadTeam, + payload, + }; + + const result = teamReducer(initialTeamState, action); + + expect(result.team).toEqual(payload); + }); + it('should set team members', () => { const mockTeamMember = getMockTeamMember(); - const mockTeam = getMockTeam(); - const state = { - ...initialTeamState, - team: mockTeam, - }; const action: Action = { type: ActionTypes.LoadTeamMembers, payload: [mockTeamMember], }; - const result = teamReducer(state, action); - const expectedState = { team: { ...mockTeam, members: [mockTeamMember] }, searchQuery: '' }; + const result = teamReducer(initialTeamState, action); - expect(result).toEqual(expectedState); + expect(result.members).toEqual([mockTeamMember]); + }); + + it('should set member search query', () => { + const payload = 'member'; + + const action: Action = { + type: ActionTypes.SetSearchMemberQuery, + payload, + }; + + const result = teamReducer(initialTeamState, action); + + expect(result.searchMemberQuery).toEqual('member'); }); }); diff --git a/public/app/features/teams/state/reducers.ts b/public/app/features/teams/state/reducers.ts index e30fddb22a5..f02ade60923 100644 --- a/public/app/features/teams/state/reducers.ts +++ b/public/app/features/teams/state/reducers.ts @@ -1,8 +1,13 @@ -import { Team, TeamsState, TeamState } from '../../../types'; +import { Team, TeamGroup, TeamMember, TeamsState, TeamState } from '../../../types'; import { Action, ActionTypes } from './actions'; export const initialTeamsState: TeamsState = { teams: [], searchQuery: '' }; -export const initialTeamState: TeamState = { team: {} as Team, searchMemberQuery: '' }; +export const initialTeamState: TeamState = { + team: {} as Team, + members: [] as TeamMember[], + groups: [] as TeamGroup[], + searchMemberQuery: '', +}; export const teamsReducer = (state = initialTeamsState, action: Action): TeamsState => { switch (action.type) { @@ -21,7 +26,7 @@ export const teamReducer = (state = initialTeamState, action: Action): TeamState return { ...state, team: action.payload }; case ActionTypes.LoadTeamMembers: - return { ...state, team: { ...state.team, members: action.payload } }; + return { ...state, members: action.payload }; case ActionTypes.SetSearchMemberQuery: return { ...state, searchMemberQuery: action.payload }; diff --git a/public/app/features/teams/state/selectors.test.ts b/public/app/features/teams/state/selectors.test.ts index 66fd07444ce..e1b11cf288b 100644 --- a/public/app/features/teams/state/selectors.test.ts +++ b/public/app/features/teams/state/selectors.test.ts @@ -1,8 +1,8 @@ -import { getTeams } from './selectors'; -import { getMultipleMockTeams } from '../__mocks__/teamMocks'; -import { TeamsState } from '../../../types'; +import { getTeam, getTeams } from './selectors'; +import { getMockTeam, getMultipleMockTeams } from '../__mocks__/teamMocks'; +import { TeamsState, TeamState } from '../../../types'; -describe('Team selectors', () => { +describe('Teams selectors', () => { describe('Get teams', () => { const mockTeams = getMultipleMockTeams(5); @@ -23,3 +23,17 @@ describe('Team selectors', () => { }); }); }); + +describe('Team selectors', () => { + describe('Get team', () => { + const mockTeam = getMockTeam(); + + it('should return team if matching with location team', () => { + const mockState: TeamState = { team: mockTeam, searchMemberQuery: '' }; + + const team = getTeam(mockState, '1'); + + expect(team).toEqual(mockTeam); + }); + }); +}); diff --git a/public/app/features/teams/state/selectors.ts b/public/app/features/teams/state/selectors.ts index d6142adf157..5e22f96eaf7 100644 --- a/public/app/features/teams/state/selectors.ts +++ b/public/app/features/teams/state/selectors.ts @@ -2,8 +2,7 @@ export const getSearchQuery = state => state.searchQuery; export const getSearchMemberQuery = state => state.searchMemberQuery; export const getTeam = (state, currentTeamId) => { - if (state.team.id === currentTeamId) { - console.log('yes'); + if (state.team.id === parseInt(currentTeamId)) { return state.team; } }; @@ -15,3 +14,11 @@ export const getTeams = state => { return regex.test(team.name); }); }; + +export const getTeamMembers = state => { + const regex = RegExp(state.searchMemberQuery, 'i'); + + return state.members.filter(member => { + return regex.test(member.login) || regex.test(member.email); + }); +}; diff --git a/public/app/types/index.ts b/public/app/types/index.ts index 27ae3dbe19b..35cd9a41f4e 100644 --- a/public/app/types/index.ts +++ b/public/app/types/index.ts @@ -63,9 +63,6 @@ export interface Team { avatarUrl: string; email: string; memberCount: number; - search?: string; - members?: TeamMember[]; - groups?: TeamGroup[]; } export interface TeamMember { @@ -124,6 +121,8 @@ export interface TeamsState { export interface TeamState { team: Team; + members: TeamMember[]; + groups: TeamGroup[]; searchMemberQuery: string; }