Skip to content

Commit

Permalink
Merge pull request #100 from acelaya/feature/tests
Browse files Browse the repository at this point in the history
Feature/tests
  • Loading branch information
acelaya committed Jan 13, 2019
2 parents c2ee688 + 9318c1c commit 2d33631
Show file tree
Hide file tree
Showing 20 changed files with 455 additions and 86 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org).

## [Unreleased]
## 2.0.0 - 2019-01-13

#### Added

Expand All @@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

* [#87](https://github.com/shlinkio/shlink-web-client/issues/87) and [#89](https://github.com/shlinkio/shlink-web-client/issues/89) Updated all dependencies to latest major versions.
* [#96](https://github.com/shlinkio/shlink-web-client/issues/96) Updated visits page to load visits in multiple paginated requests of `5000` visits when used shlink server supports it. This will prevent shlink to hang when trying to load big amounts of visits.
* [#71](https://github.com/shlinkio/shlink-web-client/issues/71) Improved tests and increased code coverage.

#### Deprecated

Expand Down
8 changes: 4 additions & 4 deletions src/common/ScrollToTop.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import React from 'react';
import PropTypes from 'prop-types';

const ScrollToTop = (window) => class ScrollToTop extends React.Component {
const ScrollToTop = ({ scrollTo }) => class ScrollToTop extends React.Component {
static propTypes = {
location: PropTypes.object,
children: PropTypes.node,
};

componentDidUpdate(prevProps) {
componentDidUpdate({ location: prevLocation }) {
const { location } = this.props;

if (location !== prevProps.location) {
window.scrollTo(0, 0);
if (location !== prevLocation) {
scrollTo(0, 0);
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/container/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ import provideUtilsServices from '../utils/services/provideServices';
const bottle = new Bottle();
const { container } = bottle;

const lazyService = (container, serviceName) => (...args) => container[serviceName](...args);
const mapActionService = (map, actionName) => ({
...map,

// Wrap actual action service in a function so that it is lazily created the first time it is called
[actionName]: (...args) => container[actionName](...args),
[actionName]: lazyService(container, actionName),
});
const connect = (propsFromState, actionServiceNames) =>
reduxConnect(
Expand Down
15 changes: 10 additions & 5 deletions src/servers/helpers/ImportServersBtn.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import { UncontrolledTooltip } from 'reactstrap';
import { assoc } from 'ramda';
import { assoc, map } from 'ramda';
import { v4 as uuid } from 'uuid';
import PropTypes from 'prop-types';

Expand All @@ -22,11 +22,16 @@ const ImportServersBtn = (serversImporter) => class ImportServersBtn extends Rea
render() {
const { importServersFromFile } = serversImporter;
const { onImport, createServers } = this.props;
const onChange = (e) =>
importServersFromFile(e.target.files[0])
.then((servers) => servers.map((server) => assoc('id', uuid(), server)))
const assocId = (server) => assoc('id', uuid(), server);
const onChange = ({ target }) =>
importServersFromFile(target.files[0])
.then(map(assocId))
.then(createServers)
.then(onImport);
.then(onImport)
.then(() => {
// Reset input after processing file
target.value = null;
});

return (
<React.Fragment>
Expand Down
12 changes: 6 additions & 6 deletions src/servers/reducers/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,20 @@ export const listServers = (serversService) => () => ({
servers: serversService.listServers(),
});

export const createServer = (serversService) => (server) => {
export const createServer = (serversService, listServers) => (server) => {
serversService.createServer(server);

return listServers(serversService)();
return listServers();
};

export const deleteServer = (serversService) => (server) => {
export const deleteServer = (serversService, listServers) => (server) => {
serversService.deleteServer(server);

return listServers(serversService)();
return listServers();
};

export const createServers = (serversService) => (servers) => {
export const createServers = (serversService, listServers) => (servers) => {
serversService.createServers(servers);

return listServers(serversService)();
return listServers();
};
6 changes: 3 additions & 3 deletions src/servers/services/provideServices.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ const provideServices = (bottle, connect, withRouter) => {

// Actions
bottle.serviceFactory('selectServer', selectServer, 'ServersService');
bottle.serviceFactory('createServer', createServer, 'ServersService');
bottle.serviceFactory('createServers', createServers, 'ServersService');
bottle.serviceFactory('deleteServer', deleteServer, 'ServersService');
bottle.serviceFactory('createServer', createServer, 'ServersService', 'listServers');
bottle.serviceFactory('createServers', createServers, 'ServersService', 'listServers');
bottle.serviceFactory('deleteServer', deleteServer, 'ServersService', 'listServers');
bottle.serviceFactory('listServers', listServers, 'ServersService');

bottle.serviceFactory('resetSelectedServer', () => resetSelectedServer);
Expand Down
6 changes: 3 additions & 3 deletions src/short-urls/ShortUrlsList.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import './ShortUrlsList.scss';
const SORTABLE_FIELDS = {
dateCreated: 'Created at',
shortCode: 'Short URL',
originalUrl: 'Long URL',
longUrl: 'Long URL',
visits: 'Visits',
};

Expand Down Expand Up @@ -142,9 +142,9 @@ const ShortUrlsList = (ShortUrlsRow) => class ShortUrlsList extends React.Compon
</th>
<th
className="short-urls-list__header-cell short-urls-list__header-cell--with-action"
onClick={this.orderByColumn('originalUrl')}
onClick={this.orderByColumn('longUrl')}
>
{this.renderOrderIcon('originalUrl')}
{this.renderOrderIcon('longUrl')}
Long URL
</th>
<th className="short-urls-list__header-cell">Tags</th>
Expand Down
6 changes: 2 additions & 4 deletions src/short-urls/helpers/ShortUrlsRow.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,17 @@ const ShortUrlsRow = (

render() {
const { shortUrl, selectedServer } = this.props;
const completeShortUrl = !selectedServer ? shortUrl.shortCode : `${selectedServer.url}/${shortUrl.shortCode}`;

return (
<tr className="short-urls-row">
<td className="nowrap short-urls-row__cell" data-th="Created at: ">
<Moment format="YYYY-MM-DD HH:mm">{shortUrl.dateCreated}</Moment>
</td>
<td className="short-urls-row__cell" data-th="Short URL: ">
<ExternalLink href={completeShortUrl}>{completeShortUrl}</ExternalLink>
<ExternalLink href={shortUrl.shortUrl} />
</td>
<td className="short-urls-row__cell short-urls-row__cell--break" data-th="Long URL: ">
<ExternalLink href={shortUrl.originalUrl}>{shortUrl.originalUrl}</ExternalLink>
<ExternalLink href={shortUrl.longUrl} />
</td>
<td className="short-urls-row__cell" data-th="Tags: ">{this.renderTags(shortUrl.tags)}</td>
<td className="short-urls-row__cell text-md-right" data-th="Visits: ">{shortUrl.visitsCount}</td>
Expand All @@ -66,7 +65,6 @@ const ShortUrlsRow = (
Copied short URL!
</small>
<ShortUrlsRowMenu
completeShortUrl={completeShortUrl}
selectedServer={selectedServer}
shortUrl={shortUrl}
onCopyToClipboard={() => stateFlagTimeout(this.setState.bind(this), 'copiedToClipboard')}
Expand Down
29 changes: 8 additions & 21 deletions src/short-urls/helpers/ShortUrlsRowMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import './ShortUrlsRowMenu.scss';

const ShortUrlsRowMenu = (DeleteShortUrlModal, EditTagsModal) => class ShortUrlsRowMenu extends React.Component {
static propTypes = {
completeShortUrl: PropTypes.string,
onCopyToClipboard: PropTypes.func,
selectedServer: serverType,
shortUrl: shortUrlType,
Expand All @@ -29,18 +28,18 @@ const ShortUrlsRowMenu = (DeleteShortUrlModal, EditTagsModal) => class ShortUrls
state = {
isOpen: false,
isQrModalOpen: false,
isPreviewOpen: false,
isPreviewModalOpen: false,
isTagsModalOpen: false,
isDeleteModalOpen: false,
};
toggle = () => this.setState(({ isOpen }) => ({ isOpen: !isOpen }));

render() {
const { completeShortUrl, onCopyToClipboard, selectedServer, shortUrl } = this.props;
const serverId = selectedServer ? selectedServer.id : '';
const { onCopyToClipboard, shortUrl, selectedServer: { id } } = this.props;
const completeShortUrl = shortUrl && shortUrl.shortUrl ? shortUrl.shortUrl : '';
const toggleModal = (prop) => () => this.setState((prevState) => ({ [prop]: !prevState[prop] }));
const toggleQrCode = toggleModal('isQrModalOpen');
const togglePreview = toggleModal('isPreviewOpen');
const togglePreview = toggleModal('isPreviewModalOpen');
const toggleTags = toggleModal('isTagsModalOpen');
const toggleDelete = toggleModal('isDeleteModalOpen');

Expand All @@ -50,7 +49,7 @@ const ShortUrlsRowMenu = (DeleteShortUrlModal, EditTagsModal) => class ShortUrls
&nbsp;<FontAwesomeIcon icon={menuIcon} />&nbsp;
</DropdownToggle>
<DropdownMenu right>
<DropdownItem tag={Link} to={`/server/${serverId}/short-code/${shortUrl.shortCode}/visits`}>
<DropdownItem tag={Link} to={`/server/${id}/short-code/${shortUrl.shortCode}/visits`}>
<FontAwesomeIcon icon={pieChartIcon} /> &nbsp;Visit stats
</DropdownItem>

Expand All @@ -67,31 +66,19 @@ const ShortUrlsRowMenu = (DeleteShortUrlModal, EditTagsModal) => class ShortUrls
<DropdownItem className="short-urls-row-menu__dropdown-item--danger" onClick={toggleDelete}>
<FontAwesomeIcon icon={deleteIcon} /> &nbsp;Delete short URL
</DropdownItem>
<DeleteShortUrlModal
shortUrl={shortUrl}
isOpen={this.state.isDeleteModalOpen}
toggle={toggleDelete}
/>
<DeleteShortUrlModal shortUrl={shortUrl} isOpen={this.state.isDeleteModalOpen} toggle={toggleDelete} />

<DropdownItem divider />

<DropdownItem onClick={togglePreview}>
<FontAwesomeIcon icon={pictureIcon} /> &nbsp;Preview
</DropdownItem>
<PreviewModal
url={completeShortUrl}
isOpen={this.state.isPreviewOpen}
toggle={togglePreview}
/>
<PreviewModal url={completeShortUrl} isOpen={this.state.isPreviewModalOpen} toggle={togglePreview} />

<DropdownItem onClick={toggleQrCode}>
<FontAwesomeIcon icon={qrIcon} /> &nbsp;QR code
</DropdownItem>
<QrCodeModal
url={completeShortUrl}
isOpen={this.state.isQrModalOpen}
toggle={toggleQrCode}
/>
<QrCodeModal url={completeShortUrl} isOpen={this.state.isQrModalOpen} toggle={toggleQrCode} />

<DropdownItem divider />

Expand Down
5 changes: 3 additions & 2 deletions src/short-urls/reducers/shortUrlsList.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ export const LIST_SHORT_URLS = 'shlink/shortUrlsList/LIST_SHORT_URLS';
/* eslint-enable padding-line-between-statements, newline-after-var */

export const shortUrlType = PropTypes.shape({
tags: PropTypes.arrayOf(PropTypes.string),
shortCode: PropTypes.string,
originalUrl: PropTypes.string,
shortUrl: PropTypes.string,
longUrl: PropTypes.string,
tags: PropTypes.arrayOf(PropTypes.string),
});

const initialState = {
Expand Down
9 changes: 4 additions & 5 deletions src/tags/helpers/DeleteTagConfirmModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,12 @@ export default class DeleteTagConfirmModal extends React.Component {
tagDeleted: PropTypes.func,
};

doDelete = () => {
doDelete = async () => {
const { tag, toggle, deleteTag } = this.props;

return deleteTag(tag).then(() => {
this.tagWasDeleted = true;
toggle();
});
await deleteTag(tag);
this.tagWasDeleted = true;
toggle();
};
handleOnClosed = () => {
if (!this.tagWasDeleted) {
Expand Down
8 changes: 1 addition & 7 deletions src/visits/VisitsHeader.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,7 @@ export function VisitsHeader({ shortUrlDetail, shortUrlVisits }) {
Visit stats for <ExternalLink href={shortLink} />
</h2>
<hr />
{shortUrl.dateCreated && (
<div>
Created:
&nbsp;
{renderDate()}
</div>
)}
<div>Created: {renderDate()}</div>
<div>
Long URL:
&nbsp;
Expand Down
29 changes: 29 additions & 0 deletions test/common/ScrollToTop.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from 'react';
import { shallow } from 'enzyme';
import * as sinon from 'sinon';
import createScrollToTop from '../../src/common/ScrollToTop';

describe('<ScrollToTop />', () => {
let wrapper;
const window = {
scrollTo: sinon.spy(),
};

beforeEach(() => {
const ScrollToTop = createScrollToTop(window);

wrapper = shallow(<ScrollToTop locaction={{ href: 'foo' }}>Foobar</ScrollToTop>);
});

afterEach(() => {
wrapper.unmount();
window.scrollTo.resetHistory();
});

it('just renders children', () => expect(wrapper.text()).toEqual('Foobar'));

it('scrolls to top when location changes', () => {
wrapper.instance().componentDidUpdate({ location: { href: 'bar' } });
expect(window.scrollTo.calledOnce).toEqual(true);
});
});

0 comments on commit 2d33631

Please sign in to comment.