0
Fork 0
mirror of https://github.com/verdaccio/verdaccio.git synced 2025-02-17 23:45:29 -05:00

refactor: header component test (#878)

* refactor: improves <Header /> component test cases

* refactor: adds destructuring for state in render block
This commit is contained in:
Ayush Sharma 2018-08-01 15:00:39 +02:00 committed by GitHub
parent 9c4931dc77
commit dc9460ff07
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 679 additions and 590 deletions

View file

@ -14,7 +14,6 @@ import {HEADERS} from '../../../lib/constants';
import classes from './header.scss';
import './logo.png';
export default class Header extends React.Component {
state = {
showLogin: false,
@ -31,6 +30,7 @@ export default class Header extends React.Component {
this.handleSubmit = this.handleSubmit.bind(this);
this.handleInput = this.handleInput.bind(this);
this.loadLogo = this.loadLogo.bind(this);
this.renderUserActionButton = this.renderUserActionButton.bind(this);
}
toggleLoginModal() {
@ -63,11 +63,13 @@ export default class Header extends React.Component {
event.preventDefault();
if (this.state.username === '' || this.state.password === '') {
return this.setState({loginError: {
title: 'Unable to login',
type: 'error',
description: 'Username or password can\'t be empty!'
}});
return this.setState({
loginError: {
title: 'Unable to login',
type: 'error',
description: 'Username or password can\'t be empty!'
}
});
}
try {
@ -119,8 +121,8 @@ export default class Header extends React.Component {
if (!payload.exp || !isNumber(payload.exp)) {
return true;
}
const jsTimestamp = (payload.exp * 1000) - 30000; // Report as expire before (real expire time - 30s)
// Report as expire before (real expire time - 30s)
const jsTimestamp = payload.exp * 1000 - 30000;
const expired = Date.now() >= jsTimestamp;
if (expired) {
@ -136,35 +138,54 @@ export default class Header extends React.Component {
}
renderUserActionButton() {
if (!this.isTokenExpire) { // TODO: Check jwt token expire
if (!this.isTokenExpire) {
// TODO: Check jwt token expire
const username = capitalize(storage.getItem('username'));
return (
<div className="user-logged">
<span className="user-logged-greetings" style={{marginRight: '10px'}}>Hi, {username}</span>
<Button className={`${classes.headerButton} header-button-logout`} type="danger" onClick={this.handleLogout}>Logout</Button>
<span
className="user-logged-greetings"
style={{marginRight: '10px'}}
>
Hi, {username}
</span>
<Button
className={`${classes.headerButton} header-button-logout`}
type="danger"
onClick={this.handleLogout}
>
Logout
</Button>
</div>
);
} else {
return <Button className={`${classes.headerButton} header-button-login`} onClick={ this.toggleLoginModal }>Login</Button>;
return (
<Button
className={`${classes.headerButton} header-button-login`}
onClick={this.toggleLoginModal}
>
Login
</Button>
);
}
}
render() {
const registryURL = getRegistryURL();
const {logo, scope, loginError, showLogin} = this.state;
return (
<header className={ classes.header }>
<div className={ classes.headerWrap }>
<header className={classes.header}>
<div className={classes.headerWrap}>
<Link to="/">
<img src={ this.state.logo } className={ classes.logo } />
<img src={logo} className={classes.logo} />
</Link>
<figure>
npm set { this.state.scope }registry { registryURL }
npm set { scope }registry { registryURL }
<br/>
npm adduser --registry { registryURL }
</figure>
<div className={ classes.headerRight }>
<div className={classes.headerRight}>
{this.renderUserActionButton()}
</div>
</div>
@ -172,27 +193,47 @@ export default class Header extends React.Component {
<Dialog
title="Login"
size="tiny"
visible={ this.state.showLogin }
onCancel={ () => this.toggleLoginModal() }
visible={showLogin}
onCancel={this.toggleLoginModal}
>
<Form className="login-form">
<Dialog.Body>
{ this.state.loginError &&
<Alert
title={this.state.loginError.title} type={this.state.loginError.type}
description={this.state.loginError.description} showIcon={true} closable={false}>
</Alert>
}
<br/>
<Input name="username" placeholder="Username" onChange={this.handleInput.bind(this, 'username')} />
<br/><br/>
<Input name="password" type="password" placeholder="Type your password" onChange={this.handleInput.bind(this, 'password')} />
{loginError && (
<Alert
title={loginError.title}
type={loginError.type}
description={loginError.description}
showIcon={true}
closable={false}
/>
)}
<br />
<Input
name="username"
placeholder="Username"
onChange={this.handleInput.bind(this, 'username')}
/>
<br />
<br />
<Input
name="password"
type="password"
placeholder="Type your password"
onChange={this.handleInput.bind(this, 'password')}
/>
</Dialog.Body>
<Dialog.Footer className="dialog-footer">
<Button onClick={ () => this.toggleLoginModal() } className="cancel-login-button">
<Button
onClick={this.toggleLoginModal}
className="cancel-login-button"
>
Cancel
</Button>
<Button nativeType="submit" className="login-button" onClick={ this.handleSubmit }>
<Button
nativeType="submit"
className="login-button"
onClick={this.handleSubmit}
>
Login
</Button>
</Dialog.Footer>

View file

@ -8,4 +8,28 @@ import Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });
global.__APP_VERSION__ = '1.0.0';
global.__APP_VERSION__ = '1.0.0';
class LocalStorageMock {
constructor() {
this.store = {};
}
clear() {
this.store = {};
}
getItem(key) {
return this.store[key] || null;
}
setItem(key, value) {
this.store[key] = value.toString();
}
removeItem(key) {
delete this.store[key];
}
}
global.localStorage = new LocalStorageMock();

View file

@ -1,3 +1,48 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Header /> component shallow renderUserActionButton - should show login button 1`] = `
<Button
className="headerButton header-button-login"
disabled={false}
loading={false}
nativeType="button"
onClick={[Function]}
plain={false}
type="default"
>
Login
</Button>
`;
exports[`<Header /> component shallow renderUserActionButton - should show users as loggedin 1`] = `
<div
className="user-logged"
>
<span
className="user-logged-greetings"
style={
Object {
"marginRight": "10px",
}
}
>
Hi,
Sam
</span>
<Button
className="headerButton header-button-logout"
disabled={false}
loading={false}
nativeType="button"
onClick={[Function]}
plain={false}
type="danger"
>
Logout
</Button>
</div>
`;
exports[`<Header /> snapshot for loggedin user should match snapshot 1`] = `"<header class=\\"header\\"><div class=\\"headerWrap\\"><a href=\\"/\\"><img src=\\"\\" class=\\"logo\\"></a><figure>npm set registry http://localhost<br>npm adduser --registry http://localhost</figure><div class=\\"headerRight\\"><div class=\\"user-logged\\"><span class=\\"user-logged-greetings\\" style=\\"margin-right: 10px;\\">Hi, Verdaccio</span><button class=\\"el-button el-button--danger headerButton header-button-logout\\" type=\\"button\\"><span>Logout</span></button></div></div></div><div><div style=\\"z-index: 1013; display: none;\\" class=\\"el-dialog__wrapper\\"><div style=\\"top: 15%;\\" class=\\"el-dialog el-dialog--tiny\\"><div class=\\"el-dialog__header\\"><span class=\\"el-dialog__title\\">Login</span><button type=\\"button\\" class=\\"el-dialog__headerbtn\\"><i class=\\"el-dialog__close el-icon el-icon-close\\"></i></button></div><form class=\\"el-form el-form--label-right login-form\\"><div class=\\"el-dialog__body\\"><br><div class=\\"el-input\\"><input type=\\"text\\" name=\\"username\\" placeholder=\\"Username\\" class=\\"el-input__inner\\" autocomplete=\\"off\\"></div><br><br><div class=\\"el-input\\"><input type=\\"password\\" name=\\"password\\" placeholder=\\"Type your password\\" class=\\"el-input__inner\\" autocomplete=\\"off\\"></div></div><div class=\\"el-dialog__footer dialog-footer\\"><button class=\\"el-button el-button--default cancel-login-button\\" type=\\"button\\"><span>Cancel</span></button><button class=\\"el-button el-button--default login-button\\" type=\\"submit\\"><span>Login</span></button></div></form></div></div><div class=\\"v-modal\\" style=\\"z-index: 1012; display: none;\\"></div></div></header>"`;
exports[`<Header /> snapshot test shoud match snapshot 1`] = `"<header class=\\"header\\"><div class=\\"headerWrap\\"><a href=\\"/\\"><img src=\\"\\" class=\\"logo\\"></a><figure>npm set registry http://localhost<br>npm adduser --registry http://localhost</figure><div class=\\"headerRight\\"><button class=\\"el-button el-button--default headerButton header-button-login\\" type=\\"button\\"><span>Login</span></button></div></div><div><div style=\\"z-index: 1013; display: none;\\" class=\\"el-dialog__wrapper\\"><div style=\\"top: 15%;\\" class=\\"el-dialog el-dialog--tiny\\"><div class=\\"el-dialog__header\\"><span class=\\"el-dialog__title\\">Login</span><button type=\\"button\\" class=\\"el-dialog__headerbtn\\"><i class=\\"el-dialog__close el-icon el-icon-close\\"></i></button></div><form class=\\"el-form el-form--label-right login-form\\"><div class=\\"el-dialog__body\\"><br><div class=\\"el-input\\"><input type=\\"text\\" name=\\"username\\" placeholder=\\"Username\\" class=\\"el-input__inner\\" autocomplete=\\"off\\"></div><br><br><div class=\\"el-input\\"><input type=\\"password\\" name=\\"password\\" placeholder=\\"Type your password\\" class=\\"el-input__inner\\" autocomplete=\\"off\\"></div></div><div class=\\"el-dialog__footer dialog-footer\\"><button class=\\"el-button el-button--default cancel-login-button\\" type=\\"button\\"><span>Cancel</span></button><button class=\\"el-button el-button--default login-button\\" type=\\"submit\\"><span>Login</span></button></div></form></div></div><div class=\\"v-modal\\" style=\\"z-index: 1012; display: none;\\"></div></div></header>"`;

View file

@ -3,14 +3,51 @@
*/
import React from 'react';
import { shallow, mount } from 'enzyme';
import { Base64 } from 'js-base64';
import addHours from 'date-fns/add_hours'
import Header from '../../../../src/webui/components/Header';
import { BrowserRouter } from 'react-router-dom';
import storage from '../../../../src/webui/utils/storage';
jest.mock('../../../../src/webui/utils/storage', () => {
class LocalStorageMock {
constructor() {
this.store = {};
}
clear() {
this.store = {};
}
getItem(key) {
return this.store[key] || null;
}
setItem(key, value) {
this.store[key] = value.toString();
}
removeItem(key) {
delete this.store[key];
}
}
return new LocalStorageMock();
});
jest.mock('../../../../src/webui/utils/api', () => ({
request: require('./__mocks__/api').default.request,
}));
console.error = jest.fn();
const generateTokenWithTimeRange = (limit = 0) => {
const payload = {
username: 'verdaccio',
exp: Number.parseInt((addHours(new Date(), limit).getTime() / 1000), 10)
}
return `xxxxxx.${Base64.encode(JSON.stringify(payload))}.xxxxxx`;
}
describe('<Header /> component shallow', () => {
let wrapper;
@ -35,7 +72,7 @@ describe('<Header /> component shallow', () => {
expect(HeaderWrapper.state()).toEqual(state);
});
it('should load verdaccio logo', () => {
it('loadLogo - should load verdaccio logo', () => {
const HeaderWrapper = wrapper.find(Header).dive();
const { loadLogo } = HeaderWrapper.instance();
@ -45,15 +82,24 @@ describe('<Header /> component shallow', () => {
});
});
it('should toggleLogin modal', () => {
it('toggleLoginModal - should toggle login modal', () => {
const HeaderWrapper = wrapper.find(Header).dive();
const { toggleLoginModal } = HeaderWrapper.instance();
expect(toggleLoginModal()).toBeUndefined();
expect(HeaderWrapper.state('showLogin')).toBeTruthy();
});
it('should handleInput set state', () => {
it('toggleLoginModal - click on login button and cancel button in login dialog', () => {
const HeaderWrapper = wrapper.find(Header).dive();
const spy = jest.spyOn(HeaderWrapper.instance(), 'toggleLoginModal');
HeaderWrapper.find('.header-button-login').simulate('click');
HeaderWrapper.find('.cancel-login-button').simulate('click');
expect(spy).toHaveBeenCalled();
spy.mockRestore();
})
it('handleInput - should set username and password in state', () => {
const HeaderWrapper = wrapper.find(Header).dive();
const handleInput = HeaderWrapper.instance().handleInput;
@ -127,8 +173,110 @@ describe('<Header /> component shallow', () => {
expect(HeaderWrapper.state('loginError')).toEqual(error);
});
});
it('renderUserActionButton - should show login button', () => {
const HeaderWrapper = wrapper.find(Header).dive();
const { renderUserActionButton } = HeaderWrapper.instance();
expect(renderUserActionButton()).toMatchSnapshot();
});
it('renderUserActionButton - should show users as loggedin', () => {
class MockedHeader extends Header {
get isTokenExpire() {
return false;
}
}
wrapper = shallow(
<BrowserRouter>
<MockedHeader />
</BrowserRouter>);
const HeaderWrapper = wrapper.find(MockedHeader).dive();
const { renderUserActionButton } = HeaderWrapper.instance();
expect(renderUserActionButton()).toMatchSnapshot();
});
it('isTokenExpire - token is not availabe in storage', () => {
const HeaderWrapper = wrapper.find(Header).dive();
const { isTokenExpire } = HeaderWrapper.instance();
expect(isTokenExpire).toBeTruthy();
});
it('isTokenExpire - token is not a valid payload', () => {
const HeaderWrapper = wrapper.find(Header).dive();
const { isTokenExpire } = HeaderWrapper.instance();
storage.setItem('token', 'not_a_valid_token');
expect(isTokenExpire).toBeTruthy();
});
it('isTokenExpire - token should not expire in 24 hrs range', () => {
const HeaderWrapper = wrapper.find(Header).dive();
storage.setItem('token', generateTokenWithTimeRange(24));
expect(HeaderWrapper.instance().isTokenExpire).toBeFalsy();
storage.removeItem('token');
});
it('isTokenExpire - token should expire for present', () => {
const HeaderWrapper = wrapper.find(Header).dive();
storage.setItem('token', generateTokenWithTimeRange());
expect(HeaderWrapper.instance().isTokenExpire).toBeTruthy();
storage.removeItem('token');
});
it('isTokenExpire - token expiration is not available', () => {
const generateTokenWithOutExpiration = () => {
const payload = {
username: 'verdaccio'
}
return `xxxxxx.${Base64.encode(JSON.stringify(payload))}.xxxxxx`;
}
const HeaderWrapper = wrapper.find(Header).dive();
storage.setItem('token', generateTokenWithOutExpiration());
expect(HeaderWrapper.instance().isTokenExpire).toBeTruthy();
storage.removeItem('token');
})
it('isTokenExpire - token expiration is not a number', () => {
const generateTokenWithExpirationAsString = () => {
const payload = {
username: 'verdaccio',
exp: 'I am not a number'
};
return `xxxxxx.${Base64.encode(JSON.stringify(payload))}.xxxxxx`;
};
const HeaderWrapper = wrapper.find(Header).dive();
storage.setItem('token', generateTokenWithExpirationAsString());
expect(HeaderWrapper.instance().isTokenExpire).toBeTruthy();
storage.removeItem('token');
});
it('isTokenExpire - token is not a valid json token', () => {
const generateTokenWithExpirationAsString = () => {
const payload = { username: 'verdaccio', exp: 'I am not a number' };
return `xxxxxx.${Base64.encode(payload)}.xxxxxx`;
};
const result = [
'Invalid token:',
SyntaxError('Unexpected token o in JSON at position 1'),
'xxxxxx.W29iamVjdCBPYmplY3Rd.xxxxxx'
]
storage.setItem('token', generateTokenWithExpirationAsString());
wrapper.find(Header).dive().isTokenExpire;
expect(console.error).toBeCalledWith(...result);
storage.removeItem('token');
});
it('handleLogout - should clear the local stoage', () => {
const storageSpy = jest.spyOn(storage, 'clear');
const locationSpy = jest.spyOn(window.location, 'reload');
const HeaderWrapper = wrapper.find(Header).dive();
const { handleLogout } = HeaderWrapper.instance();
handleLogout();
expect(storageSpy).toHaveBeenCalled();
expect(locationSpy).toHaveBeenCalled()
});
});
describe('<Header /> snapshot test', () => {
it('shoud match snapshot', () => {
const wrapper = mount(
@ -139,3 +287,20 @@ describe('<Header /> snapshot test', () => {
expect(wrapper.html()).toMatchSnapshot();
});
});
describe('<Header /> snapshot for loggedin user', () => {
beforeAll(() => {
storage.setItem('token', generateTokenWithTimeRange(24));
storage.setItem('username', 'verdaccio');
})
afterAll(() => {
storage.removeItem('token');
})
it('should match snapshot', () => {
const wrapper = mount(
<BrowserRouter>
<Header />
</BrowserRouter>);
expect(wrapper.html()).toMatchSnapshot();
});
})

920
yarn.lock

File diff suppressed because it is too large Load diff