mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-23 09:30:31 +03:00
Merge pull request #214 from acelaya-forks/feature/consistent-server-loading
Feature/consistent server loading
This commit is contained in:
commit
451c77d47f
25 changed files with 129 additions and 166 deletions
|
@ -1,53 +1,47 @@
|
|||
import React from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
import { Swipeable } from 'react-swipeable';
|
||||
import { faBars as burgerIcon } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import classnames from 'classnames';
|
||||
import classNames from 'classnames';
|
||||
import * as PropTypes from 'prop-types';
|
||||
import { serverType } from '../servers/prop-types';
|
||||
import MutedMessage from '../utils/MutedMessage';
|
||||
import NotFound from './NotFound';
|
||||
import './MenuLayout.scss';
|
||||
|
||||
const MenuLayout = (TagsList, ShortUrls, AsideMenu, CreateShortUrl, ShortUrlVisits) =>
|
||||
class MenuLayout extends React.Component {
|
||||
static propTypes = {
|
||||
const propTypes = {
|
||||
match: PropTypes.object,
|
||||
selectServer: PropTypes.func,
|
||||
location: PropTypes.object,
|
||||
selectedServer: serverType,
|
||||
};
|
||||
};
|
||||
|
||||
state = { showSideBar: false };
|
||||
const MenuLayout = (TagsList, ShortUrls, AsideMenu, CreateShortUrl, ShortUrlVisits) => {
|
||||
const MenuLayoutComp = ({ match, location, selectedServer, selectServer }) => {
|
||||
const [ showSideBar, setShowSidebar ] = useState(false);
|
||||
|
||||
componentDidMount() {
|
||||
const { match, selectServer } = this.props;
|
||||
useEffect(() => {
|
||||
const { params: { serverId } } = match;
|
||||
|
||||
selectServer(serverId);
|
||||
}, []);
|
||||
useEffect(() => setShowSidebar(false), [ location ]);
|
||||
|
||||
if (!selectedServer) {
|
||||
return <MutedMessage loading />;
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { location } = this.props;
|
||||
|
||||
// Hide sidebar when location changes
|
||||
if (location !== prevProps.location) {
|
||||
this.setState({ showSideBar: false });
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { selectedServer, match } = this.props;
|
||||
const { params: { serverId } } = match;
|
||||
const burgerClasses = classnames('menu-layout__burger-icon', {
|
||||
'menu-layout__burger-icon--active': this.state.showSideBar,
|
||||
const burgerClasses = classNames('menu-layout__burger-icon', {
|
||||
'menu-layout__burger-icon--active': showSideBar,
|
||||
});
|
||||
const swipeMenuIfNoModalExists = (showSideBar) => () => {
|
||||
if (document.querySelector('.modal')) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ showSideBar });
|
||||
setShowSidebar(showSideBar);
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -55,7 +49,7 @@ const MenuLayout = (TagsList, ShortUrls, AsideMenu, CreateShortUrl, ShortUrlVisi
|
|||
<FontAwesomeIcon
|
||||
icon={burgerIcon}
|
||||
className={burgerClasses}
|
||||
onClick={() => this.setState(({ showSideBar }) => ({ showSideBar: !showSideBar }))}
|
||||
onClick={() => setShowSidebar(!showSideBar)}
|
||||
/>
|
||||
|
||||
<Swipeable
|
||||
|
@ -65,36 +59,13 @@ const MenuLayout = (TagsList, ShortUrls, AsideMenu, CreateShortUrl, ShortUrlVisi
|
|||
onSwipedRight={swipeMenuIfNoModalExists(true)}
|
||||
>
|
||||
<div className="row menu-layout__swipeable-inner">
|
||||
<AsideMenu
|
||||
className="col-lg-2 col-md-3"
|
||||
selectedServer={selectedServer}
|
||||
showOnMobile={this.state.showSideBar}
|
||||
/>
|
||||
<div
|
||||
className="col-lg-10 offset-lg-2 col-md-9 offset-md-3"
|
||||
onClick={() => this.setState({ showSideBar: false })}
|
||||
>
|
||||
<AsideMenu className="col-lg-2 col-md-3" selectedServer={selectedServer} showOnMobile={showSideBar} />
|
||||
<div className="col-lg-10 offset-lg-2 col-md-9 offset-md-3" onClick={() => setShowSidebar(false)}>
|
||||
<Switch>
|
||||
<Route
|
||||
exact
|
||||
path="/server/:serverId/list-short-urls/:page"
|
||||
component={ShortUrls}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/server/:serverId/create-short-url"
|
||||
component={CreateShortUrl}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/server/:serverId/short-code/:shortCode/visits"
|
||||
component={ShortUrlVisits}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/server/:serverId/manage-tags"
|
||||
component={TagsList}
|
||||
/>
|
||||
<Route exact path="/server/:serverId/list-short-urls/:page" component={ShortUrls} />
|
||||
<Route exact path="/server/:serverId/create-short-url" component={CreateShortUrl} />
|
||||
<Route exact path="/server/:serverId/short-code/:shortCode/visits" component={ShortUrlVisits} />
|
||||
<Route exact path="/server/:serverId/manage-tags" component={TagsList} />
|
||||
<Route
|
||||
render={() => <NotFound to={`/server/${serverId}/list-short-urls/1`} btnText="List short URLs" />}
|
||||
/>
|
||||
|
@ -104,7 +75,11 @@ const MenuLayout = (TagsList, ShortUrls, AsideMenu, CreateShortUrl, ShortUrlVisi
|
|||
</Swipeable>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
MenuLayoutComp.propTypes = propTypes;
|
||||
|
||||
return MenuLayoutComp;
|
||||
};
|
||||
|
||||
export default MenuLayout;
|
||||
|
|
|
@ -19,7 +19,7 @@ export const selectServer = ({ findServerById }, buildShlinkApiClient) => (serve
|
|||
dispatch(resetShortUrlParams());
|
||||
|
||||
const selectedServer = findServerById(serverId);
|
||||
const { health } = await buildShlinkApiClient(selectedServer);
|
||||
const { health } = buildShlinkApiClient(selectedServer);
|
||||
const version = await health()
|
||||
.then(({ version }) => version === LATEST_VERSION_CONSTRAINT ? MAX_FALLBACK_VERSION : version)
|
||||
.then((version) => !versionIsValidSemVer(version) ? MIN_FALLBACK_VERSION : version)
|
||||
|
|
|
@ -31,8 +31,7 @@ export default handleActions({
|
|||
|
||||
export const createShortUrl = (buildShlinkApiClient) => (data) => async (dispatch, getState) => {
|
||||
dispatch({ type: CREATE_SHORT_URL_START });
|
||||
|
||||
const { createShortUrl } = await buildShlinkApiClient(getState);
|
||||
const { createShortUrl } = buildShlinkApiClient(getState);
|
||||
|
||||
try {
|
||||
const result = await createShortUrl(data);
|
||||
|
|
|
@ -32,8 +32,7 @@ export default handleActions({
|
|||
|
||||
export const deleteShortUrl = (buildShlinkApiClient) => (shortCode, domain) => async (dispatch, getState) => {
|
||||
dispatch({ type: DELETE_SHORT_URL_START });
|
||||
|
||||
const { deleteShortUrl } = await buildShlinkApiClient(getState);
|
||||
const { deleteShortUrl } = buildShlinkApiClient(getState);
|
||||
|
||||
try {
|
||||
await deleteShortUrl(shortCode, domain);
|
||||
|
|
|
@ -37,7 +37,7 @@ export default handleActions({
|
|||
|
||||
export const editShortUrlMeta = (buildShlinkApiClient) => (shortCode, domain, meta) => async (dispatch, getState) => {
|
||||
dispatch({ type: EDIT_SHORT_URL_META_START });
|
||||
const { updateShortUrlMeta } = await buildShlinkApiClient(getState);
|
||||
const { updateShortUrlMeta } = buildShlinkApiClient(getState);
|
||||
|
||||
try {
|
||||
await updateShortUrlMeta(shortCode, domain, meta);
|
||||
|
|
|
@ -31,7 +31,7 @@ export default handleActions({
|
|||
|
||||
export const editShortUrlTags = (buildShlinkApiClient) => (shortCode, domain, tags) => async (dispatch, getState) => {
|
||||
dispatch({ type: EDIT_SHORT_URL_TAGS_START });
|
||||
const { updateShortUrlTags } = await buildShlinkApiClient(getState);
|
||||
const { updateShortUrlTags } = buildShlinkApiClient(getState);
|
||||
|
||||
try {
|
||||
const normalizedTags = await updateShortUrlTags(shortCode, domain, tags);
|
||||
|
|
|
@ -58,8 +58,7 @@ export default handleActions({
|
|||
|
||||
export const listShortUrls = (buildShlinkApiClient) => (params = {}) => async (dispatch, getState) => {
|
||||
dispatch({ type: LIST_SHORT_URLS_START });
|
||||
|
||||
const { listShortUrls } = await buildShlinkApiClient(getState);
|
||||
const { listShortUrls } = buildShlinkApiClient(getState);
|
||||
|
||||
try {
|
||||
const shortUrls = await listShortUrls(params);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import { splitEvery } from 'ramda';
|
||||
import PropTypes from 'prop-types';
|
||||
import MuttedMessage from '../utils/MuttedMessage';
|
||||
import MutedMessage from '../utils/MutedMessage';
|
||||
import SearchField from '../utils/SearchField';
|
||||
|
||||
const { ceil } = Math;
|
||||
|
@ -29,7 +29,7 @@ const TagsList = (TagCard) => class TagsList extends React.Component {
|
|||
const { tagsList, match } = this.props;
|
||||
|
||||
if (tagsList.loading) {
|
||||
return <MuttedMessage marginSize={0}>Loading...</MuttedMessage>;
|
||||
return <MutedMessage noMargin loading />;
|
||||
}
|
||||
|
||||
if (tagsList.error) {
|
||||
|
@ -43,7 +43,7 @@ const TagsList = (TagCard) => class TagsList extends React.Component {
|
|||
const tagsCount = tagsList.filteredTags.length;
|
||||
|
||||
if (tagsCount < 1) {
|
||||
return <MuttedMessage>No tags found</MuttedMessage>;
|
||||
return <MutedMessage>No tags found</MutedMessage>;
|
||||
}
|
||||
|
||||
const tagsGroups = splitEvery(ceil(tagsCount / TAGS_GROUPS_AMOUNT), tagsList.filteredTags);
|
||||
|
|
|
@ -26,8 +26,7 @@ export default handleActions({
|
|||
|
||||
export const deleteTag = (buildShlinkApiClient) => (tag) => async (dispatch, getState) => {
|
||||
dispatch({ type: DELETE_TAG_START });
|
||||
|
||||
const { deleteTags } = await buildShlinkApiClient(getState);
|
||||
const { deleteTags } = buildShlinkApiClient(getState);
|
||||
|
||||
try {
|
||||
await deleteTags([ tag ]);
|
||||
|
|
|
@ -31,8 +31,7 @@ export const editTag = (buildShlinkApiClient, colorGenerator) => (oldName, newNa
|
|||
getState
|
||||
) => {
|
||||
dispatch({ type: EDIT_TAG_START });
|
||||
|
||||
const { editTag } = await buildShlinkApiClient(getState);
|
||||
const { editTag } = buildShlinkApiClient(getState);
|
||||
|
||||
try {
|
||||
await editTag(oldName, newName);
|
||||
|
|
|
@ -50,7 +50,7 @@ export const listTags = (buildShlinkApiClient, force = true) => () => async (dis
|
|||
dispatch({ type: LIST_TAGS_START });
|
||||
|
||||
try {
|
||||
const { listTags } = await buildShlinkApiClient(getState);
|
||||
const { listTags } = buildShlinkApiClient(getState);
|
||||
const tags = await listTags();
|
||||
|
||||
dispatch({ tags, type: LIST_TAGS });
|
||||
|
|
34
src/utils/MutedMessage.js
Normal file
34
src/utils/MutedMessage.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
import React from 'react';
|
||||
import { Card } from 'reactstrap';
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import { faCircleNotch as preloader } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
const propTypes = {
|
||||
noMargin: PropTypes.bool,
|
||||
loading: PropTypes.bool,
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
const MutedMessage = ({ children, loading = false, noMargin = false }) => {
|
||||
const cardClasses = classNames('bg-light', {
|
||||
'mt-4': !noMargin,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="col-md-10 offset-md-1">
|
||||
<Card className={cardClasses} body>
|
||||
<h3 className="text-center text-muted mb-0">
|
||||
{loading && <FontAwesomeIcon icon={preloader} spin />}
|
||||
{loading && !children && <span className="ml-2">Loading...</span>}
|
||||
{children}
|
||||
</h3>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
MutedMessage.propTypes = propTypes;
|
||||
|
||||
export default MutedMessage;
|
|
@ -1,28 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Card } from 'reactstrap';
|
||||
import classnames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const DEFAULT_MARGIN_SIZE = 4;
|
||||
const propTypes = {
|
||||
marginSize: PropTypes.number,
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
export default function MutedMessage({ children, marginSize = DEFAULT_MARGIN_SIZE }) {
|
||||
const cardClasses = classnames('bg-light', {
|
||||
[`mt-${marginSize}`]: marginSize > 0,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="col-md-10 offset-md-1">
|
||||
<Card className={cardClasses} body>
|
||||
<h3 className="text-center text-muted mb-0">
|
||||
{children}
|
||||
</h3>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
MutedMessage.propTypes = propTypes;
|
|
@ -1,21 +1,16 @@
|
|||
import { wait } from '../utils';
|
||||
import ShlinkApiClient from './ShlinkApiClient';
|
||||
|
||||
const apiClients = {};
|
||||
|
||||
const getSelectedServerFromState = async (getState) => {
|
||||
const getSelectedServerFromState = (getState) => {
|
||||
const { selectedServer } = getState();
|
||||
|
||||
if (!selectedServer) {
|
||||
return wait(250).then(() => getSelectedServerFromState(getState));
|
||||
}
|
||||
|
||||
return selectedServer;
|
||||
};
|
||||
|
||||
const buildShlinkApiClient = (axios) => async (getStateOrSelectedServer) => {
|
||||
const buildShlinkApiClient = (axios) => (getStateOrSelectedServer) => {
|
||||
const { url, apiKey } = typeof getStateOrSelectedServer === 'function'
|
||||
? await getSelectedServerFromState(getStateOrSelectedServer)
|
||||
? getSelectedServerFromState(getStateOrSelectedServer)
|
||||
: getStateOrSelectedServer;
|
||||
const clientKey = `${url}_${apiKey}`;
|
||||
|
||||
|
|
|
@ -53,8 +53,6 @@ export const useToggle = (initialValue = false) => {
|
|||
return [ flag, () => setFlag(!flag) ];
|
||||
};
|
||||
|
||||
export const wait = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
|
||||
|
||||
export const compareVersions = (firstVersion, operator, secondVersion) => compare(
|
||||
firstVersion,
|
||||
secondVersion,
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import { faCircleNotch as preloader } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { isEmpty, mapObjIndexed, values } from 'ramda';
|
||||
import React from 'react';
|
||||
import { Card } from 'reactstrap';
|
||||
import PropTypes from 'prop-types';
|
||||
import qs from 'qs';
|
||||
import DateRangeRow from '../utils/DateRangeRow';
|
||||
import MutedMessage from '../utils/MuttedMessage';
|
||||
import MutedMessage from '../utils/MutedMessage';
|
||||
import { formatDate } from '../utils/utils';
|
||||
import SortableBarGraph from './SortableBarGraph';
|
||||
import { shortUrlVisitsType } from './reducers/shortUrlVisits';
|
||||
|
@ -66,7 +64,7 @@ const ShortUrlVisits = (
|
|||
if (loading) {
|
||||
const message = loadingLarge ? 'This is going to take a while... :S' : 'Loading...';
|
||||
|
||||
return <MutedMessage><FontAwesomeIcon icon={preloader} spin /> {message}</MutedMessage>;
|
||||
return <MutedMessage loading>{message}</MutedMessage>;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
|
|
|
@ -28,8 +28,7 @@ export default handleActions({
|
|||
|
||||
export const getShortUrlDetail = (buildShlinkApiClient) => (shortCode, domain) => async (dispatch, getState) => {
|
||||
dispatch({ type: GET_SHORT_URL_DETAIL_START });
|
||||
|
||||
const { getShortUrl } = await buildShlinkApiClient(getState);
|
||||
const { getShortUrl } = buildShlinkApiClient(getState);
|
||||
|
||||
try {
|
||||
const shortUrl = await getShortUrl(shortCode, domain);
|
||||
|
|
|
@ -51,8 +51,7 @@ export default handleActions({
|
|||
|
||||
export const getShortUrlVisits = (buildShlinkApiClient) => (shortCode, query) => async (dispatch, getState) => {
|
||||
dispatch({ type: GET_SHORT_URL_VISITS_START });
|
||||
|
||||
const { getShortUrlVisits } = await buildShlinkApiClient(getState);
|
||||
const { getShortUrlVisits } = buildShlinkApiClient(getState);
|
||||
const itemsPerPage = 5000;
|
||||
const isLastPage = ({ currentPage, pagesCount }) => currentPage >= pagesCount;
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ describe('selectedServerReducer', () => {
|
|||
const apiClientMock = {
|
||||
health: jest.fn(),
|
||||
};
|
||||
const buildApiClient = jest.fn().mockResolvedValue(apiClientMock);
|
||||
const buildApiClient = jest.fn().mockReturnValue(apiClientMock);
|
||||
const dispatch = jest.fn();
|
||||
|
||||
afterEach(jest.clearAllMocks);
|
||||
|
|
|
@ -51,7 +51,7 @@ describe('shortUrlMetaReducer', () => {
|
|||
|
||||
describe('editShortUrlMeta', () => {
|
||||
const updateShortUrlMeta = jest.fn().mockResolvedValue({});
|
||||
const buildShlinkApiClient = jest.fn().mockResolvedValue({ updateShortUrlMeta });
|
||||
const buildShlinkApiClient = jest.fn().mockReturnValue({ updateShortUrlMeta });
|
||||
const dispatch = jest.fn();
|
||||
|
||||
afterEach(jest.clearAllMocks);
|
||||
|
|
|
@ -51,14 +51,10 @@ describe('shortUrlTagsReducer', () => {
|
|||
|
||||
describe('editShortUrlTags', () => {
|
||||
const updateShortUrlTags = jest.fn();
|
||||
const buildShlinkApiClient = jest.fn().mockResolvedValue({ updateShortUrlTags });
|
||||
const buildShlinkApiClient = jest.fn().mockReturnValue({ updateShortUrlTags });
|
||||
const dispatch = jest.fn();
|
||||
|
||||
afterEach(() => {
|
||||
updateShortUrlTags.mockReset();
|
||||
buildShlinkApiClient.mockClear();
|
||||
dispatch.mockReset();
|
||||
});
|
||||
afterEach(jest.clearAllMocks);
|
||||
|
||||
it.each([[ undefined ], [ null ], [ 'example.com' ]])('dispatches normalized tags on success', async (domain) => {
|
||||
const normalizedTags = [ 'bar', 'foo' ];
|
||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import { shallow } from 'enzyme';
|
||||
import { identity } from 'ramda';
|
||||
import createTagsList from '../../src/tags/TagsList';
|
||||
import MuttedMessage from '../../src/utils/MuttedMessage';
|
||||
import MutedMessage from '../../src/utils/MutedMessage';
|
||||
import SearchField from '../../src/utils/SearchField';
|
||||
import { rangeOf } from '../../src/utils/utils';
|
||||
|
||||
|
@ -28,7 +28,7 @@ describe('<TagsList />', () => {
|
|||
|
||||
it('shows a loading message when tags are being loaded', () => {
|
||||
const wrapper = createWrapper({ loading: true });
|
||||
const loadingMsg = wrapper.find(MuttedMessage);
|
||||
const loadingMsg = wrapper.find(MutedMessage);
|
||||
|
||||
expect(loadingMsg).toHaveLength(1);
|
||||
expect(loadingMsg.html()).toContain('Loading...');
|
||||
|
@ -44,7 +44,7 @@ describe('<TagsList />', () => {
|
|||
|
||||
it('shows a message when the list of tags is empty', () => {
|
||||
const wrapper = createWrapper({ filteredTags: [] });
|
||||
const msg = wrapper.find(MuttedMessage);
|
||||
const msg = wrapper.find(MutedMessage);
|
||||
|
||||
expect(msg).toHaveLength(1);
|
||||
expect(msg.html()).toContain('No tags found');
|
||||
|
|
|
@ -104,7 +104,7 @@ describe('tagsListReducer', () => {
|
|||
const tags = [ 'foo', 'bar', 'baz' ];
|
||||
|
||||
listTagsMock.mockResolvedValue(tags);
|
||||
buildShlinkApiClient.mockResolvedValue({ listTags: listTagsMock });
|
||||
buildShlinkApiClient.mockReturnValue({ listTags: listTagsMock });
|
||||
|
||||
await listTags(buildShlinkApiClient, true)()(dispatch, getState);
|
||||
|
||||
|
@ -127,7 +127,7 @@ describe('tagsListReducer', () => {
|
|||
|
||||
it('dispatches error when error occurs on list call', async () => {
|
||||
listTagsMock.mockRejectedValue(new Error());
|
||||
buildShlinkApiClient.mockResolvedValue({ listTags: listTagsMock });
|
||||
buildShlinkApiClient.mockReturnValue({ listTags: listTagsMock });
|
||||
|
||||
await assertErrorResult();
|
||||
|
||||
|
@ -135,7 +135,9 @@ describe('tagsListReducer', () => {
|
|||
});
|
||||
|
||||
it('dispatches error when error occurs on build call', async () => {
|
||||
buildShlinkApiClient.mockRejectedValue(new Error());
|
||||
buildShlinkApiClient.mockImplementation(() => {
|
||||
throw new Error();
|
||||
});
|
||||
|
||||
await assertErrorResult();
|
||||
|
||||
|
|
|
@ -34,10 +34,10 @@ describe('ShlinkApiClientBuilder', () => {
|
|||
expect(secondApiClient).toBe(thirdApiClient);
|
||||
});
|
||||
|
||||
it('does not fetch from state when provided param is already selected server', async () => {
|
||||
it('does not fetch from state when provided param is already selected server', () => {
|
||||
const url = 'url';
|
||||
const apiKey = 'apiKey';
|
||||
const apiClient = await buildShlinkApiClient({})({ url, apiKey });
|
||||
const apiClient = buildShlinkApiClient({})({ url, apiKey });
|
||||
|
||||
expect(apiClient._baseUrl).toEqual(url);
|
||||
expect(apiClient._apiKey).toEqual(apiKey);
|
||||
|
|
|
@ -3,7 +3,7 @@ import { shallow } from 'enzyme';
|
|||
import { identity } from 'ramda';
|
||||
import { Card } from 'reactstrap';
|
||||
import createShortUrlVisits from '../../src/visits/ShortUrlVisits';
|
||||
import MutedMessage from '../../src/utils/MuttedMessage';
|
||||
import MutedMessage from '../../src/utils/MutedMessage';
|
||||
import GraphCard from '../../src/visits/GraphCard';
|
||||
import SortableBarGraph from '../../src/visits/SortableBarGraph';
|
||||
import DateRangeRow from '../../src/utils/DateRangeRow';
|
||||
|
|
Loading…
Reference in a new issue