set search query action and tests
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user