Просмотр исходного кода

set search query action and tests

Peter Holmberg 7 лет назад
Родитель
Сommit
f68ac20218

+ 16 - 14
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');
+    });
+  });
 });

+ 11 - 9
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<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));

+ 32 - 0
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: [],
+  };
+};

+ 202 - 2
public/app/features/teams/__snapshots__/TeamList.test.tsx.snap

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

+ 12 - 1
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<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 });

+ 41 - 0
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');
+  });
+});

+ 5 - 2
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;
 };

+ 25 - 0
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);
+    });
+  });
+});

+ 9 - 1
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);
+  });
+};

+ 1 - 0
public/app/types/index.ts

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