Unverified Commit 147bd907 authored by Tamika Tannis's avatar Tamika Tannis Committed by GitHub

Increase Application Test Coverage (#99)

* Increase coverage

* More tests

* Tweak thresholds

* Fix ts errors

* Clean duplicate id issue, moving away from ids in future tests
parent 08cb87ed
module.exports = {
coverageThreshold: {
'./js/config': {
branches: 0, // ??
functions: 0, // ??
lines: 0, // ??
statements: 0, // ??
},
'./js/components': {
branches: 10, // 75
functions: 10, // 75
lines: 10, // 75
statements: 10, // 75
branches: 15, // 75
functions: 25, // 75
lines: 25, // 75
statements: 25, // 75
},
'./js/ducks': {
branches: 0, // 75
......@@ -12,6 +18,12 @@ module.exports = {
lines: 0, // 75
statements: 0, // 75
},
'./js/fixtures': {
branches: 100,
functions: 100,
lines: 100,
statements: 100,
},
},
roots: [
'<rootDir>/js',
......
......@@ -26,7 +26,7 @@ export interface DispatchFromProps {
announcementsGet: () => AnnouncementsGetRequest;
}
type AnnouncementPageProps = StateFromProps & DispatchFromProps;
export type AnnouncementPageProps = StateFromProps & DispatchFromProps;
export class AnnouncementPage extends React.Component<AnnouncementPageProps, AnnouncementPageState> {
constructor(props) {
......@@ -37,11 +37,6 @@ export class AnnouncementPage extends React.Component<AnnouncementPageProps, Ann
};
}
static getDerivedStateFromProps(nextProps, prevState) {
const { posts } = nextProps;
return { posts };
}
componentDidMount() {
this.props.announcementsGet();
}
......@@ -60,22 +55,24 @@ export class AnnouncementPage extends React.Component<AnnouncementPageProps, Ann
);
}
createPosts() {
return this.state.posts.map((post, index) => {
return this.createPost(post, index)
});
}
render() {
return (
<DocumentTitle title="Announcements - Amundsen">
<div className="container announcement-container">
<div className="row">
<div className="col-xs-12">
<div className="announcement-header">
<div id="announcement-header" className="announcement-header">
Announcements
</div>
<hr />
<div className='announcement-content'>
{
this.state.posts.map((post, index) => {
return this.createPost(post, index)
})
}
<div id="announcement-content" className='announcement-content'>
{this.createPosts()}
</div>
</div>
</div>
......
import * as React from 'react';
import * as DocumentTitle from 'react-document-title';
import SanitizedHTML from 'react-sanitized-html';
import { mount, shallow } from 'enzyme';
import { AnnouncementPage, AnnouncementPageProps, mapDispatchToProps, mapStateToProps } from '../';
import globalState from 'fixtures/globalState';
describe('AnnouncementPage', () => {
let props: AnnouncementPageProps;
let subject;
beforeEach(() => {
props = {
announcementsGet: jest.fn(),
posts: [{
date: '12/31/1999',
title: 'Y2K',
html_content: '<div>The end of the world</div>',
},
{
date: '01/01/2000',
title: 'False Alarm',
html_content: '<div>Just kidding</div>',
}],
};
subject = mount(<AnnouncementPage {...props} />);
});
describe('componentDidMount', () => {
it('calls props.announcementsGet', () => {
expect(props.announcementsGet).toHaveBeenCalled();
});
});
describe('createPost', () => {
let content;
let post;
beforeEach(() => {
post = subject.state().posts[0];
content = shallow(subject.instance().createPost(post, 0));
});
it('renders the post title', () => {
expect(content.children().at(0).children().at(0).text()).toEqual(post.title);
});
it('renders the post date', () => {
expect(content.children().at(0).children().at(1).text()).toEqual(post.date);
});
it('renders SanitizedHTML with the post content', () => {
expect(content.find(SanitizedHTML).props()).toMatchObject({
html: post.html_content,
});
});
});
describe('createPosts', () => {
beforeEach(() => {
subject.instance().createPost = jest.fn();
subject.instance().createPosts();
});
it('call createPost() for each state.posts[] item', () => {
expect(subject.instance().createPost).toHaveBeenCalledTimes(subject.state().posts.length)
});
it('passes correct props to createPost()', () => {
expect(subject.instance().createPost).toHaveBeenCalledWith(subject.state().posts[0], 0);
});
});
describe('render', () => {
let spy;
beforeEach(() => {
spy = jest.spyOn(AnnouncementPage.prototype, 'createPosts');
});
it('renders DocumentTitle w/ correct title', () => {
expect(subject.find(DocumentTitle).props().title).toEqual('Announcements - Amundsen');
});
it('renders correct header', () => {
expect(subject.find('#announcement-header').text()).toEqual('Announcements');
});
it('calls createPosts', () => {
expect(spy).toHaveBeenCalled();
});
});
});
describe('mapDispatchToProps', () => {
let dispatch;
let result;
beforeEach(() => {
dispatch = jest.fn(() => Promise.resolve());
result = mapDispatchToProps(dispatch);
});
it('sets announcementsGet on the props', () => {
expect(result.announcementsGet).toBeInstanceOf(Function);
});
});
describe('mapStateToProps', () => {
let result;
beforeEach(() => {
result = mapStateToProps(globalState);
});
it('sets posts on the props', () => {
expect(result.posts).toEqual(globalState.announcements.posts);
});
});
......@@ -29,7 +29,7 @@ interface BrowsePageState {
isLoading: boolean;
}
type BrowsePageProps = StateFromProps & DispatchFromProps;
export type BrowsePageProps = StateFromProps & DispatchFromProps;
export class BrowsePage extends React.Component<BrowsePageProps, BrowsePageState> {
constructor(props) {
......@@ -65,6 +65,11 @@ export class BrowsePage extends React.Component<BrowsePageProps, BrowsePageState
this.props.getAllTags();
}
generateTagInfo(tagArray: Tag[]) {
return tagArray.map((tag, index) =>
<TagInfo data={ tag } compact={ false } key={ index }/>)
}
render() {
let innerContent;
if (this.state.isLoading) {
......@@ -74,21 +79,15 @@ export class BrowsePage extends React.Component<BrowsePageProps, BrowsePageState
<div className="container">
<div className="row">
<div className="col-xs-12">
<h3 className="header">Browse Tags</h3>
<h3 id="browse-header" className="header">Browse Tags</h3>
<hr className="header-hr"/>
<div className="browse-body">
{
this.state.curatedTags.map((tag, index) =>
<TagInfo data={ tag } compact={ false } key={ index }/>)
}
<div id="browse-body" className="browse-body">
{this.generateTagInfo(this.state.curatedTags)}
{
this.state.curatedTags.length > 0 && this.state.otherTags.length > 0 &&
<hr />
}
{
this.state.otherTags.map((tag, index) =>
<TagInfo data={ tag } compact={ false } key={ index }/>)
}
{this.generateTagInfo(this.state.otherTags)}
</div>
</div>
</div>
......
import * as React from 'react';
import * as DocumentTitle from 'react-document-title';
import { shallow } from 'enzyme';
import LoadingSpinner from 'components/common/LoadingSpinner';
import TagInfo from 'components/Tags/TagInfo';
import { BrowsePage, BrowsePageProps, mapDispatchToProps, mapStateToProps } from '../';
import globalState from 'fixtures/globalState';
import AppConfig from 'config/config';
AppConfig.browse.curatedTags = ['test1'];
describe('BrowsePage', () => {
let props: BrowsePageProps;
let subject;
beforeEach(() => {
props = {
allTags: [
{
tag_count: 2,
tag_name: 'test1',
},
{
tag_count: 1,
tag_name: 'test2',
},
],
isLoading: false,
getAllTags: jest.fn(),
};
subject = shallow(<BrowsePage {...props} />);
});
describe('getDerivedStateFromProps', () => {
it('returns correct state if props.isLoading', () => {
const prevState = subject.state();
props.isLoading = true;
subject.setProps(props);
expect(subject.state()).toMatchObject({
...prevState,
isLoading: true,
});
});
it('returns correct state if !props.isLoading', () => {
props.isLoading = false;
subject.setProps(props);
expect(subject.state()).toMatchObject({
curatedTags: [{ tag_count: 2, tag_name: 'test1'}],
otherTags: [{ tag_count: 1, tag_name: 'test2'}],
isLoading: false,
});
});
it('returns correct state if !props.isLoading and !AppConfig.browse.showAllTags', () => {
AppConfig.browse.showAllTags = false;
subject = shallow(<BrowsePage {...props} />);
expect(subject.state()).toMatchObject({
curatedTags: [{tag_count: 2, tag_name: 'test1'}],
otherTags: [],
isLoading: false,
});
AppConfig.browse.showAllTags = true; // reset so other tests aren't affected
});
});
describe('componentDidMount', () => {
it('calls props.getAllTags', () => {
expect(props.getAllTags).toHaveBeenCalled();
});
});
describe('render', () => {
let spy;
beforeEach(() => {
spy = jest.spyOn(BrowsePage.prototype, 'generateTagInfo');
});
it('renders LoadingSpinner if state.isLoading', () => {
/* Note: For some reason setState is not updating the component in this case */
props.isLoading = true;
subject.setProps(props);
expect(subject.find(LoadingSpinner).exists()).toBeTruthy();
});
it('renders DocumentTitle w/ correct title', () => {
expect(subject.find(DocumentTitle).props().title).toEqual('Browse - Amundsen');
});
it('renders correct header', () => {
expect(subject.find('#browse-header').text()).toEqual('Browse Tags');
});
it('renders <hr> in if curatedTags.length > 0 & otherTags.length > 0 ', () => {
expect(subject.find('#browse-body').find('hr').exists()).toBeTruthy();
});
it('does not render <hr> if !(curatedTags.length > 0 & otherTags.length > 0) ', () => {
AppConfig.browse.curatedTags = ['test1', 'test2'];
subject = shallow(<BrowsePage {...props} />);
expect(subject.find('#browse-body').find('hr').exists()).toBeFalsy();
AppConfig.browse.curatedTags = ['test1']; // reset so other tests aren't affected
});
it('calls generateTagInfo with curatedTags', () => {
expect(spy).toHaveBeenCalledWith(subject.state().curatedTags);
});
it('call generateTagInfo with otherTags', () => {
expect(spy).toHaveBeenCalledWith(subject.state().otherTags);
});
});
});
describe('mapDispatchToProps', () => {
let dispatch;
let result;
beforeEach(() => {
dispatch = jest.fn(() => Promise.resolve());
result = mapDispatchToProps(dispatch);
});
it('sets getAllTags on the props', () => {
expect(result.getAllTags).toBeInstanceOf(Function);
});
});
describe('mapStateToProps', () => {
let result;
beforeEach(() => {
result = mapStateToProps(globalState);
});
it('sets allTags on the props', () => {
expect(result.allTags).toEqual(globalState.allTags.allTags);
});
it('sets isLoading on the props', () => {
expect(result.isLoading).toEqual(globalState.allTags.isLoading);
});
});
......@@ -17,9 +17,8 @@ interface DispatchFromProps {
getLastIndexed: () => GetLastIndexedRequest;
}
type FooterProps = StateFromProps & DispatchFromProps;
export type FooterProps = StateFromProps & DispatchFromProps;
// State
interface FooterState {
lastIndexed: number;
}
......@@ -33,26 +32,24 @@ export class Footer extends React.Component<FooterProps, FooterState> {
};
}
static getDerivedStateFromProps(nextProps, prevState) {
const { lastIndexed } = nextProps;
return { lastIndexed };
}
componentDidMount() {
this.props.getLastIndexed();
}
generateDateTimeString = () => {
// 'moment.local' will utilize the client's local timezone.
return moment.unix(this.state.lastIndexed).local().format('MMMM Do YYYY [at] h:mm:ss a');
}
render() {
let content;
if (this.state.lastIndexed !== null) {
// 'moment.local' will utilize the client's local timezone.
const dateTimeString = moment.unix(this.state.lastIndexed).local().format('MMMM Do YYYY [at] h:mm:ss a');
content = <div>{`Amundsen was last indexed on ${dateTimeString}`}</div>;
content = <div>{`Amundsen was last indexed on ${this.generateDateTimeString()}`}</div>;
}
return (
<div>
<div className="phantom-div" />
<div className="footer">
<div id="footer" className="footer">
{ content }
</div>
</div>
......
import * as React from 'react';
import moment from 'moment-timezone';
import { mount } from 'enzyme';
import { Footer, FooterProps, mapDispatchToProps, mapStateToProps } from '../';
import globalState from 'fixtures/globalState';
/* TODO: Issues with mocking moment-timezone, all tests are failing
describe('Footer', () => {
let props: FooterProps;
let subject;
beforeEach(() => {
props = {
lastIndexed: 1555632106,
getLastIndexed: jest.fn(),
};
subject = mount(<Footer {...props} />);
});
describe('componentDidMount', () => {
it('calls props.getLastIndexed', () => {
expect(props.getLastIndexed).toHaveBeenCalled();
});
});
describe('render', () => {
it('calls generateDateTimeString if this.state.lastIndexed', () => {
jest.spyOn(subject.instance(), 'generateDateTimeString');
subject.instance().render();
expect(subject.instance().generateDateTimeString).toHaveBeenCalled();
});
it('renders correct content if this.state.lastIndexed', () => {
const expectedText = 'Amundsen was last indexed on April 18th 2019 at 5:01:46 pm';
expect(subject.find('#footer').props().children).toBeTruthy();
// expect(subject.find('#footer').props().children().at(0).text()).toEqual(expectedText);
});
it('renders no content if !this.state.lastIndexed', () => {
subject.setState({ lastIndexed: null });
expect(subject.find('#footer').props().children).toBeFalsy();
});
});
});*/
describe('mapDispatchToProps', () => {
let dispatch;
let result;
beforeEach(() => {
dispatch = jest.fn(() => Promise.resolve());
result = mapDispatchToProps(dispatch);
});
it('sets getLastIndexed on the props', () => {
expect(result.getLastIndexed).toBeInstanceOf(Function);
});
});
describe('mapStateToProps', () => {
let result;
beforeEach(() => {
result = mapStateToProps(globalState);
});
it('sets lastIndexed on the props', () => {
expect(result.lastIndexed).toEqual(globalState.tableMetadata.lastIndexed);
});
});
......@@ -5,6 +5,7 @@ import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import AppConfig from 'config/config';
import { LinkConfig } from 'config/config-types';
import { GlobalState } from 'ducks/rootReducer';
import { getLoggedInUser } from 'ducks/user/reducer';
import { LoggedInUser, GetLoggedInUserRequest } from 'ducks/user/types';
......@@ -21,7 +22,7 @@ interface DispatchFromProps {
getLoggedInUser: () => GetLoggedInUserRequest;
}
type NavBarProps = StateFromProps & DispatchFromProps;
export type NavBarProps = StateFromProps & DispatchFromProps;
// State
interface NavBarState {
......@@ -37,50 +38,41 @@ export class NavBar extends React.Component<NavBarProps, NavBarState> {
};
}
static getDerivedStateFromProps(nextProps, prevState) {
const { loggedInUser } = nextProps;
return { loggedInUser };
}
componentDidMount() {
this.props.getLoggedInUser();
}
generateNavLinks(navLinks: LinkConfig[]) {
return navLinks.map((link, index) => {
if (link.use_router) {
return <NavLink key={index} to={link.href} target={link.target} onClick={logClick}>{link.label}</NavLink>
}
return <a key={index} href={link.href} target={link.target} onClick={logClick}>{link.label}</a>
});
}
render() {
return (
<div className="container-fluid">
<div className="row">
<div className="nav-bar">
<div className="nav-bar-left">
<div id="nav-bar-left" className="nav-bar-left">
{
AppConfig.logoPath &&
<img className="logo-icon" src={AppConfig.logoPath} />
<img id="logo-icon" className="logo-icon" src={AppConfig.logoPath} />
}
<Link to={`/`}>
AMUNDSEN
</Link>
</div>
<div className="nav-bar-right">
{
AppConfig.navLinks.map((link, index) => {
if (link.use_router) {
return (
<NavLink id={ link.id } key={ index } to={ link.href } target={ link.target } onClick={logClick}>
{link.label}
</NavLink>
)
}
return (
<a id={ link.id } key={ index } href={ link.href } target={ link.target } onClick={logClick}>
{link.label}
</a>
)
})
}
<div id="nav-bar-right" className="nav-bar-right">
{this.generateNavLinks(AppConfig.navLinks)}
{
// TODO PEOPLE - Add link to user profile
this.state.loggedInUser &&
<div id="nav-bar-avatar">
<Avatar name={this.state.loggedInUser.display_name} size={32} round={true} />
</div>
}
</div>
</div>
......@@ -96,7 +88,7 @@ export const mapStateToProps = (state: GlobalState) => {
}
};
const mapDispatchToProps = (dispatch) => {
export const mapDispatchToProps = (dispatch) => {
return bindActionCreators({ getLoggedInUser }, dispatch);
};
......
import * as React from 'react';
import { shallow } from 'enzyme';
import Avatar from 'react-avatar';
import { Link, NavLink } from 'react-router-dom';
import { NavBar, NavBarProps, mapDispatchToProps, mapStateToProps } from '../';
import { logClick } from "ducks/utilMethods";
jest.mock('ducks/utilMethods', () => {
return jest.fn().mockImplementation(() => {
return {logClick: jest.fn()};
});
});
import AppConfig from 'config/config';
AppConfig.logoPath = '/test';
AppConfig.navLinks = [
{
label: 'Announcements',
href: '/announcements',
id: 'link1',
target: '_blank',
use_router: true,
},
{
label: 'Browse',
href: '/browse',
id: 'link2',
target: '_blank',
use_router: false,
}
];
import globalState from 'fixtures/globalState';
describe('NavBar', () => {
let props: NavBarProps;
let subject;
beforeEach(() => {
props = {
loggedInUser: {
user_id: 'test0',
display_name: 'Test User',
},
getLoggedInUser: jest.fn(),
};
subject = shallow(<NavBar {...props} />);
});
describe('componentDidMount', () => {
it('calls props.getLoggedInUser', () => {
subject.instance().componentDidMount();
expect(props.getLoggedInUser).toHaveBeenCalled();
});
});
describe('generateNavLinks', () => {
let content;
beforeEach(() => {
content = subject.instance().generateNavLinks(AppConfig.navLinks);
});
it('returns a NavLink w/ correct props if user_router is true', () => {
const expectedContent = JSON.stringify(<NavLink key={0} to='/announcements' target='_blank' onClick={logClick}>Announcements</NavLink>);
expect(JSON.stringify(content[0])).toEqual(expectedContent);
});
it('returns an anchor w/ correct props if user_router is false', () => {
expect(shallow(content[1]).find('a').props()).toMatchObject({
href: '/browse',
target: '_blank',
});
});
it('returns an anchor w/ correct test if user_router is false', () => {
expect(shallow(content[1]).find('a').text()).toEqual('Browse');
});
});
describe('render', () => {
let element;
const spy = jest.spyOn(NavBar.prototype, 'generateNavLinks');
it('renders img with AppConfig.logoPath', () => {
element = subject.find('img#logo-icon');
expect(element.props()).toMatchObject({
id: 'logo-icon',
className: 'logo-icon',
src: AppConfig.logoPath,
});
});
it('renders homepage Link with correct path ', () => {
element = subject.find('#nav-bar-left').find(Link);
expect(element.props().to).toEqual('/');
});
it('renders homepage Link with correct text', () => {
element = subject.find('#nav-bar-left').find(Link);
expect(element.children().text()).toEqual('AMUNDSEN');
})
it('calls generateNavLinks with correct props', () => {
expect(spy).toHaveBeenCalledWith(AppConfig.navLinks);
});
it('renders Avatar for loggedInUser', () => {
/* Note: subject.find(Avatar) does not work - workaround is to directly check the content */
const expectedContent = <Avatar name={props.loggedInUser.display_name} size={32} round={true} />;
expect(subject.find('#nav-bar-avatar').props().children).toEqual(expectedContent);
});
});
});
describe('mapDispatchToProps', () => {
let dispatch;
let result;
beforeEach(() => {
dispatch = jest.fn(() => Promise.resolve());
result = mapDispatchToProps(dispatch);
});
it('sets getLoggedInUser on the props', () => {
expect(result.getLoggedInUser).toBeInstanceOf(Function);
});
});
describe('mapStateToProps', () => {
let result;
beforeEach(() => {
result = mapStateToProps(globalState);
});
it('sets loggedInUser on the props', () => {
expect(result.loggedInUser).toEqual(globalState.user.loggedInUser);
});
});
import * as React from 'react';
import * as DocumentTitle from 'react-document-title';
import { shallow } from 'enzyme';
import Breadcrumb from 'components/common/Breadcrumb';
import NotFoundPage from '../';
describe('NotFoundPage', () => {
let subject;
beforeEach(() => {
subject = shallow(<NotFoundPage />);
});
it('renders DocumentTitle w/ correct title', () => {
expect(subject.find(DocumentTitle).props().title).toEqual('404 Page Not Found - Amundsen');
});
it('renders Breadcrumb to homepage', () => {
expect(subject.find(Breadcrumb).props()).toMatchObject({
path: '/',
text: 'Home',
});
});
it('renders header correct text', () => {
expect(subject.find('h1').text()).toEqual('404 Page Not Found');
});
it('renders span with glyphicon', () => {
expect(subject.find('span').props()).toMatchObject({
className: 'glyphicon glyphicon-exclamation-sign',
});
});
});
......@@ -4,33 +4,31 @@ import Avatar from 'react-avatar';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import LoadingSpinner from 'components/common/LoadingSpinner';
import { GlobalState } from 'ducks/rootReducer';
import { getUserById } from 'ducks/user/reducer';
import { LoggedInUser, GetUserRequest } from 'ducks/user/types';
import Breadcrumb from 'components/common/Breadcrumb';
import Flag from 'components/common/Flag';
import Tabs from 'components/common/Tabs';
import { GlobalState } from 'ducks/rootReducer';
import { getUserById } from 'ducks/user/reducer';
import { User, GetUserRequest } from 'ducks/user/types';
import './styles.scss';
interface StateFromProps {
user: LoggedInUser;
user: User;
}
interface DispatchFromProps {
getUserById: (userId: string) => GetUserRequest;
}
type ProfilePageProps = StateFromProps & DispatchFromProps;
export type ProfilePageProps = StateFromProps & DispatchFromProps;
interface ProfilePageState {
user: LoggedInUser;
user: User;
}
class ProfilePage extends React.Component<ProfilePageProps, ProfilePageState> {
export class ProfilePage extends React.Component<ProfilePageProps, ProfilePageState> {
private userId: string;
constructor(props) {
......@@ -38,18 +36,13 @@ class ProfilePage extends React.Component<ProfilePageProps, ProfilePageState> {
const { match } = props;
const params = match.params;
this.userId = params ? params.userId : '';
this.userId = params && params.userId ? params.userId : '';
this.state = {
user: this.props.user,
};
}
static getDerivedStateFromProps(nextProps, prevState) {
const { user } = nextProps;
return { user };
}
componentDidMount() {
this.props.getUserById(this.userId);
}
......@@ -97,7 +90,7 @@ class ProfilePage extends React.Component<ProfilePageProps, ProfilePageState> {
<div className="container profile-page">
<Breadcrumb path='/' text='Search Results'/>
<div className="profile-header">
<div className="profile-avatar">
<div id="profile-avatar" className="profile-avatar">
{
// default Avatar looks a bit jarring -- intentionally not rendering if no display_name
user.display_name && user.display_name.length > 0 &&
......@@ -105,45 +98,45 @@ class ProfilePage extends React.Component<ProfilePageProps, ProfilePageState> {
}
</div>
<div className="profile-details">
<div className="profile-title">
<div id="profile-title" className="profile-title">
<h1>{ user.display_name }</h1>
{
(!user.is_active) &&
<Flag caseType="sentenceCase" labelStyle="label-danger" text="Alumni"/>
}
</div>
<text>{ `${user.role_name} on ${user.team_name}` }</text>
<text>{ `Manager: ${user.manager_name}` }</text>
<text id="user-role">{ `${user.role_name} on ${user.team_name}` }</text>
<text id="user-manager">{ `Manager: ${user.manager_name}` }</text>
<div className="profile-icons">
{
user.is_active &&
<a href={user.slack_url} className='btn btn-flat-icon' target='_blank'>
<a id="slack-link" href={user.slack_url} className='btn btn-flat-icon' target='_blank'>
<img className='icon icon-slack'/>
<span>Slack</span>
</a>
}
{
user.is_active &&
<a href={`mailto:${user.email}`} className='btn btn-flat-icon' target='_blank'>
<a id="email-link" href={`mailto:${user.email}`} className='btn btn-flat-icon' target='_blank'>
<img className='icon icon-mail'/>
<span>{ user.email }</span>
</a>
}
{
user.is_active &&
<a href={user.profile_url} className='btn btn-flat-icon' target='_blank'>
<a id="profile-link" href={user.profile_url} className='btn btn-flat-icon' target='_blank'>
<img className='icon icon-users'/>
<span>Employee Profile</span>
</a>
}
<a href={`https://github.com/${user.github_name}`} className='btn btn-flat-icon' target='_blank'>
<a id="github-link" href={`https://github.com/${user.github_name}`} className='btn btn-flat-icon' target='_blank'>
<img className='icon icon-github'/>
<span>Github</span>
</a>
</div>
</div>
</div>
<div className="profile-tabs">
<div id="profile-tabs" className="profile-tabs">
<Tabs tabs={ this.generateTabInfo() } defaultTab='frequentUses_tab' />
</div>
</div>
......@@ -152,13 +145,13 @@ class ProfilePage extends React.Component<ProfilePageProps, ProfilePageState> {
}
}
const mapStateToProps = (state: GlobalState) => {
export const mapStateToProps = (state: GlobalState) => {
return {
user: state.user.profileUser,
}
}
const mapDispatchToProps = (dispatch) => {
export const mapDispatchToProps = (dispatch) => {
return bindActionCreators({ getUserById }, dispatch);
};
......
import * as React from 'react';
import * as DocumentTitle from 'react-document-title';
import Avatar from 'react-avatar';
import { shallow } from 'enzyme';
import Breadcrumb from 'components/common/Breadcrumb';
import Flag from 'components/common/Flag';
import Tabs from 'components/common/Tabs';
import { ProfilePage, ProfilePageProps, mapDispatchToProps, mapStateToProps } from '../';
import globalState from 'fixtures/globalState';
describe('ProfilePage', () => {
let props: ProfilePageProps;
let subject;
beforeEach(() => {
props = {
user: {
user_id: 'test0',
display_name: 'Test User',
email: 'test@test.com',
github_name: 'githubName',
is_active: true,
manager_name: 'Test Manager',
profile_url: 'www.test.com',
role_name: 'Tester',
slack_url: 'www.slack.com',
team_name: 'QA',
},
getUserById: jest.fn(),
};
// @ts-ignore : complains about match
subject = shallow(<ProfilePage {...props} match={{params: {userId: 'test0'}}}/>);
});
describe('constructor', () => {
it('sets the userId if it exists on match.params', () => {
expect(subject.instance().userId).toEqual('test0');
});
it('sets the userId as empty string if no match.params.userId', () => {
// @ts-ignore : complains about match
subject = shallow(<ProfilePage {...props} match={{params: {}}}/>);
expect(subject.instance().userId).toEqual('');
});
});
describe('componentDidMount', () => {
it('calls props.getUserById', () => {
expect(props.getUserById).toHaveBeenCalled();
});
});
describe('createEmptyTabMessage', () => {
let content;
beforeEach(() => {
content = subject.instance().createEmptyTabMessage('Empty message');
});
it('creates div w/ correct class', () => {
expect(shallow(content).find('div').props().className).toEqual('empty-tab-message');
});
it('creates text with given message', () => {
expect(shallow(content).find('text').text()).toEqual('Empty message');
});
});
/* TODO: Implement proper test when the real logic for this component is written */
describe('generateTabInfo', () => {
let tabInfoArray;
beforeEach(() => {
tabInfoArray = subject.instance().generateTabInfo();
});
it('has a passing test so that it does not throw an error', () => {
expect(true).toEqual(true);
});
});
describe('render', () => {
it('renders DocumentTitle w/ correct title', () => {
expect(subject.find(DocumentTitle).props().title).toEqual(`${props.user.display_name} - Amundsen Profile`);
});
it('renders Breadcrumb with correct props', () => {
expect(subject.find(Breadcrumb).props()).toMatchObject({
path: '/',
text: 'Search Results',
});
});
it('renders Avatar for user.display_name', () => {
/* Note: subject.find(Avatar) does not work - workaround is to directly check the content */
const expectedContent = <Avatar name={props.user.display_name} size={74} round={true} />;
expect(subject.find('#profile-avatar').props().children).toEqual(expectedContent);
});
it('does not render Avatar if user.display_name is empty string', () => {
props.user.display_name = '';
subject.setProps(props);
expect(subject.find('#profile-avatar').children().exists()).toBeFalsy();
});
it('renders header with display_name', () => {
expect(subject.find('#profile-title').find('h1').text()).toEqual(props.user.display_name);
});
it('renders Flag with correct props if user not active', () => {
props.user.is_active = false;
subject.setProps(props);
expect(subject.find('#profile-title').find(Flag).props()).toMatchObject({
caseType: 'sentenceCase',
labelStyle: 'label-danger',
text: 'Alumni',
});
});
it('renders user role', () => {
expect(subject.find('#user-role').text()).toEqual('Tester on QA');
});
it('renders user manager', () => {
expect(subject.find('#user-manager').text()).toEqual('Manager: Test Manager');
});
it('renders user manager', () => {
expect(subject.find('#user-manager').text()).toEqual('Manager: Test Manager');
});
it('renders slack link with correct href if user.is_active', () => {
props.user.is_active = true;
subject.setProps(props);
expect(subject.find('#slack-link').props().href).toEqual('www.slack.com');
});
it('renders slack link with correct text if user.is_active', () => {
props.user.is_active = true;
subject.setProps(props);
expect(subject.find('#slack-link').find('span').text()).toEqual('Slack');
});
it('renders email link with correct href if user.is_active', () => {
props.user.is_active = true;
subject.setProps(props);
expect(subject.find('#email-link').props().href).toEqual('mailto:test@test.com');
});
it('renders email link with correct text if user.is_active', () => {
props.user.is_active = true;
subject.setProps(props);
expect(subject.find('#email-link').find('span').text()).toEqual('test@test.com');
});
it('renders profile link with correct href if user.is_active', () => {
props.user.is_active = true;
subject.setProps(props);
expect(subject.find('#profile-link').props().href).toEqual('www.test.com');
});
it('renders profile link with correct text if user.is_active', () => {
props.user.is_active = true;
subject.setProps(props);
expect(subject.find('#profile-link').find('span').text()).toEqual('Employee Profile');
});
it('renders github link with correct href', () => {
expect(subject.find('#github-link').props().href).toEqual('https://github.com/githubName');
});
it('renders github link with correct text', () => {
expect(subject.find('#github-link').find('span').text()).toEqual('Github');
});
it('renders Tabs w/ correct props', () => {
expect(subject.find('#profile-tabs').find(Tabs).props()).toMatchObject({
tabs: subject.instance().generateTabInfo(),
defaultTab: 'frequentUses_tab',
});
});
});
});
describe('mapDispatchToProps', () => {
let dispatch;
let result;
beforeEach(() => {
dispatch = jest.fn(() => Promise.resolve());
result = mapDispatchToProps(dispatch);
});
it('sets getUserById on the props', () => {
expect(result.getUserById).toBeInstanceOf(Function);
});
});
describe('mapStateToProps', () => {
let result;
beforeEach(() => {
result = mapStateToProps(globalState);
});
it('sets user on the props', () => {
expect(result.user).toEqual(globalState.user.profileUser);
});
});
import * as React from 'react';
import { mount, shallow } from 'enzyme';
import { shallow } from 'enzyme';
import { Avatar } from 'react-avatar';
import AvatarLabel, { AvatarLabelProps } from '../';
......
......@@ -35,8 +35,8 @@ class TableListItem extends React.Component<TableListItemProps, {}> {
<img className="icon icon-database icon-color" />
<div className="content">
<div className={ hasLastUpdated? "col-sm-9 col-md-10" : "col-sm-12"}>
<div id="main-title" className="main-title truncated">{ `${table.schema_name}.${table.name}`}</div>
<div id="main-description" className="description truncated">{ table.description }</div>
<div className="main-title truncated">{ `${table.schema_name}.${table.name}`}</div>
<div className="description truncated">{ table.description }</div>
</div>
{/*<div className={ hasLastUpdated? "hidden-xs col-sm-3 col-md-4" : "hidden-xs col-sm-6"}>*/}
{/*<div className="secondary-title">Frequent Users</div>*/}
......@@ -47,8 +47,8 @@ class TableListItem extends React.Component<TableListItemProps, {}> {
{
hasLastUpdated &&
<div className="hidden-xs col-sm-3 col-md-2">
<div id="secondary-title" className="secondary-title">Latest Data</div>
<div id="secondary-description" className="description truncated">
<div className="secondary-title">Last Updated</div>
<div className="description truncated">
{ this.getDateLabel() }
</div>
</div>
......
......@@ -19,11 +19,11 @@ describe('TableListItem', () => {
type: ResourceType.table,
cluster: '',
database: '',
description: '',
description: 'I am the description',
key: '',
last_updated_epoch: null,
name: '',
schema_name: '',
name: 'tableName',
schema_name: 'tableSchema',
},
}
subject = shallow(<TableListItem {...props} />);
......@@ -34,29 +34,33 @@ describe('TableListItem', () => {
expect(subject.find(Link).exists()).toBeTruthy();
});
it('renders table name', () => {
expect(subject.find('.content').children().at(0).children().at(0).text()).toEqual('tableSchema.tableName');
});
it('renders correct text in main-title', () => {
const { table } = props;
expect(subject.find('#main-title').text()).toEqual(`${table.schema_name}.${table.name}`);
it('renders table description', () => {
expect(subject.find('.content').children().at(0).children().at(1).text()).toEqual('I am the description');
});
it('renders main-description', () => {
const { table } = props;
expect(subject.find('#main-description').text()).toEqual(table.description);
it('renders secondary title if table has last_updated_epoch ', () => {
props.table.last_updated_epoch = 1553829681;
subject.setProps(props);
expect(subject.find('.content').children().at(1).children().at(0).text()).toEqual('Last Updated');
});
it('renders secondary-description w/ getDateLabel value if table has last_updated_epoch ', () => {
it('renders secondary description w/ getDateLabel value if table has last_updated_epoch ', () => {
subject.instance().getDateLabel = jest.fn(() => 'Mar 28, 2019')
props.table.last_updated_epoch = 1553829681;
subject.setProps(props);
expect(subject.find('#secondary-description').text()).toEqual('Mar 28, 2019');
expect(subject.find('.content').children().at(1).children().at(1).text()).toEqual('Mar 28, 2019');
});
});
describe('getDateLabel', () => {
it('getDateLabel returns correct string', () => {
props.table.last_updated_epoch = 1553829681;
subject.setProps(props);
/* Note: Jest will convert date to UTC, expect to see different strings for an epoch value in the tests vs UI */
/* Note: Jest will convert date to UTC, expect to see different strings for an epoch value in the tests vs UI*/
expect(subject.instance().getDateLabel()).toEqual('Mar 29, 2019');
});
});
......
......@@ -28,20 +28,20 @@ class UserListItem extends React.Component<UserListItemProps, {}> {
<Avatar name={ user.name } size={ 24 } round={ true } />
<div className="content">
<div className="col-xs-12 col-sm-6">
<div id="main-title" className="main-title">
<div className="main-title">
{ user.name }
{
!user.active &&
<Flag text="Alumni" labelStyle='danger' />
}
</div>
<div id="main-description" className="description">
<div className="description">
{ `${user.role} on ${user.team_name}` }
</div>
</div>
<div className="hidden-xs col-sm-6">
<div id="secondary-title" className="secondary-title">Frequently Uses</div>
<div id="secondary-description" className="description truncated">
<div className="secondary-title">Frequently Uses</div>
<div className="description truncated">
{ /*TODO Fill this with a real value*/ }
<label>{ user.title }</label>
</div>
......
......@@ -49,19 +49,19 @@ describe('UserListItem', () => {
expect(subject.find(Link).find(Avatar).exists()).toBeTruthy();
});*/
it('renders user.name in title', () => {
expect(subject.find('#main-title').text()).toEqual(props.user.name);
it('renders user.name', () => {
expect(subject.find('.content').children().at(0).children().at(0).children().at(0).text()).toEqual(props.user.name);
});
it('renders Alumni flag if user not active', () => {
props.user.active = false;
subject.setProps(props);
expect(subject.find('#main-title').find(Flag).exists()).toBeTruthy();
expect(subject.find('.content').children().at(0).children().at(0).find(Flag).exists()).toBeTruthy();
});
it('renders description', () => {
const { user } = props;
expect(subject.find('#main-description').text()).toEqual(`${user.role} on ${user.team_name}`);
expect(subject.find('.content').children().at(0).children().at(1).text()).toEqual(`${user.role} on ${user.team_name}`);
});
});
......
......@@ -72,7 +72,7 @@ interface TableLineageConfig {
urlGenerator: (database: string, cluster: string, schema: string , table: string) => string;
}
interface LinkConfig {
export interface LinkConfig {
href: string;
id: string;
label: string;
......
import { GlobalState } from 'ducks/rootReducer';
import { SendingState } from 'components/Feedback/types';
const globalState: GlobalState = {
announcements: {
posts: [{
date: '12/31/1999',
title: 'Y2K',
html_content: '<div>The end of the world</div>',
},
{
date: '01/01/2000',
title: 'False Alarm',
html_content: '<div>Just kidding</div>',
}],
},
feedback: {
sendState: SendingState.IDLE,
},
popularTables: [],
search: {
search_term: '',
dashboards: {
page_index: 0,
results: [],
total_results: 0,
},
tables: {
page_index: 0,
results: [],
total_results: 0,
},
users: {
page_index: 0,
results: [],
total_results: 0,
},
},
tableMetadata: {
isLoading: true,
lastIndexed: 1555632106,
preview: {
data: {},
status: null,
},
statusCode: null,
tableData: {
cluster: '',
columns: [],
database: '',
is_editable: false,
schema: '',
table_name: '',
table_description: '',
table_writer: { application_url: '', description: '', id: '', name: '' },
partition: { is_partitioned: false },
table_readers: [],
source: { source: '', source_type: '' },
watermarks: [],
},
tableOwners: {
isLoading: true,
owners: {},
},
tableTags: {
isLoading: true,
tags: [],
},
},
allTags: {
allTags: [],
isLoading: false,
},
user: {
loggedInUser: {
user_id: 'user0',
display_name: 'User Name',
},
profileUser: {
user_id: 'user1',
display_name: 'User1 Name',
},
},
};
export default globalState;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment