diff --git a/src/common/services/provideServices.js b/src/common/services/provideServices.js new file mode 100644 index 00000000..584ddcf9 --- /dev/null +++ b/src/common/services/provideServices.js @@ -0,0 +1,32 @@ +import ScrollToTop from '../ScrollToTop'; +import MainHeader from '../MainHeader'; +import Home from '../Home'; +import MenuLayout from '../MenuLayout'; +import AsideMenu from '../AsideMenu'; + +const provideServices = (bottle, connect, withRouter) => { + bottle.constant('ScrollToTop', ScrollToTop); + bottle.decorator('ScrollToTop', withRouter); + + bottle.serviceFactory('MainHeader', MainHeader, 'ServersDropdown'); + bottle.decorator('MainHeader', withRouter); + + bottle.serviceFactory('Home', () => Home); + bottle.decorator('Home', connect([ 'servers' ], [ 'resetSelectedServer' ])); + + bottle.serviceFactory( + 'MenuLayout', + MenuLayout, + 'TagsList', + 'ShortUrls', + 'AsideMenu', + 'CreateShortUrl', + 'ShortUrlVisits' + ); + bottle.decorator('MenuLayout', connect([ 'selectedServer', 'shortUrlsListParams' ], [ 'selectServer' ])); + bottle.decorator('MenuLayout', withRouter); + + bottle.serviceFactory('AsideMenu', AsideMenu, 'DeleteServerButton'); +}; + +export default provideServices; diff --git a/src/container/index.js b/src/container/index.js index 79a65834..ffe5de5d 100644 --- a/src/container/index.js +++ b/src/container/index.js @@ -2,20 +2,13 @@ import Bottle from 'bottlejs'; import { withRouter } from 'react-router-dom'; import { connect as reduxConnect } from 'react-redux'; import { pick } from 'ramda'; -import axios from 'axios'; import App from '../App'; -import ScrollToTop from '../common/ScrollToTop'; -import MainHeader from '../common/MainHeader'; -import Home from '../common/Home'; -import MenuLayout from '../common/MenuLayout'; -import AsideMenu from '../common/AsideMenu'; -import ColorGenerator from '../utils/ColorGenerator'; -import Storage from '../utils/Storage'; -import buildShlinkApiClient from '../api/ShlinkApiClientBuilder'; +import provideCommonServices from '../common/services/provideServices'; import provideShortUrlsServices from '../short-urls/services/provideServices'; import provideServersServices from '../servers/services/provideServices'; import provideVisitsServices from '../visits/services/provideServices'; import provideTagsServices from '../tags/services/provideServices'; +import provideUtilsServices from '../utils/services/provideServices'; const bottle = new Bottle(); const { container } = bottle; @@ -29,44 +22,16 @@ const mapActionService = (map, actionName) => ({ const connect = (propsFromState, actionServiceNames) => reduxConnect( propsFromState ? pick(propsFromState) : null, - Array.isArray(actionServiceNames) ? actionServiceNames.reduce(mapActionService, {}) : actionServiceNames + actionServiceNames.reduce(mapActionService, {}) ); -bottle.constant('ScrollToTop', ScrollToTop); -bottle.decorator('ScrollToTop', withRouter); - bottle.serviceFactory('App', App, 'MainHeader', 'Home', 'MenuLayout', 'CreateServer'); -bottle.serviceFactory('MainHeader', MainHeader, 'ServersDropdown'); -bottle.decorator('MainHeader', withRouter); - -bottle.serviceFactory('Home', () => Home); -bottle.decorator('Home', connect([ 'servers' ], [ 'resetSelectedServer' ])); - -bottle.serviceFactory( - 'MenuLayout', - MenuLayout, - 'TagsList', - 'ShortUrls', - 'AsideMenu', - 'CreateShortUrl', - 'ShortUrlVisits' -); -bottle.decorator('MenuLayout', connect([ 'selectedServer', 'shortUrlsListParams' ], [ 'selectServer' ])); -bottle.decorator('MenuLayout', withRouter); - -bottle.serviceFactory('AsideMenu', AsideMenu, 'DeleteServerButton'); - -bottle.constant('localStorage', global.localStorage); -bottle.service('Storage', Storage, 'localStorage'); -bottle.service('ColorGenerator', ColorGenerator, 'Storage'); - -bottle.constant('axios', axios); -bottle.serviceFactory('buildShlinkApiClient', buildShlinkApiClient, 'axios'); - +provideCommonServices(bottle, connect, withRouter); provideShortUrlsServices(bottle, connect); provideServersServices(bottle, connect, withRouter); provideTagsServices(bottle, connect); provideVisitsServices(bottle, connect); +provideUtilsServices(bottle); export default container; diff --git a/src/tags/helpers/Tag.js b/src/tags/helpers/Tag.js index 2356e818..29515af5 100644 --- a/src/tags/helpers/Tag.js +++ b/src/tags/helpers/Tag.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import './Tag.scss'; -import { colorGeneratorType } from '../../utils/ColorGenerator'; +import { colorGeneratorType } from '../../utils/services/ColorGenerator'; const propTypes = { text: PropTypes.string, diff --git a/src/tags/helpers/TagBullet.js b/src/tags/helpers/TagBullet.js index 6afcd79d..896eaf8f 100644 --- a/src/tags/helpers/TagBullet.js +++ b/src/tags/helpers/TagBullet.js @@ -1,6 +1,6 @@ import React from 'react'; import * as PropTypes from 'prop-types'; -import { colorGeneratorType } from '../../utils/ColorGenerator'; +import { colorGeneratorType } from '../../utils/services/ColorGenerator'; import './TagBullet.scss'; const propTypes = { diff --git a/src/tags/reducers/tagsList.js b/src/tags/reducers/tagsList.js index 94338266..9b4fe65e 100644 --- a/src/tags/reducers/tagsList.js +++ b/src/tags/reducers/tagsList.js @@ -1,5 +1,5 @@ import { isEmpty, reject } from 'ramda'; -import { buildShlinkApiClientWithAxios as buildShlinkApiClient } from '../../api/ShlinkApiClientBuilder'; +import { buildShlinkApiClientWithAxios as buildShlinkApiClient } from '../../utils/services/ShlinkApiClientBuilder'; import { TAG_DELETED } from './tagDelete'; import { TAG_EDITED } from './tagEdit'; diff --git a/src/utils/ColorGenerator.js b/src/utils/services/ColorGenerator.js similarity index 100% rename from src/utils/ColorGenerator.js rename to src/utils/services/ColorGenerator.js diff --git a/src/api/ShlinkApiClient.js b/src/utils/services/ShlinkApiClient.js similarity index 100% rename from src/api/ShlinkApiClient.js rename to src/utils/services/ShlinkApiClient.js diff --git a/src/api/ShlinkApiClientBuilder.js b/src/utils/services/ShlinkApiClientBuilder.js similarity index 100% rename from src/api/ShlinkApiClientBuilder.js rename to src/utils/services/ShlinkApiClientBuilder.js diff --git a/src/utils/Storage.js b/src/utils/services/Storage.js similarity index 100% rename from src/utils/Storage.js rename to src/utils/services/Storage.js diff --git a/src/utils/services/provideServices.js b/src/utils/services/provideServices.js new file mode 100644 index 00000000..fddb74bd --- /dev/null +++ b/src/utils/services/provideServices.js @@ -0,0 +1,15 @@ +import axios from 'axios'; +import Storage from './Storage'; +import ColorGenerator from './ColorGenerator'; +import buildShlinkApiClient from './ShlinkApiClientBuilder'; + +const provideServices = (bottle) => { + bottle.constant('localStorage', global.localStorage); + bottle.service('Storage', Storage, 'localStorage'); + bottle.service('ColorGenerator', ColorGenerator, 'Storage'); + + bottle.constant('axios', axios); + bottle.serviceFactory('buildShlinkApiClient', buildShlinkApiClient, 'axios'); +}; + +export default provideServices; diff --git a/test/utils/ColorGenerator.test.js b/test/utils/services/ColorGenerator.test.js similarity index 95% rename from test/utils/ColorGenerator.test.js rename to test/utils/services/ColorGenerator.test.js index a70de95b..937c3b2a 100644 --- a/test/utils/ColorGenerator.test.js +++ b/test/utils/services/ColorGenerator.test.js @@ -1,5 +1,5 @@ import * as sinon from 'sinon'; -import ColorGenerator from '../../src/utils/ColorGenerator'; +import ColorGenerator from '../../../src/utils/services/ColorGenerator'; describe('ColorGenerator', () => { let colorGenerator; diff --git a/test/api/ShlinkApiClient.test.js b/test/utils/services/ShlinkApiClient.test.js similarity index 98% rename from test/api/ShlinkApiClient.test.js rename to test/utils/services/ShlinkApiClient.test.js index 02935f1d..f92b00f7 100644 --- a/test/api/ShlinkApiClient.test.js +++ b/test/utils/services/ShlinkApiClient.test.js @@ -1,6 +1,6 @@ import sinon from 'sinon'; import { head, last } from 'ramda'; -import ShlinkApiClient from '../../src/api/ShlinkApiClient'; +import ShlinkApiClient from '../../../src/utils/services/ShlinkApiClient'; describe('ShlinkApiClient', () => { const createAxiosMock = (extraData) => () => diff --git a/test/utils/services/ShlinkApiClientBuilder.test.js b/test/utils/services/ShlinkApiClientBuilder.test.js new file mode 100644 index 00000000..9316087f --- /dev/null +++ b/test/utils/services/ShlinkApiClientBuilder.test.js @@ -0,0 +1,26 @@ +import buildShlinkApiClient from '../../../src/utils/services/ShlinkApiClientBuilder'; + +describe('ShlinkApiClientBuilder', () => { + const builder = buildShlinkApiClient({}); + + it('creates new instances when provided params are different', () => { + const firstApiClient = builder({ url: 'foo', apiKey: 'bar' }); + const secondApiClient = builder({ url: 'bar', apiKey: 'bar' }); + const thirdApiClient = builder({ url: 'bar', apiKey: 'foo' }); + + expect(firstApiClient).not.toBe(secondApiClient); + expect(firstApiClient).not.toBe(thirdApiClient); + expect(secondApiClient).not.toBe(thirdApiClient); + }); + + it('returns existing instances when provided params are the same', () => { + const params = { url: 'foo', apiKey: 'bar' }; + const firstApiClient = builder(params); + const secondApiClient = builder(params); + const thirdApiClient = builder(params); + + expect(firstApiClient).toBe(secondApiClient); + expect(firstApiClient).toBe(thirdApiClient); + expect(secondApiClient).toBe(thirdApiClient); + }); +});