diff --git a/src/App.js b/src/App.js index 5547e1e3..4cecebff 100644 --- a/src/App.js +++ b/src/App.js @@ -3,7 +3,7 @@ import { Route, Switch } from 'react-router-dom'; import './App.scss'; import NotFound from './common/NotFound'; -const App = (MainHeader, Home, MenuLayout, CreateServer) => () => ( +const App = (MainHeader, Home, MenuLayout, CreateServer, EditServer) => () => (
@@ -11,7 +11,7 @@ const App = (MainHeader, Home, MenuLayout, CreateServer) => () => ( - + diff --git a/src/container/index.js b/src/container/index.js index 483c8acd..771a46e8 100644 --- a/src/container/index.js +++ b/src/container/index.js @@ -26,7 +26,7 @@ const connect = (propsFromState, actionServiceNames = []) => actionServiceNames.reduce(mapActionService, {}) ); -bottle.serviceFactory('App', App, 'MainHeader', 'Home', 'MenuLayout', 'CreateServer'); +bottle.serviceFactory('App', App, 'MainHeader', 'Home', 'MenuLayout', 'CreateServer', 'EditServer'); provideCommonServices(bottle, connect, withRouter); provideShortUrlsServices(bottle, connect); diff --git a/src/servers/EditServer.js b/src/servers/EditServer.js new file mode 100644 index 00000000..f3adbc58 --- /dev/null +++ b/src/servers/EditServer.js @@ -0,0 +1,65 @@ +import React, { useState, useEffect } from 'react'; +import PropTypes, { serverType } from 'prop-types'; +import { HorizontalFormGroup } from '../utils/HorizontalFormGroup'; +import './CreateServer.scss'; +import Message from '../utils/Message'; + +const propTypes = { + editServer: PropTypes.func, + selectServer: PropTypes.func, + selectedServer: serverType, + match: PropTypes.object, + history: PropTypes.shape({ + push: PropTypes.func, + }), +}; + +export const EditServer = (ServerError) => { + const EditServerComp = ({ editServer, selectServer, selectedServer, match, history: { push } }) => { + const [ name, setName ] = useState(''); + const [ url, setUrl ] = useState(''); + const [ apiKey, setApiKey ] = useState(''); + const { params: { serverId } } = match; + const handleSubmit = (e) => { + e.preventDefault(); + + editServer(serverId, { name, url, apiKey }); + push(`/server/${serverId}/list-short-urls/1`); + }; + + useEffect(() => { + selectServer(serverId); + }, [ serverId ]); + useEffect(() => { + selectedServer && setName(selectedServer.name); + selectedServer && setUrl(selectedServer.url); + selectedServer && setApiKey(selectedServer.apiKey); + }, [ selectedServer ]); + + if (!selectedServer) { + return ; + } + + if (selectedServer.serverNotFound) { + return ; + } + + return ( +
+
+ Name + URL + API key + +
+ +
+
+
+ ); + }; + + EditServerComp.propTypes = propTypes; + + return EditServerComp; +}; diff --git a/src/servers/reducers/server.js b/src/servers/reducers/server.js index 576eca14..e665751c 100644 --- a/src/servers/reducers/server.js +++ b/src/servers/reducers/server.js @@ -52,6 +52,8 @@ export const listServers = ({ listServers, createServers }, { get }) => () => as export const createServer = ({ createServer }, listServersAction) => pipe(createServer, listServersAction); +export const editServer = ({ editServer }, listServersAction) => pipe(editServer, listServersAction); + export const deleteServer = ({ deleteServer }, listServersAction) => pipe(deleteServer, listServersAction); export const createServers = ({ createServers }, listServersAction) => pipe( diff --git a/src/servers/services/ServersService.js b/src/servers/services/ServersService.js index e8ffdb68..9ec44afa 100644 --- a/src/servers/services/ServersService.js +++ b/src/servers/services/ServersService.js @@ -25,4 +25,14 @@ export default class ServersService { deleteServer = ({ id }) => this.storage.set(SERVERS_STORAGE_KEY, dissoc(id, this.listServers())); + + editServer = (id, serverData) => { + const allServers = this.listServers(); + + if (!allServers[id]) { + return; + } + + this.storage.set(SERVERS_STORAGE_KEY, assoc(id, { ...allServers[id], ...serverData }, allServers)); + } } diff --git a/src/servers/services/provideServices.js b/src/servers/services/provideServices.js index 54c25130..0516f665 100644 --- a/src/servers/services/provideServices.js +++ b/src/servers/services/provideServices.js @@ -3,9 +3,10 @@ import CreateServer from '../CreateServer'; import ServersDropdown from '../ServersDropdown'; import DeleteServerModal from '../DeleteServerModal'; import DeleteServerButton from '../DeleteServerButton'; +import { EditServer } from '../EditServer'; import ImportServersBtn from '../helpers/ImportServersBtn'; import { resetSelectedServer, selectServer } from '../reducers/selectedServer'; -import { createServer, createServers, deleteServer, listServers } from '../reducers/server'; +import { createServer, createServers, deleteServer, editServer, listServers } from '../reducers/server'; import ForServerVersion from '../helpers/ForServerVersion'; import { ServerError } from '../helpers/ServerError'; import ServersImporter from './ServersImporter'; @@ -17,6 +18,9 @@ const provideServices = (bottle, connect, withRouter) => { bottle.serviceFactory('CreateServer', CreateServer, 'ImportServersBtn', 'useStateFlagTimeout'); bottle.decorator('CreateServer', connect([ 'selectedServer' ], [ 'createServer', 'resetSelectedServer' ])); + bottle.serviceFactory('EditServer', EditServer, 'ServerError'); + bottle.decorator('EditServer', connect([ 'selectedServer' ], [ 'editServer', 'selectServer' ])); + bottle.serviceFactory('ServersDropdown', ServersDropdown, 'ServersExporter'); bottle.decorator('ServersDropdown', withRouter); bottle.decorator('ServersDropdown', connect([ 'servers', 'selectedServer' ], [ 'listServers' ])); @@ -47,6 +51,7 @@ const provideServices = (bottle, connect, withRouter) => { bottle.serviceFactory('createServer', createServer, 'ServersService', 'listServers'); bottle.serviceFactory('createServers', createServers, 'ServersService', 'listServers'); bottle.serviceFactory('deleteServer', deleteServer, 'ServersService', 'listServers'); + bottle.serviceFactory('editServer', editServer, 'ServersService', 'listServers'); bottle.serviceFactory('listServers', listServers, 'ServersService', 'axios'); bottle.serviceFactory('resetSelectedServer', () => resetSelectedServer); diff --git a/test/App.test.js b/test/App.test.js index 15208214..668239ca 100644 --- a/test/App.test.js +++ b/test/App.test.js @@ -9,7 +9,7 @@ describe('', () => { const MainHeader = () => ''; beforeEach(() => { - const App = appFactory(MainHeader, identity, identity, identity); + const App = appFactory(MainHeader, identity, identity, identity, identity); wrapper = shallow(); }); diff --git a/test/servers/reducers/server.test.js b/test/servers/reducers/server.test.js index f819ee34..f6c55325 100644 --- a/test/servers/reducers/server.test.js +++ b/test/servers/reducers/server.test.js @@ -4,7 +4,7 @@ import reducer, { deleteServer, listServers, createServers, - FETCH_SERVERS, FETCH_SERVERS_START, + FETCH_SERVERS, FETCH_SERVERS_START, editServer, } from '../../../src/servers/reducers/server'; describe('serverReducer', () => { @@ -16,6 +16,7 @@ describe('serverReducer', () => { const ServersServiceMock = { listServers: jest.fn(() => list), createServer: jest.fn(), + editServer: jest.fn(), deleteServer: jest.fn(), createServers: jest.fn(), }; @@ -41,6 +42,7 @@ describe('serverReducer', () => { expect(dispatch).toHaveBeenNthCalledWith(2, expectedFetchServersResult); expect(ServersServiceMock.listServers).toHaveBeenCalledTimes(1); expect(ServersServiceMock.createServer).not.toHaveBeenCalled(); + expect(ServersServiceMock.editServer).not.toHaveBeenCalled(); expect(ServersServiceMock.deleteServer).not.toHaveBeenCalled(); expect(ServersServiceMock.createServers).not.toHaveBeenCalled(); expect(axios.get).not.toHaveBeenCalled(); @@ -91,6 +93,7 @@ describe('serverReducer', () => { expect(dispatch).toHaveBeenNthCalledWith(2, { type: FETCH_SERVERS, list: expectedList }); expect(NoListServersServiceMock.listServers).toHaveBeenCalledTimes(1); expect(NoListServersServiceMock.createServer).not.toHaveBeenCalled(); + expect(NoListServersServiceMock.editServer).not.toHaveBeenCalled(); expect(NoListServersServiceMock.deleteServer).not.toHaveBeenCalled(); expect(NoListServersServiceMock.createServers).toHaveBeenCalledTimes(1); expect(axios.get).toHaveBeenCalledTimes(1); @@ -103,9 +106,25 @@ describe('serverReducer', () => { const result = createServer(ServersServiceMock, () => expectedFetchServersResult)(serverToCreate); expect(result).toEqual(expectedFetchServersResult); + expect(ServersServiceMock.listServers).not.toHaveBeenCalled(); expect(ServersServiceMock.createServer).toHaveBeenCalledTimes(1); expect(ServersServiceMock.createServer).toHaveBeenCalledWith(serverToCreate); + expect(ServersServiceMock.editServer).not.toHaveBeenCalled(); + expect(ServersServiceMock.deleteServer).not.toHaveBeenCalled(); + expect(ServersServiceMock.createServers).not.toHaveBeenCalled(); + }); + }); + + describe('editServer', () => { + it('edits existing server and then fetches servers again', () => { + const serverToEdit = { name: 'edited' }; + const result = editServer(ServersServiceMock, () => expectedFetchServersResult)('123', serverToEdit); + + expect(result).toEqual(expectedFetchServersResult); expect(ServersServiceMock.listServers).not.toHaveBeenCalled(); + expect(ServersServiceMock.createServer).not.toHaveBeenCalled(); + expect(ServersServiceMock.editServer).toHaveBeenCalledTimes(1); + expect(ServersServiceMock.editServer).toHaveBeenCalledWith('123', serverToEdit); expect(ServersServiceMock.deleteServer).not.toHaveBeenCalled(); expect(ServersServiceMock.createServers).not.toHaveBeenCalled(); }); @@ -120,12 +139,13 @@ describe('serverReducer', () => { expect(ServersServiceMock.listServers).not.toHaveBeenCalled(); expect(ServersServiceMock.createServer).not.toHaveBeenCalled(); expect(ServersServiceMock.createServers).not.toHaveBeenCalled(); + expect(ServersServiceMock.editServer).not.toHaveBeenCalled(); expect(ServersServiceMock.deleteServer).toHaveBeenCalledTimes(1); expect(ServersServiceMock.deleteServer).toHaveBeenCalledWith(serverToDelete); }); }); - describe('createServer', () => { + describe('createServers', () => { it('creates multiple servers and then fetches servers again', () => { const serversToCreate = values(list); const result = createServers(ServersServiceMock, () => expectedFetchServersResult)(serversToCreate); @@ -133,9 +153,10 @@ describe('serverReducer', () => { expect(result).toEqual(expectedFetchServersResult); expect(ServersServiceMock.listServers).not.toHaveBeenCalled(); expect(ServersServiceMock.createServer).not.toHaveBeenCalled(); + expect(ServersServiceMock.editServer).not.toHaveBeenCalled(); + expect(ServersServiceMock.deleteServer).not.toHaveBeenCalled(); expect(ServersServiceMock.createServers).toHaveBeenCalledTimes(1); expect(ServersServiceMock.createServers).toHaveBeenCalledWith(serversToCreate); - expect(ServersServiceMock.deleteServer).not.toHaveBeenCalled(); }); }); }); diff --git a/test/servers/services/ServersService.test.js b/test/servers/services/ServersService.test.js index 87346c82..3c8b805f 100644 --- a/test/servers/services/ServersService.test.js +++ b/test/servers/services/ServersService.test.js @@ -5,15 +5,19 @@ describe('ServersService', () => { abc123: { id: 'abc123' }, def456: { id: 'def456' }, }; - const createStorageMock = (returnValue) => ({ - set: jest.fn(), - get: jest.fn(() => returnValue), - }); + const createService = (withServers = true) => { + const storageMock = { + set: jest.fn(), + get: jest.fn(() => withServers ? servers : undefined), + }; + const service = new ServersService(storageMock); + + return [ service, storageMock ]; + }; describe('listServers', () => { it('returns an empty object when servers are not found in storage', () => { - const storageMock = createStorageMock(); - const service = new ServersService(storageMock); + const [ service, storageMock ] = createService(false); const result = service.listServers(); @@ -23,8 +27,7 @@ describe('ServersService', () => { }); it('returns value from storage when found', () => { - const storageMock = createStorageMock(servers); - const service = new ServersService(storageMock); + const [ service, storageMock ] = createService(); const result = service.listServers(); @@ -36,8 +39,7 @@ describe('ServersService', () => { describe('findServerById', () => { it('returns undefined when requested server is not found', () => { - const storageMock = createStorageMock(servers); - const service = new ServersService(storageMock); + const [ service, storageMock ] = createService(); const result = service.findServerById('ghi789'); @@ -47,8 +49,7 @@ describe('ServersService', () => { }); it('returns server from list when found', () => { - const storageMock = createStorageMock(servers); - const service = new ServersService(storageMock); + const [ service, storageMock ] = createService(); const result = service.findServerById('abc123'); @@ -60,8 +61,7 @@ describe('ServersService', () => { describe('createServer', () => { it('adds one server to the list', () => { - const storageMock = createStorageMock(servers); - const service = new ServersService(storageMock); + const [ service, storageMock ] = createService(); service.createServer({ id: 'ghi789' }); @@ -77,8 +77,7 @@ describe('ServersService', () => { describe('createServers', () => { it('adds multiple servers to the list', () => { - const storageMock = createStorageMock(servers); - const service = new ServersService(storageMock); + const [ service, storageMock ] = createService(); service.createServers([{ id: 'ghi789' }, { id: 'jkl123' }]); @@ -95,8 +94,7 @@ describe('ServersService', () => { describe('deleteServer', () => { it('removes one server from the list', () => { - const storageMock = createStorageMock(servers); - const service = new ServersService(storageMock); + const [ service, storageMock ] = createService(); service.deleteServer({ id: 'abc123' }); @@ -107,4 +105,26 @@ describe('ServersService', () => { }); }); }); + + describe('editServer', () => { + it('dos nothing is provided server does not exist', () => { + const [ service, storageMock ] = createService(); + + service.editServer('notFound', {}); + + expect(storageMock.set).not.toHaveBeenCalled(); + }); + + it('updates the list with provided server data', () => { + const [ service, storageMock ] = createService(); + const serverData = { name: 'foo', apiKey: 'bar' }; + + service.editServer('abc123', serverData); + + expect(storageMock.set).toHaveBeenCalledWith(expect.anything(), { + abc123: { id: 'abc123', ...serverData }, + def456: { id: 'def456' }, + }); + }); + }); });