functions and tests

pull/13488/head
Peter Holmberg 7 years ago
parent 8f99276606
commit 94971abd9c
  1. 51
      public/app/features/users/UsersListPage.test.tsx
  2. 32
      public/app/features/users/UsersListPage.tsx
  3. 33
      public/app/features/users/UsersTable.test.tsx
  4. 80
      public/app/features/users/UsersTable.tsx
  5. 31
      public/app/features/users/__mocks__/userMocks.ts
  6. 29
      public/app/features/users/__snapshots__/UsersListPage.test.tsx.snap
  7. 448
      public/app/features/users/__snapshots__/UsersTable.test.tsx.snap
  8. 14
      public/app/features/users/state/actions.ts

@ -0,0 +1,51 @@
import React from 'react';
import { shallow } from 'enzyme';
import { UsersListPage, Props } from './UsersListPage';
import { NavModel, User } from 'app/types';
import { getMockUser } from './__mocks__/userMocks';
import appEvents from '../../core/app_events';
jest.mock('../../core/app_events', () => ({
emit: jest.fn(),
}));
const setup = (propOverrides?: object) => {
const props: Props = {
navModel: {} as NavModel,
users: [] as User[],
searchQuery: '',
loadUsers: jest.fn(),
updateUser: jest.fn(),
removeUser: jest.fn(),
setUsersSearchQuery: jest.fn(),
};
Object.assign(props, propOverrides);
const wrapper = shallow(<UsersListPage {...props} />);
const instance = wrapper.instance() as UsersListPage;
return {
wrapper,
instance,
};
};
describe('Render', () => {
it('should render component', () => {
const { wrapper } = setup();
expect(wrapper).toMatchSnapshot();
});
});
describe('Functions', () => {
it('should emit show remove user modal', () => {
const { instance } = setup();
const mockUser = getMockUser();
instance.onRemoveUser(mockUser);
expect(appEvents.emit).toHaveBeenCalled();
});
});

