From f68ac2021873bcc827de00423a5af90daab34faa Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Wed, 5 Sep 2018 16:49:36 +0200 Subject: [PATCH] set search query action and tests --- public/app/features/teams/TeamList.test.tsx | 30 +-- public/app/features/teams/TeamList.tsx | 20 +- .../app/features/teams/__mocks__/teamMocks.ts | 32 +++ .../__snapshots__/TeamList.test.tsx.snap | 204 +++++++++++++++++- public/app/features/teams/state/actions.ts | 13 +- .../app/features/teams/state/reducers.test.ts | 41 ++++ public/app/features/teams/state/reducers.ts | 7 +- .../features/teams/state/selectors.test.ts | 25 +++ public/app/features/teams/state/selectors.ts | 10 +- public/app/types/index.ts | 1 + 10 files changed, 354 insertions(+), 29 deletions(-) create mode 100644 public/app/features/teams/__mocks__/teamMocks.ts create mode 100644 public/app/features/teams/state/reducers.test.ts create mode 100644 public/app/features/teams/state/selectors.test.ts diff --git a/public/app/features/teams/TeamList.test.tsx b/public/app/features/teams/TeamList.test.tsx index 7e4caff80ae..6c12f1357e5 100644 --- a/public/app/features/teams/TeamList.test.tsx +++ b/public/app/features/teams/TeamList.test.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import { Props, TeamList } from './TeamList'; import { NavModel, Team } from '../../types'; +import { getMockTeam, getMultipleMockTeams } from './__mocks__/teamMocks'; const setup = (propOverrides?: object) => { const props: Props = { @@ -9,7 +10,8 @@ const setup = (propOverrides?: object) => { teams: [] as Team[], loadTeams: jest.fn(), deleteTeam: jest.fn(), - search: '', + setSearchQuery: jest.fn(), + searchQuery: '', }; Object.assign(props, propOverrides); @@ -23,17 +25,6 @@ const setup = (propOverrides?: object) => { }; }; -const mockTeam: Team = { - id: 1, - name: 'test', - avatarUrl: 'some/url/', - email: 'test@test.com', - memberCount: 1, - search: '', - members: [], - groups: [], -}; - describe('Render', () => { it('should render component', () => { const { wrapper } = setup(); @@ -42,7 +33,7 @@ describe('Render', () => { it('should render teams table', () => { const { wrapper } = setup({ - teams: [mockTeam], + teams: getMultipleMockTeams(5), }); expect(wrapper).toMatchSnapshot(); @@ -63,9 +54,20 @@ describe('Functions', () => { describe('Delete team', () => { it('should call delete team', () => { const { instance } = setup(); - instance.deleteTeam(mockTeam); + instance.deleteTeam(getMockTeam()); expect(instance.props.deleteTeam).toHaveBeenCalledWith(1); }); }); + + describe('on search query change', () => { + it('should call setSearchQuery', () => { + const { instance } = setup(); + const mockEvent = { target: { value: 'test' } }; + + instance.onSearchQueryChange(mockEvent); + + expect(instance.props.setSearchQuery).toHaveBeenCalledWith('test'); + }); + }); }); diff --git a/public/app/features/teams/TeamList.tsx b/public/app/features/teams/TeamList.tsx index df8776920d7..a95f0f17a27 100644 --- a/public/app/features/teams/TeamList.tsx +++ b/public/app/features/teams/TeamList.tsx @@ -4,8 +4,8 @@ import { hot } from 'react-hot-loader'; import PageHeader from 'app/core/components/PageHeader/PageHeader'; import DeleteButton from 'app/core/components/DeleteButton/DeleteButton'; import { NavModel, Team } from '../../types'; -import { loadTeams, deleteTeam } from './state/actions'; -import { getTeams } from './state/selectors'; +import { loadTeams, deleteTeam, setSearchQuery } from './state/actions'; +import { getSearchQuery, getTeams } from './state/selectors'; import { getNavModel } from 'app/core/selectors/navModel'; export interface Props { @@ -13,7 +13,8 @@ export interface Props { teams: Team[]; loadTeams: typeof loadTeams; deleteTeam: typeof deleteTeam; - search: string; + setSearchQuery: typeof setSearchQuery; + searchQuery: string; } export class TeamList extends PureComponent { @@ -30,10 +31,10 @@ export class TeamList extends PureComponent { }; onSearchQueryChange = event => { - console.log('set search', event.target.value); + this.props.setSearchQuery(event.target.value); }; - renderTeamMember(team: Team) { + renderTeam(team: Team) { const teamUrl = `org/teams/edit/${team.id}`; return ( @@ -60,7 +61,7 @@ export class TeamList extends PureComponent { } render() { - const { navModel, teams, search } = this.props; + const { navModel, teams, searchQuery } = this.props; return (
@@ -73,7 +74,7 @@ export class TeamList extends PureComponent { type="text" className="gf-form-input" placeholder="Search teams" - value={search} + value={searchQuery} onChange={this.onSearchQueryChange} /> @@ -98,7 +99,7 @@ export class TeamList extends PureComponent { - {teams.map(team => this.renderTeamMember(team))} + {teams.map(team => this.renderTeam(team))}
@@ -111,13 +112,14 @@ function mapStateToProps(state) { return { navModel: getNavModel(state.navIndex, 'teams'), teams: getTeams(state.teams), - search: '', + searchQuery: getSearchQuery(state.teams), }; } const mapDispatchToProps = { loadTeams, deleteTeam, + setSearchQuery, }; export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(TeamList)); diff --git a/public/app/features/teams/__mocks__/teamMocks.ts b/public/app/features/teams/__mocks__/teamMocks.ts new file mode 100644 index 00000000000..34405d2ce91 --- /dev/null +++ b/public/app/features/teams/__mocks__/teamMocks.ts @@ -0,0 +1,32 @@ +import { Team } from '../../../types'; + +export const getMultipleMockTeams = (numberOfTeams: number): Team[] => { + let teams: Team[] = []; + for (let i = 1; i <= numberOfTeams; i++) { + teams.push({ + id: i, + name: `test-${i}`, + avatarUrl: 'some/url/', + email: `test-${i}@test.com`, + memberCount: i, + search: '', + members: [], + groups: [], + }); + } + + return teams; +}; + +export const getMockTeam = (): Team => { + return { + id: 1, + name: 'test', + avatarUrl: 'some/url/', + email: 'test@test.com', + memberCount: 1, + search: '', + members: [], + groups: [], + }; +}; diff --git a/public/app/features/teams/__snapshots__/TeamList.test.tsx.snap b/public/app/features/teams/__snapshots__/TeamList.test.tsx.snap index c93dafde1c6..6ea189f5dbd 100644 --- a/public/app/features/teams/__snapshots__/TeamList.test.tsx.snap +++ b/public/app/features/teams/__snapshots__/TeamList.test.tsx.snap @@ -167,7 +167,7 @@ exports[`Render should render teams table 1`] = ` - test + test-1 - test@test.com + test-1@test.com + + + + + + + + + test-2 + + + + + test-2@test.com + + + + + 2 + + + + + + + + + + + + + + + test-3 + + + + + test-3@test.com + + + + + 3 + + + + + + + + + + + + + + + test-4 + + + + + test-4@test.com + + + + + 4 + + + + + + + + + + + + + + + test-5 + + + + + test-5@test.com + + + + + 5 + + + + + + diff --git a/public/app/features/teams/state/actions.ts b/public/app/features/teams/state/actions.ts index 6afed1828c1..5914a932ad0 100644 --- a/public/app/features/teams/state/actions.ts +++ b/public/app/features/teams/state/actions.ts @@ -4,6 +4,7 @@ import { StoreState, Team } from '../../../types'; export enum ActionTypes { LoadTeams = 'LOAD_TEAMS', + SetSearchQuery = 'SET_SEARCH_QUERY', } export interface LoadTeamsAction { @@ -11,7 +12,12 @@ export interface LoadTeamsAction { payload: Team[]; } -export type Action = LoadTeamsAction; +export interface SetSearchQueryAction { + type: ActionTypes.SetSearchQuery; + payload: string; +} + +export type Action = LoadTeamsAction | SetSearchQueryAction; type ThunkResult = ThunkAction; @@ -20,6 +26,11 @@ const teamsLoaded = (teams: Team[]): LoadTeamsAction => ({ payload: teams, }); +export const setSearchQuery = (searchQuery: string): SetSearchQueryAction => ({ + type: ActionTypes.SetSearchQuery, + payload: searchQuery, +}); + export function loadTeams(): ThunkResult { return async dispatch => { const response = await getBackendSrv().get('/api/teams/search', { perpage: 1000, page: 1 }); diff --git a/public/app/features/teams/state/reducers.test.ts b/public/app/features/teams/state/reducers.test.ts new file mode 100644 index 00000000000..e115d311e37 --- /dev/null +++ b/public/app/features/teams/state/reducers.test.ts @@ -0,0 +1,41 @@ +import { Action, ActionTypes } from './actions'; +import { initialState, teamsReducer } from './reducers'; + +describe('teams reducer', () => { + it('should set teams', () => { + const payload = [ + { + id: 1, + name: 'test', + avatarUrl: 'some/url/', + email: 'test@test.com', + memberCount: 1, + search: '', + members: [], + groups: [], + }, + ]; + + const action: Action = { + type: ActionTypes.LoadTeams, + payload, + }; + + const result = teamsReducer(initialState, action); + + expect(result.teams).toEqual(payload); + }); + + it('should set search query', () => { + const payload = 'test'; + + const action: Action = { + type: ActionTypes.SetSearchQuery, + payload, + }; + + const result = teamsReducer(initialState, action); + + expect(result.searchQuery).toEqual('test'); + }); +}); diff --git a/public/app/features/teams/state/reducers.ts b/public/app/features/teams/state/reducers.ts index 968c69d862c..673fd240668 100644 --- a/public/app/features/teams/state/reducers.ts +++ b/public/app/features/teams/state/reducers.ts @@ -1,12 +1,15 @@ import { TeamsState } from '../../../types'; import { Action, ActionTypes } from './actions'; -const initialState: TeamsState = { teams: [] }; +export const initialState: TeamsState = { teams: [], searchQuery: '' }; export const teamsReducer = (state = initialState, action: Action): TeamsState => { switch (action.type) { case ActionTypes.LoadTeams: - return { teams: action.payload }; + return { ...state, teams: action.payload }; + + case ActionTypes.SetSearchQuery: + return { ...state, searchQuery: action.payload }; } return state; }; diff --git a/public/app/features/teams/state/selectors.test.ts b/public/app/features/teams/state/selectors.test.ts new file mode 100644 index 00000000000..66fd07444ce --- /dev/null +++ b/public/app/features/teams/state/selectors.test.ts @@ -0,0 +1,25 @@ +import { getTeams } from './selectors'; +import { getMultipleMockTeams } from '../__mocks__/teamMocks'; +import { TeamsState } from '../../../types'; + +describe('Team selectors', () => { + describe('Get teams', () => { + const mockTeams = getMultipleMockTeams(5); + + it('should return teams if no search query', () => { + const mockState: TeamsState = { teams: mockTeams, searchQuery: '' }; + + const teams = getTeams(mockState); + + expect(teams).toEqual(mockTeams); + }); + + it('Should filter teams if search query', () => { + const mockState: TeamsState = { teams: mockTeams, searchQuery: '5' }; + + const teams = getTeams(mockState); + + expect(teams.length).toEqual(1); + }); + }); +}); diff --git a/public/app/features/teams/state/selectors.ts b/public/app/features/teams/state/selectors.ts index f1f66695e65..632bb2cd02a 100644 --- a/public/app/features/teams/state/selectors.ts +++ b/public/app/features/teams/state/selectors.ts @@ -1 +1,9 @@ -export const getTeams = state => state.teams; +export const getSearchQuery = state => state.searchQuery; + +export const getTeams = state => { + const regex = RegExp(state.searchQuery, 'i'); + + return state.teams.filter(team => { + return regex.test(team.name); + }); +}; diff --git a/public/app/types/index.ts b/public/app/types/index.ts index beb3253787a..b867a8f6989 100644 --- a/public/app/types/index.ts +++ b/public/app/types/index.ts @@ -119,6 +119,7 @@ export interface AlertRulesState { export interface TeamsState { teams: Team[]; + searchQuery: string; } export interface StoreState {