set search query action and tests

pull/13219/head
Peter Holmberg 7 years ago
parent 7e340b7aa5
commit f68ac20218
  1. 30
      public/app/features/teams/TeamList.test.tsx
  2. 20
      public/app/features/teams/TeamList.tsx
  3. 32
      public/app/features/teams/__mocks__/teamMocks.ts
  4. 204
      public/app/features/teams/__snapshots__/TeamList.test.tsx.snap
  5. 13
      public/app/features/teams/state/actions.ts
  6. 41
      public/app/features/teams/state/reducers.test.ts
  7. 7
      public/app/features/teams/state/reducers.ts
  8. 25
      public/app/features/teams/state/selectors.test.ts
  9. 10
      public/app/features/teams/state/selectors.ts
  10. 1
      public/app/types/index.ts

@ -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');
});
});
});

@ -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<Props, any> {
@ -30,10 +31,10 @@ export class TeamList extends PureComponent<Props, any> {
};
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<Props, any> {
}
render() {
const { navModel, teams, search } = this.props;
const { navModel, teams, searchQuery } = this.props;
return (
<div>
@ -73,7 +74,7 @@ export class TeamList extends PureComponent<Props, any> {
type="text"
className="gf-form-input"
placeholder="Search teams"
value={search}
value={searchQuery}
onChange={this.onSearchQueryChange}
/>
<i className="gf-form-input-icon fa fa-search" />
@ -98,7 +99,7 @@ export class TeamList extends PureComponent<Props, any> {
<th style={{ width: '1%' }} />
</tr>
</thead>
<tbody>{teams.map(team => this.renderTeamMember(team))}</tbody>
<tbody>{teams.map(team => this.renderTeam(team))}</tbody>
</table>
</div>
</div>
@ -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));

@ -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: [],
};
};

@ -167,7 +167,7 @@ exports[`Render should render teams table 1`] = `
<a
href="org/teams/edit/1"
>
test
test-1
</a>
</td>
<td
@ -176,7 +176,7 @@ exports[`Render should render teams table 1`] = `
<a
href="org/teams/edit/1"
>
test@test.com
test-1@test.com
</a>
</td>
<td
@ -196,6 +196,206 @@ exports[`Render should render teams table 1`] = `
/>
</td>
</tr>
<tr
key="2"
>
<td
className="width-4 text-center link-td"
>
<a
href="org/teams/edit/2"
>
<img
className="filter-table__avatar"
src="some/url/"
/>
</a>
</td>
<td
className="link-td"
>
<a
href="org/teams/edit/2"
>
test-2
</a>
</td>
<td
className="link-td"
>
<a
href="org/teams/edit/2"
>
test-2@test.com
</a>
</td>
<td
className="link-td"
>
<a
href="org/teams/edit/2"
>
2
</a>
</td>
<td
className="text-right"
>
<DeleteButton
onConfirmDelete={[Function]}
/>
</td>
</tr>
<tr
key="3"
>
<td
className="width-4 text-center link-td"
>
<a
href="org/teams/edit/3"
>
<img
className="filter-table__avatar"
src="some/url/"
/>
</a>
</td>
<td
className="link-td"
>
<a
href="org/teams/edit/3"
>
test-3
</a>
</td>
<td
className="link-td"
>
<a
href="org/teams/edit/3"
>
test-3@test.com
</a>
</td>
<td
className="link-td"
>
<a
href="org/teams/edit/3"
>
3
</a>
</td>
<td
className="text-right"
>
<DeleteButton
onConfirmDelete={[Function]}
/>
</td>
</tr>
<tr
key="4"
>
<td
className="width-4 text-center link-td"
>
<a
href="org/teams/edit/4"
>
<img
className="filter-table__avatar"
src="some/url/"
/>
</a>
</td>
<td
className="link-td"
>
<a
href="org/teams/edit/4"
>
test-4
</a>
</td>
<td
className="link-td"
>
<a
href="org/teams/edit/4"
>
test-4@test.com
</a>
</td>
<td
className="link-td"
>
<a
href="org/teams/edit/4"
>
4
</a>
</td>
<td
className="text-right"
>
<DeleteButton
onConfirmDelete={[Function]}
/>
</td>
</tr>
<tr
key="5"
>
<td
className="width-4 text-center link-td"
>
<a
href="org/teams/edit/5"
>
<img
className="filter-table__avatar"
src="some/url/"
/>
</a>
</td>
<td
className="link-td"
>
<a
href="org/teams/edit/5"
>
test-5
</a>
</td>
<td
className="link-td"
>
<a
href="org/teams/edit/5"
>
test-5@test.com
</a>
</td>
<td
className="link-td"
>
<a
href="org/teams/edit/5"
>
5
</a>
</td>
<td
className="text-right"
>
<DeleteButton
onConfirmDelete={[Function]}
/>
</td>
</tr>
</tbody>
</table>
</div>

@ -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<R> = ThunkAction<R, StoreState, undefined, Action>;
@ -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<void> {
return async dispatch => {
const response = await getBackendSrv().get('/api/teams/search', { perpage: 1000, page: 1 });

@ -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');
});
});

@ -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;
};

@ -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);
});
});
});

@ -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);
});
};

@ -119,6 +119,7 @@ export interface AlertRulesState {
export interface TeamsState {
teams: Team[];
searchQuery: string;
}
export interface StoreState {

Loading…
Cancel
Save