@ -5,7 +5,8 @@ import OrgActionBar from 'app/core/components/OrgActionBar/OrgActionBar';
import PageHeader from 'app/core/components/PageHeader/PageHeader';
import UsersTable from 'app/features/users/UsersTable';
import { NavModel, User } from 'app/types';
import { loadUsers, setUsersSearchQuery } from './state/actions';
import appEvents from 'app/core/app_events';
import { loadUsers, setUsersSearchQuery, updateUser, removeUser } from './state/actions';
import { getNavModel } from '../../core/selectors/navModel';
import { getUsers, getUsersSearchQuery } from './state/selectors';
@ -15,6 +16,8 @@ export interface Props {
searchQuery: string;
loadUsers: typeof loadUsers;
setUsersSearchQuery: typeof setUsersSearchQuery;
updateUser: typeof updateUser;
removeUser: typeof removeUser;
}
export class UsersListPage extends PureComponent<Props> {
@ -25,6 +28,25 @@ export class UsersListPage extends PureComponent<Props> {
async fetchUsers() {
return await this.props.loadUsers();
}
onRoleChange = (role, user) => {
const updatedUser = { ...user, role: role };
this.props.updateUser(updatedUser);
};
onRemoveUser = user => {
appEvents.emit('confirm-modal', {
title: 'Delete',
text: 'Are you sure you want to delete user ' + user.login + '?',
yesText: 'Delete',
icon: 'fa-warning',
onConfirm: () => {
this.props.removeUser(user.userId);
},
});
};
render() {
const { navModel, searchQuery, setUsersSearchQuery, users } = this.props;
@ -43,7 +65,11 @@ export class UsersListPage extends PureComponent<Props> {
setSearchQuery={setUsersSearchQuery}
linkButton={linkButton}
/>
<UsersTable users={users} />
<UsersTable
users={users}
onRoleChange={(role, user) => this.onRoleChange(role, user)}
onRemoveUser={user => this.onRemoveUser(user)}
/>
</div>
</div>
);
@ -61,6 +87,8 @@ function mapStateToProps(state) {
const mapDispatchToProps = {
loadUsers,
setUsersSearchQuery,
updateUser,
removeUser,
};
export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(UsersListPage));

@ -0,0 +1,33 @@
import React from 'react';
import { shallow } from 'enzyme';
import UsersTable, { Props } from './UsersTable';
import { User } from 'app/types';
import { getMockUsers } from './__mocks__/userMocks';
const setup = (propOverrides?: object) => {
const props: Props = {
users: [] as User[],
onRoleChange: jest.fn(),
onRemoveUser: jest.fn(),
};
Object.assign(props, propOverrides);
return shallow(<UsersTable {...props} />);
};
describe('Render', () => {
it('should render component', () => {
const wrapper = setup();
expect(wrapper).toMatchSnapshot();
});
it('should render users table', () => {
const wrapper = setup({
users: getMockUsers(5),
});
expect(wrapper).toMatchSnapshot();
});
});

@ -3,15 +3,15 @@ import { User } from 'app/types';
export interface Props {
users: User[];
onRoleChange: (value: string) => {};
onRoleChange: (role: string, user: User) => void;
onRemoveUser: (user: User) => void;
}
const UsersTable: SFC<Props> = props => {
const { users } = props;
const { users, onRoleChange, onRemoveUser } = props;
return (
<div>
Le Table
<table className="filter-table form-inline">
<thead>
<tr>
@ -23,42 +23,44 @@ const UsersTable: SFC<Props> = props => {
<th style={{ width: '34px' }} />
</tr>
</thead>
{users.map((user, index) => {
return (
<tr key={`${user.userId}-${index}`}>
<td className="width-4 text-center">
<img className="filter-table__avatar" src={user.avatarUrl} />
</td>
<td>{user.login}</td>
<td>
<span className="ellipsis">{user.email}</span>
</td>
<td>{user.lastSeenAtAge}</td>
<td>
<div className="gf-form-select-wrapper width-12">
<select
value={user.role}
className="gf-form-input"
onChange={event => props.onRoleChange(event.target.value)}
>
{['Viewer', 'Editor', 'Admin'].map((option, index) => {
return (
<option value={option} key={`${option}-${index}`}>
{option}
</option>
);
})}
</select>
</div>
</td>
<td>
<div onClick={() => props.removeUser(user)} className="btn btn-danger btn-mini">
<i className="fa fa-remove" />
</div>
</td>
</tr>
);
})}
<tbody>
{users.map((user, index) => {
return (
<tr key={`${user.userId}-${index}`}>
<td className="width-4 text-center">
<img className="filter-table__avatar" src={user.avatarUrl} />
</td>
<td>{user.login}</td>
<td>
<span className="ellipsis">{user.email}</span>
</td>
<td>{user.lastSeenAtAge}</td>
<td>
<div className="gf-form-select-wrapper width-12">
<select
value={user.role}
className="gf-form-input"
onChange={event => onRoleChange(event.target.value, user)}
>
{['Viewer', 'Editor', 'Admin'].map((option, index) => {
return (
<option value={option} key={`${option}-${index}`}>
{option}
</option>
);
})}
</select>
</div>
</td>
<td>
<div onClick={() => onRemoveUser(user)} className="btn btn-danger btn-mini">
<i className="fa fa-remove" />
</div>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
);

@ -0,0 +1,31 @@
export const getMockUsers = (amount: number) => {
const users = [];
for (let i = 0; i <= amount; i++) {
users.push({
avatarUrl: 'url/to/avatar',
email: `user-${i}@test.com`,
lastSeenAt: '2018-10-01',
lastSeenAtAge: '',
login: `user-${i}`,
orgId: 1,
role: 'Admin',
userId: i,
});
}
return users;
};
export const getMockUser = () => {
return {
avatarUrl: 'url/to/avatar',
email: `user@test.com`,
lastSeenAt: '2018-10-01',
lastSeenAtAge: '',
login: `user`,
orgId: 1,
role: 'Admin',
userId: 2,
};
};

@ -0,0 +1,29 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Render should render component 1`] = `
<div>
<PageHeader
model={Object {}}
/>
<div
className="page-container page-body"
>
<OrgActionBar
linkButton={
Object {
"href": "/org/users/add",
"title": "Add user",
}
}
searchQuery=""
setSearchQuery={[MockFunction]}
showLayoutMode={false}
/>
<UsersTable
onRemoveUser={[Function]}
onRoleChange={[Function]}
users={Array []}
/>
</div>
</div>
`;

@ -0,0 +1,448 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Render should render component 1`] = `
<div>
<table
className="filter-table form-inline"
>
<thead>
<tr>
<th />
<th>
Login
</th>
<th>
Email
</th>
<th>
Seen
</th>
<th>
Role
</th>
<th
style={
Object {
"width": "34px",
}
}
/>
</tr>
</thead>
<tbody />
</table>
</div>
`;
exports[`Render should render users table 1`] = `
<div>
<table
className="filter-table form-inline"
>
<thead>
<tr>
<th />
<th>
Login
</th>
<th>
Email
</th>
<th>
Seen
</th>
<th>
Role
</th>
<th
style={
Object {
"width": "34px",
}
}
/>
</tr>
</thead>
<tbody>
<tr
key="0-0"
>
<td
className="width-4 text-center"
>
<img
className="filter-table__avatar"
src="url/to/avatar"
/>
</td>
<td>
user-0
</td>
<td>
<span
className="ellipsis"
>
user-0@test.com
</span>
</td>
<td />
<td>
<div
className="gf-form-select-wrapper width-12"
>
<select
className="gf-form-input"
onChange={[Function]}
value="Admin"
>
<option
key="Viewer-0"
value="Viewer"
>
Viewer
</option>
<option
key="Editor-1"
value="Editor"
>
Editor
</option>
<option
key="Admin-2"
value="Admin"
>
Admin
</option>
</select>
</div>
</td>
<td>
<div
className="btn btn-danger btn-mini"
onClick={[Function]}
>
<i
className="fa fa-remove"
/>
</div>
</td>
</tr>
<tr
key="1-1"
>
<td
className="width-4 text-center"
>
<img
className="filter-table__avatar"
src="url/to/avatar"
/>
</td>
<td>
user-1
</td>
<td>
<span
className="ellipsis"
>
user-1@test.com
</span>
</td>
<td />
<td>
<div
className="gf-form-select-wrapper width-12"
>
<select
className="gf-form-input"
onChange={[Function]}
value="Admin"
>
<option
key="Viewer-0"
value="Viewer"
>
Viewer
</option>
<option
key="Editor-1"
value="Editor"
>
Editor
</option>
<option
key="Admin-2"
value="Admin"
>
Admin
</option>
</select>
</div>
</td>
<td>
<div
className="btn btn-danger btn-mini"
onClick={[Function]}
>
<i
className="fa fa-remove"
/>
</div>
</td>
</tr>
<tr
key="2-2"
>
<td
className="width-4 text-center"
>
<img
className="filter-table__avatar"
src="url/to/avatar"
/>
</td>
<td>
user-2
</td>
<td>
<span
className="ellipsis"
>
user-2@test.com
</span>
</td>
<td />
<td>
<div
className="gf-form-select-wrapper width-12"
>
<select
className="gf-form-input"
onChange={[Function]}
value="Admin"
>
<option
key="Viewer-0"
value="Viewer"
>
Viewer
</option>
<option
key="Editor-1"
value="Editor"
>
Editor
</option>
<option
key="Admin-2"
value="Admin"
>
Admin
</option>
</select>
</div>
</td>
<td>
<div
className="btn btn-danger btn-mini"
onClick={[Function]}
>
<i
className="fa fa-remove"
/>
</div>
</td>
</tr>
<tr
key="3-3"
>
<td
className="width-4 text-center"
>
<img
className="filter-table__avatar"
src="url/to/avatar"
/>
</td>
<td>
user-3
</td>
<td>
<span
className="ellipsis"
>
user-3@test.com
</span>
</td>
<td />
<td>
<div
className="gf-form-select-wrapper width-12"
>
<select
className="gf-form-input"
onChange={[Function]}
value="Admin"
>
<option
key="Viewer-0"
value="Viewer"
>
Viewer
</option>
<option
key="Editor-1"
value="Editor"
>
Editor
</option>
<option
key="Admin-2"
value="Admin"
>
Admin
</option>
</select>
</div>
</td>
<td>
<div
className="btn btn-danger btn-mini"
onClick={[Function]}
>
<i
className="fa fa-remove"
/>
</div>
</td>
</tr>
<tr
key="4-4"
>
<td
className="width-4 text-center"
>
<img
className="filter-table__avatar"
src="url/to/avatar"
/>
</td>
<td>
user-4
</td>
<td>
<span
className="ellipsis"
>
user-4@test.com
</span>
</td>
<td />
<td>
<div
className="gf-form-select-wrapper width-12"
>
<select
className="gf-form-input"
onChange={[Function]}
value="Admin"
>
<option
key="Viewer-0"
value="Viewer"
>
Viewer
</option>
<option
key="Editor-1"
value="Editor"
>
Editor
</option>
<option
key="Admin-2"
value="Admin"
>
Admin
</option>
</select>
</div>
</td>
<td>
<div
className="btn btn-danger btn-mini"
onClick={[Function]}
>
<i
className="fa fa-remove"
/>
</div>
</td>
</tr>
<tr
key="5-5"
>
<td
className="width-4 text-center"
>
<img
className="filter-table__avatar"
src="url/to/avatar"
/>
</td>
<td>
user-5
</td>
<td>
<span
className="ellipsis"
>
user-5@test.com
</span>
</td>
<td />
<td>
<div
className="gf-form-select-wrapper width-12"
>
<select
className="gf-form-input"
onChange={[Function]}
value="Admin"
>
<option
key="Viewer-0"
value="Viewer"
>
Viewer
</option>
<option
key="Editor-1"
value="Editor"
>
Editor
</option>
<option
key="Admin-2"
value="Admin"
>
Admin
</option>
</select>
</div>
</td>
<td>
<div
className="btn btn-danger btn-mini"
onClick={[Function]}
>
<i
className="fa fa-remove"
/>
</div>
</td>
</tr>
</tbody>
</table>
</div>
`;

@ -38,3 +38,17 @@ export function loadUsers(): ThunkResult<void> {
dispatch(usersLoaded(users));
};
}
export function updateUser(user: User): ThunkResult<void> {
return async dispatch => {
await getBackendSrv().patch(`/api/org/users/${user.userId}`, user);
dispatch(loadUsers());
};
}
export function removeUser(userId: number): ThunkResult<void> {
return async dispatch => {
await getBackendSrv().delete(`/api/org/users/${userId}`);
dispatch(loadUsers());
};
}

Loading…
Cancel
Save